cmd/compile: always accept 1.18 syntax but complain if not 1.18

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>
This commit is contained in:
Robert Griesemer 2021-08-24 18:07:42 -07:00
parent bf0bc4122f
commit 4068fb6c21
13 changed files with 142 additions and 37 deletions

View 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
}

View 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)

View 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:
//

View 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)
}
info := &declInfo{file: fileScope, fdecl: d}
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: 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

View 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 {

View file

@ -146,7 +146,7 @@ type (
m1(I5)
}
I6 interface {
S0 /* ERROR "not an interface" */
S0 /* ERROR "non-interface type S0" */
}
I7 interface {
I1

View 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{}

View 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]() {}

View 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 {}

View file

@ -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)

View 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}}

View 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:

View 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 {