gopls/internal/lsp/safetoken: delete safetoken.Range

Now that its scope has been substantially reduced, there's
no need for this type, so this change expands it out to
a triple (or pair in some cases).
The defensive check in NewRange is never needed.

Alsoo, rename RangeToTokenRange to RangePos, following the convention.

Also, fix incorrect uses of the term "wire format".

Change-Id: I5bfc5bf8ce6c9504166cc928cad1df2df000737a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/464056
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Alan Donovan <adonovan@google.com>
diff --git a/gopls/doc/design/implementation.md b/gopls/doc/design/implementation.md
index a8f7f0b..859ec1c 100644
--- a/gopls/doc/design/implementation.md
+++ b/gopls/doc/design/implementation.md
@@ -29,7 +29,7 @@
 [internal/lsp/cache] | the cache layer
 [internal/lsp/cmd] | the gopls command line layer
 [internal/lsp/debug] | features to aid in debugging gopls
-[internal/lsp/protocol] | the lsp protocol layer and wire format
+[internal/lsp/protocol] | the types of LSP request and response messages
 [internal/lsp/source] | the core feature implementations
 [internal/span] | a package for dealing with source file locations
 [internal/memoize] | a function invocation cache used to reduce the work done
diff --git a/gopls/internal/lsp/analysis/fillstruct/fillstruct.go b/gopls/internal/lsp/analysis/fillstruct/fillstruct.go
index 1b33157..af29a36 100644
--- a/gopls/internal/lsp/analysis/fillstruct/fillstruct.go
+++ b/gopls/internal/lsp/analysis/fillstruct/fillstruct.go
@@ -135,12 +135,12 @@
 
 // SuggestedFix computes the suggested fix for the kinds of
 // diagnostics produced by the Analyzer above.
-func SuggestedFix(fset *token.FileSet, rng safetoken.Range, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
+func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
 	if info == nil {
 		return nil, fmt.Errorf("nil types.Info")
 	}
 
-	pos := rng.Start // don't use the end
+	pos := start // don't use the end
 
 	// TODO(rstambler): Using ast.Inspect would probably be more efficient than
 	// calling PathEnclosingInterval. Switch this approach.
@@ -205,7 +205,7 @@
 		}
 		fieldTyps = append(fieldTyps, field.Type())
 	}
-	matches := analysisinternal.MatchingIdents(fieldTyps, file, rng.Start, info, pkg)
+	matches := analysisinternal.MatchingIdents(fieldTyps, file, start, info, pkg)
 	var elts []ast.Expr
 	for i, fieldTyp := range fieldTyps {
 		if fieldTyp == nil {
diff --git a/gopls/internal/lsp/analysis/undeclaredname/undeclared.go b/gopls/internal/lsp/analysis/undeclaredname/undeclared.go
index 996bf2d..0439794 100644
--- a/gopls/internal/lsp/analysis/undeclaredname/undeclared.go
+++ b/gopls/internal/lsp/analysis/undeclaredname/undeclared.go
@@ -121,8 +121,8 @@
 	})
 }
 
-func SuggestedFix(fset *token.FileSet, rng safetoken.Range, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
-	pos := rng.Start // don't use the end
+func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
+	pos := start // don't use the end
 	path, _ := astutil.PathEnclosingInterval(file, pos, pos)
 	if len(path) < 2 {
 		return nil, fmt.Errorf("no expression found")
diff --git a/gopls/internal/lsp/code_action.go b/gopls/internal/lsp/code_action.go
index 58bb59e..ef9f921 100644
--- a/gopls/internal/lsp/code_action.go
+++ b/gopls/internal/lsp/code_action.go
@@ -320,13 +320,13 @@
 	if err != nil {
 		return nil, fmt.Errorf("getting file for Identifier: %w", err)
 	}
-	srng, err := pgf.RangeToTokenRange(rng)
+	start, end, err := pgf.RangePos(rng)
 	if err != nil {
 		return nil, err
 	}
 	puri := protocol.URIFromSpanURI(uri)
 	var commands []protocol.Command
-	if _, ok, methodOk, _ := source.CanExtractFunction(pgf.Tok, srng, pgf.Src, pgf.File); ok {
+	if _, ok, methodOk, _ := source.CanExtractFunction(pgf.Tok, start, end, pgf.Src, pgf.File); ok {
 		cmd, err := command.NewApplyFixCommand("Extract function", command.ApplyFixArgs{
 			URI:   puri,
 			Fix:   source.ExtractFunction,
@@ -348,7 +348,7 @@
 			commands = append(commands, cmd)
 		}
 	}
-	if _, _, ok, _ := source.CanExtractVariable(srng, pgf.File); ok {
+	if _, _, ok, _ := source.CanExtractVariable(start, end, pgf.File); ok {
 		cmd, err := command.NewApplyFixCommand("Extract variable", command.ApplyFixArgs{
 			URI:   puri,
 			Fix:   source.ExtractVariable,
diff --git a/gopls/internal/lsp/protocol/doc.go b/gopls/internal/lsp/protocol/doc.go
index 2ffdf51..4eb03a0 100644
--- a/gopls/internal/lsp/protocol/doc.go
+++ b/gopls/internal/lsp/protocol/doc.go
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Package protocol contains the structs that map directly to the wire format
-// of the "Language Server Protocol".
+// Package protocol contains the structs that map directly to the
+// request and response messages of the Language Server Protocol.
 //
 // It is a literal transcription, with unmodified comments, and only the changes
 // required to make it go code.
diff --git a/gopls/internal/lsp/protocol/mapper.go b/gopls/internal/lsp/protocol/mapper.go
index a0f1172..d61524d 100644
--- a/gopls/internal/lsp/protocol/mapper.go
+++ b/gopls/internal/lsp/protocol/mapper.go
@@ -18,14 +18,13 @@
 // 	token.Pos
 // 	token.FileSet
 // 	token.File
-// 	safetoken.Range = (file token.File, start, end token.Pos)
 //
 //    Because File.Offset and File.Pos panic on invalid inputs,
 //    we do not call them directly and instead use the safetoken package
 //    for these conversions. This is enforced by a static check.
 //
 //    Beware also that the methods of token.File have two bugs for which
-//    safetoken contain workarounds:
+//    safetoken contains workarounds:
 //    - #57490, whereby the parser may create ast.Nodes during error
 //      recovery whose computed positions are out of bounds (EOF+1).
 //    - #41029, whereby the wrong line number is returned for the EOF position.
@@ -45,7 +44,7 @@
 //    they are also useful for parsing user-provided positions (e.g. in
 //    the CLI) before we have access to file contents.
 //
-// 4. protocol, the LSP wire format.
+// 4. protocol, the LSP RPC message format.
 //
 //    protocol.Position = (Line, Character uint32)
 //    protocol.Range = (start, end Position)
diff --git a/gopls/internal/lsp/safetoken/range.go b/gopls/internal/lsp/safetoken/range.go
deleted file mode 100644
index f44b449..0000000
--- a/gopls/internal/lsp/safetoken/range.go
+++ /dev/null
@@ -1,62 +0,0 @@
-// 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.
-
-package safetoken
-
-import "go/token"
-
-// Range represents a source code range in token.Pos form.
-//
-// It also carries the token.File that produced the position,
-// so that it is capable of returning (file, line, col8) information.
-// However it cannot be converted to protocol (UTF-16) form
-// without access to file content; to do that, use a protocol.ContentMapper.
-//
-// TODO(adonovan): Eliminate most/all uses of Range in gopls, as
-// without a Mapper it's not really self-contained.  It is mostly used
-// by completion. Given access to complete.mapper, it could use a pair
-// of byte offsets instead.
-type Range struct {
-	TokFile    *token.File // non-nil
-	Start, End token.Pos   // both IsValid()
-}
-
-// NewRange creates a new Range from a token.File and two positions within it.
-// The given start position must be valid; if end is invalid, start is used as
-// the end position.
-//
-// (If you only have a token.FileSet, use file = fset.File(start). But
-// most callers know exactly which token.File they're dealing with and
-// should pass it explicitly. Not only does this save a lookup, but it
-// brings us a step closer to eliminating the global FileSet.)
-func NewRange(file *token.File, start, end token.Pos) Range {
-	if file == nil {
-		panic("nil *token.File")
-	}
-	if !start.IsValid() {
-		panic("invalid start token.Pos")
-	}
-	if !end.IsValid() {
-		end = start
-	}
-
-	// TODO(adonovan): ideally we would make this stronger assertion:
-	//
-	//   // Assert that file is non-nil and contains start and end.
-	//   _ = file.Offset(start)
-	//   _ = file.Offset(end)
-	//
-	// but some callers (e.g. packageCompletionSurrounding) don't ensure this precondition.
-
-	return Range{
-		TokFile: file,
-		Start:   start,
-		End:     end,
-	}
-}
-
-// IsPoint returns true if the range represents a single point.
-func (r Range) IsPoint() bool {
-	return r.Start == r.End
-}
diff --git a/gopls/internal/lsp/source/call_hierarchy.go b/gopls/internal/lsp/source/call_hierarchy.go
index 31fdf49..d9efcbe 100644
--- a/gopls/internal/lsp/source/call_hierarchy.go
+++ b/gopls/internal/lsp/source/call_hierarchy.go
@@ -119,7 +119,7 @@
 	if err != nil {
 		return protocol.CallHierarchyItem{}, err
 	}
-	srcRng, err := pgf.RangeToTokenRange(loc.Range)
+	start, end, err := pgf.RangePos(loc.Range)
 	if err != nil {
 		return protocol.CallHierarchyItem{}, err
 	}
@@ -128,7 +128,7 @@
 	var funcDecl *ast.FuncDecl
 	var funcLit *ast.FuncLit // innermost function literal
 	var litCount int
-	path, _ := astutil.PathEnclosingInterval(pgf.File, srcRng.Start, srcRng.End)
+	path, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
 outer:
 	for _, node := range path {
 		switch n := node.(type) {
diff --git a/gopls/internal/lsp/source/completion/completion.go b/gopls/internal/lsp/source/completion/completion.go
index f27190c..ad6e79f 100644
--- a/gopls/internal/lsp/source/completion/completion.go
+++ b/gopls/internal/lsp/source/completion/completion.go
@@ -24,7 +24,6 @@
 
 	"golang.org/x/tools/go/ast/astutil"
 	"golang.org/x/tools/gopls/internal/lsp/protocol"
-	"golang.org/x/tools/gopls/internal/lsp/safetoken"
 	"golang.org/x/tools/gopls/internal/lsp/snippet"
 	"golang.org/x/tools/gopls/internal/lsp/source"
 	"golang.org/x/tools/internal/event"
@@ -288,10 +287,10 @@
 
 // A Selection represents the cursor position and surrounding identifier.
 type Selection struct {
-	content string
-	cursor  token.Pos // relative to rng.TokFile
-	rng     safetoken.Range
-	mapper  *protocol.Mapper
+	content            string
+	tokFile            *token.File
+	start, end, cursor token.Pos // relative to rng.TokFile
+	mapper             *protocol.Mapper
 }
 
 func (p Selection) Content() string {
@@ -299,15 +298,15 @@
 }
 
 func (p Selection) Range() (protocol.Range, error) {
-	return p.mapper.PosRange(p.rng.TokFile, p.rng.Start, p.rng.End)
+	return p.mapper.PosRange(p.tokFile, p.start, p.end)
 }
 
 func (p Selection) Prefix() string {
-	return p.content[:p.cursor-p.rng.Start]
+	return p.content[:p.cursor-p.start]
 }
 
 func (p Selection) Suffix() string {
-	return p.content[p.cursor-p.rng.Start:]
+	return p.content[p.cursor-p.start:]
 }
 
 func (c *completer) setSurrounding(ident *ast.Ident) {
@@ -322,8 +321,10 @@
 		content: ident.Name,
 		cursor:  c.pos,
 		// Overwrite the prefix only.
-		rng:    safetoken.NewRange(c.tokFile, ident.Pos(), ident.End()),
-		mapper: c.mapper,
+		tokFile: c.tokFile,
+		start:   ident.Pos(),
+		end:     ident.End(),
+		mapper:  c.mapper,
 	}
 
 	c.setMatcherFromPrefix(c.surrounding.Prefix())
@@ -345,7 +346,9 @@
 		c.surrounding = &Selection{
 			content: "",
 			cursor:  c.pos,
-			rng:     safetoken.NewRange(c.tokFile, c.pos, c.pos),
+			tokFile: c.tokFile,
+			start:   c.pos,
+			end:     c.pos,
 			mapper:  c.mapper,
 		}
 	}
@@ -798,7 +801,9 @@
 	c.surrounding = &Selection{
 		content: content,
 		cursor:  c.pos,
-		rng:     safetoken.NewRange(c.tokFile, start, end),
+		tokFile: c.tokFile,
+		start:   start,
+		end:     end,
 		mapper:  c.mapper,
 	}
 
@@ -1019,7 +1024,9 @@
 	c.surrounding = &Selection{
 		content: cursorComment.Text[start:end],
 		cursor:  c.pos,
-		rng:     safetoken.NewRange(c.tokFile, token.Pos(int(cursorComment.Slash)+start), token.Pos(int(cursorComment.Slash)+end)),
+		tokFile: c.tokFile,
+		start:   token.Pos(int(cursorComment.Slash) + start),
+		end:     token.Pos(int(cursorComment.Slash) + end),
 		mapper:  c.mapper,
 	}
 	c.setMatcherFromPrefix(c.surrounding.Prefix())
diff --git a/gopls/internal/lsp/source/completion/definition.go b/gopls/internal/lsp/source/completion/definition.go
index 564d82c..a0160a1 100644
--- a/gopls/internal/lsp/source/completion/definition.go
+++ b/gopls/internal/lsp/source/completion/definition.go
@@ -12,7 +12,6 @@
 	"unicode/utf8"
 
 	"golang.org/x/tools/gopls/internal/lsp/protocol"
-	"golang.org/x/tools/gopls/internal/lsp/safetoken"
 	"golang.org/x/tools/gopls/internal/lsp/snippet"
 	"golang.org/x/tools/gopls/internal/lsp/source"
 )
@@ -40,7 +39,9 @@
 	sel := &Selection{
 		content: "",
 		cursor:  start,
-		rng:     safetoken.NewRange(pgf.Tok, start, end),
+		tokFile: pgf.Tok,
+		start:   start,
+		end:     end,
 		mapper:  pgf.Mapper,
 	}
 	var ans []CompletionItem
diff --git a/gopls/internal/lsp/source/completion/package.go b/gopls/internal/lsp/source/completion/package.go
index f6b5148..f3bc306 100644
--- a/gopls/internal/lsp/source/completion/package.go
+++ b/gopls/internal/lsp/source/completion/package.go
@@ -90,7 +90,9 @@
 			return &Selection{
 				content: name.Name,
 				cursor:  cursor,
-				rng:     safetoken.NewRange(tok, name.Pos(), name.End()),
+				tokFile: tok,
+				start:   name.Pos(),
+				end:     name.End(),
 				mapper:  m,
 			}, nil
 		}
@@ -128,7 +130,9 @@
 				return &Selection{
 					content: content,
 					cursor:  cursor,
-					rng:     safetoken.NewRange(tok, start, end),
+					tokFile: tok,
+					start:   start,
+					end:     end,
 					mapper:  m,
 				}, nil
 			}
@@ -149,8 +153,10 @@
 	// The surrounding range in this case is the cursor.
 	return &Selection{
 		content: "",
+		tokFile: tok,
+		start:   cursor,
+		end:     cursor,
 		cursor:  cursor,
-		rng:     safetoken.NewRange(tok, cursor, cursor),
 		mapper:  m,
 	}, nil
 }
diff --git a/gopls/internal/lsp/source/extract.go b/gopls/internal/lsp/source/extract.go
index 6f3c780..56e8a5e 100644
--- a/gopls/internal/lsp/source/extract.go
+++ b/gopls/internal/lsp/source/extract.go
@@ -23,11 +23,11 @@
 	"golang.org/x/tools/internal/bug"
 )
 
-func extractVariable(fset *token.FileSet, rng safetoken.Range, src []byte, file *ast.File, _ *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
+func extractVariable(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, _ *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
 	tokFile := fset.File(file.Pos())
-	expr, path, ok, err := CanExtractVariable(rng, file)
+	expr, path, ok, err := CanExtractVariable(start, end, file)
 	if !ok {
-		return nil, fmt.Errorf("extractVariable: cannot extract %s: %v", safetoken.StartPosition(fset, rng.Start), err)
+		return nil, fmt.Errorf("extractVariable: cannot extract %s: %v", safetoken.StartPosition(fset, start), err)
 	}
 
 	// Create new AST node for extracted code.
@@ -88,8 +88,8 @@
 				NewText: []byte(assignment),
 			},
 			{
-				Pos:     rng.Start,
-				End:     rng.End,
+				Pos:     start,
+				End:     end,
 				NewText: []byte(lhs),
 			},
 		},
@@ -98,11 +98,11 @@
 
 // CanExtractVariable reports whether the code in the given range can be
 // extracted to a variable.
-func CanExtractVariable(rng safetoken.Range, file *ast.File) (ast.Expr, []ast.Node, bool, error) {
-	if rng.Start == rng.End {
+func CanExtractVariable(start, end token.Pos, file *ast.File) (ast.Expr, []ast.Node, bool, error) {
+	if start == end {
 		return nil, nil, false, fmt.Errorf("start and end are equal")
 	}
-	path, _ := astutil.PathEnclosingInterval(file, rng.Start, rng.End)
+	path, _ := astutil.PathEnclosingInterval(file, start, end)
 	if len(path) == 0 {
 		return nil, nil, false, fmt.Errorf("no path enclosing interval")
 	}
@@ -112,7 +112,7 @@
 		}
 	}
 	node := path[0]
-	if rng.Start != node.Pos() || rng.End != node.End() {
+	if start != node.Pos() || end != node.End() {
 		return nil, nil, false, fmt.Errorf("range does not map to an AST node")
 	}
 	expr, ok := node.(ast.Expr)
@@ -189,13 +189,13 @@
 }
 
 // extractMethod refactors the selected block of code into a new method.
-func extractMethod(fset *token.FileSet, rng safetoken.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
-	return extractFunctionMethod(fset, rng, src, file, pkg, info, true)
+func extractMethod(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
+	return extractFunctionMethod(fset, start, end, src, file, pkg, info, true)
 }
 
 // extractFunction refactors the selected block of code into a new function.
-func extractFunction(fset *token.FileSet, rng safetoken.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
-	return extractFunctionMethod(fset, rng, src, file, pkg, info, false)
+func extractFunction(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
+	return extractFunctionMethod(fset, start, end, src, file, pkg, info, false)
 }
 
 // extractFunctionMethod refactors the selected block of code into a new function/method.
@@ -206,7 +206,7 @@
 // and return values of the extracted function/method. Lastly, we construct the call
 // of the function/method and insert this call as well as the extracted function/method into
 // their proper locations.
-func extractFunctionMethod(fset *token.FileSet, rng safetoken.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info, isMethod bool) (*analysis.SuggestedFix, error) {
+func extractFunctionMethod(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info, isMethod bool) (*analysis.SuggestedFix, error) {
 	errorPrefix := "extractFunction"
 	if isMethod {
 		errorPrefix = "extractMethod"
@@ -216,12 +216,12 @@
 	if tok == nil {
 		return nil, bug.Errorf("no file for position")
 	}
-	p, ok, methodOk, err := CanExtractFunction(tok, rng, src, file)
+	p, ok, methodOk, err := CanExtractFunction(tok, start, end, src, file)
 	if (!ok && !isMethod) || (!methodOk && isMethod) {
 		return nil, fmt.Errorf("%s: cannot extract %s: %v", errorPrefix,
-			safetoken.StartPosition(fset, rng.Start), err)
+			safetoken.StartPosition(fset, start), err)
 	}
-	tok, path, rng, outer, start := p.tok, p.path, p.rng, p.outer, p.start
+	tok, path, start, end, outer, node := p.tok, p.path, p.start, p.end, p.outer, p.node
 	fileScope := info.Scopes[file]
 	if fileScope == nil {
 		return nil, fmt.Errorf("%s: file scope is empty", errorPrefix)
@@ -236,13 +236,13 @@
 	// non-nested return statements are guaranteed to execute.
 	var retStmts []*ast.ReturnStmt
 	var hasNonNestedReturn bool
-	startParent := findParent(outer, start)
+	startParent := findParent(outer, node)
 	ast.Inspect(outer, func(n ast.Node) bool {
 		if n == nil {
 			return false
 		}
-		if n.Pos() < rng.Start || n.End() > rng.End {
-			return n.Pos() <= rng.End
+		if n.Pos() < start || n.End() > end {
+			return n.Pos() <= end
 		}
 		ret, ok := n.(*ast.ReturnStmt)
 		if !ok {
@@ -260,7 +260,7 @@
 	// we must determine the signature of the extracted function. We will then replace
 	// the block with an assignment statement that calls the extracted function with
 	// the appropriate parameters and return values.
-	variables, err := collectFreeVars(info, file, fileScope, pkgScope, rng, path[0])
+	variables, err := collectFreeVars(info, file, fileScope, pkgScope, start, end, path[0])
 	if err != nil {
 		return nil, err
 	}
@@ -343,7 +343,7 @@
 		if v.obj.Parent() == nil {
 			return nil, fmt.Errorf("parent nil")
 		}
-		isUsed, firstUseAfter := objUsed(info, safetoken.NewRange(tok, rng.End, v.obj.Parent().End()), v.obj)
+		isUsed, firstUseAfter := objUsed(info, end, v.obj.Parent().End(), v.obj)
 		if v.assigned && isUsed && !varOverridden(info, firstUseAfter, v.obj, v.free, outer) {
 			returnTypes = append(returnTypes, &ast.Field{Type: typ})
 			returns = append(returns, identifier)
@@ -400,7 +400,7 @@
 
 	// We put the selection in a constructed file. We can then traverse and edit
 	// the extracted selection without modifying the original AST.
-	startOffset, endOffset, err := safetoken.Offsets(tok, rng.Start, rng.End)
+	startOffset, endOffset, err := safetoken.Offsets(tok, start, end)
 	if err != nil {
 		return nil, err
 	}
@@ -499,7 +499,7 @@
 		// statements in the selection. Update the type signature of the extracted
 		// function and construct the if statement that will be inserted in the enclosing
 		// function.
-		retVars, ifReturn, err = generateReturnInfo(enclosing, pkg, path, file, info, fset, rng.Start, hasNonNestedReturn)
+		retVars, ifReturn, err = generateReturnInfo(enclosing, pkg, path, file, info, fset, start, hasNonNestedReturn)
 		if err != nil {
 			return nil, err
 		}
@@ -534,7 +534,7 @@
 		funName = name
 	} else {
 		name = "newFunction"
-		funName, _ = generateAvailableIdentifier(rng.Start, file, path, info, name, 0)
+		funName, _ = generateAvailableIdentifier(start, file, path, info, name, 0)
 	}
 	extractedFunCall := generateFuncCall(hasNonNestedReturn, hasReturnValues, params,
 		append(returns, getNames(retVars)...), funName, sym, receiverName)
@@ -587,7 +587,7 @@
 	// Find all the comments within the range and print them to be put somewhere.
 	// TODO(suzmue): print these in the extracted function at the correct place.
 	for _, cg := range file.Comments {
-		if cg.Pos().IsValid() && cg.Pos() < rng.End && cg.Pos() >= rng.Start {
+		if cg.Pos().IsValid() && cg.Pos() < end && cg.Pos() >= start {
 			for _, c := range cg.List {
 				fmt.Fprintln(&commentBuf, c.Text)
 			}
@@ -602,7 +602,7 @@
 	}
 	before := src[outerStart:startOffset]
 	after := src[endOffset:outerEnd]
-	indent, err := calculateIndentation(src, tok, start)
+	indent, err := calculateIndentation(src, tok, node)
 	if err != nil {
 		return nil, err
 	}
@@ -652,12 +652,12 @@
 // their cursors for whitespace. To support this use case, we must manually adjust the
 // ranges to match the correct AST node. In this particular example, we would adjust
 // rng.Start forward to the start of 'if' and rng.End backward to after '}'.
-func adjustRangeForCommentsAndWhiteSpace(rng safetoken.Range, tok *token.File, content []byte, file *ast.File) (safetoken.Range, error) {
+func adjustRangeForCommentsAndWhiteSpace(tok *token.File, start, end token.Pos, content []byte, file *ast.File) (token.Pos, token.Pos, error) {
 	// Adjust the end of the range to after leading whitespace and comments.
-	prevStart, start := token.NoPos, rng.Start
+	prevStart := token.NoPos
 	startComment := sort.Search(len(file.Comments), func(i int) bool {
 		// Find the index for the first comment that ends after range start.
-		return file.Comments[i].End() > rng.Start
+		return file.Comments[i].End() > start
 	})
 	for prevStart != start {
 		prevStart = start
@@ -670,7 +670,7 @@
 		// Move forwards to find a non-whitespace character.
 		offset, err := safetoken.Offset(tok, start)
 		if err != nil {
-			return safetoken.Range{}, err
+			return 0, 0, err
 		}
 		for offset < len(content) && isGoWhiteSpace(content[offset]) {
 			offset++
@@ -679,10 +679,10 @@
 	}
 
 	// Adjust the end of the range to before trailing whitespace and comments.
-	prevEnd, end := token.NoPos, rng.End
+	prevEnd := token.NoPos
 	endComment := sort.Search(len(file.Comments), func(i int) bool {
 		// Find the index for the first comment that ends after the range end.
-		return file.Comments[i].End() >= rng.End
+		return file.Comments[i].End() >= end
 	})
 	// Search will return n if not found, so we need to adjust if there are no
 	// comments that would match.
@@ -700,7 +700,7 @@
 		// Move backwards to find a non-whitespace character.
 		offset, err := safetoken.Offset(tok, end)
 		if err != nil {
-			return safetoken.Range{}, err
+			return 0, 0, err
 		}
 		for offset > 0 && isGoWhiteSpace(content[offset-1]) {
 			offset--
@@ -708,7 +708,7 @@
 		end = tok.Pos(offset)
 	}
 
-	return safetoken.NewRange(tok, start, end), nil
+	return start, end, nil
 }
 
 // isGoWhiteSpace returns true if b is a considered white space in
@@ -752,7 +752,7 @@
 // variables will be used as arguments in the extracted function. It also returns a
 // list of identifiers that may need to be returned by the extracted function.
 // Some of the code in this function has been adapted from tools/cmd/guru/freevars.go.
-func collectFreeVars(info *types.Info, file *ast.File, fileScope, pkgScope *types.Scope, rng safetoken.Range, node ast.Node) ([]*variable, error) {
+func collectFreeVars(info *types.Info, file *ast.File, fileScope, pkgScope *types.Scope, start, end token.Pos, node ast.Node) ([]*variable, error) {
 	// id returns non-nil if n denotes an object that is referenced by the span
 	// and defined either within the span or in the lexical environment. The bool
 	// return value acts as an indicator for where it was defined.
@@ -777,7 +777,7 @@
 		if scope == fileScope || scope == pkgScope {
 			return nil, false // defined at file or package scope
 		}
-		if rng.Start <= obj.Pos() && obj.Pos() <= rng.End {
+		if start <= obj.Pos() && obj.Pos() <= end {
 			return obj, false // defined within selection => not free
 		}
 		return obj, true
@@ -802,7 +802,7 @@
 		if n == nil {
 			return false
 		}
-		if rng.Start <= n.Pos() && n.End() <= rng.End {
+		if start <= n.Pos() && n.End() <= end {
 			var obj types.Object
 			var isFree, prune bool
 			switch n := n.(type) {
@@ -828,7 +828,7 @@
 				}
 			}
 		}
-		return n.Pos() <= rng.End
+		return n.Pos() <= end
 	})
 
 	// Find identifiers that are initialized or whose values are altered at some
@@ -845,8 +845,8 @@
 		if n == nil {
 			return false
 		}
-		if n.Pos() < rng.Start || n.End() > rng.End {
-			return n.Pos() <= rng.End
+		if n.Pos() < start || n.End() > end {
+			return n.Pos() <= end
 		}
 		switch n := n.(type) {
 		case *ast.AssignStmt:
@@ -959,25 +959,25 @@
 }
 
 type fnExtractParams struct {
-	tok   *token.File
-	path  []ast.Node
-	rng   safetoken.Range
-	outer *ast.FuncDecl
-	start ast.Node
+	tok        *token.File
+	start, end token.Pos
+	path       []ast.Node
+	outer      *ast.FuncDecl
+	node       ast.Node
 }
 
 // CanExtractFunction reports whether the code in the given range can be
 // extracted to a function.
-func CanExtractFunction(tok *token.File, rng safetoken.Range, src []byte, file *ast.File) (*fnExtractParams, bool, bool, error) {
-	if rng.Start == rng.End {
+func CanExtractFunction(tok *token.File, start, end token.Pos, src []byte, file *ast.File) (*fnExtractParams, bool, bool, error) {
+	if start == end {
 		return nil, false, false, fmt.Errorf("start and end are equal")
 	}
 	var err error
-	rng, err = adjustRangeForCommentsAndWhiteSpace(rng, tok, src, file)
+	start, end, err = adjustRangeForCommentsAndWhiteSpace(tok, start, end, src, file)
 	if err != nil {
 		return nil, false, false, err
 	}
-	path, _ := astutil.PathEnclosingInterval(file, rng.Start, rng.End)
+	path, _ := astutil.PathEnclosingInterval(file, start, end)
 	if len(path) == 0 {
 		return nil, false, false, fmt.Errorf("no path enclosing interval")
 	}
@@ -1001,52 +1001,53 @@
 	}
 
 	// Find the nodes at the start and end of the selection.
-	var start, end ast.Node
+	var startNode, endNode ast.Node
 	ast.Inspect(outer, func(n ast.Node) bool {
 		if n == nil {
 			return false
 		}
 		// Do not override 'start' with a node that begins at the same location
 		// but is nested further from 'outer'.
-		if start == nil && n.Pos() == rng.Start && n.End() <= rng.End {
-			start = n
+		if startNode == nil && n.Pos() == start && n.End() <= end {
+			startNode = n
 		}
-		if end == nil && n.End() == rng.End && n.Pos() >= rng.Start {
-			end = n
+		if endNode == nil && n.End() == end && n.Pos() >= start {
+			endNode = n
 		}
-		return n.Pos() <= rng.End
+		return n.Pos() <= end
 	})
-	if start == nil || end == nil {
+	if startNode == nil || endNode == nil {
 		return nil, false, false, fmt.Errorf("range does not map to AST nodes")
 	}
 	// If the region is a blockStmt, use the first and last nodes in the block
 	// statement.
 	// <rng.start>{ ... }<rng.end> => { <rng.start>...<rng.end> }
-	if blockStmt, ok := start.(*ast.BlockStmt); ok {
+	if blockStmt, ok := startNode.(*ast.BlockStmt); ok {
 		if len(blockStmt.List) == 0 {
 			return nil, false, false, fmt.Errorf("range maps to empty block statement")
 		}
-		start, end = blockStmt.List[0], blockStmt.List[len(blockStmt.List)-1]
-		rng.Start, rng.End = start.Pos(), end.End()
+		startNode, endNode = blockStmt.List[0], blockStmt.List[len(blockStmt.List)-1]
+		start, end = startNode.Pos(), endNode.End()
 	}
 	return &fnExtractParams{
 		tok:   tok,
-		path:  path,
-		rng:   rng,
-		outer: outer,
 		start: start,
+		end:   end,
+		path:  path,
+		outer: outer,
+		node:  startNode,
 	}, true, outer.Recv != nil, nil
 }
 
 // objUsed checks if the object is used within the range. It returns the first
 // occurrence of the object in the range, if it exists.
-func objUsed(info *types.Info, rng safetoken.Range, obj types.Object) (bool, *ast.Ident) {
+func objUsed(info *types.Info, start, end token.Pos, obj types.Object) (bool, *ast.Ident) {
 	var firstUse *ast.Ident
 	for id, objUse := range info.Uses {
 		if obj != objUse {
 			continue
 		}
-		if id.Pos() < rng.Start || id.End() > rng.End {
+		if id.Pos() < start || id.End() > end {
 			continue
 		}
 		if firstUse == nil || id.Pos() < firstUse.Pos() {
diff --git a/gopls/internal/lsp/source/fix.go b/gopls/internal/lsp/source/fix.go
index 49bce04..d5eca76 100644
--- a/gopls/internal/lsp/source/fix.go
+++ b/gopls/internal/lsp/source/fix.go
@@ -15,7 +15,6 @@
 	"golang.org/x/tools/gopls/internal/lsp/analysis/fillstruct"
 	"golang.org/x/tools/gopls/internal/lsp/analysis/undeclaredname"
 	"golang.org/x/tools/gopls/internal/lsp/protocol"
-	"golang.org/x/tools/gopls/internal/lsp/safetoken"
 	"golang.org/x/tools/gopls/internal/span"
 	"golang.org/x/tools/internal/bug"
 )
@@ -28,7 +27,7 @@
 	// separately. Such analyzers should provide a function with a signature of
 	// SuggestedFixFunc.
 	SuggestedFixFunc  func(ctx context.Context, snapshot Snapshot, fh FileHandle, pRng protocol.Range) (*token.FileSet, *analysis.SuggestedFix, error)
-	singleFileFixFunc func(fset *token.FileSet, rng safetoken.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error)
+	singleFileFixFunc func(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error)
 )
 
 const (
@@ -57,11 +56,11 @@
 		if err != nil {
 			return nil, nil, err
 		}
-		rng, err := pgf.RangeToTokenRange(pRng)
+		start, end, err := pgf.RangePos(pRng)
 		if err != nil {
 			return nil, nil, err
 		}
-		fix, err := sf(pkg.FileSet(), rng, pgf.Src, pgf.File, pkg.GetTypes(), pkg.GetTypesInfo())
+		fix, err := sf(pkg.FileSet(), start, end, pgf.Src, pgf.File, pkg.GetTypes(), pkg.GetTypesInfo())
 		return pkg.FileSet(), fix, err
 	}
 }
diff --git a/gopls/internal/lsp/source/inlay_hint.go b/gopls/internal/lsp/source/inlay_hint.go
index da2e31a..c5520d9 100644
--- a/gopls/internal/lsp/source/inlay_hint.go
+++ b/gopls/internal/lsp/source/inlay_hint.go
@@ -109,11 +109,11 @@
 	start, end := pgf.File.Pos(), pgf.File.End()
 	if pRng.Start.Line < pRng.End.Line || pRng.Start.Character < pRng.End.Character {
 		// Adjust start and end for the specified range.
-		rng, err := pgf.RangeToTokenRange(pRng)
+		var err error
+		start, end, err = pgf.RangePos(pRng)
 		if err != nil {
 			return nil, err
 		}
-		start, end = rng.Start, rng.End
 	}
 
 	var hints []protocol.InlayHint
diff --git a/gopls/internal/lsp/source/stub.go b/gopls/internal/lsp/source/stub.go
index c3f2661..5f6fe14 100644
--- a/gopls/internal/lsp/source/stub.go
+++ b/gopls/internal/lsp/source/stub.go
@@ -218,12 +218,12 @@
 }
 
 func getStubNodes(pgf *ParsedGoFile, pRng protocol.Range) ([]ast.Node, token.Pos, error) {
-	rng, err := pgf.RangeToTokenRange(pRng)
+	start, end, err := pgf.RangePos(pRng)
 	if err != nil {
 		return nil, 0, err
 	}
-	nodes, _ := astutil.PathEnclosingInterval(pgf.File, rng.Start, rng.End)
-	return nodes, rng.Start, nil
+	nodes, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
+	return nodes, start, nil
 }
 
 /*
diff --git a/gopls/internal/lsp/source/view.go b/gopls/internal/lsp/source/view.go
index 2227c5c..8798e1b 100644
--- a/gopls/internal/lsp/source/view.go
+++ b/gopls/internal/lsp/source/view.go
@@ -419,13 +419,13 @@
 	return pgf.Mapper.PosLocation(pgf.Tok, node.Pos(), node.End())
 }
 
-// RangeToTokenRange parses a protocol Range back into the go/token domain.
-func (pgf *ParsedGoFile) RangeToTokenRange(r protocol.Range) (safetoken.Range, error) {
+// RangePos parses a protocol Range back into the go/token domain.
+func (pgf *ParsedGoFile) RangePos(r protocol.Range) (token.Pos, token.Pos, error) {
 	start, end, err := pgf.Mapper.RangeOffsets(r)
 	if err != nil {
-		return safetoken.Range{}, err
+		return token.NoPos, token.NoPos, err
 	}
-	return safetoken.NewRange(pgf.Tok, pgf.Tok.Pos(start), pgf.Tok.Pos(end)), nil
+	return pgf.Tok.Pos(start), pgf.Tok.Pos(end), nil
 }
 
 // A ParsedModule contains the results of parsing a go.mod file.