go/types, types2: ensure signatures are instantiated if all type args

are provided

Improve the accuracy of recorded types and instances for function calls,
by instantiating their signature before checking arguments if all type
arguments are provided. This avoids a problem where fully instantiated
function signatures are are not recorded as such following an error
checking their arguments.

Fixes golang/go#51803

Change-Id: Iec4cbd219a2cd19bb1bcf2a5c4019f556e4304b1
Reviewed-on: https://go-review.googlesource.com/c/go/+/451436
Reviewed-by: Robert Griesemer <gri@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Robert Findley 2022-11-16 20:58:58 -05:00
parent 7f75b72904
commit 0789ca4951
4 changed files with 72 additions and 23 deletions

View File

@ -536,19 +536,22 @@ type T[P any] []P
{`T`, []string{`string`}, `[]string`},
},
},
{`package issue51803; func foo[T any](T) {}; func _() { foo[int]( /* leave arg away on purpose */ ) }`,
[]testInst{{`foo`, []string{`int`}, `func(int)`}},
},
}
for _, test := range tests {
imports := make(testImporter)
conf := Config{Importer: imports}
conf := Config{
Importer: imports,
Error: func(error) {}, // ignore errors
}
instMap := make(map[*syntax.Name]Instance)
useMap := make(map[*syntax.Name]Object)
makePkg := func(src string) *Package {
f := mustParse("p.go", src)
pkg, err := conf.Check("", []*syntax.File{f}, &Info{Instances: instMap, Uses: useMap})
if err != nil {
t.Fatal(err)
}
pkg, _ := conf.Check("", []*syntax.File{f}, &Info{Instances: instMap, Uses: useMap})
imports[pkg.Name()] = pkg
return pkg
}

View File

@ -52,10 +52,10 @@ func (check *Checker) funcInst(x *operand, inst *syntax.IndexExpr) {
assert(got == want)
// instantiate function signature
res := check.instantiateSignature(x.Pos(), sig, targs, xlist)
assert(res.TypeParams().Len() == 0) // signature is not generic anymore
check.recordInstance(inst.X, targs, res)
x.typ = res
sig = check.instantiateSignature(x.Pos(), sig, targs, xlist)
assert(sig.TypeParams().Len() == 0) // signature is not generic anymore
check.recordInstance(inst.X, targs, sig)
x.typ = sig
x.mode = value
x.expr = inst
}
@ -177,6 +177,9 @@ func (check *Checker) callExpr(x *operand, call *syntax.CallExpr) exprKind {
return statement
}
// Capture wasGeneric before sig is potentially instantiated below.
wasGeneric := sig.TypeParams().Len() > 0
// evaluate type arguments, if any
var xlist []syntax.Expr
var targs []Type
@ -200,14 +203,33 @@ func (check *Checker) callExpr(x *operand, call *syntax.CallExpr) exprKind {
x.expr = call
return statement
}
// If sig is generic and all type arguments are provided, preempt function
// argument type inference by explicitly instantiating the signature. This
// ensures that we record accurate type information for sig, even if there
// is an error checking its arguments (for example, if an incorrect number
// of arguments is supplied).
if got == want && want > 0 {
if !check.allowVersion(check.pkg, 1, 18) {
check.versionErrorf(inst.Pos(), "go1.18", "function instantiation")
}
sig = check.instantiateSignature(inst.Pos(), sig, targs, xlist)
assert(sig.TypeParams().Len() == 0) // signature is not generic anymore
check.recordInstance(inst, targs, sig)
// targs have been consumed; proceed with checking arguments of the
// non-generic signature.
targs = nil
xlist = nil
}
}
// evaluate arguments
args, _ := check.exprList(call.ArgList, false)
isGeneric := sig.TypeParams().Len() > 0
sig = check.arguments(call, sig, targs, args, xlist)
if isGeneric && sig.TypeParams().Len() == 0 {
if wasGeneric && sig.TypeParams().Len() == 0 {
// update the recorded type of call.Fun to its instantiated type
check.recordTypeAndValue(call.Fun, value, sig, nil)
}

View File

@ -535,19 +535,22 @@ type T[P any] []P
{`T`, []string{`string`}, `[]string`},
},
},
{`package issue51803; func foo[T any](T) {}; func _() { foo[int]( /* leave arg away on purpose */ ) }`,
[]testInst{{`foo`, []string{`int`}, `func(int)`}},
},
}
for _, test := range tests {
imports := make(testImporter)
conf := Config{Importer: imports}
conf := Config{
Importer: imports,
Error: func(error) {}, // ignore errors
}
instMap := make(map[*ast.Ident]Instance)
useMap := make(map[*ast.Ident]Object)
makePkg := func(src string) *Package {
f := mustParse(fset, "p.go", src)
pkg, err := conf.Check("", fset, []*ast.File{f}, &Info{Instances: instMap, Uses: useMap})
if err != nil {
t.Fatal(err)
}
pkg, _ := conf.Check("", fset, []*ast.File{f}, &Info{Instances: instMap, Uses: useMap})
imports[pkg.Name()] = pkg
return pkg
}

View File

@ -53,10 +53,10 @@ func (check *Checker) funcInst(x *operand, ix *typeparams.IndexExpr) {
assert(got == want)
// instantiate function signature
res := check.instantiateSignature(x.Pos(), sig, targs, ix.Indices)
assert(res.TypeParams().Len() == 0) // signature is not generic anymore
check.recordInstance(ix.Orig, targs, res)
x.typ = res
sig = check.instantiateSignature(x.Pos(), sig, targs, ix.Indices)
assert(sig.TypeParams().Len() == 0) // signature is not generic anymore
check.recordInstance(ix.Orig, targs, sig)
x.typ = sig
x.mode = value
x.expr = ix.Orig
}
@ -108,7 +108,6 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
}
x.expr = call.Fun
check.record(x)
} else {
check.exprOrType(x, call.Fun, true)
}
@ -180,6 +179,9 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
return statement
}
// Capture wasGeneric before sig is potentially instantiated below.
wasGeneric := sig.TypeParams().Len() > 0
// evaluate type arguments, if any
var xlist []ast.Expr
var targs []Type
@ -203,14 +205,33 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
x.expr = call
return statement
}
// If sig is generic and all type arguments are provided, preempt function
// argument type inference by explicitly instantiating the signature. This
// ensures that we record accurate type information for sig, even if there
// is an error checking its arguments (for example, if an incorrect number
// of arguments is supplied).
if got == want && want > 0 {
if !check.allowVersion(check.pkg, 1, 18) {
check.softErrorf(inNode(call.Fun, ix.Lbrack), UnsupportedFeature, "function instantiation requires go1.18 or later")
}
sig = check.instantiateSignature(ix.Pos(), sig, targs, xlist)
assert(sig.TypeParams().Len() == 0) // signature is not generic anymore
check.recordInstance(ix.Orig, targs, sig)
// targs have been consumed; proceed with checking arguments of the
// non-generic signature.
targs = nil
xlist = nil
}
}
// evaluate arguments
args, _ := check.exprList(call.Args, false)
isGeneric := sig.TypeParams().Len() > 0
sig = check.arguments(call, sig, targs, args, xlist)
if isGeneric && sig.TypeParams().Len() == 0 {
if wasGeneric && sig.TypeParams().Len() == 0 {
// Update the recorded type of call.Fun to its instantiated type.
check.recordTypeAndValue(call.Fun, value, sig, nil)
}