]> Cypherpunks repositories - gostls13.git/commitdiff
go/types: add error reporting for 1.18 syntax if GoVersion is below 1.18
authorRobert Findley <rfindley@google.com>
Tue, 31 Aug 2021 18:41:29 +0000 (14:41 -0400)
committerRobert Findley <rfindley@google.com>
Tue, 31 Aug 2021 21:22:54 +0000 (21:22 +0000)
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 <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
src/go/types/call.go
src/go/types/decl.go
src/go/types/resolver.go
src/go/types/subst.go
src/go/types/testdata/check/decls0.src
src/go/types/testdata/check/issues.src
src/go/types/testdata/check/main.go2
src/go/types/testdata/check/typeparams.go2
src/go/types/testdata/fixedbugs/issue47818.go2 [new file with mode: 0644]
src/go/types/typeset.go
src/go/types/typexpr.go

index 61534b632896e37eb2f5a7ebe02336a7a9e945e9..78c81e13e9161a2c379309bf4d3fa6dfbc718321 100644 (file)
@@ -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)
index 8ebaf289f1e5e17950887d502c9785c9a73c9039..8222cb3fc3603575c88e631b191c5bb44250e48f 100644 (file)
@@ -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:
index 5e58c3dcfd568839df2d82264b992298a5622464..fb7e0cc47416b15a5ab27181ba381eaa758df9e9 100644 (file)
@@ -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
index 1c53cdaf2ca7bb369633df228fa283047db09250..d3b1cad13a0417d70e51b580bf5f4b1621311ffd 100644 (file)
@@ -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 {
index 1224e46377ab45f1bb32965ffd86f53a3431bb31..18f0d32e1b4a5a878c65a5e8253b12cd8a54a8a5 100644 (file)
@@ -146,7 +146,7 @@ type (
                m1(I5)
        }
        I6 interface {
-               S0 /* ERROR "not an interface" */
+               S0 /* ERROR "non-interface type S0" */
        }
        I7 interface {
                I1
index ef1737331d7eeaac110fe8bfde549ac5a8181796..88ce452959845dd77e2ba9ccfac917dcfcf8df26 100644 (file)
@@ -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{}
index 65e9aa29629faefa5b78c5a4eaaab98239b9e304..fb567a07d085a32493b7126995a13b1a0a39962f 100644 (file)
@@ -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]() {}
index bd89d1ecada88c9e6e3a72b50c9f3677457fabbb..57b6d7a0ad87b307e8038548076fe63bf1f73717 100644 (file)
@@ -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 (file)
index 0000000..68c6a94
--- /dev/null
@@ -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)
index 7bdc708d4cc154877e202f85db4ccd4a24ef2b84..fd9df4c0104f61465b71954cc7faa3ad6b72ab23 100644 (file)
@@ -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}}
index 5c8a6b497d6375847105f7102482663a318b6342..5a679820308cc1bc76a86040a98a15462e7e12ac 100644 (file)
@@ -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)