From 0789ca4951f9c72e0a09051783276e3730d6db06 Mon Sep 17 00:00:00 2001 From: Robert Findley Date: Wed, 16 Nov 2022 20:58:58 -0500 Subject: [PATCH] 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 Run-TryBot: Robert Findley TryBot-Result: Gopher Robot --- src/cmd/compile/internal/types2/api_test.go | 13 +++++--- src/cmd/compile/internal/types2/call.go | 34 ++++++++++++++++---- src/go/types/api_test.go | 13 +++++--- src/go/types/call.go | 35 ++++++++++++++++----- 4 files changed, 72 insertions(+), 23 deletions(-) diff --git a/src/cmd/compile/internal/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go index 5c56e2b7e9..fe84720052 100644 --- a/src/cmd/compile/internal/types2/api_test.go +++ b/src/cmd/compile/internal/types2/api_test.go @@ -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 } diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go index 5b1be07e84..ffff11ea6e 100644 --- a/src/cmd/compile/internal/types2/call.go +++ b/src/cmd/compile/internal/types2/call.go @@ -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) } diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go index 32d6634f53..98ef6c423f 100644 --- a/src/go/types/api_test.go +++ b/src/go/types/api_test.go @@ -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 } diff --git a/src/go/types/call.go b/src/go/types/call.go index 4fb7b05519..7a9329613d 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -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) } -- 2.50.0