all: merge master (5ab57de) into gopls-release-branch.0.14
Also add back the replace directive.
For golang/go#63220
Merge List:
+ 2023-10-24 5ab57de36 go/packages: ensure that types.Sizes is correct
+ 2023-10-23 02048e6c2 go/packages: document that types.Sizes may be nil
+ 2023-10-23 5185da162 internal/refactor/inline: avoid redundant import names added by inlining
+ 2023-10-23 6360c0b19 gopls/internal/lsp/source: find linkname directives without parsing
+ 2023-10-19 71f6a4688 cmd/bundle: drop old +build lines
+ 2023-10-19 cdf1b5eb5 cmd/present: drop NaCl reference from docs
+ 2023-10-18 99bbd3c91 go/callgraph/vta: use core type for struct fields
+ 2023-10-17 8ed111356 go/ssa: add support for range-over-int
+ 2023-10-16 7df9d5f9f gopls/internal/lsp: fix signature crash on error.Error
Change-Id: I80b8c0040d9e3ec207446d07212ad671f60c4e7d
diff --git a/cmd/bundle/main.go b/cmd/bundle/main.go
index a5c426d..fa73eb8 100644
--- a/cmd/bundle/main.go
+++ b/cmd/bundle/main.go
@@ -228,7 +228,6 @@
var out bytes.Buffer
if buildTags != "" {
fmt.Fprintf(&out, "//go:build %s\n", buildTags)
- fmt.Fprintf(&out, "// +build %s\n\n", buildTags)
}
fmt.Fprintf(&out, "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.\n")
diff --git a/cmd/bundle/testdata/out.golden b/cmd/bundle/testdata/out.golden
index a8f0cfe..c6f536e 100644
--- a/cmd/bundle/testdata/out.golden
+++ b/cmd/bundle/testdata/out.golden
@@ -1,5 +1,4 @@
//go:build tag
-// +build tag
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
// $ bundle
diff --git a/cmd/present/doc.go b/cmd/present/doc.go
index 6545535..a5065f0 100644
--- a/cmd/present/doc.go
+++ b/cmd/present/doc.go
@@ -8,9 +8,6 @@
It may be run as a stand-alone command or an App Engine app.
-The setup of the Go version of NaCl is documented at:
-https://golang.org/wiki/NativeClient
-
To use with App Engine, copy the files in the tools/cmd/present directory to the
root of your application and create an app.yaml file similar to this:
diff --git a/go/callgraph/vta/graph.go b/go/callgraph/vta/graph.go
index 2537123..4d1d525 100644
--- a/go/callgraph/vta/graph.go
+++ b/go/callgraph/vta/graph.go
@@ -106,12 +106,12 @@
}
func (f field) Type() types.Type {
- s := f.StructType.Underlying().(*types.Struct)
+ s := typeparams.CoreType(f.StructType).(*types.Struct)
return s.Field(f.index).Type()
}
func (f field) String() string {
- s := f.StructType.Underlying().(*types.Struct)
+ s := typeparams.CoreType(f.StructType).(*types.Struct)
return fmt.Sprintf("Field(%v:%s)", f.StructType, s.Field(f.index).Name())
}
@@ -434,7 +434,7 @@
}
func (b *builder) fieldAddr(f *ssa.FieldAddr) {
- t := f.X.Type().Underlying().(*types.Pointer).Elem()
+ t := typeparams.CoreType(f.X.Type()).(*types.Pointer).Elem()
// Since we are getting pointer to a field, make a bidirectional edge.
fnode := field{StructType: t, index: f.Field}
diff --git a/go/callgraph/vta/testdata/src/issue63146.go b/go/callgraph/vta/testdata/src/issue63146.go
new file mode 100644
index 0000000..6c809c4
--- /dev/null
+++ b/go/callgraph/vta/testdata/src/issue63146.go
@@ -0,0 +1,26 @@
+package test
+
+type embedded struct{}
+
+type S struct{ embedded }
+
+func (_ S) M() {}
+
+type C interface {
+ M()
+ S
+}
+
+func G[T C]() {
+ t := T{embedded{}}
+ t.M()
+}
+
+func F() {
+ G[S]()
+}
+
+// WANT:
+// F: G[testdata.S]() -> G[testdata.S]
+// G[testdata.S]: (S).M(t2) -> S.M
+// S.M: (testdata.S).M(t1) -> S.M
diff --git a/go/callgraph/vta/vta_test.go b/go/callgraph/vta/vta_test.go
index 47962e3..69f2181 100644
--- a/go/callgraph/vta/vta_test.go
+++ b/go/callgraph/vta/vta_test.go
@@ -127,6 +127,7 @@
files := []string{
"testdata/src/arrays_generics.go",
"testdata/src/callgraph_generics.go",
+ "testdata/src/issue63146.go",
}
for _, file := range files {
t.Run(file, func(t *testing.T) {
diff --git a/go/packages/packages.go b/go/packages/packages.go
index ece0e7c..40af0ad 100644
--- a/go/packages/packages.go
+++ b/go/packages/packages.go
@@ -258,13 +258,21 @@
// proceeding with further analysis. The PrintErrors function is
// provided for convenient display of all errors.
func Load(cfg *Config, patterns ...string) ([]*Package, error) {
- l := newLoader(cfg)
- response, err := defaultDriver(&l.Config, patterns...)
+ ld := newLoader(cfg)
+ response, err := defaultDriver(&ld.Config, patterns...)
if err != nil {
return nil, err
}
- l.sizes = types.SizesFor(response.Compiler, response.Arch)
- return l.refine(response)
+
+ // If type size information is needed but unavailable.
+ // reject the whole Load since the error is the same for every package.
+ ld.sizes = types.SizesFor(response.Compiler, response.Arch)
+ if ld.sizes == nil && ld.Config.Mode&(NeedTypes|NeedTypesSizes|NeedTypesInfo) != 0 {
+ return nil, fmt.Errorf("can't determine type sizes for compiler %q on GOARCH %q",
+ response.Compiler, response.Arch)
+ }
+
+ return ld.refine(response)
}
// defaultDriver is a driver that implements go/packages' fallback behavior.
@@ -553,7 +561,7 @@
type loader struct {
pkgs map[string]*loaderPackage
Config
- sizes types.Sizes
+ sizes types.Sizes // non-nil if needed by mode
parseCache map[string]*parseValue
parseCacheMu sync.Mutex
exportMu sync.Mutex // enforces mutual exclusion of exportdata operations
@@ -678,7 +686,7 @@
}
}
- // Materialize the import graph.
+ // Materialize the import graph (if NeedImports).
const (
white = 0 // new
@@ -696,9 +704,8 @@
// visit returns whether the package needs src or has a transitive
// dependency on a package that does. These are the only packages
// for which we load source code.
- var stack []*loaderPackage
+ var stack, srcPkgs []*loaderPackage
var visit func(lpkg *loaderPackage) bool
- var srcPkgs []*loaderPackage
visit = func(lpkg *loaderPackage) bool {
switch lpkg.color {
case black:
@@ -709,35 +716,34 @@
lpkg.color = grey
stack = append(stack, lpkg) // push
stubs := lpkg.Imports // the structure form has only stubs with the ID in the Imports
- // If NeedImports isn't set, the imports fields will all be zeroed out.
- if ld.Mode&NeedImports != 0 {
- lpkg.Imports = make(map[string]*Package, len(stubs))
- for importPath, ipkg := range stubs {
- var importErr error
- imp := ld.pkgs[ipkg.ID]
- if imp == nil {
- // (includes package "C" when DisableCgo)
- importErr = fmt.Errorf("missing package: %q", ipkg.ID)
- } else if imp.color == grey {
- importErr = fmt.Errorf("import cycle: %s", stack)
- }
- if importErr != nil {
- if lpkg.importErrors == nil {
- lpkg.importErrors = make(map[string]error)
- }
- lpkg.importErrors[importPath] = importErr
- continue
- }
-
- if visit(imp) {
- lpkg.needsrc = true
- }
- lpkg.Imports[importPath] = imp.Package
+ lpkg.Imports = make(map[string]*Package, len(stubs))
+ for importPath, ipkg := range stubs {
+ var importErr error
+ imp := ld.pkgs[ipkg.ID]
+ if imp == nil {
+ // (includes package "C" when DisableCgo)
+ importErr = fmt.Errorf("missing package: %q", ipkg.ID)
+ } else if imp.color == grey {
+ importErr = fmt.Errorf("import cycle: %s", stack)
}
+ if importErr != nil {
+ if lpkg.importErrors == nil {
+ lpkg.importErrors = make(map[string]error)
+ }
+ lpkg.importErrors[importPath] = importErr
+ continue
+ }
+
+ if visit(imp) {
+ lpkg.needsrc = true
+ }
+ lpkg.Imports[importPath] = imp.Package
}
if lpkg.needsrc {
srcPkgs = append(srcPkgs, lpkg)
}
+ // NeedTypeSizes causes TypeSizes to be set even
+ // on packages for which types aren't needed.
if ld.Mode&NeedTypesSizes != 0 {
lpkg.TypesSizes = ld.sizes
}
@@ -757,17 +763,18 @@
for _, lpkg := range initial {
visit(lpkg)
}
- }
- if ld.Mode&NeedImports != 0 && ld.Mode&NeedTypes != 0 {
- for _, lpkg := range srcPkgs {
+
+ if ld.Mode&NeedTypes != 0 {
// Complete type information is required for the
// immediate dependencies of each source package.
- for _, ipkg := range lpkg.Imports {
- imp := ld.pkgs[ipkg.ID]
- imp.needtypes = true
+ for _, lpkg := range srcPkgs {
+ for _, ipkg := range lpkg.Imports {
+ ld.pkgs[ipkg.ID].needtypes = true
+ }
}
}
}
+
// Load type data and syntax if needed, starting at
// the initial packages (roots of the import DAG).
if ld.Mode&NeedTypes != 0 || ld.Mode&NeedSyntax != 0 {
@@ -1042,7 +1049,7 @@
IgnoreFuncBodies: ld.Mode&NeedDeps == 0 && !lpkg.initial,
Error: appendError,
- Sizes: ld.sizes,
+ Sizes: ld.sizes, // may be nil
}
if lpkg.Module != nil && lpkg.Module.GoVersion != "" {
typesinternal.SetGoVersion(tc, "go"+lpkg.Module.GoVersion)
diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go
index 60fdf9f..4fb7f0b 100644
--- a/go/packages/packages_test.go
+++ b/go/packages/packages_test.go
@@ -1217,6 +1217,34 @@
}
}
+// This is a regression test for the root cause of
+// github.com/golang/vscode-go/issues/3021.
+// If types are needed (any of NeedTypes{,Info,Sizes}
+// and the types.Sizes cannot be obtained (e.g. due to a bad GOARCH)
+// then the Load operation must fail. It must not return a nil
+// TypesSizes, or use the default (wrong) size.
+//
+// We use a file=... query because it suppresses the bad-GOARCH check
+// that the go command would otherwise perform eagerly.
+// (Gopls relies on this as a fallback.)
+func TestNeedTypeSizesWithBadGOARCH(t *testing.T) {
+ testAllOrModulesParallel(t, func(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "testdata",
+ Files: map[string]interface{}{"a/a.go": `package a`}}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.NeedTypesSizes // or {,Info,Sizes}
+ exported.Config.Env = append(exported.Config.Env, "GOARCH=286")
+ _, err := packages.Load(exported.Config, "file=./a/a.go")
+ got := fmt.Sprint(err)
+ want := "can't determine type sizes"
+ if !strings.Contains(got, want) {
+ t.Errorf("Load error %q does not contain substring %q", got, want)
+ }
+ })
+}
+
// TestContainsFallbackSticks ensures that when there are both contains and non-contains queries
// the decision whether to fallback to the pre-1.11 go list sticks across both sets of calls to
// go list.
diff --git a/go/ssa/builder.go b/go/ssa/builder.go
index 9e8d12e..c66a6c5 100644
--- a/go/ssa/builder.go
+++ b/go/ssa/builder.go
@@ -1784,16 +1784,16 @@
// forStmt emits to fn code for the for statement s, optionally
// labelled by label.
func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) {
- // ...init...
- // jump loop
+ // ...init...
+ // jump loop
// loop:
- // if cond goto body else done
+ // if cond goto body else done
// body:
- // ...body...
- // jump post
- // post: (target of continue)
- // ...post...
- // jump loop
+ // ...body...
+ // jump post
+ // post: (target of continue)
+ // ...post...
+ // jump loop
// done: (target of break)
if s.Init != nil {
b.stmt(fn, s.Init)
@@ -1841,17 +1841,17 @@
// forPos is the position of the "for" token.
func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, pos token.Pos) (k, v Value, loop, done *BasicBlock) {
//
- // length = len(x)
- // index = -1
- // loop: (target of continue)
- // index++
- // if index < length goto body else done
+ // length = len(x)
+ // index = -1
+ // loop: (target of continue)
+ // index++
+ // if index < length goto body else done
// body:
- // k = index
- // v = x[index]
- // ...body...
- // jump loop
- // done: (target of break)
+ // k = index
+ // v = x[index]
+ // ...body...
+ // jump loop
+ // done: (target of break)
// Determine number of iterations.
var length Value
@@ -1936,16 +1936,16 @@
// if the respective component is not wanted.
func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token.Pos) (k, v Value, loop, done *BasicBlock) {
//
- // it = range x
+ // it = range x
// loop: (target of continue)
- // okv = next it (ok, key, value)
- // ok = extract okv #0
- // if ok goto body else done
+ // okv = next it (ok, key, value)
+ // ok = extract okv #0
+ // if ok goto body else done
// body:
- // k = extract okv #1
- // v = extract okv #2
- // ...body...
- // jump loop
+ // k = extract okv #1
+ // v = extract okv #2
+ // ...body...
+ // jump loop
// done: (target of break)
//
@@ -1998,13 +1998,13 @@
func (b *builder) rangeChan(fn *Function, x Value, tk types.Type, pos token.Pos) (k Value, loop, done *BasicBlock) {
//
// loop: (target of continue)
- // ko = <-x (key, ok)
- // ok = extract ko #1
- // if ok goto body else done
+ // ko = <-x (key, ok)
+ // ok = extract ko #1
+ // if ok goto body else done
// body:
- // k = extract ko #0
- // ...
- // goto loop
+ // k = extract ko #0
+ // ...body...
+ // goto loop
// done: (target of break)
loop = fn.newBasicBlock("rangechan.loop")
@@ -2031,6 +2031,57 @@
return
}
+// rangeInt emits to fn the header for a range loop with an integer operand.
+// tk is the key value's type, or nil if the k result is not wanted.
+// pos is the position of the "for" token.
+func (b *builder) rangeInt(fn *Function, x Value, tk types.Type, pos token.Pos) (k Value, loop, done *BasicBlock) {
+ //
+ // iter = 0
+ // if 0 < x goto body else done
+ // loop: (target of continue)
+ // iter++
+ // if iter < x goto body else done
+ // body:
+ // k = x
+ // ...body...
+ // jump loop
+ // done: (target of break)
+
+ if isUntyped(x.Type()) {
+ x = emitConv(fn, x, tInt)
+ }
+
+ T := x.Type()
+ iter := fn.addLocal(T, token.NoPos)
+ // x may be unsigned. Avoid initializing x to -1.
+
+ body := fn.newBasicBlock("rangeint.body")
+ done = fn.newBasicBlock("rangeint.done")
+ emitIf(fn, emitCompare(fn, token.LSS, zeroConst(T), x, token.NoPos), body, done)
+
+ loop = fn.newBasicBlock("rangeint.loop")
+ fn.currentBlock = loop
+
+ incr := &BinOp{
+ Op: token.ADD,
+ X: emitLoad(fn, iter),
+ Y: emitConv(fn, vOne, T),
+ }
+ incr.setType(T)
+ emitStore(fn, iter, fn.emit(incr), pos)
+ emitIf(fn, emitCompare(fn, token.LSS, incr, x, token.NoPos), body, done)
+ fn.currentBlock = body
+
+ if tk != nil {
+ // Integer types (int, uint8, etc.) are named and
+ // we know that k is assignable to x when tk != nil.
+ // This implies tk and T are identical so no conversion is needed.
+ k = emitLoad(fn, iter)
+ }
+
+ return
+}
+
// rangeStmt emits to fn code for the range statement s, optionally
// labelled by label.
func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) {
@@ -2068,9 +2119,21 @@
case *types.Chan:
k, loop, done = b.rangeChan(fn, x, tk, s.For)
- case *types.Map, *types.Basic: // string
+ case *types.Map:
k, v, loop, done = b.rangeIter(fn, x, tk, tv, s.For)
+ case *types.Basic:
+ switch {
+ case rt.Info()&types.IsString != 0:
+ k, v, loop, done = b.rangeIter(fn, x, tk, tv, s.For)
+
+ case rt.Info()&types.IsInteger != 0:
+ k, loop, done = b.rangeInt(fn, x, tk, s.For)
+
+ default:
+ panic("Cannot range over basic type: " + rt.String())
+ }
+
default:
panic("Cannot range over: " + rt.String())
}
diff --git a/go/ssa/builder_generic_test.go b/go/ssa/builder_generic_test.go
index 8ddf898..9f58349 100644
--- a/go/ssa/builder_generic_test.go
+++ b/go/ssa/builder_generic_test.go
@@ -515,48 +515,19 @@
p := prog.Package(lprog.Package(pkgname).Pkg)
p.Build()
- // Collect calls to the builtin print function.
- probes := make(map[*ssa.CallCommon]*ssa.Function)
- for _, mem := range p.Members {
- if fn, ok := mem.(*ssa.Function); ok {
- for _, bb := range fn.Blocks {
- for _, i := range bb.Instrs {
- if i, ok := i.(ssa.CallInstruction); ok {
- call := i.Common()
- if b, ok := call.Value.(*ssa.Builtin); ok && b.Name() == "print" {
- probes[i.Common()] = fn
- }
- }
- }
- }
- }
- }
-
// Collect all notes in f, i.e. comments starting with "//@ types".
notes, err := expect.ExtractGo(prog.Fset, f)
if err != nil {
t.Errorf("expect.ExtractGo: %v", err)
}
- // Matches each probe with a note that has the same line.
- sameLine := func(x, y token.Pos) bool {
- xp := prog.Fset.Position(x)
- yp := prog.Fset.Position(y)
- return xp.Filename == yp.Filename && xp.Line == yp.Line
- }
- expectations := make(map[*ssa.CallCommon]*expect.Note)
+ // Collect calls to the builtin print function.
+ probes := callsTo(p, "print")
+ expectations := matchNotes(prog.Fset, notes, probes)
+
for call := range probes {
- var match *expect.Note
- for _, note := range notes {
- if note.Name == "types" && sameLine(call.Pos(), note.Pos) {
- match = note // first match is good enough.
- break
- }
- }
- if match != nil {
- expectations[call] = match
- } else {
- t.Errorf("Unmatched probe: %v", call)
+ if expectations[call] == nil {
+ t.Errorf("Unmatched call: %v", call)
}
}
@@ -575,6 +546,48 @@
}
}
+// callsTo finds all calls to an SSA value named fname,
+// and returns a map from each call site to its enclosing function.
+func callsTo(p *ssa.Package, fname string) map[*ssa.CallCommon]*ssa.Function {
+ callsites := make(map[*ssa.CallCommon]*ssa.Function)
+ for _, mem := range p.Members {
+ if fn, ok := mem.(*ssa.Function); ok {
+ for _, bb := range fn.Blocks {
+ for _, i := range bb.Instrs {
+ if i, ok := i.(ssa.CallInstruction); ok {
+ call := i.Common()
+ if call.Value.Name() == fname {
+ callsites[call] = fn
+ }
+ }
+ }
+ }
+ }
+ }
+ return callsites
+}
+
+// matchNodes returns a mapping from call sites (found by callsTo)
+// to the first "//@ note" comment on the same line.
+func matchNotes(fset *token.FileSet, notes []*expect.Note, calls map[*ssa.CallCommon]*ssa.Function) map[*ssa.CallCommon]*expect.Note {
+ // Matches each probe with a note that has the same line.
+ sameLine := func(x, y token.Pos) bool {
+ xp := fset.Position(x)
+ yp := fset.Position(y)
+ return xp.Filename == yp.Filename && xp.Line == yp.Line
+ }
+ expectations := make(map[*ssa.CallCommon]*expect.Note)
+ for call := range calls {
+ for _, note := range notes {
+ if sameLine(call.Pos(), note.Pos) {
+ expectations[call] = note
+ break // first match is good enough.
+ }
+ }
+ }
+ return expectations
+}
+
// TestInstructionString tests serializing instructions via Instruction.String().
func TestInstructionString(t *testing.T) {
if !typeparams.Enabled {
diff --git a/go/ssa/builder_go122_test.go b/go/ssa/builder_go122_test.go
index 21930d6..a57e6a5 100644
--- a/go/ssa/builder_go122_test.go
+++ b/go/ssa/builder_go122_test.go
@@ -8,14 +8,17 @@
package ssa_test
import (
+ "fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"testing"
+ "golang.org/x/tools/go/expect"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
+ "golang.org/x/tools/internal/testenv"
)
func TestMultipleGoversions(t *testing.T) {
@@ -61,7 +64,7 @@
conf := &types.Config{Importer: nil, GoVersion: "go1.21"}
p, _, err := ssautil.BuildPackage(conf, fset, pkg, files, ssa.SanityCheckFunctions)
if err != nil {
- t.Errorf("unexpected error: %v", err)
+ t.Fatalf("unexpected error: %v", err)
}
fns := ssautil.AllFunctions(p.Prog)
@@ -79,10 +82,102 @@
t.Fatalf("Could not find function named %q in package %s", item.name, p)
}
if fn.Synthetic != item.wantSyn {
- t.Errorf("Function %q.Syntethic=%q. expected %q", fn, fn.Synthetic, item.wantSyn)
+ t.Errorf("Function %q.Synthetic=%q. expected %q", fn, fn.Synthetic, item.wantSyn)
}
if got := fset.Position(fn.Pos()).String(); got != item.wantPos {
t.Errorf("Function %q.Pos()=%q. expected %q", fn, got, item.wantPos)
}
}
}
+
+const rangeOverIntSrc = `
+package p
+
+type I uint8
+
+func noKey(x int) {
+ for range x {
+ // does not crash
+ }
+}
+
+func untypedConstantOperand() {
+ for i := range 10 {
+ print(i) /*@ types("int")*/
+ }
+}
+
+func unsignedOperand(x uint64) {
+ for i := range x {
+ print(i) /*@ types("uint64")*/
+ }
+}
+
+func namedOperand(x I) {
+ for i := range x {
+ print(i) /*@ types("p.I")*/
+ }
+}
+
+func typeparamOperand[T int](x T) {
+ for i := range x {
+ print(i) /*@ types("T")*/
+ }
+}
+
+func assignment(x I) {
+ var k I
+ for k = range x {
+ print(k) /*@ types("p.I")*/
+ }
+}
+`
+
+// TestRangeOverInt tests that, in a range-over-int (#61405),
+// the type of each range var v (identified by print(v) calls)
+// has the expected type.
+func TestRangeOverInt(t *testing.T) {
+ testenv.NeedsGoExperiment(t, "range")
+
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p.go", rangeOverIntSrc, parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ pkg := types.NewPackage("p", "")
+ conf := &types.Config{}
+ p, _, err := ssautil.BuildPackage(conf, fset, pkg, []*ast.File{f}, ssa.SanityCheckFunctions)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Collect all notes in f, i.e. comments starting with "//@ types".
+ notes, err := expect.ExtractGo(fset, f)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Collect calls to the built-in print function.
+ probes := callsTo(p, "print")
+ expectations := matchNotes(fset, notes, probes)
+
+ for call := range probes {
+ if expectations[call] == nil {
+ t.Errorf("Unmatched call: %v @ %s", call, fset.Position(call.Pos()))
+ }
+ }
+
+ // Check each expectation.
+ for call, note := range expectations {
+ var args []string
+ for _, a := range call.Args {
+ args = append(args, a.Type().String())
+ }
+ if got, want := fmt.Sprint(args), fmt.Sprint(note.Args); got != want {
+ at := fset.Position(call.Pos())
+ t.Errorf("%s: arguments to print had types %s, want %s", at, got, want)
+ logFunction(t, probes[call])
+ }
+ }
+}
diff --git a/go/ssa/interp/interp_go122_test.go b/go/ssa/interp/interp_go122_test.go
new file mode 100644
index 0000000..3d808df
--- /dev/null
+++ b/go/ssa/interp/interp_go122_test.go
@@ -0,0 +1,31 @@
+// Copyright 2023 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.
+
+//go:build go1.19
+
+package interp_test
+
+// Utilities from interp_test.go require go1.19.
+
+import (
+ "log"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "golang.org/x/tools/internal/testenv"
+)
+
+// TestExperimentRange tests files in testdata with GOEXPERIMENT=range set.
+func TestExperimentRange(t *testing.T) {
+ testenv.NeedsGoExperiment(t, "range")
+
+ // TODO: Is cwd actually needed here?
+ goroot := makeGoroot(t)
+ cwd, err := os.Getwd()
+ if err != nil {
+ log.Fatal(err)
+ }
+ run(t, filepath.Join(cwd, "testdata", "rangeoverint.go"), goroot)
+}
diff --git a/go/ssa/interp/testdata/rangeoverint.go b/go/ssa/interp/testdata/rangeoverint.go
new file mode 100644
index 0000000..9a02d82
--- /dev/null
+++ b/go/ssa/interp/testdata/rangeoverint.go
@@ -0,0 +1,86 @@
+package main
+
+// Range over integers.
+
+// Currently requires 1.22 and GOEXPERIMENT=range.
+
+import "fmt"
+
+func f() {
+ s := "AB"
+ for range 5 {
+ s += s
+ }
+ if s != "ABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABABAB" {
+ panic(s)
+ }
+
+ var t []int
+ for i := range 10 {
+ t = append(t, i)
+ }
+ if got, want := fmt.Sprint(t), "[0 1 2 3 4 5 6 7 8 9]"; got != want {
+ panic(got)
+ }
+
+ var u []uint
+ for i := range uint(3) {
+ u = append(u, i)
+ }
+ if got, want := fmt.Sprint(u), "[0 1 2]"; got != want {
+ panic(got)
+ }
+
+ for i := range 0 {
+ panic(i)
+ }
+
+ for i := range int(-1) {
+ panic(i)
+ }
+
+ for _, test := range []struct {
+ x int
+ b, c bool
+ want string
+ }{
+ {-1, false, false, "[-123 -123]"},
+ {0, false, false, "[-123 -123]"},
+ {1, false, false, "[-123 0 333 333]"},
+ {2, false, false, "[-123 0 333 1 333 333]"},
+ {2, false, true, "[-123 0 222 1 222 222]"},
+ {2, true, false, "[-123 0 111 111]"},
+ {3, false, false, "[-123 0 333 1 333 2 333 333]"},
+ } {
+ got := fmt.Sprint(valueSequence(test.x, test.b, test.c))
+ if got != test.want {
+ panic(fmt.Sprint(test, got))
+ }
+ }
+}
+
+// valueSequence returns a sequence of the values of i.
+// b causes an early break and c causes a continue.
+func valueSequence(x int, b, c bool) []int {
+ var vals []int
+ var i int = -123
+ vals = append(vals, i)
+ for i = range x {
+ vals = append(vals, i)
+ if b {
+ i = 111
+ vals = append(vals, i)
+ break
+ } else if c {
+ i = 222
+ vals = append(vals, i)
+ continue
+ }
+ i = 333
+ vals = append(vals, i)
+ }
+ vals = append(vals, i)
+ return vals
+}
+
+func main() { f() }
diff --git a/gopls/go.mod b/gopls/go.mod
index 9206e7d..cbae63a 100644
--- a/gopls/go.mod
+++ b/gopls/go.mod
@@ -26,3 +26,5 @@
golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338 // indirect
)
+
+replace golang.org/x/tools => ../
diff --git a/gopls/go.sum b/gopls/go.sum
index dfefd8c..fed3b37 100644
--- a/gopls/go.sum
+++ b/gopls/go.sum
@@ -1,11 +1,18 @@
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
+github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
+github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/safehtml v0.0.2/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU=
github.com/google/safehtml v0.1.0 h1:EwLKo8qawTKfsi0orxcQAZzu07cICaBeFMegAU9eaT8=
github.com/google/safehtml v0.1.0/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU=
@@ -15,38 +22,65 @@
github.com/jba/templatecheck v0.6.0/go.mod h1:/1k7EajoSErFI9GLHAsiIJEaNLt3ALKNw2TV7z2SYv4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338 h1:2O2DON6y3XMJiQRAS1UWU+54aec2uopH3x7MAiqGW6Y=
golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/telemetry v0.0.0-20231011160506-788d5629a052 h1:1baVNneD/IRxmu8JQdBuki78zUqBtZxq8smZXQj0X2Y=
golang.org/x/telemetry v0.0.0-20231011160506-788d5629a052/go.mod h1:6p4ScoNeC2dhpQ1nSSMmkZ7mEj5JQUSCyc0uExBp5T4=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.14.1-0.20231016183243-5f29ce99b53b h1:DTn48r7sW+unzZo0o1PceluCR/YXCCNRI/suiMBOZyM=
-golang.org/x/tools v0.14.1-0.20231016183243-5f29ce99b53b/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU=
golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
@@ -61,5 +95,6 @@
honnef.co/go/tools v0.4.5/go.mod h1:GUV+uIBCLpdf0/v6UhHHG/yzI/z6qPskBeQCjcNB96k=
mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM=
mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ=
+mvdan.cc/unparam v0.0.0-20230312165513-e84e2d14e3b8/go.mod h1:Oh/d7dEtzsNHGOq1Cdv8aMm3KdKhVvPbRQcM8WFpBR8=
mvdan.cc/xurls/v2 v2.4.0 h1:tzxjVAj+wSBmDcF6zBB7/myTy3gX9xvi8Tyr28AuQgc=
mvdan.cc/xurls/v2 v2.4.0/go.mod h1:+GEjq9uNjqs8LQfM9nVnM8rff0OQ5Iash5rzX+N1CSg=
diff --git a/gopls/internal/lsp/cache/load.go b/gopls/internal/lsp/cache/load.go
index 331e0e7..0ac4e5d 100644
--- a/gopls/internal/lsp/cache/load.go
+++ b/gopls/internal/lsp/cache/load.go
@@ -413,6 +413,10 @@
return
}
+ if pkg.TypesSizes == nil {
+ panic(id + ".TypeSizes is nil")
+ }
+
// Recreate the metadata rather than reusing it to avoid locking.
m := &source.Metadata{
ID: id,
diff --git a/gopls/internal/lsp/source/definition.go b/gopls/internal/lsp/source/definition.go
index dd3feda..60f1018 100644
--- a/gopls/internal/lsp/source/definition.go
+++ b/gopls/internal/lsp/source/definition.go
@@ -60,7 +60,7 @@
}
// Handle the case where the cursor is in a linkname directive.
- locations, err := LinknameDefinition(ctx, snapshot, fh, position)
+ locations, err := LinknameDefinition(ctx, snapshot, pgf.Mapper, position)
if !errors.Is(err, ErrNoLinkname) {
return locations, err
}
diff --git a/gopls/internal/lsp/source/hover.go b/gopls/internal/lsp/source/hover.go
index 9531783..3c58074 100644
--- a/gopls/internal/lsp/source/hover.go
+++ b/gopls/internal/lsp/source/hover.go
@@ -131,7 +131,7 @@
// Handle linkname directive by overriding what to look for.
var linkedRange *protocol.Range // range referenced by linkname directive, or nil
- if pkgPath, name, offset := parseLinkname(ctx, snapshot, fh, pp); pkgPath != "" && name != "" {
+ if pkgPath, name, offset := parseLinkname(pgf.Mapper, pp); pkgPath != "" && name != "" {
// rng covering 2nd linkname argument: pkgPath.name.
rng, err := pgf.PosRange(pgf.Tok.Pos(offset), pgf.Tok.Pos(offset+len(pkgPath)+len(".")+len(name)))
if err != nil {
diff --git a/gopls/internal/lsp/source/linkname.go b/gopls/internal/lsp/source/linkname.go
index 84890a6..5a727e5 100644
--- a/gopls/internal/lsp/source/linkname.go
+++ b/gopls/internal/lsp/source/linkname.go
@@ -21,10 +21,10 @@
// As such it indicates that other definitions could be worth checking.
var ErrNoLinkname = errors.New("no linkname directive found")
-// LinknameDefinition finds the definition of the linkname directive in fh at pos.
+// LinknameDefinition finds the definition of the linkname directive in m at pos.
// If there is no linkname directive at pos, returns ErrNoLinkname.
-func LinknameDefinition(ctx context.Context, snapshot Snapshot, fh FileHandle, from protocol.Position) ([]protocol.Location, error) {
- pkgPath, name, _ := parseLinkname(ctx, snapshot, fh, from)
+func LinknameDefinition(ctx context.Context, snapshot Snapshot, m *protocol.Mapper, from protocol.Position) ([]protocol.Location, error) {
+ pkgPath, name, _ := parseLinkname(m, from)
if pkgPath == "" {
return nil, ErrNoLinkname
}
@@ -44,27 +44,34 @@
// If successful, it returns
// - package path referenced
// - object name referenced
-// - byte offset in fh of the start of the link target
+// - byte offset in mapped file of the start of the link target
// of the linkname directives 2nd argument.
//
// If the position is not in the second argument of a go:linkname directive,
// or parsing fails, it returns "", "", 0.
-func parseLinkname(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) (pkgPath, name string, targetOffset int) {
- // TODO(adonovan): opt: parsing isn't necessary here.
- // We're only looking for a line comment.
- pgf, err := snapshot.ParseGo(ctx, fh, ParseFull)
+func parseLinkname(m *protocol.Mapper, pos protocol.Position) (pkgPath, name string, targetOffset int) {
+ lineStart, err := m.PositionOffset(protocol.Position{Line: pos.Line, Character: 0})
+ if err != nil {
+ return "", "", 0
+ }
+ lineEnd, err := m.PositionOffset(protocol.Position{Line: pos.Line + 1, Character: 0})
if err != nil {
return "", "", 0
}
- offset, err := pgf.Mapper.PositionOffset(pos)
- if err != nil {
+ directive := string(m.Content[lineStart:lineEnd])
+ // (Assumes no leading spaces.)
+ if !strings.HasPrefix(directive, "//go:linkname") {
return "", "", 0
}
+ // Sometimes source code (typically tests) has another
+ // comment after the directive, trim that away.
+ if i := strings.LastIndex(directive, "//"); i != 0 {
+ directive = strings.TrimSpace(directive[:i])
+ }
// Looking for pkgpath in '//go:linkname f pkgpath.g'.
// (We ignore 1-arg linkname directives.)
- directive, end := findLinknameAtOffset(pgf, offset)
parts := strings.Fields(directive)
if len(parts) != 3 {
return "", "", 0
@@ -72,6 +79,11 @@
// Inside 2nd arg [start, end]?
// (Assumes no trailing spaces.)
+ offset, err := m.PositionOffset(pos)
+ if err != nil {
+ return "", "", 0
+ }
+ end := lineStart + len(directive)
start := end - len(parts[2])
if !(start <= offset && offset <= end) {
return "", "", 0
@@ -87,31 +99,6 @@
return linkname[:dot], linkname[dot+1:], start
}
-// findLinknameAtOffset returns the first linkname directive on line and its end offset.
-// Returns "", 0 if the offset is not in a linkname directive.
-func findLinknameAtOffset(pgf *ParsedGoFile, offset int) (string, int) {
- for _, grp := range pgf.File.Comments {
- for _, com := range grp.List {
- if strings.HasPrefix(com.Text, "//go:linkname") {
- p := safetoken.Position(pgf.Tok, com.Pos())
-
- // Sometimes source code (typically tests) has another
- // comment after the directive, trim that away.
- text := com.Text
- if i := strings.LastIndex(text, "//"); i != 0 {
- text = strings.TrimSpace(text[:i])
- }
-
- end := p.Offset + len(text)
- if p.Offset <= offset && offset < end {
- return text, end
- }
- }
- }
- }
- return "", 0
-}
-
// findLinkname searches dependencies of packages containing fh for an object
// with linker name matching the given package path and name.
func findLinkname(ctx context.Context, snapshot Snapshot, pkgPath PackagePath, name string) (Package, *ParsedGoFile, token.Pos, error) {
diff --git a/gopls/internal/lsp/source/signature_help.go b/gopls/internal/lsp/source/signature_help.go
index 1420fc3..dc45322 100644
--- a/gopls/internal/lsp/source/signature_help.go
+++ b/gopls/internal/lsp/source/signature_help.go
@@ -13,6 +13,7 @@
"strings"
"golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/gopls/internal/bug"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/internal/event"
)
@@ -74,9 +75,22 @@
obj = pkg.GetTypesInfo().ObjectOf(t.Sel)
}
- // Handle builtin functions separately.
- if obj, ok := obj.(*types.Builtin); ok {
- return builtinSignature(ctx, snapshot, callExpr, obj.Name(), pos)
+ // Built-in?
+ if obj != nil && !obj.Pos().IsValid() {
+ // built-in function?
+ if obj, ok := obj.(*types.Builtin); ok {
+ return builtinSignature(ctx, snapshot, callExpr, obj.Name(), pos)
+ }
+
+ // error.Error?
+ if fn, ok := obj.(*types.Func); ok && fn.Name() == "Error" {
+ return &protocol.SignatureInformation{
+ Label: "Error()",
+ Documentation: stringToSigInfoDocumentation("Error returns the error message.", snapshot.Options()),
+ }, 0, nil
+ }
+
+ return nil, 0, bug.Errorf("call to unexpected built-in %v (%T)", obj, obj)
}
// Get the type information for the function being called.
@@ -137,7 +151,6 @@
Documentation: stringToSigInfoDocumentation(sig.doc, snapshot.Options()),
Parameters: paramInfo,
}, activeParam, nil
-
}
func activeParameter(callExpr *ast.CallExpr, numParams int, variadic bool, pos token.Pos) (activeParam int) {
diff --git a/gopls/internal/regtest/marker/testdata/codeaction/removeparam_imports.txt b/gopls/internal/regtest/marker/testdata/codeaction/removeparam_imports.txt
index d616fe2..1a483d2 100644
--- a/gopls/internal/regtest/marker/testdata/codeaction/removeparam_imports.txt
+++ b/gopls/internal/regtest/marker/testdata/codeaction/removeparam_imports.txt
@@ -85,7 +85,7 @@
import (
"mod.test/b"
- c "mod.test/c"
+ "mod.test/c"
)
func _() {
@@ -97,7 +97,7 @@
import (
"mod.test/b"
- c "mod.test/c"
+ "mod.test/c"
)
func _() {
@@ -111,7 +111,7 @@
import (
"mod.test/b"
- c "mod.test/c"
+ "mod.test/c"
)
func _() {
@@ -129,9 +129,9 @@
// TODO(rfindley/adonovan): inlining here adds an additional import of
// mod.test/b. Can we do better?
import (
+ "mod.test/b"
. "mod.test/b"
- b "mod.test/b"
- c "mod.test/c"
+ "mod.test/c"
)
func _() {
diff --git a/gopls/internal/regtest/marker/testdata/completion/testy.txt b/gopls/internal/regtest/marker/testdata/completion/testy.txt
index f26b3ae..1fbc7a2 100644
--- a/gopls/internal/regtest/marker/testdata/completion/testy.txt
+++ b/gopls/internal/regtest/marker/testdata/completion/testy.txt
@@ -55,3 +55,7 @@
_ = snippets.X(nil) //@signature("nil", "X(_ map[sig.Alias]types.CoolAlias) map[sig.Alias]types.CoolAlias")
var _ sig.Alias
}
+
+func issue63578(err error) {
+ err.Error() //@signature(")", "Error()")
+}
diff --git a/internal/refactor/inline/callee.go b/internal/refactor/inline/callee.go
index dc74eab..c9a7ea0 100644
--- a/internal/refactor/inline/callee.go
+++ b/internal/refactor/inline/callee.go
@@ -59,9 +59,12 @@
// An object abstracts a free types.Object referenced by the callee. Gob-serializable.
type object struct {
- Name string // Object.Name()
- Kind string // one of {var,func,const,type,pkgname,nil,builtin}
- PkgPath string // pkgpath of object (or of imported package if kind="pkgname")
+ Name string // Object.Name()
+ Kind string // one of {var,func,const,type,pkgname,nil,builtin}
+ PkgPath string // path of object's package (or imported package if kind="pkgname")
+ PkgName string // name of object's package (or imported package if kind="pkgname")
+ // TODO(rfindley): should we also track LocalPkgName here? Do we want to
+ // preserve the local package name?
ValidPos bool // Object.Pos().IsValid()
Shadow map[string]bool // names shadowed at one of the object's refs
}
@@ -192,15 +195,18 @@
objidx, ok := freeObjIndex[obj]
if !ok {
objidx = len(freeObjIndex)
- var pkgpath string
- if pkgname, ok := obj.(*types.PkgName); ok {
- pkgpath = pkgname.Imported().Path()
+ var pkgpath, pkgname string
+ if pn, ok := obj.(*types.PkgName); ok {
+ pkgpath = pn.Imported().Path()
+ pkgname = pn.Imported().Name()
} else if obj.Pkg() != nil {
pkgpath = obj.Pkg().Path()
+ pkgname = obj.Pkg().Name()
}
freeObjs = append(freeObjs, object{
Name: obj.Name(),
Kind: objectKind(obj),
+ PkgName: pkgname,
PkgPath: pkgpath,
ValidPos: obj.Pos().IsValid(),
})
diff --git a/internal/refactor/inline/inline.go b/internal/refactor/inline/inline.go
index be16161..06f6401 100644
--- a/internal/refactor/inline/inline.go
+++ b/internal/refactor/inline/inline.go
@@ -222,13 +222,13 @@
importDecl = &ast.GenDecl{Tok: token.IMPORT}
f.Decls = prepend[ast.Decl](importDecl, f.Decls...)
}
- for _, spec := range res.newImports {
+ for _, imp := range res.newImports {
// Check that the new imports are accessible.
- path, _ := strconv.Unquote(spec.Path.Value)
+ path, _ := strconv.Unquote(imp.spec.Path.Value)
if !canImport(caller.Types.Path(), path) {
return nil, fmt.Errorf("can't inline function %v as its body refers to inaccessible package %q", callee, path)
}
- importDecl.Specs = append(importDecl.Specs, spec)
+ importDecl.Specs = append(importDecl.Specs, imp.spec)
}
}
@@ -300,8 +300,13 @@
return newSrc, nil
}
+type newImport struct {
+ pkgName string
+ spec *ast.ImportSpec
+}
+
type result struct {
- newImports []*ast.ImportSpec
+ newImports []newImport
old, new ast.Node // e.g. replace call expr by callee function body expression
}
@@ -387,14 +392,14 @@
}
// localImportName returns the local name for a given imported package path.
- var newImports []*ast.ImportSpec
- localImportName := func(path string, shadows map[string]bool) string {
+ var newImports []newImport
+ localImportName := func(obj *object) string {
// Does an import exist?
- for _, name := range importMap[path] {
+ for _, name := range importMap[obj.PkgPath] {
// Check that either the import preexisted,
// or that it was newly added (no PkgName) but is not shadowed,
// either in the callee (shadows) or caller (caller.lookup).
- if !shadows[name] {
+ if !obj.Shadow[name] {
found := caller.lookup(name)
if is[*types.PkgName](found) || found == nil {
return name
@@ -404,7 +409,7 @@
newlyAdded := func(name string) bool {
for _, new := range newImports {
- if new.Name.Name == name {
+ if new.pkgName == name {
return true
}
}
@@ -419,29 +424,32 @@
//
// "init" is not a legal PkgName.
//
- // TODO(adonovan): preserve the PkgName used
- // in the original source, or, for a dot import,
- // use the package's declared name.
- base := pathpkg.Base(path)
+ // TODO(rfindley): is it worth preserving local package names for callee
+ // imports? Are they likely to be better or worse than the name we choose
+ // here?
+ base := obj.PkgName
name := base
- for n := 0; shadows[name] || caller.lookup(name) != nil || newlyAdded(name) || name == "init"; n++ {
+ for n := 0; obj.Shadow[name] || caller.lookup(name) != nil || newlyAdded(name) || name == "init"; n++ {
name = fmt.Sprintf("%s%d", base, n)
}
- // TODO(adonovan): don't use a renaming import
- // unless the local name differs from either
- // the package name or the last segment of path.
- // This requires that we tabulate (path, declared name, local name)
- // triples for each package referenced by the callee.
- logf("adding import %s %q", name, path)
- newImports = append(newImports, &ast.ImportSpec{
- Name: makeIdent(name),
+ logf("adding import %s %q", name, obj.PkgPath)
+ spec := &ast.ImportSpec{
Path: &ast.BasicLit{
Kind: token.STRING,
- Value: strconv.Quote(path),
+ Value: strconv.Quote(obj.PkgPath),
},
+ }
+ // Use explicit pkgname (out of necessity) when it differs from the declared name,
+ // or (for good style) when it differs from base(pkgpath).
+ if name != obj.PkgName || name != pathpkg.Base(obj.PkgPath) {
+ spec.Name = makeIdent(name)
+ }
+ newImports = append(newImports, newImport{
+ pkgName: name,
+ spec: spec,
})
- importMap[path] = append(importMap[path], name)
+ importMap[obj.PkgPath] = append(importMap[obj.PkgPath], name)
return name
}
@@ -471,8 +479,7 @@
var newName ast.Expr
if obj.Kind == "pkgname" {
// Use locally appropriate import, creating as needed.
- newName = makeIdent(localImportName(obj.PkgPath, obj.Shadow)) // imported package
-
+ newName = makeIdent(localImportName(&obj)) // imported package
} else if !obj.ValidPos {
// Built-in function, type, or value (e.g. nil, zero):
// check not shadowed at caller.
@@ -515,7 +522,7 @@
// Form a qualified identifier, pkg.Name.
if qualify {
- pkgName := localImportName(obj.PkgPath, obj.Shadow)
+ pkgName := localImportName(&obj)
newName = &ast.SelectorExpr{
X: makeIdent(pkgName),
Sel: makeIdent(obj.Name),
diff --git a/internal/refactor/inline/testdata/crosspkg.txtar b/internal/refactor/inline/testdata/crosspkg.txtar
index 7c0704b..e0744f9 100644
--- a/internal/refactor/inline/testdata/crosspkg.txtar
+++ b/internal/refactor/inline/testdata/crosspkg.txtar
@@ -22,22 +22,30 @@
fmt.Println()
b.B1() //@ inline(re"B1", b1result)
b.B2() //@ inline(re"B2", b2result)
+ b.B3() //@ inline(re"B3", b3result)
}
-- b/b.go --
package b
import "testdata/c"
+import "testdata/d"
import "fmt"
func B1() { c.C() }
func B2() { fmt.Println() }
+func B3() { e.E() } // (note that "testdata/d" points to package e)
-- c/c.go --
package c
func C() {}
+-- d/d.go --
+package e // <- this package name intentionally mismatches the path
+
+func E() {}
+
-- b1result --
package a
@@ -46,7 +54,7 @@
import (
"fmt"
"testdata/b"
- c "testdata/c"
+ "testdata/c"
)
// Nor this one.
@@ -55,6 +63,7 @@
fmt.Println()
c.C() //@ inline(re"B1", b1result)
b.B2() //@ inline(re"B2", b2result)
+ b.B3() //@ inline(re"B3", b3result)
}
-- b2result --
@@ -73,4 +82,24 @@
fmt.Println()
b.B1() //@ inline(re"B1", b1result)
fmt.Println() //@ inline(re"B2", b2result)
+ b.B3() //@ inline(re"B3", b3result)
+}
+-- b3result --
+package a
+
+// This comment does not migrate.
+
+import (
+ "fmt"
+ "testdata/b"
+ e "testdata/d"
+)
+
+// Nor this one.
+
+func A() {
+ fmt.Println()
+ b.B1() //@ inline(re"B1", b1result)
+ b.B2() //@ inline(re"B2", b2result)
+ e.E() //@ inline(re"B3", b3result)
}
diff --git a/internal/refactor/inline/testdata/dotimport.txtar b/internal/refactor/inline/testdata/dotimport.txtar
index 8ca5f05..644398b 100644
--- a/internal/refactor/inline/testdata/dotimport.txtar
+++ b/internal/refactor/inline/testdata/dotimport.txtar
@@ -29,7 +29,7 @@
package c
import (
- a "testdata/a"
+ "testdata/a"
)
func _() {
diff --git a/internal/refactor/inline/testdata/issue63298.txtar b/internal/refactor/inline/testdata/issue63298.txtar
index 990ebcd..cc556c9 100644
--- a/internal/refactor/inline/testdata/issue63298.txtar
+++ b/internal/refactor/inline/testdata/issue63298.txtar
@@ -38,7 +38,7 @@
package a
import (
- b "testdata/b"
+ "testdata/b"
b0 "testdata/another/b"
//@ inline(re"a2", result)
diff --git a/internal/refactor/inline/testdata/revdotimport.txtar b/internal/refactor/inline/testdata/revdotimport.txtar
index 3838793..f33304f 100644
--- a/internal/refactor/inline/testdata/revdotimport.txtar
+++ b/internal/refactor/inline/testdata/revdotimport.txtar
@@ -32,8 +32,8 @@
package c
import (
+ "testdata/a"
. "testdata/a"
- a "testdata/a"
)
func _() {
diff --git a/internal/testenv/testenv.go b/internal/testenv/testenv.go
index 4d29ebe..88de3da 100644
--- a/internal/testenv/testenv.go
+++ b/internal/testenv/testenv.go
@@ -447,3 +447,32 @@
t.Skipf("skipping test: %s module path is %q, not %q", modFilePath, modulePath, want)
}
}
+
+// NeedsGoExperiment skips t if the current process environment does not
+// have a GOEXPERIMENT flag set.
+func NeedsGoExperiment(t testing.TB, flag string) {
+ t.Helper()
+
+ goexp := os.Getenv("GOEXPERIMENT")
+ set := false
+ for _, f := range strings.Split(goexp, ",") {
+ if f == "" {
+ continue
+ }
+ if f == "none" {
+ // GOEXPERIMENT=none disables all experiment flags.
+ set = false
+ break
+ }
+ val := true
+ if strings.HasPrefix(f, "no") {
+ f, val = f[2:], false
+ }
+ if f == flag {
+ set = val
+ }
+ }
+ if !set {
+ t.Skipf("skipping test: flag %q is not set in GOEXPERIMENT=%q", flag, goexp)
+ }
+}