Skip to content

Commit 5c8715f

Browse files
committed
cmd/gofmt, go/format, go/printer: move number normalization to printer
Normalization of number prefixes and exponents was added in CL 160184 directly in cmd/gofmt. The same behavior change needs to be applied in the go/format package. This is done by moving the normalization code into go/printer, behind a new StdFormat mode, which is then re-used by both cmd/gofmt and go/format. Note that formatting of Go source code changes over time, so the exact byte output produced by go/printer may change between versions of Go when using StdFormat mode. What is guaranteed is that the new formatting is equivalent Go code. Clients looking to format Go code with standard formatting consistent with cmd/gofmt and go/format would need to start using this flag, but a better alternative is to use the go/format package instead. Benchstat numbers on go test go/printer -bench=BenchmarkPrint: name old time/op new time/op delta Print-8 4.56ms ± 1% 4.57ms ± 0% ~ (p=0.700 n=3+3) name old alloc/op new alloc/op delta Print-8 467kB ± 0% 467kB ± 0% ~ (p=1.000 n=3+3) name old allocs/op new allocs/op delta Print-8 17.2k ± 0% 17.2k ± 0% ~ (all equal) That benchmark data doesn't contain any numbers that need to be normalized. More work needs to be performed when formatting Go code with numbers, but it is unavoidable to produce standard formatting. Fixes #37476. For #37453. Change-Id: If50bde4035c3ee6e6ff0ece5691f6d3566ffe8d5 Reviewed-on: https://go-review.googlesource.com/c/go/+/231461 Run-TryBot: Dmitri Shuralyov <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent cb00d93 commit 5c8715f

File tree

10 files changed

+672
-58
lines changed

10 files changed

+672
-58
lines changed

src/cmd/gofmt/gofmt.go

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ var (
3737
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file")
3838
)
3939

40+
// Keep these in sync with go/format/format.go.
4041
const (
4142
tabWidth = 8
42-
printerMode = printer.UseSpaces | printer.TabIndent
43+
printerMode = printer.UseSpaces | printer.TabIndent | printer.StdFormat
4344
)
4445

4546
var (
@@ -113,8 +114,6 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error
113114
simplify(file)
114115
}
115116

116-
ast.Inspect(file, normalizeNumbers)
117-
118117
res, err := format(fileSet, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth})
119118
if err != nil {
120119
return err
@@ -294,56 +293,3 @@ func backupFile(filename string, data []byte, perm os.FileMode) (string, error)
294293

295294
return bakname, err
296295
}
297-
298-
// normalizeNumbers rewrites base prefixes and exponents to
299-
// use lower-case letters, and removes leading 0's from
300-
// integer imaginary literals. It leaves hexadecimal digits
301-
// alone.
302-
func normalizeNumbers(n ast.Node) bool {
303-
lit, _ := n.(*ast.BasicLit)
304-
if lit == nil || (lit.Kind != token.INT && lit.Kind != token.FLOAT && lit.Kind != token.IMAG) {
305-
return true
306-
}
307-
if len(lit.Value) < 2 {
308-
return false // only one digit (common case) - nothing to do
309-
}
310-
// len(lit.Value) >= 2
311-
312-
// We ignore lit.Kind because for lit.Kind == token.IMAG the literal may be an integer
313-
// or floating-point value, decimal or not. Instead, just consider the literal pattern.
314-
x := lit.Value
315-
switch x[:2] {
316-
default:
317-
// 0-prefix octal, decimal int, or float (possibly with 'i' suffix)
318-
if i := strings.LastIndexByte(x, 'E'); i >= 0 {
319-
x = x[:i] + "e" + x[i+1:]
320-
break
321-
}
322-
// remove leading 0's from integer (but not floating-point) imaginary literals
323-
if x[len(x)-1] == 'i' && strings.IndexByte(x, '.') < 0 && strings.IndexByte(x, 'e') < 0 {
324-
x = strings.TrimLeft(x, "0_")
325-
if x == "i" {
326-
x = "0i"
327-
}
328-
}
329-
case "0X":
330-
x = "0x" + x[2:]
331-
fallthrough
332-
case "0x":
333-
// possibly a hexadecimal float
334-
if i := strings.LastIndexByte(x, 'P'); i >= 0 {
335-
x = x[:i] + "p" + x[i+1:]
336-
}
337-
case "0O":
338-
x = "0o" + x[2:]
339-
case "0o":
340-
// nothing to do
341-
case "0B":
342-
x = "0b" + x[2:]
343-
case "0b":
344-
// nothing to do
345-
}
346-
347-
lit.Value = x
348-
return false
349-
}

src/go/format/format.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ import (
2424
"io"
2525
)
2626

27-
var config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
27+
// Keep these in sync with cmd/gofmt/gofmt.go.
28+
const (
29+
tabWidth = 8
30+
printerMode = printer.UseSpaces | printer.TabIndent | printer.StdFormat
31+
)
32+
33+
var config = printer.Config{Mode: printerMode, Tabwidth: tabWidth}
2834

2935
const parserMode = parser.ParseComments
3036

src/go/format/format_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package format
66

77
import (
88
"bytes"
9+
"go/ast"
910
"go/parser"
1011
"go/token"
1112
"io/ioutil"
@@ -57,6 +58,43 @@ func TestNode(t *testing.T) {
5758
diff(t, buf.Bytes(), src)
5859
}
5960

61+
// Node is documented to not modify the AST. Test that it is so, even when
62+
// formatting changes are applied due to printer.StdFormat mode being used.
63+
func TestNodeNoModify(t *testing.T) {
64+
const (
65+
src = "package p\n\nconst _ = 0000000123i\n"
66+
golden = "package p\n\nconst _ = 123i\n"
67+
)
68+
69+
fset := token.NewFileSet()
70+
file, err := parser.ParseFile(fset, "", src, parser.ParseComments)
71+
if err != nil {
72+
t.Fatal(err)
73+
}
74+
75+
// Capture original address and value of a BasicLit node
76+
// which will undergo formatting changes during printing.
77+
wantLit := file.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Values[0].(*ast.BasicLit)
78+
wantVal := wantLit.Value
79+
80+
var buf bytes.Buffer
81+
if err = Node(&buf, fset, file); err != nil {
82+
t.Fatal("Node failed:", err)
83+
}
84+
diff(t, buf.Bytes(), []byte(golden))
85+
86+
// Check if anything changed after Node returned.
87+
gotLit := file.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Values[0].(*ast.BasicLit)
88+
gotVal := gotLit.Value
89+
90+
if gotLit != wantLit {
91+
t.Errorf("got *ast.BasicLit address %p, want %p", gotLit, wantLit)
92+
}
93+
if gotVal != wantVal {
94+
t.Errorf("got *ast.BasicLit value %q, want %q", gotVal, wantVal)
95+
}
96+
}
97+
6098
func TestSource(t *testing.T) {
6199
src, err := ioutil.ReadFile(testfile)
62100
if err != nil {

src/go/printer/nodes.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,9 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
791791
}
792792

793793
case *ast.BasicLit:
794+
if p.Config.Mode&StdFormat != 0 {
795+
x = normalizeNumbers(x)
796+
}
794797
p.print(x)
795798

796799
case *ast.FuncLit:
@@ -971,6 +974,62 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {
971974
}
972975
}
973976

977+
// normalizeNumbers rewrites base prefixes and exponents to
978+
// use lower-case letters, and removes leading 0's from
979+
// integer imaginary literals. It leaves hexadecimal digits
980+
// alone.
981+
func normalizeNumbers(lit *ast.BasicLit) *ast.BasicLit {
982+
if lit.Kind != token.INT && lit.Kind != token.FLOAT && lit.Kind != token.IMAG {
983+
return lit // not a number - nothing to do
984+
}
985+
if len(lit.Value) < 2 {
986+
return lit // only one digit (common case) - nothing to do
987+
}
988+
// len(lit.Value) >= 2
989+
990+
// We ignore lit.Kind because for lit.Kind == token.IMAG the literal may be an integer
991+
// or floating-point value, decimal or not. Instead, just consider the literal pattern.
992+
x := lit.Value
993+
switch x[:2] {
994+
default:
995+
// 0-prefix octal, decimal int, or float (possibly with 'i' suffix)
996+
if i := strings.LastIndexByte(x, 'E'); i >= 0 {
997+
x = x[:i] + "e" + x[i+1:]
998+
break
999+
}
1000+
// remove leading 0's from integer (but not floating-point) imaginary literals
1001+
if x[len(x)-1] == 'i' && strings.IndexByte(x, '.') < 0 && strings.IndexByte(x, 'e') < 0 {
1002+
x = strings.TrimLeft(x, "0_")
1003+
if x == "i" {
1004+
x = "0i"
1005+
}
1006+
}
1007+
case "0X":
1008+
x = "0x" + x[2:]
1009+
// possibly a hexadecimal float
1010+
if i := strings.LastIndexByte(x, 'P'); i >= 0 {
1011+
x = x[:i] + "p" + x[i+1:]
1012+
}
1013+
case "0x":
1014+
// possibly a hexadecimal float
1015+
i := strings.LastIndexByte(x, 'P')
1016+
if i == -1 {
1017+
return lit // nothing to do
1018+
}
1019+
x = x[:i] + "p" + x[i+1:]
1020+
case "0O":
1021+
x = "0o" + x[2:]
1022+
case "0o":
1023+
return lit // nothing to do
1024+
case "0B":
1025+
x = "0b" + x[2:]
1026+
case "0b":
1027+
return lit // nothing to do
1028+
}
1029+
1030+
return &ast.BasicLit{ValuePos: lit.ValuePos, Kind: lit.Kind, Value: x}
1031+
}
1032+
9741033
func (p *printer) possibleSelectorExpr(expr ast.Expr, prec1, depth int) bool {
9751034
if x, ok := expr.(*ast.SelectorExpr); ok {
9761035
return p.selectorExpr(x, depth, true)

src/go/printer/performance_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
var testfile *ast.File
2121

2222
func testprint(out io.Writer, file *ast.File) {
23-
if err := (&Config{TabIndent | UseSpaces, 8, 0}).Fprint(out, fset, file); err != nil {
23+
if err := (&Config{TabIndent | UseSpaces | StdFormat, 8, 0}).Fprint(out, fset, file); err != nil {
2424
log.Fatalf("print error: %s", err)
2525
}
2626
}

src/go/printer/printer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,7 @@ const (
12761276
TabIndent // use tabs for indentation independent of UseSpaces
12771277
UseSpaces // use spaces instead of tabs for alignment
12781278
SourcePos // emit //line directives to preserve original source positions
1279+
StdFormat // apply standard formatting changes (exact byte output may change between versions of Go)
12791280
)
12801281

12811282
// A Config node controls the output of Fprint.

src/go/printer/printer_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type checkMode uint
3333
const (
3434
export checkMode = 1 << iota
3535
rawFormat
36+
stdFormat
3637
idempotent
3738
)
3839

@@ -57,6 +58,9 @@ func format(src []byte, mode checkMode) ([]byte, error) {
5758
if mode&rawFormat != 0 {
5859
cfg.Mode |= RawFormat
5960
}
61+
if mode&stdFormat != 0 {
62+
cfg.Mode |= StdFormat
63+
}
6064

6165
// print AST
6266
var buf bytes.Buffer
@@ -200,6 +204,8 @@ var data = []entry{
200204
{"statements.input", "statements.golden", 0},
201205
{"slow.input", "slow.golden", idempotent},
202206
{"complit.input", "complit.x", export},
207+
{"go2numbers.input", "go2numbers.golden", idempotent},
208+
{"go2numbers.input", "go2numbers.stdfmt", stdFormat | idempotent},
203209
}
204210

205211
func TestFiles(t *testing.T) {

0 commit comments

Comments
 (0)