lang: add read-only type decorators
diff --git a/doc/changelog.md b/doc/changelog.md
index c2f5fe4..0efed9b 100644
--- a/doc/changelog.md
+++ b/doc/changelog.md
@@ -6,6 +6,7 @@
The dot points below probably aren't of interest unless you're _writing_ Wuffs
code (instead of writing C/C++ code that _uses_ Wuffs' standard library).
+- Added read-only type decorators: `roarray`, `roslice` and `rotable`.
- Wuffs struct private data now needs a "+" between the "()" pairs.
- `wuffsfmt` double-indents hanging lines and each indent is now 4 spaces (not
a tab).
diff --git a/internal/cgen/builtin.go b/internal/cgen/builtin.go
index f8981c6..12d4f0e 100644
--- a/internal/cgen/builtin.go
+++ b/internal/cgen/builtin.go
@@ -80,9 +80,9 @@
b.writeb(')')
return nil
- case t.IDSlice:
+ case t.IDRoslice, t.IDSlice:
return g.writeBuiltinSlice(b, recv, method.Ident(), n.Args(), sideEffectsOnly, depth)
- case t.IDTable:
+ case t.IDRotable, t.IDTable:
return g.writeBuiltinTable(b, recv, method.Ident(), n.Args(), sideEffectsOnly, depth)
default:
return errNoSuchBuiltin
@@ -759,7 +759,7 @@
if err := g.writeExpr(b, arrayOrSlice, sideEffectsOnly, depth); err != nil {
return err
}
- if arrayOrSlice.MType().IsSliceType() {
+ if arrayOrSlice.MType().IsEitherSliceType() {
b.writes(".ptr")
}
if lo != nil {
@@ -774,7 +774,7 @@
if err := g.writeExpr(b, n, sideEffectsOnly, depth); err != nil {
return err
}
- if n.MType().IsSliceType() {
+ if n.MType().IsEitherSliceType() {
b.writes(".ptr")
}
return nil
@@ -966,7 +966,7 @@
if err := g.writeExpr(b, foo, false, depth); err != nil {
return err
}
- if foo.MType().IsSliceType() {
+ if foo.MType().IsEitherSliceType() {
b.writes(".ptr")
}
if fIndex != nil {
@@ -979,7 +979,7 @@
if err := g.writeExpr(b, bar, false, depth); err != nil {
return err
}
- if bar.MType().IsSliceType() {
+ if bar.MType().IsEitherSliceType() {
b.writes(".ptr")
}
if bIndex != nil {
diff --git a/internal/cgen/expr.go b/internal/cgen/expr.go
index afb60f2..c4a439f 100644
--- a/internal/cgen/expr.go
+++ b/internal/cgen/expr.go
@@ -139,7 +139,7 @@
if err := g.writeExpr(b, n.LHS().AsExpr(), false, depth); err != nil {
return err
}
- if lTyp := n.LHS().AsExpr().MType(); lTyp.IsSliceType() {
+ if lTyp := n.LHS().AsExpr().MType(); lTyp.IsEitherSliceType() {
// TODO: don't assume that the slice is a slice of base.u8.
b.writes(".ptr")
}
@@ -164,7 +164,7 @@
comma = ",\n"
}
- if lhs.MType().IsArrayType() {
+ if lhs.MType().IsEitherArrayType() {
if mhs == nil {
b.writes("wuffs_base__make_slice_u8(")
} else {
@@ -449,7 +449,7 @@
// TODO: fix this, allow slices of all types, not just of base.u8's. Also
// allow arrays of slices, slices of pointers, etc.
- if n.IsSliceType() {
+ if n.IsEitherSliceType() {
o := n.Inner()
if o.Decorator() == 0 && o.QID() == (t.QID{t.IDBase, t.IDU8}) && !o.IsRefined() {
b.writes("wuffs_base__slice_u8")
@@ -462,7 +462,7 @@
}
return fmt.Errorf("cannot convert Wuffs type %q to C", n.Str(g.tm))
}
- if n.IsTableType() {
+ if n.IsEitherTableType() {
o := n.Inner()
if o.Decorator() == 0 && o.QID() == (t.QID{t.IDBase, t.IDU8}) && !o.IsRefined() {
b.writes("wuffs_base__table_u8")
@@ -480,7 +480,7 @@
const maxNumPointers = 16
x := n
- for ; x != nil && x.IsArrayType(); x = x.Inner() {
+ for ; x != nil && x.IsEitherArrayType(); x = x.Inner() {
}
numPointers, innermost := 0, x
@@ -521,7 +521,7 @@
}
x = n
- for ; x != nil && x.IsArrayType(); x = x.Inner() {
+ for ; x != nil && x.IsEitherArrayType(); x = x.Inner() {
b.writeb('[')
b.writes(x.ArrayLength().ConstValue().String())
b.writeb(']')
diff --git a/internal/cgen/func.go b/internal/cgen/func.go
index a28637a..8c5dc33 100644
--- a/internal/cgen/func.go
+++ b/internal/cgen/func.go
@@ -342,7 +342,7 @@
} else if typ.IsNumType() {
b.writes("0")
return nil
- } else if typ.IsSliceType() {
+ } else if typ.IsEitherSliceType() {
if inner := typ.Inner(); (inner.Decorator() == 0) && (inner.QID() == t.QID{t.IDBase, t.IDU8}) {
b.writes("wuffs_base__make_slice_u8(NULL, 0)")
return nil
diff --git a/internal/cgen/statement.go b/internal/cgen/statement.go
index 151b814..52d8d9a 100644
--- a/internal/cgen/statement.go
+++ b/internal/cgen/statement.go
@@ -136,7 +136,7 @@
return err
}
- if lTyp := lhs.MType(); lTyp.IsArrayType() {
+ if lTyp := lhs.MType(); lTyp.IsEitherArrayType() {
b.writes("memcpy(")
opName, closer = ",", fmt.Sprintf(", sizeof(%s))", lhsBuf)
diff --git a/lang/ast/ast.go b/lang/ast/ast.go
index 8adbfb4..84ce414 100644
--- a/lang/ast/ast.go
+++ b/lang/ast/ast.go
@@ -784,7 +784,8 @@
// TypeExpr is a type expression, such as "base.u32", "base.u32[..= 8]", "foo",
// "pkg.bar", "ptr T", "array[8] T", "slice T" or "table T":
-// - ID0: <0|IDArray|IDFunc|IDNptr|IDPtr|IDSlice|IDTable>
+// - ID0: <0|IDArray|IDFunc|IDNptr|IDPtr|IDRoarray|IDRoslice|IDRotable|
+// .... IDSlice|IDTable>
// - ID1: <0|pkg>
// - ID2: <0|type name>
// - LHS: <nil|Expr>
@@ -889,8 +890,16 @@
return n.id0 == 0 && n.id1 == t.IDBase && n.id2 == t.IDStatus
}
-func (n *TypeExpr) IsArrayType() bool {
- return n.id0 == t.IDArray
+func (n *TypeExpr) IsEitherArrayType() bool {
+ return (n.id0 == t.IDArray) || (n.id0 == t.IDRoarray)
+}
+
+func (n *TypeExpr) IsEitherSliceType() bool {
+ return (n.id0 == t.IDSlice) || (n.id0 == t.IDRoslice)
+}
+
+func (n *TypeExpr) IsEitherTableType() bool {
+ return (n.id0 == t.IDTable) || (n.id0 == t.IDRotable)
}
func (n *TypeExpr) IsFuncType() bool {
@@ -901,12 +910,8 @@
return n.id0 == t.IDNptr || n.id0 == t.IDPtr
}
-func (n *TypeExpr) IsSliceType() bool {
- return n.id0 == t.IDSlice
-}
-
-func (n *TypeExpr) IsTableType() bool {
- return n.id0 == t.IDTable
+func (n *TypeExpr) IsReadOnly() bool {
+ return (n.id0 == t.IDRoarray) || (n.id0 == t.IDRoslice) || (n.id0 == t.IDRotable)
}
func (n *TypeExpr) IsUnsignedInteger() bool {
diff --git a/lang/ast/eq.go b/lang/ast/eq.go
index 5725f8e..03e8c45 100644
--- a/lang/ast/eq.go
+++ b/lang/ast/eq.go
@@ -84,16 +84,23 @@
// Eq returns whether n and o are equal.
func (n *TypeExpr) Eq(o *TypeExpr) bool {
- return n.eq(o, false)
+ return n.eq(o, false, false)
}
// EqIgnoringRefinements returns whether n and o are equal, ignoring the
// "[i:j]" in "base.u32[i:j]".
func (n *TypeExpr) EqIgnoringRefinements(o *TypeExpr) bool {
- return n.eq(o, true)
+ return n.eq(o, true, false)
}
-func (n *TypeExpr) eq(o *TypeExpr, ignoreRefinements bool) bool {
+// EqIgnoringRefinementsLHSReadOnly returns whether n and o are equal, ignoring
+// the "[i:j]" in "base.u32[i:j]" and allowing n (the Left Hand Side of an
+// assignment) to be read-only when o (the Right Hand Side) is read-write.
+func (n *TypeExpr) EqIgnoringRefinementsLHSReadOnly(o *TypeExpr) bool {
+ return n.eq(o, true, true)
+}
+
+func (n *TypeExpr) eq(o *TypeExpr, ignoreRefinements bool, lhsReadOnly bool) bool {
for {
if n == o {
return true
@@ -101,10 +108,22 @@
if n == nil || o == nil {
return false
}
- if n.id0 != o.id0 || n.id1 != o.id1 || n.id2 != o.id2 {
+ if n.id0 != o.id0 {
+ if !lhsReadOnly {
+ return false
+ }
+ switch {
+ default:
+ return false
+ case (n.id0 == t.IDRoarray) && (o.id0 == t.IDArray):
+ case (n.id0 == t.IDRoslice) && (o.id0 == t.IDSlice):
+ case (n.id0 == t.IDRotable) && (o.id0 == t.IDTable):
+ }
+ }
+ if n.id1 != o.id1 || n.id2 != o.id2 {
return false
}
- if n.IsArrayType() || !ignoreRefinements {
+ if !ignoreRefinements || !n.IsNumType() {
if !n.lhs.AsExpr().Eq(o.lhs.AsExpr()) || !n.mhs.AsExpr().Eq(o.mhs.AsExpr()) {
return false
}
diff --git a/lang/ast/string.go b/lang/ast/string.go
index 57806e4..ee3ea4a 100644
--- a/lang/ast/string.go
+++ b/lang/ast/string.go
@@ -208,7 +208,7 @@
return append(buf, "!invalid_type!"...)
}
- switch n.Decorator() {
+ switch dec := n.Decorator(); dec {
case 0:
buf = append(buf, n.QID().Str(tm)...)
case t.IDNptr:
@@ -217,15 +217,24 @@
case t.IDPtr:
buf = append(buf, "ptr "...)
return n.Inner().appendStr(buf, tm, depth)
- case t.IDArray:
+ case t.IDArray, t.IDRoarray:
+ if dec == t.IDRoarray {
+ buf = append(buf, "ro"...)
+ }
buf = append(buf, "array["...)
buf = n.ArrayLength().appendStr(buf, tm, false, 0)
buf = append(buf, "] "...)
return n.Inner().appendStr(buf, tm, depth)
- case t.IDSlice:
+ case t.IDRoslice, t.IDSlice:
+ if dec == t.IDRoslice {
+ buf = append(buf, "ro"...)
+ }
buf = append(buf, "slice "...)
return n.Inner().appendStr(buf, tm, depth)
- case t.IDTable:
+ case t.IDRotable, t.IDTable:
+ if dec == t.IDRotable {
+ buf = append(buf, "ro"...)
+ }
buf = append(buf, "table "...)
return n.Inner().appendStr(buf, tm, depth)
case t.IDFunc:
diff --git a/lang/check/bounds.go b/lang/check/bounds.go
index 10e037f..f0c2feb 100644
--- a/lang/check/bounds.go
+++ b/lang/check/bounds.go
@@ -977,7 +977,7 @@
}
lengthExpr := (*a.Expr)(nil)
- if lTyp := lhs.MType(); lTyp.IsArrayType() {
+ if lTyp := lhs.MType(); lTyp.IsEitherArrayType() {
lengthExpr = lTyp.ArrayLength()
} else {
lengthExpr = makeSliceLength(lhs)
@@ -1013,7 +1013,7 @@
}
lengthExpr := (*a.Expr)(nil)
- if lTyp := lhs.MType(); lTyp.IsArrayType() {
+ if lTyp := lhs.MType(); lTyp.IsEitherArrayType() {
lengthExpr = lTyp.ArrayLength()
} else {
lengthExpr = makeSliceLength(lhs)
@@ -1770,7 +1770,7 @@
switch typ.Decorator() {
case 0:
// No-op.
- case t.IDArray:
+ case t.IDArray, t.IDRoarray:
if _, err := q.bcheckExpr(typ.ArrayLength(), 0); err != nil {
return bounds{}, err
}
@@ -1784,7 +1784,7 @@
return bounds{zero, one}, nil
case t.IDPtr:
return bounds{one, one}, nil
- case t.IDSlice, t.IDTable:
+ case t.IDRoslice, t.IDRotable, t.IDSlice, t.IDTable:
return bounds{zero, zero}, nil
default:
return bounds{}, fmt.Errorf("check: internal error: unrecognized decorator")
diff --git a/lang/check/check.go b/lang/check/check.go
index b0a1bc1..a4c2b5f 100644
--- a/lang/check/check.go
+++ b/lang/check/check.go
@@ -375,7 +375,7 @@
nLists := 0
for elemTyp := typ; ; {
- if elemTyp.IsArrayType() {
+ if elemTyp.IsEitherArrayType() {
if nLists == a.MaxTypeExprDepth {
return fmt.Errorf("check: type expression recursion depth too large")
}
@@ -399,7 +399,7 @@
func (c *Checker) checkConstElement(typ *a.TypeExpr, n *a.Expr, nb bounds, nLists int) error {
if nLists > 0 {
nLists--
- if !typ.IsArrayType() {
+ if !typ.IsEitherArrayType() {
return fmt.Errorf("internal error: inconsistent element type %q", typ.Str(c.tm))
}
cv := typ.ArrayLength().ConstValue()
diff --git a/lang/check/resolve.go b/lang/check/resolve.go
index 8b5ec7e..44492d1 100644
--- a/lang/check/resolve.go
+++ b/lang/check/resolve.go
@@ -182,23 +182,29 @@
lQID := lTyp.QID()
qqid := t.QQID{lQID[0], lQID[1], typ.FuncName()}
- if lTyp.IsSliceType() {
+ if lTyp.IsEitherSliceType() {
qqid[0] = t.IDBase
qqid[1] = t.IDDagger1
if f := c.builtInSliceFuncs[qqid]; f != nil {
- return f, nil
- }
- if lTyp.Eq(typeExprSliceU8) {
- if f := c.builtInSliceU8Funcs[qqid]; f != nil {
+ if (lTyp.Decorator() == t.IDSlice) || f.Effect().Pure() {
return f, nil
}
}
+ if lTyp.Eq(typeExprSliceU8) {
+ if f := c.builtInSliceU8Funcs[qqid]; f != nil {
+ if (lTyp.Decorator() == t.IDSlice) || f.Effect().Pure() {
+ return f, nil
+ }
+ }
+ }
- } else if lTyp.IsTableType() {
+ } else if lTyp.IsEitherTableType() {
qqid[0] = t.IDBase
qqid[1] = t.IDDagger2
if f := c.builtInTableFuncs[qqid]; f != nil {
- return f, nil
+ if (lTyp.Decorator() == t.IDTable) || f.Effect().Pure() {
+ return f, nil
+ }
}
} else if f := c.funcs[qqid]; f != nil {
diff --git a/lang/check/type.go b/lang/check/type.go
index 5b3f213..aa5391a 100644
--- a/lang/check/type.go
+++ b/lang/check/type.go
@@ -171,7 +171,7 @@
if err := q.tcheckExpr(n.Arg1(), 0); err != nil {
return err
}
- if typ := n.Arg1().MType(); !typ.EqIgnoringRefinements(arg1Typ) {
+ if typ := n.Arg1().MType(); !typ.EqIgnoringRefinementsLHSReadOnly(arg1Typ) {
return fmt.Errorf("check: %s expression %q, of type %q, does not have type %q",
n.Keyword().Str(q.tm), n.Arg1().Str(q.tm), typ.Str(q.tm), arg1Typ.Str(q.tm))
}
@@ -204,7 +204,7 @@
return err
}
o := o.AsAssign()
- if typ := o.LHS().MType(); !typ.IsSliceType() {
+ if typ := o.LHS().MType(); !typ.IsEitherSliceType() {
return fmt.Errorf("check: iterate assignment to %q, of type %q, does not have slice type",
o.LHS().Str(q.tm), typ.Str(q.tm))
}
@@ -236,7 +236,7 @@
return err
}
rTyp := value.MType()
- if !(rTyp.IsIdeal() && lTyp.IsNumType()) && !lTyp.EqIgnoringRefinements(rTyp) {
+ if !(rTyp.IsIdeal() && lTyp.IsNumType()) && !lTyp.EqIgnoringRefinementsLHSReadOnly(rTyp) {
return fmt.Errorf("check: cannot return %q (of type %q) as type %q",
value.Str(q.tm), rTyp.Str(q.tm), lTyp.Str(q.tm))
}
@@ -305,7 +305,7 @@
func (q *checker) tcheckEq(lID t.ID, lhs *a.Expr, lTyp *a.TypeExpr, rhs *a.Expr, rTyp *a.TypeExpr) error {
if (rTyp.IsIdeal() && lTyp.IsNumType()) ||
- (rTyp.EqIgnoringRefinements(lTyp)) ||
+ (lTyp.EqIgnoringRefinementsLHSReadOnly(rTyp)) ||
(rTyp.IsNullptr() && lTyp.Decorator() == t.IDNptr) {
return nil
}
@@ -331,6 +331,12 @@
if err := q.tcheckExpr(lhs, 0); err != nil {
return err
}
+ for l := lhs; l != nil; l = l.LHS().AsExpr() {
+ if l.MType().IsReadOnly() {
+ return fmt.Errorf("check: assignment %q: assignee fragment %q, of type %q, has read-only type",
+ n.Operator().Str(q.tm), l.Str(q.tm), l.MType().Str(q.tm))
+ }
+ }
lTyp := lhs.MType()
rTyp := rhs.MType()
@@ -363,7 +369,7 @@
}
}
- if !(rTyp.IsIdeal() && lTyp.IsNumType()) && !lTyp.EqIgnoringRefinements(rTyp) {
+ if !(rTyp.IsIdeal() && lTyp.IsNumType()) && !lTyp.EqIgnoringRefinementsLHSReadOnly(rTyp) {
return fmt.Errorf("check: assignment %q: %q and %q, of types %q and %q, do not have compatible types",
n.Operator().Str(q.tm),
lhs.Str(q.tm), rhs.Str(q.tm),
@@ -508,7 +514,7 @@
return err
}
lTyp := lhs.MType()
- if key := lTyp.Decorator(); key != t.IDArray && key != t.IDSlice {
+ if key := lTyp.Decorator(); key != t.IDArray && key != t.IDRoarray && key != t.IDRoslice && key != t.IDSlice {
return fmt.Errorf("check: %s is an index expression but %s has type %s, not an array or slice type",
n.Str(q.tm), lhs.Str(q.tm), lTyp.Str(q.tm))
}
@@ -555,7 +561,9 @@
n.Str(q.tm), lhs.Str(q.tm), lTyp.Str(q.tm))
case t.IDArray:
n.SetMType(a.NewTypeExpr(t.IDSlice, 0, 0, nil, nil, lTyp.Inner()))
- case t.IDSlice:
+ case t.IDRoarray:
+ n.SetMType(a.NewTypeExpr(t.IDRoslice, 0, 0, nil, nil, lTyp.Inner()))
+ case t.IDRoslice, t.IDSlice:
n.SetMType(lTyp)
}
return nil
@@ -621,11 +629,16 @@
case t.IDDagger1:
genericType1 = lhs.MType().Receiver()
case t.IDDagger2:
+ decorator := t.ID(0)
genericType2 = lhs.MType().Receiver()
- if genericType2.Decorator() != t.IDTable {
+ if genericType2.Decorator() == t.IDRotable {
+ decorator = t.IDRoslice
+ } else if genericType2.Decorator() == t.IDTable {
+ decorator = t.IDSlice
+ } else {
return fmt.Errorf("check: internal error: %q is not a generic table", genericType2.Str(q.tm))
}
- genericType1 = a.NewTypeExpr(t.IDSlice, 0, 0, nil, nil, genericType2.Inner())
+ genericType1 = a.NewTypeExpr(decorator, 0, 0, nil, nil, genericType2.Inner())
}
}
@@ -687,7 +700,7 @@
lQID := lTyp.QID()
qqid := t.QQID{lQID[0], lQID[1], n.Ident()}
- if lTyp.IsSliceType() {
+ if lTyp.IsEitherSliceType() {
qqid[0] = t.IDBase
qqid[1] = t.IDDagger1
if (q.c.builtInSliceFuncs[qqid] != nil) ||
@@ -697,7 +710,7 @@
}
return fmt.Errorf("check: no slice method %q", n.Ident().Str(q.tm))
- } else if lTyp.IsTableType() {
+ } else if lTyp.IsEitherTableType() {
qqid[0] = t.IDBase
qqid[1] = t.IDDagger2
if q.c.builtInTableFuncs[qqid] != nil {
@@ -1132,7 +1145,7 @@
}
return fmt.Errorf("check: %q is not a type", typ.Str(q.tm))
- case t.IDArray:
+ case t.IDArray, t.IDRoarray:
aLen := typ.ArrayLength()
if err := q.tcheckExpr(aLen, 0); err != nil {
return err
@@ -1142,7 +1155,7 @@
}
fallthrough
- case t.IDNptr, t.IDPtr, t.IDSlice, t.IDTable:
+ case t.IDNptr, t.IDPtr, t.IDRoslice, t.IDRotable, t.IDSlice, t.IDTable:
if err := q.tcheckTypeExpr(typ.Inner(), depth); err != nil {
return err
}
diff --git a/lang/parse/parse.go b/lang/parse/parse.go
index 003791e..c666705 100644
--- a/lang/parse/parse.go
+++ b/lang/parse/parse.go
@@ -510,9 +510,9 @@
}
decorator, arrayLength := t.ID(0), (*a.Expr)(nil)
- switch p.peek1() {
- case t.IDArray:
- decorator = t.IDArray
+ switch peek1 := p.peek1(); peek1 {
+ case t.IDArray, t.IDRoarray:
+ decorator = peek1
p.src = p.src[1:]
if x := p.peek1(); x != t.IDOpenBracket {
@@ -533,12 +533,8 @@
}
p.src = p.src[1:]
- case t.IDSlice:
- decorator = t.IDSlice
- p.src = p.src[1:]
-
- case t.IDTable:
- decorator = t.IDTable
+ case t.IDRoslice, t.IDRotable, t.IDSlice, t.IDTable:
+ decorator = peek1
p.src = p.src[1:]
}
diff --git a/lang/token/list.go b/lang/token/list.go
index a4a83f7..2ace176 100644
--- a/lang/token/list.go
+++ b/lang/token/list.go
@@ -398,11 +398,14 @@
minTypeModifier = 0xD0
maxTypeModifier = 0xDF
- IDArray = ID(0xD0)
- IDNptr = ID(0xD1)
- IDPtr = ID(0xD2)
- IDSlice = ID(0xD3)
- IDTable = ID(0xD4)
+ IDArray = ID(0xD0)
+ IDNptr = ID(0xD1)
+ IDPtr = ID(0xD2)
+ IDRoarray = ID(0xD3)
+ IDRoslice = ID(0xD4)
+ IDRotable = ID(0xD5)
+ IDSlice = ID(0xD6)
+ IDTable = ID(0xD7)
)
const (
@@ -820,11 +823,14 @@
IDWhile: "while",
IDYield: "yield",
- IDArray: "array",
- IDNptr: "nptr",
- IDPtr: "ptr",
- IDSlice: "slice",
- IDTable: "table",
+ IDArray: "array",
+ IDNptr: "nptr",
+ IDPtr: "ptr",
+ IDRoarray: "roarray",
+ IDRoslice: "roslice",
+ IDRotable: "rotable",
+ IDSlice: "slice",
+ IDTable: "table",
IDFalse: "false",
IDTrue: "true",