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