]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/compile: always accept 1.18 syntax but complain if not 1.18
authorRobert Griesemer <gri@golang.org>
Wed, 25 Aug 2021 01:07:42 +0000 (18:07 -0700)
committerRobert Griesemer <gri@golang.org>
Wed, 25 Aug 2021 23:43:53 +0000 (23:43 +0000)
This CL configures the parser to always accept 1.18 syntax
(type parameters, type instantiations, interface elements),
even when -lang is set to an earlier release.

Instead, the type checker looks for 1.18 operations and
complains if the language version is set to an earlier
release.

Doing these checks during type checking is necessary because it
it is possible to write "generic" code using pre-1.18 syntax;
for instance, an imported generic function may be implicitly
instantiated (as in imported.Max(2, 3)), or an imported constraint
interface may be embedded in an "ordinary" interface.

Fixes #47818.

Change-Id: I83ec302b3f4ba7196c0a4743c03670cfb901310d
Reviewed-on: https://go-review.googlesource.com/c/go/+/344871
Trust: Robert Griesemer <gri@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
13 files changed:
src/cmd/compile/internal/noder/noder.go
src/cmd/compile/internal/types2/call.go
src/cmd/compile/internal/types2/decl.go
src/cmd/compile/internal/types2/resolver.go
src/cmd/compile/internal/types2/subst.go
src/cmd/compile/internal/types2/testdata/check/decls0.src
src/cmd/compile/internal/types2/testdata/check/issues.src
src/cmd/compile/internal/types2/testdata/check/main.go2
src/cmd/compile/internal/types2/testdata/check/typeparams.go2
src/cmd/compile/internal/types2/testdata/fixedbugs/issue47818.go2 [new file with mode: 0644]
src/cmd/compile/internal/types2/typeset.go
src/cmd/compile/internal/types2/typexpr.go
test/fixedbugs/issue10975.go

index 2b67a91b3f799bddc1f69927e2bd39cc669e4056..e1b485b2b3550fc32ae8385b5edd66e01c8c2ea3 100644 (file)
@@ -35,7 +35,7 @@ func LoadPackage(filenames []string) {
        supportsGenerics := base.Flag.G != 0 || buildcfg.Experiment.Unified
 
        mode := syntax.CheckBranches
-       if supportsGenerics && types.AllowsGoVersion(types.LocalPkg, 1, 18) {
+       if supportsGenerics {
                mode |= syntax.AllowGenerics
        }
 
index 4bbc524856acf5f3cd4f8efbdbb9e09d1ef3b841..0b062b4c94d263d7b9597711faed53394c32a76d 100644 (file)
@@ -15,6 +15,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, inst *syntax.IndexExpr) {
+       if !check.allowVersion(check.pkg, 1, 18) {
+               check.softErrorf(inst.Pos(), "function instantiation requires go1.18 or later")
+       }
+
        xlist := unpackExpr(inst.Index)
        targs := check.typeList(xlist)
        if targs == nil {
@@ -318,6 +322,13 @@ func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, targs []T
 
        // infer type arguments and instantiate signature if necessary
        if sig.TParams().Len() > 0 {
+               if !check.allowVersion(check.pkg, 1, 18) {
+                       if iexpr, _ := call.Fun.(*syntax.IndexExpr); iexpr != nil {
+                               check.softErrorf(iexpr.Pos(), "function instantiation requires go1.18 or later")
+                       } else {
+                               check.softErrorf(call.Pos(), "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.Pos(), sig.TParams().list(), targs, sigParams, args, true)
index 342e1090de5ca5ba4682b5f3cc2acf0ca9f4b069..d7a33546aa4478391e33a86f442a1131002f4ae6 100644 (file)
@@ -514,11 +514,26 @@ func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init syntax.Expr) {
        check.initVars(lhs, []syntax.Expr{init}, 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 *syntax.TypeDecl, 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.Pos(), "using type constraint %s requires go1.18 or later", rhs)
+               }
        })
 
        alias := tdecl.Alias
@@ -540,7 +555,8 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named
                }
 
                obj.typ = Typ[Invalid]
-               obj.typ = check.anyType(tdecl.Type)
+               rhs = check.anyType(tdecl.Type)
+               obj.typ = rhs
                return
        }
 
@@ -555,8 +571,9 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, 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 018a20cfb2dc69722ce32b9e15aa3985d6538205..34fbc3d41b4693737acfde7c61af8e5f5318b394 100644 (file)
@@ -412,52 +412,60 @@ func (check *Checker) collectObjects() {
                                }
 
                        case *syntax.TypeDecl:
+                               if len(s.TParamList) != 0 && !check.allowVersion(pkg, 1, 18) {
+                                       check.softErrorf(s.TParamList[0], "type parameters require go1.18 or later")
+                               }
                                obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Value, nil)
                                check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, tdecl: s})
 
                        case *syntax.FuncDecl:
-                               d := s // TODO(gri) get rid of this
-                               name := d.Name.Value
-                               obj := NewFunc(d.Name.Pos(), pkg, name, nil)
-                               if d.Recv == nil {
+                               name := s.Name.Value
+                               obj := NewFunc(s.Name.Pos(), pkg, name, nil)
+                               hasTParamError := false // avoid duplicate type parameter errors
+                               if s.Recv == nil {
                                        // regular function
                                        if name == "init" || name == "main" && pkg.name == "main" {
-                                               if d.TParamList != nil {
-                                                       check.softErrorf(d, "func %s must have no type parameters", name)
+                                               if len(s.TParamList) != 0 {
+                                                       check.softErrorf(s.TParamList[0], "func %s must have no type parameters", name)
+                                                       hasTParamError = true
                                                }
-                                               if t := d.Type; len(t.ParamList) != 0 || len(t.ResultList) != 0 {
-                                                       check.softErrorf(d, "func %s must have no arguments and no return values", name)
+                                               if t := s.Type; len(t.ParamList) != 0 || len(t.ResultList) != 0 {
+                                                       check.softErrorf(s, "func %s must have no arguments and no return values", name)
                                                }
                                        }
                                        // don't declare init functions in the package scope - they are invisible
                                        if name == "init" {
                                                obj.parent = pkg.scope
-                                               check.recordDef(d.Name, obj)
+                                               check.recordDef(s.Name, obj)
                                                // init functions must have a body
-                                               if d.Body == nil {
+                                               if s.Body == nil {
                                                        // TODO(gri) make this error message consistent with the others above
                                                        check.softErrorf(obj.pos, "missing function body")
                                                }
                                        } else {
-                                               check.declare(pkg.scope, d.Name, obj, nopos)
+                                               check.declare(pkg.scope, s.Name, obj, nopos)
                                        }
                                } else {
                                        // method
                                        // d.Recv != nil
-                                       if !acceptMethodTypeParams && len(d.TParamList) != 0 {
+                                       if !acceptMethodTypeParams && len(s.TParamList) != 0 {
                                                //check.error(d.TParamList.Pos(), invalidAST + "method must have no type parameters")
-                                               check.error(d, invalidAST+"method must have no type parameters")
+                                               check.error(s.TParamList[0], invalidAST+"method must have no type parameters")
+                                               hasTParamError = true
                                        }
-                                       ptr, recv, _ := check.unpackRecv(d.Recv.Type, false)
+                                       ptr, recv, _ := check.unpackRecv(s.Recv.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
                                        // of them. They will still be type-checked with all the other functions.)
                                        if recv != nil && name != "_" {
                                                methods = append(methods, methodInfo{obj, ptr, recv})
                                        }
-                                       check.recordDef(d.Name, obj)
+                                       check.recordDef(s.Name, obj)
+                               }
+                               if len(s.TParamList) != 0 && !check.allowVersion(pkg, 1, 18) && !hasTParamError {
+                                       check.softErrorf(s.TParamList[0], "type parameters require go1.18 or later")
                                }
-                               info := &declInfo{file: fileScope, fdecl: d}
+                               info := &declInfo{file: fileScope, fdecl: s}
                                // 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 918e5f3043b59ed7844499a936caceeb18f74f59..ff8dd13b6d717cac35f32b6368c5f8703011568a 100644 (file)
@@ -38,7 +38,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 doesn't modify the
 // incoming type. If a substitution took place, the result type is different
-// from from the incoming type.
+// from the incoming type.
 //
 // If the given typMap is non-nil, it is used in lieu of check.typMap.
 func (check *Checker) subst(pos syntax.Pos, typ Type, smap substMap, typMap map[string]*Named) Type {
index f051a4f2acb812bd511661ee35a90a4c0f8f69c9..09e5d5c5ad84355af0bdea8552b1de283b5cf256 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 692ed37ef4b9e881f788155fbd1f030201952564..d83a95af0efb38c4af8eeb312d5cd629f7222749 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 b7ddeaa1a881b0ec79ac318ff474819fd61ecf3e..395e3bfec8076e3eeaf09c7ddebb475d9b69dcc2 100644 (file)
@@ -4,4 +4,4 @@
 
 package main
 
-func /* ERROR "func main must have no type parameters" */ main[T any]() {}
+func main [T /* ERROR "func main must have no type parameters" */ any]() {}
index 1ad80b1e1b90fc98d343597771d7d8f9e0e30f3d..765d561f3b57dbdfde16903aa7c3d8da86d10dd5 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/cmd/compile/internal/types2/testdata/fixedbugs/issue47818.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47818.go2
new file mode 100644 (file)
index 0000000..5334695
--- /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)
\ No newline at end of file
index 14596b68a30295d090277bfabcfc8ada16682756..56f64ab40595b0f17f37d555ed0274517ed84354 100644 (file)
@@ -271,6 +271,11 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
                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(pos, "embedding constraint interface %s requires go1.18 or later", typ)
+                               continue
+                       }
                        if tset.comparable {
                                ityp.tset.comparable = true
                        }
@@ -279,6 +284,10 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
                        }
                        terms = tset.terms
                case *Union:
+                       if check != nil && !check.allowVersion(check.pkg, 1, 18) {
+                               check.errorf(pos, "embedding interface element %s requires go1.18 or later", u)
+                               continue
+                       }
                        tset := computeUnionTypeSet(check, pos, u)
                        if tset == &invalidTypeSet {
                                continue // ignore invalid unions
@@ -293,7 +302,7 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
                                continue
                        }
                        if check != nil && !check.allowVersion(check.pkg, 1, 18) {
-                               check.errorf(pos, "%s is not an interface", typ)
+                               check.errorf(pos, "embedding non-interface type %s requires go1.18 or later", typ)
                                continue
                        }
                        terms = termlist{{false, typ}}
index 241c6d35fe4e91fe7b43dc9525cbcd6b43370d05..f3e415e4c73bf505ef26643093e04961731d5d0f 100644 (file)
@@ -38,14 +38,12 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *Named, wantType boo
                }
                return
        case universeAny, universeComparable:
+               // complain if necessary but keep going
                if !check.allowVersion(check.pkg, 1, 18) {
-                       check.errorf(e, "undeclared name: %s (requires version go1.18 or later)", e.Value)
-                       return
-               }
-               // If we allow "any" for general use, this if-statement can be removed (issue #33232).
-               if obj == universeAny {
-                       check.error(e, "cannot use any outside constraint position")
-                       return
+                       check.softErrorf(e, "undeclared name: %s (requires version go1.18 or later)", e.Value)
+               } else if obj == universeAny {
+                       // If we allow "any" for general use, this if-statement can be removed (issue #33232).
+                       check.softErrorf(e, "cannot use any outside constraint position")
                }
        }
        check.recordUse(e, obj)
@@ -274,6 +272,9 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *Named) (T Type) {
                }
 
        case *syntax.IndexExpr:
+               if !check.allowVersion(check.pkg, 1, 18) {
+                       check.softErrorf(e.Pos(), "type instantiation requires go1.18 or later")
+               }
                return check.instantiatedType(e.X, unpackExpr(e.Index), def)
 
        case *syntax.ParenExpr:
index 876ea58ef97634a9d26154ecd1dd28cb87cc185e..a58ccce2db16d43bfeed7540f0b4e0381fca9687 100644 (file)
@@ -10,7 +10,7 @@
 package main
 
 type I interface {
-       int // ERROR "interface contains embedded non-interface|not an interface"
+       int // ERROR "interface contains embedded non-interface|embedding non-interface type"
 }
 
 func New() I {