diff --git a/src/go/types/NOTES b/src/go/types/NOTES index a75cfb89bf..8fd6ffcc06 100644 --- a/src/go/types/NOTES +++ b/src/go/types/NOTES @@ -7,12 +7,17 @@ TODO - use Underlying() to return a type parameter's bound? investigate! - better error message when declaring a contract local to a function (parser gets out of sync) - if type parameters are repeated in recursive instantiation, they must be the same order (not yet checked) +- debug (and error msg) printing of generic instantiated types needs some work +- improve error messages! - use []*TypeParam for tparams in subst? (unclear) OPEN ISSUES - instantiating a parameterized function type w/o value or result parameters may have unexpected side-effects (we don't make a copy of the signature in some cases) - investigate - using a contract and enumerating type arguments currently leads to an error (e.g. func f(type T C(T)) (x T) ... ) +- leaving away unused receiver type parameters leads to an error; e.g.: "type S(type T) struct{}; func (S) _()" +- allow parenthesized embedded interfaces so we can embed parameterized interfaces and they won't be confused + for methods DESIGN/IMPLEMENTATION - 11/19/2019: For type parameters with interface bounds to work, the scope of all type parameters in @@ -54,3 +59,13 @@ DESIGN/IMPLEMENTATION of the function body (i.e., the end of the function block). - 1/2/2020: Implementation decision: contracts can only be declared at the package level. + +- 1/6/2020: Experimental: First steps towards permitting type parameters in methods as a generalization. + Type-checking problems ooccurring from this are likely to highlight general problematic areas. + First consequence: Scope of type parameters starts at "func" keyword which means that receiver type + name cannot be a type parameter name declared later (or by the receiver type specification). This + seems reasonable and should help avoid confusion which is possible otherwise. + +- 1/6/2020: Embedding a parameterized interface in an interface is not possible because it looks + like a method declaration. We probably need to find a away around this; maybe parenthesize the + embedded interface (which is currently not permitted, syntactically). diff --git a/src/go/types/check.go b/src/go/types/check.go index bac9aee300..77f191b8ec 100644 --- a/src/go/types/check.go +++ b/src/go/types/check.go @@ -17,12 +17,12 @@ import ( // debugging/development support const debug = true // leave on during development -// If Strict is set, the type-checker enforces additional +// If strict is set, the type-checker enforces additional // rules not specified by the Go 1 spec, but which will // catch guaranteed run-time errors if the respective // code is executed. In other words, programs passing in -// Strict mode are Go 1 compliant, but not all Go 1 programs -// will pass in Strict mode. The additional rules are: +// strict mode are Go 1 compliant, but not all Go 1 programs +// will pass in strict mode. The additional rules are: // // - A type assertion x.(T) where T is an interface type // is invalid if any (statically known) method that exists @@ -30,6 +30,11 @@ const debug = true // leave on during development // const strict = false +// If methodTypeParamsOk is set, type parameters are +// permitted in method declarations (in interfaces, too). +// Generalization and experimental feature. +const methodTypeParamsOk = true + // exprInfo stores information about an untyped expression. type exprInfo struct { isLhs bool // expression is lhs operand of a shift with delayed type-check diff --git a/src/go/types/decl.go b/src/go/types/decl.go index 341ac0eca8..a8d8bc0f0c 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -55,7 +55,7 @@ func pathString(path []Object) string { func (check *Checker) objDecl(obj Object, def *Named) { if check.conf.Trace && obj.Type() == nil { if check.indent == 0 { - fmt.Println() // empty line between top-lebvel objects for readability + fmt.Println() // empty line between top-level objects for readability } check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath)) check.indent++ diff --git a/src/go/types/operand.go b/src/go/types/operand.go index 28fd040d08..da398d56f4 100644 --- a/src/go/types/operand.go +++ b/src/go/types/operand.go @@ -261,14 +261,33 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) bool { // x's type V and T have identical underlying types // and at least one of V or T is not a named type - //check.dump("Vu = %s, Tu = %s, identical = %v", Vu, Tu, Identical(Vu, Tu)) if check.identical(Vu, Tu) && (!isNamed(V) || !isNamed(T)) { return true } // T is an interface type and x implements T if Ti, ok := Tu.(*Interface); ok { - if m, wrongType := check.missingMethod(V, Ti, true, nil); m != nil /* Implements(V, Ti) */ { + // update targ method signatures + // TODO(gri) This needs documentation and clean up! + update := func(V Type, sig *Signature) *Signature { + V, _ = deref(V) + pos := token.NoPos // TODO(gri) can we do better? + // check.dump(">>> %s: V = %s", pos, V) + var targs []Type + if vnamed, _ := V.(*Named); vnamed != nil { + targs = vnamed.targs + } else { + // nothing to do + return sig + } + // check.dump(">>> %s: targs = %s", pos, targs) + // check.dump(">>> %s: sig.rparams = %s", pos, sig.rparams) + // check.dump(">>> %s: sig before: %s, tparams = %s, targs = %s", pos, sig, sig.rparams, targs) + sig = check.subst(pos, sig, sig.rparams, targs).(*Signature) + // check.dump(">>> %s: sig after : %s", pos, sig) + return sig + } + if m, wrongType := check.missingMethod(V, Ti, true, update); m != nil /* Implements(V, Ti) */ { if reason != nil { if wrongType != nil { if check.identical(m.typ, wrongType.typ) { diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go index 60b9b901a1..9c74fbf70d 100644 --- a/src/go/types/resolver.go +++ b/src/go/types/resolver.go @@ -435,16 +435,9 @@ func (check *Checker) collectObjects() { } else { // method // d.Recv != nil && len(d.Recv.List) > 0 - if d.TParams != nil { - // TODO(gri) should this be done in the parser (and this an invalidAST error)? - check.softErrorf(d.TParams.Pos(), "method must have no type parameters") + if !methodTypeParamsOk && d.TParams != nil { + check.invalidAST(d.TParams.Pos(), "method must have no type parameters") } - // collect parameterized receiver type parameters, if any - // - a receiver type parameter is like any other type parameter, except that it is passed implicitly (via the receiver) - // - the receiver specification is effectively the declaration of that type parameter - // - if the receiver type is parameterized but we don't need the parameters, we permit leaving them away - // - this is effectively a declaration, and thus a receiver type parameter may be the blank identifier (_) - // - since methods cannot have other type parameters, we store receiver type parameters where function type parameters would be ptr, recv, _ := check.unpackRecv(d.Recv.List[0].Type, false) // (Methods with invalid receiver cannot be associated to a type, and // methods with blank _ names are never found; no need to collect any diff --git a/src/go/types/testdata/tmp.go2 b/src/go/types/testdata/tmp.go2 index 9f577750b4..71ec59b385 100644 --- a/src/go/types/testdata/tmp.go2 +++ b/src/go/types/testdata/tmp.go2 @@ -4,10 +4,4 @@ package p -type B1 interface{type int} - -type T3(type P B1) P - -type B2 interface{type int, string} - -func _(type P B2)(x T3(P /* ERROR missing type string */ )) \ No newline at end of file +type S(type T) struct{}; func (S) _() diff --git a/src/go/types/testdata/typeparams.go2 b/src/go/types/testdata/typeparams.go2 index 3d298ec53f..b069b24f62 100644 --- a/src/go/types/testdata/typeparams.go2 +++ b/src/go/types/testdata/typeparams.go2 @@ -117,7 +117,7 @@ var _ = f8(int, float64)(1, 2.3, 3.4, 4) var _ = f8(int, float64)(0, 0, nil...) // test case for #18268 -// init functions and methods cannot have type parameters +// init functions cannot have type parameters func init() {} func init(/* ERROR func init must have no type parameters */ type)() {} @@ -126,8 +126,9 @@ func init(/* ERROR func init must have no type parameters */ type P)() {} type T struct {} func (T) m1() {} -func (T) m2( /* ERROR method must have no type parameters */ type)() {} -func (T) m3( /* ERROR method must have no type parameters */ type P)() {} +// Experimental: We allow method type parameters. +// func (T) m2( /* ERROR method must have no type parameters */ type)() {} +// func (T) m3( /* ERROR method must have no type parameters */ type P)() {} // type inference across parameterized types @@ -184,3 +185,47 @@ func _(type T) (x T) { m := S1(T).m m(S1(T){x}) } + +// type parameters in methods (generalization) + +type R0 struct{} + +func (R0) _(type T)(x T) +func (R0 /* ERROR invalid receiver */ ) _(type R0)() // scope of type parameters starts at "func" + +type R1(type A, B) struct{} + +func (_ R1(A, B)) m0(A, B) +func (_ R1(A, B)) m1(type T)(A, B, T) T +func (_ R1 /* ERROR not a generic type */ (R1, _)) _() +func (_ R1(A, B)) _(type A /* ERROR redeclared */ )(B) + +func _() { + var r R1(int, string) + r.m1(rune)(42, "foo", 'a') + r.m1(rune)(42, "foo", 1.2 /* ERROR truncated to rune */) + r.m1(42, "foo", 1.2) // using type inferrence + var _ float64 = r.m1(42, "foo", 1.2) +} + +type I1(type A) interface { + m1(A) +} + +var _ I1(int) = r1(int){} + +type r1(type T) struct{} + +func (_ r1(T)) m1(T) + +type I2(type A, B) interface { + m1(A) + m2(A) B +} + +var _ I2(int, float32) = R2(int, float32){} + +type R2(type P, Q) struct{} + +func (_ R2(X, Y)) m1(X) +func (_ R2(X, Y)) m2(X) Y \ No newline at end of file diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 6b741c9058..58c69a50ba 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -148,12 +148,14 @@ func (check *Checker) definedType(e ast.Expr, def *Named) Type { return typ } -// generic is like typ but the type must be an (uninstantiated) generic type. -func (check *Checker) genericType(e ast.Expr) Type { +// genericType is like typ but the type must be an (uninstantiated) generic type. +func (check *Checker) genericType(e ast.Expr, reportErr bool) Type { typ := check.typInternal(e, nil) assert(isTyped(typ)) if typ != Typ[Invalid] && !isGeneric(typ) { - check.errorf(e.Pos(), "%s is not a generic type", typ) + if reportErr { + check.errorf(e.Pos(), "%s is not a generic type", typ) + } typ = Typ[Invalid] } // TODO(gri) what is the correct call below? @@ -170,6 +172,10 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftparams defer check.closeScope() if recvPar != nil && len(recvPar.List) > 0 { + // collect parameterized receiver type parameters, if any + // - a receiver type parameter is like any other type parameter, except that it is passed implicitly (via the receiver) + // - the receiver specification acts as local declaration for its type parameters (which may be blank _) + // - if the receiver type is parameterized but we don't need the parameters, we permit leaving them away _, rname, rparams := check.unpackRecv(recvPar.List[0].Type, true) if len(rparams) > 0 { sig.rparams = check.declareTypeParams(nil, rparams, nil) @@ -178,7 +184,10 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftparams var recvTParams []*TypeName if rname != nil { // recv should be a Named type (otherwise an error is reported elsewhere) - if recv, _ := check.genericType(rname).(*Named); recv != nil { + // Also: Don't report an error via genericType since it will be reported + // again when we type-check the signature. + // TODO(gri) maybe the receiver should be marked as invalid instead? + if recv, _ := check.genericType(rname, false).(*Named); recv != nil { recvTParams = recv.tparams } } @@ -210,7 +219,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftparams } // Value (non-type) parameters' scope starts in the function body. Use a temporary scope for their - // declarations and the squash that scope into the parent scope (and report any redeclarations at + // declarations and then squash that scope into the parent scope (and report any redeclarations at // at that time). scope := NewScope(check.scope, token.NoPos, token.NoPos, "function body (temp. scope)") recvList, _ := check.collectParams(scope, recvPar, false) @@ -327,7 +336,7 @@ func (check *Checker) typInternal(e ast.Expr, def *Named) (T Type) { } case *ast.CallExpr: - typ := check.genericType(e.Fun) // TODO(gri) what about cycles? + typ := check.genericType(e.Fun, true) // TODO(gri) what about cycles? if typ == Typ[Invalid] { return typ // error already reported }