| // Copyright 2026 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. |
| |
| package inline |
| |
| import ( |
| "bytes" |
| "go/ast" |
| "go/parser" |
| "go/token" |
| "go/types" |
| "strings" |
| "testing" |
| |
| "golang.org/x/tools/go/ast/inspector" |
| ) |
| |
| // TestIssue73187 tests that inlining does not panic on variadic calls when |
| // the AST param field type is not an *ast.Ellipsis (golang/go#73187). |
| // |
| // This cannot be tested via standard TestData or TestBasics |
| // because normal type checking always pairs a variadic signature with an |
| // *ast.Ellipsis. The error only arises during synthetic transformations |
| // (such as ChangeSignature) or ill-typed AST edits. |
| func TestIssue73187(t *testing.T) { |
| const src = `package p |
| func f(args ...string) { |
| println(args) |
| } |
| func main() { |
| f("a", "b") |
| } |
| ` |
| fset := token.NewFileSet() |
| file, err := parser.ParseFile(fset, "p.go", src, 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| info := &types.Info{ |
| Defs: make(map[*ast.Ident]types.Object), |
| Uses: make(map[*ast.Ident]types.Object), |
| Types: make(map[ast.Expr]types.TypeAndValue), |
| Selections: make(map[*ast.SelectorExpr]*types.Selection), |
| Scopes: make(map[ast.Node]*types.Scope), |
| Implicits: make(map[ast.Node]types.Object), |
| Instances: make(map[*ast.Ident]types.Instance), |
| FileVersions: make(map[*ast.File]string), |
| } |
| conf := types.Config{} |
| pkg, err := conf.Check("p", fset, []*ast.File{file}, info) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| var fDecl *ast.FuncDecl |
| var call *ast.CallExpr |
| for _, decl := range file.Decls { |
| if fd, ok := decl.(*ast.FuncDecl); ok { |
| if fd.Name.Name == "f" { |
| fDecl = fd |
| } else if fd.Name.Name == "main" { |
| call = fd.Body.List[0].(*ast.ExprStmt).X.(*ast.CallExpr) |
| } |
| } |
| } |
| |
| curCall, _ := inspector.New([]*ast.File{file}).Root().FindNode(call) |
| caller := &Caller{ |
| Fset: fset, |
| Types: pkg, |
| Info: info, |
| Call: curCall, |
| } |
| |
| callee, err := AnalyzeCallee(t.Logf, fset, pkg, info, fDecl, []byte(src)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Mutate Content in callee.impl with equal-length ArrayType instead of Ellipsis |
| callee.impl.Content = bytes.Replace(callee.impl.Content, []byte("...string"), []byte(" []string"), 1) |
| |
| // Inlining must not panic. |
| if _, err := Inline(caller, callee, &Options{Logf: t.Logf}); err != nil { |
| t.Fatalf("Inline failed: %v", err) |
| } |
| } |
| |
| func TestRecover(t *testing.T) { |
| // Test that Options.Recover catches inliner panics and returns them as errors. |
| var caller Caller |
| var callee Callee |
| _, err := Inline(&caller, &callee, &Options{Recover: true}) |
| if err == nil { |
| t.Fatal("Inline unexpectedly succeeded on nil caller") |
| } |
| if !strings.Contains(err.Error(), "inlining failed") { |
| t.Fatalf("expected inlining failed error, got: %v", err) |
| } |
| } |