From b93581e47d358da9fc2d08032cd73797c2cb28a5 Mon Sep 17 00:00:00 2001 From: Robert Findley Date: Tue, 31 Aug 2021 14:41:29 -0400 Subject: [PATCH] go/types: add error reporting for 1.18 syntax if GoVersion is below 1.18 This is a port of CL 344871 to go/types. Unlike the compiler, go/parser is already always producing 1.18 syntax, so the effect of this CL is to add some additional errors when Config.GoVersion is below 1.18. This is a non-trivial port, both due to different error reporting APIs and due to interacting with declaration syntax nodes, which differ between go/ast and cmd/compile/internal/syntax. Change-Id: I8003a014e6eec5e554c24e9a6cfc0548ec534834 Reviewed-on: https://go-review.googlesource.com/c/go/+/346433 Trust: Robert Findley Run-TryBot: Robert Findley TryBot-Result: Go Bot Reviewed-by: Robert Griesemer --- src/go/types/call.go | 13 ++++ src/go/types/decl.go | 23 +++++++- src/go/types/resolver.go | 14 ++++- src/go/types/subst.go | 2 +- src/go/types/testdata/check/decls0.src | 2 +- src/go/types/testdata/check/issues.src | 6 +- src/go/types/testdata/check/main.go2 | 2 +- src/go/types/testdata/check/typeparams.go2 | 4 +- .../types/testdata/fixedbugs/issue47818.go2 | 59 +++++++++++++++++++ src/go/types/typeset.go | 11 +++- src/go/types/typexpr.go | 15 ++--- 11 files changed, 129 insertions(+), 22 deletions(-) create mode 100644 src/go/types/testdata/fixedbugs/issue47818.go2 diff --git a/src/go/types/call.go b/src/go/types/call.go index 61534b6328..78c81e13e9 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -17,6 +17,10 @@ import ( // funcInst type-checks a function instantiation inst and returns the result in x. // The operand x must be the evaluation of inst.X and its type must be a signature. func (check *Checker) funcInst(x *operand, ix *typeparams.IndexExpr) { + if !check.allowVersion(check.pkg, 1, 18) { + check.softErrorf(inNode(ix.Orig, ix.Lbrack), _Todo, "function instantiation requires go1.18 or later") + } + targs := check.typeList(ix.Indices) if targs == nil { x.mode = invalid @@ -324,6 +328,15 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, targs []Type // infer type arguments and instantiate signature if necessary if sig.TParams().Len() > 0 { + if !check.allowVersion(check.pkg, 1, 18) { + switch call.Fun.(type) { + case *ast.IndexExpr, *ast.MultiIndexExpr: + ix := typeparams.UnpackIndexExpr(call.Fun) + check.softErrorf(inNode(call.Fun, ix.Lbrack), _Todo, "function instantiation requires go1.18 or later") + default: + check.softErrorf(inNode(call, call.Lparen), _Todo, "implicit function instantiation requires go1.18 or later") + } + } // TODO(gri) provide position information for targs so we can feed // it to the instantiate call for better error reporting targs := check.infer(call, sig.TParams().list(), targs, sigParams, args, true) diff --git a/src/go/types/decl.go b/src/go/types/decl.go index 8ebaf289f1..8222cb3fc3 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -567,11 +567,26 @@ func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init ast.Expr) { check.initVars(lhs, []ast.Expr{init}, token.NoPos) } +// isImportedConstraint reports whether typ is an imported type constraint. +func (check *Checker) isImportedConstraint(typ Type) bool { + named, _ := typ.(*Named) + if named == nil || named.obj.pkg == check.pkg || named.obj.pkg == nil { + return false + } + u, _ := named.under().(*Interface) + return u != nil && u.IsConstraint() +} + func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) { assert(obj.typ == nil) + var rhs Type check.later(func() { check.validType(obj.typ, nil) + // If typ is local, an error was already reported where typ is specified/defined. + if check.isImportedConstraint(rhs) && !check.allowVersion(check.pkg, 1, 18) { + check.errorf(tdecl.Type, _Todo, "using type constraint %s requires go1.18 or later", rhs) + } }) alias := tdecl.Assign.IsValid() @@ -589,7 +604,8 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) { } obj.typ = Typ[Invalid] - obj.typ = check.anyType(tdecl.Type) + rhs = check.anyType(tdecl.Type) + obj.typ = rhs return } @@ -604,8 +620,9 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) { } // determine underlying type of named - named.fromRHS = check.definedType(tdecl.Type, named) - assert(named.fromRHS != nil) + rhs = check.definedType(tdecl.Type, named) + assert(rhs != nil) + named.fromRHS = rhs // The underlying type of named may be itself a named type that is // incomplete: diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go index 5e58c3dcfd..fb7e0cc474 100644 --- a/src/go/types/resolver.go +++ b/src/go/types/resolver.go @@ -381,12 +381,15 @@ func (check *Checker) collectObjects() { check.declarePkgObj(name, obj, di) } case typeDecl: + if d.spec.TParams.NumFields() != 0 && !check.allowVersion(pkg, 1, 18) { + check.softErrorf(d.spec.TParams.List[0], _Todo, "type parameters require go1.18 or later") + } obj := NewTypeName(d.spec.Name.Pos(), pkg, d.spec.Name.Name, nil) check.declarePkgObj(d.spec.Name, obj, &declInfo{file: fileScope, tdecl: d.spec}) case funcDecl: - info := &declInfo{file: fileScope, fdecl: d.decl} name := d.decl.Name.Name obj := NewFunc(d.decl.Name.Pos(), pkg, name, nil) + hasTParamError := false // avoid duplicate type parameter errors if d.decl.Recv.NumFields() == 0 { // regular function if d.decl.Recv != nil { @@ -398,8 +401,9 @@ func (check *Checker) collectObjects() { if name == "main" { code = _InvalidMainDecl } - if tparams := typeparams.Get(d.decl.Type); tparams != nil { - check.softErrorf(tparams, code, "func %s must have no type parameters", name) + if d.decl.Type.TParams.NumFields() != 0 { + check.softErrorf(d.decl.Type.TParams.List[0], code, "func %s must have no type parameters", name) + hasTParamError = true } if t := d.decl.Type; t.Params.NumFields() != 0 || t.Results != nil { // TODO(rFindley) Should this be a hard error? @@ -435,6 +439,10 @@ func (check *Checker) collectObjects() { } check.recordDef(d.decl.Name, obj) } + if d.decl.Type.TParams.NumFields() != 0 && !check.allowVersion(pkg, 1, 18) && !hasTParamError { + check.softErrorf(d.decl.Type.TParams.List[0], _Todo, "type parameters require go1.18 or later") + } + info := &declInfo{file: fileScope, fdecl: d.decl} // Methods are not package-level objects but we still track them in the // object map so that we can handle them like regular functions (if the // receiver is invalid); also we need their fdecl info when associating diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 1c53cdaf2c..d3b1cad13a 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -41,7 +41,7 @@ func (m substMap) lookup(tpar *TypeParam) Type { // subst returns the type typ with its type parameters tpars replaced by the // corresponding type arguments targs, recursively. subst is pure in the sense // that it doesn't modify the incoming type. If a substitution took place, the -// result type is different from from the incoming type. +// result type is different from the incoming type. // // If the given typMap is non-nil, it is used in lieu of check.typMap. func (check *Checker) subst(pos token.Pos, typ Type, smap substMap, typMap map[string]*Named) Type { diff --git a/src/go/types/testdata/check/decls0.src b/src/go/types/testdata/check/decls0.src index 1224e46377..18f0d32e1b 100644 --- a/src/go/types/testdata/check/decls0.src +++ b/src/go/types/testdata/check/decls0.src @@ -146,7 +146,7 @@ type ( m1(I5) } I6 interface { - S0 /* ERROR "not an interface" */ + S0 /* ERROR "non-interface type S0" */ } I7 interface { I1 diff --git a/src/go/types/testdata/check/issues.src b/src/go/types/testdata/check/issues.src index ef1737331d..88ce452959 100644 --- a/src/go/types/testdata/check/issues.src +++ b/src/go/types/testdata/check/issues.src @@ -79,11 +79,11 @@ func issue9473(a []int, b ...int) { // Check that embedding a non-interface type in an interface results in a good error message. func issue10979() { type _ interface { - int /* ERROR int is not an interface */ + int /* ERROR non-interface type int */ } type T struct{} type _ interface { - T /* ERROR T is not an interface */ + T /* ERROR non-interface type T */ } type _ interface { nosuchtype /* ERROR undeclared name: nosuchtype */ @@ -280,7 +280,7 @@ type issue25301b /* ERROR cycle */ = interface { } type issue25301c interface { - notE // ERROR struct\{\} is not an interface + notE // ERROR non-interface type struct\{\} } type notE = struct{} diff --git a/src/go/types/testdata/check/main.go2 b/src/go/types/testdata/check/main.go2 index 65e9aa2962..fb567a07d0 100644 --- a/src/go/types/testdata/check/main.go2 +++ b/src/go/types/testdata/check/main.go2 @@ -4,4 +4,4 @@ package main -func main[ /* ERROR "func main must have no type parameters" */ T any]() {} +func main[T /* ERROR "func main must have no type parameters" */ any]() {} diff --git a/src/go/types/testdata/check/typeparams.go2 b/src/go/types/testdata/check/typeparams.go2 index bd89d1ecad..57b6d7a0ad 100644 --- a/src/go/types/testdata/check/typeparams.go2 +++ b/src/go/types/testdata/check/typeparams.go2 @@ -304,8 +304,8 @@ var _ = f8[int, float64](0, 0, nil...) // test case for #18268 // init functions cannot have type parameters func init() {} -func init[/* ERROR func init must have no type parameters */ _ any]() {} -func init[/* ERROR func init must have no type parameters */ P any]() {} +func init[_ /* ERROR func init must have no type parameters */ any]() {} +func init[P /* ERROR func init must have no type parameters */ any]() {} type T struct {} diff --git a/src/go/types/testdata/fixedbugs/issue47818.go2 b/src/go/types/testdata/fixedbugs/issue47818.go2 new file mode 100644 index 0000000000..68c6a94ed4 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue47818.go2 @@ -0,0 +1,59 @@ +// Copyright 2021 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. + +// Parser accepts type parameters but the type checker +// needs to report any operations that are not permitted +// before Go 1.18. + +package go1_17 + +type T[P /* ERROR type parameters require go1\.18 or later */ any] struct{} + +// for init (and main, but we're not in package main) we should only get one error +func init[P /* ERROR func init must have no type parameters */ any]() {} +func main[P /* ERROR type parameters require go1\.18 or later */ any]() {} + +func f[P /* ERROR type parameters require go1\.18 or later */ any](x P) { + var _ T[ /* ERROR type instantiation requires go1\.18 or later */ int] + var _ (T[ /* ERROR type instantiation requires go1\.18 or later */ int]) + _ = T[ /* ERROR type instantiation requires go1\.18 or later */ int]{} + _ = T[ /* ERROR type instantiation requires go1\.18 or later */ int](struct{}{}) +} + +func (T[ /* ERROR type instantiation requires go1\.18 or later */ P]) g(x int) { + f[ /* ERROR function instantiation requires go1\.18 or later */ int](0) // explicit instantiation + (f[ /* ERROR function instantiation requires go1\.18 or later */ int])(0) // parentheses (different code path) + f( /* ERROR implicit function instantiation requires go1\.18 or later */ x) // implicit instantiation +} + +type C1 interface { + comparable // ERROR undeclared name: comparable \(requires version go1\.18 or later\) +} + +type C2 interface { + comparable // ERROR undeclared name: comparable \(requires version go1\.18 or later\) + int // ERROR embedding non-interface type int requires go1\.18 or later + ~ /* ERROR embedding interface element ~int requires go1\.18 or later */ int + int /* ERROR embedding interface element int\|~string requires go1\.18 or later */ | ~string +} + +type _ interface { + // errors for these were reported with their declaration + C1 + C2 +} + +type ( + _ comparable // ERROR undeclared name: comparable \(requires version go1\.18 or later\) + // errors for these were reported with their declaration + _ C1 + _ C2 + + _ = comparable // ERROR undeclared name: comparable \(requires version go1\.18 or later\) + // errors for these were reported with their declaration + _ = C1 + _ = C2 +) + +// TODO(gri) need test cases for imported constraint types (see also issue #47967) diff --git a/src/go/types/typeset.go b/src/go/types/typeset.go index 7bdc708d4c..fd9df4c010 100644 --- a/src/go/types/typeset.go +++ b/src/go/types/typeset.go @@ -269,6 +269,11 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T switch u := under(typ).(type) { case *Interface: tset := computeInterfaceTypeSet(check, pos, u) + // If typ is local, an error was already reported where typ is specified/defined. + if check != nil && check.isImportedConstraint(typ) && !check.allowVersion(check.pkg, 1, 18) { + check.errorf(atPos(pos), _Todo, "embedding constraint interface %s requires go1.18 or later", typ) + continue + } if tset.comparable { ityp.tset.comparable = true } @@ -277,6 +282,10 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T } terms = tset.terms case *Union: + if check != nil && !check.allowVersion(check.pkg, 1, 18) { + check.errorf(atPos(pos), _Todo, "embedding interface element %s requires go1.18 or later", u) + continue + } tset := computeUnionTypeSet(check, pos, u) if tset == &invalidTypeSet { continue // ignore invalid unions @@ -291,7 +300,7 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T continue } if check != nil && !check.allowVersion(check.pkg, 1, 18) { - check.errorf(atPos(pos), _InvalidIfaceEmbed, "%s is not an interface", typ) + check.errorf(atPos(pos), _InvalidIfaceEmbed, "embedding non-interface type %s requires go1.18 or later", typ) continue } terms = termlist{{false, typ}} diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 5c8a6b497d..5a67982030 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -36,14 +36,12 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, wantType bool) } return case universeAny, universeComparable: + // complain if necessary but keep going if !check.allowVersion(check.pkg, 1, 18) { - check.errorf(e, _UndeclaredName, "undeclared name: %s (requires version go1.18 or later)", e.Name) - return - } - // If we allow "any" for general use, this if-statement can be removed (issue #33232). - if obj == universeAny { - check.error(e, _Todo, "cannot use any outside constraint position") - return + check.softErrorf(e, _UndeclaredName, "undeclared name: %s (requires version go1.18 or later)", e.Name) + } else if obj == universeAny { + // If we allow "any" for general use, this if-statement can be removed (issue #33232). + check.softErrorf(e, _Todo, "cannot use any outside constraint position") } } check.recordUse(e, obj) @@ -273,6 +271,9 @@ func (check *Checker) typInternal(e0 ast.Expr, def *Named) (T Type) { case *ast.IndexExpr, *ast.MultiIndexExpr: ix := typeparams.UnpackIndexExpr(e) + if !check.allowVersion(check.pkg, 1, 18) { + check.softErrorf(inNode(e, ix.Lbrack), _Todo, "type instantiation requires go1.18 or later") + } // TODO(rfindley): type instantiation should require go1.18 return check.instantiatedType(ix.X, ix.Indices, def) -- 2.50.0