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