diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index 469cb0a01c..881ff3af30 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -465,7 +465,7 @@ type ( type Constraint struct { Param *Ident // constrained type parameter; or nil (for embedded constraints) MName *Ident // method name; or nil - Type Expr // embedded constraint (CallExpr), constraint type, method type (*FuncType); or nil + Type Expr // embedded constraint (CallExpr), constraint type, or method type (*FuncType) } // Pos and End implementations for expression/type nodes. diff --git a/src/go/types/check_test.go b/src/go/types/check_test.go index a4a89559e1..743baaea75 100644 --- a/src/go/types/check_test.go +++ b/src/go/types/check_test.go @@ -99,6 +99,7 @@ var tests = [][]string{ {"testdata/issue28251.src"}, {"testdata/issue6977.src"}, {"testdata/typeparams.src"}, + {"testdata/contracts.src"}, } var fset = token.NewFileSet() diff --git a/src/go/types/expr.go b/src/go/types/expr.go index 710516513b..82198e715f 100644 --- a/src/go/types/expr.go +++ b/src/go/types/expr.go @@ -1513,7 +1513,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { goto Error case *ast.ArrayType, *ast.StructType, *ast.FuncType, - *ast.InterfaceType, *ast.MapType, *ast.ChanType: + *ast.InterfaceType, *ast.MapType, *ast.ChanType, *ast.ContractType: x.mode = typexpr x.typ = check.typ(e) // Note: rawExpr (caller of exprInternal) will call check.recordTypeAndValue diff --git a/src/go/types/testdata/contracts.src b/src/go/types/testdata/contracts.src new file mode 100644 index 0000000000..c432ed0b83 --- /dev/null +++ b/src/go/types/testdata/contracts.src @@ -0,0 +1,24 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// empty contracts +contract _(){} +contract _(A){} +contract _(A, B, C){} +contract _(A, B, A /* ERROR A redeclared */ ){} + +// For now we also allow this form of contract declaration. +type _ contract(){} +type _ contract(A, B, A /* ERROR A redeclared */ ){} + +// method constraints +contract _(A) { A } /* ERROR expected type */ +contract _(A) { A m() } +contract _(A) { B /* ERROR B not declared by contract */ m() } + +// type constraints +contract _(A) { A A } +contract _(A) { A B /* ERROR undeclared name: B */ } diff --git a/src/go/types/testdata/typeparams.src b/src/go/types/testdata/typeparams.src index f9021cfa33..51226b6302 100644 --- a/src/go/types/testdata/typeparams.src +++ b/src/go/types/testdata/typeparams.src @@ -111,5 +111,3 @@ var _ = f8(1, 2.3, 3.4, 4 /* ERROR does not match */ ) var _ = f8(int, float64)(1, 2.3, 3.4, 4) var _ = f8(int, float64)(0, 0, nil...) // test case for #18268 - -// type P contract(C) {} \ No newline at end of file diff --git a/src/go/types/type.go b/src/go/types/type.go index 4dac379bbc..4b17446fb7 100644 --- a/src/go/types/type.go +++ b/src/go/types/type.go @@ -497,6 +497,17 @@ func (t *Named) AddMethod(m *Func) { } } +// A Contract represents a contract. +type Contract struct { + TParams []*TypeName +} + +func NewContract(tparams []*TypeName) *Contract { + return &Contract{ + TParams: tparams, + } +} + // A TypeParam represents a type parameter type. type TypeParam struct { obj *TypeName @@ -525,6 +536,7 @@ func (t *Interface) Underlying() Type { return t } func (m *Map) Underlying() Type { return m } func (c *Chan) Underlying() Type { return c } func (t *Named) Underlying() Type { return t.underlying } +func (c *Contract) Underlying() Type { return c } func (c *TypeParam) Underlying() Type { return c } func (b *Basic) String() string { return TypeString(b, nil) } @@ -538,4 +550,5 @@ func (t *Interface) String() string { return TypeString(t, nil) } func (m *Map) String() string { return TypeString(m, nil) } func (c *Chan) String() string { return TypeString(c, nil) } func (t *Named) String() string { return TypeString(t, nil) } +func (c *Contract) String() string { return TypeString(c, nil) } func (c *TypeParam) String() string { return TypeString(c, nil) } diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go index 19f84127ba..ac12a3a4c2 100644 --- a/src/go/types/typestring.go +++ b/src/go/types/typestring.go @@ -247,6 +247,17 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) { } buf.WriteString(s) + case *Contract: + buf.WriteString("contract(") + for i, p := range t.TParams { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(p.name) + } + buf.WriteString("){}") + // TODO write contract body + case *TypeParam: var s string if t.obj != nil { diff --git a/src/go/types/typestring_test.go b/src/go/types/typestring_test.go index 5d9db39bfc..769c34f56d 100644 --- a/src/go/types/typestring_test.go +++ b/src/go/types/typestring_test.go @@ -107,6 +107,13 @@ var independentTestTypes = []testEntry{ dup("chan (<-chan int)"), dup("chan<- func()"), dup("<-chan []func() int"), + + // TODO(gri) These are not recognized as contracts as long as "contract" + // is not a keyword. + // contracts + // dup("contract(){}"), + // dup("contract(A){}"), + // dup("contract(A, B, C){}"), } // types that depend on other type declarations (src in TestTypes) diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 449df36385..f0ed1b62f4 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -149,8 +149,10 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, tpar *ast if tpar.NumFields() != 0 { check.scope = NewScope(check.scope, token.NoPos, token.NoPos, "function type parameters") // TODO(gri) replace with check.openScope call defer check.closeScope() + // TODO(gri) record this scope } scope := NewScope(check.scope, token.NoPos, token.NoPos, "function") + // TODO(gri) should we close this scope? scope.isFunc = true check.recordScope(ftyp, scope) @@ -345,6 +347,12 @@ func (check *Checker) typInternal(e ast.Expr, def *Named) Type { typ.elem = check.typ(e.Value) return typ + case *ast.ContractType: + typ := new(Contract) + def.setUnderlying(typ) + check.contractType(typ, e) + return typ + default: check.errorf(e.Pos(), "%s is not a type", e) } @@ -793,3 +801,57 @@ func embeddedFieldIdent(e ast.Expr) *ast.Ident { } return nil // invalid embedded field } + +func (check *Checker) contractType(ctyp *Contract, e *ast.ContractType) { + scope := NewScope(check.scope, token.NoPos, token.NoPos, "contract type parameters") + check.scope = scope + defer check.closeScope() + check.recordScope(e, scope) + + // collect type parameters + var tparams []*TypeName + for index, name := range e.TParams { + tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil) + NewTypeParam(tpar, index) // assigns type to tpar as a side-effect + check.declare(scope, name, tpar, scope.pos) + tparams = append(tparams, tpar) + } + ctyp.TParams = tparams + + // collect constraints + for _, c := range e.Constraints { + if c.Param != nil { + // If a type name is present, it must be one of the contract's type parameters. + tpar := scope.Lookup(c.Param.Name) + if tpar == nil { + check.errorf(c.Param.Pos(), "%s not declared by contract", c.Param.Name) + continue // TODO(gri) should try fall through + } + if c.Type == nil { + check.invalidAST(c.Param.Pos(), "missing method or type constraint") + continue + } + typ := check.typ(c.Type) + if c.MName != nil { + // If a method name is present, it must be unique, and c.Type + // must must be a method signature (guaranteed by AST). + sig, _ := typ.(*Signature) + if sig == nil { + check.invalidAST(c.Type.Pos(), "invalid method type") + } + // TODO(gri) what requirements do we have for sig.scope, sig.recv? + } else { + // no method name => we have a type constraint + check.typeConstraint(typ) + } + } else { + // no type name => we have an embedded contract + panic("embedded contracts unimplemented") + } + } +} + +func (check *Checker) typeConstraint(typ Type) { + // TODO(gri) verify that we have a valid type constraint + // - determine exact rules +}