|
2 | 2 | // Use of this source code is governed by a BSD-style
|
3 | 3 | // license that can be found in the LICENSE file.
|
4 | 4 |
|
5 |
| -// Package safetoken provides wrappers around methods in go/token, that return |
6 |
| -// errors rather than panicking. |
| 5 | +// Package safetoken provides wrappers around methods in go/token, |
| 6 | +// that return errors rather than panicking. It also provides a |
| 7 | +// central place for workarounds in the underlying packages. |
7 | 8 | package safetoken
|
8 | 9 |
|
9 | 10 | import (
|
10 | 11 | "fmt"
|
11 | 12 | "go/token"
|
12 | 13 | )
|
13 | 14 |
|
14 |
| -// Offset returns f.Offset(pos), but first checks that the pos is in range |
15 |
| -// for the given file. |
| 15 | +// Offset returns f.Offset(pos), but first checks that the file |
| 16 | +// contains the pos. |
| 17 | +// |
| 18 | +// The definition of "contains" here differs from that of token.File |
| 19 | +// in order to work around a bug in the parser (issue #57490): during |
| 20 | +// error recovery, the parser may create syntax nodes whose computed |
| 21 | +// End position is 1 byte beyond EOF, which would cause |
| 22 | +// token.File.Offset to panic. The workaround is that this function |
| 23 | +// accepts a Pos that is exactly 1 byte beyond EOF and maps it to the |
| 24 | +// EOF offset. |
| 25 | +// |
| 26 | +// The use of this function instead of (*token.File).Offset is |
| 27 | +// mandatory in the gopls codebase; this is enforced by static check. |
16 | 28 | func Offset(f *token.File, pos token.Pos) (int, error) {
|
17 |
| - if !InRange(f, pos) { |
| 29 | + if !inRange(f, pos) { |
| 30 | + // Accept a Pos that is 1 byte beyond EOF, |
| 31 | + // and map it to the EOF offset. |
| 32 | + // (Workaround for #57490.) |
| 33 | + if int(pos) == f.Base()+f.Size()+1 { |
| 34 | + return f.Size(), nil |
| 35 | + } |
| 36 | + |
18 | 37 | return -1, fmt.Errorf("pos %d is not in range [%d:%d] of file %s",
|
19 | 38 | pos, f.Base(), f.Base()+f.Size(), f.Name())
|
20 | 39 | }
|
21 | 40 | return int(pos) - f.Base(), nil
|
22 | 41 | }
|
23 | 42 |
|
24 |
| -// Pos returns f.Pos(offset), but first checks that the offset is valid for |
25 |
| -// the given file. |
| 43 | +// Pos returns f.Pos(offset), but first checks that the offset is |
| 44 | +// non-negative and not larger than the size of the file. |
26 | 45 | func Pos(f *token.File, offset int) (token.Pos, error) {
|
27 | 46 | if !(0 <= offset && offset <= f.Size()) {
|
28 | 47 | return token.NoPos, fmt.Errorf("offset %d is not in range for file %s of size %d", offset, f.Name(), f.Size())
|
29 | 48 | }
|
30 | 49 | return token.Pos(f.Base() + offset), nil
|
31 | 50 | }
|
32 | 51 |
|
33 |
| -// InRange reports whether file f contains position pos. |
34 |
| -func InRange(f *token.File, pos token.Pos) bool { |
| 52 | +// inRange reports whether file f contains position pos, |
| 53 | +// according to the invariants of token.File. |
| 54 | +// |
| 55 | +// This function is not public because of the ambiguity it would |
| 56 | +// create w.r.t. the definition of "contains". Use Offset instead. |
| 57 | +func inRange(f *token.File, pos token.Pos) bool { |
35 | 58 | return token.Pos(f.Base()) <= pos && pos <= token.Pos(f.Base()+f.Size())
|
36 | 59 | }
|
0 commit comments