From 73df946d56c74384511a194dd01dbe099584fd1a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 15:14:23 +0200 Subject: Adding upstream version 1.16.10. Signed-off-by: Daniel Baumann --- src/go/ast/ast.go | 1063 ++++++++ src/go/ast/ast_test.go | 79 + src/go/ast/commentmap.go | 332 +++ src/go/ast/commentmap_test.go | 170 ++ src/go/ast/example_test.go | 206 ++ src/go/ast/filter.go | 497 ++++ src/go/ast/filter_test.go | 86 + src/go/ast/import.go | 230 ++ src/go/ast/issues_test.go | 42 + src/go/ast/print.go | 254 ++ src/go/ast/print_test.go | 97 + src/go/ast/resolve.go | 174 ++ src/go/ast/scope.go | 160 ++ src/go/ast/walk.go | 386 +++ src/go/build/build.go | 1986 +++++++++++++++ src/go/build/build_test.go | 712 ++++++ src/go/build/constraint/expr.go | 574 +++++ src/go/build/constraint/expr_test.go | 317 +++ src/go/build/deps_test.go | 877 +++++++ src/go/build/doc.go | 98 + src/go/build/gc.go | 17 + src/go/build/gccgo.go | 14 + src/go/build/read.go | 546 +++++ src/go/build/read_test.go | 321 +++ src/go/build/syslist.go | 11 + src/go/build/syslist_test.go | 62 + src/go/build/testdata/cgo_disabled/cgo_disabled.go | 5 + src/go/build/testdata/cgo_disabled/empty.go | 1 + src/go/build/testdata/doc/a_test.go | 2 + src/go/build/testdata/doc/b_test.go | 1 + src/go/build/testdata/doc/c_test.go | 1 + src/go/build/testdata/doc/d_test.go | 2 + src/go/build/testdata/doc/e.go | 1 + src/go/build/testdata/doc/f.go | 2 + src/go/build/testdata/empty/dummy | 0 src/go/build/testdata/multi/file.go | 5 + src/go/build/testdata/multi/file_appengine.go | 5 + src/go/build/testdata/other/file/file.go | 5 + src/go/build/testdata/other/main.go | 11 + src/go/build/testdata/withvendor/src/a/b/b.go | 3 + .../testdata/withvendor/src/a/vendor/c/d/d.go | 1 + src/go/constant/example_test.go | 180 ++ src/go/constant/value.go | 1391 +++++++++++ src/go/constant/value_test.go | 708 ++++++ src/go/doc/Makefile | 7 + src/go/doc/comment.go | 513 ++++ src/go/doc/comment_test.go | 247 ++ src/go/doc/doc.go | 222 ++ src/go/doc/doc_test.go | 164 ++ src/go/doc/example.go | 547 +++++ src/go/doc/example_test.go | 718 ++++++ src/go/doc/exports.go | 309 +++ src/go/doc/filter.go | 105 + src/go/doc/headscan.go | 115 + src/go/doc/reader.go | 917 +++++++ src/go/doc/synopsis.go | 83 + src/go/doc/synopsis_test.go | 52 + src/go/doc/testdata/a.0.golden | 52 + src/go/doc/testdata/a.1.golden | 52 + src/go/doc/testdata/a.2.golden | 52 + src/go/doc/testdata/a0.go | 40 + src/go/doc/testdata/a1.go | 12 + src/go/doc/testdata/b.0.golden | 71 + src/go/doc/testdata/b.1.golden | 83 + src/go/doc/testdata/b.2.golden | 71 + src/go/doc/testdata/b.go | 58 + src/go/doc/testdata/benchmark.go | 293 +++ src/go/doc/testdata/blank.0.golden | 62 + src/go/doc/testdata/blank.1.golden | 83 + src/go/doc/testdata/blank.2.golden | 62 + src/go/doc/testdata/blank.go | 75 + src/go/doc/testdata/bugpara.0.golden | 20 + src/go/doc/testdata/bugpara.1.golden | 20 + src/go/doc/testdata/bugpara.2.golden | 20 + src/go/doc/testdata/bugpara.go | 9 + src/go/doc/testdata/c.0.golden | 48 + src/go/doc/testdata/c.1.golden | 48 + src/go/doc/testdata/c.2.golden | 48 + src/go/doc/testdata/c.go | 62 + src/go/doc/testdata/d.0.golden | 104 + src/go/doc/testdata/d.1.golden | 104 + src/go/doc/testdata/d.2.golden | 104 + src/go/doc/testdata/d1.go | 57 + src/go/doc/testdata/d2.go | 45 + src/go/doc/testdata/e.0.golden | 109 + src/go/doc/testdata/e.1.golden | 144 ++ src/go/doc/testdata/e.2.golden | 130 + src/go/doc/testdata/e.go | 147 ++ src/go/doc/testdata/error1.0.golden | 30 + src/go/doc/testdata/error1.1.golden | 32 + src/go/doc/testdata/error1.2.golden | 30 + src/go/doc/testdata/error1.go | 24 + src/go/doc/testdata/error2.0.golden | 27 + src/go/doc/testdata/error2.1.golden | 37 + src/go/doc/testdata/error2.2.golden | 27 + src/go/doc/testdata/error2.go | 29 + src/go/doc/testdata/example.go | 81 + src/go/doc/testdata/f.0.golden | 13 + src/go/doc/testdata/f.1.golden | 16 + src/go/doc/testdata/f.2.golden | 13 + src/go/doc/testdata/f.go | 14 + src/go/doc/testdata/g.0.golden | 32 + src/go/doc/testdata/g.1.golden | 34 + src/go/doc/testdata/g.2.golden | 32 + src/go/doc/testdata/g.go | 25 + src/go/doc/testdata/issue12839.0.golden | 51 + src/go/doc/testdata/issue12839.1.golden | 54 + src/go/doc/testdata/issue12839.2.golden | 51 + src/go/doc/testdata/issue12839.go | 69 + src/go/doc/testdata/issue13742.0.golden | 25 + src/go/doc/testdata/issue13742.1.golden | 25 + src/go/doc/testdata/issue13742.2.golden | 25 + src/go/doc/testdata/issue13742.go | 18 + src/go/doc/testdata/issue16153.0.golden | 32 + src/go/doc/testdata/issue16153.1.golden | 34 + src/go/doc/testdata/issue16153.2.golden | 32 + src/go/doc/testdata/issue16153.go | 27 + src/go/doc/testdata/issue17788.0.golden | 8 + src/go/doc/testdata/issue17788.1.golden | 8 + src/go/doc/testdata/issue17788.2.golden | 8 + src/go/doc/testdata/issue17788.go | 8 + src/go/doc/testdata/issue22856.0.golden | 45 + src/go/doc/testdata/issue22856.1.golden | 45 + src/go/doc/testdata/issue22856.2.golden | 45 + src/go/doc/testdata/issue22856.go | 27 + src/go/doc/testdata/predeclared.0.golden | 8 + src/go/doc/testdata/predeclared.1.golden | 22 + src/go/doc/testdata/predeclared.2.golden | 8 + src/go/doc/testdata/predeclared.go | 22 + src/go/doc/testdata/template.txt | 68 + src/go/doc/testdata/testing.0.golden | 156 ++ src/go/doc/testdata/testing.1.golden | 298 +++ src/go/doc/testdata/testing.2.golden | 156 ++ src/go/doc/testdata/testing.go | 404 +++ src/go/format/benchmark_test.go | 91 + src/go/format/example_test.go | 39 + src/go/format/format.go | 135 + src/go/format/format_test.go | 183 ++ src/go/format/internal.go | 176 ++ src/go/importer/importer.go | 122 + src/go/importer/importer_test.go | 87 + src/go/internal/gccgoimporter/ar.go | 171 ++ src/go/internal/gccgoimporter/gccgoinstallation.go | 97 + .../gccgoimporter/gccgoinstallation_test.go | 192 ++ src/go/internal/gccgoimporter/importer.go | 261 ++ src/go/internal/gccgoimporter/importer_test.go | 209 ++ src/go/internal/gccgoimporter/parser.go | 1284 ++++++++++ src/go/internal/gccgoimporter/parser_test.go | 78 + src/go/internal/gccgoimporter/testdata/aliases.go | 65 + src/go/internal/gccgoimporter/testdata/aliases.gox | 33 + .../internal/gccgoimporter/testdata/complexnums.go | 6 + .../gccgoimporter/testdata/complexnums.gox | 8 + .../internal/gccgoimporter/testdata/conversions.go | 5 + .../gccgoimporter/testdata/conversions.gox | 6 + .../internal/gccgoimporter/testdata/escapeinfo.go | 13 + .../internal/gccgoimporter/testdata/escapeinfo.gox | Bin 0 -> 824 bytes src/go/internal/gccgoimporter/testdata/imports.go | 5 + src/go/internal/gccgoimporter/testdata/imports.gox | 7 + .../internal/gccgoimporter/testdata/issue27856.go | 9 + .../internal/gccgoimporter/testdata/issue27856.gox | 9 + .../internal/gccgoimporter/testdata/issue29198.go | 37 + .../internal/gccgoimporter/testdata/issue29198.gox | 86 + .../internal/gccgoimporter/testdata/issue30628.go | 18 + .../internal/gccgoimporter/testdata/issue30628.gox | 28 + .../internal/gccgoimporter/testdata/issue31540.go | 26 + .../internal/gccgoimporter/testdata/issue31540.gox | 16 + .../internal/gccgoimporter/testdata/issue34182.go | 17 + .../internal/gccgoimporter/testdata/issue34182.gox | 13 + .../internal/gccgoimporter/testdata/libimportsar.a | Bin 0 -> 9302 bytes .../internal/gccgoimporter/testdata/nointerface.go | 12 + .../gccgoimporter/testdata/nointerface.gox | 8 + .../internal/gccgoimporter/testdata/notinheap.go | 4 + .../internal/gccgoimporter/testdata/notinheap.gox | 7 + src/go/internal/gccgoimporter/testdata/pointer.go | 3 + src/go/internal/gccgoimporter/testdata/pointer.gox | 4 + src/go/internal/gccgoimporter/testdata/time.gox | Bin 0 -> 7977 bytes src/go/internal/gccgoimporter/testdata/unicode.gox | Bin 0 -> 7945 bytes .../internal/gccgoimporter/testdata/v1reflect.gox | Bin 0 -> 10872 bytes src/go/internal/gcimporter/exportdata.go | 91 + src/go/internal/gcimporter/gcimporter.go | 174 ++ src/go/internal/gcimporter/gcimporter_test.go | 619 +++++ src/go/internal/gcimporter/iimport.go | 618 +++++ src/go/internal/gcimporter/support.go | 130 + src/go/internal/gcimporter/testdata/a.go | 14 + src/go/internal/gcimporter/testdata/b.go | 11 + src/go/internal/gcimporter/testdata/exports.go | 88 + src/go/internal/gcimporter/testdata/issue15920.go | 11 + src/go/internal/gcimporter/testdata/issue20046.go | 9 + src/go/internal/gcimporter/testdata/issue25301.go | 17 + src/go/internal/gcimporter/testdata/issue25596.go | 13 + src/go/internal/gcimporter/testdata/p.go | 13 + .../internal/gcimporter/testdata/versions/test.go | 28 + .../gcimporter/testdata/versions/test_go1.11_0i.a | Bin 0 -> 2420 bytes .../gcimporter/testdata/versions/test_go1.11_6b.a | Bin 0 -> 2426 bytes .../testdata/versions/test_go1.11_999b.a | Bin 0 -> 2600 bytes .../testdata/versions/test_go1.11_999i.a | Bin 0 -> 2420 bytes .../gcimporter/testdata/versions/test_go1.7_0.a | Bin 0 -> 1862 bytes .../gcimporter/testdata/versions/test_go1.7_1.a | Bin 0 -> 2316 bytes .../gcimporter/testdata/versions/test_go1.8_4.a | Bin 0 -> 1658 bytes .../gcimporter/testdata/versions/test_go1.8_5.a | Bin 0 -> 1658 bytes src/go/internal/srcimporter/srcimporter.go | 265 ++ src/go/internal/srcimporter/srcimporter_test.go | 254 ++ .../srcimporter/testdata/issue20855/issue20855.go | 7 + .../srcimporter/testdata/issue23092/issue23092.go | 5 + .../srcimporter/testdata/issue24392/issue24392.go | 5 + src/go/parser/error_test.go | 187 ++ src/go/parser/example_test.go | 43 + src/go/parser/interface.go | 241 ++ src/go/parser/parser.go | 2591 ++++++++++++++++++++ src/go/parser/parser_test.go | 579 +++++ src/go/parser/performance_test.go | 30 + src/go/parser/short_test.go | 136 + src/go/parser/testdata/commas.src | 19 + src/go/parser/testdata/issue11377.src | 27 + src/go/parser/testdata/issue23434.src | 25 + src/go/parser/testdata/issue3106.src | 46 + src/go/parser/testdata/issue34946.src | 22 + .../testdata/issue42951/not_a_file.go/invalid.go | 1 + src/go/printer/example_test.go | 67 + src/go/printer/nodes.go | 1844 ++++++++++++++ src/go/printer/performance_test.go | 58 + src/go/printer/printer.go | 1377 +++++++++++ src/go/printer/printer_test.go | 814 ++++++ src/go/printer/testdata/alignment.golden | 172 ++ src/go/printer/testdata/alignment.input | 179 ++ src/go/printer/testdata/comments.golden | 759 ++++++ src/go/printer/testdata/comments.input | 756 ++++++ src/go/printer/testdata/comments.x | 56 + src/go/printer/testdata/comments2.golden | 164 ++ src/go/printer/testdata/comments2.input | 168 ++ src/go/printer/testdata/complit.input | 65 + src/go/printer/testdata/complit.x | 62 + src/go/printer/testdata/declarations.golden | 1008 ++++++++ src/go/printer/testdata/declarations.input | 1021 ++++++++ src/go/printer/testdata/empty.golden | 5 + src/go/printer/testdata/empty.input | 5 + src/go/printer/testdata/expressions.golden | 743 ++++++ src/go/printer/testdata/expressions.input | 771 ++++++ src/go/printer/testdata/expressions.raw | 743 ++++++ src/go/printer/testdata/go2numbers.golden | 186 ++ src/go/printer/testdata/go2numbers.input | 186 ++ src/go/printer/testdata/go2numbers.norm | 186 ++ src/go/printer/testdata/linebreaks.golden | 295 +++ src/go/printer/testdata/linebreaks.input | 291 +++ src/go/printer/testdata/parser.go | 2153 ++++++++++++++++ src/go/printer/testdata/slow.golden | 85 + src/go/printer/testdata/slow.input | 85 + src/go/printer/testdata/statements.golden | 644 +++++ src/go/printer/testdata/statements.input | 555 +++++ src/go/scanner/errors.go | 124 + src/go/scanner/example_test.go | 46 + src/go/scanner/scanner.go | 940 +++++++ src/go/scanner/scanner_test.go | 1113 +++++++++ src/go/token/example_test.go | 77 + src/go/token/position.go | 552 +++++ src/go/token/position_test.go | 341 +++ src/go/token/serialize.go | 71 + src/go/token/serialize_test.go | 111 + src/go/token/token.go | 340 +++ src/go/token/token_test.go | 33 + src/go/types/api.go | 408 +++ src/go/types/api_test.go | 1514 ++++++++++++ src/go/types/assignments.go | 357 +++ src/go/types/builtins.go | 697 ++++++ src/go/types/builtins_test.go | 220 ++ src/go/types/call.go | 569 +++++ src/go/types/check.go | 417 ++++ src/go/types/check_test.go | 349 +++ src/go/types/conversions.go | 163 ++ src/go/types/decl.go | 855 +++++++ src/go/types/errorcodes.go | 1366 +++++++++++ src/go/types/errorcodes_test.go | 197 ++ src/go/types/errors.go | 227 ++ src/go/types/eval.go | 99 + src/go/types/eval_test.go | 297 +++ src/go/types/example_test.go | 334 +++ src/go/types/expr.go | 1709 +++++++++++++ src/go/types/exprstring.go | 224 ++ src/go/types/exprstring_test.go | 94 + src/go/types/fixedbugs/issue20583.src | 14 + src/go/types/fixedbugs/issue23203a.src | 14 + src/go/types/fixedbugs/issue23203b.src | 14 + src/go/types/fixedbugs/issue26390.src | 13 + src/go/types/fixedbugs/issue28251.src | 65 + src/go/types/fixedbugs/issue42695.src | 17 + src/go/types/fixedbugs/issue43110.src | 43 + src/go/types/fixedbugs/issue43124.src | 16 + src/go/types/fixedbugs/issue6977.src | 82 + src/go/types/gccgosizes.go | 40 + src/go/types/gotype.go | 354 +++ src/go/types/hilbert_test.go | 222 ++ src/go/types/initorder.go | 297 +++ src/go/types/issues_test.go | 551 +++++ src/go/types/labels.go | 272 ++ src/go/types/lookup.go | 400 +++ src/go/types/methodset.go | 260 ++ src/go/types/object.go | 486 ++++ src/go/types/object_test.go | 86 + src/go/types/objset.go | 31 + src/go/types/operand.go | 283 +++ src/go/types/package.go | 65 + src/go/types/predicates.go | 340 +++ src/go/types/resolver.go | 614 +++++ src/go/types/resolver_test.go | 216 ++ src/go/types/return.go | 184 ++ src/go/types/scope.go | 182 ++ src/go/types/selection.go | 143 ++ src/go/types/self_test.go | 121 + src/go/types/sizes.go | 263 ++ src/go/types/sizes_test.go | 112 + src/go/types/stdlib_test.go | 326 +++ src/go/types/stmt.go | 881 +++++++ src/go/types/testdata/blank.src | 5 + src/go/types/testdata/builtins.src | 902 +++++++ src/go/types/testdata/const0.src | 350 +++ src/go/types/testdata/const1.src | 322 +++ src/go/types/testdata/constdecl.src | 141 ++ src/go/types/testdata/conversions.src | 93 + src/go/types/testdata/conversions2.src | 313 +++ src/go/types/testdata/cycles.src | 174 ++ src/go/types/testdata/cycles1.src | 77 + src/go/types/testdata/cycles2.src | 98 + src/go/types/testdata/cycles3.src | 60 + src/go/types/testdata/cycles4.src | 110 + src/go/types/testdata/cycles5.src | 200 ++ src/go/types/testdata/decls0.src | 206 ++ src/go/types/testdata/decls1.src | 146 ++ src/go/types/testdata/decls2a.src | 111 + src/go/types/testdata/decls2b.src | 75 + src/go/types/testdata/decls3.src | 309 +++ src/go/types/testdata/decls4.src | 199 ++ src/go/types/testdata/decls5.src | 10 + src/go/types/testdata/errors.src | 60 + src/go/types/testdata/expr0.src | 180 ++ src/go/types/testdata/expr1.src | 127 + src/go/types/testdata/expr2.src | 260 ++ src/go/types/testdata/expr3.src | 563 +++++ src/go/types/testdata/gotos.src | 560 +++++ src/go/types/testdata/importC.src | 54 + src/go/types/testdata/importdecl0a.src | 53 + src/go/types/testdata/importdecl0b.src | 33 + src/go/types/testdata/importdecl1a.src | 22 + src/go/types/testdata/importdecl1b.src | 11 + src/go/types/testdata/init0.src | 106 + src/go/types/testdata/init1.src | 97 + src/go/types/testdata/init2.src | 139 ++ src/go/types/testdata/issue25008a.src | 15 + src/go/types/testdata/issue25008b.src | 9 + src/go/types/testdata/issues.src | 365 +++ src/go/types/testdata/labels.src | 207 ++ src/go/types/testdata/literals.src | 111 + src/go/types/testdata/methodsets.src | 214 ++ src/go/types/testdata/shifts.src | 395 +++ src/go/types/testdata/stmt0.src | 980 ++++++++ src/go/types/testdata/stmt1.src | 259 ++ src/go/types/testdata/vardecl.src | 206 ++ src/go/types/token_test.go | 47 + src/go/types/type.go | 523 ++++ src/go/types/typestring.go | 307 +++ src/go/types/typestring_test.go | 221 ++ src/go/types/typexpr.go | 764 ++++++ src/go/types/universe.go | 238 ++ 362 files changed, 77464 insertions(+) create mode 100644 src/go/ast/ast.go create mode 100644 src/go/ast/ast_test.go create mode 100644 src/go/ast/commentmap.go create mode 100644 src/go/ast/commentmap_test.go create mode 100644 src/go/ast/example_test.go create mode 100644 src/go/ast/filter.go create mode 100644 src/go/ast/filter_test.go create mode 100644 src/go/ast/import.go create mode 100644 src/go/ast/issues_test.go create mode 100644 src/go/ast/print.go create mode 100644 src/go/ast/print_test.go create mode 100644 src/go/ast/resolve.go create mode 100644 src/go/ast/scope.go create mode 100644 src/go/ast/walk.go create mode 100644 src/go/build/build.go create mode 100644 src/go/build/build_test.go create mode 100644 src/go/build/constraint/expr.go create mode 100644 src/go/build/constraint/expr_test.go create mode 100644 src/go/build/deps_test.go create mode 100644 src/go/build/doc.go create mode 100644 src/go/build/gc.go create mode 100644 src/go/build/gccgo.go create mode 100644 src/go/build/read.go create mode 100644 src/go/build/read_test.go create mode 100644 src/go/build/syslist.go create mode 100644 src/go/build/syslist_test.go create mode 100644 src/go/build/testdata/cgo_disabled/cgo_disabled.go create mode 100644 src/go/build/testdata/cgo_disabled/empty.go create mode 100644 src/go/build/testdata/doc/a_test.go create mode 100644 src/go/build/testdata/doc/b_test.go create mode 100644 src/go/build/testdata/doc/c_test.go create mode 100644 src/go/build/testdata/doc/d_test.go create mode 100644 src/go/build/testdata/doc/e.go create mode 100644 src/go/build/testdata/doc/f.go create mode 100644 src/go/build/testdata/empty/dummy create mode 100644 src/go/build/testdata/multi/file.go create mode 100644 src/go/build/testdata/multi/file_appengine.go create mode 100644 src/go/build/testdata/other/file/file.go create mode 100644 src/go/build/testdata/other/main.go create mode 100644 src/go/build/testdata/withvendor/src/a/b/b.go create mode 100644 src/go/build/testdata/withvendor/src/a/vendor/c/d/d.go create mode 100644 src/go/constant/example_test.go create mode 100644 src/go/constant/value.go create mode 100644 src/go/constant/value_test.go create mode 100644 src/go/doc/Makefile create mode 100644 src/go/doc/comment.go create mode 100644 src/go/doc/comment_test.go create mode 100644 src/go/doc/doc.go create mode 100644 src/go/doc/doc_test.go create mode 100644 src/go/doc/example.go create mode 100644 src/go/doc/example_test.go create mode 100644 src/go/doc/exports.go create mode 100644 src/go/doc/filter.go create mode 100644 src/go/doc/headscan.go create mode 100644 src/go/doc/reader.go create mode 100644 src/go/doc/synopsis.go create mode 100644 src/go/doc/synopsis_test.go create mode 100644 src/go/doc/testdata/a.0.golden create mode 100644 src/go/doc/testdata/a.1.golden create mode 100644 src/go/doc/testdata/a.2.golden create mode 100644 src/go/doc/testdata/a0.go create mode 100644 src/go/doc/testdata/a1.go create mode 100644 src/go/doc/testdata/b.0.golden create mode 100644 src/go/doc/testdata/b.1.golden create mode 100644 src/go/doc/testdata/b.2.golden create mode 100644 src/go/doc/testdata/b.go create mode 100644 src/go/doc/testdata/benchmark.go create mode 100644 src/go/doc/testdata/blank.0.golden create mode 100644 src/go/doc/testdata/blank.1.golden create mode 100644 src/go/doc/testdata/blank.2.golden create mode 100644 src/go/doc/testdata/blank.go create mode 100644 src/go/doc/testdata/bugpara.0.golden create mode 100644 src/go/doc/testdata/bugpara.1.golden create mode 100644 src/go/doc/testdata/bugpara.2.golden create mode 100644 src/go/doc/testdata/bugpara.go create mode 100644 src/go/doc/testdata/c.0.golden create mode 100644 src/go/doc/testdata/c.1.golden create mode 100644 src/go/doc/testdata/c.2.golden create mode 100644 src/go/doc/testdata/c.go create mode 100644 src/go/doc/testdata/d.0.golden create mode 100644 src/go/doc/testdata/d.1.golden create mode 100644 src/go/doc/testdata/d.2.golden create mode 100644 src/go/doc/testdata/d1.go create mode 100644 src/go/doc/testdata/d2.go create mode 100644 src/go/doc/testdata/e.0.golden create mode 100644 src/go/doc/testdata/e.1.golden create mode 100644 src/go/doc/testdata/e.2.golden create mode 100644 src/go/doc/testdata/e.go create mode 100644 src/go/doc/testdata/error1.0.golden create mode 100644 src/go/doc/testdata/error1.1.golden create mode 100644 src/go/doc/testdata/error1.2.golden create mode 100644 src/go/doc/testdata/error1.go create mode 100644 src/go/doc/testdata/error2.0.golden create mode 100644 src/go/doc/testdata/error2.1.golden create mode 100644 src/go/doc/testdata/error2.2.golden create mode 100644 src/go/doc/testdata/error2.go create mode 100644 src/go/doc/testdata/example.go create mode 100644 src/go/doc/testdata/f.0.golden create mode 100644 src/go/doc/testdata/f.1.golden create mode 100644 src/go/doc/testdata/f.2.golden create mode 100644 src/go/doc/testdata/f.go create mode 100644 src/go/doc/testdata/g.0.golden create mode 100644 src/go/doc/testdata/g.1.golden create mode 100644 src/go/doc/testdata/g.2.golden create mode 100644 src/go/doc/testdata/g.go create mode 100644 src/go/doc/testdata/issue12839.0.golden create mode 100644 src/go/doc/testdata/issue12839.1.golden create mode 100644 src/go/doc/testdata/issue12839.2.golden create mode 100644 src/go/doc/testdata/issue12839.go create mode 100644 src/go/doc/testdata/issue13742.0.golden create mode 100644 src/go/doc/testdata/issue13742.1.golden create mode 100644 src/go/doc/testdata/issue13742.2.golden create mode 100644 src/go/doc/testdata/issue13742.go create mode 100644 src/go/doc/testdata/issue16153.0.golden create mode 100644 src/go/doc/testdata/issue16153.1.golden create mode 100644 src/go/doc/testdata/issue16153.2.golden create mode 100644 src/go/doc/testdata/issue16153.go create mode 100644 src/go/doc/testdata/issue17788.0.golden create mode 100644 src/go/doc/testdata/issue17788.1.golden create mode 100644 src/go/doc/testdata/issue17788.2.golden create mode 100644 src/go/doc/testdata/issue17788.go create mode 100644 src/go/doc/testdata/issue22856.0.golden create mode 100644 src/go/doc/testdata/issue22856.1.golden create mode 100644 src/go/doc/testdata/issue22856.2.golden create mode 100644 src/go/doc/testdata/issue22856.go create mode 100644 src/go/doc/testdata/predeclared.0.golden create mode 100644 src/go/doc/testdata/predeclared.1.golden create mode 100644 src/go/doc/testdata/predeclared.2.golden create mode 100644 src/go/doc/testdata/predeclared.go create mode 100644 src/go/doc/testdata/template.txt create mode 100644 src/go/doc/testdata/testing.0.golden create mode 100644 src/go/doc/testdata/testing.1.golden create mode 100644 src/go/doc/testdata/testing.2.golden create mode 100644 src/go/doc/testdata/testing.go create mode 100644 src/go/format/benchmark_test.go create mode 100644 src/go/format/example_test.go create mode 100644 src/go/format/format.go create mode 100644 src/go/format/format_test.go create mode 100644 src/go/format/internal.go create mode 100644 src/go/importer/importer.go create mode 100644 src/go/importer/importer_test.go create mode 100644 src/go/internal/gccgoimporter/ar.go create mode 100644 src/go/internal/gccgoimporter/gccgoinstallation.go create mode 100644 src/go/internal/gccgoimporter/gccgoinstallation_test.go create mode 100644 src/go/internal/gccgoimporter/importer.go create mode 100644 src/go/internal/gccgoimporter/importer_test.go create mode 100644 src/go/internal/gccgoimporter/parser.go create mode 100644 src/go/internal/gccgoimporter/parser_test.go create mode 100644 src/go/internal/gccgoimporter/testdata/aliases.go create mode 100644 src/go/internal/gccgoimporter/testdata/aliases.gox create mode 100644 src/go/internal/gccgoimporter/testdata/complexnums.go create mode 100644 src/go/internal/gccgoimporter/testdata/complexnums.gox create mode 100644 src/go/internal/gccgoimporter/testdata/conversions.go create mode 100644 src/go/internal/gccgoimporter/testdata/conversions.gox create mode 100644 src/go/internal/gccgoimporter/testdata/escapeinfo.go create mode 100644 src/go/internal/gccgoimporter/testdata/escapeinfo.gox create mode 100644 src/go/internal/gccgoimporter/testdata/imports.go create mode 100644 src/go/internal/gccgoimporter/testdata/imports.gox create mode 100644 src/go/internal/gccgoimporter/testdata/issue27856.go create mode 100644 src/go/internal/gccgoimporter/testdata/issue27856.gox create mode 100644 src/go/internal/gccgoimporter/testdata/issue29198.go create mode 100644 src/go/internal/gccgoimporter/testdata/issue29198.gox create mode 100644 src/go/internal/gccgoimporter/testdata/issue30628.go create mode 100644 src/go/internal/gccgoimporter/testdata/issue30628.gox create mode 100644 src/go/internal/gccgoimporter/testdata/issue31540.go create mode 100644 src/go/internal/gccgoimporter/testdata/issue31540.gox create mode 100644 src/go/internal/gccgoimporter/testdata/issue34182.go create mode 100644 src/go/internal/gccgoimporter/testdata/issue34182.gox create mode 100644 src/go/internal/gccgoimporter/testdata/libimportsar.a create mode 100644 src/go/internal/gccgoimporter/testdata/nointerface.go create mode 100644 src/go/internal/gccgoimporter/testdata/nointerface.gox create mode 100644 src/go/internal/gccgoimporter/testdata/notinheap.go create mode 100644 src/go/internal/gccgoimporter/testdata/notinheap.gox create mode 100644 src/go/internal/gccgoimporter/testdata/pointer.go create mode 100644 src/go/internal/gccgoimporter/testdata/pointer.gox create mode 100644 src/go/internal/gccgoimporter/testdata/time.gox create mode 100644 src/go/internal/gccgoimporter/testdata/unicode.gox create mode 100644 src/go/internal/gccgoimporter/testdata/v1reflect.gox create mode 100644 src/go/internal/gcimporter/exportdata.go create mode 100644 src/go/internal/gcimporter/gcimporter.go create mode 100644 src/go/internal/gcimporter/gcimporter_test.go create mode 100644 src/go/internal/gcimporter/iimport.go create mode 100644 src/go/internal/gcimporter/support.go create mode 100644 src/go/internal/gcimporter/testdata/a.go create mode 100644 src/go/internal/gcimporter/testdata/b.go create mode 100644 src/go/internal/gcimporter/testdata/exports.go create mode 100644 src/go/internal/gcimporter/testdata/issue15920.go create mode 100644 src/go/internal/gcimporter/testdata/issue20046.go create mode 100644 src/go/internal/gcimporter/testdata/issue25301.go create mode 100644 src/go/internal/gcimporter/testdata/issue25596.go create mode 100644 src/go/internal/gcimporter/testdata/p.go create mode 100644 src/go/internal/gcimporter/testdata/versions/test.go create mode 100644 src/go/internal/gcimporter/testdata/versions/test_go1.11_0i.a create mode 100644 src/go/internal/gcimporter/testdata/versions/test_go1.11_6b.a create mode 100644 src/go/internal/gcimporter/testdata/versions/test_go1.11_999b.a create mode 100644 src/go/internal/gcimporter/testdata/versions/test_go1.11_999i.a create mode 100644 src/go/internal/gcimporter/testdata/versions/test_go1.7_0.a create mode 100644 src/go/internal/gcimporter/testdata/versions/test_go1.7_1.a create mode 100644 src/go/internal/gcimporter/testdata/versions/test_go1.8_4.a create mode 100644 src/go/internal/gcimporter/testdata/versions/test_go1.8_5.a create mode 100644 src/go/internal/srcimporter/srcimporter.go create mode 100644 src/go/internal/srcimporter/srcimporter_test.go create mode 100644 src/go/internal/srcimporter/testdata/issue20855/issue20855.go create mode 100644 src/go/internal/srcimporter/testdata/issue23092/issue23092.go create mode 100644 src/go/internal/srcimporter/testdata/issue24392/issue24392.go create mode 100644 src/go/parser/error_test.go create mode 100644 src/go/parser/example_test.go create mode 100644 src/go/parser/interface.go create mode 100644 src/go/parser/parser.go create mode 100644 src/go/parser/parser_test.go create mode 100644 src/go/parser/performance_test.go create mode 100644 src/go/parser/short_test.go create mode 100644 src/go/parser/testdata/commas.src create mode 100644 src/go/parser/testdata/issue11377.src create mode 100644 src/go/parser/testdata/issue23434.src create mode 100644 src/go/parser/testdata/issue3106.src create mode 100644 src/go/parser/testdata/issue34946.src create mode 100644 src/go/parser/testdata/issue42951/not_a_file.go/invalid.go create mode 100644 src/go/printer/example_test.go create mode 100644 src/go/printer/nodes.go create mode 100644 src/go/printer/performance_test.go create mode 100644 src/go/printer/printer.go create mode 100644 src/go/printer/printer_test.go create mode 100644 src/go/printer/testdata/alignment.golden create mode 100644 src/go/printer/testdata/alignment.input create mode 100644 src/go/printer/testdata/comments.golden create mode 100644 src/go/printer/testdata/comments.input create mode 100644 src/go/printer/testdata/comments.x create mode 100644 src/go/printer/testdata/comments2.golden create mode 100644 src/go/printer/testdata/comments2.input create mode 100644 src/go/printer/testdata/complit.input create mode 100644 src/go/printer/testdata/complit.x create mode 100644 src/go/printer/testdata/declarations.golden create mode 100644 src/go/printer/testdata/declarations.input create mode 100644 src/go/printer/testdata/empty.golden create mode 100644 src/go/printer/testdata/empty.input create mode 100644 src/go/printer/testdata/expressions.golden create mode 100644 src/go/printer/testdata/expressions.input create mode 100644 src/go/printer/testdata/expressions.raw create mode 100644 src/go/printer/testdata/go2numbers.golden create mode 100644 src/go/printer/testdata/go2numbers.input create mode 100644 src/go/printer/testdata/go2numbers.norm create mode 100644 src/go/printer/testdata/linebreaks.golden create mode 100644 src/go/printer/testdata/linebreaks.input create mode 100644 src/go/printer/testdata/parser.go create mode 100644 src/go/printer/testdata/slow.golden create mode 100644 src/go/printer/testdata/slow.input create mode 100644 src/go/printer/testdata/statements.golden create mode 100644 src/go/printer/testdata/statements.input create mode 100644 src/go/scanner/errors.go create mode 100644 src/go/scanner/example_test.go create mode 100644 src/go/scanner/scanner.go create mode 100644 src/go/scanner/scanner_test.go create mode 100644 src/go/token/example_test.go create mode 100644 src/go/token/position.go create mode 100644 src/go/token/position_test.go create mode 100644 src/go/token/serialize.go create mode 100644 src/go/token/serialize_test.go create mode 100644 src/go/token/token.go create mode 100644 src/go/token/token_test.go create mode 100644 src/go/types/api.go create mode 100644 src/go/types/api_test.go create mode 100644 src/go/types/assignments.go create mode 100644 src/go/types/builtins.go create mode 100644 src/go/types/builtins_test.go create mode 100644 src/go/types/call.go create mode 100644 src/go/types/check.go create mode 100644 src/go/types/check_test.go create mode 100644 src/go/types/conversions.go create mode 100644 src/go/types/decl.go create mode 100644 src/go/types/errorcodes.go create mode 100644 src/go/types/errorcodes_test.go create mode 100644 src/go/types/errors.go create mode 100644 src/go/types/eval.go create mode 100644 src/go/types/eval_test.go create mode 100644 src/go/types/example_test.go create mode 100644 src/go/types/expr.go create mode 100644 src/go/types/exprstring.go create mode 100644 src/go/types/exprstring_test.go create mode 100644 src/go/types/fixedbugs/issue20583.src create mode 100644 src/go/types/fixedbugs/issue23203a.src create mode 100644 src/go/types/fixedbugs/issue23203b.src create mode 100644 src/go/types/fixedbugs/issue26390.src create mode 100644 src/go/types/fixedbugs/issue28251.src create mode 100644 src/go/types/fixedbugs/issue42695.src create mode 100644 src/go/types/fixedbugs/issue43110.src create mode 100644 src/go/types/fixedbugs/issue43124.src create mode 100644 src/go/types/fixedbugs/issue6977.src create mode 100644 src/go/types/gccgosizes.go create mode 100644 src/go/types/gotype.go create mode 100644 src/go/types/hilbert_test.go create mode 100644 src/go/types/initorder.go create mode 100644 src/go/types/issues_test.go create mode 100644 src/go/types/labels.go create mode 100644 src/go/types/lookup.go create mode 100644 src/go/types/methodset.go create mode 100644 src/go/types/object.go create mode 100644 src/go/types/object_test.go create mode 100644 src/go/types/objset.go create mode 100644 src/go/types/operand.go create mode 100644 src/go/types/package.go create mode 100644 src/go/types/predicates.go create mode 100644 src/go/types/resolver.go create mode 100644 src/go/types/resolver_test.go create mode 100644 src/go/types/return.go create mode 100644 src/go/types/scope.go create mode 100644 src/go/types/selection.go create mode 100644 src/go/types/self_test.go create mode 100644 src/go/types/sizes.go create mode 100644 src/go/types/sizes_test.go create mode 100644 src/go/types/stdlib_test.go create mode 100644 src/go/types/stmt.go create mode 100644 src/go/types/testdata/blank.src create mode 100644 src/go/types/testdata/builtins.src create mode 100644 src/go/types/testdata/const0.src create mode 100644 src/go/types/testdata/const1.src create mode 100644 src/go/types/testdata/constdecl.src create mode 100644 src/go/types/testdata/conversions.src create mode 100644 src/go/types/testdata/conversions2.src create mode 100644 src/go/types/testdata/cycles.src create mode 100644 src/go/types/testdata/cycles1.src create mode 100644 src/go/types/testdata/cycles2.src create mode 100644 src/go/types/testdata/cycles3.src create mode 100644 src/go/types/testdata/cycles4.src create mode 100644 src/go/types/testdata/cycles5.src create mode 100644 src/go/types/testdata/decls0.src create mode 100644 src/go/types/testdata/decls1.src create mode 100644 src/go/types/testdata/decls2a.src create mode 100644 src/go/types/testdata/decls2b.src create mode 100644 src/go/types/testdata/decls3.src create mode 100644 src/go/types/testdata/decls4.src create mode 100644 src/go/types/testdata/decls5.src create mode 100644 src/go/types/testdata/errors.src create mode 100644 src/go/types/testdata/expr0.src create mode 100644 src/go/types/testdata/expr1.src create mode 100644 src/go/types/testdata/expr2.src create mode 100644 src/go/types/testdata/expr3.src create mode 100644 src/go/types/testdata/gotos.src create mode 100644 src/go/types/testdata/importC.src create mode 100644 src/go/types/testdata/importdecl0a.src create mode 100644 src/go/types/testdata/importdecl0b.src create mode 100644 src/go/types/testdata/importdecl1a.src create mode 100644 src/go/types/testdata/importdecl1b.src create mode 100644 src/go/types/testdata/init0.src create mode 100644 src/go/types/testdata/init1.src create mode 100644 src/go/types/testdata/init2.src create mode 100644 src/go/types/testdata/issue25008a.src create mode 100644 src/go/types/testdata/issue25008b.src create mode 100644 src/go/types/testdata/issues.src create mode 100644 src/go/types/testdata/labels.src create mode 100644 src/go/types/testdata/literals.src create mode 100644 src/go/types/testdata/methodsets.src create mode 100644 src/go/types/testdata/shifts.src create mode 100644 src/go/types/testdata/stmt0.src create mode 100644 src/go/types/testdata/stmt1.src create mode 100644 src/go/types/testdata/vardecl.src create mode 100644 src/go/types/token_test.go create mode 100644 src/go/types/type.go create mode 100644 src/go/types/typestring.go create mode 100644 src/go/types/typestring_test.go create mode 100644 src/go/types/typexpr.go create mode 100644 src/go/types/universe.go (limited to 'src/go') diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go new file mode 100644 index 0000000..300966a --- /dev/null +++ b/src/go/ast/ast.go @@ -0,0 +1,1063 @@ +// Copyright 2009 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 ast declares the types used to represent syntax trees for Go +// packages. +// +package ast + +import ( + "go/token" + "strings" +) + +// ---------------------------------------------------------------------------- +// Interfaces +// +// There are 3 main classes of nodes: Expressions and type nodes, +// statement nodes, and declaration nodes. The node names usually +// match the corresponding Go spec production names to which they +// correspond. The node fields correspond to the individual parts +// of the respective productions. +// +// All nodes contain position information marking the beginning of +// the corresponding source text segment; it is accessible via the +// Pos accessor method. Nodes may contain additional position info +// for language constructs where comments may be found between parts +// of the construct (typically any larger, parenthesized subpart). +// That position information is needed to properly position comments +// when printing the construct. + +// All node types implement the Node interface. +type Node interface { + Pos() token.Pos // position of first character belonging to the node + End() token.Pos // position of first character immediately after the node +} + +// All expression nodes implement the Expr interface. +type Expr interface { + Node + exprNode() +} + +// All statement nodes implement the Stmt interface. +type Stmt interface { + Node + stmtNode() +} + +// All declaration nodes implement the Decl interface. +type Decl interface { + Node + declNode() +} + +// ---------------------------------------------------------------------------- +// Comments + +// A Comment node represents a single //-style or /*-style comment. +// +// The Text field contains the comment text without carriage returns (\r) that +// may have been present in the source. Because a comment's end position is +// computed using len(Text), the position reported by End() does not match the +// true source end position for comments containing carriage returns. +type Comment struct { + Slash token.Pos // position of "/" starting the comment + Text string // comment text (excluding '\n' for //-style comments) +} + +func (c *Comment) Pos() token.Pos { return c.Slash } +func (c *Comment) End() token.Pos { return token.Pos(int(c.Slash) + len(c.Text)) } + +// A CommentGroup represents a sequence of comments +// with no other tokens and no empty lines between. +// +type CommentGroup struct { + List []*Comment // len(List) > 0 +} + +func (g *CommentGroup) Pos() token.Pos { return g.List[0].Pos() } +func (g *CommentGroup) End() token.Pos { return g.List[len(g.List)-1].End() } + +func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' } + +func stripTrailingWhitespace(s string) string { + i := len(s) + for i > 0 && isWhitespace(s[i-1]) { + i-- + } + return s[0:i] +} + +// Text returns the text of the comment. +// Comment markers (//, /*, and */), the first space of a line comment, and +// leading and trailing empty lines are removed. +// Comment directives like "//line" and "//go:noinline" are also removed. +// Multiple empty lines are reduced to one, and trailing space on lines is trimmed. +// Unless the result is empty, it is newline-terminated. +func (g *CommentGroup) Text() string { + if g == nil { + return "" + } + comments := make([]string, len(g.List)) + for i, c := range g.List { + comments[i] = c.Text + } + + lines := make([]string, 0, 10) // most comments are less than 10 lines + for _, c := range comments { + // Remove comment markers. + // The parser has given us exactly the comment text. + switch c[1] { + case '/': + //-style comment (no newline at the end) + c = c[2:] + if len(c) == 0 { + // empty line + break + } + if c[0] == ' ' { + // strip first space - required for Example tests + c = c[1:] + break + } + if isDirective(c) { + // Ignore //go:noinline, //line, and so on. + continue + } + case '*': + /*-style comment */ + c = c[2 : len(c)-2] + } + + // Split on newlines. + cl := strings.Split(c, "\n") + + // Walk lines, stripping trailing white space and adding to list. + for _, l := range cl { + lines = append(lines, stripTrailingWhitespace(l)) + } + } + + // Remove leading blank lines; convert runs of + // interior blank lines to a single blank line. + n := 0 + for _, line := range lines { + if line != "" || n > 0 && lines[n-1] != "" { + lines[n] = line + n++ + } + } + lines = lines[0:n] + + // Add final "" entry to get trailing newline from Join. + if n > 0 && lines[n-1] != "" { + lines = append(lines, "") + } + + return strings.Join(lines, "\n") +} + +// isDirective reports whether c is a comment directive. +func isDirective(c string) bool { + // "//line " is a line directive. + // (The // has been removed.) + if strings.HasPrefix(c, "line ") { + return true + } + + // "//[a-z0-9]+:[a-z0-9]" + // (The // has been removed.) + colon := strings.Index(c, ":") + if colon <= 0 || colon+1 >= len(c) { + return false + } + for i := 0; i <= colon+1; i++ { + if i == colon { + continue + } + b := c[i] + if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') { + return false + } + } + return true +} + +// ---------------------------------------------------------------------------- +// Expressions and types + +// A Field represents a Field declaration list in a struct type, +// a method list in an interface type, or a parameter/result declaration +// in a signature. +// Field.Names is nil for unnamed parameters (parameter lists which only contain types) +// and embedded struct fields. In the latter case, the field name is the type name. +// +type Field struct { + Doc *CommentGroup // associated documentation; or nil + Names []*Ident // field/method/parameter names; or nil + Type Expr // field/method/parameter type + Tag *BasicLit // field tag; or nil + Comment *CommentGroup // line comments; or nil +} + +func (f *Field) Pos() token.Pos { + if len(f.Names) > 0 { + return f.Names[0].Pos() + } + return f.Type.Pos() +} + +func (f *Field) End() token.Pos { + if f.Tag != nil { + return f.Tag.End() + } + return f.Type.End() +} + +// A FieldList represents a list of Fields, enclosed by parentheses or braces. +type FieldList struct { + Opening token.Pos // position of opening parenthesis/brace, if any + List []*Field // field list; or nil + Closing token.Pos // position of closing parenthesis/brace, if any +} + +func (f *FieldList) Pos() token.Pos { + if f.Opening.IsValid() { + return f.Opening + } + // the list should not be empty in this case; + // be conservative and guard against bad ASTs + if len(f.List) > 0 { + return f.List[0].Pos() + } + return token.NoPos +} + +func (f *FieldList) End() token.Pos { + if f.Closing.IsValid() { + return f.Closing + 1 + } + // the list should not be empty in this case; + // be conservative and guard against bad ASTs + if n := len(f.List); n > 0 { + return f.List[n-1].End() + } + return token.NoPos +} + +// NumFields returns the number of parameters or struct fields represented by a FieldList. +func (f *FieldList) NumFields() int { + n := 0 + if f != nil { + for _, g := range f.List { + m := len(g.Names) + if m == 0 { + m = 1 + } + n += m + } + } + return n +} + +// An expression is represented by a tree consisting of one +// or more of the following concrete expression nodes. +// +type ( + // A BadExpr node is a placeholder for an expression containing + // syntax errors for which a correct expression node cannot be + // created. + // + BadExpr struct { + From, To token.Pos // position range of bad expression + } + + // An Ident node represents an identifier. + Ident struct { + NamePos token.Pos // identifier position + Name string // identifier name + Obj *Object // denoted object; or nil + } + + // An Ellipsis node stands for the "..." type in a + // parameter list or the "..." length in an array type. + // + Ellipsis struct { + Ellipsis token.Pos // position of "..." + Elt Expr // ellipsis element type (parameter lists only); or nil + } + + // A BasicLit node represents a literal of basic type. + // + // Note that for the CHAR and STRING kinds, the literal is stored + // with its quotes. For example, for a double-quoted STRING, the + // first and the last rune in the Value field will be ". The + // Unquote and UnquoteChar functions in the strconv package can be + // used to unquote STRING and CHAR values, respectively. + BasicLit struct { + ValuePos token.Pos // literal position + Kind token.Token // token.INT, token.FLOAT, token.IMAG, token.CHAR, or token.STRING + Value string // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 'a', '\x7f', "foo" or `\m\n\o` + } + + // A FuncLit node represents a function literal. + FuncLit struct { + Type *FuncType // function type + Body *BlockStmt // function body + } + + // A CompositeLit node represents a composite literal. + CompositeLit struct { + Type Expr // literal type; or nil + Lbrace token.Pos // position of "{" + Elts []Expr // list of composite elements; or nil + Rbrace token.Pos // position of "}" + Incomplete bool // true if (source) expressions are missing in the Elts list + } + + // A ParenExpr node represents a parenthesized expression. + ParenExpr struct { + Lparen token.Pos // position of "(" + X Expr // parenthesized expression + Rparen token.Pos // position of ")" + } + + // A SelectorExpr node represents an expression followed by a selector. + SelectorExpr struct { + X Expr // expression + Sel *Ident // field selector + } + + // An IndexExpr node represents an expression followed by an index. + IndexExpr struct { + X Expr // expression + Lbrack token.Pos // position of "[" + Index Expr // index expression + Rbrack token.Pos // position of "]" + } + + // A SliceExpr node represents an expression followed by slice indices. + SliceExpr struct { + X Expr // expression + Lbrack token.Pos // position of "[" + Low Expr // begin of slice range; or nil + High Expr // end of slice range; or nil + Max Expr // maximum capacity of slice; or nil + Slice3 bool // true if 3-index slice (2 colons present) + Rbrack token.Pos // position of "]" + } + + // A TypeAssertExpr node represents an expression followed by a + // type assertion. + // + TypeAssertExpr struct { + X Expr // expression + Lparen token.Pos // position of "(" + Type Expr // asserted type; nil means type switch X.(type) + Rparen token.Pos // position of ")" + } + + // A CallExpr node represents an expression followed by an argument list. + CallExpr struct { + Fun Expr // function expression + Lparen token.Pos // position of "(" + Args []Expr // function arguments; or nil + Ellipsis token.Pos // position of "..." (token.NoPos if there is no "...") + Rparen token.Pos // position of ")" + } + + // A StarExpr node represents an expression of the form "*" Expression. + // Semantically it could be a unary "*" expression, or a pointer type. + // + StarExpr struct { + Star token.Pos // position of "*" + X Expr // operand + } + + // A UnaryExpr node represents a unary expression. + // Unary "*" expressions are represented via StarExpr nodes. + // + UnaryExpr struct { + OpPos token.Pos // position of Op + Op token.Token // operator + X Expr // operand + } + + // A BinaryExpr node represents a binary expression. + BinaryExpr struct { + X Expr // left operand + OpPos token.Pos // position of Op + Op token.Token // operator + Y Expr // right operand + } + + // A KeyValueExpr node represents (key : value) pairs + // in composite literals. + // + KeyValueExpr struct { + Key Expr + Colon token.Pos // position of ":" + Value Expr + } +) + +// The direction of a channel type is indicated by a bit +// mask including one or both of the following constants. +// +type ChanDir int + +const ( + SEND ChanDir = 1 << iota + RECV +) + +// A type is represented by a tree consisting of one +// or more of the following type-specific expression +// nodes. +// +type ( + // An ArrayType node represents an array or slice type. + ArrayType struct { + Lbrack token.Pos // position of "[" + Len Expr // Ellipsis node for [...]T array types, nil for slice types + Elt Expr // element type + } + + // A StructType node represents a struct type. + StructType struct { + Struct token.Pos // position of "struct" keyword + Fields *FieldList // list of field declarations + Incomplete bool // true if (source) fields are missing in the Fields list + } + + // Pointer types are represented via StarExpr nodes. + + // A FuncType node represents a function type. + FuncType struct { + Func token.Pos // position of "func" keyword (token.NoPos if there is no "func") + Params *FieldList // (incoming) parameters; non-nil + Results *FieldList // (outgoing) results; or nil + } + + // An InterfaceType node represents an interface type. + InterfaceType struct { + Interface token.Pos // position of "interface" keyword + Methods *FieldList // list of methods + Incomplete bool // true if (source) methods are missing in the Methods list + } + + // A MapType node represents a map type. + MapType struct { + Map token.Pos // position of "map" keyword + Key Expr + Value Expr + } + + // A ChanType node represents a channel type. + ChanType struct { + Begin token.Pos // position of "chan" keyword or "<-" (whichever comes first) + Arrow token.Pos // position of "<-" (token.NoPos if there is no "<-") + Dir ChanDir // channel direction + Value Expr // value type + } +) + +// Pos and End implementations for expression/type nodes. + +func (x *BadExpr) Pos() token.Pos { return x.From } +func (x *Ident) Pos() token.Pos { return x.NamePos } +func (x *Ellipsis) Pos() token.Pos { return x.Ellipsis } +func (x *BasicLit) Pos() token.Pos { return x.ValuePos } +func (x *FuncLit) Pos() token.Pos { return x.Type.Pos() } +func (x *CompositeLit) Pos() token.Pos { + if x.Type != nil { + return x.Type.Pos() + } + return x.Lbrace +} +func (x *ParenExpr) Pos() token.Pos { return x.Lparen } +func (x *SelectorExpr) Pos() token.Pos { return x.X.Pos() } +func (x *IndexExpr) Pos() token.Pos { return x.X.Pos() } +func (x *SliceExpr) Pos() token.Pos { return x.X.Pos() } +func (x *TypeAssertExpr) Pos() token.Pos { return x.X.Pos() } +func (x *CallExpr) Pos() token.Pos { return x.Fun.Pos() } +func (x *StarExpr) Pos() token.Pos { return x.Star } +func (x *UnaryExpr) Pos() token.Pos { return x.OpPos } +func (x *BinaryExpr) Pos() token.Pos { return x.X.Pos() } +func (x *KeyValueExpr) Pos() token.Pos { return x.Key.Pos() } +func (x *ArrayType) Pos() token.Pos { return x.Lbrack } +func (x *StructType) Pos() token.Pos { return x.Struct } +func (x *FuncType) Pos() token.Pos { + if x.Func.IsValid() || x.Params == nil { // see issue 3870 + return x.Func + } + return x.Params.Pos() // interface method declarations have no "func" keyword +} +func (x *InterfaceType) Pos() token.Pos { return x.Interface } +func (x *MapType) Pos() token.Pos { return x.Map } +func (x *ChanType) Pos() token.Pos { return x.Begin } + +func (x *BadExpr) End() token.Pos { return x.To } +func (x *Ident) End() token.Pos { return token.Pos(int(x.NamePos) + len(x.Name)) } +func (x *Ellipsis) End() token.Pos { + if x.Elt != nil { + return x.Elt.End() + } + return x.Ellipsis + 3 // len("...") +} +func (x *BasicLit) End() token.Pos { return token.Pos(int(x.ValuePos) + len(x.Value)) } +func (x *FuncLit) End() token.Pos { return x.Body.End() } +func (x *CompositeLit) End() token.Pos { return x.Rbrace + 1 } +func (x *ParenExpr) End() token.Pos { return x.Rparen + 1 } +func (x *SelectorExpr) End() token.Pos { return x.Sel.End() } +func (x *IndexExpr) End() token.Pos { return x.Rbrack + 1 } +func (x *SliceExpr) End() token.Pos { return x.Rbrack + 1 } +func (x *TypeAssertExpr) End() token.Pos { return x.Rparen + 1 } +func (x *CallExpr) End() token.Pos { return x.Rparen + 1 } +func (x *StarExpr) End() token.Pos { return x.X.End() } +func (x *UnaryExpr) End() token.Pos { return x.X.End() } +func (x *BinaryExpr) End() token.Pos { return x.Y.End() } +func (x *KeyValueExpr) End() token.Pos { return x.Value.End() } +func (x *ArrayType) End() token.Pos { return x.Elt.End() } +func (x *StructType) End() token.Pos { return x.Fields.End() } +func (x *FuncType) End() token.Pos { + if x.Results != nil { + return x.Results.End() + } + return x.Params.End() +} +func (x *InterfaceType) End() token.Pos { return x.Methods.End() } +func (x *MapType) End() token.Pos { return x.Value.End() } +func (x *ChanType) End() token.Pos { return x.Value.End() } + +// exprNode() ensures that only expression/type nodes can be +// assigned to an Expr. +// +func (*BadExpr) exprNode() {} +func (*Ident) exprNode() {} +func (*Ellipsis) exprNode() {} +func (*BasicLit) exprNode() {} +func (*FuncLit) exprNode() {} +func (*CompositeLit) exprNode() {} +func (*ParenExpr) exprNode() {} +func (*SelectorExpr) exprNode() {} +func (*IndexExpr) exprNode() {} +func (*SliceExpr) exprNode() {} +func (*TypeAssertExpr) exprNode() {} +func (*CallExpr) exprNode() {} +func (*StarExpr) exprNode() {} +func (*UnaryExpr) exprNode() {} +func (*BinaryExpr) exprNode() {} +func (*KeyValueExpr) exprNode() {} + +func (*ArrayType) exprNode() {} +func (*StructType) exprNode() {} +func (*FuncType) exprNode() {} +func (*InterfaceType) exprNode() {} +func (*MapType) exprNode() {} +func (*ChanType) exprNode() {} + +// ---------------------------------------------------------------------------- +// Convenience functions for Idents + +// NewIdent creates a new Ident without position. +// Useful for ASTs generated by code other than the Go parser. +// +func NewIdent(name string) *Ident { return &Ident{token.NoPos, name, nil} } + +// IsExported reports whether name starts with an upper-case letter. +// +func IsExported(name string) bool { return token.IsExported(name) } + +// IsExported reports whether id starts with an upper-case letter. +// +func (id *Ident) IsExported() bool { return token.IsExported(id.Name) } + +func (id *Ident) String() string { + if id != nil { + return id.Name + } + return "" +} + +// ---------------------------------------------------------------------------- +// Statements + +// A statement is represented by a tree consisting of one +// or more of the following concrete statement nodes. +// +type ( + // A BadStmt node is a placeholder for statements containing + // syntax errors for which no correct statement nodes can be + // created. + // + BadStmt struct { + From, To token.Pos // position range of bad statement + } + + // A DeclStmt node represents a declaration in a statement list. + DeclStmt struct { + Decl Decl // *GenDecl with CONST, TYPE, or VAR token + } + + // An EmptyStmt node represents an empty statement. + // The "position" of the empty statement is the position + // of the immediately following (explicit or implicit) semicolon. + // + EmptyStmt struct { + Semicolon token.Pos // position of following ";" + Implicit bool // if set, ";" was omitted in the source + } + + // A LabeledStmt node represents a labeled statement. + LabeledStmt struct { + Label *Ident + Colon token.Pos // position of ":" + Stmt Stmt + } + + // An ExprStmt node represents a (stand-alone) expression + // in a statement list. + // + ExprStmt struct { + X Expr // expression + } + + // A SendStmt node represents a send statement. + SendStmt struct { + Chan Expr + Arrow token.Pos // position of "<-" + Value Expr + } + + // An IncDecStmt node represents an increment or decrement statement. + IncDecStmt struct { + X Expr + TokPos token.Pos // position of Tok + Tok token.Token // INC or DEC + } + + // An AssignStmt node represents an assignment or + // a short variable declaration. + // + AssignStmt struct { + Lhs []Expr + TokPos token.Pos // position of Tok + Tok token.Token // assignment token, DEFINE + Rhs []Expr + } + + // A GoStmt node represents a go statement. + GoStmt struct { + Go token.Pos // position of "go" keyword + Call *CallExpr + } + + // A DeferStmt node represents a defer statement. + DeferStmt struct { + Defer token.Pos // position of "defer" keyword + Call *CallExpr + } + + // A ReturnStmt node represents a return statement. + ReturnStmt struct { + Return token.Pos // position of "return" keyword + Results []Expr // result expressions; or nil + } + + // A BranchStmt node represents a break, continue, goto, + // or fallthrough statement. + // + BranchStmt struct { + TokPos token.Pos // position of Tok + Tok token.Token // keyword token (BREAK, CONTINUE, GOTO, FALLTHROUGH) + Label *Ident // label name; or nil + } + + // A BlockStmt node represents a braced statement list. + BlockStmt struct { + Lbrace token.Pos // position of "{" + List []Stmt + Rbrace token.Pos // position of "}", if any (may be absent due to syntax error) + } + + // An IfStmt node represents an if statement. + IfStmt struct { + If token.Pos // position of "if" keyword + Init Stmt // initialization statement; or nil + Cond Expr // condition + Body *BlockStmt + Else Stmt // else branch; or nil + } + + // A CaseClause represents a case of an expression or type switch statement. + CaseClause struct { + Case token.Pos // position of "case" or "default" keyword + List []Expr // list of expressions or types; nil means default case + Colon token.Pos // position of ":" + Body []Stmt // statement list; or nil + } + + // A SwitchStmt node represents an expression switch statement. + SwitchStmt struct { + Switch token.Pos // position of "switch" keyword + Init Stmt // initialization statement; or nil + Tag Expr // tag expression; or nil + Body *BlockStmt // CaseClauses only + } + + // A TypeSwitchStmt node represents a type switch statement. + TypeSwitchStmt struct { + Switch token.Pos // position of "switch" keyword + Init Stmt // initialization statement; or nil + Assign Stmt // x := y.(type) or y.(type) + Body *BlockStmt // CaseClauses only + } + + // A CommClause node represents a case of a select statement. + CommClause struct { + Case token.Pos // position of "case" or "default" keyword + Comm Stmt // send or receive statement; nil means default case + Colon token.Pos // position of ":" + Body []Stmt // statement list; or nil + } + + // A SelectStmt node represents a select statement. + SelectStmt struct { + Select token.Pos // position of "select" keyword + Body *BlockStmt // CommClauses only + } + + // A ForStmt represents a for statement. + ForStmt struct { + For token.Pos // position of "for" keyword + Init Stmt // initialization statement; or nil + Cond Expr // condition; or nil + Post Stmt // post iteration statement; or nil + Body *BlockStmt + } + + // A RangeStmt represents a for statement with a range clause. + RangeStmt struct { + For token.Pos // position of "for" keyword + Key, Value Expr // Key, Value may be nil + TokPos token.Pos // position of Tok; invalid if Key == nil + Tok token.Token // ILLEGAL if Key == nil, ASSIGN, DEFINE + X Expr // value to range over + Body *BlockStmt + } +) + +// Pos and End implementations for statement nodes. + +func (s *BadStmt) Pos() token.Pos { return s.From } +func (s *DeclStmt) Pos() token.Pos { return s.Decl.Pos() } +func (s *EmptyStmt) Pos() token.Pos { return s.Semicolon } +func (s *LabeledStmt) Pos() token.Pos { return s.Label.Pos() } +func (s *ExprStmt) Pos() token.Pos { return s.X.Pos() } +func (s *SendStmt) Pos() token.Pos { return s.Chan.Pos() } +func (s *IncDecStmt) Pos() token.Pos { return s.X.Pos() } +func (s *AssignStmt) Pos() token.Pos { return s.Lhs[0].Pos() } +func (s *GoStmt) Pos() token.Pos { return s.Go } +func (s *DeferStmt) Pos() token.Pos { return s.Defer } +func (s *ReturnStmt) Pos() token.Pos { return s.Return } +func (s *BranchStmt) Pos() token.Pos { return s.TokPos } +func (s *BlockStmt) Pos() token.Pos { return s.Lbrace } +func (s *IfStmt) Pos() token.Pos { return s.If } +func (s *CaseClause) Pos() token.Pos { return s.Case } +func (s *SwitchStmt) Pos() token.Pos { return s.Switch } +func (s *TypeSwitchStmt) Pos() token.Pos { return s.Switch } +func (s *CommClause) Pos() token.Pos { return s.Case } +func (s *SelectStmt) Pos() token.Pos { return s.Select } +func (s *ForStmt) Pos() token.Pos { return s.For } +func (s *RangeStmt) Pos() token.Pos { return s.For } + +func (s *BadStmt) End() token.Pos { return s.To } +func (s *DeclStmt) End() token.Pos { return s.Decl.End() } +func (s *EmptyStmt) End() token.Pos { + if s.Implicit { + return s.Semicolon + } + return s.Semicolon + 1 /* len(";") */ +} +func (s *LabeledStmt) End() token.Pos { return s.Stmt.End() } +func (s *ExprStmt) End() token.Pos { return s.X.End() } +func (s *SendStmt) End() token.Pos { return s.Value.End() } +func (s *IncDecStmt) End() token.Pos { + return s.TokPos + 2 /* len("++") */ +} +func (s *AssignStmt) End() token.Pos { return s.Rhs[len(s.Rhs)-1].End() } +func (s *GoStmt) End() token.Pos { return s.Call.End() } +func (s *DeferStmt) End() token.Pos { return s.Call.End() } +func (s *ReturnStmt) End() token.Pos { + if n := len(s.Results); n > 0 { + return s.Results[n-1].End() + } + return s.Return + 6 // len("return") +} +func (s *BranchStmt) End() token.Pos { + if s.Label != nil { + return s.Label.End() + } + return token.Pos(int(s.TokPos) + len(s.Tok.String())) +} +func (s *BlockStmt) End() token.Pos { + if s.Rbrace.IsValid() { + return s.Rbrace + 1 + } + if n := len(s.List); n > 0 { + return s.List[n-1].End() + } + return s.Lbrace + 1 +} +func (s *IfStmt) End() token.Pos { + if s.Else != nil { + return s.Else.End() + } + return s.Body.End() +} +func (s *CaseClause) End() token.Pos { + if n := len(s.Body); n > 0 { + return s.Body[n-1].End() + } + return s.Colon + 1 +} +func (s *SwitchStmt) End() token.Pos { return s.Body.End() } +func (s *TypeSwitchStmt) End() token.Pos { return s.Body.End() } +func (s *CommClause) End() token.Pos { + if n := len(s.Body); n > 0 { + return s.Body[n-1].End() + } + return s.Colon + 1 +} +func (s *SelectStmt) End() token.Pos { return s.Body.End() } +func (s *ForStmt) End() token.Pos { return s.Body.End() } +func (s *RangeStmt) End() token.Pos { return s.Body.End() } + +// stmtNode() ensures that only statement nodes can be +// assigned to a Stmt. +// +func (*BadStmt) stmtNode() {} +func (*DeclStmt) stmtNode() {} +func (*EmptyStmt) stmtNode() {} +func (*LabeledStmt) stmtNode() {} +func (*ExprStmt) stmtNode() {} +func (*SendStmt) stmtNode() {} +func (*IncDecStmt) stmtNode() {} +func (*AssignStmt) stmtNode() {} +func (*GoStmt) stmtNode() {} +func (*DeferStmt) stmtNode() {} +func (*ReturnStmt) stmtNode() {} +func (*BranchStmt) stmtNode() {} +func (*BlockStmt) stmtNode() {} +func (*IfStmt) stmtNode() {} +func (*CaseClause) stmtNode() {} +func (*SwitchStmt) stmtNode() {} +func (*TypeSwitchStmt) stmtNode() {} +func (*CommClause) stmtNode() {} +func (*SelectStmt) stmtNode() {} +func (*ForStmt) stmtNode() {} +func (*RangeStmt) stmtNode() {} + +// ---------------------------------------------------------------------------- +// Declarations + +// A Spec node represents a single (non-parenthesized) import, +// constant, type, or variable declaration. +// +type ( + // The Spec type stands for any of *ImportSpec, *ValueSpec, and *TypeSpec. + Spec interface { + Node + specNode() + } + + // An ImportSpec node represents a single package import. + ImportSpec struct { + Doc *CommentGroup // associated documentation; or nil + Name *Ident // local package name (including "."); or nil + Path *BasicLit // import path + Comment *CommentGroup // line comments; or nil + EndPos token.Pos // end of spec (overrides Path.Pos if nonzero) + } + + // A ValueSpec node represents a constant or variable declaration + // (ConstSpec or VarSpec production). + // + ValueSpec struct { + Doc *CommentGroup // associated documentation; or nil + Names []*Ident // value names (len(Names) > 0) + Type Expr // value type; or nil + Values []Expr // initial values; or nil + Comment *CommentGroup // line comments; or nil + } + + // A TypeSpec node represents a type declaration (TypeSpec production). + TypeSpec struct { + Doc *CommentGroup // associated documentation; or nil + Name *Ident // type name + Assign token.Pos // position of '=', if any + Type Expr // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes + Comment *CommentGroup // line comments; or nil + } +) + +// Pos and End implementations for spec nodes. + +func (s *ImportSpec) Pos() token.Pos { + if s.Name != nil { + return s.Name.Pos() + } + return s.Path.Pos() +} +func (s *ValueSpec) Pos() token.Pos { return s.Names[0].Pos() } +func (s *TypeSpec) Pos() token.Pos { return s.Name.Pos() } + +func (s *ImportSpec) End() token.Pos { + if s.EndPos != 0 { + return s.EndPos + } + return s.Path.End() +} + +func (s *ValueSpec) End() token.Pos { + if n := len(s.Values); n > 0 { + return s.Values[n-1].End() + } + if s.Type != nil { + return s.Type.End() + } + return s.Names[len(s.Names)-1].End() +} +func (s *TypeSpec) End() token.Pos { return s.Type.End() } + +// specNode() ensures that only spec nodes can be +// assigned to a Spec. +// +func (*ImportSpec) specNode() {} +func (*ValueSpec) specNode() {} +func (*TypeSpec) specNode() {} + +// A declaration is represented by one of the following declaration nodes. +// +type ( + // A BadDecl node is a placeholder for a declaration containing + // syntax errors for which a correct declaration node cannot be + // created. + // + BadDecl struct { + From, To token.Pos // position range of bad declaration + } + + // A GenDecl node (generic declaration node) represents an import, + // constant, type or variable declaration. A valid Lparen position + // (Lparen.IsValid()) indicates a parenthesized declaration. + // + // Relationship between Tok value and Specs element type: + // + // token.IMPORT *ImportSpec + // token.CONST *ValueSpec + // token.TYPE *TypeSpec + // token.VAR *ValueSpec + // + GenDecl struct { + Doc *CommentGroup // associated documentation; or nil + TokPos token.Pos // position of Tok + Tok token.Token // IMPORT, CONST, TYPE, VAR + Lparen token.Pos // position of '(', if any + Specs []Spec + Rparen token.Pos // position of ')', if any + } + + // A FuncDecl node represents a function declaration. + FuncDecl struct { + Doc *CommentGroup // associated documentation; or nil + Recv *FieldList // receiver (methods); or nil (functions) + Name *Ident // function/method name + Type *FuncType // function signature: parameters, results, and position of "func" keyword + Body *BlockStmt // function body; or nil for external (non-Go) function + } +) + +// Pos and End implementations for declaration nodes. + +func (d *BadDecl) Pos() token.Pos { return d.From } +func (d *GenDecl) Pos() token.Pos { return d.TokPos } +func (d *FuncDecl) Pos() token.Pos { return d.Type.Pos() } + +func (d *BadDecl) End() token.Pos { return d.To } +func (d *GenDecl) End() token.Pos { + if d.Rparen.IsValid() { + return d.Rparen + 1 + } + return d.Specs[0].End() +} +func (d *FuncDecl) End() token.Pos { + if d.Body != nil { + return d.Body.End() + } + return d.Type.End() +} + +// declNode() ensures that only declaration nodes can be +// assigned to a Decl. +// +func (*BadDecl) declNode() {} +func (*GenDecl) declNode() {} +func (*FuncDecl) declNode() {} + +// ---------------------------------------------------------------------------- +// Files and packages + +// A File node represents a Go source file. +// +// The Comments list contains all comments in the source file in order of +// appearance, including the comments that are pointed to from other nodes +// via Doc and Comment fields. +// +// For correct printing of source code containing comments (using packages +// go/format and go/printer), special care must be taken to update comments +// when a File's syntax tree is modified: For printing, comments are interspersed +// between tokens based on their position. If syntax tree nodes are +// removed or moved, relevant comments in their vicinity must also be removed +// (from the File.Comments list) or moved accordingly (by updating their +// positions). A CommentMap may be used to facilitate some of these operations. +// +// Whether and how a comment is associated with a node depends on the +// interpretation of the syntax tree by the manipulating program: Except for Doc +// and Comment comments directly associated with nodes, the remaining comments +// are "free-floating" (see also issues #18593, #20744). +// +type File struct { + Doc *CommentGroup // associated documentation; or nil + Package token.Pos // position of "package" keyword + Name *Ident // package name + Decls []Decl // top-level declarations; or nil + Scope *Scope // package scope (this file only) + Imports []*ImportSpec // imports in this file + Unresolved []*Ident // unresolved identifiers in this file + Comments []*CommentGroup // list of all comments in the source file +} + +func (f *File) Pos() token.Pos { return f.Package } +func (f *File) End() token.Pos { + if n := len(f.Decls); n > 0 { + return f.Decls[n-1].End() + } + return f.Name.End() +} + +// A Package node represents a set of source files +// collectively building a Go package. +// +type Package struct { + Name string // package name + Scope *Scope // package scope across all files + Imports map[string]*Object // map of package id -> package object + Files map[string]*File // Go source files by filename +} + +func (p *Package) Pos() token.Pos { return token.NoPos } +func (p *Package) End() token.Pos { return token.NoPos } diff --git a/src/go/ast/ast_test.go b/src/go/ast/ast_test.go new file mode 100644 index 0000000..71b2d6c --- /dev/null +++ b/src/go/ast/ast_test.go @@ -0,0 +1,79 @@ +// Copyright 2012 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 ast + +import ( + "testing" +) + +var comments = []struct { + list []string + text string +}{ + {[]string{"//"}, ""}, + {[]string{"// "}, ""}, + {[]string{"//", "//", "// "}, ""}, + {[]string{"// foo "}, "foo\n"}, + {[]string{"//", "//", "// foo"}, "foo\n"}, + {[]string{"// foo bar "}, "foo bar\n"}, + {[]string{"// foo", "// bar"}, "foo\nbar\n"}, + {[]string{"// foo", "//", "//", "//", "// bar"}, "foo\n\nbar\n"}, + {[]string{"// foo", "/* bar */"}, "foo\n bar\n"}, + {[]string{"//", "//", "//", "// foo", "//", "//", "//"}, "foo\n"}, + + {[]string{"/**/"}, ""}, + {[]string{"/* */"}, ""}, + {[]string{"/**/", "/**/", "/* */"}, ""}, + {[]string{"/* Foo */"}, " Foo\n"}, + {[]string{"/* Foo Bar */"}, " Foo Bar\n"}, + {[]string{"/* Foo*/", "/* Bar*/"}, " Foo\n Bar\n"}, + {[]string{"/* Foo*/", "/**/", "/**/", "/**/", "// Bar"}, " Foo\n\nBar\n"}, + {[]string{"/* Foo*/", "/*\n*/", "//", "/*\n*/", "// Bar"}, " Foo\n\nBar\n"}, + {[]string{"/* Foo*/", "// Bar"}, " Foo\nBar\n"}, + {[]string{"/* Foo\n Bar*/"}, " Foo\n Bar\n"}, + + {[]string{"// foo", "//go:noinline", "// bar", "//:baz"}, "foo\nbar\n:baz\n"}, + {[]string{"// foo", "//lint123:ignore", "// bar"}, "foo\nbar\n"}, +} + +func TestCommentText(t *testing.T) { + for i, c := range comments { + list := make([]*Comment, len(c.list)) + for i, s := range c.list { + list[i] = &Comment{Text: s} + } + + text := (&CommentGroup{list}).Text() + if text != c.text { + t.Errorf("case %d: got %q; expected %q", i, text, c.text) + } + } +} + +var isDirectiveTests = []struct { + in string + ok bool +}{ + {"abc", false}, + {"go:inline", true}, + {"Go:inline", false}, + {"go:Inline", false}, + {":inline", false}, + {"lint:ignore", true}, + {"lint:1234", true}, + {"1234:lint", true}, + {"go: inline", false}, + {"go:", false}, + {"go:*", false}, + {"go:x*", true}, +} + +func TestIsDirective(t *testing.T) { + for _, tt := range isDirectiveTests { + if ok := isDirective(tt.in); ok != tt.ok { + t.Errorf("isDirective(%q) = %v, want %v", tt.in, ok, tt.ok) + } + } +} diff --git a/src/go/ast/commentmap.go b/src/go/ast/commentmap.go new file mode 100644 index 0000000..2a653a6 --- /dev/null +++ b/src/go/ast/commentmap.go @@ -0,0 +1,332 @@ +// Copyright 2012 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 ast + +import ( + "bytes" + "fmt" + "go/token" + "sort" +) + +type byPos []*CommentGroup + +func (a byPos) Len() int { return len(a) } +func (a byPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() } +func (a byPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// sortComments sorts the list of comment groups in source order. +// +func sortComments(list []*CommentGroup) { + // TODO(gri): Does it make sense to check for sorted-ness + // first (because we know that sorted-ness is + // very likely)? + if orderedList := byPos(list); !sort.IsSorted(orderedList) { + sort.Sort(orderedList) + } +} + +// A CommentMap maps an AST node to a list of comment groups +// associated with it. See NewCommentMap for a description of +// the association. +// +type CommentMap map[Node][]*CommentGroup + +func (cmap CommentMap) addComment(n Node, c *CommentGroup) { + list := cmap[n] + if len(list) == 0 { + list = []*CommentGroup{c} + } else { + list = append(list, c) + } + cmap[n] = list +} + +type byInterval []Node + +func (a byInterval) Len() int { return len(a) } +func (a byInterval) Less(i, j int) bool { + pi, pj := a[i].Pos(), a[j].Pos() + return pi < pj || pi == pj && a[i].End() > a[j].End() +} +func (a byInterval) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// nodeList returns the list of nodes of the AST n in source order. +// +func nodeList(n Node) []Node { + var list []Node + Inspect(n, func(n Node) bool { + // don't collect comments + switch n.(type) { + case nil, *CommentGroup, *Comment: + return false + } + list = append(list, n) + return true + }) + // Note: The current implementation assumes that Inspect traverses the + // AST in depth-first and thus _source_ order. If AST traversal + // does not follow source order, the sorting call below will be + // required. + // sort.Sort(byInterval(list)) + return list +} + +// A commentListReader helps iterating through a list of comment groups. +// +type commentListReader struct { + fset *token.FileSet + list []*CommentGroup + index int + comment *CommentGroup // comment group at current index + pos, end token.Position // source interval of comment group at current index +} + +func (r *commentListReader) eol() bool { + return r.index >= len(r.list) +} + +func (r *commentListReader) next() { + if !r.eol() { + r.comment = r.list[r.index] + r.pos = r.fset.Position(r.comment.Pos()) + r.end = r.fset.Position(r.comment.End()) + r.index++ + } +} + +// A nodeStack keeps track of nested nodes. +// A node lower on the stack lexically contains the nodes higher on the stack. +// +type nodeStack []Node + +// push pops all nodes that appear lexically before n +// and then pushes n on the stack. +// +func (s *nodeStack) push(n Node) { + s.pop(n.Pos()) + *s = append((*s), n) +} + +// pop pops all nodes that appear lexically before pos +// (i.e., whose lexical extent has ended before or at pos). +// It returns the last node popped. +// +func (s *nodeStack) pop(pos token.Pos) (top Node) { + i := len(*s) + for i > 0 && (*s)[i-1].End() <= pos { + top = (*s)[i-1] + i-- + } + *s = (*s)[0:i] + return top +} + +// NewCommentMap creates a new comment map by associating comment groups +// of the comments list with the nodes of the AST specified by node. +// +// A comment group g is associated with a node n if: +// +// - g starts on the same line as n ends +// - g starts on the line immediately following n, and there is +// at least one empty line after g and before the next node +// - g starts before n and is not associated to the node before n +// via the previous rules +// +// NewCommentMap tries to associate a comment group to the "largest" +// node possible: For instance, if the comment is a line comment +// trailing an assignment, the comment is associated with the entire +// assignment rather than just the last operand in the assignment. +// +func NewCommentMap(fset *token.FileSet, node Node, comments []*CommentGroup) CommentMap { + if len(comments) == 0 { + return nil // no comments to map + } + + cmap := make(CommentMap) + + // set up comment reader r + tmp := make([]*CommentGroup, len(comments)) + copy(tmp, comments) // don't change incoming comments + sortComments(tmp) + r := commentListReader{fset: fset, list: tmp} // !r.eol() because len(comments) > 0 + r.next() + + // create node list in lexical order + nodes := nodeList(node) + nodes = append(nodes, nil) // append sentinel + + // set up iteration variables + var ( + p Node // previous node + pend token.Position // end of p + pg Node // previous node group (enclosing nodes of "importance") + pgend token.Position // end of pg + stack nodeStack // stack of node groups + ) + + for _, q := range nodes { + var qpos token.Position + if q != nil { + qpos = fset.Position(q.Pos()) // current node position + } else { + // set fake sentinel position to infinity so that + // all comments get processed before the sentinel + const infinity = 1 << 30 + qpos.Offset = infinity + qpos.Line = infinity + } + + // process comments before current node + for r.end.Offset <= qpos.Offset { + // determine recent node group + if top := stack.pop(r.comment.Pos()); top != nil { + pg = top + pgend = fset.Position(pg.End()) + } + // Try to associate a comment first with a node group + // (i.e., a node of "importance" such as a declaration); + // if that fails, try to associate it with the most recent + // node. + // TODO(gri) try to simplify the logic below + var assoc Node + switch { + case pg != nil && + (pgend.Line == r.pos.Line || + pgend.Line+1 == r.pos.Line && r.end.Line+1 < qpos.Line): + // 1) comment starts on same line as previous node group ends, or + // 2) comment starts on the line immediately after the + // previous node group and there is an empty line before + // the current node + // => associate comment with previous node group + assoc = pg + case p != nil && + (pend.Line == r.pos.Line || + pend.Line+1 == r.pos.Line && r.end.Line+1 < qpos.Line || + q == nil): + // same rules apply as above for p rather than pg, + // but also associate with p if we are at the end (q == nil) + assoc = p + default: + // otherwise, associate comment with current node + if q == nil { + // we can only reach here if there was no p + // which would imply that there were no nodes + panic("internal error: no comments should be associated with sentinel") + } + assoc = q + } + cmap.addComment(assoc, r.comment) + if r.eol() { + return cmap + } + r.next() + } + + // update previous node + p = q + pend = fset.Position(p.End()) + + // update previous node group if we see an "important" node + switch q.(type) { + case *File, *Field, Decl, Spec, Stmt: + stack.push(q) + } + } + + return cmap +} + +// Update replaces an old node in the comment map with the new node +// and returns the new node. Comments that were associated with the +// old node are associated with the new node. +// +func (cmap CommentMap) Update(old, new Node) Node { + if list := cmap[old]; len(list) > 0 { + delete(cmap, old) + cmap[new] = append(cmap[new], list...) + } + return new +} + +// Filter returns a new comment map consisting of only those +// entries of cmap for which a corresponding node exists in +// the AST specified by node. +// +func (cmap CommentMap) Filter(node Node) CommentMap { + umap := make(CommentMap) + Inspect(node, func(n Node) bool { + if g := cmap[n]; len(g) > 0 { + umap[n] = g + } + return true + }) + return umap +} + +// Comments returns the list of comment groups in the comment map. +// The result is sorted in source order. +// +func (cmap CommentMap) Comments() []*CommentGroup { + list := make([]*CommentGroup, 0, len(cmap)) + for _, e := range cmap { + list = append(list, e...) + } + sortComments(list) + return list +} + +func summary(list []*CommentGroup) string { + const maxLen = 40 + var buf bytes.Buffer + + // collect comments text +loop: + for _, group := range list { + // Note: CommentGroup.Text() does too much work for what we + // need and would only replace this innermost loop. + // Just do it explicitly. + for _, comment := range group.List { + if buf.Len() >= maxLen { + break loop + } + buf.WriteString(comment.Text) + } + } + + // truncate if too long + if buf.Len() > maxLen { + buf.Truncate(maxLen - 3) + buf.WriteString("...") + } + + // replace any invisibles with blanks + bytes := buf.Bytes() + for i, b := range bytes { + switch b { + case '\t', '\n', '\r': + bytes[i] = ' ' + } + } + + return string(bytes) +} + +func (cmap CommentMap) String() string { + var buf bytes.Buffer + fmt.Fprintln(&buf, "CommentMap {") + for node, comment := range cmap { + // print name of identifiers; print node type for other nodes + var s string + if ident, ok := node.(*Ident); ok { + s = ident.Name + } else { + s = fmt.Sprintf("%T", node) + } + fmt.Fprintf(&buf, "\t%p %20s: %s\n", node, s, summary(comment)) + } + fmt.Fprintln(&buf, "}") + return buf.String() +} diff --git a/src/go/ast/commentmap_test.go b/src/go/ast/commentmap_test.go new file mode 100644 index 0000000..38c62b0 --- /dev/null +++ b/src/go/ast/commentmap_test.go @@ -0,0 +1,170 @@ +// Copyright 2012 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. + +// To avoid a cyclic dependency with go/parser, this file is in a separate package. + +package ast_test + +import ( + "bytes" + "fmt" + . "go/ast" + "go/parser" + "go/token" + "sort" + "testing" +) + +const src = ` +// the very first comment + +// package p +package p /* the name is p */ + +// imports +import ( + "bytes" // bytes + "fmt" // fmt + "go/ast" + "go/parser" +) + +// T +type T struct { + a, b, c int // associated with a, b, c + // associated with x, y + x, y float64 // float values + z complex128 // complex value +} +// also associated with T + +// x +var x = 0 // x = 0 +// also associated with x + +// f1 +func f1() { + /* associated with s1 */ + s1() + // also associated with s1 + + // associated with s2 + + // also associated with s2 + s2() // line comment for s2 +} +// associated with f1 +// also associated with f1 + +// associated with f2 + +// f2 +func f2() { +} + +func f3() { + i := 1 /* 1 */ + 2 // addition + _ = i +} + +// the very last comment +` + +// res maps a key of the form "line number: node type" +// to the associated comments' text. +// +var res = map[string]string{ + " 5: *ast.File": "the very first comment\npackage p\n", + " 5: *ast.Ident": " the name is p\n", + " 8: *ast.GenDecl": "imports\n", + " 9: *ast.ImportSpec": "bytes\n", + "10: *ast.ImportSpec": "fmt\n", + "16: *ast.GenDecl": "T\nalso associated with T\n", + "17: *ast.Field": "associated with a, b, c\n", + "19: *ast.Field": "associated with x, y\nfloat values\n", + "20: *ast.Field": "complex value\n", + "25: *ast.GenDecl": "x\nx = 0\nalso associated with x\n", + "29: *ast.FuncDecl": "f1\nassociated with f1\nalso associated with f1\n", + "31: *ast.ExprStmt": " associated with s1\nalso associated with s1\n", + "37: *ast.ExprStmt": "associated with s2\nalso associated with s2\nline comment for s2\n", + "45: *ast.FuncDecl": "associated with f2\nf2\n", + "49: *ast.AssignStmt": "addition\n", + "49: *ast.BasicLit": " 1\n", + "50: *ast.Ident": "the very last comment\n", +} + +func ctext(list []*CommentGroup) string { + var buf bytes.Buffer + for _, g := range list { + buf.WriteString(g.Text()) + } + return buf.String() +} + +func TestCommentMap(t *testing.T) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + cmap := NewCommentMap(fset, f, f.Comments) + + // very correct association of comments + for n, list := range cmap { + key := fmt.Sprintf("%2d: %T", fset.Position(n.Pos()).Line, n) + got := ctext(list) + want := res[key] + if got != want { + t.Errorf("%s: got %q; want %q", key, got, want) + } + } + + // verify that no comments got lost + if n := len(cmap.Comments()); n != len(f.Comments) { + t.Errorf("got %d comment groups in map; want %d", n, len(f.Comments)) + } + + // support code to update test: + // set genMap to true to generate res map + const genMap = false + if genMap { + out := make([]string, 0, len(cmap)) + for n, list := range cmap { + out = append(out, fmt.Sprintf("\t\"%2d: %T\":\t%q,", fset.Position(n.Pos()).Line, n, ctext(list))) + } + sort.Strings(out) + for _, s := range out { + fmt.Println(s) + } + } +} + +func TestFilter(t *testing.T) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + cmap := NewCommentMap(fset, f, f.Comments) + + // delete variable declaration + for i, decl := range f.Decls { + if gen, ok := decl.(*GenDecl); ok && gen.Tok == token.VAR { + copy(f.Decls[i:], f.Decls[i+1:]) + f.Decls = f.Decls[:len(f.Decls)-1] + break + } + } + + // check if comments are filtered correctly + cc := cmap.Filter(f) + for n, list := range cc { + key := fmt.Sprintf("%2d: %T", fset.Position(n.Pos()).Line, n) + got := ctext(list) + want := res[key] + if key == "25: *ast.GenDecl" || got != want { + t.Errorf("%s: got %q; want %q", key, got, want) + } + } +} diff --git a/src/go/ast/example_test.go b/src/go/ast/example_test.go new file mode 100644 index 0000000..e3013f6 --- /dev/null +++ b/src/go/ast/example_test.go @@ -0,0 +1,206 @@ +// Copyright 2012 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 ast_test + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" +) + +// This example demonstrates how to inspect the AST of a Go program. +func ExampleInspect() { + // src is the input for which we want to inspect the AST. + src := ` +package p +const c = 1.0 +var X = f(3.14)*2 + c +` + + // Create the AST by parsing src. + fset := token.NewFileSet() // positions are relative to fset + f, err := parser.ParseFile(fset, "src.go", src, 0) + if err != nil { + panic(err) + } + + // Inspect the AST and print all identifiers and literals. + ast.Inspect(f, func(n ast.Node) bool { + var s string + switch x := n.(type) { + case *ast.BasicLit: + s = x.Value + case *ast.Ident: + s = x.Name + } + if s != "" { + fmt.Printf("%s:\t%s\n", fset.Position(n.Pos()), s) + } + return true + }) + + // Output: + // src.go:2:9: p + // src.go:3:7: c + // src.go:3:11: 1.0 + // src.go:4:5: X + // src.go:4:9: f + // src.go:4:11: 3.14 + // src.go:4:17: 2 + // src.go:4:21: c +} + +// This example shows what an AST looks like when printed for debugging. +func ExamplePrint() { + // src is the input for which we want to print the AST. + src := ` +package main +func main() { + println("Hello, World!") +} +` + + // Create the AST by parsing src. + fset := token.NewFileSet() // positions are relative to fset + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + panic(err) + } + + // Print the AST. + ast.Print(fset, f) + + // Output: + // 0 *ast.File { + // 1 . Package: 2:1 + // 2 . Name: *ast.Ident { + // 3 . . NamePos: 2:9 + // 4 . . Name: "main" + // 5 . } + // 6 . Decls: []ast.Decl (len = 1) { + // 7 . . 0: *ast.FuncDecl { + // 8 . . . Name: *ast.Ident { + // 9 . . . . NamePos: 3:6 + // 10 . . . . Name: "main" + // 11 . . . . Obj: *ast.Object { + // 12 . . . . . Kind: func + // 13 . . . . . Name: "main" + // 14 . . . . . Decl: *(obj @ 7) + // 15 . . . . } + // 16 . . . } + // 17 . . . Type: *ast.FuncType { + // 18 . . . . Func: 3:1 + // 19 . . . . Params: *ast.FieldList { + // 20 . . . . . Opening: 3:10 + // 21 . . . . . Closing: 3:11 + // 22 . . . . } + // 23 . . . } + // 24 . . . Body: *ast.BlockStmt { + // 25 . . . . Lbrace: 3:13 + // 26 . . . . List: []ast.Stmt (len = 1) { + // 27 . . . . . 0: *ast.ExprStmt { + // 28 . . . . . . X: *ast.CallExpr { + // 29 . . . . . . . Fun: *ast.Ident { + // 30 . . . . . . . . NamePos: 4:2 + // 31 . . . . . . . . Name: "println" + // 32 . . . . . . . } + // 33 . . . . . . . Lparen: 4:9 + // 34 . . . . . . . Args: []ast.Expr (len = 1) { + // 35 . . . . . . . . 0: *ast.BasicLit { + // 36 . . . . . . . . . ValuePos: 4:10 + // 37 . . . . . . . . . Kind: STRING + // 38 . . . . . . . . . Value: "\"Hello, World!\"" + // 39 . . . . . . . . } + // 40 . . . . . . . } + // 41 . . . . . . . Ellipsis: - + // 42 . . . . . . . Rparen: 4:25 + // 43 . . . . . . } + // 44 . . . . . } + // 45 . . . . } + // 46 . . . . Rbrace: 5:1 + // 47 . . . } + // 48 . . } + // 49 . } + // 50 . Scope: *ast.Scope { + // 51 . . Objects: map[string]*ast.Object (len = 1) { + // 52 . . . "main": *(obj @ 11) + // 53 . . } + // 54 . } + // 55 . Unresolved: []*ast.Ident (len = 1) { + // 56 . . 0: *(obj @ 29) + // 57 . } + // 58 } +} + +// This example illustrates how to remove a variable declaration +// in a Go program while maintaining correct comment association +// using an ast.CommentMap. +func ExampleCommentMap() { + // src is the input for which we create the AST that we + // are going to manipulate. + src := ` +// This is the package comment. +package main + +// This comment is associated with the hello constant. +const hello = "Hello, World!" // line comment 1 + +// This comment is associated with the foo variable. +var foo = hello // line comment 2 + +// This comment is associated with the main function. +func main() { + fmt.Println(hello) // line comment 3 +} +` + + // Create the AST by parsing src. + fset := token.NewFileSet() // positions are relative to fset + f, err := parser.ParseFile(fset, "src.go", src, parser.ParseComments) + if err != nil { + panic(err) + } + + // Create an ast.CommentMap from the ast.File's comments. + // This helps keeping the association between comments + // and AST nodes. + cmap := ast.NewCommentMap(fset, f, f.Comments) + + // Remove the first variable declaration from the list of declarations. + for i, decl := range f.Decls { + if gen, ok := decl.(*ast.GenDecl); ok && gen.Tok == token.VAR { + copy(f.Decls[i:], f.Decls[i+1:]) + f.Decls = f.Decls[:len(f.Decls)-1] + break + } + } + + // Use the comment map to filter comments that don't belong anymore + // (the comments associated with the variable declaration), and create + // the new comments list. + f.Comments = cmap.Filter(f).Comments() + + // Print the modified AST. + var buf bytes.Buffer + if err := format.Node(&buf, fset, f); err != nil { + panic(err) + } + fmt.Printf("%s", buf.Bytes()) + + // Output: + // // This is the package comment. + // package main + // + // // This comment is associated with the hello constant. + // const hello = "Hello, World!" // line comment 1 + // + // // This comment is associated with the main function. + // func main() { + // fmt.Println(hello) // line comment 3 + // } +} diff --git a/src/go/ast/filter.go b/src/go/ast/filter.go new file mode 100644 index 0000000..c398e6e --- /dev/null +++ b/src/go/ast/filter.go @@ -0,0 +1,497 @@ +// Copyright 2009 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 ast + +import ( + "go/token" + "sort" +) + +// ---------------------------------------------------------------------------- +// Export filtering + +// exportFilter is a special filter function to extract exported nodes. +func exportFilter(name string) bool { + return IsExported(name) +} + +// FileExports trims the AST for a Go source file in place such that +// only exported nodes remain: all top-level identifiers which are not exported +// and their associated information (such as type, initial value, or function +// body) are removed. Non-exported fields and methods of exported types are +// stripped. The File.Comments list is not changed. +// +// FileExports reports whether there are exported declarations. +// +func FileExports(src *File) bool { + return filterFile(src, exportFilter, true) +} + +// PackageExports trims the AST for a Go package in place such that +// only exported nodes remain. The pkg.Files list is not changed, so that +// file names and top-level package comments don't get lost. +// +// PackageExports reports whether there are exported declarations; +// it returns false otherwise. +// +func PackageExports(pkg *Package) bool { + return filterPackage(pkg, exportFilter, true) +} + +// ---------------------------------------------------------------------------- +// General filtering + +type Filter func(string) bool + +func filterIdentList(list []*Ident, f Filter) []*Ident { + j := 0 + for _, x := range list { + if f(x.Name) { + list[j] = x + j++ + } + } + return list[0:j] +} + +// fieldName assumes that x is the type of an anonymous field and +// returns the corresponding field name. If x is not an acceptable +// anonymous field, the result is nil. +// +func fieldName(x Expr) *Ident { + switch t := x.(type) { + case *Ident: + return t + case *SelectorExpr: + if _, ok := t.X.(*Ident); ok { + return t.Sel + } + case *StarExpr: + return fieldName(t.X) + } + return nil +} + +func filterFieldList(fields *FieldList, filter Filter, export bool) (removedFields bool) { + if fields == nil { + return false + } + list := fields.List + j := 0 + for _, f := range list { + keepField := false + if len(f.Names) == 0 { + // anonymous field + name := fieldName(f.Type) + keepField = name != nil && filter(name.Name) + } else { + n := len(f.Names) + f.Names = filterIdentList(f.Names, filter) + if len(f.Names) < n { + removedFields = true + } + keepField = len(f.Names) > 0 + } + if keepField { + if export { + filterType(f.Type, filter, export) + } + list[j] = f + j++ + } + } + if j < len(list) { + removedFields = true + } + fields.List = list[0:j] + return +} + +func filterCompositeLit(lit *CompositeLit, filter Filter, export bool) { + n := len(lit.Elts) + lit.Elts = filterExprList(lit.Elts, filter, export) + if len(lit.Elts) < n { + lit.Incomplete = true + } +} + +func filterExprList(list []Expr, filter Filter, export bool) []Expr { + j := 0 + for _, exp := range list { + switch x := exp.(type) { + case *CompositeLit: + filterCompositeLit(x, filter, export) + case *KeyValueExpr: + if x, ok := x.Key.(*Ident); ok && !filter(x.Name) { + continue + } + if x, ok := x.Value.(*CompositeLit); ok { + filterCompositeLit(x, filter, export) + } + } + list[j] = exp + j++ + } + return list[0:j] +} + +func filterParamList(fields *FieldList, filter Filter, export bool) bool { + if fields == nil { + return false + } + var b bool + for _, f := range fields.List { + if filterType(f.Type, filter, export) { + b = true + } + } + return b +} + +func filterType(typ Expr, f Filter, export bool) bool { + switch t := typ.(type) { + case *Ident: + return f(t.Name) + case *ParenExpr: + return filterType(t.X, f, export) + case *ArrayType: + return filterType(t.Elt, f, export) + case *StructType: + if filterFieldList(t.Fields, f, export) { + t.Incomplete = true + } + return len(t.Fields.List) > 0 + case *FuncType: + b1 := filterParamList(t.Params, f, export) + b2 := filterParamList(t.Results, f, export) + return b1 || b2 + case *InterfaceType: + if filterFieldList(t.Methods, f, export) { + t.Incomplete = true + } + return len(t.Methods.List) > 0 + case *MapType: + b1 := filterType(t.Key, f, export) + b2 := filterType(t.Value, f, export) + return b1 || b2 + case *ChanType: + return filterType(t.Value, f, export) + } + return false +} + +func filterSpec(spec Spec, f Filter, export bool) bool { + switch s := spec.(type) { + case *ValueSpec: + s.Names = filterIdentList(s.Names, f) + s.Values = filterExprList(s.Values, f, export) + if len(s.Names) > 0 { + if export { + filterType(s.Type, f, export) + } + return true + } + case *TypeSpec: + if f(s.Name.Name) { + if export { + filterType(s.Type, f, export) + } + return true + } + if !export { + // For general filtering (not just exports), + // filter type even if name is not filtered + // out. + // If the type contains filtered elements, + // keep the declaration. + return filterType(s.Type, f, export) + } + } + return false +} + +func filterSpecList(list []Spec, f Filter, export bool) []Spec { + j := 0 + for _, s := range list { + if filterSpec(s, f, export) { + list[j] = s + j++ + } + } + return list[0:j] +} + +// FilterDecl trims the AST for a Go declaration in place by removing +// all names (including struct field and interface method names, but +// not from parameter lists) that don't pass through the filter f. +// +// FilterDecl reports whether there are any declared names left after +// filtering. +// +func FilterDecl(decl Decl, f Filter) bool { + return filterDecl(decl, f, false) +} + +func filterDecl(decl Decl, f Filter, export bool) bool { + switch d := decl.(type) { + case *GenDecl: + d.Specs = filterSpecList(d.Specs, f, export) + return len(d.Specs) > 0 + case *FuncDecl: + return f(d.Name.Name) + } + return false +} + +// FilterFile trims the AST for a Go file in place by removing all +// names from top-level declarations (including struct field and +// interface method names, but not from parameter lists) that don't +// pass through the filter f. If the declaration is empty afterwards, +// the declaration is removed from the AST. Import declarations are +// always removed. The File.Comments list is not changed. +// +// FilterFile reports whether there are any top-level declarations +// left after filtering. +// +func FilterFile(src *File, f Filter) bool { + return filterFile(src, f, false) +} + +func filterFile(src *File, f Filter, export bool) bool { + j := 0 + for _, d := range src.Decls { + if filterDecl(d, f, export) { + src.Decls[j] = d + j++ + } + } + src.Decls = src.Decls[0:j] + return j > 0 +} + +// FilterPackage trims the AST for a Go package in place by removing +// all names from top-level declarations (including struct field and +// interface method names, but not from parameter lists) that don't +// pass through the filter f. If the declaration is empty afterwards, +// the declaration is removed from the AST. The pkg.Files list is not +// changed, so that file names and top-level package comments don't get +// lost. +// +// FilterPackage reports whether there are any top-level declarations +// left after filtering. +// +func FilterPackage(pkg *Package, f Filter) bool { + return filterPackage(pkg, f, false) +} + +func filterPackage(pkg *Package, f Filter, export bool) bool { + hasDecls := false + for _, src := range pkg.Files { + if filterFile(src, f, export) { + hasDecls = true + } + } + return hasDecls +} + +// ---------------------------------------------------------------------------- +// Merging of package files + +// The MergeMode flags control the behavior of MergePackageFiles. +type MergeMode uint + +const ( + // If set, duplicate function declarations are excluded. + FilterFuncDuplicates MergeMode = 1 << iota + // If set, comments that are not associated with a specific + // AST node (as Doc or Comment) are excluded. + FilterUnassociatedComments + // If set, duplicate import declarations are excluded. + FilterImportDuplicates +) + +// nameOf returns the function (foo) or method name (foo.bar) for +// the given function declaration. If the AST is incorrect for the +// receiver, it assumes a function instead. +// +func nameOf(f *FuncDecl) string { + if r := f.Recv; r != nil && len(r.List) == 1 { + // looks like a correct receiver declaration + t := r.List[0].Type + // dereference pointer receiver types + if p, _ := t.(*StarExpr); p != nil { + t = p.X + } + // the receiver type must be a type name + if p, _ := t.(*Ident); p != nil { + return p.Name + "." + f.Name.Name + } + // otherwise assume a function instead + } + return f.Name.Name +} + +// separator is an empty //-style comment that is interspersed between +// different comment groups when they are concatenated into a single group +// +var separator = &Comment{token.NoPos, "//"} + +// MergePackageFiles creates a file AST by merging the ASTs of the +// files belonging to a package. The mode flags control merging behavior. +// +func MergePackageFiles(pkg *Package, mode MergeMode) *File { + // Count the number of package docs, comments and declarations across + // all package files. Also, compute sorted list of filenames, so that + // subsequent iterations can always iterate in the same order. + ndocs := 0 + ncomments := 0 + ndecls := 0 + filenames := make([]string, len(pkg.Files)) + i := 0 + for filename, f := range pkg.Files { + filenames[i] = filename + i++ + if f.Doc != nil { + ndocs += len(f.Doc.List) + 1 // +1 for separator + } + ncomments += len(f.Comments) + ndecls += len(f.Decls) + } + sort.Strings(filenames) + + // Collect package comments from all package files into a single + // CommentGroup - the collected package documentation. In general + // there should be only one file with a package comment; but it's + // better to collect extra comments than drop them on the floor. + var doc *CommentGroup + var pos token.Pos + if ndocs > 0 { + list := make([]*Comment, ndocs-1) // -1: no separator before first group + i := 0 + for _, filename := range filenames { + f := pkg.Files[filename] + if f.Doc != nil { + if i > 0 { + // not the first group - add separator + list[i] = separator + i++ + } + for _, c := range f.Doc.List { + list[i] = c + i++ + } + if f.Package > pos { + // Keep the maximum package clause position as + // position for the package clause of the merged + // files. + pos = f.Package + } + } + } + doc = &CommentGroup{list} + } + + // Collect declarations from all package files. + var decls []Decl + if ndecls > 0 { + decls = make([]Decl, ndecls) + funcs := make(map[string]int) // map of func name -> decls index + i := 0 // current index + n := 0 // number of filtered entries + for _, filename := range filenames { + f := pkg.Files[filename] + for _, d := range f.Decls { + if mode&FilterFuncDuplicates != 0 { + // A language entity may be declared multiple + // times in different package files; only at + // build time declarations must be unique. + // For now, exclude multiple declarations of + // functions - keep the one with documentation. + // + // TODO(gri): Expand this filtering to other + // entities (const, type, vars) if + // multiple declarations are common. + if f, isFun := d.(*FuncDecl); isFun { + name := nameOf(f) + if j, exists := funcs[name]; exists { + // function declared already + if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil { + // existing declaration has no documentation; + // ignore the existing declaration + decls[j] = nil + } else { + // ignore the new declaration + d = nil + } + n++ // filtered an entry + } else { + funcs[name] = i + } + } + } + decls[i] = d + i++ + } + } + + // Eliminate nil entries from the decls list if entries were + // filtered. We do this using a 2nd pass in order to not disturb + // the original declaration order in the source (otherwise, this + // would also invalidate the monotonically increasing position + // info within a single file). + if n > 0 { + i = 0 + for _, d := range decls { + if d != nil { + decls[i] = d + i++ + } + } + decls = decls[0:i] + } + } + + // Collect import specs from all package files. + var imports []*ImportSpec + if mode&FilterImportDuplicates != 0 { + seen := make(map[string]bool) + for _, filename := range filenames { + f := pkg.Files[filename] + for _, imp := range f.Imports { + if path := imp.Path.Value; !seen[path] { + // TODO: consider handling cases where: + // - 2 imports exist with the same import path but + // have different local names (one should probably + // keep both of them) + // - 2 imports exist but only one has a comment + // - 2 imports exist and they both have (possibly + // different) comments + imports = append(imports, imp) + seen[path] = true + } + } + } + } else { + // Iterate over filenames for deterministic order. + for _, filename := range filenames { + f := pkg.Files[filename] + imports = append(imports, f.Imports...) + } + } + + // Collect comments from all package files. + var comments []*CommentGroup + if mode&FilterUnassociatedComments == 0 { + comments = make([]*CommentGroup, ncomments) + i := 0 + for _, filename := range filenames { + f := pkg.Files[filename] + i += copy(comments[i:], f.Comments) + } + } + + // TODO(gri) need to compute unresolved identifiers! + return &File{doc, pos, NewIdent(pkg.Name), decls, pkg.Scope, imports, nil, comments} +} diff --git a/src/go/ast/filter_test.go b/src/go/ast/filter_test.go new file mode 100644 index 0000000..9fd86cb --- /dev/null +++ b/src/go/ast/filter_test.go @@ -0,0 +1,86 @@ +// Copyright 2013 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. + +// To avoid a cyclic dependency with go/parser, this file is in a separate package. + +package ast_test + +import ( + "bytes" + "go/ast" + "go/format" + "go/parser" + "go/token" + "testing" +) + +const input = `package p + +type t1 struct{} +type t2 struct{} + +func f1() {} +func f1() {} +func f2() {} + +func (*t1) f1() {} +func (t1) f1() {} +func (t1) f2() {} + +func (t2) f1() {} +func (t2) f2() {} +func (x *t2) f2() {} +` + +// Calling ast.MergePackageFiles with ast.FilterFuncDuplicates +// keeps a duplicate entry with attached documentation in favor +// of one without, and it favors duplicate entries appearing +// later in the source over ones appearing earlier. This is why +// (*t2).f2 is kept and t2.f2 is eliminated in this test case. +// +const golden = `package p + +type t1 struct{} +type t2 struct{} + +func f1() {} +func f2() {} + +func (t1) f1() {} +func (t1) f2() {} + +func (t2) f1() {} + +func (x *t2) f2() {} +` + +func TestFilterDuplicates(t *testing.T) { + // parse input + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "", input, 0) + if err != nil { + t.Fatal(err) + } + + // create package + files := map[string]*ast.File{"": file} + pkg, err := ast.NewPackage(fset, files, nil, nil) + if err != nil { + t.Fatal(err) + } + + // filter + merged := ast.MergePackageFiles(pkg, ast.FilterFuncDuplicates) + + // pretty-print + var buf bytes.Buffer + if err := format.Node(&buf, fset, merged); err != nil { + t.Fatal(err) + } + output := buf.String() + + if output != golden { + t.Errorf("incorrect output:\n%s", output) + } +} diff --git a/src/go/ast/import.go b/src/go/ast/import.go new file mode 100644 index 0000000..7fdf137 --- /dev/null +++ b/src/go/ast/import.go @@ -0,0 +1,230 @@ +// Copyright 2011 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 ast + +import ( + "go/token" + "sort" + "strconv" +) + +// SortImports sorts runs of consecutive import lines in import blocks in f. +// It also removes duplicate imports when it is possible to do so without data loss. +func SortImports(fset *token.FileSet, f *File) { + for _, d := range f.Decls { + d, ok := d.(*GenDecl) + if !ok || d.Tok != token.IMPORT { + // Not an import declaration, so we're done. + // Imports are always first. + break + } + + if !d.Lparen.IsValid() { + // Not a block: sorted by default. + continue + } + + // Identify and sort runs of specs on successive lines. + i := 0 + specs := d.Specs[:0] + for j, s := range d.Specs { + if j > i && lineAt(fset, s.Pos()) > 1+lineAt(fset, d.Specs[j-1].End()) { + // j begins a new run. End this one. + specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...) + i = j + } + } + specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...) + d.Specs = specs + + // Deduping can leave a blank line before the rparen; clean that up. + if len(d.Specs) > 0 { + lastSpec := d.Specs[len(d.Specs)-1] + lastLine := lineAt(fset, lastSpec.Pos()) + rParenLine := lineAt(fset, d.Rparen) + for rParenLine > lastLine+1 { + rParenLine-- + fset.File(d.Rparen).MergeLine(rParenLine) + } + } + } +} + +func lineAt(fset *token.FileSet, pos token.Pos) int { + return fset.PositionFor(pos, false).Line +} + +func importPath(s Spec) string { + t, err := strconv.Unquote(s.(*ImportSpec).Path.Value) + if err == nil { + return t + } + return "" +} + +func importName(s Spec) string { + n := s.(*ImportSpec).Name + if n == nil { + return "" + } + return n.Name +} + +func importComment(s Spec) string { + c := s.(*ImportSpec).Comment + if c == nil { + return "" + } + return c.Text() +} + +// collapse indicates whether prev may be removed, leaving only next. +func collapse(prev, next Spec) bool { + if importPath(next) != importPath(prev) || importName(next) != importName(prev) { + return false + } + return prev.(*ImportSpec).Comment == nil +} + +type posSpan struct { + Start token.Pos + End token.Pos +} + +type cgPos struct { + left bool // true if comment is to the left of the spec, false otherwise. + cg *CommentGroup +} + +func sortSpecs(fset *token.FileSet, f *File, specs []Spec) []Spec { + // Can't short-circuit here even if specs are already sorted, + // since they might yet need deduplication. + // A lone import, however, may be safely ignored. + if len(specs) <= 1 { + return specs + } + + // Record positions for specs. + pos := make([]posSpan, len(specs)) + for i, s := range specs { + pos[i] = posSpan{s.Pos(), s.End()} + } + + // Identify comments in this range. + begSpecs := pos[0].Start + endSpecs := pos[len(pos)-1].End + beg := fset.File(begSpecs).LineStart(lineAt(fset, begSpecs)) + endLine := lineAt(fset, endSpecs) + endFile := fset.File(endSpecs) + var end token.Pos + if endLine == endFile.LineCount() { + end = endSpecs + } else { + end = endFile.LineStart(endLine + 1) // beginning of next line + } + first := len(f.Comments) + last := -1 + for i, g := range f.Comments { + if g.End() >= end { + break + } + // g.End() < end + if beg <= g.Pos() { + // comment is within the range [beg, end[ of import declarations + if i < first { + first = i + } + if i > last { + last = i + } + } + } + + var comments []*CommentGroup + if last >= 0 { + comments = f.Comments[first : last+1] + } + + // Assign each comment to the import spec on the same line. + importComments := map[*ImportSpec][]cgPos{} + specIndex := 0 + for _, g := range comments { + for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() { + specIndex++ + } + var left bool + // A block comment can appear before the first import spec. + if specIndex == 0 && pos[specIndex].Start > g.Pos() { + left = true + } else if specIndex+1 < len(specs) && // Or it can appear on the left of an import spec. + lineAt(fset, pos[specIndex].Start)+1 == lineAt(fset, g.Pos()) { + specIndex++ + left = true + } + s := specs[specIndex].(*ImportSpec) + importComments[s] = append(importComments[s], cgPos{left: left, cg: g}) + } + + // Sort the import specs by import path. + // Remove duplicates, when possible without data loss. + // Reassign the import paths to have the same position sequence. + // Reassign each comment to the spec on the same line. + // Sort the comments by new position. + sort.Slice(specs, func(i, j int) bool { + ipath := importPath(specs[i]) + jpath := importPath(specs[j]) + if ipath != jpath { + return ipath < jpath + } + iname := importName(specs[i]) + jname := importName(specs[j]) + if iname != jname { + return iname < jname + } + return importComment(specs[i]) < importComment(specs[j]) + }) + + // Dedup. Thanks to our sorting, we can just consider + // adjacent pairs of imports. + deduped := specs[:0] + for i, s := range specs { + if i == len(specs)-1 || !collapse(s, specs[i+1]) { + deduped = append(deduped, s) + } else { + p := s.Pos() + fset.File(p).MergeLine(lineAt(fset, p)) + } + } + specs = deduped + + // Fix up comment positions + for i, s := range specs { + s := s.(*ImportSpec) + if s.Name != nil { + s.Name.NamePos = pos[i].Start + } + s.Path.ValuePos = pos[i].Start + s.EndPos = pos[i].End + for _, g := range importComments[s] { + for _, c := range g.cg.List { + if g.left { + c.Slash = pos[i].Start - 1 + } else { + // An import spec can have both block comment and a line comment + // to its right. In that case, both of them will have the same pos. + // But while formatting the AST, the line comment gets moved to + // after the block comment. + c.Slash = pos[i].End + } + } + } + } + + sort.Slice(comments, func(i, j int) bool { + return comments[i].Pos() < comments[j].Pos() + }) + + return specs +} diff --git a/src/go/ast/issues_test.go b/src/go/ast/issues_test.go new file mode 100644 index 0000000..788c557 --- /dev/null +++ b/src/go/ast/issues_test.go @@ -0,0 +1,42 @@ +// Copyright 2019 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 ast_test + +import ( + "go/ast" + "go/parser" + "go/token" + "testing" +) + +func TestIssue33649(t *testing.T) { + for _, src := range []string{ + `package p; func _()`, + `package p; func _() {`, + `package p; func _() { _ = 0`, + `package p; func _() { _ = 0 }`, + } { + fset := token.NewFileSet() + f, _ := parser.ParseFile(fset, "", src, parser.AllErrors) + if f == nil { + panic("invalid test setup: parser didn't return an AST") + } + + // find corresponding token.File + var tf *token.File + fset.Iterate(func(f *token.File) bool { + tf = f + return true + }) + tfEnd := tf.Base() + tf.Size() + + fd := f.Decls[len(f.Decls)-1].(*ast.FuncDecl) + fdEnd := int(fd.End()) + + if fdEnd != tfEnd { + t.Errorf("%q: got fdEnd = %d; want %d (base = %d, size = %d)", src, fdEnd, tfEnd, tf.Base(), tf.Size()) + } + } +} diff --git a/src/go/ast/print.go b/src/go/ast/print.go new file mode 100644 index 0000000..d86d9ba --- /dev/null +++ b/src/go/ast/print.go @@ -0,0 +1,254 @@ +// Copyright 2010 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. + +// This file contains printing support for ASTs. + +package ast + +import ( + "fmt" + "go/token" + "io" + "os" + "reflect" +) + +// A FieldFilter may be provided to Fprint to control the output. +type FieldFilter func(name string, value reflect.Value) bool + +// NotNilFilter returns true for field values that are not nil; +// it returns false otherwise. +func NotNilFilter(_ string, v reflect.Value) bool { + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return !v.IsNil() + } + return true +} + +// Fprint prints the (sub-)tree starting at AST node x to w. +// If fset != nil, position information is interpreted relative +// to that file set. Otherwise positions are printed as integer +// values (file set specific offsets). +// +// A non-nil FieldFilter f may be provided to control the output: +// struct fields for which f(fieldname, fieldvalue) is true are +// printed; all others are filtered from the output. Unexported +// struct fields are never printed. +func Fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) error { + return fprint(w, fset, x, f) +} + +func fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (err error) { + // setup printer + p := printer{ + output: w, + fset: fset, + filter: f, + ptrmap: make(map[interface{}]int), + last: '\n', // force printing of line number on first line + } + + // install error handler + defer func() { + if e := recover(); e != nil { + err = e.(localError).err // re-panics if it's not a localError + } + }() + + // print x + if x == nil { + p.printf("nil\n") + return + } + p.print(reflect.ValueOf(x)) + p.printf("\n") + + return +} + +// Print prints x to standard output, skipping nil fields. +// Print(fset, x) is the same as Fprint(os.Stdout, fset, x, NotNilFilter). +func Print(fset *token.FileSet, x interface{}) error { + return Fprint(os.Stdout, fset, x, NotNilFilter) +} + +type printer struct { + output io.Writer + fset *token.FileSet + filter FieldFilter + ptrmap map[interface{}]int // *T -> line number + indent int // current indentation level + last byte // the last byte processed by Write + line int // current line number +} + +var indent = []byte(". ") + +func (p *printer) Write(data []byte) (n int, err error) { + var m int + for i, b := range data { + // invariant: data[0:n] has been written + if b == '\n' { + m, err = p.output.Write(data[n : i+1]) + n += m + if err != nil { + return + } + p.line++ + } else if p.last == '\n' { + _, err = fmt.Fprintf(p.output, "%6d ", p.line) + if err != nil { + return + } + for j := p.indent; j > 0; j-- { + _, err = p.output.Write(indent) + if err != nil { + return + } + } + } + p.last = b + } + if len(data) > n { + m, err = p.output.Write(data[n:]) + n += m + } + return +} + +// localError wraps locally caught errors so we can distinguish +// them from genuine panics which we don't want to return as errors. +type localError struct { + err error +} + +// printf is a convenience wrapper that takes care of print errors. +func (p *printer) printf(format string, args ...interface{}) { + if _, err := fmt.Fprintf(p, format, args...); err != nil { + panic(localError{err}) + } +} + +// Implementation note: Print is written for AST nodes but could be +// used to print arbitrary data structures; such a version should +// probably be in a different package. +// +// Note: This code detects (some) cycles created via pointers but +// not cycles that are created via slices or maps containing the +// same slice or map. Code for general data structures probably +// should catch those as well. + +func (p *printer) print(x reflect.Value) { + if !NotNilFilter("", x) { + p.printf("nil") + return + } + + switch x.Kind() { + case reflect.Interface: + p.print(x.Elem()) + + case reflect.Map: + p.printf("%s (len = %d) {", x.Type(), x.Len()) + if x.Len() > 0 { + p.indent++ + p.printf("\n") + for _, key := range x.MapKeys() { + p.print(key) + p.printf(": ") + p.print(x.MapIndex(key)) + p.printf("\n") + } + p.indent-- + } + p.printf("}") + + case reflect.Ptr: + p.printf("*") + // type-checked ASTs may contain cycles - use ptrmap + // to keep track of objects that have been printed + // already and print the respective line number instead + ptr := x.Interface() + if line, exists := p.ptrmap[ptr]; exists { + p.printf("(obj @ %d)", line) + } else { + p.ptrmap[ptr] = p.line + p.print(x.Elem()) + } + + case reflect.Array: + p.printf("%s {", x.Type()) + if x.Len() > 0 { + p.indent++ + p.printf("\n") + for i, n := 0, x.Len(); i < n; i++ { + p.printf("%d: ", i) + p.print(x.Index(i)) + p.printf("\n") + } + p.indent-- + } + p.printf("}") + + case reflect.Slice: + if s, ok := x.Interface().([]byte); ok { + p.printf("%#q", s) + return + } + p.printf("%s (len = %d) {", x.Type(), x.Len()) + if x.Len() > 0 { + p.indent++ + p.printf("\n") + for i, n := 0, x.Len(); i < n; i++ { + p.printf("%d: ", i) + p.print(x.Index(i)) + p.printf("\n") + } + p.indent-- + } + p.printf("}") + + case reflect.Struct: + t := x.Type() + p.printf("%s {", t) + p.indent++ + first := true + for i, n := 0, t.NumField(); i < n; i++ { + // exclude non-exported fields because their + // values cannot be accessed via reflection + if name := t.Field(i).Name; IsExported(name) { + value := x.Field(i) + if p.filter == nil || p.filter(name, value) { + if first { + p.printf("\n") + first = false + } + p.printf("%s: ", name) + p.print(value) + p.printf("\n") + } + } + } + p.indent-- + p.printf("}") + + default: + v := x.Interface() + switch v := v.(type) { + case string: + // print strings in quotes + p.printf("%q", v) + return + case token.Pos: + // position values can be printed nicely if we have a file set + if p.fset != nil { + p.printf("%s", p.fset.Position(v)) + return + } + } + // default + p.printf("%v", v) + } +} diff --git a/src/go/ast/print_test.go b/src/go/ast/print_test.go new file mode 100644 index 0000000..210f164 --- /dev/null +++ b/src/go/ast/print_test.go @@ -0,0 +1,97 @@ +// Copyright 2011 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 ast + +import ( + "bytes" + "strings" + "testing" +) + +var tests = []struct { + x interface{} // x is printed as s + s string +}{ + // basic types + {nil, "0 nil"}, + {true, "0 true"}, + {42, "0 42"}, + {3.14, "0 3.14"}, + {1 + 2.718i, "0 (1+2.718i)"}, + {"foobar", "0 \"foobar\""}, + + // maps + {map[Expr]string{}, `0 map[ast.Expr]string (len = 0) {}`}, + {map[string]int{"a": 1}, + `0 map[string]int (len = 1) { + 1 . "a": 1 + 2 }`}, + + // pointers + {new(int), "0 *0"}, + + // arrays + {[0]int{}, `0 [0]int {}`}, + {[3]int{1, 2, 3}, + `0 [3]int { + 1 . 0: 1 + 2 . 1: 2 + 3 . 2: 3 + 4 }`}, + {[...]int{42}, + `0 [1]int { + 1 . 0: 42 + 2 }`}, + + // slices + {[]int{}, `0 []int (len = 0) {}`}, + {[]int{1, 2, 3}, + `0 []int (len = 3) { + 1 . 0: 1 + 2 . 1: 2 + 3 . 2: 3 + 4 }`}, + + // structs + {struct{}{}, `0 struct {} {}`}, + {struct{ x int }{007}, `0 struct { x int } {}`}, + {struct{ X, y int }{42, 991}, + `0 struct { X int; y int } { + 1 . X: 42 + 2 }`}, + {struct{ X, Y int }{42, 991}, + `0 struct { X int; Y int } { + 1 . X: 42 + 2 . Y: 991 + 3 }`}, +} + +// Split s into lines, trim whitespace from all lines, and return +// the concatenated non-empty lines. +func trim(s string) string { + lines := strings.Split(s, "\n") + i := 0 + for _, line := range lines { + line = strings.TrimSpace(line) + if line != "" { + lines[i] = line + i++ + } + } + return strings.Join(lines[0:i], "\n") +} + +func TestPrint(t *testing.T) { + var buf bytes.Buffer + for _, test := range tests { + buf.Reset() + if err := Fprint(&buf, nil, test.x, nil); err != nil { + t.Errorf("Fprint failed: %s", err) + } + if s, ts := trim(buf.String()), trim(test.s); s != ts { + t.Errorf("got:\n%s\nexpected:\n%s\n", s, ts) + } + } +} diff --git a/src/go/ast/resolve.go b/src/go/ast/resolve.go new file mode 100644 index 0000000..c1830b5 --- /dev/null +++ b/src/go/ast/resolve.go @@ -0,0 +1,174 @@ +// Copyright 2011 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. + +// This file implements NewPackage. + +package ast + +import ( + "fmt" + "go/scanner" + "go/token" + "strconv" +) + +type pkgBuilder struct { + fset *token.FileSet + errors scanner.ErrorList +} + +func (p *pkgBuilder) error(pos token.Pos, msg string) { + p.errors.Add(p.fset.Position(pos), msg) +} + +func (p *pkgBuilder) errorf(pos token.Pos, format string, args ...interface{}) { + p.error(pos, fmt.Sprintf(format, args...)) +} + +func (p *pkgBuilder) declare(scope, altScope *Scope, obj *Object) { + alt := scope.Insert(obj) + if alt == nil && altScope != nil { + // see if there is a conflicting declaration in altScope + alt = altScope.Lookup(obj.Name) + } + if alt != nil { + prevDecl := "" + if pos := alt.Pos(); pos.IsValid() { + prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", p.fset.Position(pos)) + } + p.error(obj.Pos(), fmt.Sprintf("%s redeclared in this block%s", obj.Name, prevDecl)) + } +} + +func resolve(scope *Scope, ident *Ident) bool { + for ; scope != nil; scope = scope.Outer { + if obj := scope.Lookup(ident.Name); obj != nil { + ident.Obj = obj + return true + } + } + return false +} + +// An Importer resolves import paths to package Objects. +// The imports map records the packages already imported, +// indexed by package id (canonical import path). +// An Importer must determine the canonical import path and +// check the map to see if it is already present in the imports map. +// If so, the Importer can return the map entry. Otherwise, the +// Importer should load the package data for the given path into +// a new *Object (pkg), record pkg in the imports map, and then +// return pkg. +type Importer func(imports map[string]*Object, path string) (pkg *Object, err error) + +// NewPackage creates a new Package node from a set of File nodes. It resolves +// unresolved identifiers across files and updates each file's Unresolved list +// accordingly. If a non-nil importer and universe scope are provided, they are +// used to resolve identifiers not declared in any of the package files. Any +// remaining unresolved identifiers are reported as undeclared. If the files +// belong to different packages, one package name is selected and files with +// different package names are reported and then ignored. +// The result is a package node and a scanner.ErrorList if there were errors. +// +func NewPackage(fset *token.FileSet, files map[string]*File, importer Importer, universe *Scope) (*Package, error) { + var p pkgBuilder + p.fset = fset + + // complete package scope + pkgName := "" + pkgScope := NewScope(universe) + for _, file := range files { + // package names must match + switch name := file.Name.Name; { + case pkgName == "": + pkgName = name + case name != pkgName: + p.errorf(file.Package, "package %s; expected %s", name, pkgName) + continue // ignore this file + } + + // collect top-level file objects in package scope + for _, obj := range file.Scope.Objects { + p.declare(pkgScope, nil, obj) + } + } + + // package global mapping of imported package ids to package objects + imports := make(map[string]*Object) + + // complete file scopes with imports and resolve identifiers + for _, file := range files { + // ignore file if it belongs to a different package + // (error has already been reported) + if file.Name.Name != pkgName { + continue + } + + // build file scope by processing all imports + importErrors := false + fileScope := NewScope(pkgScope) + for _, spec := range file.Imports { + if importer == nil { + importErrors = true + continue + } + path, _ := strconv.Unquote(spec.Path.Value) + pkg, err := importer(imports, path) + if err != nil { + p.errorf(spec.Path.Pos(), "could not import %s (%s)", path, err) + importErrors = true + continue + } + // TODO(gri) If a local package name != "." is provided, + // global identifier resolution could proceed even if the + // import failed. Consider adjusting the logic here a bit. + + // local name overrides imported package name + name := pkg.Name + if spec.Name != nil { + name = spec.Name.Name + } + + // add import to file scope + if name == "." { + // merge imported scope with file scope + for _, obj := range pkg.Data.(*Scope).Objects { + p.declare(fileScope, pkgScope, obj) + } + } else if name != "_" { + // declare imported package object in file scope + // (do not re-use pkg in the file scope but create + // a new object instead; the Decl field is different + // for different files) + obj := NewObj(Pkg, name) + obj.Decl = spec + obj.Data = pkg.Data + p.declare(fileScope, pkgScope, obj) + } + } + + // resolve identifiers + if importErrors { + // don't use the universe scope without correct imports + // (objects in the universe may be shadowed by imports; + // with missing imports, identifiers might get resolved + // incorrectly to universe objects) + pkgScope.Outer = nil + } + i := 0 + for _, ident := range file.Unresolved { + if !resolve(fileScope, ident) { + p.errorf(ident.Pos(), "undeclared name: %s", ident.Name) + file.Unresolved[i] = ident + i++ + } + + } + file.Unresolved = file.Unresolved[0:i] + pkgScope.Outer = universe // reset universe scope + } + + p.errors.Sort() + return &Package{pkgName, pkgScope, imports, files}, p.errors.Err() +} diff --git a/src/go/ast/scope.go b/src/go/ast/scope.go new file mode 100644 index 0000000..a400c71 --- /dev/null +++ b/src/go/ast/scope.go @@ -0,0 +1,160 @@ +// Copyright 2009 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. + +// This file implements scopes and the objects they contain. + +package ast + +import ( + "bytes" + "fmt" + "go/token" +) + +// A Scope maintains the set of named language entities declared +// in the scope and a link to the immediately surrounding (outer) +// scope. +// +type Scope struct { + Outer *Scope + Objects map[string]*Object +} + +// NewScope creates a new scope nested in the outer scope. +func NewScope(outer *Scope) *Scope { + const n = 4 // initial scope capacity + return &Scope{outer, make(map[string]*Object, n)} +} + +// Lookup returns the object with the given name if it is +// found in scope s, otherwise it returns nil. Outer scopes +// are ignored. +// +func (s *Scope) Lookup(name string) *Object { + return s.Objects[name] +} + +// Insert attempts to insert a named object obj into the scope s. +// If the scope already contains an object alt with the same name, +// Insert leaves the scope unchanged and returns alt. Otherwise +// it inserts obj and returns nil. +// +func (s *Scope) Insert(obj *Object) (alt *Object) { + if alt = s.Objects[obj.Name]; alt == nil { + s.Objects[obj.Name] = obj + } + return +} + +// Debugging support +func (s *Scope) String() string { + var buf bytes.Buffer + fmt.Fprintf(&buf, "scope %p {", s) + if s != nil && len(s.Objects) > 0 { + fmt.Fprintln(&buf) + for _, obj := range s.Objects { + fmt.Fprintf(&buf, "\t%s %s\n", obj.Kind, obj.Name) + } + } + fmt.Fprintf(&buf, "}\n") + return buf.String() +} + +// ---------------------------------------------------------------------------- +// Objects + +// An Object describes a named language entity such as a package, +// constant, type, variable, function (incl. methods), or label. +// +// The Data fields contains object-specific data: +// +// Kind Data type Data value +// Pkg *Scope package scope +// Con int iota for the respective declaration +// +type Object struct { + Kind ObjKind + Name string // declared name + Decl interface{} // corresponding Field, XxxSpec, FuncDecl, LabeledStmt, AssignStmt, Scope; or nil + Data interface{} // object-specific data; or nil + Type interface{} // placeholder for type information; may be nil +} + +// NewObj creates a new object of a given kind and name. +func NewObj(kind ObjKind, name string) *Object { + return &Object{Kind: kind, Name: name} +} + +// Pos computes the source position of the declaration of an object name. +// The result may be an invalid position if it cannot be computed +// (obj.Decl may be nil or not correct). +func (obj *Object) Pos() token.Pos { + name := obj.Name + switch d := obj.Decl.(type) { + case *Field: + for _, n := range d.Names { + if n.Name == name { + return n.Pos() + } + } + case *ImportSpec: + if d.Name != nil && d.Name.Name == name { + return d.Name.Pos() + } + return d.Path.Pos() + case *ValueSpec: + for _, n := range d.Names { + if n.Name == name { + return n.Pos() + } + } + case *TypeSpec: + if d.Name.Name == name { + return d.Name.Pos() + } + case *FuncDecl: + if d.Name.Name == name { + return d.Name.Pos() + } + case *LabeledStmt: + if d.Label.Name == name { + return d.Label.Pos() + } + case *AssignStmt: + for _, x := range d.Lhs { + if ident, isIdent := x.(*Ident); isIdent && ident.Name == name { + return ident.Pos() + } + } + case *Scope: + // predeclared object - nothing to do for now + } + return token.NoPos +} + +// ObjKind describes what an object represents. +type ObjKind int + +// The list of possible Object kinds. +const ( + Bad ObjKind = iota // for error handling + Pkg // package + Con // constant + Typ // type + Var // variable + Fun // function or method + Lbl // label +) + +var objKindStrings = [...]string{ + Bad: "bad", + Pkg: "package", + Con: "const", + Typ: "type", + Var: "var", + Fun: "func", + Lbl: "label", +} + +func (kind ObjKind) String() string { return objKindStrings[kind] } diff --git a/src/go/ast/walk.go b/src/go/ast/walk.go new file mode 100644 index 0000000..8ca2195 --- /dev/null +++ b/src/go/ast/walk.go @@ -0,0 +1,386 @@ +// Copyright 2009 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 ast + +import "fmt" + +// A Visitor's Visit method is invoked for each node encountered by Walk. +// If the result visitor w is not nil, Walk visits each of the children +// of node with the visitor w, followed by a call of w.Visit(nil). +type Visitor interface { + Visit(node Node) (w Visitor) +} + +// Helper functions for common node lists. They may be empty. + +func walkIdentList(v Visitor, list []*Ident) { + for _, x := range list { + Walk(v, x) + } +} + +func walkExprList(v Visitor, list []Expr) { + for _, x := range list { + Walk(v, x) + } +} + +func walkStmtList(v Visitor, list []Stmt) { + for _, x := range list { + Walk(v, x) + } +} + +func walkDeclList(v Visitor, list []Decl) { + for _, x := range list { + Walk(v, x) + } +} + +// TODO(gri): Investigate if providing a closure to Walk leads to +// simpler use (and may help eliminate Inspect in turn). + +// Walk traverses an AST in depth-first order: It starts by calling +// v.Visit(node); node must not be nil. If the visitor w returned by +// v.Visit(node) is not nil, Walk is invoked recursively with visitor +// w for each of the non-nil children of node, followed by a call of +// w.Visit(nil). +// +func Walk(v Visitor, node Node) { + if v = v.Visit(node); v == nil { + return + } + + // walk children + // (the order of the cases matches the order + // of the corresponding node types in ast.go) + switch n := node.(type) { + // Comments and fields + case *Comment: + // nothing to do + + case *CommentGroup: + for _, c := range n.List { + Walk(v, c) + } + + case *Field: + if n.Doc != nil { + Walk(v, n.Doc) + } + walkIdentList(v, n.Names) + Walk(v, n.Type) + if n.Tag != nil { + Walk(v, n.Tag) + } + if n.Comment != nil { + Walk(v, n.Comment) + } + + case *FieldList: + for _, f := range n.List { + Walk(v, f) + } + + // Expressions + case *BadExpr, *Ident, *BasicLit: + // nothing to do + + case *Ellipsis: + if n.Elt != nil { + Walk(v, n.Elt) + } + + case *FuncLit: + Walk(v, n.Type) + Walk(v, n.Body) + + case *CompositeLit: + if n.Type != nil { + Walk(v, n.Type) + } + walkExprList(v, n.Elts) + + case *ParenExpr: + Walk(v, n.X) + + case *SelectorExpr: + Walk(v, n.X) + Walk(v, n.Sel) + + case *IndexExpr: + Walk(v, n.X) + Walk(v, n.Index) + + case *SliceExpr: + Walk(v, n.X) + if n.Low != nil { + Walk(v, n.Low) + } + if n.High != nil { + Walk(v, n.High) + } + if n.Max != nil { + Walk(v, n.Max) + } + + case *TypeAssertExpr: + Walk(v, n.X) + if n.Type != nil { + Walk(v, n.Type) + } + + case *CallExpr: + Walk(v, n.Fun) + walkExprList(v, n.Args) + + case *StarExpr: + Walk(v, n.X) + + case *UnaryExpr: + Walk(v, n.X) + + case *BinaryExpr: + Walk(v, n.X) + Walk(v, n.Y) + + case *KeyValueExpr: + Walk(v, n.Key) + Walk(v, n.Value) + + // Types + case *ArrayType: + if n.Len != nil { + Walk(v, n.Len) + } + Walk(v, n.Elt) + + case *StructType: + Walk(v, n.Fields) + + case *FuncType: + if n.Params != nil { + Walk(v, n.Params) + } + if n.Results != nil { + Walk(v, n.Results) + } + + case *InterfaceType: + Walk(v, n.Methods) + + case *MapType: + Walk(v, n.Key) + Walk(v, n.Value) + + case *ChanType: + Walk(v, n.Value) + + // Statements + case *BadStmt: + // nothing to do + + case *DeclStmt: + Walk(v, n.Decl) + + case *EmptyStmt: + // nothing to do + + case *LabeledStmt: + Walk(v, n.Label) + Walk(v, n.Stmt) + + case *ExprStmt: + Walk(v, n.X) + + case *SendStmt: + Walk(v, n.Chan) + Walk(v, n.Value) + + case *IncDecStmt: + Walk(v, n.X) + + case *AssignStmt: + walkExprList(v, n.Lhs) + walkExprList(v, n.Rhs) + + case *GoStmt: + Walk(v, n.Call) + + case *DeferStmt: + Walk(v, n.Call) + + case *ReturnStmt: + walkExprList(v, n.Results) + + case *BranchStmt: + if n.Label != nil { + Walk(v, n.Label) + } + + case *BlockStmt: + walkStmtList(v, n.List) + + case *IfStmt: + if n.Init != nil { + Walk(v, n.Init) + } + Walk(v, n.Cond) + Walk(v, n.Body) + if n.Else != nil { + Walk(v, n.Else) + } + + case *CaseClause: + walkExprList(v, n.List) + walkStmtList(v, n.Body) + + case *SwitchStmt: + if n.Init != nil { + Walk(v, n.Init) + } + if n.Tag != nil { + Walk(v, n.Tag) + } + Walk(v, n.Body) + + case *TypeSwitchStmt: + if n.Init != nil { + Walk(v, n.Init) + } + Walk(v, n.Assign) + Walk(v, n.Body) + + case *CommClause: + if n.Comm != nil { + Walk(v, n.Comm) + } + walkStmtList(v, n.Body) + + case *SelectStmt: + Walk(v, n.Body) + + case *ForStmt: + if n.Init != nil { + Walk(v, n.Init) + } + if n.Cond != nil { + Walk(v, n.Cond) + } + if n.Post != nil { + Walk(v, n.Post) + } + Walk(v, n.Body) + + case *RangeStmt: + if n.Key != nil { + Walk(v, n.Key) + } + if n.Value != nil { + Walk(v, n.Value) + } + Walk(v, n.X) + Walk(v, n.Body) + + // Declarations + case *ImportSpec: + if n.Doc != nil { + Walk(v, n.Doc) + } + if n.Name != nil { + Walk(v, n.Name) + } + Walk(v, n.Path) + if n.Comment != nil { + Walk(v, n.Comment) + } + + case *ValueSpec: + if n.Doc != nil { + Walk(v, n.Doc) + } + walkIdentList(v, n.Names) + if n.Type != nil { + Walk(v, n.Type) + } + walkExprList(v, n.Values) + if n.Comment != nil { + Walk(v, n.Comment) + } + + case *TypeSpec: + if n.Doc != nil { + Walk(v, n.Doc) + } + Walk(v, n.Name) + Walk(v, n.Type) + if n.Comment != nil { + Walk(v, n.Comment) + } + + case *BadDecl: + // nothing to do + + case *GenDecl: + if n.Doc != nil { + Walk(v, n.Doc) + } + for _, s := range n.Specs { + Walk(v, s) + } + + case *FuncDecl: + if n.Doc != nil { + Walk(v, n.Doc) + } + if n.Recv != nil { + Walk(v, n.Recv) + } + Walk(v, n.Name) + Walk(v, n.Type) + if n.Body != nil { + Walk(v, n.Body) + } + + // Files and packages + case *File: + if n.Doc != nil { + Walk(v, n.Doc) + } + Walk(v, n.Name) + walkDeclList(v, n.Decls) + // don't walk n.Comments - they have been + // visited already through the individual + // nodes + + case *Package: + for _, f := range n.Files { + Walk(v, f) + } + + default: + panic(fmt.Sprintf("ast.Walk: unexpected node type %T", n)) + } + + v.Visit(nil) +} + +type inspector func(Node) bool + +func (f inspector) Visit(node Node) Visitor { + if f(node) { + return f + } + return nil +} + +// Inspect traverses an AST in depth-first order: It starts by calling +// f(node); node must not be nil. If f returns true, Inspect invokes f +// recursively for each of the non-nil children of node, followed by a +// call of f(nil). +// +func Inspect(node Node, f func(Node) bool) { + Walk(inspector(f), node) +} diff --git a/src/go/build/build.go b/src/go/build/build.go new file mode 100644 index 0000000..217fadf --- /dev/null +++ b/src/go/build/build.go @@ -0,0 +1,1986 @@ +// Copyright 2011 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 build + +import ( + "bytes" + "errors" + "fmt" + "go/ast" + "go/doc" + "go/token" + exec "internal/execabs" + "internal/goroot" + "internal/goversion" + "io" + "io/fs" + "io/ioutil" + "os" + pathpkg "path" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// A Context specifies the supporting context for a build. +type Context struct { + GOARCH string // target architecture + GOOS string // target operating system + GOROOT string // Go root + GOPATH string // Go path + + // Dir is the caller's working directory, or the empty string to use + // the current directory of the running process. In module mode, this is used + // to locate the main module. + // + // If Dir is non-empty, directories passed to Import and ImportDir must + // be absolute. + Dir string + + CgoEnabled bool // whether cgo files are included + UseAllFiles bool // use files regardless of +build lines, file names + Compiler string // compiler to assume when computing target paths + + // The build and release tags specify build constraints + // that should be considered satisfied when processing +build lines. + // Clients creating a new context may customize BuildTags, which + // defaults to empty, but it is usually an error to customize ReleaseTags, + // which defaults to the list of Go releases the current release is compatible with. + // BuildTags is not set for the Default build Context. + // In addition to the BuildTags and ReleaseTags, build constraints + // consider the values of GOARCH and GOOS as satisfied tags. + // The last element in ReleaseTags is assumed to be the current release. + BuildTags []string + ReleaseTags []string + + // The install suffix specifies a suffix to use in the name of the installation + // directory. By default it is empty, but custom builds that need to keep + // their outputs separate can set InstallSuffix to do so. For example, when + // using the race detector, the go command uses InstallSuffix = "race", so + // that on a Linux/386 system, packages are written to a directory named + // "linux_386_race" instead of the usual "linux_386". + InstallSuffix string + + // By default, Import uses the operating system's file system calls + // to read directories and files. To read from other sources, + // callers can set the following functions. They all have default + // behaviors that use the local file system, so clients need only set + // the functions whose behaviors they wish to change. + + // JoinPath joins the sequence of path fragments into a single path. + // If JoinPath is nil, Import uses filepath.Join. + JoinPath func(elem ...string) string + + // SplitPathList splits the path list into a slice of individual paths. + // If SplitPathList is nil, Import uses filepath.SplitList. + SplitPathList func(list string) []string + + // IsAbsPath reports whether path is an absolute path. + // If IsAbsPath is nil, Import uses filepath.IsAbs. + IsAbsPath func(path string) bool + + // IsDir reports whether the path names a directory. + // If IsDir is nil, Import calls os.Stat and uses the result's IsDir method. + IsDir func(path string) bool + + // HasSubdir reports whether dir is lexically a subdirectory of + // root, perhaps multiple levels below. It does not try to check + // whether dir exists. + // If so, HasSubdir sets rel to a slash-separated path that + // can be joined to root to produce a path equivalent to dir. + // If HasSubdir is nil, Import uses an implementation built on + // filepath.EvalSymlinks. + HasSubdir func(root, dir string) (rel string, ok bool) + + // ReadDir returns a slice of fs.FileInfo, sorted by Name, + // describing the content of the named directory. + // If ReadDir is nil, Import uses ioutil.ReadDir. + ReadDir func(dir string) ([]fs.FileInfo, error) + + // OpenFile opens a file (not a directory) for reading. + // If OpenFile is nil, Import uses os.Open. + OpenFile func(path string) (io.ReadCloser, error) +} + +// joinPath calls ctxt.JoinPath (if not nil) or else filepath.Join. +func (ctxt *Context) joinPath(elem ...string) string { + if f := ctxt.JoinPath; f != nil { + return f(elem...) + } + return filepath.Join(elem...) +} + +// splitPathList calls ctxt.SplitPathList (if not nil) or else filepath.SplitList. +func (ctxt *Context) splitPathList(s string) []string { + if f := ctxt.SplitPathList; f != nil { + return f(s) + } + return filepath.SplitList(s) +} + +// isAbsPath calls ctxt.IsAbsPath (if not nil) or else filepath.IsAbs. +func (ctxt *Context) isAbsPath(path string) bool { + if f := ctxt.IsAbsPath; f != nil { + return f(path) + } + return filepath.IsAbs(path) +} + +// isDir calls ctxt.IsDir (if not nil) or else uses os.Stat. +func (ctxt *Context) isDir(path string) bool { + if f := ctxt.IsDir; f != nil { + return f(path) + } + fi, err := os.Stat(path) + return err == nil && fi.IsDir() +} + +// hasSubdir calls ctxt.HasSubdir (if not nil) or else uses +// the local file system to answer the question. +func (ctxt *Context) hasSubdir(root, dir string) (rel string, ok bool) { + if f := ctxt.HasSubdir; f != nil { + return f(root, dir) + } + + // Try using paths we received. + if rel, ok = hasSubdir(root, dir); ok { + return + } + + // Try expanding symlinks and comparing + // expanded against unexpanded and + // expanded against expanded. + rootSym, _ := filepath.EvalSymlinks(root) + dirSym, _ := filepath.EvalSymlinks(dir) + + if rel, ok = hasSubdir(rootSym, dir); ok { + return + } + if rel, ok = hasSubdir(root, dirSym); ok { + return + } + return hasSubdir(rootSym, dirSym) +} + +// hasSubdir reports if dir is within root by performing lexical analysis only. +func hasSubdir(root, dir string) (rel string, ok bool) { + const sep = string(filepath.Separator) + root = filepath.Clean(root) + if !strings.HasSuffix(root, sep) { + root += sep + } + dir = filepath.Clean(dir) + if !strings.HasPrefix(dir, root) { + return "", false + } + return filepath.ToSlash(dir[len(root):]), true +} + +// readDir calls ctxt.ReadDir (if not nil) or else ioutil.ReadDir. +func (ctxt *Context) readDir(path string) ([]fs.FileInfo, error) { + if f := ctxt.ReadDir; f != nil { + return f(path) + } + return ioutil.ReadDir(path) +} + +// openFile calls ctxt.OpenFile (if not nil) or else os.Open. +func (ctxt *Context) openFile(path string) (io.ReadCloser, error) { + if fn := ctxt.OpenFile; fn != nil { + return fn(path) + } + + f, err := os.Open(path) + if err != nil { + return nil, err // nil interface + } + return f, nil +} + +// isFile determines whether path is a file by trying to open it. +// It reuses openFile instead of adding another function to the +// list in Context. +func (ctxt *Context) isFile(path string) bool { + f, err := ctxt.openFile(path) + if err != nil { + return false + } + f.Close() + return true +} + +// gopath returns the list of Go path directories. +func (ctxt *Context) gopath() []string { + var all []string + for _, p := range ctxt.splitPathList(ctxt.GOPATH) { + if p == "" || p == ctxt.GOROOT { + // Empty paths are uninteresting. + // If the path is the GOROOT, ignore it. + // People sometimes set GOPATH=$GOROOT. + // Do not get confused by this common mistake. + continue + } + if strings.HasPrefix(p, "~") { + // Path segments starting with ~ on Unix are almost always + // users who have incorrectly quoted ~ while setting GOPATH, + // preventing it from expanding to $HOME. + // The situation is made more confusing by the fact that + // bash allows quoted ~ in $PATH (most shells do not). + // Do not get confused by this, and do not try to use the path. + // It does not exist, and printing errors about it confuses + // those users even more, because they think "sure ~ exists!". + // The go command diagnoses this situation and prints a + // useful error. + // On Windows, ~ is used in short names, such as c:\progra~1 + // for c:\program files. + continue + } + all = append(all, p) + } + return all +} + +// SrcDirs returns a list of package source root directories. +// It draws from the current Go root and Go path but omits directories +// that do not exist. +func (ctxt *Context) SrcDirs() []string { + var all []string + if ctxt.GOROOT != "" && ctxt.Compiler != "gccgo" { + dir := ctxt.joinPath(ctxt.GOROOT, "src") + if ctxt.isDir(dir) { + all = append(all, dir) + } + } + for _, p := range ctxt.gopath() { + dir := ctxt.joinPath(p, "src") + if ctxt.isDir(dir) { + all = append(all, dir) + } + } + return all +} + +// Default is the default Context for builds. +// It uses the GOARCH, GOOS, GOROOT, and GOPATH environment variables +// if set, or else the compiled code's GOARCH, GOOS, and GOROOT. +var Default Context = defaultContext() + +func defaultGOPATH() string { + env := "HOME" + if runtime.GOOS == "windows" { + env = "USERPROFILE" + } else if runtime.GOOS == "plan9" { + env = "home" + } + if home := os.Getenv(env); home != "" { + def := filepath.Join(home, "go") + if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) { + // Don't set the default GOPATH to GOROOT, + // as that will trigger warnings from the go tool. + return "" + } + return def + } + return "" +} + +var defaultReleaseTags []string + +func defaultContext() Context { + var c Context + + c.GOARCH = envOr("GOARCH", runtime.GOARCH) + c.GOOS = envOr("GOOS", runtime.GOOS) + c.GOROOT = pathpkg.Clean(runtime.GOROOT()) + c.GOPATH = envOr("GOPATH", defaultGOPATH()) + c.Compiler = runtime.Compiler + + // Each major Go release in the Go 1.x series adds a new + // "go1.x" release tag. That is, the go1.x tag is present in + // all releases >= Go 1.x. Code that requires Go 1.x or later + // should say "+build go1.x", and code that should only be + // built before Go 1.x (perhaps it is the stub to use in that + // case) should say "+build !go1.x". + // The last element in ReleaseTags is the current release. + for i := 1; i <= goversion.Version; i++ { + c.ReleaseTags = append(c.ReleaseTags, "go1."+strconv.Itoa(i)) + } + + defaultReleaseTags = append([]string{}, c.ReleaseTags...) // our own private copy + + env := os.Getenv("CGO_ENABLED") + if env == "" { + env = defaultCGO_ENABLED + } + switch env { + case "1": + c.CgoEnabled = true + case "0": + c.CgoEnabled = false + default: + // cgo must be explicitly enabled for cross compilation builds + if runtime.GOARCH == c.GOARCH && runtime.GOOS == c.GOOS { + c.CgoEnabled = cgoEnabled[c.GOOS+"/"+c.GOARCH] + break + } + c.CgoEnabled = false + } + + return c +} + +func envOr(name, def string) string { + s := os.Getenv(name) + if s == "" { + return def + } + return s +} + +// An ImportMode controls the behavior of the Import method. +type ImportMode uint + +const ( + // If FindOnly is set, Import stops after locating the directory + // that should contain the sources for a package. It does not + // read any files in the directory. + FindOnly ImportMode = 1 << iota + + // If AllowBinary is set, Import can be satisfied by a compiled + // package object without corresponding sources. + // + // Deprecated: + // The supported way to create a compiled-only package is to + // write source code containing a //go:binary-only-package comment at + // the top of the file. Such a package will be recognized + // regardless of this flag setting (because it has source code) + // and will have BinaryOnly set to true in the returned Package. + AllowBinary + + // If ImportComment is set, parse import comments on package statements. + // Import returns an error if it finds a comment it cannot understand + // or finds conflicting comments in multiple source files. + // See golang.org/s/go14customimport for more information. + ImportComment + + // By default, Import searches vendor directories + // that apply in the given source directory before searching + // the GOROOT and GOPATH roots. + // If an Import finds and returns a package using a vendor + // directory, the resulting ImportPath is the complete path + // to the package, including the path elements leading up + // to and including "vendor". + // For example, if Import("y", "x/subdir", 0) finds + // "x/vendor/y", the returned package's ImportPath is "x/vendor/y", + // not plain "y". + // See golang.org/s/go15vendor for more information. + // + // Setting IgnoreVendor ignores vendor directories. + // + // In contrast to the package's ImportPath, + // the returned package's Imports, TestImports, and XTestImports + // are always the exact import paths from the source files: + // Import makes no attempt to resolve or check those paths. + IgnoreVendor +) + +// A Package describes the Go package found in a directory. +type Package struct { + Dir string // directory containing package sources + Name string // package name + ImportComment string // path in import comment on package statement + Doc string // documentation synopsis + ImportPath string // import path of package ("" if unknown) + Root string // root of Go tree where this package lives + SrcRoot string // package source root directory ("" if unknown) + PkgRoot string // package install root directory ("" if unknown) + PkgTargetRoot string // architecture dependent install root directory ("" if unknown) + BinDir string // command install directory ("" if unknown) + Goroot bool // package found in Go root + PkgObj string // installed .a file + AllTags []string // tags that can influence file selection in this directory + ConflictDir string // this directory shadows Dir in $GOPATH + BinaryOnly bool // cannot be rebuilt from source (has //go:binary-only-package comment) + + // Source files + GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) + CgoFiles []string // .go source files that import "C" + IgnoredGoFiles []string // .go source files ignored for this build (including ignored _test.go files) + InvalidGoFiles []string // .go source files with detected problems (parse error, wrong package name, and so on) + IgnoredOtherFiles []string // non-.go source files ignored for this build + CFiles []string // .c source files + CXXFiles []string // .cc, .cpp and .cxx source files + MFiles []string // .m (Objective-C) source files + HFiles []string // .h, .hh, .hpp and .hxx source files + FFiles []string // .f, .F, .for and .f90 Fortran source files + SFiles []string // .s source files + SwigFiles []string // .swig files + SwigCXXFiles []string // .swigcxx files + SysoFiles []string // .syso system object files to add to archive + + // Cgo directives + CgoCFLAGS []string // Cgo CFLAGS directives + CgoCPPFLAGS []string // Cgo CPPFLAGS directives + CgoCXXFLAGS []string // Cgo CXXFLAGS directives + CgoFFLAGS []string // Cgo FFLAGS directives + CgoLDFLAGS []string // Cgo LDFLAGS directives + CgoPkgConfig []string // Cgo pkg-config directives + + // Test information + TestGoFiles []string // _test.go files in package + XTestGoFiles []string // _test.go files outside package + + // Dependency information + Imports []string // import paths from GoFiles, CgoFiles + ImportPos map[string][]token.Position // line information for Imports + TestImports []string // import paths from TestGoFiles + TestImportPos map[string][]token.Position // line information for TestImports + XTestImports []string // import paths from XTestGoFiles + XTestImportPos map[string][]token.Position // line information for XTestImports + + // //go:embed patterns found in Go source files + // For example, if a source file says + // //go:embed a* b.c + // then the list will contain those two strings as separate entries. + // (See package embed for more details about //go:embed.) + EmbedPatterns []string // patterns from GoFiles, CgoFiles + EmbedPatternPos map[string][]token.Position // line information for EmbedPatterns + TestEmbedPatterns []string // patterns from TestGoFiles + TestEmbedPatternPos map[string][]token.Position // line information for TestEmbedPatterns + XTestEmbedPatterns []string // patterns from XTestGoFiles + XTestEmbedPatternPos map[string][]token.Position // line information for XTestEmbedPatternPos +} + +// IsCommand reports whether the package is considered a +// command to be installed (not just a library). +// Packages named "main" are treated as commands. +func (p *Package) IsCommand() bool { + return p.Name == "main" +} + +// ImportDir is like Import but processes the Go package found in +// the named directory. +func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) { + return ctxt.Import(".", dir, mode) +} + +// NoGoError is the error used by Import to describe a directory +// containing no buildable Go source files. (It may still contain +// test files, files hidden by build tags, and so on.) +type NoGoError struct { + Dir string +} + +func (e *NoGoError) Error() string { + return "no buildable Go source files in " + e.Dir +} + +// MultiplePackageError describes a directory containing +// multiple buildable Go source files for multiple packages. +type MultiplePackageError struct { + Dir string // directory containing files + Packages []string // package names found + Files []string // corresponding files: Files[i] declares package Packages[i] +} + +func (e *MultiplePackageError) Error() string { + // Error string limited to two entries for compatibility. + return fmt.Sprintf("found packages %s (%s) and %s (%s) in %s", e.Packages[0], e.Files[0], e.Packages[1], e.Files[1], e.Dir) +} + +func nameExt(name string) string { + i := strings.LastIndex(name, ".") + if i < 0 { + return "" + } + return name[i:] +} + +// Import returns details about the Go package named by the import path, +// interpreting local import paths relative to the srcDir directory. +// If the path is a local import path naming a package that can be imported +// using a standard import path, the returned package will set p.ImportPath +// to that path. +// +// In the directory containing the package, .go, .c, .h, and .s files are +// considered part of the package except for: +// +// - .go files in package documentation +// - files starting with _ or . (likely editor temporary files) +// - files with build constraints not satisfied by the context +// +// If an error occurs, Import returns a non-nil error and a non-nil +// *Package containing partial information. +// +func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Package, error) { + p := &Package{ + ImportPath: path, + } + if path == "" { + return p, fmt.Errorf("import %q: invalid import path", path) + } + + var pkgtargetroot string + var pkga string + var pkgerr error + suffix := "" + if ctxt.InstallSuffix != "" { + suffix = "_" + ctxt.InstallSuffix + } + switch ctxt.Compiler { + case "gccgo": + pkgtargetroot = "pkg/gccgo_" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix + case "gc": + pkgtargetroot = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix + default: + // Save error for end of function. + pkgerr = fmt.Errorf("import %q: unknown compiler %q", path, ctxt.Compiler) + } + setPkga := func() { + switch ctxt.Compiler { + case "gccgo": + dir, elem := pathpkg.Split(p.ImportPath) + pkga = pkgtargetroot + "/" + dir + "lib" + elem + ".a" + case "gc": + pkga = pkgtargetroot + "/" + p.ImportPath + ".a" + } + } + setPkga() + + binaryOnly := false + if IsLocalImport(path) { + pkga = "" // local imports have no installed path + if srcDir == "" { + return p, fmt.Errorf("import %q: import relative to unknown directory", path) + } + if !ctxt.isAbsPath(path) { + p.Dir = ctxt.joinPath(srcDir, path) + } + // p.Dir directory may or may not exist. Gather partial information first, check if it exists later. + // Determine canonical import path, if any. + // Exclude results where the import path would include /testdata/. + inTestdata := func(sub string) bool { + return strings.Contains(sub, "/testdata/") || strings.HasSuffix(sub, "/testdata") || strings.HasPrefix(sub, "testdata/") || sub == "testdata" + } + if ctxt.GOROOT != "" { + root := ctxt.joinPath(ctxt.GOROOT, "src") + if sub, ok := ctxt.hasSubdir(root, p.Dir); ok && !inTestdata(sub) { + p.Goroot = true + p.ImportPath = sub + p.Root = ctxt.GOROOT + setPkga() // p.ImportPath changed + goto Found + } + } + all := ctxt.gopath() + for i, root := range all { + rootsrc := ctxt.joinPath(root, "src") + if sub, ok := ctxt.hasSubdir(rootsrc, p.Dir); ok && !inTestdata(sub) { + // We found a potential import path for dir, + // but check that using it wouldn't find something + // else first. + if ctxt.GOROOT != "" && ctxt.Compiler != "gccgo" { + if dir := ctxt.joinPath(ctxt.GOROOT, "src", sub); ctxt.isDir(dir) { + p.ConflictDir = dir + goto Found + } + } + for _, earlyRoot := range all[:i] { + if dir := ctxt.joinPath(earlyRoot, "src", sub); ctxt.isDir(dir) { + p.ConflictDir = dir + goto Found + } + } + + // sub would not name some other directory instead of this one. + // Record it. + p.ImportPath = sub + p.Root = root + setPkga() // p.ImportPath changed + goto Found + } + } + // It's okay that we didn't find a root containing dir. + // Keep going with the information we have. + } else { + if strings.HasPrefix(path, "/") { + return p, fmt.Errorf("import %q: cannot import absolute path", path) + } + + if err := ctxt.importGo(p, path, srcDir, mode); err == nil { + goto Found + } else if err != errNoModules { + return p, err + } + + gopath := ctxt.gopath() // needed twice below; avoid computing many times + + // tried records the location of unsuccessful package lookups + var tried struct { + vendor []string + goroot string + gopath []string + } + + // Vendor directories get first chance to satisfy import. + if mode&IgnoreVendor == 0 && srcDir != "" { + searchVendor := func(root string, isGoroot bool) bool { + sub, ok := ctxt.hasSubdir(root, srcDir) + if !ok || !strings.HasPrefix(sub, "src/") || strings.Contains(sub, "/testdata/") { + return false + } + for { + vendor := ctxt.joinPath(root, sub, "vendor") + if ctxt.isDir(vendor) { + dir := ctxt.joinPath(vendor, path) + if ctxt.isDir(dir) && hasGoFiles(ctxt, dir) { + p.Dir = dir + p.ImportPath = strings.TrimPrefix(pathpkg.Join(sub, "vendor", path), "src/") + p.Goroot = isGoroot + p.Root = root + setPkga() // p.ImportPath changed + return true + } + tried.vendor = append(tried.vendor, dir) + } + i := strings.LastIndex(sub, "/") + if i < 0 { + break + } + sub = sub[:i] + } + return false + } + if ctxt.Compiler != "gccgo" && searchVendor(ctxt.GOROOT, true) { + goto Found + } + for _, root := range gopath { + if searchVendor(root, false) { + goto Found + } + } + } + + // Determine directory from import path. + if ctxt.GOROOT != "" { + // If the package path starts with "vendor/", only search GOROOT before + // GOPATH if the importer is also within GOROOT. That way, if the user has + // vendored in a package that is subsequently included in the standard + // distribution, they'll continue to pick up their own vendored copy. + gorootFirst := srcDir == "" || !strings.HasPrefix(path, "vendor/") + if !gorootFirst { + _, gorootFirst = ctxt.hasSubdir(ctxt.GOROOT, srcDir) + } + if gorootFirst { + dir := ctxt.joinPath(ctxt.GOROOT, "src", path) + if ctxt.Compiler != "gccgo" { + isDir := ctxt.isDir(dir) + binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(ctxt.GOROOT, pkga)) + if isDir || binaryOnly { + p.Dir = dir + p.Goroot = true + p.Root = ctxt.GOROOT + goto Found + } + } + tried.goroot = dir + } + } + if ctxt.Compiler == "gccgo" && goroot.IsStandardPackage(ctxt.GOROOT, ctxt.Compiler, path) { + p.Dir = ctxt.joinPath(ctxt.GOROOT, "src", path) + p.Goroot = true + p.Root = ctxt.GOROOT + goto Found + } + for _, root := range gopath { + dir := ctxt.joinPath(root, "src", path) + isDir := ctxt.isDir(dir) + binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(root, pkga)) + if isDir || binaryOnly { + p.Dir = dir + p.Root = root + goto Found + } + tried.gopath = append(tried.gopath, dir) + } + + // If we tried GOPATH first due to a "vendor/" prefix, fall back to GOPATH. + // That way, the user can still get useful results from 'go list' for + // standard-vendored paths passed on the command line. + if ctxt.GOROOT != "" && tried.goroot == "" { + dir := ctxt.joinPath(ctxt.GOROOT, "src", path) + if ctxt.Compiler != "gccgo" { + isDir := ctxt.isDir(dir) + binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(ctxt.GOROOT, pkga)) + if isDir || binaryOnly { + p.Dir = dir + p.Goroot = true + p.Root = ctxt.GOROOT + goto Found + } + } + tried.goroot = dir + } + + // package was not found + var paths []string + format := "\t%s (vendor tree)" + for _, dir := range tried.vendor { + paths = append(paths, fmt.Sprintf(format, dir)) + format = "\t%s" + } + if tried.goroot != "" { + paths = append(paths, fmt.Sprintf("\t%s (from $GOROOT)", tried.goroot)) + } else { + paths = append(paths, "\t($GOROOT not set)") + } + format = "\t%s (from $GOPATH)" + for _, dir := range tried.gopath { + paths = append(paths, fmt.Sprintf(format, dir)) + format = "\t%s" + } + if len(tried.gopath) == 0 { + paths = append(paths, "\t($GOPATH not set. For more details see: 'go help gopath')") + } + return p, fmt.Errorf("cannot find package %q in any of:\n%s", path, strings.Join(paths, "\n")) + } + +Found: + if p.Root != "" { + p.SrcRoot = ctxt.joinPath(p.Root, "src") + p.PkgRoot = ctxt.joinPath(p.Root, "pkg") + p.BinDir = ctxt.joinPath(p.Root, "bin") + if pkga != "" { + p.PkgTargetRoot = ctxt.joinPath(p.Root, pkgtargetroot) + p.PkgObj = ctxt.joinPath(p.Root, pkga) + } + } + + // If it's a local import path, by the time we get here, we still haven't checked + // that p.Dir directory exists. This is the right time to do that check. + // We can't do it earlier, because we want to gather partial information for the + // non-nil *Package returned when an error occurs. + // We need to do this before we return early on FindOnly flag. + if IsLocalImport(path) && !ctxt.isDir(p.Dir) { + if ctxt.Compiler == "gccgo" && p.Goroot { + // gccgo has no sources for GOROOT packages. + return p, nil + } + + // package was not found + return p, fmt.Errorf("cannot find package %q in:\n\t%s", path, p.Dir) + } + + if mode&FindOnly != 0 { + return p, pkgerr + } + if binaryOnly && (mode&AllowBinary) != 0 { + return p, pkgerr + } + + if ctxt.Compiler == "gccgo" && p.Goroot { + // gccgo has no sources for GOROOT packages. + return p, nil + } + + dirs, err := ctxt.readDir(p.Dir) + if err != nil { + return p, err + } + + var badGoError error + var Sfiles []string // files with ".S"(capital S)/.sx(capital s equivalent for case insensitive filesystems) + var firstFile, firstCommentFile string + embedPos := make(map[string][]token.Position) + testEmbedPos := make(map[string][]token.Position) + xTestEmbedPos := make(map[string][]token.Position) + importPos := make(map[string][]token.Position) + testImportPos := make(map[string][]token.Position) + xTestImportPos := make(map[string][]token.Position) + allTags := make(map[string]bool) + fset := token.NewFileSet() + for _, d := range dirs { + if d.IsDir() { + continue + } + if d.Mode()&fs.ModeSymlink != 0 { + if ctxt.isDir(ctxt.joinPath(p.Dir, d.Name())) { + // Symlinks to directories are not source files. + continue + } + } + + name := d.Name() + ext := nameExt(name) + + badFile := func(err error) { + if badGoError == nil { + badGoError = err + } + p.InvalidGoFiles = append(p.InvalidGoFiles, name) + } + + info, err := ctxt.matchFile(p.Dir, name, allTags, &p.BinaryOnly, fset) + if err != nil { + badFile(err) + continue + } + if info == nil { + if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") { + // not due to build constraints - don't report + } else if ext == ".go" { + p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) + } else if fileListForExt(p, ext) != nil { + p.IgnoredOtherFiles = append(p.IgnoredOtherFiles, name) + } + continue + } + data, filename := info.header, info.name + + // Going to save the file. For non-Go files, can stop here. + switch ext { + case ".go": + // keep going + case ".S", ".sx": + // special case for cgo, handled at end + Sfiles = append(Sfiles, name) + continue + default: + if list := fileListForExt(p, ext); list != nil { + *list = append(*list, name) + } + continue + } + + if info.parseErr != nil { + badFile(info.parseErr) + continue + } + pf := info.parsed + + pkg := pf.Name.Name + if pkg == "documentation" { + p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) + continue + } + + isTest := strings.HasSuffix(name, "_test.go") + isXTest := false + if isTest && strings.HasSuffix(pkg, "_test") { + isXTest = true + pkg = pkg[:len(pkg)-len("_test")] + } + + if p.Name == "" { + p.Name = pkg + firstFile = name + } else if pkg != p.Name { + badFile(&MultiplePackageError{ + Dir: p.Dir, + Packages: []string{p.Name, pkg}, + Files: []string{firstFile, name}, + }) + p.InvalidGoFiles = append(p.InvalidGoFiles, name) + } + // Grab the first package comment as docs, provided it is not from a test file. + if pf.Doc != nil && p.Doc == "" && !isTest && !isXTest { + p.Doc = doc.Synopsis(pf.Doc.Text()) + } + + if mode&ImportComment != 0 { + qcom, line := findImportComment(data) + if line != 0 { + com, err := strconv.Unquote(qcom) + if err != nil { + badFile(fmt.Errorf("%s:%d: cannot parse import comment", filename, line)) + } else if p.ImportComment == "" { + p.ImportComment = com + firstCommentFile = name + } else if p.ImportComment != com { + badFile(fmt.Errorf("found import comments %q (%s) and %q (%s) in %s", p.ImportComment, firstCommentFile, com, name, p.Dir)) + } + } + } + + // Record imports and information about cgo. + isCgo := false + for _, imp := range info.imports { + if imp.path == "C" { + if isTest { + badFile(fmt.Errorf("use of cgo in test %s not supported", filename)) + continue + } + isCgo = true + if imp.doc != nil { + if err := ctxt.saveCgo(filename, p, imp.doc); err != nil { + badFile(err) + } + } + } + } + + var fileList *[]string + var importMap, embedMap map[string][]token.Position + switch { + case isCgo: + allTags["cgo"] = true + if ctxt.CgoEnabled { + fileList = &p.CgoFiles + importMap = importPos + embedMap = embedPos + } else { + // Ignore imports and embeds from cgo files if cgo is disabled. + fileList = &p.IgnoredGoFiles + } + case isXTest: + fileList = &p.XTestGoFiles + importMap = xTestImportPos + embedMap = xTestEmbedPos + case isTest: + fileList = &p.TestGoFiles + importMap = testImportPos + embedMap = testEmbedPos + default: + fileList = &p.GoFiles + importMap = importPos + embedMap = embedPos + } + *fileList = append(*fileList, name) + if importMap != nil { + for _, imp := range info.imports { + importMap[imp.path] = append(importMap[imp.path], fset.Position(imp.pos)) + } + } + if embedMap != nil { + for _, emb := range info.embeds { + embedMap[emb.pattern] = append(embedMap[emb.pattern], emb.pos) + } + } + } + + for tag := range allTags { + p.AllTags = append(p.AllTags, tag) + } + sort.Strings(p.AllTags) + + p.EmbedPatterns, p.EmbedPatternPos = cleanDecls(embedPos) + p.TestEmbedPatterns, p.TestEmbedPatternPos = cleanDecls(testEmbedPos) + p.XTestEmbedPatterns, p.XTestEmbedPatternPos = cleanDecls(xTestEmbedPos) + + p.Imports, p.ImportPos = cleanDecls(importPos) + p.TestImports, p.TestImportPos = cleanDecls(testImportPos) + p.XTestImports, p.XTestImportPos = cleanDecls(xTestImportPos) + + // add the .S/.sx files only if we are using cgo + // (which means gcc will compile them). + // The standard assemblers expect .s files. + if len(p.CgoFiles) > 0 { + p.SFiles = append(p.SFiles, Sfiles...) + sort.Strings(p.SFiles) + } else { + p.IgnoredOtherFiles = append(p.IgnoredOtherFiles, Sfiles...) + sort.Strings(p.IgnoredOtherFiles) + } + + if badGoError != nil { + return p, badGoError + } + if len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 { + return p, &NoGoError{p.Dir} + } + return p, pkgerr +} + +func fileListForExt(p *Package, ext string) *[]string { + switch ext { + case ".c": + return &p.CFiles + case ".cc", ".cpp", ".cxx": + return &p.CXXFiles + case ".m": + return &p.MFiles + case ".h", ".hh", ".hpp", ".hxx": + return &p.HFiles + case ".f", ".F", ".for", ".f90": + return &p.FFiles + case ".s", ".S", ".sx": + return &p.SFiles + case ".swig": + return &p.SwigFiles + case ".swigcxx": + return &p.SwigCXXFiles + case ".syso": + return &p.SysoFiles + } + return nil +} + +func uniq(list []string) []string { + if list == nil { + return nil + } + out := make([]string, len(list)) + copy(out, list) + sort.Strings(out) + uniq := out[:0] + for _, x := range out { + if len(uniq) == 0 || uniq[len(uniq)-1] != x { + uniq = append(uniq, x) + } + } + return uniq +} + +var errNoModules = errors.New("not using modules") + +// importGo checks whether it can use the go command to find the directory for path. +// If using the go command is not appropriate, importGo returns errNoModules. +// Otherwise, importGo tries using the go command and reports whether that succeeded. +// Using the go command lets build.Import and build.Context.Import find code +// in Go modules. In the long term we want tools to use go/packages (currently golang.org/x/tools/go/packages), +// which will also use the go command. +// Invoking the go command here is not very efficient in that it computes information +// about the requested package and all dependencies and then only reports about the requested package. +// Then we reinvoke it for every dependency. But this is still better than not working at all. +// See golang.org/issue/26504. +func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode) error { + // To invoke the go command, + // we must not being doing special things like AllowBinary or IgnoreVendor, + // and all the file system callbacks must be nil (we're meant to use the local file system). + if mode&AllowBinary != 0 || mode&IgnoreVendor != 0 || + ctxt.JoinPath != nil || ctxt.SplitPathList != nil || ctxt.IsAbsPath != nil || ctxt.IsDir != nil || ctxt.HasSubdir != nil || ctxt.ReadDir != nil || ctxt.OpenFile != nil || !equal(ctxt.ReleaseTags, defaultReleaseTags) { + return errNoModules + } + + // Predict whether module aware mode is enabled by checking the value of + // GO111MODULE and looking for a go.mod file in the source directory or + // one of its parents. Running 'go env GOMOD' in the source directory would + // give a canonical answer, but we'd prefer not to execute another command. + go111Module := os.Getenv("GO111MODULE") + switch go111Module { + case "off": + return errNoModules + default: // "", "on", "auto", anything else + // Maybe use modules. + } + + if srcDir != "" { + var absSrcDir string + if filepath.IsAbs(srcDir) { + absSrcDir = srcDir + } else if ctxt.Dir != "" { + return fmt.Errorf("go/build: Dir is non-empty, so relative srcDir is not allowed: %v", srcDir) + } else { + // Find the absolute source directory. hasSubdir does not handle + // relative paths (and can't because the callbacks don't support this). + var err error + absSrcDir, err = filepath.Abs(srcDir) + if err != nil { + return errNoModules + } + } + + // If the source directory is in GOROOT, then the in-process code works fine + // and we should keep using it. Moreover, the 'go list' approach below doesn't + // take standard-library vendoring into account and will fail. + if _, ok := ctxt.hasSubdir(filepath.Join(ctxt.GOROOT, "src"), absSrcDir); ok { + return errNoModules + } + } + + // For efficiency, if path is a standard library package, let the usual lookup code handle it. + if ctxt.GOROOT != "" { + dir := ctxt.joinPath(ctxt.GOROOT, "src", path) + if ctxt.isDir(dir) { + return errNoModules + } + } + + // If GO111MODULE=auto, look to see if there is a go.mod. + // Since go1.13, it doesn't matter if we're inside GOPATH. + if go111Module == "auto" { + var ( + parent string + err error + ) + if ctxt.Dir == "" { + parent, err = os.Getwd() + if err != nil { + // A nonexistent working directory can't be in a module. + return errNoModules + } + } else { + parent, err = filepath.Abs(ctxt.Dir) + if err != nil { + // If the caller passed a bogus Dir explicitly, that's materially + // different from not having modules enabled. + return err + } + } + for { + if f, err := ctxt.openFile(ctxt.joinPath(parent, "go.mod")); err == nil { + buf := make([]byte, 100) + _, err := f.Read(buf) + f.Close() + if err == nil || err == io.EOF { + // go.mod exists and is readable (is a file, not a directory). + break + } + } + d := filepath.Dir(parent) + if len(d) >= len(parent) { + return errNoModules // reached top of file system, no go.mod + } + parent = d + } + } + + cmd := exec.Command("go", "list", "-e", "-compiler="+ctxt.Compiler, "-tags="+strings.Join(ctxt.BuildTags, ","), "-installsuffix="+ctxt.InstallSuffix, "-f={{.Dir}}\n{{.ImportPath}}\n{{.Root}}\n{{.Goroot}}\n{{if .Error}}{{.Error}}{{end}}\n", "--", path) + + if ctxt.Dir != "" { + cmd.Dir = ctxt.Dir + } + + var stdout, stderr strings.Builder + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + cgo := "0" + if ctxt.CgoEnabled { + cgo = "1" + } + cmd.Env = append(os.Environ(), + "GOOS="+ctxt.GOOS, + "GOARCH="+ctxt.GOARCH, + "GOROOT="+ctxt.GOROOT, + "GOPATH="+ctxt.GOPATH, + "CGO_ENABLED="+cgo, + ) + + if err := cmd.Run(); err != nil { + return fmt.Errorf("go/build: go list %s: %v\n%s\n", path, err, stderr.String()) + } + + f := strings.SplitN(stdout.String(), "\n", 5) + if len(f) != 5 { + return fmt.Errorf("go/build: importGo %s: unexpected output:\n%s\n", path, stdout.String()) + } + dir := f[0] + errStr := strings.TrimSpace(f[4]) + if errStr != "" && dir == "" { + // If 'go list' could not locate the package (dir is empty), + // return the same error that 'go list' reported. + return errors.New(errStr) + } + + // If 'go list' did locate the package, ignore the error. + // It was probably related to loading source files, and we'll + // encounter it ourselves shortly if the FindOnly flag isn't set. + p.Dir = dir + p.ImportPath = f[1] + p.Root = f[2] + p.Goroot = f[3] == "true" + return nil +} + +func equal(x, y []string) bool { + if len(x) != len(y) { + return false + } + for i, xi := range x { + if xi != y[i] { + return false + } + } + return true +} + +// hasGoFiles reports whether dir contains any files with names ending in .go. +// For a vendor check we must exclude directories that contain no .go files. +// Otherwise it is not possible to vendor just a/b/c and still import the +// non-vendored a/b. See golang.org/issue/13832. +func hasGoFiles(ctxt *Context, dir string) bool { + ents, _ := ctxt.readDir(dir) + for _, ent := range ents { + if !ent.IsDir() && strings.HasSuffix(ent.Name(), ".go") { + return true + } + } + return false +} + +func findImportComment(data []byte) (s string, line int) { + // expect keyword package + word, data := parseWord(data) + if string(word) != "package" { + return "", 0 + } + + // expect package name + _, data = parseWord(data) + + // now ready for import comment, a // or /* */ comment + // beginning and ending on the current line. + for len(data) > 0 && (data[0] == ' ' || data[0] == '\t' || data[0] == '\r') { + data = data[1:] + } + + var comment []byte + switch { + case bytes.HasPrefix(data, slashSlash): + i := bytes.Index(data, newline) + if i < 0 { + i = len(data) + } + comment = data[2:i] + case bytes.HasPrefix(data, slashStar): + data = data[2:] + i := bytes.Index(data, starSlash) + if i < 0 { + // malformed comment + return "", 0 + } + comment = data[:i] + if bytes.Contains(comment, newline) { + return "", 0 + } + } + comment = bytes.TrimSpace(comment) + + // split comment into `import`, `"pkg"` + word, arg := parseWord(comment) + if string(word) != "import" { + return "", 0 + } + + line = 1 + bytes.Count(data[:cap(data)-cap(arg)], newline) + return strings.TrimSpace(string(arg)), line +} + +var ( + slashSlash = []byte("//") + slashStar = []byte("/*") + starSlash = []byte("*/") + newline = []byte("\n") +) + +// skipSpaceOrComment returns data with any leading spaces or comments removed. +func skipSpaceOrComment(data []byte) []byte { + for len(data) > 0 { + switch data[0] { + case ' ', '\t', '\r', '\n': + data = data[1:] + continue + case '/': + if bytes.HasPrefix(data, slashSlash) { + i := bytes.Index(data, newline) + if i < 0 { + return nil + } + data = data[i+1:] + continue + } + if bytes.HasPrefix(data, slashStar) { + data = data[2:] + i := bytes.Index(data, starSlash) + if i < 0 { + return nil + } + data = data[i+2:] + continue + } + } + break + } + return data +} + +// parseWord skips any leading spaces or comments in data +// and then parses the beginning of data as an identifier or keyword, +// returning that word and what remains after the word. +func parseWord(data []byte) (word, rest []byte) { + data = skipSpaceOrComment(data) + + // Parse past leading word characters. + rest = data + for { + r, size := utf8.DecodeRune(rest) + if unicode.IsLetter(r) || '0' <= r && r <= '9' || r == '_' { + rest = rest[size:] + continue + } + break + } + + word = data[:len(data)-len(rest)] + if len(word) == 0 { + return nil, nil + } + + return word, rest +} + +// MatchFile reports whether the file with the given name in the given directory +// matches the context and would be included in a Package created by ImportDir +// of that directory. +// +// MatchFile considers the name of the file and may use ctxt.OpenFile to +// read some or all of the file's content. +func (ctxt *Context) MatchFile(dir, name string) (match bool, err error) { + info, err := ctxt.matchFile(dir, name, nil, nil, nil) + return info != nil, err +} + +var dummyPkg Package + +// fileInfo records information learned about a file included in a build. +type fileInfo struct { + name string // full name including dir + header []byte + fset *token.FileSet + parsed *ast.File + parseErr error + imports []fileImport + embeds []fileEmbed + embedErr error +} + +type fileImport struct { + path string + pos token.Pos + doc *ast.CommentGroup +} + +type fileEmbed struct { + pattern string + pos token.Position +} + +// matchFile determines whether the file with the given name in the given directory +// should be included in the package being constructed. +// If the file should be included, matchFile returns a non-nil *fileInfo (and a nil error). +// Non-nil errors are reserved for unexpected problems. +// +// If name denotes a Go program, matchFile reads until the end of the +// imports and returns that section of the file in the fileInfo's header field, +// even though it only considers text until the first non-comment +// for +build lines. +// +// If allTags is non-nil, matchFile records any encountered build tag +// by setting allTags[tag] = true. +func (ctxt *Context) matchFile(dir, name string, allTags map[string]bool, binaryOnly *bool, fset *token.FileSet) (*fileInfo, error) { + if strings.HasPrefix(name, "_") || + strings.HasPrefix(name, ".") { + return nil, nil + } + + i := strings.LastIndex(name, ".") + if i < 0 { + i = len(name) + } + ext := name[i:] + + if !ctxt.goodOSArchFile(name, allTags) && !ctxt.UseAllFiles { + return nil, nil + } + + if ext != ".go" && fileListForExt(&dummyPkg, ext) == nil { + // skip + return nil, nil + } + + info := &fileInfo{name: ctxt.joinPath(dir, name), fset: fset} + if ext == ".syso" { + // binary, no reading + return info, nil + } + + f, err := ctxt.openFile(info.name) + if err != nil { + return nil, err + } + + if strings.HasSuffix(name, ".go") { + err = readGoInfo(f, info) + if strings.HasSuffix(name, "_test.go") { + binaryOnly = nil // ignore //go:binary-only-package comments in _test.go files + } + } else { + binaryOnly = nil // ignore //go:binary-only-package comments in non-Go sources + info.header, err = readComments(f) + } + f.Close() + if err != nil { + return nil, fmt.Errorf("read %s: %v", info.name, err) + } + + // Look for +build comments to accept or reject the file. + ok, sawBinaryOnly, err := ctxt.shouldBuild(info.header, allTags) + if err != nil { + return nil, err + } + if !ok && !ctxt.UseAllFiles { + return nil, nil + } + + if binaryOnly != nil && sawBinaryOnly { + *binaryOnly = true + } + + return info, nil +} + +func cleanDecls(m map[string][]token.Position) ([]string, map[string][]token.Position) { + all := make([]string, 0, len(m)) + for path := range m { + all = append(all, path) + } + sort.Strings(all) + return all, m +} + +// Import is shorthand for Default.Import. +func Import(path, srcDir string, mode ImportMode) (*Package, error) { + return Default.Import(path, srcDir, mode) +} + +// ImportDir is shorthand for Default.ImportDir. +func ImportDir(dir string, mode ImportMode) (*Package, error) { + return Default.ImportDir(dir, mode) +} + +var ( + bSlashSlash = []byte(slashSlash) + bStarSlash = []byte(starSlash) + bSlashStar = []byte(slashStar) + + goBuildComment = []byte("//go:build") + + errGoBuildWithoutBuild = errors.New("//go:build comment without // +build comment") + errMultipleGoBuild = errors.New("multiple //go:build comments") // unused in Go 1.(N-1) +) + +func isGoBuildComment(line []byte) bool { + if !bytes.HasPrefix(line, goBuildComment) { + return false + } + line = bytes.TrimSpace(line) + rest := line[len(goBuildComment):] + return len(rest) == 0 || len(bytes.TrimSpace(rest)) < len(rest) +} + +// Special comment denoting a binary-only package. +// See https://golang.org/design/2775-binary-only-packages +// for more about the design of binary-only packages. +var binaryOnlyComment = []byte("//go:binary-only-package") + +// shouldBuild reports whether it is okay to use this file, +// The rule is that in the file's leading run of // comments +// and blank lines, which must be followed by a blank line +// (to avoid including a Go package clause doc comment), +// lines beginning with '// +build' are taken as build directives. +// +// The file is accepted only if each such line lists something +// matching the file. For example: +// +// // +build windows linux +// +// marks the file as applicable only on Windows and Linux. +// +// For each build tag it consults, shouldBuild sets allTags[tag] = true. +// +// shouldBuild reports whether the file should be built +// and whether a //go:binary-only-package comment was found. +func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool) (shouldBuild, binaryOnly bool, err error) { + + // Pass 1. Identify leading run of // comments and blank lines, + // which must be followed by a blank line. + // Also identify any //go:build comments. + content, goBuild, sawBinaryOnly, err := parseFileHeader(content) + if err != nil { + return false, false, err + } + + // Pass 2. Process each +build line in the run. + p := content + shouldBuild = true + sawBuild := false + for len(p) > 0 { + line := p + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, p = line[:i], p[i+1:] + } else { + p = p[len(p):] + } + line = bytes.TrimSpace(line) + if !bytes.HasPrefix(line, bSlashSlash) { + continue + } + line = bytes.TrimSpace(line[len(bSlashSlash):]) + if len(line) > 0 && line[0] == '+' { + // Looks like a comment +line. + f := strings.Fields(string(line)) + if f[0] == "+build" { + sawBuild = true + ok := false + for _, tok := range f[1:] { + if ctxt.match(tok, allTags) { + ok = true + } + } + if !ok { + shouldBuild = false + } + } + } + } + + if goBuild != nil && !sawBuild { + return false, false, errGoBuildWithoutBuild + } + + return shouldBuild, sawBinaryOnly, nil +} + +func parseFileHeader(content []byte) (trimmed, goBuild []byte, sawBinaryOnly bool, err error) { + end := 0 + p := content + ended := false // found non-blank, non-// line, so stopped accepting // +build lines + inSlashStar := false // in /* */ comment + +Lines: + for len(p) > 0 { + line := p + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, p = line[:i], p[i+1:] + } else { + p = p[len(p):] + } + line = bytes.TrimSpace(line) + if len(line) == 0 && !ended { // Blank line + // Remember position of most recent blank line. + // When we find the first non-blank, non-// line, + // this "end" position marks the latest file position + // where a // +build line can appear. + // (It must appear _before_ a blank line before the non-blank, non-// line. + // Yes, that's confusing, which is part of why we moved to //go:build lines.) + // Note that ended==false here means that inSlashStar==false, + // since seeing a /* would have set ended==true. + end = len(content) - len(p) + continue Lines + } + if !bytes.HasPrefix(line, slashSlash) { // Not comment line + ended = true + } + + if !inSlashStar && isGoBuildComment(line) { + if false && goBuild != nil { // enabled in Go 1.N + return nil, nil, false, errMultipleGoBuild + } + goBuild = line + } + if !inSlashStar && bytes.Equal(line, binaryOnlyComment) { + sawBinaryOnly = true + } + + Comments: + for len(line) > 0 { + if inSlashStar { + if i := bytes.Index(line, starSlash); i >= 0 { + inSlashStar = false + line = bytes.TrimSpace(line[i+len(starSlash):]) + continue Comments + } + continue Lines + } + if bytes.HasPrefix(line, bSlashSlash) { + continue Lines + } + if bytes.HasPrefix(line, bSlashStar) { + inSlashStar = true + line = bytes.TrimSpace(line[len(bSlashStar):]) + continue Comments + } + // Found non-comment text. + break Lines + } + } + + return content[:end], goBuild, sawBinaryOnly, nil +} + +// saveCgo saves the information from the #cgo lines in the import "C" comment. +// These lines set CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS and pkg-config directives +// that affect the way cgo's C code is built. +func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) error { + text := cg.Text() + for _, line := range strings.Split(text, "\n") { + orig := line + + // Line is + // #cgo [GOOS/GOARCH...] LDFLAGS: stuff + // + line = strings.TrimSpace(line) + if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') { + continue + } + + // Split at colon. + line = strings.TrimSpace(line[4:]) + i := strings.Index(line, ":") + if i < 0 { + return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) + } + line, argstr := line[:i], line[i+1:] + + // Parse GOOS/GOARCH stuff. + f := strings.Fields(line) + if len(f) < 1 { + return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) + } + + cond, verb := f[:len(f)-1], f[len(f)-1] + if len(cond) > 0 { + ok := false + for _, c := range cond { + if ctxt.match(c, nil) { + ok = true + break + } + } + if !ok { + continue + } + } + + args, err := splitQuoted(argstr) + if err != nil { + return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) + } + var ok bool + for i, arg := range args { + if arg, ok = expandSrcDir(arg, di.Dir); !ok { + return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg) + } + args[i] = arg + } + + switch verb { + case "CFLAGS", "CPPFLAGS", "CXXFLAGS", "FFLAGS", "LDFLAGS": + // Change relative paths to absolute. + ctxt.makePathsAbsolute(args, di.Dir) + } + + switch verb { + case "CFLAGS": + di.CgoCFLAGS = append(di.CgoCFLAGS, args...) + case "CPPFLAGS": + di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...) + case "CXXFLAGS": + di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...) + case "FFLAGS": + di.CgoFFLAGS = append(di.CgoFFLAGS, args...) + case "LDFLAGS": + di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...) + case "pkg-config": + di.CgoPkgConfig = append(di.CgoPkgConfig, args...) + default: + return fmt.Errorf("%s: invalid #cgo verb: %s", filename, orig) + } + } + return nil +} + +// expandSrcDir expands any occurrence of ${SRCDIR}, making sure +// the result is safe for the shell. +func expandSrcDir(str string, srcdir string) (string, bool) { + // "\" delimited paths cause safeCgoName to fail + // so convert native paths with a different delimiter + // to "/" before starting (eg: on windows). + srcdir = filepath.ToSlash(srcdir) + + chunks := strings.Split(str, "${SRCDIR}") + if len(chunks) < 2 { + return str, safeCgoName(str) + } + ok := true + for _, chunk := range chunks { + ok = ok && (chunk == "" || safeCgoName(chunk)) + } + ok = ok && (srcdir == "" || safeCgoName(srcdir)) + res := strings.Join(chunks, srcdir) + return res, ok && res != "" +} + +// makePathsAbsolute looks for compiler options that take paths and +// makes them absolute. We do this because through the 1.8 release we +// ran the compiler in the package directory, so any relative -I or -L +// options would be relative to that directory. In 1.9 we changed to +// running the compiler in the build directory, to get consistent +// build results (issue #19964). To keep builds working, we change any +// relative -I or -L options to be absolute. +// +// Using filepath.IsAbs and filepath.Join here means the results will be +// different on different systems, but that's OK: -I and -L options are +// inherently system-dependent. +func (ctxt *Context) makePathsAbsolute(args []string, srcDir string) { + nextPath := false + for i, arg := range args { + if nextPath { + if !filepath.IsAbs(arg) { + args[i] = filepath.Join(srcDir, arg) + } + nextPath = false + } else if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") { + if len(arg) == 2 { + nextPath = true + } else { + if !filepath.IsAbs(arg[2:]) { + args[i] = arg[:2] + filepath.Join(srcDir, arg[2:]) + } + } + } + } +} + +// NOTE: $ is not safe for the shell, but it is allowed here because of linker options like -Wl,$ORIGIN. +// We never pass these arguments to a shell (just to programs we construct argv for), so this should be okay. +// See golang.org/issue/6038. +// The @ is for OS X. See golang.org/issue/13720. +// The % is for Jenkins. See golang.org/issue/16959. +// The ! is because module paths may use them. See golang.org/issue/26716. +// The ~ and ^ are for sr.ht. See golang.org/issue/32260. +const safeString = "+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$@%! ~^" + +func safeCgoName(s string) bool { + if s == "" { + return false + } + for i := 0; i < len(s); i++ { + if c := s[i]; c < utf8.RuneSelf && strings.IndexByte(safeString, c) < 0 { + return false + } + } + return true +} + +// splitQuoted splits the string s around each instance of one or more consecutive +// white space characters while taking into account quotes and escaping, and +// returns an array of substrings of s or an empty list if s contains only white space. +// Single quotes and double quotes are recognized to prevent splitting within the +// quoted region, and are removed from the resulting substrings. If a quote in s +// isn't closed err will be set and r will have the unclosed argument as the +// last element. The backslash is used for escaping. +// +// For example, the following string: +// +// a b:"c d" 'e''f' "g\"" +// +// Would be parsed as: +// +// []string{"a", "b:c d", "ef", `g"`} +// +func splitQuoted(s string) (r []string, err error) { + var args []string + arg := make([]rune, len(s)) + escaped := false + quoted := false + quote := '\x00' + i := 0 + for _, rune := range s { + switch { + case escaped: + escaped = false + case rune == '\\': + escaped = true + continue + case quote != '\x00': + if rune == quote { + quote = '\x00' + continue + } + case rune == '"' || rune == '\'': + quoted = true + quote = rune + continue + case unicode.IsSpace(rune): + if quoted || i > 0 { + quoted = false + args = append(args, string(arg[:i])) + i = 0 + } + continue + } + arg[i] = rune + i++ + } + if quoted || i > 0 { + args = append(args, string(arg[:i])) + } + if quote != 0 { + err = errors.New("unclosed quote") + } else if escaped { + err = errors.New("unfinished escaping") + } + return args, err +} + +// match reports whether the name is one of: +// +// $GOOS +// $GOARCH +// cgo (if cgo is enabled) +// !cgo (if cgo is disabled) +// ctxt.Compiler +// !ctxt.Compiler +// tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags) +// !tag (if tag is not listed in ctxt.BuildTags or ctxt.ReleaseTags) +// a comma-separated list of any of these +// +func (ctxt *Context) match(name string, allTags map[string]bool) bool { + if name == "" { + if allTags != nil { + allTags[name] = true + } + return false + } + if i := strings.Index(name, ","); i >= 0 { + // comma-separated list + ok1 := ctxt.match(name[:i], allTags) + ok2 := ctxt.match(name[i+1:], allTags) + return ok1 && ok2 + } + if strings.HasPrefix(name, "!!") { // bad syntax, reject always + return false + } + if strings.HasPrefix(name, "!") { // negation + return len(name) > 1 && !ctxt.match(name[1:], allTags) + } + + if allTags != nil { + allTags[name] = true + } + + // Tags must be letters, digits, underscores or dots. + // Unlike in Go identifiers, all digits are fine (e.g., "386"). + for _, c := range name { + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { + return false + } + } + + // special tags + if ctxt.CgoEnabled && name == "cgo" { + return true + } + if name == ctxt.GOOS || name == ctxt.GOARCH || name == ctxt.Compiler { + return true + } + if ctxt.GOOS == "android" && name == "linux" { + return true + } + if ctxt.GOOS == "illumos" && name == "solaris" { + return true + } + if ctxt.GOOS == "ios" && name == "darwin" { + return true + } + + // other tags + for _, tag := range ctxt.BuildTags { + if tag == name { + return true + } + } + for _, tag := range ctxt.ReleaseTags { + if tag == name { + return true + } + } + + return false +} + +// goodOSArchFile returns false if the name contains a $GOOS or $GOARCH +// suffix which does not match the current system. +// The recognized name formats are: +// +// name_$(GOOS).* +// name_$(GOARCH).* +// name_$(GOOS)_$(GOARCH).* +// name_$(GOOS)_test.* +// name_$(GOARCH)_test.* +// name_$(GOOS)_$(GOARCH)_test.* +// +// Exceptions: +// if GOOS=android, then files with GOOS=linux are also matched. +// if GOOS=illumos, then files with GOOS=solaris are also matched. +// if GOOS=ios, then files with GOOS=darwin are also matched. +func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool { + if dot := strings.Index(name, "."); dot != -1 { + name = name[:dot] + } + + // Before Go 1.4, a file called "linux.go" would be equivalent to having a + // build tag "linux" in that file. For Go 1.4 and beyond, we require this + // auto-tagging to apply only to files with a non-empty prefix, so + // "foo_linux.go" is tagged but "linux.go" is not. This allows new operating + // systems, such as android, to arrive without breaking existing code with + // innocuous source code in "android.go". The easiest fix: cut everything + // in the name before the initial _. + i := strings.Index(name, "_") + if i < 0 { + return true + } + name = name[i:] // ignore everything before first _ + + l := strings.Split(name, "_") + if n := len(l); n > 0 && l[n-1] == "test" { + l = l[:n-1] + } + n := len(l) + if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] { + return ctxt.match(l[n-1], allTags) && ctxt.match(l[n-2], allTags) + } + if n >= 1 && (knownOS[l[n-1]] || knownArch[l[n-1]]) { + return ctxt.match(l[n-1], allTags) + } + return true +} + +var knownOS = make(map[string]bool) +var knownArch = make(map[string]bool) + +func init() { + for _, v := range strings.Fields(goosList) { + knownOS[v] = true + } + for _, v := range strings.Fields(goarchList) { + knownArch[v] = true + } +} + +// ToolDir is the directory containing build tools. +var ToolDir = getToolDir() + +// IsLocalImport reports whether the import path is +// a local import path, like ".", "..", "./foo", or "../foo". +func IsLocalImport(path string) bool { + return path == "." || path == ".." || + strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../") +} + +// ArchChar returns "?" and an error. +// In earlier versions of Go, the returned string was used to derive +// the compiler and linker tool names, the default object file suffix, +// and the default linker output name. As of Go 1.5, those strings +// no longer vary by architecture; they are compile, link, .o, and a.out, respectively. +func ArchChar(goarch string) (string, error) { + return "?", errors.New("architecture letter no longer used") +} diff --git a/src/go/build/build_test.go b/src/go/build/build_test.go new file mode 100644 index 0000000..d13ea81 --- /dev/null +++ b/src/go/build/build_test.go @@ -0,0 +1,712 @@ +// Copyright 2011 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 build + +import ( + "flag" + "internal/testenv" + "io" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + "testing" +) + +func TestMain(m *testing.M) { + flag.Parse() + if goTool, err := testenv.GoTool(); err == nil { + os.Setenv("PATH", filepath.Dir(goTool)+string(os.PathListSeparator)+os.Getenv("PATH")) + } + os.Exit(m.Run()) +} + +func TestMatch(t *testing.T) { + ctxt := Default + what := "default" + match := func(tag string, want map[string]bool) { + t.Helper() + m := make(map[string]bool) + if !ctxt.match(tag, m) { + t.Errorf("%s context should match %s, does not", what, tag) + } + if !reflect.DeepEqual(m, want) { + t.Errorf("%s tags = %v, want %v", tag, m, want) + } + } + nomatch := func(tag string, want map[string]bool) { + t.Helper() + m := make(map[string]bool) + if ctxt.match(tag, m) { + t.Errorf("%s context should NOT match %s, does", what, tag) + } + if !reflect.DeepEqual(m, want) { + t.Errorf("%s tags = %v, want %v", tag, m, want) + } + } + + match(runtime.GOOS+","+runtime.GOARCH, map[string]bool{runtime.GOOS: true, runtime.GOARCH: true}) + match(runtime.GOOS+","+runtime.GOARCH+",!foo", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "foo": true}) + nomatch(runtime.GOOS+","+runtime.GOARCH+",foo", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "foo": true}) + + what = "modified" + ctxt.BuildTags = []string{"foo"} + match(runtime.GOOS+","+runtime.GOARCH, map[string]bool{runtime.GOOS: true, runtime.GOARCH: true}) + match(runtime.GOOS+","+runtime.GOARCH+",foo", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "foo": true}) + nomatch(runtime.GOOS+","+runtime.GOARCH+",!foo", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "foo": true}) + match(runtime.GOOS+","+runtime.GOARCH+",!bar", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "bar": true}) + nomatch(runtime.GOOS+","+runtime.GOARCH+",bar", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "bar": true}) +} + +func TestDotSlashImport(t *testing.T) { + p, err := ImportDir("testdata/other", 0) + if err != nil { + t.Fatal(err) + } + if len(p.Imports) != 1 || p.Imports[0] != "./file" { + t.Fatalf("testdata/other: Imports=%v, want [./file]", p.Imports) + } + + p1, err := Import("./file", "testdata/other", 0) + if err != nil { + t.Fatal(err) + } + if p1.Name != "file" { + t.Fatalf("./file: Name=%q, want %q", p1.Name, "file") + } + dir := filepath.Clean("testdata/other/file") // Clean to use \ on Windows + if p1.Dir != dir { + t.Fatalf("./file: Dir=%q, want %q", p1.Name, dir) + } +} + +func TestEmptyImport(t *testing.T) { + p, err := Import("", Default.GOROOT, FindOnly) + if err == nil { + t.Fatal(`Import("") returned nil error.`) + } + if p == nil { + t.Fatal(`Import("") returned nil package.`) + } + if p.ImportPath != "" { + t.Fatalf("ImportPath=%q, want %q.", p.ImportPath, "") + } +} + +func TestEmptyFolderImport(t *testing.T) { + _, err := Import(".", "testdata/empty", 0) + if _, ok := err.(*NoGoError); !ok { + t.Fatal(`Import("testdata/empty") did not return NoGoError.`) + } +} + +func TestMultiplePackageImport(t *testing.T) { + _, err := Import(".", "testdata/multi", 0) + mpe, ok := err.(*MultiplePackageError) + if !ok { + t.Fatal(`Import("testdata/multi") did not return MultiplePackageError.`) + } + want := &MultiplePackageError{ + Dir: filepath.FromSlash("testdata/multi"), + Packages: []string{"main", "test_package"}, + Files: []string{"file.go", "file_appengine.go"}, + } + if !reflect.DeepEqual(mpe, want) { + t.Errorf("got %#v; want %#v", mpe, want) + } +} + +func TestLocalDirectory(t *testing.T) { + if runtime.GOOS == "ios" { + t.Skipf("skipping on %s/%s, no valid GOROOT", runtime.GOOS, runtime.GOARCH) + } + + cwd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + + p, err := ImportDir(cwd, 0) + if err != nil { + t.Fatal(err) + } + if p.ImportPath != "go/build" { + t.Fatalf("ImportPath=%q, want %q", p.ImportPath, "go/build") + } +} + +var shouldBuildTests = []struct { + name string + content string + tags map[string]bool + binaryOnly bool + shouldBuild bool + err error +}{ + { + name: "Yes", + content: "// +build yes\n\n" + + "package main\n", + tags: map[string]bool{"yes": true}, + shouldBuild: true, + }, + { + name: "Or", + content: "// +build no yes\n\n" + + "package main\n", + tags: map[string]bool{"yes": true, "no": true}, + shouldBuild: true, + }, + { + name: "And", + content: "// +build no,yes\n\n" + + "package main\n", + tags: map[string]bool{"yes": true, "no": true}, + shouldBuild: false, + }, + { + name: "Cgo", + content: "// +build cgo\n\n" + + "// Copyright The Go Authors.\n\n" + + "// This package implements parsing of tags like\n" + + "// +build tag1\n" + + "package build", + tags: map[string]bool{"cgo": true}, + shouldBuild: false, + }, + { + name: "AfterPackage", + content: "// Copyright The Go Authors.\n\n" + + "package build\n\n" + + "// shouldBuild checks tags given by lines of the form\n" + + "// +build tag\n" + + "func shouldBuild(content []byte)\n", + tags: map[string]bool{}, + shouldBuild: true, + }, + { + name: "TooClose", + content: "// +build yes\n" + + "package main\n", + tags: map[string]bool{}, + shouldBuild: true, + }, + { + name: "TooCloseNo", + content: "// +build no\n" + + "package main\n", + tags: map[string]bool{}, + shouldBuild: true, + }, + { + name: "BinaryOnly", + content: "//go:binary-only-package\n" + + "// +build yes\n" + + "package main\n", + tags: map[string]bool{}, + binaryOnly: true, + shouldBuild: true, + }, + { + name: "ValidGoBuild", + content: "// +build yes\n\n" + + "//go:build no\n" + + "package main\n", + tags: map[string]bool{"yes": true}, + shouldBuild: true, + }, + { + name: "MissingBuild", + content: "//go:build no\n" + + "package main\n", + tags: map[string]bool{}, + shouldBuild: false, + err: errGoBuildWithoutBuild, + }, + { + name: "MissingBuild2", + content: "/* */\n" + + "// +build yes\n\n" + + "//go:build no\n" + + "package main\n", + tags: map[string]bool{}, + shouldBuild: false, + err: errGoBuildWithoutBuild, + }, + { + name: "MissingBuild2", + content: "/*\n" + + "// +build yes\n\n" + + "*/\n" + + "//go:build no\n" + + "package main\n", + tags: map[string]bool{}, + shouldBuild: false, + err: errGoBuildWithoutBuild, + }, + { + name: "Comment1", + content: "/*\n" + + "//go:build no\n" + + "*/\n\n" + + "package main\n", + tags: map[string]bool{}, + shouldBuild: true, + }, + { + name: "Comment2", + content: "/*\n" + + "text\n" + + "*/\n\n" + + "//go:build no\n" + + "package main\n", + tags: map[string]bool{}, + shouldBuild: false, + err: errGoBuildWithoutBuild, + }, + { + name: "Comment3", + content: "/*/*/ /* hi *//* \n" + + "text\n" + + "*/\n\n" + + "//go:build no\n" + + "package main\n", + tags: map[string]bool{}, + shouldBuild: false, + err: errGoBuildWithoutBuild, + }, + { + name: "Comment4", + content: "/**///go:build no\n" + + "package main\n", + tags: map[string]bool{}, + shouldBuild: true, + }, + { + name: "Comment5", + content: "/**/\n" + + "//go:build no\n" + + "package main\n", + tags: map[string]bool{}, + shouldBuild: false, + err: errGoBuildWithoutBuild, + }, +} + +func TestShouldBuild(t *testing.T) { + for _, tt := range shouldBuildTests { + t.Run(tt.name, func(t *testing.T) { + ctx := &Context{BuildTags: []string{"yes"}} + tags := map[string]bool{} + shouldBuild, binaryOnly, err := ctx.shouldBuild([]byte(tt.content), tags) + if shouldBuild != tt.shouldBuild || binaryOnly != tt.binaryOnly || !reflect.DeepEqual(tags, tt.tags) || err != tt.err { + t.Errorf("mismatch:\n"+ + "have shouldBuild=%v, binaryOnly=%v, tags=%v, err=%v\n"+ + "want shouldBuild=%v, binaryOnly=%v, tags=%v, err=%v", + shouldBuild, binaryOnly, tags, err, + tt.shouldBuild, tt.binaryOnly, tt.tags, tt.err) + } + }) + } +} + +func TestGoodOSArchFile(t *testing.T) { + ctx := &Context{BuildTags: []string{"linux"}, GOOS: "darwin"} + m := map[string]bool{} + want := map[string]bool{"linux": true} + if !ctx.goodOSArchFile("hello_linux.go", m) { + t.Errorf("goodOSArchFile(hello_linux.go) = false, want true") + } + if !reflect.DeepEqual(m, want) { + t.Errorf("goodOSArchFile(hello_linux.go) tags = %v, want %v", m, want) + } +} + +type readNopCloser struct { + io.Reader +} + +func (r readNopCloser) Close() error { + return nil +} + +var ( + ctxtP9 = Context{GOARCH: "arm", GOOS: "plan9"} + ctxtAndroid = Context{GOARCH: "arm", GOOS: "android"} +) + +var matchFileTests = []struct { + ctxt Context + name string + data string + match bool +}{ + {ctxtP9, "foo_arm.go", "", true}, + {ctxtP9, "foo1_arm.go", "// +build linux\n\npackage main\n", false}, + {ctxtP9, "foo_darwin.go", "", false}, + {ctxtP9, "foo.go", "", true}, + {ctxtP9, "foo1.go", "// +build linux\n\npackage main\n", false}, + {ctxtP9, "foo.badsuffix", "", false}, + {ctxtAndroid, "foo_linux.go", "", true}, + {ctxtAndroid, "foo_android.go", "", true}, + {ctxtAndroid, "foo_plan9.go", "", false}, + {ctxtAndroid, "android.go", "", true}, + {ctxtAndroid, "plan9.go", "", true}, + {ctxtAndroid, "plan9_test.go", "", true}, + {ctxtAndroid, "arm.s", "", true}, + {ctxtAndroid, "amd64.s", "", true}, +} + +func TestMatchFile(t *testing.T) { + for _, tt := range matchFileTests { + ctxt := tt.ctxt + ctxt.OpenFile = func(path string) (r io.ReadCloser, err error) { + if path != "x+"+tt.name { + t.Fatalf("OpenFile asked for %q, expected %q", path, "x+"+tt.name) + } + return &readNopCloser{strings.NewReader(tt.data)}, nil + } + ctxt.JoinPath = func(elem ...string) string { + return strings.Join(elem, "+") + } + match, err := ctxt.MatchFile("x", tt.name) + if match != tt.match || err != nil { + t.Fatalf("MatchFile(%q) = %v, %v, want %v, nil", tt.name, match, err, tt.match) + } + } +} + +func TestImportCmd(t *testing.T) { + if runtime.GOOS == "ios" { + t.Skipf("skipping on %s/%s, no valid GOROOT", runtime.GOOS, runtime.GOARCH) + } + + p, err := Import("cmd/internal/objfile", "", 0) + if err != nil { + t.Fatal(err) + } + if !strings.HasSuffix(filepath.ToSlash(p.Dir), "src/cmd/internal/objfile") { + t.Fatalf("Import cmd/internal/objfile returned Dir=%q, want %q", filepath.ToSlash(p.Dir), ".../src/cmd/internal/objfile") + } +} + +var ( + expandSrcDirPath = filepath.Join(string(filepath.Separator)+"projects", "src", "add") +) + +var expandSrcDirTests = []struct { + input, expected string +}{ + {"-L ${SRCDIR}/libs -ladd", "-L /projects/src/add/libs -ladd"}, + {"${SRCDIR}/add_linux_386.a -pthread -lstdc++", "/projects/src/add/add_linux_386.a -pthread -lstdc++"}, + {"Nothing to expand here!", "Nothing to expand here!"}, + {"$", "$"}, + {"$$", "$$"}, + {"${", "${"}, + {"$}", "$}"}, + {"$FOO ${BAR}", "$FOO ${BAR}"}, + {"Find me the $SRCDIRECTORY.", "Find me the $SRCDIRECTORY."}, + {"$SRCDIR is missing braces", "$SRCDIR is missing braces"}, +} + +func TestExpandSrcDir(t *testing.T) { + for _, test := range expandSrcDirTests { + output, _ := expandSrcDir(test.input, expandSrcDirPath) + if output != test.expected { + t.Errorf("%q expands to %q with SRCDIR=%q when %q is expected", test.input, output, expandSrcDirPath, test.expected) + } else { + t.Logf("%q expands to %q with SRCDIR=%q", test.input, output, expandSrcDirPath) + } + } +} + +func TestShellSafety(t *testing.T) { + tests := []struct { + input, srcdir, expected string + result bool + }{ + {"-I${SRCDIR}/../include", "/projects/src/issue 11868", "-I/projects/src/issue 11868/../include", true}, + {"-I${SRCDIR}", "~wtf$@%^", "-I~wtf$@%^", true}, + {"-X${SRCDIR}/1,${SRCDIR}/2", "/projects/src/issue 11868", "-X/projects/src/issue 11868/1,/projects/src/issue 11868/2", true}, + {"-I/tmp -I/tmp", "/tmp2", "-I/tmp -I/tmp", true}, + {"-I/tmp", "/tmp/[0]", "-I/tmp", true}, + {"-I${SRCDIR}/dir", "/tmp/[0]", "-I/tmp/[0]/dir", false}, + {"-I${SRCDIR}/dir", "/tmp/go go", "-I/tmp/go go/dir", true}, + {"-I${SRCDIR}/dir dir", "/tmp/go", "-I/tmp/go/dir dir", true}, + } + for _, test := range tests { + output, ok := expandSrcDir(test.input, test.srcdir) + if ok != test.result { + t.Errorf("Expected %t while %q expands to %q with SRCDIR=%q; got %t", test.result, test.input, output, test.srcdir, ok) + } + if output != test.expected { + t.Errorf("Expected %q while %q expands with SRCDIR=%q; got %q", test.expected, test.input, test.srcdir, output) + } + } +} + +// Want to get a "cannot find package" error when directory for package does not exist. +// There should be valid partial information in the returned non-nil *Package. +func TestImportDirNotExist(t *testing.T) { + testenv.MustHaveGoBuild(t) // really must just have source + ctxt := Default + + emptyDir, err := os.MkdirTemp("", t.Name()) + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(emptyDir) + + ctxt.GOPATH = emptyDir + ctxt.Dir = emptyDir + + tests := []struct { + label string + path, srcDir string + mode ImportMode + }{ + {"Import(full, 0)", "go/build/doesnotexist", "", 0}, + {"Import(local, 0)", "./doesnotexist", filepath.Join(ctxt.GOROOT, "src/go/build"), 0}, + {"Import(full, FindOnly)", "go/build/doesnotexist", "", FindOnly}, + {"Import(local, FindOnly)", "./doesnotexist", filepath.Join(ctxt.GOROOT, "src/go/build"), FindOnly}, + } + + defer os.Setenv("GO111MODULE", os.Getenv("GO111MODULE")) + + for _, GO111MODULE := range []string{"off", "on"} { + t.Run("GO111MODULE="+GO111MODULE, func(t *testing.T) { + os.Setenv("GO111MODULE", GO111MODULE) + + for _, test := range tests { + p, err := ctxt.Import(test.path, test.srcDir, test.mode) + + errOk := (err != nil && strings.HasPrefix(err.Error(), "cannot find package")) + wantErr := `"cannot find package" error` + if test.srcDir == "" { + if err != nil && strings.Contains(err.Error(), "is not in GOROOT") { + errOk = true + } + wantErr = `"cannot find package" or "is not in GOROOT" error` + } + if !errOk { + t.Errorf("%s got error: %q, want %s", test.label, err, wantErr) + } + // If an error occurs, build.Import is documented to return + // a non-nil *Package containing partial information. + if p == nil { + t.Fatalf(`%s got nil p, want non-nil *Package`, test.label) + } + // Verify partial information in p. + if p.ImportPath != "go/build/doesnotexist" { + t.Errorf(`%s got p.ImportPath: %q, want "go/build/doesnotexist"`, test.label, p.ImportPath) + } + } + }) + } +} + +func TestImportVendor(t *testing.T) { + testenv.MustHaveGoBuild(t) // really must just have source + + defer os.Setenv("GO111MODULE", os.Getenv("GO111MODULE")) + os.Setenv("GO111MODULE", "off") + + ctxt := Default + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + ctxt.GOPATH = filepath.Join(wd, "testdata/withvendor") + p, err := ctxt.Import("c/d", filepath.Join(ctxt.GOPATH, "src/a/b"), 0) + if err != nil { + t.Fatalf("cannot find vendored c/d from testdata src/a/b directory: %v", err) + } + want := "a/vendor/c/d" + if p.ImportPath != want { + t.Fatalf("Import succeeded but found %q, want %q", p.ImportPath, want) + } +} + +func TestImportVendorFailure(t *testing.T) { + testenv.MustHaveGoBuild(t) // really must just have source + + defer os.Setenv("GO111MODULE", os.Getenv("GO111MODULE")) + os.Setenv("GO111MODULE", "off") + + ctxt := Default + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + ctxt.GOPATH = filepath.Join(wd, "testdata/withvendor") + p, err := ctxt.Import("x.com/y/z", filepath.Join(ctxt.GOPATH, "src/a/b"), 0) + if err == nil { + t.Fatalf("found made-up package x.com/y/z in %s", p.Dir) + } + + e := err.Error() + if !strings.Contains(e, " (vendor tree)") { + t.Fatalf("error on failed import does not mention GOROOT/src/vendor directory:\n%s", e) + } +} + +func TestImportVendorParentFailure(t *testing.T) { + testenv.MustHaveGoBuild(t) // really must just have source + + defer os.Setenv("GO111MODULE", os.Getenv("GO111MODULE")) + os.Setenv("GO111MODULE", "off") + + ctxt := Default + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + ctxt.GOPATH = filepath.Join(wd, "testdata/withvendor") + // This import should fail because the vendor/c directory has no source code. + p, err := ctxt.Import("c", filepath.Join(ctxt.GOPATH, "src/a/b"), 0) + if err == nil { + t.Fatalf("found empty parent in %s", p.Dir) + } + if p != nil && p.Dir != "" { + t.Fatalf("decided to use %s", p.Dir) + } + e := err.Error() + if !strings.Contains(e, " (vendor tree)") { + t.Fatalf("error on failed import does not mention GOROOT/src/vendor directory:\n%s", e) + } +} + +// Check that a package is loaded in module mode if GO111MODULE=on, even when +// no go.mod file is present. It should fail to resolve packages outside std. +// Verifies golang.org/issue/34669. +func TestImportPackageOutsideModule(t *testing.T) { + testenv.MustHaveGoBuild(t) + + // Disable module fetching for this test so that 'go list' fails quickly + // without trying to find the latest version of a module. + defer os.Setenv("GOPROXY", os.Getenv("GOPROXY")) + os.Setenv("GOPROXY", "off") + + // Create a GOPATH in a temporary directory. We don't use testdata + // because it's in GOROOT, which interferes with the module heuristic. + gopath, err := os.MkdirTemp("", "gobuild-notmodule") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(gopath) + if err := os.MkdirAll(filepath.Join(gopath, "src/example.com/p"), 0777); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(gopath, "src/example.com/p/p.go"), []byte("package p"), 0666); err != nil { + t.Fatal(err) + } + + defer os.Setenv("GO111MODULE", os.Getenv("GO111MODULE")) + os.Setenv("GO111MODULE", "on") + defer os.Setenv("GOPATH", os.Getenv("GOPATH")) + os.Setenv("GOPATH", gopath) + ctxt := Default + ctxt.GOPATH = gopath + ctxt.Dir = filepath.Join(gopath, "src/example.com/p") + + want := "go.mod file not found in current directory or any parent directory" + if _, err := ctxt.Import("example.com/p", gopath, FindOnly); err == nil { + t.Fatal("importing package when no go.mod is present succeeded unexpectedly") + } else if errStr := err.Error(); !strings.Contains(errStr, want) { + t.Fatalf("error when importing package when no go.mod is present: got %q; want %q", errStr, want) + } else { + t.Logf(`ctxt.Import("example.com/p", _, FindOnly): %v`, err) + } +} + +func TestImportDirTarget(t *testing.T) { + testenv.MustHaveGoBuild(t) // really must just have source + ctxt := Default + ctxt.GOPATH = "" + p, err := ctxt.ImportDir(filepath.Join(ctxt.GOROOT, "src/path"), 0) + if err != nil { + t.Fatal(err) + } + if p.PkgTargetRoot == "" || p.PkgObj == "" { + t.Errorf("p.PkgTargetRoot == %q, p.PkgObj == %q, want non-empty", p.PkgTargetRoot, p.PkgObj) + } +} + +// TestIssue23594 prevents go/build from regressing and populating Package.Doc +// from comments in test files. +func TestIssue23594(t *testing.T) { + // Package testdata/doc contains regular and external test files + // with comments attached to their package declarations. The names of the files + // ensure that we see the comments from the test files first. + p, err := ImportDir("testdata/doc", 0) + if err != nil { + t.Fatalf("could not import testdata: %v", err) + } + + if p.Doc != "Correct" { + t.Fatalf("incorrectly set .Doc to %q", p.Doc) + } +} + +// TestMissingImportErrorRepetition checks that when an unknown package is +// imported, the package path is only shown once in the error. +// Verifies golang.org/issue/34752. +func TestMissingImportErrorRepetition(t *testing.T) { + testenv.MustHaveGoBuild(t) // need 'go list' internally + tmp, err := os.MkdirTemp("", "") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + if err := os.WriteFile(filepath.Join(tmp, "go.mod"), []byte("module m"), 0666); err != nil { + t.Fatal(err) + } + defer os.Setenv("GO111MODULE", os.Getenv("GO111MODULE")) + os.Setenv("GO111MODULE", "on") + defer os.Setenv("GOPROXY", os.Getenv("GOPROXY")) + os.Setenv("GOPROXY", "off") + defer os.Setenv("GONOPROXY", os.Getenv("GONOPROXY")) + os.Setenv("GONOPROXY", "none") + + ctxt := Default + ctxt.Dir = tmp + + pkgPath := "example.com/hello" + _, err = ctxt.Import(pkgPath, tmp, FindOnly) + if err == nil { + t.Fatal("unexpected success") + } + + // Don't count the package path with a URL like https://...?go-get=1. + // See golang.org/issue/35986. + errStr := strings.ReplaceAll(err.Error(), "://"+pkgPath+"?go-get=1", "://...?go-get=1") + + // Also don't count instances in suggested "go get" or similar commands + // (see https://golang.org/issue/41576). The suggested command typically + // follows a semicolon. + errStr = strings.SplitN(errStr, ";", 2)[0] + + if n := strings.Count(errStr, pkgPath); n != 1 { + t.Fatalf("package path %q appears in error %d times; should appear once\nerror: %v", pkgPath, n, err) + } +} + +// TestCgoImportsIgnored checks that imports in cgo files are not included +// in the imports list when cgo is disabled. +// Verifies golang.org/issue/35946. +func TestCgoImportsIgnored(t *testing.T) { + ctxt := Default + ctxt.CgoEnabled = false + p, err := ctxt.ImportDir("testdata/cgo_disabled", 0) + if err != nil { + t.Fatal(err) + } + for _, path := range p.Imports { + if path == "should/be/ignored" { + t.Errorf("found import %q in ignored cgo file", path) + } + } +} diff --git a/src/go/build/constraint/expr.go b/src/go/build/constraint/expr.go new file mode 100644 index 0000000..3b27870 --- /dev/null +++ b/src/go/build/constraint/expr.go @@ -0,0 +1,574 @@ +// Copyright 2020 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 constraint implements parsing and evaluation of build constraint lines. +// See https://golang.org/cmd/go/#hdr-Build_constraints for documentation about build constraints themselves. +// +// This package parses both the original “// +build” syntax and the “//go:build” syntax that will be added in Go 1.17. +// The parser is being included in Go 1.16 to allow tools that need to process Go 1.17 source code +// to still be built against the Go 1.16 release. +// See https://golang.org/design/draft-gobuild for details about the “//go:build” syntax. +package constraint + +import ( + "errors" + "strings" + "unicode" + "unicode/utf8" +) + +// An Expr is a build tag constraint expression. +// The underlying concrete type is *AndExpr, *OrExpr, *NotExpr, or *TagExpr. +type Expr interface { + // String returns the string form of the expression, + // using the boolean syntax used in //go:build lines. + String() string + + // Eval reports whether the expression evaluates to true. + // It calls ok(tag) as needed to find out whether a given build tag + // is satisfied by the current build configuration. + Eval(ok func(tag string) bool) bool + + // The presence of an isExpr method explicitly marks the type as an Expr. + // Only implementations in this package should be used as Exprs. + isExpr() +} + +// A TagExpr is an Expr for the single tag Tag. +type TagExpr struct { + Tag string // for example, “linux” or “cgo” +} + +func (x *TagExpr) isExpr() {} + +func (x *TagExpr) Eval(ok func(tag string) bool) bool { + return ok(x.Tag) +} + +func (x *TagExpr) String() string { + return x.Tag +} + +func tag(tag string) Expr { return &TagExpr{tag} } + +// A NotExpr represents the expression !X (the negation of X). +type NotExpr struct { + X Expr +} + +func (x *NotExpr) isExpr() {} + +func (x *NotExpr) Eval(ok func(tag string) bool) bool { + return !x.X.Eval(ok) +} + +func (x *NotExpr) String() string { + s := x.X.String() + switch x.X.(type) { + case *AndExpr, *OrExpr: + s = "(" + s + ")" + } + return "!" + s +} + +func not(x Expr) Expr { return &NotExpr{x} } + +// An AndExpr represents the expression X && Y. +type AndExpr struct { + X, Y Expr +} + +func (x *AndExpr) isExpr() {} + +func (x *AndExpr) Eval(ok func(tag string) bool) bool { + // Note: Eval both, to make sure ok func observes all tags. + xok := x.X.Eval(ok) + yok := x.Y.Eval(ok) + return xok && yok +} + +func (x *AndExpr) String() string { + return andArg(x.X) + " && " + andArg(x.Y) +} + +func andArg(x Expr) string { + s := x.String() + if _, ok := x.(*OrExpr); ok { + s = "(" + s + ")" + } + return s +} + +func and(x, y Expr) Expr { + return &AndExpr{x, y} +} + +// An OrExpr represents the expression X || Y. +type OrExpr struct { + X, Y Expr +} + +func (x *OrExpr) isExpr() {} + +func (x *OrExpr) Eval(ok func(tag string) bool) bool { + // Note: Eval both, to make sure ok func observes all tags. + xok := x.X.Eval(ok) + yok := x.Y.Eval(ok) + return xok || yok +} + +func (x *OrExpr) String() string { + return orArg(x.X) + " || " + orArg(x.Y) +} + +func orArg(x Expr) string { + s := x.String() + if _, ok := x.(*AndExpr); ok { + s = "(" + s + ")" + } + return s +} + +func or(x, y Expr) Expr { + return &OrExpr{x, y} +} + +// A SyntaxError reports a syntax error in a parsed build expression. +type SyntaxError struct { + Offset int // byte offset in input where error was detected + Err string // description of error +} + +func (e *SyntaxError) Error() string { + return e.Err +} + +var errNotConstraint = errors.New("not a build constraint") + +// Parse parses a single build constraint line of the form “//go:build ...” or “// +build ...” +// and returns the corresponding boolean expression. +func Parse(line string) (Expr, error) { + if text, ok := splitGoBuild(line); ok { + return parseExpr(text) + } + if text, ok := splitPlusBuild(line); ok { + return parsePlusBuildExpr(text), nil + } + return nil, errNotConstraint +} + +// IsGoBuild reports whether the line of text is a “//go:build” constraint. +// It only checks the prefix of the text, not that the expression itself parses. +func IsGoBuild(line string) bool { + _, ok := splitGoBuild(line) + return ok +} + +// splitGoBuild splits apart the leading //go:build prefix in line from the build expression itself. +// It returns "", false if the input is not a //go:build line or if the input contains multiple lines. +func splitGoBuild(line string) (expr string, ok bool) { + // A single trailing newline is OK; otherwise multiple lines are not. + if len(line) > 0 && line[len(line)-1] == '\n' { + line = line[:len(line)-1] + } + if strings.Contains(line, "\n") { + return "", false + } + + if !strings.HasPrefix(line, "//go:build") { + return "", false + } + + line = strings.TrimSpace(line) + line = line[len("//go:build"):] + + // If strings.TrimSpace finds more to trim after removing the //go:build prefix, + // it means that the prefix was followed by a space, making this a //go:build line + // (as opposed to a //go:buildsomethingelse line). + // If line is empty, we had "//go:build" by itself, which also counts. + trim := strings.TrimSpace(line) + if len(line) == len(trim) && line != "" { + return "", false + } + + return trim, true +} + +// An exprParser holds state for parsing a build expression. +type exprParser struct { + s string // input string + i int // next read location in s + + tok string // last token read + isTag bool + pos int // position (start) of last token +} + +// parseExpr parses a boolean build tag expression. +func parseExpr(text string) (x Expr, err error) { + defer func() { + if e := recover(); e != nil { + if e, ok := e.(*SyntaxError); ok { + err = e + return + } + panic(e) // unreachable unless parser has a bug + } + }() + + p := &exprParser{s: text} + x = p.or() + if p.tok != "" { + panic(&SyntaxError{Offset: p.pos, Err: "unexpected token " + p.tok}) + } + return x, nil +} + +// or parses a sequence of || expressions. +// On entry, the next input token has not yet been lexed. +// On exit, the next input token has been lexed and is in p.tok. +func (p *exprParser) or() Expr { + x := p.and() + for p.tok == "||" { + x = or(x, p.and()) + } + return x +} + +// and parses a sequence of && expressions. +// On entry, the next input token has not yet been lexed. +// On exit, the next input token has been lexed and is in p.tok. +func (p *exprParser) and() Expr { + x := p.not() + for p.tok == "&&" { + x = and(x, p.not()) + } + return x +} + +// not parses a ! expression. +// On entry, the next input token has not yet been lexed. +// On exit, the next input token has been lexed and is in p.tok. +func (p *exprParser) not() Expr { + p.lex() + if p.tok == "!" { + p.lex() + if p.tok == "!" { + panic(&SyntaxError{Offset: p.pos, Err: "double negation not allowed"}) + } + return not(p.atom()) + } + return p.atom() +} + +// atom parses a tag or a parenthesized expression. +// On entry, the next input token HAS been lexed. +// On exit, the next input token has been lexed and is in p.tok. +func (p *exprParser) atom() Expr { + // first token already in p.tok + if p.tok == "(" { + pos := p.pos + defer func() { + if e := recover(); e != nil { + if e, ok := e.(*SyntaxError); ok && e.Err == "unexpected end of expression" { + e.Err = "missing close paren" + } + panic(e) + } + }() + x := p.or() + if p.tok != ")" { + panic(&SyntaxError{Offset: pos, Err: "missing close paren"}) + } + p.lex() + return x + } + + if !p.isTag { + if p.tok == "" { + panic(&SyntaxError{Offset: p.pos, Err: "unexpected end of expression"}) + } + panic(&SyntaxError{Offset: p.pos, Err: "unexpected token " + p.tok}) + } + tok := p.tok + p.lex() + return tag(tok) +} + +// lex finds and consumes the next token in the input stream. +// On return, p.tok is set to the token text, +// p.isTag reports whether the token was a tag, +// and p.pos records the byte offset of the start of the token in the input stream. +// If lex reaches the end of the input, p.tok is set to the empty string. +// For any other syntax error, lex panics with a SyntaxError. +func (p *exprParser) lex() { + p.isTag = false + for p.i < len(p.s) && (p.s[p.i] == ' ' || p.s[p.i] == '\t') { + p.i++ + } + if p.i >= len(p.s) { + p.tok = "" + p.pos = p.i + return + } + switch p.s[p.i] { + case '(', ')', '!': + p.pos = p.i + p.i++ + p.tok = p.s[p.pos:p.i] + return + + case '&', '|': + if p.i+1 >= len(p.s) || p.s[p.i+1] != p.s[p.i] { + panic(&SyntaxError{Offset: p.i, Err: "invalid syntax at " + string(rune(p.s[p.i]))}) + } + p.pos = p.i + p.i += 2 + p.tok = p.s[p.pos:p.i] + return + } + + tag := p.s[p.i:] + for i, c := range tag { + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { + tag = tag[:i] + break + } + } + if tag == "" { + c, _ := utf8.DecodeRuneInString(p.s[p.i:]) + panic(&SyntaxError{Offset: p.i, Err: "invalid syntax at " + string(c)}) + } + + p.pos = p.i + p.i += len(tag) + p.tok = p.s[p.pos:p.i] + p.isTag = true + return +} + +// IsPlusBuild reports whether the line of text is a “// +build” constraint. +// It only checks the prefix of the text, not that the expression itself parses. +func IsPlusBuild(line string) bool { + _, ok := splitPlusBuild(line) + return ok +} + +// splitGoBuild splits apart the leading //go:build prefix in line from the build expression itself. +// It returns "", false if the input is not a //go:build line or if the input contains multiple lines. +func splitPlusBuild(line string) (expr string, ok bool) { + // A single trailing newline is OK; otherwise multiple lines are not. + if len(line) > 0 && line[len(line)-1] == '\n' { + line = line[:len(line)-1] + } + if strings.Contains(line, "\n") { + return "", false + } + + if !strings.HasPrefix(line, "//") { + return "", false + } + line = line[len("//"):] + // Note the space is optional; "//+build" is recognized too. + line = strings.TrimSpace(line) + + if !strings.HasPrefix(line, "+build") { + return "", false + } + line = line[len("+build"):] + + // If strings.TrimSpace finds more to trim after removing the +build prefix, + // it means that the prefix was followed by a space, making this a +build line + // (as opposed to a +buildsomethingelse line). + // If line is empty, we had "// +build" by itself, which also counts. + trim := strings.TrimSpace(line) + if len(line) == len(trim) && line != "" { + return "", false + } + + return trim, true +} + +// parsePlusBuildExpr parses a legacy build tag expression (as used with “// +build”). +func parsePlusBuildExpr(text string) Expr { + var x Expr + for _, clause := range strings.Fields(text) { + var y Expr + for _, lit := range strings.Split(clause, ",") { + var z Expr + var neg bool + if strings.HasPrefix(lit, "!!") || lit == "!" { + z = tag("ignore") + } else { + if strings.HasPrefix(lit, "!") { + neg = true + lit = lit[len("!"):] + } + if isValidTag(lit) { + z = tag(lit) + } else { + z = tag("ignore") + } + if neg { + z = not(z) + } + } + if y == nil { + y = z + } else { + y = and(y, z) + } + } + if x == nil { + x = y + } else { + x = or(x, y) + } + } + return x +} + +// isValidTag reports whether the word is a valid build tag. +// Tags must be letters, digits, underscores or dots. +// Unlike in Go identifiers, all digits are fine (e.g., "386"). +func isValidTag(word string) bool { + if word == "" { + return false + } + for _, c := range word { + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { + return false + } + } + return true +} + +var errComplex = errors.New("expression too complex for // +build lines") + +// PlusBuildLines returns a sequence of “// +build” lines that evaluate to the build expression x. +// If the expression is too complex to convert directly to “// +build” lines, PlusBuildLines returns an error. +func PlusBuildLines(x Expr) ([]string, error) { + // Push all NOTs to the expression leaves, so that //go:build !(x && y) can be treated as !x || !y. + // This rewrite is both efficient and commonly needed, so it's worth doing. + // Essentially all other possible rewrites are too expensive and too rarely needed. + x = pushNot(x, false) + + // Split into AND of ORs of ANDs of literals (tag or NOT tag). + var split [][][]Expr + for _, or := range appendSplitAnd(nil, x) { + var ands [][]Expr + for _, and := range appendSplitOr(nil, or) { + var lits []Expr + for _, lit := range appendSplitAnd(nil, and) { + switch lit.(type) { + case *TagExpr, *NotExpr: + lits = append(lits, lit) + default: + return nil, errComplex + } + } + ands = append(ands, lits) + } + split = append(split, ands) + } + + // If all the ORs have length 1 (no actual OR'ing going on), + // push the top-level ANDs to the bottom level, so that we get + // one // +build line instead of many. + maxOr := 0 + for _, or := range split { + if maxOr < len(or) { + maxOr = len(or) + } + } + if maxOr == 1 { + var lits []Expr + for _, or := range split { + lits = append(lits, or[0]...) + } + split = [][][]Expr{{lits}} + } + + // Prepare the +build lines. + var lines []string + for _, or := range split { + line := "// +build" + for _, and := range or { + clause := "" + for i, lit := range and { + if i > 0 { + clause += "," + } + clause += lit.String() + } + line += " " + clause + } + lines = append(lines, line) + } + + return lines, nil +} + +// pushNot applies DeMorgan's law to push negations down the expression, +// so that only tags are negated in the result. +// (It applies the rewrites !(X && Y) => (!X || !Y) and !(X || Y) => (!X && !Y).) +func pushNot(x Expr, not bool) Expr { + switch x := x.(type) { + default: + // unreachable + return x + case *NotExpr: + if _, ok := x.X.(*TagExpr); ok && !not { + return x + } + return pushNot(x.X, !not) + case *TagExpr: + if not { + return &NotExpr{X: x} + } + return x + case *AndExpr: + x1 := pushNot(x.X, not) + y1 := pushNot(x.Y, not) + if not { + return or(x1, y1) + } + if x1 == x.X && y1 == x.Y { + return x + } + return and(x1, y1) + case *OrExpr: + x1 := pushNot(x.X, not) + y1 := pushNot(x.Y, not) + if not { + return and(x1, y1) + } + if x1 == x.X && y1 == x.Y { + return x + } + return or(x1, y1) + } +} + +// appendSplitAnd appends x to list while splitting apart any top-level && expressions. +// For example, appendSplitAnd({W}, X && Y && Z) = {W, X, Y, Z}. +func appendSplitAnd(list []Expr, x Expr) []Expr { + if x, ok := x.(*AndExpr); ok { + list = appendSplitAnd(list, x.X) + list = appendSplitAnd(list, x.Y) + return list + } + return append(list, x) +} + +// appendSplitOr appends x to list while splitting apart any top-level || expressions. +// For example, appendSplitOr({W}, X || Y || Z) = {W, X, Y, Z}. +func appendSplitOr(list []Expr, x Expr) []Expr { + if x, ok := x.(*OrExpr); ok { + list = appendSplitOr(list, x.X) + list = appendSplitOr(list, x.Y) + return list + } + return append(list, x) +} diff --git a/src/go/build/constraint/expr_test.go b/src/go/build/constraint/expr_test.go new file mode 100644 index 0000000..4979f8b --- /dev/null +++ b/src/go/build/constraint/expr_test.go @@ -0,0 +1,317 @@ +// Copyright 2020 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 constraint + +import ( + "fmt" + "reflect" + "strings" + "testing" +) + +var exprStringTests = []struct { + x Expr + out string +}{ + { + x: tag("abc"), + out: "abc", + }, + { + x: not(tag("abc")), + out: "!abc", + }, + { + x: not(and(tag("abc"), tag("def"))), + out: "!(abc && def)", + }, + { + x: and(tag("abc"), or(tag("def"), tag("ghi"))), + out: "abc && (def || ghi)", + }, + { + x: or(and(tag("abc"), tag("def")), tag("ghi")), + out: "(abc && def) || ghi", + }, +} + +func TestExprString(t *testing.T) { + for i, tt := range exprStringTests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + s := tt.x.String() + if s != tt.out { + t.Errorf("String() mismatch:\nhave %s\nwant %s", s, tt.out) + } + }) + } +} + +var lexTests = []struct { + in string + out string +}{ + {"", ""}, + {"x", "x"}, + {"x.y", "x.y"}, + {"x_y", "x_y"}, + {"αx", "αx"}, + {"αx²", "αx err: invalid syntax at ²"}, + {"go1.2", "go1.2"}, + {"x y", "x y"}, + {"x!y", "x ! y"}, + {"&&||!()xy yx ", "&& || ! ( ) xy yx"}, + {"x~", "x err: invalid syntax at ~"}, + {"x ~", "x err: invalid syntax at ~"}, + {"x &", "x err: invalid syntax at &"}, + {"x &y", "x err: invalid syntax at &"}, +} + +func TestLex(t *testing.T) { + for i, tt := range lexTests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + p := &exprParser{s: tt.in} + out := "" + for { + tok, err := lexHelp(p) + if tok == "" && err == nil { + break + } + if out != "" { + out += " " + } + if err != nil { + out += "err: " + err.Error() + break + } + out += tok + } + if out != tt.out { + t.Errorf("lex(%q):\nhave %s\nwant %s", tt.in, out, tt.out) + } + }) + } +} + +func lexHelp(p *exprParser) (tok string, err error) { + defer func() { + if e := recover(); e != nil { + if e, ok := e.(*SyntaxError); ok { + err = e + return + } + panic(e) + } + }() + + p.lex() + return p.tok, nil +} + +var parseExprTests = []struct { + in string + x Expr +}{ + {"x", tag("x")}, + {"x&&y", and(tag("x"), tag("y"))}, + {"x||y", or(tag("x"), tag("y"))}, + {"(x)", tag("x")}, + {"x||y&&z", or(tag("x"), and(tag("y"), tag("z")))}, + {"x&&y||z", or(and(tag("x"), tag("y")), tag("z"))}, + {"x&&(y||z)", and(tag("x"), or(tag("y"), tag("z")))}, + {"(x||y)&&z", and(or(tag("x"), tag("y")), tag("z"))}, + {"!(x&&y)", not(and(tag("x"), tag("y")))}, +} + +func TestParseExpr(t *testing.T) { + for i, tt := range parseExprTests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + x, err := parseExpr(tt.in) + if err != nil { + t.Fatal(err) + } + if x.String() != tt.x.String() { + t.Errorf("parseExpr(%q):\nhave %s\nwant %s", tt.in, x, tt.x) + } + }) + } +} + +var parseExprErrorTests = []struct { + in string + err error +}{ + {"x && ", &SyntaxError{Offset: 5, Err: "unexpected end of expression"}}, + {"x && (", &SyntaxError{Offset: 6, Err: "missing close paren"}}, + {"x && ||", &SyntaxError{Offset: 5, Err: "unexpected token ||"}}, + {"x && !", &SyntaxError{Offset: 6, Err: "unexpected end of expression"}}, + {"x && !!", &SyntaxError{Offset: 6, Err: "double negation not allowed"}}, + {"x !", &SyntaxError{Offset: 2, Err: "unexpected token !"}}, + {"x && (y", &SyntaxError{Offset: 5, Err: "missing close paren"}}, +} + +func TestParseError(t *testing.T) { + for i, tt := range parseExprErrorTests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + x, err := parseExpr(tt.in) + if err == nil { + t.Fatalf("parseExpr(%q) = %v, want error", tt.in, x) + } + if !reflect.DeepEqual(err, tt.err) { + t.Fatalf("parseExpr(%q): wrong error:\nhave %#v\nwant %#v", tt.in, err, tt.err) + } + }) + } +} + +var exprEvalTests = []struct { + in string + ok bool + tags string +}{ + {"x", false, "x"}, + {"x && y", false, "x y"}, + {"x || y", false, "x y"}, + {"!x && yes", true, "x yes"}, + {"yes || y", true, "y yes"}, +} + +func TestExprEval(t *testing.T) { + for i, tt := range exprEvalTests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + x, err := parseExpr(tt.in) + if err != nil { + t.Fatal(err) + } + tags := make(map[string]bool) + wantTags := make(map[string]bool) + for _, tag := range strings.Fields(tt.tags) { + wantTags[tag] = true + } + hasTag := func(tag string) bool { + tags[tag] = true + return tag == "yes" + } + ok := x.Eval(hasTag) + if ok != tt.ok || !reflect.DeepEqual(tags, wantTags) { + t.Errorf("Eval(%#q):\nhave ok=%v, tags=%v\nwant ok=%v, tags=%v", + tt.in, ok, tags, tt.ok, wantTags) + } + }) + } +} + +var parsePlusBuildExprTests = []struct { + in string + x Expr +}{ + {"x", tag("x")}, + {"x,y", and(tag("x"), tag("y"))}, + {"x y", or(tag("x"), tag("y"))}, + {"x y,z", or(tag("x"), and(tag("y"), tag("z")))}, + {"x,y z", or(and(tag("x"), tag("y")), tag("z"))}, + {"x,!y !z", or(and(tag("x"), not(tag("y"))), not(tag("z")))}, + {"!! x", or(tag("ignore"), tag("x"))}, + {"!!x", tag("ignore")}, + {"!x", not(tag("x"))}, + {"!", tag("ignore")}, +} + +func TestParsePlusBuildExpr(t *testing.T) { + for i, tt := range parsePlusBuildExprTests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + x := parsePlusBuildExpr(tt.in) + if x.String() != tt.x.String() { + t.Errorf("parsePlusBuildExpr(%q):\nhave %v\nwant %v", tt.in, x, tt.x) + } + }) + } +} + +var constraintTests = []struct { + in string + x Expr + err error +}{ + {"//+build x y", or(tag("x"), tag("y")), nil}, + {"// +build x y \n", or(tag("x"), tag("y")), nil}, + {"// +build x y \n ", nil, errNotConstraint}, + {"// +build x y \nmore", nil, errNotConstraint}, + {" //+build x y", nil, errNotConstraint}, + + {"//go:build x && y", and(tag("x"), tag("y")), nil}, + {"//go:build x && y\n", and(tag("x"), tag("y")), nil}, + {"//go:build x && y\n ", nil, errNotConstraint}, + {"//go:build x && y\nmore", nil, errNotConstraint}, + {" //go:build x && y", nil, errNotConstraint}, +} + +func TestParse(t *testing.T) { + for i, tt := range constraintTests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + x, err := Parse(tt.in) + if err != nil { + if tt.err == nil { + t.Errorf("Constraint(%q): unexpected error: %v", tt.in, err) + } else if tt.err != err { + t.Errorf("Constraint(%q): error %v, want %v", tt.in, err, tt.err) + } + return + } + if tt.err != nil { + t.Errorf("Constraint(%q) = %v, want error %v", tt.in, x, tt.err) + return + } + if x.String() != tt.x.String() { + t.Errorf("Constraint(%q):\nhave %v\nwant %v", tt.in, x, tt.x) + } + }) + } +} + +var plusBuildLinesTests = []struct { + in string + out []string + err error +}{ + {"x", []string{"x"}, nil}, + {"x && !y", []string{"x,!y"}, nil}, + {"x || y", []string{"x y"}, nil}, + {"x && (y || z)", []string{"x", "y z"}, nil}, + {"!(x && y)", []string{"!x !y"}, nil}, + {"x || (y && z)", []string{"x y,z"}, nil}, + {"w && (x || (y && z))", []string{"w", "x y,z"}, nil}, + {"v || (w && (x || (y && z)))", nil, errComplex}, +} + +func TestPlusBuildLines(t *testing.T) { + for i, tt := range plusBuildLinesTests { + t.Run(fmt.Sprint(i), func(t *testing.T) { + x, err := parseExpr(tt.in) + if err != nil { + t.Fatal(err) + } + lines, err := PlusBuildLines(x) + if err != nil { + if tt.err == nil { + t.Errorf("PlusBuildLines(%q): unexpected error: %v", tt.in, err) + } else if tt.err != err { + t.Errorf("PlusBuildLines(%q): error %v, want %v", tt.in, err, tt.err) + } + return + } + if tt.err != nil { + t.Errorf("PlusBuildLines(%q) = %v, want error %v", tt.in, lines, tt.err) + return + } + var want []string + for _, line := range tt.out { + want = append(want, "// +build "+line) + } + if !reflect.DeepEqual(lines, want) { + t.Errorf("PlusBuildLines(%q):\nhave %q\nwant %q", tt.in, lines, want) + } + }) + } +} diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go new file mode 100644 index 0000000..c97c668 --- /dev/null +++ b/src/go/build/deps_test.go @@ -0,0 +1,877 @@ +// Copyright 2012 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. + +// This file exercises the import parser but also checks that +// some low-level packages do not have new dependencies added. + +package build + +import ( + "bytes" + "fmt" + "go/token" + "internal/testenv" + "io/fs" + "os" + "path/filepath" + "runtime" + "sort" + "strings" + "testing" +) + +// depsRules defines the expected dependencies between packages in +// the Go source tree. It is a statement of policy. +// +// DO NOT CHANGE THIS DATA TO FIX BUILDS. +// Existing packages should not have their constraints relaxed +// without prior discussion. +// Negative assertions should almost never be removed. +// +// The general syntax of a rule is: +// +// a, b < c, d; +// +// which means c and d come after a and b in the partial order +// (that is, c and d can import a and b), +// but doesn't provide a relative order between a vs b or c vs d. +// +// The rules can chain together, as in: +// +// e < f, g < h; +// +// which is equivalent to +// +// e < f, g; +// f, g < h; +// +// Except for the special bottom element "NONE", each name +// must appear exactly once on the right-hand side of a rule. +// That rule serves as the definition of the allowed dependencies +// for that name. The definition must appear before any uses +// of the name on the left-hand side of a rule. (That is, the +// rules themselves must be ordered according to the partial +// order, for easier reading by people.) +// +// Negative assertions double-check the partial order: +// +// i !< j +// +// means that it must NOT be the case that i < j. +// Negative assertions may appear anywhere in the rules, +// even before i and j have been defined. +// +// Comments begin with #. +// +// All-caps names are pseudo-names for specific points +// in the dependency lattice. +// +var depsRules = ` + # No dependencies allowed for any of these packages. + NONE + < container/list, container/ring, + internal/cfg, internal/cpu, + internal/goversion, internal/nettrace, + unicode/utf8, unicode/utf16, unicode, + unsafe; + + # RUNTIME is the core runtime group of packages, all of them very light-weight. + internal/cpu, unsafe + < internal/bytealg + < internal/unsafeheader + < runtime/internal/sys + < runtime/internal/atomic + < runtime/internal/math + < runtime + < sync/atomic + < internal/race + < sync + < internal/reflectlite + < errors + < internal/oserror, math/bits + < RUNTIME; + + RUNTIME + < sort + < container/heap; + + RUNTIME + < io; + + syscall !< io; + reflect !< sort; + + RUNTIME, unicode/utf8 + < path; + + unicode !< path; + + # SYSCALL is RUNTIME plus the packages necessary for basic system calls. + RUNTIME, unicode/utf8, unicode/utf16 + < internal/syscall/windows/sysdll, syscall/js + < syscall + < internal/syscall/unix, internal/syscall/windows, internal/syscall/windows/registry + < internal/syscall/execenv + < SYSCALL; + + # TIME is SYSCALL plus the core packages about time, including context. + SYSCALL + < time/tzdata + < time + < context + < TIME; + + TIME, io, path, sort + < io/fs; + + # MATH is RUNTIME plus the basic math packages. + RUNTIME + < math + < MATH; + + unicode !< math; + + MATH + < math/cmplx; + + MATH + < math/rand; + + MATH + < runtime/metrics; + + MATH, unicode/utf8 + < strconv; + + unicode !< strconv; + + # STR is basic string and buffer manipulation. + RUNTIME, io, unicode/utf8, unicode/utf16, unicode + < bytes, strings + < bufio; + + bufio, path, strconv + < STR; + + # OS is basic OS access, including helpers (path/filepath, os/exec, etc). + # OS includes string routines, but those must be layered above package os. + # OS does not include reflection. + io/fs + < internal/testlog + < internal/poll + < os + < os/signal; + + io/fs + < embed; + + unicode, fmt !< os, os/signal; + + os/signal, STR + < path/filepath + < io/ioutil, os/exec; + + io/ioutil, os/exec, os/signal + < OS; + + reflect !< OS; + + OS + < golang.org/x/sys/cpu; + + # FMT is OS (which includes string routines) plus reflect and fmt. + # It does not include package log, which should be avoided in core packages. + strconv, unicode + < reflect; + + os, reflect + < internal/fmtsort + < fmt; + + OS, fmt + < FMT; + + log !< FMT; + + OS, FMT + < internal/execabs; + + OS, internal/execabs + < internal/goroot; + + # Misc packages needing only FMT. + FMT + < flag, + html, + mime/quotedprintable, + net/internal/socktest, + net/url, + runtime/debug, + runtime/trace, + text/scanner, + text/tabwriter; + + # encodings + # core ones do not use fmt. + io, strconv + < encoding; + + encoding, reflect + < encoding/binary + < encoding/base32, encoding/base64; + + fmt !< encoding/base32, encoding/base64; + + FMT, encoding/base32, encoding/base64 + < encoding/ascii85, encoding/csv, encoding/gob, encoding/hex, + encoding/json, encoding/pem, encoding/xml, mime; + + # hashes + io + < hash + < hash/adler32, hash/crc32, hash/crc64, hash/fnv, hash/maphash; + + # math/big + FMT, encoding/binary, math/rand + < math/big; + + # compression + FMT, encoding/binary, hash/adler32, hash/crc32 + < compress/bzip2, compress/flate, compress/lzw + < archive/zip, compress/gzip, compress/zlib; + + # templates + FMT + < text/template/parse; + + net/url, text/template/parse + < text/template + < internal/lazytemplate; + + encoding/json, html, text/template + < html/template; + + # regexp + FMT + < regexp/syntax + < regexp + < internal/lazyregexp; + + # suffix array + encoding/binary, regexp + < index/suffixarray; + + # executable parsing + FMT, encoding/binary, compress/zlib + < debug/dwarf + < debug/elf, debug/gosym, debug/macho, debug/pe, debug/plan9obj, internal/xcoff + < DEBUG; + + # go parser and friends. + FMT + < go/token + < go/scanner + < go/ast + < go/parser; + + go/parser, text/tabwriter + < go/printer + < go/format; + + go/parser, internal/lazyregexp, text/template + < go/doc; + + math/big, go/token + < go/constant; + + container/heap, go/constant, go/parser + < go/types; + + FMT + < go/build/constraint; + + go/doc, go/parser, internal/goroot, internal/goversion + < go/build; + + DEBUG, go/build, go/types, text/scanner + < go/internal/gcimporter, go/internal/gccgoimporter, go/internal/srcimporter + < go/importer; + + # databases + FMT + < database/sql/internal + < database/sql/driver + < database/sql; + + # images + FMT, compress/lzw, compress/zlib + < image/color + < image, image/color/palette + < image/internal/imageutil + < image/draw + < image/gif, image/jpeg, image/png; + + # cgo, delayed as long as possible. + # If you add a dependency on CGO, you must add the package + # to cgoPackages in cmd/dist/test.go as well. + RUNTIME + < C + < runtime/cgo + < CGO + < runtime/race, runtime/msan; + + # Bulk of the standard library must not use cgo. + # The prohibition stops at net and os/user. + C !< fmt, go/types, CRYPTO-MATH; + + CGO, OS + < plugin; + + CGO, FMT + < os/user + < archive/tar; + + sync + < internal/singleflight; + + os + < golang.org/x/net/dns/dnsmessage, + golang.org/x/net/lif, + golang.org/x/net/route; + + # net is unavoidable when doing any networking, + # so large dependencies must be kept out. + # This is a long-looking list but most of these + # are small with few dependencies. + CGO, + golang.org/x/net/dns/dnsmessage, + golang.org/x/net/lif, + golang.org/x/net/route, + internal/nettrace, + internal/poll, + internal/singleflight, + internal/race, + os + < net; + + fmt, unicode !< net; + math/rand !< net; # net uses runtime instead + + # NET is net plus net-helper packages. + FMT, net + < net/textproto; + + mime, net/textproto, net/url + < NET; + + # logging - most packages should not import; http and up is allowed + FMT + < log; + + log !< crypto/tls, database/sql, go/importer, testing; + + FMT, log, net + < log/syslog; + + NET, log + < net/mail; + + # CRYPTO is core crypto algorithms - no cgo, fmt, net. + # Unfortunately, stuck with reflect via encoding/binary. + encoding/binary, golang.org/x/sys/cpu, hash + < crypto + < crypto/subtle + < crypto/internal/subtle + < crypto/cipher + < crypto/aes, crypto/des, crypto/hmac, crypto/md5, crypto/rc4, + crypto/sha1, crypto/sha256, crypto/sha512 + < CRYPTO; + + CGO, fmt, net !< CRYPTO; + + # CRYPTO-MATH is core bignum-based crypto - no cgo, net; fmt now ok. + CRYPTO, FMT, math/big + < crypto/rand + < crypto/internal/randutil + < crypto/ed25519/internal/edwards25519 + < crypto/ed25519 + < encoding/asn1 + < golang.org/x/crypto/cryptobyte/asn1 + < golang.org/x/crypto/cryptobyte + < golang.org/x/crypto/curve25519 + < crypto/dsa, crypto/elliptic, crypto/rsa + < crypto/ecdsa + < CRYPTO-MATH; + + CGO, net !< CRYPTO-MATH; + + # TLS, Prince of Dependencies. + CRYPTO-MATH, NET, container/list, encoding/hex, encoding/pem + < golang.org/x/crypto/internal/subtle + < golang.org/x/crypto/chacha20 + < golang.org/x/crypto/poly1305 + < golang.org/x/crypto/chacha20poly1305 + < golang.org/x/crypto/hkdf + < crypto/x509/internal/macos + < crypto/x509/pkix + < crypto/x509 + < crypto/tls; + + # crypto-aware packages + + NET, crypto/rand, mime/quotedprintable + < mime/multipart; + + crypto/tls + < net/smtp; + + # HTTP, King of Dependencies. + + FMT + < golang.org/x/net/http2/hpack, net/http/internal; + + FMT, NET, container/list, encoding/binary, log + < golang.org/x/text/transform + < golang.org/x/text/unicode/norm + < golang.org/x/text/unicode/bidi + < golang.org/x/text/secure/bidirule + < golang.org/x/net/idna + < golang.org/x/net/http/httpguts, golang.org/x/net/http/httpproxy; + + NET, crypto/tls + < net/http/httptrace; + + compress/gzip, + golang.org/x/net/http/httpguts, + golang.org/x/net/http/httpproxy, + golang.org/x/net/http2/hpack, + net/http/internal, + net/http/httptrace, + mime/multipart, + log + < net/http; + + # HTTP-aware packages + + encoding/json, net/http + < expvar; + + net/http + < net/http/cookiejar, net/http/httputil; + + net/http, flag + < net/http/httptest; + + net/http, regexp + < net/http/cgi + < net/http/fcgi; + + # Profiling + FMT, compress/gzip, encoding/binary, text/tabwriter + < runtime/pprof; + + OS, compress/gzip, regexp + < internal/profile; + + html, internal/profile, net/http, runtime/pprof, runtime/trace + < net/http/pprof; + + # RPC + encoding/gob, encoding/json, go/token, html/template, net/http + < net/rpc + < net/rpc/jsonrpc; + + # System Information + internal/cpu, sync + < internal/sysinfo; + + # Test-only + log + < testing/iotest + < testing/fstest; + + FMT, flag, math/rand + < testing/quick; + + FMT, flag, runtime/debug, runtime/trace, internal/sysinfo + < testing; + + internal/testlog, runtime/pprof, regexp + < testing/internal/testdeps; + + OS, flag, testing, internal/cfg + < internal/testenv; + + OS, encoding/base64 + < internal/obscuretestdata; + + CGO, OS, fmt + < os/signal/internal/pty; + + NET, testing, math/rand + < golang.org/x/net/nettest; + + FMT, container/heap, math/rand + < internal/trace; +` + +// listStdPkgs returns the same list of packages as "go list std". +func listStdPkgs(goroot string) ([]string, error) { + // Based on cmd/go's matchPackages function. + var pkgs []string + + src := filepath.Join(goroot, "src") + string(filepath.Separator) + walkFn := func(path string, d fs.DirEntry, err error) error { + if err != nil || !d.IsDir() || path == src { + return nil + } + + base := filepath.Base(path) + if strings.HasPrefix(base, ".") || strings.HasPrefix(base, "_") || base == "testdata" { + return filepath.SkipDir + } + + name := filepath.ToSlash(path[len(src):]) + if name == "builtin" || name == "cmd" { + return filepath.SkipDir + } + + pkgs = append(pkgs, strings.TrimPrefix(name, "vendor/")) + return nil + } + if err := filepath.WalkDir(src, walkFn); err != nil { + return nil, err + } + return pkgs, nil +} + +func TestDependencies(t *testing.T) { + if !testenv.HasSrc() { + // Tests run in a limited file system and we do not + // provide access to every source file. + t.Skipf("skipping on %s/%s, missing full GOROOT", runtime.GOOS, runtime.GOARCH) + } + + ctxt := Default + all, err := listStdPkgs(ctxt.GOROOT) + if err != nil { + t.Fatal(err) + } + sort.Strings(all) + + sawImport := map[string]map[string]bool{} // from package => to package => true + policy := depsPolicy(t) + + for _, pkg := range all { + imports, err := findImports(pkg) + if err != nil { + t.Error(err) + continue + } + if sawImport[pkg] == nil { + sawImport[pkg] = map[string]bool{} + } + ok := policy[pkg] + var bad []string + for _, imp := range imports { + sawImport[pkg][imp] = true + if !ok[imp] { + bad = append(bad, imp) + } + } + if bad != nil { + t.Errorf("unexpected dependency: %s imports %v", pkg, bad) + } + } + + // depPath returns the path between the given from and to packages. + // It returns the empty string if there's no dependency path. + var depPath func(string, string) string + depPath = func(from, to string) string { + if sawImport[from][to] { + return from + " => " + to + } + for pkg := range sawImport[from] { + if p := depPath(pkg, to); p != "" { + return from + " => " + p + } + } + return "" + } +} + +var buildIgnore = []byte("\n// +build ignore") + +func findImports(pkg string) ([]string, error) { + vpkg := pkg + if strings.HasPrefix(pkg, "golang.org") { + vpkg = "vendor/" + pkg + } + dir := filepath.Join(Default.GOROOT, "src", vpkg) + files, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + var imports []string + var haveImport = map[string]bool{} + fset := token.NewFileSet() + for _, file := range files { + name := file.Name() + if name == "slice_go14.go" || name == "slice_go18.go" { + // These files are for compiler bootstrap with older versions of Go and not built in the standard build. + continue + } + if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") { + continue + } + info := fileInfo{ + name: filepath.Join(dir, name), + fset: fset, + } + f, err := os.Open(info.name) + if err != nil { + return nil, err + } + err = readGoInfo(f, &info) + f.Close() + if err != nil { + return nil, fmt.Errorf("reading %v: %v", name, err) + } + if bytes.Contains(info.header, buildIgnore) { + continue + } + for _, imp := range info.imports { + path := imp.path + if !haveImport[path] { + haveImport[path] = true + imports = append(imports, path) + } + } + } + sort.Strings(imports) + return imports, nil +} + +// depsPolicy returns a map m such that m[p][d] == true when p can import d. +func depsPolicy(t *testing.T) map[string]map[string]bool { + allowed := map[string]map[string]bool{"NONE": {}} + disallowed := [][2][]string{} + + parseDepsRules(t, func(deps []string, op string, users []string) { + if op == "!<" { + disallowed = append(disallowed, [2][]string{deps, users}) + return + } + for _, u := range users { + if allowed[u] != nil { + t.Errorf("multiple deps lists for %s", u) + } + allowed[u] = make(map[string]bool) + for _, d := range deps { + if allowed[d] == nil { + t.Errorf("use of %s before its deps list", d) + } + allowed[u][d] = true + } + } + }) + + // Check for missing deps info. + for _, deps := range allowed { + for d := range deps { + if allowed[d] == nil { + t.Errorf("missing deps list for %s", d) + } + } + } + + // Complete transitive allowed deps. + for k := range allowed { + for i := range allowed { + for j := range allowed { + if i != k && k != j && allowed[i][k] && allowed[k][j] { + if i == j { + // Can only happen along with a "use of X before deps" error above, + // but this error is more specific - it makes clear that reordering the + // rules will not be enough to fix the problem. + t.Errorf("deps policy cycle: %s < %s < %s", j, k, i) + } + allowed[i][j] = true + } + } + } + } + + // Check negative assertions against completed allowed deps. + for _, bad := range disallowed { + deps, users := bad[0], bad[1] + for _, d := range deps { + for _, u := range users { + if allowed[u][d] { + t.Errorf("deps policy incorrect: assertion failed: %s !< %s", d, u) + } + } + } + } + + if t.Failed() { + t.FailNow() + } + + return allowed +} + +// parseDepsRules parses depsRules, calling save(deps, op, users) +// for each deps < users or deps !< users rule +// (op is "<" or "!<"). +func parseDepsRules(t *testing.T, save func(deps []string, op string, users []string)) { + p := &depsParser{t: t, lineno: 1, text: depsRules} + + var prev []string + var op string + for { + list, tok := p.nextList() + if tok == "" { + if prev == nil { + break + } + p.syntaxError("unexpected EOF") + } + if prev != nil { + save(prev, op, list) + } + prev = list + if tok == ";" { + prev = nil + op = "" + continue + } + if tok != "<" && tok != "!<" { + p.syntaxError("missing <") + } + op = tok + } +} + +// A depsParser parses the depsRules syntax described above. +type depsParser struct { + t *testing.T + lineno int + lastWord string + text string +} + +// syntaxError reports a parsing error. +func (p *depsParser) syntaxError(msg string) { + p.t.Fatalf("deps:%d: syntax error: %s near %s", p.lineno, msg, p.lastWord) +} + +// nextList parses and returns a comma-separated list of names. +func (p *depsParser) nextList() (list []string, token string) { + for { + tok := p.nextToken() + switch tok { + case "": + if len(list) == 0 { + return nil, "" + } + fallthrough + case ",", "<", "!<", ";": + p.syntaxError("bad list syntax") + } + list = append(list, tok) + + tok = p.nextToken() + if tok != "," { + return list, tok + } + } +} + +// nextToken returns the next token in the deps rules, +// one of ";" "," "<" "!<" or a name. +func (p *depsParser) nextToken() string { + for { + if p.text == "" { + return "" + } + switch p.text[0] { + case ';', ',', '<': + t := p.text[:1] + p.text = p.text[1:] + return t + + case '!': + if len(p.text) < 2 || p.text[1] != '<' { + p.syntaxError("unexpected token !") + } + p.text = p.text[2:] + return "!<" + + case '#': + i := strings.Index(p.text, "\n") + if i < 0 { + i = len(p.text) + } + p.text = p.text[i:] + continue + + case '\n': + p.lineno++ + fallthrough + case ' ', '\t': + p.text = p.text[1:] + continue + + default: + i := strings.IndexAny(p.text, "!;,<#\n \t") + if i < 0 { + i = len(p.text) + } + t := p.text[:i] + p.text = p.text[i:] + p.lastWord = t + return t + } + } +} + +// TestStdlibLowercase tests that all standard library package names are +// lowercase. See Issue 40065. +func TestStdlibLowercase(t *testing.T) { + if !testenv.HasSrc() { + t.Skipf("skipping on %s/%s, missing full GOROOT", runtime.GOOS, runtime.GOARCH) + } + + ctxt := Default + all, err := listStdPkgs(ctxt.GOROOT) + if err != nil { + t.Fatal(err) + } + + for _, pkgname := range all { + if strings.ToLower(pkgname) != pkgname { + t.Errorf("package %q should not use upper-case path", pkgname) + } + } +} + +// TestFindImports tests that findImports works. See #43249. +func TestFindImports(t *testing.T) { + imports, err := findImports("go/build") + if err != nil { + t.Fatal(err) + } + t.Logf("go/build imports %q", imports) + want := []string{"bytes", "os", "path/filepath", "strings"} +wantLoop: + for _, w := range want { + for _, imp := range imports { + if imp == w { + continue wantLoop + } + } + t.Errorf("expected to find %q in import list", w) + } +} diff --git a/src/go/build/doc.go b/src/go/build/doc.go new file mode 100644 index 0000000..2c6f0a8 --- /dev/null +++ b/src/go/build/doc.go @@ -0,0 +1,98 @@ +// Copyright 2011 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 build gathers information about Go packages. +// +// Go Path +// +// The Go path is a list of directory trees containing Go source code. +// It is consulted to resolve imports that cannot be found in the standard +// Go tree. The default path is the value of the GOPATH environment +// variable, interpreted as a path list appropriate to the operating system +// (on Unix, the variable is a colon-separated string; +// on Windows, a semicolon-separated string; +// on Plan 9, a list). +// +// Each directory listed in the Go path must have a prescribed structure: +// +// The src/ directory holds source code. The path below 'src' determines +// the import path or executable name. +// +// The pkg/ directory holds installed package objects. +// As in the Go tree, each target operating system and +// architecture pair has its own subdirectory of pkg +// (pkg/GOOS_GOARCH). +// +// If DIR is a directory listed in the Go path, a package with +// source in DIR/src/foo/bar can be imported as "foo/bar" and +// has its compiled form installed to "DIR/pkg/GOOS_GOARCH/foo/bar.a" +// (or, for gccgo, "DIR/pkg/gccgo/foo/libbar.a"). +// +// The bin/ directory holds compiled commands. +// Each command is named for its source directory, but only +// using the final element, not the entire path. That is, the +// command with source in DIR/src/foo/quux is installed into +// DIR/bin/quux, not DIR/bin/foo/quux. The foo/ is stripped +// so that you can add DIR/bin to your PATH to get at the +// installed commands. +// +// Here's an example directory layout: +// +// GOPATH=/home/user/gocode +// +// /home/user/gocode/ +// src/ +// foo/ +// bar/ (go code in package bar) +// x.go +// quux/ (go code in package main) +// y.go +// bin/ +// quux (installed command) +// pkg/ +// linux_amd64/ +// foo/ +// bar.a (installed package object) +// +// Build Constraints +// +// A build constraint, also known as a build tag, is a line comment that begins +// +// // +build +// +// that lists the conditions under which a file should be included in the +// package. Build constraints may also be part of a file's name +// (for example, source_windows.go will only be included if the target +// operating system is windows). +// +// See 'go help buildconstraint' +// (https://golang.org/cmd/go/#hdr-Build_constraints) for details. +// +// Binary-Only Packages +// +// In Go 1.12 and earlier, it was possible to distribute packages in binary +// form without including the source code used for compiling the package. +// The package was distributed with a source file not excluded by build +// constraints and containing a "//go:binary-only-package" comment. Like a +// build constraint, this comment appeared at the top of a file, preceded +// only by blank lines and other line comments and with a blank line +// following the comment, to separate it from the package documentation. +// Unlike build constraints, this comment is only recognized in non-test +// Go source files. +// +// The minimal source code for a binary-only package was therefore: +// +// //go:binary-only-package +// +// package mypkg +// +// The source code could include additional Go code. That code was never +// compiled but would be processed by tools like godoc and might be useful +// as end-user documentation. +// +// "go build" and other commands no longer support binary-only-packages. +// Import and ImportDir will still set the BinaryOnly flag in packages +// containing these comments for use in tools and error messages. +// +package build diff --git a/src/go/build/gc.go b/src/go/build/gc.go new file mode 100644 index 0000000..3025cd5 --- /dev/null +++ b/src/go/build/gc.go @@ -0,0 +1,17 @@ +// Copyright 2018 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. + +// +build gc + +package build + +import ( + "path/filepath" + "runtime" +) + +// getToolDir returns the default value of ToolDir. +func getToolDir() string { + return filepath.Join(runtime.GOROOT(), "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH) +} diff --git a/src/go/build/gccgo.go b/src/go/build/gccgo.go new file mode 100644 index 0000000..c6aac9a --- /dev/null +++ b/src/go/build/gccgo.go @@ -0,0 +1,14 @@ +// Copyright 2018 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. + +// +build gccgo + +package build + +import "runtime" + +// getToolDir returns the default value of ToolDir. +func getToolDir() string { + return envOr("GCCGOTOOLDIR", runtime.GCCGOTOOLDIR) +} diff --git a/src/go/build/read.go b/src/go/build/read.go new file mode 100644 index 0000000..aa7c6ee --- /dev/null +++ b/src/go/build/read.go @@ -0,0 +1,546 @@ +// Copyright 2012 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 build + +import ( + "bufio" + "errors" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +type importReader struct { + b *bufio.Reader + buf []byte + peek byte + err error + eof bool + nerr int + pos token.Position +} + +func newImportReader(name string, r io.Reader) *importReader { + return &importReader{ + b: bufio.NewReader(r), + pos: token.Position{ + Filename: name, + Line: 1, + Column: 1, + }, + } +} + +func isIdent(c byte) bool { + return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf +} + +var ( + errSyntax = errors.New("syntax error") + errNUL = errors.New("unexpected NUL in input") +) + +// syntaxError records a syntax error, but only if an I/O error has not already been recorded. +func (r *importReader) syntaxError() { + if r.err == nil { + r.err = errSyntax + } +} + +// readByte reads the next byte from the input, saves it in buf, and returns it. +// If an error occurs, readByte records the error in r.err and returns 0. +func (r *importReader) readByte() byte { + c, err := r.b.ReadByte() + if err == nil { + r.buf = append(r.buf, c) + if c == 0 { + err = errNUL + } + } + if err != nil { + if err == io.EOF { + r.eof = true + } else if r.err == nil { + r.err = err + } + c = 0 + } + return c +} + +// readByteNoBuf is like readByte but doesn't buffer the byte. +// It exhausts r.buf before reading from r.b. +func (r *importReader) readByteNoBuf() byte { + var c byte + var err error + if len(r.buf) > 0 { + c = r.buf[0] + r.buf = r.buf[1:] + } else { + c, err = r.b.ReadByte() + if err == nil && c == 0 { + err = errNUL + } + } + + if err != nil { + if err == io.EOF { + r.eof = true + } else if r.err == nil { + r.err = err + } + return 0 + } + r.pos.Offset++ + if c == '\n' { + r.pos.Line++ + r.pos.Column = 1 + } else { + r.pos.Column++ + } + return c +} + +// peekByte returns the next byte from the input reader but does not advance beyond it. +// If skipSpace is set, peekByte skips leading spaces and comments. +func (r *importReader) peekByte(skipSpace bool) byte { + if r.err != nil { + if r.nerr++; r.nerr > 10000 { + panic("go/build: import reader looping") + } + return 0 + } + + // Use r.peek as first input byte. + // Don't just return r.peek here: it might have been left by peekByte(false) + // and this might be peekByte(true). + c := r.peek + if c == 0 { + c = r.readByte() + } + for r.err == nil && !r.eof { + if skipSpace { + // For the purposes of this reader, semicolons are never necessary to + // understand the input and are treated as spaces. + switch c { + case ' ', '\f', '\t', '\r', '\n', ';': + c = r.readByte() + continue + + case '/': + c = r.readByte() + if c == '/' { + for c != '\n' && r.err == nil && !r.eof { + c = r.readByte() + } + } else if c == '*' { + var c1 byte + for (c != '*' || c1 != '/') && r.err == nil { + if r.eof { + r.syntaxError() + } + c, c1 = c1, r.readByte() + } + } else { + r.syntaxError() + } + c = r.readByte() + continue + } + } + break + } + r.peek = c + return r.peek +} + +// nextByte is like peekByte but advances beyond the returned byte. +func (r *importReader) nextByte(skipSpace bool) byte { + c := r.peekByte(skipSpace) + r.peek = 0 + return c +} + +var goEmbed = []byte("go:embed") + +// findEmbed advances the input reader to the next //go:embed comment. +// It reports whether it found a comment. +// (Otherwise it found an error or EOF.) +func (r *importReader) findEmbed(first bool) bool { + // The import block scan stopped after a non-space character, + // so the reader is not at the start of a line on the first call. + // After that, each //go:embed extraction leaves the reader + // at the end of a line. + startLine := !first + var c byte + for r.err == nil && !r.eof { + c = r.readByteNoBuf() + Reswitch: + switch c { + default: + startLine = false + + case '\n': + startLine = true + + case ' ', '\t': + // leave startLine alone + + case '"': + startLine = false + for r.err == nil { + if r.eof { + r.syntaxError() + } + c = r.readByteNoBuf() + if c == '\\' { + r.readByteNoBuf() + if r.err != nil { + r.syntaxError() + return false + } + continue + } + if c == '"' { + c = r.readByteNoBuf() + goto Reswitch + } + } + goto Reswitch + + case '`': + startLine = false + for r.err == nil { + if r.eof { + r.syntaxError() + } + c = r.readByteNoBuf() + if c == '`' { + c = r.readByteNoBuf() + goto Reswitch + } + } + + case '/': + c = r.readByteNoBuf() + switch c { + default: + startLine = false + goto Reswitch + + case '*': + var c1 byte + for (c != '*' || c1 != '/') && r.err == nil { + if r.eof { + r.syntaxError() + } + c, c1 = c1, r.readByteNoBuf() + } + startLine = false + + case '/': + if startLine { + // Try to read this as a //go:embed comment. + for i := range goEmbed { + c = r.readByteNoBuf() + if c != goEmbed[i] { + goto SkipSlashSlash + } + } + c = r.readByteNoBuf() + if c == ' ' || c == '\t' { + // Found one! + return true + } + } + SkipSlashSlash: + for c != '\n' && r.err == nil && !r.eof { + c = r.readByteNoBuf() + } + startLine = true + } + } + } + return false +} + +// readKeyword reads the given keyword from the input. +// If the keyword is not present, readKeyword records a syntax error. +func (r *importReader) readKeyword(kw string) { + r.peekByte(true) + for i := 0; i < len(kw); i++ { + if r.nextByte(false) != kw[i] { + r.syntaxError() + return + } + } + if isIdent(r.peekByte(false)) { + r.syntaxError() + } +} + +// readIdent reads an identifier from the input. +// If an identifier is not present, readIdent records a syntax error. +func (r *importReader) readIdent() { + c := r.peekByte(true) + if !isIdent(c) { + r.syntaxError() + return + } + for isIdent(r.peekByte(false)) { + r.peek = 0 + } +} + +// readString reads a quoted string literal from the input. +// If an identifier is not present, readString records a syntax error. +func (r *importReader) readString() { + switch r.nextByte(true) { + case '`': + for r.err == nil { + if r.nextByte(false) == '`' { + break + } + if r.eof { + r.syntaxError() + } + } + case '"': + for r.err == nil { + c := r.nextByte(false) + if c == '"' { + break + } + if r.eof || c == '\n' { + r.syntaxError() + } + if c == '\\' { + r.nextByte(false) + } + } + default: + r.syntaxError() + } +} + +// readImport reads an import clause - optional identifier followed by quoted string - +// from the input. +func (r *importReader) readImport() { + c := r.peekByte(true) + if c == '.' { + r.peek = 0 + } else if isIdent(c) { + r.readIdent() + } + r.readString() +} + +// readComments is like io.ReadAll, except that it only reads the leading +// block of comments in the file. +func readComments(f io.Reader) ([]byte, error) { + r := newImportReader("", f) + r.peekByte(true) + if r.err == nil && !r.eof { + // Didn't reach EOF, so must have found a non-space byte. Remove it. + r.buf = r.buf[:len(r.buf)-1] + } + return r.buf, r.err +} + +// readGoInfo expects a Go file as input and reads the file up to and including the import section. +// It records what it learned in *info. +// If info.fset is non-nil, readGoInfo parses the file and sets info.parsed, info.parseErr, +// info.imports, info.embeds, and info.embedErr. +// +// It only returns an error if there are problems reading the file, +// not for syntax errors in the file itself. +func readGoInfo(f io.Reader, info *fileInfo) error { + r := newImportReader(info.name, f) + + r.readKeyword("package") + r.readIdent() + for r.peekByte(true) == 'i' { + r.readKeyword("import") + if r.peekByte(true) == '(' { + r.nextByte(false) + for r.peekByte(true) != ')' && r.err == nil { + r.readImport() + } + r.nextByte(false) + } else { + r.readImport() + } + } + + info.header = r.buf + + // If we stopped successfully before EOF, we read a byte that told us we were done. + // Return all but that last byte, which would cause a syntax error if we let it through. + if r.err == nil && !r.eof { + info.header = r.buf[:len(r.buf)-1] + } + + // If we stopped for a syntax error, consume the whole file so that + // we are sure we don't change the errors that go/parser returns. + if r.err == errSyntax { + r.err = nil + for r.err == nil && !r.eof { + r.readByte() + } + info.header = r.buf + } + if r.err != nil { + return r.err + } + + if info.fset == nil { + return nil + } + + // Parse file header & record imports. + info.parsed, info.parseErr = parser.ParseFile(info.fset, info.name, info.header, parser.ImportsOnly|parser.ParseComments) + if info.parseErr != nil { + return nil + } + + hasEmbed := false + for _, decl := range info.parsed.Decls { + d, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + for _, dspec := range d.Specs { + spec, ok := dspec.(*ast.ImportSpec) + if !ok { + continue + } + quoted := spec.Path.Value + path, err := strconv.Unquote(quoted) + if err != nil { + return fmt.Errorf("parser returned invalid quoted string: <%s>", quoted) + } + if path == "embed" { + hasEmbed = true + } + + doc := spec.Doc + if doc == nil && len(d.Specs) == 1 { + doc = d.Doc + } + info.imports = append(info.imports, fileImport{path, spec.Pos(), doc}) + } + } + + // If the file imports "embed", + // we have to look for //go:embed comments + // in the remainder of the file. + // The compiler will enforce the mapping of comments to + // declared variables. We just need to know the patterns. + // If there were //go:embed comments earlier in the file + // (near the package statement or imports), the compiler + // will reject them. They can be (and have already been) ignored. + if hasEmbed { + var line []byte + for first := true; r.findEmbed(first); first = false { + line = line[:0] + pos := r.pos + for { + c := r.readByteNoBuf() + if c == '\n' || r.err != nil || r.eof { + break + } + line = append(line, c) + } + // Add args if line is well-formed. + // Ignore badly-formed lines - the compiler will report them when it finds them, + // and we can pretend they are not there to help go list succeed with what it knows. + embs, err := parseGoEmbed(string(line), pos) + if err == nil { + info.embeds = append(info.embeds, embs...) + } + } + } + + return nil +} + +// parseGoEmbed parses the text following "//go:embed" to extract the glob patterns. +// It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings. +// This is based on a similar function in cmd/compile/internal/gc/noder.go; +// this version calculates position information as well. +func parseGoEmbed(args string, pos token.Position) ([]fileEmbed, error) { + trimBytes := func(n int) { + pos.Offset += n + pos.Column += utf8.RuneCountInString(args[:n]) + args = args[n:] + } + trimSpace := func() { + trim := strings.TrimLeftFunc(args, unicode.IsSpace) + trimBytes(len(args) - len(trim)) + } + + var list []fileEmbed + for trimSpace(); args != ""; trimSpace() { + var path string + pathPos := pos + Switch: + switch args[0] { + default: + i := len(args) + for j, c := range args { + if unicode.IsSpace(c) { + i = j + break + } + } + path = args[:i] + trimBytes(i) + + case '`': + i := strings.Index(args[1:], "`") + if i < 0 { + return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) + } + path = args[1 : 1+i] + trimBytes(1 + i + 1) + + case '"': + i := 1 + for ; i < len(args); i++ { + if args[i] == '\\' { + i++ + continue + } + if args[i] == '"' { + q, err := strconv.Unquote(args[:i+1]) + if err != nil { + return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1]) + } + path = q + trimBytes(i + 1) + break Switch + } + } + if i >= len(args) { + return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) + } + } + + if args != "" { + r, _ := utf8.DecodeRuneInString(args) + if !unicode.IsSpace(r) { + return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) + } + } + list = append(list, fileEmbed{path, pathPos}) + } + return list, nil +} diff --git a/src/go/build/read_test.go b/src/go/build/read_test.go new file mode 100644 index 0000000..32e6bae --- /dev/null +++ b/src/go/build/read_test.go @@ -0,0 +1,321 @@ +// Copyright 2012 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 build + +import ( + "fmt" + "go/token" + "io" + "strings" + "testing" +) + +const quote = "`" + +type readTest struct { + // Test input contains ℙ where readGoInfo should stop. + in string + err string +} + +var readGoInfoTests = []readTest{ + { + `package p`, + "", + }, + { + `package p; import "x"`, + "", + }, + { + `package p; import . "x"`, + "", + }, + { + `package p; import "x";ℙvar x = 1`, + "", + }, + { + `package p + + // comment + + import "x" + import _ "x" + import a "x" + + /* comment */ + + import ( + "x" /* comment */ + _ "x" + a "x" // comment + ` + quote + `x` + quote + ` + _ /*comment*/ ` + quote + `x` + quote + ` + a ` + quote + `x` + quote + ` + ) + import ( + ) + import () + import()import()import() + import();import();import() + + ℙvar x = 1 + `, + "", + }, +} + +var readCommentsTests = []readTest{ + { + `ℙpackage p`, + "", + }, + { + `ℙpackage p; import "x"`, + "", + }, + { + `ℙpackage p; import . "x"`, + "", + }, + { + `// foo + + /* bar */ + + /* quux */ // baz + + /*/ zot */ + + // asdf + ℙHello, world`, + "", + }, +} + +func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, error)) { + for i, tt := range tests { + var in, testOut string + j := strings.Index(tt.in, "ℙ") + if j < 0 { + in = tt.in + testOut = tt.in + } else { + in = tt.in[:j] + tt.in[j+len("ℙ"):] + testOut = tt.in[:j] + } + r := strings.NewReader(in) + buf, err := read(r) + if err != nil { + if tt.err == "" { + t.Errorf("#%d: err=%q, expected success (%q)", i, err, string(buf)) + } else if !strings.Contains(err.Error(), tt.err) { + t.Errorf("#%d: err=%q, expected %q", i, err, tt.err) + } + continue + } + if tt.err != "" { + t.Errorf("#%d: success, expected %q", i, tt.err) + continue + } + + out := string(buf) + if out != testOut { + t.Errorf("#%d: wrong output:\nhave %q\nwant %q\n", i, out, testOut) + } + } +} + +func TestReadGoInfo(t *testing.T) { + testRead(t, readGoInfoTests, func(r io.Reader) ([]byte, error) { + var info fileInfo + err := readGoInfo(r, &info) + return info.header, err + }) +} + +func TestReadComments(t *testing.T) { + testRead(t, readCommentsTests, readComments) +} + +var readFailuresTests = []readTest{ + { + `package`, + "syntax error", + }, + { + "package p\n\x00\nimport `math`\n", + "unexpected NUL in input", + }, + { + `package p; import`, + "syntax error", + }, + { + `package p; import "`, + "syntax error", + }, + { + "package p; import ` \n\n", + "syntax error", + }, + { + `package p; import "x`, + "syntax error", + }, + { + `package p; import _`, + "syntax error", + }, + { + `package p; import _ "`, + "syntax error", + }, + { + `package p; import _ "x`, + "syntax error", + }, + { + `package p; import .`, + "syntax error", + }, + { + `package p; import . "`, + "syntax error", + }, + { + `package p; import . "x`, + "syntax error", + }, + { + `package p; import (`, + "syntax error", + }, + { + `package p; import ("`, + "syntax error", + }, + { + `package p; import ("x`, + "syntax error", + }, + { + `package p; import ("x"`, + "syntax error", + }, +} + +func TestReadFailuresIgnored(t *testing.T) { + // Syntax errors should not be reported (false arg to readImports). + // Instead, entire file should be the output and no error. + // Convert tests not to return syntax errors. + tests := make([]readTest, len(readFailuresTests)) + copy(tests, readFailuresTests) + for i := range tests { + tt := &tests[i] + if !strings.Contains(tt.err, "NUL") { + tt.err = "" + } + } + testRead(t, tests, func(r io.Reader) ([]byte, error) { + var info fileInfo + err := readGoInfo(r, &info) + return info.header, err + }) +} + +var readEmbedTests = []struct { + in, out string +}{ + { + "package p\n", + "", + }, + { + "package p\nimport \"embed\"\nvar i int\n//go:embed x y z\nvar files embed.FS", + `test:4:12:x + test:4:14:y + test:4:16:z`, + }, + { + "package p\nimport \"embed\"\nvar i int\n//go:embed x \"\\x79\" `z`\nvar files embed.FS", + `test:4:12:x + test:4:14:y + test:4:21:z`, + }, + { + "package p\nimport \"embed\"\nvar i int\n//go:embed x y\n//go:embed z\nvar files embed.FS", + `test:4:12:x + test:4:14:y + test:5:12:z`, + }, + { + "package p\nimport \"embed\"\nvar i int\n\t //go:embed x y\n\t //go:embed z\n\t var files embed.FS", + `test:4:14:x + test:4:16:y + test:5:14:z`, + }, + { + "package p\nimport \"embed\"\n//go:embed x y z\nvar files embed.FS", + `test:3:12:x + test:3:14:y + test:3:16:z`, + }, + { + "package p\nimport \"embed\"\nvar s = \"/*\"\n//go:embed x\nvar files embed.FS", + `test:4:12:x`, + }, + { + `package p + import "embed" + var s = "\"\\\\" + //go:embed x + var files embed.FS`, + `test:4:15:x`, + }, + { + "package p\nimport \"embed\"\nvar s = `/*`\n//go:embed x\nvar files embed.FS", + `test:4:12:x`, + }, + { + "package p\nimport \"embed\"\nvar s = z/ *y\n//go:embed pointer\nvar pointer embed.FS", + "test:4:12:pointer", + }, + { + "package p\n//go:embed x y z\n", // no import, no scan + "", + }, + { + "package p\n//go:embed x y z\nvar files embed.FS", // no import, no scan + "", + }, +} + +func TestReadEmbed(t *testing.T) { + fset := token.NewFileSet() + for i, tt := range readEmbedTests { + info := fileInfo{ + name: "test", + fset: fset, + } + err := readGoInfo(strings.NewReader(tt.in), &info) + if err != nil { + t.Errorf("#%d: %v", i, err) + continue + } + b := &strings.Builder{} + sep := "" + for _, emb := range info.embeds { + fmt.Fprintf(b, "%s%v:%s", sep, emb.pos, emb.pattern) + sep = "\n" + } + got := b.String() + want := strings.Join(strings.Fields(tt.out), "\n") + if got != want { + t.Errorf("#%d: embeds:\n%s\nwant:\n%s", i, got, want) + } + } +} diff --git a/src/go/build/syslist.go b/src/go/build/syslist.go new file mode 100644 index 0000000..1275f7c --- /dev/null +++ b/src/go/build/syslist.go @@ -0,0 +1,11 @@ +// Copyright 2011 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 build + +// List of past, present, and future known GOOS and GOARCH values. +// Do not remove from this list, as these are used for go/build filename matching. + +const goosList = "aix android darwin dragonfly freebsd hurd illumos ios js linux nacl netbsd openbsd plan9 solaris windows zos " +const goarchList = "386 amd64 amd64p32 arm armbe arm64 arm64be ppc64 ppc64le mips mipsle mips64 mips64le mips64p32 mips64p32le ppc riscv riscv64 s390 s390x sparc sparc64 wasm " diff --git a/src/go/build/syslist_test.go b/src/go/build/syslist_test.go new file mode 100644 index 0000000..2b7b4c7 --- /dev/null +++ b/src/go/build/syslist_test.go @@ -0,0 +1,62 @@ +// Copyright 2011 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 build + +import ( + "runtime" + "testing" +) + +var ( + thisOS = runtime.GOOS + thisArch = runtime.GOARCH + otherOS = anotherOS() + otherArch = anotherArch() +) + +func anotherOS() string { + if thisOS != "darwin" && thisOS != "ios" { + return "darwin" + } + return "linux" +} + +func anotherArch() string { + if thisArch != "amd64" { + return "amd64" + } + return "386" +} + +type GoodFileTest struct { + name string + result bool +} + +var tests = []GoodFileTest{ + {"file.go", true}, + {"file.c", true}, + {"file_foo.go", true}, + {"file_" + thisArch + ".go", true}, + {"file_" + otherArch + ".go", false}, + {"file_" + thisOS + ".go", true}, + {"file_" + otherOS + ".go", false}, + {"file_" + thisOS + "_" + thisArch + ".go", true}, + {"file_" + otherOS + "_" + thisArch + ".go", false}, + {"file_" + thisOS + "_" + otherArch + ".go", false}, + {"file_" + otherOS + "_" + otherArch + ".go", false}, + {"file_foo_" + thisArch + ".go", true}, + {"file_foo_" + otherArch + ".go", false}, + {"file_" + thisOS + ".c", true}, + {"file_" + otherOS + ".c", false}, +} + +func TestGoodOSArch(t *testing.T) { + for _, test := range tests { + if Default.goodOSArchFile(test.name, make(map[string]bool)) != test.result { + t.Fatalf("goodOSArchFile(%q) != %v", test.name, test.result) + } + } +} diff --git a/src/go/build/testdata/cgo_disabled/cgo_disabled.go b/src/go/build/testdata/cgo_disabled/cgo_disabled.go new file mode 100644 index 0000000..d1edb99 --- /dev/null +++ b/src/go/build/testdata/cgo_disabled/cgo_disabled.go @@ -0,0 +1,5 @@ +package cgo_disabled + +import "C" + +import _ "should/be/ignored" diff --git a/src/go/build/testdata/cgo_disabled/empty.go b/src/go/build/testdata/cgo_disabled/empty.go new file mode 100644 index 0000000..63afe42 --- /dev/null +++ b/src/go/build/testdata/cgo_disabled/empty.go @@ -0,0 +1 @@ +package cgo_disabled diff --git a/src/go/build/testdata/doc/a_test.go b/src/go/build/testdata/doc/a_test.go new file mode 100644 index 0000000..1c07b56 --- /dev/null +++ b/src/go/build/testdata/doc/a_test.go @@ -0,0 +1,2 @@ +// Doc from xtests +package doc_test diff --git a/src/go/build/testdata/doc/b_test.go b/src/go/build/testdata/doc/b_test.go new file mode 100644 index 0000000..0cf1605 --- /dev/null +++ b/src/go/build/testdata/doc/b_test.go @@ -0,0 +1 @@ +package doc_test diff --git a/src/go/build/testdata/doc/c_test.go b/src/go/build/testdata/doc/c_test.go new file mode 100644 index 0000000..1025707 --- /dev/null +++ b/src/go/build/testdata/doc/c_test.go @@ -0,0 +1 @@ +package doc diff --git a/src/go/build/testdata/doc/d_test.go b/src/go/build/testdata/doc/d_test.go new file mode 100644 index 0000000..ec19564 --- /dev/null +++ b/src/go/build/testdata/doc/d_test.go @@ -0,0 +1,2 @@ +// Doc from regular tests. +package doc diff --git a/src/go/build/testdata/doc/e.go b/src/go/build/testdata/doc/e.go new file mode 100644 index 0000000..1025707 --- /dev/null +++ b/src/go/build/testdata/doc/e.go @@ -0,0 +1 @@ +package doc diff --git a/src/go/build/testdata/doc/f.go b/src/go/build/testdata/doc/f.go new file mode 100644 index 0000000..ab1d0bc --- /dev/null +++ b/src/go/build/testdata/doc/f.go @@ -0,0 +1,2 @@ +// Correct +package doc diff --git a/src/go/build/testdata/empty/dummy b/src/go/build/testdata/empty/dummy new file mode 100644 index 0000000..e69de29 diff --git a/src/go/build/testdata/multi/file.go b/src/go/build/testdata/multi/file.go new file mode 100644 index 0000000..ee946eb --- /dev/null +++ b/src/go/build/testdata/multi/file.go @@ -0,0 +1,5 @@ +// Test data - not compiled. + +package main + +func main() {} diff --git a/src/go/build/testdata/multi/file_appengine.go b/src/go/build/testdata/multi/file_appengine.go new file mode 100644 index 0000000..4ea31e7 --- /dev/null +++ b/src/go/build/testdata/multi/file_appengine.go @@ -0,0 +1,5 @@ +// Test data - not compiled. + +package test_package + +func init() {} diff --git a/src/go/build/testdata/other/file/file.go b/src/go/build/testdata/other/file/file.go new file mode 100644 index 0000000..bbfd3e9 --- /dev/null +++ b/src/go/build/testdata/other/file/file.go @@ -0,0 +1,5 @@ +// Test data - not compiled. + +package file + +func F() {} diff --git a/src/go/build/testdata/other/main.go b/src/go/build/testdata/other/main.go new file mode 100644 index 0000000..e090435 --- /dev/null +++ b/src/go/build/testdata/other/main.go @@ -0,0 +1,11 @@ +// Test data - not compiled. + +package main + +import ( + "./file" +) + +func main() { + file.F() +} diff --git a/src/go/build/testdata/withvendor/src/a/b/b.go b/src/go/build/testdata/withvendor/src/a/b/b.go new file mode 100644 index 0000000..4405d54 --- /dev/null +++ b/src/go/build/testdata/withvendor/src/a/b/b.go @@ -0,0 +1,3 @@ +package b + +import _ "c/d" diff --git a/src/go/build/testdata/withvendor/src/a/vendor/c/d/d.go b/src/go/build/testdata/withvendor/src/a/vendor/c/d/d.go new file mode 100644 index 0000000..142fb42 --- /dev/null +++ b/src/go/build/testdata/withvendor/src/a/vendor/c/d/d.go @@ -0,0 +1 @@ +package d diff --git a/src/go/constant/example_test.go b/src/go/constant/example_test.go new file mode 100644 index 0000000..6443ee6 --- /dev/null +++ b/src/go/constant/example_test.go @@ -0,0 +1,180 @@ +// Copyright 2018 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 constant_test + +import ( + "fmt" + "go/constant" + "go/token" + "math" + "sort" +) + +func Example_complexNumbers() { + // Create the complex number 2.3 + 5i. + ar := constant.MakeFloat64(2.3) + ai := constant.MakeImag(constant.MakeInt64(5)) + a := constant.BinaryOp(ar, token.ADD, ai) + + // Compute (2.3 + 5i) * 11. + b := constant.MakeUint64(11) + c := constant.BinaryOp(a, token.MUL, b) + + // Convert c into a complex128. + Ar, exact := constant.Float64Val(constant.Real(c)) + if !exact { + fmt.Printf("Could not represent real part %s exactly as float64\n", constant.Real(c)) + } + Ai, exact := constant.Float64Val(constant.Imag(c)) + if !exact { + fmt.Printf("Could not represent imaginary part %s as exactly as float64\n", constant.Imag(c)) + } + C := complex(Ar, Ai) + + fmt.Println("literal", 25.3+55i) + fmt.Println("go/constant", c) + fmt.Println("complex128", C) + + // Output: + // + // Could not represent real part 25.3 exactly as float64 + // literal (25.3+55i) + // go/constant (25.3 + 55i) + // complex128 (25.299999999999997+55i) +} + +func ExampleBinaryOp() { + // 11 / 0.5 + a := constant.MakeUint64(11) + b := constant.MakeFloat64(0.5) + c := constant.BinaryOp(a, token.QUO, b) + fmt.Println(c) + + // Output: 22 +} + +func ExampleUnaryOp() { + vs := []constant.Value{ + constant.MakeBool(true), + constant.MakeFloat64(2.7), + constant.MakeUint64(42), + } + + for i, v := range vs { + switch v.Kind() { + case constant.Bool: + vs[i] = constant.UnaryOp(token.NOT, v, 0) + + case constant.Float: + vs[i] = constant.UnaryOp(token.SUB, v, 0) + + case constant.Int: + // Use 16-bit precision. + // This would be equivalent to ^uint16(v). + vs[i] = constant.UnaryOp(token.XOR, v, 16) + } + } + + for _, v := range vs { + fmt.Println(v) + } + + // Output: + // + // false + // -2.7 + // 65493 +} + +func ExampleCompare() { + vs := []constant.Value{ + constant.MakeString("Z"), + constant.MakeString("bacon"), + constant.MakeString("go"), + constant.MakeString("Frame"), + constant.MakeString("defer"), + constant.MakeFromLiteral(`"a"`, token.STRING, 0), + } + + sort.Slice(vs, func(i, j int) bool { + // Equivalent to vs[i] <= vs[j]. + return constant.Compare(vs[i], token.LEQ, vs[j]) + }) + + for _, v := range vs { + fmt.Println(constant.StringVal(v)) + } + + // Output: + // + // Frame + // Z + // a + // bacon + // defer + // go +} + +func ExampleSign() { + zero := constant.MakeInt64(0) + one := constant.MakeInt64(1) + negOne := constant.MakeInt64(-1) + + mkComplex := func(a, b constant.Value) constant.Value { + b = constant.MakeImag(b) + return constant.BinaryOp(a, token.ADD, b) + } + + vs := []constant.Value{ + negOne, + mkComplex(zero, negOne), + mkComplex(one, negOne), + mkComplex(negOne, one), + mkComplex(negOne, negOne), + zero, + mkComplex(zero, zero), + one, + mkComplex(zero, one), + mkComplex(one, one), + } + + for _, v := range vs { + fmt.Printf("% d %s\n", constant.Sign(v), v) + } + + // Output: + // + // -1 -1 + // -1 (0 + -1i) + // -1 (1 + -1i) + // -1 (-1 + 1i) + // -1 (-1 + -1i) + // 0 0 + // 0 (0 + 0i) + // 1 1 + // 1 (0 + 1i) + // 1 (1 + 1i) +} + +func ExampleVal() { + maxint := constant.MakeInt64(math.MaxInt64) + fmt.Printf("%v\n", constant.Val(maxint)) + + e := constant.MakeFloat64(math.E) + fmt.Printf("%v\n", constant.Val(e)) + + b := constant.MakeBool(true) + fmt.Printf("%v\n", constant.Val(b)) + + b = constant.Make(false) + fmt.Printf("%v\n", constant.Val(b)) + + // Output: + // + // 9223372036854775807 + // 6121026514868073/2251799813685248 + // true + // false +} diff --git a/src/go/constant/value.go b/src/go/constant/value.go new file mode 100644 index 0000000..4641442 --- /dev/null +++ b/src/go/constant/value.go @@ -0,0 +1,1391 @@ +// Copyright 2013 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 constant implements Values representing untyped +// Go constants and their corresponding operations. +// +// A special Unknown value may be used when a value +// is unknown due to an error. Operations on unknown +// values produce unknown values unless specified +// otherwise. +// +package constant + +import ( + "fmt" + "go/token" + "math" + "math/big" + "strconv" + "strings" + "sync" + "unicode/utf8" +) + +// Kind specifies the kind of value represented by a Value. +type Kind int + +const ( + // unknown values + Unknown Kind = iota + + // non-numeric values + Bool + String + + // numeric values + Int + Float + Complex +) + +// A Value represents the value of a Go constant. +type Value interface { + // Kind returns the value kind. + Kind() Kind + + // String returns a short, quoted (human-readable) form of the value. + // For numeric values, the result may be an approximation; + // for String values the result may be a shortened string. + // Use ExactString for a string representing a value exactly. + String() string + + // ExactString returns an exact, quoted (human-readable) form of the value. + // If the Value is of Kind String, use StringVal to obtain the unquoted string. + ExactString() string + + // Prevent external implementations. + implementsValue() +} + +// ---------------------------------------------------------------------------- +// Implementations + +// Maximum supported mantissa precision. +// The spec requires at least 256 bits; typical implementations use 512 bits. +const prec = 512 + +// TODO(gri) Consider storing "error" information in an unknownVal so clients +// can provide better error messages. For instance, if a number is +// too large (incl. infinity), that could be recorded in unknownVal. +// See also #20583 and #42695 for use cases. + +type ( + unknownVal struct{} + boolVal bool + stringVal struct { + // Lazy value: either a string (l,r==nil) or an addition (l,r!=nil). + mu sync.Mutex + s string + l, r *stringVal + } + int64Val int64 // Int values representable as an int64 + intVal struct{ val *big.Int } // Int values not representable as an int64 + ratVal struct{ val *big.Rat } // Float values representable as a fraction + floatVal struct{ val *big.Float } // Float values not representable as a fraction + complexVal struct{ re, im Value } +) + +func (unknownVal) Kind() Kind { return Unknown } +func (boolVal) Kind() Kind { return Bool } +func (*stringVal) Kind() Kind { return String } +func (int64Val) Kind() Kind { return Int } +func (intVal) Kind() Kind { return Int } +func (ratVal) Kind() Kind { return Float } +func (floatVal) Kind() Kind { return Float } +func (complexVal) Kind() Kind { return Complex } + +func (unknownVal) String() string { return "unknown" } +func (x boolVal) String() string { return strconv.FormatBool(bool(x)) } + +// String returns a possibly shortened quoted form of the String value. +func (x *stringVal) String() string { + const maxLen = 72 // a reasonable length + s := strconv.Quote(x.string()) + if utf8.RuneCountInString(s) > maxLen { + // The string without the enclosing quotes is greater than maxLen-2 runes + // long. Remove the last 3 runes (including the closing '"') by keeping + // only the first maxLen-3 runes; then add "...". + i := 0 + for n := 0; n < maxLen-3; n++ { + _, size := utf8.DecodeRuneInString(s[i:]) + i += size + } + s = s[:i] + "..." + } + return s +} + +// string constructs and returns the actual string literal value. +// If x represents an addition, then it rewrites x to be a single +// string, to speed future calls. This lazy construction avoids +// building different string values for all subpieces of a large +// concatenation. See golang.org/issue/23348. +func (x *stringVal) string() string { + x.mu.Lock() + if x.l != nil { + x.s = strings.Join(reverse(x.appendReverse(nil)), "") + x.l = nil + x.r = nil + } + s := x.s + x.mu.Unlock() + + return s +} + +// reverse reverses x in place and returns it. +func reverse(x []string) []string { + n := len(x) + for i := 0; i+i < n; i++ { + x[i], x[n-1-i] = x[n-1-i], x[i] + } + return x +} + +// appendReverse appends to list all of x's subpieces, but in reverse, +// and returns the result. Appending the reversal allows processing +// the right side in a recursive call and the left side in a loop. +// Because a chain like a + b + c + d + e is actually represented +// as ((((a + b) + c) + d) + e), the left-side loop avoids deep recursion. +// x must be locked. +func (x *stringVal) appendReverse(list []string) []string { + y := x + for y.r != nil { + y.r.mu.Lock() + list = y.r.appendReverse(list) + y.r.mu.Unlock() + + l := y.l + if y != x { + y.mu.Unlock() + } + l.mu.Lock() + y = l + } + s := y.s + if y != x { + y.mu.Unlock() + } + return append(list, s) +} + +func (x int64Val) String() string { return strconv.FormatInt(int64(x), 10) } +func (x intVal) String() string { return x.val.String() } +func (x ratVal) String() string { return rtof(x).String() } + +// String returns a decimal approximation of the Float value. +func (x floatVal) String() string { + f := x.val + + // Don't try to convert infinities (will not terminate). + if f.IsInf() { + return f.String() + } + + // Use exact fmt formatting if in float64 range (common case): + // proceed if f doesn't underflow to 0 or overflow to inf. + if x, _ := f.Float64(); f.Sign() == 0 == (x == 0) && !math.IsInf(x, 0) { + return fmt.Sprintf("%.6g", x) + } + + // Out of float64 range. Do approximate manual to decimal + // conversion to avoid precise but possibly slow Float + // formatting. + // f = mant * 2**exp + var mant big.Float + exp := f.MantExp(&mant) // 0.5 <= |mant| < 1.0 + + // approximate float64 mantissa m and decimal exponent d + // f ~ m * 10**d + m, _ := mant.Float64() // 0.5 <= |m| < 1.0 + d := float64(exp) * (math.Ln2 / math.Ln10) // log_10(2) + + // adjust m for truncated (integer) decimal exponent e + e := int64(d) + m *= math.Pow(10, d-float64(e)) + + // ensure 1 <= |m| < 10 + switch am := math.Abs(m); { + case am < 1-0.5e-6: + // The %.6g format below rounds m to 5 digits after the + // decimal point. Make sure that m*10 < 10 even after + // rounding up: m*10 + 0.5e-5 < 10 => m < 1 - 0.5e6. + m *= 10 + e-- + case am >= 10: + m /= 10 + e++ + } + + return fmt.Sprintf("%.6ge%+d", m, e) +} + +func (x complexVal) String() string { return fmt.Sprintf("(%s + %si)", x.re, x.im) } + +func (x unknownVal) ExactString() string { return x.String() } +func (x boolVal) ExactString() string { return x.String() } +func (x *stringVal) ExactString() string { return strconv.Quote(x.string()) } +func (x int64Val) ExactString() string { return x.String() } +func (x intVal) ExactString() string { return x.String() } + +func (x ratVal) ExactString() string { + r := x.val + if r.IsInt() { + return r.Num().String() + } + return r.String() +} + +func (x floatVal) ExactString() string { return x.val.Text('p', 0) } + +func (x complexVal) ExactString() string { + return fmt.Sprintf("(%s + %si)", x.re.ExactString(), x.im.ExactString()) +} + +func (unknownVal) implementsValue() {} +func (boolVal) implementsValue() {} +func (*stringVal) implementsValue() {} +func (int64Val) implementsValue() {} +func (ratVal) implementsValue() {} +func (intVal) implementsValue() {} +func (floatVal) implementsValue() {} +func (complexVal) implementsValue() {} + +func newInt() *big.Int { return new(big.Int) } +func newRat() *big.Rat { return new(big.Rat) } +func newFloat() *big.Float { return new(big.Float).SetPrec(prec) } + +func i64toi(x int64Val) intVal { return intVal{newInt().SetInt64(int64(x))} } +func i64tor(x int64Val) ratVal { return ratVal{newRat().SetInt64(int64(x))} } +func i64tof(x int64Val) floatVal { return floatVal{newFloat().SetInt64(int64(x))} } +func itor(x intVal) ratVal { return ratVal{newRat().SetInt(x.val)} } +func itof(x intVal) floatVal { return floatVal{newFloat().SetInt(x.val)} } + +func rtof(x ratVal) floatVal { + a := newFloat().SetInt(x.val.Num()) + b := newFloat().SetInt(x.val.Denom()) + return floatVal{a.Quo(a, b)} +} + +func vtoc(x Value) complexVal { return complexVal{x, int64Val(0)} } + +func makeInt(x *big.Int) Value { + if x.IsInt64() { + return int64Val(x.Int64()) + } + return intVal{x} +} + +// Permit fractions with component sizes up to maxExp +// before switching to using floating-point numbers. +const maxExp = 4 << 10 + +func makeRat(x *big.Rat) Value { + a := x.Num() + b := x.Denom() + if a.BitLen() < maxExp && b.BitLen() < maxExp { + // ok to remain fraction + return ratVal{x} + } + // components too large => switch to float + fa := newFloat().SetInt(a) + fb := newFloat().SetInt(b) + return floatVal{fa.Quo(fa, fb)} +} + +var floatVal0 = floatVal{newFloat()} + +func makeFloat(x *big.Float) Value { + // convert -0 + if x.Sign() == 0 { + return floatVal0 + } + if x.IsInf() { + return unknownVal{} + } + return floatVal{x} +} + +func makeComplex(re, im Value) Value { + if re.Kind() == Unknown || im.Kind() == Unknown { + return unknownVal{} + } + return complexVal{re, im} +} + +func makeFloatFromLiteral(lit string) Value { + if f, ok := newFloat().SetString(lit); ok { + if smallRat(f) { + // ok to use rationals + if f.Sign() == 0 { + // Issue 20228: If the float underflowed to zero, parse just "0". + // Otherwise, lit might contain a value with a large negative exponent, + // such as -6e-1886451601. As a float, that will underflow to 0, + // but it'll take forever to parse as a Rat. + lit = "0" + } + if r, ok := newRat().SetString(lit); ok { + return ratVal{r} + } + } + // otherwise use floats + return makeFloat(f) + } + return nil +} + +// smallRat reports whether x would lead to "reasonably"-sized fraction +// if converted to a *big.Rat. +func smallRat(x *big.Float) bool { + if !x.IsInf() { + e := x.MantExp(nil) + return -maxExp < e && e < maxExp + } + return false +} + +// ---------------------------------------------------------------------------- +// Factories + +// MakeUnknown returns the Unknown value. +func MakeUnknown() Value { return unknownVal{} } + +// MakeBool returns the Bool value for b. +func MakeBool(b bool) Value { return boolVal(b) } + +// MakeString returns the String value for s. +func MakeString(s string) Value { return &stringVal{s: s} } + +// MakeInt64 returns the Int value for x. +func MakeInt64(x int64) Value { return int64Val(x) } + +// MakeUint64 returns the Int value for x. +func MakeUint64(x uint64) Value { + if x < 1<<63 { + return int64Val(int64(x)) + } + return intVal{newInt().SetUint64(x)} +} + +// MakeFloat64 returns the Float value for x. +// If x is -0.0, the result is 0.0. +// If x is not finite, the result is an Unknown. +func MakeFloat64(x float64) Value { + if math.IsInf(x, 0) || math.IsNaN(x) { + return unknownVal{} + } + return ratVal{newRat().SetFloat64(x + 0)} // convert -0 to 0 +} + +// MakeFromLiteral returns the corresponding integer, floating-point, +// imaginary, character, or string value for a Go literal string. The +// tok value must be one of token.INT, token.FLOAT, token.IMAG, +// token.CHAR, or token.STRING. The final argument must be zero. +// If the literal string syntax is invalid, the result is an Unknown. +func MakeFromLiteral(lit string, tok token.Token, zero uint) Value { + if zero != 0 { + panic("MakeFromLiteral called with non-zero last argument") + } + + switch tok { + case token.INT: + if x, err := strconv.ParseInt(lit, 0, 64); err == nil { + return int64Val(x) + } + if x, ok := newInt().SetString(lit, 0); ok { + return intVal{x} + } + + case token.FLOAT: + if x := makeFloatFromLiteral(lit); x != nil { + return x + } + + case token.IMAG: + if n := len(lit); n > 0 && lit[n-1] == 'i' { + if im := makeFloatFromLiteral(lit[:n-1]); im != nil { + return makeComplex(int64Val(0), im) + } + } + + case token.CHAR: + if n := len(lit); n >= 2 { + if code, _, _, err := strconv.UnquoteChar(lit[1:n-1], '\''); err == nil { + return MakeInt64(int64(code)) + } + } + + case token.STRING: + if s, err := strconv.Unquote(lit); err == nil { + return MakeString(s) + } + + default: + panic(fmt.Sprintf("%v is not a valid token", tok)) + } + + return unknownVal{} +} + +// ---------------------------------------------------------------------------- +// Accessors +// +// For unknown arguments the result is the zero value for the respective +// accessor type, except for Sign, where the result is 1. + +// BoolVal returns the Go boolean value of x, which must be a Bool or an Unknown. +// If x is Unknown, the result is false. +func BoolVal(x Value) bool { + switch x := x.(type) { + case boolVal: + return bool(x) + case unknownVal: + return false + default: + panic(fmt.Sprintf("%v not a Bool", x)) + } +} + +// StringVal returns the Go string value of x, which must be a String or an Unknown. +// If x is Unknown, the result is "". +func StringVal(x Value) string { + switch x := x.(type) { + case *stringVal: + return x.string() + case unknownVal: + return "" + default: + panic(fmt.Sprintf("%v not a String", x)) + } +} + +// Int64Val returns the Go int64 value of x and whether the result is exact; +// x must be an Int or an Unknown. If the result is not exact, its value is undefined. +// If x is Unknown, the result is (0, false). +func Int64Val(x Value) (int64, bool) { + switch x := x.(type) { + case int64Val: + return int64(x), true + case intVal: + return x.val.Int64(), false // not an int64Val and thus not exact + case unknownVal: + return 0, false + default: + panic(fmt.Sprintf("%v not an Int", x)) + } +} + +// Uint64Val returns the Go uint64 value of x and whether the result is exact; +// x must be an Int or an Unknown. If the result is not exact, its value is undefined. +// If x is Unknown, the result is (0, false). +func Uint64Val(x Value) (uint64, bool) { + switch x := x.(type) { + case int64Val: + return uint64(x), x >= 0 + case intVal: + return x.val.Uint64(), x.val.IsUint64() + case unknownVal: + return 0, false + default: + panic(fmt.Sprintf("%v not an Int", x)) + } +} + +// Float32Val is like Float64Val but for float32 instead of float64. +func Float32Val(x Value) (float32, bool) { + switch x := x.(type) { + case int64Val: + f := float32(x) + return f, int64Val(f) == x + case intVal: + f, acc := newFloat().SetInt(x.val).Float32() + return f, acc == big.Exact + case ratVal: + return x.val.Float32() + case floatVal: + f, acc := x.val.Float32() + return f, acc == big.Exact + case unknownVal: + return 0, false + default: + panic(fmt.Sprintf("%v not a Float", x)) + } +} + +// Float64Val returns the nearest Go float64 value of x and whether the result is exact; +// x must be numeric or an Unknown, but not Complex. For values too small (too close to 0) +// to represent as float64, Float64Val silently underflows to 0. The result sign always +// matches the sign of x, even for 0. +// If x is Unknown, the result is (0, false). +func Float64Val(x Value) (float64, bool) { + switch x := x.(type) { + case int64Val: + f := float64(int64(x)) + return f, int64Val(f) == x + case intVal: + f, acc := newFloat().SetInt(x.val).Float64() + return f, acc == big.Exact + case ratVal: + return x.val.Float64() + case floatVal: + f, acc := x.val.Float64() + return f, acc == big.Exact + case unknownVal: + return 0, false + default: + panic(fmt.Sprintf("%v not a Float", x)) + } +} + +// Val returns the underlying value for a given constant. Since it returns an +// interface, it is up to the caller to type assert the result to the expected +// type. The possible dynamic return types are: +// +// x Kind type of result +// ----------------------------------------- +// Bool bool +// String string +// Int int64 or *big.Int +// Float *big.Float or *big.Rat +// everything else nil +// +func Val(x Value) interface{} { + switch x := x.(type) { + case boolVal: + return bool(x) + case *stringVal: + return x.string() + case int64Val: + return int64(x) + case intVal: + return x.val + case ratVal: + return x.val + case floatVal: + return x.val + default: + return nil + } +} + +// Make returns the Value for x. +// +// type of x result Kind +// ---------------------------- +// bool Bool +// string String +// int64 Int +// *big.Int Int +// *big.Float Float +// *big.Rat Float +// anything else Unknown +// +func Make(x interface{}) Value { + switch x := x.(type) { + case bool: + return boolVal(x) + case string: + return &stringVal{s: x} + case int64: + return int64Val(x) + case *big.Int: + return makeInt(x) + case *big.Rat: + return makeRat(x) + case *big.Float: + return makeFloat(x) + default: + return unknownVal{} + } +} + +// BitLen returns the number of bits required to represent +// the absolute value x in binary representation; x must be an Int or an Unknown. +// If x is Unknown, the result is 0. +func BitLen(x Value) int { + switch x := x.(type) { + case int64Val: + return i64toi(x).val.BitLen() + case intVal: + return x.val.BitLen() + case unknownVal: + return 0 + default: + panic(fmt.Sprintf("%v not an Int", x)) + } +} + +// Sign returns -1, 0, or 1 depending on whether x < 0, x == 0, or x > 0; +// x must be numeric or Unknown. For complex values x, the sign is 0 if x == 0, +// otherwise it is != 0. If x is Unknown, the result is 1. +func Sign(x Value) int { + switch x := x.(type) { + case int64Val: + switch { + case x < 0: + return -1 + case x > 0: + return 1 + } + return 0 + case intVal: + return x.val.Sign() + case ratVal: + return x.val.Sign() + case floatVal: + return x.val.Sign() + case complexVal: + return Sign(x.re) | Sign(x.im) + case unknownVal: + return 1 // avoid spurious division by zero errors + default: + panic(fmt.Sprintf("%v not numeric", x)) + } +} + +// ---------------------------------------------------------------------------- +// Support for assembling/disassembling numeric values + +const ( + // Compute the size of a Word in bytes. + _m = ^big.Word(0) + _log = _m>>8&1 + _m>>16&1 + _m>>32&1 + wordSize = 1 << _log +) + +// Bytes returns the bytes for the absolute value of x in little- +// endian binary representation; x must be an Int. +func Bytes(x Value) []byte { + var t intVal + switch x := x.(type) { + case int64Val: + t = i64toi(x) + case intVal: + t = x + default: + panic(fmt.Sprintf("%v not an Int", x)) + } + + words := t.val.Bits() + bytes := make([]byte, len(words)*wordSize) + + i := 0 + for _, w := range words { + for j := 0; j < wordSize; j++ { + bytes[i] = byte(w) + w >>= 8 + i++ + } + } + // remove leading 0's + for i > 0 && bytes[i-1] == 0 { + i-- + } + + return bytes[:i] +} + +// MakeFromBytes returns the Int value given the bytes of its little-endian +// binary representation. An empty byte slice argument represents 0. +func MakeFromBytes(bytes []byte) Value { + words := make([]big.Word, (len(bytes)+(wordSize-1))/wordSize) + + i := 0 + var w big.Word + var s uint + for _, b := range bytes { + w |= big.Word(b) << s + if s += 8; s == wordSize*8 { + words[i] = w + i++ + w = 0 + s = 0 + } + } + // store last word + if i < len(words) { + words[i] = w + i++ + } + // remove leading 0's + for i > 0 && words[i-1] == 0 { + i-- + } + + return makeInt(newInt().SetBits(words[:i])) +} + +// Num returns the numerator of x; x must be Int, Float, or Unknown. +// If x is Unknown, or if it is too large or small to represent as a +// fraction, the result is Unknown. Otherwise the result is an Int +// with the same sign as x. +func Num(x Value) Value { + switch x := x.(type) { + case int64Val, intVal: + return x + case ratVal: + return makeInt(x.val.Num()) + case floatVal: + if smallRat(x.val) { + r, _ := x.val.Rat(nil) + return makeInt(r.Num()) + } + case unknownVal: + break + default: + panic(fmt.Sprintf("%v not Int or Float", x)) + } + return unknownVal{} +} + +// Denom returns the denominator of x; x must be Int, Float, or Unknown. +// If x is Unknown, or if it is too large or small to represent as a +// fraction, the result is Unknown. Otherwise the result is an Int >= 1. +func Denom(x Value) Value { + switch x := x.(type) { + case int64Val, intVal: + return int64Val(1) + case ratVal: + return makeInt(x.val.Denom()) + case floatVal: + if smallRat(x.val) { + r, _ := x.val.Rat(nil) + return makeInt(r.Denom()) + } + case unknownVal: + break + default: + panic(fmt.Sprintf("%v not Int or Float", x)) + } + return unknownVal{} +} + +// MakeImag returns the Complex value x*i; +// x must be Int, Float, or Unknown. +// If x is Unknown, the result is Unknown. +func MakeImag(x Value) Value { + switch x.(type) { + case unknownVal: + return x + case int64Val, intVal, ratVal, floatVal: + return makeComplex(int64Val(0), x) + default: + panic(fmt.Sprintf("%v not Int or Float", x)) + } +} + +// Real returns the real part of x, which must be a numeric or unknown value. +// If x is Unknown, the result is Unknown. +func Real(x Value) Value { + switch x := x.(type) { + case unknownVal, int64Val, intVal, ratVal, floatVal: + return x + case complexVal: + return x.re + default: + panic(fmt.Sprintf("%v not numeric", x)) + } +} + +// Imag returns the imaginary part of x, which must be a numeric or unknown value. +// If x is Unknown, the result is Unknown. +func Imag(x Value) Value { + switch x := x.(type) { + case unknownVal: + return x + case int64Val, intVal, ratVal, floatVal: + return int64Val(0) + case complexVal: + return x.im + default: + panic(fmt.Sprintf("%v not numeric", x)) + } +} + +// ---------------------------------------------------------------------------- +// Numeric conversions + +// ToInt converts x to an Int value if x is representable as an Int. +// Otherwise it returns an Unknown. +func ToInt(x Value) Value { + switch x := x.(type) { + case int64Val, intVal: + return x + + case ratVal: + if x.val.IsInt() { + return makeInt(x.val.Num()) + } + + case floatVal: + // avoid creation of huge integers + // (Existing tests require permitting exponents of at least 1024; + // allow any value that would also be permissible as a fraction.) + if smallRat(x.val) { + i := newInt() + if _, acc := x.val.Int(i); acc == big.Exact { + return makeInt(i) + } + + // If we can get an integer by rounding up or down, + // assume x is not an integer because of rounding + // errors in prior computations. + + const delta = 4 // a small number of bits > 0 + var t big.Float + t.SetPrec(prec - delta) + + // try rounding down a little + t.SetMode(big.ToZero) + t.Set(x.val) + if _, acc := t.Int(i); acc == big.Exact { + return makeInt(i) + } + + // try rounding up a little + t.SetMode(big.AwayFromZero) + t.Set(x.val) + if _, acc := t.Int(i); acc == big.Exact { + return makeInt(i) + } + } + + case complexVal: + if re := ToFloat(x); re.Kind() == Float { + return ToInt(re) + } + } + + return unknownVal{} +} + +// ToFloat converts x to a Float value if x is representable as a Float. +// Otherwise it returns an Unknown. +func ToFloat(x Value) Value { + switch x := x.(type) { + case int64Val: + return i64tof(x) + case intVal: + return itof(x) + case ratVal, floatVal: + return x + case complexVal: + if im := ToInt(x.im); im.Kind() == Int && Sign(im) == 0 { + // imaginary component is 0 + return ToFloat(x.re) + } + } + return unknownVal{} +} + +// ToComplex converts x to a Complex value if x is representable as a Complex. +// Otherwise it returns an Unknown. +func ToComplex(x Value) Value { + switch x := x.(type) { + case int64Val: + return vtoc(i64tof(x)) + case intVal: + return vtoc(itof(x)) + case ratVal: + return vtoc(x) + case floatVal: + return vtoc(x) + case complexVal: + return x + } + return unknownVal{} +} + +// ---------------------------------------------------------------------------- +// Operations + +// is32bit reports whether x can be represented using 32 bits. +func is32bit(x int64) bool { + const s = 32 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 +} + +// is63bit reports whether x can be represented using 63 bits. +func is63bit(x int64) bool { + const s = 63 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 +} + +// UnaryOp returns the result of the unary expression op y. +// The operation must be defined for the operand. +// If prec > 0 it specifies the ^ (xor) result size in bits. +// If y is Unknown, the result is Unknown. +// +func UnaryOp(op token.Token, y Value, prec uint) Value { + switch op { + case token.ADD: + switch y.(type) { + case unknownVal, int64Val, intVal, ratVal, floatVal, complexVal: + return y + } + + case token.SUB: + switch y := y.(type) { + case unknownVal: + return y + case int64Val: + if z := -y; z != y { + return z // no overflow + } + return makeInt(newInt().Neg(big.NewInt(int64(y)))) + case intVal: + return makeInt(newInt().Neg(y.val)) + case ratVal: + return makeRat(newRat().Neg(y.val)) + case floatVal: + return makeFloat(newFloat().Neg(y.val)) + case complexVal: + re := UnaryOp(token.SUB, y.re, 0) + im := UnaryOp(token.SUB, y.im, 0) + return makeComplex(re, im) + } + + case token.XOR: + z := newInt() + switch y := y.(type) { + case unknownVal: + return y + case int64Val: + z.Not(big.NewInt(int64(y))) + case intVal: + z.Not(y.val) + default: + goto Error + } + // For unsigned types, the result will be negative and + // thus "too large": We must limit the result precision + // to the type's precision. + if prec > 0 { + z.AndNot(z, newInt().Lsh(big.NewInt(-1), prec)) // z &^= (-1)< ord(y) { + y, x = match(y, x) + return x, y + } + // ord(x) <= ord(y) + + switch x := x.(type) { + case boolVal, *stringVal, complexVal: + return x, y + + case int64Val: + switch y := y.(type) { + case int64Val: + return x, y + case intVal: + return i64toi(x), y + case ratVal: + return i64tor(x), y + case floatVal: + return i64tof(x), y + case complexVal: + return vtoc(x), y + } + + case intVal: + switch y := y.(type) { + case intVal: + return x, y + case ratVal: + return itor(x), y + case floatVal: + return itof(x), y + case complexVal: + return vtoc(x), y + } + + case ratVal: + switch y := y.(type) { + case ratVal: + return x, y + case floatVal: + return rtof(x), y + case complexVal: + return vtoc(x), y + } + + case floatVal: + switch y := y.(type) { + case floatVal: + return x, y + case complexVal: + return vtoc(x), y + } + } + + // force unknown and invalid values into "x position" in callers of match + // (don't panic here so that callers can provide a better error message) + return x, x +} + +// BinaryOp returns the result of the binary expression x op y. +// The operation must be defined for the operands. If one of the +// operands is Unknown, the result is Unknown. +// BinaryOp doesn't handle comparisons or shifts; use Compare +// or Shift instead. +// +// To force integer division of Int operands, use op == token.QUO_ASSIGN +// instead of token.QUO; the result is guaranteed to be Int in this case. +// Division by zero leads to a run-time panic. +// +func BinaryOp(x_ Value, op token.Token, y_ Value) Value { + x, y := match(x_, y_) + + switch x := x.(type) { + case unknownVal: + return x + + case boolVal: + y := y.(boolVal) + switch op { + case token.LAND: + return x && y + case token.LOR: + return x || y + } + + case int64Val: + a := int64(x) + b := int64(y.(int64Val)) + var c int64 + switch op { + case token.ADD: + if !is63bit(a) || !is63bit(b) { + return makeInt(newInt().Add(big.NewInt(a), big.NewInt(b))) + } + c = a + b + case token.SUB: + if !is63bit(a) || !is63bit(b) { + return makeInt(newInt().Sub(big.NewInt(a), big.NewInt(b))) + } + c = a - b + case token.MUL: + if !is32bit(a) || !is32bit(b) { + return makeInt(newInt().Mul(big.NewInt(a), big.NewInt(b))) + } + c = a * b + case token.QUO: + return makeRat(big.NewRat(a, b)) + case token.QUO_ASSIGN: // force integer division + c = a / b + case token.REM: + c = a % b + case token.AND: + c = a & b + case token.OR: + c = a | b + case token.XOR: + c = a ^ b + case token.AND_NOT: + c = a &^ b + default: + goto Error + } + return int64Val(c) + + case intVal: + a := x.val + b := y.(intVal).val + c := newInt() + switch op { + case token.ADD: + c.Add(a, b) + case token.SUB: + c.Sub(a, b) + case token.MUL: + c.Mul(a, b) + case token.QUO: + return makeRat(newRat().SetFrac(a, b)) + case token.QUO_ASSIGN: // force integer division + c.Quo(a, b) + case token.REM: + c.Rem(a, b) + case token.AND: + c.And(a, b) + case token.OR: + c.Or(a, b) + case token.XOR: + c.Xor(a, b) + case token.AND_NOT: + c.AndNot(a, b) + default: + goto Error + } + return makeInt(c) + + case ratVal: + a := x.val + b := y.(ratVal).val + c := newRat() + switch op { + case token.ADD: + c.Add(a, b) + case token.SUB: + c.Sub(a, b) + case token.MUL: + c.Mul(a, b) + case token.QUO: + c.Quo(a, b) + default: + goto Error + } + return makeRat(c) + + case floatVal: + a := x.val + b := y.(floatVal).val + c := newFloat() + switch op { + case token.ADD: + c.Add(a, b) + case token.SUB: + c.Sub(a, b) + case token.MUL: + c.Mul(a, b) + case token.QUO: + c.Quo(a, b) + default: + goto Error + } + return makeFloat(c) + + case complexVal: + y := y.(complexVal) + a, b := x.re, x.im + c, d := y.re, y.im + var re, im Value + switch op { + case token.ADD: + // (a+c) + i(b+d) + re = add(a, c) + im = add(b, d) + case token.SUB: + // (a-c) + i(b-d) + re = sub(a, c) + im = sub(b, d) + case token.MUL: + // (ac-bd) + i(bc+ad) + ac := mul(a, c) + bd := mul(b, d) + bc := mul(b, c) + ad := mul(a, d) + re = sub(ac, bd) + im = add(bc, ad) + case token.QUO: + // (ac+bd)/s + i(bc-ad)/s, with s = cc + dd + ac := mul(a, c) + bd := mul(b, d) + bc := mul(b, c) + ad := mul(a, d) + cc := mul(c, c) + dd := mul(d, d) + s := add(cc, dd) + re = add(ac, bd) + re = quo(re, s) + im = sub(bc, ad) + im = quo(im, s) + default: + goto Error + } + return makeComplex(re, im) + + case *stringVal: + if op == token.ADD { + return &stringVal{l: x, r: y.(*stringVal)} + } + } + +Error: + panic(fmt.Sprintf("invalid binary operation %v %s %v", x_, op, y_)) +} + +func add(x, y Value) Value { return BinaryOp(x, token.ADD, y) } +func sub(x, y Value) Value { return BinaryOp(x, token.SUB, y) } +func mul(x, y Value) Value { return BinaryOp(x, token.MUL, y) } +func quo(x, y Value) Value { return BinaryOp(x, token.QUO, y) } + +// Shift returns the result of the shift expression x op s +// with op == token.SHL or token.SHR (<< or >>). x must be +// an Int or an Unknown. If x is Unknown, the result is x. +// +func Shift(x Value, op token.Token, s uint) Value { + switch x := x.(type) { + case unknownVal: + return x + + case int64Val: + if s == 0 { + return x + } + switch op { + case token.SHL: + z := i64toi(x).val + return makeInt(z.Lsh(z, s)) + case token.SHR: + return x >> s + } + + case intVal: + if s == 0 { + return x + } + z := newInt() + switch op { + case token.SHL: + return makeInt(z.Lsh(x.val, s)) + case token.SHR: + return makeInt(z.Rsh(x.val, s)) + } + } + + panic(fmt.Sprintf("invalid shift %v %s %d", x, op, s)) +} + +func cmpZero(x int, op token.Token) bool { + switch op { + case token.EQL: + return x == 0 + case token.NEQ: + return x != 0 + case token.LSS: + return x < 0 + case token.LEQ: + return x <= 0 + case token.GTR: + return x > 0 + case token.GEQ: + return x >= 0 + } + panic(fmt.Sprintf("invalid comparison %v %s 0", x, op)) +} + +// Compare returns the result of the comparison x op y. +// The comparison must be defined for the operands. +// If one of the operands is Unknown, the result is +// false. +// +func Compare(x_ Value, op token.Token, y_ Value) bool { + x, y := match(x_, y_) + + switch x := x.(type) { + case unknownVal: + return false + + case boolVal: + y := y.(boolVal) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + } + + case int64Val: + y := y.(int64Val) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + case token.LSS: + return x < y + case token.LEQ: + return x <= y + case token.GTR: + return x > y + case token.GEQ: + return x >= y + } + + case intVal: + return cmpZero(x.val.Cmp(y.(intVal).val), op) + + case ratVal: + return cmpZero(x.val.Cmp(y.(ratVal).val), op) + + case floatVal: + return cmpZero(x.val.Cmp(y.(floatVal).val), op) + + case complexVal: + y := y.(complexVal) + re := Compare(x.re, token.EQL, y.re) + im := Compare(x.im, token.EQL, y.im) + switch op { + case token.EQL: + return re && im + case token.NEQ: + return !re || !im + } + + case *stringVal: + xs := x.string() + ys := y.(*stringVal).string() + switch op { + case token.EQL: + return xs == ys + case token.NEQ: + return xs != ys + case token.LSS: + return xs < ys + case token.LEQ: + return xs <= ys + case token.GTR: + return xs > ys + case token.GEQ: + return xs >= ys + } + } + + panic(fmt.Sprintf("invalid comparison %v %s %v", x_, op, y_)) +} diff --git a/src/go/constant/value_test.go b/src/go/constant/value_test.go new file mode 100644 index 0000000..2866774 --- /dev/null +++ b/src/go/constant/value_test.go @@ -0,0 +1,708 @@ +// Copyright 2013 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 constant + +import ( + "fmt" + "go/token" + "math" + "math/big" + "strings" + "testing" +) + +var intTests = []string{ + // 0-octals + `0_123 = 0123`, + `0123_456 = 0123456`, + + // decimals + `1_234 = 1234`, + `1_234_567 = 1234567`, + + // hexadecimals + `0X_0 = 0`, + `0X_1234 = 0x1234`, + `0X_CAFE_f00d = 0xcafef00d`, + + // octals + `0o0 = 0`, + `0o1234 = 01234`, + `0o01234567 = 01234567`, + + `0O0 = 0`, + `0O1234 = 01234`, + `0O01234567 = 01234567`, + + `0o_0 = 0`, + `0o_1234 = 01234`, + `0o0123_4567 = 01234567`, + + `0O_0 = 0`, + `0O_1234 = 01234`, + `0O0123_4567 = 01234567`, + + // binaries + `0b0 = 0`, + `0b1011 = 0xb`, + `0b00101101 = 0x2d`, + + `0B0 = 0`, + `0B1011 = 0xb`, + `0B00101101 = 0x2d`, + + `0b_0 = 0`, + `0b10_11 = 0xb`, + `0b_0010_1101 = 0x2d`, +} + +// The RHS operand may be a floating-point quotient n/d of two integer values n and d. +var floatTests = []string{ + // decimal floats + `1_2_3. = 123.`, + `0_123. = 123.`, + + `0_0e0 = 0.`, + `1_2_3e0 = 123.`, + `0_123e0 = 123.`, + + `0e-0_0 = 0.`, + `1_2_3E+0 = 123.`, + `0123E1_2_3 = 123e123`, + + `0.e+1 = 0.`, + `123.E-1_0 = 123e-10`, + `01_23.e123 = 123e123`, + + `.0e-1 = .0`, + `.123E+10 = .123e10`, + `.0123E123 = .0123e123`, + + `1_2_3.123 = 123.123`, + `0123.01_23 = 123.0123`, + + `1e-1000000000 = 0`, + `1e+1000000000 = ?`, + `6e5518446744 = ?`, + `-6e5518446744 = ?`, + + // hexadecimal floats + `0x0.p+0 = 0.`, + `0Xdeadcafe.p-10 = 0xdeadcafe/1024`, + `0x1234.P84 = 0x1234000000000000000000000`, + + `0x.1p-0 = 1/16`, + `0X.deadcafep4 = 0xdeadcafe/0x10000000`, + `0x.1234P+12 = 0x1234/0x10`, + + `0x0p0 = 0.`, + `0Xdeadcafep+1 = 0x1bd5b95fc`, + `0x1234P-10 = 0x1234/1024`, + + `0x0.0p0 = 0.`, + `0Xdead.cafep+1 = 0x1bd5b95fc/0x10000`, + `0x12.34P-10 = 0x1234/0x40000`, + + `0Xdead_cafep+1 = 0xdeadcafep+1`, + `0x_1234P-10 = 0x1234p-10`, + + `0X_dead_cafe.p-10 = 0xdeadcafe.p-10`, + `0x12_34.P1_2_3 = 0x1234.p123`, +} + +var imagTests = []string{ + `1_234i = 1234i`, + `1_234_567i = 1234567i`, + + `0.i = 0i`, + `123.i = 123i`, + `0123.i = 123i`, + + `0.e+1i = 0i`, + `123.E-1_0i = 123e-10i`, + `01_23.e123i = 123e123i`, + + `1e-1000000000i = 0i`, + `1e+1000000000i = ?`, + `6e5518446744i = ?`, + `-6e5518446744i = ?`, +} + +func testNumbers(t *testing.T, kind token.Token, tests []string) { + for _, test := range tests { + a := strings.Split(test, " = ") + if len(a) != 2 { + t.Errorf("invalid test case: %s", test) + continue + } + + x := MakeFromLiteral(a[0], kind, 0) + var y Value + if a[1] == "?" { + y = MakeUnknown() + } else { + if i := strings.Index(a[1], "/"); i >= 0 && kind == token.FLOAT { + n := MakeFromLiteral(a[1][:i], token.INT, 0) + d := MakeFromLiteral(a[1][i+1:], token.INT, 0) + y = BinaryOp(n, token.QUO, d) + } else { + y = MakeFromLiteral(a[1], kind, 0) + } + if y.Kind() == Unknown { + panic(fmt.Sprintf("invalid test case: %s %d", test, y.Kind())) + } + } + + xk := x.Kind() + yk := y.Kind() + if xk != yk { + t.Errorf("%s: got kind %d != %d", test, xk, yk) + continue + } + + if yk == Unknown { + continue + } + + if !Compare(x, token.EQL, y) { + t.Errorf("%s: %s != %s", test, x, y) + } + } +} + +// TestNumbers verifies that differently written literals +// representing the same number do have the same value. +func TestNumbers(t *testing.T) { + testNumbers(t, token.INT, intTests) + testNumbers(t, token.FLOAT, floatTests) + testNumbers(t, token.IMAG, imagTests) +} + +var opTests = []string{ + // unary operations + `+ 0 = 0`, + `+ ? = ?`, + `- 1 = -1`, + `- ? = ?`, + `^ 0 = -1`, + `^ ? = ?`, + + `! true = false`, + `! false = true`, + `! ? = ?`, + + // etc. + + // binary operations + `"" + "" = ""`, + `"foo" + "" = "foo"`, + `"" + "bar" = "bar"`, + `"foo" + "bar" = "foobar"`, + + `0 + 0 = 0`, + `0 + 0.1 = 0.1`, + `0 + 0.1i = 0.1i`, + `0.1 + 0.9 = 1`, + `1e100 + 1e100 = 2e100`, + `? + 0 = ?`, + `0 + ? = ?`, + + `0 - 0 = 0`, + `0 - 0.1 = -0.1`, + `0 - 0.1i = -0.1i`, + `1e100 - 1e100 = 0`, + `? - 0 = ?`, + `0 - ? = ?`, + + `0 * 0 = 0`, + `1 * 0.1 = 0.1`, + `1 * 0.1i = 0.1i`, + `1i * 1i = -1`, + `? * 0 = ?`, + `0 * ? = ?`, + `0 * 1e+1000000000 = ?`, + + `0 / 0 = "division_by_zero"`, + `10 / 2 = 5`, + `5 / 3 = 5/3`, + `5i / 3i = 5/3`, + `? / 0 = ?`, + `0 / ? = ?`, + `0 * 1e+1000000000i = ?`, + + `0 % 0 = "runtime_error:_integer_divide_by_zero"`, // TODO(gri) should be the same as for / + `10 % 3 = 1`, + `? % 0 = ?`, + `0 % ? = ?`, + + `0 & 0 = 0`, + `12345 & 0 = 0`, + `0xff & 0xf = 0xf`, + `? & 0 = ?`, + `0 & ? = ?`, + + `0 | 0 = 0`, + `12345 | 0 = 12345`, + `0xb | 0xa0 = 0xab`, + `? | 0 = ?`, + `0 | ? = ?`, + + `0 ^ 0 = 0`, + `1 ^ -1 = -2`, + `? ^ 0 = ?`, + `0 ^ ? = ?`, + + `0 &^ 0 = 0`, + `0xf &^ 1 = 0xe`, + `1 &^ 0xf = 0`, + // etc. + + // shifts + `0 << 0 = 0`, + `1 << 10 = 1024`, + `0 >> 0 = 0`, + `1024 >> 10 == 1`, + `? << 0 == ?`, + `? >> 10 == ?`, + // etc. + + // comparisons + `false == false = true`, + `false == true = false`, + `true == false = false`, + `true == true = true`, + + `false != false = false`, + `false != true = true`, + `true != false = true`, + `true != true = false`, + + `"foo" == "bar" = false`, + `"foo" != "bar" = true`, + `"foo" < "bar" = false`, + `"foo" <= "bar" = false`, + `"foo" > "bar" = true`, + `"foo" >= "bar" = true`, + + `0 == 0 = true`, + `0 != 0 = false`, + `0 < 10 = true`, + `10 <= 10 = true`, + `0 > 10 = false`, + `10 >= 10 = true`, + + `1/123456789 == 1/123456789 == true`, + `1/123456789 != 1/123456789 == false`, + `1/123456789 < 1/123456788 == true`, + `1/123456788 <= 1/123456789 == false`, + `0.11 > 0.11 = false`, + `0.11 >= 0.11 = true`, + + `? == 0 = false`, + `? != 0 = false`, + `? < 10 = false`, + `? <= 10 = false`, + `? > 10 = false`, + `? >= 10 = false`, + + `0 == ? = false`, + `0 != ? = false`, + `0 < ? = false`, + `10 <= ? = false`, + `0 > ? = false`, + `10 >= ? = false`, + + // etc. +} + +func TestOps(t *testing.T) { + for _, test := range opTests { + a := strings.Split(test, " ") + i := 0 // operator index + + var x, x0 Value + switch len(a) { + case 4: + // unary operation + case 5: + // binary operation + x, x0 = val(a[0]), val(a[0]) + i = 1 + default: + t.Errorf("invalid test case: %s", test) + continue + } + + op, ok := optab[a[i]] + if !ok { + panic("missing optab entry for " + a[i]) + } + + y, y0 := val(a[i+1]), val(a[i+1]) + + got := doOp(x, op, y) + want := val(a[i+3]) + if !eql(got, want) { + t.Errorf("%s: got %s; want %s", test, got, want) + continue + } + + if x0 != nil && !eql(x, x0) { + t.Errorf("%s: x changed to %s", test, x) + continue + } + + if !eql(y, y0) { + t.Errorf("%s: y changed to %s", test, y) + continue + } + } +} + +func eql(x, y Value) bool { + _, ux := x.(unknownVal) + _, uy := y.(unknownVal) + if ux || uy { + return ux == uy + } + return Compare(x, token.EQL, y) +} + +// ---------------------------------------------------------------------------- +// String tests + +var xxx = strings.Repeat("x", 68) +var issue14262 = `"بموجب الشروط التالية نسب المصنف — يجب عليك أن تنسب العمل بالطريقة التي تحددها المؤلف أو المرخص (ولكن ليس بأي حال من الأحوال أن توحي وتقترح بتحول أو استخدامك للعمل). المشاركة على قدم المساواة — إذا كنت يعدل ، والتغيير ، أو الاستفادة من هذا العمل ، قد ينتج عن توزيع العمل إلا في ظل تشابه او تطابق فى واحد لهذا الترخيص."` + +var stringTests = []struct { + input, short, exact string +}{ + // Unknown + {"", "unknown", "unknown"}, + {"0x", "unknown", "unknown"}, + {"'", "unknown", "unknown"}, + {"1f0", "unknown", "unknown"}, + {"unknown", "unknown", "unknown"}, + + // Bool + {"true", "true", "true"}, + {"false", "false", "false"}, + + // String + {`""`, `""`, `""`}, + {`"foo"`, `"foo"`, `"foo"`}, + {`"` + xxx + `xx"`, `"` + xxx + `xx"`, `"` + xxx + `xx"`}, + {`"` + xxx + `xxx"`, `"` + xxx + `...`, `"` + xxx + `xxx"`}, + {`"` + xxx + xxx + `xxx"`, `"` + xxx + `...`, `"` + xxx + xxx + `xxx"`}, + {issue14262, `"بموجب الشروط التالية نسب المصنف — يجب عليك أن تنسب العمل بالطريقة ال...`, issue14262}, + + // Int + {"0", "0", "0"}, + {"-1", "-1", "-1"}, + {"12345", "12345", "12345"}, + {"-12345678901234567890", "-12345678901234567890", "-12345678901234567890"}, + {"12345678901234567890", "12345678901234567890", "12345678901234567890"}, + + // Float + {"0.", "0", "0"}, + {"-0.0", "0", "0"}, + {"10.0", "10", "10"}, + {"2.1", "2.1", "21/10"}, + {"-2.1", "-2.1", "-21/10"}, + {"1e9999", "1e+9999", "0x.f8d4a9da224650a8cb2959e10d985ad92adbd44c62917e608b1f24c0e1b76b6f61edffeb15c135a4b601637315f7662f325f82325422b244286a07663c9415d2p+33216"}, + {"1e-9999", "1e-9999", "0x.83b01ba6d8c0425eec1b21e96f7742d63c2653ed0a024cf8a2f9686df578d7b07d7a83d84df6a2ec70a921d1f6cd5574893a7eda4d28ee719e13a5dce2700759p-33215"}, + {"2.71828182845904523536028747135266249775724709369995957496696763", "2.71828", "271828182845904523536028747135266249775724709369995957496696763/100000000000000000000000000000000000000000000000000000000000000"}, + {"0e9999999999", "0", "0"}, // issue #16176 + {"-6e-1886451601", "0", "0"}, // issue #20228 + + // Complex + {"0i", "(0 + 0i)", "(0 + 0i)"}, + {"-0i", "(0 + 0i)", "(0 + 0i)"}, + {"10i", "(0 + 10i)", "(0 + 10i)"}, + {"-10i", "(0 + -10i)", "(0 + -10i)"}, + {"1e9999i", "(0 + 1e+9999i)", "(0 + 0x.f8d4a9da224650a8cb2959e10d985ad92adbd44c62917e608b1f24c0e1b76b6f61edffeb15c135a4b601637315f7662f325f82325422b244286a07663c9415d2p+33216i)"}, +} + +func TestString(t *testing.T) { + for _, test := range stringTests { + x := val(test.input) + if got := x.String(); got != test.short { + t.Errorf("%s: got %q; want %q as short string", test.input, got, test.short) + } + if got := x.ExactString(); got != test.exact { + t.Errorf("%s: got %q; want %q as exact string", test.input, got, test.exact) + } + } +} + +// ---------------------------------------------------------------------------- +// Support functions + +func val(lit string) Value { + if len(lit) == 0 { + return MakeUnknown() + } + + switch lit { + case "?": + return MakeUnknown() + case "true": + return MakeBool(true) + case "false": + return MakeBool(false) + } + + if i := strings.IndexByte(lit, '/'); i >= 0 { + // assume fraction + a := MakeFromLiteral(lit[:i], token.INT, 0) + b := MakeFromLiteral(lit[i+1:], token.INT, 0) + return BinaryOp(a, token.QUO, b) + } + + tok := token.INT + switch first, last := lit[0], lit[len(lit)-1]; { + case first == '"' || first == '`': + tok = token.STRING + lit = strings.ReplaceAll(lit, "_", " ") + case first == '\'': + tok = token.CHAR + case last == 'i': + tok = token.IMAG + default: + if !strings.HasPrefix(lit, "0x") && strings.ContainsAny(lit, "./Ee") { + tok = token.FLOAT + } + } + + return MakeFromLiteral(lit, tok, 0) +} + +var optab = map[string]token.Token{ + "!": token.NOT, + + "+": token.ADD, + "-": token.SUB, + "*": token.MUL, + "/": token.QUO, + "%": token.REM, + + "<<": token.SHL, + ">>": token.SHR, + + "&": token.AND, + "|": token.OR, + "^": token.XOR, + "&^": token.AND_NOT, + + "==": token.EQL, + "!=": token.NEQ, + "<": token.LSS, + "<=": token.LEQ, + ">": token.GTR, + ">=": token.GEQ, +} + +func panicHandler(v *Value) { + switch p := recover().(type) { + case nil: + // nothing to do + case string: + *v = MakeString(p) + case error: + *v = MakeString(p.Error()) + default: + panic(p) + } +} + +func doOp(x Value, op token.Token, y Value) (z Value) { + defer panicHandler(&z) + + if x == nil { + return UnaryOp(op, y, 0) + } + + switch op { + case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ: + return MakeBool(Compare(x, op, y)) + case token.SHL, token.SHR: + s, _ := Int64Val(y) + return Shift(x, op, uint(s)) + default: + return BinaryOp(x, op, y) + } +} + +// ---------------------------------------------------------------------------- +// Other tests + +var fracTests = []string{ + "0", + "1", + "-1", + "1.2", + "-0.991", + "2.718281828", + "3.14159265358979323e-10", + "1e100", + "1e1000", +} + +func TestFractions(t *testing.T) { + for _, test := range fracTests { + x := val(test) + // We don't check the actual numerator and denominator because they + // are unlikely to be 100% correct due to floatVal rounding errors. + // Instead, we compute the fraction again and compare the rounded + // result. + q := BinaryOp(Num(x), token.QUO, Denom(x)) + got := q.String() + want := x.String() + if got != want { + t.Errorf("%s: got quotient %s, want %s", x, got, want) + } + } +} + +var bytesTests = []string{ + "0", + "1", + "123456789", + "123456789012345678901234567890123456789012345678901234567890", +} + +func TestBytes(t *testing.T) { + for _, test := range bytesTests { + x := val(test) + bytes := Bytes(x) + + // special case 0 + if Sign(x) == 0 && len(bytes) != 0 { + t.Errorf("%s: got %v; want empty byte slice", test, bytes) + } + + if n := len(bytes); n > 0 && bytes[n-1] == 0 { + t.Errorf("%s: got %v; want no leading 0 byte", test, bytes) + } + + if got := MakeFromBytes(bytes); !eql(got, x) { + t.Errorf("%s: got %s; want %s (bytes = %v)", test, got, x, bytes) + } + } +} + +func TestUnknown(t *testing.T) { + u := MakeUnknown() + var values = []Value{ + u, + MakeBool(false), // token.ADD ok below, operation is never considered + MakeString(""), + MakeInt64(1), + MakeFromLiteral("''", token.CHAR, 0), + MakeFromLiteral("-1234567890123456789012345678901234567890", token.INT, 0), + MakeFloat64(1.2), + MakeImag(MakeFloat64(1.2)), + } + for _, val := range values { + x, y := val, u + for i := range [2]int{} { + if i == 1 { + x, y = y, x + } + if got := BinaryOp(x, token.ADD, y); got.Kind() != Unknown { + t.Errorf("%s + %s: got %s; want %s", x, y, got, u) + } + if got := Compare(x, token.EQL, y); got { + t.Errorf("%s == %s: got true; want false", x, y) + } + } + } +} + +func TestMakeFloat64(t *testing.T) { + var zero float64 + for _, arg := range []float64{ + -math.MaxFloat32, + -10, + -0.5, + -zero, + zero, + 1, + 10, + 123456789.87654321e-23, + 1e10, + math.MaxFloat64, + } { + val := MakeFloat64(arg) + if val.Kind() != Float { + t.Errorf("%v: got kind = %d; want %d", arg, val.Kind(), Float) + } + + // -0.0 is mapped to 0.0 + got, exact := Float64Val(val) + if !exact || math.Float64bits(got) != math.Float64bits(arg+0) { + t.Errorf("%v: got %v (exact = %v)", arg, got, exact) + } + } + + // infinity + for sign := range []int{-1, 1} { + arg := math.Inf(sign) + val := MakeFloat64(arg) + if val.Kind() != Unknown { + t.Errorf("%v: got kind = %d; want %d", arg, val.Kind(), Unknown) + } + } +} + +type makeTestCase struct { + kind Kind + arg, want interface{} +} + +func dup(k Kind, x interface{}) makeTestCase { return makeTestCase{k, x, x} } + +func TestMake(t *testing.T) { + for _, test := range []makeTestCase{ + {Bool, false, false}, + {String, "hello", "hello"}, + + {Int, int64(1), int64(1)}, + {Int, big.NewInt(10), int64(10)}, + {Int, new(big.Int).Lsh(big.NewInt(1), 62), int64(1 << 62)}, + dup(Int, new(big.Int).Lsh(big.NewInt(1), 63)), + + {Float, big.NewFloat(0), floatVal0.val}, + dup(Float, big.NewFloat(2.0)), + dup(Float, big.NewRat(1, 3)), + } { + val := Make(test.arg) + got := Val(val) + if val.Kind() != test.kind || got != test.want { + t.Errorf("got %v (%T, kind = %d); want %v (%T, kind = %d)", + got, got, val.Kind(), test.want, test.want, test.kind) + } + } +} + +func BenchmarkStringAdd(b *testing.B) { + for size := 1; size <= 65536; size *= 4 { + b.Run(fmt.Sprint(size), func(b *testing.B) { + b.ReportAllocs() + n := int64(0) + for i := 0; i < b.N; i++ { + x := MakeString(strings.Repeat("x", 100)) + y := x + for j := 0; j < size-1; j++ { + y = BinaryOp(y, token.ADD, x) + } + n += int64(len(StringVal(y))) + } + if n != int64(b.N)*int64(size)*100 { + b.Fatalf("bad string %d != %d", n, int64(b.N)*int64(size)*100) + } + }) + } +} diff --git a/src/go/doc/Makefile b/src/go/doc/Makefile new file mode 100644 index 0000000..ca4948f --- /dev/null +++ b/src/go/doc/Makefile @@ -0,0 +1,7 @@ +# Copyright 2009 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. + +# Script to test heading detection heuristic +headscan: headscan.go + go build headscan.go diff --git a/src/go/doc/comment.go b/src/go/doc/comment.go new file mode 100644 index 0000000..92131a3 --- /dev/null +++ b/src/go/doc/comment.go @@ -0,0 +1,513 @@ +// Copyright 2009 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. + +// Godoc comment extraction and comment -> HTML formatting. + +package doc + +import ( + "bytes" + "internal/lazyregexp" + "io" + "strings" + "text/template" // for HTMLEscape + "unicode" + "unicode/utf8" +) + +const ( + ldquo = "“" + rdquo = "”" + ulquo = "“" + urquo = "”" +) + +var ( + htmlQuoteReplacer = strings.NewReplacer(ulquo, ldquo, urquo, rdquo) + unicodeQuoteReplacer = strings.NewReplacer("``", ulquo, "''", urquo) +) + +// Escape comment text for HTML. If nice is set, +// also turn `` into “ and '' into ”. +func commentEscape(w io.Writer, text string, nice bool) { + if nice { + // In the first pass, we convert `` and '' into their unicode equivalents. + // This prevents them from being escaped in HTMLEscape. + text = convertQuotes(text) + var buf bytes.Buffer + template.HTMLEscape(&buf, []byte(text)) + // Now we convert the unicode quotes to their HTML escaped entities to maintain old behavior. + // We need to use a temp buffer to read the string back and do the conversion, + // otherwise HTMLEscape will escape & to & + htmlQuoteReplacer.WriteString(w, buf.String()) + return + } + template.HTMLEscape(w, []byte(text)) +} + +func convertQuotes(text string) string { + return unicodeQuoteReplacer.Replace(text) +} + +const ( + // Regexp for Go identifiers + identRx = `[\pL_][\pL_0-9]*` + + // Regexp for URLs + // Match parens, and check later for balance - see #5043, #22285 + // Match .,:;?! within path, but not at end - see #18139, #16565 + // This excludes some rare yet valid urls ending in common punctuation + // in order to allow sentences ending in URLs. + + // protocol (required) e.g. http + protoPart = `(https?|ftp|file|gopher|mailto|nntp)` + // host (required) e.g. www.example.com or [::1]:8080 + hostPart = `([a-zA-Z0-9_@\-.\[\]:]+)` + // path+query+fragment (optional) e.g. /path/index.html?q=foo#bar + pathPart = `([.,:;?!]*[a-zA-Z0-9$'()*+&#=@~_/\-\[\]%])*` + + urlRx = protoPart + `://` + hostPart + pathPart +) + +var matchRx = lazyregexp.New(`(` + urlRx + `)|(` + identRx + `)`) + +var ( + html_a = []byte(``) + html_enda = []byte("") + html_i = []byte("") + html_endi = []byte("") + html_p = []byte("

\n") + html_endp = []byte("

\n") + html_pre = []byte("
")
+	html_endpre = []byte("
\n") + html_h = []byte(`

`) + html_endh = []byte("

\n") +) + +// Emphasize and escape a line of text for HTML. URLs are converted into links; +// if the URL also appears in the words map, the link is taken from the map (if +// the corresponding map value is the empty string, the URL is not converted +// into a link). Go identifiers that appear in the words map are italicized; if +// the corresponding map value is not the empty string, it is considered a URL +// and the word is converted into a link. If nice is set, the remaining text's +// appearance is improved where it makes sense (e.g., `` is turned into “ +// and '' into ”). +func emphasize(w io.Writer, line string, words map[string]string, nice bool) { + for { + m := matchRx.FindStringSubmatchIndex(line) + if m == nil { + break + } + // m >= 6 (two parenthesized sub-regexps in matchRx, 1st one is urlRx) + + // write text before match + commentEscape(w, line[0:m[0]], nice) + + // adjust match for URLs + match := line[m[0]:m[1]] + if strings.Contains(match, "://") { + m0, m1 := m[0], m[1] + for _, s := range []string{"()", "{}", "[]"} { + open, close := s[:1], s[1:] // E.g., "(" and ")" + // require opening parentheses before closing parentheses (#22285) + if i := strings.Index(match, close); i >= 0 && i < strings.Index(match, open) { + m1 = m0 + i + match = line[m0:m1] + } + // require balanced pairs of parentheses (#5043) + for i := 0; strings.Count(match, open) != strings.Count(match, close) && i < 10; i++ { + m1 = strings.LastIndexAny(line[:m1], s) + match = line[m0:m1] + } + } + if m1 != m[1] { + // redo matching with shortened line for correct indices + m = matchRx.FindStringSubmatchIndex(line[:m[0]+len(match)]) + } + } + + // analyze match + url := "" + italics := false + if words != nil { + url, italics = words[match] + } + if m[2] >= 0 { + // match against first parenthesized sub-regexp; must be match against urlRx + if !italics { + // no alternative URL in words list, use match instead + url = match + } + italics = false // don't italicize URLs + } + + // write match + if len(url) > 0 { + w.Write(html_a) + template.HTMLEscape(w, []byte(url)) + w.Write(html_aq) + } + if italics { + w.Write(html_i) + } + commentEscape(w, match, nice) + if italics { + w.Write(html_endi) + } + if len(url) > 0 { + w.Write(html_enda) + } + + // advance + line = line[m[1]:] + } + commentEscape(w, line, nice) +} + +func indentLen(s string) int { + i := 0 + for i < len(s) && (s[i] == ' ' || s[i] == '\t') { + i++ + } + return i +} + +func isBlank(s string) bool { + return len(s) == 0 || (len(s) == 1 && s[0] == '\n') +} + +func commonPrefix(a, b string) string { + i := 0 + for i < len(a) && i < len(b) && a[i] == b[i] { + i++ + } + return a[0:i] +} + +func unindent(block []string) { + if len(block) == 0 { + return + } + + // compute maximum common white prefix + prefix := block[0][0:indentLen(block[0])] + for _, line := range block { + if !isBlank(line) { + prefix = commonPrefix(prefix, line[0:indentLen(line)]) + } + } + n := len(prefix) + + // remove + for i, line := range block { + if !isBlank(line) { + block[i] = line[n:] + } + } +} + +// heading returns the trimmed line if it passes as a section heading; +// otherwise it returns the empty string. +func heading(line string) string { + line = strings.TrimSpace(line) + if len(line) == 0 { + return "" + } + + // a heading must start with an uppercase letter + r, _ := utf8.DecodeRuneInString(line) + if !unicode.IsLetter(r) || !unicode.IsUpper(r) { + return "" + } + + // it must end in a letter or digit: + r, _ = utf8.DecodeLastRuneInString(line) + if !unicode.IsLetter(r) && !unicode.IsDigit(r) { + return "" + } + + // exclude lines with illegal characters. we allow "()," + if strings.ContainsAny(line, ";:!?+*/=[]{}_^°&§~%#@<\">\\") { + return "" + } + + // allow "'" for possessive "'s" only + for b := line; ; { + i := strings.IndexRune(b, '\'') + if i < 0 { + break + } + if i+1 >= len(b) || b[i+1] != 's' || (i+2 < len(b) && b[i+2] != ' ') { + return "" // not followed by "s " + } + b = b[i+2:] + } + + // allow "." when followed by non-space + for b := line; ; { + i := strings.IndexRune(b, '.') + if i < 0 { + break + } + if i+1 >= len(b) || b[i+1] == ' ' { + return "" // not followed by non-space + } + b = b[i+1:] + } + + return line +} + +type op int + +const ( + opPara op = iota + opHead + opPre +) + +type block struct { + op op + lines []string +} + +var nonAlphaNumRx = lazyregexp.New(`[^a-zA-Z0-9]`) + +func anchorID(line string) string { + // Add a "hdr-" prefix to avoid conflicting with IDs used for package symbols. + return "hdr-" + nonAlphaNumRx.ReplaceAllString(line, "_") +} + +// ToHTML converts comment text to formatted HTML. +// The comment was prepared by DocReader, +// so it is known not to have leading, trailing blank lines +// nor to have trailing spaces at the end of lines. +// The comment markers have already been removed. +// +// Each span of unindented non-blank lines is converted into +// a single paragraph. There is one exception to the rule: a span that +// consists of a single line, is followed by another paragraph span, +// begins with a capital letter, and contains no punctuation +// other than parentheses and commas is formatted as a heading. +// +// A span of indented lines is converted into a
 block,
+// with the common indent prefix removed.
+//
+// URLs in the comment text are converted into links; if the URL also appears
+// in the words map, the link is taken from the map (if the corresponding map
+// value is the empty string, the URL is not converted into a link).
+//
+// A pair of (consecutive) backticks (`) is converted to a unicode left quote (“), and a pair of (consecutive)
+// single quotes (') is converted to a unicode right quote (”).
+//
+// Go identifiers that appear in the words map are italicized; if the corresponding
+// map value is not the empty string, it is considered a URL and the word is converted
+// into a link.
+func ToHTML(w io.Writer, text string, words map[string]string) {
+	for _, b := range blocks(text) {
+		switch b.op {
+		case opPara:
+			w.Write(html_p)
+			for _, line := range b.lines {
+				emphasize(w, line, words, true)
+			}
+			w.Write(html_endp)
+		case opHead:
+			w.Write(html_h)
+			id := ""
+			for _, line := range b.lines {
+				if id == "" {
+					id = anchorID(line)
+					w.Write([]byte(id))
+					w.Write(html_hq)
+				}
+				commentEscape(w, line, true)
+			}
+			if id == "" {
+				w.Write(html_hq)
+			}
+			w.Write(html_endh)
+		case opPre:
+			w.Write(html_pre)
+			for _, line := range b.lines {
+				emphasize(w, line, nil, false)
+			}
+			w.Write(html_endpre)
+		}
+	}
+}
+
+func blocks(text string) []block {
+	var (
+		out  []block
+		para []string
+
+		lastWasBlank   = false
+		lastWasHeading = false
+	)
+
+	close := func() {
+		if para != nil {
+			out = append(out, block{opPara, para})
+			para = nil
+		}
+	}
+
+	lines := strings.SplitAfter(text, "\n")
+	unindent(lines)
+	for i := 0; i < len(lines); {
+		line := lines[i]
+		if isBlank(line) {
+			// close paragraph
+			close()
+			i++
+			lastWasBlank = true
+			continue
+		}
+		if indentLen(line) > 0 {
+			// close paragraph
+			close()
+
+			// count indented or blank lines
+			j := i + 1
+			for j < len(lines) && (isBlank(lines[j]) || indentLen(lines[j]) > 0) {
+				j++
+			}
+			// but not trailing blank lines
+			for j > i && isBlank(lines[j-1]) {
+				j--
+			}
+			pre := lines[i:j]
+			i = j
+
+			unindent(pre)
+
+			// put those lines in a pre block
+			out = append(out, block{opPre, pre})
+			lastWasHeading = false
+			continue
+		}
+
+		if lastWasBlank && !lastWasHeading && i+2 < len(lines) &&
+			isBlank(lines[i+1]) && !isBlank(lines[i+2]) && indentLen(lines[i+2]) == 0 {
+			// current line is non-blank, surrounded by blank lines
+			// and the next non-blank line is not indented: this
+			// might be a heading.
+			if head := heading(line); head != "" {
+				close()
+				out = append(out, block{opHead, []string{head}})
+				i += 2
+				lastWasHeading = true
+				continue
+			}
+		}
+
+		// open paragraph
+		lastWasBlank = false
+		lastWasHeading = false
+		para = append(para, lines[i])
+		i++
+	}
+	close()
+
+	return out
+}
+
+// ToText prepares comment text for presentation in textual output.
+// It wraps paragraphs of text to width or fewer Unicode code points
+// and then prefixes each line with the indent. In preformatted sections
+// (such as program text), it prefixes each non-blank line with preIndent.
+//
+// A pair of (consecutive) backticks (`) is converted to a unicode left quote (“), and a pair of (consecutive)
+// single quotes (') is converted to a unicode right quote (”).
+func ToText(w io.Writer, text string, indent, preIndent string, width int) {
+	l := lineWrapper{
+		out:    w,
+		width:  width,
+		indent: indent,
+	}
+	for _, b := range blocks(text) {
+		switch b.op {
+		case opPara:
+			// l.write will add leading newline if required
+			for _, line := range b.lines {
+				line = convertQuotes(line)
+				l.write(line)
+			}
+			l.flush()
+		case opHead:
+			w.Write(nl)
+			for _, line := range b.lines {
+				line = convertQuotes(line)
+				l.write(line + "\n")
+			}
+			l.flush()
+		case opPre:
+			w.Write(nl)
+			for _, line := range b.lines {
+				if isBlank(line) {
+					w.Write([]byte("\n"))
+				} else {
+					w.Write([]byte(preIndent))
+					w.Write([]byte(line))
+				}
+			}
+		}
+	}
+}
+
+type lineWrapper struct {
+	out       io.Writer
+	printed   bool
+	width     int
+	indent    string
+	n         int
+	pendSpace int
+}
+
+var nl = []byte("\n")
+var space = []byte(" ")
+var prefix = []byte("// ")
+
+func (l *lineWrapper) write(text string) {
+	if l.n == 0 && l.printed {
+		l.out.Write(nl) // blank line before new paragraph
+	}
+	l.printed = true
+
+	needsPrefix := false
+	isComment := strings.HasPrefix(text, "//")
+	for _, f := range strings.Fields(text) {
+		w := utf8.RuneCountInString(f)
+		// wrap if line is too long
+		if l.n > 0 && l.n+l.pendSpace+w > l.width {
+			l.out.Write(nl)
+			l.n = 0
+			l.pendSpace = 0
+			needsPrefix = isComment && !strings.HasPrefix(f, "//")
+		}
+		if l.n == 0 {
+			l.out.Write([]byte(l.indent))
+		}
+		if needsPrefix {
+			l.out.Write(prefix)
+			needsPrefix = false
+		}
+		l.out.Write(space[:l.pendSpace])
+		l.out.Write([]byte(f))
+		l.n += l.pendSpace + w
+		l.pendSpace = 1
+	}
+}
+
+func (l *lineWrapper) flush() {
+	if l.n == 0 {
+		return
+	}
+	l.out.Write(nl)
+	l.pendSpace = 0
+	l.n = 0
+}
diff --git a/src/go/doc/comment_test.go b/src/go/doc/comment_test.go
new file mode 100644
index 0000000..6d1b209
--- /dev/null
+++ b/src/go/doc/comment_test.go
@@ -0,0 +1,247 @@
+// Copyright 2011 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 doc
+
+import (
+	"bytes"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+var headingTests = []struct {
+	line string
+	ok   bool
+}{
+	{"Section", true},
+	{"A typical usage", true},
+	{"ΔΛΞ is Greek", true},
+	{"Foo 42", true},
+	{"", false},
+	{"section", false},
+	{"A typical usage:", false},
+	{"This code:", false},
+	{"δ is Greek", false},
+	{"Foo §", false},
+	{"Fermat's Last Sentence", true},
+	{"Fermat's", true},
+	{"'sX", false},
+	{"Ted 'Too' Bar", false},
+	{"Use n+m", false},
+	{"Scanning:", false},
+	{"N:M", false},
+}
+
+func TestIsHeading(t *testing.T) {
+	for _, tt := range headingTests {
+		if h := heading(tt.line); (len(h) > 0) != tt.ok {
+			t.Errorf("isHeading(%q) = %v, want %v", tt.line, h, tt.ok)
+		}
+	}
+}
+
+var blocksTests = []struct {
+	in   string
+	out  []block
+	text string
+}{
+	{
+		in: `Para 1.
+Para 1 line 2.
+
+Para 2.
+
+Section
+
+Para 3.
+
+	pre
+	pre1
+
+Para 4.
+
+	pre
+	pre1
+
+	pre2
+
+Para 5.
+
+
+	pre
+
+
+	pre1
+	pre2
+
+Para 6.
+	pre
+	pre2
+`,
+		out: []block{
+			{opPara, []string{"Para 1.\n", "Para 1 line 2.\n"}},
+			{opPara, []string{"Para 2.\n"}},
+			{opHead, []string{"Section"}},
+			{opPara, []string{"Para 3.\n"}},
+			{opPre, []string{"pre\n", "pre1\n"}},
+			{opPara, []string{"Para 4.\n"}},
+			{opPre, []string{"pre\n", "pre1\n", "\n", "pre2\n"}},
+			{opPara, []string{"Para 5.\n"}},
+			{opPre, []string{"pre\n", "\n", "\n", "pre1\n", "pre2\n"}},
+			{opPara, []string{"Para 6.\n"}},
+			{opPre, []string{"pre\n", "pre2\n"}},
+		},
+		text: `.   Para 1. Para 1 line 2.
+
+.   Para 2.
+
+
+.   Section
+
+.   Para 3.
+
+$	pre
+$	pre1
+
+.   Para 4.
+
+$	pre
+$	pre1
+
+$	pre2
+
+.   Para 5.
+
+$	pre
+
+
+$	pre1
+$	pre2
+
+.   Para 6.
+
+$	pre
+$	pre2
+`,
+	},
+	{
+		in: "Para.\n\tshould not be ``escaped''",
+		out: []block{
+			{opPara, []string{"Para.\n"}},
+			{opPre, []string{"should not be ``escaped''"}},
+		},
+		text: ".   Para.\n\n$	should not be ``escaped''",
+	},
+	{
+		in: "// A very long line of 46 char for line wrapping.",
+		out: []block{
+			{opPara, []string{"// A very long line of 46 char for line wrapping."}},
+		},
+		text: `.   // A very long line of 46 char for line
+.   // wrapping.
+`,
+	},
+	{
+		in: `/* A very long line of 46 char for line wrapping.
+A very long line of 46 char for line wrapping. */`,
+		out: []block{
+			{opPara, []string{"/* A very long line of 46 char for line wrapping.\n", "A very long line of 46 char for line wrapping. */"}},
+		},
+		text: `.   /* A very long line of 46 char for line
+.   wrapping. A very long line of 46 char
+.   for line wrapping. */
+`,
+	},
+	{
+		in: `A line of 36 char for line wrapping.
+//Another line starting with //`,
+		out: []block{
+			{opPara, []string{"A line of 36 char for line wrapping.\n",
+				"//Another line starting with //"}},
+		},
+		text: `.   A line of 36 char for line wrapping.
+.   //Another line starting with //
+`,
+	},
+}
+
+func TestBlocks(t *testing.T) {
+	for i, tt := range blocksTests {
+		b := blocks(tt.in)
+		if !reflect.DeepEqual(b, tt.out) {
+			t.Errorf("#%d: mismatch\nhave: %v\nwant: %v", i, b, tt.out)
+		}
+	}
+}
+
+func TestToText(t *testing.T) {
+	var buf bytes.Buffer
+	for i, tt := range blocksTests {
+		ToText(&buf, tt.in, ".   ", "$\t", 40)
+		if have := buf.String(); have != tt.text {
+			t.Errorf("#%d: mismatch\nhave: %s\nwant: %s\nhave vs want:\n%q\n%q", i, have, tt.text, have, tt.text)
+		}
+		buf.Reset()
+	}
+}
+
+var emphasizeTests = []struct {
+	in, out string
+}{
+	{"", ""},
+	{"http://[::1]:8080/foo.txt", `http://[::1]:8080/foo.txt`},
+	{"before (https://www.google.com) after", `before (https://www.google.com) after`},
+	{"before https://www.google.com:30/x/y/z:b::c. After", `before https://www.google.com:30/x/y/z:b::c. After`},
+	{"http://www.google.com/path/:;!-/?query=%34b#093124", `http://www.google.com/path/:;!-/?query=%34b#093124`},
+	{"http://www.google.com/path/:;!-/?query=%34bar#093124", `http://www.google.com/path/:;!-/?query=%34bar#093124`},
+	{"http://www.google.com/index.html! After", `http://www.google.com/index.html! After`},
+	{"http://www.google.com/", `http://www.google.com/`},
+	{"https://www.google.com/", `https://www.google.com/`},
+	{"http://www.google.com/path.", `http://www.google.com/path.`},
+	{"http://en.wikipedia.org/wiki/Camellia_(cipher)", `http://en.wikipedia.org/wiki/Camellia_(cipher)`},
+	{"(http://www.google.com/)", `(http://www.google.com/)`},
+	{"http://gmail.com)", `http://gmail.com)`},
+	{"((http://gmail.com))", `((http://gmail.com))`},
+	{"http://gmail.com ((http://gmail.com)) ()", `http://gmail.com ((http://gmail.com)) ()`},
+	{"Foo bar http://example.com/ quux!", `Foo bar http://example.com/ quux!`},
+	{"Hello http://example.com/%2f/ /world.", `Hello http://example.com/%2f/ /world.`},
+	{"Lorem http: ipsum //host/path", "Lorem http: ipsum //host/path"},
+	{"javascript://is/not/linked", "javascript://is/not/linked"},
+	{"http://foo", `http://foo`},
+	{"art by [[https://www.example.com/person/][Person Name]]", `art by [[https://www.example.com/person/][Person Name]]`},
+	{"please visit (http://golang.org/)", `please visit (http://golang.org/)`},
+	{"please visit http://golang.org/hello())", `please visit http://golang.org/hello())`},
+	{"http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD", `http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD`},
+	{"https://foo.bar/bal/x(])", `https://foo.bar/bal/x(])`}, // inner ] causes (]) to be cut off from URL
+	{"foo [ http://bar(])", `foo [ http://bar(])`},                      // outer [ causes ]) to be cut off from URL
+}
+
+func TestEmphasize(t *testing.T) {
+	for i, tt := range emphasizeTests {
+		var buf bytes.Buffer
+		emphasize(&buf, tt.in, nil, true)
+		out := buf.String()
+		if out != tt.out {
+			t.Errorf("#%d: mismatch\nhave: %v\nwant: %v", i, out, tt.out)
+		}
+	}
+}
+
+func TestCommentEscape(t *testing.T) {
+	commentTests := []struct {
+		in, out string
+	}{
+		{"typically invoked as ``go tool asm'',", "typically invoked as " + ldquo + "go tool asm" + rdquo + ","},
+		{"For more detail, run ``go help test'' and ``go help testflag''", "For more detail, run " + ldquo + "go help test" + rdquo + " and " + ldquo + "go help testflag" + rdquo},
+	}
+	for i, tt := range commentTests {
+		var buf strings.Builder
+		commentEscape(&buf, tt.in, true)
+		out := buf.String()
+		if out != tt.out {
+			t.Errorf("#%d: mismatch\nhave: %q\nwant: %q", i, out, tt.out)
+		}
+	}
+}
diff --git a/src/go/doc/doc.go b/src/go/doc/doc.go
new file mode 100644
index 0000000..79d3899
--- /dev/null
+++ b/src/go/doc/doc.go
@@ -0,0 +1,222 @@
+// Copyright 2009 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 doc extracts source code documentation from a Go AST.
+package doc
+
+import (
+	"fmt"
+	"go/ast"
+	"go/token"
+	"strings"
+)
+
+// Package is the documentation for an entire package.
+type Package struct {
+	Doc        string
+	Name       string
+	ImportPath string
+	Imports    []string
+	Filenames  []string
+	Notes      map[string][]*Note
+
+	// Deprecated: For backward compatibility Bugs is still populated,
+	// but all new code should use Notes instead.
+	Bugs []string
+
+	// declarations
+	Consts []*Value
+	Types  []*Type
+	Vars   []*Value
+	Funcs  []*Func
+
+	// Examples is a sorted list of examples associated with
+	// the package. Examples are extracted from _test.go files
+	// provided to NewFromFiles.
+	Examples []*Example
+}
+
+// Value is the documentation for a (possibly grouped) var or const declaration.
+type Value struct {
+	Doc   string
+	Names []string // var or const names in declaration order
+	Decl  *ast.GenDecl
+
+	order int
+}
+
+// Type is the documentation for a type declaration.
+type Type struct {
+	Doc  string
+	Name string
+	Decl *ast.GenDecl
+
+	// associated declarations
+	Consts  []*Value // sorted list of constants of (mostly) this type
+	Vars    []*Value // sorted list of variables of (mostly) this type
+	Funcs   []*Func  // sorted list of functions returning this type
+	Methods []*Func  // sorted list of methods (including embedded ones) of this type
+
+	// Examples is a sorted list of examples associated with
+	// this type. Examples are extracted from _test.go files
+	// provided to NewFromFiles.
+	Examples []*Example
+}
+
+// Func is the documentation for a func declaration.
+type Func struct {
+	Doc  string
+	Name string
+	Decl *ast.FuncDecl
+
+	// methods
+	// (for functions, these fields have the respective zero value)
+	Recv  string // actual   receiver "T" or "*T"
+	Orig  string // original receiver "T" or "*T"
+	Level int    // embedding level; 0 means not embedded
+
+	// Examples is a sorted list of examples associated with this
+	// function or method. Examples are extracted from _test.go files
+	// provided to NewFromFiles.
+	Examples []*Example
+}
+
+// A Note represents a marked comment starting with "MARKER(uid): note body".
+// Any note with a marker of 2 or more upper case [A-Z] letters and a uid of
+// at least one character is recognized. The ":" following the uid is optional.
+// Notes are collected in the Package.Notes map indexed by the notes marker.
+type Note struct {
+	Pos, End token.Pos // position range of the comment containing the marker
+	UID      string    // uid found with the marker
+	Body     string    // note body text
+}
+
+// Mode values control the operation of New and NewFromFiles.
+type Mode int
+
+const (
+	// AllDecls says to extract documentation for all package-level
+	// declarations, not just exported ones.
+	AllDecls Mode = 1 << iota
+
+	// AllMethods says to show all embedded methods, not just the ones of
+	// invisible (unexported) anonymous fields.
+	AllMethods
+
+	// PreserveAST says to leave the AST unmodified. Originally, pieces of
+	// the AST such as function bodies were nil-ed out to save memory in
+	// godoc, but not all programs want that behavior.
+	PreserveAST
+)
+
+// New computes the package documentation for the given package AST.
+// New takes ownership of the AST pkg and may edit or overwrite it.
+// To have the Examples fields populated, use NewFromFiles and include
+// the package's _test.go files.
+//
+func New(pkg *ast.Package, importPath string, mode Mode) *Package {
+	var r reader
+	r.readPackage(pkg, mode)
+	r.computeMethodSets()
+	r.cleanupTypes()
+	return &Package{
+		Doc:        r.doc,
+		Name:       pkg.Name,
+		ImportPath: importPath,
+		Imports:    sortedKeys(r.imports),
+		Filenames:  r.filenames,
+		Notes:      r.notes,
+		Bugs:       noteBodies(r.notes["BUG"]),
+		Consts:     sortedValues(r.values, token.CONST),
+		Types:      sortedTypes(r.types, mode&AllMethods != 0),
+		Vars:       sortedValues(r.values, token.VAR),
+		Funcs:      sortedFuncs(r.funcs, true),
+	}
+}
+
+// NewFromFiles computes documentation for a package.
+//
+// The package is specified by a list of *ast.Files and corresponding
+// file set, which must not be nil.
+// NewFromFiles uses all provided files when computing documentation,
+// so it is the caller's responsibility to provide only the files that
+// match the desired build context. "go/build".Context.MatchFile can
+// be used for determining whether a file matches a build context with
+// the desired GOOS and GOARCH values, and other build constraints.
+// The import path of the package is specified by importPath.
+//
+// Examples found in _test.go files are associated with the corresponding
+// type, function, method, or the package, based on their name.
+// If the example has a suffix in its name, it is set in the
+// Example.Suffix field. Examples with malformed names are skipped.
+//
+// Optionally, a single extra argument of type Mode can be provided to
+// control low-level aspects of the documentation extraction behavior.
+//
+// NewFromFiles takes ownership of the AST files and may edit them,
+// unless the PreserveAST Mode bit is on.
+//
+func NewFromFiles(fset *token.FileSet, files []*ast.File, importPath string, opts ...interface{}) (*Package, error) {
+	// Check for invalid API usage.
+	if fset == nil {
+		panic(fmt.Errorf("doc.NewFromFiles: no token.FileSet provided (fset == nil)"))
+	}
+	var mode Mode
+	switch len(opts) { // There can only be 0 or 1 options, so a simple switch works for now.
+	case 0:
+		// Nothing to do.
+	case 1:
+		m, ok := opts[0].(Mode)
+		if !ok {
+			panic(fmt.Errorf("doc.NewFromFiles: option argument type must be doc.Mode"))
+		}
+		mode = m
+	default:
+		panic(fmt.Errorf("doc.NewFromFiles: there must not be more than 1 option argument"))
+	}
+
+	// Collect .go and _test.go files.
+	var (
+		goFiles     = make(map[string]*ast.File)
+		testGoFiles []*ast.File
+	)
+	for i := range files {
+		f := fset.File(files[i].Pos())
+		if f == nil {
+			return nil, fmt.Errorf("file files[%d] is not found in the provided file set", i)
+		}
+		switch name := f.Name(); {
+		case strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go"):
+			goFiles[name] = files[i]
+		case strings.HasSuffix(name, "_test.go"):
+			testGoFiles = append(testGoFiles, files[i])
+		default:
+			return nil, fmt.Errorf("file files[%d] filename %q does not have a .go extension", i, name)
+		}
+	}
+
+	// TODO(dmitshur,gri): A relatively high level call to ast.NewPackage with a simpleImporter
+	// ast.Importer implementation is made below. It might be possible to short-circuit and simplify.
+
+	// Compute package documentation.
+	pkg, _ := ast.NewPackage(fset, goFiles, simpleImporter, nil) // Ignore errors that can happen due to unresolved identifiers.
+	p := New(pkg, importPath, mode)
+	classifyExamples(p, Examples(testGoFiles...))
+	return p, nil
+}
+
+// simpleImporter returns a (dummy) package object named by the last path
+// component of the provided package path (as is the convention for packages).
+// This is sufficient to resolve package identifiers without doing an actual
+// import. It never returns an error.
+func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
+	pkg := imports[path]
+	if pkg == nil {
+		// note that strings.LastIndex returns -1 if there is no "/"
+		pkg = ast.NewObj(ast.Pkg, path[strings.LastIndex(path, "/")+1:])
+		pkg.Data = ast.NewScope(nil) // required by ast.NewPackage for dot-import
+		imports[path] = pkg
+	}
+	return pkg, nil
+}
diff --git a/src/go/doc/doc_test.go b/src/go/doc/doc_test.go
new file mode 100644
index 0000000..cbdca62
--- /dev/null
+++ b/src/go/doc/doc_test.go
@@ -0,0 +1,164 @@
+// Copyright 2012 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 doc
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"go/ast"
+	"go/parser"
+	"go/printer"
+	"go/token"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"testing"
+	"text/template"
+)
+
+var update = flag.Bool("update", false, "update golden (.out) files")
+var files = flag.String("files", "", "consider only Go test files matching this regular expression")
+
+const dataDir = "testdata"
+
+var templateTxt = readTemplate("template.txt")
+
+func readTemplate(filename string) *template.Template {
+	t := template.New(filename)
+	t.Funcs(template.FuncMap{
+		"node":     nodeFmt,
+		"synopsis": synopsisFmt,
+		"indent":   indentFmt,
+	})
+	return template.Must(t.ParseFiles(filepath.Join(dataDir, filename)))
+}
+
+func nodeFmt(node interface{}, fset *token.FileSet) string {
+	var buf bytes.Buffer
+	printer.Fprint(&buf, fset, node)
+	return strings.ReplaceAll(strings.TrimSpace(buf.String()), "\n", "\n\t")
+}
+
+func synopsisFmt(s string) string {
+	const n = 64
+	if len(s) > n {
+		// cut off excess text and go back to a word boundary
+		s = s[0:n]
+		if i := strings.LastIndexAny(s, "\t\n "); i >= 0 {
+			s = s[0:i]
+		}
+		s = strings.TrimSpace(s) + " ..."
+	}
+	return "// " + strings.ReplaceAll(s, "\n", " ")
+}
+
+func indentFmt(indent, s string) string {
+	end := ""
+	if strings.HasSuffix(s, "\n") {
+		end = "\n"
+		s = s[:len(s)-1]
+	}
+	return indent + strings.ReplaceAll(s, "\n", "\n"+indent) + end
+}
+
+func isGoFile(fi fs.FileInfo) bool {
+	name := fi.Name()
+	return !fi.IsDir() &&
+		len(name) > 0 && name[0] != '.' && // ignore .files
+		filepath.Ext(name) == ".go"
+}
+
+type bundle struct {
+	*Package
+	FSet *token.FileSet
+}
+
+func test(t *testing.T, mode Mode) {
+	// determine file filter
+	filter := isGoFile
+	if *files != "" {
+		rx, err := regexp.Compile(*files)
+		if err != nil {
+			t.Fatal(err)
+		}
+		filter = func(fi fs.FileInfo) bool {
+			return isGoFile(fi) && rx.MatchString(fi.Name())
+		}
+	}
+
+	// get packages
+	fset := token.NewFileSet()
+	pkgs, err := parser.ParseDir(fset, dataDir, filter, parser.ParseComments)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// test packages
+	for _, pkg := range pkgs {
+		importPath := dataDir + "/" + pkg.Name
+		var files []*ast.File
+		for _, f := range pkg.Files {
+			files = append(files, f)
+		}
+		doc, err := NewFromFiles(fset, files, importPath, mode)
+		if err != nil {
+			t.Error(err)
+			continue
+		}
+
+		// golden files always use / in filenames - canonicalize them
+		for i, filename := range doc.Filenames {
+			doc.Filenames[i] = filepath.ToSlash(filename)
+		}
+
+		// print documentation
+		var buf bytes.Buffer
+		if err := templateTxt.Execute(&buf, bundle{doc, fset}); err != nil {
+			t.Error(err)
+			continue
+		}
+		got := buf.Bytes()
+
+		// update golden file if necessary
+		golden := filepath.Join(dataDir, fmt.Sprintf("%s.%d.golden", pkg.Name, mode))
+		if *update {
+			err := os.WriteFile(golden, got, 0644)
+			if err != nil {
+				t.Error(err)
+			}
+			continue
+		}
+
+		// get golden file
+		want, err := os.ReadFile(golden)
+		if err != nil {
+			t.Error(err)
+			continue
+		}
+
+		// compare
+		if !bytes.Equal(got, want) {
+			t.Errorf("package %s\n\tgot:\n%s\n\twant:\n%s", pkg.Name, got, want)
+		}
+	}
+}
+
+func Test(t *testing.T) {
+	test(t, 0)
+	test(t, AllDecls)
+	test(t, AllMethods)
+}
+
+func TestAnchorID(t *testing.T) {
+	const in = "Important Things 2 Know & Stuff"
+	const want = "hdr-Important_Things_2_Know___Stuff"
+	got := anchorID(in)
+	if got != want {
+		t.Errorf("anchorID(%q) = %q; want %q", in, got, want)
+	}
+}
diff --git a/src/go/doc/example.go b/src/go/doc/example.go
new file mode 100644
index 0000000..125fd53
--- /dev/null
+++ b/src/go/doc/example.go
@@ -0,0 +1,547 @@
+// Copyright 2011 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.
+
+// Extract example functions from file ASTs.
+
+package doc
+
+import (
+	"go/ast"
+	"go/token"
+	"internal/lazyregexp"
+	"path"
+	"sort"
+	"strconv"
+	"strings"
+	"unicode"
+	"unicode/utf8"
+)
+
+// An Example represents an example function found in a test source file.
+type Example struct {
+	Name        string // name of the item being exemplified (including optional suffix)
+	Suffix      string // example suffix, without leading '_' (only populated by NewFromFiles)
+	Doc         string // example function doc string
+	Code        ast.Node
+	Play        *ast.File // a whole program version of the example
+	Comments    []*ast.CommentGroup
+	Output      string // expected output
+	Unordered   bool
+	EmptyOutput bool // expect empty output
+	Order       int  // original source code order
+}
+
+// Examples returns the examples found in testFiles, sorted by Name field.
+// The Order fields record the order in which the examples were encountered.
+// The Suffix field is not populated when Examples is called directly, it is
+// only populated by NewFromFiles for examples it finds in _test.go files.
+//
+// Playable Examples must be in a package whose name ends in "_test".
+// An Example is "playable" (the Play field is non-nil) in either of these
+// circumstances:
+//   - The example function is self-contained: the function references only
+//     identifiers from other packages (or predeclared identifiers, such as
+//     "int") and the test file does not include a dot import.
+//   - The entire test file is the example: the file contains exactly one
+//     example function, zero test or benchmark functions, and at least one
+//     top-level function, type, variable, or constant declaration other
+//     than the example function.
+func Examples(testFiles ...*ast.File) []*Example {
+	var list []*Example
+	for _, file := range testFiles {
+		hasTests := false // file contains tests or benchmarks
+		numDecl := 0      // number of non-import declarations in the file
+		var flist []*Example
+		for _, decl := range file.Decls {
+			if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT {
+				numDecl++
+				continue
+			}
+			f, ok := decl.(*ast.FuncDecl)
+			if !ok || f.Recv != nil {
+				continue
+			}
+			numDecl++
+			name := f.Name.Name
+			if isTest(name, "Test") || isTest(name, "Benchmark") {
+				hasTests = true
+				continue
+			}
+			if !isTest(name, "Example") {
+				continue
+			}
+			if params := f.Type.Params; len(params.List) != 0 {
+				continue // function has params; not a valid example
+			}
+			if f.Body == nil { // ast.File.Body nil dereference (see issue 28044)
+				continue
+			}
+			var doc string
+			if f.Doc != nil {
+				doc = f.Doc.Text()
+			}
+			output, unordered, hasOutput := exampleOutput(f.Body, file.Comments)
+			flist = append(flist, &Example{
+				Name:        name[len("Example"):],
+				Doc:         doc,
+				Code:        f.Body,
+				Play:        playExample(file, f),
+				Comments:    file.Comments,
+				Output:      output,
+				Unordered:   unordered,
+				EmptyOutput: output == "" && hasOutput,
+				Order:       len(flist),
+			})
+		}
+		if !hasTests && numDecl > 1 && len(flist) == 1 {
+			// If this file only has one example function, some
+			// other top-level declarations, and no tests or
+			// benchmarks, use the whole file as the example.
+			flist[0].Code = file
+			flist[0].Play = playExampleFile(file)
+		}
+		list = append(list, flist...)
+	}
+	// sort by name
+	sort.Slice(list, func(i, j int) bool {
+		return list[i].Name < list[j].Name
+	})
+	return list
+}
+
+var outputPrefix = lazyregexp.New(`(?i)^[[:space:]]*(unordered )?output:`)
+
+// Extracts the expected output and whether there was a valid output comment
+func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output string, unordered, ok bool) {
+	if _, last := lastComment(b, comments); last != nil {
+		// test that it begins with the correct prefix
+		text := last.Text()
+		if loc := outputPrefix.FindStringSubmatchIndex(text); loc != nil {
+			if loc[2] != -1 {
+				unordered = true
+			}
+			text = text[loc[1]:]
+			// Strip zero or more spaces followed by \n or a single space.
+			text = strings.TrimLeft(text, " ")
+			if len(text) > 0 && text[0] == '\n' {
+				text = text[1:]
+			}
+			return text, unordered, true
+		}
+	}
+	return "", false, false // no suitable comment found
+}
+
+// isTest tells whether name looks like a test, example, or benchmark.
+// It is a Test (say) if there is a character after Test that is not a
+// lower-case letter. (We don't want Testiness.)
+func isTest(name, prefix string) bool {
+	if !strings.HasPrefix(name, prefix) {
+		return false
+	}
+	if len(name) == len(prefix) { // "Test" is ok
+		return true
+	}
+	rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
+	return !unicode.IsLower(rune)
+}
+
+// playExample synthesizes a new *ast.File based on the provided
+// file with the provided function body as the body of main.
+func playExample(file *ast.File, f *ast.FuncDecl) *ast.File {
+	body := f.Body
+
+	if !strings.HasSuffix(file.Name.Name, "_test") {
+		// We don't support examples that are part of the
+		// greater package (yet).
+		return nil
+	}
+
+	// Collect top-level declarations in the file.
+	topDecls := make(map[*ast.Object]ast.Decl)
+	typMethods := make(map[string][]ast.Decl)
+
+	for _, decl := range file.Decls {
+		switch d := decl.(type) {
+		case *ast.FuncDecl:
+			if d.Recv == nil {
+				topDecls[d.Name.Obj] = d
+			} else {
+				if len(d.Recv.List) == 1 {
+					t := d.Recv.List[0].Type
+					tname, _ := baseTypeName(t)
+					typMethods[tname] = append(typMethods[tname], d)
+				}
+			}
+		case *ast.GenDecl:
+			for _, spec := range d.Specs {
+				switch s := spec.(type) {
+				case *ast.TypeSpec:
+					topDecls[s.Name.Obj] = d
+				case *ast.ValueSpec:
+					for _, name := range s.Names {
+						topDecls[name.Obj] = d
+					}
+				}
+			}
+		}
+	}
+
+	// Find unresolved identifiers and uses of top-level declarations.
+	unresolved := make(map[string]bool)
+	var depDecls []ast.Decl
+	hasDepDecls := make(map[ast.Decl]bool)
+
+	var inspectFunc func(ast.Node) bool
+	inspectFunc = func(n ast.Node) bool {
+		switch e := n.(type) {
+		case *ast.Ident:
+			if e.Obj == nil && e.Name != "_" {
+				unresolved[e.Name] = true
+			} else if d := topDecls[e.Obj]; d != nil {
+				if !hasDepDecls[d] {
+					hasDepDecls[d] = true
+					depDecls = append(depDecls, d)
+				}
+			}
+			return true
+		case *ast.SelectorExpr:
+			// For selector expressions, only inspect the left hand side.
+			// (For an expression like fmt.Println, only add "fmt" to the
+			// set of unresolved names, not "Println".)
+			ast.Inspect(e.X, inspectFunc)
+			return false
+		case *ast.KeyValueExpr:
+			// For key value expressions, only inspect the value
+			// as the key should be resolved by the type of the
+			// composite literal.
+			ast.Inspect(e.Value, inspectFunc)
+			return false
+		}
+		return true
+	}
+	ast.Inspect(body, inspectFunc)
+	for i := 0; i < len(depDecls); i++ {
+		switch d := depDecls[i].(type) {
+		case *ast.FuncDecl:
+			// Inspect types of parameters and results. See #28492.
+			if d.Type.Params != nil {
+				for _, p := range d.Type.Params.List {
+					ast.Inspect(p.Type, inspectFunc)
+				}
+			}
+			if d.Type.Results != nil {
+				for _, r := range d.Type.Results.List {
+					ast.Inspect(r.Type, inspectFunc)
+				}
+			}
+
+			ast.Inspect(d.Body, inspectFunc)
+		case *ast.GenDecl:
+			for _, spec := range d.Specs {
+				switch s := spec.(type) {
+				case *ast.TypeSpec:
+					ast.Inspect(s.Type, inspectFunc)
+
+					depDecls = append(depDecls, typMethods[s.Name.Name]...)
+				case *ast.ValueSpec:
+					if s.Type != nil {
+						ast.Inspect(s.Type, inspectFunc)
+					}
+					for _, val := range s.Values {
+						ast.Inspect(val, inspectFunc)
+					}
+				}
+			}
+		}
+	}
+
+	// Remove predeclared identifiers from unresolved list.
+	for n := range unresolved {
+		if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] {
+			delete(unresolved, n)
+		}
+	}
+
+	// Use unresolved identifiers to determine the imports used by this
+	// example. The heuristic assumes package names match base import
+	// paths for imports w/o renames (should be good enough most of the time).
+	namedImports := make(map[string]string) // [name]path
+	var blankImports []ast.Spec             // _ imports
+	for _, s := range file.Imports {
+		p, err := strconv.Unquote(s.Path.Value)
+		if err != nil {
+			continue
+		}
+		if p == "syscall/js" {
+			// We don't support examples that import syscall/js,
+			// because the package syscall/js is not available in the playground.
+			return nil
+		}
+		n := path.Base(p)
+		if s.Name != nil {
+			n = s.Name.Name
+			switch n {
+			case "_":
+				blankImports = append(blankImports, s)
+				continue
+			case ".":
+				// We can't resolve dot imports (yet).
+				return nil
+			}
+		}
+		if unresolved[n] {
+			namedImports[n] = p
+			delete(unresolved, n)
+		}
+	}
+
+	// If there are other unresolved identifiers, give up because this
+	// synthesized file is not going to build.
+	if len(unresolved) > 0 {
+		return nil
+	}
+
+	// Include documentation belonging to blank imports.
+	var comments []*ast.CommentGroup
+	for _, s := range blankImports {
+		if c := s.(*ast.ImportSpec).Doc; c != nil {
+			comments = append(comments, c)
+		}
+	}
+
+	// Include comments that are inside the function body.
+	for _, c := range file.Comments {
+		if body.Pos() <= c.Pos() && c.End() <= body.End() {
+			comments = append(comments, c)
+		}
+	}
+
+	// Strip the "Output:" or "Unordered output:" comment and adjust body
+	// end position.
+	body, comments = stripOutputComment(body, comments)
+
+	// Include documentation belonging to dependent declarations.
+	for _, d := range depDecls {
+		switch d := d.(type) {
+		case *ast.GenDecl:
+			if d.Doc != nil {
+				comments = append(comments, d.Doc)
+			}
+		case *ast.FuncDecl:
+			if d.Doc != nil {
+				comments = append(comments, d.Doc)
+			}
+		}
+	}
+
+	// Synthesize import declaration.
+	importDecl := &ast.GenDecl{
+		Tok:    token.IMPORT,
+		Lparen: 1, // Need non-zero Lparen and Rparen so that printer
+		Rparen: 1, // treats this as a factored import.
+	}
+	for n, p := range namedImports {
+		s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}}
+		if path.Base(p) != n {
+			s.Name = ast.NewIdent(n)
+		}
+		importDecl.Specs = append(importDecl.Specs, s)
+	}
+	importDecl.Specs = append(importDecl.Specs, blankImports...)
+
+	// Synthesize main function.
+	funcDecl := &ast.FuncDecl{
+		Name: ast.NewIdent("main"),
+		Type: f.Type,
+		Body: body,
+	}
+
+	decls := make([]ast.Decl, 0, 2+len(depDecls))
+	decls = append(decls, importDecl)
+	decls = append(decls, depDecls...)
+	decls = append(decls, funcDecl)
+
+	sort.Slice(decls, func(i, j int) bool {
+		return decls[i].Pos() < decls[j].Pos()
+	})
+
+	sort.Slice(comments, func(i, j int) bool {
+		return comments[i].Pos() < comments[j].Pos()
+	})
+
+	// Synthesize file.
+	return &ast.File{
+		Name:     ast.NewIdent("main"),
+		Decls:    decls,
+		Comments: comments,
+	}
+}
+
+// playExampleFile takes a whole file example and synthesizes a new *ast.File
+// such that the example is function main in package main.
+func playExampleFile(file *ast.File) *ast.File {
+	// Strip copyright comment if present.
+	comments := file.Comments
+	if len(comments) > 0 && strings.HasPrefix(comments[0].Text(), "Copyright") {
+		comments = comments[1:]
+	}
+
+	// Copy declaration slice, rewriting the ExampleX function to main.
+	var decls []ast.Decl
+	for _, d := range file.Decls {
+		if f, ok := d.(*ast.FuncDecl); ok && isTest(f.Name.Name, "Example") {
+			// Copy the FuncDecl, as it may be used elsewhere.
+			newF := *f
+			newF.Name = ast.NewIdent("main")
+			newF.Body, comments = stripOutputComment(f.Body, comments)
+			d = &newF
+		}
+		decls = append(decls, d)
+	}
+
+	// Copy the File, as it may be used elsewhere.
+	f := *file
+	f.Name = ast.NewIdent("main")
+	f.Decls = decls
+	f.Comments = comments
+	return &f
+}
+
+// stripOutputComment finds and removes the "Output:" or "Unordered output:"
+// comment from body and comments, and adjusts the body block's end position.
+func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) {
+	// Do nothing if there is no "Output:" or "Unordered output:" comment.
+	i, last := lastComment(body, comments)
+	if last == nil || !outputPrefix.MatchString(last.Text()) {
+		return body, comments
+	}
+
+	// Copy body and comments, as the originals may be used elsewhere.
+	newBody := &ast.BlockStmt{
+		Lbrace: body.Lbrace,
+		List:   body.List,
+		Rbrace: last.Pos(),
+	}
+	newComments := make([]*ast.CommentGroup, len(comments)-1)
+	copy(newComments, comments[:i])
+	copy(newComments[i:], comments[i+1:])
+	return newBody, newComments
+}
+
+// lastComment returns the last comment inside the provided block.
+func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) {
+	if b == nil {
+		return
+	}
+	pos, end := b.Pos(), b.End()
+	for j, cg := range c {
+		if cg.Pos() < pos {
+			continue
+		}
+		if cg.End() > end {
+			break
+		}
+		i, last = j, cg
+	}
+	return
+}
+
+// classifyExamples classifies examples and assigns them to the Examples field
+// of the relevant Func, Type, or Package that the example is associated with.
+//
+// The classification process is ambiguous in some cases:
+//
+// 	- ExampleFoo_Bar matches a type named Foo_Bar
+// 	  or a method named Foo.Bar.
+// 	- ExampleFoo_bar matches a type named Foo_bar
+// 	  or Foo (with a "bar" suffix).
+//
+// Examples with malformed names are not associated with anything.
+//
+func classifyExamples(p *Package, examples []*Example) {
+	if len(examples) == 0 {
+		return
+	}
+
+	// Mapping of names for funcs, types, and methods to the example listing.
+	ids := make(map[string]*[]*Example)
+	ids[""] = &p.Examples // package-level examples have an empty name
+	for _, f := range p.Funcs {
+		if !token.IsExported(f.Name) {
+			continue
+		}
+		ids[f.Name] = &f.Examples
+	}
+	for _, t := range p.Types {
+		if !token.IsExported(t.Name) {
+			continue
+		}
+		ids[t.Name] = &t.Examples
+		for _, f := range t.Funcs {
+			if !token.IsExported(f.Name) {
+				continue
+			}
+			ids[f.Name] = &f.Examples
+		}
+		for _, m := range t.Methods {
+			if !token.IsExported(m.Name) {
+				continue
+			}
+			ids[strings.TrimPrefix(m.Recv, "*")+"_"+m.Name] = &m.Examples
+		}
+	}
+
+	// Group each example with the associated func, type, or method.
+	for _, ex := range examples {
+		// Consider all possible split points for the suffix
+		// by starting at the end of string (no suffix case),
+		// then trying all positions that contain a '_' character.
+		//
+		// An association is made on the first successful match.
+		// Examples with malformed names that match nothing are skipped.
+		for i := len(ex.Name); i >= 0; i = strings.LastIndexByte(ex.Name[:i], '_') {
+			prefix, suffix, ok := splitExampleName(ex.Name, i)
+			if !ok {
+				continue
+			}
+			exs, ok := ids[prefix]
+			if !ok {
+				continue
+			}
+			ex.Suffix = suffix
+			*exs = append(*exs, ex)
+			break
+		}
+	}
+
+	// Sort list of example according to the user-specified suffix name.
+	for _, exs := range ids {
+		sort.Slice((*exs), func(i, j int) bool {
+			return (*exs)[i].Suffix < (*exs)[j].Suffix
+		})
+	}
+}
+
+// splitExampleName attempts to split example name s at index i,
+// and reports if that produces a valid split. The suffix may be
+// absent. Otherwise, it must start with a lower-case letter and
+// be preceded by '_'.
+//
+// One of i == len(s) or s[i] == '_' must be true.
+func splitExampleName(s string, i int) (prefix, suffix string, ok bool) {
+	if i == len(s) {
+		return s, "", true
+	}
+	if i == len(s)-1 {
+		return "", "", false
+	}
+	prefix, suffix = s[:i], s[i+1:]
+	return prefix, suffix, isExampleSuffix(suffix)
+}
+
+func isExampleSuffix(s string) bool {
+	r, size := utf8.DecodeRuneInString(s)
+	return size > 0 && unicode.IsLower(r)
+}
diff --git a/src/go/doc/example_test.go b/src/go/doc/example_test.go
new file mode 100644
index 0000000..7c96f03
--- /dev/null
+++ b/src/go/doc/example_test.go
@@ -0,0 +1,718 @@
+// Copyright 2013 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 doc_test
+
+import (
+	"bytes"
+	"fmt"
+	"go/ast"
+	"go/doc"
+	"go/format"
+	"go/parser"
+	"go/token"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+const exampleTestFile = `
+package foo_test
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"sort"
+	"os/exec"
+)
+
+func ExampleHello() {
+	fmt.Println("Hello, world!")
+	// Output: Hello, world!
+}
+
+func ExampleImport() {
+	out, err := exec.Command("date").Output()
+	if err != nil {
+		log.Fatal(err)
+	}
+	fmt.Printf("The date is %s\n", out)
+}
+
+func ExampleKeyValue() {
+	v := struct {
+		a string
+		b int
+	}{
+		a: "A",
+		b: 1,
+	}
+	fmt.Print(v)
+	// Output: a: "A", b: 1
+}
+
+func ExampleKeyValueImport() {
+	f := flag.Flag{
+		Name: "play",
+	}
+	fmt.Print(f)
+	// Output: Name: "play"
+}
+
+var keyValueTopDecl = struct {
+	a string
+	b int
+}{
+	a: "B",
+	b: 2,
+}
+
+func ExampleKeyValueTopDecl() {
+	fmt.Print(keyValueTopDecl)
+	// Output: a: "B", b: 2
+}
+
+// Person represents a person by name and age.
+type Person struct {
+    Name string
+    Age  int
+}
+
+// String returns a string representation of the Person.
+func (p Person) String() string {
+    return fmt.Sprintf("%s: %d", p.Name, p.Age)
+}
+
+// ByAge implements sort.Interface for []Person based on
+// the Age field.
+type ByAge []Person
+
+// Len returns the number of elements in ByAge.
+func (a (ByAge)) Len() int { return len(a) }
+
+// Swap swaps the elements in ByAge.
+func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
+
+// people is the array of Person
+var people = []Person{
+	{"Bob", 31},
+	{"John", 42},
+	{"Michael", 17},
+	{"Jenny", 26},
+}
+
+func ExampleSort() {
+    fmt.Println(people)
+    sort.Sort(ByAge(people))
+    fmt.Println(people)
+    // Output:
+    // [Bob: 31 John: 42 Michael: 17 Jenny: 26]
+    // [Michael: 17 Jenny: 26 Bob: 31 John: 42]
+}
+`
+
+var exampleTestCases = []struct {
+	Name, Play, Output string
+}{
+	{
+		Name:   "Hello",
+		Play:   exampleHelloPlay,
+		Output: "Hello, world!\n",
+	},
+	{
+		Name: "Import",
+		Play: exampleImportPlay,
+	},
+	{
+		Name:   "KeyValue",
+		Play:   exampleKeyValuePlay,
+		Output: "a: \"A\", b: 1\n",
+	},
+	{
+		Name:   "KeyValueImport",
+		Play:   exampleKeyValueImportPlay,
+		Output: "Name: \"play\"\n",
+	},
+	{
+		Name:   "KeyValueTopDecl",
+		Play:   exampleKeyValueTopDeclPlay,
+		Output: "a: \"B\", b: 2\n",
+	},
+	{
+		Name:   "Sort",
+		Play:   exampleSortPlay,
+		Output: "[Bob: 31 John: 42 Michael: 17 Jenny: 26]\n[Michael: 17 Jenny: 26 Bob: 31 John: 42]\n",
+	},
+}
+
+const exampleHelloPlay = `package main
+
+import (
+	"fmt"
+)
+
+func main() {
+	fmt.Println("Hello, world!")
+}
+`
+const exampleImportPlay = `package main
+
+import (
+	"fmt"
+	"log"
+	"os/exec"
+)
+
+func main() {
+	out, err := exec.Command("date").Output()
+	if err != nil {
+		log.Fatal(err)
+	}
+	fmt.Printf("The date is %s\n", out)
+}
+`
+
+const exampleKeyValuePlay = `package main
+
+import (
+	"fmt"
+)
+
+func main() {
+	v := struct {
+		a string
+		b int
+	}{
+		a: "A",
+		b: 1,
+	}
+	fmt.Print(v)
+}
+`
+
+const exampleKeyValueImportPlay = `package main
+
+import (
+	"flag"
+	"fmt"
+)
+
+func main() {
+	f := flag.Flag{
+		Name: "play",
+	}
+	fmt.Print(f)
+}
+`
+
+const exampleKeyValueTopDeclPlay = `package main
+
+import (
+	"fmt"
+)
+
+var keyValueTopDecl = struct {
+	a string
+	b int
+}{
+	a: "B",
+	b: 2,
+}
+
+func main() {
+	fmt.Print(keyValueTopDecl)
+}
+`
+
+const exampleSortPlay = `package main
+
+import (
+	"fmt"
+	"sort"
+)
+
+// Person represents a person by name and age.
+type Person struct {
+	Name string
+	Age  int
+}
+
+// String returns a string representation of the Person.
+func (p Person) String() string {
+	return fmt.Sprintf("%s: %d", p.Name, p.Age)
+}
+
+// ByAge implements sort.Interface for []Person based on
+// the Age field.
+type ByAge []Person
+
+// Len returns the number of elements in ByAge.
+func (a ByAge) Len() int { return len(a) }
+
+// Swap swaps the elements in ByAge.
+func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
+
+// people is the array of Person
+var people = []Person{
+	{"Bob", 31},
+	{"John", 42},
+	{"Michael", 17},
+	{"Jenny", 26},
+}
+
+func main() {
+	fmt.Println(people)
+	sort.Sort(ByAge(people))
+	fmt.Println(people)
+}
+`
+
+func TestExamples(t *testing.T) {
+	fset := token.NewFileSet()
+	file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleTestFile), parser.ParseComments)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for i, e := range doc.Examples(file) {
+		c := exampleTestCases[i]
+		if e.Name != c.Name {
+			t.Errorf("got Name == %q, want %q", e.Name, c.Name)
+		}
+		if w := c.Play; w != "" {
+			g := formatFile(t, fset, e.Play)
+			if g != w {
+				t.Errorf("%s: got Play == %q, want %q", c.Name, g, w)
+			}
+		}
+		if g, w := e.Output, c.Output; g != w {
+			t.Errorf("%s: got Output == %q, want %q", c.Name, g, w)
+		}
+	}
+}
+
+const exampleWholeFile = `package foo_test
+
+type X int
+
+func (X) Foo() {
+}
+
+func (X) TestBlah() {
+}
+
+func (X) BenchmarkFoo() {
+}
+
+func Example() {
+	fmt.Println("Hello, world!")
+	// Output: Hello, world!
+}
+`
+
+const exampleWholeFileOutput = `package main
+
+type X int
+
+func (X) Foo() {
+}
+
+func (X) TestBlah() {
+}
+
+func (X) BenchmarkFoo() {
+}
+
+func main() {
+	fmt.Println("Hello, world!")
+}
+`
+
+const exampleWholeFileFunction = `package foo_test
+
+func Foo(x int) {
+}
+
+func Example() {
+	fmt.Println("Hello, world!")
+	// Output: Hello, world!
+}
+`
+
+const exampleWholeFileFunctionOutput = `package main
+
+func Foo(x int) {
+}
+
+func main() {
+	fmt.Println("Hello, world!")
+}
+`
+
+var exampleWholeFileTestCases = []struct {
+	Title, Source, Play, Output string
+}{
+	{
+		"Methods",
+		exampleWholeFile,
+		exampleWholeFileOutput,
+		"Hello, world!\n",
+	},
+	{
+		"Function",
+		exampleWholeFileFunction,
+		exampleWholeFileFunctionOutput,
+		"Hello, world!\n",
+	},
+}
+
+func TestExamplesWholeFile(t *testing.T) {
+	for _, c := range exampleWholeFileTestCases {
+		fset := token.NewFileSet()
+		file, err := parser.ParseFile(fset, "test.go", strings.NewReader(c.Source), parser.ParseComments)
+		if err != nil {
+			t.Fatal(err)
+		}
+		es := doc.Examples(file)
+		if len(es) != 1 {
+			t.Fatalf("%s: wrong number of examples; got %d want 1", c.Title, len(es))
+		}
+		e := es[0]
+		if e.Name != "" {
+			t.Errorf("%s: got Name == %q, want %q", c.Title, e.Name, "")
+		}
+		if g, w := formatFile(t, fset, e.Play), c.Play; g != w {
+			t.Errorf("%s: got Play == %q, want %q", c.Title, g, w)
+		}
+		if g, w := e.Output, c.Output; g != w {
+			t.Errorf("%s: got Output == %q, want %q", c.Title, g, w)
+		}
+	}
+}
+
+const exampleInspectSignature = `package foo_test
+
+import (
+	"bytes"
+	"io"
+)
+
+func getReader() io.Reader { return nil }
+
+func do(b bytes.Reader) {}
+
+func Example() {
+	getReader()
+	do()
+	// Output:
+}
+
+func ExampleIgnored() {
+}
+`
+
+const exampleInspectSignatureOutput = `package main
+
+import (
+	"bytes"
+	"io"
+)
+
+func getReader() io.Reader { return nil }
+
+func do(b bytes.Reader) {}
+
+func main() {
+	getReader()
+	do()
+}
+`
+
+func TestExampleInspectSignature(t *testing.T) {
+	// Verify that "bytes" and "io" are imported. See issue #28492.
+	fset := token.NewFileSet()
+	file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleInspectSignature), parser.ParseComments)
+	if err != nil {
+		t.Fatal(err)
+	}
+	es := doc.Examples(file)
+	if len(es) != 2 {
+		t.Fatalf("wrong number of examples; got %d want 2", len(es))
+	}
+	// We are interested in the first example only.
+	e := es[0]
+	if e.Name != "" {
+		t.Errorf("got Name == %q, want %q", e.Name, "")
+	}
+	if g, w := formatFile(t, fset, e.Play), exampleInspectSignatureOutput; g != w {
+		t.Errorf("got Play == %q, want %q", g, w)
+	}
+	if g, w := e.Output, ""; g != w {
+		t.Errorf("got Output == %q, want %q", g, w)
+	}
+}
+
+const exampleEmpty = `
+package p
+func Example() {}
+func Example_a()
+`
+
+const exampleEmptyOutput = `package main
+
+func main() {}
+func main()
+`
+
+func TestExampleEmpty(t *testing.T) {
+	fset := token.NewFileSet()
+	file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleEmpty), parser.ParseComments)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	es := doc.Examples(file)
+	if len(es) != 1 {
+		t.Fatalf("wrong number of examples; got %d want 1", len(es))
+	}
+	e := es[0]
+	if e.Name != "" {
+		t.Errorf("got Name == %q, want %q", e.Name, "")
+	}
+	if g, w := formatFile(t, fset, e.Play), exampleEmptyOutput; g != w {
+		t.Errorf("got Play == %q, want %q", g, w)
+	}
+	if g, w := e.Output, ""; g != w {
+		t.Errorf("got Output == %q, want %q", g, w)
+	}
+}
+
+func formatFile(t *testing.T, fset *token.FileSet, n *ast.File) string {
+	if n == nil {
+		return ""
+	}
+	var buf bytes.Buffer
+	if err := format.Node(&buf, fset, n); err != nil {
+		t.Fatal(err)
+	}
+	return buf.String()
+}
+
+// This example illustrates how to use NewFromFiles
+// to compute package documentation with examples.
+func ExampleNewFromFiles() {
+	// src and test are two source files that make up
+	// a package whose documentation will be computed.
+	const src = `
+// This is the package comment.
+package p
+
+import "fmt"
+
+// This comment is associated with the Greet function.
+func Greet(who string) {
+	fmt.Printf("Hello, %s!\n", who)
+}
+`
+	const test = `
+package p_test
+
+// This comment is associated with the ExampleGreet_world example.
+func ExampleGreet_world() {
+	Greet("world")
+}
+`
+
+	// Create the AST by parsing src and test.
+	fset := token.NewFileSet()
+	files := []*ast.File{
+		mustParse(fset, "src.go", src),
+		mustParse(fset, "src_test.go", test),
+	}
+
+	// Compute package documentation with examples.
+	p, err := doc.NewFromFiles(fset, files, "example.com/p")
+	if err != nil {
+		panic(err)
+	}
+
+	fmt.Printf("package %s - %s", p.Name, p.Doc)
+	fmt.Printf("func %s - %s", p.Funcs[0].Name, p.Funcs[0].Doc)
+	fmt.Printf(" ⤷ example with suffix %q - %s", p.Funcs[0].Examples[0].Suffix, p.Funcs[0].Examples[0].Doc)
+
+	// Output:
+	// package p - This is the package comment.
+	// func Greet - This comment is associated with the Greet function.
+	//  ⤷ example with suffix "world" - This comment is associated with the ExampleGreet_world example.
+}
+
+func TestClassifyExamples(t *testing.T) {
+	const src = `
+package p
+
+const Const1 = 0
+var   Var1   = 0
+
+type (
+	Type1     int
+	Type1_Foo int
+	Type1_foo int
+	type2     int
+
+	Embed struct { Type1 }
+	Uembed struct { type2 }
+)
+
+func Func1()     {}
+func Func1_Foo() {}
+func Func1_foo() {}
+func func2()     {}
+
+func (Type1) Func1() {}
+func (Type1) Func1_Foo() {}
+func (Type1) Func1_foo() {}
+func (Type1) func2() {}
+
+func (type2) Func1() {}
+
+type (
+	Conflict          int
+	Conflict_Conflict int
+	Conflict_conflict int
+)
+
+func (Conflict) Conflict() {}
+`
+	const test = `
+package p_test
+
+func ExampleConst1() {} // invalid - no support for consts and vars
+func ExampleVar1()   {} // invalid - no support for consts and vars
+
+func Example()               {}
+func Example_()              {} // invalid - suffix must start with a lower-case letter
+func Example_suffix()        {}
+func Example_suffix_xX_X_x() {}
+func Example_世界()           {} // invalid - suffix must start with a lower-case letter
+func Example_123()           {} // invalid - suffix must start with a lower-case letter
+func Example_BadSuffix()     {} // invalid - suffix must start with a lower-case letter
+
+func ExampleType1()               {}
+func ExampleType1_()              {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_suffix()        {}
+func ExampleType1_BadSuffix()     {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_Foo()           {}
+func ExampleType1_Foo_suffix()    {}
+func ExampleType1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_foo()           {}
+func ExampleType1_foo_suffix()    {}
+func ExampleType1_foo_Suffix()    {} // matches Type1, instead of Type1_foo
+func Exampletype2()               {} // invalid - cannot match unexported
+
+func ExampleFunc1()               {}
+func ExampleFunc1_()              {} // invalid - suffix must start with a lower-case letter
+func ExampleFunc1_suffix()        {}
+func ExampleFunc1_BadSuffix()     {} // invalid - suffix must start with a lower-case letter
+func ExampleFunc1_Foo()           {}
+func ExampleFunc1_Foo_suffix()    {}
+func ExampleFunc1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleFunc1_foo()           {}
+func ExampleFunc1_foo_suffix()    {}
+func ExampleFunc1_foo_Suffix()    {} // matches Func1, instead of Func1_foo
+func Examplefunc1()               {} // invalid - cannot match unexported
+
+func ExampleType1_Func1()               {}
+func ExampleType1_Func1_()              {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_Func1_suffix()        {}
+func ExampleType1_Func1_BadSuffix()     {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_Func1_Foo()           {}
+func ExampleType1_Func1_Foo_suffix()    {}
+func ExampleType1_Func1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_Func1_foo()           {}
+func ExampleType1_Func1_foo_suffix()    {}
+func ExampleType1_Func1_foo_Suffix()    {} // matches Type1.Func1, instead of Type1.Func1_foo
+func ExampleType1_func2()               {} // matches Type1, instead of Type1.func2
+
+func ExampleEmbed_Func1()         {} // invalid - no support for forwarded methods from embedding exported type
+func ExampleUembed_Func1()        {} // methods from embedding unexported types are OK
+func ExampleUembed_Func1_suffix() {}
+
+func ExampleConflict_Conflict()        {} // ambiguous with either Conflict or Conflict_Conflict type
+func ExampleConflict_conflict()        {} // ambiguous with either Conflict or Conflict_conflict type
+func ExampleConflict_Conflict_suffix() {} // ambiguous with either Conflict or Conflict_Conflict type
+func ExampleConflict_conflict_suffix() {} // ambiguous with either Conflict or Conflict_conflict type
+`
+
+	// Parse literal source code as a *doc.Package.
+	fset := token.NewFileSet()
+	files := []*ast.File{
+		mustParse(fset, "src.go", src),
+		mustParse(fset, "src_test.go", test),
+	}
+	p, err := doc.NewFromFiles(fset, files, "example.com/p")
+	if err != nil {
+		t.Fatalf("doc.NewFromFiles: %v", err)
+	}
+
+	// Collect the association of examples to top-level identifiers.
+	got := map[string][]string{}
+	got[""] = exampleNames(p.Examples)
+	for _, f := range p.Funcs {
+		got[f.Name] = exampleNames(f.Examples)
+	}
+	for _, t := range p.Types {
+		got[t.Name] = exampleNames(t.Examples)
+		for _, f := range t.Funcs {
+			got[f.Name] = exampleNames(f.Examples)
+		}
+		for _, m := range t.Methods {
+			got[t.Name+"."+m.Name] = exampleNames(m.Examples)
+		}
+	}
+
+	want := map[string][]string{
+		"": {"", "suffix", "suffix_xX_X_x"}, // Package-level examples.
+
+		"Type1":     {"", "foo_Suffix", "func2", "suffix"},
+		"Type1_Foo": {"", "suffix"},
+		"Type1_foo": {"", "suffix"},
+
+		"Func1":     {"", "foo_Suffix", "suffix"},
+		"Func1_Foo": {"", "suffix"},
+		"Func1_foo": {"", "suffix"},
+
+		"Type1.Func1":     {"", "foo_Suffix", "suffix"},
+		"Type1.Func1_Foo": {"", "suffix"},
+		"Type1.Func1_foo": {"", "suffix"},
+
+		"Uembed.Func1": {"", "suffix"},
+
+		// These are implementation dependent due to the ambiguous parsing.
+		"Conflict_Conflict": {"", "suffix"},
+		"Conflict_conflict": {"", "suffix"},
+	}
+
+	for id := range got {
+		if !reflect.DeepEqual(got[id], want[id]) {
+			t.Errorf("classification mismatch for %q:\ngot  %q\nwant %q", id, got[id], want[id])
+		}
+	}
+}
+
+func exampleNames(exs []*doc.Example) (out []string) {
+	for _, ex := range exs {
+		out = append(out, ex.Suffix)
+	}
+	return out
+}
+
+func mustParse(fset *token.FileSet, filename, src string) *ast.File {
+	f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
+	if err != nil {
+		panic(err)
+	}
+	return f
+}
diff --git a/src/go/doc/exports.go b/src/go/doc/exports.go
new file mode 100644
index 0000000..819c030
--- /dev/null
+++ b/src/go/doc/exports.go
@@ -0,0 +1,309 @@
+// Copyright 2011 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.
+
+// This file implements export filtering of an AST.
+
+package doc
+
+import (
+	"go/ast"
+	"go/token"
+)
+
+// filterIdentList removes unexported names from list in place
+// and returns the resulting list.
+//
+func filterIdentList(list []*ast.Ident) []*ast.Ident {
+	j := 0
+	for _, x := range list {
+		if token.IsExported(x.Name) {
+			list[j] = x
+			j++
+		}
+	}
+	return list[0:j]
+}
+
+var underscore = ast.NewIdent("_")
+
+func filterCompositeLit(lit *ast.CompositeLit, filter Filter, export bool) {
+	n := len(lit.Elts)
+	lit.Elts = filterExprList(lit.Elts, filter, export)
+	if len(lit.Elts) < n {
+		lit.Incomplete = true
+	}
+}
+
+func filterExprList(list []ast.Expr, filter Filter, export bool) []ast.Expr {
+	j := 0
+	for _, exp := range list {
+		switch x := exp.(type) {
+		case *ast.CompositeLit:
+			filterCompositeLit(x, filter, export)
+		case *ast.KeyValueExpr:
+			if x, ok := x.Key.(*ast.Ident); ok && !filter(x.Name) {
+				continue
+			}
+			if x, ok := x.Value.(*ast.CompositeLit); ok {
+				filterCompositeLit(x, filter, export)
+			}
+		}
+		list[j] = exp
+		j++
+	}
+	return list[0:j]
+}
+
+// updateIdentList replaces all unexported identifiers with underscore
+// and reports whether at least one exported name exists.
+func updateIdentList(list []*ast.Ident) (hasExported bool) {
+	for i, x := range list {
+		if token.IsExported(x.Name) {
+			hasExported = true
+		} else {
+			list[i] = underscore
+		}
+	}
+	return hasExported
+}
+
+// hasExportedName reports whether list contains any exported names.
+//
+func hasExportedName(list []*ast.Ident) bool {
+	for _, x := range list {
+		if x.IsExported() {
+			return true
+		}
+	}
+	return false
+}
+
+// removeErrorField removes anonymous fields named "error" from an interface.
+// This is called when "error" has been determined to be a local name,
+// not the predeclared type.
+//
+func removeErrorField(ityp *ast.InterfaceType) {
+	list := ityp.Methods.List // we know that ityp.Methods != nil
+	j := 0
+	for _, field := range list {
+		keepField := true
+		if n := len(field.Names); n == 0 {
+			// anonymous field
+			if fname, _ := baseTypeName(field.Type); fname == "error" {
+				keepField = false
+			}
+		}
+		if keepField {
+			list[j] = field
+			j++
+		}
+	}
+	if j < len(list) {
+		ityp.Incomplete = true
+	}
+	ityp.Methods.List = list[0:j]
+}
+
+// filterFieldList removes unexported fields (field names) from the field list
+// in place and reports whether fields were removed. Anonymous fields are
+// recorded with the parent type. filterType is called with the types of
+// all remaining fields.
+//
+func (r *reader) filterFieldList(parent *namedType, fields *ast.FieldList, ityp *ast.InterfaceType) (removedFields bool) {
+	if fields == nil {
+		return
+	}
+	list := fields.List
+	j := 0
+	for _, field := range list {
+		keepField := false
+		if n := len(field.Names); n == 0 {
+			// anonymous field
+			fname := r.recordAnonymousField(parent, field.Type)
+			if token.IsExported(fname) {
+				keepField = true
+			} else if ityp != nil && fname == "error" {
+				// possibly the predeclared error interface; keep
+				// it for now but remember this interface so that
+				// it can be fixed if error is also defined locally
+				keepField = true
+				r.remember(ityp)
+			}
+		} else {
+			field.Names = filterIdentList(field.Names)
+			if len(field.Names) < n {
+				removedFields = true
+			}
+			if len(field.Names) > 0 {
+				keepField = true
+			}
+		}
+		if keepField {
+			r.filterType(nil, field.Type)
+			list[j] = field
+			j++
+		}
+	}
+	if j < len(list) {
+		removedFields = true
+	}
+	fields.List = list[0:j]
+	return
+}
+
+// filterParamList applies filterType to each parameter type in fields.
+//
+func (r *reader) filterParamList(fields *ast.FieldList) {
+	if fields != nil {
+		for _, f := range fields.List {
+			r.filterType(nil, f.Type)
+		}
+	}
+}
+
+// filterType strips any unexported struct fields or method types from typ
+// in place. If fields (or methods) have been removed, the corresponding
+// struct or interface type has the Incomplete field set to true.
+//
+func (r *reader) filterType(parent *namedType, typ ast.Expr) {
+	switch t := typ.(type) {
+	case *ast.Ident:
+		// nothing to do
+	case *ast.ParenExpr:
+		r.filterType(nil, t.X)
+	case *ast.ArrayType:
+		r.filterType(nil, t.Elt)
+	case *ast.StructType:
+		if r.filterFieldList(parent, t.Fields, nil) {
+			t.Incomplete = true
+		}
+	case *ast.FuncType:
+		r.filterParamList(t.Params)
+		r.filterParamList(t.Results)
+	case *ast.InterfaceType:
+		if r.filterFieldList(parent, t.Methods, t) {
+			t.Incomplete = true
+		}
+	case *ast.MapType:
+		r.filterType(nil, t.Key)
+		r.filterType(nil, t.Value)
+	case *ast.ChanType:
+		r.filterType(nil, t.Value)
+	}
+}
+
+func (r *reader) filterSpec(spec ast.Spec) bool {
+	switch s := spec.(type) {
+	case *ast.ImportSpec:
+		// always keep imports so we can collect them
+		return true
+	case *ast.ValueSpec:
+		s.Values = filterExprList(s.Values, token.IsExported, true)
+		if len(s.Values) > 0 || s.Type == nil && len(s.Values) == 0 {
+			// If there are values declared on RHS, just replace the unexported
+			// identifiers on the LHS with underscore, so that it matches
+			// the sequence of expression on the RHS.
+			//
+			// Similarly, if there are no type and values, then this expression
+			// must be following an iota expression, where order matters.
+			if updateIdentList(s.Names) {
+				r.filterType(nil, s.Type)
+				return true
+			}
+		} else {
+			s.Names = filterIdentList(s.Names)
+			if len(s.Names) > 0 {
+				r.filterType(nil, s.Type)
+				return true
+			}
+		}
+	case *ast.TypeSpec:
+		if name := s.Name.Name; token.IsExported(name) {
+			r.filterType(r.lookupType(s.Name.Name), s.Type)
+			return true
+		} else if name == "error" {
+			// special case: remember that error is declared locally
+			r.errorDecl = true
+		}
+	}
+	return false
+}
+
+// copyConstType returns a copy of typ with position pos.
+// typ must be a valid constant type.
+// In practice, only (possibly qualified) identifiers are possible.
+//
+func copyConstType(typ ast.Expr, pos token.Pos) ast.Expr {
+	switch typ := typ.(type) {
+	case *ast.Ident:
+		return &ast.Ident{Name: typ.Name, NamePos: pos}
+	case *ast.SelectorExpr:
+		if id, ok := typ.X.(*ast.Ident); ok {
+			// presumably a qualified identifier
+			return &ast.SelectorExpr{
+				Sel: ast.NewIdent(typ.Sel.Name),
+				X:   &ast.Ident{Name: id.Name, NamePos: pos},
+			}
+		}
+	}
+	return nil // shouldn't happen, but be conservative and don't panic
+}
+
+func (r *reader) filterSpecList(list []ast.Spec, tok token.Token) []ast.Spec {
+	if tok == token.CONST {
+		// Propagate any type information that would get lost otherwise
+		// when unexported constants are filtered.
+		var prevType ast.Expr
+		for _, spec := range list {
+			spec := spec.(*ast.ValueSpec)
+			if spec.Type == nil && len(spec.Values) == 0 && prevType != nil {
+				// provide current spec with an explicit type
+				spec.Type = copyConstType(prevType, spec.Pos())
+			}
+			if hasExportedName(spec.Names) {
+				// exported names are preserved so there's no need to propagate the type
+				prevType = nil
+			} else {
+				prevType = spec.Type
+			}
+		}
+	}
+
+	j := 0
+	for _, s := range list {
+		if r.filterSpec(s) {
+			list[j] = s
+			j++
+		}
+	}
+	return list[0:j]
+}
+
+func (r *reader) filterDecl(decl ast.Decl) bool {
+	switch d := decl.(type) {
+	case *ast.GenDecl:
+		d.Specs = r.filterSpecList(d.Specs, d.Tok)
+		return len(d.Specs) > 0
+	case *ast.FuncDecl:
+		// ok to filter these methods early because any
+		// conflicting method will be filtered here, too -
+		// thus, removing these methods early will not lead
+		// to the false removal of possible conflicts
+		return token.IsExported(d.Name.Name)
+	}
+	return false
+}
+
+// fileExports removes unexported declarations from src in place.
+//
+func (r *reader) fileExports(src *ast.File) {
+	j := 0
+	for _, d := range src.Decls {
+		if r.filterDecl(d) {
+			src.Decls[j] = d
+			j++
+		}
+	}
+	src.Decls = src.Decls[0:j]
+}
diff --git a/src/go/doc/filter.go b/src/go/doc/filter.go
new file mode 100644
index 0000000..a6f243f
--- /dev/null
+++ b/src/go/doc/filter.go
@@ -0,0 +1,105 @@
+// Copyright 2009 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 doc
+
+import "go/ast"
+
+type Filter func(string) bool
+
+func matchFields(fields *ast.FieldList, f Filter) bool {
+	if fields != nil {
+		for _, field := range fields.List {
+			for _, name := range field.Names {
+				if f(name.Name) {
+					return true
+				}
+			}
+		}
+	}
+	return false
+}
+
+func matchDecl(d *ast.GenDecl, f Filter) bool {
+	for _, d := range d.Specs {
+		switch v := d.(type) {
+		case *ast.ValueSpec:
+			for _, name := range v.Names {
+				if f(name.Name) {
+					return true
+				}
+			}
+		case *ast.TypeSpec:
+			if f(v.Name.Name) {
+				return true
+			}
+			switch t := v.Type.(type) {
+			case *ast.StructType:
+				if matchFields(t.Fields, f) {
+					return true
+				}
+			case *ast.InterfaceType:
+				if matchFields(t.Methods, f) {
+					return true
+				}
+			}
+		}
+	}
+	return false
+}
+
+func filterValues(a []*Value, f Filter) []*Value {
+	w := 0
+	for _, vd := range a {
+		if matchDecl(vd.Decl, f) {
+			a[w] = vd
+			w++
+		}
+	}
+	return a[0:w]
+}
+
+func filterFuncs(a []*Func, f Filter) []*Func {
+	w := 0
+	for _, fd := range a {
+		if f(fd.Name) {
+			a[w] = fd
+			w++
+		}
+	}
+	return a[0:w]
+}
+
+func filterTypes(a []*Type, f Filter) []*Type {
+	w := 0
+	for _, td := range a {
+		n := 0 // number of matches
+		if matchDecl(td.Decl, f) {
+			n = 1
+		} else {
+			// type name doesn't match, but we may have matching consts, vars, factories or methods
+			td.Consts = filterValues(td.Consts, f)
+			td.Vars = filterValues(td.Vars, f)
+			td.Funcs = filterFuncs(td.Funcs, f)
+			td.Methods = filterFuncs(td.Methods, f)
+			n += len(td.Consts) + len(td.Vars) + len(td.Funcs) + len(td.Methods)
+		}
+		if n > 0 {
+			a[w] = td
+			w++
+		}
+	}
+	return a[0:w]
+}
+
+// Filter eliminates documentation for names that don't pass through the filter f.
+// TODO(gri): Recognize "Type.Method" as a name.
+//
+func (p *Package) Filter(f Filter) {
+	p.Consts = filterValues(p.Consts, f)
+	p.Vars = filterValues(p.Vars, f)
+	p.Types = filterTypes(p.Types, f)
+	p.Funcs = filterFuncs(p.Funcs, f)
+	p.Doc = "" // don't show top-level package doc
+}
diff --git a/src/go/doc/headscan.go b/src/go/doc/headscan.go
new file mode 100644
index 0000000..fe26a0e
--- /dev/null
+++ b/src/go/doc/headscan.go
@@ -0,0 +1,115 @@
+// Copyright 2011 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.
+
+// +build ignore
+
+/*
+	The headscan command extracts comment headings from package files;
+	it is used to detect false positives which may require an adjustment
+	to the comment formatting heuristics in comment.go.
+
+	Usage: headscan [-root root_directory]
+
+	By default, the $GOROOT/src directory is scanned.
+*/
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"go/doc"
+	"go/parser"
+	"go/token"
+	"internal/lazyregexp"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+)
+
+var (
+	root    = flag.String("root", filepath.Join(runtime.GOROOT(), "src"), "root of filesystem tree to scan")
+	verbose = flag.Bool("v", false, "verbose mode")
+)
+
+// ToHTML in comment.go assigns a (possibly blank) ID to each heading
+var html_h = lazyregexp.New(`

`) + +const html_endh = "

\n" + +func isGoFile(fi fs.FileInfo) bool { + return strings.HasSuffix(fi.Name(), ".go") && + !strings.HasSuffix(fi.Name(), "_test.go") +} + +func appendHeadings(list []string, comment string) []string { + var buf bytes.Buffer + doc.ToHTML(&buf, comment, nil) + for s := buf.String(); ; { + loc := html_h.FindStringIndex(s) + if len(loc) == 0 { + break + } + i := loc[1] + j := strings.Index(s, html_endh) + if j < 0 { + list = append(list, s[i:]) // incorrect HTML + break + } + list = append(list, s[i:j]) + s = s[j+len(html_endh):] + } + return list +} + +func main() { + flag.Parse() + fset := token.NewFileSet() + nheadings := 0 + err := filepath.WalkDir(*root, func(path string, info fs.DirEntry, err error) error { + if !info.IsDir() { + return nil + } + pkgs, err := parser.ParseDir(fset, path, isGoFile, parser.ParseComments) + if err != nil { + if *verbose { + fmt.Fprintln(os.Stderr, err) + } + return nil + } + for _, pkg := range pkgs { + d := doc.New(pkg, path, doc.Mode(0)) + list := appendHeadings(nil, d.Doc) + for _, d := range d.Consts { + list = appendHeadings(list, d.Doc) + } + for _, d := range d.Types { + list = appendHeadings(list, d.Doc) + } + for _, d := range d.Vars { + list = appendHeadings(list, d.Doc) + } + for _, d := range d.Funcs { + list = appendHeadings(list, d.Doc) + } + if len(list) > 0 { + // directories may contain multiple packages; + // print path and package name + fmt.Printf("%s (package %s)\n", path, pkg.Name) + for _, h := range list { + fmt.Printf("\t%s\n", h) + } + nheadings += len(list) + } + } + return nil + }) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + fmt.Println(nheadings, "headings found") +} diff --git a/src/go/doc/reader.go b/src/go/doc/reader.go new file mode 100644 index 0000000..c277b35 --- /dev/null +++ b/src/go/doc/reader.go @@ -0,0 +1,917 @@ +// Copyright 2009 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 doc + +import ( + "go/ast" + "go/token" + "internal/lazyregexp" + "sort" + "strconv" +) + +// ---------------------------------------------------------------------------- +// function/method sets +// +// Internally, we treat functions like methods and collect them in method sets. + +// A methodSet describes a set of methods. Entries where Decl == nil are conflict +// entries (more than one method with the same name at the same embedding level). +// +type methodSet map[string]*Func + +// recvString returns a string representation of recv of the +// form "T", "*T", or "BADRECV" (if not a proper receiver type). +// +func recvString(recv ast.Expr) string { + switch t := recv.(type) { + case *ast.Ident: + return t.Name + case *ast.StarExpr: + return "*" + recvString(t.X) + } + return "BADRECV" +} + +// set creates the corresponding Func for f and adds it to mset. +// If there are multiple f's with the same name, set keeps the first +// one with documentation; conflicts are ignored. The boolean +// specifies whether to leave the AST untouched. +// +func (mset methodSet) set(f *ast.FuncDecl, preserveAST bool) { + name := f.Name.Name + if g := mset[name]; g != nil && g.Doc != "" { + // A function with the same name has already been registered; + // since it has documentation, assume f is simply another + // implementation and ignore it. This does not happen if the + // caller is using go/build.ScanDir to determine the list of + // files implementing a package. + return + } + // function doesn't exist or has no documentation; use f + recv := "" + if f.Recv != nil { + var typ ast.Expr + // be careful in case of incorrect ASTs + if list := f.Recv.List; len(list) == 1 { + typ = list[0].Type + } + recv = recvString(typ) + } + mset[name] = &Func{ + Doc: f.Doc.Text(), + Name: name, + Decl: f, + Recv: recv, + Orig: recv, + } + if !preserveAST { + f.Doc = nil // doc consumed - remove from AST + } +} + +// add adds method m to the method set; m is ignored if the method set +// already contains a method with the same name at the same or a higher +// level than m. +// +func (mset methodSet) add(m *Func) { + old := mset[m.Name] + if old == nil || m.Level < old.Level { + mset[m.Name] = m + return + } + if m.Level == old.Level { + // conflict - mark it using a method with nil Decl + mset[m.Name] = &Func{ + Name: m.Name, + Level: m.Level, + } + } +} + +// ---------------------------------------------------------------------------- +// Named types + +// baseTypeName returns the name of the base type of x (or "") +// and whether the type is imported or not. +// +func baseTypeName(x ast.Expr) (name string, imported bool) { + switch t := x.(type) { + case *ast.Ident: + return t.Name, false + case *ast.SelectorExpr: + if _, ok := t.X.(*ast.Ident); ok { + // only possible for qualified type names; + // assume type is imported + return t.Sel.Name, true + } + case *ast.ParenExpr: + return baseTypeName(t.X) + case *ast.StarExpr: + return baseTypeName(t.X) + } + return +} + +// An embeddedSet describes a set of embedded types. +type embeddedSet map[*namedType]bool + +// A namedType represents a named unqualified (package local, or possibly +// predeclared) type. The namedType for a type name is always found via +// reader.lookupType. +// +type namedType struct { + doc string // doc comment for type + name string // type name + decl *ast.GenDecl // nil if declaration hasn't been seen yet + + isEmbedded bool // true if this type is embedded + isStruct bool // true if this type is a struct + embedded embeddedSet // true if the embedded type is a pointer + + // associated declarations + values []*Value // consts and vars + funcs methodSet + methods methodSet +} + +// ---------------------------------------------------------------------------- +// AST reader + +// reader accumulates documentation for a single package. +// It modifies the AST: Comments (declaration documentation) +// that have been collected by the reader are set to nil +// in the respective AST nodes so that they are not printed +// twice (once when printing the documentation and once when +// printing the corresponding AST node). +// +type reader struct { + mode Mode + + // package properties + doc string // package documentation, if any + filenames []string + notes map[string][]*Note + + // declarations + imports map[string]int + hasDotImp bool // if set, package contains a dot import + values []*Value // consts and vars + order int // sort order of const and var declarations (when we can't use a name) + types map[string]*namedType + funcs methodSet + + // support for package-local error type declarations + errorDecl bool // if set, type "error" was declared locally + fixlist []*ast.InterfaceType // list of interfaces containing anonymous field "error" +} + +func (r *reader) isVisible(name string) bool { + return r.mode&AllDecls != 0 || token.IsExported(name) +} + +// lookupType returns the base type with the given name. +// If the base type has not been encountered yet, a new +// type with the given name but no associated declaration +// is added to the type map. +// +func (r *reader) lookupType(name string) *namedType { + if name == "" || name == "_" { + return nil // no type docs for anonymous types + } + if typ, found := r.types[name]; found { + return typ + } + // type not found - add one without declaration + typ := &namedType{ + name: name, + embedded: make(embeddedSet), + funcs: make(methodSet), + methods: make(methodSet), + } + r.types[name] = typ + return typ +} + +// recordAnonymousField registers fieldType as the type of an +// anonymous field in the parent type. If the field is imported +// (qualified name) or the parent is nil, the field is ignored. +// The function returns the field name. +// +func (r *reader) recordAnonymousField(parent *namedType, fieldType ast.Expr) (fname string) { + fname, imp := baseTypeName(fieldType) + if parent == nil || imp { + return + } + if ftype := r.lookupType(fname); ftype != nil { + ftype.isEmbedded = true + _, ptr := fieldType.(*ast.StarExpr) + parent.embedded[ftype] = ptr + } + return +} + +func (r *reader) readDoc(comment *ast.CommentGroup) { + // By convention there should be only one package comment + // but collect all of them if there are more than one. + text := comment.Text() + if r.doc == "" { + r.doc = text + return + } + r.doc += "\n" + text +} + +func (r *reader) remember(typ *ast.InterfaceType) { + r.fixlist = append(r.fixlist, typ) +} + +func specNames(specs []ast.Spec) []string { + names := make([]string, 0, len(specs)) // reasonable estimate + for _, s := range specs { + // s guaranteed to be an *ast.ValueSpec by readValue + for _, ident := range s.(*ast.ValueSpec).Names { + names = append(names, ident.Name) + } + } + return names +} + +// readValue processes a const or var declaration. +// +func (r *reader) readValue(decl *ast.GenDecl) { + // determine if decl should be associated with a type + // Heuristic: For each typed entry, determine the type name, if any. + // If there is exactly one type name that is sufficiently + // frequent, associate the decl with the respective type. + domName := "" + domFreq := 0 + prev := "" + n := 0 + for _, spec := range decl.Specs { + s, ok := spec.(*ast.ValueSpec) + if !ok { + continue // should not happen, but be conservative + } + name := "" + switch { + case s.Type != nil: + // a type is present; determine its name + if n, imp := baseTypeName(s.Type); !imp { + name = n + } + case decl.Tok == token.CONST && len(s.Values) == 0: + // no type or value is present but we have a constant declaration; + // use the previous type name (possibly the empty string) + name = prev + } + if name != "" { + // entry has a named type + if domName != "" && domName != name { + // more than one type name - do not associate + // with any type + domName = "" + break + } + domName = name + domFreq++ + } + prev = name + n++ + } + + // nothing to do w/o a legal declaration + if n == 0 { + return + } + + // determine values list with which to associate the Value for this decl + values := &r.values + const threshold = 0.75 + if domName != "" && r.isVisible(domName) && domFreq >= int(float64(len(decl.Specs))*threshold) { + // typed entries are sufficiently frequent + if typ := r.lookupType(domName); typ != nil { + values = &typ.values // associate with that type + } + } + + *values = append(*values, &Value{ + Doc: decl.Doc.Text(), + Names: specNames(decl.Specs), + Decl: decl, + order: r.order, + }) + if r.mode&PreserveAST == 0 { + decl.Doc = nil // doc consumed - remove from AST + } + // Note: It's important that the order used here is global because the cleanupTypes + // methods may move values associated with types back into the global list. If the + // order is list-specific, sorting is not deterministic because the same order value + // may appear multiple times (was bug, found when fixing #16153). + r.order++ +} + +// fields returns a struct's fields or an interface's methods. +// +func fields(typ ast.Expr) (list []*ast.Field, isStruct bool) { + var fields *ast.FieldList + switch t := typ.(type) { + case *ast.StructType: + fields = t.Fields + isStruct = true + case *ast.InterfaceType: + fields = t.Methods + } + if fields != nil { + list = fields.List + } + return +} + +// readType processes a type declaration. +// +func (r *reader) readType(decl *ast.GenDecl, spec *ast.TypeSpec) { + typ := r.lookupType(spec.Name.Name) + if typ == nil { + return // no name or blank name - ignore the type + } + + // A type should be added at most once, so typ.decl + // should be nil - if it is not, simply overwrite it. + typ.decl = decl + + // compute documentation + doc := spec.Doc + if doc == nil { + // no doc associated with the spec, use the declaration doc, if any + doc = decl.Doc + } + if r.mode&PreserveAST == 0 { + spec.Doc = nil // doc consumed - remove from AST + decl.Doc = nil // doc consumed - remove from AST + } + typ.doc = doc.Text() + + // record anonymous fields (they may contribute methods) + // (some fields may have been recorded already when filtering + // exports, but that's ok) + var list []*ast.Field + list, typ.isStruct = fields(spec.Type) + for _, field := range list { + if len(field.Names) == 0 { + r.recordAnonymousField(typ, field.Type) + } + } +} + +// isPredeclared reports whether n denotes a predeclared type. +// +func (r *reader) isPredeclared(n string) bool { + return predeclaredTypes[n] && r.types[n] == nil +} + +// readFunc processes a func or method declaration. +// +func (r *reader) readFunc(fun *ast.FuncDecl) { + // strip function body if requested. + if r.mode&PreserveAST == 0 { + fun.Body = nil + } + + // associate methods with the receiver type, if any + if fun.Recv != nil { + // method + if len(fun.Recv.List) == 0 { + // should not happen (incorrect AST); (See issue 17788) + // don't show this method + return + } + recvTypeName, imp := baseTypeName(fun.Recv.List[0].Type) + if imp { + // should not happen (incorrect AST); + // don't show this method + return + } + if typ := r.lookupType(recvTypeName); typ != nil { + typ.methods.set(fun, r.mode&PreserveAST != 0) + } + // otherwise ignore the method + // TODO(gri): There may be exported methods of non-exported types + // that can be called because of exported values (consts, vars, or + // function results) of that type. Could determine if that is the + // case and then show those methods in an appropriate section. + return + } + + // Associate factory functions with the first visible result type, as long as + // others are predeclared types. + if fun.Type.Results.NumFields() >= 1 { + var typ *namedType // type to associate the function with + numResultTypes := 0 + for _, res := range fun.Type.Results.List { + factoryType := res.Type + if t, ok := factoryType.(*ast.ArrayType); ok { + // We consider functions that return slices or arrays of type + // T (or pointers to T) as factory functions of T. + factoryType = t.Elt + } + if n, imp := baseTypeName(factoryType); !imp && r.isVisible(n) && !r.isPredeclared(n) { + if t := r.lookupType(n); t != nil { + typ = t + numResultTypes++ + if numResultTypes > 1 { + break + } + } + } + } + // If there is exactly one result type, + // associate the function with that type. + if numResultTypes == 1 { + typ.funcs.set(fun, r.mode&PreserveAST != 0) + return + } + } + + // just an ordinary function + r.funcs.set(fun, r.mode&PreserveAST != 0) +} + +var ( + noteMarker = `([A-Z][A-Z]+)\(([^)]+)\):?` // MARKER(uid), MARKER at least 2 chars, uid at least 1 char + noteMarkerRx = lazyregexp.New(`^[ \t]*` + noteMarker) // MARKER(uid) at text start + noteCommentRx = lazyregexp.New(`^/[/*][ \t]*` + noteMarker) // MARKER(uid) at comment start +) + +// readNote collects a single note from a sequence of comments. +// +func (r *reader) readNote(list []*ast.Comment) { + text := (&ast.CommentGroup{List: list}).Text() + if m := noteMarkerRx.FindStringSubmatchIndex(text); m != nil { + // The note body starts after the marker. + // We remove any formatting so that we don't + // get spurious line breaks/indentation when + // showing the TODO body. + body := clean(text[m[1]:], keepNL) + if body != "" { + marker := text[m[2]:m[3]] + r.notes[marker] = append(r.notes[marker], &Note{ + Pos: list[0].Pos(), + End: list[len(list)-1].End(), + UID: text[m[4]:m[5]], + Body: body, + }) + } + } +} + +// readNotes extracts notes from comments. +// A note must start at the beginning of a comment with "MARKER(uid):" +// and is followed by the note body (e.g., "// BUG(gri): fix this"). +// The note ends at the end of the comment group or at the start of +// another note in the same comment group, whichever comes first. +// +func (r *reader) readNotes(comments []*ast.CommentGroup) { + for _, group := range comments { + i := -1 // comment index of most recent note start, valid if >= 0 + list := group.List + for j, c := range list { + if noteCommentRx.MatchString(c.Text) { + if i >= 0 { + r.readNote(list[i:j]) + } + i = j + } + } + if i >= 0 { + r.readNote(list[i:]) + } + } +} + +// readFile adds the AST for a source file to the reader. +// +func (r *reader) readFile(src *ast.File) { + // add package documentation + if src.Doc != nil { + r.readDoc(src.Doc) + if r.mode&PreserveAST == 0 { + src.Doc = nil // doc consumed - remove from AST + } + } + + // add all declarations but for functions which are processed in a separate pass + for _, decl := range src.Decls { + switch d := decl.(type) { + case *ast.GenDecl: + switch d.Tok { + case token.IMPORT: + // imports are handled individually + for _, spec := range d.Specs { + if s, ok := spec.(*ast.ImportSpec); ok { + if import_, err := strconv.Unquote(s.Path.Value); err == nil { + r.imports[import_] = 1 + if s.Name != nil && s.Name.Name == "." { + r.hasDotImp = true + } + } + } + } + case token.CONST, token.VAR: + // constants and variables are always handled as a group + r.readValue(d) + case token.TYPE: + // types are handled individually + if len(d.Specs) == 1 && !d.Lparen.IsValid() { + // common case: single declaration w/o parentheses + // (if a single declaration is parenthesized, + // create a new fake declaration below, so that + // go/doc type declarations always appear w/o + // parentheses) + if s, ok := d.Specs[0].(*ast.TypeSpec); ok { + r.readType(d, s) + } + break + } + for _, spec := range d.Specs { + if s, ok := spec.(*ast.TypeSpec); ok { + // use an individual (possibly fake) declaration + // for each type; this also ensures that each type + // gets to (re-)use the declaration documentation + // if there's none associated with the spec itself + fake := &ast.GenDecl{ + Doc: d.Doc, + // don't use the existing TokPos because it + // will lead to the wrong selection range for + // the fake declaration if there are more + // than one type in the group (this affects + // src/cmd/godoc/godoc.go's posLink_urlFunc) + TokPos: s.Pos(), + Tok: token.TYPE, + Specs: []ast.Spec{s}, + } + r.readType(fake, s) + } + } + } + } + } + + // collect MARKER(...): annotations + r.readNotes(src.Comments) + if r.mode&PreserveAST == 0 { + src.Comments = nil // consumed unassociated comments - remove from AST + } +} + +func (r *reader) readPackage(pkg *ast.Package, mode Mode) { + // initialize reader + r.filenames = make([]string, len(pkg.Files)) + r.imports = make(map[string]int) + r.mode = mode + r.types = make(map[string]*namedType) + r.funcs = make(methodSet) + r.notes = make(map[string][]*Note) + + // sort package files before reading them so that the + // result does not depend on map iteration order + i := 0 + for filename := range pkg.Files { + r.filenames[i] = filename + i++ + } + sort.Strings(r.filenames) + + // process files in sorted order + for _, filename := range r.filenames { + f := pkg.Files[filename] + if mode&AllDecls == 0 { + r.fileExports(f) + } + r.readFile(f) + } + + // process functions now that we have better type information + for _, f := range pkg.Files { + for _, decl := range f.Decls { + if d, ok := decl.(*ast.FuncDecl); ok { + r.readFunc(d) + } + } + } +} + +// ---------------------------------------------------------------------------- +// Types + +func customizeRecv(f *Func, recvTypeName string, embeddedIsPtr bool, level int) *Func { + if f == nil || f.Decl == nil || f.Decl.Recv == nil || len(f.Decl.Recv.List) != 1 { + return f // shouldn't happen, but be safe + } + + // copy existing receiver field and set new type + newField := *f.Decl.Recv.List[0] + origPos := newField.Type.Pos() + _, origRecvIsPtr := newField.Type.(*ast.StarExpr) + newIdent := &ast.Ident{NamePos: origPos, Name: recvTypeName} + var typ ast.Expr = newIdent + if !embeddedIsPtr && origRecvIsPtr { + newIdent.NamePos++ // '*' is one character + typ = &ast.StarExpr{Star: origPos, X: newIdent} + } + newField.Type = typ + + // copy existing receiver field list and set new receiver field + newFieldList := *f.Decl.Recv + newFieldList.List = []*ast.Field{&newField} + + // copy existing function declaration and set new receiver field list + newFuncDecl := *f.Decl + newFuncDecl.Recv = &newFieldList + + // copy existing function documentation and set new declaration + newF := *f + newF.Decl = &newFuncDecl + newF.Recv = recvString(typ) + // the Orig field never changes + newF.Level = level + + return &newF +} + +// collectEmbeddedMethods collects the embedded methods of typ in mset. +// +func (r *reader) collectEmbeddedMethods(mset methodSet, typ *namedType, recvTypeName string, embeddedIsPtr bool, level int, visited embeddedSet) { + visited[typ] = true + for embedded, isPtr := range typ.embedded { + // Once an embedded type is embedded as a pointer type + // all embedded types in those types are treated like + // pointer types for the purpose of the receiver type + // computation; i.e., embeddedIsPtr is sticky for this + // embedding hierarchy. + thisEmbeddedIsPtr := embeddedIsPtr || isPtr + for _, m := range embedded.methods { + // only top-level methods are embedded + if m.Level == 0 { + mset.add(customizeRecv(m, recvTypeName, thisEmbeddedIsPtr, level)) + } + } + if !visited[embedded] { + r.collectEmbeddedMethods(mset, embedded, recvTypeName, thisEmbeddedIsPtr, level+1, visited) + } + } + delete(visited, typ) +} + +// computeMethodSets determines the actual method sets for each type encountered. +// +func (r *reader) computeMethodSets() { + for _, t := range r.types { + // collect embedded methods for t + if t.isStruct { + // struct + r.collectEmbeddedMethods(t.methods, t, t.name, false, 1, make(embeddedSet)) + } else { + // interface + // TODO(gri) fix this + } + } + + // if error was declared locally, don't treat it as exported field anymore + if r.errorDecl { + for _, ityp := range r.fixlist { + removeErrorField(ityp) + } + } +} + +// cleanupTypes removes the association of functions and methods with +// types that have no declaration. Instead, these functions and methods +// are shown at the package level. It also removes types with missing +// declarations or which are not visible. +// +func (r *reader) cleanupTypes() { + for _, t := range r.types { + visible := r.isVisible(t.name) + predeclared := predeclaredTypes[t.name] + + if t.decl == nil && (predeclared || visible && (t.isEmbedded || r.hasDotImp)) { + // t.name is a predeclared type (and was not redeclared in this package), + // or it was embedded somewhere but its declaration is missing (because + // the AST is incomplete), or we have a dot-import (and all bets are off): + // move any associated values, funcs, and methods back to the top-level so + // that they are not lost. + // 1) move values + r.values = append(r.values, t.values...) + // 2) move factory functions + for name, f := range t.funcs { + // in a correct AST, package-level function names + // are all different - no need to check for conflicts + r.funcs[name] = f + } + // 3) move methods + if !predeclared { + for name, m := range t.methods { + // don't overwrite functions with the same name - drop them + if _, found := r.funcs[name]; !found { + r.funcs[name] = m + } + } + } + } + // remove types w/o declaration or which are not visible + if t.decl == nil || !visible { + delete(r.types, t.name) + } + } +} + +// ---------------------------------------------------------------------------- +// Sorting + +type data struct { + n int + swap func(i, j int) + less func(i, j int) bool +} + +func (d *data) Len() int { return d.n } +func (d *data) Swap(i, j int) { d.swap(i, j) } +func (d *data) Less(i, j int) bool { return d.less(i, j) } + +// sortBy is a helper function for sorting +func sortBy(less func(i, j int) bool, swap func(i, j int), n int) { + sort.Sort(&data{n, swap, less}) +} + +func sortedKeys(m map[string]int) []string { + list := make([]string, len(m)) + i := 0 + for key := range m { + list[i] = key + i++ + } + sort.Strings(list) + return list +} + +// sortingName returns the name to use when sorting d into place. +// +func sortingName(d *ast.GenDecl) string { + if len(d.Specs) == 1 { + if s, ok := d.Specs[0].(*ast.ValueSpec); ok { + return s.Names[0].Name + } + } + return "" +} + +func sortedValues(m []*Value, tok token.Token) []*Value { + list := make([]*Value, len(m)) // big enough in any case + i := 0 + for _, val := range m { + if val.Decl.Tok == tok { + list[i] = val + i++ + } + } + list = list[0:i] + + sortBy( + func(i, j int) bool { + if ni, nj := sortingName(list[i].Decl), sortingName(list[j].Decl); ni != nj { + return ni < nj + } + return list[i].order < list[j].order + }, + func(i, j int) { list[i], list[j] = list[j], list[i] }, + len(list), + ) + + return list +} + +func sortedTypes(m map[string]*namedType, allMethods bool) []*Type { + list := make([]*Type, len(m)) + i := 0 + for _, t := range m { + list[i] = &Type{ + Doc: t.doc, + Name: t.name, + Decl: t.decl, + Consts: sortedValues(t.values, token.CONST), + Vars: sortedValues(t.values, token.VAR), + Funcs: sortedFuncs(t.funcs, true), + Methods: sortedFuncs(t.methods, allMethods), + } + i++ + } + + sortBy( + func(i, j int) bool { return list[i].Name < list[j].Name }, + func(i, j int) { list[i], list[j] = list[j], list[i] }, + len(list), + ) + + return list +} + +func removeStar(s string) string { + if len(s) > 0 && s[0] == '*' { + return s[1:] + } + return s +} + +func sortedFuncs(m methodSet, allMethods bool) []*Func { + list := make([]*Func, len(m)) + i := 0 + for _, m := range m { + // determine which methods to include + switch { + case m.Decl == nil: + // exclude conflict entry + case allMethods, m.Level == 0, !token.IsExported(removeStar(m.Orig)): + // forced inclusion, method not embedded, or method + // embedded but original receiver type not exported + list[i] = m + i++ + } + } + list = list[0:i] + sortBy( + func(i, j int) bool { return list[i].Name < list[j].Name }, + func(i, j int) { list[i], list[j] = list[j], list[i] }, + len(list), + ) + return list +} + +// noteBodies returns a list of note body strings given a list of notes. +// This is only used to populate the deprecated Package.Bugs field. +// +func noteBodies(notes []*Note) []string { + var list []string + for _, n := range notes { + list = append(list, n.Body) + } + return list +} + +// ---------------------------------------------------------------------------- +// Predeclared identifiers + +// IsPredeclared reports whether s is a predeclared identifier. +func IsPredeclared(s string) bool { + return predeclaredTypes[s] || predeclaredFuncs[s] || predeclaredConstants[s] +} + +var predeclaredTypes = map[string]bool{ + "bool": true, + "byte": true, + "complex64": true, + "complex128": true, + "error": true, + "float32": true, + "float64": true, + "int": true, + "int8": true, + "int16": true, + "int32": true, + "int64": true, + "rune": true, + "string": true, + "uint": true, + "uint8": true, + "uint16": true, + "uint32": true, + "uint64": true, + "uintptr": true, +} + +var predeclaredFuncs = map[string]bool{ + "append": true, + "cap": true, + "close": true, + "complex": true, + "copy": true, + "delete": true, + "imag": true, + "len": true, + "make": true, + "new": true, + "panic": true, + "print": true, + "println": true, + "real": true, + "recover": true, +} + +var predeclaredConstants = map[string]bool{ + "false": true, + "iota": true, + "nil": true, + "true": true, +} diff --git a/src/go/doc/synopsis.go b/src/go/doc/synopsis.go new file mode 100644 index 0000000..3fa1616 --- /dev/null +++ b/src/go/doc/synopsis.go @@ -0,0 +1,83 @@ +// Copyright 2012 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 doc + +import ( + "strings" + "unicode" +) + +// firstSentenceLen returns the length of the first sentence in s. +// The sentence ends after the first period followed by space and +// not preceded by exactly one uppercase letter. +// +func firstSentenceLen(s string) int { + var ppp, pp, p rune + for i, q := range s { + if q == '\n' || q == '\r' || q == '\t' { + q = ' ' + } + if q == ' ' && p == '.' && (!unicode.IsUpper(pp) || unicode.IsUpper(ppp)) { + return i + } + if p == '。' || p == '.' { + return i + } + ppp, pp, p = pp, p, q + } + return len(s) +} + +const ( + keepNL = 1 << iota +) + +// clean replaces each sequence of space, \n, \r, or \t characters +// with a single space and removes any trailing and leading spaces. +// If the keepNL flag is set, newline characters are passed through +// instead of being change to spaces. +func clean(s string, flags int) string { + var b []byte + p := byte(' ') + for i := 0; i < len(s); i++ { + q := s[i] + if (flags&keepNL) == 0 && q == '\n' || q == '\r' || q == '\t' { + q = ' ' + } + if q != ' ' || p != ' ' { + b = append(b, q) + p = q + } + } + // remove trailing blank, if any + if n := len(b); n > 0 && p == ' ' { + b = b[0 : n-1] + } + return string(b) +} + +// Synopsis returns a cleaned version of the first sentence in s. +// That sentence ends after the first period followed by space and +// not preceded by exactly one uppercase letter. The result string +// has no \n, \r, or \t characters and uses only single spaces between +// words. If s starts with any of the IllegalPrefixes, the result +// is the empty string. +// +func Synopsis(s string) string { + s = clean(s[0:firstSentenceLen(s)], 0) + for _, prefix := range IllegalPrefixes { + if strings.HasPrefix(strings.ToLower(s), prefix) { + return "" + } + } + s = convertQuotes(s) + return s +} + +var IllegalPrefixes = []string{ + "copyright", + "all rights", + "author", +} diff --git a/src/go/doc/synopsis_test.go b/src/go/doc/synopsis_test.go new file mode 100644 index 0000000..3f443dc --- /dev/null +++ b/src/go/doc/synopsis_test.go @@ -0,0 +1,52 @@ +// Copyright 2012 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 doc + +import "testing" + +var tests = []struct { + txt string + fsl int + syn string +}{ + {"", 0, ""}, + {"foo", 3, "foo"}, + {"foo.", 4, "foo."}, + {"foo.bar", 7, "foo.bar"}, + {" foo. ", 6, "foo."}, + {" foo\t bar.\n", 12, "foo bar."}, + {" foo\t bar.\n", 12, "foo bar."}, + {"a b\n\nc\r\rd\t\t", 12, "a b c d"}, + {"a b\n\nc\r\rd\t\t . BLA", 15, "a b c d ."}, + {"Package poems by T.S.Eliot. To rhyme...", 27, "Package poems by T.S.Eliot."}, + {"Package poems by T. S. Eliot. To rhyme...", 29, "Package poems by T. S. Eliot."}, + {"foo implements the foo ABI. The foo ABI is...", 27, "foo implements the foo ABI."}, + {"Package\nfoo. ..", 12, "Package foo."}, + {"P . Q.", 3, "P ."}, + {"P. Q. ", 8, "P. Q."}, + {"Package Καλημέρα κόσμε.", 36, "Package Καλημέρα κόσμε."}, + {"Package こんにちは 世界\n", 31, "Package こんにちは 世界"}, + {"Package こんにちは。世界", 26, "Package こんにちは。"}, + {"Package 안녕.世界", 17, "Package 안녕."}, + {"Package foo does bar.", 21, "Package foo does bar."}, + {"Copyright 2012 Google, Inc. Package foo does bar.", 27, ""}, + {"All Rights reserved. Package foo does bar.", 20, ""}, + {"All rights reserved. Package foo does bar.", 20, ""}, + {"Authors: foo@bar.com. Package foo does bar.", 21, ""}, + {"typically invoked as ``go tool asm'',", 37, "typically invoked as " + ulquo + "go tool asm" + urquo + ","}, +} + +func TestSynopsis(t *testing.T) { + for _, e := range tests { + fsl := firstSentenceLen(e.txt) + if fsl != e.fsl { + t.Errorf("got fsl = %d; want %d for %q\n", fsl, e.fsl, e.txt) + } + syn := Synopsis(e.txt) + if syn != e.syn { + t.Errorf("got syn = %q; want %q for %q\n", syn, e.syn, e.txt) + } + } +} diff --git a/src/go/doc/testdata/a.0.golden b/src/go/doc/testdata/a.0.golden new file mode 100644 index 0000000..7e680b8 --- /dev/null +++ b/src/go/doc/testdata/a.0.golden @@ -0,0 +1,52 @@ +// comment 0 comment 1 +PACKAGE a + +IMPORTPATH + testdata/a + +FILENAMES + testdata/a0.go + testdata/a1.go + +BUGS .Bugs is now deprecated, please use .Notes instead + bug0 + + bug1 + + +BUGS +BUG(uid) bug0 + +BUG(uid) bug1 + + +NOTES +NOTE(uid) + +NOTE(foo) 1 of 4 - this is the first line of note 1 + - note 1 continues on this 2nd line + - note 1 continues on this 3rd line + +NOTE(foo) 2 of 4 + +NOTE(bar) 3 of 4 + +NOTE(bar) 4 of 4 + - this is the last line of note 4 + +NOTE(bam) This note which contains a (parenthesized) subphrase + must appear in its entirety. + +NOTE(xxx) The ':' after the marker and uid is optional. + + +SECBUGS +SECBUG(uid) sec hole 0 + need to fix asap + + +TODOS +TODO(uid) todo0 + +TODO(uid) todo1 + diff --git a/src/go/doc/testdata/a.1.golden b/src/go/doc/testdata/a.1.golden new file mode 100644 index 0000000..7e680b8 --- /dev/null +++ b/src/go/doc/testdata/a.1.golden @@ -0,0 +1,52 @@ +// comment 0 comment 1 +PACKAGE a + +IMPORTPATH + testdata/a + +FILENAMES + testdata/a0.go + testdata/a1.go + +BUGS .Bugs is now deprecated, please use .Notes instead + bug0 + + bug1 + + +BUGS +BUG(uid) bug0 + +BUG(uid) bug1 + + +NOTES +NOTE(uid) + +NOTE(foo) 1 of 4 - this is the first line of note 1 + - note 1 continues on this 2nd line + - note 1 continues on this 3rd line + +NOTE(foo) 2 of 4 + +NOTE(bar) 3 of 4 + +NOTE(bar) 4 of 4 + - this is the last line of note 4 + +NOTE(bam) This note which contains a (parenthesized) subphrase + must appear in its entirety. + +NOTE(xxx) The ':' after the marker and uid is optional. + + +SECBUGS +SECBUG(uid) sec hole 0 + need to fix asap + + +TODOS +TODO(uid) todo0 + +TODO(uid) todo1 + diff --git a/src/go/doc/testdata/a.2.golden b/src/go/doc/testdata/a.2.golden new file mode 100644 index 0000000..7e680b8 --- /dev/null +++ b/src/go/doc/testdata/a.2.golden @@ -0,0 +1,52 @@ +// comment 0 comment 1 +PACKAGE a + +IMPORTPATH + testdata/a + +FILENAMES + testdata/a0.go + testdata/a1.go + +BUGS .Bugs is now deprecated, please use .Notes instead + bug0 + + bug1 + + +BUGS +BUG(uid) bug0 + +BUG(uid) bug1 + + +NOTES +NOTE(uid) + +NOTE(foo) 1 of 4 - this is the first line of note 1 + - note 1 continues on this 2nd line + - note 1 continues on this 3rd line + +NOTE(foo) 2 of 4 + +NOTE(bar) 3 of 4 + +NOTE(bar) 4 of 4 + - this is the last line of note 4 + +NOTE(bam) This note which contains a (parenthesized) subphrase + must appear in its entirety. + +NOTE(xxx) The ':' after the marker and uid is optional. + + +SECBUGS +SECBUG(uid) sec hole 0 + need to fix asap + + +TODOS +TODO(uid) todo0 + +TODO(uid) todo1 + diff --git a/src/go/doc/testdata/a0.go b/src/go/doc/testdata/a0.go new file mode 100644 index 0000000..2420c8a --- /dev/null +++ b/src/go/doc/testdata/a0.go @@ -0,0 +1,40 @@ +// Copyright 2012 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. + +// comment 0 +package a + +//BUG(uid): bug0 + +//TODO(uid): todo0 + +// A note with some spaces after it, should be ignored (watch out for +// emacs modes that remove trailing whitespace). +//NOTE(uid): + +// SECBUG(uid): sec hole 0 +// need to fix asap + +// Multiple notes may be in the same comment group and should be +// recognized individually. Notes may start in the middle of a +// comment group as long as they start at the beginning of an +// individual comment. +// +// NOTE(foo): 1 of 4 - this is the first line of note 1 +// - note 1 continues on this 2nd line +// - note 1 continues on this 3rd line +// NOTE(foo): 2 of 4 +// NOTE(bar): 3 of 4 +/* NOTE(bar): 4 of 4 */ +// - this is the last line of note 4 +// +// + +// NOTE(bam): This note which contains a (parenthesized) subphrase +// must appear in its entirety. + +// NOTE(xxx) The ':' after the marker and uid is optional. + +// NOTE(): NO uid - should not show up. +// NOTE() NO uid - should not show up. diff --git a/src/go/doc/testdata/a1.go b/src/go/doc/testdata/a1.go new file mode 100644 index 0000000..9fad1e0 --- /dev/null +++ b/src/go/doc/testdata/a1.go @@ -0,0 +1,12 @@ +// Copyright 2012 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. + +// comment 1 +package a + +//BUG(uid): bug1 + +//TODO(uid): todo1 + +//TODO(): ignored diff --git a/src/go/doc/testdata/b.0.golden b/src/go/doc/testdata/b.0.golden new file mode 100644 index 0000000..9d93392 --- /dev/null +++ b/src/go/doc/testdata/b.0.golden @@ -0,0 +1,71 @@ +// +PACKAGE b + +IMPORTPATH + testdata/b + +IMPORTS + a + +FILENAMES + testdata/b.go + +CONSTANTS + // + const ( + C1 notExported = iota + C2 + + C4 + C5 + ) + + // + const C notExported = 0 + + // + const Pi = 3.14 // Pi + + +VARIABLES + // + var ( + U1, U2, U4, U5 notExported + + U7 notExported = 7 + ) + + // + var MaxInt int // MaxInt + + // + var V notExported + + // + var V1, V2, V4, V5 notExported + + +FUNCTIONS + // + func F(x int) int + + // + func F1() notExported + + // Always under the package functions list. + func NotAFactory() int + + // Associated with uint type if AllDecls is set. + func UintFactory() uint + + +TYPES + // + type T struct{} // T + + // + var V T // v + + // + func (x *T) M() + diff --git a/src/go/doc/testdata/b.1.golden b/src/go/doc/testdata/b.1.golden new file mode 100644 index 0000000..66c47b5 --- /dev/null +++ b/src/go/doc/testdata/b.1.golden @@ -0,0 +1,83 @@ +// +PACKAGE b + +IMPORTPATH + testdata/b + +IMPORTS + a + +FILENAMES + testdata/b.go + +CONSTANTS + // + const Pi = 3.14 // Pi + + +VARIABLES + // + var MaxInt int // MaxInt + + +FUNCTIONS + // + func F(x int) int + + // Always under the package functions list. + func NotAFactory() int + + +TYPES + // + type T struct{} // T + + // + var V T // v + + // + func (x *T) M() + + // + type notExported int + + // + const ( + C1 notExported = iota + C2 + c3 + C4 + C5 + ) + + // + const C notExported = 0 + + // + var ( + U1, U2, u3, U4, U5 notExported + u6 notExported + U7 notExported = 7 + ) + + // + var V notExported + + // + var V1, V2, v3, V4, V5 notExported + + // + func F1() notExported + + // + func f2() notExported + + // Should only appear if AllDecls is set. + type uint struct{} // overrides a predeclared type uint + + // Associated with uint type if AllDecls is set. + func UintFactory() uint + + // Associated with uint type if AllDecls is set. + func uintFactory() uint + diff --git a/src/go/doc/testdata/b.2.golden b/src/go/doc/testdata/b.2.golden new file mode 100644 index 0000000..9d93392 --- /dev/null +++ b/src/go/doc/testdata/b.2.golden @@ -0,0 +1,71 @@ +// +PACKAGE b + +IMPORTPATH + testdata/b + +IMPORTS + a + +FILENAMES + testdata/b.go + +CONSTANTS + // + const ( + C1 notExported = iota + C2 + + C4 + C5 + ) + + // + const C notExported = 0 + + // + const Pi = 3.14 // Pi + + +VARIABLES + // + var ( + U1, U2, U4, U5 notExported + + U7 notExported = 7 + ) + + // + var MaxInt int // MaxInt + + // + var V notExported + + // + var V1, V2, V4, V5 notExported + + +FUNCTIONS + // + func F(x int) int + + // + func F1() notExported + + // Always under the package functions list. + func NotAFactory() int + + // Associated with uint type if AllDecls is set. + func UintFactory() uint + + +TYPES + // + type T struct{} // T + + // + var V T // v + + // + func (x *T) M() + diff --git a/src/go/doc/testdata/b.go b/src/go/doc/testdata/b.go new file mode 100644 index 0000000..e50663b --- /dev/null +++ b/src/go/doc/testdata/b.go @@ -0,0 +1,58 @@ +// Copyright 2012 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 b + +import "a" + +// ---------------------------------------------------------------------------- +// Basic declarations + +const Pi = 3.14 // Pi +var MaxInt int // MaxInt +type T struct{} // T +var V T // v +func F(x int) int {} // F +func (x *T) M() {} // M + +// Corner cases: association with (presumed) predeclared types + +// Always under the package functions list. +func NotAFactory() int {} + +// Associated with uint type if AllDecls is set. +func UintFactory() uint {} + +// Associated with uint type if AllDecls is set. +func uintFactory() uint {} + +// Should only appear if AllDecls is set. +type uint struct{} // overrides a predeclared type uint + +// ---------------------------------------------------------------------------- +// Exported declarations associated with non-exported types must always be shown. + +type notExported int + +const C notExported = 0 + +const ( + C1 notExported = iota + C2 + c3 + C4 + C5 +) + +var V notExported +var V1, V2, v3, V4, V5 notExported + +var ( + U1, U2, u3, U4, U5 notExported + u6 notExported + U7 notExported = 7 +) + +func F1() notExported {} +func f2() notExported {} diff --git a/src/go/doc/testdata/benchmark.go b/src/go/doc/testdata/benchmark.go new file mode 100644 index 0000000..1d581f0 --- /dev/null +++ b/src/go/doc/testdata/benchmark.go @@ -0,0 +1,293 @@ +// Copyright 2009 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 testing + +import ( + "flag" + "fmt" + "os" + "runtime" + "time" +) + +var matchBenchmarks = flag.String("test.bench", "", "regular expression to select benchmarks to run") +var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark") + +// An internal type but exported because it is cross-package; part of the implementation +// of go test. +type InternalBenchmark struct { + Name string + F func(b *B) +} + +// B is a type passed to Benchmark functions to manage benchmark +// timing and to specify the number of iterations to run. +type B struct { + common + N int + benchmark InternalBenchmark + bytes int64 + timerOn bool + result BenchmarkResult +} + +// StartTimer starts timing a test. This function is called automatically +// before a benchmark starts, but it can also used to resume timing after +// a call to StopTimer. +func (b *B) StartTimer() { + if !b.timerOn { + b.start = time.Now() + b.timerOn = true + } +} + +// StopTimer stops timing a test. This can be used to pause the timer +// while performing complex initialization that you don't +// want to measure. +func (b *B) StopTimer() { + if b.timerOn { + b.duration += time.Now().Sub(b.start) + b.timerOn = false + } +} + +// ResetTimer sets the elapsed benchmark time to zero. +// It does not affect whether the timer is running. +func (b *B) ResetTimer() { + if b.timerOn { + b.start = time.Now() + } + b.duration = 0 +} + +// SetBytes records the number of bytes processed in a single operation. +// If this is called, the benchmark will report ns/op and MB/s. +func (b *B) SetBytes(n int64) { b.bytes = n } + +func (b *B) nsPerOp() int64 { + if b.N <= 0 { + return 0 + } + return b.duration.Nanoseconds() / int64(b.N) +} + +// runN runs a single benchmark for the specified number of iterations. +func (b *B) runN(n int) { + // Try to get a comparable environment for each run + // by clearing garbage from previous runs. + runtime.GC() + b.N = n + b.ResetTimer() + b.StartTimer() + b.benchmark.F(b) + b.StopTimer() +} + +func min(x, y int) int { + if x > y { + return y + } + return x +} + +func max(x, y int) int { + if x < y { + return y + } + return x +} + +// roundDown10 rounds a number down to the nearest power of 10. +func roundDown10(n int) int { + var tens = 0 + // tens = floor(log_10(n)) + for n > 10 { + n = n / 10 + tens++ + } + // result = 10^tens + result := 1 + for i := 0; i < tens; i++ { + result *= 10 + } + return result +} + +// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX]. +func roundUp(n int) int { + base := roundDown10(n) + if n < (2 * base) { + return 2 * base + } + if n < (5 * base) { + return 5 * base + } + return 10 * base +} + +// run times the benchmark function in a separate goroutine. +func (b *B) run() BenchmarkResult { + go b.launch() + <-b.signal + return b.result +} + +// launch launches the benchmark function. It gradually increases the number +// of benchmark iterations until the benchmark runs for a second in order +// to get a reasonable measurement. It prints timing information in this form +// testing.BenchmarkHello 100000 19 ns/op +// launch is run by the fun function as a separate goroutine. +func (b *B) launch() { + // Run the benchmark for a single iteration in case it's expensive. + n := 1 + + // Signal that we're done whether we return normally + // or by FailNow's runtime.Goexit. + defer func() { + b.signal <- b + }() + + b.runN(n) + // Run the benchmark for at least the specified amount of time. + d := *benchTime + for !b.failed && b.duration < d && n < 1e9 { + last := n + // Predict iterations/sec. + if b.nsPerOp() == 0 { + n = 1e9 + } else { + n = int(d.Nanoseconds() / b.nsPerOp()) + } + // Run more iterations than we think we'll need for a second (1.5x). + // Don't grow too fast in case we had timing errors previously. + // Be sure to run at least one more than last time. + n = max(min(n+n/2, 100*last), last+1) + // Round up to something easy to read. + n = roundUp(n) + b.runN(n) + } + b.result = BenchmarkResult{b.N, b.duration, b.bytes} +} + +// The results of a benchmark run. +type BenchmarkResult struct { + N int // The number of iterations. + T time.Duration // The total time taken. + Bytes int64 // Bytes processed in one iteration. +} + +func (r BenchmarkResult) NsPerOp() int64 { + if r.N <= 0 { + return 0 + } + return r.T.Nanoseconds() / int64(r.N) +} + +func (r BenchmarkResult) mbPerSec() float64 { + if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 { + return 0 + } + return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds() +} + +func (r BenchmarkResult) String() string { + mbs := r.mbPerSec() + mb := "" + if mbs != 0 { + mb = fmt.Sprintf("\t%7.2f MB/s", mbs) + } + nsop := r.NsPerOp() + ns := fmt.Sprintf("%10d ns/op", nsop) + if r.N > 0 && nsop < 100 { + // The format specifiers here make sure that + // the ones digits line up for all three possible formats. + if nsop < 10 { + ns = fmt.Sprintf("%13.2f ns/op", float64(r.T.Nanoseconds())/float64(r.N)) + } else { + ns = fmt.Sprintf("%12.1f ns/op", float64(r.T.Nanoseconds())/float64(r.N)) + } + } + return fmt.Sprintf("%8d\t%s%s", r.N, ns, mb) +} + +// An internal function but exported because it is cross-package; part of the implementation +// of go test. +func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) { + // If no flag was specified, don't run benchmarks. + if len(*matchBenchmarks) == 0 { + return + } + for _, Benchmark := range benchmarks { + matched, err := matchString(*matchBenchmarks, Benchmark.Name) + if err != nil { + fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.bench: %s\n", err) + os.Exit(1) + } + if !matched { + continue + } + for _, procs := range cpuList { + runtime.GOMAXPROCS(procs) + b := &B{ + common: common{ + signal: make(chan interface{}), + }, + benchmark: Benchmark, + } + benchName := Benchmark.Name + if procs != 1 { + benchName = fmt.Sprintf("%s-%d", Benchmark.Name, procs) + } + fmt.Printf("%s\t", benchName) + r := b.run() + if b.failed { + // The output could be very long here, but probably isn't. + // We print it all, regardless, because we don't want to trim the reason + // the benchmark failed. + fmt.Printf("--- FAIL: %s\n%s", benchName, b.output) + continue + } + fmt.Printf("%v\n", r) + // Unlike with tests, we ignore the -chatty flag and always print output for + // benchmarks since the output generation time will skew the results. + if len(b.output) > 0 { + b.trimOutput() + fmt.Printf("--- BENCH: %s\n%s", benchName, b.output) + } + if p := runtime.GOMAXPROCS(-1); p != procs { + fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p) + } + } + } +} + +// trimOutput shortens the output from a benchmark, which can be very long. +func (b *B) trimOutput() { + // The output is likely to appear multiple times because the benchmark + // is run multiple times, but at least it will be seen. This is not a big deal + // because benchmarks rarely print, but just in case, we trim it if it's too long. + const maxNewlines = 10 + for nlCount, j := 0, 0; j < len(b.output); j++ { + if b.output[j] == '\n' { + nlCount++ + if nlCount >= maxNewlines { + b.output = append(b.output[:j], "\n\t... [output truncated]\n"...) + break + } + } + } +} + +// Benchmark benchmarks a single function. Useful for creating +// custom benchmarks that do not use go test. +func Benchmark(f func(b *B)) BenchmarkResult { + b := &B{ + common: common{ + signal: make(chan interface{}), + }, + benchmark: InternalBenchmark{"", f}, + } + return b.run() +} diff --git a/src/go/doc/testdata/blank.0.golden b/src/go/doc/testdata/blank.0.golden new file mode 100644 index 0000000..70f2929 --- /dev/null +++ b/src/go/doc/testdata/blank.0.golden @@ -0,0 +1,62 @@ +// Package blank is a go/doc test for the handling of _. See issue ... +PACKAGE blank + +IMPORTPATH + testdata/blank + +IMPORTS + os + +FILENAMES + testdata/blank.go + +CONSTANTS + // T constants counting from unexported constants. + const ( + C1 T + C2 + + C3 + + C4 int + ) + + // Constants with a single type that is not propagated. + const ( + Default = 0644 + Useless = 0312 + WideOpen = 0777 + ) + + // Constants with an imported type that is propagated. + const ( + M1 os.FileMode + M2 + M3 + ) + + // Package constants. + const ( + I1 int + I2 + ) + + +TYPES + // S has a padding field. + type S struct { + H uint32 + + A uint8 + // contains filtered or unexported fields + } + + // + type T int + + // T constants counting from a blank constant. + const ( + T1 T + T2 + ) + diff --git a/src/go/doc/testdata/blank.1.golden b/src/go/doc/testdata/blank.1.golden new file mode 100644 index 0000000..8098cb6 --- /dev/null +++ b/src/go/doc/testdata/blank.1.golden @@ -0,0 +1,83 @@ +// Package blank is a go/doc test for the handling of _. See issue ... +PACKAGE blank + +IMPORTPATH + testdata/blank + +IMPORTS + os + +FILENAMES + testdata/blank.go + +CONSTANTS + // T constants counting from unexported constants. + const ( + tweedledee T = iota + tweedledum + C1 + C2 + alice + C3 + redQueen int = iota + C4 + ) + + // Constants with a single type that is not propagated. + const ( + zero os.FileMode = 0 + Default = 0644 + Useless = 0312 + WideOpen = 0777 + ) + + // Constants with an imported type that is propagated. + const ( + zero os.FileMode = 0 + M1 + M2 + M3 + ) + + // Package constants. + const ( + _ int = iota + I1 + I2 + ) + + // Unexported constants counting from blank iota. See issue 9615. + const ( + _ = iota + one = iota + 1 + ) + + +VARIABLES + // + var _ = T(55) + + +FUNCTIONS + // + func _() + + +TYPES + // S has a padding field. + type S struct { + H uint32 + _ uint8 + A uint8 + } + + // + type T int + + // T constants counting from a blank constant. + const ( + _ T = iota + T1 + T2 + ) + diff --git a/src/go/doc/testdata/blank.2.golden b/src/go/doc/testdata/blank.2.golden new file mode 100644 index 0000000..70f2929 --- /dev/null +++ b/src/go/doc/testdata/blank.2.golden @@ -0,0 +1,62 @@ +// Package blank is a go/doc test for the handling of _. See issue ... +PACKAGE blank + +IMPORTPATH + testdata/blank + +IMPORTS + os + +FILENAMES + testdata/blank.go + +CONSTANTS + // T constants counting from unexported constants. + const ( + C1 T + C2 + + C3 + + C4 int + ) + + // Constants with a single type that is not propagated. + const ( + Default = 0644 + Useless = 0312 + WideOpen = 0777 + ) + + // Constants with an imported type that is propagated. + const ( + M1 os.FileMode + M2 + M3 + ) + + // Package constants. + const ( + I1 int + I2 + ) + + +TYPES + // S has a padding field. + type S struct { + H uint32 + + A uint8 + // contains filtered or unexported fields + } + + // + type T int + + // T constants counting from a blank constant. + const ( + T1 T + T2 + ) + diff --git a/src/go/doc/testdata/blank.go b/src/go/doc/testdata/blank.go new file mode 100644 index 0000000..5ea6186 --- /dev/null +++ b/src/go/doc/testdata/blank.go @@ -0,0 +1,75 @@ +// Copyright 2014 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 blank is a go/doc test for the handling of _. +// See issue 5397. +package blank + +import "os" + +type T int + +// T constants counting from a blank constant. +const ( + _ T = iota + T1 + T2 +) + +// T constants counting from unexported constants. +const ( + tweedledee T = iota + tweedledum + C1 + C2 + alice + C3 + redQueen int = iota + C4 +) + +// Constants with a single type that is not propagated. +const ( + zero os.FileMode = 0 + Default = 0644 + Useless = 0312 + WideOpen = 0777 +) + +// Constants with an imported type that is propagated. +const ( + zero os.FileMode = 0 + M1 + M2 + M3 +) + +// Package constants. +const ( + _ int = iota + I1 + I2 +) + +// Unexported constants counting from blank iota. +// See issue 9615. +const ( + _ = iota + one = iota + 1 +) + +// Blanks not in doc output: + +// S has a padding field. +type S struct { + H uint32 + _ uint8 + A uint8 +} + +func _() {} + +type _ T + +var _ = T(55) diff --git a/src/go/doc/testdata/bugpara.0.golden b/src/go/doc/testdata/bugpara.0.golden new file mode 100644 index 0000000..5804859 --- /dev/null +++ b/src/go/doc/testdata/bugpara.0.golden @@ -0,0 +1,20 @@ +// +PACKAGE bugpara + +IMPORTPATH + testdata/bugpara + +FILENAMES + testdata/bugpara.go + +BUGS .Bugs is now deprecated, please use .Notes instead + Sometimes bugs have multiple paragraphs. + + Like this one. + + +BUGS +BUG(rsc) Sometimes bugs have multiple paragraphs. + + Like this one. + diff --git a/src/go/doc/testdata/bugpara.1.golden b/src/go/doc/testdata/bugpara.1.golden new file mode 100644 index 0000000..5804859 --- /dev/null +++ b/src/go/doc/testdata/bugpara.1.golden @@ -0,0 +1,20 @@ +// +PACKAGE bugpara + +IMPORTPATH + testdata/bugpara + +FILENAMES + testdata/bugpara.go + +BUGS .Bugs is now deprecated, please use .Notes instead + Sometimes bugs have multiple paragraphs. + + Like this one. + + +BUGS +BUG(rsc) Sometimes bugs have multiple paragraphs. + + Like this one. + diff --git a/src/go/doc/testdata/bugpara.2.golden b/src/go/doc/testdata/bugpara.2.golden new file mode 100644 index 0000000..5804859 --- /dev/null +++ b/src/go/doc/testdata/bugpara.2.golden @@ -0,0 +1,20 @@ +// +PACKAGE bugpara + +IMPORTPATH + testdata/bugpara + +FILENAMES + testdata/bugpara.go + +BUGS .Bugs is now deprecated, please use .Notes instead + Sometimes bugs have multiple paragraphs. + + Like this one. + + +BUGS +BUG(rsc) Sometimes bugs have multiple paragraphs. + + Like this one. + diff --git a/src/go/doc/testdata/bugpara.go b/src/go/doc/testdata/bugpara.go new file mode 100644 index 0000000..0360a6f --- /dev/null +++ b/src/go/doc/testdata/bugpara.go @@ -0,0 +1,9 @@ +// Copyright 2013 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 bugpara + +// BUG(rsc): Sometimes bugs have multiple paragraphs. +// +// Like this one. diff --git a/src/go/doc/testdata/c.0.golden b/src/go/doc/testdata/c.0.golden new file mode 100644 index 0000000..e21959b --- /dev/null +++ b/src/go/doc/testdata/c.0.golden @@ -0,0 +1,48 @@ +// +PACKAGE c + +IMPORTPATH + testdata/c + +IMPORTS + a + +FILENAMES + testdata/c.go + +TYPES + // A (should see this) + type A struct{} + + // B (should see this) + type B struct{} + + // C (should see this) + type C struct{} + + // D (should see this) + type D struct{} + + // E1 (should see this) + type E1 struct{} + + // E (should see this for E2 and E3) + type E2 struct{} + + // E (should see this for E2 and E3) + type E3 struct{} + + // E4 (should see this) + type E4 struct{} + + // + type T1 struct{} + + // + func (t1 *T1) M() + + // T2 must not show methods of local T1 + type T2 struct { + a.T1 // not the same as locally declared T1 + } + diff --git a/src/go/doc/testdata/c.1.golden b/src/go/doc/testdata/c.1.golden new file mode 100644 index 0000000..e21959b --- /dev/null +++ b/src/go/doc/testdata/c.1.golden @@ -0,0 +1,48 @@ +// +PACKAGE c + +IMPORTPATH + testdata/c + +IMPORTS + a + +FILENAMES + testdata/c.go + +TYPES + // A (should see this) + type A struct{} + + // B (should see this) + type B struct{} + + // C (should see this) + type C struct{} + + // D (should see this) + type D struct{} + + // E1 (should see this) + type E1 struct{} + + // E (should see this for E2 and E3) + type E2 struct{} + + // E (should see this for E2 and E3) + type E3 struct{} + + // E4 (should see this) + type E4 struct{} + + // + type T1 struct{} + + // + func (t1 *T1) M() + + // T2 must not show methods of local T1 + type T2 struct { + a.T1 // not the same as locally declared T1 + } + diff --git a/src/go/doc/testdata/c.2.golden b/src/go/doc/testdata/c.2.golden new file mode 100644 index 0000000..e21959b --- /dev/null +++ b/src/go/doc/testdata/c.2.golden @@ -0,0 +1,48 @@ +// +PACKAGE c + +IMPORTPATH + testdata/c + +IMPORTS + a + +FILENAMES + testdata/c.go + +TYPES + // A (should see this) + type A struct{} + + // B (should see this) + type B struct{} + + // C (should see this) + type C struct{} + + // D (should see this) + type D struct{} + + // E1 (should see this) + type E1 struct{} + + // E (should see this for E2 and E3) + type E2 struct{} + + // E (should see this for E2 and E3) + type E3 struct{} + + // E4 (should see this) + type E4 struct{} + + // + type T1 struct{} + + // + func (t1 *T1) M() + + // T2 must not show methods of local T1 + type T2 struct { + a.T1 // not the same as locally declared T1 + } + diff --git a/src/go/doc/testdata/c.go b/src/go/doc/testdata/c.go new file mode 100644 index 0000000..e0f3919 --- /dev/null +++ b/src/go/doc/testdata/c.go @@ -0,0 +1,62 @@ +// Copyright 2012 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 c + +import "a" + +// ---------------------------------------------------------------------------- +// Test that empty declarations don't cause problems + +const () + +type () + +var () + +// ---------------------------------------------------------------------------- +// Test that types with documentation on both, the Decl and the Spec node +// are handled correctly. + +// A (should see this) +type A struct{} + +// B (should see this) +type ( + B struct{} +) + +type ( + // C (should see this) + C struct{} +) + +// D (should not see this) +type ( + // D (should see this) + D struct{} +) + +// E (should see this for E2 and E3) +type ( + // E1 (should see this) + E1 struct{} + E2 struct{} + E3 struct{} + // E4 (should see this) + E4 struct{} +) + +// ---------------------------------------------------------------------------- +// Test that local and imported types are different when +// handling anonymous fields. + +type T1 struct{} + +func (t1 *T1) M() {} + +// T2 must not show methods of local T1 +type T2 struct { + a.T1 // not the same as locally declared T1 +} diff --git a/src/go/doc/testdata/d.0.golden b/src/go/doc/testdata/d.0.golden new file mode 100644 index 0000000..c005199 --- /dev/null +++ b/src/go/doc/testdata/d.0.golden @@ -0,0 +1,104 @@ +// +PACKAGE d + +IMPORTPATH + testdata/d + +FILENAMES + testdata/d1.go + testdata/d2.go + +CONSTANTS + // CBx constants should appear before CAx constants. + const ( + CB2 = iota // before CB1 + CB1 // before CB0 + CB0 // at end + ) + + // CAx constants should appear after CBx constants. + const ( + CA2 = iota // before CA1 + CA1 // before CA0 + CA0 // at end + ) + + // C0 should be first. + const C0 = 0 + + // C1 should be second. + const C1 = 1 + + // C2 should be third. + const C2 = 2 + + // + const ( + // Single const declarations inside ()'s are considered ungrouped + // and show up in sorted order. + Cungrouped = 0 + ) + + +VARIABLES + // VBx variables should appear before VAx variables. + var ( + VB2 int // before VB1 + VB1 int // before VB0 + VB0 int // at end + ) + + // VAx variables should appear after VBx variables. + var ( + VA2 int // before VA1 + VA1 int // before VA0 + VA0 int // at end + ) + + // V0 should be first. + var V0 uintptr + + // V1 should be second. + var V1 uint + + // V2 should be third. + var V2 int + + // + var ( + // Single var declarations inside ()'s are considered ungrouped + // and show up in sorted order. + Vungrouped = 0 + ) + + +FUNCTIONS + // F0 should be first. + func F0() + + // F1 should be second. + func F1() + + // F2 should be third. + func F2() + + +TYPES + // T0 should be first. + type T0 struct{} + + // T1 should be second. + type T1 struct{} + + // T2 should be third. + type T2 struct{} + + // TG0 should be first. + type TG0 struct{} + + // TG1 should be second. + type TG1 struct{} + + // TG2 should be third. + type TG2 struct{} + diff --git a/src/go/doc/testdata/d.1.golden b/src/go/doc/testdata/d.1.golden new file mode 100644 index 0000000..c005199 --- /dev/null +++ b/src/go/doc/testdata/d.1.golden @@ -0,0 +1,104 @@ +// +PACKAGE d + +IMPORTPATH + testdata/d + +FILENAMES + testdata/d1.go + testdata/d2.go + +CONSTANTS + // CBx constants should appear before CAx constants. + const ( + CB2 = iota // before CB1 + CB1 // before CB0 + CB0 // at end + ) + + // CAx constants should appear after CBx constants. + const ( + CA2 = iota // before CA1 + CA1 // before CA0 + CA0 // at end + ) + + // C0 should be first. + const C0 = 0 + + // C1 should be second. + const C1 = 1 + + // C2 should be third. + const C2 = 2 + + // + const ( + // Single const declarations inside ()'s are considered ungrouped + // and show up in sorted order. + Cungrouped = 0 + ) + + +VARIABLES + // VBx variables should appear before VAx variables. + var ( + VB2 int // before VB1 + VB1 int // before VB0 + VB0 int // at end + ) + + // VAx variables should appear after VBx variables. + var ( + VA2 int // before VA1 + VA1 int // before VA0 + VA0 int // at end + ) + + // V0 should be first. + var V0 uintptr + + // V1 should be second. + var V1 uint + + // V2 should be third. + var V2 int + + // + var ( + // Single var declarations inside ()'s are considered ungrouped + // and show up in sorted order. + Vungrouped = 0 + ) + + +FUNCTIONS + // F0 should be first. + func F0() + + // F1 should be second. + func F1() + + // F2 should be third. + func F2() + + +TYPES + // T0 should be first. + type T0 struct{} + + // T1 should be second. + type T1 struct{} + + // T2 should be third. + type T2 struct{} + + // TG0 should be first. + type TG0 struct{} + + // TG1 should be second. + type TG1 struct{} + + // TG2 should be third. + type TG2 struct{} + diff --git a/src/go/doc/testdata/d.2.golden b/src/go/doc/testdata/d.2.golden new file mode 100644 index 0000000..c005199 --- /dev/null +++ b/src/go/doc/testdata/d.2.golden @@ -0,0 +1,104 @@ +// +PACKAGE d + +IMPORTPATH + testdata/d + +FILENAMES + testdata/d1.go + testdata/d2.go + +CONSTANTS + // CBx constants should appear before CAx constants. + const ( + CB2 = iota // before CB1 + CB1 // before CB0 + CB0 // at end + ) + + // CAx constants should appear after CBx constants. + const ( + CA2 = iota // before CA1 + CA1 // before CA0 + CA0 // at end + ) + + // C0 should be first. + const C0 = 0 + + // C1 should be second. + const C1 = 1 + + // C2 should be third. + const C2 = 2 + + // + const ( + // Single const declarations inside ()'s are considered ungrouped + // and show up in sorted order. + Cungrouped = 0 + ) + + +VARIABLES + // VBx variables should appear before VAx variables. + var ( + VB2 int // before VB1 + VB1 int // before VB0 + VB0 int // at end + ) + + // VAx variables should appear after VBx variables. + var ( + VA2 int // before VA1 + VA1 int // before VA0 + VA0 int // at end + ) + + // V0 should be first. + var V0 uintptr + + // V1 should be second. + var V1 uint + + // V2 should be third. + var V2 int + + // + var ( + // Single var declarations inside ()'s are considered ungrouped + // and show up in sorted order. + Vungrouped = 0 + ) + + +FUNCTIONS + // F0 should be first. + func F0() + + // F1 should be second. + func F1() + + // F2 should be third. + func F2() + + +TYPES + // T0 should be first. + type T0 struct{} + + // T1 should be second. + type T1 struct{} + + // T2 should be third. + type T2 struct{} + + // TG0 should be first. + type TG0 struct{} + + // TG1 should be second. + type TG1 struct{} + + // TG2 should be third. + type TG2 struct{} + diff --git a/src/go/doc/testdata/d1.go b/src/go/doc/testdata/d1.go new file mode 100644 index 0000000..ebd6941 --- /dev/null +++ b/src/go/doc/testdata/d1.go @@ -0,0 +1,57 @@ +// Copyright 2012 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. + +// Test cases for sort order of declarations. + +package d + +// C2 should be third. +const C2 = 2 + +// V2 should be third. +var V2 int + +// CBx constants should appear before CAx constants. +const ( + CB2 = iota // before CB1 + CB1 // before CB0 + CB0 // at end +) + +// VBx variables should appear before VAx variables. +var ( + VB2 int // before VB1 + VB1 int // before VB0 + VB0 int // at end +) + +const ( + // Single const declarations inside ()'s are considered ungrouped + // and show up in sorted order. + Cungrouped = 0 +) + +var ( + // Single var declarations inside ()'s are considered ungrouped + // and show up in sorted order. + Vungrouped = 0 +) + +// T2 should be third. +type T2 struct{} + +// Grouped types are sorted nevertheless. +type ( + // TG2 should be third. + TG2 struct{} + + // TG1 should be second. + TG1 struct{} + + // TG0 should be first. + TG0 struct{} +) + +// F2 should be third. +func F2() {} diff --git a/src/go/doc/testdata/d2.go b/src/go/doc/testdata/d2.go new file mode 100644 index 0000000..2f56f4f --- /dev/null +++ b/src/go/doc/testdata/d2.go @@ -0,0 +1,45 @@ +// Copyright 2012 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. + +// Test cases for sort order of declarations. + +package d + +// C1 should be second. +const C1 = 1 + +// C0 should be first. +const C0 = 0 + +// V1 should be second. +var V1 uint + +// V0 should be first. +var V0 uintptr + +// CAx constants should appear after CBx constants. +const ( + CA2 = iota // before CA1 + CA1 // before CA0 + CA0 // at end +) + +// VAx variables should appear after VBx variables. +var ( + VA2 int // before VA1 + VA1 int // before VA0 + VA0 int // at end +) + +// T1 should be second. +type T1 struct{} + +// T0 should be first. +type T0 struct{} + +// F1 should be second. +func F1() {} + +// F0 should be first. +func F0() {} diff --git a/src/go/doc/testdata/e.0.golden b/src/go/doc/testdata/e.0.golden new file mode 100644 index 0000000..6987e58 --- /dev/null +++ b/src/go/doc/testdata/e.0.golden @@ -0,0 +1,109 @@ +// The package e is a go/doc test for embedded methods. +PACKAGE e + +IMPORTPATH + testdata/e + +FILENAMES + testdata/e.go + +TYPES + // T1 has no embedded (level 1) M method due to conflict. + type T1 struct { + // contains filtered or unexported fields + } + + // T2 has only M as top-level method. + type T2 struct { + // contains filtered or unexported fields + } + + // T2.M should appear as method of T2. + func (T2) M() + + // T3 has only M as top-level method. + type T3 struct { + // contains filtered or unexported fields + } + + // T3.M should appear as method of T3. + func (T3) M() + + // + type T4 struct{} + + // T4.M should appear as method of T5 only if AllMethods is set. + func (*T4) M() + + // + type T5 struct { + T4 + } + + // + type U1 struct { + *U1 + } + + // U1.M should appear as method of U1. + func (*U1) M() + + // + type U2 struct { + *U3 + } + + // U2.M should appear as method of U2 and as method of U3 only if ... + func (*U2) M() + + // + type U3 struct { + *U2 + } + + // U3.N should appear as method of U3 and as method of U2 only if ... + func (*U3) N() + + // + type U4 struct { + // contains filtered or unexported fields + } + + // U4.M should appear as method of U4. + func (*U4) M() + + // + type V1 struct { + *V2 + *V5 + } + + // + type V2 struct { + *V3 + } + + // + type V3 struct { + *V4 + } + + // + type V4 struct { + *V5 + } + + // V4.M should appear as method of V2 and V3 if AllMethods is set. + func (*V4) M() + + // + type V5 struct { + *V6 + } + + // + type V6 struct{} + + // V6.M should appear as method of V1 and V5 if AllMethods is set. + func (*V6) M() + diff --git a/src/go/doc/testdata/e.1.golden b/src/go/doc/testdata/e.1.golden new file mode 100644 index 0000000..cbe22e0 --- /dev/null +++ b/src/go/doc/testdata/e.1.golden @@ -0,0 +1,144 @@ +// The package e is a go/doc test for embedded methods. +PACKAGE e + +IMPORTPATH + testdata/e + +FILENAMES + testdata/e.go + +TYPES + // T1 has no embedded (level 1) M method due to conflict. + type T1 struct { + t1 + t2 + } + + // T2 has only M as top-level method. + type T2 struct { + t1 + } + + // T2.M should appear as method of T2. + func (T2) M() + + // T3 has only M as top-level method. + type T3 struct { + t1e + t2e + } + + // T3.M should appear as method of T3. + func (T3) M() + + // + type T4 struct{} + + // T4.M should appear as method of T5 only if AllMethods is set. + func (*T4) M() + + // + type T5 struct { + T4 + } + + // + type U1 struct { + *U1 + } + + // U1.M should appear as method of U1. + func (*U1) M() + + // + type U2 struct { + *U3 + } + + // U2.M should appear as method of U2 and as method of U3 only if ... + func (*U2) M() + + // + type U3 struct { + *U2 + } + + // U3.N should appear as method of U3 and as method of U2 only if ... + func (*U3) N() + + // + type U4 struct { + *u5 + } + + // U4.M should appear as method of U4. + func (*U4) M() + + // + type V1 struct { + *V2 + *V5 + } + + // + type V2 struct { + *V3 + } + + // + type V3 struct { + *V4 + } + + // + type V4 struct { + *V5 + } + + // V4.M should appear as method of V2 and V3 if AllMethods is set. + func (*V4) M() + + // + type V5 struct { + *V6 + } + + // + type V6 struct{} + + // V6.M should appear as method of V1 and V5 if AllMethods is set. + func (*V6) M() + + // + type t1 struct{} + + // t1.M should not appear as method in a Tx type. + func (t1) M() + + // + type t1e struct { + t1 + } + + // t1.M should not appear as method in a Tx type. + func (t1e) M() + + // + type t2 struct{} + + // t2.M should not appear as method in a Tx type. + func (t2) M() + + // + type t2e struct { + t2 + } + + // t2.M should not appear as method in a Tx type. + func (t2e) M() + + // + type u5 struct { + *U4 + } + diff --git a/src/go/doc/testdata/e.2.golden b/src/go/doc/testdata/e.2.golden new file mode 100644 index 0000000..e7b05e8 --- /dev/null +++ b/src/go/doc/testdata/e.2.golden @@ -0,0 +1,130 @@ +// The package e is a go/doc test for embedded methods. +PACKAGE e + +IMPORTPATH + testdata/e + +FILENAMES + testdata/e.go + +TYPES + // T1 has no embedded (level 1) M method due to conflict. + type T1 struct { + // contains filtered or unexported fields + } + + // T2 has only M as top-level method. + type T2 struct { + // contains filtered or unexported fields + } + + // T2.M should appear as method of T2. + func (T2) M() + + // T3 has only M as top-level method. + type T3 struct { + // contains filtered or unexported fields + } + + // T3.M should appear as method of T3. + func (T3) M() + + // + type T4 struct{} + + // T4.M should appear as method of T5 only if AllMethods is set. + func (*T4) M() + + // + type T5 struct { + T4 + } + + // T4.M should appear as method of T5 only if AllMethods is set. + func (*T5) M() + + // + type U1 struct { + *U1 + } + + // U1.M should appear as method of U1. + func (*U1) M() + + // + type U2 struct { + *U3 + } + + // U2.M should appear as method of U2 and as method of U3 only if ... + func (*U2) M() + + // U3.N should appear as method of U3 and as method of U2 only if ... + func (U2) N() + + // + type U3 struct { + *U2 + } + + // U2.M should appear as method of U2 and as method of U3 only if ... + func (U3) M() + + // U3.N should appear as method of U3 and as method of U2 only if ... + func (*U3) N() + + // + type U4 struct { + // contains filtered or unexported fields + } + + // U4.M should appear as method of U4. + func (*U4) M() + + // + type V1 struct { + *V2 + *V5 + } + + // V6.M should appear as method of V1 and V5 if AllMethods is set. + func (V1) M() + + // + type V2 struct { + *V3 + } + + // V4.M should appear as method of V2 and V3 if AllMethods is set. + func (V2) M() + + // + type V3 struct { + *V4 + } + + // V4.M should appear as method of V2 and V3 if AllMethods is set. + func (V3) M() + + // + type V4 struct { + *V5 + } + + // V4.M should appear as method of V2 and V3 if AllMethods is set. + func (*V4) M() + + // + type V5 struct { + *V6 + } + + // V6.M should appear as method of V1 and V5 if AllMethods is set. + func (V5) M() + + // + type V6 struct{} + + // V6.M should appear as method of V1 and V5 if AllMethods is set. + func (*V6) M() + diff --git a/src/go/doc/testdata/e.go b/src/go/doc/testdata/e.go new file mode 100644 index 0000000..ec432e3 --- /dev/null +++ b/src/go/doc/testdata/e.go @@ -0,0 +1,147 @@ +// Copyright 2012 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. + +// The package e is a go/doc test for embedded methods. +package e + +// ---------------------------------------------------------------------------- +// Conflicting methods M must not show up. + +type t1 struct{} + +// t1.M should not appear as method in a Tx type. +func (t1) M() {} + +type t2 struct{} + +// t2.M should not appear as method in a Tx type. +func (t2) M() {} + +// T1 has no embedded (level 1) M method due to conflict. +type T1 struct { + t1 + t2 +} + +// ---------------------------------------------------------------------------- +// Higher-level method M wins over lower-level method M. + +// T2 has only M as top-level method. +type T2 struct { + t1 +} + +// T2.M should appear as method of T2. +func (T2) M() {} + +// ---------------------------------------------------------------------------- +// Higher-level method M wins over lower-level conflicting methods M. + +type t1e struct { + t1 +} + +type t2e struct { + t2 +} + +// T3 has only M as top-level method. +type T3 struct { + t1e + t2e +} + +// T3.M should appear as method of T3. +func (T3) M() {} + +// ---------------------------------------------------------------------------- +// Don't show conflicting methods M embedded via an exported and non-exported +// type. + +// T1 has no embedded (level 1) M method due to conflict. +type T4 struct { + t2 + T2 +} + +// ---------------------------------------------------------------------------- +// Don't show embedded methods of exported anonymous fields unless AllMethods +// is set. + +type T4 struct{} + +// T4.M should appear as method of T5 only if AllMethods is set. +func (*T4) M() {} + +type T5 struct { + T4 +} + +// ---------------------------------------------------------------------------- +// Recursive type declarations must not lead to endless recursion. + +type U1 struct { + *U1 +} + +// U1.M should appear as method of U1. +func (*U1) M() {} + +type U2 struct { + *U3 +} + +// U2.M should appear as method of U2 and as method of U3 only if AllMethods is set. +func (*U2) M() {} + +type U3 struct { + *U2 +} + +// U3.N should appear as method of U3 and as method of U2 only if AllMethods is set. +func (*U3) N() {} + +type U4 struct { + *u5 +} + +// U4.M should appear as method of U4. +func (*U4) M() {} + +type u5 struct { + *U4 +} + +// ---------------------------------------------------------------------------- +// A higher-level embedded type (and its methods) wins over the same type (and +// its methods) embedded at a lower level. + +type V1 struct { + *V2 + *V5 +} + +type V2 struct { + *V3 +} + +type V3 struct { + *V4 +} + +type V4 struct { + *V5 +} + +type V5 struct { + *V6 +} + +type V6 struct{} + +// V4.M should appear as method of V2 and V3 if AllMethods is set. +func (*V4) M() {} + +// V6.M should appear as method of V1 and V5 if AllMethods is set. +func (*V6) M() {} diff --git a/src/go/doc/testdata/error1.0.golden b/src/go/doc/testdata/error1.0.golden new file mode 100644 index 0000000..6c6fe5d --- /dev/null +++ b/src/go/doc/testdata/error1.0.golden @@ -0,0 +1,30 @@ +// +PACKAGE error1 + +IMPORTPATH + testdata/error1 + +FILENAMES + testdata/error1.go + +TYPES + // + type I0 interface { + // When embedded, the predeclared error interface + // must remain visible in interface types. + error + } + + // + type S0 struct { + // contains filtered or unexported fields + } + + // + type T0 struct { + ExportedField interface { + // error should be visible + error + } + } + diff --git a/src/go/doc/testdata/error1.1.golden b/src/go/doc/testdata/error1.1.golden new file mode 100644 index 0000000..a8dc2e7 --- /dev/null +++ b/src/go/doc/testdata/error1.1.golden @@ -0,0 +1,32 @@ +// +PACKAGE error1 + +IMPORTPATH + testdata/error1 + +FILENAMES + testdata/error1.go + +TYPES + // + type I0 interface { + // When embedded, the predeclared error interface + // must remain visible in interface types. + error + } + + // + type S0 struct { + // In struct types, an embedded error must only be visible + // if AllDecls is set. + error + } + + // + type T0 struct { + ExportedField interface { + // error should be visible + error + } + } + diff --git a/src/go/doc/testdata/error1.2.golden b/src/go/doc/testdata/error1.2.golden new file mode 100644 index 0000000..6c6fe5d --- /dev/null +++ b/src/go/doc/testdata/error1.2.golden @@ -0,0 +1,30 @@ +// +PACKAGE error1 + +IMPORTPATH + testdata/error1 + +FILENAMES + testdata/error1.go + +TYPES + // + type I0 interface { + // When embedded, the predeclared error interface + // must remain visible in interface types. + error + } + + // + type S0 struct { + // contains filtered or unexported fields + } + + // + type T0 struct { + ExportedField interface { + // error should be visible + error + } + } + diff --git a/src/go/doc/testdata/error1.go b/src/go/doc/testdata/error1.go new file mode 100644 index 0000000..3c777a7 --- /dev/null +++ b/src/go/doc/testdata/error1.go @@ -0,0 +1,24 @@ +// Copyright 2012 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 error1 + +type I0 interface { + // When embedded, the predeclared error interface + // must remain visible in interface types. + error +} + +type T0 struct { + ExportedField interface { + // error should be visible + error + } +} + +type S0 struct { + // In struct types, an embedded error must only be visible + // if AllDecls is set. + error +} diff --git a/src/go/doc/testdata/error2.0.golden b/src/go/doc/testdata/error2.0.golden new file mode 100644 index 0000000..dedfe41 --- /dev/null +++ b/src/go/doc/testdata/error2.0.golden @@ -0,0 +1,27 @@ +// +PACKAGE error2 + +IMPORTPATH + testdata/error2 + +FILENAMES + testdata/error2.go + +TYPES + // + type I0 interface { + // contains filtered or unexported methods + } + + // + type S0 struct { + // contains filtered or unexported fields + } + + // + type T0 struct { + ExportedField interface { + // contains filtered or unexported methods + } + } + diff --git a/src/go/doc/testdata/error2.1.golden b/src/go/doc/testdata/error2.1.golden new file mode 100644 index 0000000..dbcc1b0 --- /dev/null +++ b/src/go/doc/testdata/error2.1.golden @@ -0,0 +1,37 @@ +// +PACKAGE error2 + +IMPORTPATH + testdata/error2 + +FILENAMES + testdata/error2.go + +TYPES + // + type I0 interface { + // When embedded, the locally-declared error interface + // is only visible if all declarations are shown. + error + } + + // + type S0 struct { + // In struct types, an embedded error must only be visible + // if AllDecls is set. + error + } + + // + type T0 struct { + ExportedField interface { + // error should not be visible + error + } + } + + // This error declaration shadows the predeclared error type. + type error interface { + Error() string + } + diff --git a/src/go/doc/testdata/error2.2.golden b/src/go/doc/testdata/error2.2.golden new file mode 100644 index 0000000..dedfe41 --- /dev/null +++ b/src/go/doc/testdata/error2.2.golden @@ -0,0 +1,27 @@ +// +PACKAGE error2 + +IMPORTPATH + testdata/error2 + +FILENAMES + testdata/error2.go + +TYPES + // + type I0 interface { + // contains filtered or unexported methods + } + + // + type S0 struct { + // contains filtered or unexported fields + } + + // + type T0 struct { + ExportedField interface { + // contains filtered or unexported methods + } + } + diff --git a/src/go/doc/testdata/error2.go b/src/go/doc/testdata/error2.go new file mode 100644 index 0000000..6ee96c2 --- /dev/null +++ b/src/go/doc/testdata/error2.go @@ -0,0 +1,29 @@ +// Copyright 2012 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 error2 + +type I0 interface { + // When embedded, the locally-declared error interface + // is only visible if all declarations are shown. + error +} + +type T0 struct { + ExportedField interface { + // error should not be visible + error + } +} + +type S0 struct { + // In struct types, an embedded error must only be visible + // if AllDecls is set. + error +} + +// This error declaration shadows the predeclared error type. +type error interface { + Error() string +} diff --git a/src/go/doc/testdata/example.go b/src/go/doc/testdata/example.go new file mode 100644 index 0000000..fdeda13 --- /dev/null +++ b/src/go/doc/testdata/example.go @@ -0,0 +1,81 @@ +// Copyright 2009 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 testing + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" + "time" +) + +type InternalExample struct { + Name string + F func() + Output string +} + +func RunExamples(examples []InternalExample) (ok bool) { + ok = true + + var eg InternalExample + + stdout, stderr := os.Stdout, os.Stderr + defer func() { + os.Stdout, os.Stderr = stdout, stderr + if e := recover(); e != nil { + fmt.Printf("--- FAIL: %s\npanic: %v\n", eg.Name, e) + os.Exit(1) + } + }() + + for _, eg = range examples { + if *chatty { + fmt.Printf("=== RUN: %s\n", eg.Name) + } + + // capture stdout and stderr + r, w, err := os.Pipe() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Stdout, os.Stderr = w, w + outC := make(chan string) + go func() { + buf := new(bytes.Buffer) + _, err := io.Copy(buf, r) + if err != nil { + fmt.Fprintf(stderr, "testing: copying pipe: %v\n", err) + os.Exit(1) + } + outC <- buf.String() + }() + + // run example + t0 := time.Now() + eg.F() + dt := time.Now().Sub(t0) + + // close pipe, restore stdout/stderr, get output + w.Close() + os.Stdout, os.Stderr = stdout, stderr + out := <-outC + + // report any errors + tstr := fmt.Sprintf("(%.2f seconds)", dt.Seconds()) + if g, e := strings.TrimSpace(out), strings.TrimSpace(eg.Output); g != e { + fmt.Printf("--- FAIL: %s %s\ngot:\n%s\nwant:\n%s\n", + eg.Name, tstr, g, e) + ok = false + } else if *chatty { + fmt.Printf("--- PASS: %s %s\n", eg.Name, tstr) + } + } + + return +} diff --git a/src/go/doc/testdata/f.0.golden b/src/go/doc/testdata/f.0.golden new file mode 100644 index 0000000..8175901 --- /dev/null +++ b/src/go/doc/testdata/f.0.golden @@ -0,0 +1,13 @@ +// The package f is a go/doc test for functions and factory ... +PACKAGE f + +IMPORTPATH + testdata/f + +FILENAMES + testdata/f.go + +FUNCTIONS + // Exported must always be visible. Was issue 2824. + func Exported() private + diff --git a/src/go/doc/testdata/f.1.golden b/src/go/doc/testdata/f.1.golden new file mode 100644 index 0000000..ba68e88 --- /dev/null +++ b/src/go/doc/testdata/f.1.golden @@ -0,0 +1,16 @@ +// The package f is a go/doc test for functions and factory ... +PACKAGE f + +IMPORTPATH + testdata/f + +FILENAMES + testdata/f.go + +TYPES + // + type private struct{} + + // Exported must always be visible. Was issue 2824. + func Exported() private + diff --git a/src/go/doc/testdata/f.2.golden b/src/go/doc/testdata/f.2.golden new file mode 100644 index 0000000..8175901 --- /dev/null +++ b/src/go/doc/testdata/f.2.golden @@ -0,0 +1,13 @@ +// The package f is a go/doc test for functions and factory ... +PACKAGE f + +IMPORTPATH + testdata/f + +FILENAMES + testdata/f.go + +FUNCTIONS + // Exported must always be visible. Was issue 2824. + func Exported() private + diff --git a/src/go/doc/testdata/f.go b/src/go/doc/testdata/f.go new file mode 100644 index 0000000..7e9add9 --- /dev/null +++ b/src/go/doc/testdata/f.go @@ -0,0 +1,14 @@ +// Copyright 2012 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. + +// The package f is a go/doc test for functions and factory methods. +package f + +// ---------------------------------------------------------------------------- +// Factory functions for non-exported types must not get lost. + +type private struct{} + +// Exported must always be visible. Was issue 2824. +func Exported() private {} diff --git a/src/go/doc/testdata/g.0.golden b/src/go/doc/testdata/g.0.golden new file mode 100644 index 0000000..487cf06 --- /dev/null +++ b/src/go/doc/testdata/g.0.golden @@ -0,0 +1,32 @@ +// The package g is a go/doc test for mixed exported/unexported ... +PACKAGE g + +IMPORTPATH + testdata/g + +FILENAMES + testdata/g.go + +CONSTANTS + // + const ( + A, _ = iota, iota + _, D + E, _ + G, H + ) + + +VARIABLES + // + var ( + _, C2, _ = 1, 2, 3 + C4, _, C6 = 4, 5, 6 + _, C8, _ = 7, 8, 9 + ) + + // + var ( + _, X = f() + ) + diff --git a/src/go/doc/testdata/g.1.golden b/src/go/doc/testdata/g.1.golden new file mode 100644 index 0000000..438441a --- /dev/null +++ b/src/go/doc/testdata/g.1.golden @@ -0,0 +1,34 @@ +// The package g is a go/doc test for mixed exported/unexported ... +PACKAGE g + +IMPORTPATH + testdata/g + +FILENAMES + testdata/g.go + +CONSTANTS + // + const ( + A, b = iota, iota + c, D + E, f + G, H + ) + + +VARIABLES + // + var ( + c1, C2, c3 = 1, 2, 3 + C4, c5, C6 = 4, 5, 6 + c7, C8, c9 = 7, 8, 9 + xx, yy, zz = 0, 0, 0 // all unexported and hidden + ) + + // + var ( + x, X = f() + y, z = f() + ) + diff --git a/src/go/doc/testdata/g.2.golden b/src/go/doc/testdata/g.2.golden new file mode 100644 index 0000000..487cf06 --- /dev/null +++ b/src/go/doc/testdata/g.2.golden @@ -0,0 +1,32 @@ +// The package g is a go/doc test for mixed exported/unexported ... +PACKAGE g + +IMPORTPATH + testdata/g + +FILENAMES + testdata/g.go + +CONSTANTS + // + const ( + A, _ = iota, iota + _, D + E, _ + G, H + ) + + +VARIABLES + // + var ( + _, C2, _ = 1, 2, 3 + C4, _, C6 = 4, 5, 6 + _, C8, _ = 7, 8, 9 + ) + + // + var ( + _, X = f() + ) + diff --git a/src/go/doc/testdata/g.go b/src/go/doc/testdata/g.go new file mode 100644 index 0000000..ceeb417 --- /dev/null +++ b/src/go/doc/testdata/g.go @@ -0,0 +1,25 @@ +// Copyright 2018 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. + +// The package g is a go/doc test for mixed exported/unexported values. +package g + +const ( + A, b = iota, iota + c, D + E, f + G, H +) + +var ( + c1, C2, c3 = 1, 2, 3 + C4, c5, C6 = 4, 5, 6 + c7, C8, c9 = 7, 8, 9 + xx, yy, zz = 0, 0, 0 // all unexported and hidden +) + +var ( + x, X = f() + y, z = f() +) diff --git a/src/go/doc/testdata/issue12839.0.golden b/src/go/doc/testdata/issue12839.0.golden new file mode 100644 index 0000000..6b59774 --- /dev/null +++ b/src/go/doc/testdata/issue12839.0.golden @@ -0,0 +1,51 @@ +// Package issue12839 is a go/doc test to test association of a ... +PACKAGE issue12839 + +IMPORTPATH + testdata/issue12839 + +IMPORTS + p + +FILENAMES + testdata/issue12839.go + +FUNCTIONS + // F1 should not be associated with T1 + func F1() (*T1, *T2) + + // F10 should not be associated with T1. + func F10() (T1, T2, error) + + // F4 should not be associated with a type (same as F1) + func F4() (a T1, b T2) + + // F9 should not be associated with T1. + func F9() (int, T1, T2) + + +TYPES + // + type T1 struct{} + + // F2 should be associated with T1 + func F2() (a, b, c T1) + + // F3 should be associated with T1 because b.T3 is from a ... + func F3() (a T1, b p.T3) + + // F5 should be associated with T1. + func F5() (T1, error) + + // F6 should be associated with T1. + func F6() (*T1, error) + + // F7 should be associated with T1. + func F7() (T1, string) + + // F8 should be associated with T1. + func F8() (int, T1, string) + + // + type T2 struct{} + diff --git a/src/go/doc/testdata/issue12839.1.golden b/src/go/doc/testdata/issue12839.1.golden new file mode 100644 index 0000000..4b9b9f6 --- /dev/null +++ b/src/go/doc/testdata/issue12839.1.golden @@ -0,0 +1,54 @@ +// Package issue12839 is a go/doc test to test association of a ... +PACKAGE issue12839 + +IMPORTPATH + testdata/issue12839 + +IMPORTS + p + +FILENAMES + testdata/issue12839.go + +FUNCTIONS + // F1 should not be associated with T1 + func F1() (*T1, *T2) + + // F10 should not be associated with T1. + func F10() (T1, T2, error) + + // F4 should not be associated with a type (same as F1) + func F4() (a T1, b T2) + + // F9 should not be associated with T1. + func F9() (int, T1, T2) + + +TYPES + // + type T1 struct{} + + // F2 should be associated with T1 + func F2() (a, b, c T1) + + // F3 should be associated with T1 because b.T3 is from a ... + func F3() (a T1, b p.T3) + + // F5 should be associated with T1. + func F5() (T1, error) + + // F6 should be associated with T1. + func F6() (*T1, error) + + // F7 should be associated with T1. + func F7() (T1, string) + + // F8 should be associated with T1. + func F8() (int, T1, string) + + // + func (t T1) hello() string + + // + type T2 struct{} + diff --git a/src/go/doc/testdata/issue12839.2.golden b/src/go/doc/testdata/issue12839.2.golden new file mode 100644 index 0000000..6b59774 --- /dev/null +++ b/src/go/doc/testdata/issue12839.2.golden @@ -0,0 +1,51 @@ +// Package issue12839 is a go/doc test to test association of a ... +PACKAGE issue12839 + +IMPORTPATH + testdata/issue12839 + +IMPORTS + p + +FILENAMES + testdata/issue12839.go + +FUNCTIONS + // F1 should not be associated with T1 + func F1() (*T1, *T2) + + // F10 should not be associated with T1. + func F10() (T1, T2, error) + + // F4 should not be associated with a type (same as F1) + func F4() (a T1, b T2) + + // F9 should not be associated with T1. + func F9() (int, T1, T2) + + +TYPES + // + type T1 struct{} + + // F2 should be associated with T1 + func F2() (a, b, c T1) + + // F3 should be associated with T1 because b.T3 is from a ... + func F3() (a T1, b p.T3) + + // F5 should be associated with T1. + func F5() (T1, error) + + // F6 should be associated with T1. + func F6() (*T1, error) + + // F7 should be associated with T1. + func F7() (T1, string) + + // F8 should be associated with T1. + func F8() (int, T1, string) + + // + type T2 struct{} + diff --git a/src/go/doc/testdata/issue12839.go b/src/go/doc/testdata/issue12839.go new file mode 100644 index 0000000..51c7ac1 --- /dev/null +++ b/src/go/doc/testdata/issue12839.go @@ -0,0 +1,69 @@ +// Copyright 2018 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 issue12839 is a go/doc test to test association of a function +// that returns multiple types. +// See golang.org/issue/12839. +// (See also golang.org/issue/27928.) +package issue12839 + +import "p" + +type T1 struct{} + +type T2 struct{} + +func (t T1) hello() string { + return "hello" +} + +// F1 should not be associated with T1 +func F1() (*T1, *T2) { + return &T1{}, &T2{} +} + +// F2 should be associated with T1 +func F2() (a, b, c T1) { + return T1{}, T1{}, T1{} +} + +// F3 should be associated with T1 because b.T3 is from a different package +func F3() (a T1, b p.T3) { + return T1{}, p.T3{} +} + +// F4 should not be associated with a type (same as F1) +func F4() (a T1, b T2) { + return T1{}, T2{} +} + +// F5 should be associated with T1. +func F5() (T1, error) { + return T1{}, nil +} + +// F6 should be associated with T1. +func F6() (*T1, error) { + return &T1{}, nil +} + +// F7 should be associated with T1. +func F7() (T1, string) { + return T1{}, nil +} + +// F8 should be associated with T1. +func F8() (int, T1, string) { + return 0, T1{}, nil +} + +// F9 should not be associated with T1. +func F9() (int, T1, T2) { + return 0, T1{}, T2{} +} + +// F10 should not be associated with T1. +func F10() (T1, T2, error) { + return T1{}, T2{}, nil +} diff --git a/src/go/doc/testdata/issue13742.0.golden b/src/go/doc/testdata/issue13742.0.golden new file mode 100644 index 0000000..8dee9aa --- /dev/null +++ b/src/go/doc/testdata/issue13742.0.golden @@ -0,0 +1,25 @@ +// +PACKAGE issue13742 + +IMPORTPATH + testdata/issue13742 + +IMPORTS + go/ast + +FILENAMES + testdata/issue13742.go + +FUNCTIONS + // Both F0 and G0 should appear as functions. + func F0(Node) + + // Both F1 and G1 should appear as functions. + func F1(ast.Node) + + // + func G0() Node + + // + func G1() ast.Node + diff --git a/src/go/doc/testdata/issue13742.1.golden b/src/go/doc/testdata/issue13742.1.golden new file mode 100644 index 0000000..8dee9aa --- /dev/null +++ b/src/go/doc/testdata/issue13742.1.golden @@ -0,0 +1,25 @@ +// +PACKAGE issue13742 + +IMPORTPATH + testdata/issue13742 + +IMPORTS + go/ast + +FILENAMES + testdata/issue13742.go + +FUNCTIONS + // Both F0 and G0 should appear as functions. + func F0(Node) + + // Both F1 and G1 should appear as functions. + func F1(ast.Node) + + // + func G0() Node + + // + func G1() ast.Node + diff --git a/src/go/doc/testdata/issue13742.2.golden b/src/go/doc/testdata/issue13742.2.golden new file mode 100644 index 0000000..8dee9aa --- /dev/null +++ b/src/go/doc/testdata/issue13742.2.golden @@ -0,0 +1,25 @@ +// +PACKAGE issue13742 + +IMPORTPATH + testdata/issue13742 + +IMPORTS + go/ast + +FILENAMES + testdata/issue13742.go + +FUNCTIONS + // Both F0 and G0 should appear as functions. + func F0(Node) + + // Both F1 and G1 should appear as functions. + func F1(ast.Node) + + // + func G0() Node + + // + func G1() ast.Node + diff --git a/src/go/doc/testdata/issue13742.go b/src/go/doc/testdata/issue13742.go new file mode 100644 index 0000000..dbc1941 --- /dev/null +++ b/src/go/doc/testdata/issue13742.go @@ -0,0 +1,18 @@ +// Copyright 2016 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 issue13742 + +import ( + "go/ast" + . "go/ast" +) + +// Both F0 and G0 should appear as functions. +func F0(Node) {} +func G0() Node { return nil } + +// Both F1 and G1 should appear as functions. +func F1(ast.Node) {} +func G1() ast.Node { return nil } diff --git a/src/go/doc/testdata/issue16153.0.golden b/src/go/doc/testdata/issue16153.0.golden new file mode 100644 index 0000000..189260b --- /dev/null +++ b/src/go/doc/testdata/issue16153.0.golden @@ -0,0 +1,32 @@ +// +PACKAGE issue16153 + +IMPORTPATH + testdata/issue16153 + +FILENAMES + testdata/issue16153.go + +CONSTANTS + // + const ( + X3 int64 = iota + Y3 = 1 + ) + + // + const ( + X4 int64 = iota + Y4 + ) + + // original test case + const ( + Y1 = 256 + ) + + // variations + const ( + Y2 uint8 + ) + diff --git a/src/go/doc/testdata/issue16153.1.golden b/src/go/doc/testdata/issue16153.1.golden new file mode 100644 index 0000000..803df3e --- /dev/null +++ b/src/go/doc/testdata/issue16153.1.golden @@ -0,0 +1,34 @@ +// +PACKAGE issue16153 + +IMPORTPATH + testdata/issue16153 + +FILENAMES + testdata/issue16153.go + +CONSTANTS + // original test case + const ( + x1 uint8 = 255 + Y1 = 256 + ) + + // variations + const ( + x2 uint8 = 255 + Y2 + ) + + // + const ( + X3 int64 = iota + Y3 = 1 + ) + + // + const ( + X4 int64 = iota + Y4 + ) + diff --git a/src/go/doc/testdata/issue16153.2.golden b/src/go/doc/testdata/issue16153.2.golden new file mode 100644 index 0000000..189260b --- /dev/null +++ b/src/go/doc/testdata/issue16153.2.golden @@ -0,0 +1,32 @@ +// +PACKAGE issue16153 + +IMPORTPATH + testdata/issue16153 + +FILENAMES + testdata/issue16153.go + +CONSTANTS + // + const ( + X3 int64 = iota + Y3 = 1 + ) + + // + const ( + X4 int64 = iota + Y4 + ) + + // original test case + const ( + Y1 = 256 + ) + + // variations + const ( + Y2 uint8 + ) + diff --git a/src/go/doc/testdata/issue16153.go b/src/go/doc/testdata/issue16153.go new file mode 100644 index 0000000..528be42 --- /dev/null +++ b/src/go/doc/testdata/issue16153.go @@ -0,0 +1,27 @@ +// Copyright 2017 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 issue16153 + +// original test case +const ( + x1 uint8 = 255 + Y1 = 256 +) + +// variations +const ( + x2 uint8 = 255 + Y2 +) + +const ( + X3 int64 = iota + Y3 = 1 +) + +const ( + X4 int64 = iota + Y4 +) diff --git a/src/go/doc/testdata/issue17788.0.golden b/src/go/doc/testdata/issue17788.0.golden new file mode 100644 index 0000000..42c00da --- /dev/null +++ b/src/go/doc/testdata/issue17788.0.golden @@ -0,0 +1,8 @@ +// +PACKAGE issue17788 + +IMPORTPATH + testdata/issue17788 + +FILENAMES + testdata/issue17788.go diff --git a/src/go/doc/testdata/issue17788.1.golden b/src/go/doc/testdata/issue17788.1.golden new file mode 100644 index 0000000..42c00da --- /dev/null +++ b/src/go/doc/testdata/issue17788.1.golden @@ -0,0 +1,8 @@ +// +PACKAGE issue17788 + +IMPORTPATH + testdata/issue17788 + +FILENAMES + testdata/issue17788.go diff --git a/src/go/doc/testdata/issue17788.2.golden b/src/go/doc/testdata/issue17788.2.golden new file mode 100644 index 0000000..42c00da --- /dev/null +++ b/src/go/doc/testdata/issue17788.2.golden @@ -0,0 +1,8 @@ +// +PACKAGE issue17788 + +IMPORTPATH + testdata/issue17788 + +FILENAMES + testdata/issue17788.go diff --git a/src/go/doc/testdata/issue17788.go b/src/go/doc/testdata/issue17788.go new file mode 100644 index 0000000..883ad5f --- /dev/null +++ b/src/go/doc/testdata/issue17788.go @@ -0,0 +1,8 @@ +// Copyright 2016 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 issue17788 + +func ( /* receiver type */ ) f0() { +} diff --git a/src/go/doc/testdata/issue22856.0.golden b/src/go/doc/testdata/issue22856.0.golden new file mode 100644 index 0000000..a88f43f --- /dev/null +++ b/src/go/doc/testdata/issue22856.0.golden @@ -0,0 +1,45 @@ +// +PACKAGE issue22856 + +IMPORTPATH + testdata/issue22856 + +FILENAMES + testdata/issue22856.go + +FUNCTIONS + // NewPointerSliceOfSlice is not a factory function because slices ... + func NewPointerSliceOfSlice() [][]*T + + // NewSlice3 is not a factory function because 3 nested slices of ... + func NewSlice3() [][][]T + + // NewSliceOfSlice is not a factory function because slices of a ... + func NewSliceOfSlice() [][]T + + +TYPES + // + type T struct{} + + // + func New() T + + // + func NewArray() [1]T + + // + func NewPointer() *T + + // + func NewPointerArray() [1]*T + + // + func NewPointerOfPointer() **T + + // + func NewPointerSlice() []*T + + // + func NewSlice() []T + diff --git a/src/go/doc/testdata/issue22856.1.golden b/src/go/doc/testdata/issue22856.1.golden new file mode 100644 index 0000000..a88f43f --- /dev/null +++ b/src/go/doc/testdata/issue22856.1.golden @@ -0,0 +1,45 @@ +// +PACKAGE issue22856 + +IMPORTPATH + testdata/issue22856 + +FILENAMES + testdata/issue22856.go + +FUNCTIONS + // NewPointerSliceOfSlice is not a factory function because slices ... + func NewPointerSliceOfSlice() [][]*T + + // NewSlice3 is not a factory function because 3 nested slices of ... + func NewSlice3() [][][]T + + // NewSliceOfSlice is not a factory function because slices of a ... + func NewSliceOfSlice() [][]T + + +TYPES + // + type T struct{} + + // + func New() T + + // + func NewArray() [1]T + + // + func NewPointer() *T + + // + func NewPointerArray() [1]*T + + // + func NewPointerOfPointer() **T + + // + func NewPointerSlice() []*T + + // + func NewSlice() []T + diff --git a/src/go/doc/testdata/issue22856.2.golden b/src/go/doc/testdata/issue22856.2.golden new file mode 100644 index 0000000..a88f43f --- /dev/null +++ b/src/go/doc/testdata/issue22856.2.golden @@ -0,0 +1,45 @@ +// +PACKAGE issue22856 + +IMPORTPATH + testdata/issue22856 + +FILENAMES + testdata/issue22856.go + +FUNCTIONS + // NewPointerSliceOfSlice is not a factory function because slices ... + func NewPointerSliceOfSlice() [][]*T + + // NewSlice3 is not a factory function because 3 nested slices of ... + func NewSlice3() [][][]T + + // NewSliceOfSlice is not a factory function because slices of a ... + func NewSliceOfSlice() [][]T + + +TYPES + // + type T struct{} + + // + func New() T + + // + func NewArray() [1]T + + // + func NewPointer() *T + + // + func NewPointerArray() [1]*T + + // + func NewPointerOfPointer() **T + + // + func NewPointerSlice() []*T + + // + func NewSlice() []T + diff --git a/src/go/doc/testdata/issue22856.go b/src/go/doc/testdata/issue22856.go new file mode 100644 index 0000000..f456998 --- /dev/null +++ b/src/go/doc/testdata/issue22856.go @@ -0,0 +1,27 @@ +// Copyright 2017 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 issue22856 + +type T struct{} + +func New() T { return T{} } +func NewPointer() *T { return &T{} } +func NewPointerSlice() []*T { return []*T{&T{}} } +func NewSlice() []T { return []T{T{}} } +func NewPointerOfPointer() **T { x := &T{}; return &x } +func NewArray() [1]T { return [1]T{T{}} } +func NewPointerArray() [1]*T { return [1]*T{&T{}} } + +// NewSliceOfSlice is not a factory function because slices of a slice of +// type *T are not factory functions of type T. +func NewSliceOfSlice() [][]T { return []T{[]T{}} } + +// NewPointerSliceOfSlice is not a factory function because slices of a +// slice of type *T are not factory functions of type T. +func NewPointerSliceOfSlice() [][]*T { return []*T{[]*T{}} } + +// NewSlice3 is not a factory function because 3 nested slices of type T +// are not factory functions of type T. +func NewSlice3() [][][]T { return []T{[]T{[]T{}}} } diff --git a/src/go/doc/testdata/predeclared.0.golden b/src/go/doc/testdata/predeclared.0.golden new file mode 100644 index 0000000..9f37b06 --- /dev/null +++ b/src/go/doc/testdata/predeclared.0.golden @@ -0,0 +1,8 @@ +// Package predeclared is a go/doc test for handling of exported ... +PACKAGE predeclared + +IMPORTPATH + testdata/predeclared + +FILENAMES + testdata/predeclared.go diff --git a/src/go/doc/testdata/predeclared.1.golden b/src/go/doc/testdata/predeclared.1.golden new file mode 100644 index 0000000..2ff8ee6 --- /dev/null +++ b/src/go/doc/testdata/predeclared.1.golden @@ -0,0 +1,22 @@ +// Package predeclared is a go/doc test for handling of exported ... +PACKAGE predeclared + +IMPORTPATH + testdata/predeclared + +FILENAMES + testdata/predeclared.go + +TYPES + // + type bool int + + // Must not be visible. + func (b bool) String() string + + // + type error struct{} + + // Must not be visible. + func (e error) Error() string + diff --git a/src/go/doc/testdata/predeclared.2.golden b/src/go/doc/testdata/predeclared.2.golden new file mode 100644 index 0000000..9f37b06 --- /dev/null +++ b/src/go/doc/testdata/predeclared.2.golden @@ -0,0 +1,8 @@ +// Package predeclared is a go/doc test for handling of exported ... +PACKAGE predeclared + +IMPORTPATH + testdata/predeclared + +FILENAMES + testdata/predeclared.go diff --git a/src/go/doc/testdata/predeclared.go b/src/go/doc/testdata/predeclared.go new file mode 100644 index 0000000..c6dd806 --- /dev/null +++ b/src/go/doc/testdata/predeclared.go @@ -0,0 +1,22 @@ +// Copyright 2016 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 predeclared is a go/doc test for handling of +// exported methods on locally-defined predeclared types. +// See issue 9860. +package predeclared + +type error struct{} + +// Must not be visible. +func (e error) Error() string { + return "" +} + +type bool int + +// Must not be visible. +func (b bool) String() string { + return "" +} diff --git a/src/go/doc/testdata/template.txt b/src/go/doc/testdata/template.txt new file mode 100644 index 0000000..1b07382 --- /dev/null +++ b/src/go/doc/testdata/template.txt @@ -0,0 +1,68 @@ +{{synopsis .Doc}} +PACKAGE {{.Name}} + +IMPORTPATH + {{.ImportPath}} + +{{with .Imports}}IMPORTS +{{range .}} {{.}} +{{end}} +{{end}}{{/* + +*/}}FILENAMES +{{range .Filenames}} {{.}} +{{end}}{{/* + +*/}}{{with .Consts}} +CONSTANTS +{{range .}} {{synopsis .Doc}} + {{node .Decl $.FSet}} + +{{end}}{{end}}{{/* + +*/}}{{with .Vars}} +VARIABLES +{{range .}} {{synopsis .Doc}} + {{node .Decl $.FSet}} + +{{end}}{{end}}{{/* + +*/}}{{with .Funcs}} +FUNCTIONS +{{range .}} {{synopsis .Doc}} + {{node .Decl $.FSet}} + +{{end}}{{end}}{{/* + +*/}}{{with .Types}} +TYPES +{{range .}} {{synopsis .Doc}} + {{node .Decl $.FSet}} + +{{range .Consts}} {{synopsis .Doc}} + {{node .Decl $.FSet}} + +{{end}}{{/* + +*/}}{{range .Vars}} {{synopsis .Doc}} + {{node .Decl $.FSet}} + +{{end}}{{/* + +*/}}{{range .Funcs}} {{synopsis .Doc}} + {{node .Decl $.FSet}} + +{{end}}{{/* + +*/}}{{range .Methods}} {{synopsis .Doc}} + {{node .Decl $.FSet}} + +{{end}}{{end}}{{end}}{{/* + +*/}}{{with .Bugs}} +BUGS .Bugs is now deprecated, please use .Notes instead +{{range .}}{{indent "\t" .}} +{{end}}{{end}}{{with .Notes}}{{range $marker, $content := .}} +{{$marker}}S +{{range $content}}{{$marker}}({{.UID}}){{indent "\t" .Body}} +{{end}}{{end}}{{end}} \ No newline at end of file diff --git a/src/go/doc/testdata/testing.0.golden b/src/go/doc/testdata/testing.0.golden new file mode 100644 index 0000000..83cf37c --- /dev/null +++ b/src/go/doc/testdata/testing.0.golden @@ -0,0 +1,156 @@ +// Package testing provides support for automated testing of Go ... +PACKAGE testing + +IMPORTPATH + testdata/testing + +IMPORTS + bytes + flag + fmt + io + os + runtime + runtime/pprof + strconv + strings + time + +FILENAMES + testdata/benchmark.go + testdata/example.go + testdata/testing.go + +FUNCTIONS + // An internal function but exported because it is cross-package; ... + func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) + + // An internal function but exported because it is cross-package; ... + func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) + + // + func RunExamples(examples []InternalExample) (ok bool) + + // + func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) + + // Short reports whether the -test.short flag is set. + func Short() bool + + +TYPES + // B is a type passed to Benchmark functions to manage benchmark ... + type B struct { + N int + // contains filtered or unexported fields + } + + // Error is equivalent to Log() followed by Fail(). + func (c *B) Error(args ...interface{}) + + // Errorf is equivalent to Logf() followed by Fail(). + func (c *B) Errorf(format string, args ...interface{}) + + // Fail marks the function as having failed but continues ... + func (c *B) Fail() + + // FailNow marks the function as having failed and stops its ... + func (c *B) FailNow() + + // Failed reports whether the function has failed. + func (c *B) Failed() bool + + // Fatal is equivalent to Log() followed by FailNow(). + func (c *B) Fatal(args ...interface{}) + + // Fatalf is equivalent to Logf() followed by FailNow(). + func (c *B) Fatalf(format string, args ...interface{}) + + // Log formats its arguments using default formatting, analogous ... + func (c *B) Log(args ...interface{}) + + // Logf formats its arguments according to the format, analogous ... + func (c *B) Logf(format string, args ...interface{}) + + // ResetTimer sets the elapsed benchmark time to zero. It does not ... + func (b *B) ResetTimer() + + // SetBytes records the number of bytes processed in a single ... + func (b *B) SetBytes(n int64) + + // StartTimer starts timing a test. This function is called ... + func (b *B) StartTimer() + + // StopTimer stops timing a test. This can be used to pause the ... + func (b *B) StopTimer() + + // The results of a benchmark run. + type BenchmarkResult struct { + N int // The number of iterations. + T time.Duration // The total time taken. + Bytes int64 // Bytes processed in one iteration. + } + + // Benchmark benchmarks a single function. Useful for creating ... + func Benchmark(f func(b *B)) BenchmarkResult + + // + func (r BenchmarkResult) NsPerOp() int64 + + // + func (r BenchmarkResult) String() string + + // An internal type but exported because it is cross-package; part ... + type InternalBenchmark struct { + Name string + F func(b *B) + } + + // + type InternalExample struct { + Name string + F func() + Output string + } + + // An internal type but exported because it is cross-package; part ... + type InternalTest struct { + Name string + F func(*T) + } + + // T is a type passed to Test functions to manage test state and ... + type T struct { + // contains filtered or unexported fields + } + + // Error is equivalent to Log() followed by Fail(). + func (c *T) Error(args ...interface{}) + + // Errorf is equivalent to Logf() followed by Fail(). + func (c *T) Errorf(format string, args ...interface{}) + + // Fail marks the function as having failed but continues ... + func (c *T) Fail() + + // FailNow marks the function as having failed and stops its ... + func (c *T) FailNow() + + // Failed reports whether the function has failed. + func (c *T) Failed() bool + + // Fatal is equivalent to Log() followed by FailNow(). + func (c *T) Fatal(args ...interface{}) + + // Fatalf is equivalent to Logf() followed by FailNow(). + func (c *T) Fatalf(format string, args ...interface{}) + + // Log formats its arguments using default formatting, analogous ... + func (c *T) Log(args ...interface{}) + + // Logf formats its arguments according to the format, analogous ... + func (c *T) Logf(format string, args ...interface{}) + + // Parallel signals that this test is to be run in parallel with ... + func (t *T) Parallel() + diff --git a/src/go/doc/testdata/testing.1.golden b/src/go/doc/testdata/testing.1.golden new file mode 100644 index 0000000..b9d1451 --- /dev/null +++ b/src/go/doc/testdata/testing.1.golden @@ -0,0 +1,298 @@ +// Package testing provides support for automated testing of Go ... +PACKAGE testing + +IMPORTPATH + testdata/testing + +IMPORTS + bytes + flag + fmt + io + os + runtime + runtime/pprof + strconv + strings + time + +FILENAMES + testdata/benchmark.go + testdata/example.go + testdata/testing.go + +VARIABLES + // + var ( + // The short flag requests that tests run more quickly, but its functionality + // is provided by test writers themselves. The testing package is just its + // home. The all.bash installation script sets it to make installation more + // efficient, but by default the flag is off so a plain "go test" will do a + // full test of the package. + short = flag.Bool("test.short", false, "run smaller test suite to save time") + + // Report as tests are run; default is silent for success. + chatty = flag.Bool("test.v", false, "verbose: print additional output") + match = flag.String("test.run", "", "regular expression to select tests to run") + memProfile = flag.String("test.memprofile", "", "write a memory profile to the named file after execution") + memProfileRate = flag.Int("test.memprofilerate", 0, "if >=0, sets runtime.MemProfileRate") + cpuProfile = flag.String("test.cpuprofile", "", "write a cpu profile to the named file during execution") + timeout = flag.Duration("test.timeout", 0, "if positive, sets an aggregate time limit for all tests") + cpuListStr = flag.String("test.cpu", "", "comma-separated list of number of CPUs to use for each test") + parallel = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "maximum test parallelism") + + cpuList []int + ) + + // + var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark") + + // + var matchBenchmarks = flag.String("test.bench", "", "regular expression to select benchmarks to run") + + // + var timer *time.Timer + + +FUNCTIONS + // An internal function but exported because it is cross-package; ... + func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) + + // An internal function but exported because it is cross-package; ... + func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) + + // + func RunExamples(examples []InternalExample) (ok bool) + + // + func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) + + // Short reports whether the -test.short flag is set. + func Short() bool + + // after runs after all testing. + func after() + + // alarm is called if the timeout expires. + func alarm() + + // before runs before all testing. + func before() + + // decorate inserts the final newline if needed and indentation ... + func decorate(s string, addFileLine bool) string + + // + func max(x, y int) int + + // + func min(x, y int) int + + // + func parseCpuList() + + // roundDown10 rounds a number down to the nearest power of 10. + func roundDown10(n int) int + + // roundUp rounds x up to a number of the form [1eX, 2eX, 5eX]. + func roundUp(n int) int + + // startAlarm starts an alarm if requested. + func startAlarm() + + // stopAlarm turns off the alarm. + func stopAlarm() + + // + func tRunner(t *T, test *InternalTest) + + +TYPES + // B is a type passed to Benchmark functions to manage benchmark ... + type B struct { + common + N int + benchmark InternalBenchmark + bytes int64 + timerOn bool + result BenchmarkResult + } + + // Error is equivalent to Log() followed by Fail(). + func (c *B) Error(args ...interface{}) + + // Errorf is equivalent to Logf() followed by Fail(). + func (c *B) Errorf(format string, args ...interface{}) + + // Fail marks the function as having failed but continues ... + func (c *B) Fail() + + // FailNow marks the function as having failed and stops its ... + func (c *B) FailNow() + + // Failed reports whether the function has failed. + func (c *B) Failed() bool + + // Fatal is equivalent to Log() followed by FailNow(). + func (c *B) Fatal(args ...interface{}) + + // Fatalf is equivalent to Logf() followed by FailNow(). + func (c *B) Fatalf(format string, args ...interface{}) + + // Log formats its arguments using default formatting, analogous ... + func (c *B) Log(args ...interface{}) + + // Logf formats its arguments according to the format, analogous ... + func (c *B) Logf(format string, args ...interface{}) + + // ResetTimer sets the elapsed benchmark time to zero. It does not ... + func (b *B) ResetTimer() + + // SetBytes records the number of bytes processed in a single ... + func (b *B) SetBytes(n int64) + + // StartTimer starts timing a test. This function is called ... + func (b *B) StartTimer() + + // StopTimer stops timing a test. This can be used to pause the ... + func (b *B) StopTimer() + + // launch launches the benchmark function. It gradually increases ... + func (b *B) launch() + + // log generates the output. It's always at the same stack depth. + func (c *B) log(s string) + + // + func (b *B) nsPerOp() int64 + + // run times the benchmark function in a separate goroutine. + func (b *B) run() BenchmarkResult + + // runN runs a single benchmark for the specified number of ... + func (b *B) runN(n int) + + // trimOutput shortens the output from a benchmark, which can be ... + func (b *B) trimOutput() + + // The results of a benchmark run. + type BenchmarkResult struct { + N int // The number of iterations. + T time.Duration // The total time taken. + Bytes int64 // Bytes processed in one iteration. + } + + // Benchmark benchmarks a single function. Useful for creating ... + func Benchmark(f func(b *B)) BenchmarkResult + + // + func (r BenchmarkResult) NsPerOp() int64 + + // + func (r BenchmarkResult) String() string + + // + func (r BenchmarkResult) mbPerSec() float64 + + // An internal type but exported because it is cross-package; part ... + type InternalBenchmark struct { + Name string + F func(b *B) + } + + // + type InternalExample struct { + Name string + F func() + Output string + } + + // An internal type but exported because it is cross-package; part ... + type InternalTest struct { + Name string + F func(*T) + } + + // T is a type passed to Test functions to manage test state and ... + type T struct { + common + name string // Name of test. + startParallel chan bool // Parallel tests will wait on this. + } + + // Error is equivalent to Log() followed by Fail(). + func (c *T) Error(args ...interface{}) + + // Errorf is equivalent to Logf() followed by Fail(). + func (c *T) Errorf(format string, args ...interface{}) + + // Fail marks the function as having failed but continues ... + func (c *T) Fail() + + // FailNow marks the function as having failed and stops its ... + func (c *T) FailNow() + + // Failed reports whether the function has failed. + func (c *T) Failed() bool + + // Fatal is equivalent to Log() followed by FailNow(). + func (c *T) Fatal(args ...interface{}) + + // Fatalf is equivalent to Logf() followed by FailNow(). + func (c *T) Fatalf(format string, args ...interface{}) + + // Log formats its arguments using default formatting, analogous ... + func (c *T) Log(args ...interface{}) + + // Logf formats its arguments according to the format, analogous ... + func (c *T) Logf(format string, args ...interface{}) + + // Parallel signals that this test is to be run in parallel with ... + func (t *T) Parallel() + + // log generates the output. It's always at the same stack depth. + func (c *T) log(s string) + + // + func (t *T) report() + + // common holds the elements common between T and B and captures ... + type common struct { + output []byte // Output generated by test or benchmark. + failed bool // Test or benchmark has failed. + start time.Time // Time test or benchmark started + duration time.Duration + self interface{} // To be sent on signal channel when done. + signal chan interface{} // Output for serial tests. + } + + // Error is equivalent to Log() followed by Fail(). + func (c *common) Error(args ...interface{}) + + // Errorf is equivalent to Logf() followed by Fail(). + func (c *common) Errorf(format string, args ...interface{}) + + // Fail marks the function as having failed but continues ... + func (c *common) Fail() + + // FailNow marks the function as having failed and stops its ... + func (c *common) FailNow() + + // Failed reports whether the function has failed. + func (c *common) Failed() bool + + // Fatal is equivalent to Log() followed by FailNow(). + func (c *common) Fatal(args ...interface{}) + + // Fatalf is equivalent to Logf() followed by FailNow(). + func (c *common) Fatalf(format string, args ...interface{}) + + // Log formats its arguments using default formatting, analogous ... + func (c *common) Log(args ...interface{}) + + // Logf formats its arguments according to the format, analogous ... + func (c *common) Logf(format string, args ...interface{}) + + // log generates the output. It's always at the same stack depth. + func (c *common) log(s string) + diff --git a/src/go/doc/testdata/testing.2.golden b/src/go/doc/testdata/testing.2.golden new file mode 100644 index 0000000..83cf37c --- /dev/null +++ b/src/go/doc/testdata/testing.2.golden @@ -0,0 +1,156 @@ +// Package testing provides support for automated testing of Go ... +PACKAGE testing + +IMPORTPATH + testdata/testing + +IMPORTS + bytes + flag + fmt + io + os + runtime + runtime/pprof + strconv + strings + time + +FILENAMES + testdata/benchmark.go + testdata/example.go + testdata/testing.go + +FUNCTIONS + // An internal function but exported because it is cross-package; ... + func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) + + // An internal function but exported because it is cross-package; ... + func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) + + // + func RunExamples(examples []InternalExample) (ok bool) + + // + func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) + + // Short reports whether the -test.short flag is set. + func Short() bool + + +TYPES + // B is a type passed to Benchmark functions to manage benchmark ... + type B struct { + N int + // contains filtered or unexported fields + } + + // Error is equivalent to Log() followed by Fail(). + func (c *B) Error(args ...interface{}) + + // Errorf is equivalent to Logf() followed by Fail(). + func (c *B) Errorf(format string, args ...interface{}) + + // Fail marks the function as having failed but continues ... + func (c *B) Fail() + + // FailNow marks the function as having failed and stops its ... + func (c *B) FailNow() + + // Failed reports whether the function has failed. + func (c *B) Failed() bool + + // Fatal is equivalent to Log() followed by FailNow(). + func (c *B) Fatal(args ...interface{}) + + // Fatalf is equivalent to Logf() followed by FailNow(). + func (c *B) Fatalf(format string, args ...interface{}) + + // Log formats its arguments using default formatting, analogous ... + func (c *B) Log(args ...interface{}) + + // Logf formats its arguments according to the format, analogous ... + func (c *B) Logf(format string, args ...interface{}) + + // ResetTimer sets the elapsed benchmark time to zero. It does not ... + func (b *B) ResetTimer() + + // SetBytes records the number of bytes processed in a single ... + func (b *B) SetBytes(n int64) + + // StartTimer starts timing a test. This function is called ... + func (b *B) StartTimer() + + // StopTimer stops timing a test. This can be used to pause the ... + func (b *B) StopTimer() + + // The results of a benchmark run. + type BenchmarkResult struct { + N int // The number of iterations. + T time.Duration // The total time taken. + Bytes int64 // Bytes processed in one iteration. + } + + // Benchmark benchmarks a single function. Useful for creating ... + func Benchmark(f func(b *B)) BenchmarkResult + + // + func (r BenchmarkResult) NsPerOp() int64 + + // + func (r BenchmarkResult) String() string + + // An internal type but exported because it is cross-package; part ... + type InternalBenchmark struct { + Name string + F func(b *B) + } + + // + type InternalExample struct { + Name string + F func() + Output string + } + + // An internal type but exported because it is cross-package; part ... + type InternalTest struct { + Name string + F func(*T) + } + + // T is a type passed to Test functions to manage test state and ... + type T struct { + // contains filtered or unexported fields + } + + // Error is equivalent to Log() followed by Fail(). + func (c *T) Error(args ...interface{}) + + // Errorf is equivalent to Logf() followed by Fail(). + func (c *T) Errorf(format string, args ...interface{}) + + // Fail marks the function as having failed but continues ... + func (c *T) Fail() + + // FailNow marks the function as having failed and stops its ... + func (c *T) FailNow() + + // Failed reports whether the function has failed. + func (c *T) Failed() bool + + // Fatal is equivalent to Log() followed by FailNow(). + func (c *T) Fatal(args ...interface{}) + + // Fatalf is equivalent to Logf() followed by FailNow(). + func (c *T) Fatalf(format string, args ...interface{}) + + // Log formats its arguments using default formatting, analogous ... + func (c *T) Log(args ...interface{}) + + // Logf formats its arguments according to the format, analogous ... + func (c *T) Logf(format string, args ...interface{}) + + // Parallel signals that this test is to be run in parallel with ... + func (t *T) Parallel() + diff --git a/src/go/doc/testdata/testing.go b/src/go/doc/testdata/testing.go new file mode 100644 index 0000000..52810f7 --- /dev/null +++ b/src/go/doc/testdata/testing.go @@ -0,0 +1,404 @@ +// Copyright 2009 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 testing provides support for automated testing of Go packages. +// It is intended to be used in concert with the ``go test'' utility, which automates +// execution of any function of the form +// func TestXxx(*testing.T) +// where Xxx can be any alphanumeric string (but the first letter must not be in +// [a-z]) and serves to identify the test routine. +// These TestXxx routines should be declared within the package they are testing. +// +// Functions of the form +// func BenchmarkXxx(*testing.B) +// are considered benchmarks, and are executed by go test when the -test.bench +// flag is provided. +// +// A sample benchmark function looks like this: +// func BenchmarkHello(b *testing.B) { +// for i := 0; i < b.N; i++ { +// fmt.Sprintf("hello") +// } +// } +// The benchmark package will vary b.N until the benchmark function lasts +// long enough to be timed reliably. The output +// testing.BenchmarkHello 10000000 282 ns/op +// means that the loop ran 10000000 times at a speed of 282 ns per loop. +// +// If a benchmark needs some expensive setup before running, the timer +// may be stopped: +// func BenchmarkBigLen(b *testing.B) { +// b.StopTimer() +// big := NewBig() +// b.StartTimer() +// for i := 0; i < b.N; i++ { +// big.Len() +// } +// } +package testing + +import ( + "flag" + "fmt" + "os" + "runtime" + "runtime/pprof" + "strconv" + "strings" + "time" +) + +var ( + // The short flag requests that tests run more quickly, but its functionality + // is provided by test writers themselves. The testing package is just its + // home. The all.bash installation script sets it to make installation more + // efficient, but by default the flag is off so a plain "go test" will do a + // full test of the package. + short = flag.Bool("test.short", false, "run smaller test suite to save time") + + // Report as tests are run; default is silent for success. + chatty = flag.Bool("test.v", false, "verbose: print additional output") + match = flag.String("test.run", "", "regular expression to select tests to run") + memProfile = flag.String("test.memprofile", "", "write a memory profile to the named file after execution") + memProfileRate = flag.Int("test.memprofilerate", 0, "if >=0, sets runtime.MemProfileRate") + cpuProfile = flag.String("test.cpuprofile", "", "write a cpu profile to the named file during execution") + timeout = flag.Duration("test.timeout", 0, "if positive, sets an aggregate time limit for all tests") + cpuListStr = flag.String("test.cpu", "", "comma-separated list of number of CPUs to use for each test") + parallel = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "maximum test parallelism") + + cpuList []int +) + +// common holds the elements common between T and B and +// captures common methods such as Errorf. +type common struct { + output []byte // Output generated by test or benchmark. + failed bool // Test or benchmark has failed. + start time.Time // Time test or benchmark started + duration time.Duration + self interface{} // To be sent on signal channel when done. + signal chan interface{} // Output for serial tests. +} + +// Short reports whether the -test.short flag is set. +func Short() bool { + return *short +} + +// decorate inserts the final newline if needed and indentation tabs for formatting. +// If addFileLine is true, it also prefixes the string with the file and line of the call site. +func decorate(s string, addFileLine bool) string { + if addFileLine { + _, file, line, ok := runtime.Caller(3) // decorate + log + public function. + if ok { + // Truncate file name at last file name separator. + if index := strings.LastIndex(file, "/"); index >= 0 { + file = file[index+1:] + } else if index = strings.LastIndex(file, "\\"); index >= 0 { + file = file[index+1:] + } + } else { + file = "???" + line = 1 + } + s = fmt.Sprintf("%s:%d: %s", file, line, s) + } + s = "\t" + s // Every line is indented at least one tab. + n := len(s) + if n > 0 && s[n-1] != '\n' { + s += "\n" + n++ + } + for i := 0; i < n-1; i++ { // -1 to avoid final newline + if s[i] == '\n' { + // Second and subsequent lines are indented an extra tab. + return s[0:i+1] + "\t" + decorate(s[i+1:n], false) + } + } + return s +} + +// T is a type passed to Test functions to manage test state and support formatted test logs. +// Logs are accumulated during execution and dumped to standard error when done. +type T struct { + common + name string // Name of test. + startParallel chan bool // Parallel tests will wait on this. +} + +// Fail marks the function as having failed but continues execution. +func (c *common) Fail() { c.failed = true } + +// Failed reports whether the function has failed. +func (c *common) Failed() bool { return c.failed } + +// FailNow marks the function as having failed and stops its execution. +// Execution will continue at the next Test. +func (c *common) FailNow() { + c.Fail() + + // Calling runtime.Goexit will exit the goroutine, which + // will run the deferred functions in this goroutine, + // which will eventually run the deferred lines in tRunner, + // which will signal to the test loop that this test is done. + // + // A previous version of this code said: + // + // c.duration = ... + // c.signal <- c.self + // runtime.Goexit() + // + // This previous version duplicated code (those lines are in + // tRunner no matter what), but worse the goroutine teardown + // implicit in runtime.Goexit was not guaranteed to complete + // before the test exited. If a test deferred an important cleanup + // function (like removing temporary files), there was no guarantee + // it would run on a test failure. Because we send on c.signal during + // a top-of-stack deferred function now, we know that the send + // only happens after any other stacked defers have completed. + runtime.Goexit() +} + +// log generates the output. It's always at the same stack depth. +func (c *common) log(s string) { + c.output = append(c.output, decorate(s, true)...) +} + +// Log formats its arguments using default formatting, analogous to Println(), +// and records the text in the error log. +func (c *common) Log(args ...interface{}) { c.log(fmt.Sprintln(args...)) } + +// Logf formats its arguments according to the format, analogous to Printf(), +// and records the text in the error log. +func (c *common) Logf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) } + +// Error is equivalent to Log() followed by Fail(). +func (c *common) Error(args ...interface{}) { + c.log(fmt.Sprintln(args...)) + c.Fail() +} + +// Errorf is equivalent to Logf() followed by Fail(). +func (c *common) Errorf(format string, args ...interface{}) { + c.log(fmt.Sprintf(format, args...)) + c.Fail() +} + +// Fatal is equivalent to Log() followed by FailNow(). +func (c *common) Fatal(args ...interface{}) { + c.log(fmt.Sprintln(args...)) + c.FailNow() +} + +// Fatalf is equivalent to Logf() followed by FailNow(). +func (c *common) Fatalf(format string, args ...interface{}) { + c.log(fmt.Sprintf(format, args...)) + c.FailNow() +} + +// Parallel signals that this test is to be run in parallel with (and only with) +// other parallel tests in this CPU group. +func (t *T) Parallel() { + t.signal <- (*T)(nil) // Release main testing loop + <-t.startParallel // Wait for serial tests to finish +} + +// An internal type but exported because it is cross-package; part of the implementation +// of go test. +type InternalTest struct { + Name string + F func(*T) +} + +func tRunner(t *T, test *InternalTest) { + t.start = time.Now() + + // When this goroutine is done, either because test.F(t) + // returned normally or because a test failure triggered + // a call to runtime.Goexit, record the duration and send + // a signal saying that the test is done. + defer func() { + t.duration = time.Now().Sub(t.start) + t.signal <- t + }() + + test.F(t) +} + +// An internal function but exported because it is cross-package; part of the implementation +// of go test. +func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) { + flag.Parse() + parseCpuList() + + before() + startAlarm() + testOk := RunTests(matchString, tests) + exampleOk := RunExamples(examples) + if !testOk || !exampleOk { + fmt.Println("FAIL") + os.Exit(1) + } + fmt.Println("PASS") + stopAlarm() + RunBenchmarks(matchString, benchmarks) + after() +} + +func (t *T) report() { + tstr := fmt.Sprintf("(%.2f seconds)", t.duration.Seconds()) + format := "--- %s: %s %s\n%s" + if t.failed { + fmt.Printf(format, "FAIL", t.name, tstr, t.output) + } else if *chatty { + fmt.Printf(format, "PASS", t.name, tstr, t.output) + } +} + +func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) { + ok = true + if len(tests) == 0 { + fmt.Fprintln(os.Stderr, "testing: warning: no tests to run") + return + } + for _, procs := range cpuList { + runtime.GOMAXPROCS(procs) + // We build a new channel tree for each run of the loop. + // collector merges in one channel all the upstream signals from parallel tests. + // If all tests pump to the same channel, a bug can occur where a test + // kicks off a goroutine that Fails, yet the test still delivers a completion signal, + // which skews the counting. + var collector = make(chan interface{}) + + numParallel := 0 + startParallel := make(chan bool) + + for i := 0; i < len(tests); i++ { + matched, err := matchString(*match, tests[i].Name) + if err != nil { + fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.run: %s\n", err) + os.Exit(1) + } + if !matched { + continue + } + testName := tests[i].Name + if procs != 1 { + testName = fmt.Sprintf("%s-%d", tests[i].Name, procs) + } + t := &T{ + common: common{ + signal: make(chan interface{}), + }, + name: testName, + startParallel: startParallel, + } + t.self = t + if *chatty { + fmt.Printf("=== RUN %s\n", t.name) + } + go tRunner(t, &tests[i]) + out := (<-t.signal).(*T) + if out == nil { // Parallel run. + go func() { + collector <- <-t.signal + }() + numParallel++ + continue + } + t.report() + ok = ok && !out.failed + } + + running := 0 + for numParallel+running > 0 { + if running < *parallel && numParallel > 0 { + startParallel <- true + running++ + numParallel-- + continue + } + t := (<-collector).(*T) + t.report() + ok = ok && !t.failed + running-- + } + } + return +} + +// before runs before all testing. +func before() { + if *memProfileRate > 0 { + runtime.MemProfileRate = *memProfileRate + } + if *cpuProfile != "" { + f, err := os.Create(*cpuProfile) + if err != nil { + fmt.Fprintf(os.Stderr, "testing: %s", err) + return + } + if err := pprof.StartCPUProfile(f); err != nil { + fmt.Fprintf(os.Stderr, "testing: can't start cpu profile: %s", err) + f.Close() + return + } + // Could save f so after can call f.Close; not worth the effort. + } + +} + +// after runs after all testing. +func after() { + if *cpuProfile != "" { + pprof.StopCPUProfile() // flushes profile to disk + } + if *memProfile != "" { + f, err := os.Create(*memProfile) + if err != nil { + fmt.Fprintf(os.Stderr, "testing: %s", err) + return + } + if err = pprof.WriteHeapProfile(f); err != nil { + fmt.Fprintf(os.Stderr, "testing: can't write %s: %s", *memProfile, err) + } + f.Close() + } +} + +var timer *time.Timer + +// startAlarm starts an alarm if requested. +func startAlarm() { + if *timeout > 0 { + timer = time.AfterFunc(*timeout, alarm) + } +} + +// stopAlarm turns off the alarm. +func stopAlarm() { + if *timeout > 0 { + timer.Stop() + } +} + +// alarm is called if the timeout expires. +func alarm() { + panic("test timed out") +} + +func parseCpuList() { + if len(*cpuListStr) == 0 { + cpuList = append(cpuList, runtime.GOMAXPROCS(-1)) + } else { + for _, val := range strings.Split(*cpuListStr, ",") { + cpu, err := strconv.Atoi(val) + if err != nil || cpu <= 0 { + fmt.Fprintf(os.Stderr, "testing: invalid value %q for -test.cpu", val) + os.Exit(1) + } + cpuList = append(cpuList, cpu) + } + } +} diff --git a/src/go/format/benchmark_test.go b/src/go/format/benchmark_test.go new file mode 100644 index 0000000..ac19aa3 --- /dev/null +++ b/src/go/format/benchmark_test.go @@ -0,0 +1,91 @@ +// Copyright 2018 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. + +// This file provides a simple framework to add benchmarks +// based on generated input (source) files. + +package format_test + +import ( + "bytes" + "flag" + "fmt" + "go/format" + "os" + "testing" +) + +var debug = flag.Bool("debug", false, "write .src files containing formatting input; for debugging") + +// array1 generates an array literal with n elements of the form: +// +// var _ = [...]byte{ +// // 0 +// 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, +// 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, +// 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, +// 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, +// 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, +// // 40 +// 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, +// 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, +// ... +// +func array1(buf *bytes.Buffer, n int) { + buf.WriteString("var _ = [...]byte{\n") + for i := 0; i < n; { + if i%10 == 0 { + fmt.Fprintf(buf, "\t// %d\n", i) + } + buf.WriteByte('\t') + for j := 0; j < 8; j++ { + fmt.Fprintf(buf, "0x%02x, ", byte(i)) + i++ + } + buf.WriteString("\n") + } + buf.WriteString("}\n") +} + +var tests = []struct { + name string + gen func(*bytes.Buffer, int) + n int +}{ + {"array1", array1, 10000}, + // add new test cases here as needed +} + +func BenchmarkFormat(b *testing.B) { + var src bytes.Buffer + for _, t := range tests { + src.Reset() + src.WriteString("package p\n") + t.gen(&src, t.n) + data := src.Bytes() + + if *debug { + filename := t.name + ".src" + err := os.WriteFile(filename, data, 0660) + if err != nil { + b.Fatalf("couldn't write %s: %v", filename, err) + } + } + + b.Run(fmt.Sprintf("%s-%d", t.name, t.n), func(b *testing.B) { + b.SetBytes(int64(len(data))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + var err error + sink, err = format.Source(data) + if err != nil { + b.Fatal(err) + } + } + }) + } +} + +var sink []byte diff --git a/src/go/format/example_test.go b/src/go/format/example_test.go new file mode 100644 index 0000000..5b6789a --- /dev/null +++ b/src/go/format/example_test.go @@ -0,0 +1,39 @@ +// Copyright 2018 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 format_test + +import ( + "bytes" + "fmt" + "go/format" + "go/parser" + "go/token" + "log" +) + +func ExampleNode() { + const expr = "(6+2*3)/4" + + // parser.ParseExpr parses the argument and returns the + // corresponding ast.Node. + node, err := parser.ParseExpr(expr) + if err != nil { + log.Fatal(err) + } + + // Create a FileSet for node. Since the node does not come + // from a real source file, fset will be empty. + fset := token.NewFileSet() + + var buf bytes.Buffer + err = format.Node(&buf, fset, node) + if err != nil { + log.Fatal(err) + } + + fmt.Println(buf.String()) + + // Output: (6 + 2*3) / 4 +} diff --git a/src/go/format/format.go b/src/go/format/format.go new file mode 100644 index 0000000..a603d96 --- /dev/null +++ b/src/go/format/format.go @@ -0,0 +1,135 @@ +// Copyright 2012 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 format implements standard formatting of Go source. +// +// Note that formatting of Go source code changes over time, so tools relying on +// consistent formatting should execute a specific version of the gofmt binary +// instead of using this package. That way, the formatting will be stable, and +// the tools won't need to be recompiled each time gofmt changes. +// +// For example, pre-submit checks that use this package directly would behave +// differently depending on what Go version each developer uses, causing the +// check to be inherently fragile. +package format + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "io" +) + +// Keep these in sync with cmd/gofmt/gofmt.go. +const ( + tabWidth = 8 + printerMode = printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers + + // printerNormalizeNumbers means to canonicalize number literal prefixes + // and exponents while printing. See https://golang.org/doc/go1.13#gofmt. + // + // This value is defined in go/printer specifically for go/format and cmd/gofmt. + printerNormalizeNumbers = 1 << 30 +) + +var config = printer.Config{Mode: printerMode, Tabwidth: tabWidth} + +const parserMode = parser.ParseComments + +// Node formats node in canonical gofmt style and writes the result to dst. +// +// The node type must be *ast.File, *printer.CommentedNode, []ast.Decl, +// []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, +// or ast.Stmt. Node does not modify node. Imports are not sorted for +// nodes representing partial source files (for instance, if the node is +// not an *ast.File or a *printer.CommentedNode not wrapping an *ast.File). +// +// The function may return early (before the entire result is written) +// and return a formatting error, for instance due to an incorrect AST. +// +func Node(dst io.Writer, fset *token.FileSet, node interface{}) error { + // Determine if we have a complete source file (file != nil). + var file *ast.File + var cnode *printer.CommentedNode + switch n := node.(type) { + case *ast.File: + file = n + case *printer.CommentedNode: + if f, ok := n.Node.(*ast.File); ok { + file = f + cnode = n + } + } + + // Sort imports if necessary. + if file != nil && hasUnsortedImports(file) { + // Make a copy of the AST because ast.SortImports is destructive. + // TODO(gri) Do this more efficiently. + var buf bytes.Buffer + err := config.Fprint(&buf, fset, file) + if err != nil { + return err + } + file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode) + if err != nil { + // We should never get here. If we do, provide good diagnostic. + return fmt.Errorf("format.Node internal error (%s)", err) + } + ast.SortImports(fset, file) + + // Use new file with sorted imports. + node = file + if cnode != nil { + node = &printer.CommentedNode{Node: file, Comments: cnode.Comments} + } + } + + return config.Fprint(dst, fset, node) +} + +// Source formats src in canonical gofmt style and returns the result +// or an (I/O or syntax) error. src is expected to be a syntactically +// correct Go source file, or a list of Go declarations or statements. +// +// If src is a partial source file, the leading and trailing space of src +// is applied to the result (such that it has the same leading and trailing +// space as src), and the result is indented by the same amount as the first +// line of src containing code. Imports are not sorted for partial source files. +// +func Source(src []byte) ([]byte, error) { + fset := token.NewFileSet() + file, sourceAdj, indentAdj, err := parse(fset, "", src, true) + if err != nil { + return nil, err + } + + if sourceAdj == nil { + // Complete source file. + // TODO(gri) consider doing this always. + ast.SortImports(fset, file) + } + + return format(fset, file, sourceAdj, indentAdj, src, config) +} + +func hasUnsortedImports(file *ast.File) bool { + for _, d := range file.Decls { + d, ok := d.(*ast.GenDecl) + if !ok || d.Tok != token.IMPORT { + // Not an import declaration, so we're done. + // Imports are always first. + return false + } + if d.Lparen.IsValid() { + // For now assume all grouped imports are unsorted. + // TODO(gri) Should check if they are sorted already. + return true + } + // Ungrouped imports are sorted by default. + } + return false +} diff --git a/src/go/format/format_test.go b/src/go/format/format_test.go new file mode 100644 index 0000000..27f4c74 --- /dev/null +++ b/src/go/format/format_test.go @@ -0,0 +1,183 @@ +// Copyright 2012 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 format + +import ( + "bytes" + "go/ast" + "go/parser" + "go/token" + "os" + "strings" + "testing" +) + +const testfile = "format_test.go" + +func diff(t *testing.T, dst, src []byte) { + line := 1 + offs := 0 // line offset + for i := 0; i < len(dst) && i < len(src); i++ { + d := dst[i] + s := src[i] + if d != s { + t.Errorf("dst:%d: %s\n", line, dst[offs:i+1]) + t.Errorf("src:%d: %s\n", line, src[offs:i+1]) + return + } + if s == '\n' { + line++ + offs = i + 1 + } + } + if len(dst) != len(src) { + t.Errorf("len(dst) = %d, len(src) = %d\nsrc = %q", len(dst), len(src), src) + } +} + +func TestNode(t *testing.T) { + src, err := os.ReadFile(testfile) + if err != nil { + t.Fatal(err) + } + + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, testfile, src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + + var buf bytes.Buffer + + if err = Node(&buf, fset, file); err != nil { + t.Fatal("Node failed:", err) + } + + diff(t, buf.Bytes(), src) +} + +// Node is documented to not modify the AST. +// Test that it is so even when numbers are normalized. +func TestNodeNoModify(t *testing.T) { + const ( + src = "package p\n\nconst _ = 0000000123i\n" + golden = "package p\n\nconst _ = 123i\n" + ) + + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "", src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + + // Capture original address and value of a BasicLit node + // which will undergo formatting changes during printing. + wantLit := file.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Values[0].(*ast.BasicLit) + wantVal := wantLit.Value + + var buf bytes.Buffer + if err = Node(&buf, fset, file); err != nil { + t.Fatal("Node failed:", err) + } + diff(t, buf.Bytes(), []byte(golden)) + + // Check if anything changed after Node returned. + gotLit := file.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Values[0].(*ast.BasicLit) + gotVal := gotLit.Value + + if gotLit != wantLit { + t.Errorf("got *ast.BasicLit address %p, want %p", gotLit, wantLit) + } + if gotVal != wantVal { + t.Errorf("got *ast.BasicLit value %q, want %q", gotVal, wantVal) + } +} + +func TestSource(t *testing.T) { + src, err := os.ReadFile(testfile) + if err != nil { + t.Fatal(err) + } + + res, err := Source(src) + if err != nil { + t.Fatal("Source failed:", err) + } + + diff(t, res, src) +} + +// Test cases that are expected to fail are marked by the prefix "ERROR". +// The formatted result must look the same as the input for successful tests. +var tests = []string{ + // declaration lists + `import "go/format"`, + "var x int", + "var x int\n\ntype T struct{}", + + // statement lists + "x := 0", + "f(a, b, c)\nvar x int = f(1, 2, 3)", + + // indentation, leading and trailing space + "\tx := 0\n\tgo f()", + "\tx := 0\n\tgo f()\n\n\n", + "\n\t\t\n\n\tx := 0\n\tgo f()\n\n\n", + "\n\t\t\n\n\t\t\tx := 0\n\t\t\tgo f()\n\n\n", + "\n\t\t\n\n\t\t\tx := 0\n\t\t\tconst s = `\nfoo\n`\n\n\n", // no indentation added inside raw strings + "\n\t\t\n\n\t\t\tx := 0\n\t\t\tconst s = `\n\t\tfoo\n`\n\n\n", // no indentation removed inside raw strings + + // comments + "/* Comment */", + "\t/* Comment */ ", + "\n/* Comment */ ", + "i := 5 /* Comment */", // issue #5551 + "\ta()\n//line :1", // issue #11276 + "\t//xxx\n\ta()\n//line :2", // issue #11276 + "\ta() //line :1\n\tb()\n", // issue #11276 + "x := 0\n//line :1\n//line :2", // issue #11276 + + // whitespace + "", // issue #11275 + " ", // issue #11275 + "\t", // issue #11275 + "\t\t", // issue #11275 + "\n", // issue #11275 + "\n\n", // issue #11275 + "\t\n", // issue #11275 + + // erroneous programs + "ERROR1 + 2 +", + "ERRORx := 0", +} + +func String(s string) (string, error) { + res, err := Source([]byte(s)) + if err != nil { + return "", err + } + return string(res), nil +} + +func TestPartial(t *testing.T) { + for _, src := range tests { + if strings.HasPrefix(src, "ERROR") { + // test expected to fail + src = src[5:] // remove ERROR prefix + res, err := String(src) + if err == nil && res == src { + t.Errorf("formatting succeeded but was expected to fail:\n%q", src) + } + } else { + // test expected to succeed + res, err := String(src) + if err != nil { + t.Errorf("formatting failed (%s):\n%q", err, src) + } else if res != src { + t.Errorf("formatting incorrect:\nsource: %q\nresult: %q", src, res) + } + } + } +} diff --git a/src/go/format/internal.go b/src/go/format/internal.go new file mode 100644 index 0000000..2f3b0e4 --- /dev/null +++ b/src/go/format/internal.go @@ -0,0 +1,176 @@ +// Copyright 2015 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. + +// TODO(gri): This file and the file src/cmd/gofmt/internal.go are +// the same (but for this comment and the package name). Do not modify +// one without the other. Determine if we can factor out functionality +// in a public API. See also #11844 for context. + +package format + +import ( + "bytes" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "strings" +) + +// parse parses src, which was read from the named file, +// as a Go source file, declaration, or statement list. +func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) ( + file *ast.File, + sourceAdj func(src []byte, indent int) []byte, + indentAdj int, + err error, +) { + // Try as whole source file. + file, err = parser.ParseFile(fset, filename, src, parserMode) + // If there's no error, return. If the error is that the source file didn't begin with a + // package line and source fragments are ok, fall through to + // try as a source fragment. Stop and return on any other error. + if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") { + return + } + + // If this is a declaration list, make it a source file + // by inserting a package clause. + // Insert using a ';', not a newline, so that the line numbers + // in psrc match the ones in src. + psrc := append([]byte("package p;"), src...) + file, err = parser.ParseFile(fset, filename, psrc, parserMode) + if err == nil { + sourceAdj = func(src []byte, indent int) []byte { + // Remove the package clause. + // Gofmt has turned the ';' into a '\n'. + src = src[indent+len("package p\n"):] + return bytes.TrimSpace(src) + } + return + } + // If the error is that the source file didn't begin with a + // declaration, fall through to try as a statement list. + // Stop and return on any other error. + if !strings.Contains(err.Error(), "expected declaration") { + return + } + + // If this is a statement list, make it a source file + // by inserting a package clause and turning the list + // into a function body. This handles expressions too. + // Insert using a ';', not a newline, so that the line numbers + // in fsrc match the ones in src. Add an extra '\n' before the '}' + // to make sure comments are flushed before the '}'. + fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '\n', '}') + file, err = parser.ParseFile(fset, filename, fsrc, parserMode) + if err == nil { + sourceAdj = func(src []byte, indent int) []byte { + // Cap adjusted indent to zero. + if indent < 0 { + indent = 0 + } + // Remove the wrapping. + // Gofmt has turned the "; " into a "\n\n". + // There will be two non-blank lines with indent, hence 2*indent. + src = src[2*indent+len("package p\n\nfunc _() {"):] + // Remove only the "}\n" suffix: remaining whitespaces will be trimmed anyway + src = src[:len(src)-len("}\n")] + return bytes.TrimSpace(src) + } + // Gofmt has also indented the function body one level. + // Adjust that with indentAdj. + indentAdj = -1 + } + + // Succeeded, or out of options. + return +} + +// format formats the given package file originally obtained from src +// and adjusts the result based on the original source via sourceAdj +// and indentAdj. +func format( + fset *token.FileSet, + file *ast.File, + sourceAdj func(src []byte, indent int) []byte, + indentAdj int, + src []byte, + cfg printer.Config, +) ([]byte, error) { + if sourceAdj == nil { + // Complete source file. + var buf bytes.Buffer + err := cfg.Fprint(&buf, fset, file) + if err != nil { + return nil, err + } + return buf.Bytes(), nil + } + + // Partial source file. + // Determine and prepend leading space. + i, j := 0, 0 + for j < len(src) && isSpace(src[j]) { + if src[j] == '\n' { + i = j + 1 // byte offset of last line in leading space + } + j++ + } + var res []byte + res = append(res, src[:i]...) + + // Determine and prepend indentation of first code line. + // Spaces are ignored unless there are no tabs, + // in which case spaces count as one tab. + indent := 0 + hasSpace := false + for _, b := range src[i:j] { + switch b { + case ' ': + hasSpace = true + case '\t': + indent++ + } + } + if indent == 0 && hasSpace { + indent = 1 + } + for i := 0; i < indent; i++ { + res = append(res, '\t') + } + + // Format the source. + // Write it without any leading and trailing space. + cfg.Indent = indent + indentAdj + var buf bytes.Buffer + err := cfg.Fprint(&buf, fset, file) + if err != nil { + return nil, err + } + out := sourceAdj(buf.Bytes(), cfg.Indent) + + // If the adjusted output is empty, the source + // was empty but (possibly) for white space. + // The result is the incoming source. + if len(out) == 0 { + return src, nil + } + + // Otherwise, append output to leading space. + res = append(res, out...) + + // Determine and append trailing space. + i = len(src) + for i > 0 && isSpace(src[i-1]) { + i-- + } + return append(res, src[i:]...), nil +} + +// isSpace reports whether the byte is a space character. +// isSpace defines a space as being among the following bytes: ' ', '\t', '\n' and '\r'. +func isSpace(b byte) bool { + return b == ' ' || b == '\t' || b == '\n' || b == '\r' +} diff --git a/src/go/importer/importer.go b/src/go/importer/importer.go new file mode 100644 index 0000000..23118d3 --- /dev/null +++ b/src/go/importer/importer.go @@ -0,0 +1,122 @@ +// Copyright 2015 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 importer provides access to export data importers. +package importer + +import ( + "go/build" + "go/internal/gccgoimporter" + "go/internal/gcimporter" + "go/internal/srcimporter" + "go/token" + "go/types" + "io" + "runtime" +) + +// A Lookup function returns a reader to access package data for +// a given import path, or an error if no matching package is found. +type Lookup func(path string) (io.ReadCloser, error) + +// ForCompiler returns an Importer for importing from installed packages +// for the compilers "gc" and "gccgo", or for importing directly +// from the source if the compiler argument is "source". In this +// latter case, importing may fail under circumstances where the +// exported API is not entirely defined in pure Go source code +// (if the package API depends on cgo-defined entities, the type +// checker won't have access to those). +// +// The lookup function is called each time the resulting importer needs +// to resolve an import path. In this mode the importer can only be +// invoked with canonical import paths (not relative or absolute ones); +// it is assumed that the translation to canonical import paths is being +// done by the client of the importer. +// +// A lookup function must be provided for correct module-aware operation. +// Deprecated: If lookup is nil, for backwards-compatibility, the importer +// will attempt to resolve imports in the $GOPATH workspace. +func ForCompiler(fset *token.FileSet, compiler string, lookup Lookup) types.Importer { + switch compiler { + case "gc": + return &gcimports{ + fset: fset, + packages: make(map[string]*types.Package), + lookup: lookup, + } + + case "gccgo": + var inst gccgoimporter.GccgoInstallation + if err := inst.InitFromDriver("gccgo"); err != nil { + return nil + } + return &gccgoimports{ + packages: make(map[string]*types.Package), + importer: inst.GetImporter(nil, nil), + lookup: lookup, + } + + case "source": + if lookup != nil { + panic("source importer for custom import path lookup not supported (issue #13847).") + } + + return srcimporter.New(&build.Default, fset, make(map[string]*types.Package)) + } + + // compiler not supported + return nil +} + +// For calls ForCompiler with a new FileSet. +// +// Deprecated: Use ForCompiler, which populates a FileSet +// with the positions of objects created by the importer. +func For(compiler string, lookup Lookup) types.Importer { + return ForCompiler(token.NewFileSet(), compiler, lookup) +} + +// Default returns an Importer for the compiler that built the running binary. +// If available, the result implements types.ImporterFrom. +func Default() types.Importer { + return For(runtime.Compiler, nil) +} + +// gc importer + +type gcimports struct { + fset *token.FileSet + packages map[string]*types.Package + lookup Lookup +} + +func (m *gcimports) Import(path string) (*types.Package, error) { + return m.ImportFrom(path, "" /* no vendoring */, 0) +} + +func (m *gcimports) ImportFrom(path, srcDir string, mode types.ImportMode) (*types.Package, error) { + if mode != 0 { + panic("mode must be 0") + } + return gcimporter.Import(m.fset, m.packages, path, srcDir, m.lookup) +} + +// gccgo importer + +type gccgoimports struct { + packages map[string]*types.Package + importer gccgoimporter.Importer + lookup Lookup +} + +func (m *gccgoimports) Import(path string) (*types.Package, error) { + return m.ImportFrom(path, "" /* no vendoring */, 0) +} + +func (m *gccgoimports) ImportFrom(path, srcDir string, mode types.ImportMode) (*types.Package, error) { + if mode != 0 { + panic("mode must be 0") + } + return m.importer(m.packages, path, srcDir, m.lookup) +} diff --git a/src/go/importer/importer_test.go b/src/go/importer/importer_test.go new file mode 100644 index 0000000..0f5121d --- /dev/null +++ b/src/go/importer/importer_test.go @@ -0,0 +1,87 @@ +// Copyright 2017 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 importer + +import ( + "go/token" + "internal/testenv" + "io" + "os" + "os/exec" + "runtime" + "strings" + "testing" +) + +func TestForCompiler(t *testing.T) { + testenv.MustHaveGoBuild(t) + + const thePackage = "math/big" + out, err := exec.Command(testenv.GoToolPath(t), "list", "-f={{context.Compiler}}:{{.Target}}", thePackage).CombinedOutput() + if err != nil { + t.Fatalf("go list %s: %v\n%s", thePackage, err, out) + } + target := strings.TrimSpace(string(out)) + i := strings.Index(target, ":") + compiler, target := target[:i], target[i+1:] + if !strings.HasSuffix(target, ".a") { + t.Fatalf("unexpected package %s target %q (not *.a)", thePackage, target) + } + + if compiler == "gccgo" { + t.Skip("golang.org/issue/22500") + } + + fset := token.NewFileSet() + + t.Run("LookupDefault", func(t *testing.T) { + imp := ForCompiler(fset, compiler, nil) + pkg, err := imp.Import(thePackage) + if err != nil { + t.Fatal(err) + } + if pkg.Path() != thePackage { + t.Fatalf("Path() = %q, want %q", pkg.Path(), thePackage) + } + + // Check that the fileset positions are accurate. + // https://github.com/golang/go#28995 + mathBigInt := pkg.Scope().Lookup("Int") + posn := fset.Position(mathBigInt.Pos()) // "$GOROOT/src/math/big/int.go:25:1" + filename := strings.Replace(posn.Filename, "$GOROOT", runtime.GOROOT(), 1) + data, err := os.ReadFile(filename) + if err != nil { + t.Fatalf("can't read file containing declaration of math/big.Int: %v", err) + } + lines := strings.Split(string(data), "\n") + if posn.Line > len(lines) || !strings.HasPrefix(lines[posn.Line-1], "type Int") { + t.Fatalf("Object %v position %s does not contain its declaration", + mathBigInt, posn) + } + }) + + t.Run("LookupCustom", func(t *testing.T) { + lookup := func(path string) (io.ReadCloser, error) { + if path != "math/bigger" { + t.Fatalf("lookup called with unexpected path %q", path) + } + f, err := os.Open(target) + if err != nil { + t.Fatal(err) + } + return f, nil + } + imp := ForCompiler(fset, compiler, lookup) + pkg, err := imp.Import("math/bigger") + if err != nil { + t.Fatal(err) + } + // Even though we open math/big.a, the import request was for math/bigger + // and that should be recorded in pkg.Path(), at least for the gc toolchain. + if pkg.Path() != "math/bigger" { + t.Fatalf("Path() = %q, want %q", pkg.Path(), "math/bigger") + } + }) +} diff --git a/src/go/internal/gccgoimporter/ar.go b/src/go/internal/gccgoimporter/ar.go new file mode 100644 index 0000000..443aa26 --- /dev/null +++ b/src/go/internal/gccgoimporter/ar.go @@ -0,0 +1,171 @@ +// Copyright 2018 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 gccgoimporter + +import ( + "bytes" + "debug/elf" + "errors" + "fmt" + "internal/xcoff" + "io" + "strconv" + "strings" +) + +// Magic strings for different archive file formats. +const ( + armag = "!\n" + armagt = "!\n" + armagb = "\n" +) + +// Offsets and sizes for fields in a standard archive header. +const ( + arNameOff = 0 + arNameSize = 16 + arDateOff = arNameOff + arNameSize + arDateSize = 12 + arUIDOff = arDateOff + arDateSize + arUIDSize = 6 + arGIDOff = arUIDOff + arUIDSize + arGIDSize = 6 + arModeOff = arGIDOff + arGIDSize + arModeSize = 8 + arSizeOff = arModeOff + arModeSize + arSizeSize = 10 + arFmagOff = arSizeOff + arSizeSize + arFmagSize = 2 + + arHdrSize = arFmagOff + arFmagSize +) + +// The contents of the fmag field of a standard archive header. +const arfmag = "`\n" + +// arExportData takes an archive file and returns a ReadSeeker for the +// export data in that file. This assumes that there is only one +// object in the archive containing export data, which is not quite +// what gccgo does; gccgo concatenates together all the export data +// for all the objects in the file. In practice that case does not arise. +func arExportData(archive io.ReadSeeker) (io.ReadSeeker, error) { + if _, err := archive.Seek(0, io.SeekStart); err != nil { + return nil, err + } + + var buf [len(armag)]byte + if _, err := archive.Read(buf[:]); err != nil { + return nil, err + } + + switch string(buf[:]) { + case armag: + return standardArExportData(archive) + case armagt: + return nil, errors.New("unsupported thin archive") + case armagb: + return aixBigArExportData(archive) + default: + return nil, fmt.Errorf("unrecognized archive file format %q", buf[:]) + } +} + +// standardArExportData returns export data from a standard archive. +func standardArExportData(archive io.ReadSeeker) (io.ReadSeeker, error) { + off := int64(len(armag)) + for { + var hdrBuf [arHdrSize]byte + if _, err := archive.Read(hdrBuf[:]); err != nil { + return nil, err + } + off += arHdrSize + + if bytes.Compare(hdrBuf[arFmagOff:arFmagOff+arFmagSize], []byte(arfmag)) != 0 { + return nil, fmt.Errorf("archive header format header (%q)", hdrBuf[:]) + } + + size, err := strconv.ParseInt(strings.TrimSpace(string(hdrBuf[arSizeOff:arSizeOff+arSizeSize])), 10, 64) + if err != nil { + return nil, fmt.Errorf("error parsing size in archive header (%q): %v", hdrBuf[:], err) + } + + fn := hdrBuf[arNameOff : arNameOff+arNameSize] + if fn[0] == '/' && (fn[1] == ' ' || fn[1] == '/' || bytes.Compare(fn[:8], []byte("/SYM64/ ")) == 0) { + // Archive symbol table or extended name table, + // which we don't care about. + } else { + archiveAt := readerAtFromSeeker(archive) + ret, err := elfFromAr(io.NewSectionReader(archiveAt, off, size)) + if ret != nil || err != nil { + return ret, err + } + } + + if size&1 != 0 { + size++ + } + off += size + if _, err := archive.Seek(off, io.SeekStart); err != nil { + return nil, err + } + } +} + +// elfFromAr tries to get export data from an archive member as an ELF file. +// If there is no export data, this returns nil, nil. +func elfFromAr(member *io.SectionReader) (io.ReadSeeker, error) { + ef, err := elf.NewFile(member) + if err != nil { + return nil, err + } + sec := ef.Section(".go_export") + if sec == nil { + return nil, nil + } + return sec.Open(), nil +} + +// aixBigArExportData returns export data from an AIX big archive. +func aixBigArExportData(archive io.ReadSeeker) (io.ReadSeeker, error) { + archiveAt := readerAtFromSeeker(archive) + arch, err := xcoff.NewArchive(archiveAt) + if err != nil { + return nil, err + } + + for _, mem := range arch.Members { + f, err := arch.GetFile(mem.Name) + if err != nil { + return nil, err + } + sdat := f.CSect(".go_export") + if sdat != nil { + return bytes.NewReader(sdat), nil + } + } + + return nil, fmt.Errorf(".go_export not found in this archive") +} + +// readerAtFromSeeker turns an io.ReadSeeker into an io.ReaderAt. +// This is only safe because there won't be any concurrent seeks +// while this code is executing. +func readerAtFromSeeker(rs io.ReadSeeker) io.ReaderAt { + if ret, ok := rs.(io.ReaderAt); ok { + return ret + } + return seekerReadAt{rs} +} + +type seekerReadAt struct { + seeker io.ReadSeeker +} + +func (sra seekerReadAt) ReadAt(p []byte, off int64) (int, error) { + if _, err := sra.seeker.Seek(off, io.SeekStart); err != nil { + return 0, err + } + return sra.seeker.Read(p) +} diff --git a/src/go/internal/gccgoimporter/gccgoinstallation.go b/src/go/internal/gccgoimporter/gccgoinstallation.go new file mode 100644 index 0000000..e90a3cc --- /dev/null +++ b/src/go/internal/gccgoimporter/gccgoinstallation.go @@ -0,0 +1,97 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "bufio" + "go/types" + exec "internal/execabs" + "os" + "path/filepath" + "strings" +) + +// Information about a specific installation of gccgo. +type GccgoInstallation struct { + // Version of gcc (e.g. 4.8.0). + GccVersion string + + // Target triple (e.g. x86_64-unknown-linux-gnu). + TargetTriple string + + // Built-in library paths used by this installation. + LibPaths []string +} + +// Ask the driver at the given path for information for this GccgoInstallation. +// The given arguments are passed directly to the call of the driver. +func (inst *GccgoInstallation) InitFromDriver(gccgoPath string, args ...string) (err error) { + argv := append([]string{"-###", "-S", "-x", "go", "-"}, args...) + cmd := exec.Command(gccgoPath, argv...) + stderr, err := cmd.StderrPipe() + if err != nil { + return + } + + err = cmd.Start() + if err != nil { + return + } + + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + line := scanner.Text() + switch { + case strings.HasPrefix(line, "Target: "): + inst.TargetTriple = line[8:] + + case line[0] == ' ': + args := strings.Fields(line) + for _, arg := range args[1:] { + if strings.HasPrefix(arg, "-L") { + inst.LibPaths = append(inst.LibPaths, arg[2:]) + } + } + } + } + + argv = append([]string{"-dumpversion"}, args...) + stdout, err := exec.Command(gccgoPath, argv...).Output() + if err != nil { + return + } + inst.GccVersion = strings.TrimSpace(string(stdout)) + + return +} + +// Return the list of export search paths for this GccgoInstallation. +func (inst *GccgoInstallation) SearchPaths() (paths []string) { + for _, lpath := range inst.LibPaths { + spath := filepath.Join(lpath, "go", inst.GccVersion) + fi, err := os.Stat(spath) + if err != nil || !fi.IsDir() { + continue + } + paths = append(paths, spath) + + spath = filepath.Join(spath, inst.TargetTriple) + fi, err = os.Stat(spath) + if err != nil || !fi.IsDir() { + continue + } + paths = append(paths, spath) + } + + paths = append(paths, inst.LibPaths...) + + return +} + +// Return an importer that searches incpaths followed by the gcc installation's +// built-in search paths and the current directory. +func (inst *GccgoInstallation) GetImporter(incpaths []string, initmap map[*types.Package]InitData) Importer { + return GetImporter(append(append(incpaths, inst.SearchPaths()...), "."), initmap) +} diff --git a/src/go/internal/gccgoimporter/gccgoinstallation_test.go b/src/go/internal/gccgoimporter/gccgoinstallation_test.go new file mode 100644 index 0000000..df0188a --- /dev/null +++ b/src/go/internal/gccgoimporter/gccgoinstallation_test.go @@ -0,0 +1,192 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "go/types" + "testing" +) + +// importablePackages is a list of packages that we verify that we can +// import. This should be all standard library packages in all relevant +// versions of gccgo. Note that since gccgo follows a different release +// cycle, and since different systems have different versions installed, +// we can't use the last-two-versions rule of the gc toolchain. +var importablePackages = [...]string{ + "archive/tar", + "archive/zip", + "bufio", + "bytes", + "compress/bzip2", + "compress/flate", + "compress/gzip", + "compress/lzw", + "compress/zlib", + "container/heap", + "container/list", + "container/ring", + "crypto/aes", + "crypto/cipher", + "crypto/des", + "crypto/dsa", + "crypto/ecdsa", + "crypto/elliptic", + "crypto", + "crypto/hmac", + "crypto/md5", + "crypto/rand", + "crypto/rc4", + "crypto/rsa", + "crypto/sha1", + "crypto/sha256", + "crypto/sha512", + "crypto/subtle", + "crypto/tls", + "crypto/x509", + "crypto/x509/pkix", + "database/sql/driver", + "database/sql", + "debug/dwarf", + "debug/elf", + "debug/gosym", + "debug/macho", + "debug/pe", + "encoding/ascii85", + "encoding/asn1", + "encoding/base32", + "encoding/base64", + "encoding/binary", + "encoding/csv", + "encoding/gob", + // "encoding", // Added in GCC 4.9. + "encoding/hex", + "encoding/json", + "encoding/pem", + "encoding/xml", + "errors", + "expvar", + "flag", + "fmt", + "go/ast", + "go/build", + "go/doc", + // "go/format", // Added in GCC 4.8. + "go/parser", + "go/printer", + "go/scanner", + "go/token", + "hash/adler32", + "hash/crc32", + "hash/crc64", + "hash/fnv", + "hash", + "html", + "html/template", + "image/color", + // "image/color/palette", // Added in GCC 4.9. + "image/draw", + "image/gif", + "image", + "image/jpeg", + "image/png", + "index/suffixarray", + "io", + "io/ioutil", + "log", + "log/syslog", + "math/big", + "math/cmplx", + "math", + "math/rand", + "mime", + "mime/multipart", + "net", + "net/http/cgi", + // "net/http/cookiejar", // Added in GCC 4.8. + "net/http/fcgi", + "net/http", + "net/http/httptest", + "net/http/httputil", + "net/http/pprof", + "net/mail", + "net/rpc", + "net/rpc/jsonrpc", + "net/smtp", + "net/textproto", + "net/url", + "os/exec", + "os", + "os/signal", + "os/user", + "path/filepath", + "path", + "reflect", + "regexp", + "regexp/syntax", + "runtime/debug", + "runtime", + "runtime/pprof", + "sort", + "strconv", + "strings", + "sync/atomic", + "sync", + "syscall", + "testing", + "testing/iotest", + "testing/quick", + "text/scanner", + "text/tabwriter", + "text/template", + "text/template/parse", + "time", + "unicode", + "unicode/utf16", + "unicode/utf8", +} + +func TestInstallationImporter(t *testing.T) { + // This test relies on gccgo being around. + gpath := gccgoPath() + if gpath == "" { + t.Skip("This test needs gccgo") + } + + var inst GccgoInstallation + err := inst.InitFromDriver(gpath) + if err != nil { + t.Fatal(err) + } + imp := inst.GetImporter(nil, nil) + + // Ensure we don't regress the number of packages we can parse. First import + // all packages into the same map and then each individually. + pkgMap := make(map[string]*types.Package) + for _, pkg := range importablePackages { + _, err = imp(pkgMap, pkg, ".", nil) + if err != nil { + t.Error(err) + } + } + + for _, pkg := range importablePackages { + _, err = imp(make(map[string]*types.Package), pkg, ".", nil) + if err != nil { + t.Error(err) + } + } + + // Test for certain specific entities in the imported data. + for _, test := range [...]importerTest{ + {pkgpath: "io", name: "Reader", want: "type Reader interface{Read(p []byte) (n int, err error)}"}, + {pkgpath: "io", name: "ReadWriter", want: "type ReadWriter interface{Reader; Writer}"}, + {pkgpath: "math", name: "Pi", want: "const Pi untyped float"}, + {pkgpath: "math", name: "Sin", want: "func Sin(x float64) float64"}, + {pkgpath: "sort", name: "Search", want: "func Search(n int, f func(int) bool) int"}, + {pkgpath: "unsafe", name: "Pointer", want: "type Pointer"}, + } { + runImporterTest(t, imp, nil, &test) + } +} diff --git a/src/go/internal/gccgoimporter/importer.go b/src/go/internal/gccgoimporter/importer.go new file mode 100644 index 0000000..94f2def --- /dev/null +++ b/src/go/internal/gccgoimporter/importer.go @@ -0,0 +1,261 @@ +// Copyright 2013 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 gccgoimporter implements Import for gccgo-generated object files. +package gccgoimporter // import "go/internal/gccgoimporter" + +import ( + "bytes" + "debug/elf" + "fmt" + "go/types" + "internal/xcoff" + "io" + "os" + "path/filepath" + "strings" +) + +// A PackageInit describes an imported package that needs initialization. +type PackageInit struct { + Name string // short package name + InitFunc string // name of init function + Priority int // priority of init function, see InitData.Priority +} + +// The gccgo-specific init data for a package. +type InitData struct { + // Initialization priority of this package relative to other packages. + // This is based on the maximum depth of the package's dependency graph; + // it is guaranteed to be greater than that of its dependencies. + Priority int + + // The list of packages which this package depends on to be initialized, + // including itself if needed. This is the subset of the transitive closure of + // the package's dependencies that need initialization. + Inits []PackageInit +} + +// Locate the file from which to read export data. +// This is intended to replicate the logic in gofrontend. +func findExportFile(searchpaths []string, pkgpath string) (string, error) { + for _, spath := range searchpaths { + pkgfullpath := filepath.Join(spath, pkgpath) + pkgdir, name := filepath.Split(pkgfullpath) + + for _, filepath := range [...]string{ + pkgfullpath, + pkgfullpath + ".gox", + pkgdir + "lib" + name + ".so", + pkgdir + "lib" + name + ".a", + pkgfullpath + ".o", + } { + fi, err := os.Stat(filepath) + if err == nil && !fi.IsDir() { + return filepath, nil + } + } + } + + return "", fmt.Errorf("%s: could not find export data (tried %s)", pkgpath, strings.Join(searchpaths, ":")) +} + +const ( + gccgov1Magic = "v1;\n" + gccgov2Magic = "v2;\n" + gccgov3Magic = "v3;\n" + goimporterMagic = "\n$$ " + archiveMagic = "!" + // Take name from Name method (like on os.File) if present. + if n, ok := rc.(interface{ Name() string }); ok { + fpath = n.Name() + } + } else { + fpath, err = findExportFile(searchpaths, pkgpath) + if err != nil { + return nil, err + } + + r, closer, err := openExportFile(fpath) + if err != nil { + return nil, err + } + if closer != nil { + defer closer.Close() + } + reader = r + } + + var magics string + magics, err = readMagic(reader) + if err != nil { + return + } + + if magics == archiveMagic || magics == aixbigafMagic { + reader, err = arExportData(reader) + if err != nil { + return + } + magics, err = readMagic(reader) + if err != nil { + return + } + } + + switch magics { + case gccgov1Magic, gccgov2Magic, gccgov3Magic: + var p parser + p.init(fpath, reader, imports) + pkg = p.parsePackage() + if initmap != nil { + initmap[pkg] = p.initdata + } + + // Excluded for now: Standard gccgo doesn't support this import format currently. + // case goimporterMagic: + // var data []byte + // data, err = io.ReadAll(reader) + // if err != nil { + // return + // } + // var n int + // n, pkg, err = importer.ImportData(imports, data) + // if err != nil { + // return + // } + + // if initmap != nil { + // suffixreader := bytes.NewReader(data[n:]) + // var p parser + // p.init(fpath, suffixreader, nil) + // p.parseInitData() + // initmap[pkg] = p.initdata + // } + + default: + err = fmt.Errorf("unrecognized magic string: %q", magics) + } + + return + } +} + +// readMagic reads the four bytes at the start of a ReadSeeker and +// returns them as a string. +func readMagic(reader io.ReadSeeker) (string, error) { + var magic [4]byte + if _, err := reader.Read(magic[:]); err != nil { + return "", err + } + if _, err := reader.Seek(0, io.SeekStart); err != nil { + return "", err + } + return string(magic[:]), nil +} diff --git a/src/go/internal/gccgoimporter/importer_test.go b/src/go/internal/gccgoimporter/importer_test.go new file mode 100644 index 0000000..35240c8 --- /dev/null +++ b/src/go/internal/gccgoimporter/importer_test.go @@ -0,0 +1,209 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "go/types" + "internal/testenv" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "testing" +) + +type importerTest struct { + pkgpath, name, want, wantval string + wantinits []string + gccgoVersion int // minimum gccgo version (0 => any) +} + +func runImporterTest(t *testing.T, imp Importer, initmap map[*types.Package]InitData, test *importerTest) { + pkg, err := imp(make(map[string]*types.Package), test.pkgpath, ".", nil) + if err != nil { + t.Error(err) + return + } + + if test.name != "" { + obj := pkg.Scope().Lookup(test.name) + if obj == nil { + t.Errorf("%s: object not found", test.name) + return + } + + got := types.ObjectString(obj, types.RelativeTo(pkg)) + if got != test.want { + t.Errorf("%s: got %q; want %q", test.name, got, test.want) + } + + if test.wantval != "" { + gotval := obj.(*types.Const).Val().String() + if gotval != test.wantval { + t.Errorf("%s: got val %q; want val %q", test.name, gotval, test.wantval) + } + } + } + + if len(test.wantinits) > 0 { + initdata := initmap[pkg] + found := false + // Check that the package's own init function has the package's priority + for _, pkginit := range initdata.Inits { + if pkginit.InitFunc == test.wantinits[0] { + found = true + break + } + } + + if !found { + t.Errorf("%s: could not find expected function %q", test.pkgpath, test.wantinits[0]) + } + + // FIXME: the original version of this test was written against + // the v1 export data scheme for capturing init functions, so it + // verified the priority values. We moved away from the priority + // scheme some time ago; it is not clear how much work it would be + // to validate the new init export data. + } +} + +// When adding tests to this list, be sure to set the 'gccgoVersion' +// field if the testcases uses a "recent" Go addition (ex: aliases). +var importerTests = [...]importerTest{ + {pkgpath: "pointer", name: "Int8Ptr", want: "type Int8Ptr *int8"}, + {pkgpath: "complexnums", name: "NN", want: "const NN untyped complex", wantval: "(-1 + -1i)"}, + {pkgpath: "complexnums", name: "NP", want: "const NP untyped complex", wantval: "(-1 + 1i)"}, + {pkgpath: "complexnums", name: "PN", want: "const PN untyped complex", wantval: "(1 + -1i)"}, + {pkgpath: "complexnums", name: "PP", want: "const PP untyped complex", wantval: "(1 + 1i)"}, + {pkgpath: "conversions", name: "Bits", want: "const Bits Units", wantval: `"bits"`}, + {pkgpath: "time", name: "Duration", want: "type Duration int64"}, + {pkgpath: "time", name: "Nanosecond", want: "const Nanosecond Duration", wantval: "1"}, + {pkgpath: "unicode", name: "IsUpper", want: "func IsUpper(r rune) bool"}, + {pkgpath: "unicode", name: "MaxRune", want: "const MaxRune untyped rune", wantval: "1114111"}, + {pkgpath: "imports", wantinits: []string{"imports..import", "fmt..import"}}, + {pkgpath: "importsar", name: "Hello", want: "var Hello string"}, + {pkgpath: "aliases", name: "A14", gccgoVersion: 7, want: "type A14 = func(int, T0) chan T2"}, + {pkgpath: "aliases", name: "C0", gccgoVersion: 7, want: "type C0 struct{f1 C1; f2 C1}"}, + {pkgpath: "escapeinfo", name: "NewT", want: "func NewT(data []byte) *T"}, + {pkgpath: "issue27856", name: "M", gccgoVersion: 7, want: "type M struct{E F}"}, + {pkgpath: "v1reflect", name: "Type", want: "type Type interface{Align() int; AssignableTo(u Type) bool; Bits() int; ChanDir() ChanDir; Elem() Type; Field(i int) StructField; FieldAlign() int; FieldByIndex(index []int) StructField; FieldByName(name string) (StructField, bool); FieldByNameFunc(match func(string) bool) (StructField, bool); Implements(u Type) bool; In(i int) Type; IsVariadic() bool; Key() Type; Kind() Kind; Len() int; Method(int) Method; MethodByName(string) (Method, bool); Name() string; NumField() int; NumIn() int; NumMethod() int; NumOut() int; Out(i int) Type; PkgPath() string; Size() uintptr; String() string; common() *commonType; rawString() string; runtimeType() *runtimeType; uncommon() *uncommonType}"}, + {pkgpath: "nointerface", name: "I", want: "type I int"}, + {pkgpath: "issue29198", name: "FooServer", gccgoVersion: 7, want: "type FooServer struct{FooServer *FooServer; user string; ctx context.Context}"}, + {pkgpath: "issue30628", name: "Apple", want: "type Apple struct{hey sync.RWMutex; x int; RQ [517]struct{Count uintptr; NumBytes uintptr; Last uintptr}}"}, + {pkgpath: "issue31540", name: "S", gccgoVersion: 7, want: "type S struct{b int; map[Y]Z}"}, + {pkgpath: "issue34182", name: "T1", want: "type T1 struct{f *T2}"}, + {pkgpath: "notinheap", name: "S", want: "type S struct{}"}, +} + +func TestGoxImporter(t *testing.T) { + testenv.MustHaveExec(t) + initmap := make(map[*types.Package]InitData) + imp := GetImporter([]string{"testdata"}, initmap) + + for _, test := range importerTests { + runImporterTest(t, imp, initmap, &test) + } +} + +// gccgoPath returns a path to gccgo if it is present (either in +// path or specified via GCCGO environment variable), or an +// empty string if no gccgo is available. +func gccgoPath() string { + gccgoname := os.Getenv("GCCGO") + if gccgoname == "" { + gccgoname = "gccgo" + } + if gpath, gerr := exec.LookPath(gccgoname); gerr == nil { + return gpath + } + return "" +} + +func TestObjImporter(t *testing.T) { + // This test relies on gccgo being around. + gpath := gccgoPath() + if gpath == "" { + t.Skip("This test needs gccgo") + } + + verout, err := exec.Command(gpath, "--version").CombinedOutput() + if err != nil { + t.Logf("%s", verout) + t.Fatal(err) + } + vers := regexp.MustCompile(`([0-9]+)\.([0-9]+)`).FindSubmatch(verout) + if len(vers) == 0 { + t.Fatalf("could not find version number in %s", verout) + } + major, err := strconv.Atoi(string(vers[1])) + if err != nil { + t.Fatal(err) + } + minor, err := strconv.Atoi(string(vers[2])) + if err != nil { + t.Fatal(err) + } + t.Logf("gccgo version %d.%d", major, minor) + + tmpdir, err := os.MkdirTemp("", "TestObjImporter") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + + initmap := make(map[*types.Package]InitData) + imp := GetImporter([]string{tmpdir}, initmap) + + artmpdir, err := os.MkdirTemp("", "TestObjImporter") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(artmpdir) + + arinitmap := make(map[*types.Package]InitData) + arimp := GetImporter([]string{artmpdir}, arinitmap) + + for _, test := range importerTests { + if major < test.gccgoVersion { + // Support for type aliases was added in GCC 7. + t.Logf("skipping %q: not supported before gccgo version %d", test.pkgpath, test.gccgoVersion) + continue + } + + gofile := filepath.Join("testdata", test.pkgpath+".go") + if _, err := os.Stat(gofile); os.IsNotExist(err) { + continue + } + ofile := filepath.Join(tmpdir, test.pkgpath+".o") + afile := filepath.Join(artmpdir, "lib"+test.pkgpath+".a") + + cmd := exec.Command(gpath, "-fgo-pkgpath="+test.pkgpath, "-c", "-o", ofile, gofile) + out, err := cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("gccgo %s failed: %s", gofile, err) + } + + runImporterTest(t, imp, initmap, &test) + + cmd = exec.Command("ar", "cr", afile, ofile) + out, err = cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("ar cr %s %s failed: %s", afile, ofile, err) + } + + runImporterTest(t, arimp, arinitmap, &test) + + if err = os.Remove(ofile); err != nil { + t.Fatal(err) + } + if err = os.Remove(afile); err != nil { + t.Fatal(err) + } + } +} diff --git a/src/go/internal/gccgoimporter/parser.go b/src/go/internal/gccgoimporter/parser.go new file mode 100644 index 0000000..1b1d07d --- /dev/null +++ b/src/go/internal/gccgoimporter/parser.go @@ -0,0 +1,1284 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "bytes" + "errors" + "fmt" + "go/constant" + "go/token" + "go/types" + "io" + "strconv" + "strings" + "text/scanner" + "unicode/utf8" +) + +type parser struct { + scanner *scanner.Scanner + version string // format version + tok rune // current token + lit string // literal string; only valid for Ident, Int, String tokens + pkgpath string // package path of imported package + pkgname string // name of imported package + pkg *types.Package // reference to imported package + imports map[string]*types.Package // package path -> package object + typeList []types.Type // type number -> type + typeData []string // unparsed type data (v3 and later) + fixups []fixupRecord // fixups to apply at end of parsing + initdata InitData // package init priority data + aliases map[int]string // maps saved type number to alias name +} + +// When reading export data it's possible to encounter a defined type +// N1 with an underlying defined type N2 while we are still reading in +// that defined type N2; see issues #29006 and #29198 for instances +// of this. Example: +// +// type N1 N2 +// type N2 struct { +// ... +// p *N1 +// } +// +// To handle such cases, the parser generates a fixup record (below) and +// delays setting of N1's underlying type until parsing is complete, at +// which point fixups are applied. + +type fixupRecord struct { + toUpdate *types.Named // type to modify when fixup is processed + target types.Type // type that was incomplete when fixup was created +} + +func (p *parser) init(filename string, src io.Reader, imports map[string]*types.Package) { + p.scanner = new(scanner.Scanner) + p.initScanner(filename, src) + p.imports = imports + p.aliases = make(map[int]string) + p.typeList = make([]types.Type, 1 /* type numbers start at 1 */, 16) +} + +func (p *parser) initScanner(filename string, src io.Reader) { + p.scanner.Init(src) + p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) } + p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings + p.scanner.Whitespace = 1<<'\t' | 1<<' ' + p.scanner.Filename = filename // for good error messages + p.next() +} + +type importError struct { + pos scanner.Position + err error +} + +func (e importError) Error() string { + return fmt.Sprintf("import error %s (byte offset = %d): %s", e.pos, e.pos.Offset, e.err) +} + +func (p *parser) error(err interface{}) { + if s, ok := err.(string); ok { + err = errors.New(s) + } + // panic with a runtime.Error if err is not an error + panic(importError{p.scanner.Pos(), err.(error)}) +} + +func (p *parser) errorf(format string, args ...interface{}) { + p.error(fmt.Errorf(format, args...)) +} + +func (p *parser) expect(tok rune) string { + lit := p.lit + if p.tok != tok { + p.errorf("expected %s, got %s (%s)", scanner.TokenString(tok), scanner.TokenString(p.tok), lit) + } + p.next() + return lit +} + +func (p *parser) expectEOL() { + if p.version == "v1" || p.version == "v2" { + p.expect(';') + } + p.expect('\n') +} + +func (p *parser) expectKeyword(keyword string) { + lit := p.expect(scanner.Ident) + if lit != keyword { + p.errorf("expected keyword %s, got %q", keyword, lit) + } +} + +func (p *parser) parseString() string { + str, err := strconv.Unquote(p.expect(scanner.String)) + if err != nil { + p.error(err) + } + return str +} + +// unquotedString = { unquotedStringChar } . +// unquotedStringChar = . +func (p *parser) parseUnquotedString() string { + if p.tok == scanner.EOF { + p.error("unexpected EOF") + } + var buf bytes.Buffer + buf.WriteString(p.scanner.TokenText()) + // This loop needs to examine each character before deciding whether to consume it. If we see a semicolon, + // we need to let it be consumed by p.next(). + for ch := p.scanner.Peek(); ch != '\n' && ch != ';' && ch != scanner.EOF && p.scanner.Whitespace&(1<" . (optional and ignored) + p.next() + p.expectKeyword("esc") + p.expect(':') + p.expect(scanner.Int) + p.expect('>') + } + if p.tok == '.' { + p.next() + p.expect('.') + p.expect('.') + isVariadic = true + } + typ := p.parseType(pkg) + if isVariadic { + typ = types.NewSlice(typ) + } + param = types.NewParam(token.NoPos, pkg, name, typ) + return +} + +// Var = Name Type . +func (p *parser) parseVar(pkg *types.Package) *types.Var { + name := p.parseName() + v := types.NewVar(token.NoPos, pkg, name, p.parseType(pkg)) + if name[0] == '.' || name[0] == '<' { + // This is an unexported variable, + // or a variable defined in a different package. + // We only want to record exported variables. + return nil + } + return v +} + +// Conversion = "convert" "(" Type "," ConstValue ")" . +func (p *parser) parseConversion(pkg *types.Package) (val constant.Value, typ types.Type) { + p.expectKeyword("convert") + p.expect('(') + typ = p.parseType(pkg) + p.expect(',') + val, _ = p.parseConstValue(pkg) + p.expect(')') + return +} + +// ConstValue = string | "false" | "true" | ["-"] (int ["'"] | FloatOrComplex) | Conversion . +// FloatOrComplex = float ["i" | ("+"|"-") float "i"] . +func (p *parser) parseConstValue(pkg *types.Package) (val constant.Value, typ types.Type) { + // v3 changed to $false, $true, $convert, to avoid confusion + // with variable names in inline function bodies. + if p.tok == '$' { + p.next() + if p.tok != scanner.Ident { + p.errorf("expected identifier after '$', got %s (%q)", scanner.TokenString(p.tok), p.lit) + } + } + + switch p.tok { + case scanner.String: + str := p.parseString() + val = constant.MakeString(str) + typ = types.Typ[types.UntypedString] + return + + case scanner.Ident: + b := false + switch p.lit { + case "false": + case "true": + b = true + + case "convert": + return p.parseConversion(pkg) + + default: + p.errorf("expected const value, got %s (%q)", scanner.TokenString(p.tok), p.lit) + } + + p.next() + val = constant.MakeBool(b) + typ = types.Typ[types.UntypedBool] + return + } + + sign := "" + if p.tok == '-' { + p.next() + sign = "-" + } + + switch p.tok { + case scanner.Int: + val = constant.MakeFromLiteral(sign+p.lit, token.INT, 0) + if val == nil { + p.error("could not parse integer literal") + } + + p.next() + if p.tok == '\'' { + p.next() + typ = types.Typ[types.UntypedRune] + } else { + typ = types.Typ[types.UntypedInt] + } + + case scanner.Float: + re := sign + p.lit + p.next() + + var im string + switch p.tok { + case '+': + p.next() + im = p.expect(scanner.Float) + + case '-': + p.next() + im = "-" + p.expect(scanner.Float) + + case scanner.Ident: + // re is in fact the imaginary component. Expect "i" below. + im = re + re = "0" + + default: + val = constant.MakeFromLiteral(re, token.FLOAT, 0) + if val == nil { + p.error("could not parse float literal") + } + typ = types.Typ[types.UntypedFloat] + return + } + + p.expectKeyword("i") + reval := constant.MakeFromLiteral(re, token.FLOAT, 0) + if reval == nil { + p.error("could not parse real component of complex literal") + } + imval := constant.MakeFromLiteral(im+"i", token.IMAG, 0) + if imval == nil { + p.error("could not parse imag component of complex literal") + } + val = constant.BinaryOp(reval, token.ADD, imval) + typ = types.Typ[types.UntypedComplex] + + default: + p.errorf("expected const value, got %s (%q)", scanner.TokenString(p.tok), p.lit) + } + + return +} + +// Const = Name [Type] "=" ConstValue . +func (p *parser) parseConst(pkg *types.Package) *types.Const { + name := p.parseName() + var typ types.Type + if p.tok == '<' { + typ = p.parseType(pkg) + } + p.expect('=') + val, vtyp := p.parseConstValue(pkg) + if typ == nil { + typ = vtyp + } + return types.NewConst(token.NoPos, pkg, name, typ, val) +} + +// reserved is a singleton type used to fill type map slots that have +// been reserved (i.e., for which a type number has been parsed) but +// which don't have their actual type yet. When the type map is updated, +// the actual type must replace a reserved entry (or we have an internal +// error). Used for self-verification only - not required for correctness. +var reserved = new(struct{ types.Type }) + +// reserve reserves the type map entry n for future use. +func (p *parser) reserve(n int) { + // Notes: + // - for pre-V3 export data, the type numbers we see are + // guaranteed to be in increasing order, so we append a + // reserved entry onto the list. + // - for V3+ export data, type numbers can appear in + // any order, however the 'types' section tells us the + // total number of types, hence typeList is pre-allocated. + if len(p.typeData) == 0 { + if n != len(p.typeList) { + p.errorf("invalid type number %d (out of sync)", n) + } + p.typeList = append(p.typeList, reserved) + } else { + if p.typeList[n] != nil { + p.errorf("previously visited type number %d", n) + } + p.typeList[n] = reserved + } +} + +// update sets the type map entries for the entries in nlist to t. +// An entry in nlist can be a type number in p.typeList, +// used to resolve named types, or it can be a *types.Pointer, +// used to resolve pointers to named types in case they are referenced +// by embedded fields. +func (p *parser) update(t types.Type, nlist []interface{}) { + if t == reserved { + p.errorf("internal error: update(%v) invoked on reserved", nlist) + } + if t == nil { + p.errorf("internal error: update(%v) invoked on nil", nlist) + } + for _, n := range nlist { + switch n := n.(type) { + case int: + if p.typeList[n] == t { + continue + } + if p.typeList[n] != reserved { + p.errorf("internal error: update(%v): %d not reserved", nlist, n) + } + p.typeList[n] = t + case *types.Pointer: + if *n != (types.Pointer{}) { + elem := n.Elem() + if elem == t { + continue + } + p.errorf("internal error: update: pointer already set to %v, expected %v", elem, t) + } + *n = *types.NewPointer(t) + default: + p.errorf("internal error: %T on nlist", n) + } + } +} + +// NamedType = TypeName [ "=" ] Type { Method } . +// TypeName = ExportedName . +// Method = "func" "(" Param ")" Name ParamList ResultList [InlineBody] ";" . +func (p *parser) parseNamedType(nlist []interface{}) types.Type { + pkg, name := p.parseExportedName() + scope := pkg.Scope() + obj := scope.Lookup(name) + if obj != nil && obj.Type() == nil { + p.errorf("%v has nil type", obj) + } + + if p.tok == scanner.Ident && p.lit == "notinheap" { + p.next() + // The go/types package has no way of recording that + // this type is marked notinheap. Presumably no user + // of this package actually cares. + } + + // type alias + if p.tok == '=' { + p.next() + p.aliases[nlist[len(nlist)-1].(int)] = name + if obj != nil { + // use the previously imported (canonical) type + t := obj.Type() + p.update(t, nlist) + p.parseType(pkg) // discard + return t + } + t := p.parseType(pkg, nlist...) + obj = types.NewTypeName(token.NoPos, pkg, name, t) + scope.Insert(obj) + return t + } + + // defined type + if obj == nil { + // A named type may be referred to before the underlying type + // is known - set it up. + tname := types.NewTypeName(token.NoPos, pkg, name, nil) + types.NewNamed(tname, nil, nil) + scope.Insert(tname) + obj = tname + } + + // use the previously imported (canonical), or newly created type + t := obj.Type() + p.update(t, nlist) + + nt, ok := t.(*types.Named) + if !ok { + // This can happen for unsafe.Pointer, which is a TypeName holding a Basic type. + pt := p.parseType(pkg) + if pt != t { + p.error("unexpected underlying type for non-named TypeName") + } + return t + } + + underlying := p.parseType(pkg) + if nt.Underlying() == nil { + if underlying.Underlying() == nil { + fix := fixupRecord{toUpdate: nt, target: underlying} + p.fixups = append(p.fixups, fix) + } else { + nt.SetUnderlying(underlying.Underlying()) + } + } + + if p.tok == '\n' { + p.next() + // collect associated methods + for p.tok == scanner.Ident { + p.expectKeyword("func") + if p.tok == '/' { + // Skip a /*nointerface*/ or /*asm ID */ comment. + p.expect('/') + p.expect('*') + if p.expect(scanner.Ident) == "asm" { + p.parseUnquotedString() + } + p.expect('*') + p.expect('/') + } + p.expect('(') + receiver, _ := p.parseParam(pkg) + p.expect(')') + name := p.parseName() + params, isVariadic := p.parseParamList(pkg) + results := p.parseResultList(pkg) + p.skipInlineBody() + p.expectEOL() + + sig := types.NewSignature(receiver, params, results, isVariadic) + nt.AddMethod(types.NewFunc(token.NoPos, pkg, name, sig)) + } + } + + return nt +} + +func (p *parser) parseInt64() int64 { + lit := p.expect(scanner.Int) + n, err := strconv.ParseInt(lit, 10, 64) + if err != nil { + p.error(err) + } + return n +} + +func (p *parser) parseInt() int { + lit := p.expect(scanner.Int) + n, err := strconv.ParseInt(lit, 10, 0 /* int */) + if err != nil { + p.error(err) + } + return int(n) +} + +// ArrayOrSliceType = "[" [ int ] "]" Type . +func (p *parser) parseArrayOrSliceType(pkg *types.Package, nlist []interface{}) types.Type { + p.expect('[') + if p.tok == ']' { + p.next() + + t := new(types.Slice) + p.update(t, nlist) + + *t = *types.NewSlice(p.parseType(pkg)) + return t + } + + t := new(types.Array) + p.update(t, nlist) + + len := p.parseInt64() + p.expect(']') + + *t = *types.NewArray(p.parseType(pkg), len) + return t +} + +// MapType = "map" "[" Type "]" Type . +func (p *parser) parseMapType(pkg *types.Package, nlist []interface{}) types.Type { + p.expectKeyword("map") + + t := new(types.Map) + p.update(t, nlist) + + p.expect('[') + key := p.parseType(pkg) + p.expect(']') + elem := p.parseType(pkg) + + *t = *types.NewMap(key, elem) + return t +} + +// ChanType = "chan" ["<-" | "-<"] Type . +func (p *parser) parseChanType(pkg *types.Package, nlist []interface{}) types.Type { + p.expectKeyword("chan") + + t := new(types.Chan) + p.update(t, nlist) + + dir := types.SendRecv + switch p.tok { + case '-': + p.next() + p.expect('<') + dir = types.SendOnly + + case '<': + // don't consume '<' if it belongs to Type + if p.scanner.Peek() == '-' { + p.next() + p.expect('-') + dir = types.RecvOnly + } + } + + *t = *types.NewChan(dir, p.parseType(pkg)) + return t +} + +// StructType = "struct" "{" { Field } "}" . +func (p *parser) parseStructType(pkg *types.Package, nlist []interface{}) types.Type { + p.expectKeyword("struct") + + t := new(types.Struct) + p.update(t, nlist) + + var fields []*types.Var + var tags []string + + p.expect('{') + for p.tok != '}' && p.tok != scanner.EOF { + field, tag := p.parseField(pkg) + p.expect(';') + fields = append(fields, field) + tags = append(tags, tag) + } + p.expect('}') + + *t = *types.NewStruct(fields, tags) + return t +} + +// ParamList = "(" [ { Parameter "," } Parameter ] ")" . +func (p *parser) parseParamList(pkg *types.Package) (*types.Tuple, bool) { + var list []*types.Var + isVariadic := false + + p.expect('(') + for p.tok != ')' && p.tok != scanner.EOF { + if len(list) > 0 { + p.expect(',') + } + par, variadic := p.parseParam(pkg) + list = append(list, par) + if variadic { + if isVariadic { + p.error("... not on final argument") + } + isVariadic = true + } + } + p.expect(')') + + return types.NewTuple(list...), isVariadic +} + +// ResultList = Type | ParamList . +func (p *parser) parseResultList(pkg *types.Package) *types.Tuple { + switch p.tok { + case '<': + p.next() + if p.tok == scanner.Ident && p.lit == "inl" { + return nil + } + taa, _ := p.parseTypeAfterAngle(pkg) + return types.NewTuple(types.NewParam(token.NoPos, pkg, "", taa)) + + case '(': + params, _ := p.parseParamList(pkg) + return params + + default: + return nil + } +} + +// FunctionType = ParamList ResultList . +func (p *parser) parseFunctionType(pkg *types.Package, nlist []interface{}) *types.Signature { + t := new(types.Signature) + p.update(t, nlist) + + params, isVariadic := p.parseParamList(pkg) + results := p.parseResultList(pkg) + + *t = *types.NewSignature(nil, params, results, isVariadic) + return t +} + +// Func = Name FunctionType [InlineBody] . +func (p *parser) parseFunc(pkg *types.Package) *types.Func { + if p.tok == '/' { + // Skip an /*asm ID */ comment. + p.expect('/') + p.expect('*') + if p.expect(scanner.Ident) == "asm" { + p.parseUnquotedString() + } + p.expect('*') + p.expect('/') + } + + name := p.parseName() + f := types.NewFunc(token.NoPos, pkg, name, p.parseFunctionType(pkg, nil)) + p.skipInlineBody() + + if name[0] == '.' || name[0] == '<' || strings.ContainsRune(name, '$') { + // This is an unexported function, + // or a function defined in a different package, + // or a type$equal or type$hash function. + // We only want to record exported functions. + return nil + } + + return f +} + +// InterfaceType = "interface" "{" { ("?" Type | Func) ";" } "}" . +func (p *parser) parseInterfaceType(pkg *types.Package, nlist []interface{}) types.Type { + p.expectKeyword("interface") + + t := new(types.Interface) + p.update(t, nlist) + + var methods []*types.Func + var embeddeds []types.Type + + p.expect('{') + for p.tok != '}' && p.tok != scanner.EOF { + if p.tok == '?' { + p.next() + embeddeds = append(embeddeds, p.parseType(pkg)) + } else { + method := p.parseFunc(pkg) + if method != nil { + methods = append(methods, method) + } + } + p.expect(';') + } + p.expect('}') + + *t = *types.NewInterfaceType(methods, embeddeds) + return t +} + +// PointerType = "*" ("any" | Type) . +func (p *parser) parsePointerType(pkg *types.Package, nlist []interface{}) types.Type { + p.expect('*') + if p.tok == scanner.Ident { + p.expectKeyword("any") + t := types.Typ[types.UnsafePointer] + p.update(t, nlist) + return t + } + + t := new(types.Pointer) + p.update(t, nlist) + + *t = *types.NewPointer(p.parseType(pkg, t)) + + return t +} + +// TypeSpec = NamedType | MapType | ChanType | StructType | InterfaceType | PointerType | ArrayOrSliceType | FunctionType . +func (p *parser) parseTypeSpec(pkg *types.Package, nlist []interface{}) types.Type { + switch p.tok { + case scanner.String: + return p.parseNamedType(nlist) + + case scanner.Ident: + switch p.lit { + case "map": + return p.parseMapType(pkg, nlist) + + case "chan": + return p.parseChanType(pkg, nlist) + + case "struct": + return p.parseStructType(pkg, nlist) + + case "interface": + return p.parseInterfaceType(pkg, nlist) + } + + case '*': + return p.parsePointerType(pkg, nlist) + + case '[': + return p.parseArrayOrSliceType(pkg, nlist) + + case '(': + return p.parseFunctionType(pkg, nlist) + } + + p.errorf("expected type name or literal, got %s", scanner.TokenString(p.tok)) + return nil +} + +const ( + // From gofrontend/go/export.h + // Note that these values are negative in the gofrontend and have been made positive + // in the gccgoimporter. + gccgoBuiltinINT8 = 1 + gccgoBuiltinINT16 = 2 + gccgoBuiltinINT32 = 3 + gccgoBuiltinINT64 = 4 + gccgoBuiltinUINT8 = 5 + gccgoBuiltinUINT16 = 6 + gccgoBuiltinUINT32 = 7 + gccgoBuiltinUINT64 = 8 + gccgoBuiltinFLOAT32 = 9 + gccgoBuiltinFLOAT64 = 10 + gccgoBuiltinINT = 11 + gccgoBuiltinUINT = 12 + gccgoBuiltinUINTPTR = 13 + gccgoBuiltinBOOL = 15 + gccgoBuiltinSTRING = 16 + gccgoBuiltinCOMPLEX64 = 17 + gccgoBuiltinCOMPLEX128 = 18 + gccgoBuiltinERROR = 19 + gccgoBuiltinBYTE = 20 + gccgoBuiltinRUNE = 21 +) + +func lookupBuiltinType(typ int) types.Type { + return [...]types.Type{ + gccgoBuiltinINT8: types.Typ[types.Int8], + gccgoBuiltinINT16: types.Typ[types.Int16], + gccgoBuiltinINT32: types.Typ[types.Int32], + gccgoBuiltinINT64: types.Typ[types.Int64], + gccgoBuiltinUINT8: types.Typ[types.Uint8], + gccgoBuiltinUINT16: types.Typ[types.Uint16], + gccgoBuiltinUINT32: types.Typ[types.Uint32], + gccgoBuiltinUINT64: types.Typ[types.Uint64], + gccgoBuiltinFLOAT32: types.Typ[types.Float32], + gccgoBuiltinFLOAT64: types.Typ[types.Float64], + gccgoBuiltinINT: types.Typ[types.Int], + gccgoBuiltinUINT: types.Typ[types.Uint], + gccgoBuiltinUINTPTR: types.Typ[types.Uintptr], + gccgoBuiltinBOOL: types.Typ[types.Bool], + gccgoBuiltinSTRING: types.Typ[types.String], + gccgoBuiltinCOMPLEX64: types.Typ[types.Complex64], + gccgoBuiltinCOMPLEX128: types.Typ[types.Complex128], + gccgoBuiltinERROR: types.Universe.Lookup("error").Type(), + gccgoBuiltinBYTE: types.Universe.Lookup("byte").Type(), + gccgoBuiltinRUNE: types.Universe.Lookup("rune").Type(), + }[typ] +} + +// Type = "<" "type" ( "-" int | int [ TypeSpec ] ) ">" . +// +// parseType updates the type map to t for all type numbers n. +// +func (p *parser) parseType(pkg *types.Package, n ...interface{}) types.Type { + p.expect('<') + t, _ := p.parseTypeAfterAngle(pkg, n...) + return t +} + +// (*parser).Type after reading the "<". +func (p *parser) parseTypeAfterAngle(pkg *types.Package, n ...interface{}) (t types.Type, n1 int) { + p.expectKeyword("type") + + n1 = 0 + switch p.tok { + case scanner.Int: + n1 = p.parseInt() + if p.tok == '>' { + if len(p.typeData) > 0 && p.typeList[n1] == nil { + p.parseSavedType(pkg, n1, n) + } + t = p.typeList[n1] + if len(p.typeData) == 0 && t == reserved { + p.errorf("invalid type cycle, type %d not yet defined (nlist=%v)", n1, n) + } + p.update(t, n) + } else { + p.reserve(n1) + t = p.parseTypeSpec(pkg, append(n, n1)) + } + + case '-': + p.next() + n1 := p.parseInt() + t = lookupBuiltinType(n1) + p.update(t, n) + + default: + p.errorf("expected type number, got %s (%q)", scanner.TokenString(p.tok), p.lit) + return nil, 0 + } + + if t == nil || t == reserved { + p.errorf("internal error: bad return from parseType(%v)", n) + } + + p.expect('>') + return +} + +// parseTypeExtended is identical to parseType, but if the type in +// question is a saved type, returns the index as well as the type +// pointer (index returned is zero if we parsed a builtin). +func (p *parser) parseTypeExtended(pkg *types.Package, n ...interface{}) (t types.Type, n1 int) { + p.expect('<') + t, n1 = p.parseTypeAfterAngle(pkg, n...) + return +} + +// InlineBody = "" .{NN} +// Reports whether a body was skipped. +func (p *parser) skipInlineBody() { + // We may or may not have seen the '<' already, depending on + // whether the function had a result type or not. + if p.tok == '<' { + p.next() + p.expectKeyword("inl") + } else if p.tok != scanner.Ident || p.lit != "inl" { + return + } else { + p.next() + } + + p.expect(':') + want := p.parseInt() + p.expect('>') + + defer func(w uint64) { + p.scanner.Whitespace = w + }(p.scanner.Whitespace) + p.scanner.Whitespace = 0 + + got := 0 + for got < want { + r := p.scanner.Next() + if r == scanner.EOF { + p.error("unexpected EOF") + } + got += utf8.RuneLen(r) + } +} + +// Types = "types" maxp1 exportedp1 (offset length)* . +func (p *parser) parseTypes(pkg *types.Package) { + maxp1 := p.parseInt() + exportedp1 := p.parseInt() + p.typeList = make([]types.Type, maxp1, maxp1) + + type typeOffset struct { + offset int + length int + } + var typeOffsets []typeOffset + + total := 0 + for i := 1; i < maxp1; i++ { + len := p.parseInt() + typeOffsets = append(typeOffsets, typeOffset{total, len}) + total += len + } + + defer func(w uint64) { + p.scanner.Whitespace = w + }(p.scanner.Whitespace) + p.scanner.Whitespace = 0 + + // We should now have p.tok pointing to the final newline. + // The next runes from the scanner should be the type data. + + var sb strings.Builder + for sb.Len() < total { + r := p.scanner.Next() + if r == scanner.EOF { + p.error("unexpected EOF") + } + sb.WriteRune(r) + } + allTypeData := sb.String() + + p.typeData = []string{""} // type 0, unused + for _, to := range typeOffsets { + p.typeData = append(p.typeData, allTypeData[to.offset:to.offset+to.length]) + } + + for i := 1; i < int(exportedp1); i++ { + p.parseSavedType(pkg, i, nil) + } +} + +// parseSavedType parses one saved type definition. +func (p *parser) parseSavedType(pkg *types.Package, i int, nlist []interface{}) { + defer func(s *scanner.Scanner, tok rune, lit string) { + p.scanner = s + p.tok = tok + p.lit = lit + }(p.scanner, p.tok, p.lit) + + p.scanner = new(scanner.Scanner) + p.initScanner(p.scanner.Filename, strings.NewReader(p.typeData[i])) + p.expectKeyword("type") + id := p.parseInt() + if id != i { + p.errorf("type ID mismatch: got %d, want %d", id, i) + } + if p.typeList[i] == reserved { + p.errorf("internal error: %d already reserved in parseSavedType", i) + } + if p.typeList[i] == nil { + p.reserve(i) + p.parseTypeSpec(pkg, append(nlist, i)) + } + if p.typeList[i] == nil || p.typeList[i] == reserved { + p.errorf("internal error: parseSavedType(%d,%v) reserved/nil", i, nlist) + } +} + +// PackageInit = unquotedString unquotedString int . +func (p *parser) parsePackageInit() PackageInit { + name := p.parseUnquotedString() + initfunc := p.parseUnquotedString() + priority := -1 + if p.version == "v1" { + priority = p.parseInt() + } + return PackageInit{Name: name, InitFunc: initfunc, Priority: priority} +} + +// Create the package if we have parsed both the package path and package name. +func (p *parser) maybeCreatePackage() { + if p.pkgname != "" && p.pkgpath != "" { + p.pkg = p.getPkg(p.pkgpath, p.pkgname) + } +} + +// InitDataDirective = ( "v1" | "v2" | "v3" ) ";" | +// "priority" int ";" | +// "init" { PackageInit } ";" | +// "checksum" unquotedString ";" . +func (p *parser) parseInitDataDirective() { + if p.tok != scanner.Ident { + // unexpected token kind; panic + p.expect(scanner.Ident) + } + + switch p.lit { + case "v1", "v2", "v3": + p.version = p.lit + p.next() + p.expect(';') + p.expect('\n') + + case "priority": + p.next() + p.initdata.Priority = p.parseInt() + p.expectEOL() + + case "init": + p.next() + for p.tok != '\n' && p.tok != ';' && p.tok != scanner.EOF { + p.initdata.Inits = append(p.initdata.Inits, p.parsePackageInit()) + } + p.expectEOL() + + case "init_graph": + p.next() + // The graph data is thrown away for now. + for p.tok != '\n' && p.tok != ';' && p.tok != scanner.EOF { + p.parseInt64() + p.parseInt64() + } + p.expectEOL() + + case "checksum": + // Don't let the scanner try to parse the checksum as a number. + defer func(mode uint) { + p.scanner.Mode = mode + }(p.scanner.Mode) + p.scanner.Mode &^= scanner.ScanInts | scanner.ScanFloats + p.next() + p.parseUnquotedString() + p.expectEOL() + + default: + p.errorf("unexpected identifier: %q", p.lit) + } +} + +// Directive = InitDataDirective | +// "package" unquotedString [ unquotedString ] [ unquotedString ] ";" | +// "pkgpath" unquotedString ";" | +// "prefix" unquotedString ";" | +// "import" unquotedString unquotedString string ";" | +// "indirectimport" unquotedString unquotedstring ";" | +// "func" Func ";" | +// "type" Type ";" | +// "var" Var ";" | +// "const" Const ";" . +func (p *parser) parseDirective() { + if p.tok != scanner.Ident { + // unexpected token kind; panic + p.expect(scanner.Ident) + } + + switch p.lit { + case "v1", "v2", "v3", "priority", "init", "init_graph", "checksum": + p.parseInitDataDirective() + + case "package": + p.next() + p.pkgname = p.parseUnquotedString() + p.maybeCreatePackage() + if p.version != "v1" && p.tok != '\n' && p.tok != ';' { + p.parseUnquotedString() + p.parseUnquotedString() + } + p.expectEOL() + + case "pkgpath": + p.next() + p.pkgpath = p.parseUnquotedString() + p.maybeCreatePackage() + p.expectEOL() + + case "prefix": + p.next() + p.pkgpath = p.parseUnquotedString() + p.expectEOL() + + case "import": + p.next() + pkgname := p.parseUnquotedString() + pkgpath := p.parseUnquotedString() + p.getPkg(pkgpath, pkgname) + p.parseString() + p.expectEOL() + + case "indirectimport": + p.next() + pkgname := p.parseUnquotedString() + pkgpath := p.parseUnquotedString() + p.getPkg(pkgpath, pkgname) + p.expectEOL() + + case "types": + p.next() + p.parseTypes(p.pkg) + p.expectEOL() + + case "func": + p.next() + fun := p.parseFunc(p.pkg) + if fun != nil { + p.pkg.Scope().Insert(fun) + } + p.expectEOL() + + case "type": + p.next() + p.parseType(p.pkg) + p.expectEOL() + + case "var": + p.next() + v := p.parseVar(p.pkg) + if v != nil { + p.pkg.Scope().Insert(v) + } + p.expectEOL() + + case "const": + p.next() + c := p.parseConst(p.pkg) + p.pkg.Scope().Insert(c) + p.expectEOL() + + default: + p.errorf("unexpected identifier: %q", p.lit) + } +} + +// Package = { Directive } . +func (p *parser) parsePackage() *types.Package { + for p.tok != scanner.EOF { + p.parseDirective() + } + for _, f := range p.fixups { + if f.target.Underlying() == nil { + p.errorf("internal error: fixup can't be applied, loop required") + } + f.toUpdate.SetUnderlying(f.target.Underlying()) + } + p.fixups = nil + for _, typ := range p.typeList { + if it, ok := typ.(*types.Interface); ok { + it.Complete() + } + } + p.pkg.MarkComplete() + return p.pkg +} diff --git a/src/go/internal/gccgoimporter/parser_test.go b/src/go/internal/gccgoimporter/parser_test.go new file mode 100644 index 0000000..00128b4 --- /dev/null +++ b/src/go/internal/gccgoimporter/parser_test.go @@ -0,0 +1,78 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "bytes" + "go/types" + "strings" + "testing" + "text/scanner" +) + +var typeParserTests = []struct { + id, typ, want, underlying, methods string +}{ + {id: "foo", typ: "", want: "int8"}, + {id: "foo", typ: ">", want: "*error"}, + {id: "foo", typ: "", want: "unsafe.Pointer"}, + {id: "foo", typ: ">>", want: "foo.Bar", underlying: "*foo.Bar"}, + {id: "foo", typ: "\nfunc (? ) M ();\n>", want: "bar.Foo", underlying: "int8", methods: "func (bar.Foo).M()"}, + {id: "foo", typ: ">", want: "bar.foo", underlying: "int8"}, + {id: "foo", typ: ">", want: "[]int8"}, + {id: "foo", typ: ">", want: "[42]int8"}, + {id: "foo", typ: "] >", want: "map[int8]int16"}, + {id: "foo", typ: ">", want: "chan int8"}, + {id: "foo", typ: ">", want: "<-chan int8"}, + {id: "foo", typ: ">", want: "chan<- int8"}, + {id: "foo", typ: "; I16 \"i16\"; }>", want: "struct{I8 int8; I16 int16 \"i16\"}"}, + {id: "foo", typ: ", b ) ; Bar (? , ? ...) (? , ? ); Baz (); }>", want: "interface{Bar(int16, ...int8) (int16, int8); Baz(); Foo(a int8, b int16) int8}"}, + {id: "foo", typ: ") >", want: "func(int8) int16"}, +} + +func TestTypeParser(t *testing.T) { + for _, test := range typeParserTests { + var p parser + p.init("test.gox", strings.NewReader(test.typ), make(map[string]*types.Package)) + p.version = "v2" + p.pkgname = test.id + p.pkgpath = test.id + p.maybeCreatePackage() + typ := p.parseType(p.pkg) + + if p.tok != scanner.EOF { + t.Errorf("expected full parse, stopped at %q", p.lit) + } + + // interfaces must be explicitly completed + if ityp, _ := typ.(*types.Interface); ityp != nil { + ityp.Complete() + } + + got := typ.String() + if got != test.want { + t.Errorf("got type %q, expected %q", got, test.want) + } + + if test.underlying != "" { + underlying := typ.Underlying().String() + if underlying != test.underlying { + t.Errorf("got underlying type %q, expected %q", underlying, test.underlying) + } + } + + if test.methods != "" { + nt := typ.(*types.Named) + var buf bytes.Buffer + for i := 0; i != nt.NumMethods(); i++ { + buf.WriteString(nt.Method(i).String()) + } + methods := buf.String() + if methods != test.methods { + t.Errorf("got methods %q, expected %q", methods, test.methods) + } + } + } +} diff --git a/src/go/internal/gccgoimporter/testdata/aliases.go b/src/go/internal/gccgoimporter/testdata/aliases.go new file mode 100644 index 0000000..cfb59b3 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/aliases.go @@ -0,0 +1,65 @@ +package aliases + +type ( + T0 [10]int + T1 []byte + T2 struct { + x int + } + T3 interface { + m() T2 + } + T4 func(int, T0) chan T2 +) + +// basic aliases +type ( + Ai = int + A0 = T0 + A1 = T1 + A2 = T2 + A3 = T3 + A4 = T4 + + A10 = [10]int + A11 = []byte + A12 = struct { + x int + } + A13 = interface { + m() A2 + } + A14 = func(int, A0) chan A2 +) + +// alias receiver types +func (T0) m1() {} +func (A0) m2() {} + +// alias receiver types (long type declaration chains) +type ( + V0 = V1 + V1 = (V2) + V2 = (V3) + V3 = T0 +) + +func (V1) n() {} + +// cycles +type C0 struct { + f1 C1 + f2 C2 +} + +type ( + C1 *C0 + C2 = C1 +) + +type ( + C5 struct { + f *C6 + } + C6 = C5 +) diff --git a/src/go/internal/gccgoimporter/testdata/aliases.gox b/src/go/internal/gccgoimporter/testdata/aliases.gox new file mode 100644 index 0000000..2428c06 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/aliases.gox @@ -0,0 +1,33 @@ +v2; +package aliases; +prefix go; +package aliases go.aliases go.aliases; +type > + func (? ) .go.aliases.m1 (); + func (? ) .go.aliases.m2 (); + func (? >>>) .go.aliases.n (); +>>; +type >>>; +type >>; +type >>; +type ; }>>; +type ; }>>>; }>>; +type , ? ) >>>; +type ; +type ; }>>>; +type , ? ) >>>>; +type >; +type >>; .go.aliases.f2 >; }>>; +type ; +type ; +type >>; }>>; +type ; +type ; +type ; +type ; +type ; +type ; +type >; +type ; +type ; +type ; diff --git a/src/go/internal/gccgoimporter/testdata/complexnums.go b/src/go/internal/gccgoimporter/testdata/complexnums.go new file mode 100644 index 0000000..a51b6b0 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/complexnums.go @@ -0,0 +1,6 @@ +package complexnums + +const NN = -1 - 1i +const NP = -1 + 1i +const PN = 1 - 1i +const PP = 1 + 1i diff --git a/src/go/internal/gccgoimporter/testdata/complexnums.gox b/src/go/internal/gccgoimporter/testdata/complexnums.gox new file mode 100644 index 0000000..b66524f --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/complexnums.gox @@ -0,0 +1,8 @@ +v1; +package complexnums; +pkgpath complexnums; +priority 1; +const NN = -0.1E1-0.1E1i ; +const NP = -0.1E1+0.1E1i ; +const PN = 0.1E1-0.1E1i ; +const PP = 0.1E1+0.1E1i ; diff --git a/src/go/internal/gccgoimporter/testdata/conversions.go b/src/go/internal/gccgoimporter/testdata/conversions.go new file mode 100644 index 0000000..653927a --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/conversions.go @@ -0,0 +1,5 @@ +package conversions + +type Units string + +const Bits = Units("bits") diff --git a/src/go/internal/gccgoimporter/testdata/conversions.gox b/src/go/internal/gccgoimporter/testdata/conversions.gox new file mode 100644 index 0000000..7de6cda --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/conversions.gox @@ -0,0 +1,6 @@ +v2; +package conversions; +prefix go; +package conversions go.conversions go.conversions; +const Bits > = convert(, "bits"); +type ; diff --git a/src/go/internal/gccgoimporter/testdata/escapeinfo.go b/src/go/internal/gccgoimporter/testdata/escapeinfo.go new file mode 100644 index 0000000..103ad95 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/escapeinfo.go @@ -0,0 +1,13 @@ +// Test case for escape info in export data. To compile and extract .gox file: +// gccgo -fgo-optimize-allocs -c escapeinfo.go +// objcopy -j .go_export escapeinfo.o escapeinfo.gox + +package escapeinfo + +type T struct{ data []byte } + +func NewT(data []byte) *T { + return &T{data} +} + +func (*T) Read(p []byte) {} diff --git a/src/go/internal/gccgoimporter/testdata/escapeinfo.gox b/src/go/internal/gccgoimporter/testdata/escapeinfo.gox new file mode 100644 index 0000000..1db8156 Binary files /dev/null and b/src/go/internal/gccgoimporter/testdata/escapeinfo.gox differ diff --git a/src/go/internal/gccgoimporter/testdata/imports.go b/src/go/internal/gccgoimporter/testdata/imports.go new file mode 100644 index 0000000..7907316 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/imports.go @@ -0,0 +1,5 @@ +package imports + +import "fmt" + +var Hello = fmt.Sprintf("Hello, world") diff --git a/src/go/internal/gccgoimporter/testdata/imports.gox b/src/go/internal/gccgoimporter/testdata/imports.gox new file mode 100644 index 0000000..958a4f5 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/imports.gox @@ -0,0 +1,7 @@ +v1; +package imports; +pkgpath imports; +priority 7; +import fmt fmt "fmt"; +init imports imports..import 7 math math..import 1 runtime runtime..import 1 strconv strconv..import 2 io io..import 3 reflect reflect..import 3 syscall syscall..import 3 time time..import 4 os os..import 5 fmt fmt..import 6; +var Hello ; diff --git a/src/go/internal/gccgoimporter/testdata/issue27856.go b/src/go/internal/gccgoimporter/testdata/issue27856.go new file mode 100644 index 0000000..bf361e1 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue27856.go @@ -0,0 +1,9 @@ +package lib + +type M struct { + E E +} +type F struct { + _ *M +} +type E = F diff --git a/src/go/internal/gccgoimporter/testdata/issue27856.gox b/src/go/internal/gccgoimporter/testdata/issue27856.gox new file mode 100644 index 0000000..6665e64 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue27856.gox @@ -0,0 +1,9 @@ +v2; +package main; +pkgpath main; +import runtime runtime "runtime"; +init runtime runtime..import sys runtime_internal_sys..import; +init_graph 0 1; +type ; }>>>; }>>>; +type ; +type ; diff --git a/src/go/internal/gccgoimporter/testdata/issue29198.go b/src/go/internal/gccgoimporter/testdata/issue29198.go new file mode 100644 index 0000000..75c2162 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue29198.go @@ -0,0 +1,37 @@ +// Copyright 2018 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 server + +import ( + "context" + "errors" +) + +type A struct { + x int +} + +func (a *A) AMethod(y int) *Server { + return nil +} + +// FooServer is a server that provides Foo services +type FooServer Server + +func (f *FooServer) WriteEvents(ctx context.Context, x int) error { + return errors.New("hey!") +} + +type Server struct { + FooServer *FooServer + user string + ctx context.Context +} + +func New(sctx context.Context, u string) (*Server, error) { + s := &Server{user: u, ctx: sctx} + s.FooServer = (*FooServer)(s) + return s, nil +} diff --git a/src/go/internal/gccgoimporter/testdata/issue29198.gox b/src/go/internal/gccgoimporter/testdata/issue29198.gox new file mode 100644 index 0000000..905c866 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue29198.gox @@ -0,0 +1,86 @@ +v2; +package server; +pkgpath issue29198; +import context context "context"; +import errors errors "errors"; +init context context..import fmt fmt..import poll internal_poll..import testlog internal_testlog..import io io..import os os..import reflect reflect..import runtime runtime..import sys runtime_internal_sys..import strconv strconv..import sync sync..import syscall syscall..import time time..import unicode unicode..import; +init_graph 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 0 10 0 11 0 12 0 13 1 2 1 3 1 4 1 5 1 6 1 7 1 8 1 9 1 10 1 11 1 12 1 13 2 4 2 7 2 8 2 10 2 11 2 12 4 7 4 8 4 10 5 2 5 3 5 4 5 7 5 8 5 10 5 11 5 12 6 7 6 8 6 9 6 10 6 13 7 8 9 7 9 8 10 7 10 8 11 7 11 8 11 10 12 7 12 8 12 10 12 11; +type ; }> + func (a >) AMethod (y ) + func (f >) WriteEvents (ctx ; .time.ext ; .time.loc ; .time.zone ; .time.offset ; .time.isDST ; }>>>; .time.tx ; .time.index ; .time.isstd ; .time.isutc ; }>>>; .time.cacheStart ; .time.cacheEnd ; .time.cacheZone >; }> + func (l >) String () ; + func (l ) .time.lookupFirstZone () ; + func (l ) .time.get () ; + func (l ) .time.lookup (sec ) (name , offset , isDST , start , end ); + func (l ) .time.lookupName (name , unix ) (offset , ok ); + func (l ) .time.firstZoneUsed () ; +>>; }> + func (t ) In (loc ) ; + func (t ) .time.date (full ) (year , month + func (m ) String () ; +>, day , yday ); + func (t ) Sub (u ) + func (d ) Truncate (m ) ; + func (d ) String () ; + func (d ) Round (m ) ; + func (d ) Seconds () ; + func (d ) Nanoseconds () ; + func (d ) Minutes () ; + func (d ) Hours () ; +>; + func (t ) Add (d ) ; + func (t ) UTC () ; + func (t ) AddDate (years , months , days ) ; + func (t ) MarshalBinary () (? >, ? ); + func (t ) Nanosecond () ; + func (t ) Round (d ) ; + func (t ) Minute () ; + func (t ) Clock () (hour , min , sec ); + func (t ) ISOWeek () (year , week ); + func (t ) Day () ; + func (t >) .time.mono () ; + func (t ) UnixNano () ; + func (t ) .time.sec () ; + func (t ) Second () ; + func (t ) Before (u ) ; + func (t ) UnmarshalBinary (data >) ; + func (t ) Month () ; + func (t ) YearDay () ; + func (t ) Location () ; + func (t ) Zone () (name , offset ); + func (t ) Local () ; + func (t ) .time.setLoc (loc ); + func (t ) Truncate (d ) ; + func (t ) MarshalJSON () (? >, ? ); + func (t ) AppendFormat (b >, layout ) >; + func (t ) GobDecode (data >) ; + func (t ) UnmarshalJSON (data >) ; + func (t ) MarshalText () (? >, ? ); + func (t ) GobEncode () (? >, ? ); + func (t ) .time.stripMono (); + func (t ) After (u ) ; + func (t ) Hour () ; + func (t ) UnmarshalText (data >) ; + func (t ) Equal (u ) ; + func (t ) .time.setMono (m ); + func (t ) Year () ; + func (t ) IsZero () ; + func (t ) .time.addSec (d ); + func (t ) Weekday () + func (d ) String () ; +>; + func (t ) String () ; + func (t ) .time.nsec () ; + func (t ) Format (layout ) ; + func (t ) .time.unixSec () ; + func (t ) Unix () ; + func (t ) .time.abs () ; + func (t ) .time.locabs () (name , offset , abs ); + func (t ) Date () (year , month , day ); +>, ok ); Done () >; Err () ; Value (key ) ; }>>, x ) ; +>>; .issue29198.user ; .issue29198.ctx ; }>>>; +>; +type ; +func New (sctx , u ) (? >, ? ); +type ; +checksum 86C8D76B2582F55A8BD2CA9E00060358EC1CE214; diff --git a/src/go/internal/gccgoimporter/testdata/issue30628.go b/src/go/internal/gccgoimporter/testdata/issue30628.go new file mode 100644 index 0000000..8fd7c13 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue30628.go @@ -0,0 +1,18 @@ +package issue30628 + +import ( + "os" + "sync" +) + +const numR = int32(os.O_TRUNC + 5) + +type Apple struct { + hey sync.RWMutex + x int + RQ [numR]struct { + Count uintptr + NumBytes uintptr + Last uintptr + } +} diff --git a/src/go/internal/gccgoimporter/testdata/issue30628.gox b/src/go/internal/gccgoimporter/testdata/issue30628.gox new file mode 100644 index 0000000..0ff6259 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue30628.gox @@ -0,0 +1,28 @@ +v3; +package issue30628 +pkgpath issue30628 +import os os "os" +import sync sync "sync" +init cpu internal..z2fcpu..import poll internal..z2fpoll..import testlog internal..z2ftestlog..import io io..import os os..import runtime runtime..import sys runtime..z2finternal..z2fsys..import sync sync..import syscall syscall..import time time..import +init_graph 1 0 1 3 1 5 1 6 1 7 1 8 1 9 3 0 3 5 3 6 3 7 4 0 4 1 4 2 4 3 4 5 4 6 4 7 4 8 4 9 5 0 5 6 7 0 7 5 7 6 8 0 8 5 8 6 8 7 9 0 9 5 9 6 9 7 9 8 +types 13 2 24 84 208 17 30 41 147 86 17 64 25 75 +type 1 "Apple" +type 2 struct { .issue30628.hey ; .issue30628.x ; RQ ; } +type 3 "sync.RWMutex" + func (rw ) Lock () + func (rw ) RLocker () ($ret8 ) + func (rw ) RUnlock () + func (rw ) Unlock () + func (rw ) RLock () +type 4 * +type 5 "sync.Locker" +type 6 interface { Lock (); Unlock (); } +type 7 struct { .sync.w ; .sync.writerSem ; .sync.readerSem ; .sync.readerCount ; .sync.readerWait ; } +type 8 "sync.Mutex" + func (m ) Unlock () + func (m ) Lock () +type 9 * +type 10 struct { .sync.state ; .sync.sema ; } +type 11 [517 ] +type 12 struct { Count ; NumBytes ; Last ; } +checksum 199DCF6D3EE2FCF39F715B4E42B5F87F5B15D3AF diff --git a/src/go/internal/gccgoimporter/testdata/issue31540.go b/src/go/internal/gccgoimporter/testdata/issue31540.go new file mode 100644 index 0000000..2c6799e --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue31540.go @@ -0,0 +1,26 @@ +// Copyright 2019 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 issue31540 + +type Y struct { + q int +} + +type Z map[int]int + +type X = map[Y]Z + +type A1 = X + +type A2 = A1 + +type S struct { + b int + A2 +} + +func Hallo() S { + return S{} +} diff --git a/src/go/internal/gccgoimporter/testdata/issue31540.gox b/src/go/internal/gccgoimporter/testdata/issue31540.gox new file mode 100644 index 0000000..abdc696 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue31540.gox @@ -0,0 +1,16 @@ +v3; +package issue31540 +pkgpath issue31540 +types 11 7 23 23 20 22 20 21 57 31 45 36 +type 1 "A1" = +type 2 "A2" = +type 3 "S" +type 4 "X" = +type 5 "Y" +type 6 "Z" +type 7 struct { .go.mapalias.b ; ? ; } +type 8 map [] +type 9 struct { .go.mapalias.q ; } +type 10 map [] +func Hallo () +checksum C3FAF2524A90BC11225EE65D059BF27DFB69134B diff --git a/src/go/internal/gccgoimporter/testdata/issue34182.go b/src/go/internal/gccgoimporter/testdata/issue34182.go new file mode 100644 index 0000000..2a5c333 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue34182.go @@ -0,0 +1,17 @@ +// Copyright 2019 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 issue34182 + +type T1 struct { + f *T2 +} + +type T2 struct { + f T3 +} + +type T3 struct { + *T2 +} diff --git a/src/go/internal/gccgoimporter/testdata/issue34182.gox b/src/go/internal/gccgoimporter/testdata/issue34182.gox new file mode 100644 index 0000000..671a7d6 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue34182.gox @@ -0,0 +1,13 @@ +v3; +package issue34182 +pkgpath issue34182 +init issue34182 ~go.issue34182 +types 8 4 21 21 21 17 30 45 45 +type 1 "T1" +type 2 "T2" +type 3 "T3" +type 4 * +type 5 struct { ? ; } +type 6 struct { .go.issue34182.f ; } +type 7 struct { .go.issue34182.f ; } +checksum FF02C49BAF44B06C087ED4E573F7CC880C79C208 diff --git a/src/go/internal/gccgoimporter/testdata/libimportsar.a b/src/go/internal/gccgoimporter/testdata/libimportsar.a new file mode 100644 index 0000000..6f30758 Binary files /dev/null and b/src/go/internal/gccgoimporter/testdata/libimportsar.a differ diff --git a/src/go/internal/gccgoimporter/testdata/nointerface.go b/src/go/internal/gccgoimporter/testdata/nointerface.go new file mode 100644 index 0000000..6a545f2 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/nointerface.go @@ -0,0 +1,12 @@ +// Copyright 2018 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 nointerface + +type I int + +//go:nointerface +func (p *I) Get() int { return int(*p) } + +func (p *I) Set(v int) { *p = I(v) } diff --git a/src/go/internal/gccgoimporter/testdata/nointerface.gox b/src/go/internal/gccgoimporter/testdata/nointerface.gox new file mode 100644 index 0000000..7b73d17 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/nointerface.gox @@ -0,0 +1,8 @@ +v3; +package nointerface +pkgpath nointerface +types 3 2 133 17 +type 1 "I" + func /*nointerface*/ (p ) Get () + func (p ) Set (v ) +type 2 * diff --git a/src/go/internal/gccgoimporter/testdata/notinheap.go b/src/go/internal/gccgoimporter/testdata/notinheap.go new file mode 100644 index 0000000..b1ac967 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/notinheap.go @@ -0,0 +1,4 @@ +package notinheap + +//go:notinheap +type S struct{} diff --git a/src/go/internal/gccgoimporter/testdata/notinheap.gox b/src/go/internal/gccgoimporter/testdata/notinheap.gox new file mode 100644 index 0000000..cc438e7 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/notinheap.gox @@ -0,0 +1,7 @@ +v3; +package notinheap +pkgpath notinheap +init notinheap ~notinheap +types 3 2 30 18 +type 1 "S" notinheap +type 2 struct { } diff --git a/src/go/internal/gccgoimporter/testdata/pointer.go b/src/go/internal/gccgoimporter/testdata/pointer.go new file mode 100644 index 0000000..4ebc671 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/pointer.go @@ -0,0 +1,3 @@ +package pointer + +type Int8Ptr *int8 diff --git a/src/go/internal/gccgoimporter/testdata/pointer.gox b/src/go/internal/gccgoimporter/testdata/pointer.gox new file mode 100644 index 0000000..d96ebbd --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/pointer.gox @@ -0,0 +1,4 @@ +v1; +package pointer; +pkgpath pointer; +type >>; diff --git a/src/go/internal/gccgoimporter/testdata/time.gox b/src/go/internal/gccgoimporter/testdata/time.gox new file mode 100644 index 0000000..80c2dbc Binary files /dev/null and b/src/go/internal/gccgoimporter/testdata/time.gox differ diff --git a/src/go/internal/gccgoimporter/testdata/unicode.gox b/src/go/internal/gccgoimporter/testdata/unicode.gox new file mode 100644 index 0000000..e70e539 Binary files /dev/null and b/src/go/internal/gccgoimporter/testdata/unicode.gox differ diff --git a/src/go/internal/gccgoimporter/testdata/v1reflect.gox b/src/go/internal/gccgoimporter/testdata/v1reflect.gox new file mode 100644 index 0000000..ea46841 Binary files /dev/null and b/src/go/internal/gccgoimporter/testdata/v1reflect.gox differ diff --git a/src/go/internal/gcimporter/exportdata.go b/src/go/internal/gcimporter/exportdata.go new file mode 100644 index 0000000..c12e459 --- /dev/null +++ b/src/go/internal/gcimporter/exportdata.go @@ -0,0 +1,91 @@ +// Copyright 2011 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. + +// This file implements FindExportData. + +package gcimporter + +import ( + "bufio" + "fmt" + "io" + "strconv" + "strings" +) + +func readGopackHeader(r *bufio.Reader) (name string, size int, err error) { + // See $GOROOT/include/ar.h. + hdr := make([]byte, 16+12+6+6+8+10+2) + _, err = io.ReadFull(r, hdr) + if err != nil { + return + } + // leave for debugging + if false { + fmt.Printf("header: %s", hdr) + } + s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10])) + size, err = strconv.Atoi(s) + if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' { + err = fmt.Errorf("invalid archive header") + return + } + name = strings.TrimSpace(string(hdr[:16])) + return +} + +// FindExportData positions the reader r at the beginning of the +// export data section of an underlying GC-created object/archive +// file by reading from it. The reader must be positioned at the +// start of the file before calling this function. The hdr result +// is the string before the export data, either "$$" or "$$B". +// +func FindExportData(r *bufio.Reader) (hdr string, err error) { + // Read first line to make sure this is an object file. + line, err := r.ReadSlice('\n') + if err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + + if string(line) == "!\n" { + // Archive file. Scan to __.PKGDEF. + var name string + if name, _, err = readGopackHeader(r); err != nil { + return + } + + // First entry should be __.PKGDEF. + if name != "__.PKGDEF" { + err = fmt.Errorf("go archive is missing __.PKGDEF") + return + } + + // Read first line of __.PKGDEF data, so that line + // is once again the first line of the input. + if line, err = r.ReadSlice('\n'); err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + } + + // Now at __.PKGDEF in archive or still at beginning of file. + // Either way, line should begin with "go object ". + if !strings.HasPrefix(string(line), "go object ") { + err = fmt.Errorf("not a Go object file") + return + } + + // Skip over object header to export data. + // Begins after first line starting with $$. + for line[0] != '$' { + if line, err = r.ReadSlice('\n'); err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + } + hdr = string(line) + + return +} diff --git a/src/go/internal/gcimporter/gcimporter.go b/src/go/internal/gcimporter/gcimporter.go new file mode 100644 index 0000000..b74daca --- /dev/null +++ b/src/go/internal/gcimporter/gcimporter.go @@ -0,0 +1,174 @@ +// Copyright 2011 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 gcimporter implements Import for gc-generated object files. +package gcimporter // import "go/internal/gcimporter" + +import ( + "bufio" + "fmt" + "go/build" + "go/token" + "go/types" + "io" + "os" + "path/filepath" + "strings" +) + +// debugging/development support +const debug = false + +var pkgExts = [...]string{".a", ".o"} + +// FindPkg returns the filename and unique package id for an import +// path based on package information provided by build.Import (using +// the build.Default build.Context). A relative srcDir is interpreted +// relative to the current working directory. +// If no file was found, an empty filename is returned. +// +func FindPkg(path, srcDir string) (filename, id string) { + if path == "" { + return + } + + var noext string + switch { + default: + // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" + // Don't require the source files to be present. + if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282 + srcDir = abs + } + bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary) + if bp.PkgObj == "" { + id = path // make sure we have an id to print in error message + return + } + noext = strings.TrimSuffix(bp.PkgObj, ".a") + id = bp.ImportPath + + case build.IsLocalImport(path): + // "./x" -> "/this/directory/x.ext", "/this/directory/x" + noext = filepath.Join(srcDir, path) + id = noext + + case filepath.IsAbs(path): + // for completeness only - go/build.Import + // does not support absolute imports + // "/x" -> "/x.ext", "/x" + noext = path + id = path + } + + if false { // for debugging + if path != id { + fmt.Printf("%s -> %s\n", path, id) + } + } + + // try extensions + for _, ext := range pkgExts { + filename = noext + ext + if f, err := os.Stat(filename); err == nil && !f.IsDir() { + return + } + } + + filename = "" // not found + return +} + +// Import imports a gc-generated package given its import path and srcDir, adds +// the corresponding package object to the packages map, and returns the object. +// The packages map must contain all packages already imported. +// +func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) { + var rc io.ReadCloser + var id string + if lookup != nil { + // With custom lookup specified, assume that caller has + // converted path to a canonical import path for use in the map. + if path == "unsafe" { + return types.Unsafe, nil + } + id = path + + // No need to re-import if the package was imported completely before. + if pkg = packages[id]; pkg != nil && pkg.Complete() { + return + } + f, err := lookup(path) + if err != nil { + return nil, err + } + rc = f + } else { + var filename string + filename, id = FindPkg(path, srcDir) + if filename == "" { + if path == "unsafe" { + return types.Unsafe, nil + } + return nil, fmt.Errorf("can't find import: %q", id) + } + + // no need to re-import if the package was imported completely before + if pkg = packages[id]; pkg != nil && pkg.Complete() { + return + } + + // open file + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + // add file name to error + err = fmt.Errorf("%s: %v", filename, err) + } + }() + rc = f + } + defer rc.Close() + + var hdr string + buf := bufio.NewReader(rc) + if hdr, err = FindExportData(buf); err != nil { + return + } + + switch hdr { + case "$$\n": + err = fmt.Errorf("import %q: old textual export format no longer supported (recompile library)", path) + + case "$$B\n": + var data []byte + data, err = io.ReadAll(buf) + if err != nil { + break + } + + // The indexed export format starts with an 'i'; the older + // binary export format starts with a 'c', 'd', or 'v' + // (from "version"). Select appropriate importer. + if len(data) > 0 && data[0] == 'i' { + _, pkg, err = iImportData(fset, packages, data[1:], id) + } else { + err = fmt.Errorf("import %q: old binary export format no longer supported (recompile library)", path) + } + + default: + err = fmt.Errorf("import %q: unknown export data header: %q", path, hdr) + } + + return +} + +type byPath []*types.Package + +func (a byPath) Len() int { return len(a) } +func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() } diff --git a/src/go/internal/gcimporter/gcimporter_test.go b/src/go/internal/gcimporter/gcimporter_test.go new file mode 100644 index 0000000..3c76aaf --- /dev/null +++ b/src/go/internal/gcimporter/gcimporter_test.go @@ -0,0 +1,619 @@ +// Copyright 2011 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 gcimporter + +import ( + "bytes" + "fmt" + "internal/testenv" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "go/token" + "go/types" +) + +// skipSpecialPlatforms causes the test to be skipped for platforms where +// builders (build.golang.org) don't have access to compiled packages for +// import. +func skipSpecialPlatforms(t *testing.T) { + switch platform := runtime.GOOS + "-" + runtime.GOARCH; platform { + case "darwin-arm64": + t.Skipf("no compiled packages available for import on %s", platform) + } +} + +// compile runs the compiler on filename, with dirname as the working directory, +// and writes the output file to outdirname. +func compile(t *testing.T, dirname, filename, outdirname string) string { + // filename must end with ".go" + if !strings.HasSuffix(filename, ".go") { + t.Fatalf("filename doesn't end in .go: %s", filename) + } + basename := filepath.Base(filename) + outname := filepath.Join(outdirname, basename[:len(basename)-2]+"o") + cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-o", outname, filename) + cmd.Dir = dirname + out, err := cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("go tool compile %s failed: %s", filename, err) + } + return outname +} + +func testPath(t *testing.T, path, srcDir string) *types.Package { + t0 := time.Now() + fset := token.NewFileSet() + pkg, err := Import(fset, make(map[string]*types.Package), path, srcDir, nil) + if err != nil { + t.Errorf("testPath(%s): %s", path, err) + return nil + } + t.Logf("testPath(%s): %v", path, time.Since(t0)) + return pkg +} + +const maxTime = 30 * time.Second + +func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) { + dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir) + list, err := os.ReadDir(dirname) + if err != nil { + t.Fatalf("testDir(%s): %s", dirname, err) + } + for _, f := range list { + if time.Now().After(endTime) { + t.Log("testing time used up") + return + } + switch { + case !f.IsDir(): + // try extensions + for _, ext := range pkgExts { + if strings.HasSuffix(f.Name(), ext) { + name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension + if testPath(t, filepath.Join(dir, name), dir) != nil { + nimports++ + } + } + } + case f.IsDir(): + nimports += testDir(t, filepath.Join(dir, f.Name()), endTime) + } + } + return +} + +func mktmpdir(t *testing.T) string { + tmpdir, err := os.MkdirTemp("", "gcimporter_test") + if err != nil { + t.Fatal("mktmpdir:", err) + } + if err := os.Mkdir(filepath.Join(tmpdir, "testdata"), 0700); err != nil { + os.RemoveAll(tmpdir) + t.Fatal("mktmpdir:", err) + } + return tmpdir +} + +func TestImportTestdata(t *testing.T) { + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + + compile(t, "testdata", "exports.go", filepath.Join(tmpdir, "testdata")) + + if pkg := testPath(t, "./testdata/exports", tmpdir); pkg != nil { + // The package's Imports list must include all packages + // explicitly imported by exports.go, plus all packages + // referenced indirectly via exported objects in exports.go. + // With the textual export format, the list may also include + // additional packages that are not strictly required for + // import processing alone (they are exported to err "on + // the safe side"). + // TODO(gri) update the want list to be precise, now that + // the textual export data is gone. + got := fmt.Sprint(pkg.Imports()) + for _, want := range []string{"go/ast", "go/token"} { + if !strings.Contains(got, want) { + t.Errorf(`Package("exports").Imports() = %s, does not contain %s`, got, want) + } + } + } +} + +func TestVersionHandling(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + const dir = "./testdata/versions" + list, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + corruptdir := filepath.Join(tmpdir, "testdata", "versions") + if err := os.Mkdir(corruptdir, 0700); err != nil { + t.Fatal(err) + } + + fset := token.NewFileSet() + + for _, f := range list { + name := f.Name() + if !strings.HasSuffix(name, ".a") { + continue // not a package file + } + if strings.Contains(name, "corrupted") { + continue // don't process a leftover corrupted file + } + pkgpath := "./" + name[:len(name)-2] + + if testing.Verbose() { + t.Logf("importing %s", name) + } + + // test that export data can be imported + _, err := Import(fset, make(map[string]*types.Package), pkgpath, dir, nil) + if err != nil { + // ok to fail if it fails with a no longer supported error for select files + if strings.Contains(err.Error(), "no longer supported") { + switch name { + case "test_go1.7_0.a", "test_go1.7_1.a", + "test_go1.8_4.a", "test_go1.8_5.a", + "test_go1.11_6b.a", "test_go1.11_999b.a": + continue + } + // fall through + } + // ok to fail if it fails with a newer version error for select files + if strings.Contains(err.Error(), "newer version") { + switch name { + case "test_go1.11_999i.a": + continue + } + // fall through + } + t.Errorf("import %q failed: %v", pkgpath, err) + continue + } + + // create file with corrupted export data + // 1) read file + data, err := os.ReadFile(filepath.Join(dir, name)) + if err != nil { + t.Fatal(err) + } + // 2) find export data + i := bytes.Index(data, []byte("\n$$B\n")) + 5 + j := bytes.Index(data[i:], []byte("\n$$\n")) + i + if i < 0 || j < 0 || i > j { + t.Fatalf("export data section not found (i = %d, j = %d)", i, j) + } + // 3) corrupt the data (increment every 7th byte) + for k := j - 13; k >= i; k -= 7 { + data[k]++ + } + // 4) write the file + pkgpath += "_corrupted" + filename := filepath.Join(corruptdir, pkgpath) + ".a" + os.WriteFile(filename, data, 0666) + + // test that importing the corrupted file results in an error + _, err = Import(fset, make(map[string]*types.Package), pkgpath, corruptdir, nil) + if err == nil { + t.Errorf("import corrupted %q succeeded", pkgpath) + } else if msg := err.Error(); !strings.Contains(msg, "version skew") { + t.Errorf("import %q error incorrect (%s)", pkgpath, msg) + } + } +} + +func TestImportStdLib(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + dt := maxTime + if testing.Short() && testenv.Builder() == "" { + dt = 10 * time.Millisecond + } + nimports := testDir(t, "", time.Now().Add(dt)) // installed packages + t.Logf("tested %d imports", nimports) +} + +var importedObjectTests = []struct { + name string + want string +}{ + // non-interfaces + {"crypto.Hash", "type Hash uint"}, + {"go/ast.ObjKind", "type ObjKind int"}, + {"go/types.Qualifier", "type Qualifier func(*Package) string"}, + {"go/types.Comparable", "func Comparable(T Type) bool"}, + {"math.Pi", "const Pi untyped float"}, + {"math.Sin", "func Sin(x float64) float64"}, + {"go/ast.NotNilFilter", "func NotNilFilter(_ string, v reflect.Value) bool"}, + {"go/internal/gcimporter.FindPkg", "func FindPkg(path string, srcDir string) (filename string, id string)"}, + + // interfaces + {"context.Context", "type Context interface{Deadline() (deadline time.Time, ok bool); Done() <-chan struct{}; Err() error; Value(key interface{}) interface{}}"}, + {"crypto.Decrypter", "type Decrypter interface{Decrypt(rand io.Reader, msg []byte, opts DecrypterOpts) (plaintext []byte, err error); Public() PublicKey}"}, + {"encoding.BinaryMarshaler", "type BinaryMarshaler interface{MarshalBinary() (data []byte, err error)}"}, + {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"}, + {"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"}, + {"go/ast.Node", "type Node interface{End() go/token.Pos; Pos() go/token.Pos}"}, + {"go/types.Type", "type Type interface{String() string; Underlying() Type}"}, +} + +func TestImportedTypes(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + fset := token.NewFileSet() + for _, test := range importedObjectTests { + s := strings.Split(test.name, ".") + if len(s) != 2 { + t.Fatal("inconsistent test data") + } + importPath := s[0] + objName := s[1] + + pkg, err := Import(fset, make(map[string]*types.Package), importPath, ".", nil) + if err != nil { + t.Error(err) + continue + } + + obj := pkg.Scope().Lookup(objName) + if obj == nil { + t.Errorf("%s: object not found", test.name) + continue + } + + got := types.ObjectString(obj, types.RelativeTo(pkg)) + if got != test.want { + t.Errorf("%s: got %q; want %q", test.name, got, test.want) + } + + if named, _ := obj.Type().(*types.Named); named != nil { + verifyInterfaceMethodRecvs(t, named, 0) + } + } +} + +// verifyInterfaceMethodRecvs verifies that method receiver types +// are named if the methods belong to a named interface type. +func verifyInterfaceMethodRecvs(t *testing.T, named *types.Named, level int) { + // avoid endless recursion in case of an embedding bug that lead to a cycle + if level > 10 { + t.Errorf("%s: embeds itself", named) + return + } + + iface, _ := named.Underlying().(*types.Interface) + if iface == nil { + return // not an interface + } + + // check explicitly declared methods + for i := 0; i < iface.NumExplicitMethods(); i++ { + m := iface.ExplicitMethod(i) + recv := m.Type().(*types.Signature).Recv() + if recv == nil { + t.Errorf("%s: missing receiver type", m) + continue + } + if recv.Type() != named { + t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named) + } + } + + // check embedded interfaces (if they are named, too) + for i := 0; i < iface.NumEmbeddeds(); i++ { + // embedding of interfaces cannot have cycles; recursion will terminate + if etype, _ := iface.EmbeddedType(i).(*types.Named); etype != nil { + verifyInterfaceMethodRecvs(t, etype, level+1) + } + } +} + +func TestIssue5815(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + pkg := importPkg(t, "strings", ".") + + scope := pkg.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + if obj.Pkg() == nil { + t.Errorf("no pkg for %s", obj) + } + if tname, _ := obj.(*types.TypeName); tname != nil { + named := tname.Type().(*types.Named) + for i := 0; i < named.NumMethods(); i++ { + m := named.Method(i) + if m.Pkg() == nil { + t.Errorf("no pkg for %s", m) + } + } + } + } +} + +// Smoke test to ensure that imported methods get the correct package. +func TestCorrectMethodPackage(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + imports := make(map[string]*types.Package) + fset := token.NewFileSet() + _, err := Import(fset, imports, "net/http", ".", nil) + if err != nil { + t.Fatal(err) + } + + mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type() + mset := types.NewMethodSet(types.NewPointer(mutex)) // methods of *sync.Mutex + sel := mset.Lookup(nil, "Lock") + lock := sel.Obj().(*types.Func) + if got, want := lock.Pkg().Path(), "sync"; got != want { + t.Errorf("got package path %q; want %q", got, want) + } +} + +func TestIssue13566(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + testoutdir := filepath.Join(tmpdir, "testdata") + + // b.go needs to be compiled from the output directory so that the compiler can + // find the compiled package a. We pass the full path to compile() so that we + // don't have to copy the file to that directory. + bpath, err := filepath.Abs(filepath.Join("testdata", "b.go")) + if err != nil { + t.Fatal(err) + } + compile(t, "testdata", "a.go", testoutdir) + compile(t, testoutdir, bpath, testoutdir) + + // import must succeed (test for issue at hand) + pkg := importPkg(t, "./testdata/b", tmpdir) + + // make sure all indirectly imported packages have names + for _, imp := range pkg.Imports() { + if imp.Name() == "" { + t.Errorf("no name for %s package", imp.Path()) + } + } +} + +func TestIssue13898(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // import go/internal/gcimporter which imports go/types partially + fset := token.NewFileSet() + imports := make(map[string]*types.Package) + _, err := Import(fset, imports, "go/internal/gcimporter", ".", nil) + if err != nil { + t.Fatal(err) + } + + // look for go/types package + var goTypesPkg *types.Package + for path, pkg := range imports { + if path == "go/types" { + goTypesPkg = pkg + break + } + } + if goTypesPkg == nil { + t.Fatal("go/types not found") + } + + // look for go/types.Object type + obj := lookupObj(t, goTypesPkg.Scope(), "Object") + typ, ok := obj.Type().(*types.Named) + if !ok { + t.Fatalf("go/types.Object type is %v; wanted named type", typ) + } + + // lookup go/types.Object.Pkg method + m, index, indirect := types.LookupFieldOrMethod(typ, false, nil, "Pkg") + if m == nil { + t.Fatalf("go/types.Object.Pkg not found (index = %v, indirect = %v)", index, indirect) + } + + // the method must belong to go/types + if m.Pkg().Path() != "go/types" { + t.Fatalf("found %v; want go/types", m.Pkg()) + } +} + +func TestIssue15517(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + + compile(t, "testdata", "p.go", filepath.Join(tmpdir, "testdata")) + + // Multiple imports of p must succeed without redeclaration errors. + // We use an import path that's not cleaned up so that the eventual + // file path for the package is different from the package path; this + // will expose the error if it is present. + // + // (Issue: Both the textual and the binary importer used the file path + // of the package to be imported as key into the shared packages map. + // However, the binary importer then used the package path to identify + // the imported package to mark it as complete; effectively marking the + // wrong package as complete. By using an "unclean" package path, the + // file and package path are different, exposing the problem if present. + // The same issue occurs with vendoring.) + imports := make(map[string]*types.Package) + fset := token.NewFileSet() + for i := 0; i < 3; i++ { + if _, err := Import(fset, imports, "./././testdata/p", tmpdir, nil); err != nil { + t.Fatal(err) + } + } +} + +func TestIssue15920(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + compileAndImportPkg(t, "issue15920") +} + +func TestIssue20046(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + // "./issue20046".V.M must exist + pkg := compileAndImportPkg(t, "issue20046") + obj := lookupObj(t, pkg.Scope(), "V") + if m, index, indirect := types.LookupFieldOrMethod(obj.Type(), false, nil, "M"); m == nil { + t.Fatalf("V.M not found (index = %v, indirect = %v)", index, indirect) + } +} +func TestIssue25301(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + compileAndImportPkg(t, "issue25301") +} + +func TestIssue25596(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + compileAndImportPkg(t, "issue25596") +} + +func importPkg(t *testing.T, path, srcDir string) *types.Package { + fset := token.NewFileSet() + pkg, err := Import(fset, make(map[string]*types.Package), path, srcDir, nil) + if err != nil { + t.Fatal(err) + } + return pkg +} + +func compileAndImportPkg(t *testing.T, name string) *types.Package { + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + compile(t, "testdata", name+".go", filepath.Join(tmpdir, "testdata")) + return importPkg(t, "./testdata/"+name, tmpdir) +} + +func lookupObj(t *testing.T, scope *types.Scope, name string) types.Object { + if obj := scope.Lookup(name); obj != nil { + return obj + } + t.Fatalf("%s not found", name) + return nil +} diff --git a/src/go/internal/gcimporter/iimport.go b/src/go/internal/gcimporter/iimport.go new file mode 100644 index 0000000..c59dd16 --- /dev/null +++ b/src/go/internal/gcimporter/iimport.go @@ -0,0 +1,618 @@ +// Copyright 2018 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. + +// Indexed package import. +// See cmd/compile/internal/gc/iexport.go for the export data format. + +package gcimporter + +import ( + "bytes" + "encoding/binary" + "fmt" + "go/constant" + "go/token" + "go/types" + "io" + "sort" +) + +type intReader struct { + *bytes.Reader + path string +} + +func (r *intReader) int64() int64 { + i, err := binary.ReadVarint(r.Reader) + if err != nil { + errorf("import %q: read varint error: %v", r.path, err) + } + return i +} + +func (r *intReader) uint64() uint64 { + i, err := binary.ReadUvarint(r.Reader) + if err != nil { + errorf("import %q: read varint error: %v", r.path, err) + } + return i +} + +const predeclReserved = 32 + +type itag uint64 + +const ( + // Types + definedType itag = iota + pointerType + sliceType + arrayType + chanType + mapType + signatureType + structType + interfaceType +) + +// iImportData imports a package from the serialized package data +// and returns the number of bytes consumed and a reference to the package. +// If the export data version is not recognized or the format is otherwise +// compromised, an error is returned. +func iImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { + const currentVersion = 1 + version := int64(-1) + defer func() { + if e := recover(); e != nil { + if version > currentVersion { + err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e) + } else { + err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e) + } + } + }() + + r := &intReader{bytes.NewReader(data), path} + + version = int64(r.uint64()) + switch version { + case currentVersion, 0: + default: + errorf("unknown iexport format version %d", version) + } + + sLen := int64(r.uint64()) + dLen := int64(r.uint64()) + + whence, _ := r.Seek(0, io.SeekCurrent) + stringData := data[whence : whence+sLen] + declData := data[whence+sLen : whence+sLen+dLen] + r.Seek(sLen+dLen, io.SeekCurrent) + + p := iimporter{ + ipath: path, + version: int(version), + + stringData: stringData, + stringCache: make(map[uint64]string), + pkgCache: make(map[uint64]*types.Package), + + declData: declData, + pkgIndex: make(map[*types.Package]map[string]uint64), + typCache: make(map[uint64]types.Type), + + fake: fakeFileSet{ + fset: fset, + files: make(map[string]*token.File), + }, + } + + for i, pt := range predeclared { + p.typCache[uint64(i)] = pt + } + + pkgList := make([]*types.Package, r.uint64()) + for i := range pkgList { + pkgPathOff := r.uint64() + pkgPath := p.stringAt(pkgPathOff) + pkgName := p.stringAt(r.uint64()) + _ = r.uint64() // package height; unused by go/types + + if pkgPath == "" { + pkgPath = path + } + pkg := imports[pkgPath] + if pkg == nil { + pkg = types.NewPackage(pkgPath, pkgName) + imports[pkgPath] = pkg + } else if pkg.Name() != pkgName { + errorf("conflicting names %s and %s for package %q", pkg.Name(), pkgName, path) + } + + p.pkgCache[pkgPathOff] = pkg + + nameIndex := make(map[string]uint64) + for nSyms := r.uint64(); nSyms > 0; nSyms-- { + name := p.stringAt(r.uint64()) + nameIndex[name] = r.uint64() + } + + p.pkgIndex[pkg] = nameIndex + pkgList[i] = pkg + } + + localpkg := pkgList[0] + + names := make([]string, 0, len(p.pkgIndex[localpkg])) + for name := range p.pkgIndex[localpkg] { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + p.doDecl(localpkg, name) + } + + for _, typ := range p.interfaceList { + typ.Complete() + } + + // record all referenced packages as imports + list := append(([]*types.Package)(nil), pkgList[1:]...) + sort.Sort(byPath(list)) + localpkg.SetImports(list) + + // package was imported completely and without errors + localpkg.MarkComplete() + + consumed, _ := r.Seek(0, io.SeekCurrent) + return int(consumed), localpkg, nil +} + +type iimporter struct { + ipath string + version int + + stringData []byte + stringCache map[uint64]string + pkgCache map[uint64]*types.Package + + declData []byte + pkgIndex map[*types.Package]map[string]uint64 + typCache map[uint64]types.Type + + fake fakeFileSet + interfaceList []*types.Interface +} + +func (p *iimporter) doDecl(pkg *types.Package, name string) { + // See if we've already imported this declaration. + if obj := pkg.Scope().Lookup(name); obj != nil { + return + } + + off, ok := p.pkgIndex[pkg][name] + if !ok { + errorf("%v.%v not in index", pkg, name) + } + + r := &importReader{p: p, currPkg: pkg} + r.declReader.Reset(p.declData[off:]) + + r.obj(name) +} + +func (p *iimporter) stringAt(off uint64) string { + if s, ok := p.stringCache[off]; ok { + return s + } + + slen, n := binary.Uvarint(p.stringData[off:]) + if n <= 0 { + errorf("varint failed") + } + spos := off + uint64(n) + s := string(p.stringData[spos : spos+slen]) + p.stringCache[off] = s + return s +} + +func (p *iimporter) pkgAt(off uint64) *types.Package { + if pkg, ok := p.pkgCache[off]; ok { + return pkg + } + path := p.stringAt(off) + errorf("missing package %q in %q", path, p.ipath) + return nil +} + +func (p *iimporter) typAt(off uint64, base *types.Named) types.Type { + if t, ok := p.typCache[off]; ok && (base == nil || !isInterface(t)) { + return t + } + + if off < predeclReserved { + errorf("predeclared type missing from cache: %v", off) + } + + r := &importReader{p: p} + r.declReader.Reset(p.declData[off-predeclReserved:]) + t := r.doType(base) + + if base == nil || !isInterface(t) { + p.typCache[off] = t + } + return t +} + +type importReader struct { + p *iimporter + declReader bytes.Reader + currPkg *types.Package + prevFile string + prevLine int64 + prevColumn int64 +} + +func (r *importReader) obj(name string) { + tag := r.byte() + pos := r.pos() + + switch tag { + case 'A': + typ := r.typ() + + r.declare(types.NewTypeName(pos, r.currPkg, name, typ)) + + case 'C': + typ, val := r.value() + + r.declare(types.NewConst(pos, r.currPkg, name, typ, val)) + + case 'F': + sig := r.signature(nil) + + r.declare(types.NewFunc(pos, r.currPkg, name, sig)) + + case 'T': + // Types can be recursive. We need to setup a stub + // declaration before recursing. + obj := types.NewTypeName(pos, r.currPkg, name, nil) + named := types.NewNamed(obj, nil, nil) + r.declare(obj) + + underlying := r.p.typAt(r.uint64(), named).Underlying() + named.SetUnderlying(underlying) + + if !isInterface(underlying) { + for n := r.uint64(); n > 0; n-- { + mpos := r.pos() + mname := r.ident() + recv := r.param() + msig := r.signature(recv) + + named.AddMethod(types.NewFunc(mpos, r.currPkg, mname, msig)) + } + } + + case 'V': + typ := r.typ() + + r.declare(types.NewVar(pos, r.currPkg, name, typ)) + + default: + errorf("unexpected tag: %v", tag) + } +} + +func (r *importReader) declare(obj types.Object) { + obj.Pkg().Scope().Insert(obj) +} + +func (r *importReader) value() (typ types.Type, val constant.Value) { + typ = r.typ() + + switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType { + case types.IsBoolean: + val = constant.MakeBool(r.bool()) + + case types.IsString: + val = constant.MakeString(r.string()) + + case types.IsInteger: + val = r.mpint(b) + + case types.IsFloat: + val = r.mpfloat(b) + + case types.IsComplex: + re := r.mpfloat(b) + im := r.mpfloat(b) + val = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + + default: + errorf("unexpected type %v", typ) // panics + panic("unreachable") + } + + return +} + +func intSize(b *types.Basic) (signed bool, maxBytes uint) { + if (b.Info() & types.IsUntyped) != 0 { + return true, 64 + } + + switch b.Kind() { + case types.Float32, types.Complex64: + return true, 3 + case types.Float64, types.Complex128: + return true, 7 + } + + signed = (b.Info() & types.IsUnsigned) == 0 + switch b.Kind() { + case types.Int8, types.Uint8: + maxBytes = 1 + case types.Int16, types.Uint16: + maxBytes = 2 + case types.Int32, types.Uint32: + maxBytes = 4 + default: + maxBytes = 8 + } + + return +} + +func (r *importReader) mpint(b *types.Basic) constant.Value { + signed, maxBytes := intSize(b) + + maxSmall := 256 - maxBytes + if signed { + maxSmall = 256 - 2*maxBytes + } + if maxBytes == 1 { + maxSmall = 256 + } + + n, _ := r.declReader.ReadByte() + if uint(n) < maxSmall { + v := int64(n) + if signed { + v >>= 1 + if n&1 != 0 { + v = ^v + } + } + return constant.MakeInt64(v) + } + + v := -n + if signed { + v = -(n &^ 1) >> 1 + } + if v < 1 || uint(v) > maxBytes { + errorf("weird decoding: %v, %v => %v", n, signed, v) + } + + buf := make([]byte, v) + io.ReadFull(&r.declReader, buf) + + // convert to little endian + // TODO(gri) go/constant should have a more direct conversion function + // (e.g., once it supports a big.Float based implementation) + for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 { + buf[i], buf[j] = buf[j], buf[i] + } + + x := constant.MakeFromBytes(buf) + if signed && n&1 != 0 { + x = constant.UnaryOp(token.SUB, x, 0) + } + return x +} + +func (r *importReader) mpfloat(b *types.Basic) constant.Value { + x := r.mpint(b) + if constant.Sign(x) == 0 { + return x + } + + exp := r.int64() + switch { + case exp > 0: + x = constant.Shift(x, token.SHL, uint(exp)) + case exp < 0: + d := constant.Shift(constant.MakeInt64(1), token.SHL, uint(-exp)) + x = constant.BinaryOp(x, token.QUO, d) + } + return x +} + +func (r *importReader) ident() string { + return r.string() +} + +func (r *importReader) qualifiedIdent() (*types.Package, string) { + name := r.string() + pkg := r.pkg() + return pkg, name +} + +func (r *importReader) pos() token.Pos { + if r.p.version >= 1 { + r.posv1() + } else { + r.posv0() + } + + if r.prevFile == "" && r.prevLine == 0 && r.prevColumn == 0 { + return token.NoPos + } + return r.p.fake.pos(r.prevFile, int(r.prevLine), int(r.prevColumn)) +} + +func (r *importReader) posv0() { + delta := r.int64() + if delta != deltaNewFile { + r.prevLine += delta + } else if l := r.int64(); l == -1 { + r.prevLine += deltaNewFile + } else { + r.prevFile = r.string() + r.prevLine = l + } +} + +func (r *importReader) posv1() { + delta := r.int64() + r.prevColumn += delta >> 1 + if delta&1 != 0 { + delta = r.int64() + r.prevLine += delta >> 1 + if delta&1 != 0 { + r.prevFile = r.string() + } + } +} + +func (r *importReader) typ() types.Type { + return r.p.typAt(r.uint64(), nil) +} + +func isInterface(t types.Type) bool { + _, ok := t.(*types.Interface) + return ok +} + +func (r *importReader) pkg() *types.Package { return r.p.pkgAt(r.uint64()) } +func (r *importReader) string() string { return r.p.stringAt(r.uint64()) } + +func (r *importReader) doType(base *types.Named) types.Type { + switch k := r.kind(); k { + default: + errorf("unexpected kind tag in %q: %v", r.p.ipath, k) + return nil + + case definedType: + pkg, name := r.qualifiedIdent() + r.p.doDecl(pkg, name) + return pkg.Scope().Lookup(name).(*types.TypeName).Type() + case pointerType: + return types.NewPointer(r.typ()) + case sliceType: + return types.NewSlice(r.typ()) + case arrayType: + n := r.uint64() + return types.NewArray(r.typ(), int64(n)) + case chanType: + dir := chanDir(int(r.uint64())) + return types.NewChan(dir, r.typ()) + case mapType: + return types.NewMap(r.typ(), r.typ()) + case signatureType: + r.currPkg = r.pkg() + return r.signature(nil) + + case structType: + r.currPkg = r.pkg() + + fields := make([]*types.Var, r.uint64()) + tags := make([]string, len(fields)) + for i := range fields { + fpos := r.pos() + fname := r.ident() + ftyp := r.typ() + emb := r.bool() + tag := r.string() + + fields[i] = types.NewField(fpos, r.currPkg, fname, ftyp, emb) + tags[i] = tag + } + return types.NewStruct(fields, tags) + + case interfaceType: + r.currPkg = r.pkg() + + embeddeds := make([]types.Type, r.uint64()) + for i := range embeddeds { + _ = r.pos() + embeddeds[i] = r.typ() + } + + methods := make([]*types.Func, r.uint64()) + for i := range methods { + mpos := r.pos() + mname := r.ident() + + // TODO(mdempsky): Matches bimport.go, but I + // don't agree with this. + var recv *types.Var + if base != nil { + recv = types.NewVar(token.NoPos, r.currPkg, "", base) + } + + msig := r.signature(recv) + methods[i] = types.NewFunc(mpos, r.currPkg, mname, msig) + } + + typ := types.NewInterfaceType(methods, embeddeds) + r.p.interfaceList = append(r.p.interfaceList, typ) + return typ + } +} + +func (r *importReader) kind() itag { + return itag(r.uint64()) +} + +func (r *importReader) signature(recv *types.Var) *types.Signature { + params := r.paramList() + results := r.paramList() + variadic := params.Len() > 0 && r.bool() + return types.NewSignature(recv, params, results, variadic) +} + +func (r *importReader) paramList() *types.Tuple { + xs := make([]*types.Var, r.uint64()) + for i := range xs { + xs[i] = r.param() + } + return types.NewTuple(xs...) +} + +func (r *importReader) param() *types.Var { + pos := r.pos() + name := r.ident() + typ := r.typ() + return types.NewParam(pos, r.currPkg, name, typ) +} + +func (r *importReader) bool() bool { + return r.uint64() != 0 +} + +func (r *importReader) int64() int64 { + n, err := binary.ReadVarint(&r.declReader) + if err != nil { + errorf("readVarint: %v", err) + } + return n +} + +func (r *importReader) uint64() uint64 { + n, err := binary.ReadUvarint(&r.declReader) + if err != nil { + errorf("readUvarint: %v", err) + } + return n +} + +func (r *importReader) byte() byte { + x, err := r.declReader.ReadByte() + if err != nil { + errorf("declReader.ReadByte: %v", err) + } + return x +} diff --git a/src/go/internal/gcimporter/support.go b/src/go/internal/gcimporter/support.go new file mode 100644 index 0000000..b8bb14d --- /dev/null +++ b/src/go/internal/gcimporter/support.go @@ -0,0 +1,130 @@ +// Copyright 2015 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. + +// This file implements support functionality for iimport.go. + +package gcimporter + +import ( + "fmt" + "go/token" + "go/types" + "sync" +) + +func errorf(format string, args ...interface{}) { + panic(fmt.Sprintf(format, args...)) +} + +// deltaNewFile is a magic line delta offset indicating a new file. +// We use -64 because it is rare; see issue 20080 and CL 41619. +// -64 is the smallest int that fits in a single byte as a varint. +const deltaNewFile = -64 + +// Synthesize a token.Pos +type fakeFileSet struct { + fset *token.FileSet + files map[string]*token.File +} + +func (s *fakeFileSet) pos(file string, line, column int) token.Pos { + // TODO(mdempsky): Make use of column. + + // Since we don't know the set of needed file positions, we + // reserve maxlines positions per file. + const maxlines = 64 * 1024 + f := s.files[file] + if f == nil { + f = s.fset.AddFile(file, -1, maxlines) + s.files[file] = f + // Allocate the fake linebreak indices on first use. + // TODO(adonovan): opt: save ~512KB using a more complex scheme? + fakeLinesOnce.Do(func() { + fakeLines = make([]int, maxlines) + for i := range fakeLines { + fakeLines[i] = i + } + }) + f.SetLines(fakeLines) + } + + if line > maxlines { + line = 1 + } + + // Treat the file as if it contained only newlines + // and column=1: use the line number as the offset. + return f.Pos(line - 1) +} + +var ( + fakeLines []int + fakeLinesOnce sync.Once +) + +func chanDir(d int) types.ChanDir { + // tag values must match the constants in cmd/compile/internal/gc/go.go + switch d { + case 1 /* Crecv */ : + return types.RecvOnly + case 2 /* Csend */ : + return types.SendOnly + case 3 /* Cboth */ : + return types.SendRecv + default: + errorf("unexpected channel dir %d", d) + return 0 + } +} + +var predeclared = []types.Type{ + // basic types + types.Typ[types.Bool], + types.Typ[types.Int], + types.Typ[types.Int8], + types.Typ[types.Int16], + types.Typ[types.Int32], + types.Typ[types.Int64], + types.Typ[types.Uint], + types.Typ[types.Uint8], + types.Typ[types.Uint16], + types.Typ[types.Uint32], + types.Typ[types.Uint64], + types.Typ[types.Uintptr], + types.Typ[types.Float32], + types.Typ[types.Float64], + types.Typ[types.Complex64], + types.Typ[types.Complex128], + types.Typ[types.String], + + // basic type aliases + types.Universe.Lookup("byte").Type(), + types.Universe.Lookup("rune").Type(), + + // error + types.Universe.Lookup("error").Type(), + + // untyped types + types.Typ[types.UntypedBool], + types.Typ[types.UntypedInt], + types.Typ[types.UntypedRune], + types.Typ[types.UntypedFloat], + types.Typ[types.UntypedComplex], + types.Typ[types.UntypedString], + types.Typ[types.UntypedNil], + + // package unsafe + types.Typ[types.UnsafePointer], + + // invalid type + types.Typ[types.Invalid], // only appears in packages with errors + + // used internally by gc; never used by this package or in .a files + anyType{}, +} + +type anyType struct{} + +func (t anyType) Underlying() types.Type { return t } +func (t anyType) String() string { return "any" } diff --git a/src/go/internal/gcimporter/testdata/a.go b/src/go/internal/gcimporter/testdata/a.go new file mode 100644 index 0000000..56e4292 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/a.go @@ -0,0 +1,14 @@ +// Copyright 2016 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. + +// Input for TestIssue13566 + +package a + +import "encoding/json" + +type A struct { + a *A + json json.RawMessage +} diff --git a/src/go/internal/gcimporter/testdata/b.go b/src/go/internal/gcimporter/testdata/b.go new file mode 100644 index 0000000..4196678 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/b.go @@ -0,0 +1,11 @@ +// Copyright 2016 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. + +// Input for TestIssue13566 + +package b + +import "./a" + +type A a.A diff --git a/src/go/internal/gcimporter/testdata/exports.go b/src/go/internal/gcimporter/testdata/exports.go new file mode 100644 index 0000000..8ba3242 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/exports.go @@ -0,0 +1,88 @@ +// Copyright 2011 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. + +// This file is used to generate an object file which +// serves as test file for gcimporter_test.go. + +package exports + +import "go/ast" + +// Issue 3682: Correctly read dotted identifiers from export data. +const init1 = 0 + +func init() {} + +const ( + C0 int = 0 + C1 = 3.14159265 + C2 = 2.718281828i + C3 = -123.456e-789 + C4 = +123.456e+789 + C5 = 1234i + C6 = "foo\n" + C7 = `bar\n` +) + +type ( + T1 int + T2 [10]int + T3 []int + T4 *int + T5 chan int + T6a chan<- int + T6b chan (<-chan int) + T6c chan<- (chan int) + T7 <-chan *ast.File + T8 struct{} + T9 struct { + a int + b, c float32 + d []string `go:"tag"` + } + T10 struct { + T8 + T9 + _ *T10 + } + T11 map[int]string + T12 interface{} + T13 interface { + m1() + m2(int) float32 + } + T14 interface { + T12 + T13 + m3(x ...struct{}) []T9 + } + T15 func() + T16 func(int) + T17 func(x int) + T18 func() float32 + T19 func() (x float32) + T20 func(...interface{}) + T21 struct{ next *T21 } + T22 struct{ link *T23 } + T23 struct{ link *T22 } + T24 *T24 + T25 *T26 + T26 *T27 + T27 *T25 + T28 func(T28) T28 +) + +var ( + V0 int + V1 = -991.0 + V2 float32 = 1.2 +) + +func F1() {} +func F2(x int) {} +func F3() int { return 0 } +func F4() float32 { return 0 } +func F5(a, b, c int, u, v, w struct{ x, y T1 }, more ...interface{}) (p, q, r chan<- T10) + +func (p *T1) M1() diff --git a/src/go/internal/gcimporter/testdata/issue15920.go b/src/go/internal/gcimporter/testdata/issue15920.go new file mode 100644 index 0000000..c70f7d8 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/issue15920.go @@ -0,0 +1,11 @@ +// Copyright 2016 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 p + +// The underlying type of Error is the underlying type of error. +// Make sure we can import this again without problems. +type Error error + +func F() Error { return nil } diff --git a/src/go/internal/gcimporter/testdata/issue20046.go b/src/go/internal/gcimporter/testdata/issue20046.go new file mode 100644 index 0000000..c63ee82 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/issue20046.go @@ -0,0 +1,9 @@ +// Copyright 2017 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 p + +var V interface { + M() +} diff --git a/src/go/internal/gcimporter/testdata/issue25301.go b/src/go/internal/gcimporter/testdata/issue25301.go new file mode 100644 index 0000000..e3dc98b --- /dev/null +++ b/src/go/internal/gcimporter/testdata/issue25301.go @@ -0,0 +1,17 @@ +// Copyright 2018 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 issue25301 + +type ( + A = interface { + M() + } + T interface { + A + } + S struct{} +) + +func (S) M() { println("m") } diff --git a/src/go/internal/gcimporter/testdata/issue25596.go b/src/go/internal/gcimporter/testdata/issue25596.go new file mode 100644 index 0000000..8923373 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/issue25596.go @@ -0,0 +1,13 @@ +// Copyright 2018 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 issue25596 + +type E interface { + M() T +} + +type T interface { + E +} diff --git a/src/go/internal/gcimporter/testdata/p.go b/src/go/internal/gcimporter/testdata/p.go new file mode 100644 index 0000000..9e2e705 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/p.go @@ -0,0 +1,13 @@ +// Copyright 2016 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. + +// Input for TestIssue15517 + +package p + +const C = 0 + +var V int + +func F() {} diff --git a/src/go/internal/gcimporter/testdata/versions/test.go b/src/go/internal/gcimporter/testdata/versions/test.go new file mode 100644 index 0000000..227fc09 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/versions/test.go @@ -0,0 +1,28 @@ +// Copyright 2016 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. + +// To create a test case for a new export format version, +// build this package with the latest compiler and store +// the resulting .a file appropriately named in the versions +// directory. The VersionHandling test will pick it up. +// +// In the testdata/versions: +// +// go build -o test_go1.$X_$Y.a test.go +// +// with $X = Go version and $Y = export format version +// (add 'b' or 'i' to distinguish between binary and +// indexed format starting with 1.11 as long as both +// formats are supported). +// +// Make sure this source is extended such that it exercises +// whatever export format change has taken place. + +package test + +// Any release before and including Go 1.7 didn't encode +// the package for a blank struct field. +type BlankField struct { + _ int +} diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.11_0i.a b/src/go/internal/gcimporter/testdata/versions/test_go1.11_0i.a new file mode 100644 index 0000000..b00fefe Binary files /dev/null and b/src/go/internal/gcimporter/testdata/versions/test_go1.11_0i.a differ diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.11_6b.a b/src/go/internal/gcimporter/testdata/versions/test_go1.11_6b.a new file mode 100644 index 0000000..c0a211e Binary files /dev/null and b/src/go/internal/gcimporter/testdata/versions/test_go1.11_6b.a differ diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.11_999b.a b/src/go/internal/gcimporter/testdata/versions/test_go1.11_999b.a new file mode 100644 index 0000000..c35d22d Binary files /dev/null and b/src/go/internal/gcimporter/testdata/versions/test_go1.11_999b.a differ diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.11_999i.a b/src/go/internal/gcimporter/testdata/versions/test_go1.11_999i.a new file mode 100644 index 0000000..99401d7 Binary files /dev/null and b/src/go/internal/gcimporter/testdata/versions/test_go1.11_999i.a differ diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.7_0.a b/src/go/internal/gcimporter/testdata/versions/test_go1.7_0.a new file mode 100644 index 0000000..edb6c3f Binary files /dev/null and b/src/go/internal/gcimporter/testdata/versions/test_go1.7_0.a differ diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.7_1.a b/src/go/internal/gcimporter/testdata/versions/test_go1.7_1.a new file mode 100644 index 0000000..554d04a Binary files /dev/null and b/src/go/internal/gcimporter/testdata/versions/test_go1.7_1.a differ diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.8_4.a b/src/go/internal/gcimporter/testdata/versions/test_go1.8_4.a new file mode 100644 index 0000000..26b8531 Binary files /dev/null and b/src/go/internal/gcimporter/testdata/versions/test_go1.8_4.a differ diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.8_5.a b/src/go/internal/gcimporter/testdata/versions/test_go1.8_5.a new file mode 100644 index 0000000..60e52ef Binary files /dev/null and b/src/go/internal/gcimporter/testdata/versions/test_go1.8_5.a differ diff --git a/src/go/internal/srcimporter/srcimporter.go b/src/go/internal/srcimporter/srcimporter.go new file mode 100644 index 0000000..438ae0f --- /dev/null +++ b/src/go/internal/srcimporter/srcimporter.go @@ -0,0 +1,265 @@ +// Copyright 2017 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 srcimporter implements importing directly +// from source files rather than installed packages. +package srcimporter // import "go/internal/srcimporter" + +import ( + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "go/types" + exec "internal/execabs" + "io" + "os" + "path/filepath" + "strings" + "sync" + _ "unsafe" // for go:linkname +) + +// An Importer provides the context for importing packages from source code. +type Importer struct { + ctxt *build.Context + fset *token.FileSet + sizes types.Sizes + packages map[string]*types.Package +} + +// NewImporter returns a new Importer for the given context, file set, and map +// of packages. The context is used to resolve import paths to package paths, +// and identifying the files belonging to the package. If the context provides +// non-nil file system functions, they are used instead of the regular package +// os functions. The file set is used to track position information of package +// files; and imported packages are added to the packages map. +func New(ctxt *build.Context, fset *token.FileSet, packages map[string]*types.Package) *Importer { + return &Importer{ + ctxt: ctxt, + fset: fset, + sizes: types.SizesFor(ctxt.Compiler, ctxt.GOARCH), // uses go/types default if GOARCH not found + packages: packages, + } +} + +// Importing is a sentinel taking the place in Importer.packages +// for a package that is in the process of being imported. +var importing types.Package + +// Import(path) is a shortcut for ImportFrom(path, ".", 0). +func (p *Importer) Import(path string) (*types.Package, error) { + return p.ImportFrom(path, ".", 0) // use "." rather than "" (see issue #24441) +} + +// ImportFrom imports the package with the given import path resolved from the given srcDir, +// adds the new package to the set of packages maintained by the importer, and returns the +// package. Package path resolution and file system operations are controlled by the context +// maintained with the importer. The import mode must be zero but is otherwise ignored. +// Packages that are not comprised entirely of pure Go files may fail to import because the +// type checker may not be able to determine all exported entities (e.g. due to cgo dependencies). +func (p *Importer) ImportFrom(path, srcDir string, mode types.ImportMode) (*types.Package, error) { + if mode != 0 { + panic("non-zero import mode") + } + + if abs, err := p.absPath(srcDir); err == nil { // see issue #14282 + srcDir = abs + } + bp, err := p.ctxt.Import(path, srcDir, 0) + if err != nil { + return nil, err // err may be *build.NoGoError - return as is + } + + // package unsafe is known to the type checker + if bp.ImportPath == "unsafe" { + return types.Unsafe, nil + } + + // no need to re-import if the package was imported completely before + pkg := p.packages[bp.ImportPath] + if pkg != nil { + if pkg == &importing { + return nil, fmt.Errorf("import cycle through package %q", bp.ImportPath) + } + if !pkg.Complete() { + // Package exists but is not complete - we cannot handle this + // at the moment since the source importer replaces the package + // wholesale rather than augmenting it (see #19337 for details). + // Return incomplete package with error (see #16088). + return pkg, fmt.Errorf("reimported partially imported package %q", bp.ImportPath) + } + return pkg, nil + } + + p.packages[bp.ImportPath] = &importing + defer func() { + // clean up in case of error + // TODO(gri) Eventually we may want to leave a (possibly empty) + // package in the map in all cases (and use that package to + // identify cycles). See also issue 16088. + if p.packages[bp.ImportPath] == &importing { + p.packages[bp.ImportPath] = nil + } + }() + + var filenames []string + filenames = append(filenames, bp.GoFiles...) + filenames = append(filenames, bp.CgoFiles...) + + files, err := p.parseFiles(bp.Dir, filenames) + if err != nil { + return nil, err + } + + // type-check package files + var firstHardErr error + conf := types.Config{ + IgnoreFuncBodies: true, + // continue type-checking after the first error + Error: func(err error) { + if firstHardErr == nil && !err.(types.Error).Soft { + firstHardErr = err + } + }, + Importer: p, + Sizes: p.sizes, + } + if len(bp.CgoFiles) > 0 { + if p.ctxt.OpenFile != nil { + // cgo, gcc, pkg-config, etc. do not support + // build.Context's VFS. + conf.FakeImportC = true + } else { + setUsesCgo(&conf) + file, err := p.cgo(bp) + if err != nil { + return nil, err + } + files = append(files, file) + } + } + + pkg, err = conf.Check(bp.ImportPath, p.fset, files, nil) + if err != nil { + // If there was a hard error it is possibly unsafe + // to use the package as it may not be fully populated. + // Do not return it (see also #20837, #20855). + if firstHardErr != nil { + pkg = nil + err = firstHardErr // give preference to first hard error over any soft error + } + return pkg, fmt.Errorf("type-checking package %q failed (%v)", bp.ImportPath, err) + } + if firstHardErr != nil { + // this can only happen if we have a bug in go/types + panic("package is not safe yet no error was returned") + } + + p.packages[bp.ImportPath] = pkg + return pkg, nil +} + +func (p *Importer) parseFiles(dir string, filenames []string) ([]*ast.File, error) { + // use build.Context's OpenFile if there is one + open := p.ctxt.OpenFile + if open == nil { + open = func(name string) (io.ReadCloser, error) { return os.Open(name) } + } + + files := make([]*ast.File, len(filenames)) + errors := make([]error, len(filenames)) + + var wg sync.WaitGroup + wg.Add(len(filenames)) + for i, filename := range filenames { + go func(i int, filepath string) { + defer wg.Done() + src, err := open(filepath) + if err != nil { + errors[i] = err // open provides operation and filename in error + return + } + files[i], errors[i] = parser.ParseFile(p.fset, filepath, src, 0) + src.Close() // ignore Close error - parsing may have succeeded which is all we need + }(i, p.joinPath(dir, filename)) + } + wg.Wait() + + // if there are errors, return the first one for deterministic results + for _, err := range errors { + if err != nil { + return nil, err + } + } + + return files, nil +} + +func (p *Importer) cgo(bp *build.Package) (*ast.File, error) { + tmpdir, err := os.MkdirTemp("", "srcimporter") + if err != nil { + return nil, err + } + defer os.RemoveAll(tmpdir) + + args := []string{"go", "tool", "cgo", "-objdir", tmpdir} + if bp.Goroot { + switch bp.ImportPath { + case "runtime/cgo": + args = append(args, "-import_runtime_cgo=false", "-import_syscall=false") + case "runtime/race": + args = append(args, "-import_syscall=false") + } + } + args = append(args, "--") + args = append(args, strings.Fields(os.Getenv("CGO_CPPFLAGS"))...) + args = append(args, bp.CgoCPPFLAGS...) + if len(bp.CgoPkgConfig) > 0 { + cmd := exec.Command("pkg-config", append([]string{"--cflags"}, bp.CgoPkgConfig...)...) + out, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + args = append(args, strings.Fields(string(out))...) + } + args = append(args, "-I", tmpdir) + args = append(args, strings.Fields(os.Getenv("CGO_CFLAGS"))...) + args = append(args, bp.CgoCFLAGS...) + args = append(args, bp.CgoFiles...) + + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = bp.Dir + if err := cmd.Run(); err != nil { + return nil, err + } + + return parser.ParseFile(p.fset, filepath.Join(tmpdir, "_cgo_gotypes.go"), nil, 0) +} + +// context-controlled file system operations + +func (p *Importer) absPath(path string) (string, error) { + // TODO(gri) This should be using p.ctxt.AbsPath which doesn't + // exist but probably should. See also issue #14282. + return filepath.Abs(path) +} + +func (p *Importer) isAbsPath(path string) bool { + if f := p.ctxt.IsAbsPath; f != nil { + return f(path) + } + return filepath.IsAbs(path) +} + +func (p *Importer) joinPath(elem ...string) string { + if f := p.ctxt.JoinPath; f != nil { + return f(elem...) + } + return filepath.Join(elem...) +} + +//go:linkname setUsesCgo go/types.srcimporter_setUsesCgo +func setUsesCgo(conf *types.Config) diff --git a/src/go/internal/srcimporter/srcimporter_test.go b/src/go/internal/srcimporter/srcimporter_test.go new file mode 100644 index 0000000..05b12f1 --- /dev/null +++ b/src/go/internal/srcimporter/srcimporter_test.go @@ -0,0 +1,254 @@ +// Copyright 2017 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 srcimporter + +import ( + "flag" + "go/build" + "go/token" + "go/types" + "internal/testenv" + "os" + "path" + "path/filepath" + "runtime" + "strings" + "testing" + "time" +) + +func TestMain(m *testing.M) { + flag.Parse() + if goTool, err := testenv.GoTool(); err == nil { + os.Setenv("PATH", filepath.Dir(goTool)+string(os.PathListSeparator)+os.Getenv("PATH")) + } + os.Exit(m.Run()) +} + +const maxTime = 2 * time.Second + +var importer = New(&build.Default, token.NewFileSet(), make(map[string]*types.Package)) + +func doImport(t *testing.T, path, srcDir string) { + t0 := time.Now() + if _, err := importer.ImportFrom(path, srcDir, 0); err != nil { + // don't report an error if there's no buildable Go files + if _, nogo := err.(*build.NoGoError); !nogo { + t.Errorf("import %q failed (%v)", path, err) + } + return + } + t.Logf("import %q: %v", path, time.Since(t0)) +} + +// walkDir imports the all the packages with the given path +// prefix recursively. It returns the number of packages +// imported and whether importing was aborted because time +// has passed endTime. +func walkDir(t *testing.T, path string, endTime time.Time) (int, bool) { + if time.Now().After(endTime) { + t.Log("testing time used up") + return 0, true + } + + // ignore fake packages and testdata directories + if path == "builtin" || path == "unsafe" || strings.HasSuffix(path, "testdata") { + return 0, false + } + + list, err := os.ReadDir(filepath.Join(runtime.GOROOT(), "src", path)) + if err != nil { + t.Fatalf("walkDir %s failed (%v)", path, err) + } + + nimports := 0 + hasGoFiles := false + for _, f := range list { + if f.IsDir() { + n, abort := walkDir(t, filepath.Join(path, f.Name()), endTime) + nimports += n + if abort { + return nimports, true + } + } else if strings.HasSuffix(f.Name(), ".go") { + hasGoFiles = true + } + } + + if hasGoFiles { + doImport(t, path, "") + nimports++ + } + + return nimports, false +} + +func TestImportStdLib(t *testing.T) { + if !testenv.HasSrc() { + t.Skip("no source code available") + } + + if testing.Short() && testenv.Builder() == "" { + t.Skip("skipping in -short mode") + } + dt := maxTime + nimports, _ := walkDir(t, "", time.Now().Add(dt)) // installed packages + t.Logf("tested %d imports", nimports) +} + +var importedObjectTests = []struct { + name string + want string +}{ + {"flag.Bool", "func Bool(name string, value bool, usage string) *bool"}, + {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"}, + {"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"}, // go/types.gcCompatibilityMode is off => interface not flattened + {"math.Pi", "const Pi untyped float"}, + {"math.Sin", "func Sin(x float64) float64"}, + {"math/big.Int", "type Int struct{neg bool; abs nat}"}, + {"golang.org/x/text/unicode/norm.MaxSegmentSize", "const MaxSegmentSize untyped int"}, +} + +func TestImportedTypes(t *testing.T) { + if !testenv.HasSrc() { + t.Skip("no source code available") + } + + for _, test := range importedObjectTests { + i := strings.LastIndex(test.name, ".") + if i < 0 { + t.Fatal("invalid test data format") + } + importPath := test.name[:i] + objName := test.name[i+1:] + + pkg, err := importer.ImportFrom(importPath, ".", 0) + if err != nil { + t.Error(err) + continue + } + + obj := pkg.Scope().Lookup(objName) + if obj == nil { + t.Errorf("%s: object not found", test.name) + continue + } + + got := types.ObjectString(obj, types.RelativeTo(pkg)) + if got != test.want { + t.Errorf("%s: got %q; want %q", test.name, got, test.want) + } + + if named, _ := obj.Type().(*types.Named); named != nil { + verifyInterfaceMethodRecvs(t, named, 0) + } + } +} + +// verifyInterfaceMethodRecvs verifies that method receiver types +// are named if the methods belong to a named interface type. +func verifyInterfaceMethodRecvs(t *testing.T, named *types.Named, level int) { + // avoid endless recursion in case of an embedding bug that lead to a cycle + if level > 10 { + t.Errorf("%s: embeds itself", named) + return + } + + iface, _ := named.Underlying().(*types.Interface) + if iface == nil { + return // not an interface + } + + // check explicitly declared methods + for i := 0; i < iface.NumExplicitMethods(); i++ { + m := iface.ExplicitMethod(i) + recv := m.Type().(*types.Signature).Recv() + if recv == nil { + t.Errorf("%s: missing receiver type", m) + continue + } + if recv.Type() != named { + t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named) + } + } + + // check embedded interfaces (they are named, too) + for i := 0; i < iface.NumEmbeddeds(); i++ { + // embedding of interfaces cannot have cycles; recursion will terminate + verifyInterfaceMethodRecvs(t, iface.Embedded(i), level+1) + } +} + +func TestReimport(t *testing.T) { + if !testenv.HasSrc() { + t.Skip("no source code available") + } + + // Reimporting a partially imported (incomplete) package is not supported (see issue #19337). + // Make sure we recognize the situation and report an error. + + mathPkg := types.NewPackage("math", "math") // incomplete package + importer := New(&build.Default, token.NewFileSet(), map[string]*types.Package{mathPkg.Path(): mathPkg}) + _, err := importer.ImportFrom("math", ".", 0) + if err == nil || !strings.HasPrefix(err.Error(), "reimport") { + t.Errorf("got %v; want reimport error", err) + } +} + +func TestIssue20855(t *testing.T) { + if !testenv.HasSrc() { + t.Skip("no source code available") + } + + pkg, err := importer.ImportFrom("go/internal/srcimporter/testdata/issue20855", ".", 0) + if err == nil || !strings.Contains(err.Error(), "missing function body") { + t.Fatalf("got unexpected or no error: %v", err) + } + if pkg == nil { + t.Error("got no package despite no hard errors") + } +} + +func testImportPath(t *testing.T, pkgPath string) { + if !testenv.HasSrc() { + t.Skip("no source code available") + } + + pkgName := path.Base(pkgPath) + + pkg, err := importer.Import(pkgPath) + if err != nil { + t.Fatal(err) + } + + if pkg.Name() != pkgName { + t.Errorf("got %q; want %q", pkg.Name(), pkgName) + } + + if pkg.Path() != pkgPath { + t.Errorf("got %q; want %q", pkg.Path(), pkgPath) + } +} + +// TestIssue23092 tests relative imports. +func TestIssue23092(t *testing.T) { + testImportPath(t, "./testdata/issue23092") +} + +// TestIssue24392 tests imports against a path containing 'testdata'. +func TestIssue24392(t *testing.T) { + testImportPath(t, "go/internal/srcimporter/testdata/issue24392") +} + +func TestCgo(t *testing.T) { + testenv.MustHaveGoBuild(t) + testenv.MustHaveCGO(t) + + importer := New(&build.Default, token.NewFileSet(), make(map[string]*types.Package)) + _, err := importer.ImportFrom("./misc/cgo/test", runtime.GOROOT(), 0) + if err != nil { + t.Fatalf("Import failed: %v", err) + } +} diff --git a/src/go/internal/srcimporter/testdata/issue20855/issue20855.go b/src/go/internal/srcimporter/testdata/issue20855/issue20855.go new file mode 100644 index 0000000..d55448b --- /dev/null +++ b/src/go/internal/srcimporter/testdata/issue20855/issue20855.go @@ -0,0 +1,7 @@ +// Copyright 2017 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 issue20855 + +func init() // "missing function body" is a soft error diff --git a/src/go/internal/srcimporter/testdata/issue23092/issue23092.go b/src/go/internal/srcimporter/testdata/issue23092/issue23092.go new file mode 100644 index 0000000..608698b --- /dev/null +++ b/src/go/internal/srcimporter/testdata/issue23092/issue23092.go @@ -0,0 +1,5 @@ +// Copyright 2018 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 issue23092 diff --git a/src/go/internal/srcimporter/testdata/issue24392/issue24392.go b/src/go/internal/srcimporter/testdata/issue24392/issue24392.go new file mode 100644 index 0000000..8ad5221 --- /dev/null +++ b/src/go/internal/srcimporter/testdata/issue24392/issue24392.go @@ -0,0 +1,5 @@ +// Copyright 2018 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 issue24392 diff --git a/src/go/parser/error_test.go b/src/go/parser/error_test.go new file mode 100644 index 0000000..358a844 --- /dev/null +++ b/src/go/parser/error_test.go @@ -0,0 +1,187 @@ +// Copyright 2012 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. + +// This file implements a parser test harness. The files in the testdata +// directory are parsed and the errors reported are compared against the +// error messages expected in the test files. The test files must end in +// .src rather than .go so that they are not disturbed by gofmt runs. +// +// Expected errors are indicated in the test files by putting a comment +// of the form /* ERROR "rx" */ immediately following an offending token. +// The harness will verify that an error matching the regular expression +// rx is reported at that source position. +// +// For instance, the following test file indicates that a "not declared" +// error should be reported for the undeclared variable x: +// +// package p +// func f() { +// _ = x /* ERROR "not declared" */ + 1 +// } + +package parser + +import ( + "go/scanner" + "go/token" + "os" + "path/filepath" + "regexp" + "strings" + "testing" +) + +const testdata = "testdata" + +// getFile assumes that each filename occurs at most once +func getFile(fset *token.FileSet, filename string) (file *token.File) { + fset.Iterate(func(f *token.File) bool { + if f.Name() == filename { + if file != nil { + panic(filename + " used multiple times") + } + file = f + } + return true + }) + return file +} + +func getPos(fset *token.FileSet, filename string, offset int) token.Pos { + if f := getFile(fset, filename); f != nil { + return f.Pos(offset) + } + return token.NoPos +} + +// ERROR comments must be of the form /* ERROR "rx" */ and rx is +// a regular expression that matches the expected error message. +// The special form /* ERROR HERE "rx" */ must be used for error +// messages that appear immediately after a token, rather than at +// a token's position. +// +var errRx = regexp.MustCompile(`^/\* *ERROR *(HERE)? *"([^"]*)" *\*/$`) + +// expectedErrors collects the regular expressions of ERROR comments found +// in files and returns them as a map of error positions to error messages. +// +func expectedErrors(fset *token.FileSet, filename string, src []byte) map[token.Pos]string { + errors := make(map[token.Pos]string) + + var s scanner.Scanner + // file was parsed already - do not add it again to the file + // set otherwise the position information returned here will + // not match the position information collected by the parser + s.Init(getFile(fset, filename), src, nil, scanner.ScanComments) + var prev token.Pos // position of last non-comment, non-semicolon token + var here token.Pos // position immediately after the token at position prev + + for { + pos, tok, lit := s.Scan() + switch tok { + case token.EOF: + return errors + case token.COMMENT: + s := errRx.FindStringSubmatch(lit) + if len(s) == 3 { + pos := prev + if s[1] == "HERE" { + pos = here + } + errors[pos] = string(s[2]) + } + case token.SEMICOLON: + // don't use the position of auto-inserted (invisible) semicolons + if lit != ";" { + break + } + fallthrough + default: + prev = pos + var l int // token length + if tok.IsLiteral() { + l = len(lit) + } else { + l = len(tok.String()) + } + here = prev + token.Pos(l) + } + } +} + +// compareErrors compares the map of expected error messages with the list +// of found errors and reports discrepancies. +// +func compareErrors(t *testing.T, fset *token.FileSet, expected map[token.Pos]string, found scanner.ErrorList) { + for _, error := range found { + // error.Pos is a token.Position, but we want + // a token.Pos so we can do a map lookup + pos := getPos(fset, error.Pos.Filename, error.Pos.Offset) + if msg, found := expected[pos]; found { + // we expect a message at pos; check if it matches + rx, err := regexp.Compile(msg) + if err != nil { + t.Errorf("%s: %v", error.Pos, err) + continue + } + if match := rx.MatchString(error.Msg); !match { + t.Errorf("%s: %q does not match %q", error.Pos, error.Msg, msg) + continue + } + // we have a match - eliminate this error + delete(expected, pos) + } else { + // To keep in mind when analyzing failed test output: + // If the same error position occurs multiple times in errors, + // this message will be triggered (because the first error at + // the position removes this position from the expected errors). + t.Errorf("%s: unexpected error: %s", error.Pos, error.Msg) + } + } + + // there should be no expected errors left + if len(expected) > 0 { + t.Errorf("%d errors not reported:", len(expected)) + for pos, msg := range expected { + t.Errorf("%s: %s\n", fset.Position(pos), msg) + } + } +} + +func checkErrors(t *testing.T, filename string, input interface{}) { + src, err := readSource(filename, input) + if err != nil { + t.Error(err) + return + } + + fset := token.NewFileSet() + _, err = ParseFile(fset, filename, src, DeclarationErrors|AllErrors) + found, ok := err.(scanner.ErrorList) + if err != nil && !ok { + t.Error(err) + return + } + found.RemoveMultiples() + + // we are expecting the following errors + // (collect these after parsing a file so that it is found in the file set) + expected := expectedErrors(fset, filename, src) + + // verify errors returned by the parser + compareErrors(t, fset, expected, found) +} + +func TestErrors(t *testing.T) { + list, err := os.ReadDir(testdata) + if err != nil { + t.Fatal(err) + } + for _, d := range list { + name := d.Name() + if !d.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".src") { + checkErrors(t, filepath.Join(testdata, name), nil) + } + } +} diff --git a/src/go/parser/example_test.go b/src/go/parser/example_test.go new file mode 100644 index 0000000..c2f7f29 --- /dev/null +++ b/src/go/parser/example_test.go @@ -0,0 +1,43 @@ +// Copyright 2012 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 parser_test + +import ( + "fmt" + "go/parser" + "go/token" +) + +func ExampleParseFile() { + fset := token.NewFileSet() // positions are relative to fset + + src := `package foo + +import ( + "fmt" + "time" +) + +func bar() { + fmt.Println(time.Now()) +}` + + // Parse src but stop after processing the imports. + f, err := parser.ParseFile(fset, "", src, parser.ImportsOnly) + if err != nil { + fmt.Println(err) + return + } + + // Print the imports from the file's AST. + for _, s := range f.Imports { + fmt.Println(s.Path.Value) + } + + // output: + // + // "fmt" + // "time" +} diff --git a/src/go/parser/interface.go b/src/go/parser/interface.go new file mode 100644 index 0000000..56ff5fe --- /dev/null +++ b/src/go/parser/interface.go @@ -0,0 +1,241 @@ +// Copyright 2009 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. + +// This file contains the exported entry points for invoking the parser. + +package parser + +import ( + "bytes" + "errors" + "go/ast" + "go/token" + "io" + "io/fs" + "os" + "path/filepath" + "strings" +) + +// If src != nil, readSource converts src to a []byte if possible; +// otherwise it returns an error. If src == nil, readSource returns +// the result of reading the file specified by filename. +// +func readSource(filename string, src interface{}) ([]byte, error) { + if src != nil { + switch s := src.(type) { + case string: + return []byte(s), nil + case []byte: + return s, nil + case *bytes.Buffer: + // is io.Reader, but src is already available in []byte form + if s != nil { + return s.Bytes(), nil + } + case io.Reader: + return io.ReadAll(s) + } + return nil, errors.New("invalid source") + } + return os.ReadFile(filename) +} + +// A Mode value is a set of flags (or 0). +// They control the amount of source code parsed and other optional +// parser functionality. +// +type Mode uint + +const ( + PackageClauseOnly Mode = 1 << iota // stop parsing after package clause + ImportsOnly // stop parsing after import declarations + ParseComments // parse comments and add them to AST + Trace // print a trace of parsed productions + DeclarationErrors // report declaration errors + SpuriousErrors // same as AllErrors, for backward-compatibility + AllErrors = SpuriousErrors // report all errors (not just the first 10 on different lines) +) + +// ParseFile parses the source code of a single Go source file and returns +// the corresponding ast.File node. The source code may be provided via +// the filename of the source file, or via the src parameter. +// +// If src != nil, ParseFile parses the source from src and the filename is +// only used when recording position information. The type of the argument +// for the src parameter must be string, []byte, or io.Reader. +// If src == nil, ParseFile parses the file specified by filename. +// +// The mode parameter controls the amount of source text parsed and other +// optional parser functionality. Position information is recorded in the +// file set fset, which must not be nil. +// +// If the source couldn't be read, the returned AST is nil and the error +// indicates the specific failure. If the source was read but syntax +// errors were found, the result is a partial AST (with ast.Bad* nodes +// representing the fragments of erroneous source code). Multiple errors +// are returned via a scanner.ErrorList which is sorted by source position. +// +func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (f *ast.File, err error) { + if fset == nil { + panic("parser.ParseFile: no token.FileSet provided (fset == nil)") + } + + // get source + text, err := readSource(filename, src) + if err != nil { + return nil, err + } + + var p parser + defer func() { + if e := recover(); e != nil { + // resume same panic if it's not a bailout + if _, ok := e.(bailout); !ok { + panic(e) + } + } + + // set result values + if f == nil { + // source is not a valid Go source file - satisfy + // ParseFile API and return a valid (but) empty + // *ast.File + f = &ast.File{ + Name: new(ast.Ident), + Scope: ast.NewScope(nil), + } + } + + p.errors.Sort() + err = p.errors.Err() + }() + + // parse source + p.init(fset, filename, text, mode) + f = p.parseFile() + + return +} + +// ParseDir calls ParseFile for all files with names ending in ".go" in the +// directory specified by path and returns a map of package name -> package +// AST with all the packages found. +// +// If filter != nil, only the files with fs.FileInfo entries passing through +// the filter (and ending in ".go") are considered. The mode bits are passed +// to ParseFile unchanged. Position information is recorded in fset, which +// must not be nil. +// +// If the directory couldn't be read, a nil map and the respective error are +// returned. If a parse error occurred, a non-nil but incomplete map and the +// first error encountered are returned. +// +func ParseDir(fset *token.FileSet, path string, filter func(fs.FileInfo) bool, mode Mode) (pkgs map[string]*ast.Package, first error) { + list, err := os.ReadDir(path) + if err != nil { + return nil, err + } + + pkgs = make(map[string]*ast.Package) + for _, d := range list { + if d.IsDir() || !strings.HasSuffix(d.Name(), ".go") { + continue + } + if filter != nil { + info, err := d.Info() + if err != nil { + return nil, err + } + if !filter(info) { + continue + } + } + filename := filepath.Join(path, d.Name()) + if src, err := ParseFile(fset, filename, nil, mode); err == nil { + name := src.Name.Name + pkg, found := pkgs[name] + if !found { + pkg = &ast.Package{ + Name: name, + Files: make(map[string]*ast.File), + } + pkgs[name] = pkg + } + pkg.Files[filename] = src + } else if first == nil { + first = err + } + } + + return +} + +// ParseExprFrom is a convenience function for parsing an expression. +// The arguments have the same meaning as for ParseFile, but the source must +// be a valid Go (type or value) expression. Specifically, fset must not +// be nil. +// +// If the source couldn't be read, the returned AST is nil and the error +// indicates the specific failure. If the source was read but syntax +// errors were found, the result is a partial AST (with ast.Bad* nodes +// representing the fragments of erroneous source code). Multiple errors +// are returned via a scanner.ErrorList which is sorted by source position. +// +func ParseExprFrom(fset *token.FileSet, filename string, src interface{}, mode Mode) (expr ast.Expr, err error) { + if fset == nil { + panic("parser.ParseExprFrom: no token.FileSet provided (fset == nil)") + } + + // get source + text, err := readSource(filename, src) + if err != nil { + return nil, err + } + + var p parser + defer func() { + if e := recover(); e != nil { + // resume same panic if it's not a bailout + if _, ok := e.(bailout); !ok { + panic(e) + } + } + p.errors.Sort() + err = p.errors.Err() + }() + + // parse expr + p.init(fset, filename, text, mode) + // Set up pkg-level scopes to avoid nil-pointer errors. + // This is not needed for a correct expression x as the + // parser will be ok with a nil topScope, but be cautious + // in case of an erroneous x. + p.openScope() + p.pkgScope = p.topScope + expr = p.parseRhsOrType() + p.closeScope() + assert(p.topScope == nil, "unbalanced scopes") + + // If a semicolon was inserted, consume it; + // report an error if there's more tokens. + if p.tok == token.SEMICOLON && p.lit == "\n" { + p.next() + } + p.expect(token.EOF) + + return +} + +// ParseExpr is a convenience function for obtaining the AST of an expression x. +// The position information recorded in the AST is undefined. The filename used +// in error messages is the empty string. +// +// If syntax errors were found, the result is a partial AST (with ast.Bad* nodes +// representing the fragments of erroneous source code). Multiple errors are +// returned via a scanner.ErrorList which is sorted by source position. +// +func ParseExpr(x string) (ast.Expr, error) { + return ParseExprFrom(token.NewFileSet(), "", []byte(x), 0) +} diff --git a/src/go/parser/parser.go b/src/go/parser/parser.go new file mode 100644 index 0000000..31a7398 --- /dev/null +++ b/src/go/parser/parser.go @@ -0,0 +1,2591 @@ +// Copyright 2009 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 parser implements a parser for Go source files. Input may be +// provided in a variety of forms (see the various Parse* functions); the +// output is an abstract syntax tree (AST) representing the Go source. The +// parser is invoked through one of the Parse* functions. +// +// The parser accepts a larger language than is syntactically permitted by +// the Go spec, for simplicity, and for improved robustness in the presence +// of syntax errors. For instance, in method declarations, the receiver is +// treated like an ordinary parameter list and thus may contain multiple +// entries where the spec permits exactly one. Consequently, the corresponding +// field in the AST (ast.FuncDecl.Recv) field is not restricted to one entry. +// +package parser + +import ( + "fmt" + "go/ast" + "go/scanner" + "go/token" + "strconv" + "strings" + "unicode" +) + +// The parser structure holds the parser's internal state. +type parser struct { + file *token.File + errors scanner.ErrorList + scanner scanner.Scanner + + // Tracing/debugging + mode Mode // parsing mode + trace bool // == (mode & Trace != 0) + indent int // indentation used for tracing output + + // Comments + comments []*ast.CommentGroup + leadComment *ast.CommentGroup // last lead comment + lineComment *ast.CommentGroup // last line comment + + // Next token + pos token.Pos // token position + tok token.Token // one token look-ahead + lit string // token literal + + // Error recovery + // (used to limit the number of calls to parser.advance + // w/o making scanning progress - avoids potential endless + // loops across multiple parser functions during error recovery) + syncPos token.Pos // last synchronization position + syncCnt int // number of parser.advance calls without progress + + // Non-syntactic parser control + exprLev int // < 0: in control clause, >= 0: in expression + inRhs bool // if set, the parser is parsing a rhs expression + + // Ordinary identifier scopes + pkgScope *ast.Scope // pkgScope.Outer == nil + topScope *ast.Scope // top-most scope; may be pkgScope + unresolved []*ast.Ident // unresolved identifiers + imports []*ast.ImportSpec // list of imports + + // Label scopes + // (maintained by open/close LabelScope) + labelScope *ast.Scope // label scope for current function + targetStack [][]*ast.Ident // stack of unresolved labels +} + +func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mode) { + p.file = fset.AddFile(filename, -1, len(src)) + var m scanner.Mode + if mode&ParseComments != 0 { + m = scanner.ScanComments + } + eh := func(pos token.Position, msg string) { p.errors.Add(pos, msg) } + p.scanner.Init(p.file, src, eh, m) + + p.mode = mode + p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently) + + p.next() +} + +// ---------------------------------------------------------------------------- +// Scoping support + +func (p *parser) openScope() { + p.topScope = ast.NewScope(p.topScope) +} + +func (p *parser) closeScope() { + p.topScope = p.topScope.Outer +} + +func (p *parser) openLabelScope() { + p.labelScope = ast.NewScope(p.labelScope) + p.targetStack = append(p.targetStack, nil) +} + +func (p *parser) closeLabelScope() { + // resolve labels + n := len(p.targetStack) - 1 + scope := p.labelScope + for _, ident := range p.targetStack[n] { + ident.Obj = scope.Lookup(ident.Name) + if ident.Obj == nil && p.mode&DeclarationErrors != 0 { + p.error(ident.Pos(), fmt.Sprintf("label %s undefined", ident.Name)) + } + } + // pop label scope + p.targetStack = p.targetStack[0:n] + p.labelScope = p.labelScope.Outer +} + +func (p *parser) declare(decl, data interface{}, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) { + for _, ident := range idents { + assert(ident.Obj == nil, "identifier already declared or resolved") + obj := ast.NewObj(kind, ident.Name) + // remember the corresponding declaration for redeclaration + // errors and global variable resolution/typechecking phase + obj.Decl = decl + obj.Data = data + ident.Obj = obj + if ident.Name != "_" { + if alt := scope.Insert(obj); alt != nil && p.mode&DeclarationErrors != 0 { + prevDecl := "" + if pos := alt.Pos(); pos.IsValid() { + prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", p.file.Position(pos)) + } + p.error(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl)) + } + } + } +} + +func (p *parser) shortVarDecl(decl *ast.AssignStmt, list []ast.Expr) { + // Go spec: A short variable declaration may redeclare variables + // provided they were originally declared in the same block with + // the same type, and at least one of the non-blank variables is new. + n := 0 // number of new variables + for _, x := range list { + if ident, isIdent := x.(*ast.Ident); isIdent { + assert(ident.Obj == nil, "identifier already declared or resolved") + obj := ast.NewObj(ast.Var, ident.Name) + // remember corresponding assignment for other tools + obj.Decl = decl + ident.Obj = obj + if ident.Name != "_" { + if alt := p.topScope.Insert(obj); alt != nil { + ident.Obj = alt // redeclaration + } else { + n++ // new declaration + } + } + } else { + p.errorExpected(x.Pos(), "identifier on left side of :=") + } + } + if n == 0 && p.mode&DeclarationErrors != 0 { + p.error(list[0].Pos(), "no new variables on left side of :=") + } +} + +// The unresolved object is a sentinel to mark identifiers that have been added +// to the list of unresolved identifiers. The sentinel is only used for verifying +// internal consistency. +var unresolved = new(ast.Object) + +// If x is an identifier, tryResolve attempts to resolve x by looking up +// the object it denotes. If no object is found and collectUnresolved is +// set, x is marked as unresolved and collected in the list of unresolved +// identifiers. +// +func (p *parser) tryResolve(x ast.Expr, collectUnresolved bool) { + // nothing to do if x is not an identifier or the blank identifier + ident, _ := x.(*ast.Ident) + if ident == nil { + return + } + assert(ident.Obj == nil, "identifier already declared or resolved") + if ident.Name == "_" { + return + } + // try to resolve the identifier + for s := p.topScope; s != nil; s = s.Outer { + if obj := s.Lookup(ident.Name); obj != nil { + ident.Obj = obj + return + } + } + // all local scopes are known, so any unresolved identifier + // must be found either in the file scope, package scope + // (perhaps in another file), or universe scope --- collect + // them so that they can be resolved later + if collectUnresolved { + ident.Obj = unresolved + p.unresolved = append(p.unresolved, ident) + } +} + +func (p *parser) resolve(x ast.Expr) { + p.tryResolve(x, true) +} + +// ---------------------------------------------------------------------------- +// Parsing support + +func (p *parser) printTrace(a ...interface{}) { + const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + const n = len(dots) + pos := p.file.Position(p.pos) + fmt.Printf("%5d:%3d: ", pos.Line, pos.Column) + i := 2 * p.indent + for i > n { + fmt.Print(dots) + i -= n + } + // i <= n + fmt.Print(dots[0:i]) + fmt.Println(a...) +} + +func trace(p *parser, msg string) *parser { + p.printTrace(msg, "(") + p.indent++ + return p +} + +// Usage pattern: defer un(trace(p, "...")) +func un(p *parser) { + p.indent-- + p.printTrace(")") +} + +// Advance to the next token. +func (p *parser) next0() { + // Because of one-token look-ahead, print the previous token + // when tracing as it provides a more readable output. The + // very first token (!p.pos.IsValid()) is not initialized + // (it is token.ILLEGAL), so don't print it . + if p.trace && p.pos.IsValid() { + s := p.tok.String() + switch { + case p.tok.IsLiteral(): + p.printTrace(s, p.lit) + case p.tok.IsOperator(), p.tok.IsKeyword(): + p.printTrace("\"" + s + "\"") + default: + p.printTrace(s) + } + } + + p.pos, p.tok, p.lit = p.scanner.Scan() +} + +// Consume a comment and return it and the line on which it ends. +func (p *parser) consumeComment() (comment *ast.Comment, endline int) { + // /*-style comments may end on a different line than where they start. + // Scan the comment for '\n' chars and adjust endline accordingly. + endline = p.file.Line(p.pos) + if p.lit[1] == '*' { + // don't use range here - no need to decode Unicode code points + for i := 0; i < len(p.lit); i++ { + if p.lit[i] == '\n' { + endline++ + } + } + } + + comment = &ast.Comment{Slash: p.pos, Text: p.lit} + p.next0() + + return +} + +// Consume a group of adjacent comments, add it to the parser's +// comments list, and return it together with the line at which +// the last comment in the group ends. A non-comment token or n +// empty lines terminate a comment group. +// +func (p *parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline int) { + var list []*ast.Comment + endline = p.file.Line(p.pos) + for p.tok == token.COMMENT && p.file.Line(p.pos) <= endline+n { + var comment *ast.Comment + comment, endline = p.consumeComment() + list = append(list, comment) + } + + // add comment group to the comments list + comments = &ast.CommentGroup{List: list} + p.comments = append(p.comments, comments) + + return +} + +// Advance to the next non-comment token. In the process, collect +// any comment groups encountered, and remember the last lead and +// line comments. +// +// A lead comment is a comment group that starts and ends in a +// line without any other tokens and that is followed by a non-comment +// token on the line immediately after the comment group. +// +// A line comment is a comment group that follows a non-comment +// token on the same line, and that has no tokens after it on the line +// where it ends. +// +// Lead and line comments may be considered documentation that is +// stored in the AST. +// +func (p *parser) next() { + p.leadComment = nil + p.lineComment = nil + prev := p.pos + p.next0() + + if p.tok == token.COMMENT { + var comment *ast.CommentGroup + var endline int + + if p.file.Line(p.pos) == p.file.Line(prev) { + // The comment is on same line as the previous token; it + // cannot be a lead comment but may be a line comment. + comment, endline = p.consumeCommentGroup(0) + if p.file.Line(p.pos) != endline || p.tok == token.EOF { + // The next token is on a different line, thus + // the last comment group is a line comment. + p.lineComment = comment + } + } + + // consume successor comments, if any + endline = -1 + for p.tok == token.COMMENT { + comment, endline = p.consumeCommentGroup(1) + } + + if endline+1 == p.file.Line(p.pos) { + // The next token is following on the line immediately after the + // comment group, thus the last comment group is a lead comment. + p.leadComment = comment + } + } +} + +// A bailout panic is raised to indicate early termination. +type bailout struct{} + +func (p *parser) error(pos token.Pos, msg string) { + epos := p.file.Position(pos) + + // If AllErrors is not set, discard errors reported on the same line + // as the last recorded error and stop parsing if there are more than + // 10 errors. + if p.mode&AllErrors == 0 { + n := len(p.errors) + if n > 0 && p.errors[n-1].Pos.Line == epos.Line { + return // discard - likely a spurious error + } + if n > 10 { + panic(bailout{}) + } + } + + p.errors.Add(epos, msg) +} + +func (p *parser) errorExpected(pos token.Pos, msg string) { + msg = "expected " + msg + if pos == p.pos { + // the error happened at the current position; + // make the error message more specific + switch { + case p.tok == token.SEMICOLON && p.lit == "\n": + msg += ", found newline" + case p.tok.IsLiteral(): + // print 123 rather than 'INT', etc. + msg += ", found " + p.lit + default: + msg += ", found '" + p.tok.String() + "'" + } + } + p.error(pos, msg) +} + +func (p *parser) expect(tok token.Token) token.Pos { + pos := p.pos + if p.tok != tok { + p.errorExpected(pos, "'"+tok.String()+"'") + } + p.next() // make progress + return pos +} + +// expect2 is like expect, but it returns an invalid position +// if the expected token is not found. +func (p *parser) expect2(tok token.Token) (pos token.Pos) { + if p.tok == tok { + pos = p.pos + } else { + p.errorExpected(p.pos, "'"+tok.String()+"'") + } + p.next() // make progress + return +} + +// expectClosing is like expect but provides a better error message +// for the common case of a missing comma before a newline. +// +func (p *parser) expectClosing(tok token.Token, context string) token.Pos { + if p.tok != tok && p.tok == token.SEMICOLON && p.lit == "\n" { + p.error(p.pos, "missing ',' before newline in "+context) + p.next() + } + return p.expect(tok) +} + +func (p *parser) expectSemi() { + // semicolon is optional before a closing ')' or '}' + if p.tok != token.RPAREN && p.tok != token.RBRACE { + switch p.tok { + case token.COMMA: + // permit a ',' instead of a ';' but complain + p.errorExpected(p.pos, "';'") + fallthrough + case token.SEMICOLON: + p.next() + default: + p.errorExpected(p.pos, "';'") + p.advance(stmtStart) + } + } +} + +func (p *parser) atComma(context string, follow token.Token) bool { + if p.tok == token.COMMA { + return true + } + if p.tok != follow { + msg := "missing ','" + if p.tok == token.SEMICOLON && p.lit == "\n" { + msg += " before newline" + } + p.error(p.pos, msg+" in "+context) + return true // "insert" comma and continue + } + return false +} + +func assert(cond bool, msg string) { + if !cond { + panic("go/parser internal error: " + msg) + } +} + +// advance consumes tokens until the current token p.tok +// is in the 'to' set, or token.EOF. For error recovery. +func (p *parser) advance(to map[token.Token]bool) { + for ; p.tok != token.EOF; p.next() { + if to[p.tok] { + // Return only if parser made some progress since last + // sync or if it has not reached 10 advance calls without + // progress. Otherwise consume at least one token to + // avoid an endless parser loop (it is possible that + // both parseOperand and parseStmt call advance and + // correctly do not advance, thus the need for the + // invocation limit p.syncCnt). + if p.pos == p.syncPos && p.syncCnt < 10 { + p.syncCnt++ + return + } + if p.pos > p.syncPos { + p.syncPos = p.pos + p.syncCnt = 0 + return + } + // Reaching here indicates a parser bug, likely an + // incorrect token list in this function, but it only + // leads to skipping of possibly correct code if a + // previous error is present, and thus is preferred + // over a non-terminating parse. + } + } +} + +var stmtStart = map[token.Token]bool{ + token.BREAK: true, + token.CONST: true, + token.CONTINUE: true, + token.DEFER: true, + token.FALLTHROUGH: true, + token.FOR: true, + token.GO: true, + token.GOTO: true, + token.IF: true, + token.RETURN: true, + token.SELECT: true, + token.SWITCH: true, + token.TYPE: true, + token.VAR: true, +} + +var declStart = map[token.Token]bool{ + token.CONST: true, + token.TYPE: true, + token.VAR: true, +} + +var exprEnd = map[token.Token]bool{ + token.COMMA: true, + token.COLON: true, + token.SEMICOLON: true, + token.RPAREN: true, + token.RBRACK: true, + token.RBRACE: true, +} + +// safePos returns a valid file position for a given position: If pos +// is valid to begin with, safePos returns pos. If pos is out-of-range, +// safePos returns the EOF position. +// +// This is hack to work around "artificial" end positions in the AST which +// are computed by adding 1 to (presumably valid) token positions. If the +// token positions are invalid due to parse errors, the resulting end position +// may be past the file's EOF position, which would lead to panics if used +// later on. +// +func (p *parser) safePos(pos token.Pos) (res token.Pos) { + defer func() { + if recover() != nil { + res = token.Pos(p.file.Base() + p.file.Size()) // EOF position + } + }() + _ = p.file.Offset(pos) // trigger a panic if position is out-of-range + return pos +} + +// ---------------------------------------------------------------------------- +// Identifiers + +func (p *parser) parseIdent() *ast.Ident { + pos := p.pos + name := "_" + if p.tok == token.IDENT { + name = p.lit + p.next() + } else { + p.expect(token.IDENT) // use expect() error handling + } + return &ast.Ident{NamePos: pos, Name: name} +} + +func (p *parser) parseIdentList() (list []*ast.Ident) { + if p.trace { + defer un(trace(p, "IdentList")) + } + + list = append(list, p.parseIdent()) + for p.tok == token.COMMA { + p.next() + list = append(list, p.parseIdent()) + } + + return +} + +// ---------------------------------------------------------------------------- +// Common productions + +// If lhs is set, result list elements which are identifiers are not resolved. +func (p *parser) parseExprList(lhs bool) (list []ast.Expr) { + if p.trace { + defer un(trace(p, "ExpressionList")) + } + + list = append(list, p.checkExpr(p.parseExpr(lhs))) + for p.tok == token.COMMA { + p.next() + list = append(list, p.checkExpr(p.parseExpr(lhs))) + } + + return +} + +func (p *parser) parseLhsList() []ast.Expr { + old := p.inRhs + p.inRhs = false + list := p.parseExprList(true) + switch p.tok { + case token.DEFINE: + // lhs of a short variable declaration + // but doesn't enter scope until later: + // caller must call p.shortVarDecl(p.makeIdentList(list)) + // at appropriate time. + case token.COLON: + // lhs of a label declaration or a communication clause of a select + // statement (parseLhsList is not called when parsing the case clause + // of a switch statement): + // - labels are declared by the caller of parseLhsList + // - for communication clauses, if there is a stand-alone identifier + // followed by a colon, we have a syntax error; there is no need + // to resolve the identifier in that case + default: + // identifiers must be declared elsewhere + for _, x := range list { + p.resolve(x) + } + } + p.inRhs = old + return list +} + +func (p *parser) parseRhsList() []ast.Expr { + old := p.inRhs + p.inRhs = true + list := p.parseExprList(false) + p.inRhs = old + return list +} + +// ---------------------------------------------------------------------------- +// Types + +func (p *parser) parseType() ast.Expr { + if p.trace { + defer un(trace(p, "Type")) + } + + typ := p.tryType() + + if typ == nil { + pos := p.pos + p.errorExpected(pos, "type") + p.advance(exprEnd) + return &ast.BadExpr{From: pos, To: p.pos} + } + + return typ +} + +// If the result is an identifier, it is not resolved. +func (p *parser) parseTypeName() ast.Expr { + if p.trace { + defer un(trace(p, "TypeName")) + } + + ident := p.parseIdent() + // don't resolve ident yet - it may be a parameter or field name + + if p.tok == token.PERIOD { + // ident is a package name + p.next() + p.resolve(ident) + sel := p.parseIdent() + return &ast.SelectorExpr{X: ident, Sel: sel} + } + + return ident +} + +func (p *parser) parseArrayType() ast.Expr { + if p.trace { + defer un(trace(p, "ArrayType")) + } + + lbrack := p.expect(token.LBRACK) + p.exprLev++ + var len ast.Expr + // always permit ellipsis for more fault-tolerant parsing + if p.tok == token.ELLIPSIS { + len = &ast.Ellipsis{Ellipsis: p.pos} + p.next() + } else if p.tok != token.RBRACK { + len = p.parseRhs() + } + p.exprLev-- + p.expect(token.RBRACK) + elt := p.parseType() + + return &ast.ArrayType{Lbrack: lbrack, Len: len, Elt: elt} +} + +func (p *parser) makeIdentList(list []ast.Expr) []*ast.Ident { + idents := make([]*ast.Ident, len(list)) + for i, x := range list { + ident, isIdent := x.(*ast.Ident) + if !isIdent { + if _, isBad := x.(*ast.BadExpr); !isBad { + // only report error if it's a new one + p.errorExpected(x.Pos(), "identifier") + } + ident = &ast.Ident{NamePos: x.Pos(), Name: "_"} + } + idents[i] = ident + } + return idents +} + +func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field { + if p.trace { + defer un(trace(p, "FieldDecl")) + } + + doc := p.leadComment + + // 1st FieldDecl + // A type name used as an anonymous field looks like a field identifier. + var list []ast.Expr + for { + list = append(list, p.parseVarType(false)) + if p.tok != token.COMMA { + break + } + p.next() + } + + typ := p.tryVarType(false) + + // analyze case + var idents []*ast.Ident + if typ != nil { + // IdentifierList Type + idents = p.makeIdentList(list) + } else { + // ["*"] TypeName (AnonymousField) + typ = list[0] // we always have at least one element + if n := len(list); n > 1 { + p.errorExpected(p.pos, "type") + typ = &ast.BadExpr{From: p.pos, To: p.pos} + } else if !isTypeName(deref(typ)) { + p.errorExpected(typ.Pos(), "anonymous field") + typ = &ast.BadExpr{From: typ.Pos(), To: p.safePos(typ.End())} + } + } + + // Tag + var tag *ast.BasicLit + if p.tok == token.STRING { + tag = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} + p.next() + } + + p.expectSemi() // call before accessing p.linecomment + + field := &ast.Field{Doc: doc, Names: idents, Type: typ, Tag: tag, Comment: p.lineComment} + p.declare(field, nil, scope, ast.Var, idents...) + p.resolve(typ) + + return field +} + +func (p *parser) parseStructType() *ast.StructType { + if p.trace { + defer un(trace(p, "StructType")) + } + + pos := p.expect(token.STRUCT) + lbrace := p.expect(token.LBRACE) + scope := ast.NewScope(nil) // struct scope + var list []*ast.Field + for p.tok == token.IDENT || p.tok == token.MUL || p.tok == token.LPAREN { + // a field declaration cannot start with a '(' but we accept + // it here for more robust parsing and better error messages + // (parseFieldDecl will check and complain if necessary) + list = append(list, p.parseFieldDecl(scope)) + } + rbrace := p.expect(token.RBRACE) + + return &ast.StructType{ + Struct: pos, + Fields: &ast.FieldList{ + Opening: lbrace, + List: list, + Closing: rbrace, + }, + } +} + +func (p *parser) parsePointerType() *ast.StarExpr { + if p.trace { + defer un(trace(p, "PointerType")) + } + + star := p.expect(token.MUL) + base := p.parseType() + + return &ast.StarExpr{Star: star, X: base} +} + +// If the result is an identifier, it is not resolved. +func (p *parser) tryVarType(isParam bool) ast.Expr { + if isParam && p.tok == token.ELLIPSIS { + pos := p.pos + p.next() + typ := p.tryIdentOrType() // don't use parseType so we can provide better error message + if typ != nil { + p.resolve(typ) + } else { + p.error(pos, "'...' parameter is missing type") + typ = &ast.BadExpr{From: pos, To: p.pos} + } + return &ast.Ellipsis{Ellipsis: pos, Elt: typ} + } + return p.tryIdentOrType() +} + +// If the result is an identifier, it is not resolved. +func (p *parser) parseVarType(isParam bool) ast.Expr { + typ := p.tryVarType(isParam) + if typ == nil { + pos := p.pos + p.errorExpected(pos, "type") + p.next() // make progress + typ = &ast.BadExpr{From: pos, To: p.pos} + } + return typ +} + +func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params []*ast.Field) { + if p.trace { + defer un(trace(p, "ParameterList")) + } + + // 1st ParameterDecl + // A list of identifiers looks like a list of type names. + var list []ast.Expr + for { + list = append(list, p.parseVarType(ellipsisOk)) + if p.tok != token.COMMA { + break + } + p.next() + if p.tok == token.RPAREN { + break + } + } + + // analyze case + if typ := p.tryVarType(ellipsisOk); typ != nil { + // IdentifierList Type + idents := p.makeIdentList(list) + field := &ast.Field{Names: idents, Type: typ} + params = append(params, field) + // Go spec: The scope of an identifier denoting a function + // parameter or result variable is the function body. + p.declare(field, nil, scope, ast.Var, idents...) + p.resolve(typ) + if !p.atComma("parameter list", token.RPAREN) { + return + } + p.next() + for p.tok != token.RPAREN && p.tok != token.EOF { + idents := p.parseIdentList() + typ := p.parseVarType(ellipsisOk) + field := &ast.Field{Names: idents, Type: typ} + params = append(params, field) + // Go spec: The scope of an identifier denoting a function + // parameter or result variable is the function body. + p.declare(field, nil, scope, ast.Var, idents...) + p.resolve(typ) + if !p.atComma("parameter list", token.RPAREN) { + break + } + p.next() + } + return + } + + // Type { "," Type } (anonymous parameters) + params = make([]*ast.Field, len(list)) + for i, typ := range list { + p.resolve(typ) + params[i] = &ast.Field{Type: typ} + } + return +} + +func (p *parser) parseParameters(scope *ast.Scope, ellipsisOk bool) *ast.FieldList { + if p.trace { + defer un(trace(p, "Parameters")) + } + + var params []*ast.Field + lparen := p.expect(token.LPAREN) + if p.tok != token.RPAREN { + params = p.parseParameterList(scope, ellipsisOk) + } + rparen := p.expect(token.RPAREN) + + return &ast.FieldList{Opening: lparen, List: params, Closing: rparen} +} + +func (p *parser) parseResult(scope *ast.Scope) *ast.FieldList { + if p.trace { + defer un(trace(p, "Result")) + } + + if p.tok == token.LPAREN { + return p.parseParameters(scope, false) + } + + typ := p.tryType() + if typ != nil { + list := make([]*ast.Field, 1) + list[0] = &ast.Field{Type: typ} + return &ast.FieldList{List: list} + } + + return nil +} + +func (p *parser) parseSignature(scope *ast.Scope) (params, results *ast.FieldList) { + if p.trace { + defer un(trace(p, "Signature")) + } + + params = p.parseParameters(scope, true) + results = p.parseResult(scope) + + return +} + +func (p *parser) parseFuncType() (*ast.FuncType, *ast.Scope) { + if p.trace { + defer un(trace(p, "FuncType")) + } + + pos := p.expect(token.FUNC) + scope := ast.NewScope(p.topScope) // function scope + params, results := p.parseSignature(scope) + + return &ast.FuncType{Func: pos, Params: params, Results: results}, scope +} + +func (p *parser) parseMethodSpec(scope *ast.Scope) *ast.Field { + if p.trace { + defer un(trace(p, "MethodSpec")) + } + + doc := p.leadComment + var idents []*ast.Ident + var typ ast.Expr + x := p.parseTypeName() + if ident, isIdent := x.(*ast.Ident); isIdent && p.tok == token.LPAREN { + // method + idents = []*ast.Ident{ident} + scope := ast.NewScope(nil) // method scope + params, results := p.parseSignature(scope) + typ = &ast.FuncType{Func: token.NoPos, Params: params, Results: results} + } else { + // embedded interface + typ = x + p.resolve(typ) + } + p.expectSemi() // call before accessing p.linecomment + + spec := &ast.Field{Doc: doc, Names: idents, Type: typ, Comment: p.lineComment} + p.declare(spec, nil, scope, ast.Fun, idents...) + + return spec +} + +func (p *parser) parseInterfaceType() *ast.InterfaceType { + if p.trace { + defer un(trace(p, "InterfaceType")) + } + + pos := p.expect(token.INTERFACE) + lbrace := p.expect(token.LBRACE) + scope := ast.NewScope(nil) // interface scope + var list []*ast.Field + for p.tok == token.IDENT { + list = append(list, p.parseMethodSpec(scope)) + } + rbrace := p.expect(token.RBRACE) + + return &ast.InterfaceType{ + Interface: pos, + Methods: &ast.FieldList{ + Opening: lbrace, + List: list, + Closing: rbrace, + }, + } +} + +func (p *parser) parseMapType() *ast.MapType { + if p.trace { + defer un(trace(p, "MapType")) + } + + pos := p.expect(token.MAP) + p.expect(token.LBRACK) + key := p.parseType() + p.expect(token.RBRACK) + value := p.parseType() + + return &ast.MapType{Map: pos, Key: key, Value: value} +} + +func (p *parser) parseChanType() *ast.ChanType { + if p.trace { + defer un(trace(p, "ChanType")) + } + + pos := p.pos + dir := ast.SEND | ast.RECV + var arrow token.Pos + if p.tok == token.CHAN { + p.next() + if p.tok == token.ARROW { + arrow = p.pos + p.next() + dir = ast.SEND + } + } else { + arrow = p.expect(token.ARROW) + p.expect(token.CHAN) + dir = ast.RECV + } + value := p.parseType() + + return &ast.ChanType{Begin: pos, Arrow: arrow, Dir: dir, Value: value} +} + +// If the result is an identifier, it is not resolved. +func (p *parser) tryIdentOrType() ast.Expr { + switch p.tok { + case token.IDENT: + return p.parseTypeName() + case token.LBRACK: + return p.parseArrayType() + case token.STRUCT: + return p.parseStructType() + case token.MUL: + return p.parsePointerType() + case token.FUNC: + typ, _ := p.parseFuncType() + return typ + case token.INTERFACE: + return p.parseInterfaceType() + case token.MAP: + return p.parseMapType() + case token.CHAN, token.ARROW: + return p.parseChanType() + case token.LPAREN: + lparen := p.pos + p.next() + typ := p.parseType() + rparen := p.expect(token.RPAREN) + return &ast.ParenExpr{Lparen: lparen, X: typ, Rparen: rparen} + } + + // no type found + return nil +} + +func (p *parser) tryType() ast.Expr { + typ := p.tryIdentOrType() + if typ != nil { + p.resolve(typ) + } + return typ +} + +// ---------------------------------------------------------------------------- +// Blocks + +func (p *parser) parseStmtList() (list []ast.Stmt) { + if p.trace { + defer un(trace(p, "StatementList")) + } + + for p.tok != token.CASE && p.tok != token.DEFAULT && p.tok != token.RBRACE && p.tok != token.EOF { + list = append(list, p.parseStmt()) + } + + return +} + +func (p *parser) parseBody(scope *ast.Scope) *ast.BlockStmt { + if p.trace { + defer un(trace(p, "Body")) + } + + lbrace := p.expect(token.LBRACE) + p.topScope = scope // open function scope + p.openLabelScope() + list := p.parseStmtList() + p.closeLabelScope() + p.closeScope() + rbrace := p.expect2(token.RBRACE) + + return &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace} +} + +func (p *parser) parseBlockStmt() *ast.BlockStmt { + if p.trace { + defer un(trace(p, "BlockStmt")) + } + + lbrace := p.expect(token.LBRACE) + p.openScope() + list := p.parseStmtList() + p.closeScope() + rbrace := p.expect2(token.RBRACE) + + return &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace} +} + +// ---------------------------------------------------------------------------- +// Expressions + +func (p *parser) parseFuncTypeOrLit() ast.Expr { + if p.trace { + defer un(trace(p, "FuncTypeOrLit")) + } + + typ, scope := p.parseFuncType() + if p.tok != token.LBRACE { + // function type only + return typ + } + + p.exprLev++ + body := p.parseBody(scope) + p.exprLev-- + + return &ast.FuncLit{Type: typ, Body: body} +} + +// parseOperand may return an expression or a raw type (incl. array +// types of the form [...]T. Callers must verify the result. +// If lhs is set and the result is an identifier, it is not resolved. +// +func (p *parser) parseOperand(lhs bool) ast.Expr { + if p.trace { + defer un(trace(p, "Operand")) + } + + switch p.tok { + case token.IDENT: + x := p.parseIdent() + if !lhs { + p.resolve(x) + } + return x + + case token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING: + x := &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} + p.next() + return x + + case token.LPAREN: + lparen := p.pos + p.next() + p.exprLev++ + x := p.parseRhsOrType() // types may be parenthesized: (some type) + p.exprLev-- + rparen := p.expect(token.RPAREN) + return &ast.ParenExpr{Lparen: lparen, X: x, Rparen: rparen} + + case token.FUNC: + return p.parseFuncTypeOrLit() + } + + if typ := p.tryIdentOrType(); typ != nil { + // could be type for composite literal or conversion + _, isIdent := typ.(*ast.Ident) + assert(!isIdent, "type cannot be identifier") + return typ + } + + // we have an error + pos := p.pos + p.errorExpected(pos, "operand") + p.advance(stmtStart) + return &ast.BadExpr{From: pos, To: p.pos} +} + +func (p *parser) parseSelector(x ast.Expr) ast.Expr { + if p.trace { + defer un(trace(p, "Selector")) + } + + sel := p.parseIdent() + + return &ast.SelectorExpr{X: x, Sel: sel} +} + +func (p *parser) parseTypeAssertion(x ast.Expr) ast.Expr { + if p.trace { + defer un(trace(p, "TypeAssertion")) + } + + lparen := p.expect(token.LPAREN) + var typ ast.Expr + if p.tok == token.TYPE { + // type switch: typ == nil + p.next() + } else { + typ = p.parseType() + } + rparen := p.expect(token.RPAREN) + + return &ast.TypeAssertExpr{X: x, Type: typ, Lparen: lparen, Rparen: rparen} +} + +func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr { + if p.trace { + defer un(trace(p, "IndexOrSlice")) + } + + const N = 3 // change the 3 to 2 to disable 3-index slices + lbrack := p.expect(token.LBRACK) + p.exprLev++ + var index [N]ast.Expr + var colons [N - 1]token.Pos + if p.tok != token.COLON { + index[0] = p.parseRhs() + } + ncolons := 0 + for p.tok == token.COLON && ncolons < len(colons) { + colons[ncolons] = p.pos + ncolons++ + p.next() + if p.tok != token.COLON && p.tok != token.RBRACK && p.tok != token.EOF { + index[ncolons] = p.parseRhs() + } + } + p.exprLev-- + rbrack := p.expect(token.RBRACK) + + if ncolons > 0 { + // slice expression + slice3 := false + if ncolons == 2 { + slice3 = true + // Check presence of 2nd and 3rd index here rather than during type-checking + // to prevent erroneous programs from passing through gofmt (was issue 7305). + if index[1] == nil { + p.error(colons[0], "2nd index required in 3-index slice") + index[1] = &ast.BadExpr{From: colons[0] + 1, To: colons[1]} + } + if index[2] == nil { + p.error(colons[1], "3rd index required in 3-index slice") + index[2] = &ast.BadExpr{From: colons[1] + 1, To: rbrack} + } + } + return &ast.SliceExpr{X: x, Lbrack: lbrack, Low: index[0], High: index[1], Max: index[2], Slice3: slice3, Rbrack: rbrack} + } + + return &ast.IndexExpr{X: x, Lbrack: lbrack, Index: index[0], Rbrack: rbrack} +} + +func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr { + if p.trace { + defer un(trace(p, "CallOrConversion")) + } + + lparen := p.expect(token.LPAREN) + p.exprLev++ + var list []ast.Expr + var ellipsis token.Pos + for p.tok != token.RPAREN && p.tok != token.EOF && !ellipsis.IsValid() { + list = append(list, p.parseRhsOrType()) // builtins may expect a type: make(some type, ...) + if p.tok == token.ELLIPSIS { + ellipsis = p.pos + p.next() + } + if !p.atComma("argument list", token.RPAREN) { + break + } + p.next() + } + p.exprLev-- + rparen := p.expectClosing(token.RPAREN, "argument list") + + return &ast.CallExpr{Fun: fun, Lparen: lparen, Args: list, Ellipsis: ellipsis, Rparen: rparen} +} + +func (p *parser) parseValue(keyOk bool) ast.Expr { + if p.trace { + defer un(trace(p, "Element")) + } + + if p.tok == token.LBRACE { + return p.parseLiteralValue(nil) + } + + // Because the parser doesn't know the composite literal type, it cannot + // know if a key that's an identifier is a struct field name or a name + // denoting a value. The former is not resolved by the parser or the + // resolver. + // + // Instead, _try_ to resolve such a key if possible. If it resolves, + // it a) has correctly resolved, or b) incorrectly resolved because + // the key is a struct field with a name matching another identifier. + // In the former case we are done, and in the latter case we don't + // care because the type checker will do a separate field lookup. + // + // If the key does not resolve, it a) must be defined at the top + // level in another file of the same package, the universe scope, or be + // undeclared; or b) it is a struct field. In the former case, the type + // checker can do a top-level lookup, and in the latter case it will do + // a separate field lookup. + x := p.checkExpr(p.parseExpr(keyOk)) + if keyOk { + if p.tok == token.COLON { + // Try to resolve the key but don't collect it + // as unresolved identifier if it fails so that + // we don't get (possibly false) errors about + // undeclared names. + p.tryResolve(x, false) + } else { + // not a key + p.resolve(x) + } + } + + return x +} + +func (p *parser) parseElement() ast.Expr { + if p.trace { + defer un(trace(p, "Element")) + } + + x := p.parseValue(true) + if p.tok == token.COLON { + colon := p.pos + p.next() + x = &ast.KeyValueExpr{Key: x, Colon: colon, Value: p.parseValue(false)} + } + + return x +} + +func (p *parser) parseElementList() (list []ast.Expr) { + if p.trace { + defer un(trace(p, "ElementList")) + } + + for p.tok != token.RBRACE && p.tok != token.EOF { + list = append(list, p.parseElement()) + if !p.atComma("composite literal", token.RBRACE) { + break + } + p.next() + } + + return +} + +func (p *parser) parseLiteralValue(typ ast.Expr) ast.Expr { + if p.trace { + defer un(trace(p, "LiteralValue")) + } + + lbrace := p.expect(token.LBRACE) + var elts []ast.Expr + p.exprLev++ + if p.tok != token.RBRACE { + elts = p.parseElementList() + } + p.exprLev-- + rbrace := p.expectClosing(token.RBRACE, "composite literal") + return &ast.CompositeLit{Type: typ, Lbrace: lbrace, Elts: elts, Rbrace: rbrace} +} + +// checkExpr checks that x is an expression (and not a type). +func (p *parser) checkExpr(x ast.Expr) ast.Expr { + switch unparen(x).(type) { + case *ast.BadExpr: + case *ast.Ident: + case *ast.BasicLit: + case *ast.FuncLit: + case *ast.CompositeLit: + case *ast.ParenExpr: + panic("unreachable") + case *ast.SelectorExpr: + case *ast.IndexExpr: + case *ast.SliceExpr: + case *ast.TypeAssertExpr: + // If t.Type == nil we have a type assertion of the form + // y.(type), which is only allowed in type switch expressions. + // It's hard to exclude those but for the case where we are in + // a type switch. Instead be lenient and test this in the type + // checker. + case *ast.CallExpr: + case *ast.StarExpr: + case *ast.UnaryExpr: + case *ast.BinaryExpr: + default: + // all other nodes are not proper expressions + p.errorExpected(x.Pos(), "expression") + x = &ast.BadExpr{From: x.Pos(), To: p.safePos(x.End())} + } + return x +} + +// isTypeName reports whether x is a (qualified) TypeName. +func isTypeName(x ast.Expr) bool { + switch t := x.(type) { + case *ast.BadExpr: + case *ast.Ident: + case *ast.SelectorExpr: + _, isIdent := t.X.(*ast.Ident) + return isIdent + default: + return false // all other nodes are not type names + } + return true +} + +// isLiteralType reports whether x is a legal composite literal type. +func isLiteralType(x ast.Expr) bool { + switch t := x.(type) { + case *ast.BadExpr: + case *ast.Ident: + case *ast.SelectorExpr: + _, isIdent := t.X.(*ast.Ident) + return isIdent + case *ast.ArrayType: + case *ast.StructType: + case *ast.MapType: + default: + return false // all other nodes are not legal composite literal types + } + return true +} + +// If x is of the form *T, deref returns T, otherwise it returns x. +func deref(x ast.Expr) ast.Expr { + if p, isPtr := x.(*ast.StarExpr); isPtr { + x = p.X + } + return x +} + +// If x is of the form (T), unparen returns unparen(T), otherwise it returns x. +func unparen(x ast.Expr) ast.Expr { + if p, isParen := x.(*ast.ParenExpr); isParen { + x = unparen(p.X) + } + return x +} + +// checkExprOrType checks that x is an expression or a type +// (and not a raw type such as [...]T). +// +func (p *parser) checkExprOrType(x ast.Expr) ast.Expr { + switch t := unparen(x).(type) { + case *ast.ParenExpr: + panic("unreachable") + case *ast.ArrayType: + if len, isEllipsis := t.Len.(*ast.Ellipsis); isEllipsis { + p.error(len.Pos(), "expected array length, found '...'") + x = &ast.BadExpr{From: x.Pos(), To: p.safePos(x.End())} + } + } + + // all other nodes are expressions or types + return x +} + +// If lhs is set and the result is an identifier, it is not resolved. +func (p *parser) parsePrimaryExpr(lhs bool) ast.Expr { + if p.trace { + defer un(trace(p, "PrimaryExpr")) + } + + x := p.parseOperand(lhs) +L: + for { + switch p.tok { + case token.PERIOD: + p.next() + if lhs { + p.resolve(x) + } + switch p.tok { + case token.IDENT: + x = p.parseSelector(p.checkExprOrType(x)) + case token.LPAREN: + x = p.parseTypeAssertion(p.checkExpr(x)) + default: + pos := p.pos + p.errorExpected(pos, "selector or type assertion") + p.next() // make progress + sel := &ast.Ident{NamePos: pos, Name: "_"} + x = &ast.SelectorExpr{X: x, Sel: sel} + } + case token.LBRACK: + if lhs { + p.resolve(x) + } + x = p.parseIndexOrSlice(p.checkExpr(x)) + case token.LPAREN: + if lhs { + p.resolve(x) + } + x = p.parseCallOrConversion(p.checkExprOrType(x)) + case token.LBRACE: + if isLiteralType(x) && (p.exprLev >= 0 || !isTypeName(x)) { + if lhs { + p.resolve(x) + } + x = p.parseLiteralValue(x) + } else { + break L + } + default: + break L + } + lhs = false // no need to try to resolve again + } + + return x +} + +// If lhs is set and the result is an identifier, it is not resolved. +func (p *parser) parseUnaryExpr(lhs bool) ast.Expr { + if p.trace { + defer un(trace(p, "UnaryExpr")) + } + + switch p.tok { + case token.ADD, token.SUB, token.NOT, token.XOR, token.AND: + pos, op := p.pos, p.tok + p.next() + x := p.parseUnaryExpr(false) + return &ast.UnaryExpr{OpPos: pos, Op: op, X: p.checkExpr(x)} + + case token.ARROW: + // channel type or receive expression + arrow := p.pos + p.next() + + // If the next token is token.CHAN we still don't know if it + // is a channel type or a receive operation - we only know + // once we have found the end of the unary expression. There + // are two cases: + // + // <- type => (<-type) must be channel type + // <- expr => <-(expr) is a receive from an expression + // + // In the first case, the arrow must be re-associated with + // the channel type parsed already: + // + // <- (chan type) => (<-chan type) + // <- (chan<- type) => (<-chan (<-type)) + + x := p.parseUnaryExpr(false) + + // determine which case we have + if typ, ok := x.(*ast.ChanType); ok { + // (<-type) + + // re-associate position info and <- + dir := ast.SEND + for ok && dir == ast.SEND { + if typ.Dir == ast.RECV { + // error: (<-type) is (<-(<-chan T)) + p.errorExpected(typ.Arrow, "'chan'") + } + arrow, typ.Begin, typ.Arrow = typ.Arrow, arrow, arrow + dir, typ.Dir = typ.Dir, ast.RECV + typ, ok = typ.Value.(*ast.ChanType) + } + if dir == ast.SEND { + p.errorExpected(arrow, "channel type") + } + + return x + } + + // <-(expr) + return &ast.UnaryExpr{OpPos: arrow, Op: token.ARROW, X: p.checkExpr(x)} + + case token.MUL: + // pointer type or unary "*" expression + pos := p.pos + p.next() + x := p.parseUnaryExpr(false) + return &ast.StarExpr{Star: pos, X: p.checkExprOrType(x)} + } + + return p.parsePrimaryExpr(lhs) +} + +func (p *parser) tokPrec() (token.Token, int) { + tok := p.tok + if p.inRhs && tok == token.ASSIGN { + tok = token.EQL + } + return tok, tok.Precedence() +} + +// If lhs is set and the result is an identifier, it is not resolved. +func (p *parser) parseBinaryExpr(lhs bool, prec1 int) ast.Expr { + if p.trace { + defer un(trace(p, "BinaryExpr")) + } + + x := p.parseUnaryExpr(lhs) + for { + op, oprec := p.tokPrec() + if oprec < prec1 { + return x + } + pos := p.expect(op) + if lhs { + p.resolve(x) + lhs = false + } + y := p.parseBinaryExpr(false, oprec+1) + x = &ast.BinaryExpr{X: p.checkExpr(x), OpPos: pos, Op: op, Y: p.checkExpr(y)} + } +} + +// If lhs is set and the result is an identifier, it is not resolved. +// The result may be a type or even a raw type ([...]int). Callers must +// check the result (using checkExpr or checkExprOrType), depending on +// context. +func (p *parser) parseExpr(lhs bool) ast.Expr { + if p.trace { + defer un(trace(p, "Expression")) + } + + return p.parseBinaryExpr(lhs, token.LowestPrec+1) +} + +func (p *parser) parseRhs() ast.Expr { + old := p.inRhs + p.inRhs = true + x := p.checkExpr(p.parseExpr(false)) + p.inRhs = old + return x +} + +func (p *parser) parseRhsOrType() ast.Expr { + old := p.inRhs + p.inRhs = true + x := p.checkExprOrType(p.parseExpr(false)) + p.inRhs = old + return x +} + +// ---------------------------------------------------------------------------- +// Statements + +// Parsing modes for parseSimpleStmt. +const ( + basic = iota + labelOk + rangeOk +) + +// parseSimpleStmt returns true as 2nd result if it parsed the assignment +// of a range clause (with mode == rangeOk). The returned statement is an +// assignment with a right-hand side that is a single unary expression of +// the form "range x". No guarantees are given for the left-hand side. +func (p *parser) parseSimpleStmt(mode int) (ast.Stmt, bool) { + if p.trace { + defer un(trace(p, "SimpleStmt")) + } + + x := p.parseLhsList() + + switch p.tok { + case + token.DEFINE, token.ASSIGN, token.ADD_ASSIGN, + token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, + token.REM_ASSIGN, token.AND_ASSIGN, token.OR_ASSIGN, + token.XOR_ASSIGN, token.SHL_ASSIGN, token.SHR_ASSIGN, token.AND_NOT_ASSIGN: + // assignment statement, possibly part of a range clause + pos, tok := p.pos, p.tok + p.next() + var y []ast.Expr + isRange := false + if mode == rangeOk && p.tok == token.RANGE && (tok == token.DEFINE || tok == token.ASSIGN) { + pos := p.pos + p.next() + y = []ast.Expr{&ast.UnaryExpr{OpPos: pos, Op: token.RANGE, X: p.parseRhs()}} + isRange = true + } else { + y = p.parseRhsList() + } + as := &ast.AssignStmt{Lhs: x, TokPos: pos, Tok: tok, Rhs: y} + if tok == token.DEFINE { + p.shortVarDecl(as, x) + } + return as, isRange + } + + if len(x) > 1 { + p.errorExpected(x[0].Pos(), "1 expression") + // continue with first expression + } + + switch p.tok { + case token.COLON: + // labeled statement + colon := p.pos + p.next() + if label, isIdent := x[0].(*ast.Ident); mode == labelOk && isIdent { + // Go spec: The scope of a label is the body of the function + // in which it is declared and excludes the body of any nested + // function. + stmt := &ast.LabeledStmt{Label: label, Colon: colon, Stmt: p.parseStmt()} + p.declare(stmt, nil, p.labelScope, ast.Lbl, label) + return stmt, false + } + // The label declaration typically starts at x[0].Pos(), but the label + // declaration may be erroneous due to a token after that position (and + // before the ':'). If SpuriousErrors is not set, the (only) error + // reported for the line is the illegal label error instead of the token + // before the ':' that caused the problem. Thus, use the (latest) colon + // position for error reporting. + p.error(colon, "illegal label declaration") + return &ast.BadStmt{From: x[0].Pos(), To: colon + 1}, false + + case token.ARROW: + // send statement + arrow := p.pos + p.next() + y := p.parseRhs() + return &ast.SendStmt{Chan: x[0], Arrow: arrow, Value: y}, false + + case token.INC, token.DEC: + // increment or decrement + s := &ast.IncDecStmt{X: x[0], TokPos: p.pos, Tok: p.tok} + p.next() + return s, false + } + + // expression + return &ast.ExprStmt{X: x[0]}, false +} + +func (p *parser) parseCallExpr(callType string) *ast.CallExpr { + x := p.parseRhsOrType() // could be a conversion: (some type)(x) + if call, isCall := x.(*ast.CallExpr); isCall { + return call + } + if _, isBad := x.(*ast.BadExpr); !isBad { + // only report error if it's a new one + p.error(p.safePos(x.End()), fmt.Sprintf("function must be invoked in %s statement", callType)) + } + return nil +} + +func (p *parser) parseGoStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "GoStmt")) + } + + pos := p.expect(token.GO) + call := p.parseCallExpr("go") + p.expectSemi() + if call == nil { + return &ast.BadStmt{From: pos, To: pos + 2} // len("go") + } + + return &ast.GoStmt{Go: pos, Call: call} +} + +func (p *parser) parseDeferStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "DeferStmt")) + } + + pos := p.expect(token.DEFER) + call := p.parseCallExpr("defer") + p.expectSemi() + if call == nil { + return &ast.BadStmt{From: pos, To: pos + 5} // len("defer") + } + + return &ast.DeferStmt{Defer: pos, Call: call} +} + +func (p *parser) parseReturnStmt() *ast.ReturnStmt { + if p.trace { + defer un(trace(p, "ReturnStmt")) + } + + pos := p.pos + p.expect(token.RETURN) + var x []ast.Expr + if p.tok != token.SEMICOLON && p.tok != token.RBRACE { + x = p.parseRhsList() + } + p.expectSemi() + + return &ast.ReturnStmt{Return: pos, Results: x} +} + +func (p *parser) parseBranchStmt(tok token.Token) *ast.BranchStmt { + if p.trace { + defer un(trace(p, "BranchStmt")) + } + + pos := p.expect(tok) + var label *ast.Ident + if tok != token.FALLTHROUGH && p.tok == token.IDENT { + label = p.parseIdent() + // add to list of unresolved targets + n := len(p.targetStack) - 1 + p.targetStack[n] = append(p.targetStack[n], label) + } + p.expectSemi() + + return &ast.BranchStmt{TokPos: pos, Tok: tok, Label: label} +} + +func (p *parser) makeExpr(s ast.Stmt, want string) ast.Expr { + if s == nil { + return nil + } + if es, isExpr := s.(*ast.ExprStmt); isExpr { + return p.checkExpr(es.X) + } + found := "simple statement" + if _, isAss := s.(*ast.AssignStmt); isAss { + found = "assignment" + } + p.error(s.Pos(), fmt.Sprintf("expected %s, found %s (missing parentheses around composite literal?)", want, found)) + return &ast.BadExpr{From: s.Pos(), To: p.safePos(s.End())} +} + +// parseIfHeader is an adjusted version of parser.header +// in cmd/compile/internal/syntax/parser.go, which has +// been tuned for better error handling. +func (p *parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) { + if p.tok == token.LBRACE { + p.error(p.pos, "missing condition in if statement") + cond = &ast.BadExpr{From: p.pos, To: p.pos} + return + } + // p.tok != token.LBRACE + + outer := p.exprLev + p.exprLev = -1 + + if p.tok != token.SEMICOLON { + // accept potential variable declaration but complain + if p.tok == token.VAR { + p.next() + p.error(p.pos, fmt.Sprintf("var declaration not allowed in 'IF' initializer")) + } + init, _ = p.parseSimpleStmt(basic) + } + + var condStmt ast.Stmt + var semi struct { + pos token.Pos + lit string // ";" or "\n"; valid if pos.IsValid() + } + if p.tok != token.LBRACE { + if p.tok == token.SEMICOLON { + semi.pos = p.pos + semi.lit = p.lit + p.next() + } else { + p.expect(token.SEMICOLON) + } + if p.tok != token.LBRACE { + condStmt, _ = p.parseSimpleStmt(basic) + } + } else { + condStmt = init + init = nil + } + + if condStmt != nil { + cond = p.makeExpr(condStmt, "boolean expression") + } else if semi.pos.IsValid() { + if semi.lit == "\n" { + p.error(semi.pos, "unexpected newline, expecting { after if clause") + } else { + p.error(semi.pos, "missing condition in if statement") + } + } + + // make sure we have a valid AST + if cond == nil { + cond = &ast.BadExpr{From: p.pos, To: p.pos} + } + + p.exprLev = outer + return +} + +func (p *parser) parseIfStmt() *ast.IfStmt { + if p.trace { + defer un(trace(p, "IfStmt")) + } + + pos := p.expect(token.IF) + p.openScope() + defer p.closeScope() + + init, cond := p.parseIfHeader() + body := p.parseBlockStmt() + + var else_ ast.Stmt + if p.tok == token.ELSE { + p.next() + switch p.tok { + case token.IF: + else_ = p.parseIfStmt() + case token.LBRACE: + else_ = p.parseBlockStmt() + p.expectSemi() + default: + p.errorExpected(p.pos, "if statement or block") + else_ = &ast.BadStmt{From: p.pos, To: p.pos} + } + } else { + p.expectSemi() + } + + return &ast.IfStmt{If: pos, Init: init, Cond: cond, Body: body, Else: else_} +} + +func (p *parser) parseTypeList() (list []ast.Expr) { + if p.trace { + defer un(trace(p, "TypeList")) + } + + list = append(list, p.parseType()) + for p.tok == token.COMMA { + p.next() + list = append(list, p.parseType()) + } + + return +} + +func (p *parser) parseCaseClause(typeSwitch bool) *ast.CaseClause { + if p.trace { + defer un(trace(p, "CaseClause")) + } + + pos := p.pos + var list []ast.Expr + if p.tok == token.CASE { + p.next() + if typeSwitch { + list = p.parseTypeList() + } else { + list = p.parseRhsList() + } + } else { + p.expect(token.DEFAULT) + } + + colon := p.expect(token.COLON) + p.openScope() + body := p.parseStmtList() + p.closeScope() + + return &ast.CaseClause{Case: pos, List: list, Colon: colon, Body: body} +} + +func isTypeSwitchAssert(x ast.Expr) bool { + a, ok := x.(*ast.TypeAssertExpr) + return ok && a.Type == nil +} + +func (p *parser) isTypeSwitchGuard(s ast.Stmt) bool { + switch t := s.(type) { + case *ast.ExprStmt: + // x.(type) + return isTypeSwitchAssert(t.X) + case *ast.AssignStmt: + // v := x.(type) + if len(t.Lhs) == 1 && len(t.Rhs) == 1 && isTypeSwitchAssert(t.Rhs[0]) { + switch t.Tok { + case token.ASSIGN: + // permit v = x.(type) but complain + p.error(t.TokPos, "expected ':=', found '='") + fallthrough + case token.DEFINE: + return true + } + } + } + return false +} + +func (p *parser) parseSwitchStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "SwitchStmt")) + } + + pos := p.expect(token.SWITCH) + p.openScope() + defer p.closeScope() + + var s1, s2 ast.Stmt + if p.tok != token.LBRACE { + prevLev := p.exprLev + p.exprLev = -1 + if p.tok != token.SEMICOLON { + s2, _ = p.parseSimpleStmt(basic) + } + if p.tok == token.SEMICOLON { + p.next() + s1 = s2 + s2 = nil + if p.tok != token.LBRACE { + // A TypeSwitchGuard may declare a variable in addition + // to the variable declared in the initial SimpleStmt. + // Introduce extra scope to avoid redeclaration errors: + // + // switch t := 0; t := x.(T) { ... } + // + // (this code is not valid Go because the first t + // cannot be accessed and thus is never used, the extra + // scope is needed for the correct error message). + // + // If we don't have a type switch, s2 must be an expression. + // Having the extra nested but empty scope won't affect it. + p.openScope() + defer p.closeScope() + s2, _ = p.parseSimpleStmt(basic) + } + } + p.exprLev = prevLev + } + + typeSwitch := p.isTypeSwitchGuard(s2) + lbrace := p.expect(token.LBRACE) + var list []ast.Stmt + for p.tok == token.CASE || p.tok == token.DEFAULT { + list = append(list, p.parseCaseClause(typeSwitch)) + } + rbrace := p.expect(token.RBRACE) + p.expectSemi() + body := &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace} + + if typeSwitch { + return &ast.TypeSwitchStmt{Switch: pos, Init: s1, Assign: s2, Body: body} + } + + return &ast.SwitchStmt{Switch: pos, Init: s1, Tag: p.makeExpr(s2, "switch expression"), Body: body} +} + +func (p *parser) parseCommClause() *ast.CommClause { + if p.trace { + defer un(trace(p, "CommClause")) + } + + p.openScope() + pos := p.pos + var comm ast.Stmt + if p.tok == token.CASE { + p.next() + lhs := p.parseLhsList() + if p.tok == token.ARROW { + // SendStmt + if len(lhs) > 1 { + p.errorExpected(lhs[0].Pos(), "1 expression") + // continue with first expression + } + arrow := p.pos + p.next() + rhs := p.parseRhs() + comm = &ast.SendStmt{Chan: lhs[0], Arrow: arrow, Value: rhs} + } else { + // RecvStmt + if tok := p.tok; tok == token.ASSIGN || tok == token.DEFINE { + // RecvStmt with assignment + if len(lhs) > 2 { + p.errorExpected(lhs[0].Pos(), "1 or 2 expressions") + // continue with first two expressions + lhs = lhs[0:2] + } + pos := p.pos + p.next() + rhs := p.parseRhs() + as := &ast.AssignStmt{Lhs: lhs, TokPos: pos, Tok: tok, Rhs: []ast.Expr{rhs}} + if tok == token.DEFINE { + p.shortVarDecl(as, lhs) + } + comm = as + } else { + // lhs must be single receive operation + if len(lhs) > 1 { + p.errorExpected(lhs[0].Pos(), "1 expression") + // continue with first expression + } + comm = &ast.ExprStmt{X: lhs[0]} + } + } + } else { + p.expect(token.DEFAULT) + } + + colon := p.expect(token.COLON) + body := p.parseStmtList() + p.closeScope() + + return &ast.CommClause{Case: pos, Comm: comm, Colon: colon, Body: body} +} + +func (p *parser) parseSelectStmt() *ast.SelectStmt { + if p.trace { + defer un(trace(p, "SelectStmt")) + } + + pos := p.expect(token.SELECT) + lbrace := p.expect(token.LBRACE) + var list []ast.Stmt + for p.tok == token.CASE || p.tok == token.DEFAULT { + list = append(list, p.parseCommClause()) + } + rbrace := p.expect(token.RBRACE) + p.expectSemi() + body := &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace} + + return &ast.SelectStmt{Select: pos, Body: body} +} + +func (p *parser) parseForStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "ForStmt")) + } + + pos := p.expect(token.FOR) + p.openScope() + defer p.closeScope() + + var s1, s2, s3 ast.Stmt + var isRange bool + if p.tok != token.LBRACE { + prevLev := p.exprLev + p.exprLev = -1 + if p.tok != token.SEMICOLON { + if p.tok == token.RANGE { + // "for range x" (nil lhs in assignment) + pos := p.pos + p.next() + y := []ast.Expr{&ast.UnaryExpr{OpPos: pos, Op: token.RANGE, X: p.parseRhs()}} + s2 = &ast.AssignStmt{Rhs: y} + isRange = true + } else { + s2, isRange = p.parseSimpleStmt(rangeOk) + } + } + if !isRange && p.tok == token.SEMICOLON { + p.next() + s1 = s2 + s2 = nil + if p.tok != token.SEMICOLON { + s2, _ = p.parseSimpleStmt(basic) + } + p.expectSemi() + if p.tok != token.LBRACE { + s3, _ = p.parseSimpleStmt(basic) + } + } + p.exprLev = prevLev + } + + body := p.parseBlockStmt() + p.expectSemi() + + if isRange { + as := s2.(*ast.AssignStmt) + // check lhs + var key, value ast.Expr + switch len(as.Lhs) { + case 0: + // nothing to do + case 1: + key = as.Lhs[0] + case 2: + key, value = as.Lhs[0], as.Lhs[1] + default: + p.errorExpected(as.Lhs[len(as.Lhs)-1].Pos(), "at most 2 expressions") + return &ast.BadStmt{From: pos, To: p.safePos(body.End())} + } + // parseSimpleStmt returned a right-hand side that + // is a single unary expression of the form "range x" + x := as.Rhs[0].(*ast.UnaryExpr).X + return &ast.RangeStmt{ + For: pos, + Key: key, + Value: value, + TokPos: as.TokPos, + Tok: as.Tok, + X: x, + Body: body, + } + } + + // regular for statement + return &ast.ForStmt{ + For: pos, + Init: s1, + Cond: p.makeExpr(s2, "boolean or range expression"), + Post: s3, + Body: body, + } +} + +func (p *parser) parseStmt() (s ast.Stmt) { + if p.trace { + defer un(trace(p, "Statement")) + } + + switch p.tok { + case token.CONST, token.TYPE, token.VAR: + s = &ast.DeclStmt{Decl: p.parseDecl(stmtStart)} + case + // tokens that may start an expression + token.IDENT, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING, token.FUNC, token.LPAREN, // operands + token.LBRACK, token.STRUCT, token.MAP, token.CHAN, token.INTERFACE, // composite types + token.ADD, token.SUB, token.MUL, token.AND, token.XOR, token.ARROW, token.NOT: // unary operators + s, _ = p.parseSimpleStmt(labelOk) + // because of the required look-ahead, labeled statements are + // parsed by parseSimpleStmt - don't expect a semicolon after + // them + if _, isLabeledStmt := s.(*ast.LabeledStmt); !isLabeledStmt { + p.expectSemi() + } + case token.GO: + s = p.parseGoStmt() + case token.DEFER: + s = p.parseDeferStmt() + case token.RETURN: + s = p.parseReturnStmt() + case token.BREAK, token.CONTINUE, token.GOTO, token.FALLTHROUGH: + s = p.parseBranchStmt(p.tok) + case token.LBRACE: + s = p.parseBlockStmt() + p.expectSemi() + case token.IF: + s = p.parseIfStmt() + case token.SWITCH: + s = p.parseSwitchStmt() + case token.SELECT: + s = p.parseSelectStmt() + case token.FOR: + s = p.parseForStmt() + case token.SEMICOLON: + // Is it ever possible to have an implicit semicolon + // producing an empty statement in a valid program? + // (handle correctly anyway) + s = &ast.EmptyStmt{Semicolon: p.pos, Implicit: p.lit == "\n"} + p.next() + case token.RBRACE: + // a semicolon may be omitted before a closing "}" + s = &ast.EmptyStmt{Semicolon: p.pos, Implicit: true} + default: + // no statement found + pos := p.pos + p.errorExpected(pos, "statement") + p.advance(stmtStart) + s = &ast.BadStmt{From: pos, To: p.pos} + } + + return +} + +// ---------------------------------------------------------------------------- +// Declarations + +type parseSpecFunction func(doc *ast.CommentGroup, keyword token.Token, iota int) ast.Spec + +func isValidImport(lit string) bool { + const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" + s, _ := strconv.Unquote(lit) // go/scanner returns a legal string literal + for _, r := range s { + if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { + return false + } + } + return s != "" +} + +func (p *parser) parseImportSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast.Spec { + if p.trace { + defer un(trace(p, "ImportSpec")) + } + + var ident *ast.Ident + switch p.tok { + case token.PERIOD: + ident = &ast.Ident{NamePos: p.pos, Name: "."} + p.next() + case token.IDENT: + ident = p.parseIdent() + } + + pos := p.pos + var path string + if p.tok == token.STRING { + path = p.lit + if !isValidImport(path) { + p.error(pos, "invalid import path: "+path) + } + p.next() + } else { + p.expect(token.STRING) // use expect() error handling + } + p.expectSemi() // call before accessing p.linecomment + + // collect imports + spec := &ast.ImportSpec{ + Doc: doc, + Name: ident, + Path: &ast.BasicLit{ValuePos: pos, Kind: token.STRING, Value: path}, + Comment: p.lineComment, + } + p.imports = append(p.imports, spec) + + return spec +} + +func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota int) ast.Spec { + if p.trace { + defer un(trace(p, keyword.String()+"Spec")) + } + + pos := p.pos + idents := p.parseIdentList() + typ := p.tryType() + var values []ast.Expr + // always permit optional initialization for more tolerant parsing + if p.tok == token.ASSIGN { + p.next() + values = p.parseRhsList() + } + p.expectSemi() // call before accessing p.linecomment + + switch keyword { + case token.VAR: + if typ == nil && values == nil { + p.error(pos, "missing variable type or initialization") + } + case token.CONST: + if values == nil && (iota == 0 || typ != nil) { + p.error(pos, "missing constant value") + } + } + + // Go spec: The scope of a constant or variable identifier declared inside + // a function begins at the end of the ConstSpec or VarSpec and ends at + // the end of the innermost containing block. + // (Global identifiers are resolved in a separate phase after parsing.) + spec := &ast.ValueSpec{ + Doc: doc, + Names: idents, + Type: typ, + Values: values, + Comment: p.lineComment, + } + kind := ast.Con + if keyword == token.VAR { + kind = ast.Var + } + p.declare(spec, iota, p.topScope, kind, idents...) + + return spec +} + +func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast.Spec { + if p.trace { + defer un(trace(p, "TypeSpec")) + } + + ident := p.parseIdent() + + // Go spec: The scope of a type identifier declared inside a function begins + // at the identifier in the TypeSpec and ends at the end of the innermost + // containing block. + // (Global identifiers are resolved in a separate phase after parsing.) + spec := &ast.TypeSpec{Doc: doc, Name: ident} + p.declare(spec, nil, p.topScope, ast.Typ, ident) + if p.tok == token.ASSIGN { + spec.Assign = p.pos + p.next() + } + spec.Type = p.parseType() + p.expectSemi() // call before accessing p.linecomment + spec.Comment = p.lineComment + + return spec +} + +func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.GenDecl { + if p.trace { + defer un(trace(p, "GenDecl("+keyword.String()+")")) + } + + doc := p.leadComment + pos := p.expect(keyword) + var lparen, rparen token.Pos + var list []ast.Spec + if p.tok == token.LPAREN { + lparen = p.pos + p.next() + for iota := 0; p.tok != token.RPAREN && p.tok != token.EOF; iota++ { + list = append(list, f(p.leadComment, keyword, iota)) + } + rparen = p.expect(token.RPAREN) + p.expectSemi() + } else { + list = append(list, f(nil, keyword, 0)) + } + + return &ast.GenDecl{ + Doc: doc, + TokPos: pos, + Tok: keyword, + Lparen: lparen, + Specs: list, + Rparen: rparen, + } +} + +func (p *parser) parseFuncDecl() *ast.FuncDecl { + if p.trace { + defer un(trace(p, "FunctionDecl")) + } + + doc := p.leadComment + pos := p.expect(token.FUNC) + scope := ast.NewScope(p.topScope) // function scope + + var recv *ast.FieldList + if p.tok == token.LPAREN { + recv = p.parseParameters(scope, false) + } + + ident := p.parseIdent() + + params, results := p.parseSignature(scope) + + var body *ast.BlockStmt + if p.tok == token.LBRACE { + body = p.parseBody(scope) + p.expectSemi() + } else if p.tok == token.SEMICOLON { + p.next() + if p.tok == token.LBRACE { + // opening { of function declaration on next line + p.error(p.pos, "unexpected semicolon or newline before {") + body = p.parseBody(scope) + p.expectSemi() + } + } else { + p.expectSemi() + } + + decl := &ast.FuncDecl{ + Doc: doc, + Recv: recv, + Name: ident, + Type: &ast.FuncType{ + Func: pos, + Params: params, + Results: results, + }, + Body: body, + } + if recv == nil { + // Go spec: The scope of an identifier denoting a constant, type, + // variable, or function (but not method) declared at top level + // (outside any function) is the package block. + // + // init() functions cannot be referred to and there may + // be more than one - don't put them in the pkgScope + if ident.Name != "init" { + p.declare(decl, nil, p.pkgScope, ast.Fun, ident) + } + } + + return decl +} + +func (p *parser) parseDecl(sync map[token.Token]bool) ast.Decl { + if p.trace { + defer un(trace(p, "Declaration")) + } + + var f parseSpecFunction + switch p.tok { + case token.CONST, token.VAR: + f = p.parseValueSpec + + case token.TYPE: + f = p.parseTypeSpec + + case token.FUNC: + return p.parseFuncDecl() + + default: + pos := p.pos + p.errorExpected(pos, "declaration") + p.advance(sync) + return &ast.BadDecl{From: pos, To: p.pos} + } + + return p.parseGenDecl(p.tok, f) +} + +// ---------------------------------------------------------------------------- +// Source files + +func (p *parser) parseFile() *ast.File { + if p.trace { + defer un(trace(p, "File")) + } + + // Don't bother parsing the rest if we had errors scanning the first token. + // Likely not a Go source file at all. + if p.errors.Len() != 0 { + return nil + } + + // package clause + doc := p.leadComment + pos := p.expect(token.PACKAGE) + // Go spec: The package clause is not a declaration; + // the package name does not appear in any scope. + ident := p.parseIdent() + if ident.Name == "_" && p.mode&DeclarationErrors != 0 { + p.error(p.pos, "invalid package name _") + } + p.expectSemi() + + // Don't bother parsing the rest if we had errors parsing the package clause. + // Likely not a Go source file at all. + if p.errors.Len() != 0 { + return nil + } + + p.openScope() + p.pkgScope = p.topScope + var decls []ast.Decl + if p.mode&PackageClauseOnly == 0 { + // import decls + for p.tok == token.IMPORT { + decls = append(decls, p.parseGenDecl(token.IMPORT, p.parseImportSpec)) + } + + if p.mode&ImportsOnly == 0 { + // rest of package body + for p.tok != token.EOF { + decls = append(decls, p.parseDecl(declStart)) + } + } + } + p.closeScope() + assert(p.topScope == nil, "unbalanced scopes") + assert(p.labelScope == nil, "unbalanced label scopes") + + // resolve global identifiers within the same file + i := 0 + for _, ident := range p.unresolved { + // i <= index for current ident + assert(ident.Obj == unresolved, "object already resolved") + ident.Obj = p.pkgScope.Lookup(ident.Name) // also removes unresolved sentinel + if ident.Obj == nil { + p.unresolved[i] = ident + i++ + } + } + + return &ast.File{ + Doc: doc, + Package: pos, + Name: ident, + Decls: decls, + Scope: p.pkgScope, + Imports: p.imports, + Unresolved: p.unresolved[0:i], + Comments: p.comments, + } +} diff --git a/src/go/parser/parser_test.go b/src/go/parser/parser_test.go new file mode 100644 index 0000000..a4f882d --- /dev/null +++ b/src/go/parser/parser_test.go @@ -0,0 +1,579 @@ +// Copyright 2009 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 parser + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + "io/fs" + "strings" + "testing" +) + +var validFiles = []string{ + "parser.go", + "parser_test.go", + "error_test.go", + "short_test.go", +} + +func TestParse(t *testing.T) { + for _, filename := range validFiles { + _, err := ParseFile(token.NewFileSet(), filename, nil, DeclarationErrors) + if err != nil { + t.Fatalf("ParseFile(%s): %v", filename, err) + } + } +} + +func nameFilter(filename string) bool { + switch filename { + case "parser.go", "interface.go", "parser_test.go": + return true + case "parser.go.orig": + return true // permit but should be ignored by ParseDir + } + return false +} + +func dirFilter(f fs.FileInfo) bool { return nameFilter(f.Name()) } + +func TestParseFile(t *testing.T) { + src := "package p\nvar _=s[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]" + _, err := ParseFile(token.NewFileSet(), "", src, 0) + if err == nil { + t.Errorf("ParseFile(%s) succeeded unexpectedly", src) + } +} + +func TestParseExprFrom(t *testing.T) { + src := "s[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]+\ns[::]" + _, err := ParseExprFrom(token.NewFileSet(), "", src, 0) + if err == nil { + t.Errorf("ParseExprFrom(%s) succeeded unexpectedly", src) + } +} + +func TestParseDir(t *testing.T) { + path := "." + pkgs, err := ParseDir(token.NewFileSet(), path, dirFilter, 0) + if err != nil { + t.Fatalf("ParseDir(%s): %v", path, err) + } + if n := len(pkgs); n != 1 { + t.Errorf("got %d packages; want 1", n) + } + pkg := pkgs["parser"] + if pkg == nil { + t.Errorf(`package "parser" not found`) + return + } + if n := len(pkg.Files); n != 3 { + t.Errorf("got %d package files; want 3", n) + } + for filename := range pkg.Files { + if !nameFilter(filename) { + t.Errorf("unexpected package file: %s", filename) + } + } +} + +func TestIssue42951(t *testing.T) { + path := "./testdata/issue42951" + _, err := ParseDir(token.NewFileSet(), path, nil, 0) + if err != nil { + t.Errorf("ParseDir(%s): %v", path, err) + } +} + +func TestParseExpr(t *testing.T) { + // just kicking the tires: + // a valid arithmetic expression + src := "a + b" + x, err := ParseExpr(src) + if err != nil { + t.Errorf("ParseExpr(%q): %v", src, err) + } + // sanity check + if _, ok := x.(*ast.BinaryExpr); !ok { + t.Errorf("ParseExpr(%q): got %T, want *ast.BinaryExpr", src, x) + } + + // a valid type expression + src = "struct{x *int}" + x, err = ParseExpr(src) + if err != nil { + t.Errorf("ParseExpr(%q): %v", src, err) + } + // sanity check + if _, ok := x.(*ast.StructType); !ok { + t.Errorf("ParseExpr(%q): got %T, want *ast.StructType", src, x) + } + + // an invalid expression + src = "a + *" + x, err = ParseExpr(src) + if err == nil { + t.Errorf("ParseExpr(%q): got no error", src) + } + if x == nil { + t.Errorf("ParseExpr(%q): got no (partial) result", src) + } + if _, ok := x.(*ast.BinaryExpr); !ok { + t.Errorf("ParseExpr(%q): got %T, want *ast.BinaryExpr", src, x) + } + + // a valid expression followed by extra tokens is invalid + src = "a[i] := x" + if _, err := ParseExpr(src); err == nil { + t.Errorf("ParseExpr(%q): got no error", src) + } + + // a semicolon is not permitted unless automatically inserted + src = "a + b\n" + if _, err := ParseExpr(src); err != nil { + t.Errorf("ParseExpr(%q): got error %s", src, err) + } + src = "a + b;" + if _, err := ParseExpr(src); err == nil { + t.Errorf("ParseExpr(%q): got no error", src) + } + + // various other stuff following a valid expression + const validExpr = "a + b" + const anything = "dh3*#D)#_" + for _, c := range "!)]};," { + src := validExpr + string(c) + anything + if _, err := ParseExpr(src); err == nil { + t.Errorf("ParseExpr(%q): got no error", src) + } + } + + // ParseExpr must not crash + for _, src := range valids { + ParseExpr(src) + } +} + +func TestColonEqualsScope(t *testing.T) { + f, err := ParseFile(token.NewFileSet(), "", `package p; func f() { x, y, z := x, y, z }`, 0) + if err != nil { + t.Fatal(err) + } + + // RHS refers to undefined globals; LHS does not. + as := f.Decls[0].(*ast.FuncDecl).Body.List[0].(*ast.AssignStmt) + for _, v := range as.Rhs { + id := v.(*ast.Ident) + if id.Obj != nil { + t.Errorf("rhs %s has Obj, should not", id.Name) + } + } + for _, v := range as.Lhs { + id := v.(*ast.Ident) + if id.Obj == nil { + t.Errorf("lhs %s does not have Obj, should", id.Name) + } + } +} + +func TestVarScope(t *testing.T) { + f, err := ParseFile(token.NewFileSet(), "", `package p; func f() { var x, y, z = x, y, z }`, 0) + if err != nil { + t.Fatal(err) + } + + // RHS refers to undefined globals; LHS does not. + as := f.Decls[0].(*ast.FuncDecl).Body.List[0].(*ast.DeclStmt).Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec) + for _, v := range as.Values { + id := v.(*ast.Ident) + if id.Obj != nil { + t.Errorf("rhs %s has Obj, should not", id.Name) + } + } + for _, id := range as.Names { + if id.Obj == nil { + t.Errorf("lhs %s does not have Obj, should", id.Name) + } + } +} + +func TestObjects(t *testing.T) { + const src = ` +package p +import fmt "fmt" +const pi = 3.14 +type T struct{} +var x int +func f() { L: } +` + + f, err := ParseFile(token.NewFileSet(), "", src, 0) + if err != nil { + t.Fatal(err) + } + + objects := map[string]ast.ObjKind{ + "p": ast.Bad, // not in a scope + "fmt": ast.Bad, // not resolved yet + "pi": ast.Con, + "T": ast.Typ, + "x": ast.Var, + "int": ast.Bad, // not resolved yet + "f": ast.Fun, + "L": ast.Lbl, + } + + ast.Inspect(f, func(n ast.Node) bool { + if ident, ok := n.(*ast.Ident); ok { + obj := ident.Obj + if obj == nil { + if objects[ident.Name] != ast.Bad { + t.Errorf("no object for %s", ident.Name) + } + return true + } + if obj.Name != ident.Name { + t.Errorf("names don't match: obj.Name = %s, ident.Name = %s", obj.Name, ident.Name) + } + kind := objects[ident.Name] + if obj.Kind != kind { + t.Errorf("%s: obj.Kind = %s; want %s", ident.Name, obj.Kind, kind) + } + } + return true + }) +} + +func TestUnresolved(t *testing.T) { + f, err := ParseFile(token.NewFileSet(), "", ` +package p +// +func f1a(int) +func f2a(byte, int, float) +func f3a(a, b int, c float) +func f4a(...complex) +func f5a(a s1a, b ...complex) +// +func f1b(*int) +func f2b([]byte, (int), *float) +func f3b(a, b *int, c []float) +func f4b(...*complex) +func f5b(a s1a, b ...[]complex) +// +type s1a struct { int } +type s2a struct { byte; int; s1a } +type s3a struct { a, b int; c float } +// +type s1b struct { *int } +type s2b struct { byte; int; *float } +type s3b struct { a, b *s3b; c []float } +`, 0) + if err != nil { + t.Fatal(err) + } + + want := "int " + // f1a + "byte int float " + // f2a + "int float " + // f3a + "complex " + // f4a + "complex " + // f5a + // + "int " + // f1b + "byte int float " + // f2b + "int float " + // f3b + "complex " + // f4b + "complex " + // f5b + // + "int " + // s1a + "byte int " + // s2a + "int float " + // s3a + // + "int " + // s1a + "byte int float " + // s2a + "float " // s3a + + // collect unresolved identifiers + var buf bytes.Buffer + for _, u := range f.Unresolved { + buf.WriteString(u.Name) + buf.WriteByte(' ') + } + got := buf.String() + + if got != want { + t.Errorf("\ngot: %s\nwant: %s", got, want) + } +} + +var imports = map[string]bool{ + `"a"`: true, + "`a`": true, + `"a/b"`: true, + `"a.b"`: true, + `"m\x61th"`: true, + `"greek/αβ"`: true, + `""`: false, + + // Each of these pairs tests both `` vs "" strings + // and also use of invalid characters spelled out as + // escape sequences and written directly. + // For example `"\x00"` tests import "\x00" + // while "`\x00`" tests import ``. + `"\x00"`: false, + "`\x00`": false, + `"\x7f"`: false, + "`\x7f`": false, + `"a!"`: false, + "`a!`": false, + `"a b"`: false, + "`a b`": false, + `"a\\b"`: false, + "`a\\b`": false, + "\"`a`\"": false, + "`\"a\"`": false, + `"\x80\x80"`: false, + "`\x80\x80`": false, + `"\xFFFD"`: false, + "`\xFFFD`": false, +} + +func TestImports(t *testing.T) { + for path, isValid := range imports { + src := fmt.Sprintf("package p; import %s", path) + _, err := ParseFile(token.NewFileSet(), "", src, 0) + switch { + case err != nil && isValid: + t.Errorf("ParseFile(%s): got %v; expected no error", src, err) + case err == nil && !isValid: + t.Errorf("ParseFile(%s): got no error; expected one", src) + } + } +} + +func TestCommentGroups(t *testing.T) { + f, err := ParseFile(token.NewFileSet(), "", ` +package p /* 1a */ /* 1b */ /* 1c */ // 1d +/* 2a +*/ +// 2b +const pi = 3.1415 +/* 3a */ // 3b +/* 3c */ const e = 2.7182 + +// Example from issue 3139 +func ExampleCount() { + fmt.Println(strings.Count("cheese", "e")) + fmt.Println(strings.Count("five", "")) // before & after each rune + // Output: + // 3 + // 5 +} +`, ParseComments) + if err != nil { + t.Fatal(err) + } + expected := [][]string{ + {"/* 1a */", "/* 1b */", "/* 1c */", "// 1d"}, + {"/* 2a\n*/", "// 2b"}, + {"/* 3a */", "// 3b", "/* 3c */"}, + {"// Example from issue 3139"}, + {"// before & after each rune"}, + {"// Output:", "// 3", "// 5"}, + } + if len(f.Comments) != len(expected) { + t.Fatalf("got %d comment groups; expected %d", len(f.Comments), len(expected)) + } + for i, exp := range expected { + got := f.Comments[i].List + if len(got) != len(exp) { + t.Errorf("got %d comments in group %d; expected %d", len(got), i, len(exp)) + continue + } + for j, exp := range exp { + got := got[j].Text + if got != exp { + t.Errorf("got %q in group %d; expected %q", got, i, exp) + } + } + } +} + +func getField(file *ast.File, fieldname string) *ast.Field { + parts := strings.Split(fieldname, ".") + for _, d := range file.Decls { + if d, ok := d.(*ast.GenDecl); ok && d.Tok == token.TYPE { + for _, s := range d.Specs { + if s, ok := s.(*ast.TypeSpec); ok && s.Name.Name == parts[0] { + if s, ok := s.Type.(*ast.StructType); ok { + for _, f := range s.Fields.List { + for _, name := range f.Names { + if name.Name == parts[1] { + return f + } + } + } + } + } + } + } + } + return nil +} + +// Don't use ast.CommentGroup.Text() - we want to see exact comment text. +func commentText(c *ast.CommentGroup) string { + var buf bytes.Buffer + if c != nil { + for _, c := range c.List { + buf.WriteString(c.Text) + } + } + return buf.String() +} + +func checkFieldComments(t *testing.T, file *ast.File, fieldname, lead, line string) { + f := getField(file, fieldname) + if f == nil { + t.Fatalf("field not found: %s", fieldname) + } + if got := commentText(f.Doc); got != lead { + t.Errorf("got lead comment %q; expected %q", got, lead) + } + if got := commentText(f.Comment); got != line { + t.Errorf("got line comment %q; expected %q", got, line) + } +} + +func TestLeadAndLineComments(t *testing.T) { + f, err := ParseFile(token.NewFileSet(), "", ` +package p +type T struct { + /* F1 lead comment */ + // + F1 int /* F1 */ // line comment + // F2 lead + // comment + F2 int // F2 line comment + // f3 lead comment + f3 int // f3 line comment +} +`, ParseComments) + if err != nil { + t.Fatal(err) + } + checkFieldComments(t, f, "T.F1", "/* F1 lead comment *///", "/* F1 */// line comment") + checkFieldComments(t, f, "T.F2", "// F2 lead// comment", "// F2 line comment") + checkFieldComments(t, f, "T.f3", "// f3 lead comment", "// f3 line comment") + ast.FileExports(f) + checkFieldComments(t, f, "T.F1", "/* F1 lead comment *///", "/* F1 */// line comment") + checkFieldComments(t, f, "T.F2", "// F2 lead// comment", "// F2 line comment") + if getField(f, "T.f3") != nil { + t.Error("not expected to find T.f3") + } +} + +// TestIssue9979 verifies that empty statements are contained within their enclosing blocks. +func TestIssue9979(t *testing.T) { + for _, src := range []string{ + "package p; func f() {;}", + "package p; func f() {L:}", + "package p; func f() {L:;}", + "package p; func f() {L:\n}", + "package p; func f() {L:\n;}", + "package p; func f() { ; }", + "package p; func f() { L: }", + "package p; func f() { L: ; }", + "package p; func f() { L: \n}", + "package p; func f() { L: \n; }", + } { + fset := token.NewFileSet() + f, err := ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + var pos, end token.Pos + ast.Inspect(f, func(x ast.Node) bool { + switch s := x.(type) { + case *ast.BlockStmt: + pos, end = s.Pos()+1, s.End()-1 // exclude "{", "}" + case *ast.LabeledStmt: + pos, end = s.Pos()+2, s.End() // exclude "L:" + case *ast.EmptyStmt: + // check containment + if s.Pos() < pos || s.End() > end { + t.Errorf("%s: %T[%d, %d] not inside [%d, %d]", src, s, s.Pos(), s.End(), pos, end) + } + // check semicolon + offs := fset.Position(s.Pos()).Offset + if ch := src[offs]; ch != ';' != s.Implicit { + want := "want ';'" + if s.Implicit { + want = "but ';' is implicit" + } + t.Errorf("%s: found %q at offset %d; %s", src, ch, offs, want) + } + } + return true + }) + } +} + +// TestIncompleteSelection ensures that an incomplete selector +// expression is parsed as a (blank) *ast.SelectorExpr, not a +// *ast.BadExpr. +func TestIncompleteSelection(t *testing.T) { + for _, src := range []string{ + "package p; var _ = fmt.", // at EOF + "package p; var _ = fmt.\ntype X int", // not at EOF + } { + fset := token.NewFileSet() + f, err := ParseFile(fset, "", src, 0) + if err == nil { + t.Errorf("ParseFile(%s) succeeded unexpectedly", src) + continue + } + + const wantErr = "expected selector or type assertion" + if !strings.Contains(err.Error(), wantErr) { + t.Errorf("ParseFile returned wrong error %q, want %q", err, wantErr) + } + + var sel *ast.SelectorExpr + ast.Inspect(f, func(n ast.Node) bool { + if n, ok := n.(*ast.SelectorExpr); ok { + sel = n + } + return true + }) + if sel == nil { + t.Error("found no *ast.SelectorExpr") + continue + } + const wantSel = "&{fmt _}" + if fmt.Sprint(sel) != wantSel { + t.Errorf("found selector %s, want %s", sel, wantSel) + continue + } + } +} + +func TestLastLineComment(t *testing.T) { + const src = `package main +type x int // comment +` + fset := token.NewFileSet() + f, err := ParseFile(fset, "", src, ParseComments) + if err != nil { + t.Fatal(err) + } + comment := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.TypeSpec).Comment.List[0].Text + if comment != "// comment" { + t.Errorf("got %q, want %q", comment, "// comment") + } +} diff --git a/src/go/parser/performance_test.go b/src/go/parser/performance_test.go new file mode 100644 index 0000000..f81bcee --- /dev/null +++ b/src/go/parser/performance_test.go @@ -0,0 +1,30 @@ +// Copyright 2012 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 parser + +import ( + "go/token" + "os" + "testing" +) + +var src = readFile("parser.go") + +func readFile(filename string) []byte { + data, err := os.ReadFile(filename) + if err != nil { + panic(err) + } + return data +} + +func BenchmarkParse(b *testing.B) { + b.SetBytes(int64(len(src))) + for i := 0; i < b.N; i++ { + if _, err := ParseFile(token.NewFileSet(), "", src, ParseComments); err != nil { + b.Fatalf("benchmark failed due to parse error: %s", err) + } + } +} diff --git a/src/go/parser/short_test.go b/src/go/parser/short_test.go new file mode 100644 index 0000000..49bb681 --- /dev/null +++ b/src/go/parser/short_test.go @@ -0,0 +1,136 @@ +// Copyright 2009 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. + +// This file contains test cases for short valid and invalid programs. + +package parser + +import "testing" + +var valids = []string{ + "package p\n", + `package p;`, + `package p; import "fmt"; func f() { fmt.Println("Hello, World!") };`, + `package p; func f() { if f(T{}) {} };`, + `package p; func f() { _ = <-chan int(nil) };`, + `package p; func f() { _ = (<-chan int)(nil) };`, + `package p; func f() { _ = (<-chan <-chan int)(nil) };`, + `package p; func f() { _ = <-chan <-chan <-chan <-chan <-int(nil) };`, + `package p; func f(func() func() func());`, + `package p; func f(...T);`, + `package p; func f(float, ...int);`, + `package p; func f(x int, a ...int) { f(0, a...); f(1, a...,) };`, + `package p; func f(int,) {};`, + `package p; func f(...int,) {};`, + `package p; func f(x ...int,) {};`, + `package p; type T []int; var a []bool; func f() { if a[T{42}[0]] {} };`, + `package p; type T []int; func g(int) bool { return true }; func f() { if g(T{42}[0]) {} };`, + `package p; type T []int; func f() { for _ = range []int{T{42}[0]} {} };`, + `package p; var a = T{{1, 2}, {3, 4}}`, + `package p; func f() { select { case <- c: case c <- d: case c <- <- d: case <-c <- d: } };`, + `package p; func f() { select { case x := (<-c): } };`, + `package p; func f() { if ; true {} };`, + `package p; func f() { switch ; {} };`, + `package p; func f() { for _ = range "foo" + "bar" {} };`, + `package p; func f() { var s []int; g(s[:], s[i:], s[:j], s[i:j], s[i:j:k], s[:j:k]) };`, + `package p; var ( _ = (struct {*T}).m; _ = (interface {T}).m )`, + `package p; func ((T),) m() {}`, + `package p; func ((*T),) m() {}`, + `package p; func (*(T),) m() {}`, + `package p; func _(x []int) { for range x {} }`, + `package p; func _() { if [T{}.n]int{} {} }`, + `package p; func _() { map[int]int{}[0]++; map[int]int{}[0] += 1 }`, + `package p; func _(x interface{f()}) { interface{f()}(x).f() }`, + `package p; func _(x chan int) { chan int(x) <- 0 }`, + `package p; const (x = 0; y; z)`, // issue 9639 + `package p; var _ = map[P]int{P{}:0, {}:1}`, + `package p; var _ = map[*P]int{&P{}:0, {}:1}`, + `package p; type T = int`, + `package p; type (T = p.T; _ = struct{}; x = *T)`, +} + +func TestValid(t *testing.T) { + for _, src := range valids { + checkErrors(t, src, src) + } +} + +var invalids = []string{ + `foo /* ERROR "expected 'package'" */ !`, + `package p; func f() { if { /* ERROR "missing condition" */ } };`, + `package p; func f() { if ; /* ERROR "missing condition" */ {} };`, + `package p; func f() { if f(); /* ERROR "missing condition" */ {} };`, + `package p; func f() { if _ = range /* ERROR "expected operand" */ x; true {} };`, + `package p; func f() { switch _ /* ERROR "expected switch expression" */ = range x; true {} };`, + `package p; func f() { for _ = range x ; /* ERROR "expected '{'" */ ; {} };`, + `package p; func f() { for ; ; _ = range /* ERROR "expected operand" */ x {} };`, + `package p; func f() { for ; _ /* ERROR "expected boolean or range expression" */ = range x ; {} };`, + `package p; func f() { switch t = /* ERROR "expected ':=', found '='" */ t.(type) {} };`, + `package p; func f() { switch t /* ERROR "expected switch expression" */ , t = t.(type) {} };`, + `package p; func f() { switch t /* ERROR "expected switch expression" */ = t.(type), t {} };`, + `package p; var a = [ /* ERROR "expected expression" */ 1]int;`, + `package p; var a = [ /* ERROR "expected expression" */ ...]int;`, + `package p; var a = struct /* ERROR "expected expression" */ {}`, + `package p; var a = func /* ERROR "expected expression" */ ();`, + `package p; var a = interface /* ERROR "expected expression" */ {}`, + `package p; var a = [ /* ERROR "expected expression" */ ]int`, + `package p; var a = map /* ERROR "expected expression" */ [int]int`, + `package p; var a = chan /* ERROR "expected expression" */ int;`, + `package p; var a = []int{[ /* ERROR "expected expression" */ ]int};`, + `package p; var a = ( /* ERROR "expected expression" */ []int);`, + `package p; var a = a[[ /* ERROR "expected expression" */ ]int:[]int];`, + `package p; var a = <- /* ERROR "expected expression" */ chan int;`, + `package p; func f() { select { case _ <- chan /* ERROR "expected expression" */ int: } };`, + `package p; func f() { _ = (<-<- /* ERROR "expected 'chan'" */ chan int)(nil) };`, + `package p; func f() { _ = (<-chan<-chan<-chan<-chan<-chan<- /* ERROR "expected channel type" */ int)(nil) };`, + `package p; func f() { var t []int; t /* ERROR "expected identifier on left side of :=" */ [0] := 0 };`, + `package p; func f() { if x := g(); x /* ERROR "expected boolean expression" */ = 0 {}};`, + `package p; func f() { _ = x = /* ERROR "expected '=='" */ 0 {}};`, + `package p; func f() { _ = 1 == func()int { var x bool; x = x = /* ERROR "expected '=='" */ true; return x }() };`, + `package p; func f() { var s []int; _ = s[] /* ERROR "expected operand" */ };`, + `package p; func f() { var s []int; _ = s[i:j: /* ERROR "3rd index required" */ ] };`, + `package p; func f() { var s []int; _ = s[i: /* ERROR "2nd index required" */ :k] };`, + `package p; func f() { var s []int; _ = s[i: /* ERROR "2nd index required" */ :] };`, + `package p; func f() { var s []int; _ = s[: /* ERROR "2nd index required" */ :] };`, + `package p; func f() { var s []int; _ = s[: /* ERROR "2nd index required" */ ::] };`, + `package p; func f() { var s []int; _ = s[i:j:k: /* ERROR "expected ']'" */ l] };`, + `package p; func f() { for x /* ERROR "boolean or range expression" */ = []string {} }`, + `package p; func f() { for x /* ERROR "boolean or range expression" */ := []string {} }`, + `package p; func f() { for i /* ERROR "boolean or range expression" */ , x = []string {} }`, + `package p; func f() { for i /* ERROR "boolean or range expression" */ , x := []string {} }`, + `package p; func f() { go f /* ERROR HERE "function must be invoked" */ }`, + `package p; func f() { defer func() {} /* ERROR HERE "function must be invoked" */ }`, + `package p; func f() { go func() { func() { f(x func /* ERROR "missing ','" */ (){}) } } }`, + `package p; func f(x func(), u v func /* ERROR "missing ','" */ ()){}`, + + // issue 8656 + `package p; func f() (a b string /* ERROR "missing ','" */ , ok bool)`, + + // issue 9639 + `package p; var x /* ERROR "missing variable type or initialization" */ , y, z;`, + `package p; const x /* ERROR "missing constant value" */ ;`, + `package p; const x /* ERROR "missing constant value" */ int;`, + `package p; const (x = 0; y; z /* ERROR "missing constant value" */ int);`, + + // issue 12437 + `package p; var _ = struct { x int, /* ERROR "expected ';', found ','" */ }{};`, + `package p; var _ = struct { x int, /* ERROR "expected ';', found ','" */ y float }{};`, + + // issue 11611 + `package p; type _ struct { int, } /* ERROR "expected type, found '}'" */ ;`, + `package p; type _ struct { int, float } /* ERROR "expected type, found '}'" */ ;`, + `package p; type _ struct { ( /* ERROR "expected anonymous field" */ int) };`, + `package p; func _()(x, y, z ... /* ERROR "expected '\)', found '...'" */ int){}`, + `package p; func _()(... /* ERROR "expected type, found '...'" */ int){}`, + + // issue 13475 + `package p; func f() { if true {} else ; /* ERROR "expected if statement or block" */ }`, + `package p; func f() { if true {} else defer /* ERROR "expected if statement or block" */ f() }`, +} + +func TestInvalid(t *testing.T) { + for _, src := range invalids { + checkErrors(t, src, src) + } +} diff --git a/src/go/parser/testdata/commas.src b/src/go/parser/testdata/commas.src new file mode 100644 index 0000000..e0603cf --- /dev/null +++ b/src/go/parser/testdata/commas.src @@ -0,0 +1,19 @@ +// Copyright 2012 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. + +// Test case for error messages/parser synchronization +// after missing commas. + +package p + +var _ = []int{ + 0/* ERROR HERE "missing ','" */ +} + +var _ = []int{ + 0, + 1, + 2, + 3/* ERROR HERE "missing ','" */ +} diff --git a/src/go/parser/testdata/issue11377.src b/src/go/parser/testdata/issue11377.src new file mode 100644 index 0000000..1c43800 --- /dev/null +++ b/src/go/parser/testdata/issue11377.src @@ -0,0 +1,27 @@ +// Copyright 2018 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. + +// Test case for issue 11377: Better synchronization of +// parser after certain syntax errors. + +package p + +func bad1() { + if f()) /* ERROR "expected ';', found '\)'" */ { + return + } +} + +// There shouldn't be any errors down below. + +func F1() {} +func F2() {} +func F3() {} +func F4() {} +func F5() {} +func F6() {} +func F7() {} +func F8() {} +func F9() {} +func F10() {} diff --git a/src/go/parser/testdata/issue23434.src b/src/go/parser/testdata/issue23434.src new file mode 100644 index 0000000..24a0832 --- /dev/null +++ b/src/go/parser/testdata/issue23434.src @@ -0,0 +1,25 @@ +// Copyright 2018 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. + +// Test case for issue 23434: Better synchronization of +// parser after missing type. There should be exactly +// one error each time, with now follow errors. + +package p + +func g() { + m := make(map[string]! /* ERROR "expected type, found '!'" */ ) + for { + x := 1 + print(x) + } +} + +func f() { + m := make(map[string]) /* ERROR "expected type, found '\)'" */ + for { + x := 1 + print(x) + } +} diff --git a/src/go/parser/testdata/issue3106.src b/src/go/parser/testdata/issue3106.src new file mode 100644 index 0000000..2db10be --- /dev/null +++ b/src/go/parser/testdata/issue3106.src @@ -0,0 +1,46 @@ +// Copyright 2012 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. + +// Test case for issue 3106: Better synchronization of +// parser after certain syntax errors. + +package main + +func f() { + var m Mutex + c := MakeCond(&m) + percent := 0 + const step = 10 + for i := 0; i < 5; i++ { + go func() { + for { + // Emulates some useful work. + time.Sleep(1e8) + m.Lock() + defer + if /* ERROR "expected ';', found 'if'" */ percent == 100 { + m.Unlock() + break + } + percent++ + if percent % step == 0 { + //c.Signal() + } + m.Unlock() + } + }() + } + for { + m.Lock() + if percent == 0 || percent % step != 0 { + c.Wait() + } + fmt.Print(",") + if percent == 100 { + m.Unlock() + break + } + m.Unlock() + } +} diff --git a/src/go/parser/testdata/issue34946.src b/src/go/parser/testdata/issue34946.src new file mode 100644 index 0000000..6bb15e1 --- /dev/null +++ b/src/go/parser/testdata/issue34946.src @@ -0,0 +1,22 @@ +// Copyright 2019 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. + +// Test case for issue 34946: Better synchronization of +// parser for function declarations that start their +// body's opening { on a new line. + +package p + +// accept Allman/BSD-style declaration but complain +// (implicit semicolon between signature and body) +func _() int +{ /* ERROR "unexpected semicolon or newline before {" */ + { return 0 } +} + +func _() {} + +func _(); { /* ERROR "unexpected semicolon or newline before {" */ } + +func _() {} diff --git a/src/go/parser/testdata/issue42951/not_a_file.go/invalid.go b/src/go/parser/testdata/issue42951/not_a_file.go/invalid.go new file mode 100644 index 0000000..bb698be --- /dev/null +++ b/src/go/parser/testdata/issue42951/not_a_file.go/invalid.go @@ -0,0 +1 @@ +This file should not be parsed by ParseDir. diff --git a/src/go/printer/example_test.go b/src/go/printer/example_test.go new file mode 100644 index 0000000..3081693 --- /dev/null +++ b/src/go/printer/example_test.go @@ -0,0 +1,67 @@ +// Copyright 2012 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 printer_test + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "strings" + "testing" +) + +// Dummy test function so that godoc does not use the entire file as example. +func Test(*testing.T) {} + +func parseFunc(filename, functionname string) (fun *ast.FuncDecl, fset *token.FileSet) { + fset = token.NewFileSet() + if file, err := parser.ParseFile(fset, filename, nil, 0); err == nil { + for _, d := range file.Decls { + if f, ok := d.(*ast.FuncDecl); ok && f.Name.Name == functionname { + fun = f + return + } + } + } + panic("function not found") +} + +func ExampleFprint() { + // Parse source file and extract the AST without comments for + // this function, with position information referring to the + // file set fset. + funcAST, fset := parseFunc("example_test.go", "ExampleFprint") + + // Print the function body into buffer buf. + // The file set is provided to the printer so that it knows + // about the original source formatting and can add additional + // line breaks where they were present in the source. + var buf bytes.Buffer + printer.Fprint(&buf, fset, funcAST.Body) + + // Remove braces {} enclosing the function body, unindent, + // and trim leading and trailing white space. + s := buf.String() + s = s[1 : len(s)-1] + s = strings.TrimSpace(strings.ReplaceAll(s, "\n\t", "\n")) + + // Print the cleaned-up body text to stdout. + fmt.Println(s) + + // output: + // funcAST, fset := parseFunc("example_test.go", "ExampleFprint") + // + // var buf bytes.Buffer + // printer.Fprint(&buf, fset, funcAST.Body) + // + // s := buf.String() + // s = s[1 : len(s)-1] + // s = strings.TrimSpace(strings.ReplaceAll(s, "\n\t", "\n")) + // + // fmt.Println(s) +} diff --git a/src/go/printer/nodes.go b/src/go/printer/nodes.go new file mode 100644 index 0000000..95b9e91 --- /dev/null +++ b/src/go/printer/nodes.go @@ -0,0 +1,1844 @@ +// Copyright 2009 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. + +// This file implements printing of AST nodes; specifically +// expressions, statements, declarations, and files. It uses +// the print functionality implemented in printer.go. + +package printer + +import ( + "bytes" + "go/ast" + "go/token" + "math" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// Formatting issues: +// - better comment formatting for /*-style comments at the end of a line (e.g. a declaration) +// when the comment spans multiple lines; if such a comment is just two lines, formatting is +// not idempotent +// - formatting of expression lists +// - should use blank instead of tab to separate one-line function bodies from +// the function header unless there is a group of consecutive one-liners + +// ---------------------------------------------------------------------------- +// Common AST nodes. + +// Print as many newlines as necessary (but at least min newlines) to get to +// the current line. ws is printed before the first line break. If newSection +// is set, the first line break is printed as formfeed. Returns 0 if no line +// breaks were printed, returns 1 if there was exactly one newline printed, +// and returns a value > 1 if there was a formfeed or more than one newline +// printed. +// +// TODO(gri): linebreak may add too many lines if the next statement at "line" +// is preceded by comments because the computation of n assumes +// the current position before the comment and the target position +// after the comment. Thus, after interspersing such comments, the +// space taken up by them is not considered to reduce the number of +// linebreaks. At the moment there is no easy way to know about +// future (not yet interspersed) comments in this function. +// +func (p *printer) linebreak(line, min int, ws whiteSpace, newSection bool) (nbreaks int) { + n := nlimit(line - p.pos.Line) + if n < min { + n = min + } + if n > 0 { + p.print(ws) + if newSection { + p.print(formfeed) + n-- + nbreaks = 2 + } + nbreaks += n + for ; n > 0; n-- { + p.print(newline) + } + } + return +} + +// setComment sets g as the next comment if g != nil and if node comments +// are enabled - this mode is used when printing source code fragments such +// as exports only. It assumes that there is no pending comment in p.comments +// and at most one pending comment in the p.comment cache. +func (p *printer) setComment(g *ast.CommentGroup) { + if g == nil || !p.useNodeComments { + return + } + if p.comments == nil { + // initialize p.comments lazily + p.comments = make([]*ast.CommentGroup, 1) + } else if p.cindex < len(p.comments) { + // for some reason there are pending comments; this + // should never happen - handle gracefully and flush + // all comments up to g, ignore anything after that + p.flush(p.posFor(g.List[0].Pos()), token.ILLEGAL) + p.comments = p.comments[0:1] + // in debug mode, report error + p.internalError("setComment found pending comments") + } + p.comments[0] = g + p.cindex = 0 + // don't overwrite any pending comment in the p.comment cache + // (there may be a pending comment when a line comment is + // immediately followed by a lead comment with no other + // tokens between) + if p.commentOffset == infinity { + p.nextComment() // get comment ready for use + } +} + +type exprListMode uint + +const ( + commaTerm exprListMode = 1 << iota // list is optionally terminated by a comma + noIndent // no extra indentation in multi-line lists +) + +// If indent is set, a multi-line identifier list is indented after the +// first linebreak encountered. +func (p *printer) identList(list []*ast.Ident, indent bool) { + // convert into an expression list so we can re-use exprList formatting + xlist := make([]ast.Expr, len(list)) + for i, x := range list { + xlist[i] = x + } + var mode exprListMode + if !indent { + mode = noIndent + } + p.exprList(token.NoPos, xlist, 1, mode, token.NoPos, false) +} + +const filteredMsg = "contains filtered or unexported fields" + +// Print a list of expressions. If the list spans multiple +// source lines, the original line breaks are respected between +// expressions. +// +// TODO(gri) Consider rewriting this to be independent of []ast.Expr +// so that we can use the algorithm for any kind of list +// (e.g., pass list via a channel over which to range). +func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, next0 token.Pos, isIncomplete bool) { + if len(list) == 0 { + if isIncomplete { + prev := p.posFor(prev0) + next := p.posFor(next0) + if prev.IsValid() && prev.Line == next.Line { + p.print("/* " + filteredMsg + " */") + } else { + p.print(newline) + p.print(indent, "// "+filteredMsg, unindent, newline) + } + } + return + } + + prev := p.posFor(prev0) + next := p.posFor(next0) + line := p.lineFor(list[0].Pos()) + endLine := p.lineFor(list[len(list)-1].End()) + + if prev.IsValid() && prev.Line == line && line == endLine { + // all list entries on a single line + for i, x := range list { + if i > 0 { + // use position of expression following the comma as + // comma position for correct comment placement + p.print(x.Pos(), token.COMMA, blank) + } + p.expr0(x, depth) + } + if isIncomplete { + p.print(token.COMMA, blank, "/* "+filteredMsg+" */") + } + return + } + + // list entries span multiple lines; + // use source code positions to guide line breaks + + // Don't add extra indentation if noIndent is set; + // i.e., pretend that the first line is already indented. + ws := ignore + if mode&noIndent == 0 { + ws = indent + } + + // The first linebreak is always a formfeed since this section must not + // depend on any previous formatting. + prevBreak := -1 // index of last expression that was followed by a linebreak + if prev.IsValid() && prev.Line < line && p.linebreak(line, 0, ws, true) > 0 { + ws = ignore + prevBreak = 0 + } + + // initialize expression/key size: a zero value indicates expr/key doesn't fit on a single line + size := 0 + + // We use the ratio between the geometric mean of the previous key sizes and + // the current size to determine if there should be a break in the alignment. + // To compute the geometric mean we accumulate the ln(size) values (lnsum) + // and the number of sizes included (count). + lnsum := 0.0 + count := 0 + + // print all list elements + prevLine := prev.Line + for i, x := range list { + line = p.lineFor(x.Pos()) + + // Determine if the next linebreak, if any, needs to use formfeed: + // in general, use the entire node size to make the decision; for + // key:value expressions, use the key size. + // TODO(gri) for a better result, should probably incorporate both + // the key and the node size into the decision process + useFF := true + + // Determine element size: All bets are off if we don't have + // position information for the previous and next token (likely + // generated code - simply ignore the size in this case by setting + // it to 0). + prevSize := size + const infinity = 1e6 // larger than any source line + size = p.nodeSize(x, infinity) + pair, isPair := x.(*ast.KeyValueExpr) + if size <= infinity && prev.IsValid() && next.IsValid() { + // x fits on a single line + if isPair { + size = p.nodeSize(pair.Key, infinity) // size <= infinity + } + } else { + // size too large or we don't have good layout information + size = 0 + } + + // If the previous line and the current line had single- + // line-expressions and the key sizes are small or the + // ratio between the current key and the geometric mean + // if the previous key sizes does not exceed a threshold, + // align columns and do not use formfeed. + if prevSize > 0 && size > 0 { + const smallSize = 40 + if count == 0 || prevSize <= smallSize && size <= smallSize { + useFF = false + } else { + const r = 2.5 // threshold + geomean := math.Exp(lnsum / float64(count)) // count > 0 + ratio := float64(size) / geomean + useFF = r*ratio <= 1 || r <= ratio + } + } + + needsLinebreak := 0 < prevLine && prevLine < line + if i > 0 { + // Use position of expression following the comma as + // comma position for correct comment placement, but + // only if the expression is on the same line. + if !needsLinebreak { + p.print(x.Pos()) + } + p.print(token.COMMA) + needsBlank := true + if needsLinebreak { + // Lines are broken using newlines so comments remain aligned + // unless useFF is set or there are multiple expressions on + // the same line in which case formfeed is used. + nbreaks := p.linebreak(line, 0, ws, useFF || prevBreak+1 < i) + if nbreaks > 0 { + ws = ignore + prevBreak = i + needsBlank = false // we got a line break instead + } + // If there was a new section or more than one new line + // (which means that the tabwriter will implicitly break + // the section), reset the geomean variables since we are + // starting a new group of elements with the next element. + if nbreaks > 1 { + lnsum = 0 + count = 0 + } + } + if needsBlank { + p.print(blank) + } + } + + if len(list) > 1 && isPair && size > 0 && needsLinebreak { + // We have a key:value expression that fits onto one line + // and it's not on the same line as the prior expression: + // Use a column for the key such that consecutive entries + // can align if possible. + // (needsLinebreak is set if we started a new line before) + p.expr(pair.Key) + p.print(pair.Colon, token.COLON, vtab) + p.expr(pair.Value) + } else { + p.expr0(x, depth) + } + + if size > 0 { + lnsum += math.Log(float64(size)) + count++ + } + + prevLine = line + } + + if mode&commaTerm != 0 && next.IsValid() && p.pos.Line < next.Line { + // Print a terminating comma if the next token is on a new line. + p.print(token.COMMA) + if isIncomplete { + p.print(newline) + p.print("// " + filteredMsg) + } + if ws == ignore && mode&noIndent == 0 { + // unindent if we indented + p.print(unindent) + } + p.print(formfeed) // terminating comma needs a line break to look good + return + } + + if isIncomplete { + p.print(token.COMMA, newline) + p.print("// "+filteredMsg, newline) + } + + if ws == ignore && mode&noIndent == 0 { + // unindent if we indented + p.print(unindent) + } +} + +func (p *printer) parameters(fields *ast.FieldList) { + p.print(fields.Opening, token.LPAREN) + if len(fields.List) > 0 { + prevLine := p.lineFor(fields.Opening) + ws := indent + for i, par := range fields.List { + // determine par begin and end line (may be different + // if there are multiple parameter names for this par + // or the type is on a separate line) + var parLineBeg int + if len(par.Names) > 0 { + parLineBeg = p.lineFor(par.Names[0].Pos()) + } else { + parLineBeg = p.lineFor(par.Type.Pos()) + } + var parLineEnd = p.lineFor(par.Type.End()) + // separating "," if needed + needsLinebreak := 0 < prevLine && prevLine < parLineBeg + if i > 0 { + // use position of parameter following the comma as + // comma position for correct comma placement, but + // only if the next parameter is on the same line + if !needsLinebreak { + p.print(par.Pos()) + } + p.print(token.COMMA) + } + // separator if needed (linebreak or blank) + if needsLinebreak && p.linebreak(parLineBeg, 0, ws, true) > 0 { + // break line if the opening "(" or previous parameter ended on a different line + ws = ignore + } else if i > 0 { + p.print(blank) + } + // parameter names + if len(par.Names) > 0 { + // Very subtle: If we indented before (ws == ignore), identList + // won't indent again. If we didn't (ws == indent), identList will + // indent if the identList spans multiple lines, and it will outdent + // again at the end (and still ws == indent). Thus, a subsequent indent + // by a linebreak call after a type, or in the next multi-line identList + // will do the right thing. + p.identList(par.Names, ws == indent) + p.print(blank) + } + // parameter type + p.expr(stripParensAlways(par.Type)) + prevLine = parLineEnd + } + // if the closing ")" is on a separate line from the last parameter, + // print an additional "," and line break + if closing := p.lineFor(fields.Closing); 0 < prevLine && prevLine < closing { + p.print(token.COMMA) + p.linebreak(closing, 0, ignore, true) + } + // unindent if we indented + if ws == ignore { + p.print(unindent) + } + } + p.print(fields.Closing, token.RPAREN) +} + +func (p *printer) signature(params, result *ast.FieldList) { + if params != nil { + p.parameters(params) + } else { + p.print(token.LPAREN, token.RPAREN) + } + n := result.NumFields() + if n > 0 { + // result != nil + p.print(blank) + if n == 1 && result.List[0].Names == nil { + // single anonymous result; no ()'s + p.expr(stripParensAlways(result.List[0].Type)) + return + } + p.parameters(result) + } +} + +func identListSize(list []*ast.Ident, maxSize int) (size int) { + for i, x := range list { + if i > 0 { + size += len(", ") + } + size += utf8.RuneCountInString(x.Name) + if size >= maxSize { + break + } + } + return +} + +func (p *printer) isOneLineFieldList(list []*ast.Field) bool { + if len(list) != 1 { + return false // allow only one field + } + f := list[0] + if f.Tag != nil || f.Comment != nil { + return false // don't allow tags or comments + } + // only name(s) and type + const maxSize = 30 // adjust as appropriate, this is an approximate value + namesSize := identListSize(f.Names, maxSize) + if namesSize > 0 { + namesSize = 1 // blank between names and types + } + typeSize := p.nodeSize(f.Type, maxSize) + return namesSize+typeSize <= maxSize +} + +func (p *printer) setLineComment(text string) { + p.setComment(&ast.CommentGroup{List: []*ast.Comment{{Slash: token.NoPos, Text: text}}}) +} + +func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) { + lbrace := fields.Opening + list := fields.List + rbrace := fields.Closing + hasComments := isIncomplete || p.commentBefore(p.posFor(rbrace)) + srcIsOneLine := lbrace.IsValid() && rbrace.IsValid() && p.lineFor(lbrace) == p.lineFor(rbrace) + + if !hasComments && srcIsOneLine { + // possibly a one-line struct/interface + if len(list) == 0 { + // no blank between keyword and {} in this case + p.print(lbrace, token.LBRACE, rbrace, token.RBRACE) + return + } else if p.isOneLineFieldList(list) { + // small enough - print on one line + // (don't use identList and ignore source line breaks) + p.print(lbrace, token.LBRACE, blank) + f := list[0] + if isStruct { + for i, x := range f.Names { + if i > 0 { + // no comments so no need for comma position + p.print(token.COMMA, blank) + } + p.expr(x) + } + if len(f.Names) > 0 { + p.print(blank) + } + p.expr(f.Type) + } else { // interface + if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp { + // method + p.expr(f.Names[0]) + p.signature(ftyp.Params, ftyp.Results) + } else { + // embedded interface + p.expr(f.Type) + } + } + p.print(blank, rbrace, token.RBRACE) + return + } + } + // hasComments || !srcIsOneLine + + p.print(blank, lbrace, token.LBRACE, indent) + if hasComments || len(list) > 0 { + p.print(formfeed) + } + + if isStruct { + + sep := vtab + if len(list) == 1 { + sep = blank + } + var line int + for i, f := range list { + if i > 0 { + p.linebreak(p.lineFor(f.Pos()), 1, ignore, p.linesFrom(line) > 0) + } + extraTabs := 0 + p.setComment(f.Doc) + p.recordLine(&line) + if len(f.Names) > 0 { + // named fields + p.identList(f.Names, false) + p.print(sep) + p.expr(f.Type) + extraTabs = 1 + } else { + // anonymous field + p.expr(f.Type) + extraTabs = 2 + } + if f.Tag != nil { + if len(f.Names) > 0 && sep == vtab { + p.print(sep) + } + p.print(sep) + p.expr(f.Tag) + extraTabs = 0 + } + if f.Comment != nil { + for ; extraTabs > 0; extraTabs-- { + p.print(sep) + } + p.setComment(f.Comment) + } + } + if isIncomplete { + if len(list) > 0 { + p.print(formfeed) + } + p.flush(p.posFor(rbrace), token.RBRACE) // make sure we don't lose the last line comment + p.setLineComment("// " + filteredMsg) + } + + } else { // interface + + var line int + for i, f := range list { + if i > 0 { + p.linebreak(p.lineFor(f.Pos()), 1, ignore, p.linesFrom(line) > 0) + } + p.setComment(f.Doc) + p.recordLine(&line) + if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp { + // method + p.expr(f.Names[0]) + p.signature(ftyp.Params, ftyp.Results) + } else { + // embedded interface + p.expr(f.Type) + } + p.setComment(f.Comment) + } + if isIncomplete { + if len(list) > 0 { + p.print(formfeed) + } + p.flush(p.posFor(rbrace), token.RBRACE) // make sure we don't lose the last line comment + p.setLineComment("// contains filtered or unexported methods") + } + + } + p.print(unindent, formfeed, rbrace, token.RBRACE) +} + +// ---------------------------------------------------------------------------- +// Expressions + +func walkBinary(e *ast.BinaryExpr) (has4, has5 bool, maxProblem int) { + switch e.Op.Precedence() { + case 4: + has4 = true + case 5: + has5 = true + } + + switch l := e.X.(type) { + case *ast.BinaryExpr: + if l.Op.Precedence() < e.Op.Precedence() { + // parens will be inserted. + // pretend this is an *ast.ParenExpr and do nothing. + break + } + h4, h5, mp := walkBinary(l) + has4 = has4 || h4 + has5 = has5 || h5 + if maxProblem < mp { + maxProblem = mp + } + } + + switch r := e.Y.(type) { + case *ast.BinaryExpr: + if r.Op.Precedence() <= e.Op.Precedence() { + // parens will be inserted. + // pretend this is an *ast.ParenExpr and do nothing. + break + } + h4, h5, mp := walkBinary(r) + has4 = has4 || h4 + has5 = has5 || h5 + if maxProblem < mp { + maxProblem = mp + } + + case *ast.StarExpr: + if e.Op == token.QUO { // `*/` + maxProblem = 5 + } + + case *ast.UnaryExpr: + switch e.Op.String() + r.Op.String() { + case "/*", "&&", "&^": + maxProblem = 5 + case "++", "--": + if maxProblem < 4 { + maxProblem = 4 + } + } + } + return +} + +func cutoff(e *ast.BinaryExpr, depth int) int { + has4, has5, maxProblem := walkBinary(e) + if maxProblem > 0 { + return maxProblem + 1 + } + if has4 && has5 { + if depth == 1 { + return 5 + } + return 4 + } + if depth == 1 { + return 6 + } + return 4 +} + +func diffPrec(expr ast.Expr, prec int) int { + x, ok := expr.(*ast.BinaryExpr) + if !ok || prec != x.Op.Precedence() { + return 1 + } + return 0 +} + +func reduceDepth(depth int) int { + depth-- + if depth < 1 { + depth = 1 + } + return depth +} + +// Format the binary expression: decide the cutoff and then format. +// Let's call depth == 1 Normal mode, and depth > 1 Compact mode. +// (Algorithm suggestion by Russ Cox.) +// +// The precedences are: +// 5 * / % << >> & &^ +// 4 + - | ^ +// 3 == != < <= > >= +// 2 && +// 1 || +// +// The only decision is whether there will be spaces around levels 4 and 5. +// There are never spaces at level 6 (unary), and always spaces at levels 3 and below. +// +// To choose the cutoff, look at the whole expression but excluding primary +// expressions (function calls, parenthesized exprs), and apply these rules: +// +// 1) If there is a binary operator with a right side unary operand +// that would clash without a space, the cutoff must be (in order): +// +// /* 6 +// && 6 +// &^ 6 +// ++ 5 +// -- 5 +// +// (Comparison operators always have spaces around them.) +// +// 2) If there is a mix of level 5 and level 4 operators, then the cutoff +// is 5 (use spaces to distinguish precedence) in Normal mode +// and 4 (never use spaces) in Compact mode. +// +// 3) If there are no level 4 operators or no level 5 operators, then the +// cutoff is 6 (always use spaces) in Normal mode +// and 4 (never use spaces) in Compact mode. +// +func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int) { + prec := x.Op.Precedence() + if prec < prec1 { + // parenthesis needed + // Note: The parser inserts an ast.ParenExpr node; thus this case + // can only occur if the AST is created in a different way. + p.print(token.LPAREN) + p.expr0(x, reduceDepth(depth)) // parentheses undo one level of depth + p.print(token.RPAREN) + return + } + + printBlank := prec < cutoff + + ws := indent + p.expr1(x.X, prec, depth+diffPrec(x.X, prec)) + if printBlank { + p.print(blank) + } + xline := p.pos.Line // before the operator (it may be on the next line!) + yline := p.lineFor(x.Y.Pos()) + p.print(x.OpPos, x.Op) + if xline != yline && xline > 0 && yline > 0 { + // at least one line break, but respect an extra empty line + // in the source + if p.linebreak(yline, 1, ws, true) > 0 { + ws = ignore + printBlank = false // no blank after line break + } + } + if printBlank { + p.print(blank) + } + p.expr1(x.Y, prec+1, depth+1) + if ws == ignore { + p.print(unindent) + } +} + +func isBinary(expr ast.Expr) bool { + _, ok := expr.(*ast.BinaryExpr) + return ok +} + +func (p *printer) expr1(expr ast.Expr, prec1, depth int) { + p.print(expr.Pos()) + + switch x := expr.(type) { + case *ast.BadExpr: + p.print("BadExpr") + + case *ast.Ident: + p.print(x) + + case *ast.BinaryExpr: + if depth < 1 { + p.internalError("depth < 1:", depth) + depth = 1 + } + p.binaryExpr(x, prec1, cutoff(x, depth), depth) + + case *ast.KeyValueExpr: + p.expr(x.Key) + p.print(x.Colon, token.COLON, blank) + p.expr(x.Value) + + case *ast.StarExpr: + const prec = token.UnaryPrec + if prec < prec1 { + // parenthesis needed + p.print(token.LPAREN) + p.print(token.MUL) + p.expr(x.X) + p.print(token.RPAREN) + } else { + // no parenthesis needed + p.print(token.MUL) + p.expr(x.X) + } + + case *ast.UnaryExpr: + const prec = token.UnaryPrec + if prec < prec1 { + // parenthesis needed + p.print(token.LPAREN) + p.expr(x) + p.print(token.RPAREN) + } else { + // no parenthesis needed + p.print(x.Op) + if x.Op == token.RANGE { + // TODO(gri) Remove this code if it cannot be reached. + p.print(blank) + } + p.expr1(x.X, prec, depth) + } + + case *ast.BasicLit: + if p.Config.Mode&normalizeNumbers != 0 { + x = normalizedNumber(x) + } + p.print(x) + + case *ast.FuncLit: + p.print(x.Type.Pos(), token.FUNC) + // See the comment in funcDecl about how the header size is computed. + startCol := p.out.Column - len("func") + p.signature(x.Type.Params, x.Type.Results) + p.funcBody(p.distanceFrom(x.Type.Pos(), startCol), blank, x.Body) + + case *ast.ParenExpr: + if _, hasParens := x.X.(*ast.ParenExpr); hasParens { + // don't print parentheses around an already parenthesized expression + // TODO(gri) consider making this more general and incorporate precedence levels + p.expr0(x.X, depth) + } else { + p.print(token.LPAREN) + p.expr0(x.X, reduceDepth(depth)) // parentheses undo one level of depth + p.print(x.Rparen, token.RPAREN) + } + + case *ast.SelectorExpr: + p.selectorExpr(x, depth, false) + + case *ast.TypeAssertExpr: + p.expr1(x.X, token.HighestPrec, depth) + p.print(token.PERIOD, x.Lparen, token.LPAREN) + if x.Type != nil { + p.expr(x.Type) + } else { + p.print(token.TYPE) + } + p.print(x.Rparen, token.RPAREN) + + case *ast.IndexExpr: + // TODO(gri): should treat[] like parentheses and undo one level of depth + p.expr1(x.X, token.HighestPrec, 1) + p.print(x.Lbrack, token.LBRACK) + p.expr0(x.Index, depth+1) + p.print(x.Rbrack, token.RBRACK) + + case *ast.SliceExpr: + // TODO(gri): should treat[] like parentheses and undo one level of depth + p.expr1(x.X, token.HighestPrec, 1) + p.print(x.Lbrack, token.LBRACK) + indices := []ast.Expr{x.Low, x.High} + if x.Max != nil { + indices = append(indices, x.Max) + } + // determine if we need extra blanks around ':' + var needsBlanks bool + if depth <= 1 { + var indexCount int + var hasBinaries bool + for _, x := range indices { + if x != nil { + indexCount++ + if isBinary(x) { + hasBinaries = true + } + } + } + if indexCount > 1 && hasBinaries { + needsBlanks = true + } + } + for i, x := range indices { + if i > 0 { + if indices[i-1] != nil && needsBlanks { + p.print(blank) + } + p.print(token.COLON) + if x != nil && needsBlanks { + p.print(blank) + } + } + if x != nil { + p.expr0(x, depth+1) + } + } + p.print(x.Rbrack, token.RBRACK) + + case *ast.CallExpr: + if len(x.Args) > 1 { + depth++ + } + var wasIndented bool + if _, ok := x.Fun.(*ast.FuncType); ok { + // conversions to literal function types require parentheses around the type + p.print(token.LPAREN) + wasIndented = p.possibleSelectorExpr(x.Fun, token.HighestPrec, depth) + p.print(token.RPAREN) + } else { + wasIndented = p.possibleSelectorExpr(x.Fun, token.HighestPrec, depth) + } + p.print(x.Lparen, token.LPAREN) + if x.Ellipsis.IsValid() { + p.exprList(x.Lparen, x.Args, depth, 0, x.Ellipsis, false) + p.print(x.Ellipsis, token.ELLIPSIS) + if x.Rparen.IsValid() && p.lineFor(x.Ellipsis) < p.lineFor(x.Rparen) { + p.print(token.COMMA, formfeed) + } + } else { + p.exprList(x.Lparen, x.Args, depth, commaTerm, x.Rparen, false) + } + p.print(x.Rparen, token.RPAREN) + if wasIndented { + p.print(unindent) + } + + case *ast.CompositeLit: + // composite literal elements that are composite literals themselves may have the type omitted + if x.Type != nil { + p.expr1(x.Type, token.HighestPrec, depth) + } + p.level++ + p.print(x.Lbrace, token.LBRACE) + p.exprList(x.Lbrace, x.Elts, 1, commaTerm, x.Rbrace, x.Incomplete) + // do not insert extra line break following a /*-style comment + // before the closing '}' as it might break the code if there + // is no trailing ',' + mode := noExtraLinebreak + // do not insert extra blank following a /*-style comment + // before the closing '}' unless the literal is empty + if len(x.Elts) > 0 { + mode |= noExtraBlank + } + // need the initial indent to print lone comments with + // the proper level of indentation + p.print(indent, unindent, mode, x.Rbrace, token.RBRACE, mode) + p.level-- + + case *ast.Ellipsis: + p.print(token.ELLIPSIS) + if x.Elt != nil { + p.expr(x.Elt) + } + + case *ast.ArrayType: + p.print(token.LBRACK) + if x.Len != nil { + p.expr(x.Len) + } + p.print(token.RBRACK) + p.expr(x.Elt) + + case *ast.StructType: + p.print(token.STRUCT) + p.fieldList(x.Fields, true, x.Incomplete) + + case *ast.FuncType: + p.print(token.FUNC) + p.signature(x.Params, x.Results) + + case *ast.InterfaceType: + p.print(token.INTERFACE) + p.fieldList(x.Methods, false, x.Incomplete) + + case *ast.MapType: + p.print(token.MAP, token.LBRACK) + p.expr(x.Key) + p.print(token.RBRACK) + p.expr(x.Value) + + case *ast.ChanType: + switch x.Dir { + case ast.SEND | ast.RECV: + p.print(token.CHAN) + case ast.RECV: + p.print(token.ARROW, token.CHAN) // x.Arrow and x.Pos() are the same + case ast.SEND: + p.print(token.CHAN, x.Arrow, token.ARROW) + } + p.print(blank) + p.expr(x.Value) + + default: + panic("unreachable") + } +} + +// normalizedNumber rewrites base prefixes and exponents +// of numbers to use lower-case letters (0X123 to 0x123 and 1.2E3 to 1.2e3), +// and removes leading 0's from integer imaginary literals (0765i to 765i). +// It leaves hexadecimal digits alone. +// +// normalizedNumber doesn't modify the ast.BasicLit value lit points to. +// If lit is not a number or a number in canonical format already, +// lit is returned as is. Otherwise a new ast.BasicLit is created. +func normalizedNumber(lit *ast.BasicLit) *ast.BasicLit { + if lit.Kind != token.INT && lit.Kind != token.FLOAT && lit.Kind != token.IMAG { + return lit // not a number - nothing to do + } + if len(lit.Value) < 2 { + return lit // only one digit (common case) - nothing to do + } + // len(lit.Value) >= 2 + + // We ignore lit.Kind because for lit.Kind == token.IMAG the literal may be an integer + // or floating-point value, decimal or not. Instead, just consider the literal pattern. + x := lit.Value + switch x[:2] { + default: + // 0-prefix octal, decimal int, or float (possibly with 'i' suffix) + if i := strings.LastIndexByte(x, 'E'); i >= 0 { + x = x[:i] + "e" + x[i+1:] + break + } + // remove leading 0's from integer (but not floating-point) imaginary literals + if x[len(x)-1] == 'i' && strings.IndexByte(x, '.') < 0 && strings.IndexByte(x, 'e') < 0 { + x = strings.TrimLeft(x, "0_") + if x == "i" { + x = "0i" + } + } + case "0X": + x = "0x" + x[2:] + // possibly a hexadecimal float + if i := strings.LastIndexByte(x, 'P'); i >= 0 { + x = x[:i] + "p" + x[i+1:] + } + case "0x": + // possibly a hexadecimal float + i := strings.LastIndexByte(x, 'P') + if i == -1 { + return lit // nothing to do + } + x = x[:i] + "p" + x[i+1:] + case "0O": + x = "0o" + x[2:] + case "0o": + return lit // nothing to do + case "0B": + x = "0b" + x[2:] + case "0b": + return lit // nothing to do + } + + return &ast.BasicLit{ValuePos: lit.ValuePos, Kind: lit.Kind, Value: x} +} + +func (p *printer) possibleSelectorExpr(expr ast.Expr, prec1, depth int) bool { + if x, ok := expr.(*ast.SelectorExpr); ok { + return p.selectorExpr(x, depth, true) + } + p.expr1(expr, prec1, depth) + return false +} + +// selectorExpr handles an *ast.SelectorExpr node and reports whether x spans +// multiple lines. +func (p *printer) selectorExpr(x *ast.SelectorExpr, depth int, isMethod bool) bool { + p.expr1(x.X, token.HighestPrec, depth) + p.print(token.PERIOD) + if line := p.lineFor(x.Sel.Pos()); p.pos.IsValid() && p.pos.Line < line { + p.print(indent, newline, x.Sel.Pos(), x.Sel) + if !isMethod { + p.print(unindent) + } + return true + } + p.print(x.Sel.Pos(), x.Sel) + return false +} + +func (p *printer) expr0(x ast.Expr, depth int) { + p.expr1(x, token.LowestPrec, depth) +} + +func (p *printer) expr(x ast.Expr) { + const depth = 1 + p.expr1(x, token.LowestPrec, depth) +} + +// ---------------------------------------------------------------------------- +// Statements + +// Print the statement list indented, but without a newline after the last statement. +// Extra line breaks between statements in the source are respected but at most one +// empty line is printed between statements. +func (p *printer) stmtList(list []ast.Stmt, nindent int, nextIsRBrace bool) { + if nindent > 0 { + p.print(indent) + } + var line int + i := 0 + for _, s := range list { + // ignore empty statements (was issue 3466) + if _, isEmpty := s.(*ast.EmptyStmt); !isEmpty { + // nindent == 0 only for lists of switch/select case clauses; + // in those cases each clause is a new section + if len(p.output) > 0 { + // only print line break if we are not at the beginning of the output + // (i.e., we are not printing only a partial program) + p.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || nindent == 0 || p.linesFrom(line) > 0) + } + p.recordLine(&line) + p.stmt(s, nextIsRBrace && i == len(list)-1) + // labeled statements put labels on a separate line, but here + // we only care about the start line of the actual statement + // without label - correct line for each label + for t := s; ; { + lt, _ := t.(*ast.LabeledStmt) + if lt == nil { + break + } + line++ + t = lt.Stmt + } + i++ + } + } + if nindent > 0 { + p.print(unindent) + } +} + +// block prints an *ast.BlockStmt; it always spans at least two lines. +func (p *printer) block(b *ast.BlockStmt, nindent int) { + p.print(b.Lbrace, token.LBRACE) + p.stmtList(b.List, nindent, true) + p.linebreak(p.lineFor(b.Rbrace), 1, ignore, true) + p.print(b.Rbrace, token.RBRACE) +} + +func isTypeName(x ast.Expr) bool { + switch t := x.(type) { + case *ast.Ident: + return true + case *ast.SelectorExpr: + return isTypeName(t.X) + } + return false +} + +func stripParens(x ast.Expr) ast.Expr { + if px, strip := x.(*ast.ParenExpr); strip { + // parentheses must not be stripped if there are any + // unparenthesized composite literals starting with + // a type name + ast.Inspect(px.X, func(node ast.Node) bool { + switch x := node.(type) { + case *ast.ParenExpr: + // parentheses protect enclosed composite literals + return false + case *ast.CompositeLit: + if isTypeName(x.Type) { + strip = false // do not strip parentheses + } + return false + } + // in all other cases, keep inspecting + return true + }) + if strip { + return stripParens(px.X) + } + } + return x +} + +func stripParensAlways(x ast.Expr) ast.Expr { + if x, ok := x.(*ast.ParenExpr); ok { + return stripParensAlways(x.X) + } + return x +} + +func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, post ast.Stmt) { + p.print(blank) + needsBlank := false + if init == nil && post == nil { + // no semicolons required + if expr != nil { + p.expr(stripParens(expr)) + needsBlank = true + } + } else { + // all semicolons required + // (they are not separators, print them explicitly) + if init != nil { + p.stmt(init, false) + } + p.print(token.SEMICOLON, blank) + if expr != nil { + p.expr(stripParens(expr)) + needsBlank = true + } + if isForStmt { + p.print(token.SEMICOLON, blank) + needsBlank = false + if post != nil { + p.stmt(post, false) + needsBlank = true + } + } + } + if needsBlank { + p.print(blank) + } +} + +// indentList reports whether an expression list would look better if it +// were indented wholesale (starting with the very first element, rather +// than starting at the first line break). +// +func (p *printer) indentList(list []ast.Expr) bool { + // Heuristic: indentList reports whether there are more than one multi- + // line element in the list, or if there is any element that is not + // starting on the same line as the previous one ends. + if len(list) >= 2 { + var b = p.lineFor(list[0].Pos()) + var e = p.lineFor(list[len(list)-1].End()) + if 0 < b && b < e { + // list spans multiple lines + n := 0 // multi-line element count + line := b + for _, x := range list { + xb := p.lineFor(x.Pos()) + xe := p.lineFor(x.End()) + if line < xb { + // x is not starting on the same + // line as the previous one ended + return true + } + if xb < xe { + // x is a multi-line element + n++ + } + line = xe + } + return n > 1 + } + } + return false +} + +func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) { + p.print(stmt.Pos()) + + switch s := stmt.(type) { + case *ast.BadStmt: + p.print("BadStmt") + + case *ast.DeclStmt: + p.decl(s.Decl) + + case *ast.EmptyStmt: + // nothing to do + + case *ast.LabeledStmt: + // a "correcting" unindent immediately following a line break + // is applied before the line break if there is no comment + // between (see writeWhitespace) + p.print(unindent) + p.expr(s.Label) + p.print(s.Colon, token.COLON, indent) + if e, isEmpty := s.Stmt.(*ast.EmptyStmt); isEmpty { + if !nextIsRBrace { + p.print(newline, e.Pos(), token.SEMICOLON) + break + } + } else { + p.linebreak(p.lineFor(s.Stmt.Pos()), 1, ignore, true) + } + p.stmt(s.Stmt, nextIsRBrace) + + case *ast.ExprStmt: + const depth = 1 + p.expr0(s.X, depth) + + case *ast.SendStmt: + const depth = 1 + p.expr0(s.Chan, depth) + p.print(blank, s.Arrow, token.ARROW, blank) + p.expr0(s.Value, depth) + + case *ast.IncDecStmt: + const depth = 1 + p.expr0(s.X, depth+1) + p.print(s.TokPos, s.Tok) + + case *ast.AssignStmt: + var depth = 1 + if len(s.Lhs) > 1 && len(s.Rhs) > 1 { + depth++ + } + p.exprList(s.Pos(), s.Lhs, depth, 0, s.TokPos, false) + p.print(blank, s.TokPos, s.Tok, blank) + p.exprList(s.TokPos, s.Rhs, depth, 0, token.NoPos, false) + + case *ast.GoStmt: + p.print(token.GO, blank) + p.expr(s.Call) + + case *ast.DeferStmt: + p.print(token.DEFER, blank) + p.expr(s.Call) + + case *ast.ReturnStmt: + p.print(token.RETURN) + if s.Results != nil { + p.print(blank) + // Use indentList heuristic to make corner cases look + // better (issue 1207). A more systematic approach would + // always indent, but this would cause significant + // reformatting of the code base and not necessarily + // lead to more nicely formatted code in general. + if p.indentList(s.Results) { + p.print(indent) + // Use NoPos so that a newline never goes before + // the results (see issue #32854). + p.exprList(token.NoPos, s.Results, 1, noIndent, token.NoPos, false) + p.print(unindent) + } else { + p.exprList(token.NoPos, s.Results, 1, 0, token.NoPos, false) + } + } + + case *ast.BranchStmt: + p.print(s.Tok) + if s.Label != nil { + p.print(blank) + p.expr(s.Label) + } + + case *ast.BlockStmt: + p.block(s, 1) + + case *ast.IfStmt: + p.print(token.IF) + p.controlClause(false, s.Init, s.Cond, nil) + p.block(s.Body, 1) + if s.Else != nil { + p.print(blank, token.ELSE, blank) + switch s.Else.(type) { + case *ast.BlockStmt, *ast.IfStmt: + p.stmt(s.Else, nextIsRBrace) + default: + // This can only happen with an incorrectly + // constructed AST. Permit it but print so + // that it can be parsed without errors. + p.print(token.LBRACE, indent, formfeed) + p.stmt(s.Else, true) + p.print(unindent, formfeed, token.RBRACE) + } + } + + case *ast.CaseClause: + if s.List != nil { + p.print(token.CASE, blank) + p.exprList(s.Pos(), s.List, 1, 0, s.Colon, false) + } else { + p.print(token.DEFAULT) + } + p.print(s.Colon, token.COLON) + p.stmtList(s.Body, 1, nextIsRBrace) + + case *ast.SwitchStmt: + p.print(token.SWITCH) + p.controlClause(false, s.Init, s.Tag, nil) + p.block(s.Body, 0) + + case *ast.TypeSwitchStmt: + p.print(token.SWITCH) + if s.Init != nil { + p.print(blank) + p.stmt(s.Init, false) + p.print(token.SEMICOLON) + } + p.print(blank) + p.stmt(s.Assign, false) + p.print(blank) + p.block(s.Body, 0) + + case *ast.CommClause: + if s.Comm != nil { + p.print(token.CASE, blank) + p.stmt(s.Comm, false) + } else { + p.print(token.DEFAULT) + } + p.print(s.Colon, token.COLON) + p.stmtList(s.Body, 1, nextIsRBrace) + + case *ast.SelectStmt: + p.print(token.SELECT, blank) + body := s.Body + if len(body.List) == 0 && !p.commentBefore(p.posFor(body.Rbrace)) { + // print empty select statement w/o comments on one line + p.print(body.Lbrace, token.LBRACE, body.Rbrace, token.RBRACE) + } else { + p.block(body, 0) + } + + case *ast.ForStmt: + p.print(token.FOR) + p.controlClause(true, s.Init, s.Cond, s.Post) + p.block(s.Body, 1) + + case *ast.RangeStmt: + p.print(token.FOR, blank) + if s.Key != nil { + p.expr(s.Key) + if s.Value != nil { + // use position of value following the comma as + // comma position for correct comment placement + p.print(s.Value.Pos(), token.COMMA, blank) + p.expr(s.Value) + } + p.print(blank, s.TokPos, s.Tok, blank) + } + p.print(token.RANGE, blank) + p.expr(stripParens(s.X)) + p.print(blank) + p.block(s.Body, 1) + + default: + panic("unreachable") + } +} + +// ---------------------------------------------------------------------------- +// Declarations + +// The keepTypeColumn function determines if the type column of a series of +// consecutive const or var declarations must be kept, or if initialization +// values (V) can be placed in the type column (T) instead. The i'th entry +// in the result slice is true if the type column in spec[i] must be kept. +// +// For example, the declaration: +// +// const ( +// foobar int = 42 // comment +// x = 7 // comment +// foo +// bar = 991 +// ) +// +// leads to the type/values matrix below. A run of value columns (V) can +// be moved into the type column if there is no type for any of the values +// in that column (we only move entire columns so that they align properly). +// +// matrix formatted result +// matrix +// T V -> T V -> true there is a T and so the type +// - V - V true column must be kept +// - - - - false +// - V V - false V is moved into T column +// +func keepTypeColumn(specs []ast.Spec) []bool { + m := make([]bool, len(specs)) + + populate := func(i, j int, keepType bool) { + if keepType { + for ; i < j; i++ { + m[i] = true + } + } + } + + i0 := -1 // if i0 >= 0 we are in a run and i0 is the start of the run + var keepType bool + for i, s := range specs { + t := s.(*ast.ValueSpec) + if t.Values != nil { + if i0 < 0 { + // start of a run of ValueSpecs with non-nil Values + i0 = i + keepType = false + } + } else { + if i0 >= 0 { + // end of a run + populate(i0, i, keepType) + i0 = -1 + } + } + if t.Type != nil { + keepType = true + } + } + if i0 >= 0 { + // end of a run + populate(i0, len(specs), keepType) + } + + return m +} + +func (p *printer) valueSpec(s *ast.ValueSpec, keepType bool) { + p.setComment(s.Doc) + p.identList(s.Names, false) // always present + extraTabs := 3 + if s.Type != nil || keepType { + p.print(vtab) + extraTabs-- + } + if s.Type != nil { + p.expr(s.Type) + } + if s.Values != nil { + p.print(vtab, token.ASSIGN, blank) + p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos, false) + extraTabs-- + } + if s.Comment != nil { + for ; extraTabs > 0; extraTabs-- { + p.print(vtab) + } + p.setComment(s.Comment) + } +} + +func sanitizeImportPath(lit *ast.BasicLit) *ast.BasicLit { + // Note: An unmodified AST generated by go/parser will already + // contain a backward- or double-quoted path string that does + // not contain any invalid characters, and most of the work + // here is not needed. However, a modified or generated AST + // may possibly contain non-canonical paths. Do the work in + // all cases since it's not too hard and not speed-critical. + + // if we don't have a proper string, be conservative and return whatever we have + if lit.Kind != token.STRING { + return lit + } + s, err := strconv.Unquote(lit.Value) + if err != nil { + return lit + } + + // if the string is an invalid path, return whatever we have + // + // spec: "Implementation restriction: A compiler may restrict + // ImportPaths to non-empty strings using only characters belonging + // to Unicode's L, M, N, P, and S general categories (the Graphic + // characters without spaces) and may also exclude the characters + // !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character + // U+FFFD." + if s == "" { + return lit + } + const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" + for _, r := range s { + if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { + return lit + } + } + + // otherwise, return the double-quoted path + s = strconv.Quote(s) + if s == lit.Value { + return lit // nothing wrong with lit + } + return &ast.BasicLit{ValuePos: lit.ValuePos, Kind: token.STRING, Value: s} +} + +// The parameter n is the number of specs in the group. If doIndent is set, +// multi-line identifier lists in the spec are indented when the first +// linebreak is encountered. +// +func (p *printer) spec(spec ast.Spec, n int, doIndent bool) { + switch s := spec.(type) { + case *ast.ImportSpec: + p.setComment(s.Doc) + if s.Name != nil { + p.expr(s.Name) + p.print(blank) + } + p.expr(sanitizeImportPath(s.Path)) + p.setComment(s.Comment) + p.print(s.EndPos) + + case *ast.ValueSpec: + if n != 1 { + p.internalError("expected n = 1; got", n) + } + p.setComment(s.Doc) + p.identList(s.Names, doIndent) // always present + if s.Type != nil { + p.print(blank) + p.expr(s.Type) + } + if s.Values != nil { + p.print(blank, token.ASSIGN, blank) + p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos, false) + } + p.setComment(s.Comment) + + case *ast.TypeSpec: + p.setComment(s.Doc) + p.expr(s.Name) + if n == 1 { + p.print(blank) + } else { + p.print(vtab) + } + if s.Assign.IsValid() { + p.print(token.ASSIGN, blank) + } + p.expr(s.Type) + p.setComment(s.Comment) + + default: + panic("unreachable") + } +} + +func (p *printer) genDecl(d *ast.GenDecl) { + p.setComment(d.Doc) + p.print(d.Pos(), d.Tok, blank) + + if d.Lparen.IsValid() || len(d.Specs) > 1 { + // group of parenthesized declarations + p.print(d.Lparen, token.LPAREN) + if n := len(d.Specs); n > 0 { + p.print(indent, formfeed) + if n > 1 && (d.Tok == token.CONST || d.Tok == token.VAR) { + // two or more grouped const/var declarations: + // determine if the type column must be kept + keepType := keepTypeColumn(d.Specs) + var line int + for i, s := range d.Specs { + if i > 0 { + p.linebreak(p.lineFor(s.Pos()), 1, ignore, p.linesFrom(line) > 0) + } + p.recordLine(&line) + p.valueSpec(s.(*ast.ValueSpec), keepType[i]) + } + } else { + var line int + for i, s := range d.Specs { + if i > 0 { + p.linebreak(p.lineFor(s.Pos()), 1, ignore, p.linesFrom(line) > 0) + } + p.recordLine(&line) + p.spec(s, n, false) + } + } + p.print(unindent, formfeed) + } + p.print(d.Rparen, token.RPAREN) + + } else if len(d.Specs) > 0 { + // single declaration + p.spec(d.Specs[0], 1, true) + } +} + +// nodeSize determines the size of n in chars after formatting. +// The result is <= maxSize if the node fits on one line with at +// most maxSize chars and the formatted output doesn't contain +// any control chars. Otherwise, the result is > maxSize. +// +func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) { + // nodeSize invokes the printer, which may invoke nodeSize + // recursively. For deep composite literal nests, this can + // lead to an exponential algorithm. Remember previous + // results to prune the recursion (was issue 1628). + if size, found := p.nodeSizes[n]; found { + return size + } + + size = maxSize + 1 // assume n doesn't fit + p.nodeSizes[n] = size + + // nodeSize computation must be independent of particular + // style so that we always get the same decision; print + // in RawFormat + cfg := Config{Mode: RawFormat} + var buf bytes.Buffer + if err := cfg.fprint(&buf, p.fset, n, p.nodeSizes); err != nil { + return + } + if buf.Len() <= maxSize { + for _, ch := range buf.Bytes() { + if ch < ' ' { + return + } + } + size = buf.Len() // n fits + p.nodeSizes[n] = size + } + return +} + +// numLines returns the number of lines spanned by node n in the original source. +func (p *printer) numLines(n ast.Node) int { + if from := n.Pos(); from.IsValid() { + if to := n.End(); to.IsValid() { + return p.lineFor(to) - p.lineFor(from) + 1 + } + } + return infinity +} + +// bodySize is like nodeSize but it is specialized for *ast.BlockStmt's. +func (p *printer) bodySize(b *ast.BlockStmt, maxSize int) int { + pos1 := b.Pos() + pos2 := b.Rbrace + if pos1.IsValid() && pos2.IsValid() && p.lineFor(pos1) != p.lineFor(pos2) { + // opening and closing brace are on different lines - don't make it a one-liner + return maxSize + 1 + } + if len(b.List) > 5 { + // too many statements - don't make it a one-liner + return maxSize + 1 + } + // otherwise, estimate body size + bodySize := p.commentSizeBefore(p.posFor(pos2)) + for i, s := range b.List { + if bodySize > maxSize { + break // no need to continue + } + if i > 0 { + bodySize += 2 // space for a semicolon and blank + } + bodySize += p.nodeSize(s, maxSize) + } + return bodySize +} + +// funcBody prints a function body following a function header of given headerSize. +// If the header's and block's size are "small enough" and the block is "simple enough", +// the block is printed on the current line, without line breaks, spaced from the header +// by sep. Otherwise the block's opening "{" is printed on the current line, followed by +// lines for the block's statements and its closing "}". +// +func (p *printer) funcBody(headerSize int, sep whiteSpace, b *ast.BlockStmt) { + if b == nil { + return + } + + // save/restore composite literal nesting level + defer func(level int) { + p.level = level + }(p.level) + p.level = 0 + + const maxSize = 100 + if headerSize+p.bodySize(b, maxSize) <= maxSize { + p.print(sep, b.Lbrace, token.LBRACE) + if len(b.List) > 0 { + p.print(blank) + for i, s := range b.List { + if i > 0 { + p.print(token.SEMICOLON, blank) + } + p.stmt(s, i == len(b.List)-1) + } + p.print(blank) + } + p.print(noExtraLinebreak, b.Rbrace, token.RBRACE, noExtraLinebreak) + return + } + + if sep != ignore { + p.print(blank) // always use blank + } + p.block(b, 1) +} + +// distanceFrom returns the column difference between p.out (the current output +// position) and startOutCol. If the start position is on a different line from +// the current position (or either is unknown), the result is infinity. +func (p *printer) distanceFrom(startPos token.Pos, startOutCol int) int { + if startPos.IsValid() && p.pos.IsValid() && p.posFor(startPos).Line == p.pos.Line { + return p.out.Column - startOutCol + } + return infinity +} + +func (p *printer) funcDecl(d *ast.FuncDecl) { + p.setComment(d.Doc) + p.print(d.Pos(), token.FUNC, blank) + // We have to save startCol only after emitting FUNC; otherwise it can be on a + // different line (all whitespace preceding the FUNC is emitted only when the + // FUNC is emitted). + startCol := p.out.Column - len("func ") + if d.Recv != nil { + p.parameters(d.Recv) // method: print receiver + p.print(blank) + } + p.expr(d.Name) + p.signature(d.Type.Params, d.Type.Results) + p.funcBody(p.distanceFrom(d.Pos(), startCol), vtab, d.Body) +} + +func (p *printer) decl(decl ast.Decl) { + switch d := decl.(type) { + case *ast.BadDecl: + p.print(d.Pos(), "BadDecl") + case *ast.GenDecl: + p.genDecl(d) + case *ast.FuncDecl: + p.funcDecl(d) + default: + panic("unreachable") + } +} + +// ---------------------------------------------------------------------------- +// Files + +func declToken(decl ast.Decl) (tok token.Token) { + tok = token.ILLEGAL + switch d := decl.(type) { + case *ast.GenDecl: + tok = d.Tok + case *ast.FuncDecl: + tok = token.FUNC + } + return +} + +func (p *printer) declList(list []ast.Decl) { + tok := token.ILLEGAL + for _, d := range list { + prev := tok + tok = declToken(d) + // If the declaration token changed (e.g., from CONST to TYPE) + // or the next declaration has documentation associated with it, + // print an empty line between top-level declarations. + // (because p.linebreak is called with the position of d, which + // is past any documentation, the minimum requirement is satisfied + // even w/o the extra getDoc(d) nil-check - leave it in case the + // linebreak logic improves - there's already a TODO). + if len(p.output) > 0 { + // only print line break if we are not at the beginning of the output + // (i.e., we are not printing only a partial program) + min := 1 + if prev != tok || getDoc(d) != nil { + min = 2 + } + // start a new section if the next declaration is a function + // that spans multiple lines (see also issue #19544) + p.linebreak(p.lineFor(d.Pos()), min, ignore, tok == token.FUNC && p.numLines(d) > 1) + } + p.decl(d) + } +} + +func (p *printer) file(src *ast.File) { + p.setComment(src.Doc) + p.print(src.Pos(), token.PACKAGE, blank) + p.expr(src.Name) + p.declList(src.Decls) + p.print(newline) +} diff --git a/src/go/printer/performance_test.go b/src/go/printer/performance_test.go new file mode 100644 index 0000000..e655fa1 --- /dev/null +++ b/src/go/printer/performance_test.go @@ -0,0 +1,58 @@ +// Copyright 2009 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. + +// This file implements a simple printer performance benchmark: +// go test -bench=BenchmarkPrint + +package printer + +import ( + "bytes" + "go/ast" + "go/parser" + "io" + "log" + "os" + "testing" +) + +var testfile *ast.File + +func testprint(out io.Writer, file *ast.File) { + if err := (&Config{TabIndent | UseSpaces | normalizeNumbers, 8, 0}).Fprint(out, fset, file); err != nil { + log.Fatalf("print error: %s", err) + } +} + +// cannot initialize in init because (printer) Fprint launches goroutines. +func initialize() { + const filename = "testdata/parser.go" + + src, err := os.ReadFile(filename) + if err != nil { + log.Fatalf("%s", err) + } + + file, err := parser.ParseFile(fset, filename, src, parser.ParseComments) + if err != nil { + log.Fatalf("%s", err) + } + + var buf bytes.Buffer + testprint(&buf, file) + if !bytes.Equal(buf.Bytes(), src) { + log.Fatalf("print error: %s not idempotent", filename) + } + + testfile = file +} + +func BenchmarkPrint(b *testing.B) { + if testfile == nil { + initialize() + } + for i := 0; i < b.N; i++ { + testprint(io.Discard, testfile) + } +} diff --git a/src/go/printer/printer.go b/src/go/printer/printer.go new file mode 100644 index 0000000..0077afe --- /dev/null +++ b/src/go/printer/printer.go @@ -0,0 +1,1377 @@ +// Copyright 2009 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 printer implements printing of AST nodes. +package printer + +import ( + "fmt" + "go/ast" + "go/token" + "io" + "os" + "strings" + "text/tabwriter" + "unicode" +) + +const ( + maxNewlines = 2 // max. number of newlines between source text + debug = false // enable for debugging + infinity = 1 << 30 +) + +type whiteSpace byte + +const ( + ignore = whiteSpace(0) + blank = whiteSpace(' ') + vtab = whiteSpace('\v') + newline = whiteSpace('\n') + formfeed = whiteSpace('\f') + indent = whiteSpace('>') + unindent = whiteSpace('<') +) + +// A pmode value represents the current printer mode. +type pmode int + +const ( + noExtraBlank pmode = 1 << iota // disables extra blank after /*-style comment + noExtraLinebreak // disables extra line break after /*-style comment +) + +type commentInfo struct { + cindex int // current comment index + comment *ast.CommentGroup // = printer.comments[cindex]; or nil + commentOffset int // = printer.posFor(printer.comments[cindex].List[0].Pos()).Offset; or infinity + commentNewline bool // true if the comment group contains newlines +} + +type printer struct { + // Configuration (does not change after initialization) + Config + fset *token.FileSet + + // Current state + output []byte // raw printer result + indent int // current indentation + level int // level == 0: outside composite literal; level > 0: inside composite literal + mode pmode // current printer mode + endAlignment bool // if set, terminate alignment immediately + impliedSemi bool // if set, a linebreak implies a semicolon + lastTok token.Token // last token printed (token.ILLEGAL if it's whitespace) + prevOpen token.Token // previous non-brace "open" token (, [, or token.ILLEGAL + wsbuf []whiteSpace // delayed white space + + // Positions + // The out position differs from the pos position when the result + // formatting differs from the source formatting (in the amount of + // white space). If there's a difference and SourcePos is set in + // ConfigMode, //line directives are used in the output to restore + // original source positions for a reader. + pos token.Position // current position in AST (source) space + out token.Position // current position in output space + last token.Position // value of pos after calling writeString + linePtr *int // if set, record out.Line for the next token in *linePtr + + // The list of all source comments, in order of appearance. + comments []*ast.CommentGroup // may be nil + useNodeComments bool // if not set, ignore lead and line comments of nodes + + // Information about p.comments[p.cindex]; set up by nextComment. + commentInfo + + // Cache of already computed node sizes. + nodeSizes map[ast.Node]int + + // Cache of most recently computed line position. + cachedPos token.Pos + cachedLine int // line corresponding to cachedPos +} + +func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) { + p.Config = *cfg + p.fset = fset + p.pos = token.Position{Line: 1, Column: 1} + p.out = token.Position{Line: 1, Column: 1} + p.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short + p.nodeSizes = nodeSizes + p.cachedPos = -1 +} + +func (p *printer) internalError(msg ...interface{}) { + if debug { + fmt.Print(p.pos.String() + ": ") + fmt.Println(msg...) + panic("go/printer") + } +} + +// commentsHaveNewline reports whether a list of comments belonging to +// an *ast.CommentGroup contains newlines. Because the position information +// may only be partially correct, we also have to read the comment text. +func (p *printer) commentsHaveNewline(list []*ast.Comment) bool { + // len(list) > 0 + line := p.lineFor(list[0].Pos()) + for i, c := range list { + if i > 0 && p.lineFor(list[i].Pos()) != line { + // not all comments on the same line + return true + } + if t := c.Text; len(t) >= 2 && (t[1] == '/' || strings.Contains(t, "\n")) { + return true + } + } + _ = line + return false +} + +func (p *printer) nextComment() { + for p.cindex < len(p.comments) { + c := p.comments[p.cindex] + p.cindex++ + if list := c.List; len(list) > 0 { + p.comment = c + p.commentOffset = p.posFor(list[0].Pos()).Offset + p.commentNewline = p.commentsHaveNewline(list) + return + } + // we should not reach here (correct ASTs don't have empty + // ast.CommentGroup nodes), but be conservative and try again + } + // no more comments + p.commentOffset = infinity +} + +// commentBefore reports whether the current comment group occurs +// before the next position in the source code and printing it does +// not introduce implicit semicolons. +// +func (p *printer) commentBefore(next token.Position) bool { + return p.commentOffset < next.Offset && (!p.impliedSemi || !p.commentNewline) +} + +// commentSizeBefore returns the estimated size of the +// comments on the same line before the next position. +// +func (p *printer) commentSizeBefore(next token.Position) int { + // save/restore current p.commentInfo (p.nextComment() modifies it) + defer func(info commentInfo) { + p.commentInfo = info + }(p.commentInfo) + + size := 0 + for p.commentBefore(next) { + for _, c := range p.comment.List { + size += len(c.Text) + } + p.nextComment() + } + return size +} + +// recordLine records the output line number for the next non-whitespace +// token in *linePtr. It is used to compute an accurate line number for a +// formatted construct, independent of pending (not yet emitted) whitespace +// or comments. +// +func (p *printer) recordLine(linePtr *int) { + p.linePtr = linePtr +} + +// linesFrom returns the number of output lines between the current +// output line and the line argument, ignoring any pending (not yet +// emitted) whitespace or comments. It is used to compute an accurate +// size (in number of lines) for a formatted construct. +// +func (p *printer) linesFrom(line int) int { + return p.out.Line - line +} + +func (p *printer) posFor(pos token.Pos) token.Position { + // not used frequently enough to cache entire token.Position + return p.fset.PositionFor(pos, false /* absolute position */) +} + +func (p *printer) lineFor(pos token.Pos) int { + if pos != p.cachedPos { + p.cachedPos = pos + p.cachedLine = p.fset.PositionFor(pos, false /* absolute position */).Line + } + return p.cachedLine +} + +// writeLineDirective writes a //line directive if necessary. +func (p *printer) writeLineDirective(pos token.Position) { + if pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) { + p.output = append(p.output, tabwriter.Escape) // protect '\n' in //line from tabwriter interpretation + p.output = append(p.output, fmt.Sprintf("//line %s:%d\n", pos.Filename, pos.Line)...) + p.output = append(p.output, tabwriter.Escape) + // p.out must match the //line directive + p.out.Filename = pos.Filename + p.out.Line = pos.Line + } +} + +// writeIndent writes indentation. +func (p *printer) writeIndent() { + // use "hard" htabs - indentation columns + // must not be discarded by the tabwriter + n := p.Config.Indent + p.indent // include base indentation + for i := 0; i < n; i++ { + p.output = append(p.output, '\t') + } + + // update positions + p.pos.Offset += n + p.pos.Column += n + p.out.Column += n +} + +// writeByte writes ch n times to p.output and updates p.pos. +// Only used to write formatting (white space) characters. +func (p *printer) writeByte(ch byte, n int) { + if p.endAlignment { + // Ignore any alignment control character; + // and at the end of the line, break with + // a formfeed to indicate termination of + // existing columns. + switch ch { + case '\t', '\v': + ch = ' ' + case '\n', '\f': + ch = '\f' + p.endAlignment = false + } + } + + if p.out.Column == 1 { + // no need to write line directives before white space + p.writeIndent() + } + + for i := 0; i < n; i++ { + p.output = append(p.output, ch) + } + + // update positions + p.pos.Offset += n + if ch == '\n' || ch == '\f' { + p.pos.Line += n + p.out.Line += n + p.pos.Column = 1 + p.out.Column = 1 + return + } + p.pos.Column += n + p.out.Column += n +} + +// writeString writes the string s to p.output and updates p.pos, p.out, +// and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters +// to protect s from being interpreted by the tabwriter. +// +// Note: writeString is only used to write Go tokens, literals, and +// comments, all of which must be written literally. Thus, it is correct +// to always set isLit = true. However, setting it explicitly only when +// needed (i.e., when we don't know that s contains no tabs or line breaks) +// avoids processing extra escape characters and reduces run time of the +// printer benchmark by up to 10%. +// +func (p *printer) writeString(pos token.Position, s string, isLit bool) { + if p.out.Column == 1 { + if p.Config.Mode&SourcePos != 0 { + p.writeLineDirective(pos) + } + p.writeIndent() + } + + if pos.IsValid() { + // update p.pos (if pos is invalid, continue with existing p.pos) + // Note: Must do this after handling line beginnings because + // writeIndent updates p.pos if there's indentation, but p.pos + // is the position of s. + p.pos = pos + } + + if isLit { + // Protect s such that is passes through the tabwriter + // unchanged. Note that valid Go programs cannot contain + // tabwriter.Escape bytes since they do not appear in legal + // UTF-8 sequences. + p.output = append(p.output, tabwriter.Escape) + } + + if debug { + p.output = append(p.output, fmt.Sprintf("/*%s*/", pos)...) // do not update p.pos! + } + p.output = append(p.output, s...) + + // update positions + nlines := 0 + var li int // index of last newline; valid if nlines > 0 + for i := 0; i < len(s); i++ { + // Raw string literals may contain any character except back quote (`). + if ch := s[i]; ch == '\n' || ch == '\f' { + // account for line break + nlines++ + li = i + // A line break inside a literal will break whatever column + // formatting is in place; ignore any further alignment through + // the end of the line. + p.endAlignment = true + } + } + p.pos.Offset += len(s) + if nlines > 0 { + p.pos.Line += nlines + p.out.Line += nlines + c := len(s) - li + p.pos.Column = c + p.out.Column = c + } else { + p.pos.Column += len(s) + p.out.Column += len(s) + } + + if isLit { + p.output = append(p.output, tabwriter.Escape) + } + + p.last = p.pos +} + +// writeCommentPrefix writes the whitespace before a comment. +// If there is any pending whitespace, it consumes as much of +// it as is likely to help position the comment nicely. +// pos is the comment position, next the position of the item +// after all pending comments, prev is the previous comment in +// a group of comments (or nil), and tok is the next token. +// +func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment, tok token.Token) { + if len(p.output) == 0 { + // the comment is the first item to be printed - don't write any whitespace + return + } + + if pos.IsValid() && pos.Filename != p.last.Filename { + // comment in a different file - separate with newlines + p.writeByte('\f', maxNewlines) + return + } + + if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') { + // comment on the same line as last item: + // separate with at least one separator + hasSep := false + if prev == nil { + // first comment of a comment group + j := 0 + for i, ch := range p.wsbuf { + switch ch { + case blank: + // ignore any blanks before a comment + p.wsbuf[i] = ignore + continue + case vtab: + // respect existing tabs - important + // for proper formatting of commented structs + hasSep = true + continue + case indent: + // apply pending indentation + continue + } + j = i + break + } + p.writeWhitespace(j) + } + // make sure there is at least one separator + if !hasSep { + sep := byte('\t') + if pos.Line == next.Line { + // next item is on the same line as the comment + // (which must be a /*-style comment): separate + // with a blank instead of a tab + sep = ' ' + } + p.writeByte(sep, 1) + } + + } else { + // comment on a different line: + // separate with at least one line break + droppedLinebreak := false + j := 0 + for i, ch := range p.wsbuf { + switch ch { + case blank, vtab: + // ignore any horizontal whitespace before line breaks + p.wsbuf[i] = ignore + continue + case indent: + // apply pending indentation + continue + case unindent: + // if this is not the last unindent, apply it + // as it is (likely) belonging to the last + // construct (e.g., a multi-line expression list) + // and is not part of closing a block + if i+1 < len(p.wsbuf) && p.wsbuf[i+1] == unindent { + continue + } + // if the next token is not a closing }, apply the unindent + // if it appears that the comment is aligned with the + // token; otherwise assume the unindent is part of a + // closing block and stop (this scenario appears with + // comments before a case label where the comments + // apply to the next case instead of the current one) + if tok != token.RBRACE && pos.Column == next.Column { + continue + } + case newline, formfeed: + p.wsbuf[i] = ignore + droppedLinebreak = prev == nil // record only if first comment of a group + } + j = i + break + } + p.writeWhitespace(j) + + // determine number of linebreaks before the comment + n := 0 + if pos.IsValid() && p.last.IsValid() { + n = pos.Line - p.last.Line + if n < 0 { // should never happen + n = 0 + } + } + + // at the package scope level only (p.indent == 0), + // add an extra newline if we dropped one before: + // this preserves a blank line before documentation + // comments at the package scope level (issue 2570) + if p.indent == 0 && droppedLinebreak { + n++ + } + + // make sure there is at least one line break + // if the previous comment was a line comment + if n == 0 && prev != nil && prev.Text[1] == '/' { + n = 1 + } + + if n > 0 { + // use formfeeds to break columns before a comment; + // this is analogous to using formfeeds to separate + // individual lines of /*-style comments + p.writeByte('\f', nlimit(n)) + } + } +} + +// Returns true if s contains only white space +// (only tabs and blanks can appear in the printer's context). +// +func isBlank(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] > ' ' { + return false + } + } + return true +} + +// commonPrefix returns the common prefix of a and b. +func commonPrefix(a, b string) string { + i := 0 + for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') { + i++ + } + return a[0:i] +} + +// trimRight returns s with trailing whitespace removed. +func trimRight(s string) string { + return strings.TrimRightFunc(s, unicode.IsSpace) +} + +// stripCommonPrefix removes a common prefix from /*-style comment lines (unless no +// comment line is indented, all but the first line have some form of space prefix). +// The prefix is computed using heuristics such that is likely that the comment +// contents are nicely laid out after re-printing each line using the printer's +// current indentation. +// +func stripCommonPrefix(lines []string) { + if len(lines) <= 1 { + return // at most one line - nothing to do + } + // len(lines) > 1 + + // The heuristic in this function tries to handle a few + // common patterns of /*-style comments: Comments where + // the opening /* and closing */ are aligned and the + // rest of the comment text is aligned and indented with + // blanks or tabs, cases with a vertical "line of stars" + // on the left, and cases where the closing */ is on the + // same line as the last comment text. + + // Compute maximum common white prefix of all but the first, + // last, and blank lines, and replace blank lines with empty + // lines (the first line starts with /* and has no prefix). + // In cases where only the first and last lines are not blank, + // such as two-line comments, or comments where all inner lines + // are blank, consider the last line for the prefix computation + // since otherwise the prefix would be empty. + // + // Note that the first and last line are never empty (they + // contain the opening /* and closing */ respectively) and + // thus they can be ignored by the blank line check. + prefix := "" + prefixSet := false + if len(lines) > 2 { + for i, line := range lines[1 : len(lines)-1] { + if isBlank(line) { + lines[1+i] = "" // range starts with lines[1] + } else { + if !prefixSet { + prefix = line + prefixSet = true + } + prefix = commonPrefix(prefix, line) + } + + } + } + // If we don't have a prefix yet, consider the last line. + if !prefixSet { + line := lines[len(lines)-1] + prefix = commonPrefix(line, line) + } + + /* + * Check for vertical "line of stars" and correct prefix accordingly. + */ + lineOfStars := false + if i := strings.Index(prefix, "*"); i >= 0 { + // Line of stars present. + if i > 0 && prefix[i-1] == ' ' { + i-- // remove trailing blank from prefix so stars remain aligned + } + prefix = prefix[0:i] + lineOfStars = true + } else { + // No line of stars present. + // Determine the white space on the first line after the /* + // and before the beginning of the comment text, assume two + // blanks instead of the /* unless the first character after + // the /* is a tab. If the first comment line is empty but + // for the opening /*, assume up to 3 blanks or a tab. This + // whitespace may be found as suffix in the common prefix. + first := lines[0] + if isBlank(first[2:]) { + // no comment text on the first line: + // reduce prefix by up to 3 blanks or a tab + // if present - this keeps comment text indented + // relative to the /* and */'s if it was indented + // in the first place + i := len(prefix) + for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ { + i-- + } + if i == len(prefix) && i > 0 && prefix[i-1] == '\t' { + i-- + } + prefix = prefix[0:i] + } else { + // comment text on the first line + suffix := make([]byte, len(first)) + n := 2 // start after opening /* + for n < len(first) && first[n] <= ' ' { + suffix[n] = first[n] + n++ + } + if n > 2 && suffix[2] == '\t' { + // assume the '\t' compensates for the /* + suffix = suffix[2:n] + } else { + // otherwise assume two blanks + suffix[0], suffix[1] = ' ', ' ' + suffix = suffix[0:n] + } + // Shorten the computed common prefix by the length of + // suffix, if it is found as suffix of the prefix. + prefix = strings.TrimSuffix(prefix, string(suffix)) + } + } + + // Handle last line: If it only contains a closing */, align it + // with the opening /*, otherwise align the text with the other + // lines. + last := lines[len(lines)-1] + closing := "*/" + i := strings.Index(last, closing) // i >= 0 (closing is always present) + if isBlank(last[0:i]) { + // last line only contains closing */ + if lineOfStars { + closing = " */" // add blank to align final star + } + lines[len(lines)-1] = prefix + closing + } else { + // last line contains more comment text - assume + // it is aligned like the other lines and include + // in prefix computation + prefix = commonPrefix(prefix, last) + } + + // Remove the common prefix from all but the first and empty lines. + for i, line := range lines { + if i > 0 && line != "" { + lines[i] = line[len(prefix):] + } + } +} + +func (p *printer) writeComment(comment *ast.Comment) { + text := comment.Text + pos := p.posFor(comment.Pos()) + + const linePrefix = "//line " + if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) { + // Possibly a //-style line directive. + // Suspend indentation temporarily to keep line directive valid. + defer func(indent int) { p.indent = indent }(p.indent) + p.indent = 0 + } + + // shortcut common case of //-style comments + if text[1] == '/' { + p.writeString(pos, trimRight(text), true) + return + } + + // for /*-style comments, print line by line and let the + // write function take care of the proper indentation + lines := strings.Split(text, "\n") + + // The comment started in the first column but is going + // to be indented. For an idempotent result, add indentation + // to all lines such that they look like they were indented + // before - this will make sure the common prefix computation + // is the same independent of how many times formatting is + // applied (was issue 1835). + if pos.IsValid() && pos.Column == 1 && p.indent > 0 { + for i, line := range lines[1:] { + lines[1+i] = " " + line + } + } + + stripCommonPrefix(lines) + + // write comment lines, separated by formfeed, + // without a line break after the last line + for i, line := range lines { + if i > 0 { + p.writeByte('\f', 1) + pos = p.pos + } + if len(line) > 0 { + p.writeString(pos, trimRight(line), true) + } + } +} + +// writeCommentSuffix writes a line break after a comment if indicated +// and processes any leftover indentation information. If a line break +// is needed, the kind of break (newline vs formfeed) depends on the +// pending whitespace. The writeCommentSuffix result indicates if a +// newline was written or if a formfeed was dropped from the whitespace +// buffer. +// +func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, droppedFF bool) { + for i, ch := range p.wsbuf { + switch ch { + case blank, vtab: + // ignore trailing whitespace + p.wsbuf[i] = ignore + case indent, unindent: + // don't lose indentation information + case newline, formfeed: + // if we need a line break, keep exactly one + // but remember if we dropped any formfeeds + if needsLinebreak { + needsLinebreak = false + wroteNewline = true + } else { + if ch == formfeed { + droppedFF = true + } + p.wsbuf[i] = ignore + } + } + } + p.writeWhitespace(len(p.wsbuf)) + + // make sure we have a line break + if needsLinebreak { + p.writeByte('\n', 1) + wroteNewline = true + } + + return +} + +// containsLinebreak reports whether the whitespace buffer contains any line breaks. +func (p *printer) containsLinebreak() bool { + for _, ch := range p.wsbuf { + if ch == newline || ch == formfeed { + return true + } + } + return false +} + +// intersperseComments consumes all comments that appear before the next token +// tok and prints it together with the buffered whitespace (i.e., the whitespace +// that needs to be written before the next token). A heuristic is used to mix +// the comments and whitespace. The intersperseComments result indicates if a +// newline was written or if a formfeed was dropped from the whitespace buffer. +// +func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) { + var last *ast.Comment + for p.commentBefore(next) { + for _, c := range p.comment.List { + p.writeCommentPrefix(p.posFor(c.Pos()), next, last, tok) + p.writeComment(c) + last = c + } + p.nextComment() + } + + if last != nil { + // If the last comment is a /*-style comment and the next item + // follows on the same line but is not a comma, and not a "closing" + // token immediately following its corresponding "opening" token, + // add an extra separator unless explicitly disabled. Use a blank + // as separator unless we have pending linebreaks, they are not + // disabled, and we are outside a composite literal, in which case + // we want a linebreak (issue 15137). + // TODO(gri) This has become overly complicated. We should be able + // to track whether we're inside an expression or statement and + // use that information to decide more directly. + needsLinebreak := false + if p.mode&noExtraBlank == 0 && + last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line && + tok != token.COMMA && + (tok != token.RPAREN || p.prevOpen == token.LPAREN) && + (tok != token.RBRACK || p.prevOpen == token.LBRACK) { + if p.containsLinebreak() && p.mode&noExtraLinebreak == 0 && p.level == 0 { + needsLinebreak = true + } else { + p.writeByte(' ', 1) + } + } + // Ensure that there is a line break after a //-style comment, + // before EOF, and before a closing '}' unless explicitly disabled. + if last.Text[1] == '/' || + tok == token.EOF || + tok == token.RBRACE && p.mode&noExtraLinebreak == 0 { + needsLinebreak = true + } + return p.writeCommentSuffix(needsLinebreak) + } + + // no comment was written - we should never reach here since + // intersperseComments should not be called in that case + p.internalError("intersperseComments called without pending comments") + return +} + +// whiteWhitespace writes the first n whitespace entries. +func (p *printer) writeWhitespace(n int) { + // write entries + for i := 0; i < n; i++ { + switch ch := p.wsbuf[i]; ch { + case ignore: + // ignore! + case indent: + p.indent++ + case unindent: + p.indent-- + if p.indent < 0 { + p.internalError("negative indentation:", p.indent) + p.indent = 0 + } + case newline, formfeed: + // A line break immediately followed by a "correcting" + // unindent is swapped with the unindent - this permits + // proper label positioning. If a comment is between + // the line break and the label, the unindent is not + // part of the comment whitespace prefix and the comment + // will be positioned correctly indented. + if i+1 < n && p.wsbuf[i+1] == unindent { + // Use a formfeed to terminate the current section. + // Otherwise, a long label name on the next line leading + // to a wide column may increase the indentation column + // of lines before the label; effectively leading to wrong + // indentation. + p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed + i-- // do it again + continue + } + fallthrough + default: + p.writeByte(byte(ch), 1) + } + } + + // shift remaining entries down + l := copy(p.wsbuf, p.wsbuf[n:]) + p.wsbuf = p.wsbuf[:l] +} + +// ---------------------------------------------------------------------------- +// Printing interface + +// nlines limits n to maxNewlines. +func nlimit(n int) int { + if n > maxNewlines { + n = maxNewlines + } + return n +} + +func mayCombine(prev token.Token, next byte) (b bool) { + switch prev { + case token.INT: + b = next == '.' // 1. + case token.ADD: + b = next == '+' // ++ + case token.SUB: + b = next == '-' // -- + case token.QUO: + b = next == '*' // /* + case token.LSS: + b = next == '-' || next == '<' // <- or << + case token.AND: + b = next == '&' || next == '^' // && or &^ + } + return +} + +// print prints a list of "items" (roughly corresponding to syntactic +// tokens, but also including whitespace and formatting information). +// It is the only print function that should be called directly from +// any of the AST printing functions in nodes.go. +// +// Whitespace is accumulated until a non-whitespace token appears. Any +// comments that need to appear before that token are printed first, +// taking into account the amount and structure of any pending white- +// space for best comment placement. Then, any leftover whitespace is +// printed, followed by the actual token. +// +func (p *printer) print(args ...interface{}) { + for _, arg := range args { + // information about the current arg + var data string + var isLit bool + var impliedSemi bool // value for p.impliedSemi after this arg + + // record previous opening token, if any + switch p.lastTok { + case token.ILLEGAL: + // ignore (white space) + case token.LPAREN, token.LBRACK: + p.prevOpen = p.lastTok + default: + // other tokens followed any opening token + p.prevOpen = token.ILLEGAL + } + + switch x := arg.(type) { + case pmode: + // toggle printer mode + p.mode ^= x + continue + + case whiteSpace: + if x == ignore { + // don't add ignore's to the buffer; they + // may screw up "correcting" unindents (see + // LabeledStmt) + continue + } + i := len(p.wsbuf) + if i == cap(p.wsbuf) { + // Whitespace sequences are very short so this should + // never happen. Handle gracefully (but possibly with + // bad comment placement) if it does happen. + p.writeWhitespace(i) + i = 0 + } + p.wsbuf = p.wsbuf[0 : i+1] + p.wsbuf[i] = x + if x == newline || x == formfeed { + // newlines affect the current state (p.impliedSemi) + // and not the state after printing arg (impliedSemi) + // because comments can be interspersed before the arg + // in this case + p.impliedSemi = false + } + p.lastTok = token.ILLEGAL + continue + + case *ast.Ident: + data = x.Name + impliedSemi = true + p.lastTok = token.IDENT + + case *ast.BasicLit: + data = x.Value + isLit = true + impliedSemi = true + p.lastTok = x.Kind + + case token.Token: + s := x.String() + if mayCombine(p.lastTok, s[0]) { + // the previous and the current token must be + // separated by a blank otherwise they combine + // into a different incorrect token sequence + // (except for token.INT followed by a '.' this + // should never happen because it is taken care + // of via binary expression formatting) + if len(p.wsbuf) != 0 { + p.internalError("whitespace buffer not empty") + } + p.wsbuf = p.wsbuf[0:1] + p.wsbuf[0] = ' ' + } + data = s + // some keywords followed by a newline imply a semicolon + switch x { + case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN, + token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE: + impliedSemi = true + } + p.lastTok = x + + case token.Pos: + if x.IsValid() { + p.pos = p.posFor(x) // accurate position of next item + } + continue + + case string: + // incorrect AST - print error message + data = x + isLit = true + impliedSemi = true + p.lastTok = token.STRING + + default: + fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg) + panic("go/printer type") + } + // data != "" + + next := p.pos // estimated/accurate position of next item + wroteNewline, droppedFF := p.flush(next, p.lastTok) + + // intersperse extra newlines if present in the source and + // if they don't cause extra semicolons (don't do this in + // flush as it will cause extra newlines at the end of a file) + if !p.impliedSemi { + n := nlimit(next.Line - p.pos.Line) + // don't exceed maxNewlines if we already wrote one + if wroteNewline && n == maxNewlines { + n = maxNewlines - 1 + } + if n > 0 { + ch := byte('\n') + if droppedFF { + ch = '\f' // use formfeed since we dropped one before + } + p.writeByte(ch, n) + impliedSemi = false + } + } + + // the next token starts now - record its line number if requested + if p.linePtr != nil { + *p.linePtr = p.out.Line + p.linePtr = nil + } + + p.writeString(next, data, isLit) + p.impliedSemi = impliedSemi + } +} + +// flush prints any pending comments and whitespace occurring textually +// before the position of the next token tok. The flush result indicates +// if a newline was written or if a formfeed was dropped from the whitespace +// buffer. +// +func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) { + if p.commentBefore(next) { + // if there are comments before the next item, intersperse them + wroteNewline, droppedFF = p.intersperseComments(next, tok) + } else { + // otherwise, write any leftover whitespace + p.writeWhitespace(len(p.wsbuf)) + } + return +} + +// getNode returns the ast.CommentGroup associated with n, if any. +func getDoc(n ast.Node) *ast.CommentGroup { + switch n := n.(type) { + case *ast.Field: + return n.Doc + case *ast.ImportSpec: + return n.Doc + case *ast.ValueSpec: + return n.Doc + case *ast.TypeSpec: + return n.Doc + case *ast.GenDecl: + return n.Doc + case *ast.FuncDecl: + return n.Doc + case *ast.File: + return n.Doc + } + return nil +} + +func getLastComment(n ast.Node) *ast.CommentGroup { + switch n := n.(type) { + case *ast.Field: + return n.Comment + case *ast.ImportSpec: + return n.Comment + case *ast.ValueSpec: + return n.Comment + case *ast.TypeSpec: + return n.Comment + case *ast.GenDecl: + if len(n.Specs) > 0 { + return getLastComment(n.Specs[len(n.Specs)-1]) + } + case *ast.File: + if len(n.Comments) > 0 { + return n.Comments[len(n.Comments)-1] + } + } + return nil +} + +func (p *printer) printNode(node interface{}) error { + // unpack *CommentedNode, if any + var comments []*ast.CommentGroup + if cnode, ok := node.(*CommentedNode); ok { + node = cnode.Node + comments = cnode.Comments + } + + if comments != nil { + // commented node - restrict comment list to relevant range + n, ok := node.(ast.Node) + if !ok { + goto unsupported + } + beg := n.Pos() + end := n.End() + // if the node has associated documentation, + // include that commentgroup in the range + // (the comment list is sorted in the order + // of the comment appearance in the source code) + if doc := getDoc(n); doc != nil { + beg = doc.Pos() + } + if com := getLastComment(n); com != nil { + if e := com.End(); e > end { + end = e + } + } + // token.Pos values are global offsets, we can + // compare them directly + i := 0 + for i < len(comments) && comments[i].End() < beg { + i++ + } + j := i + for j < len(comments) && comments[j].Pos() < end { + j++ + } + if i < j { + p.comments = comments[i:j] + } + } else if n, ok := node.(*ast.File); ok { + // use ast.File comments, if any + p.comments = n.Comments + } + + // if there are no comments, use node comments + p.useNodeComments = p.comments == nil + + // get comments ready for use + p.nextComment() + + // format node + switch n := node.(type) { + case ast.Expr: + p.expr(n) + case ast.Stmt: + // A labeled statement will un-indent to position the label. + // Set p.indent to 1 so we don't get indent "underflow". + if _, ok := n.(*ast.LabeledStmt); ok { + p.indent = 1 + } + p.stmt(n, false) + case ast.Decl: + p.decl(n) + case ast.Spec: + p.spec(n, 1, false) + case []ast.Stmt: + // A labeled statement will un-indent to position the label. + // Set p.indent to 1 so we don't get indent "underflow". + for _, s := range n { + if _, ok := s.(*ast.LabeledStmt); ok { + p.indent = 1 + } + } + p.stmtList(n, 0, false) + case []ast.Decl: + p.declList(n) + case *ast.File: + p.file(n) + default: + goto unsupported + } + + return nil + +unsupported: + return fmt.Errorf("go/printer: unsupported node type %T", node) +} + +// ---------------------------------------------------------------------------- +// Trimmer + +// A trimmer is an io.Writer filter for stripping tabwriter.Escape +// characters, trailing blanks and tabs, and for converting formfeed +// and vtab characters into newlines and htabs (in case no tabwriter +// is used). Text bracketed by tabwriter.Escape characters is passed +// through unchanged. +// +type trimmer struct { + output io.Writer + state int + space []byte +} + +// trimmer is implemented as a state machine. +// It can be in one of the following states: +const ( + inSpace = iota // inside space + inEscape // inside text bracketed by tabwriter.Escapes + inText // inside text +) + +func (p *trimmer) resetSpace() { + p.state = inSpace + p.space = p.space[0:0] +} + +// Design note: It is tempting to eliminate extra blanks occurring in +// whitespace in this function as it could simplify some +// of the blanks logic in the node printing functions. +// However, this would mess up any formatting done by +// the tabwriter. + +var aNewline = []byte("\n") + +func (p *trimmer) Write(data []byte) (n int, err error) { + // invariants: + // p.state == inSpace: + // p.space is unwritten + // p.state == inEscape, inText: + // data[m:n] is unwritten + m := 0 + var b byte + for n, b = range data { + if b == '\v' { + b = '\t' // convert to htab + } + switch p.state { + case inSpace: + switch b { + case '\t', ' ': + p.space = append(p.space, b) + case '\n', '\f': + p.resetSpace() // discard trailing space + _, err = p.output.Write(aNewline) + case tabwriter.Escape: + _, err = p.output.Write(p.space) + p.state = inEscape + m = n + 1 // +1: skip tabwriter.Escape + default: + _, err = p.output.Write(p.space) + p.state = inText + m = n + } + case inEscape: + if b == tabwriter.Escape { + _, err = p.output.Write(data[m:n]) + p.resetSpace() + } + case inText: + switch b { + case '\t', ' ': + _, err = p.output.Write(data[m:n]) + p.resetSpace() + p.space = append(p.space, b) + case '\n', '\f': + _, err = p.output.Write(data[m:n]) + p.resetSpace() + if err == nil { + _, err = p.output.Write(aNewline) + } + case tabwriter.Escape: + _, err = p.output.Write(data[m:n]) + p.state = inEscape + m = n + 1 // +1: skip tabwriter.Escape + } + default: + panic("unreachable") + } + if err != nil { + return + } + } + n = len(data) + + switch p.state { + case inEscape, inText: + _, err = p.output.Write(data[m:n]) + p.resetSpace() + } + + return +} + +// ---------------------------------------------------------------------------- +// Public interface + +// A Mode value is a set of flags (or 0). They control printing. +type Mode uint + +const ( + RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored + TabIndent // use tabs for indentation independent of UseSpaces + UseSpaces // use spaces instead of tabs for alignment + SourcePos // emit //line directives to preserve original source positions +) + +// The mode below is not included in printer's public API because +// editing code text is deemed out of scope. Because this mode is +// unexported, it's also possible to modify or remove it based on +// the evolving needs of go/format and cmd/gofmt without breaking +// users. See discussion in CL 240683. +const ( + // normalizeNumbers means to canonicalize number + // literal prefixes and exponents while printing. + // + // This value is known in and used by go/format and cmd/gofmt. + // It is currently more convenient and performant for those + // packages to apply number normalization during printing, + // rather than by modifying the AST in advance. + normalizeNumbers Mode = 1 << 30 +) + +// A Config node controls the output of Fprint. +type Config struct { + Mode Mode // default: 0 + Tabwidth int // default: 8 + Indent int // default: 0 (all code is indented at least by this much) +} + +// fprint implements Fprint and takes a nodesSizes map for setting up the printer state. +func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (err error) { + // print node + var p printer + p.init(cfg, fset, nodeSizes) + if err = p.printNode(node); err != nil { + return + } + // print outstanding comments + p.impliedSemi = false // EOF acts like a newline + p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF) + + // redirect output through a trimmer to eliminate trailing whitespace + // (Input to a tabwriter must be untrimmed since trailing tabs provide + // formatting information. The tabwriter could provide trimming + // functionality but no tabwriter is used when RawFormat is set.) + output = &trimmer{output: output} + + // redirect output through a tabwriter if necessary + if cfg.Mode&RawFormat == 0 { + minwidth := cfg.Tabwidth + + padchar := byte('\t') + if cfg.Mode&UseSpaces != 0 { + padchar = ' ' + } + + twmode := tabwriter.DiscardEmptyColumns + if cfg.Mode&TabIndent != 0 { + minwidth = 0 + twmode |= tabwriter.TabIndent + } + + output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode) + } + + // write printer result via tabwriter/trimmer to output + if _, err = output.Write(p.output); err != nil { + return + } + + // flush tabwriter, if any + if tw, _ := output.(*tabwriter.Writer); tw != nil { + err = tw.Flush() + } + + return +} + +// A CommentedNode bundles an AST node and corresponding comments. +// It may be provided as argument to any of the Fprint functions. +// +type CommentedNode struct { + Node interface{} // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt + Comments []*ast.CommentGroup +} + +// Fprint "pretty-prints" an AST node to output for a given configuration cfg. +// Position information is interpreted relative to the file set fset. +// The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt, +// or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt. +// +func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error { + return cfg.fprint(output, fset, node, make(map[ast.Node]int)) +} + +// Fprint "pretty-prints" an AST node to output. +// It calls Config.Fprint with default settings. +// Note that gofmt uses tabs for indentation but spaces for alignment; +// use format.Node (package go/format) for output that matches gofmt. +// +func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error { + return (&Config{Tabwidth: 8}).Fprint(output, fset, node) +} diff --git a/src/go/printer/printer_test.go b/src/go/printer/printer_test.go new file mode 100644 index 0000000..45e5011 --- /dev/null +++ b/src/go/printer/printer_test.go @@ -0,0 +1,814 @@ +// Copyright 2009 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 printer + +import ( + "bytes" + "errors" + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io" + "os" + "path/filepath" + "testing" + "time" +) + +const ( + dataDir = "testdata" + tabwidth = 8 +) + +var update = flag.Bool("update", false, "update golden files") + +var fset = token.NewFileSet() + +type checkMode uint + +const ( + export checkMode = 1 << iota + rawFormat + normNumber + idempotent +) + +// format parses src, prints the corresponding AST, verifies the resulting +// src is syntactically correct, and returns the resulting src or an error +// if any. +func format(src []byte, mode checkMode) ([]byte, error) { + // parse src + f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + if err != nil { + return nil, fmt.Errorf("parse: %s\n%s", err, src) + } + + // filter exports if necessary + if mode&export != 0 { + ast.FileExports(f) // ignore result + f.Comments = nil // don't print comments that are not in AST + } + + // determine printer configuration + cfg := Config{Tabwidth: tabwidth} + if mode&rawFormat != 0 { + cfg.Mode |= RawFormat + } + if mode&normNumber != 0 { + cfg.Mode |= normalizeNumbers + } + + // print AST + var buf bytes.Buffer + if err := cfg.Fprint(&buf, fset, f); err != nil { + return nil, fmt.Errorf("print: %s", err) + } + + // make sure formatted output is syntactically correct + res := buf.Bytes() + if _, err := parser.ParseFile(fset, "", res, 0); err != nil { + return nil, fmt.Errorf("re-parse: %s\n%s", err, buf.Bytes()) + } + + return res, nil +} + +// lineAt returns the line in text starting at offset offs. +func lineAt(text []byte, offs int) []byte { + i := offs + for i < len(text) && text[i] != '\n' { + i++ + } + return text[offs:i] +} + +// diff compares a and b. +func diff(aname, bname string, a, b []byte) error { + var buf bytes.Buffer // holding long error message + + // compare lengths + if len(a) != len(b) { + fmt.Fprintf(&buf, "\nlength changed: len(%s) = %d, len(%s) = %d", aname, len(a), bname, len(b)) + } + + // compare contents + line := 1 + offs := 1 + for i := 0; i < len(a) && i < len(b); i++ { + ch := a[i] + if ch != b[i] { + fmt.Fprintf(&buf, "\n%s:%d:%d: %s", aname, line, i-offs+1, lineAt(a, offs)) + fmt.Fprintf(&buf, "\n%s:%d:%d: %s", bname, line, i-offs+1, lineAt(b, offs)) + fmt.Fprintf(&buf, "\n\n") + break + } + if ch == '\n' { + line++ + offs = i + 1 + } + } + + if buf.Len() > 0 { + return errors.New(buf.String()) + } + return nil +} + +func runcheck(t *testing.T, source, golden string, mode checkMode) { + src, err := os.ReadFile(source) + if err != nil { + t.Error(err) + return + } + + res, err := format(src, mode) + if err != nil { + t.Error(err) + return + } + + // update golden files if necessary + if *update { + if err := os.WriteFile(golden, res, 0644); err != nil { + t.Error(err) + } + return + } + + // get golden + gld, err := os.ReadFile(golden) + if err != nil { + t.Error(err) + return + } + + // formatted source and golden must be the same + if err := diff(source, golden, res, gld); err != nil { + t.Error(err) + return + } + + if mode&idempotent != 0 { + // formatting golden must be idempotent + // (This is very difficult to achieve in general and for now + // it is only checked for files explicitly marked as such.) + res, err = format(gld, mode) + if err != nil { + t.Error(err) + return + } + if err := diff(golden, fmt.Sprintf("format(%s)", golden), gld, res); err != nil { + t.Errorf("golden is not idempotent: %s", err) + } + } +} + +func check(t *testing.T, source, golden string, mode checkMode) { + // run the test + cc := make(chan int, 1) + go func() { + runcheck(t, source, golden, mode) + cc <- 0 + }() + + // wait with timeout + select { + case <-time.After(10 * time.Second): // plenty of a safety margin, even for very slow machines + // test running past time out + t.Errorf("%s: running too slowly", source) + case <-cc: + // test finished within allotted time margin + } +} + +type entry struct { + source, golden string + mode checkMode +} + +// Use go test -update to create/update the respective golden files. +var data = []entry{ + {"empty.input", "empty.golden", idempotent}, + {"comments.input", "comments.golden", 0}, + {"comments.input", "comments.x", export}, + {"comments2.input", "comments2.golden", idempotent}, + {"alignment.input", "alignment.golden", idempotent}, + {"linebreaks.input", "linebreaks.golden", idempotent}, + {"expressions.input", "expressions.golden", idempotent}, + {"expressions.input", "expressions.raw", rawFormat | idempotent}, + {"declarations.input", "declarations.golden", 0}, + {"statements.input", "statements.golden", 0}, + {"slow.input", "slow.golden", idempotent}, + {"complit.input", "complit.x", export}, + {"go2numbers.input", "go2numbers.golden", idempotent}, + {"go2numbers.input", "go2numbers.norm", normNumber | idempotent}, +} + +func TestFiles(t *testing.T) { + t.Parallel() + for _, e := range data { + source := filepath.Join(dataDir, e.source) + golden := filepath.Join(dataDir, e.golden) + mode := e.mode + t.Run(e.source, func(t *testing.T) { + t.Parallel() + check(t, source, golden, mode) + // TODO(gri) check that golden is idempotent + //check(t, golden, golden, e.mode) + }) + } +} + +// TestLineComments, using a simple test case, checks that consecutive line +// comments are properly terminated with a newline even if the AST position +// information is incorrect. +// +func TestLineComments(t *testing.T) { + const src = `// comment 1 + // comment 2 + // comment 3 + package main + ` + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + if err != nil { + panic(err) // error in test + } + + var buf bytes.Buffer + fset = token.NewFileSet() // use the wrong file set + Fprint(&buf, fset, f) + + nlines := 0 + for _, ch := range buf.Bytes() { + if ch == '\n' { + nlines++ + } + } + + const expected = 3 + if nlines < expected { + t.Errorf("got %d, expected %d\n", nlines, expected) + t.Errorf("result:\n%s", buf.Bytes()) + } +} + +// Verify that the printer can be invoked during initialization. +func init() { + const name = "foobar" + var buf bytes.Buffer + if err := Fprint(&buf, fset, &ast.Ident{Name: name}); err != nil { + panic(err) // error in test + } + // in debug mode, the result contains additional information; + // ignore it + if s := buf.String(); !debug && s != name { + panic("got " + s + ", want " + name) + } +} + +// Verify that the printer doesn't crash if the AST contains BadXXX nodes. +func TestBadNodes(t *testing.T) { + const src = "package p\n(" + const res = "package p\nBadDecl\n" + f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + if err == nil { + t.Error("expected illegal program") // error in test + } + var buf bytes.Buffer + Fprint(&buf, fset, f) + if buf.String() != res { + t.Errorf("got %q, expected %q", buf.String(), res) + } +} + +// testComment verifies that f can be parsed again after printing it +// with its first comment set to comment at any possible source offset. +func testComment(t *testing.T, f *ast.File, srclen int, comment *ast.Comment) { + f.Comments[0].List[0] = comment + var buf bytes.Buffer + for offs := 0; offs <= srclen; offs++ { + buf.Reset() + // Printing f should result in a correct program no + // matter what the (incorrect) comment position is. + if err := Fprint(&buf, fset, f); err != nil { + t.Error(err) + } + if _, err := parser.ParseFile(fset, "", buf.Bytes(), 0); err != nil { + t.Fatalf("incorrect program for pos = %d:\n%s", comment.Slash, buf.String()) + } + // Position information is just an offset. + // Move comment one byte down in the source. + comment.Slash++ + } +} + +// Verify that the printer produces a correct program +// even if the position information of comments introducing newlines +// is incorrect. +func TestBadComments(t *testing.T) { + t.Parallel() + const src = ` +// first comment - text and position changed by test +package p +import "fmt" +const pi = 3.14 // rough circle +var ( + x, y, z int = 1, 2, 3 + u, v float64 +) +func fibo(n int) { + if n < 2 { + return n /* seed values */ + } + return fibo(n-1) + fibo(n-2) +} +` + + f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + if err != nil { + t.Error(err) // error in test + } + + comment := f.Comments[0].List[0] + pos := comment.Pos() + if fset.PositionFor(pos, false /* absolute position */).Offset != 1 { + t.Error("expected offset 1") // error in test + } + + testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "//-style comment"}) + testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style comment */"}) + testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style \n comment */"}) + testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style comment \n\n\n */"}) +} + +type visitor chan *ast.Ident + +func (v visitor) Visit(n ast.Node) (w ast.Visitor) { + if ident, ok := n.(*ast.Ident); ok { + v <- ident + } + return v +} + +// idents is an iterator that returns all idents in f via the result channel. +func idents(f *ast.File) <-chan *ast.Ident { + v := make(visitor) + go func() { + ast.Walk(v, f) + close(v) + }() + return v +} + +// identCount returns the number of identifiers found in f. +func identCount(f *ast.File) int { + n := 0 + for range idents(f) { + n++ + } + return n +} + +// Verify that the SourcePos mode emits correct //line directives +// by testing that position information for matching identifiers +// is maintained. +func TestSourcePos(t *testing.T) { + const src = ` +package p +import ( "go/printer"; "math" ) +const pi = 3.14; var x = 0 +type t struct{ x, y, z int; u, v, w float32 } +func (t *t) foo(a, b, c int) int { + return a*t.x + b*t.y + + // two extra lines here + // ... + c*t.z +} +` + + // parse original + f1, err := parser.ParseFile(fset, "src", src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + + // pretty-print original + var buf bytes.Buffer + err = (&Config{Mode: UseSpaces | SourcePos, Tabwidth: 8}).Fprint(&buf, fset, f1) + if err != nil { + t.Fatal(err) + } + + // parse pretty printed original + // (//line directives must be interpreted even w/o parser.ParseComments set) + f2, err := parser.ParseFile(fset, "", buf.Bytes(), 0) + if err != nil { + t.Fatalf("%s\n%s", err, buf.Bytes()) + } + + // At this point the position information of identifiers in f2 should + // match the position information of corresponding identifiers in f1. + + // number of identifiers must be > 0 (test should run) and must match + n1 := identCount(f1) + n2 := identCount(f2) + if n1 == 0 { + t.Fatal("got no idents") + } + if n2 != n1 { + t.Errorf("got %d idents; want %d", n2, n1) + } + + // verify that all identifiers have correct line information + i2range := idents(f2) + for i1 := range idents(f1) { + i2 := <-i2range + + if i2.Name != i1.Name { + t.Errorf("got ident %s; want %s", i2.Name, i1.Name) + } + + // here we care about the relative (line-directive adjusted) positions + l1 := fset.Position(i1.Pos()).Line + l2 := fset.Position(i2.Pos()).Line + if l2 != l1 { + t.Errorf("got line %d; want %d for %s", l2, l1, i1.Name) + } + } + + if t.Failed() { + t.Logf("\n%s", buf.Bytes()) + } +} + +// Verify that the SourcePos mode doesn't emit unnecessary //line directives +// before empty lines. +func TestIssue5945(t *testing.T) { + const orig = ` +package p // line 2 +func f() {} // line 3 + +var x, y, z int + + +func g() { // line 8 +} +` + + const want = `//line src.go:2 +package p + +//line src.go:3 +func f() {} + +var x, y, z int + +//line src.go:8 +func g() { +} +` + + // parse original + f1, err := parser.ParseFile(fset, "src.go", orig, 0) + if err != nil { + t.Fatal(err) + } + + // pretty-print original + var buf bytes.Buffer + err = (&Config{Mode: UseSpaces | SourcePos, Tabwidth: 8}).Fprint(&buf, fset, f1) + if err != nil { + t.Fatal(err) + } + got := buf.String() + + // compare original with desired output + if got != want { + t.Errorf("got:\n%s\nwant:\n%s\n", got, want) + } +} + +var decls = []string{ + `import "fmt"`, + "const pi = 3.1415\nconst e = 2.71828\n\nvar x = pi", + "func sum(x, y int) int\t{ return x + y }", +} + +func TestDeclLists(t *testing.T) { + for _, src := range decls { + file, err := parser.ParseFile(fset, "", "package p;"+src, parser.ParseComments) + if err != nil { + panic(err) // error in test + } + + var buf bytes.Buffer + err = Fprint(&buf, fset, file.Decls) // only print declarations + if err != nil { + panic(err) // error in test + } + + out := buf.String() + if out != src { + t.Errorf("\ngot : %q\nwant: %q\n", out, src) + } + } +} + +var stmts = []string{ + "i := 0", + "select {}\nvar a, b = 1, 2\nreturn a + b", + "go f()\ndefer func() {}()", +} + +func TestStmtLists(t *testing.T) { + for _, src := range stmts { + file, err := parser.ParseFile(fset, "", "package p; func _() {"+src+"}", parser.ParseComments) + if err != nil { + panic(err) // error in test + } + + var buf bytes.Buffer + err = Fprint(&buf, fset, file.Decls[0].(*ast.FuncDecl).Body.List) // only print statements + if err != nil { + panic(err) // error in test + } + + out := buf.String() + if out != src { + t.Errorf("\ngot : %q\nwant: %q\n", out, src) + } + } +} + +func TestBaseIndent(t *testing.T) { + t.Parallel() + // The testfile must not contain multi-line raw strings since those + // are not indented (because their values must not change) and make + // this test fail. + const filename = "printer.go" + src, err := os.ReadFile(filename) + if err != nil { + panic(err) // error in test + } + + file, err := parser.ParseFile(fset, filename, src, 0) + if err != nil { + panic(err) // error in test + } + + for indent := 0; indent < 4; indent++ { + indent := indent + t.Run(fmt.Sprint(indent), func(t *testing.T) { + t.Parallel() + var buf bytes.Buffer + (&Config{Tabwidth: tabwidth, Indent: indent}).Fprint(&buf, fset, file) + // all code must be indented by at least 'indent' tabs + lines := bytes.Split(buf.Bytes(), []byte{'\n'}) + for i, line := range lines { + if len(line) == 0 { + continue // empty lines don't have indentation + } + n := 0 + for j, b := range line { + if b != '\t' { + // end of indentation + n = j + break + } + } + if n < indent { + t.Errorf("line %d: got only %d tabs; want at least %d: %q", i, n, indent, line) + } + } + }) + } +} + +// TestFuncType tests that an ast.FuncType with a nil Params field +// can be printed (per go/ast specification). Test case for issue 3870. +func TestFuncType(t *testing.T) { + src := &ast.File{ + Name: &ast.Ident{Name: "p"}, + Decls: []ast.Decl{ + &ast.FuncDecl{ + Name: &ast.Ident{Name: "f"}, + Type: &ast.FuncType{}, + }, + }, + } + + var buf bytes.Buffer + if err := Fprint(&buf, fset, src); err != nil { + t.Fatal(err) + } + got := buf.String() + + const want = `package p + +func f() +` + + if got != want { + t.Fatalf("got:\n%s\nwant:\n%s\n", got, want) + } +} + +type limitWriter struct { + remaining int + errCount int +} + +func (l *limitWriter) Write(buf []byte) (n int, err error) { + n = len(buf) + if n >= l.remaining { + n = l.remaining + err = io.EOF + l.errCount++ + } + l.remaining -= n + return n, err +} + +// Test whether the printer stops writing after the first error +func TestWriteErrors(t *testing.T) { + t.Parallel() + const filename = "printer.go" + src, err := os.ReadFile(filename) + if err != nil { + panic(err) // error in test + } + file, err := parser.ParseFile(fset, filename, src, 0) + if err != nil { + panic(err) // error in test + } + for i := 0; i < 20; i++ { + lw := &limitWriter{remaining: i} + err := (&Config{Mode: RawFormat}).Fprint(lw, fset, file) + if lw.errCount > 1 { + t.Fatal("Writes continued after first error returned") + } + // We expect errCount be 1 iff err is set + if (lw.errCount != 0) != (err != nil) { + t.Fatal("Expected err when errCount != 0") + } + } +} + +// TextX is a skeleton test that can be filled in for debugging one-off cases. +// Do not remove. +func TestX(t *testing.T) { + const src = ` +package p +func _() {} +` + _, err := format([]byte(src), 0) + if err != nil { + t.Error(err) + } +} + +func TestCommentedNode(t *testing.T) { + const ( + input = `package main + +func foo() { + // comment inside func +} + +// leading comment +type bar int // comment2 + +` + + foo = `func foo() { + // comment inside func +}` + + bar = `// leading comment +type bar int // comment2 +` + ) + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "input.go", input, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + + var buf bytes.Buffer + + err = Fprint(&buf, fset, &CommentedNode{Node: f.Decls[0], Comments: f.Comments}) + if err != nil { + t.Fatal(err) + } + + if buf.String() != foo { + t.Errorf("got %q, want %q", buf.String(), foo) + } + + buf.Reset() + + err = Fprint(&buf, fset, &CommentedNode{Node: f.Decls[1], Comments: f.Comments}) + if err != nil { + t.Fatal(err) + } + + if buf.String() != bar { + t.Errorf("got %q, want %q", buf.String(), bar) + } +} + +func TestIssue11151(t *testing.T) { + const src = "package p\t/*\r/1\r*\r/2*\r\r\r\r/3*\r\r+\r\r/4*/\n" + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + + var buf bytes.Buffer + Fprint(&buf, fset, f) + got := buf.String() + const want = "package p\t/*/1*\r/2*\r/3*+/4*/\n" // \r following opening /* should be stripped + if got != want { + t.Errorf("\ngot : %q\nwant: %q", got, want) + } + + // the resulting program must be valid + _, err = parser.ParseFile(fset, "", got, 0) + if err != nil { + t.Errorf("%v\norig: %q\ngot : %q", err, src, got) + } +} + +// If a declaration has multiple specifications, a parenthesized +// declaration must be printed even if Lparen is token.NoPos. +func TestParenthesizedDecl(t *testing.T) { + // a package with multiple specs in a single declaration + const src = "package p; var ( a float64; b int )" + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + // print the original package + var buf bytes.Buffer + err = Fprint(&buf, fset, f) + if err != nil { + t.Fatal(err) + } + original := buf.String() + + // now remove parentheses from the declaration + for i := 0; i != len(f.Decls); i++ { + f.Decls[i].(*ast.GenDecl).Lparen = token.NoPos + } + buf.Reset() + err = Fprint(&buf, fset, f) + if err != nil { + t.Fatal(err) + } + noparen := buf.String() + + if noparen != original { + t.Errorf("got %q, want %q", noparen, original) + } +} + +// Verify that we don't print a newline between "return" and its results, as +// that would incorrectly cause a naked return. +func TestIssue32854(t *testing.T) { + src := `package foo + +func f() { + return Composite{ + call(), + } +}` + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + panic(err) + } + + // Replace the result with call(), which is on the next line. + fd := file.Decls[0].(*ast.FuncDecl) + ret := fd.Body.List[0].(*ast.ReturnStmt) + ret.Results[0] = ret.Results[0].(*ast.CompositeLit).Elts[0] + + var buf bytes.Buffer + if err := Fprint(&buf, fset, ret); err != nil { + t.Fatal(err) + } + want := "return call()" + if got := buf.String(); got != want { + t.Fatalf("got %q, want %q", got, want) + } +} diff --git a/src/go/printer/testdata/alignment.golden b/src/go/printer/testdata/alignment.golden new file mode 100644 index 0000000..96086ed --- /dev/null +++ b/src/go/printer/testdata/alignment.golden @@ -0,0 +1,172 @@ +// Copyright 2018 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 alignment + +// ---------------------------------------------------------------------------- +// Examples from issue #7335. + +func main() { + z := MyStruct{ + Foo: "foo", + Bar: "bar", + Name: "name", + LongName: "longname", + Baz: "baz", + } + y := MyStruct{ + Foo: "foo", + Bar: "bar", + NameXX: "name", + LongNameXXXXXXXXXXXXX: "longname", + Baz: "baz", + } + z := MyStruct{ + Foo: "foo", + Bar: "bar", + Name: "name", + LongNameXXXXXXXXXXXXX: "longname", + Baz: "baz", + } +} + +// ---------------------------------------------------------------------------- +// Examples from issue #10392. + +var kcfg = KubeletConfig{ + Address: s.Address, + AllowPrivileged: s.AllowPrivileged, + HostNetworkSources: hostNetworkSources, + HostnameOverride: s.HostnameOverride, + RootDirectory: s.RootDirectory, + ConfigFile: s.Config, + ManifestURL: s.ManifestURL, + FileCheckFrequency: s.FileCheckFrequency, + HTTPCheckFrequency: s.HTTPCheckFrequency, + PodInfraContainerImage: s.PodInfraContainerImage, + SyncFrequency: s.SyncFrequency, + RegistryPullQPS: s.RegistryPullQPS, + RegistryBurst: s.RegistryBurst, + MinimumGCAge: s.MinimumGCAge, + MaxPerPodContainerCount: s.MaxPerPodContainerCount, + MaxContainerCount: s.MaxContainerCount, + ClusterDomain: s.ClusterDomain, + ClusterDNS: s.ClusterDNS, + Runonce: s.RunOnce, + Port: s.Port, + ReadOnlyPort: s.ReadOnlyPort, + CadvisorInterface: cadvisorInterface, + EnableServer: s.EnableServer, + EnableDebuggingHandlers: s.EnableDebuggingHandlers, + DockerClient: dockertools.ConnectToDockerOrDie(s.DockerEndpoint), + KubeClient: client, + MasterServiceNamespace: s.MasterServiceNamespace, + VolumePlugins: ProbeVolumePlugins(), + NetworkPlugins: ProbeNetworkPlugins(), + NetworkPluginName: s.NetworkPluginName, + StreamingConnectionIdleTimeout: s.StreamingConnectionIdleTimeout, + TLSOptions: tlsOptions, + ImageGCPolicy: imageGCPolicy, imageGCPolicy, + Cloud: cloud, + NodeStatusUpdateFrequency: s.NodeStatusUpdateFrequency, +} + +var a = A{ + Long: 1, + LongLong: 1, + LongLongLong: 1, + LongLongLongLong: 1, + LongLongLongLongLong: 1, + LongLongLongLongLongLong: 1, + LongLongLongLongLongLongLong: 1, + LongLongLongLongLongLongLongLong: 1, + Short: 1, + LongLongLongLongLongLongLongLongLong: 3, +} + +// ---------------------------------------------------------------------------- +// Examples from issue #22852. + +var fmtMap = map[string]string{ + "1": "123", + "12": "123", + "123": "123", + "1234": "123", + "12345": "123", + "123456": "123", + "12345678901234567890123456789": "123", + "abcde": "123", + "123456789012345678901234567890": "123", + "1234567": "123", + "abcdefghijklmnopqrstuvwxyzabcd": "123", + "abcd": "123", +} + +type Fmt struct { + abcdefghijklmnopqrstuvwx string + abcdefghijklmnopqrstuvwxy string + abcdefghijklmnopqrstuvwxyz string + abcdefghijklmnopqrstuvwxyza string + abcdefghijklmnopqrstuvwxyzab string + abcdefghijklmnopqrstuvwxyzabc string + abcde string + abcdefghijklmnopqrstuvwxyzabcde string + abcdefg string +} + +func main() { + _ := Fmt{ + abcdefghijklmnopqrstuvwx: "foo", + abcdefghijklmnopqrstuvwxyza: "foo", + abcdefghijklmnopqrstuvwxyzab: "foo", + abcdefghijklmnopqrstuvwxyzabc: "foo", + abcde: "foo", + abcdefghijklmnopqrstuvwxyzabcde: "foo", + abcdefg: "foo", + abcdefghijklmnopqrstuvwxy: "foo", + abcdefghijklmnopqrstuvwxyz: "foo", + } +} + +// ---------------------------------------------------------------------------- +// Examples from issue #26352. + +var _ = map[int]string{ + 1: "", + + 12345678901234567890123456789: "", + 12345678901234567890123456789012345678: "", +} + +func f() { + _ = map[int]string{ + 1: "", + + 12345678901234567: "", + 12345678901234567890123456789012345678901: "", + } +} + +// ---------------------------------------------------------------------------- +// Examples from issue #26930. + +var _ = S{ + F1: []string{}, + F2____: []string{}, +} + +var _ = S{ + F1: []string{}, + F2____: []string{}, +} + +var _ = S{ + F1____: []string{}, + F2: []string{}, +} + +var _ = S{ + F1____: []string{}, + F2: []string{}, +} diff --git a/src/go/printer/testdata/alignment.input b/src/go/printer/testdata/alignment.input new file mode 100644 index 0000000..323d268 --- /dev/null +++ b/src/go/printer/testdata/alignment.input @@ -0,0 +1,179 @@ +// Copyright 2018 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 alignment + +// ---------------------------------------------------------------------------- +// Examples from issue #7335. + +func main() { + z := MyStruct{ + Foo: "foo", + Bar: "bar", + Name: "name", + LongName: "longname", + Baz: "baz", + } + y := MyStruct{ + Foo: "foo", + Bar: "bar", + NameXX: "name", + LongNameXXXXXXXXXXXXX: "longname", + Baz: "baz", + } + z := MyStruct{ + Foo: "foo", + Bar: "bar", + Name: "name", + LongNameXXXXXXXXXXXXX: "longname", + Baz: "baz", + } +} + +// ---------------------------------------------------------------------------- +// Examples from issue #10392. + +var kcfg = KubeletConfig{ + Address: s.Address, + AllowPrivileged: s.AllowPrivileged, + HostNetworkSources: hostNetworkSources, + HostnameOverride: s.HostnameOverride, + RootDirectory: s.RootDirectory, + ConfigFile: s.Config, + ManifestURL: s.ManifestURL, + FileCheckFrequency: s.FileCheckFrequency, + HTTPCheckFrequency: s.HTTPCheckFrequency, + PodInfraContainerImage: s.PodInfraContainerImage, + SyncFrequency: s.SyncFrequency, + RegistryPullQPS: s.RegistryPullQPS, + RegistryBurst: s.RegistryBurst, + MinimumGCAge: s.MinimumGCAge, + MaxPerPodContainerCount: s.MaxPerPodContainerCount, + MaxContainerCount: s.MaxContainerCount, + ClusterDomain: s.ClusterDomain, + ClusterDNS: s.ClusterDNS, + Runonce: s.RunOnce, + Port: s.Port, + ReadOnlyPort: s.ReadOnlyPort, + CadvisorInterface: cadvisorInterface, + EnableServer: s.EnableServer, + EnableDebuggingHandlers: s.EnableDebuggingHandlers, + DockerClient: dockertools.ConnectToDockerOrDie(s.DockerEndpoint), + KubeClient: client, + MasterServiceNamespace: s.MasterServiceNamespace, + VolumePlugins: ProbeVolumePlugins(), + NetworkPlugins: ProbeNetworkPlugins(), + NetworkPluginName: s.NetworkPluginName, + StreamingConnectionIdleTimeout: s.StreamingConnectionIdleTimeout, + TLSOptions: tlsOptions, + ImageGCPolicy: imageGCPolicy,imageGCPolicy, + Cloud: cloud, + NodeStatusUpdateFrequency: s.NodeStatusUpdateFrequency, +} + +var a = A{ + Long: 1, + LongLong: 1, + LongLongLong: 1, + LongLongLongLong: 1, + LongLongLongLongLong: 1, + LongLongLongLongLongLong: 1, + LongLongLongLongLongLongLong: 1, + LongLongLongLongLongLongLongLong: 1, + Short: 1, + LongLongLongLongLongLongLongLongLong: 3, +} + +// ---------------------------------------------------------------------------- +// Examples from issue #22852. + +var fmtMap = map[string]string{ + "1": "123", + "12": "123", + "123": "123", + "1234": "123", + "12345": "123", + "123456": "123", + "12345678901234567890123456789": "123", + "abcde": "123", + "123456789012345678901234567890": "123", + "1234567": "123", + "abcdefghijklmnopqrstuvwxyzabcd": "123", + "abcd": "123", +} + +type Fmt struct { + abcdefghijklmnopqrstuvwx string + abcdefghijklmnopqrstuvwxy string + abcdefghijklmnopqrstuvwxyz string + abcdefghijklmnopqrstuvwxyza string + abcdefghijklmnopqrstuvwxyzab string + abcdefghijklmnopqrstuvwxyzabc string + abcde string + abcdefghijklmnopqrstuvwxyzabcde string + abcdefg string +} + +func main() { + _ := Fmt{ + abcdefghijklmnopqrstuvwx: "foo", + abcdefghijklmnopqrstuvwxyza: "foo", + abcdefghijklmnopqrstuvwxyzab: "foo", + abcdefghijklmnopqrstuvwxyzabc: "foo", + abcde: "foo", + abcdefghijklmnopqrstuvwxyzabcde: "foo", + abcdefg: "foo", + abcdefghijklmnopqrstuvwxy: "foo", + abcdefghijklmnopqrstuvwxyz: "foo", + } +} + +// ---------------------------------------------------------------------------- +// Examples from issue #26352. + +var _ = map[int]string{ + 1: "", + + 12345678901234567890123456789: "", + 12345678901234567890123456789012345678: "", +} + +func f() { + _ = map[int]string{ + 1: "", + + 12345678901234567: "", + 12345678901234567890123456789012345678901: "", + } +} + +// ---------------------------------------------------------------------------- +// Examples from issue #26930. + +var _ = S{ + F1: []string{ + }, + F2____: []string{}, +} + +var _ = S{ + F1: []string{ + + + }, + F2____: []string{}, +} + +var _ = S{ + F1____: []string{ + }, + F2: []string{}, +} + +var _ = S{ + F1____: []string{ + + }, + F2: []string{}, +} diff --git a/src/go/printer/testdata/comments.golden b/src/go/printer/testdata/comments.golden new file mode 100644 index 0000000..1a21fff --- /dev/null +++ b/src/go/printer/testdata/comments.golden @@ -0,0 +1,759 @@ +// Copyright 2009 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. + +// This is a package for testing comment placement by go/printer. +// +package main + +import "fmt" // fmt + +const c0 = 0 // zero +const ( + c1 = iota // c1 + c2 // c2 +) + +// Alignment of comments in declarations> +const ( + _ T = iota // comment + _ // comment + _ // comment + _ = iota + 10 + _ // comments + + _ = 10 // comment + _ T = 20 // comment +) + +const ( + _____ = iota // foo + _ // bar + _ = 0 // bal + _ // bat +) + +const ( + _ T = iota // comment + _ // comment + _ // comment + _ = iota + 10 + _ // comment + _ = 10 + _ = 20 // comment + _ T = 0 // comment +) + +// The SZ struct; it is empty. +type SZ struct{} + +// The S0 struct; no field is exported. +type S0 struct { + int + x, y, z int // 3 unexported fields +} + +// The S1 struct; some fields are not exported. +type S1 struct { + S0 + A, B, C float // 3 exported fields + D, b, c int // 2 unexported fields +} + +// The S2 struct; all fields are exported. +type S2 struct { + S1 + A, B, C float // 3 exported fields +} + +// The IZ interface; it is empty. +type SZ interface{} + +// The I0 interface; no method is exported. +type I0 interface { + f(x int) int // unexported method +} + +// The I1 interface; some methods are not exported. +type I1 interface { + I0 + F(x float) float // exported methods + g(x int) int // unexported method +} + +// The I2 interface; all methods are exported. +type I2 interface { + I0 + F(x float) float // exported method + G(x float) float // exported method +} + +// The S3 struct; all comments except for the last one must appear in the export. +type S3 struct { + // lead comment for F1 + F1 int // line comment for F1 + // lead comment for F2 + F2 int // line comment for F2 + f3 int // f3 is not exported +} + +// This comment group should be separated +// with a newline from the next comment +// group. + +// This comment should NOT be associated with the next declaration. + +var x int // x +var () + +// This comment SHOULD be associated with f0. +func f0() { + const pi = 3.14 // pi + var s1 struct{} /* an empty struct */ /* foo */ + // a struct constructor + // -------------------- + var s2 struct{} = struct{}{} + x := pi +} + +// +// This comment should be associated with f1, with one blank line before the comment. +// +func f1() { + f0() + /* 1 */ + // 2 + /* 3 */ + /* 4 */ + f0() +} + +func _() { + // this comment should be properly indented +} + +func _(x int) int { + if x < 0 { // the tab printed before this comment's // must not affect the remaining lines + return -x // this statement should be properly indented + } + if x < 0 { /* the tab printed before this comment's /* must not affect the remaining lines */ + return -x // this statement should be properly indented + } + return x +} + +func typeswitch(x interface{}) { + switch v := x.(type) { + case bool, int, float: + case string: + default: + } + + switch x.(type) { + } + + switch v0, ok := x.(int); v := x.(type) { + } + + switch v0, ok := x.(int); x.(type) { + case byte: // this comment should be on the same line as the keyword + // this comment should be normally indented + _ = 0 + case bool, int, float: + // this comment should be indented + case string: + default: + // this comment should be indented + } + // this comment should not be indented +} + +// +// Indentation of comments after possibly indented multi-line constructs +// (test cases for issue 3147). +// + +func _() { + s := 1 + + 2 + // should be indented like s +} + +func _() { + s := 1 + + 2 // comment + // should be indented like s +} + +func _() { + s := 1 + + 2 // comment + // should be indented like s + _ = 0 +} + +func _() { + s := 1 + + 2 + // should be indented like s + _ = 0 +} + +func _() { + s := 1 + + 2 + + // should be indented like s +} + +func _() { + s := 1 + + 2 // comment + + // should be indented like s +} + +func _() { + s := 1 + + 2 // comment + + // should be indented like s + _ = 0 +} + +func _() { + s := 1 + + 2 + + // should be indented like s + _ = 0 +} + +// Test case from issue 3147. +func f() { + templateText := "a" + // A + "b" + // B + "c" // C + + // should be aligned with f() + f() +} + +// Modified test case from issue 3147. +func f() { + templateText := "a" + // A + "b" + // B + "c" // C + + // may not be aligned with f() (source is not aligned) + f() +} + +// +// Test cases for alignment of lines in general comments. +// + +func _() { + /* freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line + aligned line */ +} + +func _() { + /* freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line + aligned line */ +} + +func _() { + /* + freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line + aligned line */ +} + +func _() { + /* + freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line + aligned line */ +} + +func _() { + /* freestanding comment + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line */ +} + +func _() { + /* freestanding comment + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line */ +} + +func _() { + /* + freestanding comment + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line */ +} + +func _() { + /* + freestanding comment + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line */ +} + +// Issue 9751. +func _() { + /*a string + + b string*/ + + /*A string + + + + Z string*/ + + /*a string + + b string + + c string*/ + + { + /*a string + b string*/ + + /*a string + + b string*/ + + /*a string + + b string + + c string*/ + } + + { + /*a string + b string*/ + + /*a string + + b string*/ + + /*a string + + b string + + c string*/ + } + + /* + */ + + /* + + */ + + /* + + * line + + */ +} + +/* + * line + * of + * stars + */ + +/* another line + * of + * stars */ + +/* and another line + * of + * stars */ + +/* a line of + * stars */ + +/* and another line of + * stars */ + +/* a line of stars + */ + +/* and another line of + */ + +/* a line of stars + */ + +/* and another line of + */ + +/* +aligned in middle +here + not here +*/ + +/* +blank line in middle: + +with no leading spaces on blank line. +*/ + +/* + aligned in middle + here + not here +*/ + +/* + blank line in middle: + + with no leading spaces on blank line. +*/ + +func _() { + /* + * line + * of + * stars + */ + + /* + aligned in middle + here + not here + */ + + /* + blank line in middle: + + with no leading spaces on blank line. + */ +} + +// Some interesting interspersed comments. +// See below for more common cases. +func _( /* this */ x /* is */ /* an */ int) { +} + +func _( /* no params - extra blank before and after comment */ ) {} +func _(a, b int /* params - no extra blank after comment */) {} + +func _() { f( /* no args - extra blank before and after comment */ ) } +func _() { f(a, b /* args - no extra blank after comment */) } + +func _() { + f( /* no args - extra blank before and after comment */ ) + f(a, b /* args - no extra blank after comment */) +} + +func ( /* comment1 */ T /* comment2 */) _() {} + +func _() { /* "short-ish one-line functions with comments are formatted as multi-line functions */ } +func _() { x := 0; /* comment */ y = x /* comment */ } + +func _() { + _ = 0 + /* closing curly brace should be on new line */ +} + +func _() { + _ = []int{0, 1 /* don't introduce a newline after this comment - was issue 1365 */} +} + +// Test cases from issue 1542: +// Comments must not be placed before commas and cause invalid programs. +func _() { + var a = []int{1, 2 /*jasldf*/} + _ = a +} + +func _() { + var a = []int{1, 2}/*jasldf + */ + + _ = a +} + +func _() { + var a = []int{1, 2}// jasldf + + _ = a +} + +// Test cases from issues 11274, 15137: +// Semicolon must not be lost when multiple statements are on the same line with a comment. +func _() { + x := 0 /**/ + y := 1 +} + +func _() { + f() + f() + f() /* comment */ + f() + f() /* comment */ + f() + f() /* a */ /* b */ + f() + f() /* a */ /* b */ + f() + f() /* a */ /* b */ + f() +} + +func _() { + f() /* a */ /* b */ +} + +// Comments immediately adjacent to punctuation followed by a newline +// remain after the punctuation (looks better and permits alignment of +// comments). +func _() { + _ = T{ + 1, // comment after comma + 2, /* comment after comma */ + 3, // comment after comma + } + _ = T{ + 1, // comment after comma + 2, /* comment after comma */ + 3, // comment after comma + } + _ = T{ + /* comment before literal */ 1, + 2, /* comment before comma - ok to move after comma */ + 3, /* comment before comma - ok to move after comma */ + } + + for i = 0; // comment after semicolon + i < 9; /* comment after semicolon */ + i++ { // comment after opening curly brace + } + + // TODO(gri) the last comment in this example should be aligned */ + for i = 0; // comment after semicolon + i < 9; /* comment before semicolon - ok to move after semicolon */ + i++ /* comment before opening curly brace */ { + } +} + +// If there is no newline following punctuation, commas move before the punctuation. +// This way, commas interspersed in lists stay with the respective expression. +func f(x /* comment */, y int, z int /* comment */, u, v, w int /* comment */) { + f(x /* comment */, y) + f(x, /* comment */ + y) + f( + x, /* comment */ + ) +} + +func g( + x int, /* comment */ +) { +} + +type _ struct { + a, b /* comment */, c int +} + +type _ struct { + a, b /* comment */, c int +} + +func _() { + for a /* comment */, b := range x { + } +} + +// Print line directives correctly. + +// The following is a legal line directive. +//line foo:1 +func _() { + _ = 0 + // The following is a legal line directive. It must not be indented: +//line foo:2 + _ = 1 + + // The following is not a legal line directive (it doesn't start in column 1): + //line foo:2 + _ = 2 + + // The following is not a legal line directive (missing colon): +//line foo -3 + _ = 3 +} + +// Line comments with tabs +func _() { + var finput *bufio.Reader // input file + var stderr *bufio.Writer + var ftable *bufio.Writer // y.go file + var foutput *bufio.Writer // y.output file + + var oflag string // -o [y.go] - y.go file + var vflag string // -v [y.output] - y.output file + var lflag bool // -l - disable line directives +} + +// Trailing white space in comments should be trimmed +func _() { + // This comment has 4 blanks following that should be trimmed: + /* Each line of this comment has blanks or tabs following that should be trimmed: + line 2: + line 3: + */ +} + +var _ = []T{ /* lone comment */ } + +var _ = []T{ + /* lone comment */ +} + +var _ = []T{ + // lone comments + // in composite lit +} + +var _ = [][]T{ + { + // lone comments + // in composite lit + }, +} + +// TODO: gofmt doesn't add these tabs; make it so that these golden +// tests run the printer in a way that it's exactly like gofmt. + +var _ = []T{ // lone comment +} + +var _ = []T{ // lone comments + // in composite lit +} + +/* This comment is the last entry in this file. It must be printed and should be followed by a newline */ diff --git a/src/go/printer/testdata/comments.input b/src/go/printer/testdata/comments.input new file mode 100644 index 0000000..aa428a2 --- /dev/null +++ b/src/go/printer/testdata/comments.input @@ -0,0 +1,756 @@ +// Copyright 2009 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. + +// This is a package for testing comment placement by go/printer. +// +package main + +import "fmt" // fmt + +const c0 = 0 // zero +const ( + c1 = iota // c1 + c2 // c2 +) + +// Alignment of comments in declarations> +const ( + _ T = iota // comment + _ // comment + _ // comment + _ = iota+10 + _ // comments + + _ = 10 // comment + _ T = 20 // comment +) + +const ( + _____ = iota // foo + _ // bar + _ = 0 // bal + _ // bat +) + +const ( + _ T = iota // comment + _ // comment + _ // comment + _ = iota + 10 + _ // comment + _ = 10 + _ = 20 // comment + _ T = 0 // comment +) + +// The SZ struct; it is empty. +type SZ struct {} + +// The S0 struct; no field is exported. +type S0 struct { + int + x, y, z int // 3 unexported fields +} + +// The S1 struct; some fields are not exported. +type S1 struct { + S0 + A, B, C float // 3 exported fields + D, b, c int // 2 unexported fields +} + +// The S2 struct; all fields are exported. +type S2 struct { + S1 + A, B, C float // 3 exported fields +} + +// The IZ interface; it is empty. +type SZ interface {} + +// The I0 interface; no method is exported. +type I0 interface { + f(x int) int // unexported method +} + +// The I1 interface; some methods are not exported. +type I1 interface { + I0 + F(x float) float // exported methods + g(x int) int // unexported method +} + +// The I2 interface; all methods are exported. +type I2 interface { + I0 + F(x float) float // exported method + G(x float) float // exported method +} + +// The S3 struct; all comments except for the last one must appear in the export. +type S3 struct { + // lead comment for F1 + F1 int // line comment for F1 + // lead comment for F2 + F2 int // line comment for F2 + f3 int // f3 is not exported +} + +// This comment group should be separated +// with a newline from the next comment +// group. + +// This comment should NOT be associated with the next declaration. + +var x int // x +var () + + +// This comment SHOULD be associated with f0. +func f0() { + const pi = 3.14 // pi + var s1 struct {} /* an empty struct */ /* foo */ + // a struct constructor + // -------------------- + var s2 struct {} = struct {}{} + x := pi +} +// +// This comment should be associated with f1, with one blank line before the comment. +// +func f1() { + f0() + /* 1 */ + // 2 + /* 3 */ + /* 4 */ + f0() +} + + +func _() { +// this comment should be properly indented +} + + +func _(x int) int { + if x < 0 { // the tab printed before this comment's // must not affect the remaining lines + return -x // this statement should be properly indented + } + if x < 0 { /* the tab printed before this comment's /* must not affect the remaining lines */ + return -x // this statement should be properly indented + } + return x +} + + +func typeswitch(x interface{}) { + switch v := x.(type) { + case bool, int, float: + case string: + default: + } + + switch x.(type) { + } + + switch v0, ok := x.(int); v := x.(type) { + } + + switch v0, ok := x.(int); x.(type) { + case byte: // this comment should be on the same line as the keyword + // this comment should be normally indented + _ = 0 + case bool, int, float: + // this comment should be indented + case string: + default: + // this comment should be indented + } + // this comment should not be indented +} + +// +// Indentation of comments after possibly indented multi-line constructs +// (test cases for issue 3147). +// + +func _() { + s := 1 + + 2 +// should be indented like s +} + +func _() { + s := 1 + + 2 // comment + // should be indented like s +} + +func _() { + s := 1 + + 2 // comment + // should be indented like s + _ = 0 +} + +func _() { + s := 1 + + 2 + // should be indented like s + _ = 0 +} + +func _() { + s := 1 + + 2 + +// should be indented like s +} + +func _() { + s := 1 + + 2 // comment + + // should be indented like s +} + +func _() { + s := 1 + + 2 // comment + + // should be indented like s + _ = 0 +} + +func _() { + s := 1 + + 2 + + // should be indented like s + _ = 0 +} + +// Test case from issue 3147. +func f() { + templateText := "a" + // A + "b" + // B + "c" // C + + // should be aligned with f() + f() +} + +// Modified test case from issue 3147. +func f() { + templateText := "a" + // A + "b" + // B + "c" // C + + // may not be aligned with f() (source is not aligned) + f() +} + +// +// Test cases for alignment of lines in general comments. +// + +func _() { + /* freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line + aligned line */ +} + +func _() { + /* freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line + aligned line */ +} + + +func _() { + /* + freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line + aligned line */ +} + +func _() { + /* + freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line + aligned line */ +} + +func _() { + /* freestanding comment + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line */ +} + +func _() { + /* freestanding comment + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line + */ +} + +func _() { + /* freestanding comment + aligned line */ +} + + +func _() { + /* + freestanding comment + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line */ +} + +func _() { + /* + freestanding comment + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line + */ +} + +func _() { + /* + freestanding comment + aligned line */ +} + +// Issue 9751. +func _() { + /*a string + + b string*/ + + /*A string + + + + Z string*/ + + /*a string + + b string + + c string*/ + + { + /*a string +b string*/ + + /*a string + +b string*/ + + /*a string + +b string + +c string*/ + } + + { + /*a string + b string*/ + + /*a string + + b string*/ + + /*a string + + b string + + c string*/ + } + + /* + */ + + /* + + */ + + /* + + * line + + */ +} + +/* + * line + * of + * stars + */ + +/* another line + * of + * stars */ + +/* and another line + * of + * stars */ + +/* a line of + * stars */ + +/* and another line of + * stars */ + +/* a line of stars +*/ + +/* and another line of +*/ + +/* a line of stars + */ + +/* and another line of + */ + +/* +aligned in middle +here + not here +*/ + +/* +blank line in middle: + +with no leading spaces on blank line. +*/ + +/* + aligned in middle + here + not here +*/ + +/* + blank line in middle: + + with no leading spaces on blank line. +*/ + +func _() { + /* + * line + * of + * stars + */ + + /* + aligned in middle + here + not here + */ + + /* + blank line in middle: + + with no leading spaces on blank line. +*/ +} + + +// Some interesting interspersed comments. +// See below for more common cases. +func _(/* this */x/* is *//* an */ int) { +} + +func _(/* no params - extra blank before and after comment */) {} +func _(a, b int /* params - no extra blank after comment */) {} + +func _() { f(/* no args - extra blank before and after comment */) } +func _() { f(a, b /* args - no extra blank after comment */) } + +func _() { + f(/* no args - extra blank before and after comment */) + f(a, b /* args - no extra blank after comment */) +} + +func (/* comment1 */ T /* comment2 */) _() {} + +func _() { /* "short-ish one-line functions with comments are formatted as multi-line functions */ } +func _() { x := 0; /* comment */ y = x /* comment */ } + +func _() { + _ = 0 + /* closing curly brace should be on new line */ } + +func _() { + _ = []int{0, 1 /* don't introduce a newline after this comment - was issue 1365 */} +} + +// Test cases from issue 1542: +// Comments must not be placed before commas and cause invalid programs. +func _() { + var a = []int{1, 2, /*jasldf*/ + } + _ = a +} + +func _() { + var a = []int{1, 2, /*jasldf + */ + } + _ = a +} + +func _() { + var a = []int{1, 2, // jasldf + } + _ = a +} + +// Test cases from issues 11274, 15137: +// Semicolon must not be lost when multiple statements are on the same line with a comment. +func _() { + x := 0 /**/; y := 1 +} + +func _() { + f(); f() + f(); /* comment */ f() + f() /* comment */; f() + f(); /* a */ /* b */ f() + f() /* a */ /* b */; f() + f() /* a */; /* b */ f() +} + +func _() { + f() /* a */ /* b */ } + +// Comments immediately adjacent to punctuation followed by a newline +// remain after the punctuation (looks better and permits alignment of +// comments). +func _() { + _ = T{ + 1, // comment after comma + 2, /* comment after comma */ + 3 , // comment after comma + } + _ = T{ + 1 ,// comment after comma + 2 ,/* comment after comma */ + 3,// comment after comma + } + _ = T{ + /* comment before literal */1, + 2/* comment before comma - ok to move after comma */, + 3 /* comment before comma - ok to move after comma */ , + } + + for + i=0;// comment after semicolon + i<9;/* comment after semicolon */ + i++{// comment after opening curly brace + } + + // TODO(gri) the last comment in this example should be aligned */ + for + i=0;// comment after semicolon + i<9/* comment before semicolon - ok to move after semicolon */; + i++ /* comment before opening curly brace */ { + } +} + +// If there is no newline following punctuation, commas move before the punctuation. +// This way, commas interspersed in lists stay with the respective expression. +func f(x/* comment */, y int, z int /* comment */, u, v, w int /* comment */) { + f(x /* comment */, y) + f(x /* comment */, + y) + f( + x /* comment */, + ) +} + +func g( + x int /* comment */, +) {} + +type _ struct { + a, b /* comment */, c int +} + +type _ struct { a, b /* comment */, c int } + +func _() { + for a /* comment */, b := range x { + } +} + +// Print line directives correctly. + +// The following is a legal line directive. +//line foo:1 +func _() { + _ = 0 +// The following is a legal line directive. It must not be indented: +//line foo:2 + _ = 1 + +// The following is not a legal line directive (it doesn't start in column 1): + //line foo:2 + _ = 2 + +// The following is not a legal line directive (missing colon): +//line foo -3 + _ = 3 +} + +// Line comments with tabs +func _() { +var finput *bufio.Reader // input file +var stderr *bufio.Writer +var ftable *bufio.Writer // y.go file +var foutput *bufio.Writer // y.output file + +var oflag string // -o [y.go] - y.go file +var vflag string // -v [y.output] - y.output file +var lflag bool // -l - disable line directives +} + +// Trailing white space in comments should be trimmed +func _() { +// This comment has 4 blanks following that should be trimmed: +/* Each line of this comment has blanks or tabs following that should be trimmed: + line 2: + line 3: +*/ +} + +var _ = []T{/* lone comment */} + +var _ = []T{ +/* lone comment */ +} + +var _ = []T{ +// lone comments +// in composite lit +} + +var _ = [][]T{ + { + // lone comments + // in composite lit + }, +} + +// TODO: gofmt doesn't add these tabs; make it so that these golden +// tests run the printer in a way that it's exactly like gofmt. + +var _ = []T{// lone comment +} + +var _ = []T{// lone comments +// in composite lit +} + +/* This comment is the last entry in this file. It must be printed and should be followed by a newline */ diff --git a/src/go/printer/testdata/comments.x b/src/go/printer/testdata/comments.x new file mode 100644 index 0000000..ae77292 --- /dev/null +++ b/src/go/printer/testdata/comments.x @@ -0,0 +1,56 @@ +// This is a package for testing comment placement by go/printer. +// +package main + +// The SZ struct; it is empty. +type SZ struct{} + +// The S0 struct; no field is exported. +type S0 struct { + // contains filtered or unexported fields +} + +// The S1 struct; some fields are not exported. +type S1 struct { + S0 + A, B, C float // 3 exported fields + D int // 2 unexported fields + // contains filtered or unexported fields +} + +// The S2 struct; all fields are exported. +type S2 struct { + S1 + A, B, C float // 3 exported fields +} + +// The IZ interface; it is empty. +type SZ interface{} + +// The I0 interface; no method is exported. +type I0 interface { + // contains filtered or unexported methods +} + +// The I1 interface; some methods are not exported. +type I1 interface { + I0 + F(x float) float // exported methods + // contains filtered or unexported methods +} + +// The I2 interface; all methods are exported. +type I2 interface { + I0 + F(x float) float // exported method + G(x float) float // exported method +} + +// The S3 struct; all comments except for the last one must appear in the export. +type S3 struct { + // lead comment for F1 + F1 int // line comment for F1 + // lead comment for F2 + F2 int // line comment for F2 + // contains filtered or unexported fields +} diff --git a/src/go/printer/testdata/comments2.golden b/src/go/printer/testdata/comments2.golden new file mode 100644 index 0000000..8b3a94d --- /dev/null +++ b/src/go/printer/testdata/comments2.golden @@ -0,0 +1,164 @@ +// Copyright 2012 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. + +// This is a package for testing comment placement by go/printer. +// +package main + +// Test cases for idempotent comment formatting (was issue 1835). +/* +c1a +*/ +/* + c1b +*/ +/* foo +c1c +*/ +/* foo + c1d +*/ +/* +c1e +foo */ +/* + c1f + foo */ + +func f() { + /* + c2a + */ + /* + c2b + */ + /* foo + c2c + */ + /* foo + c2d + */ + /* + c2e + foo */ + /* + c2f + foo */ +} + +func g() { + /* + c3a + */ + /* + c3b + */ + /* foo + c3c + */ + /* foo + c3d + */ + /* + c3e + foo */ + /* + c3f + foo */ +} + +// Test case taken literally from issue 1835. +func main() { + /* + prints test 5 times + */ + for i := 0; i < 5; i++ { + println("test") + } +} + +func issue5623() { +L: + _ = yyyyyyyyyyyyyyyy // comment - should be aligned + _ = xxxxxxxxxxxxxxxxxxxxxxxxxxxx /* comment */ + + _ = yyyyyyyyyyyyyyyy /* comment - should be aligned */ + _ = xxxxxxxxxxxxxxxxxxxxxxxxxxxx // comment + +LLLLLLL: + _ = yyyyyyyyyyyyyyyy // comment - should be aligned + _ = xxxxxxxxxxxxxxxxxxxxxxxxxxxx // comment + +LL: +LLLLL: + _ = xxxxxxxxxxxxxxxxxxxxxxxxxxxx /* comment */ + _ = yyyyyyyyyyyyyyyy /* comment - should be aligned */ + + _ = xxxxxxxxxxxxxxxxxxxxxxxxxxxx // comment + _ = yyyyyyyyyyyyyyyy // comment - should be aligned + + // test case from issue +label: + mask := uint64(1)<>4 + _ = "foo" + s + _ = s + "foo" + _ = 'a' + 'b' + _ = len(s) / 2 + _ = len(t0.x) / a + + // spaces around expressions of different precedence or expressions containing spaces + _ = a + -b + _ = a - ^b + _ = a / *p + _ = a + b*c + _ = 1 + b*c + _ = a + 2*c + _ = a + c*2 + _ = 1 + 2*3 + _ = s[1 : 2*3] + _ = s[a : b-c] + _ = s[0:] + _ = s[a+b] + _ = s[:b-c] + _ = s[a+b:] + _ = a[a< b + _ = a >= b + _ = a < b + _ = a <= b + _ = a < b && c > d + _ = a < b || c > d + + // spaces around "long" operands + _ = a + longIdentifier1 + _ = longIdentifier1 + a + _ = longIdentifier1 + longIdentifier2*longIdentifier3 + _ = s + "a longer string" + + // some selected cases + _ = a + t0.x + _ = a + t0.x + t1.x*t2.x + _ = a + b + c + d + e + 2*3 + _ = a + b + c + 2*3 + d + e + _ = (a + b + c) * 2 + _ = a - b + c - d + (a + b + c) + d&e + _ = under_bar - 1 + _ = Open(dpath+"/file", O_WRONLY|O_CREAT, 0666) + _ = int(c0&_Mask4)<<18 | int(c1&_Maskx)<<12 | int(c2&_Maskx)<<6 | int(c3&_Maskx) + + // test case for issue 8021 + // want: + // ([]bool{})[([]int{})[((1)+(((1)+((((1)*(((1)+(1))+(1)))+(1))*(1)))+(1)))]] + _ = ([]bool{})[([]int{})[((1)+(((1)+((((1)*(((1)+(1))+(1)))+(1))*(1)))+(1)))]] + + // the parser does not restrict expressions that may appear as statements + true + 42 + "foo" + x + (x) + a + b + a + b + c + a + (b * c) + a + (b / c) + 1 + a + a + 1 + s[a] + x << 1 + (s[0] << 1) & 0xf + "foo" + s + x == y + x < y || z > 42 +} + +// slice expressions with cap +func _() { + _ = x[a:b:c] + _ = x[a : b : c+d] + _ = x[a : b+d : c] + _ = x[a : b+d : c+d] + _ = x[a+d : b : c] + _ = x[a+d : b : c+d] + _ = x[a+d : b+d : c] + _ = x[a+d : b+d : c+d] + + _ = x[:b:c] + _ = x[: b : c+d] + _ = x[: b+d : c] + _ = x[: b+d : c+d] +} + +func issue22111() { + _ = x[:] + + _ = x[:b] + _ = x[:b+1] + + _ = x[a:] + _ = x[a+1:] + + _ = x[a:b] + _ = x[a+1 : b] + _ = x[a : b+1] + _ = x[a+1 : b+1] + + _ = x[:b:c] + _ = x[: b+1 : c] + _ = x[: b : c+1] + _ = x[: b+1 : c+1] + + _ = x[a:b:c] + _ = x[a+1 : b : c] + _ = x[a : b+1 : c] + _ = x[a+1 : b+1 : c] + _ = x[a : b : c+1] + _ = x[a+1 : b : c+1] + _ = x[a : b+1 : c+1] + _ = x[a+1 : b+1 : c+1] +} + +func _() { + _ = a + b + _ = a + b + c + _ = a + b*c + _ = a + (b * c) + _ = (a + b) * c + _ = a + (b * c * d) + _ = a + (b*c + d) + + _ = 1 << x + _ = -1 << x + _ = 1<>4 + + b.buf = b.buf[0 : b.off+m+n] + b.buf = b.buf[0 : b.off+m*n] + f(b.buf[0 : b.off+m+n]) + + signed += ' ' * 8 + tw.octal(header[148:155], chksum) + + _ = x > 0 && i >= 0 + + x1, x0 := x>>w2, x&m2 + z0 = t1<>w2) >> w2 + q1, r1 := x1/d1, x1%d1 + r1 = r1*b2 | x0>>w2 + x1 = (x1 << z) | (x0 >> (uint(w) - z)) + x1 = x1<>(uint(w)-z) + + _ = buf[0 : len(buf)+1] + _ = buf[0 : n+1] + + a, b = b, a + a = b + c + a = b*c + d + _ = a*b + c + _ = a - b - c + _ = a - (b - c) + _ = a - b*c + _ = a - (b * c) + _ = a * b / c + _ = a / *b + _ = x[a|^b] + _ = x[a / *b] + _ = a & ^b + _ = a + +b + _ = a - -b + _ = x[a*-b] + _ = x[a + +b] + _ = x ^ y ^ z + _ = b[a>>24] ^ b[(a>>16)&0xFF] ^ b[(a>>8)&0xFF] ^ b[a&0xFF] + _ = len(longVariableName) * 2 + + _ = token(matchType + xlength<> 4 + _ = "foo"+s + _ = s+"foo" + _ = 'a'+'b' + _ = len(s)/2 + _ = len(t0.x)/a + + // spaces around expressions of different precedence or expressions containing spaces + _ = a + -b + _ = a - ^b + _ = a / *p + _ = a + b*c + _ = 1 + b*c + _ = a + 2*c + _ = a + c*2 + _ = 1 + 2*3 + _ = s[1 : 2*3] + _ = s[a : b-c] + _ = s[0:] + _ = s[a+b] + _ = s[: b-c] + _ = s[a+b :] + _ = a[a< b + _ = a >= b + _ = a < b + _ = a <= b + _ = a < b && c > d + _ = a < b || c > d + + // spaces around "long" operands + _ = a + longIdentifier1 + _ = longIdentifier1 + a + _ = longIdentifier1 + longIdentifier2 * longIdentifier3 + _ = s + "a longer string" + + // some selected cases + _ = a + t0.x + _ = a + t0.x + t1.x * t2.x + _ = a + b + c + d + e + 2*3 + _ = a + b + c + 2*3 + d + e + _ = (a+b+c)*2 + _ = a - b + c - d + (a+b+c) + d&e + _ = under_bar-1 + _ = Open(dpath + "/file", O_WRONLY | O_CREAT, 0666) + _ = int(c0&_Mask4)<<18 | int(c1&_Maskx)<<12 | int(c2&_Maskx)<<6 | int(c3&_Maskx) + + // test case for issue 8021 + // want: + // ([]bool{})[([]int{})[((1)+(((1)+((((1)*(((1)+(1))+(1)))+(1))*(1)))+(1)))]] + _ = ([]bool{})[([]int{})[((1) + (((((1) + (((((((1) * (((((1) + (1))) + (1))))) + (1))) * (1))))) + (1))))]] + + // the parser does not restrict expressions that may appear as statements + true + 42 + "foo" + x + (x) + a+b + a+b+c + a+(b*c) + a+(b/c) + 1+a + a+1 + s[a] + x<<1 + (s[0]<<1)&0xf + "foo"+s + x == y + x < y || z > 42 +} + + +// slice expressions with cap +func _() { + _ = x[a:b:c] + _ = x[a:b:c+d] + _ = x[a:b+d:c] + _ = x[a:b+d:c+d] + _ = x[a+d:b:c] + _ = x[a+d:b:c+d] + _ = x[a+d:b+d:c] + _ = x[a+d:b+d:c+d] + + _ = x[:b:c] + _ = x[:b:c+d] + _ = x[:b+d:c] + _ = x[:b+d:c+d] +} + +func issue22111() { + _ = x[:] + + _ = x[:b] + _ = x[:b+1] + + _ = x[a:] + _ = x[a+1:] + + _ = x[a:b] + _ = x[a+1:b] + _ = x[a:b+1] + _ = x[a+1:b+1] + + _ = x[:b:c] + _ = x[:b+1:c] + _ = x[:b:c+1] + _ = x[:b+1:c+1] + + _ = x[a:b:c] + _ = x[a+1:b:c] + _ = x[a:b+1:c] + _ = x[a+1:b+1:c] + _ = x[a:b:c+1] + _ = x[a+1:b:c+1] + _ = x[a:b+1:c+1] + _ = x[a+1:b+1:c+1] +} + +func _() { + _ = a+b + _ = a+b+c + _ = a+b*c + _ = a+(b*c) + _ = (a+b)*c + _ = a+(b*c*d) + _ = a+(b*c+d) + + _ = 1<>4 + + b.buf = b.buf[0:b.off+m+n] + b.buf = b.buf[0:b.off+m*n] + f(b.buf[0:b.off+m+n]) + + signed += ' '*8 + tw.octal(header[148:155], chksum) + + _ = x > 0 && i >= 0 + + x1, x0 := x>>w2, x&m2 + z0 = t1<>w2)>>w2 + q1, r1 := x1/d1, x1%d1 + r1 = r1*b2 | x0>>w2 + x1 = (x1<>(uint(w)-z)) + x1 = x1<>(uint(w)-z) + + _ = buf[0:len(buf)+1] + _ = buf[0:n+1] + + a,b = b,a + a = b+c + a = b*c+d + _ = a*b+c + _ = a-b-c + _ = a-(b-c) + _ = a-b*c + _ = a-(b*c) + _ = a*b/c + _ = a/ *b + _ = x[a|^b] + _ = x[a/ *b] + _ = a& ^b + _ = a+ +b + _ = a- -b + _ = x[a*-b] + _ = x[a+ +b] + _ = x^y^z + _ = b[a>>24] ^ b[(a>>16)&0xFF] ^ b[(a>>8)&0xFF] ^ b[a&0xFF] + _ = len(longVariableName)*2 + + _ = token(matchType + xlength<>4 + _ = "foo" + s + _ = s + "foo" + _ = 'a' + 'b' + _ = len(s) / 2 + _ = len(t0.x) / a + + // spaces around expressions of different precedence or expressions containing spaces + _ = a + -b + _ = a - ^b + _ = a / *p + _ = a + b*c + _ = 1 + b*c + _ = a + 2*c + _ = a + c*2 + _ = 1 + 2*3 + _ = s[1 : 2*3] + _ = s[a : b-c] + _ = s[0:] + _ = s[a+b] + _ = s[:b-c] + _ = s[a+b:] + _ = a[a< b + _ = a >= b + _ = a < b + _ = a <= b + _ = a < b && c > d + _ = a < b || c > d + + // spaces around "long" operands + _ = a + longIdentifier1 + _ = longIdentifier1 + a + _ = longIdentifier1 + longIdentifier2*longIdentifier3 + _ = s + "a longer string" + + // some selected cases + _ = a + t0.x + _ = a + t0.x + t1.x*t2.x + _ = a + b + c + d + e + 2*3 + _ = a + b + c + 2*3 + d + e + _ = (a + b + c) * 2 + _ = a - b + c - d + (a + b + c) + d&e + _ = under_bar - 1 + _ = Open(dpath+"/file", O_WRONLY|O_CREAT, 0666) + _ = int(c0&_Mask4)<<18 | int(c1&_Maskx)<<12 | int(c2&_Maskx)<<6 | int(c3&_Maskx) + + // test case for issue 8021 + // want: + // ([]bool{})[([]int{})[((1)+(((1)+((((1)*(((1)+(1))+(1)))+(1))*(1)))+(1)))]] + _ = ([]bool{})[([]int{})[((1)+(((1)+((((1)*(((1)+(1))+(1)))+(1))*(1)))+(1)))]] + + // the parser does not restrict expressions that may appear as statements + true + 42 + "foo" + x + (x) + a + b + a + b + c + a + (b * c) + a + (b / c) + 1 + a + a + 1 + s[a] + x << 1 + (s[0] << 1) & 0xf + "foo" + s + x == y + x < y || z > 42 +} + +// slice expressions with cap +func _() { + _ = x[a:b:c] + _ = x[a : b : c+d] + _ = x[a : b+d : c] + _ = x[a : b+d : c+d] + _ = x[a+d : b : c] + _ = x[a+d : b : c+d] + _ = x[a+d : b+d : c] + _ = x[a+d : b+d : c+d] + + _ = x[:b:c] + _ = x[: b : c+d] + _ = x[: b+d : c] + _ = x[: b+d : c+d] +} + +func issue22111() { + _ = x[:] + + _ = x[:b] + _ = x[:b+1] + + _ = x[a:] + _ = x[a+1:] + + _ = x[a:b] + _ = x[a+1 : b] + _ = x[a : b+1] + _ = x[a+1 : b+1] + + _ = x[:b:c] + _ = x[: b+1 : c] + _ = x[: b : c+1] + _ = x[: b+1 : c+1] + + _ = x[a:b:c] + _ = x[a+1 : b : c] + _ = x[a : b+1 : c] + _ = x[a+1 : b+1 : c] + _ = x[a : b : c+1] + _ = x[a+1 : b : c+1] + _ = x[a : b+1 : c+1] + _ = x[a+1 : b+1 : c+1] +} + +func _() { + _ = a + b + _ = a + b + c + _ = a + b*c + _ = a + (b * c) + _ = (a + b) * c + _ = a + (b * c * d) + _ = a + (b*c + d) + + _ = 1 << x + _ = -1 << x + _ = 1<>4 + + b.buf = b.buf[0 : b.off+m+n] + b.buf = b.buf[0 : b.off+m*n] + f(b.buf[0 : b.off+m+n]) + + signed += ' ' * 8 + tw.octal(header[148:155], chksum) + + _ = x > 0 && i >= 0 + + x1, x0 := x>>w2, x&m2 + z0 = t1<>w2) >> w2 + q1, r1 := x1/d1, x1%d1 + r1 = r1*b2 | x0>>w2 + x1 = (x1 << z) | (x0 >> (uint(w) - z)) + x1 = x1<>(uint(w)-z) + + _ = buf[0 : len(buf)+1] + _ = buf[0 : n+1] + + a, b = b, a + a = b + c + a = b*c + d + _ = a*b + c + _ = a - b - c + _ = a - (b - c) + _ = a - b*c + _ = a - (b * c) + _ = a * b / c + _ = a / *b + _ = x[a|^b] + _ = x[a / *b] + _ = a & ^b + _ = a + +b + _ = a - -b + _ = x[a*-b] + _ = x[a + +b] + _ = x ^ y ^ z + _ = b[a>>24] ^ b[(a>>16)&0xFF] ^ b[(a>>8)&0xFF] ^ b[a&0xFF] + _ = len(longVariableName) * 2 + + _ = token(matchType + xlength< /tmp/16gig.txt + // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar + &writerTest{ + file: "testdata/writer-big.tar", + entries: []*writerTestEntry{ + &writerTestEntry{ + header: &Header{ + Name: "tmp/16gig.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 16 << 30, + Mtime: 1254699560, + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + }, + // no contents + }, + }, + }, +} + +type untarTest struct { + file string + headers []*Header +} + +var untarTests = []*untarTest{ + &untarTest{ + file: "testdata/gnu.tar", + headers: []*Header{ + &Header{ + Name: "small.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 5, + Mtime: 1244428340, + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + }, + &Header{ + Name: "small2.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 11, + Mtime: 1244436044, + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + }, + }, + }, + &untarTest{ + file: "testdata/star.tar", + headers: []*Header{ + &Header{ + Name: "small.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 5, + Mtime: 1244592783, + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + Atime: 1244592783, + Ctime: 1244592783, + }, + &Header{ + Name: "small2.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 11, + Mtime: 1244592783, + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + Atime: 1244592783, + Ctime: 1244592783, + }, + }, + }, + &untarTest{ + file: "testdata/v7.tar", + headers: []*Header{ + &Header{ + Name: "small.txt", + Mode: 0444, + Uid: 73025, + Gid: 5000, + Size: 5, + Mtime: 1244593104, + Typeflag: '\x00', + }, + &Header{ + Name: "small2.txt", + Mode: 0444, + Uid: 73025, + Gid: 5000, + Size: 11, + Mtime: 1244593104, + Typeflag: '\x00', + }, + }, + }, +} + +var facts = map[int]string{ + 0: "1", + 1: "1", + 2: "2", + 10: "3628800", + 20: "2432902008176640000", + 100: "933262154439441526816992388562667004907159682643816214685929" + + "638952175999932299156089414639761565182862536979208272237582" + + "51185210916864000000000000000000000000", +} + +func usage() { + fmt.Fprintf(os.Stderr, + // TODO(gri): the 2nd string of this string list should not be indented + "usage: godoc package [name ...]\n"+ + " godoc -http=:6060\n") + flag.PrintDefaults() + os.Exit(2) +} + +func TestReader(t *testing.T) { +testLoop: + for i, test := range untarTests { + f, err := os.Open(test.file, os.O_RDONLY, 0444) + if err != nil { + t.Errorf("test %d: Unexpected error: %v", i, err) + continue + } + tr := NewReader(f) + for j, header := range test.headers { + hdr, err := tr.Next() + if err != nil || hdr == nil { + t.Errorf("test %d, entry %d: Didn't get entry: %v", i, j, err) + f.Close() + continue testLoop + } + if !reflect.DeepEqual(hdr, header) { + t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v", + i, j, *hdr, *header) + } + } + hdr, err := tr.Next() + if hdr != nil || err != nil { + t.Errorf("test %d: Unexpected entry or error: hdr=%v err=%v", i, err) + } + f.Close() + } +} + +// Respect line breaks in function calls. +func _() { + f(x) + f(x, + x) + f(x, + x, + ) + f( + x, + x) + f( + x, + x, + ) +} + +// Respect line breaks in function declarations. +func _(x T) {} +func _(x T, + y T) { +} +func _(x T, + y T, +) { +} +func _( + x T, + y T) { +} +func _( + x T, + y T, +) { +} + +// Example from issue #2597. +func ManageStatus0( + in <-chan *Status, + req <-chan Request, + stat chan<- *TargetInfo, + TargetHistorySize int) { +} + +func ManageStatus1( + in <-chan *Status, + req <-chan Request, + stat chan<- *TargetInfo, + TargetHistorySize int, +) { +} + +// Example from issue #9064. +func (y *y) xerrors() error { + _ = "xerror.test" //TODO- + _ = []byte(` +foo bar foo bar foo bar +`) //TODO- +} + +func _() { + _ = "abc" // foo + _ = `abc_0123456789_` // foo +} + +func _() { + _ = "abc" // foo + _ = `abc +0123456789 +` // foo +} + +// There should be exactly one linebreak after this comment. diff --git a/src/go/printer/testdata/linebreaks.input b/src/go/printer/testdata/linebreaks.input new file mode 100644 index 0000000..9e714f3 --- /dev/null +++ b/src/go/printer/testdata/linebreaks.input @@ -0,0 +1,291 @@ +// Copyright 2009 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 linebreaks + +import ( + "bytes" + "fmt" + "io" + "os" + "reflect" + "strings" + "testing" +) + +type writerTestEntry struct { + header *Header + contents string +} + +type writerTest struct { + file string // filename of expected output + entries []*writerTestEntry +} + +var writerTests = []*writerTest{ + &writerTest{ + file: "testdata/writer.tar", + entries: []*writerTestEntry{ + &writerTestEntry{ + header: &Header{ + Name: "small.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 5, + Mtime: 1246508266, + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + }, + contents: "Kilts", + }, + &writerTestEntry{ + header: &Header{ + Name: "small2.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 11, + Mtime: 1245217492, + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + }, + contents: "Google.com\n", + }, + }, + }, + // The truncated test file was produced using these commands: + // dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt + // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar + &writerTest{ + file: "testdata/writer-big.tar", + entries: []*writerTestEntry{ + &writerTestEntry{ + header: &Header{ + Name: "tmp/16gig.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 16 << 30, + Mtime: 1254699560, + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + }, + // no contents + }, + }, + }, +} + +type untarTest struct { + file string + headers []*Header +} + +var untarTests = []*untarTest{ + &untarTest{ + file: "testdata/gnu.tar", + headers: []*Header{ + &Header{ + Name: "small.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 5, + Mtime: 1244428340, + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + }, + &Header{ + Name: "small2.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 11, + Mtime: 1244436044, + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + }, + }, + }, + &untarTest{ + file: "testdata/star.tar", + headers: []*Header{ + &Header{ + Name: "small.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 5, + Mtime: 1244592783, + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + Atime: 1244592783, + Ctime: 1244592783, + }, + &Header{ + Name: "small2.txt", + Mode: 0640, + Uid: 73025, + Gid: 5000, + Size: 11, + Mtime: 1244592783, + Typeflag: '0', + Uname: "dsymonds", + Gname: "eng", + Atime: 1244592783, + Ctime: 1244592783, + }, + }, + }, + &untarTest{ + file: "testdata/v7.tar", + headers: []*Header{ + &Header{ + Name: "small.txt", + Mode: 0444, + Uid: 73025, + Gid: 5000, + Size: 5, + Mtime: 1244593104, + Typeflag: '\x00', + }, + &Header{ + Name: "small2.txt", + Mode: 0444, + Uid: 73025, + Gid: 5000, + Size: 11, + Mtime: 1244593104, + Typeflag: '\x00', + }, + }, + }, +} + +var facts = map[int] string { + 0: "1", + 1: "1", + 2: "2", + 10: "3628800", + 20: "2432902008176640000", + 100: "933262154439441526816992388562667004907159682643816214685929" + + "638952175999932299156089414639761565182862536979208272237582" + + "51185210916864000000000000000000000000", +} + +func usage() { + fmt.Fprintf(os.Stderr, + // TODO(gri): the 2nd string of this string list should not be indented + "usage: godoc package [name ...]\n" + + " godoc -http=:6060\n") + flag.PrintDefaults() + os.Exit(2) +} + +func TestReader(t *testing.T) { +testLoop: + for i, test := range untarTests { + f, err := os.Open(test.file, os.O_RDONLY, 0444) + if err != nil { + t.Errorf("test %d: Unexpected error: %v", i, err) + continue + } + tr := NewReader(f) + for j, header := range test.headers { + hdr, err := tr.Next() + if err != nil || hdr == nil { + t.Errorf("test %d, entry %d: Didn't get entry: %v", i, j, err) + f.Close() + continue testLoop + } + if !reflect.DeepEqual(hdr, header) { + t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v", + i, j, *hdr, *header) + } + } + hdr, err := tr.Next() + if hdr != nil || err != nil { + t.Errorf("test %d: Unexpected entry or error: hdr=%v err=%v", i, err) + } + f.Close() + } +} + +// Respect line breaks in function calls. +func _() { + f(x) + f(x, + x) + f(x, + x, + ) + f( + x, + x) + f( + x, + x, + ) +} + +// Respect line breaks in function declarations. +func _(x T) {} +func _(x T, + y T) {} +func _(x T, + y T, +) {} +func _( + x T, + y T) {} +func _( + x T, + y T, +) {} + +// Example from issue #2597. +func ManageStatus0( + in <-chan *Status, + req <-chan Request, + stat chan<- *TargetInfo, + TargetHistorySize int) { +} + +func ManageStatus1( + in <-chan *Status, + req <-chan Request, + stat chan<- *TargetInfo, + TargetHistorySize int, +) { +} + +// Example from issue #9064. +func (y *y) xerrors() error { + _ = "xerror.test" //TODO- + _ = []byte(` +foo bar foo bar foo bar +`) //TODO- +} + +func _() { + _ = "abc" // foo + _ = `abc_0123456789_` // foo +} + +func _() { + _ = "abc" // foo + _ = `abc +0123456789 +` // foo +} + +// There should be exactly one linebreak after this comment. diff --git a/src/go/printer/testdata/parser.go b/src/go/printer/testdata/parser.go new file mode 100644 index 0000000..80b476c --- /dev/null +++ b/src/go/printer/testdata/parser.go @@ -0,0 +1,2153 @@ +// Copyright 2009 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 parser implements a parser for Go source files. Input may be +// provided in a variety of forms (see the various Parse* functions); the +// output is an abstract syntax tree (AST) representing the Go source. The +// parser is invoked through one of the Parse* functions. + +package parser + +import ( + "fmt" + "go/ast" + "go/scanner" + "go/token" +) + +// The mode parameter to the Parse* functions is a set of flags (or 0). +// They control the amount of source code parsed and other optional +// parser functionality. +// +const ( + PackageClauseOnly uint = 1 << iota // parsing stops after package clause + ImportsOnly // parsing stops after import declarations + ParseComments // parse comments and add them to AST + Trace // print a trace of parsed productions + DeclarationErrors // report declaration errors +) + +// The parser structure holds the parser's internal state. +type parser struct { + file *token.File + scanner.ErrorVector + scanner scanner.Scanner + + // Tracing/debugging + mode uint // parsing mode + trace bool // == (mode & Trace != 0) + indent uint // indentation used for tracing output + + // Comments + comments []*ast.CommentGroup + leadComment *ast.CommentGroup // last lead comment + lineComment *ast.CommentGroup // last line comment + + // Next token + pos token.Pos // token position + tok token.Token // one token look-ahead + lit string // token literal + + // Non-syntactic parser control + exprLev int // < 0: in control clause, >= 0: in expression + + // Ordinary identifier scopes + pkgScope *ast.Scope // pkgScope.Outer == nil + topScope *ast.Scope // top-most scope; may be pkgScope + unresolved []*ast.Ident // unresolved identifiers + imports []*ast.ImportSpec // list of imports + + // Label scope + // (maintained by open/close LabelScope) + labelScope *ast.Scope // label scope for current function + targetStack [][]*ast.Ident // stack of unresolved labels +} + +// scannerMode returns the scanner mode bits given the parser's mode bits. +func scannerMode(mode uint) uint { + var m uint = scanner.InsertSemis + if mode&ParseComments != 0 { + m |= scanner.ScanComments + } + return m +} + +func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode uint) { + p.file = fset.AddFile(filename, fset.Base(), len(src)) + p.scanner.Init(p.file, src, p, scannerMode(mode)) + + p.mode = mode + p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently) + + p.next() + + // set up the pkgScope here (as opposed to in parseFile) because + // there are other parser entry points (ParseExpr, etc.) + p.openScope() + p.pkgScope = p.topScope + + // for the same reason, set up a label scope + p.openLabelScope() +} + +// ---------------------------------------------------------------------------- +// Scoping support + +func (p *parser) openScope() { + p.topScope = ast.NewScope(p.topScope) +} + +func (p *parser) closeScope() { + p.topScope = p.topScope.Outer +} + +func (p *parser) openLabelScope() { + p.labelScope = ast.NewScope(p.labelScope) + p.targetStack = append(p.targetStack, nil) +} + +func (p *parser) closeLabelScope() { + // resolve labels + n := len(p.targetStack) - 1 + scope := p.labelScope + for _, ident := range p.targetStack[n] { + ident.Obj = scope.Lookup(ident.Name) + if ident.Obj == nil && p.mode&DeclarationErrors != 0 { + p.error(ident.Pos(), fmt.Sprintf("label %s undefined", ident.Name)) + } + } + // pop label scope + p.targetStack = p.targetStack[0:n] + p.labelScope = p.labelScope.Outer +} + +func (p *parser) declare(decl interface{}, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) { + for _, ident := range idents { + assert(ident.Obj == nil, "identifier already declared or resolved") + if ident.Name != "_" { + obj := ast.NewObj(kind, ident.Name) + // remember the corresponding declaration for redeclaration + // errors and global variable resolution/typechecking phase + obj.Decl = decl + if alt := scope.Insert(obj); alt != nil && p.mode&DeclarationErrors != 0 { + prevDecl := "" + if pos := alt.Pos(); pos.IsValid() { + prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", p.file.Position(pos)) + } + p.error(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl)) + } + ident.Obj = obj + } + } +} + +func (p *parser) shortVarDecl(idents []*ast.Ident) { + // Go spec: A short variable declaration may redeclare variables + // provided they were originally declared in the same block with + // the same type, and at least one of the non-blank variables is new. + n := 0 // number of new variables + for _, ident := range idents { + assert(ident.Obj == nil, "identifier already declared or resolved") + if ident.Name != "_" { + obj := ast.NewObj(ast.Var, ident.Name) + // short var declarations cannot have redeclaration errors + // and are not global => no need to remember the respective + // declaration + alt := p.topScope.Insert(obj) + if alt == nil { + n++ // new declaration + alt = obj + } + ident.Obj = alt + } + } + if n == 0 && p.mode&DeclarationErrors != 0 { + p.error(idents[0].Pos(), "no new variables on left side of :=") + } +} + +// The unresolved object is a sentinel to mark identifiers that have been added +// to the list of unresolved identifiers. The sentinel is only used for verifying +// internal consistency. +var unresolved = new(ast.Object) + +func (p *parser) resolve(x ast.Expr) { + // nothing to do if x is not an identifier or the blank identifier + ident, _ := x.(*ast.Ident) + if ident == nil { + return + } + assert(ident.Obj == nil, "identifier already declared or resolved") + if ident.Name == "_" { + return + } + // try to resolve the identifier + for s := p.topScope; s != nil; s = s.Outer { + if obj := s.Lookup(ident.Name); obj != nil { + ident.Obj = obj + return + } + } + // all local scopes are known, so any unresolved identifier + // must be found either in the file scope, package scope + // (perhaps in another file), or universe scope --- collect + // them so that they can be resolved later + ident.Obj = unresolved + p.unresolved = append(p.unresolved, ident) +} + +// ---------------------------------------------------------------------------- +// Parsing support + +func (p *parser) printTrace(a ...interface{}) { + const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + + ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + const n = uint(len(dots)) + pos := p.file.Position(p.pos) + fmt.Printf("%5d:%3d: ", pos.Line, pos.Column) + i := 2 * p.indent + for ; i > n; i -= n { + fmt.Print(dots) + } + fmt.Print(dots[0:i]) + fmt.Println(a...) +} + +func trace(p *parser, msg string) *parser { + p.printTrace(msg, "(") + p.indent++ + return p +} + +// Usage pattern: defer un(trace(p, "...")); +func un(p *parser) { + p.indent-- + p.printTrace(")") +} + +// Advance to the next token. +func (p *parser) next0() { + // Because of one-token look-ahead, print the previous token + // when tracing as it provides a more readable output. The + // very first token (!p.pos.IsValid()) is not initialized + // (it is token.ILLEGAL), so don't print it . + if p.trace && p.pos.IsValid() { + s := p.tok.String() + switch { + case p.tok.IsLiteral(): + p.printTrace(s, p.lit) + case p.tok.IsOperator(), p.tok.IsKeyword(): + p.printTrace("\"" + s + "\"") + default: + p.printTrace(s) + } + } + + p.pos, p.tok, p.lit = p.scanner.Scan() +} + +// Consume a comment and return it and the line on which it ends. +func (p *parser) consumeComment() (comment *ast.Comment, endline int) { + // /*-style comments may end on a different line than where they start. + // Scan the comment for '\n' chars and adjust endline accordingly. + endline = p.file.Line(p.pos) + if p.lit[1] == '*' { + // don't use range here - no need to decode Unicode code points + for i := 0; i < len(p.lit); i++ { + if p.lit[i] == '\n' { + endline++ + } + } + } + + comment = &ast.Comment{p.pos, p.lit} + p.next0() + + return +} + +// Consume a group of adjacent comments, add it to the parser's +// comments list, and return it together with the line at which +// the last comment in the group ends. An empty line or non-comment +// token terminates a comment group. +// +func (p *parser) consumeCommentGroup() (comments *ast.CommentGroup, endline int) { + var list []*ast.Comment + endline = p.file.Line(p.pos) + for p.tok == token.COMMENT && endline+1 >= p.file.Line(p.pos) { + var comment *ast.Comment + comment, endline = p.consumeComment() + list = append(list, comment) + } + + // add comment group to the comments list + comments = &ast.CommentGroup{list} + p.comments = append(p.comments, comments) + + return +} + +// Advance to the next non-comment token. In the process, collect +// any comment groups encountered, and remember the last lead and +// line comments. +// +// A lead comment is a comment group that starts and ends in a +// line without any other tokens and that is followed by a non-comment +// token on the line immediately after the comment group. +// +// A line comment is a comment group that follows a non-comment +// token on the same line, and that has no tokens after it on the line +// where it ends. +// +// Lead and line comments may be considered documentation that is +// stored in the AST. +// +func (p *parser) next() { + p.leadComment = nil + p.lineComment = nil + line := p.file.Line(p.pos) // current line + p.next0() + + if p.tok == token.COMMENT { + var comment *ast.CommentGroup + var endline int + + if p.file.Line(p.pos) == line { + // The comment is on same line as the previous token; it + // cannot be a lead comment but may be a line comment. + comment, endline = p.consumeCommentGroup() + if p.file.Line(p.pos) != endline { + // The next token is on a different line, thus + // the last comment group is a line comment. + p.lineComment = comment + } + } + + // consume successor comments, if any + endline = -1 + for p.tok == token.COMMENT { + comment, endline = p.consumeCommentGroup() + } + + if endline+1 == p.file.Line(p.pos) { + // The next token is following on the line immediately after the + // comment group, thus the last comment group is a lead comment. + p.leadComment = comment + } + } +} + +func (p *parser) error(pos token.Pos, msg string) { + p.Error(p.file.Position(pos), msg) +} + +func (p *parser) errorExpected(pos token.Pos, msg string) { + msg = "expected " + msg + if pos == p.pos { + // the error happened at the current position; + // make the error message more specific + if p.tok == token.SEMICOLON && p.lit[0] == '\n' { + msg += ", found newline" + } else { + msg += ", found '" + p.tok.String() + "'" + if p.tok.IsLiteral() { + msg += " " + p.lit + } + } + } + p.error(pos, msg) +} + +func (p *parser) expect(tok token.Token) token.Pos { + pos := p.pos + if p.tok != tok { + p.errorExpected(pos, "'"+tok.String()+"'") + } + p.next() // make progress + return pos +} + +func (p *parser) expectSemi() { + if p.tok != token.RPAREN && p.tok != token.RBRACE { + p.expect(token.SEMICOLON) + } +} + +func assert(cond bool, msg string) { + if !cond { + panic("go/parser internal error: " + msg) + } +} + +// ---------------------------------------------------------------------------- +// Identifiers + +func (p *parser) parseIdent() *ast.Ident { + pos := p.pos + name := "_" + if p.tok == token.IDENT { + name = p.lit + p.next() + } else { + p.expect(token.IDENT) // use expect() error handling + } + return &ast.Ident{pos, name, nil} +} + +func (p *parser) parseIdentList() (list []*ast.Ident) { + if p.trace { + defer un(trace(p, "IdentList")) + } + + list = append(list, p.parseIdent()) + for p.tok == token.COMMA { + p.next() + list = append(list, p.parseIdent()) + } + + return +} + +// ---------------------------------------------------------------------------- +// Common productions + +// If lhs is set, result list elements which are identifiers are not resolved. +func (p *parser) parseExprList(lhs bool) (list []ast.Expr) { + if p.trace { + defer un(trace(p, "ExpressionList")) + } + + list = append(list, p.parseExpr(lhs)) + for p.tok == token.COMMA { + p.next() + list = append(list, p.parseExpr(lhs)) + } + + return +} + +func (p *parser) parseLhsList() []ast.Expr { + list := p.parseExprList(true) + switch p.tok { + case token.DEFINE: + // lhs of a short variable declaration + p.shortVarDecl(p.makeIdentList(list)) + case token.COLON: + // lhs of a label declaration or a communication clause of a select + // statement (parseLhsList is not called when parsing the case clause + // of a switch statement): + // - labels are declared by the caller of parseLhsList + // - for communication clauses, if there is a stand-alone identifier + // followed by a colon, we have a syntax error; there is no need + // to resolve the identifier in that case + default: + // identifiers must be declared elsewhere + for _, x := range list { + p.resolve(x) + } + } + return list +} + +func (p *parser) parseRhsList() []ast.Expr { + return p.parseExprList(false) +} + +// ---------------------------------------------------------------------------- +// Types + +func (p *parser) parseType() ast.Expr { + if p.trace { + defer un(trace(p, "Type")) + } + + typ := p.tryType() + + if typ == nil { + pos := p.pos + p.errorExpected(pos, "type") + p.next() // make progress + return &ast.BadExpr{pos, p.pos} + } + + return typ +} + +// If the result is an identifier, it is not resolved. +func (p *parser) parseTypeName() ast.Expr { + if p.trace { + defer un(trace(p, "TypeName")) + } + + ident := p.parseIdent() + // don't resolve ident yet - it may be a parameter or field name + + if p.tok == token.PERIOD { + // ident is a package name + p.next() + p.resolve(ident) + sel := p.parseIdent() + return &ast.SelectorExpr{ident, sel} + } + + return ident +} + +func (p *parser) parseArrayType(ellipsisOk bool) ast.Expr { + if p.trace { + defer un(trace(p, "ArrayType")) + } + + lbrack := p.expect(token.LBRACK) + var len ast.Expr + if ellipsisOk && p.tok == token.ELLIPSIS { + len = &ast.Ellipsis{p.pos, nil} + p.next() + } else if p.tok != token.RBRACK { + len = p.parseRhs() + } + p.expect(token.RBRACK) + elt := p.parseType() + + return &ast.ArrayType{lbrack, len, elt} +} + +func (p *parser) makeIdentList(list []ast.Expr) []*ast.Ident { + idents := make([]*ast.Ident, len(list)) + for i, x := range list { + ident, isIdent := x.(*ast.Ident) + if !isIdent { + pos := x.(ast.Expr).Pos() + p.errorExpected(pos, "identifier") + ident = &ast.Ident{pos, "_", nil} + } + idents[i] = ident + } + return idents +} + +func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field { + if p.trace { + defer un(trace(p, "FieldDecl")) + } + + doc := p.leadComment + + // fields + list, typ := p.parseVarList(false) + + // optional tag + var tag *ast.BasicLit + if p.tok == token.STRING { + tag = &ast.BasicLit{p.pos, p.tok, p.lit} + p.next() + } + + // analyze case + var idents []*ast.Ident + if typ != nil { + // IdentifierList Type + idents = p.makeIdentList(list) + } else { + // ["*"] TypeName (AnonymousField) + typ = list[0] // we always have at least one element + p.resolve(typ) + if n := len(list); n > 1 || !isTypeName(deref(typ)) { + pos := typ.Pos() + p.errorExpected(pos, "anonymous field") + typ = &ast.BadExpr{pos, list[n-1].End()} + } + } + + p.expectSemi() // call before accessing p.linecomment + + field := &ast.Field{doc, idents, typ, tag, p.lineComment} + p.declare(field, scope, ast.Var, idents...) + + return field +} + +func (p *parser) parseStructType() *ast.StructType { + if p.trace { + defer un(trace(p, "StructType")) + } + + pos := p.expect(token.STRUCT) + lbrace := p.expect(token.LBRACE) + scope := ast.NewScope(nil) // struct scope + var list []*ast.Field + for p.tok == token.IDENT || p.tok == token.MUL || p.tok == token.LPAREN { + // a field declaration cannot start with a '(' but we accept + // it here for more robust parsing and better error messages + // (parseFieldDecl will check and complain if necessary) + list = append(list, p.parseFieldDecl(scope)) + } + rbrace := p.expect(token.RBRACE) + + // TODO(gri): store struct scope in AST + return &ast.StructType{pos, &ast.FieldList{lbrace, list, rbrace}, false} +} + +func (p *parser) parsePointerType() *ast.StarExpr { + if p.trace { + defer un(trace(p, "PointerType")) + } + + star := p.expect(token.MUL) + base := p.parseType() + + return &ast.StarExpr{star, base} +} + +func (p *parser) tryVarType(isParam bool) ast.Expr { + if isParam && p.tok == token.ELLIPSIS { + pos := p.pos + p.next() + typ := p.tryIdentOrType(isParam) // don't use parseType so we can provide better error message + if typ == nil { + p.error(pos, "'...' parameter is missing type") + typ = &ast.BadExpr{pos, p.pos} + } + if p.tok != token.RPAREN { + p.error(pos, "can use '...' with last parameter type only") + } + return &ast.Ellipsis{pos, typ} + } + return p.tryIdentOrType(false) +} + +func (p *parser) parseVarType(isParam bool) ast.Expr { + typ := p.tryVarType(isParam) + if typ == nil { + pos := p.pos + p.errorExpected(pos, "type") + p.next() // make progress + typ = &ast.BadExpr{pos, p.pos} + } + return typ +} + +func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr) { + if p.trace { + defer un(trace(p, "VarList")) + } + + // a list of identifiers looks like a list of type names + for { + // parseVarType accepts any type (including parenthesized ones) + // even though the syntax does not permit them here: we + // accept them all for more robust parsing and complain + // afterwards + list = append(list, p.parseVarType(isParam)) + if p.tok != token.COMMA { + break + } + p.next() + } + + // if we had a list of identifiers, it must be followed by a type + typ = p.tryVarType(isParam) + if typ != nil { + p.resolve(typ) + } + + return +} + +func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params []*ast.Field) { + if p.trace { + defer un(trace(p, "ParameterList")) + } + + list, typ := p.parseVarList(ellipsisOk) + if typ != nil { + // IdentifierList Type + idents := p.makeIdentList(list) + field := &ast.Field{nil, idents, typ, nil, nil} + params = append(params, field) + // Go spec: The scope of an identifier denoting a function + // parameter or result variable is the function body. + p.declare(field, scope, ast.Var, idents...) + if p.tok == token.COMMA { + p.next() + } + + for p.tok != token.RPAREN && p.tok != token.EOF { + idents := p.parseIdentList() + typ := p.parseVarType(ellipsisOk) + field := &ast.Field{nil, idents, typ, nil, nil} + params = append(params, field) + // Go spec: The scope of an identifier denoting a function + // parameter or result variable is the function body. + p.declare(field, scope, ast.Var, idents...) + if p.tok != token.COMMA { + break + } + p.next() + } + + } else { + // Type { "," Type } (anonymous parameters) + params = make([]*ast.Field, len(list)) + for i, x := range list { + p.resolve(x) + params[i] = &ast.Field{Type: x} + } + } + + return +} + +func (p *parser) parseParameters(scope *ast.Scope, ellipsisOk bool) *ast.FieldList { + if p.trace { + defer un(trace(p, "Parameters")) + } + + var params []*ast.Field + lparen := p.expect(token.LPAREN) + if p.tok != token.RPAREN { + params = p.parseParameterList(scope, ellipsisOk) + } + rparen := p.expect(token.RPAREN) + + return &ast.FieldList{lparen, params, rparen} +} + +func (p *parser) parseResult(scope *ast.Scope) *ast.FieldList { + if p.trace { + defer un(trace(p, "Result")) + } + + if p.tok == token.LPAREN { + return p.parseParameters(scope, false) + } + + typ := p.tryType() + if typ != nil { + list := make([]*ast.Field, 1) + list[0] = &ast.Field{Type: typ} + return &ast.FieldList{List: list} + } + + return nil +} + +func (p *parser) parseSignature(scope *ast.Scope) (params, results *ast.FieldList) { + if p.trace { + defer un(trace(p, "Signature")) + } + + params = p.parseParameters(scope, true) + results = p.parseResult(scope) + + return +} + +func (p *parser) parseFuncType() (*ast.FuncType, *ast.Scope) { + if p.trace { + defer un(trace(p, "FuncType")) + } + + pos := p.expect(token.FUNC) + scope := ast.NewScope(p.topScope) // function scope + params, results := p.parseSignature(scope) + + return &ast.FuncType{pos, params, results}, scope +} + +func (p *parser) parseMethodSpec(scope *ast.Scope) *ast.Field { + if p.trace { + defer un(trace(p, "MethodSpec")) + } + + doc := p.leadComment + var idents []*ast.Ident + var typ ast.Expr + x := p.parseTypeName() + if ident, isIdent := x.(*ast.Ident); isIdent && p.tok == token.LPAREN { + // method + idents = []*ast.Ident{ident} + scope := ast.NewScope(nil) // method scope + params, results := p.parseSignature(scope) + typ = &ast.FuncType{token.NoPos, params, results} + } else { + // embedded interface + typ = x + } + p.expectSemi() // call before accessing p.linecomment + + spec := &ast.Field{doc, idents, typ, nil, p.lineComment} + p.declare(spec, scope, ast.Fun, idents...) + + return spec +} + +func (p *parser) parseInterfaceType() *ast.InterfaceType { + if p.trace { + defer un(trace(p, "InterfaceType")) + } + + pos := p.expect(token.INTERFACE) + lbrace := p.expect(token.LBRACE) + scope := ast.NewScope(nil) // interface scope + var list []*ast.Field + for p.tok == token.IDENT { + list = append(list, p.parseMethodSpec(scope)) + } + rbrace := p.expect(token.RBRACE) + + // TODO(gri): store interface scope in AST + return &ast.InterfaceType{pos, &ast.FieldList{lbrace, list, rbrace}, false} +} + +func (p *parser) parseMapType() *ast.MapType { + if p.trace { + defer un(trace(p, "MapType")) + } + + pos := p.expect(token.MAP) + p.expect(token.LBRACK) + key := p.parseType() + p.expect(token.RBRACK) + value := p.parseType() + + return &ast.MapType{pos, key, value} +} + +func (p *parser) parseChanType() *ast.ChanType { + if p.trace { + defer un(trace(p, "ChanType")) + } + + pos := p.pos + dir := ast.SEND | ast.RECV + if p.tok == token.CHAN { + p.next() + if p.tok == token.ARROW { + p.next() + dir = ast.SEND + } + } else { + p.expect(token.ARROW) + p.expect(token.CHAN) + dir = ast.RECV + } + value := p.parseType() + + return &ast.ChanType{pos, dir, value} +} + +// If the result is an identifier, it is not resolved. +func (p *parser) tryIdentOrType(ellipsisOk bool) ast.Expr { + switch p.tok { + case token.IDENT: + return p.parseTypeName() + case token.LBRACK: + return p.parseArrayType(ellipsisOk) + case token.STRUCT: + return p.parseStructType() + case token.MUL: + return p.parsePointerType() + case token.FUNC: + typ, _ := p.parseFuncType() + return typ + case token.INTERFACE: + return p.parseInterfaceType() + case token.MAP: + return p.parseMapType() + case token.CHAN, token.ARROW: + return p.parseChanType() + case token.LPAREN: + lparen := p.pos + p.next() + typ := p.parseType() + rparen := p.expect(token.RPAREN) + return &ast.ParenExpr{lparen, typ, rparen} + } + + // no type found + return nil +} + +func (p *parser) tryType() ast.Expr { + typ := p.tryIdentOrType(false) + if typ != nil { + p.resolve(typ) + } + return typ +} + +// ---------------------------------------------------------------------------- +// Blocks + +func (p *parser) parseStmtList() (list []ast.Stmt) { + if p.trace { + defer un(trace(p, "StatementList")) + } + + for p.tok != token.CASE && p.tok != token.DEFAULT && p.tok != token.RBRACE && p.tok != token.EOF { + list = append(list, p.parseStmt()) + } + + return +} + +func (p *parser) parseBody(scope *ast.Scope) *ast.BlockStmt { + if p.trace { + defer un(trace(p, "Body")) + } + + lbrace := p.expect(token.LBRACE) + p.topScope = scope // open function scope + p.openLabelScope() + list := p.parseStmtList() + p.closeLabelScope() + p.closeScope() + rbrace := p.expect(token.RBRACE) + + return &ast.BlockStmt{lbrace, list, rbrace} +} + +func (p *parser) parseBlockStmt() *ast.BlockStmt { + if p.trace { + defer un(trace(p, "BlockStmt")) + } + + lbrace := p.expect(token.LBRACE) + p.openScope() + list := p.parseStmtList() + p.closeScope() + rbrace := p.expect(token.RBRACE) + + return &ast.BlockStmt{lbrace, list, rbrace} +} + +// ---------------------------------------------------------------------------- +// Expressions + +func (p *parser) parseFuncTypeOrLit() ast.Expr { + if p.trace { + defer un(trace(p, "FuncTypeOrLit")) + } + + typ, scope := p.parseFuncType() + if p.tok != token.LBRACE { + // function type only + return typ + } + + p.exprLev++ + body := p.parseBody(scope) + p.exprLev-- + + return &ast.FuncLit{typ, body} +} + +// parseOperand may return an expression or a raw type (incl. array +// types of the form [...]T. Callers must verify the result. +// If lhs is set and the result is an identifier, it is not resolved. +// +func (p *parser) parseOperand(lhs bool) ast.Expr { + if p.trace { + defer un(trace(p, "Operand")) + } + + switch p.tok { + case token.IDENT: + x := p.parseIdent() + if !lhs { + p.resolve(x) + } + return x + + case token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING: + x := &ast.BasicLit{p.pos, p.tok, p.lit} + p.next() + return x + + case token.LPAREN: + lparen := p.pos + p.next() + p.exprLev++ + x := p.parseRhs() + p.exprLev-- + rparen := p.expect(token.RPAREN) + return &ast.ParenExpr{lparen, x, rparen} + + case token.FUNC: + return p.parseFuncTypeOrLit() + + default: + if typ := p.tryIdentOrType(true); typ != nil { + // could be type for composite literal or conversion + _, isIdent := typ.(*ast.Ident) + assert(!isIdent, "type cannot be identifier") + return typ + } + } + + pos := p.pos + p.errorExpected(pos, "operand") + p.next() // make progress + return &ast.BadExpr{pos, p.pos} +} + +func (p *parser) parseSelector(x ast.Expr) ast.Expr { + if p.trace { + defer un(trace(p, "Selector")) + } + + sel := p.parseIdent() + + return &ast.SelectorExpr{x, sel} +} + +func (p *parser) parseTypeAssertion(x ast.Expr) ast.Expr { + if p.trace { + defer un(trace(p, "TypeAssertion")) + } + + p.expect(token.LPAREN) + var typ ast.Expr + if p.tok == token.TYPE { + // type switch: typ == nil + p.next() + } else { + typ = p.parseType() + } + p.expect(token.RPAREN) + + return &ast.TypeAssertExpr{x, typ} +} + +func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr { + if p.trace { + defer un(trace(p, "IndexOrSlice")) + } + + lbrack := p.expect(token.LBRACK) + p.exprLev++ + var low, high ast.Expr + isSlice := false + if p.tok != token.COLON { + low = p.parseRhs() + } + if p.tok == token.COLON { + isSlice = true + p.next() + if p.tok != token.RBRACK { + high = p.parseRhs() + } + } + p.exprLev-- + rbrack := p.expect(token.RBRACK) + + if isSlice { + return &ast.SliceExpr{x, lbrack, low, high, rbrack} + } + return &ast.IndexExpr{x, lbrack, low, rbrack} +} + +func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr { + if p.trace { + defer un(trace(p, "CallOrConversion")) + } + + lparen := p.expect(token.LPAREN) + p.exprLev++ + var list []ast.Expr + var ellipsis token.Pos + for p.tok != token.RPAREN && p.tok != token.EOF && !ellipsis.IsValid() { + list = append(list, p.parseRhs()) + if p.tok == token.ELLIPSIS { + ellipsis = p.pos + p.next() + } + if p.tok != token.COMMA { + break + } + p.next() + } + p.exprLev-- + rparen := p.expect(token.RPAREN) + + return &ast.CallExpr{fun, lparen, list, ellipsis, rparen} +} + +func (p *parser) parseElement(keyOk bool) ast.Expr { + if p.trace { + defer un(trace(p, "Element")) + } + + if p.tok == token.LBRACE { + return p.parseLiteralValue(nil) + } + + x := p.parseExpr(keyOk) // don't resolve if map key + if keyOk { + if p.tok == token.COLON { + colon := p.pos + p.next() + return &ast.KeyValueExpr{x, colon, p.parseElement(false)} + } + p.resolve(x) // not a map key + } + + return x +} + +func (p *parser) parseElementList() (list []ast.Expr) { + if p.trace { + defer un(trace(p, "ElementList")) + } + + for p.tok != token.RBRACE && p.tok != token.EOF { + list = append(list, p.parseElement(true)) + if p.tok != token.COMMA { + break + } + p.next() + } + + return +} + +func (p *parser) parseLiteralValue(typ ast.Expr) ast.Expr { + if p.trace { + defer un(trace(p, "LiteralValue")) + } + + lbrace := p.expect(token.LBRACE) + var elts []ast.Expr + p.exprLev++ + if p.tok != token.RBRACE { + elts = p.parseElementList() + } + p.exprLev-- + rbrace := p.expect(token.RBRACE) + return &ast.CompositeLit{typ, lbrace, elts, rbrace} +} + +// checkExpr checks that x is an expression (and not a type). +func (p *parser) checkExpr(x ast.Expr) ast.Expr { + switch t := unparen(x).(type) { + case *ast.BadExpr: + case *ast.Ident: + case *ast.BasicLit: + case *ast.FuncLit: + case *ast.CompositeLit: + case *ast.ParenExpr: + panic("unreachable") + case *ast.SelectorExpr: + case *ast.IndexExpr: + case *ast.SliceExpr: + case *ast.TypeAssertExpr: + if t.Type == nil { + // the form X.(type) is only allowed in type switch expressions + p.errorExpected(x.Pos(), "expression") + x = &ast.BadExpr{x.Pos(), x.End()} + } + case *ast.CallExpr: + case *ast.StarExpr: + case *ast.UnaryExpr: + if t.Op == token.RANGE { + // the range operator is only allowed at the top of a for statement + p.errorExpected(x.Pos(), "expression") + x = &ast.BadExpr{x.Pos(), x.End()} + } + case *ast.BinaryExpr: + default: + // all other nodes are not proper expressions + p.errorExpected(x.Pos(), "expression") + x = &ast.BadExpr{x.Pos(), x.End()} + } + return x +} + +// isTypeName reports whether x is a (qualified) TypeName. +func isTypeName(x ast.Expr) bool { + switch t := x.(type) { + case *ast.BadExpr: + case *ast.Ident: + case *ast.SelectorExpr: + _, isIdent := t.X.(*ast.Ident) + return isIdent + default: + return false // all other nodes are not type names + } + return true +} + +// isLiteralType reports whether x is a legal composite literal type. +func isLiteralType(x ast.Expr) bool { + switch t := x.(type) { + case *ast.BadExpr: + case *ast.Ident: + case *ast.SelectorExpr: + _, isIdent := t.X.(*ast.Ident) + return isIdent + case *ast.ArrayType: + case *ast.StructType: + case *ast.MapType: + default: + return false // all other nodes are not legal composite literal types + } + return true +} + +// If x is of the form *T, deref returns T, otherwise it returns x. +func deref(x ast.Expr) ast.Expr { + if p, isPtr := x.(*ast.StarExpr); isPtr { + x = p.X + } + return x +} + +// If x is of the form (T), unparen returns unparen(T), otherwise it returns x. +func unparen(x ast.Expr) ast.Expr { + if p, isParen := x.(*ast.ParenExpr); isParen { + x = unparen(p.X) + } + return x +} + +// checkExprOrType checks that x is an expression or a type +// (and not a raw type such as [...]T). +// +func (p *parser) checkExprOrType(x ast.Expr) ast.Expr { + switch t := unparen(x).(type) { + case *ast.ParenExpr: + panic("unreachable") + case *ast.UnaryExpr: + if t.Op == token.RANGE { + // the range operator is only allowed at the top of a for statement + p.errorExpected(x.Pos(), "expression") + x = &ast.BadExpr{x.Pos(), x.End()} + } + case *ast.ArrayType: + if len, isEllipsis := t.Len.(*ast.Ellipsis); isEllipsis { + p.error(len.Pos(), "expected array length, found '...'") + x = &ast.BadExpr{x.Pos(), x.End()} + } + } + + // all other nodes are expressions or types + return x +} + +// If lhs is set and the result is an identifier, it is not resolved. +func (p *parser) parsePrimaryExpr(lhs bool) ast.Expr { + if p.trace { + defer un(trace(p, "PrimaryExpr")) + } + + x := p.parseOperand(lhs) +L: + for { + switch p.tok { + case token.PERIOD: + p.next() + if lhs { + p.resolve(x) + } + switch p.tok { + case token.IDENT: + x = p.parseSelector(p.checkExpr(x)) + case token.LPAREN: + x = p.parseTypeAssertion(p.checkExpr(x)) + default: + pos := p.pos + p.next() // make progress + p.errorExpected(pos, "selector or type assertion") + x = &ast.BadExpr{pos, p.pos} + } + case token.LBRACK: + if lhs { + p.resolve(x) + } + x = p.parseIndexOrSlice(p.checkExpr(x)) + case token.LPAREN: + if lhs { + p.resolve(x) + } + x = p.parseCallOrConversion(p.checkExprOrType(x)) + case token.LBRACE: + if isLiteralType(x) && (p.exprLev >= 0 || !isTypeName(x)) { + if lhs { + p.resolve(x) + } + x = p.parseLiteralValue(x) + } else { + break L + } + default: + break L + } + lhs = false // no need to try to resolve again + } + + return x +} + +// If lhs is set and the result is an identifier, it is not resolved. +func (p *parser) parseUnaryExpr(lhs bool) ast.Expr { + if p.trace { + defer un(trace(p, "UnaryExpr")) + } + + switch p.tok { + case token.ADD, token.SUB, token.NOT, token.XOR, token.AND, token.RANGE: + pos, op := p.pos, p.tok + p.next() + x := p.parseUnaryExpr(false) + return &ast.UnaryExpr{pos, op, p.checkExpr(x)} + + case token.ARROW: + // channel type or receive expression + pos := p.pos + p.next() + if p.tok == token.CHAN { + p.next() + value := p.parseType() + return &ast.ChanType{pos, ast.RECV, value} + } + + x := p.parseUnaryExpr(false) + return &ast.UnaryExpr{pos, token.ARROW, p.checkExpr(x)} + + case token.MUL: + // pointer type or unary "*" expression + pos := p.pos + p.next() + x := p.parseUnaryExpr(false) + return &ast.StarExpr{pos, p.checkExprOrType(x)} + } + + return p.parsePrimaryExpr(lhs) +} + +// If lhs is set and the result is an identifier, it is not resolved. +func (p *parser) parseBinaryExpr(lhs bool, prec1 int) ast.Expr { + if p.trace { + defer un(trace(p, "BinaryExpr")) + } + + x := p.parseUnaryExpr(lhs) + for prec := p.tok.Precedence(); prec >= prec1; prec-- { + for p.tok.Precedence() == prec { + pos, op := p.pos, p.tok + p.next() + if lhs { + p.resolve(x) + lhs = false + } + y := p.parseBinaryExpr(false, prec+1) + x = &ast.BinaryExpr{p.checkExpr(x), pos, op, p.checkExpr(y)} + } + } + + return x +} + +// If lhs is set and the result is an identifier, it is not resolved. +// TODO(gri): parseExpr may return a type or even a raw type ([..]int) - +// should reject when a type/raw type is obviously not allowed +func (p *parser) parseExpr(lhs bool) ast.Expr { + if p.trace { + defer un(trace(p, "Expression")) + } + + return p.parseBinaryExpr(lhs, token.LowestPrec+1) +} + +func (p *parser) parseRhs() ast.Expr { + return p.parseExpr(false) +} + +// ---------------------------------------------------------------------------- +// Statements + +func (p *parser) parseSimpleStmt(labelOk bool) ast.Stmt { + if p.trace { + defer un(trace(p, "SimpleStmt")) + } + + x := p.parseLhsList() + + switch p.tok { + case + token.DEFINE, token.ASSIGN, token.ADD_ASSIGN, + token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, + token.REM_ASSIGN, token.AND_ASSIGN, token.OR_ASSIGN, + token.XOR_ASSIGN, token.SHL_ASSIGN, token.SHR_ASSIGN, token.AND_NOT_ASSIGN: + // assignment statement + pos, tok := p.pos, p.tok + p.next() + y := p.parseRhsList() + return &ast.AssignStmt{x, pos, tok, y} + } + + if len(x) > 1 { + p.errorExpected(x[0].Pos(), "1 expression") + // continue with first expression + } + + switch p.tok { + case token.COLON: + // labeled statement + colon := p.pos + p.next() + if label, isIdent := x[0].(*ast.Ident); labelOk && isIdent { + // Go spec: The scope of a label is the body of the function + // in which it is declared and excludes the body of any nested + // function. + stmt := &ast.LabeledStmt{label, colon, p.parseStmt()} + p.declare(stmt, p.labelScope, ast.Lbl, label) + return stmt + } + p.error(x[0].Pos(), "illegal label declaration") + return &ast.BadStmt{x[0].Pos(), colon + 1} + + case token.ARROW: + // send statement + arrow := p.pos + p.next() // consume "<-" + y := p.parseRhs() + return &ast.SendStmt{x[0], arrow, y} + + case token.INC, token.DEC: + // increment or decrement + s := &ast.IncDecStmt{x[0], p.pos, p.tok} + p.next() // consume "++" or "--" + return s + } + + // expression + return &ast.ExprStmt{x[0]} +} + +func (p *parser) parseCallExpr() *ast.CallExpr { + x := p.parseRhs() + if call, isCall := x.(*ast.CallExpr); isCall { + return call + } + p.errorExpected(x.Pos(), "function/method call") + return nil +} + +func (p *parser) parseGoStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "GoStmt")) + } + + pos := p.expect(token.GO) + call := p.parseCallExpr() + p.expectSemi() + if call == nil { + return &ast.BadStmt{pos, pos + 2} // len("go") + } + + return &ast.GoStmt{pos, call} +} + +func (p *parser) parseDeferStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "DeferStmt")) + } + + pos := p.expect(token.DEFER) + call := p.parseCallExpr() + p.expectSemi() + if call == nil { + return &ast.BadStmt{pos, pos + 5} // len("defer") + } + + return &ast.DeferStmt{pos, call} +} + +func (p *parser) parseReturnStmt() *ast.ReturnStmt { + if p.trace { + defer un(trace(p, "ReturnStmt")) + } + + pos := p.pos + p.expect(token.RETURN) + var x []ast.Expr + if p.tok != token.SEMICOLON && p.tok != token.RBRACE { + x = p.parseRhsList() + } + p.expectSemi() + + return &ast.ReturnStmt{pos, x} +} + +func (p *parser) parseBranchStmt(tok token.Token) *ast.BranchStmt { + if p.trace { + defer un(trace(p, "BranchStmt")) + } + + pos := p.expect(tok) + var label *ast.Ident + if tok != token.FALLTHROUGH && p.tok == token.IDENT { + label = p.parseIdent() + // add to list of unresolved targets + n := len(p.targetStack) - 1 + p.targetStack[n] = append(p.targetStack[n], label) + } + p.expectSemi() + + return &ast.BranchStmt{pos, tok, label} +} + +func (p *parser) makeExpr(s ast.Stmt) ast.Expr { + if s == nil { + return nil + } + if es, isExpr := s.(*ast.ExprStmt); isExpr { + return p.checkExpr(es.X) + } + p.error(s.Pos(), "expected condition, found simple statement") + return &ast.BadExpr{s.Pos(), s.End()} +} + +func (p *parser) parseIfStmt() *ast.IfStmt { + if p.trace { + defer un(trace(p, "IfStmt")) + } + + pos := p.expect(token.IF) + p.openScope() + defer p.closeScope() + + var s ast.Stmt + var x ast.Expr + { + prevLev := p.exprLev + p.exprLev = -1 + if p.tok == token.SEMICOLON { + p.next() + x = p.parseRhs() + } else { + s = p.parseSimpleStmt(false) + if p.tok == token.SEMICOLON { + p.next() + x = p.parseRhs() + } else { + x = p.makeExpr(s) + s = nil + } + } + p.exprLev = prevLev + } + + body := p.parseBlockStmt() + var else_ ast.Stmt + if p.tok == token.ELSE { + p.next() + else_ = p.parseStmt() + } else { + p.expectSemi() + } + + return &ast.IfStmt{pos, s, x, body, else_} +} + +func (p *parser) parseTypeList() (list []ast.Expr) { + if p.trace { + defer un(trace(p, "TypeList")) + } + + list = append(list, p.parseType()) + for p.tok == token.COMMA { + p.next() + list = append(list, p.parseType()) + } + + return +} + +func (p *parser) parseCaseClause(exprSwitch bool) *ast.CaseClause { + if p.trace { + defer un(trace(p, "CaseClause")) + } + + pos := p.pos + var list []ast.Expr + if p.tok == token.CASE { + p.next() + if exprSwitch { + list = p.parseRhsList() + } else { + list = p.parseTypeList() + } + } else { + p.expect(token.DEFAULT) + } + + colon := p.expect(token.COLON) + p.openScope() + body := p.parseStmtList() + p.closeScope() + + return &ast.CaseClause{pos, list, colon, body} +} + +func isExprSwitch(s ast.Stmt) bool { + if s == nil { + return true + } + if e, ok := s.(*ast.ExprStmt); ok { + if a, ok := e.X.(*ast.TypeAssertExpr); ok { + return a.Type != nil // regular type assertion + } + return true + } + return false +} + +func (p *parser) parseSwitchStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "SwitchStmt")) + } + + pos := p.expect(token.SWITCH) + p.openScope() + defer p.closeScope() + + var s1, s2 ast.Stmt + if p.tok != token.LBRACE { + prevLev := p.exprLev + p.exprLev = -1 + if p.tok != token.SEMICOLON { + s2 = p.parseSimpleStmt(false) + } + if p.tok == token.SEMICOLON { + p.next() + s1 = s2 + s2 = nil + if p.tok != token.LBRACE { + s2 = p.parseSimpleStmt(false) + } + } + p.exprLev = prevLev + } + + exprSwitch := isExprSwitch(s2) + lbrace := p.expect(token.LBRACE) + var list []ast.Stmt + for p.tok == token.CASE || p.tok == token.DEFAULT { + list = append(list, p.parseCaseClause(exprSwitch)) + } + rbrace := p.expect(token.RBRACE) + p.expectSemi() + body := &ast.BlockStmt{lbrace, list, rbrace} + + if exprSwitch { + return &ast.SwitchStmt{pos, s1, p.makeExpr(s2), body} + } + // type switch + // TODO(gri): do all the checks! + return &ast.TypeSwitchStmt{pos, s1, s2, body} +} + +func (p *parser) parseCommClause() *ast.CommClause { + if p.trace { + defer un(trace(p, "CommClause")) + } + + p.openScope() + pos := p.pos + var comm ast.Stmt + if p.tok == token.CASE { + p.next() + lhs := p.parseLhsList() + if p.tok == token.ARROW { + // SendStmt + if len(lhs) > 1 { + p.errorExpected(lhs[0].Pos(), "1 expression") + // continue with first expression + } + arrow := p.pos + p.next() + rhs := p.parseRhs() + comm = &ast.SendStmt{lhs[0], arrow, rhs} + } else { + // RecvStmt + pos := p.pos + tok := p.tok + var rhs ast.Expr + if tok == token.ASSIGN || tok == token.DEFINE { + // RecvStmt with assignment + if len(lhs) > 2 { + p.errorExpected(lhs[0].Pos(), "1 or 2 expressions") + // continue with first two expressions + lhs = lhs[0:2] + } + p.next() + rhs = p.parseRhs() + } else { + // rhs must be single receive operation + if len(lhs) > 1 { + p.errorExpected(lhs[0].Pos(), "1 expression") + // continue with first expression + } + rhs = lhs[0] + lhs = nil // there is no lhs + } + if x, isUnary := rhs.(*ast.UnaryExpr); !isUnary || x.Op != token.ARROW { + p.errorExpected(rhs.Pos(), "send or receive operation") + rhs = &ast.BadExpr{rhs.Pos(), rhs.End()} + } + if lhs != nil { + comm = &ast.AssignStmt{lhs, pos, tok, []ast.Expr{rhs}} + } else { + comm = &ast.ExprStmt{rhs} + } + } + } else { + p.expect(token.DEFAULT) + } + + colon := p.expect(token.COLON) + body := p.parseStmtList() + p.closeScope() + + return &ast.CommClause{pos, comm, colon, body} +} + +func (p *parser) parseSelectStmt() *ast.SelectStmt { + if p.trace { + defer un(trace(p, "SelectStmt")) + } + + pos := p.expect(token.SELECT) + lbrace := p.expect(token.LBRACE) + var list []ast.Stmt + for p.tok == token.CASE || p.tok == token.DEFAULT { + list = append(list, p.parseCommClause()) + } + rbrace := p.expect(token.RBRACE) + p.expectSemi() + body := &ast.BlockStmt{lbrace, list, rbrace} + + return &ast.SelectStmt{pos, body} +} + +func (p *parser) parseForStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "ForStmt")) + } + + pos := p.expect(token.FOR) + p.openScope() + defer p.closeScope() + + var s1, s2, s3 ast.Stmt + if p.tok != token.LBRACE { + prevLev := p.exprLev + p.exprLev = -1 + if p.tok != token.SEMICOLON { + s2 = p.parseSimpleStmt(false) + } + if p.tok == token.SEMICOLON { + p.next() + s1 = s2 + s2 = nil + if p.tok != token.SEMICOLON { + s2 = p.parseSimpleStmt(false) + } + p.expectSemi() + if p.tok != token.LBRACE { + s3 = p.parseSimpleStmt(false) + } + } + p.exprLev = prevLev + } + + body := p.parseBlockStmt() + p.expectSemi() + + if as, isAssign := s2.(*ast.AssignStmt); isAssign { + // possibly a for statement with a range clause; check assignment operator + if as.Tok != token.ASSIGN && as.Tok != token.DEFINE { + p.errorExpected(as.TokPos, "'=' or ':='") + return &ast.BadStmt{pos, body.End()} + } + // check lhs + var key, value ast.Expr + switch len(as.Lhs) { + case 2: + key, value = as.Lhs[0], as.Lhs[1] + case 1: + key = as.Lhs[0] + default: + p.errorExpected(as.Lhs[0].Pos(), "1 or 2 expressions") + return &ast.BadStmt{pos, body.End()} + } + // check rhs + if len(as.Rhs) != 1 { + p.errorExpected(as.Rhs[0].Pos(), "1 expression") + return &ast.BadStmt{pos, body.End()} + } + if rhs, isUnary := as.Rhs[0].(*ast.UnaryExpr); isUnary && rhs.Op == token.RANGE { + // rhs is range expression + // (any short variable declaration was handled by parseSimpleStat above) + return &ast.RangeStmt{pos, key, value, as.TokPos, as.Tok, rhs.X, body} + } + p.errorExpected(s2.Pos(), "range clause") + return &ast.BadStmt{pos, body.End()} + } + + // regular for statement + return &ast.ForStmt{pos, s1, p.makeExpr(s2), s3, body} +} + +func (p *parser) parseStmt() (s ast.Stmt) { + if p.trace { + defer un(trace(p, "Statement")) + } + + switch p.tok { + case token.CONST, token.TYPE, token.VAR: + s = &ast.DeclStmt{p.parseDecl()} + case + // tokens that may start a top-level expression + token.IDENT, token.INT, token.FLOAT, token.CHAR, token.STRING, token.FUNC, token.LPAREN, // operand + token.LBRACK, token.STRUCT, // composite type + token.MUL, token.AND, token.ARROW, token.ADD, token.SUB, token.XOR: // unary operators + s = p.parseSimpleStmt(true) + // because of the required look-ahead, labeled statements are + // parsed by parseSimpleStmt - don't expect a semicolon after + // them + if _, isLabeledStmt := s.(*ast.LabeledStmt); !isLabeledStmt { + p.expectSemi() + } + case token.GO: + s = p.parseGoStmt() + case token.DEFER: + s = p.parseDeferStmt() + case token.RETURN: + s = p.parseReturnStmt() + case token.BREAK, token.CONTINUE, token.GOTO, token.FALLTHROUGH: + s = p.parseBranchStmt(p.tok) + case token.LBRACE: + s = p.parseBlockStmt() + p.expectSemi() + case token.IF: + s = p.parseIfStmt() + case token.SWITCH: + s = p.parseSwitchStmt() + case token.SELECT: + s = p.parseSelectStmt() + case token.FOR: + s = p.parseForStmt() + case token.SEMICOLON: + s = &ast.EmptyStmt{p.pos} + p.next() + case token.RBRACE: + // a semicolon may be omitted before a closing "}" + s = &ast.EmptyStmt{p.pos} + default: + // no statement found + pos := p.pos + p.errorExpected(pos, "statement") + p.next() // make progress + s = &ast.BadStmt{pos, p.pos} + } + + return +} + +// ---------------------------------------------------------------------------- +// Declarations + +type parseSpecFunction func(p *parser, doc *ast.CommentGroup, iota int) ast.Spec + +func parseImportSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec { + if p.trace { + defer un(trace(p, "ImportSpec")) + } + + var ident *ast.Ident + switch p.tok { + case token.PERIOD: + ident = &ast.Ident{p.pos, ".", nil} + p.next() + case token.IDENT: + ident = p.parseIdent() + } + + var path *ast.BasicLit + if p.tok == token.STRING { + path = &ast.BasicLit{p.pos, p.tok, p.lit} + p.next() + } else { + p.expect(token.STRING) // use expect() error handling + } + p.expectSemi() // call before accessing p.linecomment + + // collect imports + spec := &ast.ImportSpec{doc, ident, path, p.lineComment} + p.imports = append(p.imports, spec) + + return spec +} + +func parseConstSpec(p *parser, doc *ast.CommentGroup, iota int) ast.Spec { + if p.trace { + defer un(trace(p, "ConstSpec")) + } + + idents := p.parseIdentList() + typ := p.tryType() + var values []ast.Expr + if typ != nil || p.tok == token.ASSIGN || iota == 0 { + p.expect(token.ASSIGN) + values = p.parseRhsList() + } + p.expectSemi() // call before accessing p.linecomment + + // Go spec: The scope of a constant or variable identifier declared inside + // a function begins at the end of the ConstSpec or VarSpec and ends at + // the end of the innermost containing block. + // (Global identifiers are resolved in a separate phase after parsing.) + spec := &ast.ValueSpec{doc, idents, typ, values, p.lineComment} + p.declare(spec, p.topScope, ast.Con, idents...) + + return spec +} + +func parseTypeSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec { + if p.trace { + defer un(trace(p, "TypeSpec")) + } + + ident := p.parseIdent() + + // Go spec: The scope of a type identifier declared inside a function begins + // at the identifier in the TypeSpec and ends at the end of the innermost + // containing block. + // (Global identifiers are resolved in a separate phase after parsing.) + spec := &ast.TypeSpec{doc, ident, nil, nil} + p.declare(spec, p.topScope, ast.Typ, ident) + + spec.Type = p.parseType() + p.expectSemi() // call before accessing p.linecomment + spec.Comment = p.lineComment + + return spec +} + +func parseVarSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec { + if p.trace { + defer un(trace(p, "VarSpec")) + } + + idents := p.parseIdentList() + typ := p.tryType() + var values []ast.Expr + if typ == nil || p.tok == token.ASSIGN { + p.expect(token.ASSIGN) + values = p.parseRhsList() + } + p.expectSemi() // call before accessing p.linecomment + + // Go spec: The scope of a constant or variable identifier declared inside + // a function begins at the end of the ConstSpec or VarSpec and ends at + // the end of the innermost containing block. + // (Global identifiers are resolved in a separate phase after parsing.) + spec := &ast.ValueSpec{doc, idents, typ, values, p.lineComment} + p.declare(spec, p.topScope, ast.Var, idents...) + + return spec +} + +func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.GenDecl { + if p.trace { + defer un(trace(p, "GenDecl("+keyword.String()+")")) + } + + doc := p.leadComment + pos := p.expect(keyword) + var lparen, rparen token.Pos + var list []ast.Spec + if p.tok == token.LPAREN { + lparen = p.pos + p.next() + for iota := 0; p.tok != token.RPAREN && p.tok != token.EOF; iota++ { + list = append(list, f(p, p.leadComment, iota)) + } + rparen = p.expect(token.RPAREN) + p.expectSemi() + } else { + list = append(list, f(p, nil, 0)) + } + + return &ast.GenDecl{doc, pos, keyword, lparen, list, rparen} +} + +func (p *parser) parseReceiver(scope *ast.Scope) *ast.FieldList { + if p.trace { + defer un(trace(p, "Receiver")) + } + + pos := p.pos + par := p.parseParameters(scope, false) + + // must have exactly one receiver + if par.NumFields() != 1 { + p.errorExpected(pos, "exactly one receiver") + // TODO determine a better range for BadExpr below + par.List = []*ast.Field{{Type: &ast.BadExpr{pos, pos}}} + return par + } + + // recv type must be of the form ["*"] identifier + recv := par.List[0] + base := deref(recv.Type) + if _, isIdent := base.(*ast.Ident); !isIdent { + p.errorExpected(base.Pos(), "(unqualified) identifier") + par.List = []*ast.Field{{Type: &ast.BadExpr{recv.Pos(), recv.End()}}} + } + + return par +} + +func (p *parser) parseFuncDecl() *ast.FuncDecl { + if p.trace { + defer un(trace(p, "FunctionDecl")) + } + + doc := p.leadComment + pos := p.expect(token.FUNC) + scope := ast.NewScope(p.topScope) // function scope + + var recv *ast.FieldList + if p.tok == token.LPAREN { + recv = p.parseReceiver(scope) + } + + ident := p.parseIdent() + + params, results := p.parseSignature(scope) + + var body *ast.BlockStmt + if p.tok == token.LBRACE { + body = p.parseBody(scope) + } + p.expectSemi() + + decl := &ast.FuncDecl{doc, recv, ident, &ast.FuncType{pos, params, results}, body} + if recv == nil { + // Go spec: The scope of an identifier denoting a constant, type, + // variable, or function (but not method) declared at top level + // (outside any function) is the package block. + // + // init() functions cannot be referred to and there may + // be more than one - don't put them in the pkgScope + if ident.Name != "init" { + p.declare(decl, p.pkgScope, ast.Fun, ident) + } + } + + return decl +} + +func (p *parser) parseDecl() ast.Decl { + if p.trace { + defer un(trace(p, "Declaration")) + } + + var f parseSpecFunction + switch p.tok { + case token.CONST: + f = parseConstSpec + + case token.TYPE: + f = parseTypeSpec + + case token.VAR: + f = parseVarSpec + + case token.FUNC: + return p.parseFuncDecl() + + default: + pos := p.pos + p.errorExpected(pos, "declaration") + p.next() // make progress + decl := &ast.BadDecl{pos, p.pos} + return decl + } + + return p.parseGenDecl(p.tok, f) +} + +func (p *parser) parseDeclList() (list []ast.Decl) { + if p.trace { + defer un(trace(p, "DeclList")) + } + + for p.tok != token.EOF { + list = append(list, p.parseDecl()) + } + + return +} + +// ---------------------------------------------------------------------------- +// Source files + +func (p *parser) parseFile() *ast.File { + if p.trace { + defer un(trace(p, "File")) + } + + // package clause + doc := p.leadComment + pos := p.expect(token.PACKAGE) + // Go spec: The package clause is not a declaration; + // the package name does not appear in any scope. + ident := p.parseIdent() + if ident.Name == "_" { + p.error(p.pos, "invalid package name _") + } + p.expectSemi() + + var decls []ast.Decl + + // Don't bother parsing the rest if we had errors already. + // Likely not a Go source file at all. + + if p.ErrorCount() == 0 && p.mode&PackageClauseOnly == 0 { + // import decls + for p.tok == token.IMPORT { + decls = append(decls, p.parseGenDecl(token.IMPORT, parseImportSpec)) + } + + if p.mode&ImportsOnly == 0 { + // rest of package body + for p.tok != token.EOF { + decls = append(decls, p.parseDecl()) + } + } + } + + assert(p.topScope == p.pkgScope, "imbalanced scopes") + + // resolve global identifiers within the same file + i := 0 + for _, ident := range p.unresolved { + // i <= index for current ident + assert(ident.Obj == unresolved, "object already resolved") + ident.Obj = p.pkgScope.Lookup(ident.Name) // also removes unresolved sentinel + if ident.Obj == nil { + p.unresolved[i] = ident + i++ + } + } + + // TODO(gri): store p.imports in AST + return &ast.File{doc, pos, ident, decls, p.pkgScope, p.imports, p.unresolved[0:i], p.comments} +} diff --git a/src/go/printer/testdata/slow.golden b/src/go/printer/testdata/slow.golden new file mode 100644 index 0000000..43a15cb --- /dev/null +++ b/src/go/printer/testdata/slow.golden @@ -0,0 +1,85 @@ +// Copyright 2011 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 deepequal_test + +import ( + "testing" + "google3/spam/archer/frontend/deepequal" +) + +func TestTwoNilValues(t *testing.T) { + if err := deepequal.Check(nil, nil); err != nil { + t.Errorf("expected nil, saw %v", err) + } +} + +type Foo struct { + bar *Bar + bang *Bar +} + +type Bar struct { + baz *Baz + foo []*Foo +} + +type Baz struct { + entries map[int]interface{} + whatever string +} + +func newFoo() *Foo { + return &Foo{bar: &Bar{baz: &Baz{ + entries: map[int]interface{}{ + 42: &Foo{}, + 21: &Bar{}, + 11: &Baz{whatever: "it's just a test"}}}}, + bang: &Bar{foo: []*Foo{ + &Foo{bar: &Bar{baz: &Baz{ + entries: map[int]interface{}{ + 43: &Foo{}, + 22: &Bar{}, + 13: &Baz{whatever: "this is nuts"}}}}, + bang: &Bar{foo: []*Foo{ + &Foo{bar: &Bar{baz: &Baz{ + entries: map[int]interface{}{ + 61: &Foo{}, + 71: &Bar{}, + 11: &Baz{whatever: "no, it's Go"}}}}, + bang: &Bar{foo: []*Foo{ + &Foo{bar: &Bar{baz: &Baz{ + entries: map[int]interface{}{ + 0: &Foo{}, + -2: &Bar{}, + -11: &Baz{whatever: "we need to go deeper"}}}}, + bang: &Bar{foo: []*Foo{ + &Foo{bar: &Bar{baz: &Baz{ + entries: map[int]interface{}{ + -2: &Foo{}, + -5: &Bar{}, + -7: &Baz{whatever: "are you serious?"}}}}, + bang: &Bar{foo: []*Foo{}}}, + &Foo{bar: &Bar{baz: &Baz{ + entries: map[int]interface{}{ + -100: &Foo{}, + 50: &Bar{}, + 20: &Baz{whatever: "na, not really ..."}}}}, + bang: &Bar{foo: []*Foo{}}}}}}}}}, + &Foo{bar: &Bar{baz: &Baz{ + entries: map[int]interface{}{ + 2: &Foo{}, + 1: &Bar{}, + -1: &Baz{whatever: "... it's just a test."}}}}, + bang: &Bar{foo: []*Foo{}}}}}}}}} +} + +func TestElaborate(t *testing.T) { + a := newFoo() + b := newFoo() + + if err := deepequal.Check(a, b); err != nil { + t.Errorf("expected nil, saw %v", err) + } +} diff --git a/src/go/printer/testdata/slow.input b/src/go/printer/testdata/slow.input new file mode 100644 index 0000000..0e5a23d --- /dev/null +++ b/src/go/printer/testdata/slow.input @@ -0,0 +1,85 @@ +// Copyright 2011 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 deepequal_test + +import ( + "testing" + "google3/spam/archer/frontend/deepequal" +) + +func TestTwoNilValues(t *testing.T) { + if err := deepequal.Check(nil, nil); err != nil { + t.Errorf("expected nil, saw %v", err) + } +} + +type Foo struct { + bar *Bar + bang *Bar +} + +type Bar struct { + baz *Baz + foo []*Foo +} + +type Baz struct { + entries map[int]interface{} + whatever string +} + +func newFoo() (*Foo) { +return &Foo{bar: &Bar{ baz: &Baz{ +entries: map[int]interface{}{ +42: &Foo{}, +21: &Bar{}, +11: &Baz{ whatever: "it's just a test" }}}}, + bang: &Bar{foo: []*Foo{ +&Foo{bar: &Bar{ baz: &Baz{ +entries: map[int]interface{}{ +43: &Foo{}, +22: &Bar{}, +13: &Baz{ whatever: "this is nuts" }}}}, + bang: &Bar{foo: []*Foo{ +&Foo{bar: &Bar{ baz: &Baz{ +entries: map[int]interface{}{ +61: &Foo{}, +71: &Bar{}, +11: &Baz{ whatever: "no, it's Go" }}}}, + bang: &Bar{foo: []*Foo{ +&Foo{bar: &Bar{ baz: &Baz{ +entries: map[int]interface{}{ +0: &Foo{}, +-2: &Bar{}, +-11: &Baz{ whatever: "we need to go deeper" }}}}, + bang: &Bar{foo: []*Foo{ +&Foo{bar: &Bar{ baz: &Baz{ +entries: map[int]interface{}{ +-2: &Foo{}, +-5: &Bar{}, +-7: &Baz{ whatever: "are you serious?" }}}}, + bang: &Bar{foo: []*Foo{}}}, +&Foo{bar: &Bar{ baz: &Baz{ +entries: map[int]interface{}{ +-100: &Foo{}, +50: &Bar{}, +20: &Baz{ whatever: "na, not really ..." }}}}, + bang: &Bar{foo: []*Foo{}}}}}}}}}, +&Foo{bar: &Bar{ baz: &Baz{ +entries: map[int]interface{}{ +2: &Foo{}, +1: &Bar{}, +-1: &Baz{ whatever: "... it's just a test." }}}}, + bang: &Bar{foo: []*Foo{}}}}}}}}} +} + +func TestElaborate(t *testing.T) { + a := newFoo() + b := newFoo() + + if err := deepequal.Check(a, b); err != nil { + t.Errorf("expected nil, saw %v", err) + } +} diff --git a/src/go/printer/testdata/statements.golden b/src/go/printer/testdata/statements.golden new file mode 100644 index 0000000..4b13460 --- /dev/null +++ b/src/go/printer/testdata/statements.golden @@ -0,0 +1,644 @@ +// Copyright 2009 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 statements + +var expr bool + +func use(x interface{}) {} + +// Formatting of multi-line return statements. +func _f() { + return + return x, y, z + return T{} + return T{1, 2, 3}, + x, y, z + return T{1, 2, 3}, + x, y, + z + return T{1, + 2, + 3} + return T{1, + 2, + 3, + } + return T{ + 1, + 2, + 3} + return T{ + 1, + 2, + 3, + } + return T{ + 1, + T{1, 2, 3}, + 3, + } + return T{ + 1, + T{1, + 2, 3}, + 3, + } + return T{ + 1, + T{1, + 2, + 3}, + 3, + } + return T{ + 1, + 2, + }, nil + return T{ + 1, + 2, + }, + T{ + x: 3, + y: 4, + }, nil + return T{ + 1, + 2, + }, + nil + return T{ + 1, + 2, + }, + T{ + x: 3, + y: 4, + }, + nil + return x + y + + z + return func() {} + return func() { + _ = 0 + }, T{ + 1, 2, + } + return func() { + _ = 0 + } + return func() T { + return T{ + 1, 2, + } + } +} + +// Formatting of multi-line returns: test cases from issue 1207. +func F() (*T, os.Error) { + return &T{ + X: 1, + Y: 2, + }, + nil +} + +func G() (*T, *T, os.Error) { + return &T{ + X: 1, + Y: 2, + }, + &T{ + X: 3, + Y: 4, + }, + nil +} + +func _() interface{} { + return &fileStat{ + name: basename(file.name), + size: mkSize(d.FileSizeHigh, d.FileSizeLow), + modTime: mkModTime(d.LastWriteTime), + mode: mkMode(d.FileAttributes), + sys: mkSysFromFI(&d), + }, nil +} + +// Formatting of if-statement headers. +func _() { + if true { + } + if true { + } // no semicolon printed + if expr { + } + if expr { + } // no semicolon printed + if expr { + } // no parens printed + if expr { + } // no semicolon and parens printed + if x := expr; true { + use(x) + } + if x := expr; expr { + use(x) + } +} + +// Formatting of switch-statement headers. +func _() { + switch { + } + switch { + } // no semicolon printed + switch expr { + } + switch expr { + } // no semicolon printed + switch expr { + } // no parens printed + switch expr { + } // no semicolon and parens printed + switch x := expr; { + default: + use( + x) + } + switch x := expr; expr { + default: + use(x) + } +} + +// Formatting of switch statement bodies. +func _() { + switch { + } + + switch x := 0; x { + case 1: + use(x) + use(x) // followed by an empty line + + case 2: // followed by an empty line + + use(x) // followed by an empty line + + case 3: // no empty lines + use(x) + use(x) + } + + switch x { + case 0: + use(x) + case 1: // this comment should have no effect on the previous or next line + use(x) + } + + switch x := 0; x { + case 1: + x = 0 + // this comment should be indented + case 2: + x = 0 + // this comment should not be indented, it is aligned with the next case + case 3: + x = 0 + /* indented comment + aligned + aligned + */ + // bla + /* and more */ + case 4: + x = 0 + /* not indented comment + aligned + aligned + */ + // bla + /* and more */ + case 5: + } +} + +// Formatting of selected select statements. +func _() { + select {} + select { /* this comment should not be tab-aligned because the closing } is on the same line */ + } + select { /* this comment should be tab-aligned */ + } + select { // this comment should be tab-aligned + } + select { + case <-c: + } +} + +// Formatting of for-statement headers for single-line for-loops. +func _() { + for { + } + for expr { + } + for expr { + } // no parens printed + for { + } // no semicolons printed + for x := expr; ; { + use(x) + } + for expr { + } // no semicolons printed + for expr { + } // no semicolons and parens printed + for ; ; expr = false { + } + for x := expr; expr; { + use(x) + } + for x := expr; ; expr = false { + use(x) + } + for ; expr; expr = false { + } + for x := expr; expr; expr = false { + use(x) + } + for x := range []int{} { + use(x) + } + for x := range []int{} { + use(x) + } // no parens printed +} + +// Formatting of for-statement headers for multi-line for-loops. +func _() { + for { + } + for expr { + } + for expr { + } // no parens printed + for { + } // no semicolons printed + for x := expr; ; { + use(x) + } + for expr { + } // no semicolons printed + for expr { + } // no semicolons and parens printed + for ; ; expr = false { + } + for x := expr; expr; { + use(x) + } + for x := expr; ; expr = false { + use(x) + } + for ; expr; expr = false { + } + for x := expr; expr; expr = false { + use(x) + } + for range []int{} { + println("foo") + } + for x := range []int{} { + use(x) + } + for x := range []int{} { + use(x) + } // no parens printed +} + +// Formatting of selected short single- and multi-line statements. +func _() { + if cond { + } + if cond { + } // multiple lines + if cond { + } else { + } // else clause always requires multiple lines + + for { + } + for i := 0; i < len(a); 1++ { + } + for i := 0; i < len(a); 1++ { + a[i] = i + } + for i := 0; i < len(a); 1++ { + a[i] = i + } // multiple lines + + for range a { + } + for _ = range a { + } + for _, _ = range a { + } + for i := range a { + } + for i := range a { + a[i] = i + } + for i := range a { + a[i] = i + } // multiple lines + + go func() { + for { + a <- <-b + } + }() + defer func() { + if x := recover(); x != nil { + err = fmt.Sprintf("error: %s", x.msg) + } + }() +} + +// Don't remove mandatory parentheses around composite literals in control clauses. +func _() { + // strip parentheses - no composite literals or composite literals don't start with a type name + if x { + } + if x { + } + if []T{} { + } + if []T{} { + } + if []T{} { + } + + for x { + } + for x { + } + for []T{} { + } + for []T{} { + } + for []T{} { + } + + switch x { + } + switch x { + } + switch []T{} { + } + switch []T{} { + } + + for _ = range []T{T{42}} { + } + + // leave parentheses - composite literals start with a type name + if (T{}) { + } + if (T{}) { + } + if (T{}) { + } + + for (T{}) { + } + for (T{}) { + } + for (T{}) { + } + + switch (T{}) { + } + switch (T{}) { + } + + for _ = range (T1{T{42}}) { + } + + if x == (T{42}[0]) { + } + if (x == T{42}[0]) { + } + if x == (T{42}[0]) { + } + if x == (T{42}[0]) { + } + if x == (T{42}[0]) { + } + if x == a+b*(T{42}[0]) { + } + if (x == a+b*T{42}[0]) { + } + if x == a+b*(T{42}[0]) { + } + if x == a+(b*(T{42}[0])) { + } + if x == a+b*(T{42}[0]) { + } + if (a + b*(T{42}[0])) == x { + } + if (a + b*(T{42}[0])) == x { + } + + if struct{ x bool }{false}.x { + } + if (struct{ x bool }{false}.x) == false { + } + if struct{ x bool }{false}.x == false { + } +} + +// Extra empty lines inside functions. Do respect source code line +// breaks between statement boundaries but print at most one empty +// line at a time. +func _() { + + const _ = 0 + + const _ = 1 + type _ int + type _ float + + var _ = 0 + var x = 1 + + // Each use(x) call below should have at most one empty line before and after. + // Known bug: The first use call may have more than one empty line before + // (see go/printer/nodes.go, func linebreak). + + use(x) + + if x < x { + + use(x) + + } else { + + use(x) + + } +} + +// Formatting around labels. +func _() { +L: +} + +func _() { + // this comment should be indented +L: // no semicolon needed +} + +func _() { + switch 0 { + case 0: + L0: + ; // semicolon required + case 1: + L1: + ; // semicolon required + default: + L2: // no semicolon needed + } +} + +func _() { + f() +L1: + f() +L2: + ; +L3: +} + +func _() { + // this comment should be indented +L: +} + +func _() { +L: + _ = 0 +} + +func _() { + // this comment should be indented +L: + _ = 0 +} + +func _() { + for { + L1: + _ = 0 + L2: + _ = 0 + } +} + +func _() { + // this comment should be indented + for { + L1: + _ = 0 + L2: + _ = 0 + } +} + +func _() { + if true { + _ = 0 + } + _ = 0 // the indentation here should not be affected by the long label name +AnOverlongLabel: + _ = 0 + + if true { + _ = 0 + } + _ = 0 + +L: + _ = 0 +} + +func _() { + for { + goto L + } +L: + + MoreCode() +} + +func _() { + for { + goto L + } +L: // A comment on the same line as the label, followed by a single empty line. + // Known bug: There may be more than one empty line before MoreCode() + // (see go/printer/nodes.go, func linebreak). + + MoreCode() +} + +func _() { + for { + goto L + } +L: + + // There should be a single empty line before this comment. + MoreCode() +} + +func _() { + for { + goto AVeryLongLabelThatShouldNotAffectFormatting + } +AVeryLongLabelThatShouldNotAffectFormatting: + // There should be a single empty line after this comment. + + // There should be a single empty line before this comment. + MoreCode() +} + +// Formatting of empty statements. +func _() { + +} + +func _() { +} + +func _() { +} + +func _() { + f() +} + +func _() { +L: + ; +} + +func _() { +L: + ; + f() +} diff --git a/src/go/printer/testdata/statements.input b/src/go/printer/testdata/statements.input new file mode 100644 index 0000000..cade157 --- /dev/null +++ b/src/go/printer/testdata/statements.input @@ -0,0 +1,555 @@ +// Copyright 2009 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 statements + +var expr bool + +func use(x interface{}) {} + +// Formatting of multi-line return statements. +func _f() { + return + return x, y, z + return T{} + return T{1, 2, 3}, + x, y, z + return T{1, 2, 3}, + x, y, + z + return T{1, + 2, + 3} + return T{1, + 2, + 3, + } + return T{ + 1, + 2, + 3} + return T{ + 1, + 2, + 3, + } + return T{ + 1, + T{1, 2, 3}, + 3, + } + return T{ + 1, + T{1, + 2, 3}, + 3, + } + return T{ + 1, + T{1, + 2, + 3}, + 3, + } + return T{ + 1, + 2, + }, nil + return T{ + 1, + 2, + }, + T{ + x: 3, + y: 4, + }, nil + return T{ + 1, + 2, + }, + nil + return T{ + 1, + 2, + }, + T{ + x: 3, + y: 4, + }, + nil + return x + y + + z + return func() {} + return func() { + _ = 0 + }, T{ + 1, 2, + } + return func() { + _ = 0 + } + return func() T { + return T { + 1, 2, + } + } +} + +// Formatting of multi-line returns: test cases from issue 1207. +func F() (*T, os.Error) { + return &T{ + X: 1, + Y: 2, + }, + nil +} + +func G() (*T, *T, os.Error) { + return &T{ + X: 1, + Y: 2, + }, + &T{ + X: 3, + Y: 4, + }, + nil +} + +func _() interface{} { + return &fileStat{ + name: basename(file.name), + size: mkSize(d.FileSizeHigh, d.FileSizeLow), + modTime: mkModTime(d.LastWriteTime), + mode: mkMode(d.FileAttributes), + sys: mkSysFromFI(&d), + }, nil +} + +// Formatting of if-statement headers. +func _() { + if true {} + if; true {} // no semicolon printed + if expr{} + if;expr{} // no semicolon printed + if (expr){} // no parens printed + if;((expr)){} // no semicolon and parens printed + if x:=expr;true{ + use(x)} + if x:=expr; expr {use(x)} +} + + +// Formatting of switch-statement headers. +func _() { + switch {} + switch;{} // no semicolon printed + switch expr {} + switch;expr{} // no semicolon printed + switch (expr) {} // no parens printed + switch;((expr)){} // no semicolon and parens printed + switch x := expr; { default:use( +x) + } + switch x := expr; expr {default:use(x)} +} + + +// Formatting of switch statement bodies. +func _() { + switch { + } + + switch x := 0; x { + case 1: + use(x) + use(x) // followed by an empty line + + case 2: // followed by an empty line + + use(x) // followed by an empty line + + case 3: // no empty lines + use(x) + use(x) + } + + switch x { + case 0: + use(x) + case 1: // this comment should have no effect on the previous or next line + use(x) + } + + switch x := 0; x { + case 1: + x = 0 + // this comment should be indented + case 2: + x = 0 + // this comment should not be indented, it is aligned with the next case + case 3: + x = 0 + /* indented comment + aligned + aligned + */ + // bla + /* and more */ + case 4: + x = 0 + /* not indented comment + aligned + aligned + */ + // bla + /* and more */ + case 5: + } +} + + +// Formatting of selected select statements. +func _() { + select { + } + select { /* this comment should not be tab-aligned because the closing } is on the same line */ } + select { /* this comment should be tab-aligned */ + } + select { // this comment should be tab-aligned + } + select { case <-c: } +} + + +// Formatting of for-statement headers for single-line for-loops. +func _() { + for{} + for expr {} + for (expr) {} // no parens printed + for;;{} // no semicolons printed + for x :=expr;; {use( x)} + for; expr;{} // no semicolons printed + for; ((expr));{} // no semicolons and parens printed + for; ; expr = false {} + for x :=expr; expr; {use(x)} + for x := expr;; expr=false {use(x)} + for;expr;expr =false {} + for x := expr;expr;expr = false { use(x) } + for x := range []int{} { use(x) } + for x := range (([]int{})) { use(x) } // no parens printed +} + + +// Formatting of for-statement headers for multi-line for-loops. +func _() { + for{ + } + for expr { + } + for (expr) { + } // no parens printed + for;;{ + } // no semicolons printed + for x :=expr;; {use( x) + } + for; expr;{ + } // no semicolons printed + for; ((expr));{ + } // no semicolons and parens printed + for; ; expr = false { + } + for x :=expr; expr; {use(x) + } + for x := expr;; expr=false {use(x) + } + for;expr;expr =false { + } + for x := expr;expr;expr = false { + use(x) + } + for range []int{} { + println("foo")} + for x := range []int{} { + use(x) } + for x := range (([]int{})) { + use(x) } // no parens printed +} + + +// Formatting of selected short single- and multi-line statements. +func _() { + if cond {} + if cond { + } // multiple lines + if cond {} else {} // else clause always requires multiple lines + + for {} + for i := 0; i < len(a); 1++ {} + for i := 0; i < len(a); 1++ { a[i] = i } + for i := 0; i < len(a); 1++ { a[i] = i + } // multiple lines + + for range a{} + for _ = range a{} + for _, _ = range a{} + for i := range a {} + for i := range a { a[i] = i } + for i := range a { a[i] = i + } // multiple lines + + go func() { for { a <- <-b } }() + defer func() { if x := recover(); x != nil { err = fmt.Sprintf("error: %s", x.msg) } }() +} + + +// Don't remove mandatory parentheses around composite literals in control clauses. +func _() { + // strip parentheses - no composite literals or composite literals don't start with a type name + if (x) {} + if (((x))) {} + if ([]T{}) {} + if (([]T{})) {} + if ; (((([]T{})))) {} + + for (x) {} + for (((x))) {} + for ([]T{}) {} + for (([]T{})) {} + for ; (((([]T{})))) ; {} + + switch (x) {} + switch (((x))) {} + switch ([]T{}) {} + switch ; (((([]T{})))) {} + + for _ = range ((([]T{T{42}}))) {} + + // leave parentheses - composite literals start with a type name + if (T{}) {} + if ((T{})) {} + if ; ((((T{})))) {} + + for (T{}) {} + for ((T{})) {} + for ; ((((T{})))) ; {} + + switch (T{}) {} + switch ; ((((T{})))) {} + + for _ = range (((T1{T{42}}))) {} + + if x == (T{42}[0]) {} + if (x == T{42}[0]) {} + if (x == (T{42}[0])) {} + if (x == (((T{42}[0])))) {} + if (((x == (T{42}[0])))) {} + if x == a + b*(T{42}[0]) {} + if (x == a + b*T{42}[0]) {} + if (x == a + b*(T{42}[0])) {} + if (x == a + ((b * (T{42}[0])))) {} + if (((x == a + b * (T{42}[0])))) {} + if (((a + b * (T{42}[0])) == x)) {} + if (((a + b * (T{42}[0])))) == x {} + + if (struct{x bool}{false}.x) {} + if (struct{x bool}{false}.x) == false {} + if (struct{x bool}{false}.x == false) {} +} + + +// Extra empty lines inside functions. Do respect source code line +// breaks between statement boundaries but print at most one empty +// line at a time. +func _() { + + const _ = 0 + + const _ = 1 + type _ int + type _ float + + var _ = 0 + var x = 1 + + // Each use(x) call below should have at most one empty line before and after. + // Known bug: The first use call may have more than one empty line before + // (see go/printer/nodes.go, func linebreak). + + + + use(x) + + if x < x { + + use(x) + + } else { + + use(x) + + } +} + + +// Formatting around labels. +func _() { + L: +} + + +func _() { + // this comment should be indented + L: ; // no semicolon needed +} + + +func _() { + switch 0 { + case 0: + L0: ; // semicolon required + case 1: + L1: ; // semicolon required + default: + L2: ; // no semicolon needed + } +} + + +func _() { + f() +L1: + f() +L2: + ; +L3: +} + + +func _() { + // this comment should be indented + L: +} + + +func _() { + L: _ = 0 +} + + +func _() { + // this comment should be indented + L: _ = 0 +} + + +func _() { + for { + L1: _ = 0 + L2: + _ = 0 + } +} + + +func _() { + // this comment should be indented + for { + L1: _ = 0 + L2: + _ = 0 + } +} + + +func _() { + if true { + _ = 0 + } + _ = 0 // the indentation here should not be affected by the long label name +AnOverlongLabel: + _ = 0 + + if true { + _ = 0 + } + _ = 0 + +L: _ = 0 +} + + +func _() { + for { + goto L + } +L: + + MoreCode() +} + + +func _() { + for { + goto L + } +L: // A comment on the same line as the label, followed by a single empty line. + // Known bug: There may be more than one empty line before MoreCode() + // (see go/printer/nodes.go, func linebreak). + + + + + MoreCode() +} + + +func _() { + for { + goto L + } +L: + + + + + // There should be a single empty line before this comment. + MoreCode() +} + + +func _() { + for { + goto AVeryLongLabelThatShouldNotAffectFormatting + } +AVeryLongLabelThatShouldNotAffectFormatting: + // There should be a single empty line after this comment. + + // There should be a single empty line before this comment. + MoreCode() +} + + +// Formatting of empty statements. +func _() { + ;;;;;;;;;;;;;;;;;;;;;;;;; +} + +func _() {;;;;;;;;;;;;;;;;;;;;;;;;; +} + +func _() {;;;;;;;;;;;;;;;;;;;;;;;;;} + +func _() { +f();;;;;;;;;;;;;;;;;;;;;;;;; +} + +func _() { +L:;;;;;;;;;;;; +} + +func _() { +L:;;;;;;;;;;;; + f() +} diff --git a/src/go/scanner/errors.go b/src/go/scanner/errors.go new file mode 100644 index 0000000..bf7bfa3 --- /dev/null +++ b/src/go/scanner/errors.go @@ -0,0 +1,124 @@ +// Copyright 2009 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 scanner + +import ( + "fmt" + "go/token" + "io" + "sort" +) + +// In an ErrorList, an error is represented by an *Error. +// The position Pos, if valid, points to the beginning of +// the offending token, and the error condition is described +// by Msg. +// +type Error struct { + Pos token.Position + Msg string +} + +// Error implements the error interface. +func (e Error) Error() string { + if e.Pos.Filename != "" || e.Pos.IsValid() { + // don't print "" + // TODO(gri) reconsider the semantics of Position.IsValid + return e.Pos.String() + ": " + e.Msg + } + return e.Msg +} + +// ErrorList is a list of *Errors. +// The zero value for an ErrorList is an empty ErrorList ready to use. +// +type ErrorList []*Error + +// Add adds an Error with given position and error message to an ErrorList. +func (p *ErrorList) Add(pos token.Position, msg string) { + *p = append(*p, &Error{pos, msg}) +} + +// Reset resets an ErrorList to no errors. +func (p *ErrorList) Reset() { *p = (*p)[0:0] } + +// ErrorList implements the sort Interface. +func (p ErrorList) Len() int { return len(p) } +func (p ErrorList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +func (p ErrorList) Less(i, j int) bool { + e := &p[i].Pos + f := &p[j].Pos + // Note that it is not sufficient to simply compare file offsets because + // the offsets do not reflect modified line information (through //line + // comments). + if e.Filename != f.Filename { + return e.Filename < f.Filename + } + if e.Line != f.Line { + return e.Line < f.Line + } + if e.Column != f.Column { + return e.Column < f.Column + } + return p[i].Msg < p[j].Msg +} + +// Sort sorts an ErrorList. *Error entries are sorted by position, +// other errors are sorted by error message, and before any *Error +// entry. +// +func (p ErrorList) Sort() { + sort.Sort(p) +} + +// RemoveMultiples sorts an ErrorList and removes all but the first error per line. +func (p *ErrorList) RemoveMultiples() { + sort.Sort(p) + var last token.Position // initial last.Line is != any legal error line + i := 0 + for _, e := range *p { + if e.Pos.Filename != last.Filename || e.Pos.Line != last.Line { + last = e.Pos + (*p)[i] = e + i++ + } + } + (*p) = (*p)[0:i] +} + +// An ErrorList implements the error interface. +func (p ErrorList) Error() string { + switch len(p) { + case 0: + return "no errors" + case 1: + return p[0].Error() + } + return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1) +} + +// Err returns an error equivalent to this error list. +// If the list is empty, Err returns nil. +func (p ErrorList) Err() error { + if len(p) == 0 { + return nil + } + return p +} + +// PrintError is a utility function that prints a list of errors to w, +// one error per line, if the err parameter is an ErrorList. Otherwise +// it prints the err string. +// +func PrintError(w io.Writer, err error) { + if list, ok := err.(ErrorList); ok { + for _, e := range list { + fmt.Fprintf(w, "%s\n", e) + } + } else if err != nil { + fmt.Fprintf(w, "%s\n", err) + } +} diff --git a/src/go/scanner/example_test.go b/src/go/scanner/example_test.go new file mode 100644 index 0000000..9004a4a --- /dev/null +++ b/src/go/scanner/example_test.go @@ -0,0 +1,46 @@ +// Copyright 2012 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 scanner_test + +import ( + "fmt" + "go/scanner" + "go/token" +) + +func ExampleScanner_Scan() { + // src is the input that we want to tokenize. + src := []byte("cos(x) + 1i*sin(x) // Euler") + + // Initialize the scanner. + var s scanner.Scanner + fset := token.NewFileSet() // positions are relative to fset + file := fset.AddFile("", fset.Base(), len(src)) // register input "file" + s.Init(file, src, nil /* no error handler */, scanner.ScanComments) + + // Repeated calls to Scan yield the token sequence found in the input. + for { + pos, tok, lit := s.Scan() + if tok == token.EOF { + break + } + fmt.Printf("%s\t%s\t%q\n", fset.Position(pos), tok, lit) + } + + // output: + // 1:1 IDENT "cos" + // 1:4 ( "" + // 1:5 IDENT "x" + // 1:6 ) "" + // 1:8 + "" + // 1:10 IMAG "1i" + // 1:12 * "" + // 1:13 IDENT "sin" + // 1:16 ( "" + // 1:17 IDENT "x" + // 1:18 ) "" + // 1:20 ; "\n" + // 1:20 COMMENT "// Euler" +} diff --git a/src/go/scanner/scanner.go b/src/go/scanner/scanner.go new file mode 100644 index 0000000..00fe2dc --- /dev/null +++ b/src/go/scanner/scanner.go @@ -0,0 +1,940 @@ +// Copyright 2009 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 scanner implements a scanner for Go source text. +// It takes a []byte as source which can then be tokenized +// through repeated calls to the Scan method. +// +package scanner + +import ( + "bytes" + "fmt" + "go/token" + "path/filepath" + "strconv" + "unicode" + "unicode/utf8" +) + +// An ErrorHandler may be provided to Scanner.Init. If a syntax error is +// encountered and a handler was installed, the handler is called with a +// position and an error message. The position points to the beginning of +// the offending token. +// +type ErrorHandler func(pos token.Position, msg string) + +// A Scanner holds the scanner's internal state while processing +// a given text. It can be allocated as part of another data +// structure but must be initialized via Init before use. +// +type Scanner struct { + // immutable state + file *token.File // source file handle + dir string // directory portion of file.Name() + src []byte // source + err ErrorHandler // error reporting; or nil + mode Mode // scanning mode + + // scanning state + ch rune // current character + offset int // character offset + rdOffset int // reading offset (position after current character) + lineOffset int // current line offset + insertSemi bool // insert a semicolon before next newline + + // public state - ok to modify + ErrorCount int // number of errors encountered +} + +const bom = 0xFEFF // byte order mark, only permitted as very first character + +// Read the next Unicode char into s.ch. +// s.ch < 0 means end-of-file. +// +func (s *Scanner) next() { + if s.rdOffset < len(s.src) { + s.offset = s.rdOffset + if s.ch == '\n' { + s.lineOffset = s.offset + s.file.AddLine(s.offset) + } + r, w := rune(s.src[s.rdOffset]), 1 + switch { + case r == 0: + s.error(s.offset, "illegal character NUL") + case r >= utf8.RuneSelf: + // not ASCII + r, w = utf8.DecodeRune(s.src[s.rdOffset:]) + if r == utf8.RuneError && w == 1 { + s.error(s.offset, "illegal UTF-8 encoding") + } else if r == bom && s.offset > 0 { + s.error(s.offset, "illegal byte order mark") + } + } + s.rdOffset += w + s.ch = r + } else { + s.offset = len(s.src) + if s.ch == '\n' { + s.lineOffset = s.offset + s.file.AddLine(s.offset) + } + s.ch = -1 // eof + } +} + +// peek returns the byte following the most recently read character without +// advancing the scanner. If the scanner is at EOF, peek returns 0. +func (s *Scanner) peek() byte { + if s.rdOffset < len(s.src) { + return s.src[s.rdOffset] + } + return 0 +} + +// A mode value is a set of flags (or 0). +// They control scanner behavior. +// +type Mode uint + +const ( + ScanComments Mode = 1 << iota // return comments as COMMENT tokens + dontInsertSemis // do not automatically insert semicolons - for testing only +) + +// Init prepares the scanner s to tokenize the text src by setting the +// scanner at the beginning of src. The scanner uses the file set file +// for position information and it adds line information for each line. +// It is ok to re-use the same file when re-scanning the same file as +// line information which is already present is ignored. Init causes a +// panic if the file size does not match the src size. +// +// Calls to Scan will invoke the error handler err if they encounter a +// syntax error and err is not nil. Also, for each error encountered, +// the Scanner field ErrorCount is incremented by one. The mode parameter +// determines how comments are handled. +// +// Note that Init may call err if there is an error in the first character +// of the file. +// +func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) { + // Explicitly initialize all fields since a scanner may be reused. + if file.Size() != len(src) { + panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src))) + } + s.file = file + s.dir, _ = filepath.Split(file.Name()) + s.src = src + s.err = err + s.mode = mode + + s.ch = ' ' + s.offset = 0 + s.rdOffset = 0 + s.lineOffset = 0 + s.insertSemi = false + s.ErrorCount = 0 + + s.next() + if s.ch == bom { + s.next() // ignore BOM at file beginning + } +} + +func (s *Scanner) error(offs int, msg string) { + if s.err != nil { + s.err(s.file.Position(s.file.Pos(offs)), msg) + } + s.ErrorCount++ +} + +func (s *Scanner) errorf(offs int, format string, args ...interface{}) { + s.error(offs, fmt.Sprintf(format, args...)) +} + +func (s *Scanner) scanComment() string { + // initial '/' already consumed; s.ch == '/' || s.ch == '*' + offs := s.offset - 1 // position of initial '/' + next := -1 // position immediately following the comment; < 0 means invalid comment + numCR := 0 + + if s.ch == '/' { + //-style comment + // (the final '\n' is not considered part of the comment) + s.next() + for s.ch != '\n' && s.ch >= 0 { + if s.ch == '\r' { + numCR++ + } + s.next() + } + // if we are at '\n', the position following the comment is afterwards + next = s.offset + if s.ch == '\n' { + next++ + } + goto exit + } + + /*-style comment */ + s.next() + for s.ch >= 0 { + ch := s.ch + if ch == '\r' { + numCR++ + } + s.next() + if ch == '*' && s.ch == '/' { + s.next() + next = s.offset + goto exit + } + } + + s.error(offs, "comment not terminated") + +exit: + lit := s.src[offs:s.offset] + + // On Windows, a (//-comment) line may end in "\r\n". + // Remove the final '\r' before analyzing the text for + // line directives (matching the compiler). Remove any + // other '\r' afterwards (matching the pre-existing be- + // havior of the scanner). + if numCR > 0 && len(lit) >= 2 && lit[1] == '/' && lit[len(lit)-1] == '\r' { + lit = lit[:len(lit)-1] + numCR-- + } + + // interpret line directives + // (//line directives must start at the beginning of the current line) + if next >= 0 /* implies valid comment */ && (lit[1] == '*' || offs == s.lineOffset) && bytes.HasPrefix(lit[2:], prefix) { + s.updateLineInfo(next, offs, lit) + } + + if numCR > 0 { + lit = stripCR(lit, lit[1] == '*') + } + + return string(lit) +} + +var prefix = []byte("line ") + +// updateLineInfo parses the incoming comment text at offset offs +// as a line directive. If successful, it updates the line info table +// for the position next per the line directive. +func (s *Scanner) updateLineInfo(next, offs int, text []byte) { + // extract comment text + if text[1] == '*' { + text = text[:len(text)-2] // lop off trailing "*/" + } + text = text[7:] // lop off leading "//line " or "/*line " + offs += 7 + + i, n, ok := trailingDigits(text) + if i == 0 { + return // ignore (not a line directive) + } + // i > 0 + + if !ok { + // text has a suffix :xxx but xxx is not a number + s.error(offs+i, "invalid line number: "+string(text[i:])) + return + } + + var line, col int + i2, n2, ok2 := trailingDigits(text[:i-1]) + if ok2 { + //line filename:line:col + i, i2 = i2, i + line, col = n2, n + if col == 0 { + s.error(offs+i2, "invalid column number: "+string(text[i2:])) + return + } + text = text[:i2-1] // lop off ":col" + } else { + //line filename:line + line = n + } + + if line == 0 { + s.error(offs+i, "invalid line number: "+string(text[i:])) + return + } + + // If we have a column (//line filename:line:col form), + // an empty filename means to use the previous filename. + filename := string(text[:i-1]) // lop off ":line", and trim white space + if filename == "" && ok2 { + filename = s.file.Position(s.file.Pos(offs)).Filename + } else if filename != "" { + // Put a relative filename in the current directory. + // This is for compatibility with earlier releases. + // See issue 26671. + filename = filepath.Clean(filename) + if !filepath.IsAbs(filename) { + filename = filepath.Join(s.dir, filename) + } + } + + s.file.AddLineColumnInfo(next, filename, line, col) +} + +func trailingDigits(text []byte) (int, int, bool) { + i := bytes.LastIndexByte(text, ':') // look from right (Windows filenames may contain ':') + if i < 0 { + return 0, 0, false // no ":" + } + // i >= 0 + n, err := strconv.ParseUint(string(text[i+1:]), 10, 0) + return i + 1, int(n), err == nil +} + +func (s *Scanner) findLineEnd() bool { + // initial '/' already consumed + + defer func(offs int) { + // reset scanner state to where it was upon calling findLineEnd + s.ch = '/' + s.offset = offs + s.rdOffset = offs + 1 + s.next() // consume initial '/' again + }(s.offset - 1) + + // read ahead until a newline, EOF, or non-comment token is found + for s.ch == '/' || s.ch == '*' { + if s.ch == '/' { + //-style comment always contains a newline + return true + } + /*-style comment: look for newline */ + s.next() + for s.ch >= 0 { + ch := s.ch + if ch == '\n' { + return true + } + s.next() + if ch == '*' && s.ch == '/' { + s.next() + break + } + } + s.skipWhitespace() // s.insertSemi is set + if s.ch < 0 || s.ch == '\n' { + return true + } + if s.ch != '/' { + // non-comment token + return false + } + s.next() // consume '/' + } + + return false +} + +func isLetter(ch rune) bool { + return 'a' <= lower(ch) && lower(ch) <= 'z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch) +} + +func isDigit(ch rune) bool { + return isDecimal(ch) || ch >= utf8.RuneSelf && unicode.IsDigit(ch) +} + +func (s *Scanner) scanIdentifier() string { + offs := s.offset + for isLetter(s.ch) || isDigit(s.ch) { + s.next() + } + return string(s.src[offs:s.offset]) +} + +func digitVal(ch rune) int { + switch { + case '0' <= ch && ch <= '9': + return int(ch - '0') + case 'a' <= lower(ch) && lower(ch) <= 'f': + return int(lower(ch) - 'a' + 10) + } + return 16 // larger than any legal digit val +} + +func lower(ch rune) rune { return ('a' - 'A') | ch } // returns lower-case ch iff ch is ASCII letter +func isDecimal(ch rune) bool { return '0' <= ch && ch <= '9' } +func isHex(ch rune) bool { return '0' <= ch && ch <= '9' || 'a' <= lower(ch) && lower(ch) <= 'f' } + +// digits accepts the sequence { digit | '_' }. +// If base <= 10, digits accepts any decimal digit but records +// the offset (relative to the source start) of a digit >= base +// in *invalid, if *invalid < 0. +// digits returns a bitset describing whether the sequence contained +// digits (bit 0 is set), or separators '_' (bit 1 is set). +func (s *Scanner) digits(base int, invalid *int) (digsep int) { + if base <= 10 { + max := rune('0' + base) + for isDecimal(s.ch) || s.ch == '_' { + ds := 1 + if s.ch == '_' { + ds = 2 + } else if s.ch >= max && *invalid < 0 { + *invalid = int(s.offset) // record invalid rune offset + } + digsep |= ds + s.next() + } + } else { + for isHex(s.ch) || s.ch == '_' { + ds := 1 + if s.ch == '_' { + ds = 2 + } + digsep |= ds + s.next() + } + } + return +} + +func (s *Scanner) scanNumber() (token.Token, string) { + offs := s.offset + tok := token.ILLEGAL + + base := 10 // number base + prefix := rune(0) // one of 0 (decimal), '0' (0-octal), 'x', 'o', or 'b' + digsep := 0 // bit 0: digit present, bit 1: '_' present + invalid := -1 // index of invalid digit in literal, or < 0 + + // integer part + if s.ch != '.' { + tok = token.INT + if s.ch == '0' { + s.next() + switch lower(s.ch) { + case 'x': + s.next() + base, prefix = 16, 'x' + case 'o': + s.next() + base, prefix = 8, 'o' + case 'b': + s.next() + base, prefix = 2, 'b' + default: + base, prefix = 8, '0' + digsep = 1 // leading 0 + } + } + digsep |= s.digits(base, &invalid) + } + + // fractional part + if s.ch == '.' { + tok = token.FLOAT + if prefix == 'o' || prefix == 'b' { + s.error(s.offset, "invalid radix point in "+litname(prefix)) + } + s.next() + digsep |= s.digits(base, &invalid) + } + + if digsep&1 == 0 { + s.error(s.offset, litname(prefix)+" has no digits") + } + + // exponent + if e := lower(s.ch); e == 'e' || e == 'p' { + switch { + case e == 'e' && prefix != 0 && prefix != '0': + s.errorf(s.offset, "%q exponent requires decimal mantissa", s.ch) + case e == 'p' && prefix != 'x': + s.errorf(s.offset, "%q exponent requires hexadecimal mantissa", s.ch) + } + s.next() + tok = token.FLOAT + if s.ch == '+' || s.ch == '-' { + s.next() + } + ds := s.digits(10, nil) + digsep |= ds + if ds&1 == 0 { + s.error(s.offset, "exponent has no digits") + } + } else if prefix == 'x' && tok == token.FLOAT { + s.error(s.offset, "hexadecimal mantissa requires a 'p' exponent") + } + + // suffix 'i' + if s.ch == 'i' { + tok = token.IMAG + s.next() + } + + lit := string(s.src[offs:s.offset]) + if tok == token.INT && invalid >= 0 { + s.errorf(invalid, "invalid digit %q in %s", lit[invalid-offs], litname(prefix)) + } + if digsep&2 != 0 { + if i := invalidSep(lit); i >= 0 { + s.error(offs+i, "'_' must separate successive digits") + } + } + + return tok, lit +} + +func litname(prefix rune) string { + switch prefix { + case 'x': + return "hexadecimal literal" + case 'o', '0': + return "octal literal" + case 'b': + return "binary literal" + } + return "decimal literal" +} + +// invalidSep returns the index of the first invalid separator in x, or -1. +func invalidSep(x string) int { + x1 := ' ' // prefix char, we only care if it's 'x' + d := '.' // digit, one of '_', '0' (a digit), or '.' (anything else) + i := 0 + + // a prefix counts as a digit + if len(x) >= 2 && x[0] == '0' { + x1 = lower(rune(x[1])) + if x1 == 'x' || x1 == 'o' || x1 == 'b' { + d = '0' + i = 2 + } + } + + // mantissa and exponent + for ; i < len(x); i++ { + p := d // previous digit + d = rune(x[i]) + switch { + case d == '_': + if p != '0' { + return i + } + case isDecimal(d) || x1 == 'x' && isHex(d): + d = '0' + default: + if p == '_' { + return i - 1 + } + d = '.' + } + } + if d == '_' { + return len(x) - 1 + } + + return -1 +} + +// scanEscape parses an escape sequence where rune is the accepted +// escaped quote. In case of a syntax error, it stops at the offending +// character (without consuming it) and returns false. Otherwise +// it returns true. +func (s *Scanner) scanEscape(quote rune) bool { + offs := s.offset + + var n int + var base, max uint32 + switch s.ch { + case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote: + s.next() + return true + case '0', '1', '2', '3', '4', '5', '6', '7': + n, base, max = 3, 8, 255 + case 'x': + s.next() + n, base, max = 2, 16, 255 + case 'u': + s.next() + n, base, max = 4, 16, unicode.MaxRune + case 'U': + s.next() + n, base, max = 8, 16, unicode.MaxRune + default: + msg := "unknown escape sequence" + if s.ch < 0 { + msg = "escape sequence not terminated" + } + s.error(offs, msg) + return false + } + + var x uint32 + for n > 0 { + d := uint32(digitVal(s.ch)) + if d >= base { + msg := fmt.Sprintf("illegal character %#U in escape sequence", s.ch) + if s.ch < 0 { + msg = "escape sequence not terminated" + } + s.error(s.offset, msg) + return false + } + x = x*base + d + s.next() + n-- + } + + if x > max || 0xD800 <= x && x < 0xE000 { + s.error(offs, "escape sequence is invalid Unicode code point") + return false + } + + return true +} + +func (s *Scanner) scanRune() string { + // '\'' opening already consumed + offs := s.offset - 1 + + valid := true + n := 0 + for { + ch := s.ch + if ch == '\n' || ch < 0 { + // only report error if we don't have one already + if valid { + s.error(offs, "rune literal not terminated") + valid = false + } + break + } + s.next() + if ch == '\'' { + break + } + n++ + if ch == '\\' { + if !s.scanEscape('\'') { + valid = false + } + // continue to read to closing quote + } + } + + if valid && n != 1 { + s.error(offs, "illegal rune literal") + } + + return string(s.src[offs:s.offset]) +} + +func (s *Scanner) scanString() string { + // '"' opening already consumed + offs := s.offset - 1 + + for { + ch := s.ch + if ch == '\n' || ch < 0 { + s.error(offs, "string literal not terminated") + break + } + s.next() + if ch == '"' { + break + } + if ch == '\\' { + s.scanEscape('"') + } + } + + return string(s.src[offs:s.offset]) +} + +func stripCR(b []byte, comment bool) []byte { + c := make([]byte, len(b)) + i := 0 + for j, ch := range b { + // In a /*-style comment, don't strip \r from *\r/ (incl. + // sequences of \r from *\r\r...\r/) since the resulting + // */ would terminate the comment too early unless the \r + // is immediately following the opening /* in which case + // it's ok because /*/ is not closed yet (issue #11151). + if ch != '\r' || comment && i > len("/*") && c[i-1] == '*' && j+1 < len(b) && b[j+1] == '/' { + c[i] = ch + i++ + } + } + return c[:i] +} + +func (s *Scanner) scanRawString() string { + // '`' opening already consumed + offs := s.offset - 1 + + hasCR := false + for { + ch := s.ch + if ch < 0 { + s.error(offs, "raw string literal not terminated") + break + } + s.next() + if ch == '`' { + break + } + if ch == '\r' { + hasCR = true + } + } + + lit := s.src[offs:s.offset] + if hasCR { + lit = stripCR(lit, false) + } + + return string(lit) +} + +func (s *Scanner) skipWhitespace() { + for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !s.insertSemi || s.ch == '\r' { + s.next() + } +} + +// Helper functions for scanning multi-byte tokens such as >> += >>= . +// Different routines recognize different length tok_i based on matches +// of ch_i. If a token ends in '=', the result is tok1 or tok3 +// respectively. Otherwise, the result is tok0 if there was no other +// matching character, or tok2 if the matching character was ch2. + +func (s *Scanner) switch2(tok0, tok1 token.Token) token.Token { + if s.ch == '=' { + s.next() + return tok1 + } + return tok0 +} + +func (s *Scanner) switch3(tok0, tok1 token.Token, ch2 rune, tok2 token.Token) token.Token { + if s.ch == '=' { + s.next() + return tok1 + } + if s.ch == ch2 { + s.next() + return tok2 + } + return tok0 +} + +func (s *Scanner) switch4(tok0, tok1 token.Token, ch2 rune, tok2, tok3 token.Token) token.Token { + if s.ch == '=' { + s.next() + return tok1 + } + if s.ch == ch2 { + s.next() + if s.ch == '=' { + s.next() + return tok3 + } + return tok2 + } + return tok0 +} + +// Scan scans the next token and returns the token position, the token, +// and its literal string if applicable. The source end is indicated by +// token.EOF. +// +// If the returned token is a literal (token.IDENT, token.INT, token.FLOAT, +// token.IMAG, token.CHAR, token.STRING) or token.COMMENT, the literal string +// has the corresponding value. +// +// If the returned token is a keyword, the literal string is the keyword. +// +// If the returned token is token.SEMICOLON, the corresponding +// literal string is ";" if the semicolon was present in the source, +// and "\n" if the semicolon was inserted because of a newline or +// at EOF. +// +// If the returned token is token.ILLEGAL, the literal string is the +// offending character. +// +// In all other cases, Scan returns an empty literal string. +// +// For more tolerant parsing, Scan will return a valid token if +// possible even if a syntax error was encountered. Thus, even +// if the resulting token sequence contains no illegal tokens, +// a client may not assume that no error occurred. Instead it +// must check the scanner's ErrorCount or the number of calls +// of the error handler, if there was one installed. +// +// Scan adds line information to the file added to the file +// set with Init. Token positions are relative to that file +// and thus relative to the file set. +// +func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string) { +scanAgain: + s.skipWhitespace() + + // current token start + pos = s.file.Pos(s.offset) + + // determine token value + insertSemi := false + switch ch := s.ch; { + case isLetter(ch): + lit = s.scanIdentifier() + if len(lit) > 1 { + // keywords are longer than one letter - avoid lookup otherwise + tok = token.Lookup(lit) + switch tok { + case token.IDENT, token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN: + insertSemi = true + } + } else { + insertSemi = true + tok = token.IDENT + } + case isDecimal(ch) || ch == '.' && isDecimal(rune(s.peek())): + insertSemi = true + tok, lit = s.scanNumber() + default: + s.next() // always make progress + switch ch { + case -1: + if s.insertSemi { + s.insertSemi = false // EOF consumed + return pos, token.SEMICOLON, "\n" + } + tok = token.EOF + case '\n': + // we only reach here if s.insertSemi was + // set in the first place and exited early + // from s.skipWhitespace() + s.insertSemi = false // newline consumed + return pos, token.SEMICOLON, "\n" + case '"': + insertSemi = true + tok = token.STRING + lit = s.scanString() + case '\'': + insertSemi = true + tok = token.CHAR + lit = s.scanRune() + case '`': + insertSemi = true + tok = token.STRING + lit = s.scanRawString() + case ':': + tok = s.switch2(token.COLON, token.DEFINE) + case '.': + // fractions starting with a '.' are handled by outer switch + tok = token.PERIOD + if s.ch == '.' && s.peek() == '.' { + s.next() + s.next() // consume last '.' + tok = token.ELLIPSIS + } + case ',': + tok = token.COMMA + case ';': + tok = token.SEMICOLON + lit = ";" + case '(': + tok = token.LPAREN + case ')': + insertSemi = true + tok = token.RPAREN + case '[': + tok = token.LBRACK + case ']': + insertSemi = true + tok = token.RBRACK + case '{': + tok = token.LBRACE + case '}': + insertSemi = true + tok = token.RBRACE + case '+': + tok = s.switch3(token.ADD, token.ADD_ASSIGN, '+', token.INC) + if tok == token.INC { + insertSemi = true + } + case '-': + tok = s.switch3(token.SUB, token.SUB_ASSIGN, '-', token.DEC) + if tok == token.DEC { + insertSemi = true + } + case '*': + tok = s.switch2(token.MUL, token.MUL_ASSIGN) + case '/': + if s.ch == '/' || s.ch == '*' { + // comment + if s.insertSemi && s.findLineEnd() { + // reset position to the beginning of the comment + s.ch = '/' + s.offset = s.file.Offset(pos) + s.rdOffset = s.offset + 1 + s.insertSemi = false // newline consumed + return pos, token.SEMICOLON, "\n" + } + comment := s.scanComment() + if s.mode&ScanComments == 0 { + // skip comment + s.insertSemi = false // newline consumed + goto scanAgain + } + tok = token.COMMENT + lit = comment + } else { + tok = s.switch2(token.QUO, token.QUO_ASSIGN) + } + case '%': + tok = s.switch2(token.REM, token.REM_ASSIGN) + case '^': + tok = s.switch2(token.XOR, token.XOR_ASSIGN) + case '<': + if s.ch == '-' { + s.next() + tok = token.ARROW + } else { + tok = s.switch4(token.LSS, token.LEQ, '<', token.SHL, token.SHL_ASSIGN) + } + case '>': + tok = s.switch4(token.GTR, token.GEQ, '>', token.SHR, token.SHR_ASSIGN) + case '=': + tok = s.switch2(token.ASSIGN, token.EQL) + case '!': + tok = s.switch2(token.NOT, token.NEQ) + case '&': + if s.ch == '^' { + s.next() + tok = s.switch2(token.AND_NOT, token.AND_NOT_ASSIGN) + } else { + tok = s.switch3(token.AND, token.AND_ASSIGN, '&', token.LAND) + } + case '|': + tok = s.switch3(token.OR, token.OR_ASSIGN, '|', token.LOR) + default: + // next reports unexpected BOMs - don't repeat + if ch != bom { + s.errorf(s.file.Offset(pos), "illegal character %#U", ch) + } + insertSemi = s.insertSemi // preserve insertSemi info + tok = token.ILLEGAL + lit = string(ch) + } + } + if s.mode&dontInsertSemis == 0 { + s.insertSemi = insertSemi + } + + return +} diff --git a/src/go/scanner/scanner_test.go b/src/go/scanner/scanner_test.go new file mode 100644 index 0000000..ab4c2dd --- /dev/null +++ b/src/go/scanner/scanner_test.go @@ -0,0 +1,1113 @@ +// Copyright 2009 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 scanner + +import ( + "go/token" + "os" + "path/filepath" + "runtime" + "strings" + "testing" +) + +var fset = token.NewFileSet() + +const /* class */ ( + special = iota + literal + operator + keyword +) + +func tokenclass(tok token.Token) int { + switch { + case tok.IsLiteral(): + return literal + case tok.IsOperator(): + return operator + case tok.IsKeyword(): + return keyword + } + return special +} + +type elt struct { + tok token.Token + lit string + class int +} + +var tokens = [...]elt{ + // Special tokens + {token.COMMENT, "/* a comment */", special}, + {token.COMMENT, "// a comment \n", special}, + {token.COMMENT, "/*\r*/", special}, + {token.COMMENT, "/**\r/*/", special}, // issue 11151 + {token.COMMENT, "/**\r\r/*/", special}, + {token.COMMENT, "//\r\n", special}, + + // Identifiers and basic type literals + {token.IDENT, "foobar", literal}, + {token.IDENT, "a۰۱۸", literal}, + {token.IDENT, "foo६४", literal}, + {token.IDENT, "bar9876", literal}, + {token.IDENT, "ŝ", literal}, // was bug (issue 4000) + {token.IDENT, "ŝfoo", literal}, // was bug (issue 4000) + {token.INT, "0", literal}, + {token.INT, "1", literal}, + {token.INT, "123456789012345678890", literal}, + {token.INT, "01234567", literal}, + {token.INT, "0xcafebabe", literal}, + {token.FLOAT, "0.", literal}, + {token.FLOAT, ".0", literal}, + {token.FLOAT, "3.14159265", literal}, + {token.FLOAT, "1e0", literal}, + {token.FLOAT, "1e+100", literal}, + {token.FLOAT, "1e-100", literal}, + {token.FLOAT, "2.71828e-1000", literal}, + {token.IMAG, "0i", literal}, + {token.IMAG, "1i", literal}, + {token.IMAG, "012345678901234567889i", literal}, + {token.IMAG, "123456789012345678890i", literal}, + {token.IMAG, "0.i", literal}, + {token.IMAG, ".0i", literal}, + {token.IMAG, "3.14159265i", literal}, + {token.IMAG, "1e0i", literal}, + {token.IMAG, "1e+100i", literal}, + {token.IMAG, "1e-100i", literal}, + {token.IMAG, "2.71828e-1000i", literal}, + {token.CHAR, "'a'", literal}, + {token.CHAR, "'\\000'", literal}, + {token.CHAR, "'\\xFF'", literal}, + {token.CHAR, "'\\uff16'", literal}, + {token.CHAR, "'\\U0000ff16'", literal}, + {token.STRING, "`foobar`", literal}, + {token.STRING, "`" + `foo + bar` + + "`", + literal, + }, + {token.STRING, "`\r`", literal}, + {token.STRING, "`foo\r\nbar`", literal}, + + // Operators and delimiters + {token.ADD, "+", operator}, + {token.SUB, "-", operator}, + {token.MUL, "*", operator}, + {token.QUO, "/", operator}, + {token.REM, "%", operator}, + + {token.AND, "&", operator}, + {token.OR, "|", operator}, + {token.XOR, "^", operator}, + {token.SHL, "<<", operator}, + {token.SHR, ">>", operator}, + {token.AND_NOT, "&^", operator}, + + {token.ADD_ASSIGN, "+=", operator}, + {token.SUB_ASSIGN, "-=", operator}, + {token.MUL_ASSIGN, "*=", operator}, + {token.QUO_ASSIGN, "/=", operator}, + {token.REM_ASSIGN, "%=", operator}, + + {token.AND_ASSIGN, "&=", operator}, + {token.OR_ASSIGN, "|=", operator}, + {token.XOR_ASSIGN, "^=", operator}, + {token.SHL_ASSIGN, "<<=", operator}, + {token.SHR_ASSIGN, ">>=", operator}, + {token.AND_NOT_ASSIGN, "&^=", operator}, + + {token.LAND, "&&", operator}, + {token.LOR, "||", operator}, + {token.ARROW, "<-", operator}, + {token.INC, "++", operator}, + {token.DEC, "--", operator}, + + {token.EQL, "==", operator}, + {token.LSS, "<", operator}, + {token.GTR, ">", operator}, + {token.ASSIGN, "=", operator}, + {token.NOT, "!", operator}, + + {token.NEQ, "!=", operator}, + {token.LEQ, "<=", operator}, + {token.GEQ, ">=", operator}, + {token.DEFINE, ":=", operator}, + {token.ELLIPSIS, "...", operator}, + + {token.LPAREN, "(", operator}, + {token.LBRACK, "[", operator}, + {token.LBRACE, "{", operator}, + {token.COMMA, ",", operator}, + {token.PERIOD, ".", operator}, + + {token.RPAREN, ")", operator}, + {token.RBRACK, "]", operator}, + {token.RBRACE, "}", operator}, + {token.SEMICOLON, ";", operator}, + {token.COLON, ":", operator}, + + // Keywords + {token.BREAK, "break", keyword}, + {token.CASE, "case", keyword}, + {token.CHAN, "chan", keyword}, + {token.CONST, "const", keyword}, + {token.CONTINUE, "continue", keyword}, + + {token.DEFAULT, "default", keyword}, + {token.DEFER, "defer", keyword}, + {token.ELSE, "else", keyword}, + {token.FALLTHROUGH, "fallthrough", keyword}, + {token.FOR, "for", keyword}, + + {token.FUNC, "func", keyword}, + {token.GO, "go", keyword}, + {token.GOTO, "goto", keyword}, + {token.IF, "if", keyword}, + {token.IMPORT, "import", keyword}, + + {token.INTERFACE, "interface", keyword}, + {token.MAP, "map", keyword}, + {token.PACKAGE, "package", keyword}, + {token.RANGE, "range", keyword}, + {token.RETURN, "return", keyword}, + + {token.SELECT, "select", keyword}, + {token.STRUCT, "struct", keyword}, + {token.SWITCH, "switch", keyword}, + {token.TYPE, "type", keyword}, + {token.VAR, "var", keyword}, +} + +const whitespace = " \t \n\n\n" // to separate tokens + +var source = func() []byte { + var src []byte + for _, t := range tokens { + src = append(src, t.lit...) + src = append(src, whitespace...) + } + return src +}() + +func newlineCount(s string) int { + n := 0 + for i := 0; i < len(s); i++ { + if s[i] == '\n' { + n++ + } + } + return n +} + +func checkPos(t *testing.T, lit string, p token.Pos, expected token.Position) { + pos := fset.Position(p) + // Check cleaned filenames so that we don't have to worry about + // different os.PathSeparator values. + if pos.Filename != expected.Filename && filepath.Clean(pos.Filename) != filepath.Clean(expected.Filename) { + t.Errorf("bad filename for %q: got %s, expected %s", lit, pos.Filename, expected.Filename) + } + if pos.Offset != expected.Offset { + t.Errorf("bad position for %q: got %d, expected %d", lit, pos.Offset, expected.Offset) + } + if pos.Line != expected.Line { + t.Errorf("bad line for %q: got %d, expected %d", lit, pos.Line, expected.Line) + } + if pos.Column != expected.Column { + t.Errorf("bad column for %q: got %d, expected %d", lit, pos.Column, expected.Column) + } +} + +// Verify that calling Scan() provides the correct results. +func TestScan(t *testing.T) { + whitespace_linecount := newlineCount(whitespace) + + // error handler + eh := func(_ token.Position, msg string) { + t.Errorf("error handler called (msg = %s)", msg) + } + + // verify scan + var s Scanner + s.Init(fset.AddFile("", fset.Base(), len(source)), source, eh, ScanComments|dontInsertSemis) + + // set up expected position + epos := token.Position{ + Filename: "", + Offset: 0, + Line: 1, + Column: 1, + } + + index := 0 + for { + pos, tok, lit := s.Scan() + + // check position + if tok == token.EOF { + // correction for EOF + epos.Line = newlineCount(string(source)) + epos.Column = 2 + } + checkPos(t, lit, pos, epos) + + // check token + e := elt{token.EOF, "", special} + if index < len(tokens) { + e = tokens[index] + index++ + } + if tok != e.tok { + t.Errorf("bad token for %q: got %s, expected %s", lit, tok, e.tok) + } + + // check token class + if tokenclass(tok) != e.class { + t.Errorf("bad class for %q: got %d, expected %d", lit, tokenclass(tok), e.class) + } + + // check literal + elit := "" + switch e.tok { + case token.COMMENT: + // no CRs in comments + elit = string(stripCR([]byte(e.lit), e.lit[1] == '*')) + //-style comment literal doesn't contain newline + if elit[1] == '/' { + elit = elit[0 : len(elit)-1] + } + case token.IDENT: + elit = e.lit + case token.SEMICOLON: + elit = ";" + default: + if e.tok.IsLiteral() { + // no CRs in raw string literals + elit = e.lit + if elit[0] == '`' { + elit = string(stripCR([]byte(elit), false)) + } + } else if e.tok.IsKeyword() { + elit = e.lit + } + } + if lit != elit { + t.Errorf("bad literal for %q: got %q, expected %q", lit, lit, elit) + } + + if tok == token.EOF { + break + } + + // update position + epos.Offset += len(e.lit) + len(whitespace) + epos.Line += newlineCount(e.lit) + whitespace_linecount + + } + + if s.ErrorCount != 0 { + t.Errorf("found %d errors", s.ErrorCount) + } +} + +func TestStripCR(t *testing.T) { + for _, test := range []struct{ have, want string }{ + {"//\n", "//\n"}, + {"//\r\n", "//\n"}, + {"//\r\r\r\n", "//\n"}, + {"//\r*\r/\r\n", "//*/\n"}, + {"/**/", "/**/"}, + {"/*\r/*/", "/*/*/"}, + {"/*\r*/", "/**/"}, + {"/**\r/*/", "/**\r/*/"}, + {"/*\r/\r*\r/*/", "/*/*\r/*/"}, + {"/*\r\r\r\r*/", "/**/"}, + } { + got := string(stripCR([]byte(test.have), len(test.have) >= 2 && test.have[1] == '*')) + if got != test.want { + t.Errorf("stripCR(%q) = %q; want %q", test.have, got, test.want) + } + } +} + +func checkSemi(t *testing.T, line string, mode Mode) { + var S Scanner + file := fset.AddFile("TestSemis", fset.Base(), len(line)) + S.Init(file, []byte(line), nil, mode) + pos, tok, lit := S.Scan() + for tok != token.EOF { + if tok == token.ILLEGAL { + // the illegal token literal indicates what + // kind of semicolon literal to expect + semiLit := "\n" + if lit[0] == '#' { + semiLit = ";" + } + // next token must be a semicolon + semiPos := file.Position(pos) + semiPos.Offset++ + semiPos.Column++ + pos, tok, lit = S.Scan() + if tok == token.SEMICOLON { + if lit != semiLit { + t.Errorf(`bad literal for %q: got %q, expected %q`, line, lit, semiLit) + } + checkPos(t, line, pos, semiPos) + } else { + t.Errorf("bad token for %q: got %s, expected ;", line, tok) + } + } else if tok == token.SEMICOLON { + t.Errorf("bad token for %q: got ;, expected no ;", line) + } + pos, tok, lit = S.Scan() + } +} + +var lines = []string{ + // # indicates a semicolon present in the source + // $ indicates an automatically inserted semicolon + "", + "\ufeff#;", // first BOM is ignored + "#;", + "foo$\n", + "123$\n", + "1.2$\n", + "'x'$\n", + `"x"` + "$\n", + "`x`$\n", + + "+\n", + "-\n", + "*\n", + "/\n", + "%\n", + + "&\n", + "|\n", + "^\n", + "<<\n", + ">>\n", + "&^\n", + + "+=\n", + "-=\n", + "*=\n", + "/=\n", + "%=\n", + + "&=\n", + "|=\n", + "^=\n", + "<<=\n", + ">>=\n", + "&^=\n", + + "&&\n", + "||\n", + "<-\n", + "++$\n", + "--$\n", + + "==\n", + "<\n", + ">\n", + "=\n", + "!\n", + + "!=\n", + "<=\n", + ">=\n", + ":=\n", + "...\n", + + "(\n", + "[\n", + "{\n", + ",\n", + ".\n", + + ")$\n", + "]$\n", + "}$\n", + "#;\n", + ":\n", + + "break$\n", + "case\n", + "chan\n", + "const\n", + "continue$\n", + + "default\n", + "defer\n", + "else\n", + "fallthrough$\n", + "for\n", + + "func\n", + "go\n", + "goto\n", + "if\n", + "import\n", + + "interface\n", + "map\n", + "package\n", + "range\n", + "return$\n", + + "select\n", + "struct\n", + "switch\n", + "type\n", + "var\n", + + "foo$//comment\n", + "foo$//comment", + "foo$/*comment*/\n", + "foo$/*\n*/", + "foo$/*comment*/ \n", + "foo$/*\n*/ ", + + "foo $// comment\n", + "foo $// comment", + "foo $/*comment*/\n", + "foo $/*\n*/", + "foo $/* */ /* \n */ bar$/**/\n", + "foo $/*0*/ /*1*/ /*2*/\n", + + "foo $/*comment*/ \n", + "foo $/*0*/ /*1*/ /*2*/ \n", + "foo $/**/ /*-------------*/ /*----\n*/bar $/* \n*/baa$\n", + "foo $/* an EOF terminates a line */", + "foo $/* an EOF terminates a line */ /*", + "foo $/* an EOF terminates a line */ //", + + "package main$\n\nfunc main() {\n\tif {\n\t\treturn /* */ }$\n}$\n", + "package main$", +} + +func TestSemis(t *testing.T) { + for _, line := range lines { + checkSemi(t, line, 0) + checkSemi(t, line, ScanComments) + + // if the input ended in newlines, the input must tokenize the + // same with or without those newlines + for i := len(line) - 1; i >= 0 && line[i] == '\n'; i-- { + checkSemi(t, line[0:i], 0) + checkSemi(t, line[0:i], ScanComments) + } + } +} + +type segment struct { + srcline string // a line of source text + filename string // filename for current token; error message for invalid line directives + line, column int // line and column for current token; error position for invalid line directives +} + +var segments = []segment{ + // exactly one token per line since the test consumes one token per segment + {" line1", "TestLineDirectives", 1, 3}, + {"\nline2", "TestLineDirectives", 2, 1}, + {"\nline3 //line File1.go:100", "TestLineDirectives", 3, 1}, // bad line comment, ignored + {"\nline4", "TestLineDirectives", 4, 1}, + {"\n//line File1.go:100\n line100", "File1.go", 100, 0}, + {"\n//line \t :42\n line1", " \t ", 42, 0}, + {"\n//line File2.go:200\n line200", "File2.go", 200, 0}, + {"\n//line foo\t:42\n line42", "foo\t", 42, 0}, + {"\n //line foo:42\n line43", "foo\t", 44, 0}, // bad line comment, ignored (use existing, prior filename) + {"\n//line foo 42\n line44", "foo\t", 46, 0}, // bad line comment, ignored (use existing, prior filename) + {"\n//line /bar:42\n line45", "/bar", 42, 0}, + {"\n//line ./foo:42\n line46", "foo", 42, 0}, + {"\n//line a/b/c/File1.go:100\n line100", "a/b/c/File1.go", 100, 0}, + {"\n//line c:\\bar:42\n line200", "c:\\bar", 42, 0}, + {"\n//line c:\\dir\\File1.go:100\n line201", "c:\\dir\\File1.go", 100, 0}, + + // tests for new line directive syntax + {"\n//line :100\na1", "", 100, 0}, // missing filename means empty filename + {"\n//line bar:100\nb1", "bar", 100, 0}, + {"\n//line :100:10\nc1", "bar", 100, 10}, // missing filename means current filename + {"\n//line foo:100:10\nd1", "foo", 100, 10}, + + {"\n/*line :100*/a2", "", 100, 0}, // missing filename means empty filename + {"\n/*line bar:100*/b2", "bar", 100, 0}, + {"\n/*line :100:10*/c2", "bar", 100, 10}, // missing filename means current filename + {"\n/*line foo:100:10*/d2", "foo", 100, 10}, + {"\n/*line foo:100:10*/ e2", "foo", 100, 14}, // line-directive relative column + {"\n/*line foo:100:10*/\n\nf2", "foo", 102, 1}, // absolute column since on new line +} + +var dirsegments = []segment{ + // exactly one token per line since the test consumes one token per segment + {" line1", "TestLineDir/TestLineDirectives", 1, 3}, + {"\n//line File1.go:100\n line100", "TestLineDir/File1.go", 100, 0}, +} + +var dirUnixSegments = []segment{ + {"\n//line /bar:42\n line42", "/bar", 42, 0}, +} + +var dirWindowsSegments = []segment{ + {"\n//line c:\\bar:42\n line42", "c:\\bar", 42, 0}, +} + +// Verify that line directives are interpreted correctly. +func TestLineDirectives(t *testing.T) { + testSegments(t, segments, "TestLineDirectives") + testSegments(t, dirsegments, "TestLineDir/TestLineDirectives") + if runtime.GOOS == "windows" { + testSegments(t, dirWindowsSegments, "TestLineDir/TestLineDirectives") + } else { + testSegments(t, dirUnixSegments, "TestLineDir/TestLineDirectives") + } +} + +func testSegments(t *testing.T, segments []segment, filename string) { + var src string + for _, e := range segments { + src += e.srcline + } + + // verify scan + var S Scanner + file := fset.AddFile(filename, fset.Base(), len(src)) + S.Init(file, []byte(src), func(pos token.Position, msg string) { t.Error(Error{pos, msg}) }, dontInsertSemis) + for _, s := range segments { + p, _, lit := S.Scan() + pos := file.Position(p) + checkPos(t, lit, p, token.Position{ + Filename: s.filename, + Offset: pos.Offset, + Line: s.line, + Column: s.column, + }) + } + + if S.ErrorCount != 0 { + t.Errorf("got %d errors", S.ErrorCount) + } +} + +// The filename is used for the error message in these test cases. +// The first line directive is valid and used to control the expected error line. +var invalidSegments = []segment{ + {"\n//line :1:1\n//line foo:42 extra text\ndummy", "invalid line number: 42 extra text", 1, 12}, + {"\n//line :2:1\n//line foobar:\ndummy", "invalid line number: ", 2, 15}, + {"\n//line :5:1\n//line :0\ndummy", "invalid line number: 0", 5, 9}, + {"\n//line :10:1\n//line :1:0\ndummy", "invalid column number: 0", 10, 11}, + {"\n//line :1:1\n//line :foo:0\ndummy", "invalid line number: 0", 1, 13}, // foo is considered part of the filename +} + +// Verify that invalid line directives get the correct error message. +func TestInvalidLineDirectives(t *testing.T) { + // make source + var src string + for _, e := range invalidSegments { + src += e.srcline + } + + // verify scan + var S Scanner + var s segment // current segment + file := fset.AddFile(filepath.Join("dir", "TestInvalidLineDirectives"), fset.Base(), len(src)) + S.Init(file, []byte(src), func(pos token.Position, msg string) { + if msg != s.filename { + t.Errorf("got error %q; want %q", msg, s.filename) + } + if pos.Line != s.line || pos.Column != s.column { + t.Errorf("got position %d:%d; want %d:%d", pos.Line, pos.Column, s.line, s.column) + } + }, dontInsertSemis) + for _, s = range invalidSegments { + S.Scan() + } + + if S.ErrorCount != len(invalidSegments) { + t.Errorf("go %d errors; want %d", S.ErrorCount, len(invalidSegments)) + } +} + +// Verify that initializing the same scanner more than once works correctly. +func TestInit(t *testing.T) { + var s Scanner + + // 1st init + src1 := "if true { }" + f1 := fset.AddFile("src1", fset.Base(), len(src1)) + s.Init(f1, []byte(src1), nil, dontInsertSemis) + if f1.Size() != len(src1) { + t.Errorf("bad file size: got %d, expected %d", f1.Size(), len(src1)) + } + s.Scan() // if + s.Scan() // true + _, tok, _ := s.Scan() // { + if tok != token.LBRACE { + t.Errorf("bad token: got %s, expected %s", tok, token.LBRACE) + } + + // 2nd init + src2 := "go true { ]" + f2 := fset.AddFile("src2", fset.Base(), len(src2)) + s.Init(f2, []byte(src2), nil, dontInsertSemis) + if f2.Size() != len(src2) { + t.Errorf("bad file size: got %d, expected %d", f2.Size(), len(src2)) + } + _, tok, _ = s.Scan() // go + if tok != token.GO { + t.Errorf("bad token: got %s, expected %s", tok, token.GO) + } + + if s.ErrorCount != 0 { + t.Errorf("found %d errors", s.ErrorCount) + } +} + +func TestStdErrorHander(t *testing.T) { + const src = "@\n" + // illegal character, cause an error + "@ @\n" + // two errors on the same line + "//line File2:20\n" + + "@\n" + // different file, but same line + "//line File2:1\n" + + "@ @\n" + // same file, decreasing line number + "//line File1:1\n" + + "@ @ @" // original file, line 1 again + + var list ErrorList + eh := func(pos token.Position, msg string) { list.Add(pos, msg) } + + var s Scanner + s.Init(fset.AddFile("File1", fset.Base(), len(src)), []byte(src), eh, dontInsertSemis) + for { + if _, tok, _ := s.Scan(); tok == token.EOF { + break + } + } + + if len(list) != s.ErrorCount { + t.Errorf("found %d errors, expected %d", len(list), s.ErrorCount) + } + + if len(list) != 9 { + t.Errorf("found %d raw errors, expected 9", len(list)) + PrintError(os.Stderr, list) + } + + list.Sort() + if len(list) != 9 { + t.Errorf("found %d sorted errors, expected 9", len(list)) + PrintError(os.Stderr, list) + } + + list.RemoveMultiples() + if len(list) != 4 { + t.Errorf("found %d one-per-line errors, expected 4", len(list)) + PrintError(os.Stderr, list) + } +} + +type errorCollector struct { + cnt int // number of errors encountered + msg string // last error message encountered + pos token.Position // last error position encountered +} + +func checkError(t *testing.T, src string, tok token.Token, pos int, lit, err string) { + var s Scanner + var h errorCollector + eh := func(pos token.Position, msg string) { + h.cnt++ + h.msg = msg + h.pos = pos + } + s.Init(fset.AddFile("", fset.Base(), len(src)), []byte(src), eh, ScanComments|dontInsertSemis) + _, tok0, lit0 := s.Scan() + if tok0 != tok { + t.Errorf("%q: got %s, expected %s", src, tok0, tok) + } + if tok0 != token.ILLEGAL && lit0 != lit { + t.Errorf("%q: got literal %q, expected %q", src, lit0, lit) + } + cnt := 0 + if err != "" { + cnt = 1 + } + if h.cnt != cnt { + t.Errorf("%q: got cnt %d, expected %d", src, h.cnt, cnt) + } + if h.msg != err { + t.Errorf("%q: got msg %q, expected %q", src, h.msg, err) + } + if h.pos.Offset != pos { + t.Errorf("%q: got offset %d, expected %d", src, h.pos.Offset, pos) + } +} + +var errors = []struct { + src string + tok token.Token + pos int + lit string + err string +}{ + {"\a", token.ILLEGAL, 0, "", "illegal character U+0007"}, + {`#`, token.ILLEGAL, 0, "", "illegal character U+0023 '#'"}, + {`…`, token.ILLEGAL, 0, "", "illegal character U+2026 '…'"}, + {"..", token.PERIOD, 0, "", ""}, // two periods, not invalid token (issue #28112) + {`' '`, token.CHAR, 0, `' '`, ""}, + {`''`, token.CHAR, 0, `''`, "illegal rune literal"}, + {`'12'`, token.CHAR, 0, `'12'`, "illegal rune literal"}, + {`'123'`, token.CHAR, 0, `'123'`, "illegal rune literal"}, + {`'\0'`, token.CHAR, 3, `'\0'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\07'`, token.CHAR, 4, `'\07'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\8'`, token.CHAR, 2, `'\8'`, "unknown escape sequence"}, + {`'\08'`, token.CHAR, 3, `'\08'`, "illegal character U+0038 '8' in escape sequence"}, + {`'\x'`, token.CHAR, 3, `'\x'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\x0'`, token.CHAR, 4, `'\x0'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\x0g'`, token.CHAR, 4, `'\x0g'`, "illegal character U+0067 'g' in escape sequence"}, + {`'\u'`, token.CHAR, 3, `'\u'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\u0'`, token.CHAR, 4, `'\u0'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\u00'`, token.CHAR, 5, `'\u00'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\u000'`, token.CHAR, 6, `'\u000'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\u000`, token.CHAR, 6, `'\u000`, "escape sequence not terminated"}, + {`'\u0000'`, token.CHAR, 0, `'\u0000'`, ""}, + {`'\U'`, token.CHAR, 3, `'\U'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\U0'`, token.CHAR, 4, `'\U0'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\U00'`, token.CHAR, 5, `'\U00'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\U000'`, token.CHAR, 6, `'\U000'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\U0000'`, token.CHAR, 7, `'\U0000'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\U00000'`, token.CHAR, 8, `'\U00000'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\U000000'`, token.CHAR, 9, `'\U000000'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\U0000000'`, token.CHAR, 10, `'\U0000000'`, "illegal character U+0027 ''' in escape sequence"}, + {`'\U0000000`, token.CHAR, 10, `'\U0000000`, "escape sequence not terminated"}, + {`'\U00000000'`, token.CHAR, 0, `'\U00000000'`, ""}, + {`'\Uffffffff'`, token.CHAR, 2, `'\Uffffffff'`, "escape sequence is invalid Unicode code point"}, + {`'`, token.CHAR, 0, `'`, "rune literal not terminated"}, + {`'\`, token.CHAR, 2, `'\`, "escape sequence not terminated"}, + {"'\n", token.CHAR, 0, "'", "rune literal not terminated"}, + {"'\n ", token.CHAR, 0, "'", "rune literal not terminated"}, + {`""`, token.STRING, 0, `""`, ""}, + {`"abc`, token.STRING, 0, `"abc`, "string literal not terminated"}, + {"\"abc\n", token.STRING, 0, `"abc`, "string literal not terminated"}, + {"\"abc\n ", token.STRING, 0, `"abc`, "string literal not terminated"}, + {"``", token.STRING, 0, "``", ""}, + {"`", token.STRING, 0, "`", "raw string literal not terminated"}, + {"/**/", token.COMMENT, 0, "/**/", ""}, + {"/*", token.COMMENT, 0, "/*", "comment not terminated"}, + {"077", token.INT, 0, "077", ""}, + {"078.", token.FLOAT, 0, "078.", ""}, + {"07801234567.", token.FLOAT, 0, "07801234567.", ""}, + {"078e0", token.FLOAT, 0, "078e0", ""}, + {"0E", token.FLOAT, 2, "0E", "exponent has no digits"}, // issue 17621 + {"078", token.INT, 2, "078", "invalid digit '8' in octal literal"}, + {"07090000008", token.INT, 3, "07090000008", "invalid digit '9' in octal literal"}, + {"0x", token.INT, 2, "0x", "hexadecimal literal has no digits"}, + {"\"abc\x00def\"", token.STRING, 4, "\"abc\x00def\"", "illegal character NUL"}, + {"\"abc\x80def\"", token.STRING, 4, "\"abc\x80def\"", "illegal UTF-8 encoding"}, + {"\ufeff\ufeff", token.ILLEGAL, 3, "\ufeff\ufeff", "illegal byte order mark"}, // only first BOM is ignored + {"//\ufeff", token.COMMENT, 2, "//\ufeff", "illegal byte order mark"}, // only first BOM is ignored + {"'\ufeff" + `'`, token.CHAR, 1, "'\ufeff" + `'`, "illegal byte order mark"}, // only first BOM is ignored + {`"` + "abc\ufeffdef" + `"`, token.STRING, 4, `"` + "abc\ufeffdef" + `"`, "illegal byte order mark"}, // only first BOM is ignored +} + +func TestScanErrors(t *testing.T) { + for _, e := range errors { + checkError(t, e.src, e.tok, e.pos, e.lit, e.err) + } +} + +// Verify that no comments show up as literal values when skipping comments. +func TestIssue10213(t *testing.T) { + const src = ` + var ( + A = 1 // foo + ) + + var ( + B = 2 + // foo + ) + + var C = 3 // foo + + var D = 4 + // foo + + func anycode() { + // foo + } + ` + var s Scanner + s.Init(fset.AddFile("", fset.Base(), len(src)), []byte(src), nil, 0) + for { + pos, tok, lit := s.Scan() + class := tokenclass(tok) + if lit != "" && class != keyword && class != literal && tok != token.SEMICOLON { + t.Errorf("%s: tok = %s, lit = %q", fset.Position(pos), tok, lit) + } + if tok <= token.EOF { + break + } + } +} + +func TestIssue28112(t *testing.T) { + const src = "... .. 0.. .." // make sure to have stand-alone ".." immediately before EOF to test EOF behavior + tokens := []token.Token{token.ELLIPSIS, token.PERIOD, token.PERIOD, token.FLOAT, token.PERIOD, token.PERIOD, token.PERIOD, token.EOF} + var s Scanner + s.Init(fset.AddFile("", fset.Base(), len(src)), []byte(src), nil, 0) + for _, want := range tokens { + pos, got, lit := s.Scan() + if got != want { + t.Errorf("%s: got %s, want %s", fset.Position(pos), got, want) + } + // literals expect to have a (non-empty) literal string and we don't care about other tokens for this test + if tokenclass(got) == literal && lit == "" { + t.Errorf("%s: for %s got empty literal string", fset.Position(pos), got) + } + } +} + +func BenchmarkScan(b *testing.B) { + b.StopTimer() + fset := token.NewFileSet() + file := fset.AddFile("", fset.Base(), len(source)) + var s Scanner + b.StartTimer() + for i := 0; i < b.N; i++ { + s.Init(file, source, nil, ScanComments) + for { + _, tok, _ := s.Scan() + if tok == token.EOF { + break + } + } + } +} + +func BenchmarkScanFile(b *testing.B) { + b.StopTimer() + const filename = "scanner.go" + src, err := os.ReadFile(filename) + if err != nil { + panic(err) + } + fset := token.NewFileSet() + file := fset.AddFile(filename, fset.Base(), len(src)) + b.SetBytes(int64(len(src))) + var s Scanner + b.StartTimer() + for i := 0; i < b.N; i++ { + s.Init(file, src, nil, ScanComments) + for { + _, tok, _ := s.Scan() + if tok == token.EOF { + break + } + } + } +} + +func TestNumbers(t *testing.T) { + for _, test := range []struct { + tok token.Token + src, tokens, err string + }{ + // binaries + {token.INT, "0b0", "0b0", ""}, + {token.INT, "0b1010", "0b1010", ""}, + {token.INT, "0B1110", "0B1110", ""}, + + {token.INT, "0b", "0b", "binary literal has no digits"}, + {token.INT, "0b0190", "0b0190", "invalid digit '9' in binary literal"}, + {token.INT, "0b01a0", "0b01 a0", ""}, // only accept 0-9 + + {token.FLOAT, "0b.", "0b.", "invalid radix point in binary literal"}, + {token.FLOAT, "0b.1", "0b.1", "invalid radix point in binary literal"}, + {token.FLOAT, "0b1.0", "0b1.0", "invalid radix point in binary literal"}, + {token.FLOAT, "0b1e10", "0b1e10", "'e' exponent requires decimal mantissa"}, + {token.FLOAT, "0b1P-1", "0b1P-1", "'P' exponent requires hexadecimal mantissa"}, + + {token.IMAG, "0b10i", "0b10i", ""}, + {token.IMAG, "0b10.0i", "0b10.0i", "invalid radix point in binary literal"}, + + // octals + {token.INT, "0o0", "0o0", ""}, + {token.INT, "0o1234", "0o1234", ""}, + {token.INT, "0O1234", "0O1234", ""}, + + {token.INT, "0o", "0o", "octal literal has no digits"}, + {token.INT, "0o8123", "0o8123", "invalid digit '8' in octal literal"}, + {token.INT, "0o1293", "0o1293", "invalid digit '9' in octal literal"}, + {token.INT, "0o12a3", "0o12 a3", ""}, // only accept 0-9 + + {token.FLOAT, "0o.", "0o.", "invalid radix point in octal literal"}, + {token.FLOAT, "0o.2", "0o.2", "invalid radix point in octal literal"}, + {token.FLOAT, "0o1.2", "0o1.2", "invalid radix point in octal literal"}, + {token.FLOAT, "0o1E+2", "0o1E+2", "'E' exponent requires decimal mantissa"}, + {token.FLOAT, "0o1p10", "0o1p10", "'p' exponent requires hexadecimal mantissa"}, + + {token.IMAG, "0o10i", "0o10i", ""}, + {token.IMAG, "0o10e0i", "0o10e0i", "'e' exponent requires decimal mantissa"}, + + // 0-octals + {token.INT, "0", "0", ""}, + {token.INT, "0123", "0123", ""}, + + {token.INT, "08123", "08123", "invalid digit '8' in octal literal"}, + {token.INT, "01293", "01293", "invalid digit '9' in octal literal"}, + {token.INT, "0F.", "0 F .", ""}, // only accept 0-9 + {token.INT, "0123F.", "0123 F .", ""}, + {token.INT, "0123456x", "0123456 x", ""}, + + // decimals + {token.INT, "1", "1", ""}, + {token.INT, "1234", "1234", ""}, + + {token.INT, "1f", "1 f", ""}, // only accept 0-9 + + {token.IMAG, "0i", "0i", ""}, + {token.IMAG, "0678i", "0678i", ""}, + + // decimal floats + {token.FLOAT, "0.", "0.", ""}, + {token.FLOAT, "123.", "123.", ""}, + {token.FLOAT, "0123.", "0123.", ""}, + + {token.FLOAT, ".0", ".0", ""}, + {token.FLOAT, ".123", ".123", ""}, + {token.FLOAT, ".0123", ".0123", ""}, + + {token.FLOAT, "0.0", "0.0", ""}, + {token.FLOAT, "123.123", "123.123", ""}, + {token.FLOAT, "0123.0123", "0123.0123", ""}, + + {token.FLOAT, "0e0", "0e0", ""}, + {token.FLOAT, "123e+0", "123e+0", ""}, + {token.FLOAT, "0123E-1", "0123E-1", ""}, + + {token.FLOAT, "0.e+1", "0.e+1", ""}, + {token.FLOAT, "123.E-10", "123.E-10", ""}, + {token.FLOAT, "0123.e123", "0123.e123", ""}, + + {token.FLOAT, ".0e-1", ".0e-1", ""}, + {token.FLOAT, ".123E+10", ".123E+10", ""}, + {token.FLOAT, ".0123E123", ".0123E123", ""}, + + {token.FLOAT, "0.0e1", "0.0e1", ""}, + {token.FLOAT, "123.123E-10", "123.123E-10", ""}, + {token.FLOAT, "0123.0123e+456", "0123.0123e+456", ""}, + + {token.FLOAT, "0e", "0e", "exponent has no digits"}, + {token.FLOAT, "0E+", "0E+", "exponent has no digits"}, + {token.FLOAT, "1e+f", "1e+ f", "exponent has no digits"}, + {token.FLOAT, "0p0", "0p0", "'p' exponent requires hexadecimal mantissa"}, + {token.FLOAT, "1.0P-1", "1.0P-1", "'P' exponent requires hexadecimal mantissa"}, + + {token.IMAG, "0.i", "0.i", ""}, + {token.IMAG, ".123i", ".123i", ""}, + {token.IMAG, "123.123i", "123.123i", ""}, + {token.IMAG, "123e+0i", "123e+0i", ""}, + {token.IMAG, "123.E-10i", "123.E-10i", ""}, + {token.IMAG, ".123E+10i", ".123E+10i", ""}, + + // hexadecimals + {token.INT, "0x0", "0x0", ""}, + {token.INT, "0x1234", "0x1234", ""}, + {token.INT, "0xcafef00d", "0xcafef00d", ""}, + {token.INT, "0XCAFEF00D", "0XCAFEF00D", ""}, + + {token.INT, "0x", "0x", "hexadecimal literal has no digits"}, + {token.INT, "0x1g", "0x1 g", ""}, + + {token.IMAG, "0xf00i", "0xf00i", ""}, + + // hexadecimal floats + {token.FLOAT, "0x0p0", "0x0p0", ""}, + {token.FLOAT, "0x12efp-123", "0x12efp-123", ""}, + {token.FLOAT, "0xABCD.p+0", "0xABCD.p+0", ""}, + {token.FLOAT, "0x.0189P-0", "0x.0189P-0", ""}, + {token.FLOAT, "0x1.ffffp+1023", "0x1.ffffp+1023", ""}, + + {token.FLOAT, "0x.", "0x.", "hexadecimal literal has no digits"}, + {token.FLOAT, "0x0.", "0x0.", "hexadecimal mantissa requires a 'p' exponent"}, + {token.FLOAT, "0x.0", "0x.0", "hexadecimal mantissa requires a 'p' exponent"}, + {token.FLOAT, "0x1.1", "0x1.1", "hexadecimal mantissa requires a 'p' exponent"}, + {token.FLOAT, "0x1.1e0", "0x1.1e0", "hexadecimal mantissa requires a 'p' exponent"}, + {token.FLOAT, "0x1.2gp1a", "0x1.2 gp1a", "hexadecimal mantissa requires a 'p' exponent"}, + {token.FLOAT, "0x0p", "0x0p", "exponent has no digits"}, + {token.FLOAT, "0xeP-", "0xeP-", "exponent has no digits"}, + {token.FLOAT, "0x1234PAB", "0x1234P AB", "exponent has no digits"}, + {token.FLOAT, "0x1.2p1a", "0x1.2p1 a", ""}, + + {token.IMAG, "0xf00.bap+12i", "0xf00.bap+12i", ""}, + + // separators + {token.INT, "0b_1000_0001", "0b_1000_0001", ""}, + {token.INT, "0o_600", "0o_600", ""}, + {token.INT, "0_466", "0_466", ""}, + {token.INT, "1_000", "1_000", ""}, + {token.FLOAT, "1_000.000_1", "1_000.000_1", ""}, + {token.IMAG, "10e+1_2_3i", "10e+1_2_3i", ""}, + {token.INT, "0x_f00d", "0x_f00d", ""}, + {token.FLOAT, "0x_f00d.0p1_2", "0x_f00d.0p1_2", ""}, + + {token.INT, "0b__1000", "0b__1000", "'_' must separate successive digits"}, + {token.INT, "0o60___0", "0o60___0", "'_' must separate successive digits"}, + {token.INT, "0466_", "0466_", "'_' must separate successive digits"}, + {token.FLOAT, "1_.", "1_.", "'_' must separate successive digits"}, + {token.FLOAT, "0._1", "0._1", "'_' must separate successive digits"}, + {token.FLOAT, "2.7_e0", "2.7_e0", "'_' must separate successive digits"}, + {token.IMAG, "10e+12_i", "10e+12_i", "'_' must separate successive digits"}, + {token.INT, "0x___0", "0x___0", "'_' must separate successive digits"}, + {token.FLOAT, "0x1.0_p0", "0x1.0_p0", "'_' must separate successive digits"}, + } { + var s Scanner + var err string + s.Init(fset.AddFile("", fset.Base(), len(test.src)), []byte(test.src), func(_ token.Position, msg string) { + if err == "" { + err = msg + } + }, 0) + for i, want := range strings.Split(test.tokens, " ") { + err = "" + _, tok, lit := s.Scan() + + // compute lit where for tokens where lit is not defined + switch tok { + case token.PERIOD: + lit = "." + case token.ADD: + lit = "+" + case token.SUB: + lit = "-" + } + + if i == 0 { + if tok != test.tok { + t.Errorf("%q: got token %s; want %s", test.src, tok, test.tok) + } + if err != test.err { + t.Errorf("%q: got error %q; want %q", test.src, err, test.err) + } + } + + if lit != want { + t.Errorf("%q: got literal %q (%s); want %s", test.src, lit, tok, want) + } + } + + // make sure we read all + _, tok, _ := s.Scan() + if tok == token.SEMICOLON { + _, tok, _ = s.Scan() + } + if tok != token.EOF { + t.Errorf("%q: got %s; want EOF", test.src, tok) + } + } +} diff --git a/src/go/token/example_test.go b/src/go/token/example_test.go new file mode 100644 index 0000000..0011703 --- /dev/null +++ b/src/go/token/example_test.go @@ -0,0 +1,77 @@ +// Copyright 2018 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 token_test + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" +) + +func Example_retrievePositionInfo() { + fset := token.NewFileSet() + + const src = `package main + +import "fmt" + +import "go/token" + +//line :1:5 +type p = token.Pos + +const bad = token.NoPos + +//line fake.go:42:11 +func ok(pos p) bool { + return pos != bad +} + +/*line :7:9*/func main() { + fmt.Println(ok(bad) == bad.IsValid()) +} +` + + f, err := parser.ParseFile(fset, "main.go", src, 0) + if err != nil { + fmt.Println(err) + return + } + + // Print the location and kind of each declaration in f. + for _, decl := range f.Decls { + // Get the filename, line, and column back via the file set. + // We get both the relative and absolute position. + // The relative position is relative to the last line directive. + // The absolute position is the exact position in the source. + pos := decl.Pos() + relPosition := fset.Position(pos) + absPosition := fset.PositionFor(pos, false) + + // Either a FuncDecl or GenDecl, since we exit on error. + kind := "func" + if gen, ok := decl.(*ast.GenDecl); ok { + kind = gen.Tok.String() + } + + // If the relative and absolute positions differ, show both. + fmtPosition := relPosition.String() + if relPosition != absPosition { + fmtPosition += "[" + absPosition.String() + "]" + } + + fmt.Printf("%s: %s\n", fmtPosition, kind) + } + + //Output: + // + // main.go:3:1: import + // main.go:5:1: import + // main.go:1:5[main.go:8:1]: type + // main.go:3:1[main.go:10:1]: const + // fake.go:42:11[main.go:13:1]: func + // fake.go:7:9[main.go:17:14]: func +} diff --git a/src/go/token/position.go b/src/go/token/position.go new file mode 100644 index 0000000..a21f5fd --- /dev/null +++ b/src/go/token/position.go @@ -0,0 +1,552 @@ +// Copyright 2010 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 token + +import ( + "fmt" + "sort" + "sync" +) + +// ----------------------------------------------------------------------------- +// Positions + +// Position describes an arbitrary source position +// including the file, line, and column location. +// A Position is valid if the line number is > 0. +// +type Position struct { + Filename string // filename, if any + Offset int // offset, starting at 0 + Line int // line number, starting at 1 + Column int // column number, starting at 1 (byte count) +} + +// IsValid reports whether the position is valid. +func (pos *Position) IsValid() bool { return pos.Line > 0 } + +// String returns a string in one of several forms: +// +// file:line:column valid position with file name +// file:line valid position with file name but no column (column == 0) +// line:column valid position without file name +// line valid position without file name and no column (column == 0) +// file invalid position with file name +// - invalid position without file name +// +func (pos Position) String() string { + s := pos.Filename + if pos.IsValid() { + if s != "" { + s += ":" + } + s += fmt.Sprintf("%d", pos.Line) + if pos.Column != 0 { + s += fmt.Sprintf(":%d", pos.Column) + } + } + if s == "" { + s = "-" + } + return s +} + +// Pos is a compact encoding of a source position within a file set. +// It can be converted into a Position for a more convenient, but much +// larger, representation. +// +// The Pos value for a given file is a number in the range [base, base+size], +// where base and size are specified when a file is added to the file set. +// The difference between a Pos value and the corresponding file base +// corresponds to the byte offset of that position (represented by the Pos value) +// from the beginning of the file. Thus, the file base offset is the Pos value +// representing the first byte in the file. +// +// To create the Pos value for a specific source offset (measured in bytes), +// first add the respective file to the current file set using FileSet.AddFile +// and then call File.Pos(offset) for that file. Given a Pos value p +// for a specific file set fset, the corresponding Position value is +// obtained by calling fset.Position(p). +// +// Pos values can be compared directly with the usual comparison operators: +// If two Pos values p and q are in the same file, comparing p and q is +// equivalent to comparing the respective source file offsets. If p and q +// are in different files, p < q is true if the file implied by p was added +// to the respective file set before the file implied by q. +// +type Pos int + +// The zero value for Pos is NoPos; there is no file and line information +// associated with it, and NoPos.IsValid() is false. NoPos is always +// smaller than any other Pos value. The corresponding Position value +// for NoPos is the zero value for Position. +// +const NoPos Pos = 0 + +// IsValid reports whether the position is valid. +func (p Pos) IsValid() bool { + return p != NoPos +} + +// ----------------------------------------------------------------------------- +// File + +// A File is a handle for a file belonging to a FileSet. +// A File has a name, size, and line offset table. +// +type File struct { + set *FileSet + name string // file name as provided to AddFile + base int // Pos value range for this file is [base...base+size] + size int // file size as provided to AddFile + + // lines and infos are protected by mutex + mutex sync.Mutex + lines []int // lines contains the offset of the first character for each line (the first entry is always 0) + infos []lineInfo +} + +// Name returns the file name of file f as registered with AddFile. +func (f *File) Name() string { + return f.name +} + +// Base returns the base offset of file f as registered with AddFile. +func (f *File) Base() int { + return f.base +} + +// Size returns the size of file f as registered with AddFile. +func (f *File) Size() int { + return f.size +} + +// LineCount returns the number of lines in file f. +func (f *File) LineCount() int { + f.mutex.Lock() + n := len(f.lines) + f.mutex.Unlock() + return n +} + +// AddLine adds the line offset for a new line. +// The line offset must be larger than the offset for the previous line +// and smaller than the file size; otherwise the line offset is ignored. +// +func (f *File) AddLine(offset int) { + f.mutex.Lock() + if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size { + f.lines = append(f.lines, offset) + } + f.mutex.Unlock() +} + +// MergeLine merges a line with the following line. It is akin to replacing +// the newline character at the end of the line with a space (to not change the +// remaining offsets). To obtain the line number, consult e.g. Position.Line. +// MergeLine will panic if given an invalid line number. +// +func (f *File) MergeLine(line int) { + if line < 1 { + panic(fmt.Sprintf("invalid line number %d (should be >= 1)", line)) + } + f.mutex.Lock() + defer f.mutex.Unlock() + if line >= len(f.lines) { + panic(fmt.Sprintf("invalid line number %d (should be < %d)", line, len(f.lines))) + } + // To merge the line numbered with the line numbered , + // we need to remove the entry in lines corresponding to the line + // numbered . The entry in lines corresponding to the line + // numbered is located at index , since indices in lines + // are 0-based and line numbers are 1-based. + copy(f.lines[line:], f.lines[line+1:]) + f.lines = f.lines[:len(f.lines)-1] +} + +// SetLines sets the line offsets for a file and reports whether it succeeded. +// The line offsets are the offsets of the first character of each line; +// for instance for the content "ab\nc\n" the line offsets are {0, 3}. +// An empty file has an empty line offset table. +// Each line offset must be larger than the offset for the previous line +// and smaller than the file size; otherwise SetLines fails and returns +// false. +// Callers must not mutate the provided slice after SetLines returns. +// +func (f *File) SetLines(lines []int) bool { + // verify validity of lines table + size := f.size + for i, offset := range lines { + if i > 0 && offset <= lines[i-1] || size <= offset { + return false + } + } + + // set lines table + f.mutex.Lock() + f.lines = lines + f.mutex.Unlock() + return true +} + +// SetLinesForContent sets the line offsets for the given file content. +// It ignores position-altering //line comments. +func (f *File) SetLinesForContent(content []byte) { + var lines []int + line := 0 + for offset, b := range content { + if line >= 0 { + lines = append(lines, line) + } + line = -1 + if b == '\n' { + line = offset + 1 + } + } + + // set lines table + f.mutex.Lock() + f.lines = lines + f.mutex.Unlock() +} + +// LineStart returns the Pos value of the start of the specified line. +// It ignores any alternative positions set using AddLineColumnInfo. +// LineStart panics if the 1-based line number is invalid. +func (f *File) LineStart(line int) Pos { + if line < 1 { + panic(fmt.Sprintf("invalid line number %d (should be >= 1)", line)) + } + f.mutex.Lock() + defer f.mutex.Unlock() + if line > len(f.lines) { + panic(fmt.Sprintf("invalid line number %d (should be < %d)", line, len(f.lines))) + } + return Pos(f.base + f.lines[line-1]) +} + +// A lineInfo object describes alternative file, line, and column +// number information (such as provided via a //line directive) +// for a given file offset. +type lineInfo struct { + // fields are exported to make them accessible to gob + Offset int + Filename string + Line, Column int +} + +// AddLineInfo is like AddLineColumnInfo with a column = 1 argument. +// It is here for backward-compatibility for code prior to Go 1.11. +// +func (f *File) AddLineInfo(offset int, filename string, line int) { + f.AddLineColumnInfo(offset, filename, line, 1) +} + +// AddLineColumnInfo adds alternative file, line, and column number +// information for a given file offset. The offset must be larger +// than the offset for the previously added alternative line info +// and smaller than the file size; otherwise the information is +// ignored. +// +// AddLineColumnInfo is typically used to register alternative position +// information for line directives such as //line filename:line:column. +// +func (f *File) AddLineColumnInfo(offset int, filename string, line, column int) { + f.mutex.Lock() + if i := len(f.infos); i == 0 || f.infos[i-1].Offset < offset && offset < f.size { + f.infos = append(f.infos, lineInfo{offset, filename, line, column}) + } + f.mutex.Unlock() +} + +// Pos returns the Pos value for the given file offset; +// the offset must be <= f.Size(). +// f.Pos(f.Offset(p)) == p. +// +func (f *File) Pos(offset int) Pos { + if offset > f.size { + panic(fmt.Sprintf("invalid file offset %d (should be <= %d)", offset, f.size)) + } + return Pos(f.base + offset) +} + +// Offset returns the offset for the given file position p; +// p must be a valid Pos value in that file. +// f.Offset(f.Pos(offset)) == offset. +// +func (f *File) Offset(p Pos) int { + if int(p) < f.base || int(p) > f.base+f.size { + panic(fmt.Sprintf("invalid Pos value %d (should be in [%d, %d[)", p, f.base, f.base+f.size)) + } + return int(p) - f.base +} + +// Line returns the line number for the given file position p; +// p must be a Pos value in that file or NoPos. +// +func (f *File) Line(p Pos) int { + return f.Position(p).Line +} + +func searchLineInfos(a []lineInfo, x int) int { + return sort.Search(len(a), func(i int) bool { return a[i].Offset > x }) - 1 +} + +// unpack returns the filename and line and column number for a file offset. +// If adjusted is set, unpack will return the filename and line information +// possibly adjusted by //line comments; otherwise those comments are ignored. +// +func (f *File) unpack(offset int, adjusted bool) (filename string, line, column int) { + f.mutex.Lock() + defer f.mutex.Unlock() + filename = f.name + if i := searchInts(f.lines, offset); i >= 0 { + line, column = i+1, offset-f.lines[i]+1 + } + if adjusted && len(f.infos) > 0 { + // few files have extra line infos + if i := searchLineInfos(f.infos, offset); i >= 0 { + alt := &f.infos[i] + filename = alt.Filename + if i := searchInts(f.lines, alt.Offset); i >= 0 { + // i+1 is the line at which the alternative position was recorded + d := line - (i + 1) // line distance from alternative position base + line = alt.Line + d + if alt.Column == 0 { + // alternative column is unknown => relative column is unknown + // (the current specification for line directives requires + // this to apply until the next PosBase/line directive, + // not just until the new newline) + column = 0 + } else if d == 0 { + // the alternative position base is on the current line + // => column is relative to alternative column + column = alt.Column + (offset - alt.Offset) + } + } + } + } + return +} + +func (f *File) position(p Pos, adjusted bool) (pos Position) { + offset := int(p) - f.base + pos.Offset = offset + pos.Filename, pos.Line, pos.Column = f.unpack(offset, adjusted) + return +} + +// PositionFor returns the Position value for the given file position p. +// If adjusted is set, the position may be adjusted by position-altering +// //line comments; otherwise those comments are ignored. +// p must be a Pos value in f or NoPos. +// +func (f *File) PositionFor(p Pos, adjusted bool) (pos Position) { + if p != NoPos { + if int(p) < f.base || int(p) > f.base+f.size { + panic(fmt.Sprintf("invalid Pos value %d (should be in [%d, %d[)", p, f.base, f.base+f.size)) + } + pos = f.position(p, adjusted) + } + return +} + +// Position returns the Position value for the given file position p. +// Calling f.Position(p) is equivalent to calling f.PositionFor(p, true). +// +func (f *File) Position(p Pos) (pos Position) { + return f.PositionFor(p, true) +} + +// ----------------------------------------------------------------------------- +// FileSet + +// A FileSet represents a set of source files. +// Methods of file sets are synchronized; multiple goroutines +// may invoke them concurrently. +// +// The byte offsets for each file in a file set are mapped into +// distinct (integer) intervals, one interval [base, base+size] +// per file. Base represents the first byte in the file, and size +// is the corresponding file size. A Pos value is a value in such +// an interval. By determining the interval a Pos value belongs +// to, the file, its file base, and thus the byte offset (position) +// the Pos value is representing can be computed. +// +// When adding a new file, a file base must be provided. That can +// be any integer value that is past the end of any interval of any +// file already in the file set. For convenience, FileSet.Base provides +// such a value, which is simply the end of the Pos interval of the most +// recently added file, plus one. Unless there is a need to extend an +// interval later, using the FileSet.Base should be used as argument +// for FileSet.AddFile. +// +type FileSet struct { + mutex sync.RWMutex // protects the file set + base int // base offset for the next file + files []*File // list of files in the order added to the set + last *File // cache of last file looked up +} + +// NewFileSet creates a new file set. +func NewFileSet() *FileSet { + return &FileSet{ + base: 1, // 0 == NoPos + } +} + +// Base returns the minimum base offset that must be provided to +// AddFile when adding the next file. +// +func (s *FileSet) Base() int { + s.mutex.RLock() + b := s.base + s.mutex.RUnlock() + return b + +} + +// AddFile adds a new file with a given filename, base offset, and file size +// to the file set s and returns the file. Multiple files may have the same +// name. The base offset must not be smaller than the FileSet's Base(), and +// size must not be negative. As a special case, if a negative base is provided, +// the current value of the FileSet's Base() is used instead. +// +// Adding the file will set the file set's Base() value to base + size + 1 +// as the minimum base value for the next file. The following relationship +// exists between a Pos value p for a given file offset offs: +// +// int(p) = base + offs +// +// with offs in the range [0, size] and thus p in the range [base, base+size]. +// For convenience, File.Pos may be used to create file-specific position +// values from a file offset. +// +func (s *FileSet) AddFile(filename string, base, size int) *File { + s.mutex.Lock() + defer s.mutex.Unlock() + if base < 0 { + base = s.base + } + if base < s.base { + panic(fmt.Sprintf("invalid base %d (should be >= %d)", base, s.base)) + } + if size < 0 { + panic(fmt.Sprintf("invalid size %d (should be >= 0)", size)) + } + // base >= s.base && size >= 0 + f := &File{set: s, name: filename, base: base, size: size, lines: []int{0}} + base += size + 1 // +1 because EOF also has a position + if base < 0 { + panic("token.Pos offset overflow (> 2G of source code in file set)") + } + // add the file to the file set + s.base = base + s.files = append(s.files, f) + s.last = f + return f +} + +// Iterate calls f for the files in the file set in the order they were added +// until f returns false. +// +func (s *FileSet) Iterate(f func(*File) bool) { + for i := 0; ; i++ { + var file *File + s.mutex.RLock() + if i < len(s.files) { + file = s.files[i] + } + s.mutex.RUnlock() + if file == nil || !f(file) { + break + } + } +} + +func searchFiles(a []*File, x int) int { + return sort.Search(len(a), func(i int) bool { return a[i].base > x }) - 1 +} + +func (s *FileSet) file(p Pos) *File { + s.mutex.RLock() + // common case: p is in last file + if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size { + s.mutex.RUnlock() + return f + } + // p is not in last file - search all files + if i := searchFiles(s.files, int(p)); i >= 0 { + f := s.files[i] + // f.base <= int(p) by definition of searchFiles + if int(p) <= f.base+f.size { + s.mutex.RUnlock() + s.mutex.Lock() + s.last = f // race is ok - s.last is only a cache + s.mutex.Unlock() + return f + } + } + s.mutex.RUnlock() + return nil +} + +// File returns the file that contains the position p. +// If no such file is found (for instance for p == NoPos), +// the result is nil. +// +func (s *FileSet) File(p Pos) (f *File) { + if p != NoPos { + f = s.file(p) + } + return +} + +// PositionFor converts a Pos p in the fileset into a Position value. +// If adjusted is set, the position may be adjusted by position-altering +// //line comments; otherwise those comments are ignored. +// p must be a Pos value in s or NoPos. +// +func (s *FileSet) PositionFor(p Pos, adjusted bool) (pos Position) { + if p != NoPos { + if f := s.file(p); f != nil { + return f.position(p, adjusted) + } + } + return +} + +// Position converts a Pos p in the fileset into a Position value. +// Calling s.Position(p) is equivalent to calling s.PositionFor(p, true). +// +func (s *FileSet) Position(p Pos) (pos Position) { + return s.PositionFor(p, true) +} + +// ----------------------------------------------------------------------------- +// Helper functions + +func searchInts(a []int, x int) int { + // This function body is a manually inlined version of: + // + // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1 + // + // With better compiler optimizations, this may not be needed in the + // future, but at the moment this change improves the go/printer + // benchmark performance by ~30%. This has a direct impact on the + // speed of gofmt and thus seems worthwhile (2011-04-29). + // TODO(gri): Remove this when compilers have caught up. + i, j := 0, len(a) + for i < j { + h := i + (j-i)/2 // avoid overflow when computing h + // i ≤ h < j + if a[h] <= x { + i = h + 1 + } else { + j = h + } + } + return i - 1 +} diff --git a/src/go/token/position_test.go b/src/go/token/position_test.go new file mode 100644 index 0000000..7d465df --- /dev/null +++ b/src/go/token/position_test.go @@ -0,0 +1,341 @@ +// Copyright 2010 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 token + +import ( + "fmt" + "math/rand" + "sync" + "testing" +) + +func checkPos(t *testing.T, msg string, got, want Position) { + if got.Filename != want.Filename { + t.Errorf("%s: got filename = %q; want %q", msg, got.Filename, want.Filename) + } + if got.Offset != want.Offset { + t.Errorf("%s: got offset = %d; want %d", msg, got.Offset, want.Offset) + } + if got.Line != want.Line { + t.Errorf("%s: got line = %d; want %d", msg, got.Line, want.Line) + } + if got.Column != want.Column { + t.Errorf("%s: got column = %d; want %d", msg, got.Column, want.Column) + } +} + +func TestNoPos(t *testing.T) { + if NoPos.IsValid() { + t.Errorf("NoPos should not be valid") + } + var fset *FileSet + checkPos(t, "nil NoPos", fset.Position(NoPos), Position{}) + fset = NewFileSet() + checkPos(t, "fset NoPos", fset.Position(NoPos), Position{}) +} + +var tests = []struct { + filename string + source []byte // may be nil + size int + lines []int +}{ + {"a", []byte{}, 0, []int{}}, + {"b", []byte("01234"), 5, []int{0}}, + {"c", []byte("\n\n\n\n\n\n\n\n\n"), 9, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}}, + {"d", nil, 100, []int{0, 5, 10, 20, 30, 70, 71, 72, 80, 85, 90, 99}}, + {"e", nil, 777, []int{0, 80, 100, 120, 130, 180, 267, 455, 500, 567, 620}}, + {"f", []byte("package p\n\nimport \"fmt\""), 23, []int{0, 10, 11}}, + {"g", []byte("package p\n\nimport \"fmt\"\n"), 24, []int{0, 10, 11}}, + {"h", []byte("package p\n\nimport \"fmt\"\n "), 25, []int{0, 10, 11, 24}}, +} + +func linecol(lines []int, offs int) (int, int) { + prevLineOffs := 0 + for line, lineOffs := range lines { + if offs < lineOffs { + return line, offs - prevLineOffs + 1 + } + prevLineOffs = lineOffs + } + return len(lines), offs - prevLineOffs + 1 +} + +func verifyPositions(t *testing.T, fset *FileSet, f *File, lines []int) { + for offs := 0; offs < f.Size(); offs++ { + p := f.Pos(offs) + offs2 := f.Offset(p) + if offs2 != offs { + t.Errorf("%s, Offset: got offset %d; want %d", f.Name(), offs2, offs) + } + line, col := linecol(lines, offs) + msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p) + checkPos(t, msg, f.Position(f.Pos(offs)), Position{f.Name(), offs, line, col}) + checkPos(t, msg, fset.Position(p), Position{f.Name(), offs, line, col}) + } +} + +func makeTestSource(size int, lines []int) []byte { + src := make([]byte, size) + for _, offs := range lines { + if offs > 0 { + src[offs-1] = '\n' + } + } + return src +} + +func TestPositions(t *testing.T) { + const delta = 7 // a non-zero base offset increment + fset := NewFileSet() + for _, test := range tests { + // verify consistency of test case + if test.source != nil && len(test.source) != test.size { + t.Errorf("%s: inconsistent test case: got file size %d; want %d", test.filename, len(test.source), test.size) + } + + // add file and verify name and size + f := fset.AddFile(test.filename, fset.Base()+delta, test.size) + if f.Name() != test.filename { + t.Errorf("got filename %q; want %q", f.Name(), test.filename) + } + if f.Size() != test.size { + t.Errorf("%s: got file size %d; want %d", f.Name(), f.Size(), test.size) + } + if fset.File(f.Pos(0)) != f { + t.Errorf("%s: f.Pos(0) was not found in f", f.Name()) + } + + // add lines individually and verify all positions + for i, offset := range test.lines { + f.AddLine(offset) + if f.LineCount() != i+1 { + t.Errorf("%s, AddLine: got line count %d; want %d", f.Name(), f.LineCount(), i+1) + } + // adding the same offset again should be ignored + f.AddLine(offset) + if f.LineCount() != i+1 { + t.Errorf("%s, AddLine: got unchanged line count %d; want %d", f.Name(), f.LineCount(), i+1) + } + verifyPositions(t, fset, f, test.lines[0:i+1]) + } + + // add lines with SetLines and verify all positions + if ok := f.SetLines(test.lines); !ok { + t.Errorf("%s: SetLines failed", f.Name()) + } + if f.LineCount() != len(test.lines) { + t.Errorf("%s, SetLines: got line count %d; want %d", f.Name(), f.LineCount(), len(test.lines)) + } + verifyPositions(t, fset, f, test.lines) + + // add lines with SetLinesForContent and verify all positions + src := test.source + if src == nil { + // no test source available - create one from scratch + src = makeTestSource(test.size, test.lines) + } + f.SetLinesForContent(src) + if f.LineCount() != len(test.lines) { + t.Errorf("%s, SetLinesForContent: got line count %d; want %d", f.Name(), f.LineCount(), len(test.lines)) + } + verifyPositions(t, fset, f, test.lines) + } +} + +func TestLineInfo(t *testing.T) { + fset := NewFileSet() + f := fset.AddFile("foo", fset.Base(), 500) + lines := []int{0, 42, 77, 100, 210, 220, 277, 300, 333, 401} + // add lines individually and provide alternative line information + for _, offs := range lines { + f.AddLine(offs) + f.AddLineInfo(offs, "bar", 42) + } + // verify positions for all offsets + for offs := 0; offs <= f.Size(); offs++ { + p := f.Pos(offs) + _, col := linecol(lines, offs) + msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p) + checkPos(t, msg, f.Position(f.Pos(offs)), Position{"bar", offs, 42, col}) + checkPos(t, msg, fset.Position(p), Position{"bar", offs, 42, col}) + } +} + +func TestFiles(t *testing.T) { + fset := NewFileSet() + for i, test := range tests { + base := fset.Base() + if i%2 == 1 { + // Setting a negative base is equivalent to + // fset.Base(), so test some of each. + base = -1 + } + fset.AddFile(test.filename, base, test.size) + j := 0 + fset.Iterate(func(f *File) bool { + if f.Name() != tests[j].filename { + t.Errorf("got filename = %s; want %s", f.Name(), tests[j].filename) + } + j++ + return true + }) + if j != i+1 { + t.Errorf("got %d files; want %d", j, i+1) + } + } +} + +// FileSet.File should return nil if Pos is past the end of the FileSet. +func TestFileSetPastEnd(t *testing.T) { + fset := NewFileSet() + for _, test := range tests { + fset.AddFile(test.filename, fset.Base(), test.size) + } + if f := fset.File(Pos(fset.Base())); f != nil { + t.Errorf("got %v, want nil", f) + } +} + +func TestFileSetCacheUnlikely(t *testing.T) { + fset := NewFileSet() + offsets := make(map[string]int) + for _, test := range tests { + offsets[test.filename] = fset.Base() + fset.AddFile(test.filename, fset.Base(), test.size) + } + for file, pos := range offsets { + f := fset.File(Pos(pos)) + if f.Name() != file { + t.Errorf("got %q at position %d, want %q", f.Name(), pos, file) + } + } +} + +// issue 4345. Test that concurrent use of FileSet.Pos does not trigger a +// race in the FileSet position cache. +func TestFileSetRace(t *testing.T) { + fset := NewFileSet() + for i := 0; i < 100; i++ { + fset.AddFile(fmt.Sprintf("file-%d", i), fset.Base(), 1031) + } + max := int32(fset.Base()) + var stop sync.WaitGroup + r := rand.New(rand.NewSource(7)) + for i := 0; i < 2; i++ { + r := rand.New(rand.NewSource(r.Int63())) + stop.Add(1) + go func() { + for i := 0; i < 1000; i++ { + fset.Position(Pos(r.Int31n(max))) + } + stop.Done() + }() + } + stop.Wait() +} + +// issue 16548. Test that concurrent use of File.AddLine and FileSet.PositionFor +// does not trigger a race in the FileSet position cache. +func TestFileSetRace2(t *testing.T) { + const N = 1e3 + var ( + fset = NewFileSet() + file = fset.AddFile("", -1, N) + ch = make(chan int, 2) + ) + + go func() { + for i := 0; i < N; i++ { + file.AddLine(i) + } + ch <- 1 + }() + + go func() { + pos := file.Pos(0) + for i := 0; i < N; i++ { + fset.PositionFor(pos, false) + } + ch <- 1 + }() + + <-ch + <-ch +} + +func TestPositionFor(t *testing.T) { + src := []byte(` +foo +b +ar +//line :100 +foobar +//line bar:3 +done +`) + + const filename = "foo" + fset := NewFileSet() + f := fset.AddFile(filename, fset.Base(), len(src)) + f.SetLinesForContent(src) + + // verify position info + for i, offs := range f.lines { + got1 := f.PositionFor(f.Pos(offs), false) + got2 := f.PositionFor(f.Pos(offs), true) + got3 := f.Position(f.Pos(offs)) + want := Position{filename, offs, i + 1, 1} + checkPos(t, "1. PositionFor unadjusted", got1, want) + checkPos(t, "1. PositionFor adjusted", got2, want) + checkPos(t, "1. Position", got3, want) + } + + // manually add //line info on lines l1, l2 + const l1, l2 = 5, 7 + f.AddLineInfo(f.lines[l1-1], "", 100) + f.AddLineInfo(f.lines[l2-1], "bar", 3) + + // unadjusted position info must remain unchanged + for i, offs := range f.lines { + got1 := f.PositionFor(f.Pos(offs), false) + want := Position{filename, offs, i + 1, 1} + checkPos(t, "2. PositionFor unadjusted", got1, want) + } + + // adjusted position info should have changed + for i, offs := range f.lines { + got2 := f.PositionFor(f.Pos(offs), true) + got3 := f.Position(f.Pos(offs)) + want := Position{filename, offs, i + 1, 1} + // manually compute wanted filename and line + line := want.Line + if i+1 >= l1 { + want.Filename = "" + want.Line = line - l1 + 100 + } + if i+1 >= l2 { + want.Filename = "bar" + want.Line = line - l2 + 3 + } + checkPos(t, "3. PositionFor adjusted", got2, want) + checkPos(t, "3. Position", got3, want) + } +} + +func TestLineStart(t *testing.T) { + const src = "one\ntwo\nthree\n" + fset := NewFileSet() + f := fset.AddFile("input", -1, len(src)) + f.SetLinesForContent([]byte(src)) + + for line := 1; line <= 3; line++ { + pos := f.LineStart(line) + position := fset.Position(pos) + if position.Line != line || position.Column != 1 { + t.Errorf("LineStart(%d) returned wrong pos %d: %s", line, pos, position) + } + } +} diff --git a/src/go/token/serialize.go b/src/go/token/serialize.go new file mode 100644 index 0000000..d0ea345 --- /dev/null +++ b/src/go/token/serialize.go @@ -0,0 +1,71 @@ +// Copyright 2011 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 token + +type serializedFile struct { + // fields correspond 1:1 to fields with same (lower-case) name in File + Name string + Base int + Size int + Lines []int + Infos []lineInfo +} + +type serializedFileSet struct { + Base int + Files []serializedFile +} + +// Read calls decode to deserialize a file set into s; s must not be nil. +func (s *FileSet) Read(decode func(interface{}) error) error { + var ss serializedFileSet + if err := decode(&ss); err != nil { + return err + } + + s.mutex.Lock() + s.base = ss.Base + files := make([]*File, len(ss.Files)) + for i := 0; i < len(ss.Files); i++ { + f := &ss.Files[i] + files[i] = &File{ + set: s, + name: f.Name, + base: f.Base, + size: f.Size, + lines: f.Lines, + infos: f.Infos, + } + } + s.files = files + s.last = nil + s.mutex.Unlock() + + return nil +} + +// Write calls encode to serialize the file set s. +func (s *FileSet) Write(encode func(interface{}) error) error { + var ss serializedFileSet + + s.mutex.Lock() + ss.Base = s.base + files := make([]serializedFile, len(s.files)) + for i, f := range s.files { + f.mutex.Lock() + files[i] = serializedFile{ + Name: f.name, + Base: f.base, + Size: f.size, + Lines: append([]int(nil), f.lines...), + Infos: append([]lineInfo(nil), f.infos...), + } + f.mutex.Unlock() + } + ss.Files = files + s.mutex.Unlock() + + return encode(ss) +} diff --git a/src/go/token/serialize_test.go b/src/go/token/serialize_test.go new file mode 100644 index 0000000..4e925ad --- /dev/null +++ b/src/go/token/serialize_test.go @@ -0,0 +1,111 @@ +// Copyright 2011 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 token + +import ( + "bytes" + "encoding/gob" + "fmt" + "testing" +) + +// equal returns nil if p and q describe the same file set; +// otherwise it returns an error describing the discrepancy. +func equal(p, q *FileSet) error { + if p == q { + // avoid deadlock if p == q + return nil + } + + // not strictly needed for the test + p.mutex.Lock() + q.mutex.Lock() + defer q.mutex.Unlock() + defer p.mutex.Unlock() + + if p.base != q.base { + return fmt.Errorf("different bases: %d != %d", p.base, q.base) + } + + if len(p.files) != len(q.files) { + return fmt.Errorf("different number of files: %d != %d", len(p.files), len(q.files)) + } + + for i, f := range p.files { + g := q.files[i] + if f.set != p { + return fmt.Errorf("wrong fileset for %q", f.name) + } + if g.set != q { + return fmt.Errorf("wrong fileset for %q", g.name) + } + if f.name != g.name { + return fmt.Errorf("different filenames: %q != %q", f.name, g.name) + } + if f.base != g.base { + return fmt.Errorf("different base for %q: %d != %d", f.name, f.base, g.base) + } + if f.size != g.size { + return fmt.Errorf("different size for %q: %d != %d", f.name, f.size, g.size) + } + for j, l := range f.lines { + m := g.lines[j] + if l != m { + return fmt.Errorf("different offsets for %q", f.name) + } + } + for j, l := range f.infos { + m := g.infos[j] + if l.Offset != m.Offset || l.Filename != m.Filename || l.Line != m.Line { + return fmt.Errorf("different infos for %q", f.name) + } + } + } + + // we don't care about .last - it's just a cache + return nil +} + +func checkSerialize(t *testing.T, p *FileSet) { + var buf bytes.Buffer + encode := func(x interface{}) error { + return gob.NewEncoder(&buf).Encode(x) + } + if err := p.Write(encode); err != nil { + t.Errorf("writing fileset failed: %s", err) + return + } + q := NewFileSet() + decode := func(x interface{}) error { + return gob.NewDecoder(&buf).Decode(x) + } + if err := q.Read(decode); err != nil { + t.Errorf("reading fileset failed: %s", err) + return + } + if err := equal(p, q); err != nil { + t.Errorf("filesets not identical: %s", err) + } +} + +func TestSerialization(t *testing.T) { + p := NewFileSet() + checkSerialize(t, p) + // add some files + for i := 0; i < 10; i++ { + f := p.AddFile(fmt.Sprintf("file%d", i), p.Base()+i, i*100) + checkSerialize(t, p) + // add some lines and alternative file infos + line := 1000 + for offs := 0; offs < f.Size(); offs += 40 + i { + f.AddLine(offs) + if offs%7 == 0 { + f.AddLineInfo(offs, fmt.Sprintf("file%d", offs), line) + line += 33 + } + } + checkSerialize(t, p) + } +} diff --git a/src/go/token/token.go b/src/go/token/token.go new file mode 100644 index 0000000..96a1079 --- /dev/null +++ b/src/go/token/token.go @@ -0,0 +1,340 @@ +// Copyright 2009 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 token defines constants representing the lexical tokens of the Go +// programming language and basic operations on tokens (printing, predicates). +// +package token + +import ( + "strconv" + "unicode" + "unicode/utf8" +) + +// Token is the set of lexical tokens of the Go programming language. +type Token int + +// The list of tokens. +const ( + // Special tokens + ILLEGAL Token = iota + EOF + COMMENT + + literal_beg + // Identifiers and basic type literals + // (these tokens stand for classes of literals) + IDENT // main + INT // 12345 + FLOAT // 123.45 + IMAG // 123.45i + CHAR // 'a' + STRING // "abc" + literal_end + + operator_beg + // Operators and delimiters + ADD // + + SUB // - + MUL // * + QUO // / + REM // % + + AND // & + OR // | + XOR // ^ + SHL // << + SHR // >> + AND_NOT // &^ + + ADD_ASSIGN // += + SUB_ASSIGN // -= + MUL_ASSIGN // *= + QUO_ASSIGN // /= + REM_ASSIGN // %= + + AND_ASSIGN // &= + OR_ASSIGN // |= + XOR_ASSIGN // ^= + SHL_ASSIGN // <<= + SHR_ASSIGN // >>= + AND_NOT_ASSIGN // &^= + + LAND // && + LOR // || + ARROW // <- + INC // ++ + DEC // -- + + EQL // == + LSS // < + GTR // > + ASSIGN // = + NOT // ! + + NEQ // != + LEQ // <= + GEQ // >= + DEFINE // := + ELLIPSIS // ... + + LPAREN // ( + LBRACK // [ + LBRACE // { + COMMA // , + PERIOD // . + + RPAREN // ) + RBRACK // ] + RBRACE // } + SEMICOLON // ; + COLON // : + operator_end + + keyword_beg + // Keywords + BREAK + CASE + CHAN + CONST + CONTINUE + + DEFAULT + DEFER + ELSE + FALLTHROUGH + FOR + + FUNC + GO + GOTO + IF + IMPORT + + INTERFACE + MAP + PACKAGE + RANGE + RETURN + + SELECT + STRUCT + SWITCH + TYPE + VAR + keyword_end +) + +var tokens = [...]string{ + ILLEGAL: "ILLEGAL", + + EOF: "EOF", + COMMENT: "COMMENT", + + IDENT: "IDENT", + INT: "INT", + FLOAT: "FLOAT", + IMAG: "IMAG", + CHAR: "CHAR", + STRING: "STRING", + + ADD: "+", + SUB: "-", + MUL: "*", + QUO: "/", + REM: "%", + + AND: "&", + OR: "|", + XOR: "^", + SHL: "<<", + SHR: ">>", + AND_NOT: "&^", + + ADD_ASSIGN: "+=", + SUB_ASSIGN: "-=", + MUL_ASSIGN: "*=", + QUO_ASSIGN: "/=", + REM_ASSIGN: "%=", + + AND_ASSIGN: "&=", + OR_ASSIGN: "|=", + XOR_ASSIGN: "^=", + SHL_ASSIGN: "<<=", + SHR_ASSIGN: ">>=", + AND_NOT_ASSIGN: "&^=", + + LAND: "&&", + LOR: "||", + ARROW: "<-", + INC: "++", + DEC: "--", + + EQL: "==", + LSS: "<", + GTR: ">", + ASSIGN: "=", + NOT: "!", + + NEQ: "!=", + LEQ: "<=", + GEQ: ">=", + DEFINE: ":=", + ELLIPSIS: "...", + + LPAREN: "(", + LBRACK: "[", + LBRACE: "{", + COMMA: ",", + PERIOD: ".", + + RPAREN: ")", + RBRACK: "]", + RBRACE: "}", + SEMICOLON: ";", + COLON: ":", + + BREAK: "break", + CASE: "case", + CHAN: "chan", + CONST: "const", + CONTINUE: "continue", + + DEFAULT: "default", + DEFER: "defer", + ELSE: "else", + FALLTHROUGH: "fallthrough", + FOR: "for", + + FUNC: "func", + GO: "go", + GOTO: "goto", + IF: "if", + IMPORT: "import", + + INTERFACE: "interface", + MAP: "map", + PACKAGE: "package", + RANGE: "range", + RETURN: "return", + + SELECT: "select", + STRUCT: "struct", + SWITCH: "switch", + TYPE: "type", + VAR: "var", +} + +// String returns the string corresponding to the token tok. +// For operators, delimiters, and keywords the string is the actual +// token character sequence (e.g., for the token ADD, the string is +// "+"). For all other tokens the string corresponds to the token +// constant name (e.g. for the token IDENT, the string is "IDENT"). +// +func (tok Token) String() string { + s := "" + if 0 <= tok && tok < Token(len(tokens)) { + s = tokens[tok] + } + if s == "" { + s = "token(" + strconv.Itoa(int(tok)) + ")" + } + return s +} + +// A set of constants for precedence-based expression parsing. +// Non-operators have lowest precedence, followed by operators +// starting with precedence 1 up to unary operators. The highest +// precedence serves as "catch-all" precedence for selector, +// indexing, and other operator and delimiter tokens. +// +const ( + LowestPrec = 0 // non-operators + UnaryPrec = 6 + HighestPrec = 7 +) + +// Precedence returns the operator precedence of the binary +// operator op. If op is not a binary operator, the result +// is LowestPrecedence. +// +func (op Token) Precedence() int { + switch op { + case LOR: + return 1 + case LAND: + return 2 + case EQL, NEQ, LSS, LEQ, GTR, GEQ: + return 3 + case ADD, SUB, OR, XOR: + return 4 + case MUL, QUO, REM, SHL, SHR, AND, AND_NOT: + return 5 + } + return LowestPrec +} + +var keywords map[string]Token + +func init() { + keywords = make(map[string]Token) + for i := keyword_beg + 1; i < keyword_end; i++ { + keywords[tokens[i]] = i + } +} + +// Lookup maps an identifier to its keyword token or IDENT (if not a keyword). +// +func Lookup(ident string) Token { + if tok, is_keyword := keywords[ident]; is_keyword { + return tok + } + return IDENT +} + +// Predicates + +// IsLiteral returns true for tokens corresponding to identifiers +// and basic type literals; it returns false otherwise. +// +func (tok Token) IsLiteral() bool { return literal_beg < tok && tok < literal_end } + +// IsOperator returns true for tokens corresponding to operators and +// delimiters; it returns false otherwise. +// +func (tok Token) IsOperator() bool { return operator_beg < tok && tok < operator_end } + +// IsKeyword returns true for tokens corresponding to keywords; +// it returns false otherwise. +// +func (tok Token) IsKeyword() bool { return keyword_beg < tok && tok < keyword_end } + +// IsExported reports whether name starts with an upper-case letter. +// +func IsExported(name string) bool { + ch, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(ch) +} + +// IsKeyword reports whether name is a Go keyword, such as "func" or "return". +// +func IsKeyword(name string) bool { + // TODO: opt: use a perfect hash function instead of a global map. + _, ok := keywords[name] + return ok +} + +// IsIdentifier reports whether name is a Go identifier, that is, a non-empty +// string made up of letters, digits, and underscores, where the first character +// is not a digit. Keywords are not identifiers. +// +func IsIdentifier(name string) bool { + for i, c := range name { + if !unicode.IsLetter(c) && c != '_' && (i == 0 || !unicode.IsDigit(c)) { + return false + } + } + return name != "" && !IsKeyword(name) +} diff --git a/src/go/token/token_test.go b/src/go/token/token_test.go new file mode 100644 index 0000000..eff38cc --- /dev/null +++ b/src/go/token/token_test.go @@ -0,0 +1,33 @@ +// Copyright 2019 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 token + +import "testing" + +func TestIsIdentifier(t *testing.T) { + tests := []struct { + name string + in string + want bool + }{ + {"Empty", "", false}, + {"Space", " ", false}, + {"SpaceSuffix", "foo ", false}, + {"Number", "123", false}, + {"Keyword", "func", false}, + + {"LettersASCII", "foo", true}, + {"MixedASCII", "_bar123", true}, + {"UppercaseKeyword", "Func", true}, + {"LettersUnicode", "fóö", true}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if got := IsIdentifier(test.in); got != test.want { + t.Fatalf("IsIdentifier(%q) = %t, want %v", test.in, got, test.want) + } + }) + } +} diff --git a/src/go/types/api.go b/src/go/types/api.go new file mode 100644 index 0000000..d625959 --- /dev/null +++ b/src/go/types/api.go @@ -0,0 +1,408 @@ +// Copyright 2012 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 types declares the data types and implements +// the algorithms for type-checking of Go packages. Use +// Config.Check to invoke the type checker for a package. +// Alternatively, create a new type checker with NewChecker +// and invoke it incrementally by calling Checker.Files. +// +// Type-checking consists of several interdependent phases: +// +// Name resolution maps each identifier (ast.Ident) in the program to the +// language object (Object) it denotes. +// Use Info.{Defs,Uses,Implicits} for the results of name resolution. +// +// Constant folding computes the exact constant value (constant.Value) +// for every expression (ast.Expr) that is a compile-time constant. +// Use Info.Types[expr].Value for the results of constant folding. +// +// Type inference computes the type (Type) of every expression (ast.Expr) +// and checks for compliance with the language specification. +// Use Info.Types[expr].Type for the results of type inference. +// +// For a tutorial, see https://golang.org/s/types-tutorial. +// +package types + +import ( + "bytes" + "fmt" + "go/ast" + "go/constant" + "go/token" +) + +// An Error describes a type-checking error; it implements the error interface. +// A "soft" error is an error that still permits a valid interpretation of a +// package (such as "unused variable"); "hard" errors may lead to unpredictable +// behavior if ignored. +type Error struct { + Fset *token.FileSet // file set for interpretation of Pos + Pos token.Pos // error position + Msg string // error message + Soft bool // if set, error is "soft" + + // go116code is a future API, unexported as the set of error codes is large + // and likely to change significantly during experimentation. Tools wishing + // to preview this feature may read go116code using reflection (see + // errorcodes_test.go), but beware that there is no guarantee of future + // compatibility. + go116code errorCode + go116start token.Pos + go116end token.Pos +} + +// Error returns an error string formatted as follows: +// filename:line:column: message +func (err Error) Error() string { + return fmt.Sprintf("%s: %s", err.Fset.Position(err.Pos), err.Msg) +} + +// An Importer resolves import paths to Packages. +// +// CAUTION: This interface does not support the import of locally +// vendored packages. See https://golang.org/s/go15vendor. +// If possible, external implementations should implement ImporterFrom. +type Importer interface { + // Import returns the imported package for the given import path. + // The semantics is like for ImporterFrom.ImportFrom except that + // dir and mode are ignored (since they are not present). + Import(path string) (*Package, error) +} + +// ImportMode is reserved for future use. +type ImportMode int + +// An ImporterFrom resolves import paths to packages; it +// supports vendoring per https://golang.org/s/go15vendor. +// Use go/importer to obtain an ImporterFrom implementation. +type ImporterFrom interface { + // Importer is present for backward-compatibility. Calling + // Import(path) is the same as calling ImportFrom(path, "", 0); + // i.e., locally vendored packages may not be found. + // The types package does not call Import if an ImporterFrom + // is present. + Importer + + // ImportFrom returns the imported package for the given import + // path when imported by a package file located in dir. + // If the import failed, besides returning an error, ImportFrom + // is encouraged to cache and return a package anyway, if one + // was created. This will reduce package inconsistencies and + // follow-on type checker errors due to the missing package. + // The mode value must be 0; it is reserved for future use. + // Two calls to ImportFrom with the same path and dir must + // return the same package. + ImportFrom(path, dir string, mode ImportMode) (*Package, error) +} + +// A Config specifies the configuration for type checking. +// The zero value for Config is a ready-to-use default configuration. +type Config struct { + // If IgnoreFuncBodies is set, function bodies are not + // type-checked. + IgnoreFuncBodies bool + + // If FakeImportC is set, `import "C"` (for packages requiring Cgo) + // declares an empty "C" package and errors are omitted for qualified + // identifiers referring to package C (which won't find an object). + // This feature is intended for the standard library cmd/api tool. + // + // Caution: Effects may be unpredictable due to follow-on errors. + // Do not use casually! + FakeImportC bool + + // If go115UsesCgo is set, the type checker expects the + // _cgo_gotypes.go file generated by running cmd/cgo to be + // provided as a package source file. Qualified identifiers + // referring to package C will be resolved to cgo-provided + // declarations within _cgo_gotypes.go. + // + // It is an error to set both FakeImportC and go115UsesCgo. + go115UsesCgo bool + + // If Error != nil, it is called with each error found + // during type checking; err has dynamic type Error. + // Secondary errors (for instance, to enumerate all types + // involved in an invalid recursive type declaration) have + // error strings that start with a '\t' character. + // If Error == nil, type-checking stops with the first + // error found. + Error func(err error) + + // An importer is used to import packages referred to from + // import declarations. + // If the installed importer implements ImporterFrom, the type + // checker calls ImportFrom instead of Import. + // The type checker reports an error if an importer is needed + // but none was installed. + Importer Importer + + // If Sizes != nil, it provides the sizing functions for package unsafe. + // Otherwise SizesFor("gc", "amd64") is used instead. + Sizes Sizes + + // If DisableUnusedImportCheck is set, packages are not checked + // for unused imports. + DisableUnusedImportCheck bool +} + +func srcimporter_setUsesCgo(conf *Config) { + conf.go115UsesCgo = true +} + +// Info holds result type information for a type-checked package. +// Only the information for which a map is provided is collected. +// If the package has type errors, the collected information may +// be incomplete. +type Info struct { + // Types maps expressions to their types, and for constant + // expressions, also their values. Invalid expressions are + // omitted. + // + // For (possibly parenthesized) identifiers denoting built-in + // functions, the recorded signatures are call-site specific: + // if the call result is not a constant, the recorded type is + // an argument-specific signature. Otherwise, the recorded type + // is invalid. + // + // The Types map does not record the type of every identifier, + // only those that appear where an arbitrary expression is + // permitted. For instance, the identifier f in a selector + // expression x.f is found only in the Selections map, the + // identifier z in a variable declaration 'var z int' is found + // only in the Defs map, and identifiers denoting packages in + // qualified identifiers are collected in the Uses map. + Types map[ast.Expr]TypeAndValue + + // Defs maps identifiers to the objects they define (including + // package names, dots "." of dot-imports, and blank "_" identifiers). + // For identifiers that do not denote objects (e.g., the package name + // in package clauses, or symbolic variables t in t := x.(type) of + // type switch headers), the corresponding objects are nil. + // + // For an embedded field, Defs returns the field *Var it defines. + // + // Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos() + Defs map[*ast.Ident]Object + + // Uses maps identifiers to the objects they denote. + // + // For an embedded field, Uses returns the *TypeName it denotes. + // + // Invariant: Uses[id].Pos() != id.Pos() + Uses map[*ast.Ident]Object + + // Implicits maps nodes to their implicitly declared objects, if any. + // The following node and object types may appear: + // + // node declared object + // + // *ast.ImportSpec *PkgName for imports without renames + // *ast.CaseClause type-specific *Var for each type switch case clause (incl. default) + // *ast.Field anonymous parameter *Var (incl. unnamed results) + // + Implicits map[ast.Node]Object + + // Selections maps selector expressions (excluding qualified identifiers) + // to their corresponding selections. + Selections map[*ast.SelectorExpr]*Selection + + // Scopes maps ast.Nodes to the scopes they define. Package scopes are not + // associated with a specific node but with all files belonging to a package. + // Thus, the package scope can be found in the type-checked Package object. + // Scopes nest, with the Universe scope being the outermost scope, enclosing + // the package scope, which contains (one or more) files scopes, which enclose + // function scopes which in turn enclose statement and function literal scopes. + // Note that even though package-level functions are declared in the package + // scope, the function scopes are embedded in the file scope of the file + // containing the function declaration. + // + // The following node types may appear in Scopes: + // + // *ast.File + // *ast.FuncType + // *ast.BlockStmt + // *ast.IfStmt + // *ast.SwitchStmt + // *ast.TypeSwitchStmt + // *ast.CaseClause + // *ast.CommClause + // *ast.ForStmt + // *ast.RangeStmt + // + Scopes map[ast.Node]*Scope + + // InitOrder is the list of package-level initializers in the order in which + // they must be executed. Initializers referring to variables related by an + // initialization dependency appear in topological order, the others appear + // in source order. Variables without an initialization expression do not + // appear in this list. + InitOrder []*Initializer +} + +// TypeOf returns the type of expression e, or nil if not found. +// Precondition: the Types, Uses and Defs maps are populated. +// +func (info *Info) TypeOf(e ast.Expr) Type { + if t, ok := info.Types[e]; ok { + return t.Type + } + if id, _ := e.(*ast.Ident); id != nil { + if obj := info.ObjectOf(id); obj != nil { + return obj.Type() + } + } + return nil +} + +// ObjectOf returns the object denoted by the specified id, +// or nil if not found. +// +// If id is an embedded struct field, ObjectOf returns the field (*Var) +// it defines, not the type (*TypeName) it uses. +// +// Precondition: the Uses and Defs maps are populated. +// +func (info *Info) ObjectOf(id *ast.Ident) Object { + if obj := info.Defs[id]; obj != nil { + return obj + } + return info.Uses[id] +} + +// TypeAndValue reports the type and value (for constants) +// of the corresponding expression. +type TypeAndValue struct { + mode operandMode + Type Type + Value constant.Value +} + +// IsVoid reports whether the corresponding expression +// is a function call without results. +func (tv TypeAndValue) IsVoid() bool { + return tv.mode == novalue +} + +// IsType reports whether the corresponding expression specifies a type. +func (tv TypeAndValue) IsType() bool { + return tv.mode == typexpr +} + +// IsBuiltin reports whether the corresponding expression denotes +// a (possibly parenthesized) built-in function. +func (tv TypeAndValue) IsBuiltin() bool { + return tv.mode == builtin +} + +// IsValue reports whether the corresponding expression is a value. +// Builtins are not considered values. Constant values have a non- +// nil Value. +func (tv TypeAndValue) IsValue() bool { + switch tv.mode { + case constant_, variable, mapindex, value, commaok, commaerr: + return true + } + return false +} + +// IsNil reports whether the corresponding expression denotes the +// predeclared value nil. +func (tv TypeAndValue) IsNil() bool { + return tv.mode == value && tv.Type == Typ[UntypedNil] +} + +// Addressable reports whether the corresponding expression +// is addressable (https://golang.org/ref/spec#Address_operators). +func (tv TypeAndValue) Addressable() bool { + return tv.mode == variable +} + +// Assignable reports whether the corresponding expression +// is assignable to (provided a value of the right type). +func (tv TypeAndValue) Assignable() bool { + return tv.mode == variable || tv.mode == mapindex +} + +// HasOk reports whether the corresponding expression may be +// used on the rhs of a comma-ok assignment. +func (tv TypeAndValue) HasOk() bool { + return tv.mode == commaok || tv.mode == mapindex +} + +// An Initializer describes a package-level variable, or a list of variables in case +// of a multi-valued initialization expression, and the corresponding initialization +// expression. +type Initializer struct { + Lhs []*Var // var Lhs = Rhs + Rhs ast.Expr +} + +func (init *Initializer) String() string { + var buf bytes.Buffer + for i, lhs := range init.Lhs { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(lhs.Name()) + } + buf.WriteString(" = ") + WriteExpr(&buf, init.Rhs) + return buf.String() +} + +// Check type-checks a package and returns the resulting package object and +// the first error if any. Additionally, if info != nil, Check populates each +// of the non-nil maps in the Info struct. +// +// The package is marked as complete if no errors occurred, otherwise it is +// incomplete. See Config.Error for controlling behavior in the presence of +// errors. +// +// The package is specified by a list of *ast.Files and corresponding +// file set, and the package path the package is identified with. +// The clean path must not be empty or dot ("."). +func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, info *Info) (*Package, error) { + pkg := NewPackage(path, "") + return pkg, NewChecker(conf, fset, pkg, info).Files(files) +} + +// AssertableTo reports whether a value of type V can be asserted to have type T. +func AssertableTo(V *Interface, T Type) bool { + m, _ := (*Checker)(nil).assertableTo(V, T) + return m == nil +} + +// AssignableTo reports whether a value of type V is assignable to a variable of type T. +func AssignableTo(V, T Type) bool { + x := operand{mode: value, typ: V} + ok, _ := x.assignableTo(nil, T, nil) // check not needed for non-constant x + return ok +} + +// ConvertibleTo reports whether a value of type V is convertible to a value of type T. +func ConvertibleTo(V, T Type) bool { + x := operand{mode: value, typ: V} + return x.convertibleTo(nil, T) // check not needed for non-constant x +} + +// Implements reports whether type V implements interface T. +func Implements(V Type, T *Interface) bool { + f, _ := MissingMethod(V, T, true) + return f == nil +} + +// Identical reports whether x and y are identical types. +// Receivers of Signature types are ignored. +func Identical(x, y Type) bool { + return (*Checker)(nil).identical(x, y) +} + +// IdenticalIgnoreTags reports whether x and y are identical types if tags are ignored. +// Receivers of Signature types are ignored. +func IdenticalIgnoreTags(x, y Type) bool { + return (*Checker)(nil).identicalIgnoreTags(x, y) +} diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go new file mode 100644 index 0000000..75cebc9 --- /dev/null +++ b/src/go/types/api_test.go @@ -0,0 +1,1514 @@ +// Copyright 2013 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 types_test + +import ( + "bytes" + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "internal/testenv" + "reflect" + "regexp" + "strings" + "testing" + + . "go/types" +) + +func pkgFor(path, source string, info *Info) (*Package, error) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, path, source, 0) + if err != nil { + return nil, err + } + conf := Config{Importer: importer.Default()} + return conf.Check(f.Name.Name, fset, []*ast.File{f}, info) +} + +func mustTypecheck(t *testing.T, path, source string, info *Info) string { + pkg, err := pkgFor(path, source, info) + if err != nil { + name := path + if pkg != nil { + name = "package " + pkg.Name() + } + t.Fatalf("%s: didn't type-check (%s)", name, err) + } + return pkg.Name() +} + +func mayTypecheck(t *testing.T, path, source string, info *Info) string { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, path, source, 0) + if f == nil { // ignore errors unless f is nil + t.Fatalf("%s: unable to parse: %s", path, err) + } + conf := Config{ + Error: func(err error) {}, + Importer: importer.Default(), + } + pkg, _ := conf.Check(f.Name.Name, fset, []*ast.File{f}, info) + return pkg.Name() +} + +func TestValuesInfo(t *testing.T) { + var tests = []struct { + src string + expr string // constant expression + typ string // constant type + val string // constant value + }{ + {`package a0; const _ = false`, `false`, `untyped bool`, `false`}, + {`package a1; const _ = 0`, `0`, `untyped int`, `0`}, + {`package a2; const _ = 'A'`, `'A'`, `untyped rune`, `65`}, + {`package a3; const _ = 0.`, `0.`, `untyped float`, `0`}, + {`package a4; const _ = 0i`, `0i`, `untyped complex`, `(0 + 0i)`}, + {`package a5; const _ = "foo"`, `"foo"`, `untyped string`, `"foo"`}, + + {`package b0; var _ = false`, `false`, `bool`, `false`}, + {`package b1; var _ = 0`, `0`, `int`, `0`}, + {`package b2; var _ = 'A'`, `'A'`, `rune`, `65`}, + {`package b3; var _ = 0.`, `0.`, `float64`, `0`}, + {`package b4; var _ = 0i`, `0i`, `complex128`, `(0 + 0i)`}, + {`package b5; var _ = "foo"`, `"foo"`, `string`, `"foo"`}, + + {`package c0a; var _ = bool(false)`, `false`, `bool`, `false`}, + {`package c0b; var _ = bool(false)`, `bool(false)`, `bool`, `false`}, + {`package c0c; type T bool; var _ = T(false)`, `T(false)`, `c0c.T`, `false`}, + + {`package c1a; var _ = int(0)`, `0`, `int`, `0`}, + {`package c1b; var _ = int(0)`, `int(0)`, `int`, `0`}, + {`package c1c; type T int; var _ = T(0)`, `T(0)`, `c1c.T`, `0`}, + + {`package c2a; var _ = rune('A')`, `'A'`, `rune`, `65`}, + {`package c2b; var _ = rune('A')`, `rune('A')`, `rune`, `65`}, + {`package c2c; type T rune; var _ = T('A')`, `T('A')`, `c2c.T`, `65`}, + + {`package c3a; var _ = float32(0.)`, `0.`, `float32`, `0`}, + {`package c3b; var _ = float32(0.)`, `float32(0.)`, `float32`, `0`}, + {`package c3c; type T float32; var _ = T(0.)`, `T(0.)`, `c3c.T`, `0`}, + + {`package c4a; var _ = complex64(0i)`, `0i`, `complex64`, `(0 + 0i)`}, + {`package c4b; var _ = complex64(0i)`, `complex64(0i)`, `complex64`, `(0 + 0i)`}, + {`package c4c; type T complex64; var _ = T(0i)`, `T(0i)`, `c4c.T`, `(0 + 0i)`}, + + {`package c5a; var _ = string("foo")`, `"foo"`, `string`, `"foo"`}, + {`package c5b; var _ = string("foo")`, `string("foo")`, `string`, `"foo"`}, + {`package c5c; type T string; var _ = T("foo")`, `T("foo")`, `c5c.T`, `"foo"`}, + {`package c5d; var _ = string(65)`, `65`, `untyped int`, `65`}, + {`package c5e; var _ = string('A')`, `'A'`, `untyped rune`, `65`}, + {`package c5f; type T string; var _ = T('A')`, `'A'`, `untyped rune`, `65`}, + {`package c5g; var s uint; var _ = string(1 << s)`, `1 << s`, `untyped int`, ``}, + + {`package d0; var _ = []byte("foo")`, `"foo"`, `string`, `"foo"`}, + {`package d1; var _ = []byte(string("foo"))`, `"foo"`, `string`, `"foo"`}, + {`package d2; var _ = []byte(string("foo"))`, `string("foo")`, `string`, `"foo"`}, + {`package d3; type T []byte; var _ = T("foo")`, `"foo"`, `string`, `"foo"`}, + + {`package e0; const _ = float32( 1e-200)`, `float32(1e-200)`, `float32`, `0`}, + {`package e1; const _ = float32(-1e-200)`, `float32(-1e-200)`, `float32`, `0`}, + {`package e2; const _ = float64( 1e-2000)`, `float64(1e-2000)`, `float64`, `0`}, + {`package e3; const _ = float64(-1e-2000)`, `float64(-1e-2000)`, `float64`, `0`}, + {`package e4; const _ = complex64( 1e-200)`, `complex64(1e-200)`, `complex64`, `(0 + 0i)`}, + {`package e5; const _ = complex64(-1e-200)`, `complex64(-1e-200)`, `complex64`, `(0 + 0i)`}, + {`package e6; const _ = complex128( 1e-2000)`, `complex128(1e-2000)`, `complex128`, `(0 + 0i)`}, + {`package e7; const _ = complex128(-1e-2000)`, `complex128(-1e-2000)`, `complex128`, `(0 + 0i)`}, + + {`package f0 ; var _ float32 = 1e-200`, `1e-200`, `float32`, `0`}, + {`package f1 ; var _ float32 = -1e-200`, `-1e-200`, `float32`, `0`}, + {`package f2a; var _ float64 = 1e-2000`, `1e-2000`, `float64`, `0`}, + {`package f3a; var _ float64 = -1e-2000`, `-1e-2000`, `float64`, `0`}, + {`package f2b; var _ = 1e-2000`, `1e-2000`, `float64`, `0`}, + {`package f3b; var _ = -1e-2000`, `-1e-2000`, `float64`, `0`}, + {`package f4 ; var _ complex64 = 1e-200 `, `1e-200`, `complex64`, `(0 + 0i)`}, + {`package f5 ; var _ complex64 = -1e-200 `, `-1e-200`, `complex64`, `(0 + 0i)`}, + {`package f6a; var _ complex128 = 1e-2000i`, `1e-2000i`, `complex128`, `(0 + 0i)`}, + {`package f7a; var _ complex128 = -1e-2000i`, `-1e-2000i`, `complex128`, `(0 + 0i)`}, + {`package f6b; var _ = 1e-2000i`, `1e-2000i`, `complex128`, `(0 + 0i)`}, + {`package f7b; var _ = -1e-2000i`, `-1e-2000i`, `complex128`, `(0 + 0i)`}, + + {`package g0; const (a = len([iota]int{}); b; c); const _ = c`, `c`, `int`, `2`}, // issue #22341 + } + + for _, test := range tests { + info := Info{ + Types: make(map[ast.Expr]TypeAndValue), + } + name := mustTypecheck(t, "ValuesInfo", test.src, &info) + + // look for expression + var expr ast.Expr + for e := range info.Types { + if ExprString(e) == test.expr { + expr = e + break + } + } + if expr == nil { + t.Errorf("package %s: no expression found for %s", name, test.expr) + continue + } + tv := info.Types[expr] + + // check that type is correct + if got := tv.Type.String(); got != test.typ { + t.Errorf("package %s: got type %s; want %s", name, got, test.typ) + continue + } + + // if we have a constant, check that value is correct + if tv.Value != nil { + if got := tv.Value.ExactString(); got != test.val { + t.Errorf("package %s: got value %s; want %s", name, got, test.val) + } + } else { + if test.val != "" { + t.Errorf("package %s: no constant found; want %s", name, test.val) + } + } + } +} + +func TestTypesInfo(t *testing.T) { + var tests = []struct { + src string + expr string // expression + typ string // value type + }{ + // single-valued expressions of untyped constants + {`package b0; var x interface{} = false`, `false`, `bool`}, + {`package b1; var x interface{} = 0`, `0`, `int`}, + {`package b2; var x interface{} = 0.`, `0.`, `float64`}, + {`package b3; var x interface{} = 0i`, `0i`, `complex128`}, + {`package b4; var x interface{} = "foo"`, `"foo"`, `string`}, + + // comma-ok expressions + {`package p0; var x interface{}; var _, _ = x.(int)`, + `x.(int)`, + `(int, bool)`, + }, + {`package p1; var x interface{}; func _() { _, _ = x.(int) }`, + `x.(int)`, + `(int, bool)`, + }, + {`package p2a; type mybool bool; var m map[string]complex128; var b mybool; func _() { _, b = m["foo"] }`, + `m["foo"]`, + `(complex128, p2a.mybool)`, + }, + {`package p2b; var m map[string]complex128; var b bool; func _() { _, b = m["foo"] }`, + `m["foo"]`, + `(complex128, bool)`, + }, + {`package p3; var c chan string; var _, _ = <-c`, + `<-c`, + `(string, bool)`, + }, + + // issue 6796 + {`package issue6796_a; var x interface{}; var _, _ = (x.(int))`, + `x.(int)`, + `(int, bool)`, + }, + {`package issue6796_b; var c chan string; var _, _ = (<-c)`, + `(<-c)`, + `(string, bool)`, + }, + {`package issue6796_c; var c chan string; var _, _ = (<-c)`, + `<-c`, + `(string, bool)`, + }, + {`package issue6796_d; var c chan string; var _, _ = ((<-c))`, + `(<-c)`, + `(string, bool)`, + }, + {`package issue6796_e; func f(c chan string) { _, _ = ((<-c)) }`, + `(<-c)`, + `(string, bool)`, + }, + + // issue 7060 + {`package issue7060_a; var ( m map[int]string; x, ok = m[0] )`, + `m[0]`, + `(string, bool)`, + }, + {`package issue7060_b; var ( m map[int]string; x, ok interface{} = m[0] )`, + `m[0]`, + `(string, bool)`, + }, + {`package issue7060_c; func f(x interface{}, ok bool, m map[int]string) { x, ok = m[0] }`, + `m[0]`, + `(string, bool)`, + }, + {`package issue7060_d; var ( ch chan string; x, ok = <-ch )`, + `<-ch`, + `(string, bool)`, + }, + {`package issue7060_e; var ( ch chan string; x, ok interface{} = <-ch )`, + `<-ch`, + `(string, bool)`, + }, + {`package issue7060_f; func f(x interface{}, ok bool, ch chan string) { x, ok = <-ch }`, + `<-ch`, + `(string, bool)`, + }, + + // issue 28277 + {`package issue28277_a; func f(...int)`, + `...int`, + `[]int`, + }, + {`package issue28277_b; func f(a, b int, c ...[]struct{})`, + `...[]struct{}`, + `[][]struct{}`, + }, + + // tests for broken code that doesn't parse or type-check + {`package x0; func _() { var x struct {f string}; x.f := 0 }`, `x.f`, `string`}, + {`package x1; func _() { var z string; type x struct {f string}; y := &x{q: z}}`, `z`, `string`}, + {`package x2; func _() { var a, b string; type x struct {f string}; z := &x{f: a; f: b;}}`, `b`, `string`}, + {`package x3; var x = panic("");`, `panic`, `func(interface{})`}, + {`package x4; func _() { panic("") }`, `panic`, `func(interface{})`}, + {`package x5; func _() { var x map[string][...]int; x = map[string][...]int{"": {1,2,3}} }`, `x`, `map[string][-1]int`}, + } + + for _, test := range tests { + info := Info{Types: make(map[ast.Expr]TypeAndValue)} + name := mayTypecheck(t, "TypesInfo", test.src, &info) + + // look for expression type + var typ Type + for e, tv := range info.Types { + if ExprString(e) == test.expr { + typ = tv.Type + break + } + } + if typ == nil { + t.Errorf("package %s: no type found for %s", name, test.expr) + continue + } + + // check that type is correct + if got := typ.String(); got != test.typ { + t.Errorf("package %s: got %s; want %s", name, got, test.typ) + } + } +} + +func TestImplicitsInfo(t *testing.T) { + testenv.MustHaveGoBuild(t) + + var tests = []struct { + src string + want string + }{ + {`package p2; import . "fmt"; var _ = Println`, ""}, // no Implicits entry + {`package p0; import local "fmt"; var _ = local.Println`, ""}, // no Implicits entry + {`package p1; import "fmt"; var _ = fmt.Println`, "importSpec: package fmt"}, + + {`package p3; func f(x interface{}) { switch x.(type) { case int: } }`, ""}, // no Implicits entry + {`package p4; func f(x interface{}) { switch t := x.(type) { case int: _ = t } }`, "caseClause: var t int"}, + {`package p5; func f(x interface{}) { switch t := x.(type) { case int, uint: _ = t } }`, "caseClause: var t interface{}"}, + {`package p6; func f(x interface{}) { switch t := x.(type) { default: _ = t } }`, "caseClause: var t interface{}"}, + + {`package p7; func f(x int) {}`, ""}, // no Implicits entry + {`package p8; func f(int) {}`, "field: var int"}, + {`package p9; func f() (complex64) { return 0 }`, "field: var complex64"}, + {`package p10; type T struct{}; func (*T) f() {}`, "field: var *p10.T"}, + } + + for _, test := range tests { + info := Info{ + Implicits: make(map[ast.Node]Object), + } + name := mustTypecheck(t, "ImplicitsInfo", test.src, &info) + + // the test cases expect at most one Implicits entry + if len(info.Implicits) > 1 { + t.Errorf("package %s: %d Implicits entries found", name, len(info.Implicits)) + continue + } + + // extract Implicits entry, if any + var got string + for n, obj := range info.Implicits { + switch x := n.(type) { + case *ast.ImportSpec: + got = "importSpec" + case *ast.CaseClause: + got = "caseClause" + case *ast.Field: + got = "field" + default: + t.Fatalf("package %s: unexpected %T", name, x) + } + got += ": " + obj.String() + } + + // verify entry + if got != test.want { + t.Errorf("package %s: got %q; want %q", name, got, test.want) + } + } +} + +func predString(tv TypeAndValue) string { + var buf bytes.Buffer + pred := func(b bool, s string) { + if b { + if buf.Len() > 0 { + buf.WriteString(", ") + } + buf.WriteString(s) + } + } + + pred(tv.IsVoid(), "void") + pred(tv.IsType(), "type") + pred(tv.IsBuiltin(), "builtin") + pred(tv.IsValue() && tv.Value != nil, "const") + pred(tv.IsValue() && tv.Value == nil, "value") + pred(tv.IsNil(), "nil") + pred(tv.Addressable(), "addressable") + pred(tv.Assignable(), "assignable") + pred(tv.HasOk(), "hasOk") + + if buf.Len() == 0 { + return "invalid" + } + return buf.String() +} + +func TestPredicatesInfo(t *testing.T) { + testenv.MustHaveGoBuild(t) + + var tests = []struct { + src string + expr string + pred string + }{ + // void + {`package n0; func f() { f() }`, `f()`, `void`}, + + // types + {`package t0; type _ int`, `int`, `type`}, + {`package t1; type _ []int`, `[]int`, `type`}, + {`package t2; type _ func()`, `func()`, `type`}, + {`package t3; type _ func(int)`, `int`, `type`}, + {`package t3; type _ func(...int)`, `...int`, `type`}, + + // built-ins + {`package b0; var _ = len("")`, `len`, `builtin`}, + {`package b1; var _ = (len)("")`, `(len)`, `builtin`}, + + // constants + {`package c0; var _ = 42`, `42`, `const`}, + {`package c1; var _ = "foo" + "bar"`, `"foo" + "bar"`, `const`}, + {`package c2; const (i = 1i; _ = i)`, `i`, `const`}, + + // values + {`package v0; var (a, b int; _ = a + b)`, `a + b`, `value`}, + {`package v1; var _ = &[]int{1}`, `([]int literal)`, `value`}, + {`package v2; var _ = func(){}`, `(func() literal)`, `value`}, + {`package v4; func f() { _ = f }`, `f`, `value`}, + {`package v3; var _ *int = nil`, `nil`, `value, nil`}, + {`package v3; var _ *int = (nil)`, `(nil)`, `value, nil`}, + + // addressable (and thus assignable) operands + {`package a0; var (x int; _ = x)`, `x`, `value, addressable, assignable`}, + {`package a1; var (p *int; _ = *p)`, `*p`, `value, addressable, assignable`}, + {`package a2; var (s []int; _ = s[0])`, `s[0]`, `value, addressable, assignable`}, + {`package a3; var (s struct{f int}; _ = s.f)`, `s.f`, `value, addressable, assignable`}, + {`package a4; var (a [10]int; _ = a[0])`, `a[0]`, `value, addressable, assignable`}, + {`package a5; func _(x int) { _ = x }`, `x`, `value, addressable, assignable`}, + {`package a6; func _()(x int) { _ = x; return }`, `x`, `value, addressable, assignable`}, + {`package a7; type T int; func (x T) _() { _ = x }`, `x`, `value, addressable, assignable`}, + // composite literals are not addressable + + // assignable but not addressable values + {`package s0; var (m map[int]int; _ = m[0])`, `m[0]`, `value, assignable, hasOk`}, + {`package s1; var (m map[int]int; _, _ = m[0])`, `m[0]`, `value, assignable, hasOk`}, + + // hasOk expressions + {`package k0; var (ch chan int; _ = <-ch)`, `<-ch`, `value, hasOk`}, + {`package k1; var (ch chan int; _, _ = <-ch)`, `<-ch`, `value, hasOk`}, + + // missing entries + // - package names are collected in the Uses map + // - identifiers being declared are collected in the Defs map + {`package m0; import "os"; func _() { _ = os.Stdout }`, `os`, ``}, + {`package m1; import p "os"; func _() { _ = p.Stdout }`, `p`, ``}, + {`package m2; const c = 0`, `c`, ``}, + {`package m3; type T int`, `T`, ``}, + {`package m4; var v int`, `v`, ``}, + {`package m5; func f() {}`, `f`, ``}, + {`package m6; func _(x int) {}`, `x`, ``}, + {`package m6; func _()(x int) { return }`, `x`, ``}, + {`package m6; type T int; func (x T) _() {}`, `x`, ``}, + } + + for _, test := range tests { + info := Info{Types: make(map[ast.Expr]TypeAndValue)} + name := mustTypecheck(t, "PredicatesInfo", test.src, &info) + + // look for expression predicates + got := "" + for e, tv := range info.Types { + //println(name, ExprString(e)) + if ExprString(e) == test.expr { + got = predString(tv) + break + } + } + + if got != test.pred { + t.Errorf("package %s: got %s; want %s", name, got, test.pred) + } + } +} + +func TestScopesInfo(t *testing.T) { + testenv.MustHaveGoBuild(t) + + var tests = []struct { + src string + scopes []string // list of scope descriptors of the form kind:varlist + }{ + {`package p0`, []string{ + "file:", + }}, + {`package p1; import ( "fmt"; m "math"; _ "os" ); var ( _ = fmt.Println; _ = m.Pi )`, []string{ + "file:fmt m", + }}, + {`package p2; func _() {}`, []string{ + "file:", "func:", + }}, + {`package p3; func _(x, y int) {}`, []string{ + "file:", "func:x y", + }}, + {`package p4; func _(x, y int) { x, z := 1, 2; _ = z }`, []string{ + "file:", "func:x y z", // redeclaration of x + }}, + {`package p5; func _(x, y int) (u, _ int) { return }`, []string{ + "file:", "func:u x y", + }}, + {`package p6; func _() { { var x int; _ = x } }`, []string{ + "file:", "func:", "block:x", + }}, + {`package p7; func _() { if true {} }`, []string{ + "file:", "func:", "if:", "block:", + }}, + {`package p8; func _() { if x := 0; x < 0 { y := x; _ = y } }`, []string{ + "file:", "func:", "if:x", "block:y", + }}, + {`package p9; func _() { switch x := 0; x {} }`, []string{ + "file:", "func:", "switch:x", + }}, + {`package p10; func _() { switch x := 0; x { case 1: y := x; _ = y; default: }}`, []string{ + "file:", "func:", "switch:x", "case:y", "case:", + }}, + {`package p11; func _(t interface{}) { switch t.(type) {} }`, []string{ + "file:", "func:t", "type switch:", + }}, + {`package p12; func _(t interface{}) { switch t := t; t.(type) {} }`, []string{ + "file:", "func:t", "type switch:t", + }}, + {`package p13; func _(t interface{}) { switch x := t.(type) { case int: _ = x } }`, []string{ + "file:", "func:t", "type switch:", "case:x", // x implicitly declared + }}, + {`package p14; func _() { select{} }`, []string{ + "file:", "func:", + }}, + {`package p15; func _(c chan int) { select{ case <-c: } }`, []string{ + "file:", "func:c", "comm:", + }}, + {`package p16; func _(c chan int) { select{ case i := <-c: x := i; _ = x} }`, []string{ + "file:", "func:c", "comm:i x", + }}, + {`package p17; func _() { for{} }`, []string{ + "file:", "func:", "for:", "block:", + }}, + {`package p18; func _(n int) { for i := 0; i < n; i++ { _ = i } }`, []string{ + "file:", "func:n", "for:i", "block:", + }}, + {`package p19; func _(a []int) { for i := range a { _ = i} }`, []string{ + "file:", "func:a", "range:i", "block:", + }}, + {`package p20; var s int; func _(a []int) { for i, x := range a { s += x; _ = i } }`, []string{ + "file:", "func:a", "range:i x", "block:", + }}, + } + + for _, test := range tests { + info := Info{Scopes: make(map[ast.Node]*Scope)} + name := mustTypecheck(t, "ScopesInfo", test.src, &info) + + // number of scopes must match + if len(info.Scopes) != len(test.scopes) { + t.Errorf("package %s: got %d scopes; want %d", name, len(info.Scopes), len(test.scopes)) + } + + // scope descriptions must match + for node, scope := range info.Scopes { + kind := "" + switch node.(type) { + case *ast.File: + kind = "file" + case *ast.FuncType: + kind = "func" + case *ast.BlockStmt: + kind = "block" + case *ast.IfStmt: + kind = "if" + case *ast.SwitchStmt: + kind = "switch" + case *ast.TypeSwitchStmt: + kind = "type switch" + case *ast.CaseClause: + kind = "case" + case *ast.CommClause: + kind = "comm" + case *ast.ForStmt: + kind = "for" + case *ast.RangeStmt: + kind = "range" + } + + // look for matching scope description + desc := kind + ":" + strings.Join(scope.Names(), " ") + found := false + for _, d := range test.scopes { + if desc == d { + found = true + break + } + } + if !found { + t.Errorf("package %s: no matching scope found for %s", name, desc) + } + } + } +} + +func TestInitOrderInfo(t *testing.T) { + var tests = []struct { + src string + inits []string + }{ + {`package p0; var (x = 1; y = x)`, []string{ + "x = 1", "y = x", + }}, + {`package p1; var (a = 1; b = 2; c = 3)`, []string{ + "a = 1", "b = 2", "c = 3", + }}, + {`package p2; var (a, b, c = 1, 2, 3)`, []string{ + "a = 1", "b = 2", "c = 3", + }}, + {`package p3; var _ = f(); func f() int { return 1 }`, []string{ + "_ = f()", // blank var + }}, + {`package p4; var (a = 0; x = y; y = z; z = 0)`, []string{ + "a = 0", "z = 0", "y = z", "x = y", + }}, + {`package p5; var (a, _ = m[0]; m map[int]string)`, []string{ + "a, _ = m[0]", // blank var + }}, + {`package p6; var a, b = f(); func f() (_, _ int) { return z, z }; var z = 0`, []string{ + "z = 0", "a, b = f()", + }}, + {`package p7; var (a = func() int { return b }(); b = 1)`, []string{ + "b = 1", "a = (func() int literal)()", + }}, + {`package p8; var (a, b = func() (_, _ int) { return c, c }(); c = 1)`, []string{ + "c = 1", "a, b = (func() (_, _ int) literal)()", + }}, + {`package p9; type T struct{}; func (T) m() int { _ = y; return 0 }; var x, y = T.m, 1`, []string{ + "y = 1", "x = T.m", + }}, + {`package p10; var (d = c + b; a = 0; b = 0; c = 0)`, []string{ + "a = 0", "b = 0", "c = 0", "d = c + b", + }}, + {`package p11; var (a = e + c; b = d + c; c = 0; d = 0; e = 0)`, []string{ + "c = 0", "d = 0", "b = d + c", "e = 0", "a = e + c", + }}, + // emit an initializer for n:1 initializations only once (not for each node + // on the lhs which may appear in different order in the dependency graph) + {`package p12; var (a = x; b = 0; x, y = m[0]; m map[int]int)`, []string{ + "b = 0", "x, y = m[0]", "a = x", + }}, + // test case from spec section on package initialization + {`package p12 + + var ( + a = c + b + b = f() + c = f() + d = 3 + ) + + func f() int { + d++ + return d + }`, []string{ + "d = 3", "b = f()", "c = f()", "a = c + b", + }}, + // test case for issue 7131 + {`package main + + var counter int + func next() int { counter++; return counter } + + var _ = makeOrder() + func makeOrder() []int { return []int{f, b, d, e, c, a} } + + var a = next() + var b, c = next(), next() + var d, e, f = next(), next(), next() + `, []string{ + "a = next()", "b = next()", "c = next()", "d = next()", "e = next()", "f = next()", "_ = makeOrder()", + }}, + // test case for issue 10709 + {`package p13 + + var ( + v = t.m() + t = makeT(0) + ) + + type T struct{} + + func (T) m() int { return 0 } + + func makeT(n int) T { + if n > 0 { + return makeT(n-1) + } + return T{} + }`, []string{ + "t = makeT(0)", "v = t.m()", + }}, + // test case for issue 10709: same as test before, but variable decls swapped + {`package p14 + + var ( + t = makeT(0) + v = t.m() + ) + + type T struct{} + + func (T) m() int { return 0 } + + func makeT(n int) T { + if n > 0 { + return makeT(n-1) + } + return T{} + }`, []string{ + "t = makeT(0)", "v = t.m()", + }}, + // another candidate possibly causing problems with issue 10709 + {`package p15 + + var y1 = f1() + + func f1() int { return g1() } + func g1() int { f1(); return x1 } + + var x1 = 0 + + var y2 = f2() + + func f2() int { return g2() } + func g2() int { return x2 } + + var x2 = 0`, []string{ + "x1 = 0", "y1 = f1()", "x2 = 0", "y2 = f2()", + }}, + } + + for _, test := range tests { + info := Info{} + name := mustTypecheck(t, "InitOrderInfo", test.src, &info) + + // number of initializers must match + if len(info.InitOrder) != len(test.inits) { + t.Errorf("package %s: got %d initializers; want %d", name, len(info.InitOrder), len(test.inits)) + continue + } + + // initializers must match + for i, want := range test.inits { + got := info.InitOrder[i].String() + if got != want { + t.Errorf("package %s, init %d: got %s; want %s", name, i, got, want) + continue + } + } + } +} + +func TestMultiFileInitOrder(t *testing.T) { + fset := token.NewFileSet() + mustParse := func(src string) *ast.File { + f, err := parser.ParseFile(fset, "main", src, 0) + if err != nil { + t.Fatal(err) + } + return f + } + + fileA := mustParse(`package main; var a = 1`) + fileB := mustParse(`package main; var b = 2`) + + // The initialization order must not depend on the parse + // order of the files, only on the presentation order to + // the type-checker. + for _, test := range []struct { + files []*ast.File + want string + }{ + {[]*ast.File{fileA, fileB}, "[a = 1 b = 2]"}, + {[]*ast.File{fileB, fileA}, "[b = 2 a = 1]"}, + } { + var info Info + if _, err := new(Config).Check("main", fset, test.files, &info); err != nil { + t.Fatal(err) + } + if got := fmt.Sprint(info.InitOrder); got != test.want { + t.Fatalf("got %s; want %s", got, test.want) + } + } +} + +func TestFiles(t *testing.T) { + var sources = []string{ + "package p; type T struct{}; func (T) m1() {}", + "package p; func (T) m2() {}; var x interface{ m1(); m2() } = T{}", + "package p; func (T) m3() {}; var y interface{ m1(); m2(); m3() } = T{}", + "package p", + } + + var conf Config + fset := token.NewFileSet() + pkg := NewPackage("p", "p") + var info Info + check := NewChecker(&conf, fset, pkg, &info) + + for i, src := range sources { + filename := fmt.Sprintf("sources%d", i) + f, err := parser.ParseFile(fset, filename, src, 0) + if err != nil { + t.Fatal(err) + } + if err := check.Files([]*ast.File{f}); err != nil { + t.Error(err) + } + } + + // check InitOrder is [x y] + var vars []string + for _, init := range info.InitOrder { + for _, v := range init.Lhs { + vars = append(vars, v.Name()) + } + } + if got, want := fmt.Sprint(vars), "[x y]"; got != want { + t.Errorf("InitOrder == %s, want %s", got, want) + } +} + +type testImporter map[string]*Package + +func (m testImporter) Import(path string) (*Package, error) { + if pkg := m[path]; pkg != nil { + return pkg, nil + } + return nil, fmt.Errorf("package %q not found", path) +} + +func TestSelection(t *testing.T) { + selections := make(map[*ast.SelectorExpr]*Selection) + + fset := token.NewFileSet() + imports := make(testImporter) + conf := Config{Importer: imports} + makePkg := func(path, src string) { + f, err := parser.ParseFile(fset, path+".go", src, 0) + if err != nil { + t.Fatal(err) + } + pkg, err := conf.Check(path, fset, []*ast.File{f}, &Info{Selections: selections}) + if err != nil { + t.Fatal(err) + } + imports[path] = pkg + } + + const libSrc = ` +package lib +type T float64 +const C T = 3 +var V T +func F() {} +func (T) M() {} +` + const mainSrc = ` +package main +import "lib" + +type A struct { + *B + C +} + +type B struct { + b int +} + +func (B) f(int) + +type C struct { + c int +} + +func (C) g() +func (*C) h() + +func main() { + // qualified identifiers + var _ lib.T + _ = lib.C + _ = lib.F + _ = lib.V + _ = lib.T.M + + // fields + _ = A{}.B + _ = new(A).B + + _ = A{}.C + _ = new(A).C + + _ = A{}.b + _ = new(A).b + + _ = A{}.c + _ = new(A).c + + // methods + _ = A{}.f + _ = new(A).f + _ = A{}.g + _ = new(A).g + _ = new(A).h + + _ = B{}.f + _ = new(B).f + + _ = C{}.g + _ = new(C).g + _ = new(C).h + + // method expressions + _ = A.f + _ = (*A).f + _ = B.f + _ = (*B).f +}` + + wantOut := map[string][2]string{ + "lib.T.M": {"method expr (lib.T) M(lib.T)", ".[0]"}, + + "A{}.B": {"field (main.A) B *main.B", ".[0]"}, + "new(A).B": {"field (*main.A) B *main.B", "->[0]"}, + "A{}.C": {"field (main.A) C main.C", ".[1]"}, + "new(A).C": {"field (*main.A) C main.C", "->[1]"}, + "A{}.b": {"field (main.A) b int", "->[0 0]"}, + "new(A).b": {"field (*main.A) b int", "->[0 0]"}, + "A{}.c": {"field (main.A) c int", ".[1 0]"}, + "new(A).c": {"field (*main.A) c int", "->[1 0]"}, + + "A{}.f": {"method (main.A) f(int)", "->[0 0]"}, + "new(A).f": {"method (*main.A) f(int)", "->[0 0]"}, + "A{}.g": {"method (main.A) g()", ".[1 0]"}, + "new(A).g": {"method (*main.A) g()", "->[1 0]"}, + "new(A).h": {"method (*main.A) h()", "->[1 1]"}, // TODO(gri) should this report .[1 1] ? + "B{}.f": {"method (main.B) f(int)", ".[0]"}, + "new(B).f": {"method (*main.B) f(int)", "->[0]"}, + "C{}.g": {"method (main.C) g()", ".[0]"}, + "new(C).g": {"method (*main.C) g()", "->[0]"}, + "new(C).h": {"method (*main.C) h()", "->[1]"}, // TODO(gri) should this report .[1] ? + + "A.f": {"method expr (main.A) f(main.A, int)", "->[0 0]"}, + "(*A).f": {"method expr (*main.A) f(*main.A, int)", "->[0 0]"}, + "B.f": {"method expr (main.B) f(main.B, int)", ".[0]"}, + "(*B).f": {"method expr (*main.B) f(*main.B, int)", "->[0]"}, + } + + makePkg("lib", libSrc) + makePkg("main", mainSrc) + + for e, sel := range selections { + _ = sel.String() // assertion: must not panic + + start := fset.Position(e.Pos()).Offset + end := fset.Position(e.End()).Offset + syntax := mainSrc[start:end] // (all SelectorExprs are in main, not lib) + + direct := "." + if sel.Indirect() { + direct = "->" + } + got := [2]string{ + sel.String(), + fmt.Sprintf("%s%v", direct, sel.Index()), + } + want := wantOut[syntax] + if want != got { + t.Errorf("%s: got %q; want %q", syntax, got, want) + } + delete(wantOut, syntax) + + // We must explicitly assert properties of the + // Signature's receiver since it doesn't participate + // in Identical() or String(). + sig, _ := sel.Type().(*Signature) + if sel.Kind() == MethodVal { + got := sig.Recv().Type() + want := sel.Recv() + if !Identical(got, want) { + t.Errorf("%s: Recv() = %s, want %s", syntax, got, want) + } + } else if sig != nil && sig.Recv() != nil { + t.Errorf("%s: signature has receiver %s", sig, sig.Recv().Type()) + } + } + // Assert that all wantOut entries were used exactly once. + for syntax := range wantOut { + t.Errorf("no ast.Selection found with syntax %q", syntax) + } +} + +func TestIssue8518(t *testing.T) { + fset := token.NewFileSet() + imports := make(testImporter) + conf := Config{ + Error: func(err error) { t.Log(err) }, // don't exit after first error + Importer: imports, + } + makePkg := func(path, src string) { + f, err := parser.ParseFile(fset, path, src, 0) + if err != nil { + t.Fatal(err) + } + pkg, _ := conf.Check(path, fset, []*ast.File{f}, nil) // errors logged via conf.Error + imports[path] = pkg + } + + const libSrc = ` +package a +import "missing" +const C1 = foo +const C2 = missing.C +` + + const mainSrc = ` +package main +import "a" +var _ = a.C1 +var _ = a.C2 +` + + makePkg("a", libSrc) + makePkg("main", mainSrc) // don't crash when type-checking this package +} + +func TestLookupFieldOrMethod(t *testing.T) { + // Test cases assume a lookup of the form a.f or x.f, where a stands for an + // addressable value, and x for a non-addressable value (even though a variable + // for ease of test case writing). + var tests = []struct { + src string + found bool + index []int + indirect bool + }{ + // field lookups + {"var x T; type T struct{}", false, nil, false}, + {"var x T; type T struct{ f int }", true, []int{0}, false}, + {"var x T; type T struct{ a, b, f, c int }", true, []int{2}, false}, + + // method lookups + {"var a T; type T struct{}; func (T) f() {}", true, []int{0}, false}, + {"var a *T; type T struct{}; func (T) f() {}", true, []int{0}, true}, + {"var a T; type T struct{}; func (*T) f() {}", true, []int{0}, false}, + {"var a *T; type T struct{}; func (*T) f() {}", true, []int{0}, true}, // TODO(gri) should this report indirect = false? + + // collisions + {"type ( E1 struct{ f int }; E2 struct{ f int }; x struct{ E1; *E2 })", false, []int{1, 0}, false}, + {"type ( E1 struct{ f int }; E2 struct{}; x struct{ E1; *E2 }); func (E2) f() {}", false, []int{1, 0}, false}, + + // outside methodset + // (*T).f method exists, but value of type T is not addressable + {"var x T; type T struct{}; func (*T) f() {}", false, nil, true}, + } + + for _, test := range tests { + pkg, err := pkgFor("test", "package p;"+test.src, nil) + if err != nil { + t.Errorf("%s: incorrect test case: %s", test.src, err) + continue + } + + obj := pkg.Scope().Lookup("a") + if obj == nil { + if obj = pkg.Scope().Lookup("x"); obj == nil { + t.Errorf("%s: incorrect test case - no object a or x", test.src) + continue + } + } + + f, index, indirect := LookupFieldOrMethod(obj.Type(), obj.Name() == "a", pkg, "f") + if (f != nil) != test.found { + if f == nil { + t.Errorf("%s: got no object; want one", test.src) + } else { + t.Errorf("%s: got object = %v; want none", test.src, f) + } + } + if !sameSlice(index, test.index) { + t.Errorf("%s: got index = %v; want %v", test.src, index, test.index) + } + if indirect != test.indirect { + t.Errorf("%s: got indirect = %v; want %v", test.src, indirect, test.indirect) + } + } +} + +func sameSlice(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i, x := range a { + if x != b[i] { + return false + } + } + return true +} + +// TestScopeLookupParent ensures that (*Scope).LookupParent returns +// the correct result at various positions with the source. +func TestScopeLookupParent(t *testing.T) { + fset := token.NewFileSet() + imports := make(testImporter) + conf := Config{Importer: imports} + mustParse := func(src string) *ast.File { + f, err := parser.ParseFile(fset, "dummy.go", src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + return f + } + var info Info + makePkg := func(path string, files ...*ast.File) { + var err error + imports[path], err = conf.Check(path, fset, files, &info) + if err != nil { + t.Fatal(err) + } + } + + makePkg("lib", mustParse("package lib; var X int")) + // Each /*name=kind:line*/ comment makes the test look up the + // name at that point and checks that it resolves to a decl of + // the specified kind and line number. "undef" means undefined. + mainSrc := ` +/*lib=pkgname:5*/ /*X=var:1*/ /*Pi=const:8*/ /*T=typename:9*/ /*Y=var:10*/ /*F=func:12*/ +package main + +import "lib" +import . "lib" + +const Pi = 3.1415 +type T struct{} +var Y, _ = lib.X, X + +func F(){ + const pi, e = 3.1415, /*pi=undef*/ 2.71828 /*pi=const:13*/ /*e=const:13*/ + type /*t=undef*/ t /*t=typename:14*/ *t + print(Y) /*Y=var:10*/ + x, Y := Y, /*x=undef*/ /*Y=var:10*/ Pi /*x=var:16*/ /*Y=var:16*/ ; _ = x; _ = Y + var F = /*F=func:12*/ F /*F=var:17*/ ; _ = F + + var a []int + for i, x := range /*i=undef*/ /*x=var:16*/ a /*i=var:20*/ /*x=var:20*/ { _ = i; _ = x } + + var i interface{} + switch y := i.(type) { /*y=undef*/ + case /*y=undef*/ int /*y=var:23*/ : + case float32, /*y=undef*/ float64 /*y=var:23*/ : + default /*y=var:23*/: + println(y) + } + /*y=undef*/ + + switch int := i.(type) { + case /*int=typename:0*/ int /*int=var:31*/ : + println(int) + default /*int=var:31*/ : + } +} +/*main=undef*/ +` + + info.Uses = make(map[*ast.Ident]Object) + f := mustParse(mainSrc) + makePkg("main", f) + mainScope := imports["main"].Scope() + rx := regexp.MustCompile(`^/\*(\w*)=([\w:]*)\*/$`) + for _, group := range f.Comments { + for _, comment := range group.List { + // Parse the assertion in the comment. + m := rx.FindStringSubmatch(comment.Text) + if m == nil { + t.Errorf("%s: bad comment: %s", + fset.Position(comment.Pos()), comment.Text) + continue + } + name, want := m[1], m[2] + + // Look up the name in the innermost enclosing scope. + inner := mainScope.Innermost(comment.Pos()) + if inner == nil { + t.Errorf("%s: at %s: can't find innermost scope", + fset.Position(comment.Pos()), comment.Text) + continue + } + got := "undef" + if _, obj := inner.LookupParent(name, comment.Pos()); obj != nil { + kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types.")) + got = fmt.Sprintf("%s:%d", kind, fset.Position(obj.Pos()).Line) + } + if got != want { + t.Errorf("%s: at %s: %s resolved to %s, want %s", + fset.Position(comment.Pos()), comment.Text, name, got, want) + } + } + } + + // Check that for each referring identifier, + // a lookup of its name on the innermost + // enclosing scope returns the correct object. + + for id, wantObj := range info.Uses { + inner := mainScope.Innermost(id.Pos()) + if inner == nil { + t.Errorf("%s: can't find innermost scope enclosing %q", + fset.Position(id.Pos()), id.Name) + continue + } + + // Exclude selectors and qualified identifiers---lexical + // refs only. (Ideally, we'd see if the AST parent is a + // SelectorExpr, but that requires PathEnclosingInterval + // from golang.org/x/tools/go/ast/astutil.) + if id.Name == "X" { + continue + } + + _, gotObj := inner.LookupParent(id.Name, id.Pos()) + if gotObj != wantObj { + t.Errorf("%s: got %v, want %v", + fset.Position(id.Pos()), gotObj, wantObj) + continue + } + } +} + +func TestConvertibleTo(t *testing.T) { + for _, test := range []struct { + v, t Type + want bool + }{ + {Typ[Int], Typ[Int], true}, + {Typ[Int], Typ[Float32], true}, + {newDefined(Typ[Int]), Typ[Int], true}, + {newDefined(new(Struct)), new(Struct), true}, + {newDefined(Typ[Int]), new(Struct), false}, + {Typ[UntypedInt], Typ[Int], true}, + // Untyped string values are not permitted by the spec, so the below + // behavior is undefined. + {Typ[UntypedString], Typ[String], true}, + } { + if got := ConvertibleTo(test.v, test.t); got != test.want { + t.Errorf("ConvertibleTo(%v, %v) = %t, want %t", test.v, test.t, got, test.want) + } + } +} + +func TestAssignableTo(t *testing.T) { + for _, test := range []struct { + v, t Type + want bool + }{ + {Typ[Int], Typ[Int], true}, + {Typ[Int], Typ[Float32], false}, + {newDefined(Typ[Int]), Typ[Int], false}, + {newDefined(new(Struct)), new(Struct), true}, + {Typ[UntypedBool], Typ[Bool], true}, + {Typ[UntypedString], Typ[Bool], false}, + // Neither untyped string nor untyped numeric assignments arise during + // normal type checking, so the below behavior is technically undefined by + // the spec. + {Typ[UntypedString], Typ[String], true}, + {Typ[UntypedInt], Typ[Int], true}, + } { + if got := AssignableTo(test.v, test.t); got != test.want { + t.Errorf("AssignableTo(%v, %v) = %t, want %t", test.v, test.t, got, test.want) + } + } +} + +func TestIdentical_issue15173(t *testing.T) { + // Identical should allow nil arguments and be symmetric. + for _, test := range []struct { + x, y Type + want bool + }{ + {Typ[Int], Typ[Int], true}, + {Typ[Int], nil, false}, + {nil, Typ[Int], false}, + {nil, nil, true}, + } { + if got := Identical(test.x, test.y); got != test.want { + t.Errorf("Identical(%v, %v) = %t", test.x, test.y, got) + } + } +} + +func TestIssue15305(t *testing.T) { + const src = "package p; func f() int16; var _ = f(undef)" + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "issue15305.go", src, 0) + if err != nil { + t.Fatal(err) + } + conf := Config{ + Error: func(err error) {}, // allow errors + } + info := &Info{ + Types: make(map[ast.Expr]TypeAndValue), + } + conf.Check("p", fset, []*ast.File{f}, info) // ignore result + for e, tv := range info.Types { + if _, ok := e.(*ast.CallExpr); ok { + if tv.Type != Typ[Int16] { + t.Errorf("CallExpr has type %v, want int16", tv.Type) + } + return + } + } + t.Errorf("CallExpr has no type") +} + +// TestCompositeLitTypes verifies that Info.Types registers the correct +// types for composite literal expressions and composite literal type +// expressions. +func TestCompositeLitTypes(t *testing.T) { + for _, test := range []struct { + lit, typ string + }{ + {`[16]byte{}`, `[16]byte`}, + {`[...]byte{}`, `[0]byte`}, // test for issue #14092 + {`[...]int{1, 2, 3}`, `[3]int`}, // test for issue #14092 + {`[...]int{90: 0, 98: 1, 2}`, `[100]int`}, // test for issue #14092 + {`[]int{}`, `[]int`}, + {`map[string]bool{"foo": true}`, `map[string]bool`}, + {`struct{}{}`, `struct{}`}, + {`struct{x, y int; z complex128}{}`, `struct{x int; y int; z complex128}`}, + } { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, test.lit, "package p; var _ = "+test.lit, 0) + if err != nil { + t.Fatalf("%s: %v", test.lit, err) + } + + info := &Info{ + Types: make(map[ast.Expr]TypeAndValue), + } + if _, err = new(Config).Check("p", fset, []*ast.File{f}, info); err != nil { + t.Fatalf("%s: %v", test.lit, err) + } + + cmptype := func(x ast.Expr, want string) { + tv, ok := info.Types[x] + if !ok { + t.Errorf("%s: no Types entry found", test.lit) + return + } + if tv.Type == nil { + t.Errorf("%s: type is nil", test.lit) + return + } + if got := tv.Type.String(); got != want { + t.Errorf("%s: got %v, want %s", test.lit, got, want) + } + } + + // test type of composite literal expression + rhs := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Values[0] + cmptype(rhs, test.typ) + + // test type of composite literal type expression + cmptype(rhs.(*ast.CompositeLit).Type, test.typ) + } +} + +// TestObjectParents verifies that objects have parent scopes or not +// as specified by the Object interface. +func TestObjectParents(t *testing.T) { + const src = ` +package p + +const C = 0 + +type T1 struct { + a, b int + T2 +} + +type T2 interface { + im1() + im2() +} + +func (T1) m1() {} +func (*T1) m2() {} + +func f(x int) { y := x; print(y) } +` + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "src", src, 0) + if err != nil { + t.Fatal(err) + } + + info := &Info{ + Defs: make(map[*ast.Ident]Object), + } + if _, err = new(Config).Check("p", fset, []*ast.File{f}, info); err != nil { + t.Fatal(err) + } + + for ident, obj := range info.Defs { + if obj == nil { + // only package names and implicit vars have a nil object + // (in this test we only need to handle the package name) + if ident.Name != "p" { + t.Errorf("%v has nil object", ident) + } + continue + } + + // struct fields, type-associated and interface methods + // have no parent scope + wantParent := true + switch obj := obj.(type) { + case *Var: + if obj.IsField() { + wantParent = false + } + case *Func: + if obj.Type().(*Signature).Recv() != nil { // method + wantParent = false + } + } + + gotParent := obj.Parent() != nil + switch { + case gotParent && !wantParent: + t.Errorf("%v: want no parent, got %s", ident, obj.Parent()) + case !gotParent && wantParent: + t.Errorf("%v: no parent found", ident) + } + } +} + +// TestFailedImport tests that we don't get follow-on errors +// elsewhere in a package due to failing to import a package. +func TestFailedImport(t *testing.T) { + testenv.MustHaveGoBuild(t) + + const src = ` +package p + +import foo "go/types/thisdirectorymustnotexistotherwisethistestmayfail/foo" // should only see an error here + +const c = foo.C +type T = foo.T +var v T = c +func f(x T) T { return foo.F(x) } +` + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "src", src, 0) + if err != nil { + t.Fatal(err) + } + files := []*ast.File{f} + + // type-check using all possible importers + for _, compiler := range []string{"gc", "gccgo", "source"} { + errcount := 0 + conf := Config{ + Error: func(err error) { + // we should only see the import error + if errcount > 0 || !strings.Contains(err.Error(), "could not import") { + t.Errorf("for %s importer, got unexpected error: %v", compiler, err) + } + errcount++ + }, + Importer: importer.For(compiler, nil), + } + + info := &Info{ + Uses: make(map[*ast.Ident]Object), + } + pkg, _ := conf.Check("p", fset, files, info) + if pkg == nil { + t.Errorf("for %s importer, type-checking failed to return a package", compiler) + continue + } + + imports := pkg.Imports() + if len(imports) != 1 { + t.Errorf("for %s importer, got %d imports, want 1", compiler, len(imports)) + continue + } + imp := imports[0] + if imp.Name() != "foo" { + t.Errorf(`for %s importer, got %q, want "foo"`, compiler, imp.Name()) + continue + } + + // verify that all uses of foo refer to the imported package foo (imp) + for ident, obj := range info.Uses { + if ident.Name == "foo" { + if obj, ok := obj.(*PkgName); ok { + if obj.Imported() != imp { + t.Errorf("%s resolved to %v; want %v", ident, obj.Imported(), imp) + } + } else { + t.Errorf("%s resolved to %v; want package name", ident, obj) + } + } + } + } +} diff --git a/src/go/types/assignments.go b/src/go/types/assignments.go new file mode 100644 index 0000000..616564b --- /dev/null +++ b/src/go/types/assignments.go @@ -0,0 +1,357 @@ +// Copyright 2013 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. + +// This file implements initialization and assignment checks. + +package types + +import ( + "errors" + "go/ast" + "go/token" +) + +// assignment reports whether x can be assigned to a variable of type T, +// if necessary by attempting to convert untyped values to the appropriate +// type. context describes the context in which the assignment takes place. +// Use T == nil to indicate assignment to an untyped blank identifier. +// x.mode is set to invalid if the assignment failed. +func (check *Checker) assignment(x *operand, T Type, context string) { + check.singleValue(x) + + switch x.mode { + case invalid: + return // error reported before + case constant_, variable, mapindex, value, commaok, commaerr: + // ok + default: + unreachable() + } + + if isUntyped(x.typ) { + target := T + // spec: "If an untyped constant is assigned to a variable of interface + // type or the blank identifier, the constant is first converted to type + // bool, rune, int, float64, complex128 or string respectively, depending + // on whether the value is a boolean, rune, integer, floating-point, + // complex, or string constant." + if T == nil || IsInterface(T) { + if T == nil && x.typ == Typ[UntypedNil] { + check.errorf(x, _UntypedNil, "use of untyped nil in %s", context) + x.mode = invalid + return + } + target = Default(x.typ) + } + if err := check.canConvertUntyped(x, target); err != nil { + msg := check.sprintf("cannot use %s as %s value in %s", x, target, context) + code := _IncompatibleAssign + var ierr Error + if errors.As(err, &ierr) { + // Preserve these inner errors, as they are informative. + switch ierr.go116code { + case _TruncatedFloat: + msg += " (truncated)" + code = ierr.go116code + case _NumericOverflow: + msg += " (overflows)" + code = ierr.go116code + } + } + check.error(x, code, msg) + x.mode = invalid + return + } + } + // x.typ is typed + + // spec: "If a left-hand side is the blank identifier, any typed or + // non-constant value except for the predeclared identifier nil may + // be assigned to it." + if T == nil { + return + } + + reason := "" + if ok, code := x.assignableTo(check, T, &reason); !ok { + if reason != "" { + check.errorf(x, code, "cannot use %s as %s value in %s: %s", x, T, context, reason) + } else { + check.errorf(x, code, "cannot use %s as %s value in %s", x, T, context) + } + x.mode = invalid + } +} + +func (check *Checker) initConst(lhs *Const, x *operand) { + if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] { + if lhs.typ == nil { + lhs.typ = Typ[Invalid] + } + return + } + + // rhs must be a constant + if x.mode != constant_ { + check.errorf(x, _InvalidConstInit, "%s is not constant", x) + if lhs.typ == nil { + lhs.typ = Typ[Invalid] + } + return + } + assert(isConstType(x.typ)) + + // If the lhs doesn't have a type yet, use the type of x. + if lhs.typ == nil { + lhs.typ = x.typ + } + + check.assignment(x, lhs.typ, "constant declaration") + if x.mode == invalid { + return + } + + lhs.val = x.val +} + +func (check *Checker) initVar(lhs *Var, x *operand, context string) Type { + if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] { + if lhs.typ == nil { + lhs.typ = Typ[Invalid] + } + return nil + } + + // If the lhs doesn't have a type yet, use the type of x. + if lhs.typ == nil { + typ := x.typ + if isUntyped(typ) { + // convert untyped types to default types + if typ == Typ[UntypedNil] { + check.errorf(x, _UntypedNil, "use of untyped nil in %s", context) + lhs.typ = Typ[Invalid] + return nil + } + typ = Default(typ) + } + lhs.typ = typ + } + + check.assignment(x, lhs.typ, context) + if x.mode == invalid { + return nil + } + + return x.typ +} + +func (check *Checker) assignVar(lhs ast.Expr, x *operand) Type { + if x.mode == invalid || x.typ == Typ[Invalid] { + return nil + } + + // Determine if the lhs is a (possibly parenthesized) identifier. + ident, _ := unparen(lhs).(*ast.Ident) + + // Don't evaluate lhs if it is the blank identifier. + if ident != nil && ident.Name == "_" { + check.recordDef(ident, nil) + check.assignment(x, nil, "assignment to _ identifier") + if x.mode == invalid { + return nil + } + return x.typ + } + + // If the lhs is an identifier denoting a variable v, this assignment + // is not a 'use' of v. Remember current value of v.used and restore + // after evaluating the lhs via check.expr. + var v *Var + var v_used bool + if ident != nil { + if obj := check.lookup(ident.Name); obj != nil { + // It's ok to mark non-local variables, but ignore variables + // from other packages to avoid potential race conditions with + // dot-imported variables. + if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg { + v = w + v_used = v.used + } + } + } + + var z operand + check.expr(&z, lhs) + if v != nil { + v.used = v_used // restore v.used + } + + if z.mode == invalid || z.typ == Typ[Invalid] { + return nil + } + + // spec: "Each left-hand side operand must be addressable, a map index + // expression, or the blank identifier. Operands may be parenthesized." + switch z.mode { + case invalid: + return nil + case variable, mapindex: + // ok + default: + if sel, ok := z.expr.(*ast.SelectorExpr); ok { + var op operand + check.expr(&op, sel.X) + if op.mode == mapindex { + check.errorf(&z, _UnaddressableFieldAssign, "cannot assign to struct field %s in map", ExprString(z.expr)) + return nil + } + } + check.errorf(&z, _UnassignableOperand, "cannot assign to %s", &z) + return nil + } + + check.assignment(x, z.typ, "assignment") + if x.mode == invalid { + return nil + } + + return x.typ +} + +// If returnPos is valid, initVars is called to type-check the assignment of +// return expressions, and returnPos is the position of the return statement. +func (check *Checker) initVars(lhs []*Var, rhs []ast.Expr, returnPos token.Pos) { + l := len(lhs) + get, r, commaOk := unpack(func(x *operand, i int) { check.multiExpr(x, rhs[i]) }, len(rhs), l == 2 && !returnPos.IsValid()) + if get == nil || l != r { + // invalidate lhs and use rhs + for _, obj := range lhs { + if obj.typ == nil { + obj.typ = Typ[Invalid] + } + } + if get == nil { + return // error reported by unpack + } + check.useGetter(get, r) + if returnPos.IsValid() { + check.errorf(atPos(returnPos), _WrongResultCount, "wrong number of return values (want %d, got %d)", l, r) + return + } + check.errorf(rhs[0], _WrongAssignCount, "cannot initialize %d variables with %d values", l, r) + return + } + + context := "assignment" + if returnPos.IsValid() { + context = "return statement" + } + + var x operand + if commaOk { + var a [2]Type + for i := range a { + get(&x, i) + a[i] = check.initVar(lhs[i], &x, context) + } + check.recordCommaOkTypes(rhs[0], a) + return + } + + for i, lhs := range lhs { + get(&x, i) + check.initVar(lhs, &x, context) + } +} + +func (check *Checker) assignVars(lhs, rhs []ast.Expr) { + l := len(lhs) + get, r, commaOk := unpack(func(x *operand, i int) { check.multiExpr(x, rhs[i]) }, len(rhs), l == 2) + if get == nil { + check.useLHS(lhs...) + return // error reported by unpack + } + if l != r { + check.useGetter(get, r) + check.errorf(rhs[0], _WrongAssignCount, "cannot assign %d values to %d variables", r, l) + return + } + + var x operand + if commaOk { + var a [2]Type + for i := range a { + get(&x, i) + a[i] = check.assignVar(lhs[i], &x) + } + check.recordCommaOkTypes(rhs[0], a) + return + } + + for i, lhs := range lhs { + get(&x, i) + check.assignVar(lhs, &x) + } +} + +func (check *Checker) shortVarDecl(pos positioner, lhs, rhs []ast.Expr) { + top := len(check.delayed) + scope := check.scope + + // collect lhs variables + var newVars []*Var + var lhsVars = make([]*Var, len(lhs)) + for i, lhs := range lhs { + var obj *Var + if ident, _ := lhs.(*ast.Ident); ident != nil { + // Use the correct obj if the ident is redeclared. The + // variable's scope starts after the declaration; so we + // must use Scope.Lookup here and call Scope.Insert + // (via check.declare) later. + name := ident.Name + if alt := scope.Lookup(name); alt != nil { + // redeclared object must be a variable + if alt, _ := alt.(*Var); alt != nil { + obj = alt + } else { + check.errorf(lhs, _UnassignableOperand, "cannot assign to %s", lhs) + } + check.recordUse(ident, alt) + } else { + // declare new variable, possibly a blank (_) variable + obj = NewVar(ident.Pos(), check.pkg, name, nil) + if name != "_" { + newVars = append(newVars, obj) + } + check.recordDef(ident, obj) + } + } else { + check.useLHS(lhs) + check.invalidAST(lhs, "cannot declare %s", lhs) + } + if obj == nil { + obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable + } + lhsVars[i] = obj + } + + check.initVars(lhsVars, rhs, token.NoPos) + + // process function literals in rhs expressions before scope changes + check.processDelayed(top) + + // declare new variables + if len(newVars) > 0 { + // spec: "The scope of a constant or variable identifier declared inside + // a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl + // for short variable declarations) and ends at the end of the innermost + // containing block." + scopePos := rhs[len(rhs)-1].End() + for _, obj := range newVars { + check.declare(scope, nil, obj, scopePos) // recordObject already called + } + } else { + check.softErrorf(pos, _NoNewVar, "no new variables on left side of :=") + } +} diff --git a/src/go/types/builtins.go b/src/go/types/builtins.go new file mode 100644 index 0000000..fd35f78 --- /dev/null +++ b/src/go/types/builtins.go @@ -0,0 +1,697 @@ +// Copyright 2012 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. + +// This file implements typechecking of builtin function calls. + +package types + +import ( + "go/ast" + "go/constant" + "go/token" +) + +// builtin type-checks a call to the built-in specified by id and +// reports whether the call is valid, with *x holding the result; +// but x.expr is not set. If the call is invalid, the result is +// false, and *x is undefined. +// +func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ bool) { + // append is the only built-in that permits the use of ... for the last argument + bin := predeclaredFuncs[id] + if call.Ellipsis.IsValid() && id != _Append { + check.invalidOp(atPos(call.Ellipsis), + _InvalidDotDotDot, + "invalid use of ... with built-in %s", bin.name) + check.use(call.Args...) + return + } + + // For len(x) and cap(x) we need to know if x contains any function calls or + // receive operations. Save/restore current setting and set hasCallOrRecv to + // false for the evaluation of x so that we can check it afterwards. + // Note: We must do this _before_ calling unpack because unpack evaluates the + // first argument before we even call arg(x, 0)! + if id == _Len || id == _Cap { + defer func(b bool) { + check.hasCallOrRecv = b + }(check.hasCallOrRecv) + check.hasCallOrRecv = false + } + + // determine actual arguments + var arg getter + nargs := len(call.Args) + switch id { + default: + // make argument getter + arg, nargs, _ = unpack(func(x *operand, i int) { check.multiExpr(x, call.Args[i]) }, nargs, false) + if arg == nil { + return + } + // evaluate first argument, if present + if nargs > 0 { + arg(x, 0) + if x.mode == invalid { + return + } + } + case _Make, _New, _Offsetof, _Trace: + // arguments require special handling + } + + // check argument count + { + msg := "" + if nargs < bin.nargs { + msg = "not enough" + } else if !bin.variadic && nargs > bin.nargs { + msg = "too many" + } + if msg != "" { + check.invalidOp(inNode(call, call.Rparen), _WrongArgCount, "%s arguments for %s (expected %d, found %d)", msg, call, bin.nargs, nargs) + return + } + } + + switch id { + case _Append: + // append(s S, x ...T) S, where T is the element type of S + // spec: "The variadic function append appends zero or more values x to s of type + // S, which must be a slice type, and returns the resulting slice, also of type S. + // The values x are passed to a parameter of type ...T where T is the element type + // of S and the respective parameter passing rules apply." + S := x.typ + var T Type + if s, _ := S.Underlying().(*Slice); s != nil { + T = s.elem + } else { + check.invalidArg(x, _InvalidAppend, "%s is not a slice", x) + return + } + + // remember arguments that have been evaluated already + alist := []operand{*x} + + // spec: "As a special case, append also accepts a first argument assignable + // to type []byte with a second argument of string type followed by ... . + // This form appends the bytes of the string. + if nargs == 2 && call.Ellipsis.IsValid() { + if ok, _ := x.assignableTo(check, NewSlice(universeByte), nil); ok { + arg(x, 1) + if x.mode == invalid { + return + } + if isString(x.typ) { + if check.Types != nil { + sig := makeSig(S, S, x.typ) + sig.variadic = true + check.recordBuiltinType(call.Fun, sig) + } + x.mode = value + x.typ = S + break + } + alist = append(alist, *x) + // fallthrough + } + } + + // check general case by creating custom signature + sig := makeSig(S, S, NewSlice(T)) // []T required for variadic signature + sig.variadic = true + check.arguments(x, call, sig, func(x *operand, i int) { + // only evaluate arguments that have not been evaluated before + if i < len(alist) { + *x = alist[i] + return + } + arg(x, i) + }, nargs) + // ok to continue even if check.arguments reported errors + + x.mode = value + x.typ = S + if check.Types != nil { + check.recordBuiltinType(call.Fun, sig) + } + + case _Cap, _Len: + // cap(x) + // len(x) + mode := invalid + var typ Type + var val constant.Value + switch typ = implicitArrayDeref(x.typ.Underlying()); t := typ.(type) { + case *Basic: + if isString(t) && id == _Len { + if x.mode == constant_ { + mode = constant_ + val = constant.MakeInt64(int64(len(constant.StringVal(x.val)))) + } else { + mode = value + } + } + + case *Array: + mode = value + // spec: "The expressions len(s) and cap(s) are constants + // if the type of s is an array or pointer to an array and + // the expression s does not contain channel receives or + // function calls; in this case s is not evaluated." + if !check.hasCallOrRecv { + mode = constant_ + if t.len >= 0 { + val = constant.MakeInt64(t.len) + } else { + val = constant.MakeUnknown() + } + } + + case *Slice, *Chan: + mode = value + + case *Map: + if id == _Len { + mode = value + } + } + + if mode == invalid && typ != Typ[Invalid] { + code := _InvalidCap + if id == _Len { + code = _InvalidLen + } + check.invalidArg(x, code, "%s for %s", x, bin.name) + return + } + + x.mode = mode + x.typ = Typ[Int] + x.val = val + if check.Types != nil && mode != constant_ { + check.recordBuiltinType(call.Fun, makeSig(x.typ, typ)) + } + + case _Close: + // close(c) + c, _ := x.typ.Underlying().(*Chan) + if c == nil { + check.invalidArg(x, _InvalidClose, "%s is not a channel", x) + return + } + if c.dir == RecvOnly { + check.invalidArg(x, _InvalidClose, "%s must not be a receive-only channel", x) + return + } + + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, c)) + } + + case _Complex: + // complex(x, y floatT) complexT + var y operand + arg(&y, 1) + if y.mode == invalid { + return + } + + // convert or check untyped arguments + d := 0 + if isUntyped(x.typ) { + d |= 1 + } + if isUntyped(y.typ) { + d |= 2 + } + switch d { + case 0: + // x and y are typed => nothing to do + case 1: + // only x is untyped => convert to type of y + check.convertUntyped(x, y.typ) + case 2: + // only y is untyped => convert to type of x + check.convertUntyped(&y, x.typ) + case 3: + // x and y are untyped => + // 1) if both are constants, convert them to untyped + // floating-point numbers if possible, + // 2) if one of them is not constant (possible because + // it contains a shift that is yet untyped), convert + // both of them to float64 since they must have the + // same type to succeed (this will result in an error + // because shifts of floats are not permitted) + if x.mode == constant_ && y.mode == constant_ { + toFloat := func(x *operand) { + if isNumeric(x.typ) && constant.Sign(constant.Imag(x.val)) == 0 { + x.typ = Typ[UntypedFloat] + } + } + toFloat(x) + toFloat(&y) + } else { + check.convertUntyped(x, Typ[Float64]) + check.convertUntyped(&y, Typ[Float64]) + // x and y should be invalid now, but be conservative + // and check below + } + } + if x.mode == invalid || y.mode == invalid { + return + } + + // both argument types must be identical + if !check.identical(x.typ, y.typ) { + check.invalidArg(x, _InvalidComplex, "mismatched types %s and %s", x.typ, y.typ) + return + } + + // the argument types must be of floating-point type + if !isFloat(x.typ) { + check.invalidArg(x, _InvalidComplex, "arguments have type %s, expected floating-point", x.typ) + return + } + + // if both arguments are constants, the result is a constant + if x.mode == constant_ && y.mode == constant_ { + x.val = constant.BinaryOp(constant.ToFloat(x.val), token.ADD, constant.MakeImag(constant.ToFloat(y.val))) + } else { + x.mode = value + } + + // determine result type + var res BasicKind + switch x.typ.Underlying().(*Basic).kind { + case Float32: + res = Complex64 + case Float64: + res = Complex128 + case UntypedFloat: + res = UntypedComplex + default: + unreachable() + } + resTyp := Typ[res] + + if check.Types != nil && x.mode != constant_ { + check.recordBuiltinType(call.Fun, makeSig(resTyp, x.typ, x.typ)) + } + + x.typ = resTyp + + case _Copy: + // copy(x, y []T) int + var dst Type + if t, _ := x.typ.Underlying().(*Slice); t != nil { + dst = t.elem + } + + var y operand + arg(&y, 1) + if y.mode == invalid { + return + } + var src Type + switch t := y.typ.Underlying().(type) { + case *Basic: + if isString(y.typ) { + src = universeByte + } + case *Slice: + src = t.elem + } + + if dst == nil || src == nil { + check.invalidArg(x, _InvalidCopy, "copy expects slice arguments; found %s and %s", x, &y) + return + } + + if !check.identical(dst, src) { + check.invalidArg(x, _InvalidCopy, "arguments to copy %s and %s have different element types %s and %s", x, &y, dst, src) + return + } + + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(Typ[Int], x.typ, y.typ)) + } + x.mode = value + x.typ = Typ[Int] + + case _Delete: + // delete(m, k) + m, _ := x.typ.Underlying().(*Map) + if m == nil { + check.invalidArg(x, _InvalidDelete, "%s is not a map", x) + return + } + arg(x, 1) // k + if x.mode == invalid { + return + } + + if ok, code := x.assignableTo(check, m.key, nil); !ok { + check.invalidArg(x, code, "%s is not assignable to %s", x, m.key) + return + } + + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, m, m.key)) + } + + case _Imag, _Real: + // imag(complexT) floatT + // real(complexT) floatT + + // convert or check untyped argument + if isUntyped(x.typ) { + if x.mode == constant_ { + // an untyped constant number can always be considered + // as a complex constant + if isNumeric(x.typ) { + x.typ = Typ[UntypedComplex] + } + } else { + // an untyped non-constant argument may appear if + // it contains a (yet untyped non-constant) shift + // expression: convert it to complex128 which will + // result in an error (shift of complex value) + check.convertUntyped(x, Typ[Complex128]) + // x should be invalid now, but be conservative and check + if x.mode == invalid { + return + } + } + } + + // the argument must be of complex type + if !isComplex(x.typ) { + code := _InvalidImag + if id == _Real { + code = _InvalidReal + } + check.invalidArg(x, code, "argument has type %s, expected complex type", x.typ) + return + } + + // if the argument is a constant, the result is a constant + if x.mode == constant_ { + if id == _Real { + x.val = constant.Real(x.val) + } else { + x.val = constant.Imag(x.val) + } + } else { + x.mode = value + } + + // determine result type + var res BasicKind + switch x.typ.Underlying().(*Basic).kind { + case Complex64: + res = Float32 + case Complex128: + res = Float64 + case UntypedComplex: + res = UntypedFloat + default: + unreachable() + } + resTyp := Typ[res] + + if check.Types != nil && x.mode != constant_ { + check.recordBuiltinType(call.Fun, makeSig(resTyp, x.typ)) + } + + x.typ = resTyp + + case _Make: + // make(T, n) + // make(T, n, m) + // (no argument evaluated yet) + arg0 := call.Args[0] + T := check.typ(arg0) + if T == Typ[Invalid] { + return + } + + var min int // minimum number of arguments + switch T.Underlying().(type) { + case *Slice: + min = 2 + case *Map, *Chan: + min = 1 + default: + check.invalidArg(arg0, _InvalidMake, "cannot make %s; type must be slice, map, or channel", arg0) + return + } + if nargs < min || min+1 < nargs { + check.errorf(call, _WrongArgCount, "%v expects %d or %d arguments; found %d", call, min, min+1, nargs) + return + } + types := []Type{T} + var sizes []int64 // constant integer arguments, if any + for _, arg := range call.Args[1:] { + typ, size := check.index(arg, -1) // ok to continue with typ == Typ[Invalid] + types = append(types, typ) + if size >= 0 { + sizes = append(sizes, size) + } + } + if len(sizes) == 2 && sizes[0] > sizes[1] { + check.invalidArg(call.Args[1], _SwappedMakeArgs, "length and capacity swapped") + // safe to continue + } + x.mode = value + x.typ = T + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ, types...)) + } + + case _New: + // new(T) + // (no argument evaluated yet) + T := check.typ(call.Args[0]) + if T == Typ[Invalid] { + return + } + + x.mode = value + x.typ = &Pointer{base: T} + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ, T)) + } + + case _Panic: + // panic(x) + // record panic call if inside a function with result parameters + // (for use in Checker.isTerminating) + if check.sig != nil && check.sig.results.Len() > 0 { + // function has result parameters + p := check.isPanic + if p == nil { + // allocate lazily + p = make(map[*ast.CallExpr]bool) + check.isPanic = p + } + p[call] = true + } + + check.assignment(x, &emptyInterface, "argument to panic") + if x.mode == invalid { + return + } + + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, &emptyInterface)) + } + + case _Print, _Println: + // print(x, y, ...) + // println(x, y, ...) + var params []Type + if nargs > 0 { + params = make([]Type, nargs) + for i := 0; i < nargs; i++ { + if i > 0 { + arg(x, i) // first argument already evaluated + } + check.assignment(x, nil, "argument to "+predeclaredFuncs[id].name) + if x.mode == invalid { + // TODO(gri) "use" all arguments? + return + } + params[i] = x.typ + } + } + + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, params...)) + } + + case _Recover: + // recover() interface{} + x.mode = value + x.typ = &emptyInterface + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ)) + } + + case _Alignof: + // unsafe.Alignof(x T) uintptr + check.assignment(x, nil, "argument to unsafe.Alignof") + if x.mode == invalid { + return + } + + x.mode = constant_ + x.val = constant.MakeInt64(check.conf.alignof(x.typ)) + x.typ = Typ[Uintptr] + // result is constant - no need to record signature + + case _Offsetof: + // unsafe.Offsetof(x T) uintptr, where x must be a selector + // (no argument evaluated yet) + arg0 := call.Args[0] + selx, _ := unparen(arg0).(*ast.SelectorExpr) + if selx == nil { + check.invalidArg(arg0, _BadOffsetofSyntax, "%s is not a selector expression", arg0) + check.use(arg0) + return + } + + check.expr(x, selx.X) + if x.mode == invalid { + return + } + + base := derefStructPtr(x.typ) + sel := selx.Sel.Name + obj, index, indirect := check.lookupFieldOrMethod(base, false, check.pkg, sel) + switch obj.(type) { + case nil: + check.invalidArg(x, _MissingFieldOrMethod, "%s has no single field %s", base, sel) + return + case *Func: + // TODO(gri) Using derefStructPtr may result in methods being found + // that don't actually exist. An error either way, but the error + // message is confusing. See: https://play.golang.org/p/al75v23kUy , + // but go/types reports: "invalid argument: x.m is a method value". + check.invalidArg(arg0, _InvalidOffsetof, "%s is a method value", arg0) + return + } + if indirect { + check.invalidArg(x, _InvalidOffsetof, "field %s is embedded via a pointer in %s", sel, base) + return + } + + // TODO(gri) Should we pass x.typ instead of base (and indirect report if derefStructPtr indirected)? + check.recordSelection(selx, FieldVal, base, obj, index, false) + + offs := check.conf.offsetof(base, index) + x.mode = constant_ + x.val = constant.MakeInt64(offs) + x.typ = Typ[Uintptr] + // result is constant - no need to record signature + + case _Sizeof: + // unsafe.Sizeof(x T) uintptr + check.assignment(x, nil, "argument to unsafe.Sizeof") + if x.mode == invalid { + return + } + + x.mode = constant_ + x.val = constant.MakeInt64(check.conf.sizeof(x.typ)) + x.typ = Typ[Uintptr] + // result is constant - no need to record signature + + case _Assert: + // assert(pred) causes a typechecker error if pred is false. + // The result of assert is the value of pred if there is no error. + // Note: assert is only available in self-test mode. + if x.mode != constant_ || !isBoolean(x.typ) { + check.invalidArg(x, _Test, "%s is not a boolean constant", x) + return + } + if x.val.Kind() != constant.Bool { + check.errorf(x, _Test, "internal error: value of %s should be a boolean constant", x) + return + } + if !constant.BoolVal(x.val) { + check.errorf(call, _Test, "%v failed", call) + // compile-time assertion failure - safe to continue + } + // result is constant - no need to record signature + + case _Trace: + // trace(x, y, z, ...) dumps the positions, expressions, and + // values of its arguments. The result of trace is the value + // of the first argument. + // Note: trace is only available in self-test mode. + // (no argument evaluated yet) + if nargs == 0 { + check.dump("%v: trace() without arguments", call.Pos()) + x.mode = novalue + break + } + var t operand + x1 := x + for _, arg := range call.Args { + check.rawExpr(x1, arg, nil) // permit trace for types, e.g.: new(trace(T)) + check.dump("%v: %s", x1.Pos(), x1) + x1 = &t // use incoming x only for first argument + } + // trace is only available in test mode - no need to record signature + + default: + unreachable() + } + + return true +} + +// makeSig makes a signature for the given argument and result types. +// Default types are used for untyped arguments, and res may be nil. +func makeSig(res Type, args ...Type) *Signature { + list := make([]*Var, len(args)) + for i, param := range args { + list[i] = NewVar(token.NoPos, nil, "", Default(param)) + } + params := NewTuple(list...) + var result *Tuple + if res != nil { + assert(!isUntyped(res)) + result = NewTuple(NewVar(token.NoPos, nil, "", res)) + } + return &Signature{params: params, results: result} +} + +// implicitArrayDeref returns A if typ is of the form *A and A is an array; +// otherwise it returns typ. +// +func implicitArrayDeref(typ Type) Type { + if p, ok := typ.(*Pointer); ok { + if a, ok := p.base.Underlying().(*Array); ok { + return a + } + } + return typ +} + +// unparen returns e with any enclosing parentheses stripped. +func unparen(e ast.Expr) ast.Expr { + for { + p, ok := e.(*ast.ParenExpr) + if !ok { + return e + } + e = p.X + } +} diff --git a/src/go/types/builtins_test.go b/src/go/types/builtins_test.go new file mode 100644 index 0000000..cfd19d5 --- /dev/null +++ b/src/go/types/builtins_test.go @@ -0,0 +1,220 @@ +// Copyright 2013 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 types_test + +import ( + "fmt" + "go/ast" + "go/importer" + "go/parser" + "testing" + + . "go/types" +) + +var builtinCalls = []struct { + name, src, sig string +}{ + {"append", `var s []int; _ = append(s)`, `func([]int, ...int) []int`}, + {"append", `var s []int; _ = append(s, 0)`, `func([]int, ...int) []int`}, + {"append", `var s []int; _ = (append)(s, 0)`, `func([]int, ...int) []int`}, + {"append", `var s []byte; _ = ((append))(s, 0)`, `func([]byte, ...byte) []byte`}, + {"append", `var s []byte; _ = append(s, "foo"...)`, `func([]byte, string...) []byte`}, + {"append", `type T []byte; var s T; var str string; _ = append(s, str...)`, `func(p.T, string...) p.T`}, + {"append", `type T []byte; type U string; var s T; var str U; _ = append(s, str...)`, `func(p.T, p.U...) p.T`}, + + {"cap", `var s [10]int; _ = cap(s)`, `invalid type`}, // constant + {"cap", `var s [10]int; _ = cap(&s)`, `invalid type`}, // constant + {"cap", `var s []int64; _ = cap(s)`, `func([]int64) int`}, + {"cap", `var c chan<-bool; _ = cap(c)`, `func(chan<- bool) int`}, + + {"len", `_ = len("foo")`, `invalid type`}, // constant + {"len", `var s string; _ = len(s)`, `func(string) int`}, + {"len", `var s [10]int; _ = len(s)`, `invalid type`}, // constant + {"len", `var s [10]int; _ = len(&s)`, `invalid type`}, // constant + {"len", `var s []int64; _ = len(s)`, `func([]int64) int`}, + {"len", `var c chan<-bool; _ = len(c)`, `func(chan<- bool) int`}, + {"len", `var m map[string]float32; _ = len(m)`, `func(map[string]float32) int`}, + + {"close", `var c chan int; close(c)`, `func(chan int)`}, + {"close", `var c chan<- chan string; close(c)`, `func(chan<- chan string)`}, + + {"complex", `_ = complex(1, 0)`, `invalid type`}, // constant + {"complex", `var re float32; _ = complex(re, 1.0)`, `func(float32, float32) complex64`}, + {"complex", `var im float64; _ = complex(1, im)`, `func(float64, float64) complex128`}, + {"complex", `type F32 float32; var re, im F32; _ = complex(re, im)`, `func(p.F32, p.F32) complex64`}, + {"complex", `type F64 float64; var re, im F64; _ = complex(re, im)`, `func(p.F64, p.F64) complex128`}, + + {"copy", `var src, dst []byte; copy(dst, src)`, `func([]byte, []byte) int`}, + {"copy", `type T [][]int; var src, dst T; _ = copy(dst, src)`, `func(p.T, p.T) int`}, + {"copy", `var src string; var dst []byte; copy(dst, src)`, `func([]byte, string) int`}, + {"copy", `type T string; type U []byte; var src T; var dst U; copy(dst, src)`, `func(p.U, p.T) int`}, + {"copy", `var dst []byte; copy(dst, "hello")`, `func([]byte, string) int`}, + + {"delete", `var m map[string]bool; delete(m, "foo")`, `func(map[string]bool, string)`}, + {"delete", `type (K string; V int); var m map[K]V; delete(m, "foo")`, `func(map[p.K]p.V, p.K)`}, + + {"imag", `_ = imag(1i)`, `invalid type`}, // constant + {"imag", `var c complex64; _ = imag(c)`, `func(complex64) float32`}, + {"imag", `var c complex128; _ = imag(c)`, `func(complex128) float64`}, + {"imag", `type C64 complex64; var c C64; _ = imag(c)`, `func(p.C64) float32`}, + {"imag", `type C128 complex128; var c C128; _ = imag(c)`, `func(p.C128) float64`}, + + {"real", `_ = real(1i)`, `invalid type`}, // constant + {"real", `var c complex64; _ = real(c)`, `func(complex64) float32`}, + {"real", `var c complex128; _ = real(c)`, `func(complex128) float64`}, + {"real", `type C64 complex64; var c C64; _ = real(c)`, `func(p.C64) float32`}, + {"real", `type C128 complex128; var c C128; _ = real(c)`, `func(p.C128) float64`}, + + {"make", `_ = make([]int, 10)`, `func([]int, int) []int`}, + {"make", `type T []byte; _ = make(T, 10, 20)`, `func(p.T, int, int) p.T`}, + + // issue #37349 + {"make", ` _ = make([]int, 0 )`, `func([]int, int) []int`}, + {"make", `var l int; _ = make([]int, l )`, `func([]int, int) []int`}, + {"make", ` _ = make([]int, 0, 0)`, `func([]int, int, int) []int`}, + {"make", `var l int; _ = make([]int, l, 0)`, `func([]int, int, int) []int`}, + {"make", `var c int; _ = make([]int, 0, c)`, `func([]int, int, int) []int`}, + {"make", `var l, c int; _ = make([]int, l, c)`, `func([]int, int, int) []int`}, + + // issue #37393 + {"make", ` _ = make([]int , 0 )`, `func([]int, int) []int`}, + {"make", `var l byte ; _ = make([]int8 , l )`, `func([]int8, byte) []int8`}, + {"make", ` _ = make([]int16 , 0, 0)`, `func([]int16, int, int) []int16`}, + {"make", `var l int16; _ = make([]string , l, 0)`, `func([]string, int16, int) []string`}, + {"make", `var c int32; _ = make([]float64 , 0, c)`, `func([]float64, int, int32) []float64`}, + {"make", `var l, c uint ; _ = make([]complex128, l, c)`, `func([]complex128, uint, uint) []complex128`}, + + {"new", `_ = new(int)`, `func(int) *int`}, + {"new", `type T struct{}; _ = new(T)`, `func(p.T) *p.T`}, + + {"panic", `panic(0)`, `func(interface{})`}, + {"panic", `panic("foo")`, `func(interface{})`}, + + {"print", `print()`, `func()`}, + {"print", `print(0)`, `func(int)`}, + {"print", `print(1, 2.0, "foo", true)`, `func(int, float64, string, bool)`}, + + {"println", `println()`, `func()`}, + {"println", `println(0)`, `func(int)`}, + {"println", `println(1, 2.0, "foo", true)`, `func(int, float64, string, bool)`}, + + {"recover", `recover()`, `func() interface{}`}, + {"recover", `_ = recover()`, `func() interface{}`}, + + {"Alignof", `_ = unsafe.Alignof(0)`, `invalid type`}, // constant + {"Alignof", `var x struct{}; _ = unsafe.Alignof(x)`, `invalid type`}, // constant + + {"Offsetof", `var x struct{f bool}; _ = unsafe.Offsetof(x.f)`, `invalid type`}, // constant + {"Offsetof", `var x struct{_ int; f bool}; _ = unsafe.Offsetof((&x).f)`, `invalid type`}, // constant + + {"Sizeof", `_ = unsafe.Sizeof(0)`, `invalid type`}, // constant + {"Sizeof", `var x struct{}; _ = unsafe.Sizeof(x)`, `invalid type`}, // constant + + {"assert", `assert(true)`, `invalid type`}, // constant + {"assert", `type B bool; const pred B = 1 < 2; assert(pred)`, `invalid type`}, // constant + + // no tests for trace since it produces output as a side-effect +} + +func TestBuiltinSignatures(t *testing.T) { + DefPredeclaredTestFuncs() + + seen := map[string]bool{"trace": true} // no test for trace built-in; add it manually + for _, call := range builtinCalls { + testBuiltinSignature(t, call.name, call.src, call.sig) + seen[call.name] = true + } + + // make sure we didn't miss one + for _, name := range Universe.Names() { + if _, ok := Universe.Lookup(name).(*Builtin); ok && !seen[name] { + t.Errorf("missing test for %s", name) + } + } + for _, name := range Unsafe.Scope().Names() { + if _, ok := Unsafe.Scope().Lookup(name).(*Builtin); ok && !seen[name] { + t.Errorf("missing test for unsafe.%s", name) + } + } +} + +func testBuiltinSignature(t *testing.T, name, src0, want string) { + src := fmt.Sprintf(`package p; import "unsafe"; type _ unsafe.Pointer /* use unsafe */; func _() { %s }`, src0) + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Errorf("%s: %s", src0, err) + return + } + + conf := Config{Importer: importer.Default()} + uses := make(map[*ast.Ident]Object) + types := make(map[ast.Expr]TypeAndValue) + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Uses: uses, Types: types}) + if err != nil { + t.Errorf("%s: %s", src0, err) + return + } + + // find called function + n := 0 + var fun ast.Expr + for x := range types { + if call, _ := x.(*ast.CallExpr); call != nil { + fun = call.Fun + n++ + } + } + if n != 1 { + t.Errorf("%s: got %d CallExprs; want 1", src0, n) + return + } + + // check recorded types for fun and descendents (may be parenthesized) + for { + // the recorded type for the built-in must match the wanted signature + typ := types[fun].Type + if typ == nil { + t.Errorf("%s: no type recorded for %s", src0, ExprString(fun)) + return + } + if got := typ.String(); got != want { + t.Errorf("%s: got type %s; want %s", src0, got, want) + return + } + + // called function must be a (possibly parenthesized, qualified) + // identifier denoting the expected built-in + switch p := fun.(type) { + case *ast.Ident: + obj := uses[p] + if obj == nil { + t.Errorf("%s: no object found for %s", src0, p) + return + } + bin, _ := obj.(*Builtin) + if bin == nil { + t.Errorf("%s: %s does not denote a built-in", src0, p) + return + } + if bin.Name() != name { + t.Errorf("%s: got built-in %s; want %s", src0, bin.Name(), name) + return + } + return // we're done + + case *ast.ParenExpr: + fun = p.X // unpack + + case *ast.SelectorExpr: + // built-in from package unsafe - ignore details + return // we're done + + default: + t.Errorf("%s: invalid function call", src0) + return + } + } +} diff --git a/src/go/types/call.go b/src/go/types/call.go new file mode 100644 index 0000000..6765b17 --- /dev/null +++ b/src/go/types/call.go @@ -0,0 +1,569 @@ +// Copyright 2013 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. + +// This file implements typechecking of call and selector expressions. + +package types + +import ( + "go/ast" + "go/token" + "strings" + "unicode" +) + +func (check *Checker) call(x *operand, e *ast.CallExpr) exprKind { + check.exprOrType(x, e.Fun) + + switch x.mode { + case invalid: + check.use(e.Args...) + x.mode = invalid + x.expr = e + return statement + + case typexpr: + // conversion + T := x.typ + x.mode = invalid + switch n := len(e.Args); n { + case 0: + check.errorf(inNode(e, e.Rparen), _WrongArgCount, "missing argument in conversion to %s", T) + case 1: + check.expr(x, e.Args[0]) + if x.mode != invalid { + if e.Ellipsis.IsValid() { + check.errorf(e.Args[0], _BadDotDotDotSyntax, "invalid use of ... in conversion to %s", T) + break + } + check.conversion(x, T) + } + default: + check.use(e.Args...) + check.errorf(e.Args[n-1], _WrongArgCount, "too many arguments in conversion to %s", T) + } + x.expr = e + return conversion + + case builtin: + id := x.id + if !check.builtin(x, e, id) { + x.mode = invalid + } + x.expr = e + // a non-constant result implies a function call + if x.mode != invalid && x.mode != constant_ { + check.hasCallOrRecv = true + } + return predeclaredFuncs[id].kind + + default: + // function/method call + cgocall := x.mode == cgofunc + + sig, _ := x.typ.Underlying().(*Signature) + if sig == nil { + check.invalidOp(x, _InvalidCall, "cannot call non-function %s", x) + x.mode = invalid + x.expr = e + return statement + } + + arg, n, _ := unpack(func(x *operand, i int) { check.multiExpr(x, e.Args[i]) }, len(e.Args), false) + if arg != nil { + check.arguments(x, e, sig, arg, n) + } else { + x.mode = invalid + } + + // determine result + switch sig.results.Len() { + case 0: + x.mode = novalue + case 1: + if cgocall { + x.mode = commaerr + } else { + x.mode = value + } + x.typ = sig.results.vars[0].typ // unpack tuple + default: + x.mode = value + x.typ = sig.results + } + + x.expr = e + check.hasCallOrRecv = true + + return statement + } +} + +// use type-checks each argument. +// Useful to make sure expressions are evaluated +// (and variables are "used") in the presence of other errors. +// The arguments may be nil. +func (check *Checker) use(arg ...ast.Expr) { + var x operand + for _, e := range arg { + // The nil check below is necessary since certain AST fields + // may legally be nil (e.g., the ast.SliceExpr.High field). + if e != nil { + check.rawExpr(&x, e, nil) + } + } +} + +// useLHS is like use, but doesn't "use" top-level identifiers. +// It should be called instead of use if the arguments are +// expressions on the lhs of an assignment. +// The arguments must not be nil. +func (check *Checker) useLHS(arg ...ast.Expr) { + var x operand + for _, e := range arg { + // If the lhs is an identifier denoting a variable v, this assignment + // is not a 'use' of v. Remember current value of v.used and restore + // after evaluating the lhs via check.rawExpr. + var v *Var + var v_used bool + if ident, _ := unparen(e).(*ast.Ident); ident != nil { + // never type-check the blank name on the lhs + if ident.Name == "_" { + continue + } + if _, obj := check.scope.LookupParent(ident.Name, token.NoPos); obj != nil { + // It's ok to mark non-local variables, but ignore variables + // from other packages to avoid potential race conditions with + // dot-imported variables. + if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg { + v = w + v_used = v.used + } + } + } + check.rawExpr(&x, e, nil) + if v != nil { + v.used = v_used // restore v.used + } + } +} + +// useGetter is like use, but takes a getter instead of a list of expressions. +// It should be called instead of use if a getter is present to avoid repeated +// evaluation of the first argument (since the getter was likely obtained via +// unpack, which may have evaluated the first argument already). +func (check *Checker) useGetter(get getter, n int) { + var x operand + for i := 0; i < n; i++ { + get(&x, i) + } +} + +// A getter sets x as the i'th operand, where 0 <= i < n and n is the total +// number of operands (context-specific, and maintained elsewhere). A getter +// type-checks the i'th operand; the details of the actual check are getter- +// specific. +type getter func(x *operand, i int) + +// unpack takes a getter get and a number of operands n. If n == 1, unpack +// calls the incoming getter for the first operand. If that operand is +// invalid, unpack returns (nil, 0, false). Otherwise, if that operand is a +// function call, or a comma-ok expression and allowCommaOk is set, the result +// is a new getter and operand count providing access to the function results, +// or comma-ok values, respectively. The third result value reports if it +// is indeed the comma-ok case. In all other cases, the incoming getter and +// operand count are returned unchanged, and the third result value is false. +// +// In other words, if there's exactly one operand that - after type-checking +// by calling get - stands for multiple operands, the resulting getter provides +// access to those operands instead. +// +// If the returned getter is called at most once for a given operand index i +// (including i == 0), that operand is guaranteed to cause only one call of +// the incoming getter with that i. +// +func unpack(get getter, n int, allowCommaOk bool) (getter, int, bool) { + if n != 1 { + // zero or multiple values + return get, n, false + } + // possibly result of an n-valued function call or comma,ok value + var x0 operand + get(&x0, 0) + if x0.mode == invalid { + return nil, 0, false + } + + if t, ok := x0.typ.(*Tuple); ok { + // result of an n-valued function call + return func(x *operand, i int) { + x.mode = value + x.expr = x0.expr + x.typ = t.At(i).typ + }, t.Len(), false + } + + if x0.mode == mapindex || x0.mode == commaok || x0.mode == commaerr { + // comma-ok value + if allowCommaOk { + a := [2]Type{x0.typ, Typ[UntypedBool]} + if x0.mode == commaerr { + a[1] = universeError + } + return func(x *operand, i int) { + x.mode = value + x.expr = x0.expr + x.typ = a[i] + }, 2, true + } + x0.mode = value + } + + // single value + return func(x *operand, i int) { + if i != 0 { + unreachable() + } + *x = x0 + }, 1, false +} + +// arguments checks argument passing for the call with the given signature. +// The arg function provides the operand for the i'th argument. +func (check *Checker) arguments(x *operand, call *ast.CallExpr, sig *Signature, arg getter, n int) { + if call.Ellipsis.IsValid() { + // last argument is of the form x... + if !sig.variadic { + check.errorf(atPos(call.Ellipsis), _NonVariadicDotDotDot, "cannot use ... in call to non-variadic %s", call.Fun) + check.useGetter(arg, n) + return + } + if len(call.Args) == 1 && n > 1 { + // f()... is not permitted if f() is multi-valued + check.errorf(atPos(call.Ellipsis), _InvalidDotDotDotOperand, "cannot use ... with %d-valued %s", n, call.Args[0]) + check.useGetter(arg, n) + return + } + } + + // evaluate arguments + context := check.sprintf("argument to %s", call.Fun) + for i := 0; i < n; i++ { + arg(x, i) + if x.mode != invalid { + var ellipsis token.Pos + if i == n-1 && call.Ellipsis.IsValid() { + ellipsis = call.Ellipsis + } + check.argument(sig, i, x, ellipsis, context) + } + } + + // check argument count + if sig.variadic { + // a variadic function accepts an "empty" + // last argument: count one extra + n++ + } + if n < sig.params.Len() { + check.errorf(inNode(call, call.Rparen), _WrongArgCount, "too few arguments in call to %s", call.Fun) + // ok to continue + } +} + +// argument checks passing of argument x to the i'th parameter of the given signature. +// If ellipsis is valid, the argument is followed by ... at that position in the call. +func (check *Checker) argument(sig *Signature, i int, x *operand, ellipsis token.Pos, context string) { + check.singleValue(x) + if x.mode == invalid { + return + } + + n := sig.params.Len() + + // determine parameter type + var typ Type + switch { + case i < n: + typ = sig.params.vars[i].typ + case sig.variadic: + typ = sig.params.vars[n-1].typ + if debug { + if _, ok := typ.(*Slice); !ok { + check.dump("%v: expected unnamed slice type, got %s", sig.params.vars[n-1].Pos(), typ) + } + } + default: + check.errorf(x, _WrongArgCount, "too many arguments") + return + } + + if ellipsis.IsValid() { + if i != n-1 { + check.errorf(atPos(ellipsis), _MisplacedDotDotDot, "can only use ... with matching parameter") + return + } + // argument is of the form x... and x is single-valued + if _, ok := x.typ.Underlying().(*Slice); !ok && x.typ != Typ[UntypedNil] { // see issue #18268 + check.errorf(x, _InvalidDotDotDotOperand, "cannot use %s as parameter of type %s", x, typ) + return + } + } else if sig.variadic && i >= n-1 { + // use the variadic parameter slice's element type + typ = typ.(*Slice).elem + } + + check.assignment(x, typ, context) +} + +var cgoPrefixes = [...]string{ + "_Ciconst_", + "_Cfconst_", + "_Csconst_", + "_Ctype_", + "_Cvar_", // actually a pointer to the var + "_Cfpvar_fp_", + "_Cfunc_", + "_Cmacro_", // function to evaluate the expanded expression +} + +func (check *Checker) selector(x *operand, e *ast.SelectorExpr) { + // these must be declared before the "goto Error" statements + var ( + obj Object + index []int + indirect bool + ) + + sel := e.Sel.Name + // If the identifier refers to a package, handle everything here + // so we don't need a "package" mode for operands: package names + // can only appear in qualified identifiers which are mapped to + // selector expressions. + if ident, ok := e.X.(*ast.Ident); ok { + obj := check.lookup(ident.Name) + if pname, _ := obj.(*PkgName); pname != nil { + assert(pname.pkg == check.pkg) + check.recordUse(ident, pname) + pname.used = true + pkg := pname.imported + + var exp Object + funcMode := value + if pkg.cgo { + // cgo special cases C.malloc: it's + // rewritten to _CMalloc and does not + // support two-result calls. + if sel == "malloc" { + sel = "_CMalloc" + } else { + funcMode = cgofunc + } + for _, prefix := range cgoPrefixes { + // cgo objects are part of the current package (in file + // _cgo_gotypes.go). Use regular lookup. + _, exp = check.scope.LookupParent(prefix+sel, check.pos) + if exp != nil { + break + } + } + if exp == nil { + check.errorf(e.Sel, _UndeclaredImportedName, "%s not declared by package C", sel) + goto Error + } + check.objDecl(exp, nil) + } else { + exp = pkg.scope.Lookup(sel) + if exp == nil { + if !pkg.fake { + check.errorf(e.Sel, _UndeclaredImportedName, "%s not declared by package %s", sel, pkg.name) + } + goto Error + } + if !exp.Exported() { + check.errorf(e.Sel, _UnexportedName, "%s not exported by package %s", sel, pkg.name) + // ok to continue + } + } + check.recordUse(e.Sel, exp) + + // Simplified version of the code for *ast.Idents: + // - imported objects are always fully initialized + switch exp := exp.(type) { + case *Const: + assert(exp.Val() != nil) + x.mode = constant_ + x.typ = exp.typ + x.val = exp.val + case *TypeName: + x.mode = typexpr + x.typ = exp.typ + case *Var: + x.mode = variable + x.typ = exp.typ + if pkg.cgo && strings.HasPrefix(exp.name, "_Cvar_") { + x.typ = x.typ.(*Pointer).base + } + case *Func: + x.mode = funcMode + x.typ = exp.typ + if pkg.cgo && strings.HasPrefix(exp.name, "_Cmacro_") { + x.mode = value + x.typ = x.typ.(*Signature).results.vars[0].typ + } + case *Builtin: + x.mode = builtin + x.typ = exp.typ + x.id = exp.id + default: + check.dump("unexpected object %v", exp) + unreachable() + } + x.expr = e + return + } + } + + check.exprOrType(x, e.X) + if x.mode == invalid { + goto Error + } + + obj, index, indirect = check.lookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel) + if obj == nil { + switch { + case index != nil: + // TODO(gri) should provide actual type where the conflict happens + check.errorf(e.Sel, _AmbiguousSelector, "ambiguous selector %s.%s", x.expr, sel) + case indirect: + check.errorf(e.Sel, _InvalidMethodExpr, "cannot call pointer method %s on %s", sel, x.typ) + default: + // Check if capitalization of sel matters and provide better error + // message in that case. + if len(sel) > 0 { + var changeCase string + if r := rune(sel[0]); unicode.IsUpper(r) { + changeCase = string(unicode.ToLower(r)) + sel[1:] + } else { + changeCase = string(unicode.ToUpper(r)) + sel[1:] + } + if obj, _, _ = check.lookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, changeCase); obj != nil { + check.errorf(e.Sel, _MissingFieldOrMethod, "%s.%s undefined (type %s has no field or method %s, but does have %s)", x.expr, sel, x.typ, sel, changeCase) + break + } + } + check.errorf(e.Sel, _MissingFieldOrMethod, "%s.%s undefined (type %s has no field or method %s)", x.expr, sel, x.typ, sel) + } + goto Error + } + + // methods may not have a fully set up signature yet + if m, _ := obj.(*Func); m != nil { + check.objDecl(m, nil) + } + + if x.mode == typexpr { + // method expression + m, _ := obj.(*Func) + if m == nil { + // TODO(gri) should check if capitalization of sel matters and provide better error message in that case + check.errorf(e.Sel, _MissingFieldOrMethod, "%s.%s undefined (type %s has no method %s)", x.expr, sel, x.typ, sel) + goto Error + } + + check.recordSelection(e, MethodExpr, x.typ, m, index, indirect) + + // the receiver type becomes the type of the first function + // argument of the method expression's function type + var params []*Var + sig := m.typ.(*Signature) + if sig.params != nil { + params = sig.params.vars + } + x.mode = value + x.typ = &Signature{ + params: NewTuple(append([]*Var{NewVar(token.NoPos, check.pkg, "", x.typ)}, params...)...), + results: sig.results, + variadic: sig.variadic, + } + + check.addDeclDep(m) + + } else { + // regular selector + switch obj := obj.(type) { + case *Var: + check.recordSelection(e, FieldVal, x.typ, obj, index, indirect) + if x.mode == variable || indirect { + x.mode = variable + } else { + x.mode = value + } + x.typ = obj.typ + + case *Func: + // TODO(gri) If we needed to take into account the receiver's + // addressability, should we report the type &(x.typ) instead? + check.recordSelection(e, MethodVal, x.typ, obj, index, indirect) + + if debug { + // Verify that LookupFieldOrMethod and MethodSet.Lookup agree. + // TODO(gri) This only works because we call LookupFieldOrMethod + // _before_ calling NewMethodSet: LookupFieldOrMethod completes + // any incomplete interfaces so they are available to NewMethodSet + // (which assumes that interfaces have been completed already). + typ := x.typ + if x.mode == variable { + // If typ is not an (unnamed) pointer or an interface, + // use *typ instead, because the method set of *typ + // includes the methods of typ. + // Variables are addressable, so we can always take their + // address. + if _, ok := typ.(*Pointer); !ok && !IsInterface(typ) { + typ = &Pointer{base: typ} + } + } + // If we created a synthetic pointer type above, we will throw + // away the method set computed here after use. + // TODO(gri) Method set computation should probably always compute + // both, the value and the pointer receiver method set and represent + // them in a single structure. + // TODO(gri) Consider also using a method set cache for the lifetime + // of checker once we rely on MethodSet lookup instead of individual + // lookup. + mset := NewMethodSet(typ) + if m := mset.Lookup(check.pkg, sel); m == nil || m.obj != obj { + check.dump("%v: (%s).%v -> %s", e.Pos(), typ, obj.name, m) + check.dump("%s\n", mset) + // Caution: MethodSets are supposed to be used externally + // only (after all interface types were completed). It's + // now possible that we get here incorrectly. Not urgent + // to fix since we only run this code in debug mode. + // TODO(gri) fix this eventually. + panic("method sets and lookup don't agree") + } + } + + x.mode = value + + // remove receiver + sig := *obj.typ.(*Signature) + sig.recv = nil + x.typ = &sig + + check.addDeclDep(obj) + + default: + unreachable() + } + } + + // everything went well + x.expr = e + return + +Error: + x.mode = invalid + x.expr = e +} diff --git a/src/go/types/check.go b/src/go/types/check.go new file mode 100644 index 0000000..280792e --- /dev/null +++ b/src/go/types/check.go @@ -0,0 +1,417 @@ +// Copyright 2011 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. + +// This file implements the Check function, which drives type-checking. + +package types + +import ( + "errors" + "go/ast" + "go/constant" + "go/token" +) + +// debugging/development support +const ( + debug = false // leave on during development + trace = false // turn on for detailed type resolution traces +) + +// If Strict is set, the type-checker enforces additional +// rules not specified by the Go 1 spec, but which will +// catch guaranteed run-time errors if the respective +// code is executed. In other words, programs passing in +// Strict mode are Go 1 compliant, but not all Go 1 programs +// will pass in Strict mode. The additional rules are: +// +// - A type assertion x.(T) where T is an interface type +// is invalid if any (statically known) method that exists +// for both x and T have different signatures. +// +const strict = false + +// exprInfo stores information about an untyped expression. +type exprInfo struct { + isLhs bool // expression is lhs operand of a shift with delayed type-check + mode operandMode + typ *Basic + val constant.Value // constant value; or nil (if not a constant) +} + +// A context represents the context within which an object is type-checked. +type context struct { + decl *declInfo // package-level declaration whose init expression/function body is checked + scope *Scope // top-most scope for lookups + pos token.Pos // if valid, identifiers are looked up as if at position pos (used by Eval) + iota constant.Value // value of iota in a constant declaration; nil otherwise + errpos positioner // if set, identifier position of a constant with inherited initializer + sig *Signature // function signature if inside a function; nil otherwise + isPanic map[*ast.CallExpr]bool // set of panic call expressions (used for termination check) + hasLabel bool // set if a function makes use of labels (only ~1% of functions); unused outside functions + hasCallOrRecv bool // set if an expression contains a function call or channel receive operation +} + +// lookup looks up name in the current context and returns the matching object, or nil. +func (ctxt *context) lookup(name string) Object { + _, obj := ctxt.scope.LookupParent(name, ctxt.pos) + return obj +} + +// An importKey identifies an imported package by import path and source directory +// (directory containing the file containing the import). In practice, the directory +// may always be the same, or may not matter. Given an (import path, directory), an +// importer must always return the same package (but given two different import paths, +// an importer may still return the same package by mapping them to the same package +// paths). +type importKey struct { + path, dir string +} + +// A Checker maintains the state of the type checker. +// It must be created with NewChecker. +type Checker struct { + // package information + // (initialized by NewChecker, valid for the life-time of checker) + conf *Config + fset *token.FileSet + pkg *Package + *Info + objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info + impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package + posMap map[*Interface][]token.Pos // maps interface types to lists of embedded interface positions + pkgCnt map[string]int // counts number of imported packages with a given name (for better error messages) + + // information collected during type-checking of a set of package files + // (initialized by Files, valid only for the duration of check.Files; + // maps and lists are allocated on demand) + files []*ast.File // package files + unusedDotImports map[*Scope]map[*Package]*ast.ImportSpec // unused dot-imported packages + + firstErr error // first error encountered + methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods + untyped map[ast.Expr]exprInfo // map of expressions without final type + delayed []func() // stack of delayed action segments; segments are processed in FIFO order + finals []func() // list of final actions; processed at the end of type-checking the current set of files + objPath []Object // path of object dependencies during type inference (for cycle reporting) + + // context within which the current object is type-checked + // (valid only for the duration of type-checking a specific object) + context + + // debugging + indent int // indentation for tracing +} + +// addUnusedImport adds the position of a dot-imported package +// pkg to the map of dot imports for the given file scope. +func (check *Checker) addUnusedDotImport(scope *Scope, pkg *Package, spec *ast.ImportSpec) { + mm := check.unusedDotImports + if mm == nil { + mm = make(map[*Scope]map[*Package]*ast.ImportSpec) + check.unusedDotImports = mm + } + m := mm[scope] + if m == nil { + m = make(map[*Package]*ast.ImportSpec) + mm[scope] = m + } + m[pkg] = spec +} + +// addDeclDep adds the dependency edge (check.decl -> to) if check.decl exists +func (check *Checker) addDeclDep(to Object) { + from := check.decl + if from == nil { + return // not in a package-level init expression + } + if _, found := check.objMap[to]; !found { + return // to is not a package-level object + } + from.addDep(to) +} + +func (check *Checker) rememberUntyped(e ast.Expr, lhs bool, mode operandMode, typ *Basic, val constant.Value) { + m := check.untyped + if m == nil { + m = make(map[ast.Expr]exprInfo) + check.untyped = m + } + m[e] = exprInfo{lhs, mode, typ, val} +} + +// later pushes f on to the stack of actions that will be processed later; +// either at the end of the current statement, or in case of a local constant +// or variable declaration, before the constant or variable is in scope +// (so that f still sees the scope before any new declarations). +func (check *Checker) later(f func()) { + check.delayed = append(check.delayed, f) +} + +// atEnd adds f to the list of actions processed at the end +// of type-checking, before initialization order computation. +// Actions added by atEnd are processed after any actions +// added by later. +func (check *Checker) atEnd(f func()) { + check.finals = append(check.finals, f) +} + +// push pushes obj onto the object path and returns its index in the path. +func (check *Checker) push(obj Object) int { + check.objPath = append(check.objPath, obj) + return len(check.objPath) - 1 +} + +// pop pops and returns the topmost object from the object path. +func (check *Checker) pop() Object { + i := len(check.objPath) - 1 + obj := check.objPath[i] + check.objPath[i] = nil + check.objPath = check.objPath[:i] + return obj +} + +// NewChecker returns a new Checker instance for a given package. +// Package files may be added incrementally via checker.Files. +func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Checker { + // make sure we have a configuration + if conf == nil { + conf = new(Config) + } + + // make sure we have an info struct + if info == nil { + info = new(Info) + } + + return &Checker{ + conf: conf, + fset: fset, + pkg: pkg, + Info: info, + objMap: make(map[Object]*declInfo), + impMap: make(map[importKey]*Package), + posMap: make(map[*Interface][]token.Pos), + pkgCnt: make(map[string]int), + } +} + +// initFiles initializes the files-specific portion of checker. +// The provided files must all belong to the same package. +func (check *Checker) initFiles(files []*ast.File) { + // start with a clean slate (check.Files may be called multiple times) + check.files = nil + check.unusedDotImports = nil + + check.firstErr = nil + check.methods = nil + check.untyped = nil + check.delayed = nil + check.finals = nil + + // determine package name and collect valid files + pkg := check.pkg + for _, file := range files { + switch name := file.Name.Name; pkg.name { + case "": + if name != "_" { + pkg.name = name + } else { + check.errorf(file.Name, _BlankPkgName, "invalid package name _") + } + fallthrough + + case name: + check.files = append(check.files, file) + + default: + check.errorf(atPos(file.Package), _MismatchedPkgName, "package %s; expected %s", name, pkg.name) + // ignore this file + } + } +} + +// A bailout panic is used for early termination. +type bailout struct{} + +func (check *Checker) handleBailout(err *error) { + switch p := recover().(type) { + case nil, bailout: + // normal return or early exit + *err = check.firstErr + default: + // re-panic + panic(p) + } +} + +// Files checks the provided files as part of the checker's package. +func (check *Checker) Files(files []*ast.File) error { return check.checkFiles(files) } + +var errBadCgo = errors.New("cannot use FakeImportC and go115UsesCgo together") + +func (check *Checker) checkFiles(files []*ast.File) (err error) { + if check.conf.FakeImportC && check.conf.go115UsesCgo { + return errBadCgo + } + + defer check.handleBailout(&err) + + check.initFiles(files) + + check.collectObjects() + + check.packageObjects() + + check.processDelayed(0) // incl. all functions + check.processFinals() + + check.initOrder() + + if !check.conf.DisableUnusedImportCheck { + check.unusedImports() + } + + check.recordUntyped() + + check.pkg.complete = true + return +} + +// processDelayed processes all delayed actions pushed after top. +func (check *Checker) processDelayed(top int) { + // If each delayed action pushes a new action, the + // stack will continue to grow during this loop. + // However, it is only processing functions (which + // are processed in a delayed fashion) that may + // add more actions (such as nested functions), so + // this is a sufficiently bounded process. + for i := top; i < len(check.delayed); i++ { + check.delayed[i]() // may append to check.delayed + } + assert(top <= len(check.delayed)) // stack must not have shrunk + check.delayed = check.delayed[:top] +} + +func (check *Checker) processFinals() { + n := len(check.finals) + for _, f := range check.finals { + f() // must not append to check.finals + } + if len(check.finals) != n { + panic("internal error: final action list grew") + } +} + +func (check *Checker) recordUntyped() { + if !debug && check.Types == nil { + return // nothing to do + } + + for x, info := range check.untyped { + if debug && isTyped(info.typ) { + check.dump("%v: %s (type %s) is typed", x.Pos(), x, info.typ) + unreachable() + } + check.recordTypeAndValue(x, info.mode, info.typ, info.val) + } +} + +func (check *Checker) recordTypeAndValue(x ast.Expr, mode operandMode, typ Type, val constant.Value) { + assert(x != nil) + assert(typ != nil) + if mode == invalid { + return // omit + } + if mode == constant_ { + assert(val != nil) + assert(typ == Typ[Invalid] || isConstType(typ)) + } + if m := check.Types; m != nil { + m[x] = TypeAndValue{mode, typ, val} + } +} + +func (check *Checker) recordBuiltinType(f ast.Expr, sig *Signature) { + // f must be a (possibly parenthesized) identifier denoting a built-in + // (built-ins in package unsafe always produce a constant result and + // we don't record their signatures, so we don't see qualified idents + // here): record the signature for f and possible children. + for { + check.recordTypeAndValue(f, builtin, sig, nil) + switch p := f.(type) { + case *ast.Ident: + return // we're done + case *ast.ParenExpr: + f = p.X + default: + unreachable() + } + } +} + +func (check *Checker) recordCommaOkTypes(x ast.Expr, a [2]Type) { + assert(x != nil) + if a[0] == nil || a[1] == nil { + return + } + assert(isTyped(a[0]) && isTyped(a[1]) && (isBoolean(a[1]) || a[1] == universeError)) + if m := check.Types; m != nil { + for { + tv := m[x] + assert(tv.Type != nil) // should have been recorded already + pos := x.Pos() + tv.Type = NewTuple( + NewVar(pos, check.pkg, "", a[0]), + NewVar(pos, check.pkg, "", a[1]), + ) + m[x] = tv + // if x is a parenthesized expression (p.X), update p.X + p, _ := x.(*ast.ParenExpr) + if p == nil { + break + } + x = p.X + } + } +} + +func (check *Checker) recordDef(id *ast.Ident, obj Object) { + assert(id != nil) + if m := check.Defs; m != nil { + m[id] = obj + } +} + +func (check *Checker) recordUse(id *ast.Ident, obj Object) { + assert(id != nil) + assert(obj != nil) + if m := check.Uses; m != nil { + m[id] = obj + } +} + +func (check *Checker) recordImplicit(node ast.Node, obj Object) { + assert(node != nil) + assert(obj != nil) + if m := check.Implicits; m != nil { + m[node] = obj + } +} + +func (check *Checker) recordSelection(x *ast.SelectorExpr, kind SelectionKind, recv Type, obj Object, index []int, indirect bool) { + assert(obj != nil && (recv == nil || len(index) > 0)) + check.recordUse(x.Sel, obj) + if m := check.Selections; m != nil { + m[x] = &Selection{kind, recv, obj, index, indirect} + } +} + +func (check *Checker) recordScope(node ast.Node, scope *Scope) { + assert(node != nil) + assert(scope != nil) + if m := check.Scopes; m != nil { + m[node] = scope + } +} diff --git a/src/go/types/check_test.go b/src/go/types/check_test.go new file mode 100644 index 0000000..ce31dab --- /dev/null +++ b/src/go/types/check_test.go @@ -0,0 +1,349 @@ +// Copyright 2011 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. + +// This file implements a typechecker test harness. The packages specified +// in tests are typechecked. Error messages reported by the typechecker are +// compared against the error messages expected in the test files. +// +// Expected errors are indicated in the test files by putting a comment +// of the form /* ERROR "rx" */ immediately following an offending token. +// The harness will verify that an error matching the regular expression +// rx is reported at that source position. Consecutive comments may be +// used to indicate multiple errors for the same token position. +// +// For instance, the following test file indicates that a "not declared" +// error should be reported for the undeclared variable x: +// +// package p +// func f() { +// _ = x /* ERROR "not declared" */ + 1 +// } + +// TODO(gri) Also collect strict mode errors of the form /* STRICT ... */ +// and test against strict mode. + +package types_test + +import ( + "flag" + "go/ast" + "go/importer" + "go/parser" + "go/scanner" + "go/token" + "internal/testenv" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + + . "go/types" +) + +var ( + haltOnError = flag.Bool("halt", false, "halt on error") + listErrors = flag.Bool("errlist", false, "list errors") + testFiles = flag.String("files", "", "space-separated list of test files") +) + +// The test filenames do not end in .go so that they are invisible +// to gofmt since they contain comments that must not change their +// positions relative to surrounding tokens. + +// Each tests entry is list of files belonging to the same package. +var tests = [][]string{ + {"testdata/errors.src"}, + {"testdata/importdecl0a.src", "testdata/importdecl0b.src"}, + {"testdata/importdecl1a.src", "testdata/importdecl1b.src"}, + {"testdata/importC.src"}, // special handling in checkFiles + {"testdata/cycles.src"}, + {"testdata/cycles1.src"}, + {"testdata/cycles2.src"}, + {"testdata/cycles3.src"}, + {"testdata/cycles4.src"}, + {"testdata/cycles5.src"}, + {"testdata/init0.src"}, + {"testdata/init1.src"}, + {"testdata/init2.src"}, + {"testdata/decls0.src"}, + {"testdata/decls1.src"}, + {"testdata/decls2a.src", "testdata/decls2b.src"}, + {"testdata/decls3.src"}, + {"testdata/decls4.src"}, + {"testdata/decls5.src"}, + {"testdata/const0.src"}, + {"testdata/const1.src"}, + {"testdata/constdecl.src"}, + {"testdata/vardecl.src"}, + {"testdata/expr0.src"}, + {"testdata/expr1.src"}, + {"testdata/expr2.src"}, + {"testdata/expr3.src"}, + {"testdata/methodsets.src"}, + {"testdata/shifts.src"}, + {"testdata/builtins.src"}, + {"testdata/conversions.src"}, + {"testdata/conversions2.src"}, + {"testdata/stmt0.src"}, + {"testdata/stmt1.src"}, + {"testdata/gotos.src"}, + {"testdata/labels.src"}, + {"testdata/literals.src"}, + {"testdata/issues.src"}, + {"testdata/blank.src"}, + {"testdata/issue25008b.src", "testdata/issue25008a.src"}, // order (b before a) is crucial! +} + +var fset = token.NewFileSet() + +// Positioned errors are of the form filename:line:column: message . +var posMsgRx = regexp.MustCompile(`^(.*:[0-9]+:[0-9]+): *(.*)`) + +// splitError splits an error's error message into a position string +// and the actual error message. If there's no position information, +// pos is the empty string, and msg is the entire error message. +// +func splitError(err error) (pos, msg string) { + msg = err.Error() + if m := posMsgRx.FindStringSubmatch(msg); len(m) == 3 { + pos = m[1] + msg = m[2] + } + return +} + +func parseFiles(t *testing.T, filenames []string) ([]*ast.File, []error) { + var files []*ast.File + var errlist []error + for _, filename := range filenames { + file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) + if file == nil { + t.Fatalf("%s: %s", filename, err) + } + files = append(files, file) + if err != nil { + if list, _ := err.(scanner.ErrorList); len(list) > 0 { + for _, err := range list { + errlist = append(errlist, err) + } + } else { + errlist = append(errlist, err) + } + } + } + return files, errlist +} + +// ERROR comments must start with text `ERROR "rx"` or `ERROR rx` where +// rx is a regular expression that matches the expected error message. +// Space around "rx" or rx is ignored. Use the form `ERROR HERE "rx"` +// for error messages that are located immediately after rather than +// at a token's position. +// +var errRx = regexp.MustCompile(`^ *ERROR *(HERE)? *"?([^"]*)"?`) + +// errMap collects the regular expressions of ERROR comments found +// in files and returns them as a map of error positions to error messages. +// +func errMap(t *testing.T, testname string, files []*ast.File) map[string][]string { + // map of position strings to lists of error message patterns + errmap := make(map[string][]string) + + for _, file := range files { + filename := fset.Position(file.Package).Filename + src, err := os.ReadFile(filename) + if err != nil { + t.Fatalf("%s: could not read %s", testname, filename) + } + + var s scanner.Scanner + s.Init(fset.AddFile(filename, -1, len(src)), src, nil, scanner.ScanComments) + var prev token.Pos // position of last non-comment, non-semicolon token + var here token.Pos // position immediately after the token at position prev + + scanFile: + for { + pos, tok, lit := s.Scan() + switch tok { + case token.EOF: + break scanFile + case token.COMMENT: + if lit[1] == '*' { + lit = lit[:len(lit)-2] // strip trailing */ + } + if s := errRx.FindStringSubmatch(lit[2:]); len(s) == 3 { + pos := prev + if s[1] == "HERE" { + pos = here + } + p := fset.Position(pos).String() + errmap[p] = append(errmap[p], strings.TrimSpace(s[2])) + } + case token.SEMICOLON: + // ignore automatically inserted semicolon + if lit == "\n" { + continue scanFile + } + fallthrough + default: + prev = pos + var l int // token length + if tok.IsLiteral() { + l = len(lit) + } else { + l = len(tok.String()) + } + here = prev + token.Pos(l) + } + } + } + + return errmap +} + +func eliminate(t *testing.T, errmap map[string][]string, errlist []error) { + for _, err := range errlist { + pos, gotMsg := splitError(err) + list := errmap[pos] + index := -1 // list index of matching message, if any + // we expect one of the messages in list to match the error at pos + for i, wantRx := range list { + rx, err := regexp.Compile(wantRx) + if err != nil { + t.Errorf("%s: %v", pos, err) + continue + } + if rx.MatchString(gotMsg) { + index = i + break + } + } + if index >= 0 { + // eliminate from list + if n := len(list) - 1; n > 0 { + // not the last entry - swap in last element and shorten list by 1 + list[index] = list[n] + errmap[pos] = list[:n] + } else { + // last entry - remove list from map + delete(errmap, pos) + } + } else { + t.Errorf("%s: no error expected: %q", pos, gotMsg) + } + } +} + +func checkFiles(t *testing.T, testfiles []string) { + // parse files and collect parser errors + files, errlist := parseFiles(t, testfiles) + + pkgName := "" + if len(files) > 0 { + pkgName = files[0].Name.Name + } + + if *listErrors && len(errlist) > 0 { + t.Errorf("--- %s:", pkgName) + for _, err := range errlist { + t.Error(err) + } + } + + // typecheck and collect typechecker errors + var conf Config + // special case for importC.src + if len(testfiles) == 1 && strings.HasSuffix(testfiles[0], "importC.src") { + conf.FakeImportC = true + } + conf.Importer = importer.Default() + conf.Error = func(err error) { + if *haltOnError { + defer panic(err) + } + if *listErrors { + t.Error(err) + return + } + // Ignore secondary error messages starting with "\t"; + // they are clarifying messages for a primary error. + if !strings.Contains(err.Error(), ": \t") { + errlist = append(errlist, err) + } + } + conf.Check(pkgName, fset, files, nil) + + if *listErrors { + return + } + + for _, err := range errlist { + err, ok := err.(Error) + if !ok { + continue + } + code := readCode(err) + if code == 0 { + t.Errorf("missing error code: %v", err) + } + } + + // match and eliminate errors; + // we are expecting the following errors + errmap := errMap(t, pkgName, files) + eliminate(t, errmap, errlist) + + // there should be no expected errors left + if len(errmap) > 0 { + t.Errorf("--- %s: %d source positions with expected (but not reported) errors:", pkgName, len(errmap)) + for pos, list := range errmap { + for _, rx := range list { + t.Errorf("%s: %q", pos, rx) + } + } + } +} + +func TestCheck(t *testing.T) { + testenv.MustHaveGoBuild(t) + + // Declare builtins for testing. + DefPredeclaredTestFuncs() + + // If explicit test files are specified, only check those. + if files := *testFiles; files != "" { + checkFiles(t, strings.Split(files, " ")) + return + } + + // Otherwise, run all the tests. + for _, files := range tests { + checkFiles(t, files) + } +} + +func TestFixedBugs(t *testing.T) { testDir(t, "fixedbugs") } + +func testDir(t *testing.T, dir string) { + testenv.MustHaveGoBuild(t) + + dirs, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + + for _, d := range dirs { + testname := filepath.Base(d.Name()) + testname = strings.TrimSuffix(testname, filepath.Ext(testname)) + t.Run(testname, func(t *testing.T) { + filename := filepath.Join(dir, d.Name()) + if d.IsDir() { + t.Errorf("skipped directory %q", filename) + return + } + checkFiles(t, []string{filename}) + }) + } +} diff --git a/src/go/types/conversions.go b/src/go/types/conversions.go new file mode 100644 index 0000000..1cab1cc --- /dev/null +++ b/src/go/types/conversions.go @@ -0,0 +1,163 @@ +// Copyright 2012 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. + +// This file implements typechecking of conversions. + +package types + +import ( + "go/constant" + "unicode" +) + +// Conversion type-checks the conversion T(x). +// The result is in x. +func (check *Checker) conversion(x *operand, T Type) { + constArg := x.mode == constant_ + + var ok bool + switch { + case constArg && isConstType(T): + // constant conversion + switch t := T.Underlying().(*Basic); { + case representableConst(x.val, check, t, &x.val): + ok = true + case isInteger(x.typ) && isString(t): + codepoint := unicode.ReplacementChar + if i, ok := constant.Uint64Val(x.val); ok && i <= unicode.MaxRune { + codepoint = rune(i) + } + x.val = constant.MakeString(string(codepoint)) + ok = true + } + case x.convertibleTo(check, T): + // non-constant conversion + x.mode = value + ok = true + } + + if !ok { + check.errorf(x, _InvalidConversion, "cannot convert %s to %s", x, T) + x.mode = invalid + return + } + + // The conversion argument types are final. For untyped values the + // conversion provides the type, per the spec: "A constant may be + // given a type explicitly by a constant declaration or conversion,...". + if isUntyped(x.typ) { + final := T + // - For conversions to interfaces, use the argument's default type. + // - For conversions of untyped constants to non-constant types, also + // use the default type (e.g., []byte("foo") should report string + // not []byte as type for the constant "foo"). + // - Keep untyped nil for untyped nil arguments. + // - For integer to string conversions, keep the argument type. + // (See also the TODO below.) + if IsInterface(T) || constArg && !isConstType(T) { + final = Default(x.typ) + } else if isInteger(x.typ) && isString(T) { + final = x.typ + } + check.updateExprType(x.expr, final, true) + } + + x.typ = T +} + +// TODO(gri) convertibleTo checks if T(x) is valid. It assumes that the type +// of x is fully known, but that's not the case for say string(1<b-> ... ->g for a path [a, b, ... g]. +func pathString(path []Object) string { + var s string + for i, p := range path { + if i > 0 { + s += "->" + } + s += p.Name() + } + return s +} + +// objDecl type-checks the declaration of obj in its respective (file) context. +// For the meaning of def, see Checker.definedType, in typexpr.go. +func (check *Checker) objDecl(obj Object, def *Named) { + if trace { + check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath)) + check.indent++ + defer func() { + check.indent-- + check.trace(obj.Pos(), "=> %s (%s)", obj, obj.color()) + }() + } + + // Checking the declaration of obj means inferring its type + // (and possibly its value, for constants). + // An object's type (and thus the object) may be in one of + // three states which are expressed by colors: + // + // - an object whose type is not yet known is painted white (initial color) + // - an object whose type is in the process of being inferred is painted grey + // - an object whose type is fully inferred is painted black + // + // During type inference, an object's color changes from white to grey + // to black (pre-declared objects are painted black from the start). + // A black object (i.e., its type) can only depend on (refer to) other black + // ones. White and grey objects may depend on white and black objects. + // A dependency on a grey object indicates a cycle which may or may not be + // valid. + // + // When objects turn grey, they are pushed on the object path (a stack); + // they are popped again when they turn black. Thus, if a grey object (a + // cycle) is encountered, it is on the object path, and all the objects + // it depends on are the remaining objects on that path. Color encoding + // is such that the color value of a grey object indicates the index of + // that object in the object path. + + // During type-checking, white objects may be assigned a type without + // traversing through objDecl; e.g., when initializing constants and + // variables. Update the colors of those objects here (rather than + // everywhere where we set the type) to satisfy the color invariants. + if obj.color() == white && obj.Type() != nil { + obj.setColor(black) + return + } + + switch obj.color() { + case white: + assert(obj.Type() == nil) + // All color values other than white and black are considered grey. + // Because black and white are < grey, all values >= grey are grey. + // Use those values to encode the object's index into the object path. + obj.setColor(grey + color(check.push(obj))) + defer func() { + check.pop().setColor(black) + }() + + case black: + assert(obj.Type() != nil) + return + + default: + // Color values other than white or black are considered grey. + fallthrough + + case grey: + // We have a cycle. + // In the existing code, this is marked by a non-nil type + // for the object except for constants and variables whose + // type may be non-nil (known), or nil if it depends on the + // not-yet known initialization value. + // In the former case, set the type to Typ[Invalid] because + // we have an initialization cycle. The cycle error will be + // reported later, when determining initialization order. + // TODO(gri) Report cycle here and simplify initialization + // order code. + switch obj := obj.(type) { + case *Const: + if check.cycle(obj) || obj.typ == nil { + obj.typ = Typ[Invalid] + } + + case *Var: + if check.cycle(obj) || obj.typ == nil { + obj.typ = Typ[Invalid] + } + + case *TypeName: + if check.cycle(obj) { + // break cycle + // (without this, calling underlying() + // below may lead to an endless loop + // if we have a cycle for a defined + // (*Named) type) + obj.typ = Typ[Invalid] + } + + case *Func: + if check.cycle(obj) { + // Don't set obj.typ to Typ[Invalid] here + // because plenty of code type-asserts that + // functions have a *Signature type. Grey + // functions have their type set to an empty + // signature which makes it impossible to + // initialize a variable with the function. + } + + default: + unreachable() + } + assert(obj.Type() != nil) + return + } + + d := check.objMap[obj] + if d == nil { + check.dump("%v: %s should have been declared", obj.Pos(), obj) + unreachable() + } + + // save/restore current context and setup object context + defer func(ctxt context) { + check.context = ctxt + }(check.context) + check.context = context{ + scope: d.file, + } + + // Const and var declarations must not have initialization + // cycles. We track them by remembering the current declaration + // in check.decl. Initialization expressions depending on other + // consts, vars, or functions, add dependencies to the current + // check.decl. + switch obj := obj.(type) { + case *Const: + check.decl = d // new package-level const decl + check.constDecl(obj, d.typ, d.init, d.inherited) + case *Var: + check.decl = d // new package-level var decl + check.varDecl(obj, d.lhs, d.typ, d.init) + case *TypeName: + // invalid recursive types are detected via path + check.typeDecl(obj, d.typ, def, d.alias) + case *Func: + // functions may be recursive - no need to track dependencies + check.funcDecl(obj, d) + default: + unreachable() + } +} + +// cycle checks if the cycle starting with obj is valid and +// reports an error if it is not. +func (check *Checker) cycle(obj Object) (isCycle bool) { + // The object map contains the package scope objects and the non-interface methods. + if debug { + info := check.objMap[obj] + inObjMap := info != nil && (info.fdecl == nil || info.fdecl.Recv == nil) // exclude methods + isPkgObj := obj.Parent() == check.pkg.scope + if isPkgObj != inObjMap { + check.dump("%v: inconsistent object map for %s (isPkgObj = %v, inObjMap = %v)", obj.Pos(), obj, isPkgObj, inObjMap) + unreachable() + } + } + + // Count cycle objects. + assert(obj.color() >= grey) + start := obj.color() - grey // index of obj in objPath + cycle := check.objPath[start:] + nval := 0 // number of (constant or variable) values in the cycle + ndef := 0 // number of type definitions in the cycle + for _, obj := range cycle { + switch obj := obj.(type) { + case *Const, *Var: + nval++ + case *TypeName: + // Determine if the type name is an alias or not. For + // package-level objects, use the object map which + // provides syntactic information (which doesn't rely + // on the order in which the objects are set up). For + // local objects, we can rely on the order, so use + // the object's predicate. + // TODO(gri) It would be less fragile to always access + // the syntactic information. We should consider storing + // this information explicitly in the object. + var alias bool + if d := check.objMap[obj]; d != nil { + alias = d.alias // package-level object + } else { + alias = obj.IsAlias() // function local object + } + if !alias { + ndef++ + } + case *Func: + // ignored for now + default: + unreachable() + } + } + + if trace { + check.trace(obj.Pos(), "## cycle detected: objPath = %s->%s (len = %d)", pathString(cycle), obj.Name(), len(cycle)) + check.trace(obj.Pos(), "## cycle contains: %d values, %d type definitions", nval, ndef) + defer func() { + if isCycle { + check.trace(obj.Pos(), "=> error: cycle is invalid") + } + }() + } + + // A cycle involving only constants and variables is invalid but we + // ignore them here because they are reported via the initialization + // cycle check. + if nval == len(cycle) { + return false + } + + // A cycle involving only types (and possibly functions) must have at least + // one type definition to be permitted: If there is no type definition, we + // have a sequence of alias type names which will expand ad infinitum. + if nval == 0 && ndef > 0 { + return false // cycle is permitted + } + + check.cycleError(cycle) + + return true +} + +type typeInfo uint + +// validType verifies that the given type does not "expand" infinitely +// producing a cycle in the type graph. Cycles are detected by marking +// defined types. +// (Cycles involving alias types, as in "type A = [10]A" are detected +// earlier, via the objDecl cycle detection mechanism.) +func (check *Checker) validType(typ Type, path []Object) typeInfo { + const ( + unknown typeInfo = iota + marked + valid + invalid + ) + + switch t := typ.(type) { + case *Array: + return check.validType(t.elem, path) + + case *Struct: + for _, f := range t.fields { + if check.validType(f.typ, path) == invalid { + return invalid + } + } + + case *Interface: + for _, etyp := range t.embeddeds { + if check.validType(etyp, path) == invalid { + return invalid + } + } + + case *Named: + // don't touch the type if it is from a different package or the Universe scope + // (doing so would lead to a race condition - was issue #35049) + if t.obj.pkg != check.pkg { + return valid + } + + // don't report a 2nd error if we already know the type is invalid + // (e.g., if a cycle was detected earlier, via Checker.underlying). + if t.underlying == Typ[Invalid] { + t.info = invalid + return invalid + } + + switch t.info { + case unknown: + t.info = marked + t.info = check.validType(t.orig, append(path, t.obj)) // only types of current package added to path + case marked: + // cycle detected + for i, tn := range path { + if t.obj.pkg != check.pkg { + panic("internal error: type cycle via package-external type") + } + if tn == t.obj { + check.cycleError(path[i:]) + t.info = invalid + t.underlying = Typ[Invalid] + return t.info + } + } + panic("internal error: cycle start not found") + } + return t.info + } + + return valid +} + +// cycleError reports a declaration cycle starting with +// the object in cycle that is "first" in the source. +func (check *Checker) cycleError(cycle []Object) { + // TODO(gri) Should we start with the last (rather than the first) object in the cycle + // since that is the earliest point in the source where we start seeing the + // cycle? That would be more consistent with other error messages. + i := firstInSrc(cycle) + obj := cycle[i] + check.errorf(obj, _InvalidDeclCycle, "illegal cycle in declaration of %s", obj.Name()) + for range cycle { + check.errorf(obj, _InvalidDeclCycle, "\t%s refers to", obj.Name()) // secondary error, \t indented + i++ + if i >= len(cycle) { + i = 0 + } + obj = cycle[i] + } + check.errorf(obj, _InvalidDeclCycle, "\t%s", obj.Name()) +} + +// firstInSrc reports the index of the object with the "smallest" +// source position in path. path must not be empty. +func firstInSrc(path []Object) int { + fst, pos := 0, path[0].Pos() + for i, t := range path[1:] { + if t.Pos() < pos { + fst, pos = i+1, t.Pos() + } + } + return fst +} + +type ( + decl interface { + node() ast.Node + } + + importDecl struct{ spec *ast.ImportSpec } + constDecl struct { + spec *ast.ValueSpec + iota int + typ ast.Expr + init []ast.Expr + inherited bool + } + varDecl struct{ spec *ast.ValueSpec } + typeDecl struct{ spec *ast.TypeSpec } + funcDecl struct{ decl *ast.FuncDecl } +) + +func (d importDecl) node() ast.Node { return d.spec } +func (d constDecl) node() ast.Node { return d.spec } +func (d varDecl) node() ast.Node { return d.spec } +func (d typeDecl) node() ast.Node { return d.spec } +func (d funcDecl) node() ast.Node { return d.decl } + +func (check *Checker) walkDecls(decls []ast.Decl, f func(decl)) { + for _, d := range decls { + check.walkDecl(d, f) + } +} + +func (check *Checker) walkDecl(d ast.Decl, f func(decl)) { + switch d := d.(type) { + case *ast.BadDecl: + // ignore + case *ast.GenDecl: + var last *ast.ValueSpec // last ValueSpec with type or init exprs seen + for iota, s := range d.Specs { + switch s := s.(type) { + case *ast.ImportSpec: + f(importDecl{s}) + case *ast.ValueSpec: + switch d.Tok { + case token.CONST: + // determine which initialization expressions to use + inherited := true + switch { + case s.Type != nil || len(s.Values) > 0: + last = s + inherited = false + case last == nil: + last = new(ast.ValueSpec) // make sure last exists + inherited = false + } + check.arityMatch(s, last) + f(constDecl{spec: s, iota: iota, typ: last.Type, init: last.Values, inherited: inherited}) + case token.VAR: + check.arityMatch(s, nil) + f(varDecl{s}) + default: + check.invalidAST(s, "invalid token %s", d.Tok) + } + case *ast.TypeSpec: + f(typeDecl{s}) + default: + check.invalidAST(s, "unknown ast.Spec node %T", s) + } + } + case *ast.FuncDecl: + f(funcDecl{d}) + default: + check.invalidAST(d, "unknown ast.Decl node %T", d) + } +} + +func (check *Checker) constDecl(obj *Const, typ, init ast.Expr, inherited bool) { + assert(obj.typ == nil) + + // use the correct value of iota + defer func(iota constant.Value, errpos positioner) { + check.iota = iota + check.errpos = errpos + }(check.iota, check.errpos) + check.iota = obj.val + check.errpos = nil + + // provide valid constant value under all circumstances + obj.val = constant.MakeUnknown() + + // determine type, if any + if typ != nil { + t := check.typ(typ) + if !isConstType(t) { + // don't report an error if the type is an invalid C (defined) type + // (issue #22090) + if t.Underlying() != Typ[Invalid] { + check.errorf(typ, _InvalidConstType, "invalid constant type %s", t) + } + obj.typ = Typ[Invalid] + return + } + obj.typ = t + } + + // check initialization + var x operand + if init != nil { + if inherited { + // The initialization expression is inherited from a previous + // constant declaration, and (error) positions refer to that + // expression and not the current constant declaration. Use + // the constant identifier position for any errors during + // init expression evaluation since that is all we have + // (see issues #42991, #42992). + check.errpos = atPos(obj.pos) + } + check.expr(&x, init) + } + check.initConst(obj, &x) +} + +func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init ast.Expr) { + assert(obj.typ == nil) + + // determine type, if any + if typ != nil { + obj.typ = check.typ(typ) + // We cannot spread the type to all lhs variables if there + // are more than one since that would mark them as checked + // (see Checker.objDecl) and the assignment of init exprs, + // if any, would not be checked. + // + // TODO(gri) If we have no init expr, we should distribute + // a given type otherwise we need to re-evalate the type + // expr for each lhs variable, leading to duplicate work. + } + + // check initialization + if init == nil { + if typ == nil { + // error reported before by arityMatch + obj.typ = Typ[Invalid] + } + return + } + + if lhs == nil || len(lhs) == 1 { + assert(lhs == nil || lhs[0] == obj) + var x operand + check.expr(&x, init) + check.initVar(obj, &x, "variable declaration") + return + } + + if debug { + // obj must be one of lhs + found := false + for _, lhs := range lhs { + if obj == lhs { + found = true + break + } + } + if !found { + panic("inconsistent lhs") + } + } + + // We have multiple variables on the lhs and one init expr. + // Make sure all variables have been given the same type if + // one was specified, otherwise they assume the type of the + // init expression values (was issue #15755). + if typ != nil { + for _, lhs := range lhs { + lhs.typ = obj.typ + } + } + + check.initVars(lhs, []ast.Expr{init}, token.NoPos) +} + +// underlying returns the underlying type of typ; possibly by following +// forward chains of named types. Such chains only exist while named types +// are incomplete. If an underlying type is found, resolve the chain by +// setting the underlying type for each defined type in the chain before +// returning it. +// +// If no underlying type is found, a cycle error is reported and Typ[Invalid] +// is used as underlying type for each defined type in the chain and returned +// as result. +func (check *Checker) underlying(typ Type) Type { + // If typ is not a defined type, its underlying type is itself. + n0, _ := typ.(*Named) + if n0 == nil { + return typ // nothing to do + } + + // If the underlying type of a defined type is not a defined + // type, then that is the desired underlying type. + typ = n0.underlying + n, _ := typ.(*Named) + if n == nil { + return typ // common case + } + + // Otherwise, follow the forward chain. + seen := map[*Named]int{n0: 0} + path := []Object{n0.obj} + for { + typ = n.underlying + n1, _ := typ.(*Named) + if n1 == nil { + break // end of chain + } + + seen[n] = len(seen) + path = append(path, n.obj) + n = n1 + + if i, ok := seen[n]; ok { + // cycle + check.cycleError(path[i:]) + typ = Typ[Invalid] + break + } + } + + for n := range seen { + // We should never have to update the underlying type of an imported type; + // those underlying types should have been resolved during the import. + // Also, doing so would lead to a race condition (was issue #31749). + if n.obj.pkg != check.pkg { + panic("internal error: imported type with unresolved underlying type") + } + n.underlying = typ + } + + return typ +} + +func (n *Named) setUnderlying(typ Type) { + if n != nil { + n.underlying = typ + } +} + +func (check *Checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, alias bool) { + assert(obj.typ == nil) + + check.later(func() { + check.validType(obj.typ, nil) + }) + + if alias { + + obj.typ = Typ[Invalid] + obj.typ = check.typ(typ) + + } else { + + named := &Named{obj: obj} + def.setUnderlying(named) + obj.typ = named // make sure recursive type declarations terminate + + // determine underlying type of named + named.orig = check.definedType(typ, named) + + // The underlying type of named may be itself a named type that is + // incomplete: + // + // type ( + // A B + // B *C + // C A + // ) + // + // The type of C is the (named) type of A which is incomplete, + // and which has as its underlying type the named type B. + // Determine the (final, unnamed) underlying type by resolving + // any forward chain. + named.underlying = check.underlying(named) + + } + + check.addMethodDecls(obj) +} + +func (check *Checker) addMethodDecls(obj *TypeName) { + // get associated methods + // (Checker.collectObjects only collects methods with non-blank names; + // Checker.resolveBaseTypeName ensures that obj is not an alias name + // if it has attached methods.) + methods := check.methods[obj] + if methods == nil { + return + } + delete(check.methods, obj) + assert(!check.objMap[obj].alias) // don't use TypeName.IsAlias (requires fully set up object) + + // use an objset to check for name conflicts + var mset objset + + // spec: "If the base type is a struct type, the non-blank method + // and field names must be distinct." + base, _ := obj.typ.(*Named) // shouldn't fail but be conservative + if base != nil { + if t, _ := base.underlying.(*Struct); t != nil { + for _, fld := range t.fields { + if fld.name != "_" { + assert(mset.insert(fld) == nil) + } + } + } + + // Checker.Files may be called multiple times; additional package files + // may add methods to already type-checked types. Add pre-existing methods + // so that we can detect redeclarations. + for _, m := range base.methods { + assert(m.name != "_") + assert(mset.insert(m) == nil) + } + } + + // add valid methods + for _, m := range methods { + // spec: "For a base type, the non-blank names of methods bound + // to it must be unique." + assert(m.name != "_") + if alt := mset.insert(m); alt != nil { + switch alt.(type) { + case *Var: + check.errorf(m, _DuplicateFieldAndMethod, "field and method with the same name %s", m.name) + case *Func: + check.errorf(m, _DuplicateMethod, "method %s already declared for %s", m.name, obj) + default: + unreachable() + } + check.reportAltDecl(alt) + continue + } + + if base != nil { + base.methods = append(base.methods, m) + } + } +} + +func (check *Checker) funcDecl(obj *Func, decl *declInfo) { + assert(obj.typ == nil) + + // func declarations cannot use iota + assert(check.iota == nil) + + sig := new(Signature) + obj.typ = sig // guard against cycles + fdecl := decl.fdecl + check.funcType(sig, fdecl.Recv, fdecl.Type) + if sig.recv == nil && obj.name == "init" && (sig.params.Len() > 0 || sig.results.Len() > 0) { + check.errorf(fdecl, _InvalidInitSig, "func init must have no arguments and no return values") + // ok to continue + } + + // function body must be type-checked after global declarations + // (functions implemented elsewhere have no body) + if !check.conf.IgnoreFuncBodies && fdecl.Body != nil { + check.later(func() { + check.funcBody(decl, obj.name, sig, fdecl.Body, nil) + }) + } +} + +func (check *Checker) declStmt(d ast.Decl) { + pkg := check.pkg + + check.walkDecl(d, func(d decl) { + switch d := d.(type) { + case constDecl: + top := len(check.delayed) + + // declare all constants + lhs := make([]*Const, len(d.spec.Names)) + for i, name := range d.spec.Names { + obj := NewConst(name.Pos(), pkg, name.Name, nil, constant.MakeInt64(int64(d.iota))) + lhs[i] = obj + + var init ast.Expr + if i < len(d.init) { + init = d.init[i] + } + + check.constDecl(obj, d.typ, init, d.inherited) + } + + // process function literals in init expressions before scope changes + check.processDelayed(top) + + // spec: "The scope of a constant or variable identifier declared + // inside a function begins at the end of the ConstSpec or VarSpec + // (ShortVarDecl for short variable declarations) and ends at the + // end of the innermost containing block." + scopePos := d.spec.End() + for i, name := range d.spec.Names { + check.declare(check.scope, name, lhs[i], scopePos) + } + + case varDecl: + top := len(check.delayed) + + lhs0 := make([]*Var, len(d.spec.Names)) + for i, name := range d.spec.Names { + lhs0[i] = NewVar(name.Pos(), pkg, name.Name, nil) + } + + // initialize all variables + for i, obj := range lhs0 { + var lhs []*Var + var init ast.Expr + switch len(d.spec.Values) { + case len(d.spec.Names): + // lhs and rhs match + init = d.spec.Values[i] + case 1: + // rhs is expected to be a multi-valued expression + lhs = lhs0 + init = d.spec.Values[0] + default: + if i < len(d.spec.Values) { + init = d.spec.Values[i] + } + } + check.varDecl(obj, lhs, d.spec.Type, init) + if len(d.spec.Values) == 1 { + // If we have a single lhs variable we are done either way. + // If we have a single rhs expression, it must be a multi- + // valued expression, in which case handling the first lhs + // variable will cause all lhs variables to have a type + // assigned, and we are done as well. + if debug { + for _, obj := range lhs0 { + assert(obj.typ != nil) + } + } + break + } + } + + // process function literals in init expressions before scope changes + check.processDelayed(top) + + // declare all variables + // (only at this point are the variable scopes (parents) set) + scopePos := d.spec.End() // see constant declarations + for i, name := range d.spec.Names { + // see constant declarations + check.declare(check.scope, name, lhs0[i], scopePos) + } + + case typeDecl: + obj := NewTypeName(d.spec.Name.Pos(), pkg, d.spec.Name.Name, nil) + // spec: "The scope of a type identifier declared inside a function + // begins at the identifier in the TypeSpec and ends at the end of + // the innermost containing block." + scopePos := d.spec.Name.Pos() + check.declare(check.scope, d.spec.Name, obj, scopePos) + // mark and unmark type before calling typeDecl; its type is still nil (see Checker.objDecl) + obj.setColor(grey + color(check.push(obj))) + check.typeDecl(obj, d.spec.Type, nil, d.spec.Assign.IsValid()) + check.pop().setColor(black) + default: + check.invalidAST(d.node(), "unknown ast.Decl node %T", d.node()) + } + }) +} diff --git a/src/go/types/errorcodes.go b/src/go/types/errorcodes.go new file mode 100644 index 0000000..c01a12c --- /dev/null +++ b/src/go/types/errorcodes.go @@ -0,0 +1,1366 @@ +// Copyright 2020 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 types + +type errorCode int + +// This file defines the error codes that can be produced during type-checking. +// Collectively, these codes provide an identifier that may be used to +// implement special handling for certain types of errors. +// +// Error codes should be fine-grained enough that the exact nature of the error +// can be easily determined, but coarse enough that they are not an +// implementation detail of the type checking algorithm. As a rule-of-thumb, +// errors should be considered equivalent if there is a theoretical refactoring +// of the type checker in which they are emitted in exactly one place. For +// example, the type checker emits different error messages for "too many +// arguments" and "too few arguments", but one can imagine an alternative type +// checker where this check instead just emits a single "wrong number of +// arguments", so these errors should have the same code. +// +// Error code names should be as brief as possible while retaining accuracy and +// distinctiveness. In most cases names should start with an adjective +// describing the nature of the error (e.g. "invalid", "unused", "misplaced"), +// and end with a noun identifying the relevant language object. For example, +// "_DuplicateDecl" or "_InvalidSliceExpr". For brevity, naming follows the +// convention that "bad" implies a problem with syntax, and "invalid" implies a +// problem with types. + +const ( + _ errorCode = iota + + // _Test is reserved for errors that only apply while in self-test mode. + _Test + + /* package names */ + + // _BlankPkgName occurs when a package name is the blank identifier "_". + // + // Per the spec: + // "The PackageName must not be the blank identifier." + _BlankPkgName + + // _MismatchedPkgName occurs when a file's package name doesn't match the + // package name already established by other files. + _MismatchedPkgName + + // _InvalidPkgUse occurs when a package identifier is used outside of a + // selector expression. + // + // Example: + // import "fmt" + // + // var _ = fmt + _InvalidPkgUse + + /* imports */ + + // _BadImportPath occurs when an import path is not valid. + _BadImportPath + + // _BrokenImport occurs when importing a package fails. + // + // Example: + // import "amissingpackage" + _BrokenImport + + // _ImportCRenamed occurs when the special import "C" is renamed. "C" is a + // pseudo-package, and must not be renamed. + // + // Example: + // import _ "C" + _ImportCRenamed + + // _UnusedImport occurs when an import is unused. + // + // Example: + // import "fmt" + // + // func main() {} + _UnusedImport + + /* initialization */ + + // _InvalidInitCycle occurs when an invalid cycle is detected within the + // initialization graph. + // + // Example: + // var x int = f() + // + // func f() int { return x } + _InvalidInitCycle + + /* decls */ + + // _DuplicateDecl occurs when an identifier is declared multiple times. + // + // Example: + // var x = 1 + // var x = 2 + _DuplicateDecl + + // _InvalidDeclCycle occurs when a declaration cycle is not valid. + // + // Example: + // import "unsafe" + // + // type T struct { + // a [n]int + // } + // + // var n = unsafe.Sizeof(T{}) + _InvalidDeclCycle + + // _InvalidTypeCycle occurs when a cycle in type definitions results in a + // type that is not well-defined. + // + // Example: + // import "unsafe" + // + // type T [unsafe.Sizeof(T{})]int + _InvalidTypeCycle + + /* decls > const */ + + // _InvalidConstInit occurs when a const declaration has a non-constant + // initializer. + // + // Example: + // var x int + // const _ = x + _InvalidConstInit + + // _InvalidConstVal occurs when a const value cannot be converted to its + // target type. + // + // TODO(findleyr): this error code and example are not very clear. Consider + // removing it. + // + // Example: + // const _ = 1 << "hello" + _InvalidConstVal + + // _InvalidConstType occurs when the underlying type in a const declaration + // is not a valid constant type. + // + // Example: + // const c *int = 4 + _InvalidConstType + + /* decls > var (+ other variable assignment codes) */ + + // _UntypedNil occurs when the predeclared (untyped) value nil is used to + // initialize a variable declared without an explicit type. + // + // Example: + // var x = nil + _UntypedNil + + // _WrongAssignCount occurs when the number of values on the right-hand side + // of an assignment or or initialization expression does not match the number + // of variables on the left-hand side. + // + // Example: + // var x = 1, 2 + _WrongAssignCount + + // _UnassignableOperand occurs when the left-hand side of an assignment is + // not assignable. + // + // Example: + // func f() { + // const c = 1 + // c = 2 + // } + _UnassignableOperand + + // _NoNewVar occurs when a short variable declaration (':=') does not declare + // new variables. + // + // Example: + // func f() { + // x := 1 + // x := 2 + // } + _NoNewVar + + // _MultiValAssignOp occurs when an assignment operation (+=, *=, etc) does + // not have single-valued left-hand or right-hand side. + // + // Per the spec: + // "In assignment operations, both the left- and right-hand expression lists + // must contain exactly one single-valued expression" + // + // Example: + // func f() int { + // x, y := 1, 2 + // x, y += 1 + // return x + y + // } + _MultiValAssignOp + + // _InvalidIfaceAssign occurs when a value of type T is used as an + // interface, but T does not implement a method of the expected interface. + // + // Example: + // type I interface { + // f() + // } + // + // type T int + // + // var x I = T(1) + _InvalidIfaceAssign + + // _InvalidChanAssign occurs when a chan assignment is invalid. + // + // Per the spec, a value x is assignable to a channel type T if: + // "x is a bidirectional channel value, T is a channel type, x's type V and + // T have identical element types, and at least one of V or T is not a + // defined type." + // + // Example: + // type T1 chan int + // type T2 chan int + // + // var x T1 + // // Invalid assignment because both types are named + // var _ T2 = x + _InvalidChanAssign + + // _IncompatibleAssign occurs when the type of the right-hand side expression + // in an assignment cannot be assigned to the type of the variable being + // assigned. + // + // Example: + // var x []int + // var _ int = x + _IncompatibleAssign + + // _UnaddressableFieldAssign occurs when trying to assign to a struct field + // in a map value. + // + // Example: + // func f() { + // m := make(map[string]struct{i int}) + // m["foo"].i = 42 + // } + _UnaddressableFieldAssign + + /* decls > type (+ other type expression codes) */ + + // _NotAType occurs when the identifier used as the underlying type in a type + // declaration or the right-hand side of a type alias does not denote a type. + // + // Example: + // var S = 2 + // + // type T S + _NotAType + + // _InvalidArrayLen occurs when an array length is not a constant value. + // + // Example: + // var n = 3 + // var _ = [n]int{} + _InvalidArrayLen + + // _BlankIfaceMethod occurs when a method name is '_'. + // + // Per the spec: + // "The name of each explicitly specified method must be unique and not + // blank." + // + // Example: + // type T interface { + // _(int) + // } + _BlankIfaceMethod + + // _IncomparableMapKey occurs when a map key type does not support the == and + // != operators. + // + // Per the spec: + // "The comparison operators == and != must be fully defined for operands of + // the key type; thus the key type must not be a function, map, or slice." + // + // Example: + // var x map[T]int + // + // type T []int + _IncomparableMapKey + + // _InvalidIfaceEmbed occurs when a non-interface type is embedded in an + // interface. + // + // Example: + // type T struct {} + // + // func (T) m() + // + // type I interface { + // T + // } + _InvalidIfaceEmbed + + // _InvalidPtrEmbed occurs when an embedded field is of the pointer form *T, + // and T itself is itself a pointer, an unsafe.Pointer, or an interface. + // + // Per the spec: + // "An embedded field must be specified as a type name T or as a pointer to + // a non-interface type name *T, and T itself may not be a pointer type." + // + // Example: + // type T *int + // + // type S struct { + // *T + // } + _InvalidPtrEmbed + + /* decls > func and method */ + + // _BadRecv occurs when a method declaration does not have exactly one + // receiver parameter. + // + // Example: + // func () _() {} + _BadRecv + + // _InvalidRecv occurs when a receiver type expression is not of the form T + // or *T, or T is a pointer type. + // + // Example: + // type T struct {} + // + // func (**T) m() {} + _InvalidRecv + + // _DuplicateFieldAndMethod occurs when an identifier appears as both a field + // and method name. + // + // Example: + // type T struct { + // m int + // } + // + // func (T) m() {} + _DuplicateFieldAndMethod + + // _DuplicateMethod occurs when two methods on the same receiver type have + // the same name. + // + // Example: + // type T struct {} + // func (T) m() {} + // func (T) m(i int) int { return i } + _DuplicateMethod + + /* decls > special */ + + // _InvalidBlank occurs when a blank identifier is used as a value or type. + // + // Per the spec: + // "The blank identifier may appear as an operand only on the left-hand side + // of an assignment." + // + // Example: + // var x = _ + _InvalidBlank + + // _InvalidIota occurs when the predeclared identifier iota is used outside + // of a constant declaration. + // + // Example: + // var x = iota + _InvalidIota + + // _MissingInitBody occurs when an init function is missing its body. + // + // Example: + // func init() + _MissingInitBody + + // _InvalidInitSig occurs when an init function declares parameters or + // results. + // + // Example: + // func init() int { return 1 } + _InvalidInitSig + + // _InvalidInitDecl occurs when init is declared as anything other than a + // function. + // + // Example: + // var init = 1 + _InvalidInitDecl + + // _InvalidMainDecl occurs when main is declared as anything other than a + // function, in a main package. + _InvalidMainDecl + + /* exprs */ + + // _TooManyValues occurs when a function returns too many values for the + // expression context in which it is used. + // + // Example: + // func ReturnTwo() (int, int) { + // return 1, 2 + // } + // + // var x = ReturnTwo() + _TooManyValues + + // _NotAnExpr occurs when a type expression is used where a value expression + // is expected. + // + // Example: + // type T struct {} + // + // func f() { + // T + // } + _NotAnExpr + + /* exprs > const */ + + // _TruncatedFloat occurs when a float constant is truncated to an integer + // value. + // + // Example: + // var _ int = 98.6 + _TruncatedFloat + + // _NumericOverflow occurs when a numeric constant overflows its target type. + // + // Example: + // var x int8 = 1000 + _NumericOverflow + + /* exprs > operation */ + + // _UndefinedOp occurs when an operator is not defined for the type(s) used + // in an operation. + // + // Example: + // var c = "a" - "b" + _UndefinedOp + + // _MismatchedTypes occurs when operand types are incompatible in a binary + // operation. + // + // Example: + // var a = "hello" + // var b = 1 + // var c = a - b + _MismatchedTypes + + // _DivByZero occurs when a division operation is provable at compile + // time to be a division by zero. + // + // Example: + // const divisor = 0 + // var x int = 1/divisor + _DivByZero + + // _NonNumericIncDec occurs when an increment or decrement operator is + // applied to a non-numeric value. + // + // Example: + // func f() { + // var c = "c" + // c++ + // } + _NonNumericIncDec + + /* exprs > ptr */ + + // _UnaddressableOperand occurs when the & operator is applied to an + // unaddressable expression. + // + // Example: + // var x = &1 + _UnaddressableOperand + + // _InvalidIndirection occurs when a non-pointer value is indirected via the + // '*' operator. + // + // Example: + // var x int + // var y = *x + _InvalidIndirection + + /* exprs > [] */ + + // _NonIndexableOperand occurs when an index operation is applied to a value + // that cannot be indexed. + // + // Example: + // var x = 1 + // var y = x[1] + _NonIndexableOperand + + // _InvalidIndex occurs when an index argument is not of integer type, + // negative, or out-of-bounds. + // + // Example: + // var s = [...]int{1,2,3} + // var x = s[5] + // + // Example: + // var s = []int{1,2,3} + // var _ = s[-1] + // + // Example: + // var s = []int{1,2,3} + // var i string + // var _ = s[i] + _InvalidIndex + + // _SwappedSliceIndices occurs when constant indices in a slice expression + // are decreasing in value. + // + // Example: + // var _ = []int{1,2,3}[2:1] + _SwappedSliceIndices + + /* operators > slice */ + + // _NonSliceableOperand occurs when a slice operation is applied to a value + // whose type is not sliceable, or is unaddressable. + // + // Example: + // var x = [...]int{1, 2, 3}[:1] + // + // Example: + // var x = 1 + // var y = 1[:1] + _NonSliceableOperand + + // _InvalidSliceExpr occurs when a three-index slice expression (a[x:y:z]) is + // applied to a string. + // + // Example: + // var s = "hello" + // var x = s[1:2:3] + _InvalidSliceExpr + + /* exprs > shift */ + + // _InvalidShiftCount occurs when the right-hand side of a shift operation is + // either non-integer, negative, or too large. + // + // Example: + // var ( + // x string + // y int = 1 << x + // ) + _InvalidShiftCount + + // _InvalidShiftOperand occurs when the shifted operand is not an integer. + // + // Example: + // var s = "hello" + // var x = s << 2 + _InvalidShiftOperand + + /* exprs > chan */ + + // _InvalidReceive occurs when there is a channel receive from a value that + // is either not a channel, or is a send-only channel. + // + // Example: + // func f() { + // var x = 1 + // <-x + // } + _InvalidReceive + + // _InvalidSend occurs when there is a channel send to a value that is not a + // channel, or is a receive-only channel. + // + // Example: + // func f() { + // var x = 1 + // x <- "hello!" + // } + _InvalidSend + + /* exprs > literal */ + + // _DuplicateLitKey occurs when an index is duplicated in a slice, array, or + // map literal. + // + // Example: + // var _ = []int{0:1, 0:2} + // + // Example: + // var _ = map[string]int{"a": 1, "a": 2} + _DuplicateLitKey + + // _MissingLitKey occurs when a map literal is missing a key expression. + // + // Example: + // var _ = map[string]int{1} + _MissingLitKey + + // _InvalidLitIndex occurs when the key in a key-value element of a slice or + // array literal is not an integer constant. + // + // Example: + // var i = 0 + // var x = []string{i: "world"} + _InvalidLitIndex + + // _OversizeArrayLit occurs when an array literal exceeds its length. + // + // Example: + // var _ = [2]int{1,2,3} + _OversizeArrayLit + + // _MixedStructLit occurs when a struct literal contains a mix of positional + // and named elements. + // + // Example: + // var _ = struct{i, j int}{i: 1, 2} + _MixedStructLit + + // _InvalidStructLit occurs when a positional struct literal has an incorrect + // number of values. + // + // Example: + // var _ = struct{i, j int}{1,2,3} + _InvalidStructLit + + // _MissingLitField occurs when a struct literal refers to a field that does + // not exist on the struct type. + // + // Example: + // var _ = struct{i int}{j: 2} + _MissingLitField + + // _DuplicateLitField occurs when a struct literal contains duplicated + // fields. + // + // Example: + // var _ = struct{i int}{i: 1, i: 2} + _DuplicateLitField + + // _UnexportedLitField occurs when a positional struct literal implicitly + // assigns an unexported field of an imported type. + _UnexportedLitField + + // _InvalidLitField occurs when a field name is not a valid identifier. + // + // Example: + // var _ = struct{i int}{1: 1} + _InvalidLitField + + // _UntypedLit occurs when a composite literal omits a required type + // identifier. + // + // Example: + // type outer struct{ + // inner struct { i int } + // } + // + // var _ = outer{inner: {1}} + _UntypedLit + + // _InvalidLit occurs when a composite literal expression does not match its + // type. + // + // Example: + // type P *struct{ + // x int + // } + // var _ = P {} + _InvalidLit + + /* exprs > selector */ + + // _AmbiguousSelector occurs when a selector is ambiguous. + // + // Example: + // type E1 struct { i int } + // type E2 struct { i int } + // type T struct { E1; E2 } + // + // var x T + // var _ = x.i + _AmbiguousSelector + + // _UndeclaredImportedName occurs when a package-qualified identifier is + // undeclared by the imported package. + // + // Example: + // import "go/types" + // + // var _ = types.NotAnActualIdentifier + _UndeclaredImportedName + + // _UnexportedName occurs when a selector refers to an unexported identifier + // of an imported package. + // + // Example: + // import "reflect" + // + // type _ reflect.flag + _UnexportedName + + // _UndeclaredName occurs when an identifier is not declared in the current + // scope. + // + // Example: + // var x T + _UndeclaredName + + // _MissingFieldOrMethod occurs when a selector references a field or method + // that does not exist. + // + // Example: + // type T struct {} + // + // var x = T{}.f + _MissingFieldOrMethod + + /* exprs > ... */ + + // _BadDotDotDotSyntax occurs when a "..." occurs in a context where it is + // not valid. + // + // Example: + // var _ = map[int][...]int{0: {}} + _BadDotDotDotSyntax + + // _NonVariadicDotDotDot occurs when a "..." is used on the final argument to + // a non-variadic function. + // + // Example: + // func printArgs(s []string) { + // for _, a := range s { + // println(a) + // } + // } + // + // func f() { + // s := []string{"a", "b", "c"} + // printArgs(s...) + // } + _NonVariadicDotDotDot + + // _MisplacedDotDotDot occurs when a "..." is used somewhere other than the + // final argument to a function call. + // + // Example: + // func printArgs(args ...int) { + // for _, a := range args { + // println(a) + // } + // } + // + // func f() { + // a := []int{1,2,3} + // printArgs(0, a...) + // } + _MisplacedDotDotDot + + // _InvalidDotDotDotOperand occurs when a "..." operator is applied to a + // single-valued operand. + // + // Example: + // func printArgs(args ...int) { + // for _, a := range args { + // println(a) + // } + // } + // + // func f() { + // a := 1 + // printArgs(a...) + // } + // + // Example: + // func args() (int, int) { + // return 1, 2 + // } + // + // func printArgs(args ...int) { + // for _, a := range args { + // println(a) + // } + // } + // + // func g() { + // printArgs(args()...) + // } + _InvalidDotDotDotOperand + + // _InvalidDotDotDot occurs when a "..." is used in a non-variadic built-in + // function. + // + // Example: + // var s = []int{1, 2, 3} + // var l = len(s...) + _InvalidDotDotDot + + /* exprs > built-in */ + + // _UncalledBuiltin occurs when a built-in function is used as a + // function-valued expression, instead of being called. + // + // Per the spec: + // "The built-in functions do not have standard Go types, so they can only + // appear in call expressions; they cannot be used as function values." + // + // Example: + // var _ = copy + _UncalledBuiltin + + // _InvalidAppend occurs when append is called with a first argument that is + // not a slice. + // + // Example: + // var _ = append(1, 2) + _InvalidAppend + + // _InvalidCap occurs when an argument to the cap built-in function is not of + // supported type. + // + // See https://golang.org/ref/spec#Length_and_capacity for information on + // which underlying types are supported as arguments to cap and len. + // + // Example: + // var s = 2 + // var x = cap(s) + _InvalidCap + + // _InvalidClose occurs when close(...) is called with an argument that is + // not of channel type, or that is a receive-only channel. + // + // Example: + // func f() { + // var x int + // close(x) + // } + _InvalidClose + + // _InvalidCopy occurs when the arguments are not of slice type or do not + // have compatible type. + // + // See https://golang.org/ref/spec#Appending_and_copying_slices for more + // information on the type requirements for the copy built-in. + // + // Example: + // func f() { + // var x []int + // y := []int64{1,2,3} + // copy(x, y) + // } + _InvalidCopy + + // _InvalidComplex occurs when the complex built-in function is called with + // arguments with incompatible types. + // + // Example: + // var _ = complex(float32(1), float64(2)) + _InvalidComplex + + // _InvalidDelete occurs when the delete built-in function is called with a + // first argument that is not a map. + // + // Example: + // func f() { + // m := "hello" + // delete(m, "e") + // } + _InvalidDelete + + // _InvalidImag occurs when the imag built-in function is called with an + // argument that does not have complex type. + // + // Example: + // var _ = imag(int(1)) + _InvalidImag + + // _InvalidLen occurs when an argument to the len built-in function is not of + // supported type. + // + // See https://golang.org/ref/spec#Length_and_capacity for information on + // which underlying types are supported as arguments to cap and len. + // + // Example: + // var s = 2 + // var x = len(s) + _InvalidLen + + // _SwappedMakeArgs occurs when make is called with three arguments, and its + // length argument is larger than its capacity argument. + // + // Example: + // var x = make([]int, 3, 2) + _SwappedMakeArgs + + // _InvalidMake occurs when make is called with an unsupported type argument. + // + // See https://golang.org/ref/spec#Making_slices_maps_and_channels for + // information on the types that may be created using make. + // + // Example: + // var x = make(int) + _InvalidMake + + // _InvalidReal occurs when the real built-in function is called with an + // argument that does not have complex type. + // + // Example: + // var _ = real(int(1)) + _InvalidReal + + /* exprs > assertion */ + + // _InvalidAssert occurs when a type assertion is applied to a + // value that is not of interface type. + // + // Example: + // var x = 1 + // var _ = x.(float64) + _InvalidAssert + + // _ImpossibleAssert occurs for a type assertion x.(T) when the value x of + // interface cannot have dynamic type T, due to a missing or mismatching + // method on T. + // + // Example: + // type T int + // + // func (t *T) m() int { return int(*t) } + // + // type I interface { m() int } + // + // var x I + // var _ = x.(T) + _ImpossibleAssert + + /* exprs > conversion */ + + // _InvalidConversion occurs when the argument type cannot be converted to the + // target. + // + // See https://golang.org/ref/spec#Conversions for the rules of + // convertibility. + // + // Example: + // var x float64 + // var _ = string(x) + _InvalidConversion + + // _InvalidUntypedConversion occurs when an there is no valid implicit + // conversion from an untyped value satisfying the type constraints of the + // context in which it is used. + // + // Example: + // var _ = 1 + "" + _InvalidUntypedConversion + + /* offsetof */ + + // _BadOffsetofSyntax occurs when unsafe.Offsetof is called with an argument + // that is not a selector expression. + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Offsetof(x) + _BadOffsetofSyntax + + // _InvalidOffsetof occurs when unsafe.Offsetof is called with a method + // selector, rather than a field selector, or when the field is embedded via + // a pointer. + // + // Per the spec: + // + // "If f is an embedded field, it must be reachable without pointer + // indirections through fields of the struct. " + // + // Example: + // import "unsafe" + // + // type T struct { f int } + // type S struct { *T } + // var s S + // var _ = unsafe.Offsetof(s.f) + // + // Example: + // import "unsafe" + // + // type S struct{} + // + // func (S) m() {} + // + // var s S + // var _ = unsafe.Offsetof(s.m) + _InvalidOffsetof + + /* control flow > scope */ + + // _UnusedExpr occurs when a side-effect free expression is used as a + // statement. Such a statement has no effect. + // + // Example: + // func f(i int) { + // i*i + // } + _UnusedExpr + + // _UnusedVar occurs when a variable is declared but unused. + // + // Example: + // func f() { + // x := 1 + // } + _UnusedVar + + // _MissingReturn occurs when a function with results is missing a return + // statement. + // + // Example: + // func f() int {} + _MissingReturn + + // _WrongResultCount occurs when a return statement returns an incorrect + // number of values. + // + // Example: + // func ReturnOne() int { + // return 1, 2 + // } + _WrongResultCount + + // _OutOfScopeResult occurs when the name of a value implicitly returned by + // an empty return statement is shadowed in a nested scope. + // + // Example: + // func factor(n int) (i int) { + // for i := 2; i < n; i++ { + // if n%i == 0 { + // return + // } + // } + // return 0 + // } + _OutOfScopeResult + + /* control flow > if */ + + // _InvalidCond occurs when an if condition is not a boolean expression. + // + // Example: + // func checkReturn(i int) { + // if i { + // panic("non-zero return") + // } + // } + _InvalidCond + + /* control flow > for */ + + // _InvalidPostDecl occurs when there is a declaration in a for-loop post + // statement. + // + // Example: + // func f() { + // for i := 0; i < 10; j := 0 {} + // } + _InvalidPostDecl + + // _InvalidChanRange occurs when a send-only channel used in a range + // expression. + // + // Example: + // func sum(c chan<- int) { + // s := 0 + // for i := range c { + // s += i + // } + // } + _InvalidChanRange + + // _InvalidIterVar occurs when two iteration variables are used while ranging + // over a channel. + // + // Example: + // func f(c chan int) { + // for k, v := range c { + // println(k, v) + // } + // } + _InvalidIterVar + + // _InvalidRangeExpr occurs when the type of a range expression is not array, + // slice, string, map, or channel. + // + // Example: + // func f(i int) { + // for j := range i { + // println(j) + // } + // } + _InvalidRangeExpr + + /* control flow > switch */ + + // _MisplacedBreak occurs when a break statement is not within a for, switch, + // or select statement of the innermost function definition. + // + // Example: + // func f() { + // break + // } + _MisplacedBreak + + // _MisplacedContinue occurs when a continue statement is not within a for + // loop of the innermost function definition. + // + // Example: + // func sumeven(n int) int { + // proceed := func() { + // continue + // } + // sum := 0 + // for i := 1; i <= n; i++ { + // if i % 2 != 0 { + // proceed() + // } + // sum += i + // } + // return sum + // } + _MisplacedContinue + + // _MisplacedFallthrough occurs when a fallthrough statement is not within an + // expression switch. + // + // Example: + // func typename(i interface{}) string { + // switch i.(type) { + // case int64: + // fallthrough + // case int: + // return "int" + // } + // return "unsupported" + // } + _MisplacedFallthrough + + // _DuplicateCase occurs when a type or expression switch has duplicate + // cases. + // + // Example: + // func printInt(i int) { + // switch i { + // case 1: + // println("one") + // case 1: + // println("One") + // } + // } + _DuplicateCase + + // _DuplicateDefault occurs when a type or expression switch has multiple + // default clauses. + // + // Example: + // func printInt(i int) { + // switch i { + // case 1: + // println("one") + // default: + // println("One") + // default: + // println("1") + // } + // } + _DuplicateDefault + + // _BadTypeKeyword occurs when a .(type) expression is used anywhere other + // than a type switch. + // + // Example: + // type I interface { + // m() + // } + // var t I + // var _ = t.(type) + _BadTypeKeyword + + // _InvalidTypeSwitch occurs when .(type) is used on an expression that is + // not of interface type. + // + // Example: + // func f(i int) { + // switch x := i.(type) {} + // } + _InvalidTypeSwitch + + // _InvalidExprSwitch occurs when a switch expression is not comparable. + // + // Example: + // func _() { + // var a struct{ _ func() } + // switch a /* ERROR cannot switch on a */ { + // } + // } + _InvalidExprSwitch + + /* control flow > select */ + + // _InvalidSelectCase occurs when a select case is not a channel send or + // receive. + // + // Example: + // func checkChan(c <-chan int) bool { + // select { + // case c: + // return true + // default: + // return false + // } + // } + _InvalidSelectCase + + /* control flow > labels and jumps */ + + // _UndeclaredLabel occurs when an undeclared label is jumped to. + // + // Example: + // func f() { + // goto L + // } + _UndeclaredLabel + + // _DuplicateLabel occurs when a label is declared more than once. + // + // Example: + // func f() int { + // L: + // L: + // return 1 + // } + _DuplicateLabel + + // _MisplacedLabel occurs when a break or continue label is not on a for, + // switch, or select statement. + // + // Example: + // func f() { + // L: + // a := []int{1,2,3} + // for _, e := range a { + // if e > 10 { + // break L + // } + // println(a) + // } + // } + _MisplacedLabel + + // _UnusedLabel occurs when a label is declared but not used. + // + // Example: + // func f() { + // L: + // } + _UnusedLabel + + // _JumpOverDecl occurs when a label jumps over a variable declaration. + // + // Example: + // func f() int { + // goto L + // x := 2 + // L: + // x++ + // return x + // } + _JumpOverDecl + + // _JumpIntoBlock occurs when a forward jump goes to a label inside a nested + // block. + // + // Example: + // func f(x int) { + // goto L + // if x > 0 { + // L: + // print("inside block") + // } + // } + _JumpIntoBlock + + /* control flow > calls */ + + // _InvalidMethodExpr occurs when a pointer method is called but the argument + // is not addressable. + // + // Example: + // type T struct {} + // + // func (*T) m() int { return 1 } + // + // var _ = T.m(T{}) + _InvalidMethodExpr + + // _WrongArgCount occurs when too few or too many arguments are passed by a + // function call. + // + // Example: + // func f(i int) {} + // var x = f() + _WrongArgCount + + // _InvalidCall occurs when an expression is called that is not of function + // type. + // + // Example: + // var x = "x" + // var y = x() + _InvalidCall + + /* control flow > suspended */ + + // _UnusedResults occurs when a restricted expression-only built-in function + // is suspended via go or defer. Such a suspension discards the results of + // these side-effect free built-in functions, and therefore is ineffectual. + // + // Example: + // func f(a []int) int { + // defer len(a) + // return i + // } + _UnusedResults + + // _InvalidDefer occurs when a deferred expression is not a function call, + // for example if the expression is a type conversion. + // + // Example: + // func f(i int) int { + // defer int32(i) + // return i + // } + _InvalidDefer + + // _InvalidGo occurs when a go expression is not a function call, for example + // if the expression is a type conversion. + // + // Example: + // func f(i int) int { + // go int32(i) + // return i + // } + _InvalidGo +) diff --git a/src/go/types/errorcodes_test.go b/src/go/types/errorcodes_test.go new file mode 100644 index 0000000..5da1cda --- /dev/null +++ b/src/go/types/errorcodes_test.go @@ -0,0 +1,197 @@ +// Copyright 2020 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 types_test + +import ( + "fmt" + "go/ast" + "go/constant" + "go/importer" + "go/parser" + "go/token" + "reflect" + "strings" + "testing" + + . "go/types" +) + +func TestErrorCodeExamples(t *testing.T) { + walkCodes(t, func(name string, value int, spec *ast.ValueSpec) { + t.Run(name, func(t *testing.T) { + doc := spec.Doc.Text() + examples := strings.Split(doc, "Example:") + for i := 1; i < len(examples); i++ { + example := examples[i] + err := checkExample(t, example) + if err == nil { + t.Fatalf("no error in example #%d", i) + } + typerr, ok := err.(Error) + if !ok { + t.Fatalf("not a types.Error: %v", err) + } + if got := readCode(typerr); got != value { + t.Errorf("%s: example #%d returned code %d (%s), want %d", name, i, got, err, value) + } + } + }) + }) +} + +func walkCodes(t *testing.T, f func(string, int, *ast.ValueSpec)) { + t.Helper() + fset := token.NewFileSet() + files, err := pkgFiles(fset, ".", parser.ParseComments) // from self_test.go + if err != nil { + t.Fatal(err) + } + conf := Config{Importer: importer.Default()} + info := &Info{ + Types: make(map[ast.Expr]TypeAndValue), + Defs: make(map[*ast.Ident]Object), + Uses: make(map[*ast.Ident]Object), + } + _, err = conf.Check("types", fset, files, info) + if err != nil { + t.Fatal(err) + } + for _, file := range files { + for _, decl := range file.Decls { + decl, ok := decl.(*ast.GenDecl) + if !ok || decl.Tok != token.CONST { + continue + } + for _, spec := range decl.Specs { + spec, ok := spec.(*ast.ValueSpec) + if !ok || len(spec.Names) == 0 { + continue + } + obj := info.ObjectOf(spec.Names[0]) + if named, ok := obj.Type().(*Named); ok && named.Obj().Name() == "errorCode" { + if len(spec.Names) != 1 { + t.Fatalf("bad Code declaration for %q: got %d names, want exactly 1", spec.Names[0].Name, len(spec.Names)) + } + codename := spec.Names[0].Name + value := int(constant.Val(obj.(*Const).Val()).(int64)) + f(codename, value, spec) + } + } + } + } +} + +func readCode(err Error) int { + v := reflect.ValueOf(err) + return int(v.FieldByName("go116code").Int()) +} + +func checkExample(t *testing.T, example string) error { + t.Helper() + fset := token.NewFileSet() + src := fmt.Sprintf("package p\n\n%s", example) + file, err := parser.ParseFile(fset, "example.go", src, 0) + if err != nil { + t.Fatal(err) + } + conf := Config{ + FakeImportC: true, + Importer: importer.Default(), + } + _, err = conf.Check("example", fset, []*ast.File{file}, nil) + return err +} + +func TestErrorCodeStyle(t *testing.T) { + // The set of error codes is large and intended to be self-documenting, so + // this test enforces some style conventions. + forbiddenInIdent := []string{ + // use invalid instead + "illegal", + // words with a common short-form + "argument", + "assertion", + "assignment", + "boolean", + "channel", + "condition", + "declaration", + "expression", + "function", + "initial", // use init for initializer, initialization, etc. + "integer", + "interface", + "iterat", // use iter for iterator, iteration, etc. + "literal", + "operation", + "package", + "pointer", + "receiver", + "signature", + "statement", + "variable", + } + forbiddenInComment := []string{ + // lhs and rhs should be spelled-out. + "lhs", "rhs", + // builtin should be hyphenated. + "builtin", + // Use dot-dot-dot. + "ellipsis", + } + nameHist := make(map[int]int) + longestName := "" + maxValue := 0 + + walkCodes(t, func(name string, value int, spec *ast.ValueSpec) { + if name == "_" { + return + } + nameHist[len(name)]++ + if value > maxValue { + maxValue = value + } + if len(name) > len(longestName) { + longestName = name + } + if token.IsExported(name) { + // This is an experimental API, and errorCode values should not be + // exported. + t.Errorf("%q is exported", name) + } + if name[0] != '_' || !token.IsExported(name[1:]) { + t.Errorf("%q should start with _, followed by an exported identifier", name) + } + lower := strings.ToLower(name) + for _, bad := range forbiddenInIdent { + if strings.Contains(lower, bad) { + t.Errorf("%q contains forbidden word %q", name, bad) + } + } + doc := spec.Doc.Text() + if !strings.HasPrefix(doc, name) { + t.Errorf("doc for %q does not start with identifier", name) + } + lowerComment := strings.ToLower(strings.TrimPrefix(doc, name)) + for _, bad := range forbiddenInComment { + if strings.Contains(lowerComment, bad) { + t.Errorf("doc for %q contains forbidden word %q", name, bad) + } + } + }) + + if testing.Verbose() { + var totChars, totCount int + for chars, count := range nameHist { + totChars += chars * count + totCount += count + } + avg := float64(totChars) / float64(totCount) + fmt.Println() + fmt.Printf("%d error codes\n", totCount) + fmt.Printf("average length: %.2f chars\n", avg) + fmt.Printf("max length: %d (%s)\n", len(longestName), longestName) + } +} diff --git a/src/go/types/errors.go b/src/go/types/errors.go new file mode 100644 index 0000000..a219501 --- /dev/null +++ b/src/go/types/errors.go @@ -0,0 +1,227 @@ +// Copyright 2012 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. + +// This file implements various error reporters. + +package types + +import ( + "errors" + "fmt" + "go/ast" + "go/token" + "strconv" + "strings" +) + +func assert(p bool) { + if !p { + panic("assertion failed") + } +} + +func unreachable() { + panic("unreachable") +} + +func (check *Checker) qualifier(pkg *Package) string { + // Qualify the package unless it's the package being type-checked. + if pkg != check.pkg { + // If the same package name was used by multiple packages, display the full path. + if check.pkgCnt[pkg.name] > 1 { + return strconv.Quote(pkg.path) + } + return pkg.name + } + return "" +} + +func (check *Checker) sprintf(format string, args ...interface{}) string { + for i, arg := range args { + switch a := arg.(type) { + case nil: + arg = "" + case operand: + panic("internal error: should always pass *operand") + case *operand: + arg = operandString(a, check.qualifier) + case token.Pos: + arg = check.fset.Position(a).String() + case ast.Expr: + arg = ExprString(a) + case Object: + arg = ObjectString(a, check.qualifier) + case Type: + arg = TypeString(a, check.qualifier) + } + args[i] = arg + } + return fmt.Sprintf(format, args...) +} + +func (check *Checker) trace(pos token.Pos, format string, args ...interface{}) { + fmt.Printf("%s:\t%s%s\n", + check.fset.Position(pos), + strings.Repeat(". ", check.indent), + check.sprintf(format, args...), + ) +} + +// dump is only needed for debugging +func (check *Checker) dump(format string, args ...interface{}) { + fmt.Println(check.sprintf(format, args...)) +} + +func (check *Checker) err(err error) { + if err == nil { + return + } + var e Error + isInternal := errors.As(err, &e) + // Cheap trick: Don't report errors with messages containing + // "invalid operand" or "invalid type" as those tend to be + // follow-on errors which don't add useful information. Only + // exclude them if these strings are not at the beginning, + // and only if we have at least one error already reported. + isInvalidErr := isInternal && (strings.Index(e.Msg, "invalid operand") > 0 || strings.Index(e.Msg, "invalid type") > 0) + if check.firstErr != nil && isInvalidErr { + return + } + + if check.errpos != nil && isInternal { + // If we have an internal error and the errpos override is set, use it to + // augment our error positioning. + // TODO(rFindley) we may also want to augment the error message and refer + // to the position (pos) in the original expression. + span := spanOf(check.errpos) + e.Pos = span.pos + e.go116start = span.start + e.go116end = span.end + err = e + } + + if check.firstErr == nil { + check.firstErr = err + } + + if trace { + pos := e.Pos + msg := e.Msg + if !isInternal { + msg = err.Error() + pos = token.NoPos + } + check.trace(pos, "ERROR: %s", msg) + } + + f := check.conf.Error + if f == nil { + panic(bailout{}) // report only first error + } + f(err) +} + +func (check *Checker) newError(at positioner, code errorCode, soft bool, msg string) error { + span := spanOf(at) + return Error{ + Fset: check.fset, + Pos: span.pos, + Msg: msg, + Soft: soft, + go116code: code, + go116start: span.start, + go116end: span.end, + } +} + +// newErrorf creates a new Error, but does not handle it. +func (check *Checker) newErrorf(at positioner, code errorCode, soft bool, format string, args ...interface{}) error { + msg := check.sprintf(format, args...) + return check.newError(at, code, soft, msg) +} + +func (check *Checker) error(at positioner, code errorCode, msg string) { + check.err(check.newError(at, code, false, msg)) +} + +func (check *Checker) errorf(at positioner, code errorCode, format string, args ...interface{}) { + check.error(at, code, check.sprintf(format, args...)) +} + +func (check *Checker) softErrorf(at positioner, code errorCode, format string, args ...interface{}) { + check.err(check.newErrorf(at, code, true, format, args...)) +} + +func (check *Checker) invalidAST(at positioner, format string, args ...interface{}) { + check.errorf(at, 0, "invalid AST: "+format, args...) +} + +func (check *Checker) invalidArg(at positioner, code errorCode, format string, args ...interface{}) { + check.errorf(at, code, "invalid argument: "+format, args...) +} + +func (check *Checker) invalidOp(at positioner, code errorCode, format string, args ...interface{}) { + check.errorf(at, code, "invalid operation: "+format, args...) +} + +// The positioner interface is used to extract the position of type-checker +// errors. +type positioner interface { + Pos() token.Pos +} + +// posSpan holds a position range along with a highlighted position within that +// range. This is used for positioning errors, with pos by convention being the +// first position in the source where the error is known to exist, and start +// and end defining the full span of syntax being considered when the error was +// detected. Invariant: start <= pos < end || start == pos == end. +type posSpan struct { + start, pos, end token.Pos +} + +func (e posSpan) Pos() token.Pos { + return e.pos +} + +// inNode creates a posSpan for the given node. +// Invariant: node.Pos() <= pos < node.End() (node.End() is the position of the +// first byte after node within the source). +func inNode(node ast.Node, pos token.Pos) posSpan { + start, end := node.Pos(), node.End() + if debug { + assert(start <= pos && pos < end) + } + return posSpan{start, pos, end} +} + +// atPos wraps a token.Pos to implement the positioner interface. +type atPos token.Pos + +func (s atPos) Pos() token.Pos { + return token.Pos(s) +} + +// spanOf extracts an error span from the given positioner. By default this is +// the trivial span starting and ending at pos, but this span is expanded when +// the argument naturally corresponds to a span of source code. +func spanOf(at positioner) posSpan { + switch x := at.(type) { + case nil: + panic("internal error: nil") + case posSpan: + return x + case ast.Node: + pos := x.Pos() + return posSpan{pos, pos, x.End()} + case *operand: + if x.expr != nil { + pos := x.Pos() + return posSpan{pos, pos, x.expr.End()} + } + return posSpan{token.NoPos, token.NoPos, token.NoPos} + default: + pos := at.Pos() + return posSpan{pos, pos, pos} + } +} diff --git a/src/go/types/eval.go b/src/go/types/eval.go new file mode 100644 index 0000000..5125960 --- /dev/null +++ b/src/go/types/eval.go @@ -0,0 +1,99 @@ +// Copyright 2013 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 types + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" +) + +// Eval returns the type and, if constant, the value for the +// expression expr, evaluated at position pos of package pkg, +// which must have been derived from type-checking an AST with +// complete position information relative to the provided file +// set. +// +// The meaning of the parameters fset, pkg, and pos is the +// same as in CheckExpr. An error is returned if expr cannot +// be parsed successfully, or the resulting expr AST cannot be +// type-checked. +func Eval(fset *token.FileSet, pkg *Package, pos token.Pos, expr string) (_ TypeAndValue, err error) { + // parse expressions + node, err := parser.ParseExprFrom(fset, "eval", expr, 0) + if err != nil { + return TypeAndValue{}, err + } + + info := &Info{ + Types: make(map[ast.Expr]TypeAndValue), + } + err = CheckExpr(fset, pkg, pos, node, info) + return info.Types[node], err +} + +// CheckExpr type checks the expression expr as if it had appeared at +// position pos of package pkg. Type information about the expression +// is recorded in info. +// +// If pkg == nil, the Universe scope is used and the provided +// position pos is ignored. If pkg != nil, and pos is invalid, +// the package scope is used. Otherwise, pos must belong to the +// package. +// +// An error is returned if pos is not within the package or +// if the node cannot be type-checked. +// +// Note: Eval and CheckExpr should not be used instead of running Check +// to compute types and values, but in addition to Check, as these +// functions ignore the context in which an expression is used (e.g., an +// assignment). Thus, top-level untyped constants will return an +// untyped type rather then the respective context-specific type. +// +func CheckExpr(fset *token.FileSet, pkg *Package, pos token.Pos, expr ast.Expr, info *Info) (err error) { + // determine scope + var scope *Scope + if pkg == nil { + scope = Universe + pos = token.NoPos + } else if !pos.IsValid() { + scope = pkg.scope + } else { + // The package scope extent (position information) may be + // incorrect (files spread across a wide range of fset + // positions) - ignore it and just consider its children + // (file scopes). + for _, fscope := range pkg.scope.children { + if scope = fscope.Innermost(pos); scope != nil { + break + } + } + if scope == nil || debug { + s := scope + for s != nil && s != pkg.scope { + s = s.parent + } + // s == nil || s == pkg.scope + if s == nil { + return fmt.Errorf("no position %s found in package %s", fset.Position(pos), pkg.name) + } + } + } + + // initialize checker + check := NewChecker(nil, fset, pkg, info) + check.scope = scope + check.pos = pos + defer check.handleBailout(&err) + + // evaluate node + var x operand + check.rawExpr(&x, expr, nil) + check.processDelayed(0) // incl. all functions + check.recordUntyped() + + return nil +} diff --git a/src/go/types/eval_test.go b/src/go/types/eval_test.go new file mode 100644 index 0000000..d940bf0 --- /dev/null +++ b/src/go/types/eval_test.go @@ -0,0 +1,297 @@ +// Copyright 2013 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. + +// This file contains tests for Eval. + +package types_test + +import ( + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "internal/testenv" + "strings" + "testing" + + . "go/types" +) + +func testEval(t *testing.T, fset *token.FileSet, pkg *Package, pos token.Pos, expr string, typ Type, typStr, valStr string) { + gotTv, err := Eval(fset, pkg, pos, expr) + if err != nil { + t.Errorf("Eval(%q) failed: %s", expr, err) + return + } + if gotTv.Type == nil { + t.Errorf("Eval(%q) got nil type but no error", expr) + return + } + + // compare types + if typ != nil { + // we have a type, check identity + if !Identical(gotTv.Type, typ) { + t.Errorf("Eval(%q) got type %s, want %s", expr, gotTv.Type, typ) + return + } + } else { + // we have a string, compare type string + gotStr := gotTv.Type.String() + if gotStr != typStr { + t.Errorf("Eval(%q) got type %s, want %s", expr, gotStr, typStr) + return + } + } + + // compare values + gotStr := "" + if gotTv.Value != nil { + gotStr = gotTv.Value.ExactString() + } + if gotStr != valStr { + t.Errorf("Eval(%q) got value %s, want %s", expr, gotStr, valStr) + } +} + +func TestEvalBasic(t *testing.T) { + fset := token.NewFileSet() + for _, typ := range Typ[Bool : String+1] { + testEval(t, fset, nil, token.NoPos, typ.Name(), typ, "", "") + } +} + +func TestEvalComposite(t *testing.T) { + fset := token.NewFileSet() + for _, test := range independentTestTypes { + testEval(t, fset, nil, token.NoPos, test.src, nil, test.str, "") + } +} + +func TestEvalArith(t *testing.T) { + var tests = []string{ + `true`, + `false == false`, + `12345678 + 87654321 == 99999999`, + `10 * 20 == 200`, + `(1<<1000)*2 >> 100 == 2<<900`, + `"foo" + "bar" == "foobar"`, + `"abc" <= "bcd"`, + `len([10]struct{}{}) == 2*5`, + } + fset := token.NewFileSet() + for _, test := range tests { + testEval(t, fset, nil, token.NoPos, test, Typ[UntypedBool], "", "true") + } +} + +func TestEvalPos(t *testing.T) { + testenv.MustHaveGoBuild(t) + + // The contents of /*-style comments are of the form + // expr => value, type + // where value may be the empty string. + // Each expr is evaluated at the position of the comment + // and the result is compared with the expected value + // and type. + var sources = []string{ + ` + package p + import "fmt" + import m "math" + const c = 3.0 + type T []int + func f(a int, s string) float64 { + fmt.Println("calling f") + _ = m.Pi // use package math + const d int = c + 1 + var x int + x = a + len(s) + return float64(x) + /* true => true, untyped bool */ + /* fmt.Println => , func(a ...interface{}) (n int, err error) */ + /* c => 3, untyped float */ + /* T => , p.T */ + /* a => , int */ + /* s => , string */ + /* d => 4, int */ + /* x => , int */ + /* d/c => 1, int */ + /* c/2 => 3/2, untyped float */ + /* m.Pi < m.E => false, untyped bool */ + } + `, + ` + package p + /* c => 3, untyped float */ + type T1 /* T1 => , p.T1 */ struct {} + var v1 /* v1 => , int */ = 42 + func /* f1 => , func(v1 float64) */ f1(v1 float64) { + /* f1 => , func(v1 float64) */ + /* v1 => , float64 */ + var c /* c => 3, untyped float */ = "foo" /* c => , string */ + { + var c struct { + c /* c => , string */ int + } + /* c => , struct{c int} */ + _ = c + } + _ = func(a, b, c int) /* c => , string */ { + /* c => , int */ + } + _ = c + type FT /* FT => , p.FT */ interface{} + } + `, + ` + package p + /* T => , p.T */ + `, + ` + package p + import "io" + type R = io.Reader + func _() { + /* interface{R}.Read => , func(interface{io.Reader}, p []byte) (n int, err error) */ + _ = func() { + /* interface{io.Writer}.Write => , func(interface{io.Writer}, p []byte) (n int, err error) */ + type io interface {} // must not shadow io in line above + } + type R interface {} // must not shadow R in first line of this function body + } + `, + } + + fset := token.NewFileSet() + var files []*ast.File + for i, src := range sources { + file, err := parser.ParseFile(fset, "p", src, parser.ParseComments) + if err != nil { + t.Fatalf("could not parse file %d: %s", i, err) + } + files = append(files, file) + } + + conf := Config{Importer: importer.Default()} + pkg, err := conf.Check("p", fset, files, nil) + if err != nil { + t.Fatal(err) + } + + for _, file := range files { + for _, group := range file.Comments { + for _, comment := range group.List { + s := comment.Text + if len(s) >= 4 && s[:2] == "/*" && s[len(s)-2:] == "*/" { + str, typ := split(s[2:len(s)-2], ", ") + str, val := split(str, "=>") + testEval(t, fset, pkg, comment.Pos(), str, nil, typ, val) + } + } + } + } +} + +// split splits string s at the first occurrence of s. +func split(s, sep string) (string, string) { + i := strings.Index(s, sep) + return strings.TrimSpace(s[:i]), strings.TrimSpace(s[i+len(sep):]) +} + +func TestCheckExpr(t *testing.T) { + testenv.MustHaveGoBuild(t) + + // Each comment has the form /* expr => object */: + // expr is an identifier or selector expression that is passed + // to CheckExpr at the position of the comment, and object is + // the string form of the object it denotes. + const src = ` +package p + +import "fmt" + +const c = 3.0 +type T []int +type S struct{ X int } + +func f(a int, s string) S { + /* fmt.Println => func fmt.Println(a ...interface{}) (n int, err error) */ + /* fmt.Stringer.String => func (fmt.Stringer).String() string */ + fmt.Println("calling f") + + var fmt struct{ Println int } + /* fmt => var fmt struct{Println int} */ + /* fmt.Println => field Println int */ + /* f(1, "").X => field X int */ + fmt.Println = 1 + + /* append => builtin append */ + + /* new(S).X => field X int */ + + return S{} +}` + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "p", src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + + conf := Config{Importer: importer.Default()} + pkg, err := conf.Check("p", fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + + checkExpr := func(pos token.Pos, str string) (Object, error) { + expr, err := parser.ParseExprFrom(fset, "eval", str, 0) + if err != nil { + return nil, err + } + + info := &Info{ + Uses: make(map[*ast.Ident]Object), + Selections: make(map[*ast.SelectorExpr]*Selection), + } + if err := CheckExpr(fset, pkg, pos, expr, info); err != nil { + return nil, fmt.Errorf("CheckExpr(%q) failed: %s", str, err) + } + switch expr := expr.(type) { + case *ast.Ident: + if obj, ok := info.Uses[expr]; ok { + return obj, nil + } + case *ast.SelectorExpr: + if sel, ok := info.Selections[expr]; ok { + return sel.Obj(), nil + } + if obj, ok := info.Uses[expr.Sel]; ok { + return obj, nil // qualified identifier + } + } + return nil, fmt.Errorf("no object for %s", str) + } + + for _, group := range f.Comments { + for _, comment := range group.List { + s := comment.Text + if len(s) >= 4 && strings.HasPrefix(s, "/*") && strings.HasSuffix(s, "*/") { + pos := comment.Pos() + expr, wantObj := split(s[2:len(s)-2], "=>") + obj, err := checkExpr(pos, expr) + if err != nil { + t.Errorf("%s: %s", fset.Position(pos), err) + continue + } + if obj.String() != wantObj { + t.Errorf("%s: checkExpr(%s) = %s, want %v", + fset.Position(pos), expr, obj, wantObj) + } + } + } + } +} diff --git a/src/go/types/example_test.go b/src/go/types/example_test.go new file mode 100644 index 0000000..3747f3b --- /dev/null +++ b/src/go/types/example_test.go @@ -0,0 +1,334 @@ +// Copyright 2015 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. + +// Only run where builders (build.golang.org) have +// access to compiled packages for import. +// +// +build !arm,!arm64 + +package types_test + +// This file shows examples of basic usage of the go/types API. +// +// To locate a Go package, use (*go/build.Context).Import. +// To load, parse, and type-check a complete Go program +// from source, use golang.org/x/tools/go/loader. + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/importer" + "go/parser" + "go/token" + "go/types" + "log" + "regexp" + "sort" + "strings" +) + +// ExampleScope prints the tree of Scopes of a package created from a +// set of parsed files. +func ExampleScope() { + // Parse the source files for a package. + fset := token.NewFileSet() + var files []*ast.File + for _, file := range []struct{ name, input string }{ + {"main.go", ` +package main +import "fmt" +func main() { + freezing := FToC(-18) + fmt.Println(freezing, Boiling) } +`}, + {"celsius.go", ` +package main +import "fmt" +type Celsius float64 +func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } +func FToC(f float64) Celsius { return Celsius(f - 32 / 9 * 5) } +const Boiling Celsius = 100 +func Unused() { {}; {{ var x int; _ = x }} } // make sure empty block scopes get printed +`}, + } { + f, err := parser.ParseFile(fset, file.name, file.input, 0) + if err != nil { + log.Fatal(err) + } + files = append(files, f) + } + + // Type-check a package consisting of these files. + // Type information for the imported "fmt" package + // comes from $GOROOT/pkg/$GOOS_$GOOARCH/fmt.a. + conf := types.Config{Importer: importer.Default()} + pkg, err := conf.Check("temperature", fset, files, nil) + if err != nil { + log.Fatal(err) + } + + // Print the tree of scopes. + // For determinism, we redact addresses. + var buf bytes.Buffer + pkg.Scope().WriteTo(&buf, 0, true) + rx := regexp.MustCompile(` 0x[a-fA-F0-9]*`) + fmt.Println(rx.ReplaceAllString(buf.String(), "")) + + // Output: + // package "temperature" scope { + // . const temperature.Boiling temperature.Celsius + // . type temperature.Celsius float64 + // . func temperature.FToC(f float64) temperature.Celsius + // . func temperature.Unused() + // . func temperature.main() + // . main.go scope { + // . . package fmt + // . . function scope { + // . . . var freezing temperature.Celsius + // . . } + // . } + // . celsius.go scope { + // . . package fmt + // . . function scope { + // . . . var c temperature.Celsius + // . . } + // . . function scope { + // . . . var f float64 + // . . } + // . . function scope { + // . . . block scope { + // . . . } + // . . . block scope { + // . . . . block scope { + // . . . . . var x int + // . . . . } + // . . . } + // . . } + // . } + // } +} + +// ExampleMethodSet prints the method sets of various types. +func ExampleMethodSet() { + // Parse a single source file. + const input = ` +package temperature +import "fmt" +type Celsius float64 +func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } +func (c *Celsius) SetF(f float64) { *c = Celsius(f - 32 / 9 * 5) } + +type S struct { I; m int } +type I interface { m() byte } +` + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "celsius.go", input, 0) + if err != nil { + log.Fatal(err) + } + + // Type-check a package consisting of this file. + // Type information for the imported packages + // comes from $GOROOT/pkg/$GOOS_$GOOARCH/fmt.a. + conf := types.Config{Importer: importer.Default()} + pkg, err := conf.Check("temperature", fset, []*ast.File{f}, nil) + if err != nil { + log.Fatal(err) + } + + // Print the method sets of Celsius and *Celsius. + celsius := pkg.Scope().Lookup("Celsius").Type() + for _, t := range []types.Type{celsius, types.NewPointer(celsius)} { + fmt.Printf("Method set of %s:\n", t) + mset := types.NewMethodSet(t) + for i := 0; i < mset.Len(); i++ { + fmt.Println(mset.At(i)) + } + fmt.Println() + } + + // Print the method set of S. + styp := pkg.Scope().Lookup("S").Type() + fmt.Printf("Method set of %s:\n", styp) + fmt.Println(types.NewMethodSet(styp)) + + // Output: + // Method set of temperature.Celsius: + // method (temperature.Celsius) String() string + // + // Method set of *temperature.Celsius: + // method (*temperature.Celsius) SetF(f float64) + // method (*temperature.Celsius) String() string + // + // Method set of temperature.S: + // MethodSet {} +} + +// ExampleInfo prints various facts recorded by the type checker in a +// types.Info struct: definitions of and references to each named object, +// and the type, value, and mode of every expression in the package. +func ExampleInfo() { + // Parse a single source file. + const input = ` +package fib + +type S string + +var a, b, c = len(b), S(c), "hello" + +func fib(x int) int { + if x < 2 { + return x + } + return fib(x-1) - fib(x-2) +}` + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "fib.go", input, 0) + if err != nil { + log.Fatal(err) + } + + // Type-check the package. + // We create an empty map for each kind of input + // we're interested in, and Check populates them. + info := types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + } + var conf types.Config + pkg, err := conf.Check("fib", fset, []*ast.File{f}, &info) + if err != nil { + log.Fatal(err) + } + + // Print package-level variables in initialization order. + fmt.Printf("InitOrder: %v\n\n", info.InitOrder) + + // For each named object, print the line and + // column of its definition and each of its uses. + fmt.Println("Defs and Uses of each named object:") + usesByObj := make(map[types.Object][]string) + for id, obj := range info.Uses { + posn := fset.Position(id.Pos()) + lineCol := fmt.Sprintf("%d:%d", posn.Line, posn.Column) + usesByObj[obj] = append(usesByObj[obj], lineCol) + } + var items []string + for obj, uses := range usesByObj { + sort.Strings(uses) + item := fmt.Sprintf("%s:\n defined at %s\n used at %s", + types.ObjectString(obj, types.RelativeTo(pkg)), + fset.Position(obj.Pos()), + strings.Join(uses, ", ")) + items = append(items, item) + } + sort.Strings(items) // sort by line:col, in effect + fmt.Println(strings.Join(items, "\n")) + fmt.Println() + + fmt.Println("Types and Values of each expression:") + items = nil + for expr, tv := range info.Types { + var buf bytes.Buffer + posn := fset.Position(expr.Pos()) + tvstr := tv.Type.String() + if tv.Value != nil { + tvstr += " = " + tv.Value.String() + } + // line:col | expr | mode : type = value + fmt.Fprintf(&buf, "%2d:%2d | %-19s | %-7s : %s", + posn.Line, posn.Column, exprString(fset, expr), + mode(tv), tvstr) + items = append(items, buf.String()) + } + sort.Strings(items) + fmt.Println(strings.Join(items, "\n")) + + // Output: + // InitOrder: [c = "hello" b = S(c) a = len(b)] + // + // Defs and Uses of each named object: + // builtin len: + // defined at - + // used at 6:15 + // func fib(x int) int: + // defined at fib.go:8:6 + // used at 12:20, 12:9 + // type S string: + // defined at fib.go:4:6 + // used at 6:23 + // type int: + // defined at - + // used at 8:12, 8:17 + // type string: + // defined at - + // used at 4:8 + // var b S: + // defined at fib.go:6:8 + // used at 6:19 + // var c string: + // defined at fib.go:6:11 + // used at 6:25 + // var x int: + // defined at fib.go:8:10 + // used at 10:10, 12:13, 12:24, 9:5 + // + // Types and Values of each expression: + // 4: 8 | string | type : string + // 6:15 | len | builtin : func(string) int + // 6:15 | len(b) | value : int + // 6:19 | b | var : fib.S + // 6:23 | S | type : fib.S + // 6:23 | S(c) | value : fib.S + // 6:25 | c | var : string + // 6:29 | "hello" | value : string = "hello" + // 8:12 | int | type : int + // 8:17 | int | type : int + // 9: 5 | x | var : int + // 9: 5 | x < 2 | value : untyped bool + // 9: 9 | 2 | value : int = 2 + // 10:10 | x | var : int + // 12: 9 | fib | value : func(x int) int + // 12: 9 | fib(x - 1) | value : int + // 12: 9 | fib(x-1) - fib(x-2) | value : int + // 12:13 | x | var : int + // 12:13 | x - 1 | value : int + // 12:15 | 1 | value : int = 1 + // 12:20 | fib | value : func(x int) int + // 12:20 | fib(x - 2) | value : int + // 12:24 | x | var : int + // 12:24 | x - 2 | value : int + // 12:26 | 2 | value : int = 2 +} + +func mode(tv types.TypeAndValue) string { + switch { + case tv.IsVoid(): + return "void" + case tv.IsType(): + return "type" + case tv.IsBuiltin(): + return "builtin" + case tv.IsNil(): + return "nil" + case tv.Assignable(): + if tv.Addressable() { + return "var" + } + return "mapindex" + case tv.IsValue(): + return "value" + default: + return "unknown" + } +} + +func exprString(fset *token.FileSet, expr ast.Expr) string { + var buf bytes.Buffer + format.Node(&buf, fset, expr) + return buf.String() +} diff --git a/src/go/types/expr.go b/src/go/types/expr.go new file mode 100644 index 0000000..eb20561 --- /dev/null +++ b/src/go/types/expr.go @@ -0,0 +1,1709 @@ +// Copyright 2012 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. + +// This file implements typechecking of expressions. + +package types + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "math" +) + +/* +Basic algorithm: + +Expressions are checked recursively, top down. Expression checker functions +are generally of the form: + + func f(x *operand, e *ast.Expr, ...) + +where e is the expression to be checked, and x is the result of the check. +The check performed by f may fail in which case x.mode == invalid, and +related error messages will have been issued by f. + +If a hint argument is present, it is the composite literal element type +of an outer composite literal; it is used to type-check composite literal +elements that have no explicit type specification in the source +(e.g.: []T{{...}, {...}}, the hint is the type T in this case). + +All expressions are checked via rawExpr, which dispatches according +to expression kind. Upon returning, rawExpr is recording the types and +constant values for all expressions that have an untyped type (those types +may change on the way up in the expression tree). Usually these are constants, +but the results of comparisons or non-constant shifts of untyped constants +may also be untyped, but not constant. + +Untyped expressions may eventually become fully typed (i.e., not untyped), +typically when the value is assigned to a variable, or is used otherwise. +The updateExprType method is used to record this final type and update +the recorded types: the type-checked expression tree is again traversed down, +and the new type is propagated as needed. Untyped constant expression values +that become fully typed must now be representable by the full type (constant +sub-expression trees are left alone except for their roots). This mechanism +ensures that a client sees the actual (run-time) type an untyped value would +have. It also permits type-checking of lhs shift operands "as if the shift +were not present": when updateExprType visits an untyped lhs shift operand +and assigns it it's final type, that type must be an integer type, and a +constant lhs must be representable as an integer. + +When an expression gets its final type, either on the way out from rawExpr, +on the way down in updateExprType, or at the end of the type checker run, +the type (and constant value, if any) is recorded via Info.Types, if present. +*/ + +type opPredicates map[token.Token]func(Type) bool + +var unaryOpPredicates = opPredicates{ + token.ADD: isNumeric, + token.SUB: isNumeric, + token.XOR: isInteger, + token.NOT: isBoolean, +} + +func (check *Checker) op(m opPredicates, x *operand, op token.Token) bool { + if pred := m[op]; pred != nil { + if !pred(x.typ) { + check.invalidOp(x, _UndefinedOp, "operator %s not defined for %s", op, x) + return false + } + } else { + check.invalidAST(x, "unknown operator %s", op) + return false + } + return true +} + +// The unary expression e may be nil. It's passed in for better error messages only. +func (check *Checker) unary(x *operand, e *ast.UnaryExpr, op token.Token) { + switch op { + case token.AND: + // spec: "As an exception to the addressability + // requirement x may also be a composite literal." + if _, ok := unparen(x.expr).(*ast.CompositeLit); !ok && x.mode != variable { + check.invalidOp(x, _UnaddressableOperand, "cannot take address of %s", x) + x.mode = invalid + return + } + x.mode = value + x.typ = &Pointer{base: x.typ} + return + + case token.ARROW: + typ, ok := x.typ.Underlying().(*Chan) + if !ok { + check.invalidOp(x, _InvalidReceive, "cannot receive from non-channel %s", x) + x.mode = invalid + return + } + if typ.dir == SendOnly { + check.invalidOp(x, _InvalidReceive, "cannot receive from send-only channel %s", x) + x.mode = invalid + return + } + x.mode = commaok + x.typ = typ.elem + check.hasCallOrRecv = true + return + } + + if !check.op(unaryOpPredicates, x, op) { + x.mode = invalid + return + } + + if x.mode == constant_ { + typ := x.typ.Underlying().(*Basic) + var prec uint + if isUnsigned(typ) { + prec = uint(check.conf.sizeof(typ) * 8) + } + x.val = constant.UnaryOp(op, x.val, prec) + // Typed constants must be representable in + // their type after each constant operation. + if isTyped(typ) { + if e != nil { + x.expr = e // for better error message + } + check.representable(x, typ) + } + return + } + + x.mode = value + // x.typ remains unchanged +} + +func isShift(op token.Token) bool { + return op == token.SHL || op == token.SHR +} + +func isComparison(op token.Token) bool { + // Note: tokens are not ordered well to make this much easier + switch op { + case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ: + return true + } + return false +} + +func fitsFloat32(x constant.Value) bool { + f32, _ := constant.Float32Val(x) + f := float64(f32) + return !math.IsInf(f, 0) +} + +func roundFloat32(x constant.Value) constant.Value { + f32, _ := constant.Float32Val(x) + f := float64(f32) + if !math.IsInf(f, 0) { + return constant.MakeFloat64(f) + } + return nil +} + +func fitsFloat64(x constant.Value) bool { + f, _ := constant.Float64Val(x) + return !math.IsInf(f, 0) +} + +func roundFloat64(x constant.Value) constant.Value { + f, _ := constant.Float64Val(x) + if !math.IsInf(f, 0) { + return constant.MakeFloat64(f) + } + return nil +} + +// representableConst reports whether x can be represented as +// value of the given basic type and for the configuration +// provided (only needed for int/uint sizes). +// +// If rounded != nil, *rounded is set to the rounded value of x for +// representable floating-point and complex values, and to an Int +// value for integer values; it is left alone otherwise. +// It is ok to provide the addressof the first argument for rounded. +// +// The check parameter may be nil if representableConst is invoked +// (indirectly) through an exported API call (AssignableTo, ConvertibleTo) +// because we don't need the Checker's config for those calls. +func representableConst(x constant.Value, check *Checker, typ *Basic, rounded *constant.Value) bool { + if x.Kind() == constant.Unknown { + return true // avoid follow-up errors + } + + var conf *Config + if check != nil { + conf = check.conf + } + + switch { + case isInteger(typ): + x := constant.ToInt(x) + if x.Kind() != constant.Int { + return false + } + if rounded != nil { + *rounded = x + } + if x, ok := constant.Int64Val(x); ok { + switch typ.kind { + case Int: + var s = uint(conf.sizeof(typ)) * 8 + return int64(-1)<<(s-1) <= x && x <= int64(1)<<(s-1)-1 + case Int8: + const s = 8 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 + case Int16: + const s = 16 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 + case Int32: + const s = 32 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 + case Int64, UntypedInt: + return true + case Uint, Uintptr: + if s := uint(conf.sizeof(typ)) * 8; s < 64 { + return 0 <= x && x <= int64(1)<= 0 && n <= int(s) + case Uint64: + return constant.Sign(x) >= 0 && n <= 64 + case UntypedInt: + return true + } + + case isFloat(typ): + x := constant.ToFloat(x) + if x.Kind() != constant.Float { + return false + } + switch typ.kind { + case Float32: + if rounded == nil { + return fitsFloat32(x) + } + r := roundFloat32(x) + if r != nil { + *rounded = r + return true + } + case Float64: + if rounded == nil { + return fitsFloat64(x) + } + r := roundFloat64(x) + if r != nil { + *rounded = r + return true + } + case UntypedFloat: + return true + default: + unreachable() + } + + case isComplex(typ): + x := constant.ToComplex(x) + if x.Kind() != constant.Complex { + return false + } + switch typ.kind { + case Complex64: + if rounded == nil { + return fitsFloat32(constant.Real(x)) && fitsFloat32(constant.Imag(x)) + } + re := roundFloat32(constant.Real(x)) + im := roundFloat32(constant.Imag(x)) + if re != nil && im != nil { + *rounded = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + return true + } + case Complex128: + if rounded == nil { + return fitsFloat64(constant.Real(x)) && fitsFloat64(constant.Imag(x)) + } + re := roundFloat64(constant.Real(x)) + im := roundFloat64(constant.Imag(x)) + if re != nil && im != nil { + *rounded = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + return true + } + case UntypedComplex: + return true + default: + unreachable() + } + + case isString(typ): + return x.Kind() == constant.String + + case isBoolean(typ): + return x.Kind() == constant.Bool + } + + return false +} + +// representable checks that a constant operand is representable in the given +// basic type. +func (check *Checker) representable(x *operand, typ *Basic) { + if err := check.isRepresentable(x, typ); err != nil { + x.mode = invalid + check.err(err) + } +} + +func (check *Checker) isRepresentable(x *operand, typ *Basic) error { + assert(x.mode == constant_) + if !representableConst(x.val, check, typ, &x.val) { + var msg string + var code errorCode + if isNumeric(x.typ) && isNumeric(typ) { + // numeric conversion : error msg + // + // integer -> integer : overflows + // integer -> float : overflows (actually not possible) + // float -> integer : truncated + // float -> float : overflows + // + if !isInteger(x.typ) && isInteger(typ) { + msg = "%s truncated to %s" + code = _TruncatedFloat + } else { + msg = "%s overflows %s" + code = _NumericOverflow + } + } else { + msg = "cannot convert %s to %s" + code = _InvalidConstVal + } + return check.newErrorf(x, code, false, msg, x, typ) + } + return nil +} + +// updateExprType updates the type of x to typ and invokes itself +// recursively for the operands of x, depending on expression kind. +// If typ is still an untyped and not the final type, updateExprType +// only updates the recorded untyped type for x and possibly its +// operands. Otherwise (i.e., typ is not an untyped type anymore, +// or it is the final type for x), the type and value are recorded. +// Also, if x is a constant, it must be representable as a value of typ, +// and if x is the (formerly untyped) lhs operand of a non-constant +// shift, it must be an integer value. +// +func (check *Checker) updateExprType(x ast.Expr, typ Type, final bool) { + old, found := check.untyped[x] + if !found { + return // nothing to do + } + + // update operands of x if necessary + switch x := x.(type) { + case *ast.BadExpr, + *ast.FuncLit, + *ast.CompositeLit, + *ast.IndexExpr, + *ast.SliceExpr, + *ast.TypeAssertExpr, + *ast.StarExpr, + *ast.KeyValueExpr, + *ast.ArrayType, + *ast.StructType, + *ast.FuncType, + *ast.InterfaceType, + *ast.MapType, + *ast.ChanType: + // These expression are never untyped - nothing to do. + // The respective sub-expressions got their final types + // upon assignment or use. + if debug { + check.dump("%v: found old type(%s): %s (new: %s)", x.Pos(), x, old.typ, typ) + unreachable() + } + return + + case *ast.CallExpr: + // Resulting in an untyped constant (e.g., built-in complex). + // The respective calls take care of calling updateExprType + // for the arguments if necessary. + + case *ast.Ident, *ast.BasicLit, *ast.SelectorExpr: + // An identifier denoting a constant, a constant literal, + // or a qualified identifier (imported untyped constant). + // No operands to take care of. + + case *ast.ParenExpr: + check.updateExprType(x.X, typ, final) + + case *ast.UnaryExpr: + // If x is a constant, the operands were constants. + // The operands don't need to be updated since they + // never get "materialized" into a typed value. If + // left in the untyped map, they will be processed + // at the end of the type check. + if old.val != nil { + break + } + check.updateExprType(x.X, typ, final) + + case *ast.BinaryExpr: + if old.val != nil { + break // see comment for unary expressions + } + if isComparison(x.Op) { + // The result type is independent of operand types + // and the operand types must have final types. + } else if isShift(x.Op) { + // The result type depends only on lhs operand. + // The rhs type was updated when checking the shift. + check.updateExprType(x.X, typ, final) + } else { + // The operand types match the result type. + check.updateExprType(x.X, typ, final) + check.updateExprType(x.Y, typ, final) + } + + default: + unreachable() + } + + // If the new type is not final and still untyped, just + // update the recorded type. + if !final && isUntyped(typ) { + old.typ = typ.Underlying().(*Basic) + check.untyped[x] = old + return + } + + // Otherwise we have the final (typed or untyped type). + // Remove it from the map of yet untyped expressions. + delete(check.untyped, x) + + if old.isLhs { + // If x is the lhs of a shift, its final type must be integer. + // We already know from the shift check that it is representable + // as an integer if it is a constant. + if !isInteger(typ) { + check.invalidOp(x, _InvalidShiftOperand, "shifted operand %s (type %s) must be integer", x, typ) + return + } + // Even if we have an integer, if the value is a constant we + // still must check that it is representable as the specific + // int type requested (was issue #22969). Fall through here. + } + if old.val != nil { + // If x is a constant, it must be representable as a value of typ. + c := operand{old.mode, x, old.typ, old.val, 0} + check.convertUntyped(&c, typ) + if c.mode == invalid { + return + } + } + + // Everything's fine, record final type and value for x. + check.recordTypeAndValue(x, old.mode, typ, old.val) +} + +// updateExprVal updates the value of x to val. +func (check *Checker) updateExprVal(x ast.Expr, val constant.Value) { + if info, ok := check.untyped[x]; ok { + info.val = val + check.untyped[x] = info + } +} + +// convertUntyped attempts to set the type of an untyped value to the target type. +func (check *Checker) convertUntyped(x *operand, target Type) { + if err := check.canConvertUntyped(x, target); err != nil { + x.mode = invalid + check.err(err) + } +} + +func (check *Checker) canConvertUntyped(x *operand, target Type) error { + if x.mode == invalid || isTyped(x.typ) || target == Typ[Invalid] { + return nil + } + + if isUntyped(target) { + // both x and target are untyped + xkind := x.typ.(*Basic).kind + tkind := target.(*Basic).kind + if isNumeric(x.typ) && isNumeric(target) { + if xkind < tkind { + x.typ = target + check.updateExprType(x.expr, target, false) + } + } else if xkind != tkind { + return check.newErrorf(x, _InvalidUntypedConversion, false, "cannot convert %s to %s", x, target) + } + return nil + } + + if t, ok := target.Underlying().(*Basic); ok && x.mode == constant_ { + if err := check.isRepresentable(x, t); err != nil { + return err + } + // Expression value may have been rounded - update if needed. + check.updateExprVal(x.expr, x.val) + } else { + newTarget := check.implicitType(x, target) + if newTarget == nil { + return check.newErrorf(x, _InvalidUntypedConversion, false, "cannot convert %s to %s", x, target) + } + target = newTarget + } + x.typ = target + // Even though implicitType can return UntypedNil, this value is final: the + // predeclared identifier nil has no type. + check.updateExprType(x.expr, target, true) + return nil +} + +// implicitType returns the implicit type of x when used in a context where the +// target type is expected. If no such implicit conversion is possible, it +// returns nil. +func (check *Checker) implicitType(x *operand, target Type) Type { + assert(isUntyped(x.typ)) + switch t := target.Underlying().(type) { + case *Basic: + assert(x.mode != constant_) + // Non-constant untyped values may appear as the + // result of comparisons (untyped bool), intermediate + // (delayed-checked) rhs operands of shifts, and as + // the value nil. + switch x.typ.(*Basic).kind { + case UntypedBool: + if !isBoolean(target) { + return nil + } + case UntypedInt, UntypedRune, UntypedFloat, UntypedComplex: + if !isNumeric(target) { + return nil + } + case UntypedString: + // Non-constant untyped string values are not permitted by the spec and + // should not occur during normal typechecking passes, but this path is + // reachable via the AssignableTo API. + if !isString(target) { + return nil + } + case UntypedNil: + // Unsafe.Pointer is a basic type that includes nil. + if !hasNil(target) { + return nil + } + default: + return nil + } + case *Interface: + // Values must have concrete dynamic types. If the value is nil, + // keep it untyped (this is important for tools such as go vet which + // need the dynamic type for argument checking of say, print + // functions) + if x.isNil() { + return Typ[UntypedNil] + } + // cannot assign untyped values to non-empty interfaces + check.completeInterface(t) + if !t.Empty() { + return nil + } + return Default(x.typ) + case *Pointer, *Signature, *Slice, *Map, *Chan: + if !x.isNil() { + return nil + } + // Keep nil untyped - see comment for interfaces, above. + return Typ[UntypedNil] + default: + return nil + } + return target +} + +func (check *Checker) comparison(x, y *operand, op token.Token) { + // spec: "In any comparison, the first operand must be assignable + // to the type of the second operand, or vice versa." + err := "" + var code errorCode + xok, _ := x.assignableTo(check, y.typ, nil) + yok, _ := y.assignableTo(check, x.typ, nil) + if xok || yok { + defined := false + switch op { + case token.EQL, token.NEQ: + // spec: "The equality operators == and != apply to operands that are comparable." + defined = Comparable(x.typ) && Comparable(y.typ) || x.isNil() && hasNil(y.typ) || y.isNil() && hasNil(x.typ) + case token.LSS, token.LEQ, token.GTR, token.GEQ: + // spec: The ordering operators <, <=, >, and >= apply to operands that are ordered." + defined = isOrdered(x.typ) && isOrdered(y.typ) + default: + unreachable() + } + if !defined { + typ := x.typ + if x.isNil() { + typ = y.typ + } + err = check.sprintf("operator %s not defined for %s", op, typ) + code = _UndefinedOp + } + } else { + err = check.sprintf("mismatched types %s and %s", x.typ, y.typ) + code = _MismatchedTypes + } + + if err != "" { + check.errorf(x, code, "cannot compare %s %s %s (%s)", x.expr, op, y.expr, err) + x.mode = invalid + return + } + + if x.mode == constant_ && y.mode == constant_ { + x.val = constant.MakeBool(constant.Compare(x.val, op, y.val)) + // The operands are never materialized; no need to update + // their types. + } else { + x.mode = value + // The operands have now their final types, which at run- + // time will be materialized. Update the expression trees. + // If the current types are untyped, the materialized type + // is the respective default type. + check.updateExprType(x.expr, Default(x.typ), true) + check.updateExprType(y.expr, Default(y.typ), true) + } + + // spec: "Comparison operators compare two operands and yield + // an untyped boolean value." + x.typ = Typ[UntypedBool] +} + +func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) { + untypedx := isUntyped(x.typ) + + var xval constant.Value + if x.mode == constant_ { + xval = constant.ToInt(x.val) + } + + if isInteger(x.typ) || untypedx && xval != nil && xval.Kind() == constant.Int { + // The lhs is of integer type or an untyped constant representable + // as an integer. Nothing to do. + } else { + // shift has no chance + check.invalidOp(x, _InvalidShiftOperand, "shifted operand %s must be integer", x) + x.mode = invalid + return + } + + // spec: "The right operand in a shift expression must have integer type + // or be an untyped constant representable by a value of type uint." + switch { + case isInteger(y.typ): + // nothing to do + case isUntyped(y.typ): + check.convertUntyped(y, Typ[Uint]) + if y.mode == invalid { + x.mode = invalid + return + } + default: + check.invalidOp(y, _InvalidShiftCount, "shift count %s must be integer", y) + x.mode = invalid + return + } + + var yval constant.Value + if y.mode == constant_ { + // rhs must be an integer value + // (Either it was of an integer type already, or it was + // untyped and successfully converted to a uint above.) + yval = constant.ToInt(y.val) + assert(yval.Kind() == constant.Int) + if constant.Sign(yval) < 0 { + check.invalidOp(y, _InvalidShiftCount, "negative shift count %s", y) + x.mode = invalid + return + } + } + + if x.mode == constant_ { + if y.mode == constant_ { + // rhs must be within reasonable bounds in constant shifts + const shiftBound = 1023 - 1 + 52 // so we can express smallestFloat64 + s, ok := constant.Uint64Val(yval) + if !ok || s > shiftBound { + check.invalidOp(y, _InvalidShiftCount, "invalid shift count %s", y) + x.mode = invalid + return + } + // The lhs is representable as an integer but may not be an integer + // (e.g., 2.0, an untyped float) - this can only happen for untyped + // non-integer numeric constants. Correct the type so that the shift + // result is of integer type. + if !isInteger(x.typ) { + x.typ = Typ[UntypedInt] + } + // x is a constant so xval != nil and it must be of Int kind. + x.val = constant.Shift(xval, op, uint(s)) + // Typed constants must be representable in + // their type after each constant operation. + if isTyped(x.typ) { + if e != nil { + x.expr = e // for better error message + } + check.representable(x, x.typ.Underlying().(*Basic)) + } + return + } + + // non-constant shift with constant lhs + if untypedx { + // spec: "If the left operand of a non-constant shift + // expression is an untyped constant, the type of the + // constant is what it would be if the shift expression + // were replaced by its left operand alone.". + // + // Delay operand checking until we know the final type + // by marking the lhs expression as lhs shift operand. + // + // Usually (in correct programs), the lhs expression + // is in the untyped map. However, it is possible to + // create incorrect programs where the same expression + // is evaluated twice (via a declaration cycle) such + // that the lhs expression type is determined in the + // first round and thus deleted from the map, and then + // not found in the second round (double insertion of + // the same expr node still just leads to one entry for + // that node, and it can only be deleted once). + // Be cautious and check for presence of entry. + // Example: var e, f = int(1<<""[f]) // issue 11347 + if info, found := check.untyped[x.expr]; found { + info.isLhs = true + check.untyped[x.expr] = info + } + // keep x's type + x.mode = value + return + } + } + + // non-constant shift - lhs must be an integer + if !isInteger(x.typ) { + check.invalidOp(x, _InvalidShiftOperand, "shifted operand %s must be integer", x) + x.mode = invalid + return + } + + x.mode = value +} + +var binaryOpPredicates = opPredicates{ + token.ADD: func(typ Type) bool { return isNumeric(typ) || isString(typ) }, + token.SUB: isNumeric, + token.MUL: isNumeric, + token.QUO: isNumeric, + token.REM: isInteger, + + token.AND: isInteger, + token.OR: isInteger, + token.XOR: isInteger, + token.AND_NOT: isInteger, + + token.LAND: isBoolean, + token.LOR: isBoolean, +} + +// The binary expression e may be nil. It's passed in for better error messages only. +func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, op token.Token, opPos token.Pos) { + var y operand + + check.expr(x, lhs) + check.expr(&y, rhs) + + if x.mode == invalid { + return + } + if y.mode == invalid { + x.mode = invalid + x.expr = y.expr + return + } + + if isShift(op) { + check.shift(x, &y, e, op) + return + } + + check.convertUntyped(x, y.typ) + if x.mode == invalid { + return + } + check.convertUntyped(&y, x.typ) + if y.mode == invalid { + x.mode = invalid + return + } + + if isComparison(op) { + check.comparison(x, &y, op) + return + } + + if !check.identical(x.typ, y.typ) { + // only report an error if we have valid types + // (otherwise we had an error reported elsewhere already) + if x.typ != Typ[Invalid] && y.typ != Typ[Invalid] { + var posn positioner = x + if e != nil { + posn = e + } + check.invalidOp(posn, _MismatchedTypes, "mismatched types %s and %s", x.typ, y.typ) + } + x.mode = invalid + return + } + + if !check.op(binaryOpPredicates, x, op) { + x.mode = invalid + return + } + + if op == token.QUO || op == token.REM { + // check for zero divisor + if (x.mode == constant_ || isInteger(x.typ)) && y.mode == constant_ && constant.Sign(y.val) == 0 { + check.invalidOp(&y, _DivByZero, "division by zero") + x.mode = invalid + return + } + + // check for divisor underflow in complex division (see issue 20227) + if x.mode == constant_ && y.mode == constant_ && isComplex(x.typ) { + re, im := constant.Real(y.val), constant.Imag(y.val) + re2, im2 := constant.BinaryOp(re, token.MUL, re), constant.BinaryOp(im, token.MUL, im) + if constant.Sign(re2) == 0 && constant.Sign(im2) == 0 { + check.invalidOp(&y, _DivByZero, "division by zero") + x.mode = invalid + return + } + } + } + + if x.mode == constant_ && y.mode == constant_ { + xval := x.val + yval := y.val + typ := x.typ.Underlying().(*Basic) + // force integer division of integer operands + if op == token.QUO && isInteger(typ) { + op = token.QUO_ASSIGN + } + x.val = constant.BinaryOp(xval, op, yval) + // report error if valid operands lead to an invalid result + if xval.Kind() != constant.Unknown && yval.Kind() != constant.Unknown && x.val.Kind() == constant.Unknown { + // TODO(gri) We should report exactly what went wrong. At the + // moment we don't have the (go/constant) API for that. + // See also TODO in go/constant/value.go. + check.errorf(atPos(opPos), _InvalidConstVal, "constant result is not representable") + // TODO(gri) Should we mark operands with unknown values as invalid? + } + // Typed constants must be representable in + // their type after each constant operation. + if isTyped(typ) { + if e != nil { + x.expr = e // for better error message + } + check.representable(x, typ) + } + return + } + + x.mode = value + // x.typ is unchanged +} + +// index checks an index expression for validity. +// If max >= 0, it is the upper bound for index. +// If the result typ is != Typ[Invalid], index is valid and typ is its (possibly named) integer type. +// If the result val >= 0, index is valid and val is its constant int value. +func (check *Checker) index(index ast.Expr, max int64) (typ Type, val int64) { + typ = Typ[Invalid] + val = -1 + + var x operand + check.expr(&x, index) + if x.mode == invalid { + return + } + + // an untyped constant must be representable as Int + check.convertUntyped(&x, Typ[Int]) + if x.mode == invalid { + return + } + + // the index must be of integer type + if !isInteger(x.typ) { + check.invalidArg(&x, _InvalidIndex, "index %s must be integer", &x) + return + } + + if x.mode != constant_ { + return x.typ, -1 + } + + // a constant index i must be in bounds + if constant.Sign(x.val) < 0 { + check.invalidArg(&x, _InvalidIndex, "index %s must not be negative", &x) + return + } + + v, valid := constant.Int64Val(constant.ToInt(x.val)) + if !valid || max >= 0 && v >= max { + check.errorf(&x, _InvalidIndex, "index %s is out of bounds", &x) + return + } + + // 0 <= v [ && v < max ] + return Typ[Int], v +} + +// indexElts checks the elements (elts) of an array or slice composite literal +// against the literal's element type (typ), and the element indices against +// the literal length if known (length >= 0). It returns the length of the +// literal (maximum index value + 1). +// +func (check *Checker) indexedElts(elts []ast.Expr, typ Type, length int64) int64 { + visited := make(map[int64]bool, len(elts)) + var index, max int64 + for _, e := range elts { + // determine and check index + validIndex := false + eval := e + if kv, _ := e.(*ast.KeyValueExpr); kv != nil { + if typ, i := check.index(kv.Key, length); typ != Typ[Invalid] { + if i >= 0 { + index = i + validIndex = true + } else { + check.errorf(e, _InvalidLitIndex, "index %s must be integer constant", kv.Key) + } + } + eval = kv.Value + } else if length >= 0 && index >= length { + check.errorf(e, _OversizeArrayLit, "index %d is out of bounds (>= %d)", index, length) + } else { + validIndex = true + } + + // if we have a valid index, check for duplicate entries + if validIndex { + if visited[index] { + check.errorf(e, _DuplicateLitKey, "duplicate index %d in array or slice literal", index) + } + visited[index] = true + } + index++ + if index > max { + max = index + } + + // check element against composite literal element type + var x operand + check.exprWithHint(&x, eval, typ) + check.assignment(&x, typ, "array or slice literal") + } + return max +} + +// exprKind describes the kind of an expression; the kind +// determines if an expression is valid in 'statement context'. +type exprKind int + +const ( + conversion exprKind = iota + expression + statement +) + +// rawExpr typechecks expression e and initializes x with the expression +// value or type. If an error occurred, x.mode is set to invalid. +// If hint != nil, it is the type of a composite literal element. +// +func (check *Checker) rawExpr(x *operand, e ast.Expr, hint Type) exprKind { + if trace { + check.trace(e.Pos(), "%s", e) + check.indent++ + defer func() { + check.indent-- + check.trace(e.Pos(), "=> %s", x) + }() + } + + kind := check.exprInternal(x, e, hint) + + // convert x into a user-friendly set of values + // TODO(gri) this code can be simplified + var typ Type + var val constant.Value + switch x.mode { + case invalid: + typ = Typ[Invalid] + case novalue: + typ = (*Tuple)(nil) + case constant_: + typ = x.typ + val = x.val + default: + typ = x.typ + } + assert(x.expr != nil && typ != nil) + + if isUntyped(typ) { + // delay type and value recording until we know the type + // or until the end of type checking + check.rememberUntyped(x.expr, false, x.mode, typ.(*Basic), val) + } else { + check.recordTypeAndValue(e, x.mode, typ, val) + } + + return kind +} + +// exprInternal contains the core of type checking of expressions. +// Must only be called by rawExpr. +// +func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { + // make sure x has a valid state in case of bailout + // (was issue 5770) + x.mode = invalid + x.typ = Typ[Invalid] + + switch e := e.(type) { + case *ast.BadExpr: + goto Error // error was reported before + + case *ast.Ident: + check.ident(x, e, nil, false) + + case *ast.Ellipsis: + // ellipses are handled explicitly where they are legal + // (array composite literals and parameter lists) + check.error(e, _BadDotDotDotSyntax, "invalid use of '...'") + goto Error + + case *ast.BasicLit: + x.setConst(e.Kind, e.Value) + if x.mode == invalid { + // The parser already establishes syntactic correctness. + // If we reach here it's because of number under-/overflow. + // TODO(gri) setConst (and in turn the go/constant package) + // should return an error describing the issue. + check.errorf(e, _InvalidConstVal, "malformed constant: %s", e.Value) + goto Error + } + + case *ast.FuncLit: + if sig, ok := check.typ(e.Type).(*Signature); ok { + // Anonymous functions are considered part of the + // init expression/func declaration which contains + // them: use existing package-level declaration info. + decl := check.decl // capture for use in closure below + iota := check.iota // capture for use in closure below (#22345) + // Don't type-check right away because the function may + // be part of a type definition to which the function + // body refers. Instead, type-check as soon as possible, + // but before the enclosing scope contents changes (#22992). + check.later(func() { + check.funcBody(decl, "", sig, e.Body, iota) + }) + x.mode = value + x.typ = sig + } else { + check.invalidAST(e, "invalid function literal %s", e) + goto Error + } + + case *ast.CompositeLit: + var typ, base Type + + switch { + case e.Type != nil: + // composite literal type present - use it + // [...]T array types may only appear with composite literals. + // Check for them here so we don't have to handle ... in general. + if atyp, _ := e.Type.(*ast.ArrayType); atyp != nil && atyp.Len != nil { + if ellip, _ := atyp.Len.(*ast.Ellipsis); ellip != nil && ellip.Elt == nil { + // We have an "open" [...]T array type. + // Create a new ArrayType with unknown length (-1) + // and finish setting it up after analyzing the literal. + typ = &Array{len: -1, elem: check.typ(atyp.Elt)} + base = typ + break + } + } + typ = check.typ(e.Type) + base = typ + + case hint != nil: + // no composite literal type present - use hint (element type of enclosing type) + typ = hint + base, _ = deref(typ.Underlying()) // *T implies &T{} + + default: + // TODO(gri) provide better error messages depending on context + check.error(e, _UntypedLit, "missing type in composite literal") + goto Error + } + + switch utyp := base.Underlying().(type) { + case *Struct: + if len(e.Elts) == 0 { + break + } + fields := utyp.fields + if _, ok := e.Elts[0].(*ast.KeyValueExpr); ok { + // all elements must have keys + visited := make([]bool, len(fields)) + for _, e := range e.Elts { + kv, _ := e.(*ast.KeyValueExpr) + if kv == nil { + check.error(e, _MixedStructLit, "mixture of field:value and value elements in struct literal") + continue + } + key, _ := kv.Key.(*ast.Ident) + // do all possible checks early (before exiting due to errors) + // so we don't drop information on the floor + check.expr(x, kv.Value) + if key == nil { + check.errorf(kv, _InvalidLitField, "invalid field name %s in struct literal", kv.Key) + continue + } + i := fieldIndex(utyp.fields, check.pkg, key.Name) + if i < 0 { + check.errorf(kv, _MissingLitField, "unknown field %s in struct literal", key.Name) + continue + } + fld := fields[i] + check.recordUse(key, fld) + etyp := fld.typ + check.assignment(x, etyp, "struct literal") + // 0 <= i < len(fields) + if visited[i] { + check.errorf(kv, _DuplicateLitField, "duplicate field name %s in struct literal", key.Name) + continue + } + visited[i] = true + } + } else { + // no element must have a key + for i, e := range e.Elts { + if kv, _ := e.(*ast.KeyValueExpr); kv != nil { + check.error(kv, _MixedStructLit, "mixture of field:value and value elements in struct literal") + continue + } + check.expr(x, e) + if i >= len(fields) { + check.error(x, _InvalidStructLit, "too many values in struct literal") + break // cannot continue + } + // i < len(fields) + fld := fields[i] + if !fld.Exported() && fld.pkg != check.pkg { + check.errorf(x, + _UnexportedLitField, + "implicit assignment to unexported field %s in %s literal", fld.name, typ) + continue + } + etyp := fld.typ + check.assignment(x, etyp, "struct literal") + } + if len(e.Elts) < len(fields) { + check.error(inNode(e, e.Rbrace), _InvalidStructLit, "too few values in struct literal") + // ok to continue + } + } + + case *Array: + // Prevent crash if the array referred to is not yet set up. Was issue #18643. + // This is a stop-gap solution. Should use Checker.objPath to report entire + // path starting with earliest declaration in the source. TODO(gri) fix this. + if utyp.elem == nil { + check.error(e, _InvalidTypeCycle, "illegal cycle in type declaration") + goto Error + } + n := check.indexedElts(e.Elts, utyp.elem, utyp.len) + // If we have an array of unknown length (usually [...]T arrays, but also + // arrays [n]T where n is invalid) set the length now that we know it and + // record the type for the array (usually done by check.typ which is not + // called for [...]T). We handle [...]T arrays and arrays with invalid + // length the same here because it makes sense to "guess" the length for + // the latter if we have a composite literal; e.g. for [n]int{1, 2, 3} + // where n is invalid for some reason, it seems fair to assume it should + // be 3 (see also Checked.arrayLength and issue #27346). + if utyp.len < 0 { + utyp.len = n + // e.Type is missing if we have a composite literal element + // that is itself a composite literal with omitted type. In + // that case there is nothing to record (there is no type in + // the source at that point). + if e.Type != nil { + check.recordTypeAndValue(e.Type, typexpr, utyp, nil) + } + } + + case *Slice: + // Prevent crash if the slice referred to is not yet set up. + // See analogous comment for *Array. + if utyp.elem == nil { + check.error(e, _InvalidTypeCycle, "illegal cycle in type declaration") + goto Error + } + check.indexedElts(e.Elts, utyp.elem, -1) + + case *Map: + // Prevent crash if the map referred to is not yet set up. + // See analogous comment for *Array. + if utyp.key == nil || utyp.elem == nil { + check.error(e, _InvalidTypeCycle, "illegal cycle in type declaration") + goto Error + } + visited := make(map[interface{}][]Type, len(e.Elts)) + for _, e := range e.Elts { + kv, _ := e.(*ast.KeyValueExpr) + if kv == nil { + check.error(e, _MissingLitKey, "missing key in map literal") + continue + } + check.exprWithHint(x, kv.Key, utyp.key) + check.assignment(x, utyp.key, "map literal") + if x.mode == invalid { + continue + } + if x.mode == constant_ { + duplicate := false + // if the key is of interface type, the type is also significant when checking for duplicates + xkey := keyVal(x.val) + if _, ok := utyp.key.Underlying().(*Interface); ok { + for _, vtyp := range visited[xkey] { + if check.identical(vtyp, x.typ) { + duplicate = true + break + } + } + visited[xkey] = append(visited[xkey], x.typ) + } else { + _, duplicate = visited[xkey] + visited[xkey] = nil + } + if duplicate { + check.errorf(x, _DuplicateLitKey, "duplicate key %s in map literal", x.val) + continue + } + } + check.exprWithHint(x, kv.Value, utyp.elem) + check.assignment(x, utyp.elem, "map literal") + } + + default: + // when "using" all elements unpack KeyValueExpr + // explicitly because check.use doesn't accept them + for _, e := range e.Elts { + if kv, _ := e.(*ast.KeyValueExpr); kv != nil { + // Ideally, we should also "use" kv.Key but we can't know + // if it's an externally defined struct key or not. Going + // forward anyway can lead to other errors. Give up instead. + e = kv.Value + } + check.use(e) + } + // if utyp is invalid, an error was reported before + if utyp != Typ[Invalid] { + check.errorf(e, _InvalidLit, "invalid composite literal type %s", typ) + goto Error + } + } + + x.mode = value + x.typ = typ + + case *ast.ParenExpr: + kind := check.rawExpr(x, e.X, nil) + x.expr = e + return kind + + case *ast.SelectorExpr: + check.selector(x, e) + + case *ast.IndexExpr: + check.expr(x, e.X) + if x.mode == invalid { + check.use(e.Index) + goto Error + } + + valid := false + length := int64(-1) // valid if >= 0 + switch typ := x.typ.Underlying().(type) { + case *Basic: + if isString(typ) { + valid = true + if x.mode == constant_ { + length = int64(len(constant.StringVal(x.val))) + } + // an indexed string always yields a byte value + // (not a constant) even if the string and the + // index are constant + x.mode = value + x.typ = universeByte // use 'byte' name + } + + case *Array: + valid = true + length = typ.len + if x.mode != variable { + x.mode = value + } + x.typ = typ.elem + + case *Pointer: + if typ, _ := typ.base.Underlying().(*Array); typ != nil { + valid = true + length = typ.len + x.mode = variable + x.typ = typ.elem + } + + case *Slice: + valid = true + x.mode = variable + x.typ = typ.elem + + case *Map: + var key operand + check.expr(&key, e.Index) + check.assignment(&key, typ.key, "map index") + // ok to continue even if indexing failed - map element type is known + x.mode = mapindex + x.typ = typ.elem + x.expr = e + return expression + } + + if !valid { + check.invalidOp(x, _NonIndexableOperand, "cannot index %s", x) + goto Error + } + + if e.Index == nil { + check.invalidAST(e, "missing index for %s", x) + goto Error + } + + check.index(e.Index, length) + // ok to continue + + case *ast.SliceExpr: + check.expr(x, e.X) + if x.mode == invalid { + check.use(e.Low, e.High, e.Max) + goto Error + } + + valid := false + length := int64(-1) // valid if >= 0 + switch typ := x.typ.Underlying().(type) { + case *Basic: + if isString(typ) { + if e.Slice3 { + check.invalidOp(x, _InvalidSliceExpr, "3-index slice of string") + goto Error + } + valid = true + if x.mode == constant_ { + length = int64(len(constant.StringVal(x.val))) + } + // spec: "For untyped string operands the result + // is a non-constant value of type string." + if typ.kind == UntypedString { + x.typ = Typ[String] + } + } + + case *Array: + valid = true + length = typ.len + if x.mode != variable { + check.invalidOp(x, _NonSliceableOperand, "cannot slice %s (value not addressable)", x) + goto Error + } + x.typ = &Slice{elem: typ.elem} + + case *Pointer: + if typ, _ := typ.base.Underlying().(*Array); typ != nil { + valid = true + length = typ.len + x.typ = &Slice{elem: typ.elem} + } + + case *Slice: + valid = true + // x.typ doesn't change + } + + if !valid { + check.invalidOp(x, _NonSliceableOperand, "cannot slice %s", x) + goto Error + } + + x.mode = value + + // spec: "Only the first index may be omitted; it defaults to 0." + if e.Slice3 && (e.High == nil || e.Max == nil) { + check.invalidAST(inNode(e, e.Rbrack), "2nd and 3rd index required in 3-index slice") + goto Error + } + + // check indices + var ind [3]int64 + for i, expr := range []ast.Expr{e.Low, e.High, e.Max} { + x := int64(-1) + switch { + case expr != nil: + // The "capacity" is only known statically for strings, arrays, + // and pointers to arrays, and it is the same as the length for + // those types. + max := int64(-1) + if length >= 0 { + max = length + 1 + } + if _, v := check.index(expr, max); v >= 0 { + x = v + } + case i == 0: + // default is 0 for the first index + x = 0 + case length >= 0: + // default is length (== capacity) otherwise + x = length + } + ind[i] = x + } + + // constant indices must be in range + // (check.index already checks that existing indices >= 0) + L: + for i, x := range ind[:len(ind)-1] { + if x > 0 { + for _, y := range ind[i+1:] { + if y >= 0 && x > y { + check.errorf(inNode(e, e.Rbrack), _SwappedSliceIndices, "swapped slice indices: %d > %d", x, y) + break L // only report one error, ok to continue + } + } + } + } + + case *ast.TypeAssertExpr: + check.expr(x, e.X) + if x.mode == invalid { + goto Error + } + xtyp, _ := x.typ.Underlying().(*Interface) + if xtyp == nil { + check.invalidOp(x, _InvalidAssert, "%s is not an interface", x) + goto Error + } + // x.(type) expressions are handled explicitly in type switches + if e.Type == nil { + // Don't use invalidAST because this can occur in the AST produced by + // go/parser. + check.error(e, _BadTypeKeyword, "use of .(type) outside type switch") + goto Error + } + T := check.typ(e.Type) + if T == Typ[Invalid] { + goto Error + } + check.typeAssertion(x, x, xtyp, T) + x.mode = commaok + x.typ = T + + case *ast.CallExpr: + return check.call(x, e) + + case *ast.StarExpr: + check.exprOrType(x, e.X) + switch x.mode { + case invalid: + goto Error + case typexpr: + x.typ = &Pointer{base: x.typ} + default: + if typ, ok := x.typ.Underlying().(*Pointer); ok { + x.mode = variable + x.typ = typ.base + } else { + check.invalidOp(x, _InvalidIndirection, "cannot indirect %s", x) + goto Error + } + } + + case *ast.UnaryExpr: + check.expr(x, e.X) + if x.mode == invalid { + goto Error + } + check.unary(x, e, e.Op) + if x.mode == invalid { + goto Error + } + if e.Op == token.ARROW { + x.expr = e + return statement // receive operations may appear in statement context + } + + case *ast.BinaryExpr: + check.binary(x, e, e.X, e.Y, e.Op, e.OpPos) + if x.mode == invalid { + goto Error + } + + case *ast.KeyValueExpr: + // key:value expressions are handled in composite literals + check.invalidAST(e, "no key:value expected") + goto Error + + case *ast.ArrayType, *ast.StructType, *ast.FuncType, + *ast.InterfaceType, *ast.MapType, *ast.ChanType: + x.mode = typexpr + x.typ = check.typ(e) + // Note: rawExpr (caller of exprInternal) will call check.recordTypeAndValue + // even though check.typ has already called it. This is fine as both + // times the same expression and type are recorded. It is also not a + // performance issue because we only reach here for composite literal + // types, which are comparatively rare. + + default: + panic(fmt.Sprintf("%s: unknown expression type %T", check.fset.Position(e.Pos()), e)) + } + + // everything went well + x.expr = e + return expression + +Error: + x.mode = invalid + x.expr = e + return statement // avoid follow-up errors +} + +func keyVal(x constant.Value) interface{} { + switch x.Kind() { + case constant.Bool: + return constant.BoolVal(x) + case constant.String: + return constant.StringVal(x) + case constant.Int: + if v, ok := constant.Int64Val(x); ok { + return v + } + if v, ok := constant.Uint64Val(x); ok { + return v + } + case constant.Float: + v, _ := constant.Float64Val(x) + return v + case constant.Complex: + r, _ := constant.Float64Val(constant.Real(x)) + i, _ := constant.Float64Val(constant.Imag(x)) + return complex(r, i) + } + return x +} + +// typeAssertion checks that x.(T) is legal; xtyp must be the type of x. +func (check *Checker) typeAssertion(at positioner, x *operand, xtyp *Interface, T Type) { + method, wrongType := check.assertableTo(xtyp, T) + if method == nil { + return + } + var msg string + if wrongType != nil { + if check.identical(method.typ, wrongType.typ) { + msg = fmt.Sprintf("missing method %s (%s has pointer receiver)", method.name, method.name) + } else { + msg = fmt.Sprintf("wrong type for method %s (have %s, want %s)", method.name, wrongType.typ, method.typ) + } + } else { + msg = "missing method " + method.name + } + check.errorf(at, _ImpossibleAssert, "%s cannot have dynamic type %s (%s)", x, T, msg) +} + +func (check *Checker) singleValue(x *operand) { + if x.mode == value { + // tuple types are never named - no need for underlying type below + if t, ok := x.typ.(*Tuple); ok { + assert(t.Len() != 1) + check.errorf(x, _TooManyValues, "%d-valued %s where single value is expected", t.Len(), x) + x.mode = invalid + } + } +} + +// expr typechecks expression e and initializes x with the expression value. +// The result must be a single value. +// If an error occurred, x.mode is set to invalid. +// +func (check *Checker) expr(x *operand, e ast.Expr) { + check.multiExpr(x, e) + check.singleValue(x) +} + +// multiExpr is like expr but the result may be a multi-value. +func (check *Checker) multiExpr(x *operand, e ast.Expr) { + check.rawExpr(x, e, nil) + var msg string + var code errorCode + switch x.mode { + default: + return + case novalue: + msg = "%s used as value" + code = _TooManyValues + case builtin: + msg = "%s must be called" + code = _UncalledBuiltin + case typexpr: + msg = "%s is not an expression" + code = _NotAnExpr + } + check.errorf(x, code, msg, x) + x.mode = invalid +} + +// exprWithHint typechecks expression e and initializes x with the expression value; +// hint is the type of a composite literal element. +// If an error occurred, x.mode is set to invalid. +// +func (check *Checker) exprWithHint(x *operand, e ast.Expr, hint Type) { + assert(hint != nil) + check.rawExpr(x, e, hint) + check.singleValue(x) + var msg string + var code errorCode + switch x.mode { + default: + return + case novalue: + msg = "%s used as value" + code = _TooManyValues + case builtin: + msg = "%s must be called" + code = _UncalledBuiltin + case typexpr: + msg = "%s is not an expression" + code = _NotAnExpr + } + check.errorf(x, code, msg, x) + x.mode = invalid +} + +// exprOrType typechecks expression or type e and initializes x with the expression value or type. +// If an error occurred, x.mode is set to invalid. +// +func (check *Checker) exprOrType(x *operand, e ast.Expr) { + check.rawExpr(x, e, nil) + check.singleValue(x) + if x.mode == novalue { + check.errorf(x, _NotAnExpr, "%s used as value or type", x) + x.mode = invalid + } +} diff --git a/src/go/types/exprstring.go b/src/go/types/exprstring.go new file mode 100644 index 0000000..28d605f --- /dev/null +++ b/src/go/types/exprstring.go @@ -0,0 +1,224 @@ +// Copyright 2013 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. + +// This file implements printing of expressions. + +package types + +import ( + "bytes" + "go/ast" +) + +// ExprString returns the (possibly shortened) string representation for x. +// Shortened representations are suitable for user interfaces but may not +// necessarily follow Go syntax. +func ExprString(x ast.Expr) string { + var buf bytes.Buffer + WriteExpr(&buf, x) + return buf.String() +} + +// WriteExpr writes the (possibly shortened) string representation for x to buf. +// Shortened representations are suitable for user interfaces but may not +// necessarily follow Go syntax. +func WriteExpr(buf *bytes.Buffer, x ast.Expr) { + // The AST preserves source-level parentheses so there is + // no need to introduce them here to correct for different + // operator precedences. (This assumes that the AST was + // generated by a Go parser.) + + switch x := x.(type) { + default: + buf.WriteString("(bad expr)") // nil, ast.BadExpr, ast.KeyValueExpr + + case *ast.Ident: + buf.WriteString(x.Name) + + case *ast.Ellipsis: + buf.WriteString("...") + if x.Elt != nil { + WriteExpr(buf, x.Elt) + } + + case *ast.BasicLit: + buf.WriteString(x.Value) + + case *ast.FuncLit: + buf.WriteByte('(') + WriteExpr(buf, x.Type) + buf.WriteString(" literal)") // shortened + + case *ast.CompositeLit: + buf.WriteByte('(') + WriteExpr(buf, x.Type) + buf.WriteString(" literal)") // shortened + + case *ast.ParenExpr: + buf.WriteByte('(') + WriteExpr(buf, x.X) + buf.WriteByte(')') + + case *ast.SelectorExpr: + WriteExpr(buf, x.X) + buf.WriteByte('.') + buf.WriteString(x.Sel.Name) + + case *ast.IndexExpr: + WriteExpr(buf, x.X) + buf.WriteByte('[') + WriteExpr(buf, x.Index) + buf.WriteByte(']') + + case *ast.SliceExpr: + WriteExpr(buf, x.X) + buf.WriteByte('[') + if x.Low != nil { + WriteExpr(buf, x.Low) + } + buf.WriteByte(':') + if x.High != nil { + WriteExpr(buf, x.High) + } + if x.Slice3 { + buf.WriteByte(':') + if x.Max != nil { + WriteExpr(buf, x.Max) + } + } + buf.WriteByte(']') + + case *ast.TypeAssertExpr: + WriteExpr(buf, x.X) + buf.WriteString(".(") + WriteExpr(buf, x.Type) + buf.WriteByte(')') + + case *ast.CallExpr: + WriteExpr(buf, x.Fun) + buf.WriteByte('(') + for i, arg := range x.Args { + if i > 0 { + buf.WriteString(", ") + } + WriteExpr(buf, arg) + } + if x.Ellipsis.IsValid() { + buf.WriteString("...") + } + buf.WriteByte(')') + + case *ast.StarExpr: + buf.WriteByte('*') + WriteExpr(buf, x.X) + + case *ast.UnaryExpr: + buf.WriteString(x.Op.String()) + WriteExpr(buf, x.X) + + case *ast.BinaryExpr: + WriteExpr(buf, x.X) + buf.WriteByte(' ') + buf.WriteString(x.Op.String()) + buf.WriteByte(' ') + WriteExpr(buf, x.Y) + + case *ast.ArrayType: + buf.WriteByte('[') + if x.Len != nil { + WriteExpr(buf, x.Len) + } + buf.WriteByte(']') + WriteExpr(buf, x.Elt) + + case *ast.StructType: + buf.WriteString("struct{") + writeFieldList(buf, x.Fields, "; ", false) + buf.WriteByte('}') + + case *ast.FuncType: + buf.WriteString("func") + writeSigExpr(buf, x) + + case *ast.InterfaceType: + buf.WriteString("interface{") + writeFieldList(buf, x.Methods, "; ", true) + buf.WriteByte('}') + + case *ast.MapType: + buf.WriteString("map[") + WriteExpr(buf, x.Key) + buf.WriteByte(']') + WriteExpr(buf, x.Value) + + case *ast.ChanType: + var s string + switch x.Dir { + case ast.SEND: + s = "chan<- " + case ast.RECV: + s = "<-chan " + default: + s = "chan " + } + buf.WriteString(s) + WriteExpr(buf, x.Value) + } +} + +func writeSigExpr(buf *bytes.Buffer, sig *ast.FuncType) { + buf.WriteByte('(') + writeFieldList(buf, sig.Params, ", ", false) + buf.WriteByte(')') + + res := sig.Results + n := res.NumFields() + if n == 0 { + // no result + return + } + + buf.WriteByte(' ') + if n == 1 && len(res.List[0].Names) == 0 { + // single unnamed result + WriteExpr(buf, res.List[0].Type) + return + } + + // multiple or named result(s) + buf.WriteByte('(') + writeFieldList(buf, res, ", ", false) + buf.WriteByte(')') +} + +func writeFieldList(buf *bytes.Buffer, fields *ast.FieldList, sep string, iface bool) { + for i, f := range fields.List { + if i > 0 { + buf.WriteString(sep) + } + + // field list names + for i, name := range f.Names { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(name.Name) + } + + // types of interface methods consist of signatures only + if sig, _ := f.Type.(*ast.FuncType); sig != nil && iface { + writeSigExpr(buf, sig) + continue + } + + // named fields are separated with a blank from the field type + if len(f.Names) > 0 { + buf.WriteByte(' ') + } + + WriteExpr(buf, f.Type) + + // ignore tag + } +} diff --git a/src/go/types/exprstring_test.go b/src/go/types/exprstring_test.go new file mode 100644 index 0000000..5110288 --- /dev/null +++ b/src/go/types/exprstring_test.go @@ -0,0 +1,94 @@ +// Copyright 2013 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 types_test + +import ( + "go/parser" + "testing" + + . "go/types" +) + +var testExprs = []testEntry{ + // basic type literals + dup("x"), + dup("true"), + dup("42"), + dup("3.1415"), + dup("2.71828i"), + dup(`'a'`), + dup(`"foo"`), + dup("`bar`"), + + // func and composite literals + {"func(){}", "(func() literal)"}, + {"func(x int) complex128 {}", "(func(x int) complex128 literal)"}, + {"[]int{1, 2, 3}", "([]int literal)"}, + + // non-type expressions + dup("(x)"), + dup("x.f"), + dup("a[i]"), + + dup("s[:]"), + dup("s[i:]"), + dup("s[:j]"), + dup("s[i:j]"), + dup("s[:j:k]"), + dup("s[i:j:k]"), + + dup("x.(T)"), + + dup("x.([10]int)"), + dup("x.([...]int)"), + + dup("x.(struct{})"), + dup("x.(struct{x int; y, z float32; E})"), + + dup("x.(func())"), + dup("x.(func(x int))"), + dup("x.(func() int)"), + dup("x.(func(x, y int, z float32) (r int))"), + dup("x.(func(a, b, c int))"), + dup("x.(func(x ...T))"), + + dup("x.(interface{})"), + dup("x.(interface{m(); n(x int); E})"), + dup("x.(interface{m(); n(x int) T; E; F})"), + + dup("x.(map[K]V)"), + + dup("x.(chan E)"), + dup("x.(<-chan E)"), + dup("x.(chan<- chan int)"), + dup("x.(chan<- <-chan int)"), + dup("x.(<-chan chan int)"), + dup("x.(chan (<-chan int))"), + + dup("f()"), + dup("f(x)"), + dup("int(x)"), + dup("f(x, x + y)"), + dup("f(s...)"), + dup("f(a, s...)"), + + dup("*x"), + dup("&x"), + dup("x + y"), + dup("x + y << (2 * s)"), +} + +func TestExprString(t *testing.T) { + for _, test := range testExprs { + x, err := parser.ParseExpr(test.src) + if err != nil { + t.Errorf("%s: %s", test.src, err) + continue + } + if got := ExprString(x); got != test.str { + t.Errorf("%s: got %s, want %s", test.src, got, test.str) + } + } +} diff --git a/src/go/types/fixedbugs/issue20583.src b/src/go/types/fixedbugs/issue20583.src new file mode 100644 index 0000000..d26dbad --- /dev/null +++ b/src/go/types/fixedbugs/issue20583.src @@ -0,0 +1,14 @@ +// Copyright 2020 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 issue20583 + +const ( + _ = 6e886451608 /* ERROR malformed constant */ /2 + _ = 6e886451608i /* ERROR malformed constant */ /2 + _ = 0 * 1e+1000000000 // ERROR malformed constant + + x = 1e100000000 + _ = x*x*x*x*x*x* /* ERROR not representable */ x +) diff --git a/src/go/types/fixedbugs/issue23203a.src b/src/go/types/fixedbugs/issue23203a.src new file mode 100644 index 0000000..48cb588 --- /dev/null +++ b/src/go/types/fixedbugs/issue23203a.src @@ -0,0 +1,14 @@ +// Copyright 2018 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 main + +import "unsafe" + +type T struct{} + +func (T) m1() {} +func (T) m2([unsafe.Sizeof(T.m1)]int) {} + +func main() {} diff --git a/src/go/types/fixedbugs/issue23203b.src b/src/go/types/fixedbugs/issue23203b.src new file mode 100644 index 0000000..638ec6c --- /dev/null +++ b/src/go/types/fixedbugs/issue23203b.src @@ -0,0 +1,14 @@ +// Copyright 2018 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 main + +import "unsafe" + +type T struct{} + +func (T) m2([unsafe.Sizeof(T.m1)]int) {} +func (T) m1() {} + +func main() {} diff --git a/src/go/types/fixedbugs/issue26390.src b/src/go/types/fixedbugs/issue26390.src new file mode 100644 index 0000000..9e0101f --- /dev/null +++ b/src/go/types/fixedbugs/issue26390.src @@ -0,0 +1,13 @@ +// Copyright 2018 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. + +// stand-alone test to ensure case is triggered + +package issue26390 + +type A = T + +func (t *T) m() *A { return t } + +type T struct{} diff --git a/src/go/types/fixedbugs/issue28251.src b/src/go/types/fixedbugs/issue28251.src new file mode 100644 index 0000000..cd79e0e --- /dev/null +++ b/src/go/types/fixedbugs/issue28251.src @@ -0,0 +1,65 @@ +// Copyright 2018 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. + +// This file contains test cases for various forms of +// method receiver declarations, per the spec clarification +// https://golang.org/cl/142757. + +package issue28251 + +// test case from issue28251 +type T struct{} + +type T0 = *T + +func (T0) m() {} + +func _() { (&T{}).m() } + +// various alternative forms +type ( + T1 = (((T))) +) + +func ((*(T1))) m1() {} +func _() { (T{}).m2() } +func _() { (&T{}).m2() } + +type ( + T2 = (((T3))) + T3 = T +) + +func (T2) m2() {} +func _() { (T{}).m2() } +func _() { (&T{}).m2() } + +type ( + T4 = ((*(T5))) + T5 = T +) + +func (T4) m4() {} +func _() { (T{}).m4 /* ERROR "cannot call pointer method m4 on T" */ () } +func _() { (&T{}).m4() } + +type ( + T6 = (((T7))) + T7 = (*(T8)) + T8 = T +) + +func (T6) m6() {} +func _() { (T{}).m6 /* ERROR "cannot call pointer method m6 on T" */ () } +func _() { (&T{}).m6() } + +type ( + T9 = *T10 + T10 = *T11 + T11 = T +) + +func (T9 /* ERROR invalid receiver \*\*T */ ) m9() {} +func _() { (T{}).m9 /* ERROR has no field or method m9 */ () } +func _() { (&T{}).m9 /* ERROR has no field or method m9 */ () } diff --git a/src/go/types/fixedbugs/issue42695.src b/src/go/types/fixedbugs/issue42695.src new file mode 100644 index 0000000..d0d6200 --- /dev/null +++ b/src/go/types/fixedbugs/issue42695.src @@ -0,0 +1,17 @@ +// Copyright 2020 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 issue42695 + +const _ = 6e5518446744 // ERROR malformed constant +const _ uint8 = 6e5518446744 // ERROR malformed constant + +var _ = 6e5518446744 // ERROR malformed constant +var _ uint8 = 6e5518446744 // ERROR malformed constant + +func f(x int) int { + return x + 6e5518446744 // ERROR malformed constant +} + +var _ = f(6e5518446744 /* ERROR malformed constant */ ) diff --git a/src/go/types/fixedbugs/issue43110.src b/src/go/types/fixedbugs/issue43110.src new file mode 100644 index 0000000..4a46945 --- /dev/null +++ b/src/go/types/fixedbugs/issue43110.src @@ -0,0 +1,43 @@ +// Copyright 2020 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 p + +type P *struct{} + +func _() { + // want an error even if the switch is empty + var a struct{ _ func() } + switch a /* ERROR cannot switch on a */ { + } + + switch a /* ERROR cannot switch on a */ { + case a: // no follow-on error here + } + + // this is ok because f can be compared to nil + var f func() + switch f { + } + + switch f { + case nil: + } + + switch (func())(nil) { + case nil: + } + + switch (func())(nil) { + case f /* ERROR cannot compare */ : + } + + switch nil /* ERROR use of untyped nil in switch expression */ { + } + + // this is ok + switch P(nil) { + case P(nil): + } +} diff --git a/src/go/types/fixedbugs/issue43124.src b/src/go/types/fixedbugs/issue43124.src new file mode 100644 index 0000000..f429f74 --- /dev/null +++ b/src/go/types/fixedbugs/issue43124.src @@ -0,0 +1,16 @@ +// Copyright 2020 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 p + +var _ = int(0 /* ERROR invalid use of \.\.\. in conversion to int */ ...) + +// test case from issue + +type M []string + +var ( + x = []string{"a", "b"} + _ = M(x /* ERROR invalid use of \.\.\. in conversion to M */ ...) +) diff --git a/src/go/types/fixedbugs/issue6977.src b/src/go/types/fixedbugs/issue6977.src new file mode 100644 index 0000000..8f4e9ba --- /dev/null +++ b/src/go/types/fixedbugs/issue6977.src @@ -0,0 +1,82 @@ +// Copyright 2019 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 p + +import "io" + +// Alan's initial report. + +type I interface { f(); String() string } +type J interface { g(); String() string } + +type IJ1 = interface { I; J } +type IJ2 = interface { f(); g(); String() string } + +var _ = (*IJ1)(nil) == (*IJ2)(nil) // static assert that IJ1 and IJ2 are identical types + +// The canonical example. + +type ReadWriteCloser interface { io.ReadCloser; io.WriteCloser } + +// Some more cases. + +type M interface { m() } +type M32 interface { m() int32 } +type M64 interface { m() int64 } + +type U1 interface { m() } +type U2 interface { m(); M } +type U3 interface { M; m() } +type U4 interface { M; M; M } +type U5 interface { U1; U2; U3; U4 } + +type U6 interface { m(); m /* ERROR duplicate method */ () } +type U7 interface { M32 /* ERROR duplicate method */ ; m() } +type U8 interface { m(); M32 /* ERROR duplicate method */ } +type U9 interface { M32; M64 /* ERROR duplicate method */ } + +// Verify that repeated embedding of the same interface(s) +// eliminates duplicate methods early (rather than at the +// end) to prevent exponential memory and time use. +// Without early elimination, computing T29 may take dozens +// of minutes. +type ( + T0 interface { m() } + T1 interface { T0; T0 } + T2 interface { T1; T1 } + T3 interface { T2; T2 } + T4 interface { T3; T3 } + T5 interface { T4; T4 } + T6 interface { T5; T5 } + T7 interface { T6; T6 } + T8 interface { T7; T7 } + T9 interface { T8; T8 } + + T10 interface { T9; T9 } + T11 interface { T10; T10 } + T12 interface { T11; T11 } + T13 interface { T12; T12 } + T14 interface { T13; T13 } + T15 interface { T14; T14 } + T16 interface { T15; T15 } + T17 interface { T16; T16 } + T18 interface { T17; T17 } + T19 interface { T18; T18 } + + T20 interface { T19; T19 } + T21 interface { T20; T20 } + T22 interface { T21; T21 } + T23 interface { T22; T22 } + T24 interface { T23; T23 } + T25 interface { T24; T24 } + T26 interface { T25; T25 } + T27 interface { T26; T26 } + T28 interface { T27; T27 } + T29 interface { T28; T28 } +) + +// Verify that m is present. +var x T29 +var _ = x.m diff --git a/src/go/types/gccgosizes.go b/src/go/types/gccgosizes.go new file mode 100644 index 0000000..d5c92c6 --- /dev/null +++ b/src/go/types/gccgosizes.go @@ -0,0 +1,40 @@ +// Copyright 2019 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. + +// This is a copy of the file generated during the gccgo build process. +// Last update 2019-01-22. + +package types + +var gccgoArchSizes = map[string]*StdSizes{ + "386": {4, 4}, + "alpha": {8, 8}, + "amd64": {8, 8}, + "amd64p32": {4, 8}, + "arm": {4, 8}, + "armbe": {4, 8}, + "arm64": {8, 8}, + "arm64be": {8, 8}, + "ia64": {8, 8}, + "m68k": {4, 2}, + "mips": {4, 8}, + "mipsle": {4, 8}, + "mips64": {8, 8}, + "mips64le": {8, 8}, + "mips64p32": {4, 8}, + "mips64p32le": {4, 8}, + "nios2": {4, 8}, + "ppc": {4, 8}, + "ppc64": {8, 8}, + "ppc64le": {8, 8}, + "riscv": {4, 8}, + "riscv64": {8, 8}, + "s390": {4, 8}, + "s390x": {8, 8}, + "sh": {4, 8}, + "shbe": {4, 8}, + "sparc": {4, 8}, + "sparc64": {8, 8}, + "wasm": {8, 8}, +} diff --git a/src/go/types/gotype.go b/src/go/types/gotype.go new file mode 100644 index 0000000..52709df --- /dev/null +++ b/src/go/types/gotype.go @@ -0,0 +1,354 @@ +// Copyright 2011 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. + +// +build ignore + +// Build this command explicitly: go build gotype.go + +/* +The gotype command, like the front-end of a Go compiler, parses and +type-checks a single Go package. Errors are reported if the analysis +fails; otherwise gotype is quiet (unless -v is set). + +Without a list of paths, gotype reads from standard input, which +must provide a single Go source file defining a complete package. + +With a single directory argument, gotype checks the Go files in +that directory, comprising a single package. Use -t to include the +(in-package) _test.go files. Use -x to type check only external +test files. + +Otherwise, each path must be the filename of a Go file belonging +to the same package. + +Imports are processed by importing directly from the source of +imported packages (default), or by importing from compiled and +installed packages (by setting -c to the respective compiler). + +The -c flag must be set to a compiler ("gc", "gccgo") when type- +checking packages containing imports with relative import paths +(import "./mypkg") because the source importer cannot know which +files to include for such packages. + +Usage: + gotype [flags] [path...] + +The flags are: + -t + include local test files in a directory (ignored if -x is provided) + -x + consider only external test files in a directory + -e + report all errors (not just the first 10) + -v + verbose mode + -c + compiler used for installed packages (gc, gccgo, or source); default: source + +Flags controlling additional output: + -ast + print AST + -trace + print parse trace + -comments + parse comments (ignored unless -ast or -trace is provided) + -panic + panic on first error + +Examples: + +To check the files a.go, b.go, and c.go: + + gotype a.go b.go c.go + +To check an entire package including (in-package) tests in the directory dir and print the processed files: + + gotype -t -v dir + +To check the external test package (if any) in the current directory, based on installed packages compiled with +cmd/compile: + + gotype -c=gc -x . + +To verify the output of a pipe: + + echo "package foo" | gotype + +*/ +package main + +import ( + "flag" + "fmt" + "go/ast" + "go/build" + "go/importer" + "go/parser" + "go/scanner" + "go/token" + "go/types" + "io" + "os" + "path/filepath" + "sync" + "time" +) + +var ( + // main operation modes + testFiles = flag.Bool("t", false, "include in-package test files in a directory") + xtestFiles = flag.Bool("x", false, "consider only external test files in a directory") + allErrors = flag.Bool("e", false, "report all errors, not just the first 10") + verbose = flag.Bool("v", false, "verbose mode") + compiler = flag.String("c", "source", "compiler used for installed packages (gc, gccgo, or source)") + + // additional output control + printAST = flag.Bool("ast", false, "print AST") + printTrace = flag.Bool("trace", false, "print parse trace") + parseComments = flag.Bool("comments", false, "parse comments (ignored unless -ast or -trace is provided)") + panicOnError = flag.Bool("panic", false, "panic on first error") +) + +var ( + fset = token.NewFileSet() + errorCount = 0 + sequential = false + parserMode parser.Mode +) + +func initParserMode() { + if *allErrors { + parserMode |= parser.AllErrors + } + if *printAST { + sequential = true + } + if *printTrace { + parserMode |= parser.Trace + sequential = true + } + if *parseComments && (*printAST || *printTrace) { + parserMode |= parser.ParseComments + } +} + +const usageString = `usage: gotype [flags] [path ...] + +The gotype command, like the front-end of a Go compiler, parses and +type-checks a single Go package. Errors are reported if the analysis +fails; otherwise gotype is quiet (unless -v is set). + +Without a list of paths, gotype reads from standard input, which +must provide a single Go source file defining a complete package. + +With a single directory argument, gotype checks the Go files in +that directory, comprising a single package. Use -t to include the +(in-package) _test.go files. Use -x to type check only external +test files. + +Otherwise, each path must be the filename of a Go file belonging +to the same package. + +Imports are processed by importing directly from the source of +imported packages (default), or by importing from compiled and +installed packages (by setting -c to the respective compiler). + +The -c flag must be set to a compiler ("gc", "gccgo") when type- +checking packages containing imports with relative import paths +(import "./mypkg") because the source importer cannot know which +files to include for such packages. +` + +func usage() { + fmt.Fprintln(os.Stderr, usageString) + flag.PrintDefaults() + os.Exit(2) +} + +func report(err error) { + if *panicOnError { + panic(err) + } + scanner.PrintError(os.Stderr, err) + if list, ok := err.(scanner.ErrorList); ok { + errorCount += len(list) + return + } + errorCount++ +} + +// parse may be called concurrently +func parse(filename string, src interface{}) (*ast.File, error) { + if *verbose { + fmt.Println(filename) + } + file, err := parser.ParseFile(fset, filename, src, parserMode) // ok to access fset concurrently + if *printAST { + ast.Print(fset, file) + } + return file, err +} + +func parseStdin() (*ast.File, error) { + src, err := io.ReadAll(os.Stdin) + if err != nil { + return nil, err + } + return parse("", src) +} + +func parseFiles(dir string, filenames []string) ([]*ast.File, error) { + files := make([]*ast.File, len(filenames)) + errors := make([]error, len(filenames)) + + var wg sync.WaitGroup + for i, filename := range filenames { + wg.Add(1) + go func(i int, filepath string) { + defer wg.Done() + files[i], errors[i] = parse(filepath, nil) + }(i, filepath.Join(dir, filename)) + if sequential { + wg.Wait() + } + } + wg.Wait() + + // If there are errors, return the first one for deterministic results. + var first error + for _, err := range errors { + if err != nil { + first = err + // If we have an error, some files may be nil. + // Remove them. (The go/parser always returns + // a possibly partial AST even in the presence + // of errors, except if the file doesn't exist + // in the first place, in which case it cannot + // matter.) + i := 0 + for _, f := range files { + if f != nil { + files[i] = f + i++ + } + } + files = files[:i] + break + } + } + + return files, first +} + +func parseDir(dir string) ([]*ast.File, error) { + ctxt := build.Default + pkginfo, err := ctxt.ImportDir(dir, 0) + if _, nogo := err.(*build.NoGoError); err != nil && !nogo { + return nil, err + } + + if *xtestFiles { + return parseFiles(dir, pkginfo.XTestGoFiles) + } + + filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...) + if *testFiles { + filenames = append(filenames, pkginfo.TestGoFiles...) + } + return parseFiles(dir, filenames) +} + +func getPkgFiles(args []string) ([]*ast.File, error) { + if len(args) == 0 { + // stdin + file, err := parseStdin() + if err != nil { + return nil, err + } + return []*ast.File{file}, nil + } + + if len(args) == 1 { + // possibly a directory + path := args[0] + info, err := os.Stat(path) + if err != nil { + return nil, err + } + if info.IsDir() { + return parseDir(path) + } + } + + // list of files + return parseFiles("", args) +} + +func checkPkgFiles(files []*ast.File) { + type bailout struct{} + + // if checkPkgFiles is called multiple times, set up conf only once + conf := types.Config{ + FakeImportC: true, + Error: func(err error) { + if !*allErrors && errorCount >= 10 { + panic(bailout{}) + } + report(err) + }, + Importer: importer.ForCompiler(fset, *compiler, nil), + Sizes: types.SizesFor(build.Default.Compiler, build.Default.GOARCH), + } + + defer func() { + switch p := recover().(type) { + case nil, bailout: + // normal return or early exit + default: + // re-panic + panic(p) + } + }() + + const path = "pkg" // any non-empty string will do for now + conf.Check(path, fset, files, nil) +} + +func printStats(d time.Duration) { + fileCount := 0 + lineCount := 0 + fset.Iterate(func(f *token.File) bool { + fileCount++ + lineCount += f.LineCount() + return true + }) + + fmt.Printf( + "%s (%d files, %d lines, %d lines/s)\n", + d, fileCount, lineCount, int64(float64(lineCount)/d.Seconds()), + ) +} + +func main() { + flag.Usage = usage + flag.Parse() + initParserMode() + + start := time.Now() + + files, err := getPkgFiles(flag.Args()) + if err != nil { + report(err) + // ok to continue (files may be empty, but not nil) + } + + checkPkgFiles(files) + if errorCount > 0 { + os.Exit(2) + } + + if *verbose { + printStats(time.Since(start)) + } +} diff --git a/src/go/types/hilbert_test.go b/src/go/types/hilbert_test.go new file mode 100644 index 0000000..77954d2 --- /dev/null +++ b/src/go/types/hilbert_test.go @@ -0,0 +1,222 @@ +// Copyright 2013 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 types_test + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "os" + "testing" + + . "go/types" +) + +var ( + H = flag.Int("H", 5, "Hilbert matrix size") + out = flag.String("out", "", "write generated program to out") +) + +func TestHilbert(t *testing.T) { + // generate source + src := program(*H, *out) + if *out != "" { + os.WriteFile(*out, src, 0666) + return + } + + // parse source + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "hilbert.go", src, 0) + if err != nil { + t.Fatal(err) + } + + // type-check file + DefPredeclaredTestFuncs() // define assert built-in + conf := Config{Importer: importer.Default()} + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } +} + +func program(n int, out string) []byte { + var g gen + + g.p(`// Code generated by: go test -run=Hilbert -H=%d -out=%q. DO NOT EDIT. + +// +`+`build ignore + +// This program tests arbitrary precision constant arithmetic +// by generating the constant elements of a Hilbert matrix H, +// its inverse I, and the product P = H*I. The product should +// be the identity matrix. +package main + +func main() { + if !ok { + printProduct() + return + } + println("PASS") +} + +`, n, out) + g.hilbert(n) + g.inverse(n) + g.product(n) + g.verify(n) + g.printProduct(n) + g.binomials(2*n - 1) + g.factorials(2*n - 1) + + return g.Bytes() +} + +type gen struct { + bytes.Buffer +} + +func (g *gen) p(format string, args ...interface{}) { + fmt.Fprintf(&g.Buffer, format, args...) +} + +func (g *gen) hilbert(n int) { + g.p(`// Hilbert matrix, n = %d +const ( +`, n) + for i := 0; i < n; i++ { + g.p("\t") + for j := 0; j < n; j++ { + if j > 0 { + g.p(", ") + } + g.p("h%d_%d", i, j) + } + if i == 0 { + g.p(" = ") + for j := 0; j < n; j++ { + if j > 0 { + g.p(", ") + } + g.p("1.0/(iota + %d)", j+1) + } + } + g.p("\n") + } + g.p(")\n\n") +} + +func (g *gen) inverse(n int) { + g.p(`// Inverse Hilbert matrix +const ( +`) + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + s := "+" + if (i+j)&1 != 0 { + s = "-" + } + g.p("\ti%d_%d = %s%d * b%d_%d * b%d_%d * b%d_%d * b%d_%d\n", + i, j, s, i+j+1, n+i, n-j-1, n+j, n-i-1, i+j, i, i+j, i) + } + g.p("\n") + } + g.p(")\n\n") +} + +func (g *gen) product(n int) { + g.p(`// Product matrix +const ( +`) + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + g.p("\tp%d_%d = ", i, j) + for k := 0; k < n; k++ { + if k > 0 { + g.p(" + ") + } + g.p("h%d_%d*i%d_%d", i, k, k, j) + } + g.p("\n") + } + g.p("\n") + } + g.p(")\n\n") +} + +func (g *gen) verify(n int) { + g.p(`// Verify that product is the identity matrix +const ok = +`) + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + if j == 0 { + g.p("\t") + } else { + g.p(" && ") + } + v := 0 + if i == j { + v = 1 + } + g.p("p%d_%d == %d", i, j, v) + } + g.p(" &&\n") + } + g.p("\ttrue\n\n") + + // verify ok at type-check time + if *out == "" { + g.p("const _ = assert(ok)\n\n") + } +} + +func (g *gen) printProduct(n int) { + g.p("func printProduct() {\n") + for i := 0; i < n; i++ { + g.p("\tprintln(") + for j := 0; j < n; j++ { + if j > 0 { + g.p(", ") + } + g.p("p%d_%d", i, j) + } + g.p(")\n") + } + g.p("}\n\n") +} + +func (g *gen) binomials(n int) { + g.p(`// Binomials +const ( +`) + for j := 0; j <= n; j++ { + if j > 0 { + g.p("\n") + } + for k := 0; k <= j; k++ { + g.p("\tb%d_%d = f%d / (f%d*f%d)\n", j, k, j, k, j-k) + } + } + g.p(")\n\n") +} + +func (g *gen) factorials(n int) { + g.p(`// Factorials +const ( + f0 = 1 + f1 = 1 +`) + for i := 2; i <= n; i++ { + g.p("\tf%d = f%d * %d\n", i, i-1, i) + } + g.p(")\n\n") +} diff --git a/src/go/types/initorder.go b/src/go/types/initorder.go new file mode 100644 index 0000000..77a739c --- /dev/null +++ b/src/go/types/initorder.go @@ -0,0 +1,297 @@ +// Copyright 2014 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 types + +import ( + "container/heap" + "fmt" +) + +// initOrder computes the Info.InitOrder for package variables. +func (check *Checker) initOrder() { + // An InitOrder may already have been computed if a package is + // built from several calls to (*Checker).Files. Clear it. + check.Info.InitOrder = check.Info.InitOrder[:0] + + // Compute the object dependency graph and initialize + // a priority queue with the list of graph nodes. + pq := nodeQueue(dependencyGraph(check.objMap)) + heap.Init(&pq) + + const debug = false + if debug { + fmt.Printf("Computing initialization order for %s\n\n", check.pkg) + fmt.Println("Object dependency graph:") + for obj, d := range check.objMap { + // only print objects that may appear in the dependency graph + if obj, _ := obj.(dependency); obj != nil { + if len(d.deps) > 0 { + fmt.Printf("\t%s depends on\n", obj.Name()) + for dep := range d.deps { + fmt.Printf("\t\t%s\n", dep.Name()) + } + } else { + fmt.Printf("\t%s has no dependencies\n", obj.Name()) + } + } + } + fmt.Println() + + fmt.Println("Transposed object dependency graph (functions eliminated):") + for _, n := range pq { + fmt.Printf("\t%s depends on %d nodes\n", n.obj.Name(), n.ndeps) + for p := range n.pred { + fmt.Printf("\t\t%s is dependent\n", p.obj.Name()) + } + } + fmt.Println() + + fmt.Println("Processing nodes:") + } + + // Determine initialization order by removing the highest priority node + // (the one with the fewest dependencies) and its edges from the graph, + // repeatedly, until there are no nodes left. + // In a valid Go program, those nodes always have zero dependencies (after + // removing all incoming dependencies), otherwise there are initialization + // cycles. + emitted := make(map[*declInfo]bool) + for len(pq) > 0 { + // get the next node + n := heap.Pop(&pq).(*graphNode) + + if debug { + fmt.Printf("\t%s (src pos %d) depends on %d nodes now\n", + n.obj.Name(), n.obj.order(), n.ndeps) + } + + // if n still depends on other nodes, we have a cycle + if n.ndeps > 0 { + cycle := findPath(check.objMap, n.obj, n.obj, make(map[Object]bool)) + // If n.obj is not part of the cycle (e.g., n.obj->b->c->d->c), + // cycle will be nil. Don't report anything in that case since + // the cycle is reported when the algorithm gets to an object + // in the cycle. + // Furthermore, once an object in the cycle is encountered, + // the cycle will be broken (dependency count will be reduced + // below), and so the remaining nodes in the cycle don't trigger + // another error (unless they are part of multiple cycles). + if cycle != nil { + check.reportCycle(cycle) + } + // Ok to continue, but the variable initialization order + // will be incorrect at this point since it assumes no + // cycle errors. + } + + // reduce dependency count of all dependent nodes + // and update priority queue + for p := range n.pred { + p.ndeps-- + heap.Fix(&pq, p.index) + } + + // record the init order for variables with initializers only + v, _ := n.obj.(*Var) + info := check.objMap[v] + if v == nil || !info.hasInitializer() { + continue + } + + // n:1 variable declarations such as: a, b = f() + // introduce a node for each lhs variable (here: a, b); + // but they all have the same initializer - emit only + // one, for the first variable seen + if emitted[info] { + continue // initializer already emitted, if any + } + emitted[info] = true + + infoLhs := info.lhs // possibly nil (see declInfo.lhs field comment) + if infoLhs == nil { + infoLhs = []*Var{v} + } + init := &Initializer{infoLhs, info.init} + check.Info.InitOrder = append(check.Info.InitOrder, init) + } + + if debug { + fmt.Println() + fmt.Println("Initialization order:") + for _, init := range check.Info.InitOrder { + fmt.Printf("\t%s\n", init) + } + fmt.Println() + } +} + +// findPath returns the (reversed) list of objects []Object{to, ... from} +// such that there is a path of object dependencies from 'from' to 'to'. +// If there is no such path, the result is nil. +func findPath(objMap map[Object]*declInfo, from, to Object, seen map[Object]bool) []Object { + if seen[from] { + return nil + } + seen[from] = true + + for d := range objMap[from].deps { + if d == to { + return []Object{d} + } + if P := findPath(objMap, d, to, seen); P != nil { + return append(P, d) + } + } + + return nil +} + +// reportCycle reports an error for the given cycle. +func (check *Checker) reportCycle(cycle []Object) { + obj := cycle[0] + check.errorf(obj, _InvalidInitCycle, "initialization cycle for %s", obj.Name()) + // subtle loop: print cycle[i] for i = 0, n-1, n-2, ... 1 for len(cycle) = n + for i := len(cycle) - 1; i >= 0; i-- { + check.errorf(obj, _InvalidInitCycle, "\t%s refers to", obj.Name()) // secondary error, \t indented + obj = cycle[i] + } + // print cycle[0] again to close the cycle + check.errorf(obj, _InvalidInitCycle, "\t%s", obj.Name()) +} + +// ---------------------------------------------------------------------------- +// Object dependency graph + +// A dependency is an object that may be a dependency in an initialization +// expression. Only constants, variables, and functions can be dependencies. +// Constants are here because constant expression cycles are reported during +// initialization order computation. +type dependency interface { + Object + isDependency() +} + +// A graphNode represents a node in the object dependency graph. +// Each node p in n.pred represents an edge p->n, and each node +// s in n.succ represents an edge n->s; with a->b indicating that +// a depends on b. +type graphNode struct { + obj dependency // object represented by this node + pred, succ nodeSet // consumers and dependencies of this node (lazily initialized) + index int // node index in graph slice/priority queue + ndeps int // number of outstanding dependencies before this object can be initialized +} + +type nodeSet map[*graphNode]bool + +func (s *nodeSet) add(p *graphNode) { + if *s == nil { + *s = make(nodeSet) + } + (*s)[p] = true +} + +// dependencyGraph computes the object dependency graph from the given objMap, +// with any function nodes removed. The resulting graph contains only constants +// and variables. +func dependencyGraph(objMap map[Object]*declInfo) []*graphNode { + // M is the dependency (Object) -> graphNode mapping + M := make(map[dependency]*graphNode) + for obj := range objMap { + // only consider nodes that may be an initialization dependency + if obj, _ := obj.(dependency); obj != nil { + M[obj] = &graphNode{obj: obj} + } + } + + // compute edges for graph M + // (We need to include all nodes, even isolated ones, because they still need + // to be scheduled for initialization in correct order relative to other nodes.) + for obj, n := range M { + // for each dependency obj -> d (= deps[i]), create graph edges n->s and s->n + for d := range objMap[obj].deps { + // only consider nodes that may be an initialization dependency + if d, _ := d.(dependency); d != nil { + d := M[d] + n.succ.add(d) + d.pred.add(n) + } + } + } + + // remove function nodes and collect remaining graph nodes in G + // (Mutually recursive functions may introduce cycles among themselves + // which are permitted. Yet such cycles may incorrectly inflate the dependency + // count for variables which in turn may not get scheduled for initialization + // in correct order.) + var G []*graphNode + for obj, n := range M { + if _, ok := obj.(*Func); ok { + // connect each predecessor p of n with each successor s + // and drop the function node (don't collect it in G) + for p := range n.pred { + // ignore self-cycles + if p != n { + // Each successor s of n becomes a successor of p, and + // each predecessor p of n becomes a predecessor of s. + for s := range n.succ { + // ignore self-cycles + if s != n { + p.succ.add(s) + s.pred.add(p) + delete(s.pred, n) // remove edge to n + } + } + delete(p.succ, n) // remove edge to n + } + } + } else { + // collect non-function nodes + G = append(G, n) + } + } + + // fill in index and ndeps fields + for i, n := range G { + n.index = i + n.ndeps = len(n.succ) + } + + return G +} + +// ---------------------------------------------------------------------------- +// Priority queue + +// nodeQueue implements the container/heap interface; +// a nodeQueue may be used as a priority queue. +type nodeQueue []*graphNode + +func (a nodeQueue) Len() int { return len(a) } + +func (a nodeQueue) Swap(i, j int) { + x, y := a[i], a[j] + a[i], a[j] = y, x + x.index, y.index = j, i +} + +func (a nodeQueue) Less(i, j int) bool { + x, y := a[i], a[j] + // nodes are prioritized by number of incoming dependencies (1st key) + // and source order (2nd key) + return x.ndeps < y.ndeps || x.ndeps == y.ndeps && x.obj.order() < y.obj.order() +} + +func (a *nodeQueue) Push(x interface{}) { + panic("unreachable") +} + +func (a *nodeQueue) Pop() interface{} { + n := len(*a) + x := (*a)[n-1] + x.index = -1 // for safety + *a = (*a)[:n-1] + return x +} diff --git a/src/go/types/issues_test.go b/src/go/types/issues_test.go new file mode 100644 index 0000000..34850eb --- /dev/null +++ b/src/go/types/issues_test.go @@ -0,0 +1,551 @@ +// Copyright 2013 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. + +// This file implements tests for various issues. + +package types_test + +import ( + "bytes" + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "internal/testenv" + "sort" + "strings" + "testing" + + . "go/types" +) + +func mustParse(t *testing.T, src string) *ast.File { + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + return f +} +func TestIssue5770(t *testing.T) { + f := mustParse(t, `package p; type S struct{T}`) + conf := Config{Importer: importer.Default()} + _, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil) // do not crash + want := "undeclared name: T" + if err == nil || !strings.Contains(err.Error(), want) { + t.Errorf("got: %v; want: %s", err, want) + } +} + +func TestIssue5849(t *testing.T) { + src := ` +package p +var ( + s uint + _ = uint8(8) + _ = uint16(16) << s + _ = uint32(32 << s) + _ = uint64(64 << s + s) + _ = (interface{})("foo") + _ = (interface{})(nil) +)` + f := mustParse(t, src) + + var conf Config + types := make(map[ast.Expr]TypeAndValue) + _, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Types: types}) + if err != nil { + t.Fatal(err) + } + + for x, tv := range types { + var want Type + switch x := x.(type) { + case *ast.BasicLit: + switch x.Value { + case `8`: + want = Typ[Uint8] + case `16`: + want = Typ[Uint16] + case `32`: + want = Typ[Uint32] + case `64`: + want = Typ[Uint] // because of "+ s", s is of type uint + case `"foo"`: + want = Typ[String] + } + case *ast.Ident: + if x.Name == "nil" { + want = Typ[UntypedNil] + } + } + if want != nil && !Identical(tv.Type, want) { + t.Errorf("got %s; want %s", tv.Type, want) + } + } +} + +func TestIssue6413(t *testing.T) { + src := ` +package p +func f() int { + defer f() + go f() + return 0 +} +` + f := mustParse(t, src) + + var conf Config + types := make(map[ast.Expr]TypeAndValue) + _, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Types: types}) + if err != nil { + t.Fatal(err) + } + + want := Typ[Int] + n := 0 + for x, tv := range types { + if _, ok := x.(*ast.CallExpr); ok { + if tv.Type != want { + t.Errorf("%s: got %s; want %s", fset.Position(x.Pos()), tv.Type, want) + } + n++ + } + } + + if n != 2 { + t.Errorf("got %d CallExprs; want 2", n) + } +} + +func TestIssue7245(t *testing.T) { + src := ` +package p +func (T) m() (res bool) { return } +type T struct{} // receiver type after method declaration +` + f := mustParse(t, src) + + var conf Config + defs := make(map[*ast.Ident]Object) + _, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Defs: defs}) + if err != nil { + t.Fatal(err) + } + + m := f.Decls[0].(*ast.FuncDecl) + res1 := defs[m.Name].(*Func).Type().(*Signature).Results().At(0) + res2 := defs[m.Type.Results.List[0].Names[0]].(*Var) + + if res1 != res2 { + t.Errorf("got %s (%p) != %s (%p)", res1, res2, res1, res2) + } +} + +// This tests that uses of existing vars on the LHS of an assignment +// are Uses, not Defs; and also that the (illegal) use of a non-var on +// the LHS of an assignment is a Use nonetheless. +func TestIssue7827(t *testing.T) { + const src = ` +package p +func _() { + const w = 1 // defs w + x, y := 2, 3 // defs x, y + w, x, z := 4, 5, 6 // uses w, x, defs z; error: cannot assign to w + _, _, _ = x, y, z // uses x, y, z +} +` + f := mustParse(t, src) + + const want = `L3 defs func p._() +L4 defs const w untyped int +L5 defs var x int +L5 defs var y int +L6 defs var z int +L6 uses const w untyped int +L6 uses var x int +L7 uses var x int +L7 uses var y int +L7 uses var z int` + + // don't abort at the first error + conf := Config{Error: func(err error) { t.Log(err) }} + defs := make(map[*ast.Ident]Object) + uses := make(map[*ast.Ident]Object) + _, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Defs: defs, Uses: uses}) + if s := fmt.Sprint(err); !strings.HasSuffix(s, "cannot assign to w") { + t.Errorf("Check: unexpected error: %s", s) + } + + var facts []string + for id, obj := range defs { + if obj != nil { + fact := fmt.Sprintf("L%d defs %s", fset.Position(id.Pos()).Line, obj) + facts = append(facts, fact) + } + } + for id, obj := range uses { + fact := fmt.Sprintf("L%d uses %s", fset.Position(id.Pos()).Line, obj) + facts = append(facts, fact) + } + sort.Strings(facts) + + got := strings.Join(facts, "\n") + if got != want { + t.Errorf("Unexpected defs/uses\ngot:\n%s\nwant:\n%s", got, want) + } +} + +// This tests that the package associated with the types.Object.Pkg method +// is the type's package independent of the order in which the imports are +// listed in the sources src1, src2 below. +// The actual issue is in go/internal/gcimporter which has a corresponding +// test; we leave this test here to verify correct behavior at the go/types +// level. +func TestIssue13898(t *testing.T) { + testenv.MustHaveGoBuild(t) + + const src0 = ` +package main + +import "go/types" + +func main() { + var info types.Info + for _, obj := range info.Uses { + _ = obj.Pkg() + } +} +` + // like src0, but also imports go/importer + const src1 = ` +package main + +import ( + "go/types" + _ "go/importer" +) + +func main() { + var info types.Info + for _, obj := range info.Uses { + _ = obj.Pkg() + } +} +` + // like src1 but with different import order + // (used to fail with this issue) + const src2 = ` +package main + +import ( + _ "go/importer" + "go/types" +) + +func main() { + var info types.Info + for _, obj := range info.Uses { + _ = obj.Pkg() + } +} +` + f := func(test, src string) { + f := mustParse(t, src) + cfg := Config{Importer: importer.Default()} + info := Info{Uses: make(map[*ast.Ident]Object)} + _, err := cfg.Check("main", fset, []*ast.File{f}, &info) + if err != nil { + t.Fatal(err) + } + + var pkg *Package + count := 0 + for id, obj := range info.Uses { + if id.Name == "Pkg" { + pkg = obj.Pkg() + count++ + } + } + if count != 1 { + t.Fatalf("%s: got %d entries named Pkg; want 1", test, count) + } + if pkg.Name() != "types" { + t.Fatalf("%s: got %v; want package types", test, pkg) + } + } + + f("src0", src0) + f("src1", src1) + f("src2", src2) +} + +func TestIssue22525(t *testing.T) { + f := mustParse(t, `package p; func f() { var a, b, c, d, e int }`) + + got := "\n" + conf := Config{Error: func(err error) { got += err.Error() + "\n" }} + conf.Check(f.Name.Name, fset, []*ast.File{f}, nil) // do not crash + want := ` +1:27: a declared but not used +1:30: b declared but not used +1:33: c declared but not used +1:36: d declared but not used +1:39: e declared but not used +` + if got != want { + t.Errorf("got: %swant: %s", got, want) + } +} + +func TestIssue25627(t *testing.T) { + const prefix = `package p; import "unsafe"; type P *struct{}; type I interface{}; type T ` + // The src strings (without prefix) are constructed such that the number of semicolons + // plus one corresponds to the number of fields expected in the respective struct. + for _, src := range []string{ + `struct { x Missing }`, + `struct { Missing }`, + `struct { *Missing }`, + `struct { unsafe.Pointer }`, + `struct { P }`, + `struct { *I }`, + `struct { a int; b Missing; *Missing }`, + } { + f := mustParse(t, prefix+src) + + cfg := Config{Importer: importer.Default(), Error: func(err error) {}} + info := &Info{Types: make(map[ast.Expr]TypeAndValue)} + _, err := cfg.Check(f.Name.Name, fset, []*ast.File{f}, info) + if err != nil { + if _, ok := err.(Error); !ok { + t.Fatal(err) + } + } + + ast.Inspect(f, func(n ast.Node) bool { + if spec, _ := n.(*ast.TypeSpec); spec != nil { + if tv, ok := info.Types[spec.Type]; ok && spec.Name.Name == "T" { + want := strings.Count(src, ";") + 1 + if got := tv.Type.(*Struct).NumFields(); got != want { + t.Errorf("%s: got %d fields; want %d", src, got, want) + } + } + } + return true + }) + } +} + +func TestIssue28005(t *testing.T) { + // method names must match defining interface name for this test + // (see last comment in this function) + sources := [...]string{ + "package p; type A interface{ A() }", + "package p; type B interface{ B() }", + "package p; type X interface{ A; B }", + } + + // compute original file ASTs + var orig [len(sources)]*ast.File + for i, src := range sources { + orig[i] = mustParse(t, src) + } + + // run the test for all order permutations of the incoming files + for _, perm := range [][len(sources)]int{ + {0, 1, 2}, + {0, 2, 1}, + {1, 0, 2}, + {1, 2, 0}, + {2, 0, 1}, + {2, 1, 0}, + } { + // create file order permutation + files := make([]*ast.File, len(sources)) + for i := range perm { + files[i] = orig[perm[i]] + } + + // type-check package with given file order permutation + var conf Config + info := &Info{Defs: make(map[*ast.Ident]Object)} + _, err := conf.Check("", fset, files, info) + if err != nil { + t.Fatal(err) + } + + // look for interface object X + var obj Object + for name, def := range info.Defs { + if name.Name == "X" { + obj = def + break + } + } + if obj == nil { + t.Fatal("interface not found") + } + iface := obj.Type().Underlying().(*Interface) // I must be an interface + + // Each iface method m is embedded; and m's receiver base type name + // must match the method's name per the choice in the source file. + for i := 0; i < iface.NumMethods(); i++ { + m := iface.Method(i) + recvName := m.Type().(*Signature).Recv().Type().(*Named).Obj().Name() + if recvName != m.Name() { + t.Errorf("perm %v: got recv %s; want %s", perm, recvName, m.Name()) + } + } + } +} + +func TestIssue28282(t *testing.T) { + // create type interface { error } + et := Universe.Lookup("error").Type() + it := NewInterfaceType(nil, []Type{et}) + it.Complete() + // verify that after completing the interface, the embedded method remains unchanged + want := et.Underlying().(*Interface).Method(0) + got := it.Method(0) + if got != want { + t.Fatalf("%s.Method(0): got %q (%p); want %q (%p)", it, got, got, want, want) + } + // verify that lookup finds the same method in both interfaces (redundant check) + obj, _, _ := LookupFieldOrMethod(et, false, nil, "Error") + if obj != want { + t.Fatalf("%s.Lookup: got %q (%p); want %q (%p)", et, obj, obj, want, want) + } + obj, _, _ = LookupFieldOrMethod(it, false, nil, "Error") + if obj != want { + t.Fatalf("%s.Lookup: got %q (%p); want %q (%p)", it, obj, obj, want, want) + } +} + +func TestIssue29029(t *testing.T) { + f1 := mustParse(t, `package p; type A interface { M() }`) + f2 := mustParse(t, `package p; var B interface { A }`) + + // printInfo prints the *Func definitions recorded in info, one *Func per line. + printInfo := func(info *Info) string { + var buf bytes.Buffer + for _, obj := range info.Defs { + if fn, ok := obj.(*Func); ok { + fmt.Fprintln(&buf, fn) + } + } + return buf.String() + } + + // The *Func (method) definitions for package p must be the same + // independent on whether f1 and f2 are type-checked together, or + // incrementally. + + // type-check together + var conf Config + info := &Info{Defs: make(map[*ast.Ident]Object)} + check := NewChecker(&conf, fset, NewPackage("", "p"), info) + if err := check.Files([]*ast.File{f1, f2}); err != nil { + t.Fatal(err) + } + want := printInfo(info) + + // type-check incrementally + info = &Info{Defs: make(map[*ast.Ident]Object)} + check = NewChecker(&conf, fset, NewPackage("", "p"), info) + if err := check.Files([]*ast.File{f1}); err != nil { + t.Fatal(err) + } + if err := check.Files([]*ast.File{f2}); err != nil { + t.Fatal(err) + } + got := printInfo(info) + + if got != want { + t.Errorf("\ngot : %swant: %s", got, want) + } +} + +func TestIssue34151(t *testing.T) { + const asrc = `package a; type I interface{ M() }; type T struct { F interface { I } }` + const bsrc = `package b; import "a"; type T struct { F interface { a.I } }; var _ = a.T(T{})` + + a, err := pkgFor("a", asrc, nil) + if err != nil { + t.Fatalf("package %s failed to typecheck: %v", a.Name(), err) + } + + bast := mustParse(t, bsrc) + conf := Config{Importer: importHelper{a}} + b, err := conf.Check(bast.Name.Name, fset, []*ast.File{bast}, nil) + if err != nil { + t.Errorf("package %s failed to typecheck: %v", b.Name(), err) + } +} + +type importHelper struct { + pkg *Package +} + +func (h importHelper) Import(path string) (*Package, error) { + if path != h.pkg.Path() { + return nil, fmt.Errorf("got package path %q; want %q", path, h.pkg.Path()) + } + return h.pkg, nil +} + +// TestIssue34921 verifies that we don't update an imported type's underlying +// type when resolving an underlying type. Specifically, when determining the +// underlying type of b.T (which is the underlying type of a.T, which is int) +// we must not set the underlying type of a.T again since that would lead to +// a race condition if package b is imported elsewhere, in a package that is +// concurrently type-checked. +func TestIssue34921(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Error(r) + } + }() + + var sources = []string{ + `package a; type T int`, + `package b; import "a"; type T a.T`, + } + + var pkg *Package + for _, src := range sources { + f := mustParse(t, src) + conf := Config{Importer: importHelper{pkg}} + res, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil) + if err != nil { + t.Errorf("%q failed to typecheck: %v", src, err) + } + pkg = res // res is imported by the next package in this test + } +} + +func TestIssue43088(t *testing.T) { + // type T1 struct { + // _ T2 + // } + // + // type T2 struct { + // _ struct { + // _ T2 + // } + // } + n1 := NewTypeName(token.NoPos, nil, "T1", nil) + T1 := NewNamed(n1, nil, nil) + n2 := NewTypeName(token.NoPos, nil, "T2", nil) + T2 := NewNamed(n2, nil, nil) + s1 := NewStruct([]*Var{NewField(token.NoPos, nil, "_", T2, false)}, nil) + T1.SetUnderlying(s1) + s2 := NewStruct([]*Var{NewField(token.NoPos, nil, "_", T2, false)}, nil) + s3 := NewStruct([]*Var{NewField(token.NoPos, nil, "_", s2, false)}, nil) + T2.SetUnderlying(s3) + + // These calls must terminate (no endless recursion). + Comparable(T1) + Comparable(T2) +} diff --git a/src/go/types/labels.go b/src/go/types/labels.go new file mode 100644 index 0000000..8cf6e63 --- /dev/null +++ b/src/go/types/labels.go @@ -0,0 +1,272 @@ +// Copyright 2013 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 types + +import ( + "go/ast" + "go/token" +) + +// labels checks correct label use in body. +func (check *Checker) labels(body *ast.BlockStmt) { + // set of all labels in this body + all := NewScope(nil, body.Pos(), body.End(), "label") + + fwdJumps := check.blockBranches(all, nil, nil, body.List) + + // If there are any forward jumps left, no label was found for + // the corresponding goto statements. Either those labels were + // never defined, or they are inside blocks and not reachable + // for the respective gotos. + for _, jmp := range fwdJumps { + var msg string + var code errorCode + name := jmp.Label.Name + if alt := all.Lookup(name); alt != nil { + msg = "goto %s jumps into block" + alt.(*Label).used = true // avoid another error + code = _JumpIntoBlock + } else { + msg = "label %s not declared" + code = _UndeclaredLabel + } + check.errorf(jmp.Label, code, msg, name) + } + + // spec: "It is illegal to define a label that is never used." + for _, obj := range all.elems { + if lbl := obj.(*Label); !lbl.used { + check.softErrorf(lbl, _UnusedLabel, "label %s declared but not used", lbl.name) + } + } +} + +// A block tracks label declarations in a block and its enclosing blocks. +type block struct { + parent *block // enclosing block + lstmt *ast.LabeledStmt // labeled statement to which this block belongs, or nil + labels map[string]*ast.LabeledStmt // allocated lazily +} + +// insert records a new label declaration for the current block. +// The label must not have been declared before in any block. +func (b *block) insert(s *ast.LabeledStmt) { + name := s.Label.Name + if debug { + assert(b.gotoTarget(name) == nil) + } + labels := b.labels + if labels == nil { + labels = make(map[string]*ast.LabeledStmt) + b.labels = labels + } + labels[name] = s +} + +// gotoTarget returns the labeled statement in the current +// or an enclosing block with the given label name, or nil. +func (b *block) gotoTarget(name string) *ast.LabeledStmt { + for s := b; s != nil; s = s.parent { + if t := s.labels[name]; t != nil { + return t + } + } + return nil +} + +// enclosingTarget returns the innermost enclosing labeled +// statement with the given label name, or nil. +func (b *block) enclosingTarget(name string) *ast.LabeledStmt { + for s := b; s != nil; s = s.parent { + if t := s.lstmt; t != nil && t.Label.Name == name { + return t + } + } + return nil +} + +// blockBranches processes a block's statement list and returns the set of outgoing forward jumps. +// all is the scope of all declared labels, parent the set of labels declared in the immediately +// enclosing block, and lstmt is the labeled statement this block is associated with (or nil). +func (check *Checker) blockBranches(all *Scope, parent *block, lstmt *ast.LabeledStmt, list []ast.Stmt) []*ast.BranchStmt { + b := &block{parent: parent, lstmt: lstmt} + + var ( + varDeclPos token.Pos + fwdJumps, badJumps []*ast.BranchStmt + ) + + // All forward jumps jumping over a variable declaration are possibly + // invalid (they may still jump out of the block and be ok). + // recordVarDecl records them for the given position. + recordVarDecl := func(pos token.Pos) { + varDeclPos = pos + badJumps = append(badJumps[:0], fwdJumps...) // copy fwdJumps to badJumps + } + + jumpsOverVarDecl := func(jmp *ast.BranchStmt) bool { + if varDeclPos.IsValid() { + for _, bad := range badJumps { + if jmp == bad { + return true + } + } + } + return false + } + + blockBranches := func(lstmt *ast.LabeledStmt, list []ast.Stmt) { + // Unresolved forward jumps inside the nested block + // become forward jumps in the current block. + fwdJumps = append(fwdJumps, check.blockBranches(all, b, lstmt, list)...) + } + + var stmtBranches func(ast.Stmt) + stmtBranches = func(s ast.Stmt) { + switch s := s.(type) { + case *ast.DeclStmt: + if d, _ := s.Decl.(*ast.GenDecl); d != nil && d.Tok == token.VAR { + recordVarDecl(d.Pos()) + } + + case *ast.LabeledStmt: + // declare non-blank label + if name := s.Label.Name; name != "_" { + lbl := NewLabel(s.Label.Pos(), check.pkg, name) + if alt := all.Insert(lbl); alt != nil { + check.softErrorf(lbl, _DuplicateLabel, "label %s already declared", name) + check.reportAltDecl(alt) + // ok to continue + } else { + b.insert(s) + check.recordDef(s.Label, lbl) + } + // resolve matching forward jumps and remove them from fwdJumps + i := 0 + for _, jmp := range fwdJumps { + if jmp.Label.Name == name { + // match + lbl.used = true + check.recordUse(jmp.Label, lbl) + if jumpsOverVarDecl(jmp) { + check.softErrorf( + jmp.Label, + _JumpOverDecl, + "goto %s jumps over variable declaration at line %d", + name, + check.fset.Position(varDeclPos).Line, + ) + // ok to continue + } + } else { + // no match - record new forward jump + fwdJumps[i] = jmp + i++ + } + } + fwdJumps = fwdJumps[:i] + lstmt = s + } + stmtBranches(s.Stmt) + + case *ast.BranchStmt: + if s.Label == nil { + return // checked in 1st pass (check.stmt) + } + + // determine and validate target + name := s.Label.Name + switch s.Tok { + case token.BREAK: + // spec: "If there is a label, it must be that of an enclosing + // "for", "switch", or "select" statement, and that is the one + // whose execution terminates." + valid := false + if t := b.enclosingTarget(name); t != nil { + switch t.Stmt.(type) { + case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.ForStmt, *ast.RangeStmt: + valid = true + } + } + if !valid { + check.errorf(s.Label, _MisplacedLabel, "invalid break label %s", name) + return + } + + case token.CONTINUE: + // spec: "If there is a label, it must be that of an enclosing + // "for" statement, and that is the one whose execution advances." + valid := false + if t := b.enclosingTarget(name); t != nil { + switch t.Stmt.(type) { + case *ast.ForStmt, *ast.RangeStmt: + valid = true + } + } + if !valid { + check.errorf(s.Label, _MisplacedLabel, "invalid continue label %s", name) + return + } + + case token.GOTO: + if b.gotoTarget(name) == nil { + // label may be declared later - add branch to forward jumps + fwdJumps = append(fwdJumps, s) + return + } + + default: + check.invalidAST(s, "branch statement: %s %s", s.Tok, name) + return + } + + // record label use + obj := all.Lookup(name) + obj.(*Label).used = true + check.recordUse(s.Label, obj) + + case *ast.AssignStmt: + if s.Tok == token.DEFINE { + recordVarDecl(s.Pos()) + } + + case *ast.BlockStmt: + blockBranches(lstmt, s.List) + + case *ast.IfStmt: + stmtBranches(s.Body) + if s.Else != nil { + stmtBranches(s.Else) + } + + case *ast.CaseClause: + blockBranches(nil, s.Body) + + case *ast.SwitchStmt: + stmtBranches(s.Body) + + case *ast.TypeSwitchStmt: + stmtBranches(s.Body) + + case *ast.CommClause: + blockBranches(nil, s.Body) + + case *ast.SelectStmt: + stmtBranches(s.Body) + + case *ast.ForStmt: + stmtBranches(s.Body) + + case *ast.RangeStmt: + stmtBranches(s.Body) + } + } + + for _, s := range list { + stmtBranches(s) + } + + return fwdJumps +} diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go new file mode 100644 index 0000000..3c9ff18 --- /dev/null +++ b/src/go/types/lookup.go @@ -0,0 +1,400 @@ +// Copyright 2013 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. + +// This file implements various field and method lookup functions. + +package types + +// LookupFieldOrMethod looks up a field or method with given package and name +// in T and returns the corresponding *Var or *Func, an index sequence, and a +// bool indicating if there were any pointer indirections on the path to the +// field or method. If addressable is set, T is the type of an addressable +// variable (only matters for method lookups). +// +// The last index entry is the field or method index in the (possibly embedded) +// type where the entry was found, either: +// +// 1) the list of declared methods of a named type; or +// 2) the list of all methods (method set) of an interface type; or +// 3) the list of fields of a struct type. +// +// The earlier index entries are the indices of the embedded struct fields +// traversed to get to the found entry, starting at depth 0. +// +// If no entry is found, a nil object is returned. In this case, the returned +// index and indirect values have the following meaning: +// +// - If index != nil, the index sequence points to an ambiguous entry +// (the same name appeared more than once at the same embedding level). +// +// - If indirect is set, a method with a pointer receiver type was found +// but there was no pointer on the path from the actual receiver type to +// the method's formal receiver base type, nor was the receiver addressable. +// +func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) { + return (*Checker)(nil).lookupFieldOrMethod(T, addressable, pkg, name) +} + +// Internal use of Checker.lookupFieldOrMethod: If the obj result is a method +// associated with a concrete (non-interface) type, the method's signature +// may not be fully set up. Call Checker.objDecl(obj, nil) before accessing +// the method's type. +// TODO(gri) Now that we provide the *Checker, we can probably remove this +// caveat by calling Checker.objDecl from lookupFieldOrMethod. Investigate. + +// lookupFieldOrMethod is like the external version but completes interfaces +// as necessary. +func (check *Checker) lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) { + // Methods cannot be associated to a named pointer type + // (spec: "The type denoted by T is called the receiver base type; + // it must not be a pointer or interface type and it must be declared + // in the same package as the method."). + // Thus, if we have a named pointer type, proceed with the underlying + // pointer type but discard the result if it is a method since we would + // not have found it for T (see also issue 8590). + if t, _ := T.(*Named); t != nil { + if p, _ := t.underlying.(*Pointer); p != nil { + obj, index, indirect = check.rawLookupFieldOrMethod(p, false, pkg, name) + if _, ok := obj.(*Func); ok { + return nil, nil, false + } + return + } + } + + return check.rawLookupFieldOrMethod(T, addressable, pkg, name) +} + +// TODO(gri) The named type consolidation and seen maps below must be +// indexed by unique keys for a given type. Verify that named +// types always have only one representation (even when imported +// indirectly via different packages.) + +// rawLookupFieldOrMethod should only be called by lookupFieldOrMethod and missingMethod. +func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) { + // WARNING: The code in this function is extremely subtle - do not modify casually! + // This function and NewMethodSet should be kept in sync. + + if name == "_" { + return // blank fields/methods are never found + } + + typ, isPtr := deref(T) + + // *typ where typ is an interface has no methods. + if isPtr && IsInterface(typ) { + return + } + + // Start with typ as single entry at shallowest depth. + current := []embeddedType{{typ, nil, isPtr, false}} + + // Named types that we have seen already, allocated lazily. + // Used to avoid endless searches in case of recursive types. + // Since only Named types can be used for recursive types, we + // only need to track those. + // (If we ever allow type aliases to construct recursive types, + // we must use type identity rather than pointer equality for + // the map key comparison, as we do in consolidateMultiples.) + var seen map[*Named]bool + + // search current depth + for len(current) > 0 { + var next []embeddedType // embedded types found at current depth + + // look for (pkg, name) in all types at current depth + for _, e := range current { + typ := e.typ + + // If we have a named type, we may have associated methods. + // Look for those first. + if named, _ := typ.(*Named); named != nil { + if seen[named] { + // We have seen this type before, at a more shallow depth + // (note that multiples of this type at the current depth + // were consolidated before). The type at that depth shadows + // this same type at the current depth, so we can ignore + // this one. + continue + } + if seen == nil { + seen = make(map[*Named]bool) + } + seen[named] = true + + // look for a matching attached method + if i, m := lookupMethod(named.methods, pkg, name); m != nil { + // potential match + // caution: method may not have a proper signature yet + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + obj = m + indirect = e.indirect + continue // we can't have a matching field or interface method + } + + // continue with underlying type + typ = named.underlying + } + + switch t := typ.(type) { + case *Struct: + // look for a matching field and collect embedded types + for i, f := range t.fields { + if f.sameId(pkg, name) { + assert(f.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + obj = f + indirect = e.indirect + continue // we can't have a matching interface method + } + // Collect embedded struct fields for searching the next + // lower depth, but only if we have not seen a match yet + // (if we have a match it is either the desired field or + // we have a name collision on the same depth; in either + // case we don't need to look further). + // Embedded fields are always of the form T or *T where + // T is a type name. If e.typ appeared multiple times at + // this depth, f.typ appears multiple times at the next + // depth. + if obj == nil && f.embedded { + typ, isPtr := deref(f.typ) + // TODO(gri) optimization: ignore types that can't + // have fields or methods (only Named, Struct, and + // Interface types need to be considered). + next = append(next, embeddedType{typ, concat(e.index, i), e.indirect || isPtr, e.multiples}) + } + } + + case *Interface: + // look for a matching method + // TODO(gri) t.allMethods is sorted - use binary search + check.completeInterface(t) + if i, m := lookupMethod(t.allMethods, pkg, name); m != nil { + assert(m.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + obj = m + indirect = e.indirect + } + } + } + + if obj != nil { + // found a potential match + // spec: "A method call x.m() is valid if the method set of (the type of) x + // contains m and the argument list can be assigned to the parameter + // list of m. If x is addressable and &x's method set contains m, x.m() + // is shorthand for (&x).m()". + if f, _ := obj.(*Func); f != nil && ptrRecv(f) && !indirect && !addressable { + return nil, nil, true // pointer/addressable receiver required + } + return + } + + current = check.consolidateMultiples(next) + } + + return nil, nil, false // not found +} + +// embeddedType represents an embedded type +type embeddedType struct { + typ Type + index []int // embedded field indices, starting with index at depth 0 + indirect bool // if set, there was a pointer indirection on the path to this field + multiples bool // if set, typ appears multiple times at this depth +} + +// consolidateMultiples collects multiple list entries with the same type +// into a single entry marked as containing multiples. The result is the +// consolidated list. +func (check *Checker) consolidateMultiples(list []embeddedType) []embeddedType { + if len(list) <= 1 { + return list // at most one entry - nothing to do + } + + n := 0 // number of entries w/ unique type + prev := make(map[Type]int) // index at which type was previously seen + for _, e := range list { + if i, found := check.lookupType(prev, e.typ); found { + list[i].multiples = true + // ignore this entry + } else { + prev[e.typ] = n + list[n] = e + n++ + } + } + return list[:n] +} + +func (check *Checker) lookupType(m map[Type]int, typ Type) (int, bool) { + // fast path: maybe the types are equal + if i, found := m[typ]; found { + return i, true + } + + for t, i := range m { + if check.identical(t, typ) { + return i, true + } + } + + return 0, false +} + +// MissingMethod returns (nil, false) if V implements T, otherwise it +// returns a missing method required by T and whether it is missing or +// just has the wrong type. +// +// For non-interface types V, or if static is set, V implements T if all +// methods of T are present in V. Otherwise (V is an interface and static +// is not set), MissingMethod only checks that methods of T which are also +// present in V have matching types (e.g., for a type assertion x.(T) where +// x is of interface type V). +// +func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType bool) { + m, typ := (*Checker)(nil).missingMethod(V, T, static) + return m, typ != nil +} + +// missingMethod is like MissingMethod but accepts a receiver. +// The receiver may be nil if missingMethod is invoked through +// an exported API call (such as MissingMethod), i.e., when all +// methods have been type-checked. +// If the type has the correctly named method, but with the wrong +// signature, the existing method is returned as well. +// To improve error messages, also report the wrong signature +// when the method exists on *V instead of V. +func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, wrongType *Func) { + check.completeInterface(T) + + // fast path for common case + if T.Empty() { + return + } + + if ityp, _ := V.Underlying().(*Interface); ityp != nil { + check.completeInterface(ityp) + // TODO(gri) allMethods is sorted - can do this more efficiently + for _, m := range T.allMethods { + _, obj := lookupMethod(ityp.allMethods, m.pkg, m.name) + switch { + case obj == nil: + if static { + return m, nil + } + case !check.identical(obj.Type(), m.typ): + return m, obj + } + } + return + } + + // A concrete type implements T if it implements all methods of T. + for _, m := range T.allMethods { + obj, _, _ := check.rawLookupFieldOrMethod(V, false, m.pkg, m.name) + + // Check if *V implements this method of T. + if obj == nil { + ptr := NewPointer(V) + obj, _, _ = check.rawLookupFieldOrMethod(ptr, false, m.pkg, m.name) + if obj != nil { + return m, obj.(*Func) + } + } + + // we must have a method (not a field of matching function type) + f, _ := obj.(*Func) + if f == nil { + return m, nil + } + + // methods may not have a fully set up signature yet + if check != nil { + check.objDecl(f, nil) + } + + if !check.identical(f.typ, m.typ) { + return m, f + } + } + + return +} + +// assertableTo reports whether a value of type V can be asserted to have type T. +// It returns (nil, false) as affirmative answer. Otherwise it returns a missing +// method required by V and whether it is missing or just has the wrong type. +// The receiver may be nil if assertableTo is invoked through an exported API call +// (such as AssertableTo), i.e., when all methods have been type-checked. +func (check *Checker) assertableTo(V *Interface, T Type) (method, wrongType *Func) { + // no static check is required if T is an interface + // spec: "If T is an interface type, x.(T) asserts that the + // dynamic type of x implements the interface T." + if _, ok := T.Underlying().(*Interface); ok && !strict { + return + } + return check.missingMethod(T, V, false) +} + +// deref dereferences typ if it is a *Pointer and returns its base and true. +// Otherwise it returns (typ, false). +func deref(typ Type) (Type, bool) { + if p, _ := typ.(*Pointer); p != nil { + return p.base, true + } + return typ, false +} + +// derefStructPtr dereferences typ if it is a (named or unnamed) pointer to a +// (named or unnamed) struct and returns its base. Otherwise it returns typ. +func derefStructPtr(typ Type) Type { + if p, _ := typ.Underlying().(*Pointer); p != nil { + if _, ok := p.base.Underlying().(*Struct); ok { + return p.base + } + } + return typ +} + +// concat returns the result of concatenating list and i. +// The result does not share its underlying array with list. +func concat(list []int, i int) []int { + var t []int + t = append(t, list...) + return append(t, i) +} + +// fieldIndex returns the index for the field with matching package and name, or a value < 0. +func fieldIndex(fields []*Var, pkg *Package, name string) int { + if name != "_" { + for i, f := range fields { + if f.sameId(pkg, name) { + return i + } + } + } + return -1 +} + +// lookupMethod returns the index of and method with matching package and name, or (-1, nil). +func lookupMethod(methods []*Func, pkg *Package, name string) (int, *Func) { + if name != "_" { + for i, m := range methods { + if m.sameId(pkg, name) { + return i, m + } + } + } + return -1, nil +} diff --git a/src/go/types/methodset.go b/src/go/types/methodset.go new file mode 100644 index 0000000..c34d732 --- /dev/null +++ b/src/go/types/methodset.go @@ -0,0 +1,260 @@ +// Copyright 2013 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. + +// This file implements method sets. + +package types + +import ( + "fmt" + "sort" + "strings" +) + +// A MethodSet is an ordered set of concrete or abstract (interface) methods; +// a method is a MethodVal selection, and they are ordered by ascending m.Obj().Id(). +// The zero value for a MethodSet is a ready-to-use empty method set. +type MethodSet struct { + list []*Selection +} + +func (s *MethodSet) String() string { + if s.Len() == 0 { + return "MethodSet {}" + } + + var buf strings.Builder + fmt.Fprintln(&buf, "MethodSet {") + for _, f := range s.list { + fmt.Fprintf(&buf, "\t%s\n", f) + } + fmt.Fprintln(&buf, "}") + return buf.String() +} + +// Len returns the number of methods in s. +func (s *MethodSet) Len() int { return len(s.list) } + +// At returns the i'th method in s for 0 <= i < s.Len(). +func (s *MethodSet) At(i int) *Selection { return s.list[i] } + +// Lookup returns the method with matching package and name, or nil if not found. +func (s *MethodSet) Lookup(pkg *Package, name string) *Selection { + if s.Len() == 0 { + return nil + } + + key := Id(pkg, name) + i := sort.Search(len(s.list), func(i int) bool { + m := s.list[i] + return m.obj.Id() >= key + }) + if i < len(s.list) { + m := s.list[i] + if m.obj.Id() == key { + return m + } + } + return nil +} + +// Shared empty method set. +var emptyMethodSet MethodSet + +// Note: NewMethodSet is intended for external use only as it +// requires interfaces to be complete. If may be used +// internally if LookupFieldOrMethod completed the same +// interfaces beforehand. + +// NewMethodSet returns the method set for the given type T. +// It always returns a non-nil method set, even if it is empty. +func NewMethodSet(T Type) *MethodSet { + // WARNING: The code in this function is extremely subtle - do not modify casually! + // This function and lookupFieldOrMethod should be kept in sync. + + // method set up to the current depth, allocated lazily + var base methodSet + + typ, isPtr := deref(T) + + // *typ where typ is an interface has no methods. + if isPtr && IsInterface(typ) { + return &emptyMethodSet + } + + // Start with typ as single entry at shallowest depth. + current := []embeddedType{{typ, nil, isPtr, false}} + + // Named types that we have seen already, allocated lazily. + // Used to avoid endless searches in case of recursive types. + // Since only Named types can be used for recursive types, we + // only need to track those. + // (If we ever allow type aliases to construct recursive types, + // we must use type identity rather than pointer equality for + // the map key comparison, as we do in consolidateMultiples.) + var seen map[*Named]bool + + // collect methods at current depth + for len(current) > 0 { + var next []embeddedType // embedded types found at current depth + + // field and method sets at current depth, indexed by names (Id's), and allocated lazily + var fset map[string]bool // we only care about the field names + var mset methodSet + + for _, e := range current { + typ := e.typ + + // If we have a named type, we may have associated methods. + // Look for those first. + if named, _ := typ.(*Named); named != nil { + if seen[named] { + // We have seen this type before, at a more shallow depth + // (note that multiples of this type at the current depth + // were consolidated before). The type at that depth shadows + // this same type at the current depth, so we can ignore + // this one. + continue + } + if seen == nil { + seen = make(map[*Named]bool) + } + seen[named] = true + + mset = mset.add(named.methods, e.index, e.indirect, e.multiples) + + // continue with underlying type + typ = named.underlying + } + + switch t := typ.(type) { + case *Struct: + for i, f := range t.fields { + if fset == nil { + fset = make(map[string]bool) + } + fset[f.Id()] = true + + // Embedded fields are always of the form T or *T where + // T is a type name. If typ appeared multiple times at + // this depth, f.Type appears multiple times at the next + // depth. + if f.embedded { + typ, isPtr := deref(f.typ) + // TODO(gri) optimization: ignore types that can't + // have fields or methods (only Named, Struct, and + // Interface types need to be considered). + next = append(next, embeddedType{typ, concat(e.index, i), e.indirect || isPtr, e.multiples}) + } + } + + case *Interface: + mset = mset.add(t.allMethods, e.index, true, e.multiples) + } + } + + // Add methods and collisions at this depth to base if no entries with matching + // names exist already. + for k, m := range mset { + if _, found := base[k]; !found { + // Fields collide with methods of the same name at this depth. + if fset[k] { + m = nil // collision + } + if base == nil { + base = make(methodSet) + } + base[k] = m + } + } + + // Add all (remaining) fields at this depth as collisions (since they will + // hide any method further down) if no entries with matching names exist already. + for k := range fset { + if _, found := base[k]; !found { + if base == nil { + base = make(methodSet) + } + base[k] = nil // collision + } + } + + // It's ok to call consolidateMultiples with a nil *Checker because + // MethodSets are not used internally (outside debug mode). When used + // externally, interfaces are expected to be completed and then we do + // not need a *Checker to complete them when (indirectly) calling + // Checker.identical via consolidateMultiples. + current = (*Checker)(nil).consolidateMultiples(next) + } + + if len(base) == 0 { + return &emptyMethodSet + } + + // collect methods + var list []*Selection + for _, m := range base { + if m != nil { + m.recv = T + list = append(list, m) + } + } + // sort by unique name + sort.Slice(list, func(i, j int) bool { + return list[i].obj.Id() < list[j].obj.Id() + }) + return &MethodSet{list} +} + +// A methodSet is a set of methods and name collisions. +// A collision indicates that multiple methods with the +// same unique id, or a field with that id appeared. +type methodSet map[string]*Selection // a nil entry indicates a name collision + +// Add adds all functions in list to the method set s. +// If multiples is set, every function in list appears multiple times +// and is treated as a collision. +func (s methodSet) add(list []*Func, index []int, indirect bool, multiples bool) methodSet { + if len(list) == 0 { + return s + } + if s == nil { + s = make(methodSet) + } + for i, f := range list { + key := f.Id() + // if f is not in the set, add it + if !multiples { + // TODO(gri) A found method may not be added because it's not in the method set + // (!indirect && ptrRecv(f)). A 2nd method on the same level may be in the method + // set and may not collide with the first one, thus leading to a false positive. + // Is that possible? Investigate. + if _, found := s[key]; !found && (indirect || !ptrRecv(f)) { + s[key] = &Selection{MethodVal, nil, f, concat(index, i), indirect} + continue + } + } + s[key] = nil // collision + } + return s +} + +// ptrRecv reports whether the receiver is of the form *T. +func ptrRecv(f *Func) bool { + // If a method's receiver type is set, use that as the source of truth for the receiver. + // Caution: Checker.funcDecl (decl.go) marks a function by setting its type to an empty + // signature. We may reach here before the signature is fully set up: we must explicitly + // check if the receiver is set (we cannot just look for non-nil f.typ). + if sig, _ := f.typ.(*Signature); sig != nil && sig.recv != nil { + _, isPtr := deref(sig.recv.typ) + return isPtr + } + + // If a method's type is not set it may be a method/function that is: + // 1) client-supplied (via NewFunc with no signature), or + // 2) internally created but not yet type-checked. + // For case 1) we can't do anything; the client must know what they are doing. + // For case 2) we can use the information gathered by the resolver. + return f.hasPtrRecv +} diff --git a/src/go/types/object.go b/src/go/types/object.go new file mode 100644 index 0000000..374b24d --- /dev/null +++ b/src/go/types/object.go @@ -0,0 +1,486 @@ +// Copyright 2013 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 types + +import ( + "bytes" + "fmt" + "go/constant" + "go/token" +) + +// An Object describes a named language entity such as a package, +// constant, type, variable, function (incl. methods), or label. +// All objects implement the Object interface. +// +type Object interface { + Parent() *Scope // scope in which this object is declared; nil for methods and struct fields + Pos() token.Pos // position of object identifier in declaration + Pkg() *Package // package to which this object belongs; nil for labels and objects in the Universe scope + Name() string // package local object name + Type() Type // object type + Exported() bool // reports whether the name starts with a capital letter + Id() string // object name if exported, qualified name if not exported (see func Id) + + // String returns a human-readable string of the object. + String() string + + // order reflects a package-level object's source order: if object + // a is before object b in the source, then a.order() < b.order(). + // order returns a value > 0 for package-level objects; it returns + // 0 for all other objects (including objects in file scopes). + order() uint32 + + // color returns the object's color. + color() color + + // setOrder sets the order number of the object. It must be > 0. + setOrder(uint32) + + // setColor sets the object's color. It must not be white. + setColor(color color) + + // setParent sets the parent scope of the object. + setParent(*Scope) + + // sameId reports whether obj.Id() and Id(pkg, name) are the same. + sameId(pkg *Package, name string) bool + + // scopePos returns the start position of the scope of this Object + scopePos() token.Pos + + // setScopePos sets the start position of the scope for this Object. + setScopePos(pos token.Pos) +} + +// Id returns name if it is exported, otherwise it +// returns the name qualified with the package path. +func Id(pkg *Package, name string) string { + if token.IsExported(name) { + return name + } + // unexported names need the package path for differentiation + // (if there's no package, make sure we don't start with '.' + // as that may change the order of methods between a setup + // inside a package and outside a package - which breaks some + // tests) + path := "_" + // pkg is nil for objects in Universe scope and possibly types + // introduced via Eval (see also comment in object.sameId) + if pkg != nil && pkg.path != "" { + path = pkg.path + } + return path + "." + name +} + +// An object implements the common parts of an Object. +type object struct { + parent *Scope + pos token.Pos + pkg *Package + name string + typ Type + order_ uint32 + color_ color + scopePos_ token.Pos +} + +// color encodes the color of an object (see Checker.objDecl for details). +type color uint32 + +// An object may be painted in one of three colors. +// Color values other than white or black are considered grey. +const ( + white color = iota + black + grey // must be > white and black +) + +func (c color) String() string { + switch c { + case white: + return "white" + case black: + return "black" + default: + return "grey" + } +} + +// colorFor returns the (initial) color for an object depending on +// whether its type t is known or not. +func colorFor(t Type) color { + if t != nil { + return black + } + return white +} + +// Parent returns the scope in which the object is declared. +// The result is nil for methods and struct fields. +func (obj *object) Parent() *Scope { return obj.parent } + +// Pos returns the declaration position of the object's identifier. +func (obj *object) Pos() token.Pos { return obj.pos } + +// Pkg returns the package to which the object belongs. +// The result is nil for labels and objects in the Universe scope. +func (obj *object) Pkg() *Package { return obj.pkg } + +// Name returns the object's (package-local, unqualified) name. +func (obj *object) Name() string { return obj.name } + +// Type returns the object's type. +func (obj *object) Type() Type { return obj.typ } + +// Exported reports whether the object is exported (starts with a capital letter). +// It doesn't take into account whether the object is in a local (function) scope +// or not. +func (obj *object) Exported() bool { return token.IsExported(obj.name) } + +// Id is a wrapper for Id(obj.Pkg(), obj.Name()). +func (obj *object) Id() string { return Id(obj.pkg, obj.name) } + +func (obj *object) String() string { panic("abstract") } +func (obj *object) order() uint32 { return obj.order_ } +func (obj *object) color() color { return obj.color_ } +func (obj *object) scopePos() token.Pos { return obj.scopePos_ } + +func (obj *object) setParent(parent *Scope) { obj.parent = parent } +func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order } +func (obj *object) setColor(color color) { assert(color != white); obj.color_ = color } +func (obj *object) setScopePos(pos token.Pos) { obj.scopePos_ = pos } + +func (obj *object) sameId(pkg *Package, name string) bool { + // spec: + // "Two identifiers are different if they are spelled differently, + // or if they appear in different packages and are not exported. + // Otherwise, they are the same." + if name != obj.name { + return false + } + // obj.Name == name + if obj.Exported() { + return true + } + // not exported, so packages must be the same (pkg == nil for + // fields in Universe scope; this can only happen for types + // introduced via Eval) + if pkg == nil || obj.pkg == nil { + return pkg == obj.pkg + } + // pkg != nil && obj.pkg != nil + return pkg.path == obj.pkg.path +} + +// A PkgName represents an imported Go package. +// PkgNames don't have a type. +type PkgName struct { + object + imported *Package + used bool // set if the package was used +} + +// NewPkgName returns a new PkgName object representing an imported package. +// The remaining arguments set the attributes found with all Objects. +func NewPkgName(pos token.Pos, pkg *Package, name string, imported *Package) *PkgName { + return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, token.NoPos}, imported, false} +} + +// Imported returns the package that was imported. +// It is distinct from Pkg(), which is the package containing the import statement. +func (obj *PkgName) Imported() *Package { return obj.imported } + +// A Const represents a declared constant. +type Const struct { + object + val constant.Value +} + +// NewConst returns a new constant with value val. +// The remaining arguments set the attributes found with all Objects. +func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const { + return &Const{object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, val} +} + +// Val returns the constant's value. +func (obj *Const) Val() constant.Value { return obj.val } + +func (*Const) isDependency() {} // a constant may be a dependency of an initialization expression + +// A TypeName represents a name for a (defined or alias) type. +type TypeName struct { + object +} + +// NewTypeName returns a new type name denoting the given typ. +// The remaining arguments set the attributes found with all Objects. +// +// The typ argument may be a defined (Named) type or an alias type. +// It may also be nil such that the returned TypeName can be used as +// argument for NewNamed, which will set the TypeName's type as a side- +// effect. +func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName { + return &TypeName{object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}} +} + +// IsAlias reports whether obj is an alias name for a type. +func (obj *TypeName) IsAlias() bool { + switch t := obj.typ.(type) { + case nil: + return false + case *Basic: + // unsafe.Pointer is not an alias. + if obj.pkg == Unsafe { + return false + } + // Any user-defined type name for a basic type is an alias for a + // basic type (because basic types are pre-declared in the Universe + // scope, outside any package scope), and so is any type name with + // a different name than the name of the basic type it refers to. + // Additionally, we need to look for "byte" and "rune" because they + // are aliases but have the same names (for better error messages). + return obj.pkg != nil || t.name != obj.name || t == universeByte || t == universeRune + case *Named: + return obj != t.obj + default: + return true + } +} + +// A Variable represents a declared variable (including function parameters and results, and struct fields). +type Var struct { + object + embedded bool // if set, the variable is an embedded struct field, and name is the type name + isField bool // var is struct field + used bool // set if the variable was used +} + +// NewVar returns a new variable. +// The arguments set the attributes found with all Objects. +func NewVar(pos token.Pos, pkg *Package, name string, typ Type) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}} +} + +// NewParam returns a new variable representing a function parameter. +func NewParam(pos token.Pos, pkg *Package, name string, typ Type) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, used: true} // parameters are always 'used' +} + +// NewField returns a new variable representing a struct field. +// For embedded fields, the name is the unqualified type name +/// under which the field is accessible. +func NewField(pos token.Pos, pkg *Package, name string, typ Type, embedded bool) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, embedded: embedded, isField: true} +} + +// Anonymous reports whether the variable is an embedded field. +// Same as Embedded; only present for backward-compatibility. +func (obj *Var) Anonymous() bool { return obj.embedded } + +// Embedded reports whether the variable is an embedded field. +func (obj *Var) Embedded() bool { return obj.embedded } + +// IsField reports whether the variable is a struct field. +func (obj *Var) IsField() bool { return obj.isField } + +func (*Var) isDependency() {} // a variable may be a dependency of an initialization expression + +// A Func represents a declared function, concrete method, or abstract +// (interface) method. Its Type() is always a *Signature. +// An abstract method may belong to many interfaces due to embedding. +type Func struct { + object + hasPtrRecv bool // only valid for methods that don't have a type yet +} + +// NewFunc returns a new function with the given signature, representing +// the function's type. +func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func { + // don't store a nil signature + var typ Type + if sig != nil { + typ = sig + } + return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, false} +} + +// FullName returns the package- or receiver-type-qualified name of +// function or method obj. +func (obj *Func) FullName() string { + var buf bytes.Buffer + writeFuncName(&buf, obj, nil) + return buf.String() +} + +// Scope returns the scope of the function's body block. +func (obj *Func) Scope() *Scope { return obj.typ.(*Signature).scope } + +func (*Func) isDependency() {} // a function may be a dependency of an initialization expression + +// A Label represents a declared label. +// Labels don't have a type. +type Label struct { + object + used bool // set if the label was used +} + +// NewLabel returns a new label. +func NewLabel(pos token.Pos, pkg *Package, name string) *Label { + return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid], color_: black}, false} +} + +// A Builtin represents a built-in function. +// Builtins don't have a valid type. +type Builtin struct { + object + id builtinId +} + +func newBuiltin(id builtinId) *Builtin { + return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid], color_: black}, id} +} + +// Nil represents the predeclared value nil. +type Nil struct { + object +} + +func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) { + var tname *TypeName + typ := obj.Type() + + switch obj := obj.(type) { + case *PkgName: + fmt.Fprintf(buf, "package %s", obj.Name()) + if path := obj.imported.path; path != "" && path != obj.name { + fmt.Fprintf(buf, " (%q)", path) + } + return + + case *Const: + buf.WriteString("const") + + case *TypeName: + tname = obj + buf.WriteString("type") + + case *Var: + if obj.isField { + buf.WriteString("field") + } else { + buf.WriteString("var") + } + + case *Func: + buf.WriteString("func ") + writeFuncName(buf, obj, qf) + if typ != nil { + WriteSignature(buf, typ.(*Signature), qf) + } + return + + case *Label: + buf.WriteString("label") + typ = nil + + case *Builtin: + buf.WriteString("builtin") + typ = nil + + case *Nil: + buf.WriteString("nil") + return + + default: + panic(fmt.Sprintf("writeObject(%T)", obj)) + } + + buf.WriteByte(' ') + + // For package-level objects, qualify the name. + if obj.Pkg() != nil && obj.Pkg().scope.Lookup(obj.Name()) == obj { + writePackage(buf, obj.Pkg(), qf) + } + buf.WriteString(obj.Name()) + + if typ == nil { + return + } + + if tname != nil { + // We have a type object: Don't print anything more for + // basic types since there's no more information (names + // are the same; see also comment in TypeName.IsAlias). + if _, ok := typ.(*Basic); ok { + return + } + if tname.IsAlias() { + buf.WriteString(" =") + } else { + typ = typ.Underlying() + } + } + + buf.WriteByte(' ') + WriteType(buf, typ, qf) +} + +func writePackage(buf *bytes.Buffer, pkg *Package, qf Qualifier) { + if pkg == nil { + return + } + var s string + if qf != nil { + s = qf(pkg) + } else { + s = pkg.Path() + } + if s != "" { + buf.WriteString(s) + buf.WriteByte('.') + } +} + +// ObjectString returns the string form of obj. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +func ObjectString(obj Object, qf Qualifier) string { + var buf bytes.Buffer + writeObject(&buf, obj, qf) + return buf.String() +} + +func (obj *PkgName) String() string { return ObjectString(obj, nil) } +func (obj *Const) String() string { return ObjectString(obj, nil) } +func (obj *TypeName) String() string { return ObjectString(obj, nil) } +func (obj *Var) String() string { return ObjectString(obj, nil) } +func (obj *Func) String() string { return ObjectString(obj, nil) } +func (obj *Label) String() string { return ObjectString(obj, nil) } +func (obj *Builtin) String() string { return ObjectString(obj, nil) } +func (obj *Nil) String() string { return ObjectString(obj, nil) } + +func writeFuncName(buf *bytes.Buffer, f *Func, qf Qualifier) { + if f.typ != nil { + sig := f.typ.(*Signature) + if recv := sig.Recv(); recv != nil { + buf.WriteByte('(') + if _, ok := recv.Type().(*Interface); ok { + // gcimporter creates abstract methods of + // named interfaces using the interface type + // (not the named type) as the receiver. + // Don't print it in full. + buf.WriteString("interface") + } else { + WriteType(buf, recv.Type(), qf) + } + buf.WriteByte(')') + buf.WriteByte('.') + } else if f.pkg != nil { + writePackage(buf, f.pkg, qf) + } + } + buf.WriteString(f.name) +} diff --git a/src/go/types/object_test.go b/src/go/types/object_test.go new file mode 100644 index 0000000..2b6057b --- /dev/null +++ b/src/go/types/object_test.go @@ -0,0 +1,86 @@ +// Copyright 2016 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 types + +import ( + "go/ast" + "go/parser" + "go/token" + "testing" +) + +func TestIsAlias(t *testing.T) { + check := func(obj *TypeName, want bool) { + if got := obj.IsAlias(); got != want { + t.Errorf("%v: got IsAlias = %v; want %v", obj, got, want) + } + } + + // predeclared types + check(Unsafe.Scope().Lookup("Pointer").(*TypeName), false) + for _, name := range Universe.Names() { + if obj, _ := Universe.Lookup(name).(*TypeName); obj != nil { + check(obj, name == "byte" || name == "rune") + } + } + + // various other types + pkg := NewPackage("p", "p") + t1 := NewTypeName(0, pkg, "t1", nil) + n1 := NewNamed(t1, new(Struct), nil) + for _, test := range []struct { + name *TypeName + alias bool + }{ + {NewTypeName(0, nil, "t0", nil), false}, // no type yet + {NewTypeName(0, pkg, "t0", nil), false}, // no type yet + {t1, false}, // type name refers to named type and vice versa + {NewTypeName(0, nil, "t2", &emptyInterface), true}, // type name refers to unnamed type + {NewTypeName(0, pkg, "t3", n1), true}, // type name refers to named type with different type name + {NewTypeName(0, nil, "t4", Typ[Int32]), true}, // type name refers to basic type with different name + {NewTypeName(0, nil, "int32", Typ[Int32]), false}, // type name refers to basic type with same name + {NewTypeName(0, pkg, "int32", Typ[Int32]), true}, // type name is declared in user-defined package (outside Universe) + {NewTypeName(0, nil, "rune", Typ[Rune]), true}, // type name refers to basic type rune which is an alias already + } { + check(test.name, test.alias) + } +} + +// TestEmbeddedMethod checks that an embedded method is represented by +// the same Func Object as the original method. See also issue #34421. +func TestEmbeddedMethod(t *testing.T) { + const src = `package p; type I interface { error }` + + // type-check src + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatalf("parse failed: %s", err) + } + var conf Config + pkg, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil) + if err != nil { + t.Fatalf("typecheck failed: %s", err) + } + + // get original error.Error method + eface := Universe.Lookup("error") + orig, _, _ := LookupFieldOrMethod(eface.Type(), false, nil, "Error") + if orig == nil { + t.Fatalf("original error.Error not found") + } + + // get embedded error.Error method + iface := pkg.Scope().Lookup("I") + embed, _, _ := LookupFieldOrMethod(iface.Type(), false, nil, "Error") + if embed == nil { + t.Fatalf("embedded error.Error not found") + } + + // original and embedded Error object should be identical + if orig != embed { + t.Fatalf("%s (%p) != %s (%p)", orig, orig, embed, embed) + } +} diff --git a/src/go/types/objset.go b/src/go/types/objset.go new file mode 100644 index 0000000..55eb74a --- /dev/null +++ b/src/go/types/objset.go @@ -0,0 +1,31 @@ +// Copyright 2013 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. + +// This file implements objsets. +// +// An objset is similar to a Scope but objset elements +// are identified by their unique id, instead of their +// object name. + +package types + +// An objset is a set of objects identified by their unique id. +// The zero value for objset is a ready-to-use empty objset. +type objset map[string]Object // initialized lazily + +// insert attempts to insert an object obj into objset s. +// If s already contains an alternative object alt with +// the same name, insert leaves s unchanged and returns alt. +// Otherwise it inserts obj and returns nil. +func (s *objset) insert(obj Object) Object { + id := obj.Id() + if alt := (*s)[id]; alt != nil { + return alt + } + if *s == nil { + *s = make(map[string]Object) + } + (*s)[id] = obj + return nil +} diff --git a/src/go/types/operand.go b/src/go/types/operand.go new file mode 100644 index 0000000..3e1ac31 --- /dev/null +++ b/src/go/types/operand.go @@ -0,0 +1,283 @@ +// Copyright 2012 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. + +// This file defines operands and associated operations. + +package types + +import ( + "bytes" + "fmt" + "go/ast" + "go/constant" + "go/token" +) + +// An operandMode specifies the (addressing) mode of an operand. +type operandMode byte + +const ( + invalid operandMode = iota // operand is invalid + novalue // operand represents no value (result of a function call w/o result) + builtin // operand is a built-in function + typexpr // operand is a type + constant_ // operand is a constant; the operand's typ is a Basic type + variable // operand is an addressable variable + mapindex // operand is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment) + value // operand is a computed value + commaok // like value, but operand may be used in a comma,ok expression + commaerr // like commaok, but second value is error, not boolean + cgofunc // operand is a cgo function +) + +var operandModeString = [...]string{ + invalid: "invalid operand", + novalue: "no value", + builtin: "built-in", + typexpr: "type", + constant_: "constant", + variable: "variable", + mapindex: "map index expression", + value: "value", + commaok: "comma, ok expression", + commaerr: "comma, error expression", + cgofunc: "cgo function", +} + +// An operand represents an intermediate value during type checking. +// Operands have an (addressing) mode, the expression evaluating to +// the operand, the operand's type, a value for constants, and an id +// for built-in functions. +// The zero value of operand is a ready to use invalid operand. +// +type operand struct { + mode operandMode + expr ast.Expr + typ Type + val constant.Value + id builtinId +} + +// Pos returns the position of the expression corresponding to x. +// If x is invalid the position is token.NoPos. +// +func (x *operand) Pos() token.Pos { + // x.expr may not be set if x is invalid + if x.expr == nil { + return token.NoPos + } + return x.expr.Pos() +} + +// Operand string formats +// (not all "untyped" cases can appear due to the type system, +// but they fall out naturally here) +// +// mode format +// +// invalid ( ) +// novalue ( ) +// builtin ( ) +// typexpr ( ) +// +// constant ( ) +// constant ( of type ) +// constant ( ) +// constant ( of type ) +// +// variable ( ) +// variable ( of type ) +// +// mapindex ( ) +// mapindex ( of type ) +// +// value ( ) +// value ( of type ) +// +// commaok ( ) +// commaok ( of type ) +// +// commaerr ( ) +// commaerr ( of type ) +// +// cgofunc ( ) +// cgofunc ( of type ) +// +func operandString(x *operand, qf Qualifier) string { + var buf bytes.Buffer + + var expr string + if x.expr != nil { + expr = ExprString(x.expr) + } else { + switch x.mode { + case builtin: + expr = predeclaredFuncs[x.id].name + case typexpr: + expr = TypeString(x.typ, qf) + case constant_: + expr = x.val.String() + } + } + + // ( + if expr != "" { + buf.WriteString(expr) + buf.WriteString(" (") + } + + // + hasType := false + switch x.mode { + case invalid, novalue, builtin, typexpr: + // no type + default: + // should have a type, but be cautious (don't crash during printing) + if x.typ != nil { + if isUntyped(x.typ) { + buf.WriteString(x.typ.(*Basic).name) + buf.WriteByte(' ') + break + } + hasType = true + } + } + + // + buf.WriteString(operandModeString[x.mode]) + + // + if x.mode == constant_ { + if s := x.val.String(); s != expr { + buf.WriteByte(' ') + buf.WriteString(s) + } + } + + // + if hasType { + if x.typ != Typ[Invalid] { + buf.WriteString(" of type ") + WriteType(&buf, x.typ, qf) + } else { + buf.WriteString(" with invalid type") + } + } + + // ) + if expr != "" { + buf.WriteByte(')') + } + + return buf.String() +} + +func (x *operand) String() string { + return operandString(x, nil) +} + +// setConst sets x to the untyped constant for literal lit. +func (x *operand) setConst(tok token.Token, lit string) { + var kind BasicKind + switch tok { + case token.INT: + kind = UntypedInt + case token.FLOAT: + kind = UntypedFloat + case token.IMAG: + kind = UntypedComplex + case token.CHAR: + kind = UntypedRune + case token.STRING: + kind = UntypedString + default: + unreachable() + } + + val := constant.MakeFromLiteral(lit, tok, 0) + if val.Kind() == constant.Unknown { + x.mode = invalid + x.typ = Typ[Invalid] + return + } + x.mode = constant_ + x.typ = Typ[kind] + x.val = val +} + +// isNil reports whether x is the nil value. +func (x *operand) isNil() bool { + return x.mode == value && x.typ == Typ[UntypedNil] +} + +// assignableTo reports whether x is assignable to a variable of type T. If the +// result is false and a non-nil reason is provided, it may be set to a more +// detailed explanation of the failure (result != ""). The check parameter may +// be nil if assignableTo is invoked through an exported API call, i.e., when +// all methods have been type-checked. +func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, errorCode) { + if x.mode == invalid || T == Typ[Invalid] { + return true, 0 // avoid spurious errors + } + + V := x.typ + + // x's type is identical to T + if check.identical(V, T) { + return true, 0 + } + + Vu := V.Underlying() + Tu := T.Underlying() + + // x is an untyped value representable by a value of type T. + if isUntyped(Vu) { + if t, ok := Tu.(*Basic); ok && x.mode == constant_ { + return representableConst(x.val, check, t, nil), _IncompatibleAssign + } + return check.implicitType(x, Tu) != nil, _IncompatibleAssign + } + // Vu is typed + + // x's type V and T have identical underlying types and at least one of V or + // T is not a named type. + if check.identical(Vu, Tu) && (!isNamed(V) || !isNamed(T)) { + return true, 0 + } + + // T is an interface type and x implements T + if Ti, ok := Tu.(*Interface); ok { + if m, wrongType := check.missingMethod(V, Ti, true); m != nil /* Implements(V, Ti) */ { + if reason != nil { + if wrongType != nil { + if check.identical(m.typ, wrongType.typ) { + *reason = fmt.Sprintf("missing method %s (%s has pointer receiver)", m.name, m.name) + } else { + *reason = fmt.Sprintf("wrong type for method %s (have %s, want %s)", m.Name(), wrongType.typ, m.typ) + } + + } else { + *reason = "missing method " + m.Name() + } + } + return false, _InvalidIfaceAssign + } + return true, 0 + } + + // x is a bidirectional channel value, T is a channel + // type, x's type V and T have identical element types, + // and at least one of V or T is not a named type + if Vc, ok := Vu.(*Chan); ok && Vc.dir == SendRecv { + if Tc, ok := Tu.(*Chan); ok && check.identical(Vc.elem, Tc.elem) { + if !isNamed(V) || !isNamed(T) { + return true, 0 + } else { + return false, _InvalidChanAssign + } + } + } + + return false, _IncompatibleAssign +} diff --git a/src/go/types/package.go b/src/go/types/package.go new file mode 100644 index 0000000..7b89def --- /dev/null +++ b/src/go/types/package.go @@ -0,0 +1,65 @@ +// Copyright 2013 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 types + +import ( + "fmt" + "go/token" +) + +// A Package describes a Go package. +type Package struct { + path string + name string + scope *Scope + complete bool + imports []*Package + fake bool // scope lookup errors are silently dropped if package is fake (internal use only) + cgo bool // uses of this package will be rewritten into uses of declarations from _cgo_gotypes.go +} + +// NewPackage returns a new Package for the given package path and name. +// The package is not complete and contains no explicit imports. +func NewPackage(path, name string) *Package { + scope := NewScope(Universe, token.NoPos, token.NoPos, fmt.Sprintf("package %q", path)) + return &Package{path: path, name: name, scope: scope} +} + +// Path returns the package path. +func (pkg *Package) Path() string { return pkg.path } + +// Name returns the package name. +func (pkg *Package) Name() string { return pkg.name } + +// SetName sets the package name. +func (pkg *Package) SetName(name string) { pkg.name = name } + +// Scope returns the (complete or incomplete) package scope +// holding the objects declared at package level (TypeNames, +// Consts, Vars, and Funcs). +func (pkg *Package) Scope() *Scope { return pkg.scope } + +// A package is complete if its scope contains (at least) all +// exported objects; otherwise it is incomplete. +func (pkg *Package) Complete() bool { return pkg.complete } + +// MarkComplete marks a package as complete. +func (pkg *Package) MarkComplete() { pkg.complete = true } + +// Imports returns the list of packages directly imported by +// pkg; the list is in source order. +// +// If pkg was loaded from export data, Imports includes packages that +// provide package-level objects referenced by pkg. This may be more or +// less than the set of packages directly imported by pkg's source code. +func (pkg *Package) Imports() []*Package { return pkg.imports } + +// SetImports sets the list of explicitly imported packages to list. +// It is the caller's responsibility to make sure list elements are unique. +func (pkg *Package) SetImports(list []*Package) { pkg.imports = list } + +func (pkg *Package) String() string { + return fmt.Sprintf("package %s (%q)", pkg.name, pkg.path) +} diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go new file mode 100644 index 0000000..148edbf --- /dev/null +++ b/src/go/types/predicates.go @@ -0,0 +1,340 @@ +// Copyright 2012 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. + +// This file implements commonly used type predicates. + +package types + +import "sort" + +func isNamed(typ Type) bool { + if _, ok := typ.(*Basic); ok { + return ok + } + _, ok := typ.(*Named) + return ok +} + +func isBoolean(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsBoolean != 0 +} + +func isInteger(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsInteger != 0 +} + +func isUnsigned(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsUnsigned != 0 +} + +func isFloat(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsFloat != 0 +} + +func isComplex(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsComplex != 0 +} + +func isNumeric(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsNumeric != 0 +} + +func isString(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsString != 0 +} + +func isTyped(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return !ok || t.info&IsUntyped == 0 +} + +func isUntyped(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsUntyped != 0 +} + +func isOrdered(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsOrdered != 0 +} + +func isConstType(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsConstType != 0 +} + +// IsInterface reports whether typ is an interface type. +func IsInterface(typ Type) bool { + _, ok := typ.Underlying().(*Interface) + return ok +} + +// Comparable reports whether values of type T are comparable. +func Comparable(T Type) bool { + return comparable(T, nil) +} + +func comparable(T Type, seen map[Type]bool) bool { + if seen[T] { + return true + } + if seen == nil { + seen = make(map[Type]bool) + } + seen[T] = true + + switch t := T.Underlying().(type) { + case *Basic: + // assume invalid types to be comparable + // to avoid follow-up errors + return t.kind != UntypedNil + case *Pointer, *Interface, *Chan: + return true + case *Struct: + for _, f := range t.fields { + if !comparable(f.typ, seen) { + return false + } + } + return true + case *Array: + return comparable(t.elem, seen) + } + return false +} + +// hasNil reports whether a type includes the nil value. +func hasNil(typ Type) bool { + switch t := typ.Underlying().(type) { + case *Basic: + return t.kind == UnsafePointer + case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan: + return true + } + return false +} + +// identical reports whether x and y are identical types. +// Receivers of Signature types are ignored. +func (check *Checker) identical(x, y Type) bool { + return check.identical0(x, y, true, nil) +} + +// identicalIgnoreTags reports whether x and y are identical types if tags are ignored. +// Receivers of Signature types are ignored. +func (check *Checker) identicalIgnoreTags(x, y Type) bool { + return check.identical0(x, y, false, nil) +} + +// An ifacePair is a node in a stack of interface type pairs compared for identity. +type ifacePair struct { + x, y *Interface + prev *ifacePair +} + +func (p *ifacePair) identical(q *ifacePair) bool { + return p.x == q.x && p.y == q.y || p.x == q.y && p.y == q.x +} + +func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool { + if x == y { + return true + } + + switch x := x.(type) { + case *Basic: + // Basic types are singletons except for the rune and byte + // aliases, thus we cannot solely rely on the x == y check + // above. See also comment in TypeName.IsAlias. + if y, ok := y.(*Basic); ok { + return x.kind == y.kind + } + + case *Array: + // Two array types are identical if they have identical element types + // and the same array length. + if y, ok := y.(*Array); ok { + // If one or both array lengths are unknown (< 0) due to some error, + // assume they are the same to avoid spurious follow-on errors. + return (x.len < 0 || y.len < 0 || x.len == y.len) && check.identical0(x.elem, y.elem, cmpTags, p) + } + + case *Slice: + // Two slice types are identical if they have identical element types. + if y, ok := y.(*Slice); ok { + return check.identical0(x.elem, y.elem, cmpTags, p) + } + + case *Struct: + // Two struct types are identical if they have the same sequence of fields, + // and if corresponding fields have the same names, and identical types, + // and identical tags. Two embedded fields are considered to have the same + // name. Lower-case field names from different packages are always different. + if y, ok := y.(*Struct); ok { + if x.NumFields() == y.NumFields() { + for i, f := range x.fields { + g := y.fields[i] + if f.embedded != g.embedded || + cmpTags && x.Tag(i) != y.Tag(i) || + !f.sameId(g.pkg, g.name) || + !check.identical0(f.typ, g.typ, cmpTags, p) { + return false + } + } + return true + } + } + + case *Pointer: + // Two pointer types are identical if they have identical base types. + if y, ok := y.(*Pointer); ok { + return check.identical0(x.base, y.base, cmpTags, p) + } + + case *Tuple: + // Two tuples types are identical if they have the same number of elements + // and corresponding elements have identical types. + if y, ok := y.(*Tuple); ok { + if x.Len() == y.Len() { + if x != nil { + for i, v := range x.vars { + w := y.vars[i] + if !check.identical0(v.typ, w.typ, cmpTags, p) { + return false + } + } + } + return true + } + } + + case *Signature: + // Two function types are identical if they have the same number of parameters + // and result values, corresponding parameter and result types are identical, + // and either both functions are variadic or neither is. Parameter and result + // names are not required to match. + if y, ok := y.(*Signature); ok { + return x.variadic == y.variadic && + check.identical0(x.params, y.params, cmpTags, p) && + check.identical0(x.results, y.results, cmpTags, p) + } + + case *Interface: + // Two interface types are identical if they have the same set of methods with + // the same names and identical function types. Lower-case method names from + // different packages are always different. The order of the methods is irrelevant. + if y, ok := y.(*Interface); ok { + // If identical0 is called (indirectly) via an external API entry point + // (such as Identical, IdenticalIgnoreTags, etc.), check is nil. But in + // that case, interfaces are expected to be complete and lazy completion + // here is not needed. + if check != nil { + check.completeInterface(x) + check.completeInterface(y) + } + a := x.allMethods + b := y.allMethods + if len(a) == len(b) { + // Interface types are the only types where cycles can occur + // that are not "terminated" via named types; and such cycles + // can only be created via method parameter types that are + // anonymous interfaces (directly or indirectly) embedding + // the current interface. Example: + // + // type T interface { + // m() interface{T} + // } + // + // If two such (differently named) interfaces are compared, + // endless recursion occurs if the cycle is not detected. + // + // If x and y were compared before, they must be equal + // (if they were not, the recursion would have stopped); + // search the ifacePair stack for the same pair. + // + // This is a quadratic algorithm, but in practice these stacks + // are extremely short (bounded by the nesting depth of interface + // type declarations that recur via parameter types, an extremely + // rare occurrence). An alternative implementation might use a + // "visited" map, but that is probably less efficient overall. + q := &ifacePair{x, y, p} + for p != nil { + if p.identical(q) { + return true // same pair was compared before + } + p = p.prev + } + if debug { + assert(sort.IsSorted(byUniqueMethodName(a))) + assert(sort.IsSorted(byUniqueMethodName(b))) + } + for i, f := range a { + g := b[i] + if f.Id() != g.Id() || !check.identical0(f.typ, g.typ, cmpTags, q) { + return false + } + } + return true + } + } + + case *Map: + // Two map types are identical if they have identical key and value types. + if y, ok := y.(*Map); ok { + return check.identical0(x.key, y.key, cmpTags, p) && check.identical0(x.elem, y.elem, cmpTags, p) + } + + case *Chan: + // Two channel types are identical if they have identical value types + // and the same direction. + if y, ok := y.(*Chan); ok { + return x.dir == y.dir && check.identical0(x.elem, y.elem, cmpTags, p) + } + + case *Named: + // Two named types are identical if their type names originate + // in the same type declaration. + if y, ok := y.(*Named); ok { + return x.obj == y.obj + } + + case nil: + + default: + unreachable() + } + + return false +} + +// Default returns the default "typed" type for an "untyped" type; +// it returns the incoming type for all other types. The default type +// for untyped nil is untyped nil. +// +func Default(typ Type) Type { + if t, ok := typ.(*Basic); ok { + switch t.kind { + case UntypedBool: + return Typ[Bool] + case UntypedInt: + return Typ[Int] + case UntypedRune: + return universeRune // use 'rune' name + case UntypedFloat: + return Typ[Float64] + case UntypedComplex: + return Typ[Complex128] + case UntypedString: + return Typ[String] + } + } + return typ +} diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go new file mode 100644 index 0000000..b637f8b --- /dev/null +++ b/src/go/types/resolver.go @@ -0,0 +1,614 @@ +// Copyright 2013 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 types + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "sort" + "strconv" + "strings" + "unicode" +) + +// A declInfo describes a package-level const, type, var, or func declaration. +type declInfo struct { + file *Scope // scope of file containing this declaration + lhs []*Var // lhs of n:1 variable declarations, or nil + typ ast.Expr // type, or nil + init ast.Expr // init/orig expression, or nil + inherited bool // if set, the init expression is inherited from a previous constant declaration + fdecl *ast.FuncDecl // func declaration, or nil + alias bool // type alias declaration + + // The deps field tracks initialization expression dependencies. + deps map[Object]bool // lazily initialized +} + +// hasInitializer reports whether the declared object has an initialization +// expression or function body. +func (d *declInfo) hasInitializer() bool { + return d.init != nil || d.fdecl != nil && d.fdecl.Body != nil +} + +// addDep adds obj to the set of objects d's init expression depends on. +func (d *declInfo) addDep(obj Object) { + m := d.deps + if m == nil { + m = make(map[Object]bool) + d.deps = m + } + m[obj] = true +} + +// arityMatch checks that the lhs and rhs of a const or var decl +// have the appropriate number of names and init exprs. For const +// decls, init is the value spec providing the init exprs; for +// var decls, init is nil (the init exprs are in s in this case). +func (check *Checker) arityMatch(s, init *ast.ValueSpec) { + l := len(s.Names) + r := len(s.Values) + if init != nil { + r = len(init.Values) + } + + const code = _WrongAssignCount + switch { + case init == nil && r == 0: + // var decl w/o init expr + if s.Type == nil { + check.errorf(s, code, "missing type or init expr") + } + case l < r: + if l < len(s.Values) { + // init exprs from s + n := s.Values[l] + check.errorf(n, code, "extra init expr %s", n) + // TODO(gri) avoid declared but not used error here + } else { + // init exprs "inherited" + check.errorf(s, code, "extra init expr at %s", check.fset.Position(init.Pos())) + // TODO(gri) avoid declared but not used error here + } + case l > r && (init != nil || r != 1): + n := s.Names[r] + check.errorf(n, code, "missing init expr for %s", n) + } +} + +func validatedImportPath(path string) (string, error) { + s, err := strconv.Unquote(path) + if err != nil { + return "", err + } + if s == "" { + return "", fmt.Errorf("empty string") + } + const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" + for _, r := range s { + if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { + return s, fmt.Errorf("invalid character %#U", r) + } + } + return s, nil +} + +// declarePkgObj declares obj in the package scope, records its ident -> obj mapping, +// and updates check.objMap. The object must not be a function or method. +func (check *Checker) declarePkgObj(ident *ast.Ident, obj Object, d *declInfo) { + assert(ident.Name == obj.Name()) + + // spec: "A package-scope or file-scope identifier with name init + // may only be declared to be a function with this (func()) signature." + if ident.Name == "init" { + check.errorf(ident, _InvalidInitDecl, "cannot declare init - must be func") + return + } + + // spec: "The main package must have package name main and declare + // a function main that takes no arguments and returns no value." + if ident.Name == "main" && check.pkg.name == "main" { + check.errorf(ident, _InvalidMainDecl, "cannot declare main - must be func") + return + } + + check.declare(check.pkg.scope, ident, obj, token.NoPos) + check.objMap[obj] = d + obj.setOrder(uint32(len(check.objMap))) +} + +// filename returns a filename suitable for debugging output. +func (check *Checker) filename(fileNo int) string { + file := check.files[fileNo] + if pos := file.Pos(); pos.IsValid() { + return check.fset.File(pos).Name() + } + return fmt.Sprintf("file[%d]", fileNo) +} + +func (check *Checker) importPackage(pos token.Pos, path, dir string) *Package { + // If we already have a package for the given (path, dir) + // pair, use it instead of doing a full import. + // Checker.impMap only caches packages that are marked Complete + // or fake (dummy packages for failed imports). Incomplete but + // non-fake packages do require an import to complete them. + key := importKey{path, dir} + imp := check.impMap[key] + if imp != nil { + return imp + } + + // no package yet => import it + if path == "C" && (check.conf.FakeImportC || check.conf.go115UsesCgo) { + imp = NewPackage("C", "C") + imp.fake = true // package scope is not populated + imp.cgo = check.conf.go115UsesCgo + } else { + // ordinary import + var err error + if importer := check.conf.Importer; importer == nil { + err = fmt.Errorf("Config.Importer not installed") + } else if importerFrom, ok := importer.(ImporterFrom); ok { + imp, err = importerFrom.ImportFrom(path, dir, 0) + if imp == nil && err == nil { + err = fmt.Errorf("Config.Importer.ImportFrom(%s, %s, 0) returned nil but no error", path, dir) + } + } else { + imp, err = importer.Import(path) + if imp == nil && err == nil { + err = fmt.Errorf("Config.Importer.Import(%s) returned nil but no error", path) + } + } + // make sure we have a valid package name + // (errors here can only happen through manipulation of packages after creation) + if err == nil && imp != nil && (imp.name == "_" || imp.name == "") { + err = fmt.Errorf("invalid package name: %q", imp.name) + imp = nil // create fake package below + } + if err != nil { + check.errorf(atPos(pos), _BrokenImport, "could not import %s (%s)", path, err) + if imp == nil { + // create a new fake package + // come up with a sensible package name (heuristic) + name := path + if i := len(name); i > 0 && name[i-1] == '/' { + name = name[:i-1] + } + if i := strings.LastIndex(name, "/"); i >= 0 { + name = name[i+1:] + } + imp = NewPackage(path, name) + } + // continue to use the package as best as we can + imp.fake = true // avoid follow-up lookup failures + } + } + + // package should be complete or marked fake, but be cautious + if imp.complete || imp.fake { + check.impMap[key] = imp + check.pkgCnt[imp.name]++ + return imp + } + + // something went wrong (importer may have returned incomplete package without error) + return nil +} + +// collectObjects collects all file and package objects and inserts them +// into their respective scopes. It also performs imports and associates +// methods with receiver base type names. +func (check *Checker) collectObjects() { + pkg := check.pkg + + // pkgImports is the set of packages already imported by any package file seen + // so far. Used to avoid duplicate entries in pkg.imports. Allocate and populate + // it (pkg.imports may not be empty if we are checking test files incrementally). + // Note that pkgImports is keyed by package (and thus package path), not by an + // importKey value. Two different importKey values may map to the same package + // which is why we cannot use the check.impMap here. + var pkgImports = make(map[*Package]bool) + for _, imp := range pkg.imports { + pkgImports[imp] = true + } + + var methods []*Func // list of methods with non-blank _ names + for fileNo, file := range check.files { + // The package identifier denotes the current package, + // but there is no corresponding package object. + check.recordDef(file.Name, nil) + + // Use the actual source file extent rather than *ast.File extent since the + // latter doesn't include comments which appear at the start or end of the file. + // Be conservative and use the *ast.File extent if we don't have a *token.File. + pos, end := file.Pos(), file.End() + if f := check.fset.File(file.Pos()); f != nil { + pos, end = token.Pos(f.Base()), token.Pos(f.Base()+f.Size()) + } + fileScope := NewScope(check.pkg.scope, pos, end, check.filename(fileNo)) + check.recordScope(file, fileScope) + + // determine file directory, necessary to resolve imports + // FileName may be "" (typically for tests) in which case + // we get "." as the directory which is what we would want. + fileDir := dir(check.fset.Position(file.Name.Pos()).Filename) + + check.walkDecls(file.Decls, func(d decl) { + switch d := d.(type) { + case importDecl: + // import package + path, err := validatedImportPath(d.spec.Path.Value) + if err != nil { + check.errorf(d.spec.Path, _BadImportPath, "invalid import path (%s)", err) + return + } + + imp := check.importPackage(d.spec.Path.Pos(), path, fileDir) + if imp == nil { + return + } + + // add package to list of explicit imports + // (this functionality is provided as a convenience + // for clients; it is not needed for type-checking) + if !pkgImports[imp] { + pkgImports[imp] = true + pkg.imports = append(pkg.imports, imp) + } + + // local name overrides imported package name + name := imp.name + if d.spec.Name != nil { + name = d.spec.Name.Name + if path == "C" { + // match cmd/compile (not prescribed by spec) + check.errorf(d.spec.Name, _ImportCRenamed, `cannot rename import "C"`) + return + } + if name == "init" { + check.errorf(d.spec.Name, _InvalidInitDecl, "cannot declare init - must be func") + return + } + } + + obj := NewPkgName(d.spec.Pos(), pkg, name, imp) + if d.spec.Name != nil { + // in a dot-import, the dot represents the package + check.recordDef(d.spec.Name, obj) + } else { + check.recordImplicit(d.spec, obj) + } + + if path == "C" { + // match cmd/compile (not prescribed by spec) + obj.used = true + } + + // add import to file scope + if name == "." { + // merge imported scope with file scope + for _, obj := range imp.scope.elems { + // A package scope may contain non-exported objects, + // do not import them! + if obj.Exported() { + // declare dot-imported object + // (Do not use check.declare because it modifies the object + // via Object.setScopePos, which leads to a race condition; + // the object may be imported into more than one file scope + // concurrently. See issue #32154.) + if alt := fileScope.Insert(obj); alt != nil { + check.errorf(d.spec.Name, _DuplicateDecl, "%s redeclared in this block", obj.Name()) + check.reportAltDecl(alt) + } + } + } + // add position to set of dot-import positions for this file + // (this is only needed for "imported but not used" errors) + check.addUnusedDotImport(fileScope, imp, d.spec) + } else { + // declare imported package object in file scope + // (no need to provide s.Name since we called check.recordDef earlier) + check.declare(fileScope, nil, obj, token.NoPos) + } + case constDecl: + // declare all constants + for i, name := range d.spec.Names { + obj := NewConst(name.Pos(), pkg, name.Name, nil, constant.MakeInt64(int64(d.iota))) + + var init ast.Expr + if i < len(d.init) { + init = d.init[i] + } + + d := &declInfo{file: fileScope, typ: d.typ, init: init, inherited: d.inherited} + check.declarePkgObj(name, obj, d) + } + + case varDecl: + lhs := make([]*Var, len(d.spec.Names)) + // If there's exactly one rhs initializer, use + // the same declInfo d1 for all lhs variables + // so that each lhs variable depends on the same + // rhs initializer (n:1 var declaration). + var d1 *declInfo + if len(d.spec.Values) == 1 { + // The lhs elements are only set up after the for loop below, + // but that's ok because declareVar only collects the declInfo + // for a later phase. + d1 = &declInfo{file: fileScope, lhs: lhs, typ: d.spec.Type, init: d.spec.Values[0]} + } + + // declare all variables + for i, name := range d.spec.Names { + obj := NewVar(name.Pos(), pkg, name.Name, nil) + lhs[i] = obj + + di := d1 + if di == nil { + // individual assignments + var init ast.Expr + if i < len(d.spec.Values) { + init = d.spec.Values[i] + } + di = &declInfo{file: fileScope, typ: d.spec.Type, init: init} + } + + check.declarePkgObj(name, obj, di) + } + case typeDecl: + obj := NewTypeName(d.spec.Name.Pos(), pkg, d.spec.Name.Name, nil) + check.declarePkgObj(d.spec.Name, obj, &declInfo{file: fileScope, typ: d.spec.Type, alias: d.spec.Assign.IsValid()}) + case funcDecl: + info := &declInfo{file: fileScope, fdecl: d.decl} + name := d.decl.Name.Name + obj := NewFunc(d.decl.Name.Pos(), pkg, name, nil) + if d.decl.Recv == nil { + // regular function + if name == "init" { + // don't declare init functions in the package scope - they are invisible + obj.parent = pkg.scope + check.recordDef(d.decl.Name, obj) + // init functions must have a body + if d.decl.Body == nil { + check.softErrorf(obj, _MissingInitBody, "missing function body") + } + } else { + check.declare(pkg.scope, d.decl.Name, obj, token.NoPos) + } + } else { + // method + // (Methods with blank _ names are never found; no need to collect + // them for later type association. They will still be type-checked + // with all the other functions.) + if name != "_" { + methods = append(methods, obj) + } + check.recordDef(d.decl.Name, obj) + } + // Methods are not package-level objects but we still track them in the + // object map so that we can handle them like regular functions (if the + // receiver is invalid); also we need their fdecl info when associating + // them with their receiver base type, below. + check.objMap[obj] = info + obj.setOrder(uint32(len(check.objMap))) + } + }) + } + + // verify that objects in package and file scopes have different names + for _, scope := range check.pkg.scope.children /* file scopes */ { + for _, obj := range scope.elems { + if alt := pkg.scope.Lookup(obj.Name()); alt != nil { + if pkg, ok := obj.(*PkgName); ok { + check.errorf(alt, _DuplicateDecl, "%s already declared through import of %s", alt.Name(), pkg.Imported()) + check.reportAltDecl(pkg) + } else { + check.errorf(alt, _DuplicateDecl, "%s already declared through dot-import of %s", alt.Name(), obj.Pkg()) + // TODO(gri) dot-imported objects don't have a position; reportAltDecl won't print anything + check.reportAltDecl(obj) + } + } + } + } + + // Now that we have all package scope objects and all methods, + // associate methods with receiver base type name where possible. + // Ignore methods that have an invalid receiver. They will be + // type-checked later, with regular functions. + if methods == nil { + return // nothing to do + } + check.methods = make(map[*TypeName][]*Func) + for _, f := range methods { + fdecl := check.objMap[f].fdecl + if list := fdecl.Recv.List; len(list) > 0 { + // f is a method. + // Determine the receiver base type and associate f with it. + ptr, base := check.resolveBaseTypeName(list[0].Type) + if base != nil { + f.hasPtrRecv = ptr + check.methods[base] = append(check.methods[base], f) + } + } + } +} + +// resolveBaseTypeName returns the non-alias base type name for typ, and whether +// there was a pointer indirection to get to it. The base type name must be declared +// in package scope, and there can be at most one pointer indirection. If no such type +// name exists, the returned base is nil. +func (check *Checker) resolveBaseTypeName(typ ast.Expr) (ptr bool, base *TypeName) { + // Algorithm: Starting from a type expression, which may be a name, + // we follow that type through alias declarations until we reach a + // non-alias type name. If we encounter anything but pointer types or + // parentheses we're done. If we encounter more than one pointer type + // we're done. + var seen map[*TypeName]bool + for { + typ = unparen(typ) + + // check if we have a pointer type + if pexpr, _ := typ.(*ast.StarExpr); pexpr != nil { + // if we've already seen a pointer, we're done + if ptr { + return false, nil + } + ptr = true + typ = unparen(pexpr.X) // continue with pointer base type + } + + // typ must be a name + name, _ := typ.(*ast.Ident) + if name == nil { + return false, nil + } + + // name must denote an object found in the current package scope + // (note that dot-imported objects are not in the package scope!) + obj := check.pkg.scope.Lookup(name.Name) + if obj == nil { + return false, nil + } + + // the object must be a type name... + tname, _ := obj.(*TypeName) + if tname == nil { + return false, nil + } + + // ... which we have not seen before + if seen[tname] { + return false, nil + } + + // we're done if tdecl defined tname as a new type + // (rather than an alias) + tdecl := check.objMap[tname] // must exist for objects in package scope + if !tdecl.alias { + return ptr, tname + } + + // otherwise, continue resolving + typ = tdecl.typ + if seen == nil { + seen = make(map[*TypeName]bool) + } + seen[tname] = true + } +} + +// packageObjects typechecks all package objects, but not function bodies. +func (check *Checker) packageObjects() { + // process package objects in source order for reproducible results + objList := make([]Object, len(check.objMap)) + i := 0 + for obj := range check.objMap { + objList[i] = obj + i++ + } + sort.Sort(inSourceOrder(objList)) + + // add new methods to already type-checked types (from a prior Checker.Files call) + for _, obj := range objList { + if obj, _ := obj.(*TypeName); obj != nil && obj.typ != nil { + check.addMethodDecls(obj) + } + } + + // We process non-alias declarations first, in order to avoid situations where + // the type of an alias declaration is needed before it is available. In general + // this is still not enough, as it is possible to create sufficiently convoluted + // recursive type definitions that will cause a type alias to be needed before it + // is available (see issue #25838 for examples). + // As an aside, the cmd/compiler suffers from the same problem (#25838). + var aliasList []*TypeName + // phase 1 + for _, obj := range objList { + // If we have a type alias, collect it for the 2nd phase. + if tname, _ := obj.(*TypeName); tname != nil && check.objMap[tname].alias { + aliasList = append(aliasList, tname) + continue + } + + check.objDecl(obj, nil) + } + // phase 2 + for _, obj := range aliasList { + check.objDecl(obj, nil) + } + + // At this point we may have a non-empty check.methods map; this means that not all + // entries were deleted at the end of typeDecl because the respective receiver base + // types were not found. In that case, an error was reported when declaring those + // methods. We can now safely discard this map. + check.methods = nil +} + +// inSourceOrder implements the sort.Sort interface. +type inSourceOrder []Object + +func (a inSourceOrder) Len() int { return len(a) } +func (a inSourceOrder) Less(i, j int) bool { return a[i].order() < a[j].order() } +func (a inSourceOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// unusedImports checks for unused imports. +func (check *Checker) unusedImports() { + // if function bodies are not checked, packages' uses are likely missing - don't check + if check.conf.IgnoreFuncBodies { + return + } + + // spec: "It is illegal (...) to directly import a package without referring to + // any of its exported identifiers. To import a package solely for its side-effects + // (initialization), use the blank identifier as explicit package name." + + // check use of regular imported packages + for _, scope := range check.pkg.scope.children /* file scopes */ { + for _, obj := range scope.elems { + if obj, ok := obj.(*PkgName); ok { + // Unused "blank imports" are automatically ignored + // since _ identifiers are not entered into scopes. + if !obj.used { + path := obj.imported.path + base := pkgName(path) + if obj.name == base { + check.softErrorf(obj, _UnusedImport, "%q imported but not used", path) + } else { + check.softErrorf(obj, _UnusedImport, "%q imported but not used as %s", path, obj.name) + } + } + } + } + } + + // check use of dot-imported packages + for _, unusedDotImports := range check.unusedDotImports { + for pkg, pos := range unusedDotImports { + check.softErrorf(pos, _UnusedImport, "%q imported but not used", pkg.path) + } + } +} + +// pkgName returns the package name (last element) of an import path. +func pkgName(path string) string { + if i := strings.LastIndex(path, "/"); i >= 0 { + path = path[i+1:] + } + return path +} + +// dir makes a good-faith attempt to return the directory +// portion of path. If path is empty, the result is ".". +// (Per the go/build package dependency tests, we cannot import +// path/filepath and simply use filepath.Dir.) +func dir(path string) string { + if i := strings.LastIndexAny(path, `/\`); i > 0 { + return path[:i] + } + // i <= 0 + return "." +} diff --git a/src/go/types/resolver_test.go b/src/go/types/resolver_test.go new file mode 100644 index 0000000..4bb63b6 --- /dev/null +++ b/src/go/types/resolver_test.go @@ -0,0 +1,216 @@ +// Copyright 2011 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 types_test + +import ( + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "internal/testenv" + "sort" + "testing" + + . "go/types" +) + +type resolveTestImporter struct { + importer ImporterFrom + imported map[string]bool +} + +func (imp *resolveTestImporter) Import(string) (*Package, error) { + panic("should not be called") +} + +func (imp *resolveTestImporter) ImportFrom(path, srcDir string, mode ImportMode) (*Package, error) { + if mode != 0 { + panic("mode must be 0") + } + if imp.importer == nil { + imp.importer = importer.Default().(ImporterFrom) + imp.imported = make(map[string]bool) + } + pkg, err := imp.importer.ImportFrom(path, srcDir, mode) + if err != nil { + return nil, err + } + imp.imported[path] = true + return pkg, nil +} + +func TestResolveIdents(t *testing.T) { + testenv.MustHaveGoBuild(t) + + sources := []string{ + ` + package p + import "fmt" + import "math" + const pi = math.Pi + func sin(x float64) float64 { + return math.Sin(x) + } + var Println = fmt.Println + `, + ` + package p + import "fmt" + type errorStringer struct { fmt.Stringer; error } + func f() string { + _ = "foo" + return fmt.Sprintf("%d", g()) + } + func g() (x int) { return } + `, + ` + package p + import . "go/parser" + import "sync" + func h() Mode { return ImportsOnly } + var _, x int = 1, 2 + func init() {} + type T struct{ *sync.Mutex; a, b, c int} + type I interface{ m() } + var _ = T{a: 1, b: 2, c: 3} + func (_ T) m() {} + func (T) _() {} + var i I + var _ = i.m + func _(s []int) { for i, x := range s { _, _ = i, x } } + func _(x interface{}) { + switch x := x.(type) { + case int: + _ = x + } + switch {} // implicit 'true' tag + } + `, + ` + package p + type S struct{} + func (T) _() {} + func (T) _() {} + `, + ` + package p + func _() { + L0: + L1: + goto L0 + for { + goto L1 + } + if true { + goto L2 + } + L2: + } + `, + } + + pkgnames := []string{ + "fmt", + "math", + } + + // parse package files + fset := token.NewFileSet() + var files []*ast.File + for i, src := range sources { + f, err := parser.ParseFile(fset, fmt.Sprintf("sources[%d]", i), src, parser.DeclarationErrors) + if err != nil { + t.Fatal(err) + } + files = append(files, f) + } + + // resolve and type-check package AST + importer := new(resolveTestImporter) + conf := Config{Importer: importer} + uses := make(map[*ast.Ident]Object) + defs := make(map[*ast.Ident]Object) + _, err := conf.Check("testResolveIdents", fset, files, &Info{Defs: defs, Uses: uses}) + if err != nil { + t.Fatal(err) + } + + // check that all packages were imported + for _, name := range pkgnames { + if !importer.imported[name] { + t.Errorf("package %s not imported", name) + } + } + + // check that qualified identifiers are resolved + for _, f := range files { + ast.Inspect(f, func(n ast.Node) bool { + if s, ok := n.(*ast.SelectorExpr); ok { + if x, ok := s.X.(*ast.Ident); ok { + obj := uses[x] + if obj == nil { + t.Errorf("%s: unresolved qualified identifier %s", fset.Position(x.Pos()), x.Name) + return false + } + if _, ok := obj.(*PkgName); ok && uses[s.Sel] == nil { + t.Errorf("%s: unresolved selector %s", fset.Position(s.Sel.Pos()), s.Sel.Name) + return false + } + return false + } + return false + } + return true + }) + } + + for id, obj := range uses { + if obj == nil { + t.Errorf("%s: Uses[%s] == nil", fset.Position(id.Pos()), id.Name) + } + } + + // check that each identifier in the source is found in uses or defs or both + var both []string + for _, f := range files { + ast.Inspect(f, func(n ast.Node) bool { + if x, ok := n.(*ast.Ident); ok { + var objects int + if _, found := uses[x]; found { + objects |= 1 + delete(uses, x) + } + if _, found := defs[x]; found { + objects |= 2 + delete(defs, x) + } + if objects == 0 { + t.Errorf("%s: unresolved identifier %s", fset.Position(x.Pos()), x.Name) + } else if objects == 3 { + both = append(both, x.Name) + } + return false + } + return true + }) + } + + // check the expected set of idents that are simultaneously uses and defs + sort.Strings(both) + if got, want := fmt.Sprint(both), "[Mutex Stringer error]"; got != want { + t.Errorf("simultaneous uses/defs = %s, want %s", got, want) + } + + // any left-over identifiers didn't exist in the source + for x := range uses { + t.Errorf("%s: identifier %s not present in source", fset.Position(x.Pos()), x.Name) + } + for x := range defs { + t.Errorf("%s: identifier %s not present in source", fset.Position(x.Pos()), x.Name) + } + + // TODO(gri) add tests to check ImplicitObj callbacks +} diff --git a/src/go/types/return.go b/src/go/types/return.go new file mode 100644 index 0000000..2d34a70 --- /dev/null +++ b/src/go/types/return.go @@ -0,0 +1,184 @@ +// Copyright 2013 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. + +// This file implements isTerminating. + +package types + +import ( + "go/ast" + "go/token" +) + +// isTerminating reports if s is a terminating statement. +// If s is labeled, label is the label name; otherwise s +// is "". +func (check *Checker) isTerminating(s ast.Stmt, label string) bool { + switch s := s.(type) { + default: + unreachable() + + case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.SendStmt, + *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, *ast.DeferStmt, + *ast.RangeStmt: + // no chance + + case *ast.LabeledStmt: + return check.isTerminating(s.Stmt, s.Label.Name) + + case *ast.ExprStmt: + // calling the predeclared (possibly parenthesized) panic() function is terminating + if call, ok := unparen(s.X).(*ast.CallExpr); ok && check.isPanic[call] { + return true + } + + case *ast.ReturnStmt: + return true + + case *ast.BranchStmt: + if s.Tok == token.GOTO || s.Tok == token.FALLTHROUGH { + return true + } + + case *ast.BlockStmt: + return check.isTerminatingList(s.List, "") + + case *ast.IfStmt: + if s.Else != nil && + check.isTerminating(s.Body, "") && + check.isTerminating(s.Else, "") { + return true + } + + case *ast.SwitchStmt: + return check.isTerminatingSwitch(s.Body, label) + + case *ast.TypeSwitchStmt: + return check.isTerminatingSwitch(s.Body, label) + + case *ast.SelectStmt: + for _, s := range s.Body.List { + cc := s.(*ast.CommClause) + if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { + return false + } + + } + return true + + case *ast.ForStmt: + if s.Cond == nil && !hasBreak(s.Body, label, true) { + return true + } + } + + return false +} + +func (check *Checker) isTerminatingList(list []ast.Stmt, label string) bool { + // trailing empty statements are permitted - skip them + for i := len(list) - 1; i >= 0; i-- { + if _, ok := list[i].(*ast.EmptyStmt); !ok { + return check.isTerminating(list[i], label) + } + } + return false // all statements are empty +} + +func (check *Checker) isTerminatingSwitch(body *ast.BlockStmt, label string) bool { + hasDefault := false + for _, s := range body.List { + cc := s.(*ast.CaseClause) + if cc.List == nil { + hasDefault = true + } + if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { + return false + } + } + return hasDefault +} + +// TODO(gri) For nested breakable statements, the current implementation of hasBreak +// will traverse the same subtree repeatedly, once for each label. Replace +// with a single-pass label/break matching phase. + +// hasBreak reports if s is or contains a break statement +// referring to the label-ed statement or implicit-ly the +// closest outer breakable statement. +func hasBreak(s ast.Stmt, label string, implicit bool) bool { + switch s := s.(type) { + default: + unreachable() + + case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.ExprStmt, + *ast.SendStmt, *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, + *ast.DeferStmt, *ast.ReturnStmt: + // no chance + + case *ast.LabeledStmt: + return hasBreak(s.Stmt, label, implicit) + + case *ast.BranchStmt: + if s.Tok == token.BREAK { + if s.Label == nil { + return implicit + } + if s.Label.Name == label { + return true + } + } + + case *ast.BlockStmt: + return hasBreakList(s.List, label, implicit) + + case *ast.IfStmt: + if hasBreak(s.Body, label, implicit) || + s.Else != nil && hasBreak(s.Else, label, implicit) { + return true + } + + case *ast.CaseClause: + return hasBreakList(s.Body, label, implicit) + + case *ast.SwitchStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.TypeSwitchStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.CommClause: + return hasBreakList(s.Body, label, implicit) + + case *ast.SelectStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.ForStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.RangeStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + } + + return false +} + +func hasBreakList(list []ast.Stmt, label string, implicit bool) bool { + for _, s := range list { + if hasBreak(s, label, implicit) { + return true + } + } + return false +} diff --git a/src/go/types/scope.go b/src/go/types/scope.go new file mode 100644 index 0000000..8c9d9ab --- /dev/null +++ b/src/go/types/scope.go @@ -0,0 +1,182 @@ +// Copyright 2013 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. + +// This file implements Scopes. + +package types + +import ( + "bytes" + "fmt" + "go/token" + "io" + "sort" + "strings" +) + +// A Scope maintains a set of objects and links to its containing +// (parent) and contained (children) scopes. Objects may be inserted +// and looked up by name. The zero value for Scope is a ready-to-use +// empty scope. +type Scope struct { + parent *Scope + children []*Scope + elems map[string]Object // lazily allocated + pos, end token.Pos // scope extent; may be invalid + comment string // for debugging only + isFunc bool // set if this is a function scope (internal use only) +} + +// NewScope returns a new, empty scope contained in the given parent +// scope, if any. The comment is for debugging only. +func NewScope(parent *Scope, pos, end token.Pos, comment string) *Scope { + s := &Scope{parent, nil, nil, pos, end, comment, false} + // don't add children to Universe scope! + if parent != nil && parent != Universe { + parent.children = append(parent.children, s) + } + return s +} + +// Parent returns the scope's containing (parent) scope. +func (s *Scope) Parent() *Scope { return s.parent } + +// Len returns the number of scope elements. +func (s *Scope) Len() int { return len(s.elems) } + +// Names returns the scope's element names in sorted order. +func (s *Scope) Names() []string { + names := make([]string, len(s.elems)) + i := 0 + for name := range s.elems { + names[i] = name + i++ + } + sort.Strings(names) + return names +} + +// NumChildren returns the number of scopes nested in s. +func (s *Scope) NumChildren() int { return len(s.children) } + +// Child returns the i'th child scope for 0 <= i < NumChildren(). +func (s *Scope) Child(i int) *Scope { return s.children[i] } + +// Lookup returns the object in scope s with the given name if such an +// object exists; otherwise the result is nil. +func (s *Scope) Lookup(name string) Object { + return s.elems[name] +} + +// LookupParent follows the parent chain of scopes starting with s until +// it finds a scope where Lookup(name) returns a non-nil object, and then +// returns that scope and object. If a valid position pos is provided, +// only objects that were declared at or before pos are considered. +// If no such scope and object exists, the result is (nil, nil). +// +// Note that obj.Parent() may be different from the returned scope if the +// object was inserted into the scope and already had a parent at that +// time (see Insert). This can only happen for dot-imported objects +// whose scope is the scope of the package that exported them. +func (s *Scope) LookupParent(name string, pos token.Pos) (*Scope, Object) { + for ; s != nil; s = s.parent { + if obj := s.elems[name]; obj != nil && (!pos.IsValid() || obj.scopePos() <= pos) { + return s, obj + } + } + return nil, nil +} + +// Insert attempts to insert an object obj into scope s. +// If s already contains an alternative object alt with +// the same name, Insert leaves s unchanged and returns alt. +// Otherwise it inserts obj, sets the object's parent scope +// if not already set, and returns nil. +func (s *Scope) Insert(obj Object) Object { + name := obj.Name() + if alt := s.elems[name]; alt != nil { + return alt + } + if s.elems == nil { + s.elems = make(map[string]Object) + } + s.elems[name] = obj + if obj.Parent() == nil { + obj.setParent(s) + } + return nil +} + +// Pos and End describe the scope's source code extent [pos, end). +// The results are guaranteed to be valid only if the type-checked +// AST has complete position information. The extent is undefined +// for Universe and package scopes. +func (s *Scope) Pos() token.Pos { return s.pos } +func (s *Scope) End() token.Pos { return s.end } + +// Contains reports whether pos is within the scope's extent. +// The result is guaranteed to be valid only if the type-checked +// AST has complete position information. +func (s *Scope) Contains(pos token.Pos) bool { + return s.pos <= pos && pos < s.end +} + +// Innermost returns the innermost (child) scope containing +// pos. If pos is not within any scope, the result is nil. +// The result is also nil for the Universe scope. +// The result is guaranteed to be valid only if the type-checked +// AST has complete position information. +func (s *Scope) Innermost(pos token.Pos) *Scope { + // Package scopes do not have extents since they may be + // discontiguous, so iterate over the package's files. + if s.parent == Universe { + for _, s := range s.children { + if inner := s.Innermost(pos); inner != nil { + return inner + } + } + } + + if s.Contains(pos) { + for _, s := range s.children { + if s.Contains(pos) { + return s.Innermost(pos) + } + } + return s + } + return nil +} + +// WriteTo writes a string representation of the scope to w, +// with the scope elements sorted by name. +// The level of indentation is controlled by n >= 0, with +// n == 0 for no indentation. +// If recurse is set, it also writes nested (children) scopes. +func (s *Scope) WriteTo(w io.Writer, n int, recurse bool) { + const ind = ". " + indn := strings.Repeat(ind, n) + + fmt.Fprintf(w, "%s%s scope %p {\n", indn, s.comment, s) + + indn1 := indn + ind + for _, name := range s.Names() { + fmt.Fprintf(w, "%s%s\n", indn1, s.elems[name]) + } + + if recurse { + for _, s := range s.children { + s.WriteTo(w, n+1, recurse) + } + } + + fmt.Fprintf(w, "%s}\n", indn) +} + +// String returns a string representation of the scope, for debugging. +func (s *Scope) String() string { + var buf bytes.Buffer + s.WriteTo(&buf, 0, false) + return buf.String() +} diff --git a/src/go/types/selection.go b/src/go/types/selection.go new file mode 100644 index 0000000..6ec69d2 --- /dev/null +++ b/src/go/types/selection.go @@ -0,0 +1,143 @@ +// Copyright 2013 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. + +// This file implements Selections. + +package types + +import ( + "bytes" + "fmt" +) + +// SelectionKind describes the kind of a selector expression x.f +// (excluding qualified identifiers). +type SelectionKind int + +const ( + FieldVal SelectionKind = iota // x.f is a struct field selector + MethodVal // x.f is a method selector + MethodExpr // x.f is a method expression +) + +// A Selection describes a selector expression x.f. +// For the declarations: +// +// type T struct{ x int; E } +// type E struct{} +// func (e E) m() {} +// var p *T +// +// the following relations exist: +// +// Selector Kind Recv Obj Type Index Indirect +// +// p.x FieldVal T x int {0} true +// p.m MethodVal *T m func() {1, 0} true +// T.m MethodExpr T m func(T) {1, 0} false +// +type Selection struct { + kind SelectionKind + recv Type // type of x + obj Object // object denoted by x.f + index []int // path from x to x.f + indirect bool // set if there was any pointer indirection on the path +} + +// Kind returns the selection kind. +func (s *Selection) Kind() SelectionKind { return s.kind } + +// Recv returns the type of x in x.f. +func (s *Selection) Recv() Type { return s.recv } + +// Obj returns the object denoted by x.f; a *Var for +// a field selection, and a *Func in all other cases. +func (s *Selection) Obj() Object { return s.obj } + +// Type returns the type of x.f, which may be different from the type of f. +// See Selection for more information. +func (s *Selection) Type() Type { + switch s.kind { + case MethodVal: + // The type of x.f is a method with its receiver type set + // to the type of x. + sig := *s.obj.(*Func).typ.(*Signature) + recv := *sig.recv + recv.typ = s.recv + sig.recv = &recv + return &sig + + case MethodExpr: + // The type of x.f is a function (without receiver) + // and an additional first argument with the same type as x. + // TODO(gri) Similar code is already in call.go - factor! + // TODO(gri) Compute this eagerly to avoid allocations. + sig := *s.obj.(*Func).typ.(*Signature) + arg0 := *sig.recv + sig.recv = nil + arg0.typ = s.recv + var params []*Var + if sig.params != nil { + params = sig.params.vars + } + sig.params = NewTuple(append([]*Var{&arg0}, params...)...) + return &sig + } + + // In all other cases, the type of x.f is the type of x. + return s.obj.Type() +} + +// Index describes the path from x to f in x.f. +// The last index entry is the field or method index of the type declaring f; +// either: +// +// 1) the list of declared methods of a named type; or +// 2) the list of methods of an interface type; or +// 3) the list of fields of a struct type. +// +// The earlier index entries are the indices of the embedded fields implicitly +// traversed to get from (the type of) x to f, starting at embedding depth 0. +func (s *Selection) Index() []int { return s.index } + +// Indirect reports whether any pointer indirection was required to get from +// x to f in x.f. +func (s *Selection) Indirect() bool { return s.indirect } + +func (s *Selection) String() string { return SelectionString(s, nil) } + +// SelectionString returns the string form of s. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +// +// Examples: +// "field (T) f int" +// "method (T) f(X) Y" +// "method expr (T) f(X) Y" +// +func SelectionString(s *Selection, qf Qualifier) string { + var k string + switch s.kind { + case FieldVal: + k = "field " + case MethodVal: + k = "method " + case MethodExpr: + k = "method expr " + default: + unreachable() + } + var buf bytes.Buffer + buf.WriteString(k) + buf.WriteByte('(') + WriteType(&buf, s.Recv(), qf) + fmt.Fprintf(&buf, ") %s", s.obj.Name()) + if T := s.Type(); s.kind == FieldVal { + buf.WriteByte(' ') + WriteType(&buf, T, qf) + } else { + WriteSignature(&buf, T.(*Signature), qf) + } + return buf.String() +} diff --git a/src/go/types/self_test.go b/src/go/types/self_test.go new file mode 100644 index 0000000..262dc7b --- /dev/null +++ b/src/go/types/self_test.go @@ -0,0 +1,121 @@ +// Copyright 2013 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 types_test + +import ( + "go/ast" + "go/importer" + "go/parser" + "go/token" + "path" + "path/filepath" + "testing" + "time" + + . "go/types" +) + +func TestSelf(t *testing.T) { + fset := token.NewFileSet() + files, err := pkgFiles(fset, ".", 0) + if err != nil { + t.Fatal(err) + } + + conf := Config{Importer: importer.Default()} + _, err = conf.Check("go/types", fset, files, nil) + if err != nil { + // Importing go/constant doesn't work in the + // build dashboard environment. Don't report an error + // for now so that the build remains green. + // TODO(gri) fix this + t.Log(err) // replace w/ t.Fatal eventually + return + } +} + +func BenchmarkCheck(b *testing.B) { + for _, p := range []string{ + "net/http", + "go/parser", + "go/constant", + filepath.Join("go", "internal", "gcimporter"), + } { + b.Run(path.Base(p), func(b *testing.B) { + path := filepath.Join("..", "..", p) + for _, ignoreFuncBodies := range []bool{false, true} { + name := "funcbodies" + if ignoreFuncBodies { + name = "nofuncbodies" + } + b.Run(name, func(b *testing.B) { + b.Run("info", func(b *testing.B) { + runbench(b, path, ignoreFuncBodies, true) + }) + b.Run("noinfo", func(b *testing.B) { + runbench(b, path, ignoreFuncBodies, false) + }) + }) + } + }) + } +} + +func runbench(b *testing.B, path string, ignoreFuncBodies, writeInfo bool) { + fset := token.NewFileSet() + files, err := pkgFiles(fset, path, 0) + if err != nil { + b.Fatal(err) + } + // determine line count + lines := 0 + fset.Iterate(func(f *token.File) bool { + lines += f.LineCount() + return true + }) + + b.ResetTimer() + start := time.Now() + for i := 0; i < b.N; i++ { + conf := Config{ + IgnoreFuncBodies: ignoreFuncBodies, + Importer: importer.Default(), + } + var info *Info + if writeInfo { + info = &Info{ + Types: make(map[ast.Expr]TypeAndValue), + Defs: make(map[*ast.Ident]Object), + Uses: make(map[*ast.Ident]Object), + Implicits: make(map[ast.Node]Object), + Selections: make(map[*ast.SelectorExpr]*Selection), + Scopes: make(map[ast.Node]*Scope), + } + } + if _, err := conf.Check(path, fset, files, info); err != nil { + b.Fatal(err) + } + } + b.StopTimer() + b.ReportMetric(float64(lines)*float64(b.N)/time.Since(start).Seconds(), "lines/s") +} + +func pkgFiles(fset *token.FileSet, path string, mode parser.Mode) ([]*ast.File, error) { + filenames, err := pkgFilenames(path) // from stdlib_test.go + if err != nil { + return nil, err + } + + var files []*ast.File + for _, filename := range filenames { + file, err := parser.ParseFile(fset, filename, nil, mode) + if err != nil { + return nil, err + } + files = append(files, file) + } + + return files, nil +} diff --git a/src/go/types/sizes.go b/src/go/types/sizes.go new file mode 100644 index 0000000..6ab6157 --- /dev/null +++ b/src/go/types/sizes.go @@ -0,0 +1,263 @@ +// Copyright 2013 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. + +// This file implements Sizes. + +package types + +// Sizes defines the sizing functions for package unsafe. +type Sizes interface { + // Alignof returns the alignment of a variable of type T. + // Alignof must implement the alignment guarantees required by the spec. + Alignof(T Type) int64 + + // Offsetsof returns the offsets of the given struct fields, in bytes. + // Offsetsof must implement the offset guarantees required by the spec. + Offsetsof(fields []*Var) []int64 + + // Sizeof returns the size of a variable of type T. + // Sizeof must implement the size guarantees required by the spec. + Sizeof(T Type) int64 +} + +// StdSizes is a convenience type for creating commonly used Sizes. +// It makes the following simplifying assumptions: +// +// - The size of explicitly sized basic types (int16, etc.) is the +// specified size. +// - The size of strings and interfaces is 2*WordSize. +// - The size of slices is 3*WordSize. +// - The size of an array of n elements corresponds to the size of +// a struct of n consecutive fields of the array's element type. +// - The size of a struct is the offset of the last field plus that +// field's size. As with all element types, if the struct is used +// in an array its size must first be aligned to a multiple of the +// struct's alignment. +// - All other types have size WordSize. +// - Arrays and structs are aligned per spec definition; all other +// types are naturally aligned with a maximum alignment MaxAlign. +// +// *StdSizes implements Sizes. +// +type StdSizes struct { + WordSize int64 // word size in bytes - must be >= 4 (32bits) + MaxAlign int64 // maximum alignment in bytes - must be >= 1 +} + +func (s *StdSizes) Alignof(T Type) int64 { + // For arrays and structs, alignment is defined in terms + // of alignment of the elements and fields, respectively. + switch t := T.Underlying().(type) { + case *Array: + // spec: "For a variable x of array type: unsafe.Alignof(x) + // is the same as unsafe.Alignof(x[0]), but at least 1." + return s.Alignof(t.elem) + case *Struct: + // spec: "For a variable x of struct type: unsafe.Alignof(x) + // is the largest of the values unsafe.Alignof(x.f) for each + // field f of x, but at least 1." + max := int64(1) + for _, f := range t.fields { + if a := s.Alignof(f.typ); a > max { + max = a + } + } + return max + case *Slice, *Interface: + // Multiword data structures are effectively structs + // in which each element has size WordSize. + return s.WordSize + case *Basic: + // Strings are like slices and interfaces. + if t.Info()&IsString != 0 { + return s.WordSize + } + } + a := s.Sizeof(T) // may be 0 + // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1." + if a < 1 { + return 1 + } + // complex{64,128} are aligned like [2]float{32,64}. + if isComplex(T) { + a /= 2 + } + if a > s.MaxAlign { + return s.MaxAlign + } + return a +} + +func (s *StdSizes) Offsetsof(fields []*Var) []int64 { + offsets := make([]int64, len(fields)) + var o int64 + for i, f := range fields { + a := s.Alignof(f.typ) + o = align(o, a) + offsets[i] = o + o += s.Sizeof(f.typ) + } + return offsets +} + +var basicSizes = [...]byte{ + Bool: 1, + Int8: 1, + Int16: 2, + Int32: 4, + Int64: 8, + Uint8: 1, + Uint16: 2, + Uint32: 4, + Uint64: 8, + Float32: 4, + Float64: 8, + Complex64: 8, + Complex128: 16, +} + +func (s *StdSizes) Sizeof(T Type) int64 { + switch t := T.Underlying().(type) { + case *Basic: + assert(isTyped(T)) + k := t.kind + if int(k) < len(basicSizes) { + if s := basicSizes[k]; s > 0 { + return int64(s) + } + } + if k == String { + return s.WordSize * 2 + } + case *Array: + n := t.len + if n <= 0 { + return 0 + } + // n > 0 + a := s.Alignof(t.elem) + z := s.Sizeof(t.elem) + return align(z, a)*(n-1) + z + case *Slice: + return s.WordSize * 3 + case *Struct: + n := t.NumFields() + if n == 0 { + return 0 + } + offsets := s.Offsetsof(t.fields) + return offsets[n-1] + s.Sizeof(t.fields[n-1].typ) + case *Interface: + return s.WordSize * 2 + } + return s.WordSize // catch-all +} + +// common architecture word sizes and alignments +var gcArchSizes = map[string]*StdSizes{ + "386": {4, 4}, + "arm": {4, 4}, + "arm64": {8, 8}, + "amd64": {8, 8}, + "amd64p32": {4, 8}, + "mips": {4, 4}, + "mipsle": {4, 4}, + "mips64": {8, 8}, + "mips64le": {8, 8}, + "ppc64": {8, 8}, + "ppc64le": {8, 8}, + "riscv64": {8, 8}, + "s390x": {8, 8}, + "sparc64": {8, 8}, + "wasm": {8, 8}, + // When adding more architectures here, + // update the doc string of SizesFor below. +} + +// SizesFor returns the Sizes used by a compiler for an architecture. +// The result is nil if a compiler/architecture pair is not known. +// +// Supported architectures for compiler "gc": +// "386", "arm", "arm64", "amd64", "amd64p32", "mips", "mipsle", +// "mips64", "mips64le", "ppc64", "ppc64le", "riscv64", "s390x", "sparc64", "wasm". +func SizesFor(compiler, arch string) Sizes { + var m map[string]*StdSizes + switch compiler { + case "gc": + m = gcArchSizes + case "gccgo": + m = gccgoArchSizes + default: + return nil + } + s, ok := m[arch] + if !ok { + return nil + } + return s +} + +// stdSizes is used if Config.Sizes == nil. +var stdSizes = SizesFor("gc", "amd64") + +func (conf *Config) alignof(T Type) int64 { + if s := conf.Sizes; s != nil { + if a := s.Alignof(T); a >= 1 { + return a + } + panic("Config.Sizes.Alignof returned an alignment < 1") + } + return stdSizes.Alignof(T) +} + +func (conf *Config) offsetsof(T *Struct) []int64 { + var offsets []int64 + if T.NumFields() > 0 { + // compute offsets on demand + if s := conf.Sizes; s != nil { + offsets = s.Offsetsof(T.fields) + // sanity checks + if len(offsets) != T.NumFields() { + panic("Config.Sizes.Offsetsof returned the wrong number of offsets") + } + for _, o := range offsets { + if o < 0 { + panic("Config.Sizes.Offsetsof returned an offset < 0") + } + } + } else { + offsets = stdSizes.Offsetsof(T.fields) + } + } + return offsets +} + +// offsetof returns the offset of the field specified via +// the index sequence relative to typ. All embedded fields +// must be structs (rather than pointer to structs). +func (conf *Config) offsetof(typ Type, index []int) int64 { + var o int64 + for _, i := range index { + s := typ.Underlying().(*Struct) + o += conf.offsetsof(s)[i] + typ = s.fields[i].typ + } + return o +} + +func (conf *Config) sizeof(T Type) int64 { + if s := conf.Sizes; s != nil { + if z := s.Sizeof(T); z >= 0 { + return z + } + panic("Config.Sizes.Sizeof returned a size < 0") + } + return stdSizes.Sizeof(T) +} + +// align returns the smallest y >= x such that y % a == 0. +func align(x, a int64) int64 { + y := x + a - 1 + return y - y%a +} diff --git a/src/go/types/sizes_test.go b/src/go/types/sizes_test.go new file mode 100644 index 0000000..539b4e3 --- /dev/null +++ b/src/go/types/sizes_test.go @@ -0,0 +1,112 @@ +// Copyright 2016 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. + +// This file contains tests for sizes. + +package types_test + +import ( + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "testing" +) + +// findStructType typechecks src and returns the first struct type encountered. +func findStructType(t *testing.T, src string) *types.Struct { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "x.go", src, 0) + if err != nil { + t.Fatal(err) + } + info := types.Info{Types: make(map[ast.Expr]types.TypeAndValue)} + var conf types.Config + _, err = conf.Check("x", fset, []*ast.File{f}, &info) + if err != nil { + t.Fatal(err) + } + for _, tv := range info.Types { + if ts, ok := tv.Type.(*types.Struct); ok { + return ts + } + } + t.Fatalf("failed to find a struct type in src:\n%s\n", src) + return nil +} + +// Issue 16316 +func TestMultipleSizeUse(t *testing.T) { + const src = ` +package main + +type S struct { + i int + b bool + s string + n int +} +` + ts := findStructType(t, src) + sizes := types.StdSizes{WordSize: 4, MaxAlign: 4} + if got := sizes.Sizeof(ts); got != 20 { + t.Errorf("Sizeof(%v) with WordSize 4 = %d want 20", ts, got) + } + sizes = types.StdSizes{WordSize: 8, MaxAlign: 8} + if got := sizes.Sizeof(ts); got != 40 { + t.Errorf("Sizeof(%v) with WordSize 8 = %d want 40", ts, got) + } +} + +// Issue 16464 +func TestAlignofNaclSlice(t *testing.T) { + const src = ` +package main + +var s struct { + x *int + y []byte +} +` + ts := findStructType(t, src) + sizes := &types.StdSizes{WordSize: 4, MaxAlign: 8} + var fields []*types.Var + // Make a copy manually :( + for i := 0; i < ts.NumFields(); i++ { + fields = append(fields, ts.Field(i)) + } + offsets := sizes.Offsetsof(fields) + if offsets[0] != 0 || offsets[1] != 4 { + t.Errorf("OffsetsOf(%v) = %v want %v", ts, offsets, []int{0, 4}) + } +} + +func TestIssue16902(t *testing.T) { + const src = ` +package a + +import "unsafe" + +const _ = unsafe.Offsetof(struct{ x int64 }{}.x) +` + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "x.go", src, 0) + if err != nil { + t.Fatal(err) + } + info := types.Info{Types: make(map[ast.Expr]types.TypeAndValue)} + conf := types.Config{ + Importer: importer.Default(), + Sizes: &types.StdSizes{WordSize: 8, MaxAlign: 8}, + } + _, err = conf.Check("x", fset, []*ast.File{f}, &info) + if err != nil { + t.Fatal(err) + } + for _, tv := range info.Types { + _ = conf.Sizes.Sizeof(tv.Type) + _ = conf.Sizes.Alignof(tv.Type) + } +} diff --git a/src/go/types/stdlib_test.go b/src/go/types/stdlib_test.go new file mode 100644 index 0000000..5ca4493 --- /dev/null +++ b/src/go/types/stdlib_test.go @@ -0,0 +1,326 @@ +// Copyright 2013 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. + +// This file tests types.Check by using it to +// typecheck the standard library and tests. + +package types_test + +import ( + "fmt" + "go/ast" + "go/build" + "go/importer" + "go/parser" + "go/scanner" + "go/token" + "internal/testenv" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + . "go/types" +) + +// Use the same importer for all std lib tests to +// avoid repeated importing of the same packages. +var stdLibImporter = importer.Default() + +func TestStdlib(t *testing.T) { + testenv.MustHaveGoBuild(t) + + pkgCount := 0 + duration := walkPkgDirs(filepath.Join(runtime.GOROOT(), "src"), func(dir string, filenames []string) { + typecheck(t, dir, filenames) + pkgCount++ + }, t.Error) + + if testing.Verbose() { + fmt.Println(pkgCount, "packages typechecked in", duration) + } +} + +// firstComment returns the contents of the first non-empty comment in +// the given file, "skip", or the empty string. No matter the present +// comments, if any of them contains a build tag, the result is always +// "skip". Only comments before the "package" token and within the first +// 4K of the file are considered. +func firstComment(filename string) string { + f, err := os.Open(filename) + if err != nil { + return "" + } + defer f.Close() + + var src [4 << 10]byte // read at most 4KB + n, _ := f.Read(src[:]) + + var first string + var s scanner.Scanner + s.Init(fset.AddFile("", fset.Base(), n), src[:n], nil /* ignore errors */, scanner.ScanComments) + for { + _, tok, lit := s.Scan() + switch tok { + case token.COMMENT: + // remove trailing */ of multi-line comment + if lit[1] == '*' { + lit = lit[:len(lit)-2] + } + contents := strings.TrimSpace(lit[2:]) + if strings.HasPrefix(contents, "+build ") { + return "skip" + } + if first == "" { + first = contents // contents may be "" but that's ok + } + // continue as we may still see build tags + + case token.PACKAGE, token.EOF: + return first + } + } +} + +func testTestDir(t *testing.T, path string, ignore ...string) { + files, err := os.ReadDir(path) + if err != nil { + t.Fatal(err) + } + + excluded := make(map[string]bool) + for _, filename := range ignore { + excluded[filename] = true + } + + fset := token.NewFileSet() + for _, f := range files { + // filter directory contents + if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] { + continue + } + + // get per-file instructions + expectErrors := false + filename := filepath.Join(path, f.Name()) + if comment := firstComment(filename); comment != "" { + fields := strings.Fields(comment) + switch fields[0] { + case "skip", "compiledir": + continue // ignore this file + case "errorcheck": + expectErrors = true + for _, arg := range fields[1:] { + if arg == "-0" || arg == "-+" || arg == "-std" { + // Marked explicitly as not expected errors (-0), + // or marked as compiling runtime/stdlib, which is only done + // to trigger runtime/stdlib-only error output. + // In both cases, the code should typecheck. + expectErrors = false + break + } + } + } + } + + // parse and type-check file + file, err := parser.ParseFile(fset, filename, nil, 0) + if err == nil { + conf := Config{Importer: stdLibImporter} + _, err = conf.Check(filename, fset, []*ast.File{file}, nil) + } + + if expectErrors { + if err == nil { + t.Errorf("expected errors but found none in %s", filename) + } + } else { + if err != nil { + t.Error(err) + } + } + } +} + +func TestStdTest(t *testing.T) { + testenv.MustHaveGoBuild(t) + + if testing.Short() && testenv.Builder() == "" { + t.Skip("skipping in short mode") + } + + testTestDir(t, filepath.Join(runtime.GOROOT(), "test"), + "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore + "directive.go", // tests compiler rejection of bad directive placement - ignore + "embedfunc.go", // tests //go:embed + "embedvers.go", // tests //go:embed + ) +} + +func TestStdFixed(t *testing.T) { + testenv.MustHaveGoBuild(t) + + if testing.Short() && testenv.Builder() == "" { + t.Skip("skipping in short mode") + } + + testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "fixedbugs"), + "bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore + "issue6889.go", // gc-specific test + "issue7746.go", // large constants - consumes too much memory + "issue11362.go", // canonical import path check + "issue16369.go", // go/types handles this correctly - not an issue + "issue18459.go", // go/types doesn't check validity of //go:xxx directives + "issue18882.go", // go/types doesn't check validity of //go:xxx directives + "issue20232.go", // go/types handles larger constants than gc + "issue20529.go", // go/types does not have constraints on stack size + "issue22200.go", // go/types does not have constraints on stack size + "issue22200b.go", // go/types does not have constraints on stack size + "issue25507.go", // go/types does not have constraints on stack size + "issue20780.go", // go/types does not have constraints on stack size + "issue31747.go", // go/types does not have constraints on language level (-lang=go1.12) (see #31793) + "issue34329.go", // go/types does not have constraints on language level (-lang=go1.13) (see #31793) + "bug251.go", // issue #34333 which was exposed with fix for #34151 + "issue42058a.go", // go/types does not have constraints on channel element size + "issue42058b.go", // go/types does not have constraints on channel element size + ) +} + +func TestStdKen(t *testing.T) { + testenv.MustHaveGoBuild(t) + + testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "ken")) +} + +// Package paths of excluded packages. +var excluded = map[string]bool{ + "builtin": true, +} + +// typecheck typechecks the given package files. +func typecheck(t *testing.T, path string, filenames []string) { + fset := token.NewFileSet() + + // parse package files + var files []*ast.File + for _, filename := range filenames { + file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) + if err != nil { + // the parser error may be a list of individual errors; report them all + if list, ok := err.(scanner.ErrorList); ok { + for _, err := range list { + t.Error(err) + } + return + } + t.Error(err) + return + } + + if testing.Verbose() { + if len(files) == 0 { + fmt.Println("package", file.Name.Name) + } + fmt.Println("\t", filename) + } + + files = append(files, file) + } + + // typecheck package files + conf := Config{ + Error: func(err error) { t.Error(err) }, + Importer: stdLibImporter, + } + info := Info{Uses: make(map[*ast.Ident]Object)} + conf.Check(path, fset, files, &info) + + // Perform checks of API invariants. + + // All Objects have a package, except predeclared ones. + errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error + for id, obj := range info.Uses { + predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError + if predeclared == (obj.Pkg() != nil) { + posn := fset.Position(id.Pos()) + if predeclared { + t.Errorf("%s: predeclared object with package: %s", posn, obj) + } else { + t.Errorf("%s: user-defined object without package: %s", posn, obj) + } + } + } +} + +// pkgFilenames returns the list of package filenames for the given directory. +func pkgFilenames(dir string) ([]string, error) { + ctxt := build.Default + ctxt.CgoEnabled = false + pkg, err := ctxt.ImportDir(dir, 0) + if err != nil { + if _, nogo := err.(*build.NoGoError); nogo { + return nil, nil // no *.go files, not an error + } + return nil, err + } + if excluded[pkg.ImportPath] { + return nil, nil + } + var filenames []string + for _, name := range pkg.GoFiles { + filenames = append(filenames, filepath.Join(pkg.Dir, name)) + } + for _, name := range pkg.TestGoFiles { + filenames = append(filenames, filepath.Join(pkg.Dir, name)) + } + return filenames, nil +} + +func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...interface{})) time.Duration { + w := walker{time.Now(), 10 * time.Millisecond, pkgh, errh} + w.walk(dir) + return time.Since(w.start) +} + +type walker struct { + start time.Time + dmax time.Duration + pkgh func(dir string, filenames []string) + errh func(args ...interface{}) +} + +func (w *walker) walk(dir string) { + // limit run time for short tests + if testing.Short() && time.Since(w.start) >= w.dmax { + return + } + + files, err := os.ReadDir(dir) + if err != nil { + w.errh(err) + return + } + + // apply pkgh to the files in directory dir + // but ignore files directly under $GOROOT/src (might be temporary test files). + if dir != filepath.Join(runtime.GOROOT(), "src") { + files, err := pkgFilenames(dir) + if err != nil { + w.errh(err) + return + } + if files != nil { + w.pkgh(dir, files) + } + } + + // traverse subdirectories, but don't walk into testdata + for _, f := range files { + if f.IsDir() && f.Name() != "testdata" { + w.walk(filepath.Join(dir, f.Name())) + } + } +} diff --git a/src/go/types/stmt.go b/src/go/types/stmt.go new file mode 100644 index 0000000..d88e471 --- /dev/null +++ b/src/go/types/stmt.go @@ -0,0 +1,881 @@ +// Copyright 2012 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. + +// This file implements typechecking of statements. + +package types + +import ( + "go/ast" + "go/constant" + "go/token" + "sort" +) + +func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body *ast.BlockStmt, iota constant.Value) { + if trace { + check.trace(body.Pos(), "--- %s: %s", name, sig) + defer func() { + check.trace(body.End(), "--- ") + }() + } + + // set function scope extent + sig.scope.pos = body.Pos() + sig.scope.end = body.End() + + // save/restore current context and setup function context + // (and use 0 indentation at function start) + defer func(ctxt context, indent int) { + check.context = ctxt + check.indent = indent + }(check.context, check.indent) + check.context = context{ + decl: decl, + scope: sig.scope, + iota: iota, + sig: sig, + } + check.indent = 0 + + check.stmtList(0, body.List) + + if check.hasLabel { + check.labels(body) + } + + if sig.results.Len() > 0 && !check.isTerminating(body, "") { + check.error(atPos(body.Rbrace), _MissingReturn, "missing return") + } + + // spec: "Implementation restriction: A compiler may make it illegal to + // declare a variable inside a function body if the variable is never used." + check.usage(sig.scope) +} + +func (check *Checker) usage(scope *Scope) { + var unused []*Var + for _, elem := range scope.elems { + if v, _ := elem.(*Var); v != nil && !v.used { + unused = append(unused, v) + } + } + sort.Slice(unused, func(i, j int) bool { + return unused[i].pos < unused[j].pos + }) + for _, v := range unused { + check.softErrorf(v, _UnusedVar, "%s declared but not used", v.name) + } + + for _, scope := range scope.children { + // Don't go inside function literal scopes a second time; + // they are handled explicitly by funcBody. + if !scope.isFunc { + check.usage(scope) + } + } +} + +// stmtContext is a bitset describing which +// control-flow statements are permissible, +// and provides additional context information +// for better error messages. +type stmtContext uint + +const ( + // permissible control-flow statements + breakOk stmtContext = 1 << iota + continueOk + fallthroughOk + + // additional context information + finalSwitchCase +) + +func (check *Checker) simpleStmt(s ast.Stmt) { + if s != nil { + check.stmt(0, s) + } +} + +func trimTrailingEmptyStmts(list []ast.Stmt) []ast.Stmt { + for i := len(list); i > 0; i-- { + if _, ok := list[i-1].(*ast.EmptyStmt); !ok { + return list[:i] + } + } + return nil +} + +func (check *Checker) stmtList(ctxt stmtContext, list []ast.Stmt) { + ok := ctxt&fallthroughOk != 0 + inner := ctxt &^ fallthroughOk + list = trimTrailingEmptyStmts(list) // trailing empty statements are "invisible" to fallthrough analysis + for i, s := range list { + inner := inner + if ok && i+1 == len(list) { + inner |= fallthroughOk + } + check.stmt(inner, s) + } +} + +func (check *Checker) multipleDefaults(list []ast.Stmt) { + var first ast.Stmt + for _, s := range list { + var d ast.Stmt + switch c := s.(type) { + case *ast.CaseClause: + if len(c.List) == 0 { + d = s + } + case *ast.CommClause: + if c.Comm == nil { + d = s + } + default: + check.invalidAST(s, "case/communication clause expected") + } + if d != nil { + if first != nil { + check.errorf(d, _DuplicateDefault, "multiple defaults (first at %s)", check.fset.Position(first.Pos())) + } else { + first = d + } + } + } +} + +func (check *Checker) openScope(s ast.Stmt, comment string) { + scope := NewScope(check.scope, s.Pos(), s.End(), comment) + check.recordScope(s, scope) + check.scope = scope +} + +func (check *Checker) closeScope() { + check.scope = check.scope.Parent() +} + +func assignOp(op token.Token) token.Token { + // token_test.go verifies the token ordering this function relies on + if token.ADD_ASSIGN <= op && op <= token.AND_NOT_ASSIGN { + return op + (token.ADD - token.ADD_ASSIGN) + } + return token.ILLEGAL +} + +func (check *Checker) suspendedCall(keyword string, call *ast.CallExpr) { + var x operand + var msg string + var code errorCode + switch check.rawExpr(&x, call, nil) { + case conversion: + msg = "requires function call, not conversion" + code = _InvalidDefer + if keyword == "go" { + code = _InvalidGo + } + case expression: + msg = "discards result of" + code = _UnusedResults + case statement: + return + default: + unreachable() + } + check.errorf(&x, code, "%s %s %s", keyword, msg, &x) +} + +// goVal returns the Go value for val, or nil. +func goVal(val constant.Value) interface{} { + // val should exist, but be conservative and check + if val == nil { + return nil + } + // Match implementation restriction of other compilers. + // gc only checks duplicates for integer, floating-point + // and string values, so only create Go values for these + // types. + switch val.Kind() { + case constant.Int: + if x, ok := constant.Int64Val(val); ok { + return x + } + if x, ok := constant.Uint64Val(val); ok { + return x + } + case constant.Float: + if x, ok := constant.Float64Val(val); ok { + return x + } + case constant.String: + return constant.StringVal(val) + } + return nil +} + +// A valueMap maps a case value (of a basic Go type) to a list of positions +// where the same case value appeared, together with the corresponding case +// types. +// Since two case values may have the same "underlying" value but different +// types we need to also check the value's types (e.g., byte(1) vs myByte(1)) +// when the switch expression is of interface type. +type ( + valueMap map[interface{}][]valueType // underlying Go value -> valueType + valueType struct { + pos token.Pos + typ Type + } +) + +func (check *Checker) caseValues(x *operand, values []ast.Expr, seen valueMap) { +L: + for _, e := range values { + var v operand + check.expr(&v, e) + if x.mode == invalid || v.mode == invalid { + continue L + } + check.convertUntyped(&v, x.typ) + if v.mode == invalid { + continue L + } + // Order matters: By comparing v against x, error positions are at the case values. + res := v // keep original v unchanged + check.comparison(&res, x, token.EQL) + if res.mode == invalid { + continue L + } + if v.mode != constant_ { + continue L // we're done + } + // look for duplicate values + if val := goVal(v.val); val != nil { + // look for duplicate types for a given value + // (quadratic algorithm, but these lists tend to be very short) + for _, vt := range seen[val] { + if check.identical(v.typ, vt.typ) { + check.errorf(&v, _DuplicateCase, "duplicate case %s in expression switch", &v) + check.error(atPos(vt.pos), _DuplicateCase, "\tprevious case") // secondary error, \t indented + continue L + } + } + seen[val] = append(seen[val], valueType{v.Pos(), v.typ}) + } + } +} + +func (check *Checker) caseTypes(x *operand, xtyp *Interface, types []ast.Expr, seen map[Type]ast.Expr) (T Type) { +L: + for _, e := range types { + T = check.typOrNil(e) + if T == Typ[Invalid] { + continue L + } + // look for duplicate types + // (quadratic algorithm, but type switches tend to be reasonably small) + for t, other := range seen { + if T == nil && t == nil || T != nil && t != nil && check.identical(T, t) { + // talk about "case" rather than "type" because of nil case + Ts := "nil" + if T != nil { + Ts = T.String() + } + check.errorf(e, _DuplicateCase, "duplicate case %s in type switch", Ts) + check.error(other, _DuplicateCase, "\tprevious case") // secondary error, \t indented + continue L + } + } + seen[T] = e + if T != nil { + check.typeAssertion(e, x, xtyp, T) + } + } + return +} + +// stmt typechecks statement s. +func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { + // statements must end with the same top scope as they started with + if debug { + defer func(scope *Scope) { + // don't check if code is panicking + if p := recover(); p != nil { + panic(p) + } + assert(scope == check.scope) + }(check.scope) + } + + // process collected function literals before scope changes + defer check.processDelayed(len(check.delayed)) + + inner := ctxt &^ (fallthroughOk | finalSwitchCase) + switch s := s.(type) { + case *ast.BadStmt, *ast.EmptyStmt: + // ignore + + case *ast.DeclStmt: + check.declStmt(s.Decl) + + case *ast.LabeledStmt: + check.hasLabel = true + check.stmt(ctxt, s.Stmt) + + case *ast.ExprStmt: + // spec: "With the exception of specific built-in functions, + // function and method calls and receive operations can appear + // in statement context. Such statements may be parenthesized." + var x operand + kind := check.rawExpr(&x, s.X, nil) + var msg string + var code errorCode + switch x.mode { + default: + if kind == statement { + return + } + msg = "is not used" + code = _UnusedExpr + case builtin: + msg = "must be called" + code = _UncalledBuiltin + case typexpr: + msg = "is not an expression" + code = _NotAnExpr + } + check.errorf(&x, code, "%s %s", &x, msg) + + case *ast.SendStmt: + var ch, x operand + check.expr(&ch, s.Chan) + check.expr(&x, s.Value) + if ch.mode == invalid || x.mode == invalid { + return + } + + tch, ok := ch.typ.Underlying().(*Chan) + if !ok { + check.invalidOp(inNode(s, s.Arrow), _InvalidSend, "cannot send to non-chan type %s", ch.typ) + return + } + + if tch.dir == RecvOnly { + check.invalidOp(inNode(s, s.Arrow), _InvalidSend, "cannot send to receive-only type %s", tch) + return + } + + check.assignment(&x, tch.elem, "send") + + case *ast.IncDecStmt: + var op token.Token + switch s.Tok { + case token.INC: + op = token.ADD + case token.DEC: + op = token.SUB + default: + check.invalidAST(inNode(s, s.TokPos), "unknown inc/dec operation %s", s.Tok) + return + } + + var x operand + check.expr(&x, s.X) + if x.mode == invalid { + return + } + if !isNumeric(x.typ) { + check.invalidOp(s.X, _NonNumericIncDec, "%s%s (non-numeric type %s)", s.X, s.Tok, x.typ) + return + } + + Y := &ast.BasicLit{ValuePos: s.X.Pos(), Kind: token.INT, Value: "1"} // use x's position + check.binary(&x, nil, s.X, Y, op, s.TokPos) + if x.mode == invalid { + return + } + check.assignVar(s.X, &x) + + case *ast.AssignStmt: + switch s.Tok { + case token.ASSIGN, token.DEFINE: + if len(s.Lhs) == 0 { + check.invalidAST(s, "missing lhs in assignment") + return + } + if s.Tok == token.DEFINE { + check.shortVarDecl(inNode(s, s.TokPos), s.Lhs, s.Rhs) + } else { + // regular assignment + check.assignVars(s.Lhs, s.Rhs) + } + + default: + // assignment operations + if len(s.Lhs) != 1 || len(s.Rhs) != 1 { + check.errorf(inNode(s, s.TokPos), _MultiValAssignOp, "assignment operation %s requires single-valued expressions", s.Tok) + return + } + op := assignOp(s.Tok) + if op == token.ILLEGAL { + check.invalidAST(atPos(s.TokPos), "unknown assignment operation %s", s.Tok) + return + } + var x operand + check.binary(&x, nil, s.Lhs[0], s.Rhs[0], op, s.TokPos) + if x.mode == invalid { + return + } + check.assignVar(s.Lhs[0], &x) + } + + case *ast.GoStmt: + check.suspendedCall("go", s.Call) + + case *ast.DeferStmt: + check.suspendedCall("defer", s.Call) + + case *ast.ReturnStmt: + res := check.sig.results + if res.Len() > 0 { + // function returns results + // (if one, say the first, result parameter is named, all of them are named) + if len(s.Results) == 0 && res.vars[0].name != "" { + // spec: "Implementation restriction: A compiler may disallow an empty expression + // list in a "return" statement if a different entity (constant, type, or variable) + // with the same name as a result parameter is in scope at the place of the return." + for _, obj := range res.vars { + if alt := check.lookup(obj.name); alt != nil && alt != obj { + check.errorf(s, _OutOfScopeResult, "result parameter %s not in scope at return", obj.name) + check.errorf(alt, _OutOfScopeResult, "\tinner declaration of %s", obj) + // ok to continue + } + } + } else { + // return has results or result parameters are unnamed + check.initVars(res.vars, s.Results, s.Return) + } + } else if len(s.Results) > 0 { + check.error(s.Results[0], _WrongResultCount, "no result values expected") + check.use(s.Results...) + } + + case *ast.BranchStmt: + if s.Label != nil { + check.hasLabel = true + return // checked in 2nd pass (check.labels) + } + switch s.Tok { + case token.BREAK: + if ctxt&breakOk == 0 { + check.error(s, _MisplacedBreak, "break not in for, switch, or select statement") + } + case token.CONTINUE: + if ctxt&continueOk == 0 { + check.error(s, _MisplacedContinue, "continue not in for statement") + } + case token.FALLTHROUGH: + if ctxt&fallthroughOk == 0 { + msg := "fallthrough statement out of place" + code := _MisplacedFallthrough + if ctxt&finalSwitchCase != 0 { + msg = "cannot fallthrough final case in switch" + } + check.error(s, code, msg) + } + default: + check.invalidAST(s, "branch statement: %s", s.Tok) + } + + case *ast.BlockStmt: + check.openScope(s, "block") + defer check.closeScope() + + check.stmtList(inner, s.List) + + case *ast.IfStmt: + check.openScope(s, "if") + defer check.closeScope() + + check.simpleStmt(s.Init) + var x operand + check.expr(&x, s.Cond) + if x.mode != invalid && !isBoolean(x.typ) { + check.error(s.Cond, _InvalidCond, "non-boolean condition in if statement") + } + check.stmt(inner, s.Body) + // The parser produces a correct AST but if it was modified + // elsewhere the else branch may be invalid. Check again. + switch s.Else.(type) { + case nil, *ast.BadStmt: + // valid or error already reported + case *ast.IfStmt, *ast.BlockStmt: + check.stmt(inner, s.Else) + default: + check.invalidAST(s.Else, "invalid else branch in if statement") + } + + case *ast.SwitchStmt: + inner |= breakOk + check.openScope(s, "switch") + defer check.closeScope() + + check.simpleStmt(s.Init) + var x operand + if s.Tag != nil { + check.expr(&x, s.Tag) + // By checking assignment of x to an invisible temporary + // (as a compiler would), we get all the relevant checks. + check.assignment(&x, nil, "switch expression") + if x.mode != invalid && !Comparable(x.typ) && !hasNil(x.typ) { + check.errorf(&x, _InvalidExprSwitch, "cannot switch on %s (%s is not comparable)", &x, x.typ) + x.mode = invalid + } + } else { + // spec: "A missing switch expression is + // equivalent to the boolean value true." + x.mode = constant_ + x.typ = Typ[Bool] + x.val = constant.MakeBool(true) + x.expr = &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} + } + + check.multipleDefaults(s.Body.List) + + seen := make(valueMap) // map of seen case values to positions and types + for i, c := range s.Body.List { + clause, _ := c.(*ast.CaseClause) + if clause == nil { + check.invalidAST(c, "incorrect expression switch case") + continue + } + check.caseValues(&x, clause.List, seen) + check.openScope(clause, "case") + inner := inner + if i+1 < len(s.Body.List) { + inner |= fallthroughOk + } else { + inner |= finalSwitchCase + } + check.stmtList(inner, clause.Body) + check.closeScope() + } + + case *ast.TypeSwitchStmt: + inner |= breakOk + check.openScope(s, "type switch") + defer check.closeScope() + + check.simpleStmt(s.Init) + + // A type switch guard must be of the form: + // + // TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . + // + // The parser is checking syntactic correctness; + // remaining syntactic errors are considered AST errors here. + // TODO(gri) better factoring of error handling (invalid ASTs) + // + var lhs *ast.Ident // lhs identifier or nil + var rhs ast.Expr + switch guard := s.Assign.(type) { + case *ast.ExprStmt: + rhs = guard.X + case *ast.AssignStmt: + if len(guard.Lhs) != 1 || guard.Tok != token.DEFINE || len(guard.Rhs) != 1 { + check.invalidAST(s, "incorrect form of type switch guard") + return + } + + lhs, _ = guard.Lhs[0].(*ast.Ident) + if lhs == nil { + check.invalidAST(s, "incorrect form of type switch guard") + return + } + + if lhs.Name == "_" { + // _ := x.(type) is an invalid short variable declaration + check.softErrorf(lhs, _NoNewVar, "no new variable on left side of :=") + lhs = nil // avoid declared but not used error below + } else { + check.recordDef(lhs, nil) // lhs variable is implicitly declared in each cause clause + } + + rhs = guard.Rhs[0] + + default: + check.invalidAST(s, "incorrect form of type switch guard") + return + } + + // rhs must be of the form: expr.(type) and expr must be an interface + expr, _ := rhs.(*ast.TypeAssertExpr) + if expr == nil || expr.Type != nil { + check.invalidAST(s, "incorrect form of type switch guard") + return + } + var x operand + check.expr(&x, expr.X) + if x.mode == invalid { + return + } + xtyp, _ := x.typ.Underlying().(*Interface) + if xtyp == nil { + check.errorf(&x, _InvalidTypeSwitch, "%s is not an interface", &x) + return + } + + check.multipleDefaults(s.Body.List) + + var lhsVars []*Var // list of implicitly declared lhs variables + seen := make(map[Type]ast.Expr) // map of seen types to positions + for _, s := range s.Body.List { + clause, _ := s.(*ast.CaseClause) + if clause == nil { + check.invalidAST(s, "incorrect type switch case") + continue + } + // Check each type in this type switch case. + T := check.caseTypes(&x, xtyp, clause.List, seen) + check.openScope(clause, "case") + // If lhs exists, declare a corresponding variable in the case-local scope. + if lhs != nil { + // spec: "The TypeSwitchGuard may include a short variable declaration. + // When that form is used, the variable is declared at the beginning of + // the implicit block in each clause. In clauses with a case listing + // exactly one type, the variable has that type; otherwise, the variable + // has the type of the expression in the TypeSwitchGuard." + if len(clause.List) != 1 || T == nil { + T = x.typ + } + obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, T) + scopePos := clause.Pos() + token.Pos(len("default")) // for default clause (len(List) == 0) + if n := len(clause.List); n > 0 { + scopePos = clause.List[n-1].End() + } + check.declare(check.scope, nil, obj, scopePos) + check.recordImplicit(clause, obj) + // For the "declared but not used" error, all lhs variables act as + // one; i.e., if any one of them is 'used', all of them are 'used'. + // Collect them for later analysis. + lhsVars = append(lhsVars, obj) + } + check.stmtList(inner, clause.Body) + check.closeScope() + } + + // If lhs exists, we must have at least one lhs variable that was used. + if lhs != nil { + var used bool + for _, v := range lhsVars { + if v.used { + used = true + } + v.used = true // avoid usage error when checking entire function + } + if !used { + check.softErrorf(lhs, _UnusedVar, "%s declared but not used", lhs.Name) + } + } + + case *ast.SelectStmt: + inner |= breakOk + + check.multipleDefaults(s.Body.List) + + for _, s := range s.Body.List { + clause, _ := s.(*ast.CommClause) + if clause == nil { + continue // error reported before + } + + // clause.Comm must be a SendStmt, RecvStmt, or default case + valid := false + var rhs ast.Expr // rhs of RecvStmt, or nil + switch s := clause.Comm.(type) { + case nil, *ast.SendStmt: + valid = true + case *ast.AssignStmt: + if len(s.Rhs) == 1 { + rhs = s.Rhs[0] + } + case *ast.ExprStmt: + rhs = s.X + } + + // if present, rhs must be a receive operation + if rhs != nil { + if x, _ := unparen(rhs).(*ast.UnaryExpr); x != nil && x.Op == token.ARROW { + valid = true + } + } + + if !valid { + check.error(clause.Comm, _InvalidSelectCase, "select case must be send or receive (possibly with assignment)") + continue + } + + check.openScope(s, "case") + if clause.Comm != nil { + check.stmt(inner, clause.Comm) + } + check.stmtList(inner, clause.Body) + check.closeScope() + } + + case *ast.ForStmt: + inner |= breakOk | continueOk + check.openScope(s, "for") + defer check.closeScope() + + check.simpleStmt(s.Init) + if s.Cond != nil { + var x operand + check.expr(&x, s.Cond) + if x.mode != invalid && !isBoolean(x.typ) { + check.error(s.Cond, _InvalidCond, "non-boolean condition in for statement") + } + } + check.simpleStmt(s.Post) + // spec: "The init statement may be a short variable + // declaration, but the post statement must not." + if s, _ := s.Post.(*ast.AssignStmt); s != nil && s.Tok == token.DEFINE { + check.softErrorf(s, _InvalidPostDecl, "cannot declare in post statement") + // Don't call useLHS here because we want to use the lhs in + // this erroneous statement so that we don't get errors about + // these lhs variables being declared but not used. + check.use(s.Lhs...) // avoid follow-up errors + } + check.stmt(inner, s.Body) + + case *ast.RangeStmt: + inner |= breakOk | continueOk + check.openScope(s, "for") + defer check.closeScope() + + // check expression to iterate over + var x operand + check.expr(&x, s.X) + + // determine key/value types + var key, val Type + if x.mode != invalid { + switch typ := x.typ.Underlying().(type) { + case *Basic: + if isString(typ) { + key = Typ[Int] + val = universeRune // use 'rune' name + } + case *Array: + key = Typ[Int] + val = typ.elem + case *Slice: + key = Typ[Int] + val = typ.elem + case *Pointer: + if typ, _ := typ.base.Underlying().(*Array); typ != nil { + key = Typ[Int] + val = typ.elem + } + case *Map: + key = typ.key + val = typ.elem + case *Chan: + key = typ.elem + val = Typ[Invalid] + if typ.dir == SendOnly { + check.errorf(&x, _InvalidChanRange, "cannot range over send-only channel %s", &x) + // ok to continue + } + if s.Value != nil { + check.errorf(atPos(s.Value.Pos()), _InvalidIterVar, "iteration over %s permits only one iteration variable", &x) + // ok to continue + } + } + } + + if key == nil { + check.errorf(&x, _InvalidRangeExpr, "cannot range over %s", &x) + // ok to continue + } + + // check assignment to/declaration of iteration variables + // (irregular assignment, cannot easily map to existing assignment checks) + + // lhs expressions and initialization value (rhs) types + lhs := [2]ast.Expr{s.Key, s.Value} + rhs := [2]Type{key, val} // key, val may be nil + + if s.Tok == token.DEFINE { + // short variable declaration; variable scope starts after the range clause + // (the for loop opens a new scope, so variables on the lhs never redeclare + // previously declared variables) + var vars []*Var + for i, lhs := range lhs { + if lhs == nil { + continue + } + + // determine lhs variable + var obj *Var + if ident, _ := lhs.(*ast.Ident); ident != nil { + // declare new variable + name := ident.Name + obj = NewVar(ident.Pos(), check.pkg, name, nil) + check.recordDef(ident, obj) + // _ variables don't count as new variables + if name != "_" { + vars = append(vars, obj) + } + } else { + check.invalidAST(lhs, "cannot declare %s", lhs) + obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable + } + + // initialize lhs variable + if typ := rhs[i]; typ != nil { + x.mode = value + x.expr = lhs // we don't have a better rhs expression to use here + x.typ = typ + check.initVar(obj, &x, "range clause") + } else { + obj.typ = Typ[Invalid] + obj.used = true // don't complain about unused variable + } + } + + // declare variables + if len(vars) > 0 { + scopePos := s.X.End() + for _, obj := range vars { + // spec: "The scope of a constant or variable identifier declared inside + // a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl + // for short variable declarations) and ends at the end of the innermost + // containing block." + check.declare(check.scope, nil /* recordDef already called */, obj, scopePos) + } + } else { + check.error(inNode(s, s.TokPos), _NoNewVar, "no new variables on left side of :=") + } + } else { + // ordinary assignment + for i, lhs := range lhs { + if lhs == nil { + continue + } + if typ := rhs[i]; typ != nil { + x.mode = value + x.expr = lhs // we don't have a better rhs expression to use here + x.typ = typ + check.assignVar(lhs, &x) + } + } + } + + check.stmt(inner, s.Body) + + default: + check.invalidAST(s, "invalid statement") + } +} diff --git a/src/go/types/testdata/blank.src b/src/go/types/testdata/blank.src new file mode 100644 index 0000000..6a2507f --- /dev/null +++ b/src/go/types/testdata/blank.src @@ -0,0 +1,5 @@ +// Copyright 2014 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 _ /* ERROR invalid package name */ diff --git a/src/go/types/testdata/builtins.src b/src/go/types/testdata/builtins.src new file mode 100644 index 0000000..98830eb --- /dev/null +++ b/src/go/types/testdata/builtins.src @@ -0,0 +1,902 @@ +// Copyright 2012 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. + +// builtin calls + +package builtins + +import "unsafe" + +func f0() {} + +func append1() { + var b byte + var x int + var s []byte + _ = append() // ERROR not enough arguments + _ = append("foo" /* ERROR not a slice */ ) + _ = append(nil /* ERROR not a slice */ , s) + _ = append(x /* ERROR not a slice */ , s) + _ = append(s) + _ = append(s, nil...) + append /* ERROR not used */ (s) + + _ = append(s, b) + _ = append(s, x /* ERROR cannot use x */ ) + _ = append(s, s /* ERROR cannot use s */ ) + _ = append(s... /* ERROR can only use ... with matching parameter */ ) + _ = append(s, b, s... /* ERROR can only use ... with matching parameter */ ) + _ = append(s, 1, 2, 3) + _ = append(s, 1, 2, 3, x /* ERROR cannot use x */ , 5, 6, 6) + _ = append(s, 1, 2, s... /* ERROR can only use ... with matching parameter */ ) + _ = append([]interface{}(nil), 1, 2, "foo", x, 3.1425, false) + + type S []byte + type T string + var t T + _ = append(s, "foo" /* ERROR cannot use .* in argument to append */ ) + _ = append(s, "foo"...) + _ = append(S(s), "foo" /* ERROR cannot use .* in argument to append */ ) + _ = append(S(s), "foo"...) + _ = append(s, t /* ERROR cannot use t */ ) + _ = append(s, t...) + _ = append(s, T("foo")...) + _ = append(S(s), t /* ERROR cannot use t */ ) + _ = append(S(s), t...) + _ = append(S(s), T("foo")...) + _ = append([]string{}, t /* ERROR cannot use t */ , "foo") + _ = append([]T{}, t, "foo") +} + +// from the spec +func append2() { + s0 := []int{0, 0} + s1 := append(s0, 2) // append a single element s1 == []int{0, 0, 2} + s2 := append(s1, 3, 5, 7) // append multiple elements s2 == []int{0, 0, 2, 3, 5, 7} + s3 := append(s2, s0...) // append a slice s3 == []int{0, 0, 2, 3, 5, 7, 0, 0} + s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0} + + var t []interface{} + t = append(t, 42, 3.1415, "foo") // t == []interface{}{42, 3.1415, "foo"} + + var b []byte + b = append(b, "bar"...) // append string contents b == []byte{'b', 'a', 'r' } + + _ = s4 +} + +func append3() { + f1 := func() (s []int) { return } + f2 := func() (s []int, x int) { return } + f3 := func() (s []int, x, y int) { return } + f5 := func() (s []interface{}, x int, y float32, z string, b bool) { return } + ff := func() (int, float32) { return 0, 0 } + _ = append(f0 /* ERROR used as value */ ()) + _ = append(f1()) + _ = append(f2()) + _ = append(f3()) + _ = append(f5()) + _ = append(ff /* ERROR not a slice */ ()) // TODO(gri) better error message +} + +func cap1() { + var a [10]bool + var p *[20]int + var c chan string + _ = cap() // ERROR not enough arguments + _ = cap(1, 2) // ERROR too many arguments + _ = cap(42 /* ERROR invalid */) + const _3 = cap(a) + assert(_3 == 10) + const _4 = cap(p) + assert(_4 == 20) + _ = cap(c) + cap /* ERROR not used */ (c) + + // issue 4744 + type T struct{ a [10]int } + const _ = cap(((*T)(nil)).a) + + var s [][]byte + _ = cap(s) + _ = cap(s... /* ERROR invalid use of \.\.\. */ ) +} + +func cap2() { + f1a := func() (a [10]int) { return } + f1s := func() (s []int) { return } + f2 := func() (s []int, x int) { return } + _ = cap(f0 /* ERROR used as value */ ()) + _ = cap(f1a()) + _ = cap(f1s()) + _ = cap(f2()) // ERROR too many arguments +} + +// test cases for issue 7387 +func cap3() { + var f = func() int { return 0 } + var x = f() + const ( + _ = cap([4]int{}) + _ = cap([4]int{x}) + _ = cap /* ERROR not constant */ ([4]int{f()}) + _ = cap /* ERROR not constant */ ([4]int{cap([]int{})}) + _ = cap([4]int{cap([4]int{})}) + ) + var y float64 + var z complex128 + const ( + _ = cap([4]float64{}) + _ = cap([4]float64{y}) + _ = cap([4]float64{real(2i)}) + _ = cap /* ERROR not constant */ ([4]float64{real(z)}) + ) + var ch chan [10]int + const ( + _ = cap /* ERROR not constant */ (<-ch) + _ = cap /* ERROR not constant */ ([4]int{(<-ch)[0]}) + ) +} + +func close1() { + var c chan int + var r <-chan int + close() // ERROR not enough arguments + close(1, 2) // ERROR too many arguments + close(42 /* ERROR not a channel */) + close(r /* ERROR receive-only channel */) + close(c) + _ = close /* ERROR used as value */ (c) + + var s []chan int + close(s... /* ERROR invalid use of \.\.\. */ ) +} + +func close2() { + f1 := func() (ch chan int) { return } + f2 := func() (ch chan int, x int) { return } + close(f0 /* ERROR used as value */ ()) + close(f1()) + close(f2()) // ERROR too many arguments +} + +func complex1() { + var i32 int32 + var f32 float32 + var f64 float64 + var c64 complex64 + var c128 complex128 + _ = complex() // ERROR not enough arguments + _ = complex(1) // ERROR not enough arguments + _ = complex(true /* ERROR mismatched types */ , 0) + _ = complex(i32 /* ERROR expected floating-point */ , 0) + _ = complex("foo" /* ERROR mismatched types */ , 0) + _ = complex(c64 /* ERROR expected floating-point */ , 0) + _ = complex(0 /* ERROR mismatched types */ , true) + _ = complex(0 /* ERROR expected floating-point */ , i32) + _ = complex(0 /* ERROR mismatched types */ , "foo") + _ = complex(0 /* ERROR expected floating-point */ , c64) + _ = complex(f32, f32) + _ = complex(f32, 1) + _ = complex(f32, 1.0) + _ = complex(f32, 'a') + _ = complex(f64, f64) + _ = complex(f64, 1) + _ = complex(f64, 1.0) + _ = complex(f64, 'a') + _ = complex(f32 /* ERROR mismatched types */ , f64) + _ = complex(f64 /* ERROR mismatched types */ , f32) + _ = complex(1, 1) + _ = complex(1, 1.1) + _ = complex(1, 'a') + complex /* ERROR not used */ (1, 2) + + var _ complex64 = complex(f32, f32) + var _ complex64 = complex /* ERROR cannot use .* in variable declaration */ (f64, f64) + + var _ complex128 = complex /* ERROR cannot use .* in variable declaration */ (f32, f32) + var _ complex128 = complex(f64, f64) + + // untyped constants + const _ int = complex(1, 0) + const _ float32 = complex(1, 0) + const _ complex64 = complex(1, 0) + const _ complex128 = complex(1, 0) + const _ = complex(0i, 0i) + const _ = complex(0i, 0) + const _ int = 1.0 + complex(1, 0i) + + const _ int = complex /* ERROR int */ (1.1, 0) + const _ float32 = complex /* ERROR float32 */ (1, 2) + + // untyped values + var s uint + _ = complex(1 /* ERROR integer */ <>8&1 + mi>>16&1 + mi>>32&1) + logSizeofUint = uint(mu>>8&1 + mu>>16&1 + mu>>32&1) + logSizeofUintptr = uint(mp>>8&1 + mp>>16&1 + mp>>32&1) +) + +const ( + minInt8 = -1<<(8< 0) + _ = assert(smallestFloat64 > 0) +) + +const ( + maxFloat32 = 1<<127 * (1<<24 - 1) / (1.0<<23) + maxFloat64 = 1<<1023 * (1<<53 - 1) / (1.0<<52) +) + +const ( + _ int8 = minInt8 /* ERROR "overflows" */ - 1 + _ int8 = minInt8 + _ int8 = maxInt8 + _ int8 = maxInt8 /* ERROR "overflows" */ + 1 + _ int8 = smallestFloat64 /* ERROR "truncated" */ + + _ = int8(minInt8 /* ERROR "cannot convert" */ - 1) + _ = int8(minInt8) + _ = int8(maxInt8) + _ = int8(maxInt8 /* ERROR "cannot convert" */ + 1) + _ = int8(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ int16 = minInt16 /* ERROR "overflows" */ - 1 + _ int16 = minInt16 + _ int16 = maxInt16 + _ int16 = maxInt16 /* ERROR "overflows" */ + 1 + _ int16 = smallestFloat64 /* ERROR "truncated" */ + + _ = int16(minInt16 /* ERROR "cannot convert" */ - 1) + _ = int16(minInt16) + _ = int16(maxInt16) + _ = int16(maxInt16 /* ERROR "cannot convert" */ + 1) + _ = int16(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ int32 = minInt32 /* ERROR "overflows" */ - 1 + _ int32 = minInt32 + _ int32 = maxInt32 + _ int32 = maxInt32 /* ERROR "overflows" */ + 1 + _ int32 = smallestFloat64 /* ERROR "truncated" */ + + _ = int32(minInt32 /* ERROR "cannot convert" */ - 1) + _ = int32(minInt32) + _ = int32(maxInt32) + _ = int32(maxInt32 /* ERROR "cannot convert" */ + 1) + _ = int32(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ int64 = minInt64 /* ERROR "overflows" */ - 1 + _ int64 = minInt64 + _ int64 = maxInt64 + _ int64 = maxInt64 /* ERROR "overflows" */ + 1 + _ int64 = smallestFloat64 /* ERROR "truncated" */ + + _ = int64(minInt64 /* ERROR "cannot convert" */ - 1) + _ = int64(minInt64) + _ = int64(maxInt64) + _ = int64(maxInt64 /* ERROR "cannot convert" */ + 1) + _ = int64(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ int = minInt /* ERROR "overflows" */ - 1 + _ int = minInt + _ int = maxInt + _ int = maxInt /* ERROR "overflows" */ + 1 + _ int = smallestFloat64 /* ERROR "truncated" */ + + _ = int(minInt /* ERROR "cannot convert" */ - 1) + _ = int(minInt) + _ = int(maxInt) + _ = int(maxInt /* ERROR "cannot convert" */ + 1) + _ = int(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ uint8 = 0 /* ERROR "overflows" */ - 1 + _ uint8 = 0 + _ uint8 = maxUint8 + _ uint8 = maxUint8 /* ERROR "overflows" */ + 1 + _ uint8 = smallestFloat64 /* ERROR "truncated" */ + + _ = uint8(0 /* ERROR "cannot convert" */ - 1) + _ = uint8(0) + _ = uint8(maxUint8) + _ = uint8(maxUint8 /* ERROR "cannot convert" */ + 1) + _ = uint8(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ uint16 = 0 /* ERROR "overflows" */ - 1 + _ uint16 = 0 + _ uint16 = maxUint16 + _ uint16 = maxUint16 /* ERROR "overflows" */ + 1 + _ uint16 = smallestFloat64 /* ERROR "truncated" */ + + _ = uint16(0 /* ERROR "cannot convert" */ - 1) + _ = uint16(0) + _ = uint16(maxUint16) + _ = uint16(maxUint16 /* ERROR "cannot convert" */ + 1) + _ = uint16(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ uint32 = 0 /* ERROR "overflows" */ - 1 + _ uint32 = 0 + _ uint32 = maxUint32 + _ uint32 = maxUint32 /* ERROR "overflows" */ + 1 + _ uint32 = smallestFloat64 /* ERROR "truncated" */ + + _ = uint32(0 /* ERROR "cannot convert" */ - 1) + _ = uint32(0) + _ = uint32(maxUint32) + _ = uint32(maxUint32 /* ERROR "cannot convert" */ + 1) + _ = uint32(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ uint64 = 0 /* ERROR "overflows" */ - 1 + _ uint64 = 0 + _ uint64 = maxUint64 + _ uint64 = maxUint64 /* ERROR "overflows" */ + 1 + _ uint64 = smallestFloat64 /* ERROR "truncated" */ + + _ = uint64(0 /* ERROR "cannot convert" */ - 1) + _ = uint64(0) + _ = uint64(maxUint64) + _ = uint64(maxUint64 /* ERROR "cannot convert" */ + 1) + _ = uint64(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ uint = 0 /* ERROR "overflows" */ - 1 + _ uint = 0 + _ uint = maxUint + _ uint = maxUint /* ERROR "overflows" */ + 1 + _ uint = smallestFloat64 /* ERROR "truncated" */ + + _ = uint(0 /* ERROR "cannot convert" */ - 1) + _ = uint(0) + _ = uint(maxUint) + _ = uint(maxUint /* ERROR "cannot convert" */ + 1) + _ = uint(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ uintptr = 0 /* ERROR "overflows" */ - 1 + _ uintptr = 0 + _ uintptr = maxUintptr + _ uintptr = maxUintptr /* ERROR "overflows" */ + 1 + _ uintptr = smallestFloat64 /* ERROR "truncated" */ + + _ = uintptr(0 /* ERROR "cannot convert" */ - 1) + _ = uintptr(0) + _ = uintptr(maxUintptr) + _ = uintptr(maxUintptr /* ERROR "cannot convert" */ + 1) + _ = uintptr(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ float32 = minInt64 + _ float64 = minInt64 + _ complex64 = minInt64 + _ complex128 = minInt64 + + _ = float32(minInt64) + _ = float64(minInt64) + _ = complex64(minInt64) + _ = complex128(minInt64) +) + +const ( + _ float32 = maxUint64 + _ float64 = maxUint64 + _ complex64 = maxUint64 + _ complex128 = maxUint64 + + _ = float32(maxUint64) + _ = float64(maxUint64) + _ = complex64(maxUint64) + _ = complex128(maxUint64) +) + +// TODO(gri) find smaller deltas below + +const delta32 = maxFloat32/(1 << 23) + +const ( + _ float32 = - /* ERROR "overflow" */ (maxFloat32 + delta32) + _ float32 = -maxFloat32 + _ float32 = maxFloat32 + _ float32 = maxFloat32 /* ERROR "overflow" */ + delta32 + + _ = float32(- /* ERROR "cannot convert" */ (maxFloat32 + delta32)) + _ = float32(-maxFloat32) + _ = float32(maxFloat32) + _ = float32(maxFloat32 /* ERROR "cannot convert" */ + delta32) + + _ = assert(float32(smallestFloat32) == smallestFloat32) + _ = assert(float32(smallestFloat32/2) == 0) + _ = assert(float32(smallestFloat64) == 0) + _ = assert(float32(smallestFloat64/2) == 0) +) + +const delta64 = maxFloat64/(1 << 52) + +const ( + _ float64 = - /* ERROR "overflow" */ (maxFloat64 + delta64) + _ float64 = -maxFloat64 + _ float64 = maxFloat64 + _ float64 = maxFloat64 /* ERROR "overflow" */ + delta64 + + _ = float64(- /* ERROR "cannot convert" */ (maxFloat64 + delta64)) + _ = float64(-maxFloat64) + _ = float64(maxFloat64) + _ = float64(maxFloat64 /* ERROR "cannot convert" */ + delta64) + + _ = assert(float64(smallestFloat32) == smallestFloat32) + _ = assert(float64(smallestFloat32/2) == smallestFloat32/2) + _ = assert(float64(smallestFloat64) == smallestFloat64) + _ = assert(float64(smallestFloat64/2) == 0) +) + +const ( + _ complex64 = - /* ERROR "overflow" */ (maxFloat32 + delta32) + _ complex64 = -maxFloat32 + _ complex64 = maxFloat32 + _ complex64 = maxFloat32 /* ERROR "overflow" */ + delta32 + + _ = complex64(- /* ERROR "cannot convert" */ (maxFloat32 + delta32)) + _ = complex64(-maxFloat32) + _ = complex64(maxFloat32) + _ = complex64(maxFloat32 /* ERROR "cannot convert" */ + delta32) +) + +const ( + _ complex128 = - /* ERROR "overflow" */ (maxFloat64 + delta64) + _ complex128 = -maxFloat64 + _ complex128 = maxFloat64 + _ complex128 = maxFloat64 /* ERROR "overflow" */ + delta64 + + _ = complex128(- /* ERROR "cannot convert" */ (maxFloat64 + delta64)) + _ = complex128(-maxFloat64) + _ = complex128(maxFloat64) + _ = complex128(maxFloat64 /* ERROR "cannot convert" */ + delta64) +) + +// Initialization of typed constant and conversion are the same: +const ( + f32 = 1 + smallestFloat32 + x32 float32 = f32 + y32 = float32(f32) + _ = assert(x32 - y32 == 0) +) + +const ( + f64 = 1 + smallestFloat64 + x64 float64 = f64 + y64 = float64(f64) + _ = assert(x64 - y64 == 0) +) + +const ( + _ = int8(-1) << 7 + _ = int8 /* ERROR "overflows" */ (-1) << 8 + + _ = uint32(1) << 31 + _ = uint32 /* ERROR "overflows" */ (1) << 32 +) diff --git a/src/go/types/testdata/constdecl.src b/src/go/types/testdata/constdecl.src new file mode 100644 index 0000000..680c85a --- /dev/null +++ b/src/go/types/testdata/constdecl.src @@ -0,0 +1,141 @@ +// Copyright 2013 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 constdecl + +import "math" +import "unsafe" + +var v int + +// Const decls must be initialized by constants. +const _ = v /* ERROR "not constant" */ +const _ = math /* ERROR "not constant" */ .Sin(0) +const _ = int /* ERROR "not an expression" */ + +func _() { + const _ = v /* ERROR "not constant" */ + const _ = math /* ERROR "not constant" */ .Sin(0) + const _ = int /* ERROR "not an expression" */ +} + +// Identifier and expression arity must match. +// The first error message is produced by the parser. +// In a real-world scenario, the type-checker would not be run +// in this case and the 2nd error message would not appear. +const _ /* ERROR "missing constant value" */ /* ERROR "missing init expr for _" */ +const _ = 1, 2 /* ERROR "extra init expr 2" */ + +const _ /* ERROR "missing constant value" */ /* ERROR "missing init expr for _" */ int +const _ int = 1, 2 /* ERROR "extra init expr 2" */ + +const ( + _ /* ERROR "missing constant value" */ /* ERROR "missing init expr for _" */ + _ = 1, 2 /* ERROR "extra init expr 2" */ + + _ /* ERROR "missing constant value" */ /* ERROR "missing init expr for _" */ int + _ int = 1, 2 /* ERROR "extra init expr 2" */ +) + +const ( + _ = 1 + _ + _, _ /* ERROR "missing init expr for _" */ + _ +) + +const ( + _, _ = 1, 2 + _, _ + _ /* ERROR "extra init expr at" */ + _, _ + _, _, _ /* ERROR "missing init expr for _" */ + _, _ +) + +func _() { + const _ /* ERROR "missing constant value" */ /* ERROR "missing init expr for _" */ + const _ = 1, 2 /* ERROR "extra init expr 2" */ + + const _ /* ERROR "missing constant value" */ /* ERROR "missing init expr for _" */ int + const _ int = 1, 2 /* ERROR "extra init expr 2" */ + + const ( + _ /* ERROR "missing constant value" */ /* ERROR "missing init expr for _" */ + _ = 1, 2 /* ERROR "extra init expr 2" */ + + _ /* ERROR "missing constant value" */ /* ERROR "missing init expr for _" */ int + _ int = 1, 2 /* ERROR "extra init expr 2" */ + ) + + const ( + _ = 1 + _ + _, _ /* ERROR "missing init expr for _" */ + _ + ) + + const ( + _, _ = 1, 2 + _, _ + _ /* ERROR "extra init expr at" */ + _, _ + _, _, _ /* ERROR "missing init expr for _" */ + _, _ + ) +} + +// Test case for constant with invalid initialization. +// Caused panic because the constant value was not set up (gri - 7/8/2014). +func _() { + const ( + x string = missing /* ERROR "undeclared name" */ + y = x + "" + ) +} + +// Test case for constants depending on function literals (see also #22992). +const A /* ERROR initialization cycle */ = unsafe.Sizeof(func() { _ = A }) + +func _() { + // The function literal below must not see a. + const a = unsafe.Sizeof(func() { _ = a /* ERROR "undeclared name" */ }) + const b = unsafe.Sizeof(func() { _ = a }) + + // The function literal below must not see x, y, or z. + const x, y, z = 0, 1, unsafe.Sizeof(func() { _ = x /* ERROR "undeclared name" */ + y /* ERROR "undeclared name" */ + z /* ERROR "undeclared name" */ }) +} + +// Test cases for errors in inherited constant initialization expressions. +// Errors related to inherited initialization expressions must appear at +// the constant identifier being declared, not at the original expression +// (issues #42991, #42992). +const ( + _ byte = 255 + iota + /* some gap */ + _ // ERROR overflows + /* some gap */ + /* some gap */ _ /* ERROR overflows */; _ /* ERROR overflows */ + /* some gap */ + _ = 255 + iota + _ = byte /* ERROR overflows */ (255) + iota + _ /* ERROR overflows */ +) + +// Test cases from issue. +const ( + ok = byte(iota + 253) + bad + barn + bard // ERROR cannot convert +) + +const ( + c = len([1 - iota]int{}) + d + e // ERROR invalid array length + f // ERROR invalid array length +) + +// TODO(gri) move extra tests from testdata/const0.src into here diff --git a/src/go/types/testdata/conversions.src b/src/go/types/testdata/conversions.src new file mode 100644 index 0000000..e1336c0 --- /dev/null +++ b/src/go/types/testdata/conversions.src @@ -0,0 +1,93 @@ +// Copyright 2012 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. + +// conversions + +package conversions + +import "unsafe" + +// argument count +var ( + _ = int() /* ERROR "missing argument" */ + _ = int(1, 2 /* ERROR "too many arguments" */ ) +) + +// numeric constant conversions are in const1.src. + +func string_conversions() { + const A = string(65) + assert(A == "A") + const E = string(-1) + assert(E == "\uFFFD") + assert(E == string(1234567890)) + + type myint int + assert(A == string(myint(65))) + + type mystring string + const _ mystring = mystring("foo") + + const _ = string(true /* ERROR "cannot convert" */ ) + const _ = string(1.2 /* ERROR "cannot convert" */ ) + const _ = string(nil /* ERROR "cannot convert" */ ) + + // issues 11357, 11353: argument must be of integer type + _ = string(0.0 /* ERROR "cannot convert" */ ) + _ = string(0i /* ERROR "cannot convert" */ ) + _ = string(1 /* ERROR "cannot convert" */ + 2i) +} + +func interface_conversions() { + type E interface{} + + type I1 interface{ + m1() + } + + type I2 interface{ + m1() + m2(x int) + } + + type I3 interface{ + m1() + m2() int + } + + var e E + var i1 I1 + var i2 I2 + var i3 I3 + + _ = E(0) + _ = E(nil) + _ = E(e) + _ = E(i1) + _ = E(i2) + + _ = I1(0 /* ERROR "cannot convert" */ ) + _ = I1(nil) + _ = I1(i1) + _ = I1(e /* ERROR "cannot convert" */ ) + _ = I1(i2) + + _ = I2(nil) + _ = I2(i1 /* ERROR "cannot convert" */ ) + _ = I2(i2) + _ = I2(i3 /* ERROR "cannot convert" */ ) + + _ = I3(nil) + _ = I3(i1 /* ERROR "cannot convert" */ ) + _ = I3(i2 /* ERROR "cannot convert" */ ) + _ = I3(i3) + + // TODO(gri) add more tests, improve error message +} + +func issue6326() { + type T unsafe.Pointer + var x T + _ = uintptr(x) // see issue 6326 +} diff --git a/src/go/types/testdata/conversions2.src b/src/go/types/testdata/conversions2.src new file mode 100644 index 0000000..93a5f18 --- /dev/null +++ b/src/go/types/testdata/conversions2.src @@ -0,0 +1,313 @@ +// Copyright 2016 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. + +// Test various valid and invalid struct assignments and conversions. +// Does not compile. + +package conversions2 + +type I interface { + m() +} + +// conversions between structs + +func _() { + type S struct{} + type T struct{} + var s S + var t T + var u struct{} + s = s + s = t // ERROR "cannot use .* in assignment" + s = u + s = S(s) + s = S(t) + s = S(u) + t = u + t = T(u) +} + +func _() { + type S struct{ x int } + type T struct { + x int "foo" + } + var s S + var t T + var u struct { + x int "bar" + } + s = s + s = t // ERROR "cannot use .* in assignment" + s = u // ERROR "cannot use .* in assignment" + s = S(s) + s = S(t) + s = S(u) + t = u // ERROR "cannot use .* in assignment" + t = T(u) +} + +func _() { + type E struct{ x int } + type S struct{ x E } + type T struct { + x E "foo" + } + var s S + var t T + var u struct { + x E "bar" + } + s = s + s = t // ERROR "cannot use .* in assignment" + s = u // ERROR "cannot use .* in assignment" + s = S(s) + s = S(t) + s = S(u) + t = u // ERROR "cannot use .* in assignment" + t = T(u) +} + +func _() { + type S struct { + x struct { + x int "foo" + } + } + type T struct { + x struct { + x int "bar" + } "foo" + } + var s S + var t T + var u struct { + x struct { + x int "bar" + } "bar" + } + s = s + s = t // ERROR "cannot use .* in assignment" + s = u // ERROR "cannot use .* in assignment" + s = S(s) + s = S(t) + s = S(u) + t = u // ERROR "cannot use .* in assignment" + t = T(u) +} + +func _() { + type E1 struct { + x int "foo" + } + type E2 struct { + x int "bar" + } + type S struct{ x E1 } + type T struct { + x E2 "foo" + } + var s S + var t T + var u struct { + x E2 "bar" + } + s = s + s = t // ERROR "cannot use .* in assignment" + s = u // ERROR "cannot use .* in assignment" + s = S(s) + s = S(t /* ERROR "cannot convert" */ ) + s = S(u /* ERROR "cannot convert" */ ) + t = u // ERROR "cannot use .* in assignment" + t = T(u) +} + +func _() { + type E struct{ x int } + type S struct { + f func(struct { + x int "foo" + }) + } + type T struct { + f func(struct { + x int "bar" + }) + } + var s S + var t T + var u struct{ f func(E) } + s = s + s = t // ERROR "cannot use .* in assignment" + s = u // ERROR "cannot use .* in assignment" + s = S(s) + s = S(t) + s = S(u /* ERROR "cannot convert" */ ) + t = u // ERROR "cannot use .* in assignment" + t = T(u /* ERROR "cannot convert" */ ) +} + +// conversions between pointers to structs + +func _() { + type S struct{} + type T struct{} + var s *S + var t *T + var u *struct{} + s = s + s = t // ERROR "cannot use .* in assignment" + s = u // ERROR "cannot use .* in assignment" + s = (*S)(s) + s = (*S)(t) + s = (*S)(u) + t = u // ERROR "cannot use .* in assignment" + t = (*T)(u) +} + +func _() { + type S struct{ x int } + type T struct { + x int "foo" + } + var s *S + var t *T + var u *struct { + x int "bar" + } + s = s + s = t // ERROR "cannot use .* in assignment" + s = u // ERROR "cannot use .* in assignment" + s = (*S)(s) + s = (*S)(t) + s = (*S)(u) + t = u // ERROR "cannot use .* in assignment" + t = (*T)(u) +} + +func _() { + type E struct{ x int } + type S struct{ x E } + type T struct { + x E "foo" + } + var s *S + var t *T + var u *struct { + x E "bar" + } + s = s + s = t // ERROR "cannot use .* in assignment" + s = u // ERROR "cannot use .* in assignment" + s = (*S)(s) + s = (*S)(t) + s = (*S)(u) + t = u // ERROR "cannot use .* in assignment" + t = (*T)(u) +} + +func _() { + type S struct { + x struct { + x int "foo" + } + } + type T struct { + x struct { + x int "bar" + } "foo" + } + var s *S + var t *T + var u *struct { + x struct { + x int "bar" + } "bar" + } + s = s + s = t // ERROR "cannot use .* in assignment" + s = u // ERROR "cannot use .* in assignment" + s = (*S)(s) + s = (*S)(t) + s = (*S)(u) + t = u // ERROR "cannot use .* in assignment" + t = (*T)(u) +} + +func _() { + type E1 struct { + x int "foo" + } + type E2 struct { + x int "bar" + } + type S struct{ x E1 } + type T struct { + x E2 "foo" + } + var s *S + var t *T + var u *struct { + x E2 "bar" + } + s = s + s = t // ERROR "cannot use .* in assignment" + s = u // ERROR "cannot use .* in assignment" + s = (*S)(s) + s = (*S)(t /* ERROR "cannot convert" */ ) + s = (*S)(u /* ERROR "cannot convert" */ ) + t = u // ERROR "cannot use .* in assignment" + t = (*T)(u) +} + +func _() { + type E struct{ x int } + type S struct { + f func(struct { + x int "foo" + }) + } + type T struct { + f func(struct { + x int "bar" + }) + } + var s *S + var t *T + var u *struct{ f func(E) } + s = s + s = t // ERROR "cannot use .* in assignment" + s = u // ERROR "cannot use .* in assignment" + s = (*S)(s) + s = (*S)(t) + s = (*S)(u /* ERROR "cannot convert" */ ) + t = u // ERROR "cannot use .* in assignment" + t = (*T)(u /* ERROR "cannot convert" */ ) +} + +func _() { + type E struct{ x int } + type S struct { + f func(*struct { + x int "foo" + }) + } + type T struct { + f func(*struct { + x int "bar" + }) + } + var s *S + var t *T + var u *struct{ f func(E) } + s = s + s = t // ERROR "cannot use .* in assignment" + s = u // ERROR "cannot use .* in assignment" + s = (*S)(s) + s = (*S)(t) + s = (*S)(u /* ERROR "cannot convert" */ ) + t = u // ERROR "cannot use .* in assignment" + t = (*T)(u /* ERROR "cannot convert" */ ) +} diff --git a/src/go/types/testdata/cycles.src b/src/go/types/testdata/cycles.src new file mode 100644 index 0000000..218b4ca --- /dev/null +++ b/src/go/types/testdata/cycles.src @@ -0,0 +1,174 @@ +// Copyright 2013 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 cycles + +import "unsafe" + +type ( + T0 int + T1 /* ERROR cycle */ T1 + T2 *T2 + + T3 /* ERROR cycle */ T4 + T4 T5 + T5 T3 + + T6 T7 + T7 *T8 + T8 T6 + + // arrays + A0 /* ERROR cycle */ [10]A0 + A1 [10]*A1 + + A2 /* ERROR cycle */ [10]A3 + A3 [10]A4 + A4 A2 + + A5 [10]A6 + A6 *A5 + + // slices + L0 []L0 + + // structs + S0 /* ERROR cycle */ struct{ _ S0 } + S1 /* ERROR cycle */ struct{ S1 } + S2 struct{ _ *S2 } + S3 struct{ *S3 } + + S4 /* ERROR cycle */ struct{ S5 } + S5 struct{ S6 } + S6 S4 + + // pointers + P0 *P0 + + // functions + F0 func(F0) + F1 func() F1 + F2 func(F2) F2 + + // interfaces + I0 /* ERROR cycle */ interface{ I0 } + + I1 /* ERROR cycle */ interface{ I2 } + I2 interface{ I3 } + I3 interface{ I1 } + + I4 interface{ f(I4) } + + // testcase for issue 5090 + I5 interface{ f(I6) } + I6 interface{ I5 } + + // maps + M0 map[M0 /* ERROR incomparable map key */ ]M0 + + // channels + C0 chan C0 +) + +// test case for issue #34771 +type ( + AA /* ERROR cycle */ B + B C + C [10]D + D E + E AA +) + +func _() { + type ( + t1 /* ERROR cycle */ t1 + t2 *t2 + + t3 t4 /* ERROR undeclared */ + t4 t5 /* ERROR undeclared */ + t5 t3 + + // arrays + a0 /* ERROR cycle */ [10]a0 + a1 [10]*a1 + + // slices + l0 []l0 + + // structs + s0 /* ERROR cycle */ struct{ _ s0 } + s1 /* ERROR cycle */ struct{ s1 } + s2 struct{ _ *s2 } + s3 struct{ *s3 } + + // pointers + p0 *p0 + + // functions + f0 func(f0) + f1 func() f1 + f2 func(f2) f2 + + // interfaces + i0 /* ERROR cycle */ interface{ i0 } + + // maps + m0 map[m0 /* ERROR incomparable map key */ ]m0 + + // channels + c0 chan c0 + ) +} + +// test cases for issue 6667 + +type A [10]map[A /* ERROR incomparable map key */ ]bool + +type S struct { + m map[S /* ERROR incomparable map key */ ]bool +} + +// test cases for issue 7236 +// (cycle detection must not be dependent on starting point of resolution) + +type ( + P1 *T9 + T9 /* ERROR cycle */ T9 + + T10 /* ERROR cycle */ T10 + P2 *T10 +) + +func (T11) m() {} + +type T11 /* ERROR cycle */ struct{ T11 } + +type T12 /* ERROR cycle */ struct{ T12 } + +func (*T12) m() {} + +type ( + P3 *T13 + T13 /* ERROR cycle */ T13 +) + +// test cases for issue 18643 +// (type cycle detection when non-type expressions are involved) +type ( + T14 [len(T14 /* ERROR cycle */ {})]int + T15 [][len(T15 /* ERROR cycle */ {})]int + T16 map[[len(T16 /* ERROR cycle */ {1:2})]int]int + T17 map[int][len(T17 /* ERROR cycle */ {1:2})]int +) + +// Test case for types depending on function literals (see also #22992). +type T20 chan [unsafe.Sizeof(func(ch T20){ _ = <-ch })]byte +type T22 = chan [unsafe.Sizeof(func(ch T20){ _ = <-ch })]byte + +func _() { + type T0 func(T0) + type T1 /* ERROR cycle */ = func(T1) + type T2 chan [unsafe.Sizeof(func(ch T2){ _ = <-ch })]byte + type T3 /* ERROR cycle */ = chan [unsafe.Sizeof(func(ch T3){ _ = <-ch })]byte +} diff --git a/src/go/types/testdata/cycles1.src b/src/go/types/testdata/cycles1.src new file mode 100644 index 0000000..ae2b38e --- /dev/null +++ b/src/go/types/testdata/cycles1.src @@ -0,0 +1,77 @@ +// Copyright 2013 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 p + +type ( + A interface { + a() interface { + ABC1 + } + } + B interface { + b() interface { + ABC2 + } + } + C interface { + c() interface { + ABC3 + } + } + + AB interface { + A + B + } + BC interface { + B + C + } + + ABC1 interface { + A + B + C + } + ABC2 interface { + AB + C + } + ABC3 interface { + A + BC + } +) + +var ( + x1 ABC1 + x2 ABC2 + x3 ABC3 +) + +func _() { + // all types have the same method set + x1 = x2 + x2 = x1 + + x1 = x3 + x3 = x1 + + x2 = x3 + x3 = x2 + + // all methods return the same type again + x1 = x1.a() + x1 = x1.b() + x1 = x1.c() + + x2 = x2.a() + x2 = x2.b() + x2 = x2.c() + + x3 = x3.a() + x3 = x3.b() + x3 = x3.c() +} diff --git a/src/go/types/testdata/cycles2.src b/src/go/types/testdata/cycles2.src new file mode 100644 index 0000000..1a7f40a --- /dev/null +++ b/src/go/types/testdata/cycles2.src @@ -0,0 +1,98 @@ +// Copyright 2013 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 p + +import "unsafe" + +// Test case for issue 5090 + +type t interface { + f(u) +} + +type u interface { + t +} + +func _() { + var t t + var u u + + t.f(t) + t.f(u) + + u.f(t) + u.f(u) +} + + +// Test case for issues #6589, #33656. + +type A interface { + a() interface { + AB + } +} + +type B interface { + b() interface { + AB + } +} + +type AB interface { + a() interface { + A + B + } + b() interface { + A + B + } +} + +var x AB +var y interface { + A + B +} + +var _ = x == y + + +// Test case for issue 6638. + +type T interface { + m() [T(nil).m /* ERROR undefined */ ()[0]]int +} + +// Variations of this test case. + +type T1 /* ERROR cycle */ interface { + m() [x1.m()[0]]int +} + +var x1 T1 + +type T2 /* ERROR cycle */ interface { + m() [len(x2.m())]int +} + +var x2 T2 + +type T3 /* ERROR cycle */ interface { + m() [unsafe.Sizeof(x3.m)]int +} + +var x3 T3 + +type T4 /* ERROR cycle */ interface { + m() [unsafe.Sizeof(cast4(x4.m))]int // cast is invalid but we have a cycle, so all bets are off +} + +var x4 T4 +var _ = cast4(x4.m) + +type cast4 func() diff --git a/src/go/types/testdata/cycles3.src b/src/go/types/testdata/cycles3.src new file mode 100644 index 0000000..5e89b62 --- /dev/null +++ b/src/go/types/testdata/cycles3.src @@ -0,0 +1,60 @@ +// Copyright 2013 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 p + +import "unsafe" + +var ( + _ A = A(nil).a().b().c().d().e().f() + _ A = A(nil).b().c().d().e().f() + _ A = A(nil).c().d().e().f() + _ A = A(nil).d().e().f() + _ A = A(nil).e().f() + _ A = A(nil).f() + _ A = A(nil) +) + +type ( + A interface { + a() B + B + } + + B interface { + b() C + C + } + + C interface { + c() D + D + } + + D interface { + d() E + E + } + + E interface { + e() F + F + } + + F interface { + f() A + } +) + +type ( + U /* ERROR cycle */ interface { + V + } + + V interface { + v() [unsafe.Sizeof(u)]int + } +) + +var u U diff --git a/src/go/types/testdata/cycles4.src b/src/go/types/testdata/cycles4.src new file mode 100644 index 0000000..445babc --- /dev/null +++ b/src/go/types/testdata/cycles4.src @@ -0,0 +1,110 @@ +// Copyright 2013 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 p + +// Check that all methods of T are collected before +// determining the result type of m (which embeds +// all methods of T). + +type T interface { + m() interface {T} + E +} + +var _ = T.m(nil).m().e() + +type E interface { + e() int +} + +// Check that unresolved forward chains are followed +// (see also comment in resolver.go, checker.typeDecl). + +var _ = C.m(nil).m().e() + +type A B + +type B interface { + m() interface{C} + E +} + +type C A + +// Check that interface type comparison for identity +// does not recur endlessly. + +type T1 interface { + m() interface{T1} +} + +type T2 interface { + m() interface{T2} +} + +func _(x T1, y T2) { + // Checking for assignability of interfaces must check + // if all methods of x are present in y, and that they + // have identical signatures. The signatures recur via + // the result type, which is an interface that embeds + // a single method m that refers to the very interface + // that contains it. This requires cycle detection in + // identity checks for interface types. + x = y +} + +type T3 interface { + m() interface{T4} +} + +type T4 interface { + m() interface{T3} +} + +func _(x T1, y T3) { + x = y +} + +// Check that interfaces are type-checked in order of +// (embedded interface) dependencies (was issue 7158). + +var x1 T5 = T7(nil) + +type T5 interface { + T6 +} + +type T6 interface { + m() T7 +} +type T7 interface { + T5 +} + +// Actual test case from issue 7158. + +func wrapNode() Node { + return wrapElement() +} + +func wrapElement() Element { + return nil +} + +type EventTarget interface { + AddEventListener(Event) +} + +type Node interface { + EventTarget +} + +type Element interface { + Node +} + +type Event interface { + Target() Element +} diff --git a/src/go/types/testdata/cycles5.src b/src/go/types/testdata/cycles5.src new file mode 100644 index 0000000..397adcc --- /dev/null +++ b/src/go/types/testdata/cycles5.src @@ -0,0 +1,200 @@ +// Copyright 2017 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 p + +import "unsafe" + +// test case from issue #18395 + +type ( + A interface { B } + B interface { C } + C interface { D; F() A } + D interface { G() B } +) + +var _ = A(nil).G // G must be found + + +// test case from issue #21804 + +type sourceBridge interface { + listVersions() ([]Version, error) +} + +type Constraint interface { + copyTo(*ConstraintMsg) +} + +type ConstraintMsg struct{} + +func (m *ConstraintMsg) asUnpairedVersion() UnpairedVersion { + return nil +} + +type Version interface { + Constraint +} + +type UnpairedVersion interface { + Version +} + +var _ Constraint = UnpairedVersion(nil) + + +// derived test case from issue #21804 + +type ( + _ interface{ m(B1) } + A1 interface{ a(D1) } + B1 interface{ A1 } + C1 interface{ B1 } + D1 interface{ C1 } +) + +var _ A1 = C1(nil) + + +// derived test case from issue #22701 + +func F(x I4) interface{} { + return x.Method() +} + +type Unused interface { + RefersToI1(a I1) +} + +type I1 interface { + I2 + I3 +} + +type I2 interface { + RefersToI4() I4 +} + +type I3 interface { + Method() interface{} +} + +type I4 interface { + I1 +} + + +// check embedding of error interface + +type Error interface{ error } + +var err Error +var _ = err.Error() + + +// more esoteric cases + +type ( + T1 interface { T2 } + T2 /* ERROR cycle */ T2 +) + +type ( + T3 interface { T4 } + T4 /* ERROR cycle */ T5 + T5 = T6 + T6 = T7 + T7 = T4 +) + + +// arbitrary code may appear inside an interface + +const n = unsafe.Sizeof(func(){}) + +type I interface { + m([unsafe.Sizeof(func() { I.m(nil, [n]byte{}) })]byte) +} + + +// test cases for varias alias cycles + +type T10 /* ERROR cycle */ = *T10 // issue #25141 +type T11 /* ERROR cycle */ = interface{ f(T11) } // issue #23139 + +// issue #18640 +type ( + aa = bb + bb struct { + *aa + } +) + +type ( + a struct{ *b } + b = c + c struct{ *b } +) + +// issue #24939 +type ( + _ interface { + M(P) + } + + M interface { + F() P + } + + P = interface { + I() M + } +) + +// issue #8699 +type T12 /* ERROR cycle */ [len(a12)]int +var a12 = makeArray() +func makeArray() (res T12) { return } + +// issue #20770 +var r /* ERROR cycle */ = newReader() +func newReader() r + +// variations of the theme of #8699 and #20770 +var arr /* ERROR cycle */ = f() +func f() [len(arr)]int + +// issue #25790 +func ff(ff /* ERROR not a type */ ) +func gg((gg /* ERROR not a type */ )) + +type T13 /* ERROR cycle */ [len(b13)]int +var b13 T13 + +func g1() [unsafe.Sizeof(g1)]int +func g2() [unsafe.Sizeof(x2)]int +var x2 = g2 + +// verify that we get the correct sizes for the functions above +// (note: assert is statically evaluated in go/types test mode) +func init() { + assert(unsafe.Sizeof(g1) == 8) + assert(unsafe.Sizeof(x2) == 8) +} + +func h() [h /* ERROR no value */ ()[0]]int { panic(0) } + +var c14 /* ERROR cycle */ T14 +type T14 [uintptr(unsafe.Sizeof(&c14))]byte + +// issue #34333 +type T15 /* ERROR cycle */ struct { + f func() T16 + b T16 +} + +type T16 struct { + T15 +} \ No newline at end of file diff --git a/src/go/types/testdata/decls0.src b/src/go/types/testdata/decls0.src new file mode 100644 index 0000000..5501b65 --- /dev/null +++ b/src/go/types/testdata/decls0.src @@ -0,0 +1,206 @@ +// Copyright 2011 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. + +// type declarations + +package decls0 + +import "unsafe" + +const pi = 3.1415 + +type ( + N undeclared /* ERROR "undeclared" */ + B bool + I int32 + A [10]P + T struct { + x, y P + } + P *T + R (*R) + F func(A) I + Y interface { + f(A) I + } + S [](((P))) + M map[I]F + C chan<- I + + // blank types must be typechecked + _ pi /* ERROR "not a type" */ + _ struct{} + _ struct{ pi /* ERROR "not a type" */ } +) + + +// declarations of init +const _, init /* ERROR "cannot declare init" */ , _ = 0, 1, 2 +type init /* ERROR "cannot declare init" */ struct{} +var _, init /* ERROR "cannot declare init" */ int + +func init() {} +func init /* ERROR "missing function body" */ () + +func _() { const init = 0 } +func _() { type init int } +func _() { var init int; _ = init } + +// invalid array types +type ( + iA0 [... /* ERROR "invalid use of '...'" */ ]byte + // The error message below could be better. At the moment + // we believe an integer that is too large is not an integer. + // But at least we get an error. + iA1 [1 /* ERROR "must be integer" */ <<100]int + iA2 [- /* ERROR "invalid array length" */ 1]complex128 + iA3 ["foo" /* ERROR "must be integer" */ ]string + iA4 [float64 /* ERROR "must be integer" */ (0)]int +) + + +type ( + p1 pi.foo /* ERROR "no field or method foo" */ + p2 unsafe.Pointer +) + + +type ( + Pi pi /* ERROR "not a type" */ + + a /* ERROR "illegal cycle" */ a + a /* ERROR "redeclared" */ int + + b /* ERROR "illegal cycle" */ c + c d + d e + e b + + t *t + + U V + V *W + W U + + P1 *S2 + P2 P1 + + S0 struct { + } + S1 struct { + a, b, c int + u, v, a /* ERROR "redeclared" */ float32 + } + S2 struct { + S0 // embedded field + S0 /* ERROR "redeclared" */ int + } + S3 struct { + x S2 + } + S4/* ERROR "illegal cycle" */ struct { + S4 + } + S5 /* ERROR "illegal cycle" */ struct { + S6 + } + S6 struct { + field S7 + } + S7 struct { + S5 + } + + L1 []L1 + L2 []int + + A1 [10.0]int + A2 /* ERROR "illegal cycle" */ [10]A2 + A3 /* ERROR "illegal cycle" */ [10]struct { + x A4 + } + A4 [10]A3 + + F1 func() + F2 func(x, y, z float32) + F3 func(x, y, x /* ERROR "redeclared" */ float32) + F4 func() (x, y, x /* ERROR "redeclared" */ float32) + F5 func(x int) (x /* ERROR "redeclared" */ float32) + F6 func(x ...int) + + I1 interface{} + I2 interface { + m1() + } + I3 interface { + m1() + m1 /* ERROR "duplicate method" */ () + } + I4 interface { + m1(x, y, x /* ERROR "redeclared" */ float32) + m2() (x, y, x /* ERROR "redeclared" */ float32) + m3(x int) (x /* ERROR "redeclared" */ float32) + } + I5 interface { + m1(I5) + } + I6 interface { + S0 /* ERROR "not an interface" */ + } + I7 interface { + I1 + I1 + } + I8 /* ERROR "illegal cycle" */ interface { + I8 + } + I9 /* ERROR "illegal cycle" */ interface { + I10 + } + I10 interface { + I11 + } + I11 interface { + I9 + } + + C1 chan int + C2 <-chan int + C3 chan<- C3 + C4 chan C5 + C5 chan C6 + C6 chan C4 + + M1 map[Last]string + M2 map[string]M2 + + Last int +) + +// cycles in function/method declarations +// (test cases for issues #5217, #25790 and variants) +func f1(x f1 /* ERROR "not a type" */ ) {} +func f2(x *f2 /* ERROR "not a type" */ ) {} +func f3() (x f3 /* ERROR "not a type" */ ) { return } +func f4() (x *f4 /* ERROR "not a type" */ ) { return } + +func (S0) m1 /* ERROR illegal cycle */ (x S0 /* ERROR value .* is not a type */ .m1) {} +func (S0) m2 /* ERROR illegal cycle */ (x *S0 /* ERROR value .* is not a type */ .m2) {} +func (S0) m3 /* ERROR illegal cycle */ () (x S0 /* ERROR value .* is not a type */ .m3) { return } +func (S0) m4 /* ERROR illegal cycle */ () (x *S0 /* ERROR value .* is not a type */ .m4) { return } + +// interfaces may not have any blank methods +type BlankI interface { + _ /* ERROR "invalid method name" */ () + _ /* ERROR "invalid method name" */ (float32) int + m() +} + +// non-interface types may have multiple blank methods +type BlankT struct{} + +func (BlankT) _() {} +func (BlankT) _(int) {} +func (BlankT) _() int { return 0 } +func (BlankT) _(int) int { return 0} diff --git a/src/go/types/testdata/decls1.src b/src/go/types/testdata/decls1.src new file mode 100644 index 0000000..f4d2eab --- /dev/null +++ b/src/go/types/testdata/decls1.src @@ -0,0 +1,146 @@ +// Copyright 2012 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. + +// variable declarations + +package decls1 + +import ( + "math" +) + +// Global variables without initialization +var ( + a, b bool + c byte + d uint8 + r rune + i int + j, k, l int + x, y float32 + xx, yy float64 + u, v complex64 + uu, vv complex128 + s, t string + array []byte + iface interface{} + + blank _ /* ERROR "cannot use _" */ +) + +// Global variables with initialization +var ( + s1 = i + j + s2 = i /* ERROR "mismatched types" */ + x + s3 = c + d + s4 = s + t + s5 = s /* ERROR "invalid operation" */ / t + s6 = array[t1] + s7 = array[x /* ERROR "integer" */] + s8 = &a + s10 = &42 /* ERROR "cannot take address" */ + s11 = &v + s12 = -(u + *t11) / *&v + s13 = a /* ERROR "shifted operand" */ << d + s14 = i << j + s18 = math.Pi * 10.0 + s19 = s1 /* ERROR "cannot call" */ () + s20 = f0 /* ERROR "no value" */ () + s21 = f6(1, s1, i) + s22 = f6(1, s1, uu /* ERROR "cannot use .* in argument" */ ) + + t1 int = i + j + t2 int = i /* ERROR "mismatched types" */ + x + t3 int = c /* ERROR "cannot use .* variable declaration" */ + d + t4 string = s + t + t5 string = s /* ERROR "invalid operation" */ / t + t6 byte = array[t1] + t7 byte = array[x /* ERROR "must be integer" */] + t8 *int = & /* ERROR "cannot use .* variable declaration" */ a + t10 *int = &42 /* ERROR "cannot take address" */ + t11 *complex64 = &v + t12 complex64 = -(u + *t11) / *&v + t13 int = a /* ERROR "shifted operand" */ << d + t14 int = i << j + t15 math /* ERROR "not in selector" */ + t16 math.xxx /* ERROR "not declared" */ + t17 math /* ERROR "not a type" */ .Pi + t18 float64 = math.Pi * 10.0 + t19 int = t1 /* ERROR "cannot call" */ () + t20 int = f0 /* ERROR "no value" */ () + t21 int = a /* ERROR "cannot use .* variable declaration" */ +) + +// Various more complex expressions +var ( + u1 = x /* ERROR "not an interface" */ .(int) + u2 = iface.([]int) + u3 = iface.(a /* ERROR "not a type" */ ) + u4, ok = iface.(int) + u5, ok2, ok3 = iface /* ERROR "cannot initialize" */ .(int) +) + +// Constant expression initializations +var ( + v1 = 1 /* ERROR "cannot convert" */ + "foo" + v2 = c + 255 + v3 = c + 256 /* ERROR "overflows" */ + v4 = r + 2147483647 + v5 = r + 2147483648 /* ERROR "overflows" */ + v6 = 42 + v7 = v6 + 9223372036854775807 + v8 = v6 + 9223372036854775808 /* ERROR "overflows" */ + v9 = i + 1 << 10 + v10 byte = 1024 /* ERROR "overflows" */ + v11 = xx/yy*yy - xx + v12 = true && false + v13 = nil /* ERROR "use of untyped nil" */ + v14 string = 257 // ERROR cannot use 257 .* as string value in variable declaration$ + v15 int8 = 257 // ERROR cannot use 257 .* as int8 value in variable declaration .*overflows +) + +// Multiple assignment expressions +var ( + m1a, m1b = 1, 2 + m2a, m2b, m2c /* ERROR "missing init expr for m2c" */ = 1, 2 + m3a, m3b = 1, 2, 3 /* ERROR "extra init expr 3" */ +) + +func _() { + var ( + m1a, m1b = 1, 2 + m2a, m2b, m2c /* ERROR "missing init expr for m2c" */ = 1, 2 + m3a, m3b = 1, 2, 3 /* ERROR "extra init expr 3" */ + ) + + _, _ = m1a, m1b + _, _, _ = m2a, m2b, m2c + _, _ = m3a, m3b +} + +// Declaration of parameters and results +func f0() {} +func f1(a /* ERROR "not a type" */) {} +func f2(a, b, c d /* ERROR "not a type" */) {} + +func f3() int { return 0 } +func f4() a /* ERROR "not a type" */ { return 0 } +func f5() (a, b, c d /* ERROR "not a type" */) { return } + +func f6(a, b, c int) complex128 { return 0 } + +// Declaration of receivers +type T struct{} + +func (T) m0() {} +func (*T) m1() {} +func (x T) m2() {} +func (x *T) m3() {} + +// Initialization functions +func init() {} +func /* ERROR "no arguments and no return values" */ init(int) {} +func /* ERROR "no arguments and no return values" */ init() int { return 0 } +func /* ERROR "no arguments and no return values" */ init(int) int { return 0 } +func (T) init(int) int { return 0 } diff --git a/src/go/types/testdata/decls2a.src b/src/go/types/testdata/decls2a.src new file mode 100644 index 0000000..bdbecd9 --- /dev/null +++ b/src/go/types/testdata/decls2a.src @@ -0,0 +1,111 @@ +// Copyright 2012 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. + +// method declarations + +package decls2 + +import "time" +import "unsafe" + +// T1 declared before its methods. +type T1 struct{ + f int +} + +func (T1) m() {} +func (T1) m /* ERROR "already declared" */ () {} +func (x *T1) f /* ERROR "field and method" */ () {} + +// Conflict between embedded field and method name, +// with the embedded field being a basic type. +type T1b struct { + int +} + +func (T1b) int /* ERROR "field and method" */ () {} + +type T1c struct { + time.Time +} + +func (T1c) Time /* ERROR "field and method" */ () int { return 0 } + +// Disabled for now: LookupFieldOrMethod will find Pointer even though +// it's double-declared (it would cost extra in the common case to verify +// this). But the MethodSet computation will not find it due to the name +// collision caused by the double-declaration, leading to an internal +// inconsistency while we are verifying one computation against the other. +// var _ = T1c{}.Pointer + +// T2's method declared before the type. +func (*T2) f /* ERROR "field and method" */ () {} + +type T2 struct { + f int +} + +// Methods declared without a declared type. +func (undeclared /* ERROR "undeclared" */) m() {} +func (x *undeclared /* ERROR "undeclared" */) m() {} + +func (pi /* ERROR "not a type" */) m1() {} +func (x pi /* ERROR "not a type" */) m2() {} +func (x *pi /* ERROR "not a type" */ ) m3() {} + +// Blank types. +type _ struct { m int } +type _ struct { m int } + +func (_ /* ERROR "cannot use _" */) m() {} +func m(_ /* ERROR "cannot use _" */) {} + +// Methods with receiver base type declared in another file. +func (T3) m1() {} +func (*T3) m2() {} +func (x T3) m3() {} +func (x *T3) f /* ERROR "field and method" */ () {} + +// Methods of non-struct type. +type T4 func() + +func (self T4) m() func() { return self } + +// Methods associated with an interface. +type T5 interface { + m() int +} + +func (T5 /* ERROR "invalid receiver" */ ) m1() {} +func (T5 /* ERROR "invalid receiver" */ ) m2() {} + +// Methods associated with a named pointer type. +type ptr *int +func (ptr /* ERROR "invalid receiver" */ ) _() {} +func (* /* ERROR "invalid receiver" */ ptr) _() {} + +// Methods with zero or multiple receivers. +func ( /* ERROR "missing receiver" */ ) _() {} +func (T3, * /* ERROR "exactly one receiver" */ T3) _() {} +func (T3, T3, T3 /* ERROR "exactly one receiver" */ ) _() {} +func (a, b /* ERROR "exactly one receiver" */ T3) _() {} +func (a, b, c /* ERROR "exactly one receiver" */ T3) _() {} + +// Methods associated with non-local or unnamed types. +func (int /* ERROR "invalid receiver" */ ) m() {} +func ([ /* ERROR "invalid receiver" */ ]int) m() {} +func (time /* ERROR "invalid receiver" */ .Time) m() {} +func (* /* ERROR "invalid receiver" */ time.Time) m() {} +func (x /* ERROR "invalid receiver" */ interface{}) m() {} + +// Unsafe.Pointer is treated like a pointer when used as receiver type. +type UP unsafe.Pointer +func (UP /* ERROR "invalid" */ ) m1() {} +func (* /* ERROR "invalid" */ UP) m2() {} + +// Double declarations across package files +const c_double = 0 +type t_double int +var v_double int +func f_double() {} diff --git a/src/go/types/testdata/decls2b.src b/src/go/types/testdata/decls2b.src new file mode 100644 index 0000000..5c55750 --- /dev/null +++ b/src/go/types/testdata/decls2b.src @@ -0,0 +1,75 @@ +// Copyright 2012 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. + +// method declarations + +package decls2 + +import "io" + +const pi = 3.1415 + +func (T1) m /* ERROR "already declared" */ () {} +func (T2) m(io.Writer) {} + +type T3 struct { + f *T3 +} + +type T6 struct { + x int +} + +func (t *T6) m1() int { + return t.x +} + +func f() { + var t *T6 + t.m1() +} + +// Double declarations across package files +const c_double /* ERROR "redeclared" */ = 0 +type t_double /* ERROR "redeclared" */ int +var v_double /* ERROR "redeclared" */ int +func f_double /* ERROR "redeclared" */ () {} + +// Blank methods need to be type-checked. +// Verify by checking that errors are reported. +func (T /* ERROR "undeclared" */ ) _() {} +func (T1) _(undeclared /* ERROR "undeclared" */ ) {} +func (T1) _() int { return "foo" /* ERROR "cannot use .* in return statement" */ } + +// Methods with undeclared receiver type can still be checked. +// Verify by checking that errors are reported. +func (Foo /* ERROR "undeclared" */ ) m() {} +func (Foo /* ERROR "undeclared" */ ) m(undeclared /* ERROR "undeclared" */ ) {} +func (Foo /* ERROR "undeclared" */ ) m() int { return "foo" /* ERROR "cannot use .* in return statement" */ } + +func (Foo /* ERROR "undeclared" */ ) _() {} +func (Foo /* ERROR "undeclared" */ ) _(undeclared /* ERROR "undeclared" */ ) {} +func (Foo /* ERROR "undeclared" */ ) _() int { return "foo" /* ERROR "cannot use .* in return statement" */ } + +// Receiver declarations are regular parameter lists; +// receiver types may use parentheses, and the list +// may have a trailing comma. +type T7 struct {} + +func (T7) m1() {} +func ((T7)) m2() {} +func ((*T7)) m3() {} +func (x *(T7),) m4() {} +func (x (*(T7)),) m5() {} +func (x ((*((T7)))),) m6() {} + +// Check that methods with parenthesized receiver are actually present (issue #23130). +var ( + _ = T7.m1 + _ = T7.m2 + _ = (*T7).m3 + _ = (*T7).m4 + _ = (*T7).m5 + _ = (*T7).m6 +) diff --git a/src/go/types/testdata/decls3.src b/src/go/types/testdata/decls3.src new file mode 100644 index 0000000..745175c --- /dev/null +++ b/src/go/types/testdata/decls3.src @@ -0,0 +1,309 @@ +// Copyright 2012 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. + +// embedded types + +package decls3 + +import "unsafe" +import "fmt" + +// fields with the same name at the same level cancel each other out + +func _() { + type ( + T1 struct { X int } + T2 struct { X int } + T3 struct { T1; T2 } // X is embedded twice at the same level via T1->X, T2->X + ) + + var t T3 + _ = t.X /* ERROR "ambiguous selector t.X" */ +} + +func _() { + type ( + T1 struct { X int } + T2 struct { T1 } + T3 struct { T1 } + T4 struct { T2; T3 } // X is embedded twice at the same level via T2->T1->X, T3->T1->X + ) + + var t T4 + _ = t.X /* ERROR "ambiguous selector t.X" */ +} + +func issue4355() { + type ( + T1 struct {X int} + T2 struct {T1} + T3 struct {T2} + T4 struct {T2} + T5 struct {T3; T4} // X is embedded twice at the same level via T3->T2->T1->X, T4->T2->T1->X + ) + + var t T5 + _ = t.X /* ERROR "ambiguous selector t.X" */ +} + +func _() { + type State int + type A struct{ State } + type B struct{ fmt.State } + type T struct{ A; B } + + var t T + _ = t.State /* ERROR "ambiguous selector t.State" */ +} + +// Embedded fields can be predeclared types. + +func _() { + type T0 struct{ + int + float32 + f int + } + var x T0 + _ = x.int + _ = x.float32 + _ = x.f + + type T1 struct{ + T0 + } + var y T1 + _ = y.int + _ = y.float32 + _ = y.f +} + +// Restrictions on embedded field types. + +func _() { + type I1 interface{} + type I2 interface{} + type P1 *int + type P2 *int + type UP unsafe.Pointer + + type T1 struct { + I1 + * /* ERROR "cannot be a pointer to an interface" */ I2 + * /* ERROR "cannot be a pointer to an interface" */ error + P1 /* ERROR "cannot be a pointer" */ + * /* ERROR "cannot be a pointer" */ P2 + } + + // unsafe.Pointers are treated like regular pointers when embedded + type T2 struct { + unsafe /* ERROR "cannot be unsafe.Pointer" */ .Pointer + */* ERROR "cannot be unsafe.Pointer" */ /* ERROR "Pointer redeclared" */ unsafe.Pointer + UP /* ERROR "cannot be unsafe.Pointer" */ + * /* ERROR "cannot be unsafe.Pointer" */ /* ERROR "UP redeclared" */ UP + } +} + +// Named types that are pointers. + +type S struct{ x int } +func (*S) m() {} +type P *S + +func _() { + var s *S + _ = s.x + _ = s.m + + var p P + _ = p.x + _ = p.m /* ERROR "no field or method" */ + _ = P.m /* ERROR "no field or method" */ +} + +// Borrowed from the FieldByName test cases in reflect/all_test.go. + +type D1 struct { + d int +} +type D2 struct { + d int +} + +type S0 struct { + A, B, C int + D1 + D2 +} + +type S1 struct { + B int + S0 +} + +type S2 struct { + A int + *S1 +} + +type S1x struct { + S1 +} + +type S1y struct { + S1 +} + +type S3 struct { + S1x + S2 + D, E int + *S1y +} + +type S4 struct { + *S4 + A int +} + +// The X in S6 and S7 annihilate, but they also block the X in S8.S9. +type S5 struct { + S6 + S7 + S8 +} + +type S6 struct { + X int +} + +type S7 S6 + +type S8 struct { + S9 +} + +type S9 struct { + X int + Y int +} + +// The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9. +type S10 struct { + S11 + S12 + S13 +} + +type S11 struct { + S6 +} + +type S12 struct { + S6 +} + +type S13 struct { + S8 +} + +func _() { + _ = struct{}{}.Foo /* ERROR "no field or method" */ + _ = S0{}.A + _ = S0{}.D /* ERROR "no field or method" */ + _ = S1{}.A + _ = S1{}.B + _ = S1{}.S0 + _ = S1{}.C + _ = S2{}.A + _ = S2{}.S1 + _ = S2{}.B + _ = S2{}.C + _ = S2{}.D /* ERROR "no field or method" */ + _ = S3{}.S1 /* ERROR "ambiguous selector \(S3 literal\).S1" */ + _ = S3{}.A + _ = S3{}.B /* ERROR "ambiguous selector" \(S3 literal\).B */ + _ = S3{}.D + _ = S3{}.E + _ = S4{}.A + _ = S4{}.B /* ERROR "no field or method" */ + _ = S5{}.X /* ERROR "ambiguous selector \(S5 literal\).X" */ + _ = S5{}.Y + _ = S10{}.X /* ERROR "ambiguous selector \(S10 literal\).X" */ + _ = S10{}.Y +} + +// Borrowed from the FieldByName benchmark in reflect/all_test.go. + +type R0 struct { + *R1 + *R2 + *R3 + *R4 +} + +type R1 struct { + *R5 + *R6 + *R7 + *R8 +} + +type R2 R1 +type R3 R1 +type R4 R1 + +type R5 struct { + *R9 + *R10 + *R11 + *R12 +} + +type R6 R5 +type R7 R5 +type R8 R5 + +type R9 struct { + *R13 + *R14 + *R15 + *R16 +} + +type R10 R9 +type R11 R9 +type R12 R9 + +type R13 struct { + *R17 + *R18 + *R19 + *R20 +} + +type R14 R13 +type R15 R13 +type R16 R13 + +type R17 struct { + *R21 + *R22 + *R23 + *R24 +} + +type R18 R17 +type R19 R17 +type R20 R17 + +type R21 struct { + X int +} + +type R22 R21 +type R23 R21 +type R24 R21 + +var _ = R0{}.X /* ERROR "ambiguous selector \(R0 literal\).X" */ \ No newline at end of file diff --git a/src/go/types/testdata/decls4.src b/src/go/types/testdata/decls4.src new file mode 100644 index 0000000..140bbfd --- /dev/null +++ b/src/go/types/testdata/decls4.src @@ -0,0 +1,199 @@ +// Copyright 2016 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. + +// type aliases + +package decls4 + +type ( + T0 [10]int + T1 []byte + T2 struct { + x int + } + T3 interface{ + m() T2 + } + T4 func(int, T0) chan T2 +) + +type ( + Ai = int + A0 = T0 + A1 = T1 + A2 = T2 + A3 = T3 + A4 = T4 + + A10 = [10]int + A11 = []byte + A12 = struct { + x int + } + A13 = interface{ + m() A2 + } + A14 = func(int, A0) chan A2 +) + +// check assignment compatibility due to equality of types +var ( + xi_ int + ai Ai = xi_ + + x0 T0 + a0 A0 = x0 + + x1 T1 + a1 A1 = x1 + + x2 T2 + a2 A2 = x2 + + x3 T3 + a3 A3 = x3 + + x4 T4 + a4 A4 = x4 +) + +// alias receiver types +func (Ai /* ERROR "invalid receiver" */) m1() {} +func (T0) m1() {} +func (A0) m1 /* ERROR already declared */ () {} +func (A0) m2 () {} +func (A3 /* ERROR invalid receiver */ ) m1 () {} +func (A10 /* ERROR invalid receiver */ ) m1() {} + +// x0 has methods m1, m2 declared via receiver type names T0 and A0 +var _ interface{ m1(); m2() } = x0 + +// alias receiver types (test case for issue #23042) +type T struct{} + +var ( + _ = T.m + _ = T{}.m + _ interface{m()} = T{} +) + +var ( + _ = T.n + _ = T{}.n + _ interface{m(); n()} = T{} +) + +type U = T +func (U) m() {} + +// alias receiver types (long type declaration chains) +type ( + V0 = V1 + V1 = (V2) + V2 = ((V3)) + V3 = T +) + +func (V0) m /* ERROR already declared */ () {} +func (V1) n() {} + +// alias receiver types (invalid due to cycles) +type ( + W0 /* ERROR illegal cycle */ = W1 + W1 = (W2) + W2 = ((W0)) +) + +func (W0) m() {} // no error expected (due to above cycle error) +func (W1) n() {} + +// alias receiver types (invalid due to builtin underlying type) +type ( + B0 = B1 + B1 = B2 + B2 = int +) + +func (B0 /* ERROR invalid receiver */ ) m() {} +func (B1 /* ERROR invalid receiver */ ) n() {} + +// cycles +type ( + C2 /* ERROR illegal cycle */ = C2 + C3 /* ERROR illegal cycle */ = C4 + C4 = C3 + C5 struct { + f *C6 + } + C6 = C5 + C7 /* ERROR illegal cycle */ struct { + f C8 + } + C8 = C7 +) + +// embedded fields +var ( + s0 struct { T0 } + s1 struct { A0 } = s0 /* ERROR cannot use */ // embedded field names are different +) + +// embedding and lookup of fields and methods +func _(s struct{A0}) { s.A0 = x0 } + +type eX struct{xf int} + +func (eX) xm() + +type eY = struct{eX} // field/method set of eY includes xf, xm + +type eZ = *struct{eX} // field/method set of eZ includes xf, xm + +type eA struct { + eX // eX contributes xf, xm to eA +} + +type eA2 struct { + *eX // *eX contributes xf, xm to eA +} + +type eB struct { + eY // eY contributes xf, xm to eB +} + +type eB2 struct { + *eY // *eY contributes xf, xm to eB +} + +type eC struct { + eZ // eZ contributes xf, xm to eC +} + +var ( + _ = eA{}.xf + _ = eA{}.xm + _ = eA2{}.xf + _ = eA2{}.xm + _ = eB{}.xf + _ = eB{}.xm + _ = eB2{}.xf + _ = eB2{}.xm + _ = eC{}.xf + _ = eC{}.xm +) + +// ambiguous selectors due to embedding via type aliases +type eD struct { + eY + eZ +} + +var ( + _ = eD{}.xf /* ERROR ambiguous selector \(eD literal\).xf */ + _ = eD{}.xm /* ERROR ambiguous selector \(eD literal\).xm */ +) + +var ( + _ interface{ xm() } = eD /* ERROR missing method xm */ {} +) \ No newline at end of file diff --git a/src/go/types/testdata/decls5.src b/src/go/types/testdata/decls5.src new file mode 100644 index 0000000..88d3194 --- /dev/null +++ b/src/go/types/testdata/decls5.src @@ -0,0 +1,10 @@ +// Copyright 2017 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 main + +// declarations of main +const _, main /* ERROR "cannot declare main" */ , _ = 0, 1, 2 +type main /* ERROR "cannot declare main" */ struct{} +var _, main /* ERROR "cannot declare main" */ int diff --git a/src/go/types/testdata/errors.src b/src/go/types/testdata/errors.src new file mode 100644 index 0000000..ff92921 --- /dev/null +++ b/src/go/types/testdata/errors.src @@ -0,0 +1,60 @@ +// Copyright 2013 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 errors + +// Testing precise operand formatting in error messages +// (matching messages are regular expressions, hence the \'s). +func f(x int, m map[string]int) { + // no values + _ = f /* ERROR "f\(0, m\) \(no value\) used as value" */ (0, m) + + // built-ins + _ = println /* ERROR "println \(built-in\) must be called" */ + + // types + _ = complex128 /* ERROR "complex128 \(type\) is not an expression" */ + + // constants + const c1 = 991 + const c2 float32 = 0.5 + 0 /* ERROR "0 \(untyped int constant\) is not used" */ + c1 /* ERROR "c1 \(untyped int constant 991\) is not used" */ + c2 /* ERROR "c2 \(constant 0.5 of type float32\) is not used" */ + c1 /* ERROR "c1 \+ c2 \(constant 991.5 of type float32\) is not used" */ + c2 + + // variables + x /* ERROR "x \(variable of type int\) is not used" */ + + // values + x /* ERROR "x != x \(untyped bool value\) is not used" */ != x + x /* ERROR "x \+ x \(value of type int\) is not used" */ + x + + // value, ok's + const s = "foo" + m /* ERROR "m\[s\] \(map index expression of type int\) is not used" */ [s] +} + +// Valid ERROR comments can have a variety of forms. +func _() { + 0 /* ERROR "0 .* is not used" */ + 0 /* ERROR 0 .* is not used */ + 0 // ERROR "0 .* is not used" + 0 // ERROR 0 .* is not used +} + +// Don't report spurious errors as a consequence of earlier errors. +// Add more tests as needed. +func _() { + if err := foo /* ERROR undeclared */ (); err != nil /* no error here */ {} +} + +// Use unqualified names for package-local objects. +type T struct{} +var _ int = T /* ERROR value of type T */ {} // use T in error message rather then errors.T + +// Don't report errors containing "invalid type" (issue #24182). +func _(x *missing /* ERROR undeclared name: missing */ ) { + x.m() // there shouldn't be an error here referring to *invalid type +} diff --git a/src/go/types/testdata/expr0.src b/src/go/types/testdata/expr0.src new file mode 100644 index 0000000..1aac726 --- /dev/null +++ b/src/go/types/testdata/expr0.src @@ -0,0 +1,180 @@ +// Copyright 2012 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. + +// unary expressions + +package expr0 + +type mybool bool + +var ( + // bool + b0 = true + b1 bool = b0 + b2 = !true + b3 = !b1 + b4 bool = !true + b5 bool = !b4 + b6 = +b0 /* ERROR "not defined" */ + b7 = -b0 /* ERROR "not defined" */ + b8 = ^b0 /* ERROR "not defined" */ + b9 = *b0 /* ERROR "cannot indirect" */ + b10 = &true /* ERROR "cannot take address" */ + b11 = &b0 + b12 = <-b0 /* ERROR "cannot receive" */ + b13 = & & /* ERROR "cannot take address" */ b0 + + // byte + _ = byte(0) + _ = byte(- /* ERROR "cannot convert" */ 1) + _ = - /* ERROR "-byte\(1\) \(constant -1 of type byte\) overflows byte" */ byte(1) // test for issue 11367 + _ = byte /* ERROR "overflows byte" */ (0) - byte(1) + + // int + i0 = 1 + i1 int = i0 + i2 = +1 + i3 = +i0 + i4 int = +1 + i5 int = +i4 + i6 = -1 + i7 = -i0 + i8 int = -1 + i9 int = -i4 + i10 = !i0 /* ERROR "not defined" */ + i11 = ^1 + i12 = ^i0 + i13 int = ^1 + i14 int = ^i4 + i15 = *i0 /* ERROR "cannot indirect" */ + i16 = &i0 + i17 = *i16 + i18 = <-i16 /* ERROR "cannot receive" */ + + // uint + u0 = uint(1) + u1 uint = u0 + u2 = +1 + u3 = +u0 + u4 uint = +1 + u5 uint = +u4 + u6 = -1 + u7 = -u0 + u8 uint = - /* ERROR "overflows" */ 1 + u9 uint = -u4 + u10 = !u0 /* ERROR "not defined" */ + u11 = ^1 + u12 = ^i0 + u13 uint = ^ /* ERROR "overflows" */ 1 + u14 uint = ^u4 + u15 = *u0 /* ERROR "cannot indirect" */ + u16 = &u0 + u17 = *u16 + u18 = <-u16 /* ERROR "cannot receive" */ + u19 = ^uint(0) + + // float64 + f0 = float64(1) + f1 float64 = f0 + f2 = +1 + f3 = +f0 + f4 float64 = +1 + f5 float64 = +f4 + f6 = -1 + f7 = -f0 + f8 float64 = -1 + f9 float64 = -f4 + f10 = !f0 /* ERROR "not defined" */ + f11 = ^1 + f12 = ^i0 + f13 float64 = ^1 + f14 float64 = ^f4 /* ERROR "not defined" */ + f15 = *f0 /* ERROR "cannot indirect" */ + f16 = &f0 + f17 = *u16 + f18 = <-u16 /* ERROR "cannot receive" */ + + // complex128 + c0 = complex128(1) + c1 complex128 = c0 + c2 = +1 + c3 = +c0 + c4 complex128 = +1 + c5 complex128 = +c4 + c6 = -1 + c7 = -c0 + c8 complex128 = -1 + c9 complex128 = -c4 + c10 = !c0 /* ERROR "not defined" */ + c11 = ^1 + c12 = ^i0 + c13 complex128 = ^1 + c14 complex128 = ^c4 /* ERROR "not defined" */ + c15 = *c0 /* ERROR "cannot indirect" */ + c16 = &c0 + c17 = *u16 + c18 = <-u16 /* ERROR "cannot receive" */ + + // string + s0 = "foo" + s1 = +"foo" /* ERROR "not defined" */ + s2 = -s0 /* ERROR "not defined" */ + s3 = !s0 /* ERROR "not defined" */ + s4 = ^s0 /* ERROR "not defined" */ + s5 = *s4 + s6 = &s4 + s7 = *s6 + s8 = <-s7 + + // channel + ch chan int + rc <-chan float64 + sc chan <- string + ch0 = +ch /* ERROR "not defined" */ + ch1 = -ch /* ERROR "not defined" */ + ch2 = !ch /* ERROR "not defined" */ + ch3 = ^ch /* ERROR "not defined" */ + ch4 = *ch /* ERROR "cannot indirect" */ + ch5 = &ch + ch6 = *ch5 + ch7 = <-ch + ch8 = <-rc + ch9 = <-sc /* ERROR "cannot receive" */ + ch10, ok = <-ch + // ok is of type bool + ch11, myok = <-ch + _ mybool = myok /* ERROR "cannot use .* in variable declaration" */ +) + +// address of composite literals +type T struct{x, y int} + +func f() T { return T{} } + +var ( + _ = &T{1, 2} + _ = &[...]int{} + _ = &[]int{} + _ = &[]int{} + _ = &map[string]T{} + _ = &(T{1, 2}) + _ = &((((T{1, 2})))) + _ = &f /* ERROR "cannot take address" */ () +) + +// recursive pointer types +type P *P + +var ( + p1 P = new(P) + p2 P = *p1 + p3 P = &p2 +) + +func g() (a, b int) { return } + +func _() { + _ = -g /* ERROR 2-valued g */ () + _ = <-g /* ERROR 2-valued g */ () +} diff --git a/src/go/types/testdata/expr1.src b/src/go/types/testdata/expr1.src new file mode 100644 index 0000000..4ead815 --- /dev/null +++ b/src/go/types/testdata/expr1.src @@ -0,0 +1,127 @@ +// Copyright 2012 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. + +// binary expressions + +package expr1 + +type mybool bool + +func _(x, y bool, z mybool) { + x = x || y + x = x || true + x = x || false + x = x && y + x = x && true + x = x && false + + z = z /* ERROR mismatched types */ || y + z = z || true + z = z || false + z = z /* ERROR mismatched types */ && y + z = z && true + z = z && false +} + +type myint int + +func _(x, y int, z myint) { + x = x + 1 + x = x + 1.0 + x = x + 1.1 // ERROR truncated to int + x = x + y + x = x - y + x = x * y + x = x / y + x = x % y + x = x << y + x = x >> y + + z = z + 1 + z = z + 1.0 + z = z + 1.1 // ERROR truncated to int + z = z /* ERROR mismatched types */ + y + z = z /* ERROR mismatched types */ - y + z = z /* ERROR mismatched types */ * y + z = z /* ERROR mismatched types */ / y + z = z /* ERROR mismatched types */ % y + z = z << y + z = z >> y +} + +type myuint uint + +func _(x, y uint, z myuint) { + x = x + 1 + x = x + - /* ERROR overflows uint */ 1 + x = x + 1.0 + x = x + 1.1 // ERROR truncated to uint + x = x + y + x = x - y + x = x * y + x = x / y + x = x % y + x = x << y + x = x >> y + + z = z + 1 + z = x + - /* ERROR overflows uint */ 1 + z = z + 1.0 + z = z + 1.1 // ERROR truncated to uint + z = z /* ERROR mismatched types */ + y + z = z /* ERROR mismatched types */ - y + z = z /* ERROR mismatched types */ * y + z = z /* ERROR mismatched types */ / y + z = z /* ERROR mismatched types */ % y + z = z << y + z = z >> y +} + +type myfloat64 float64 + +func _(x, y float64, z myfloat64) { + x = x + 1 + x = x + -1 + x = x + 1.0 + x = x + 1.1 + x = x + y + x = x - y + x = x * y + x = x / y + x = x /* ERROR not defined */ % y + x = x /* ERROR operand x .* must be integer */ << y + x = x /* ERROR operand x .* must be integer */ >> y + + z = z + 1 + z = z + -1 + z = z + 1.0 + z = z + 1.1 + z = z /* ERROR mismatched types */ + y + z = z /* ERROR mismatched types */ - y + z = z /* ERROR mismatched types */ * y + z = z /* ERROR mismatched types */ / y + z = z /* ERROR mismatched types */ % y + z = z /* ERROR operand z .* must be integer */ << y + z = z /* ERROR operand z .* must be integer */ >> y +} + +type mystring string + +func _(x, y string, z mystring) { + x = x + "foo" + x = x /* ERROR not defined */ - "foo" + x = x + 1 // ERROR cannot convert + x = x + y + x = x /* ERROR not defined */ - y + x = x * 10 // ERROR cannot convert +} + +func f() (a, b int) { return } + +func _(x int) { + _ = f /* ERROR 2-valued f */ () + 1 + _ = x + f /* ERROR 2-valued f */ () + _ = f /* ERROR 2-valued f */ () + f + _ = f /* ERROR 2-valued f */ () + f /* ERROR 2-valued f */ () +} diff --git a/src/go/types/testdata/expr2.src b/src/go/types/testdata/expr2.src new file mode 100644 index 0000000..0c959e8 --- /dev/null +++ b/src/go/types/testdata/expr2.src @@ -0,0 +1,260 @@ +// Copyright 2012 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. + +// comparisons + +package expr2 + +func _bool() { + const t = true == true + const f = true == false + _ = t /* ERROR "cannot compare" */ < f + _ = 0 /* ERROR "cannot convert" */ == t + var b bool + var x, y float32 + b = x < y + _ = b + _ = struct{b bool}{x < y} +} + +// corner cases +var ( + v0 = nil /* ERROR "cannot compare" */ == nil +) + +func arrays() { + // basics + var a, b [10]int + _ = a == b + _ = a != b + _ = a /* ERROR < not defined */ < b + _ = a == nil /* ERROR cannot convert */ + + type C [10]int + var c C + _ = a == c + + type D [10]int + var d D + _ = c /* ERROR mismatched types */ == d + + var e [10]func() int + _ = e /* ERROR == not defined */ == e +} + +func structs() { + // basics + var s, t struct { + x int + a [10]float32 + _ bool + } + _ = s == t + _ = s != t + _ = s /* ERROR < not defined */ < t + _ = s == nil /* ERROR cannot convert */ + + type S struct { + x int + a [10]float32 + _ bool + } + type T struct { + x int + a [10]float32 + _ bool + } + var ss S + var tt T + _ = s == ss + _ = ss /* ERROR mismatched types */ == tt + + var u struct { + x int + a [10]map[string]int + } + _ = u /* ERROR cannot compare */ == u +} + +func pointers() { + // nil + _ = nil /* ERROR == not defined */ == nil + _ = nil /* ERROR != not defined */ != nil + _ = nil /* ERROR < not defined */ < nil + _ = nil /* ERROR <= not defined */ <= nil + _ = nil /* ERROR > not defined */ > nil + _ = nil /* ERROR >= not defined */ >= nil + + // basics + var p, q *int + _ = p == q + _ = p != q + + _ = p == nil + _ = p != nil + _ = nil == q + _ = nil != q + + _ = p /* ERROR < not defined */ < q + _ = p /* ERROR <= not defined */ <= q + _ = p /* ERROR > not defined */ > q + _ = p /* ERROR >= not defined */ >= q + + // various element types + type ( + S1 struct{} + S2 struct{} + P1 *S1 + P2 *S2 + ) + var ( + ps1 *S1 + ps2 *S2 + p1 P1 + p2 P2 + ) + _ = ps1 == ps1 + _ = ps1 /* ERROR mismatched types */ == ps2 + _ = ps2 /* ERROR mismatched types */ == ps1 + + _ = p1 == p1 + _ = p1 /* ERROR mismatched types */ == p2 + + _ = p1 == ps1 +} + +func channels() { + // basics + var c, d chan int + _ = c == d + _ = c != d + _ = c == nil + _ = c /* ERROR < not defined */ < d + + // various element types (named types) + type ( + C1 chan int + C1r <-chan int + C1s chan<- int + C2 chan float32 + ) + var ( + c1 C1 + c1r C1r + c1s C1s + c1a chan int + c2 C2 + ) + _ = c1 == c1 + _ = c1 /* ERROR mismatched types */ == c1r + _ = c1 /* ERROR mismatched types */ == c1s + _ = c1r /* ERROR mismatched types */ == c1s + _ = c1 == c1a + _ = c1a == c1 + _ = c1 /* ERROR mismatched types */ == c2 + _ = c1a /* ERROR mismatched types */ == c2 + + // various element types (unnamed types) + var ( + d1 chan int + d1r <-chan int + d1s chan<- int + d1a chan<- int + d2 chan float32 + ) + _ = d1 == d1 + _ = d1 == d1r + _ = d1 == d1s + _ = d1r /* ERROR mismatched types */ == d1s + _ = d1 == d1a + _ = d1a == d1 + _ = d1 /* ERROR mismatched types */ == d2 + _ = d1a /* ERROR mismatched types */ == d2 +} + +// for interfaces test +type S1 struct{} +type S11 struct{} +type S2 struct{} +func (*S1) m() int +func (*S11) m() int +func (*S11) n() +func (*S2) m() float32 + +func interfaces() { + // basics + var i, j interface{ m() int } + _ = i == j + _ = i != j + _ = i == nil + _ = i /* ERROR < not defined */ < j + + // various interfaces + var ii interface { m() int; n() } + var k interface { m() float32 } + _ = i == ii + _ = i /* ERROR mismatched types */ == k + + // interfaces vs values + var s1 S1 + var s11 S11 + var s2 S2 + + _ = i == 0 /* ERROR cannot convert */ + _ = i /* ERROR mismatched types */ == s1 + _ = i == &s1 + _ = i == &s11 + + _ = i /* ERROR mismatched types */ == s2 + _ = i /* ERROR mismatched types */ == &s2 + + // issue #28164 + // testcase from issue + _ = interface /* ERROR cannot compare */ {}(nil) == []int(nil) + + // related cases + var e interface{} + var s []int + var x int + _ = e /* ERROR cannot compare */ == s + _ = s /* ERROR cannot compare */ == e + _ = e /* ERROR cannot compare */ < x + _ = x /* ERROR cannot compare */ < e +} + +func slices() { + // basics + var s []int + _ = s == nil + _ = s != nil + _ = s /* ERROR < not defined */ < nil + + // slices are not otherwise comparable + _ = s /* ERROR == not defined */ == s + _ = s /* ERROR < not defined */ < s +} + +func maps() { + // basics + var m map[string]int + _ = m == nil + _ = m != nil + _ = m /* ERROR < not defined */ < nil + + // maps are not otherwise comparable + _ = m /* ERROR == not defined */ == m + _ = m /* ERROR < not defined */ < m +} + +func funcs() { + // basics + var f func(int) float32 + _ = f == nil + _ = f != nil + _ = f /* ERROR < not defined */ < nil + + // funcs are not otherwise comparable + _ = f /* ERROR == not defined */ == f + _ = f /* ERROR < not defined */ < f +} diff --git a/src/go/types/testdata/expr3.src b/src/go/types/testdata/expr3.src new file mode 100644 index 0000000..e6777aa --- /dev/null +++ b/src/go/types/testdata/expr3.src @@ -0,0 +1,563 @@ +// Copyright 2012 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 expr3 + +import "time" + +func indexes() { + _ = 1 /* ERROR "cannot index" */ [0] + _ = indexes /* ERROR "cannot index" */ [0] + _ = ( /* ERROR "cannot slice" */ 12 + 3)[1:2] + + var a [10]int + _ = a[true /* ERROR "cannot convert" */ ] + _ = a["foo" /* ERROR "cannot convert" */ ] + _ = a[1.1 /* ERROR "truncated" */ ] + _ = a[1.0] + _ = a[- /* ERROR "negative" */ 1] + _ = a[- /* ERROR "negative" */ 1 :] + _ = a[: - /* ERROR "negative" */ 1] + _ = a[: /* ERROR "2nd index required" */ : /* ERROR "3rd index required" */ ] + _ = a[0: /* ERROR "2nd index required" */ : /* ERROR "3rd index required" */ ] + _ = a[0: /* ERROR "2nd index required" */ :10] + _ = a[:10:10] + + var a0 int + a0 = a[0] + _ = a0 + var a1 int32 + a1 = a /* ERROR "cannot use .* in assignment" */ [1] + _ = a1 + + _ = a[9] + _ = a[10 /* ERROR "index .* out of bounds" */ ] + _ = a[1 /* ERROR "overflows" */ <<100] + _ = a[10:] + _ = a[:10] + _ = a[10:10] + _ = a[11 /* ERROR "index .* out of bounds" */ :] + _ = a[: 11 /* ERROR "index .* out of bounds" */ ] + _ = a[: 1 /* ERROR "overflows" */ <<100] + _ = a[:10:10] + _ = a[:11 /* ERROR "index .* out of bounds" */ :10] + _ = a[:10:11 /* ERROR "index .* out of bounds" */ ] + _ = a[10:0:10] /* ERROR swapped slice indices" */ + _ = a[0:10:0] /* ERROR "swapped slice indices" */ + _ = a[10:0:0] /* ERROR "swapped slice indices" */ + _ = &a /* ERROR "cannot take address" */ [:10] + + pa := &a + _ = pa[9] + _ = pa[10 /* ERROR "index .* out of bounds" */ ] + _ = pa[1 /* ERROR "overflows" */ <<100] + _ = pa[10:] + _ = pa[:10] + _ = pa[10:10] + _ = pa[11 /* ERROR "index .* out of bounds" */ :] + _ = pa[: 11 /* ERROR "index .* out of bounds" */ ] + _ = pa[: 1 /* ERROR "overflows" */ <<100] + _ = pa[:10:10] + _ = pa[:11 /* ERROR "index .* out of bounds" */ :10] + _ = pa[:10:11 /* ERROR "index .* out of bounds" */ ] + _ = pa[10:0:10] /* ERROR "swapped slice indices" */ + _ = pa[0:10:0] /* ERROR "swapped slice indices" */ + _ = pa[10:0:0] /* ERROR "swapped slice indices" */ + _ = &pa /* ERROR "cannot take address" */ [:10] + + var b [0]int + _ = b[0 /* ERROR "index .* out of bounds" */ ] + _ = b[:] + _ = b[0:] + _ = b[:0] + _ = b[0:0] + _ = b[0:0:0] + _ = b[1 /* ERROR "index .* out of bounds" */ :0:0] + + var s []int + _ = s[- /* ERROR "negative" */ 1] + _ = s[- /* ERROR "negative" */ 1 :] + _ = s[: - /* ERROR "negative" */ 1] + _ = s[0] + _ = s[1:2] + _ = s[2:1] /* ERROR "swapped slice indices" */ + _ = s[2:] + _ = s[: 1 /* ERROR "overflows" */ <<100] + _ = s[1 /* ERROR "overflows" */ <<100 :] + _ = s[1 /* ERROR "overflows" */ <<100 : 1 /* ERROR "overflows" */ <<100] + _ = s[: /* ERROR "2nd index required" */ : /* ERROR "3rd index required" */ ] + _ = s[:10:10] + _ = s[10:0:10] /* ERROR "swapped slice indices" */ + _ = s[0:10:0] /* ERROR "swapped slice indices" */ + _ = s[10:0:0] /* ERROR "swapped slice indices" */ + _ = &s /* ERROR "cannot take address" */ [:10] + + var m map[string]int + _ = m[0 /* ERROR "cannot use .* in map index" */ ] + _ = m /* ERROR "cannot slice" */ ["foo" : "bar"] + _ = m["foo"] + // ok is of type bool + type mybool bool + var ok mybool + _, ok = m["bar"] + _ = ok + _ = m[0 /* ERROR "cannot use 0" */ ] + "foo" // ERROR "cannot convert" + + var t string + _ = t[- /* ERROR "negative" */ 1] + _ = t[- /* ERROR "negative" */ 1 :] + _ = t[: - /* ERROR "negative" */ 1] + _ = t /* ERROR "3-index slice of string" */ [1:2:3] + _ = "foo" /* ERROR "3-index slice of string" */ [1:2:3] + var t0 byte + t0 = t[0] + _ = t0 + var t1 rune + t1 = t /* ERROR "cannot use .* in assignment" */ [2] + _ = t1 + _ = ("foo" + "bar")[5] + _ = ("foo" + "bar")[6 /* ERROR "index .* out of bounds" */ ] + + const c = "foo" + _ = c[- /* ERROR "negative" */ 1] + _ = c[- /* ERROR "negative" */ 1 :] + _ = c[: - /* ERROR "negative" */ 1] + var c0 byte + c0 = c[0] + _ = c0 + var c2 float32 + c2 = c /* ERROR "cannot use .* in assignment" */ [2] + _ = c[3 /* ERROR "index .* out of bounds" */ ] + _ = ""[0 /* ERROR "index .* out of bounds" */ ] + _ = c2 + + _ = s[1<<30] // no compile-time error here + + // issue 4913 + type mystring string + var ss string + var ms mystring + var i, j int + ss = "foo"[1:2] + ss = "foo"[i:j] + ms = "foo" /* ERROR "cannot use .* in assignment" */ [1:2] + ms = "foo" /* ERROR "cannot use .* in assignment" */ [i:j] + _, _ = ss, ms +} + +type T struct { + x int + y func() +} + +func (*T) m() {} + +func method_expressions() { + _ = T.a /* ERROR "no field or method" */ + _ = T.x /* ERROR "has no method" */ + _ = T.m /* ERROR "cannot call pointer method m on T" */ + _ = (*T).m + + var f func(*T) = T.m /* ERROR "cannot call pointer method m on T" */ + var g func(*T) = (*T).m + _, _ = f, g + + _ = T.y /* ERROR "has no method" */ + _ = (*T).y /* ERROR "has no method" */ +} + +func struct_literals() { + type T0 struct { + a, b, c int + } + + type T1 struct { + T0 + a, b int + u float64 + s string + } + + // keyed elements + _ = T1{} + _ = T1{a: 0, 1 /* ERROR "mixture of .* elements" */ } + _ = T1{aa /* ERROR "unknown field" */ : 0} + _ = T1{1 /* ERROR "invalid field name" */ : 0} + _ = T1{a: 0, s: "foo", u: 0, a /* ERROR "duplicate field" */: 10} + _ = T1{a: "foo" /* ERROR "cannot use .* in struct literal" */ } + _ = T1{c /* ERROR "unknown field" */ : 0} + _ = T1{T0: { /* ERROR "missing type" */ }} // struct literal element type may not be elided + _ = T1{T0: T0{}} + _ = T1{T0 /* ERROR "invalid field name" */ .a: 0} + + // unkeyed elements + _ = T0{1, 2, 3} + _ = T0{1, b /* ERROR "mixture" */ : 2, 3} + _ = T0{1, 2} /* ERROR "too few values" */ + _ = T0{1, 2, 3, 4 /* ERROR "too many values" */ } + _ = T0{1, "foo" /* ERROR "cannot use .* in struct literal" */, 3.4 /* ERROR "cannot use .*\(truncated\)" */} + + // invalid type + type P *struct{ + x int + } + _ = P /* ERROR "invalid composite literal type" */ {} + + // unexported fields + _ = time.Time{} + _ = time.Time{sec /* ERROR "unknown field" */ : 0} + _ = time.Time{ + 0 /* ERROR implicit assignment to unexported field wall in time.Time literal */, + 0 /* ERROR implicit assignment */ , + nil /* ERROR implicit assignment */ , + } +} + +func array_literals() { + type A0 [0]int + _ = A0{} + _ = A0{0 /* ERROR "index .* out of bounds" */} + _ = A0{0 /* ERROR "index .* out of bounds" */ : 0} + + type A1 [10]int + _ = A1{} + _ = A1{0, 1, 2} + _ = A1{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + _ = A1{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 /* ERROR "index .* out of bounds" */ } + _ = A1{- /* ERROR "negative" */ 1: 0} + _ = A1{8: 8, 9} + _ = A1{8: 8, 9, 10 /* ERROR "index .* out of bounds" */ } + _ = A1{0, 1, 2, 0 /* ERROR "duplicate index" */ : 0, 3: 3, 4} + _ = A1{5: 5, 6, 7, 3: 3, 4} + _ = A1{5: 5, 6, 7, 3: 3, 4, 5 /* ERROR "duplicate index" */ } + _ = A1{10 /* ERROR "index .* out of bounds" */ : 10, 10 /* ERROR "index .* out of bounds" */ : 10} + _ = A1{5: 5, 6, 7, 3: 3, 1 /* ERROR "overflows" */ <<100: 4, 5 /* ERROR "duplicate index" */ } + _ = A1{5: 5, 6, 7, 4: 4, 1 /* ERROR "overflows" */ <<100: 4} + _ = A1{2.0} + _ = A1{2.1 /* ERROR "truncated" */ } + _ = A1{"foo" /* ERROR "cannot use .* in array or slice literal" */ } + + // indices must be integer constants + i := 1 + const f = 2.1 + const s = "foo" + _ = A1{i /* ERROR "index i must be integer constant" */ : 0} + _ = A1{f /* ERROR "truncated" */ : 0} + _ = A1{s /* ERROR "cannot convert" */ : 0} + + a0 := [...]int{} + assert(len(a0) == 0) + + a1 := [...]int{0, 1, 2} + assert(len(a1) == 3) + var a13 [3]int + var a14 [4]int + a13 = a1 + a14 = a1 /* ERROR "cannot use .* in assignment" */ + _, _ = a13, a14 + + a2 := [...]int{- /* ERROR "negative" */ 1: 0} + _ = a2 + + a3 := [...]int{0, 1, 2, 0 /* ERROR "duplicate index" */ : 0, 3: 3, 4} + assert(len(a3) == 5) // somewhat arbitrary + + a4 := [...]complex128{0, 1, 2, 1<<10-2: -1i, 1i, 400: 10, 12, 14} + assert(len(a4) == 1024) + + // composite literal element types may be elided + type T []int + _ = [10]T{T{}, {}, 5: T{1, 2, 3}, 7: {1, 2, 3}} + a6 := [...]T{T{}, {}, 5: T{1, 2, 3}, 7: {1, 2, 3}} + assert(len(a6) == 8) + + // recursively so + _ = [10][10]T{{}, [10]T{{}}, {{1, 2, 3}}} + + // from the spec + type Point struct { x, y float32 } + _ = [...]Point{Point{1.5, -3.5}, Point{0, 0}} + _ = [...]Point{{1.5, -3.5}, {0, 0}} + _ = [][]int{[]int{1, 2, 3}, []int{4, 5}} + _ = [][]int{{1, 2, 3}, {4, 5}} + _ = [...]*Point{&Point{1.5, -3.5}, &Point{0, 0}} + _ = [...]*Point{{1.5, -3.5}, {0, 0}} +} + +func slice_literals() { + type S0 []int + _ = S0{} + _ = S0{0, 1, 2} + _ = S0{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + _ = S0{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + _ = S0{- /* ERROR "negative" */ 1: 0} + _ = S0{8: 8, 9} + _ = S0{8: 8, 9, 10} + _ = S0{0, 1, 2, 0 /* ERROR "duplicate index" */ : 0, 3: 3, 4} + _ = S0{5: 5, 6, 7, 3: 3, 4} + _ = S0{5: 5, 6, 7, 3: 3, 4, 5 /* ERROR "duplicate index" */ } + _ = S0{10: 10, 10 /* ERROR "duplicate index" */ : 10} + _ = S0{5: 5, 6, 7, 3: 3, 1 /* ERROR "overflows" */ <<100: 4, 5 /* ERROR "duplicate index" */ } + _ = S0{5: 5, 6, 7, 4: 4, 1 /* ERROR "overflows" */ <<100: 4} + _ = S0{2.0} + _ = S0{2.1 /* ERROR "truncated" */ } + _ = S0{"foo" /* ERROR "cannot use .* in array or slice literal" */ } + + // indices must be resolved correctly + const index1 = 1 + _ = S0{index1: 1} + _ = S0{index2: 2} + _ = S0{index3 /* ERROR "undeclared name" */ : 3} + + // indices must be integer constants + i := 1 + const f = 2.1 + const s = "foo" + _ = S0{i /* ERROR "index i must be integer constant" */ : 0} + _ = S0{f /* ERROR "truncated" */ : 0} + _ = S0{s /* ERROR "cannot convert" */ : 0} + + // composite literal element types may be elided + type T []int + _ = []T{T{}, {}, 5: T{1, 2, 3}, 7: {1, 2, 3}} + _ = [][]int{{1, 2, 3}, {4, 5}} + + // recursively so + _ = [][]T{{}, []T{{}}, {{1, 2, 3}}} + + // issue 17954 + type T0 *struct { s string } + _ = []T0{{}} + _ = []T0{{"foo"}} + + type T1 *struct{ int } + _ = []T1{} + _ = []T1{{0}, {1}, {2}} + + type T2 T1 + _ = []T2{} + _ = []T2{{0}, {1}, {2}} + + _ = map[T0]T2{} + _ = map[T0]T2{{}: {}} +} + +const index2 int = 2 + +type N int +func (N) f() {} + +func map_literals() { + type M0 map[string]int + type M1 map[bool]int + type M2 map[*int]int + + _ = M0{} + _ = M0{1 /* ERROR "missing key" */ } + _ = M0{1 /* ERROR "cannot use .* in map literal" */ : 2} + _ = M0{"foo": "bar" /* ERROR "cannot use .* in map literal" */ } + _ = M0{"foo": 1, "bar": 2, "foo" /* ERROR "duplicate key" */ : 3 } + + _ = map[interface{}]int{2: 1, 2 /* ERROR "duplicate key" */ : 1} + _ = map[interface{}]int{int(2): 1, int16(2): 1} + _ = map[interface{}]int{int16(2): 1, int16 /* ERROR "duplicate key" */ (2): 1} + + type S string + + _ = map[interface{}]int{"a": 1, "a" /* ERROR "duplicate key" */ : 1} + _ = map[interface{}]int{"a": 1, S("a"): 1} + _ = map[interface{}]int{S("a"): 1, S /* ERROR "duplicate key" */ ("a"): 1} + _ = map[interface{}]int{1.0: 1, 1.0 /* ERROR "duplicate key" */: 1} + _ = map[interface{}]int{int64(-1): 1, int64 /* ERROR "duplicate key" */ (-1) : 1} + _ = map[interface{}]int{^uint64(0): 1, ^ /* ERROR "duplicate key" */ uint64(0): 1} + _ = map[interface{}]int{complex(1,2): 1, complex /* ERROR "duplicate key" */ (1,2) : 1} + + type I interface { + f() + } + + _ = map[I]int{N(0): 1, N(2): 1} + _ = map[I]int{N(2): 1, N /* ERROR "duplicate key" */ (2): 1} + + // map keys must be resolved correctly + key1 := "foo" + _ = M0{key1: 1} + _ = M0{key2: 2} + _ = M0{key3 /* ERROR "undeclared name" */ : 2} + + var value int + _ = M1{true: 1, false: 0} + _ = M2{nil: 0, &value: 1} + + // composite literal element types may be elided + type T [2]int + _ = map[int]T{0: T{3, 4}, 1: {5, 6}} + + // recursively so + _ = map[int][]T{0: {}, 1: {{}, T{1, 2}}} + + // composite literal key types may be elided + _ = map[T]int{T{3, 4}: 0, {5, 6}: 1} + + // recursively so + _ = map[[2]T]int{{}: 0, {{}}: 1, [2]T{{}}: 2, {T{1, 2}}: 3} + + // composite literal element and key types may be elided + _ = map[T]T{{}: {}, {1, 2}: T{3, 4}, T{4, 5}: {}} + _ = map[T]M0{{} : {}, T{1, 2}: M0{"foo": 0}, {1, 3}: {"foo": 1}} + + // recursively so + _ = map[[2]T][]T{{}: {}, {{}}: {{}, T{1, 2}}, [2]T{{}}: nil, {T{1, 2}}: {{}, {}}} + + // from the spec + type Point struct { x, y float32 } + _ = map[string]Point{"orig": {0, 0}} + _ = map[*Point]string{{0, 0}: "orig"} + + // issue 17954 + type T0 *struct{ s string } + type T1 *struct{ int } + type T2 T1 + + _ = map[T0]T2{} + _ = map[T0]T2{{}: {}} +} + +var key2 string = "bar" + +type I interface { + m() +} + +type I2 interface { + m(int) +} + +type T1 struct{} +type T2 struct{} + +func (T2) m(int) {} + +type mybool bool + +func type_asserts() { + var x int + _ = x /* ERROR "not an interface" */ .(int) + + var e interface{} + var ok bool + x, ok = e.(int) + _ = ok + + // ok value is of type bool + var myok mybool + _, myok = e.(int) + _ = myok + + var t I + _ = t /* ERROR "use of .* outside type switch" */ .(type) + _ = t /* ERROR "missing method m" */ .(T) + _ = t.(*T) + _ = t /* ERROR "missing method m" */ .(T1) + _ = t /* ERROR "wrong type for method m" */ .(T2) + _ = t /* STRICT "wrong type for method m" */ .(I2) // only an error in strict mode (issue 8561) + + // e doesn't statically have an m, but may have one dynamically. + _ = e.(I2) +} + +func f0() {} +func f1(x int) {} +func f2(u float32, s string) {} +func fs(s []byte) {} +func fv(x ...int) {} +func fi(x ... interface{}) {} +func (T) fm(x ...int) + +func g0() {} +func g1() int { return 0} +func g2() (u float32, s string) { return } +func gs() []byte { return nil } + +func _calls() { + var x int + var y float32 + var s []int + + f0() + _ = f0 /* ERROR "used as value" */ () + f0(g0 /* ERROR "too many arguments" */ ) + + f1(0) + f1(x) + f1(10.0) + f1() /* ERROR "too few arguments" */ + f1(x, y /* ERROR "too many arguments" */ ) + f1(s /* ERROR "cannot use .* in argument" */ ) + f1(x ... /* ERROR "cannot use ..." */ ) + f1(g0 /* ERROR "used as value" */ ()) + f1(g1()) + f1(g2 /* ERROR "cannot use g2" */ /* ERROR "too many arguments" */ ()) + + f2() /* ERROR "too few arguments" */ + f2(3.14) /* ERROR "too few arguments" */ + f2(3.14, "foo") + f2(x /* ERROR "cannot use .* in argument" */ , "foo") + f2(g0 /* ERROR "used as value" */ ()) + f2(g1 /* ERROR "cannot use .* in argument" */ ()) /* ERROR "too few arguments" */ + f2(g2()) + + fs() /* ERROR "too few arguments" */ + fs(g0 /* ERROR "used as value" */ ()) + fs(g1 /* ERROR "cannot use .* in argument" */ ()) + fs(g2 /* ERROR "cannot use .* in argument" */ /* ERROR "too many arguments" */ ()) + fs(gs()) + + fv() + fv(1, 2.0, x) + fv(s /* ERROR "cannot use .* in argument" */ ) + fv(s...) + fv(x /* ERROR "cannot use" */ ...) + fv(1, s... /* ERROR "can only use ... with matching parameter" */ ) + fv(gs /* ERROR "cannot use .* in argument" */ ()) + fv(gs /* ERROR "cannot use .* in argument" */ ()...) + + var t T + t.fm() + t.fm(1, 2.0, x) + t.fm(s /* ERROR "cannot use .* in argument" */ ) + t.fm(g1()) + t.fm(1, s... /* ERROR "can only use ... with matching parameter" */ ) + t.fm(gs /* ERROR "cannot use .* in argument" */ ()) + t.fm(gs /* ERROR "cannot use .* in argument" */ ()...) + + T.fm(t, ) + T.fm(t, 1, 2.0, x) + T.fm(t, s /* ERROR "cannot use .* in argument" */ ) + T.fm(t, g1()) + T.fm(t, 1, s... /* ERROR "can only use ... with matching parameter" */ ) + T.fm(t, gs /* ERROR "cannot use .* in argument" */ ()) + T.fm(t, gs /* ERROR "cannot use .* in argument" */ ()...) + + var i interface{ fm(x ...int) } = t + i.fm() + i.fm(1, 2.0, x) + i.fm(s /* ERROR "cannot use .* in argument" */ ) + i.fm(g1()) + i.fm(1, s... /* ERROR "can only use ... with matching parameter" */ ) + i.fm(gs /* ERROR "cannot use .* in argument" */ ()) + i.fm(gs /* ERROR "cannot use .* in argument" */ ()...) + + fi() + fi(1, 2.0, x, 3.14, "foo") + fi(g2()) + fi(0, g2) + fi(0, g2 /* ERROR "2-valued g2" */ ()) +} + +func issue6344() { + type T []interface{} + var x T + fi(x...) // ... applies also to named slices +} diff --git a/src/go/types/testdata/gotos.src b/src/go/types/testdata/gotos.src new file mode 100644 index 0000000..069a94b --- /dev/null +++ b/src/go/types/testdata/gotos.src @@ -0,0 +1,560 @@ +// Copyright 2011 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. + +// This file is a modified copy of $GOROOT/test/goto.go. + +package gotos + +var ( + i, n int + x []int + c chan int + m map[int]int + s string +) + +// goto after declaration okay +func _() { + x := 1 + goto L +L: + _ = x +} + +// goto before declaration okay +func _() { + goto L +L: + x := 1 + _ = x +} + +// goto across declaration not okay +func _() { + goto L /* ERROR "goto L jumps over variable declaration at line 36" */ + x := 1 + _ = x +L: +} + +// goto across declaration in inner scope okay +func _() { + goto L + { + x := 1 + _ = x + } +L: +} + +// goto across declaration after inner scope not okay +func _() { + goto L /* ERROR "goto L jumps over variable declaration at line 58" */ + { + x := 1 + _ = x + } + x := 1 + _ = x +L: +} + +// goto across declaration in reverse okay +func _() { +L: + x := 1 + _ = x + goto L +} + +func _() { +L: L1: + x := 1 + _ = x + goto L + goto L1 +} + +// error shows first offending variable +func _() { + goto L /* ERROR "goto L jumps over variable declaration at line 84" */ + x := 1 + _ = x + y := 1 + _ = y +L: +} + +// goto not okay even if code path is dead +func _() { + goto L /* ERROR "goto L jumps over variable declaration" */ + x := 1 + _ = x + y := 1 + _ = y + return +L: +} + +// goto into outer block okay +func _() { + { + goto L + } +L: +} + +func _() { + { + goto L + goto L1 + } +L: L1: +} + +// goto backward into outer block okay +func _() { +L: + { + goto L + } +} + +func _() { +L: L1: + { + goto L + goto L1 + } +} + +// goto into inner block not okay +func _() { + goto L /* ERROR "goto L jumps into block" */ + { + L: + } +} + +func _() { + goto L /* ERROR "goto L jumps into block" */ + goto L1 /* ERROR "goto L1 jumps into block" */ + { + L: L1: + } +} + +// goto backward into inner block still not okay +func _() { + { + L: + } + goto L /* ERROR "goto L jumps into block" */ +} + +func _() { + { + L: L1: + } + goto L /* ERROR "goto L jumps into block" */ + goto L1 /* ERROR "goto L1 jumps into block" */ +} + +// error shows first (outermost) offending block +func _() { + goto L /* ERROR "goto L jumps into block" */ + { + { + { + L: + } + } + } +} + +// error prefers block diagnostic over declaration diagnostic +func _() { + goto L /* ERROR "goto L jumps into block" */ + x := 1 + _ = x + { + L: + } +} + +// many kinds of blocks, all invalid to jump into or among, +// but valid to jump out of + +// if + +func _() { +L: + if true { + goto L + } +} + +func _() { +L: + if true { + goto L + } else { + } +} + +func _() { +L: + if false { + } else { + goto L + } +} + +func _() { + goto L /* ERROR "goto L jumps into block" */ + if true { + L: + } +} + +func _() { + goto L /* ERROR "goto L jumps into block" */ + if true { + L: + } else { + } +} + +func _() { + goto L /* ERROR "goto L jumps into block" */ + if true { + } else { + L: + } +} + +func _() { + if false { + L: + } else { + goto L /* ERROR "goto L jumps into block" */ + } +} + +func _() { + if true { + goto L /* ERROR "goto L jumps into block" */ + } else { + L: + } +} + +func _() { + if true { + goto L /* ERROR "goto L jumps into block" */ + } else if false { + L: + } +} + +func _() { + if true { + goto L /* ERROR "goto L jumps into block" */ + } else if false { + L: + } else { + } +} + +func _() { + if true { + goto L /* ERROR "goto L jumps into block" */ + } else if false { + } else { + L: + } +} + +func _() { + if true { + goto L /* ERROR "goto L jumps into block" */ + } else { + L: + } +} + +func _() { + if true { + L: + } else { + goto L /* ERROR "goto L jumps into block" */ + } +} + +// for + +func _() { + for { + goto L + } +L: +} + +func _() { + for { + goto L + L: + } +} + +func _() { + for { + L: + } + goto L /* ERROR "goto L jumps into block" */ +} + +func _() { + for { + goto L + L1: + } +L: + goto L1 /* ERROR "goto L1 jumps into block" */ +} + +func _() { + for i < n { + L: + } + goto L /* ERROR "goto L jumps into block" */ +} + +func _() { + for i = 0; i < n; i++ { + L: + } + goto L /* ERROR "goto L jumps into block" */ +} + +func _() { + for i = range x { + L: + } + goto L /* ERROR "goto L jumps into block" */ +} + +func _() { + for i = range c { + L: + } + goto L /* ERROR "goto L jumps into block" */ +} + +func _() { + for i = range m { + L: + } + goto L /* ERROR "goto L jumps into block" */ +} + +func _() { + for i = range s { + L: + } + goto L /* ERROR "goto L jumps into block" */ +} + +// switch + +func _() { +L: + switch i { + case 0: + goto L + } +} + +func _() { +L: + switch i { + case 0: + + default: + goto L + } +} + +func _() { + switch i { + case 0: + + default: + L: + goto L + } +} + +func _() { + switch i { + case 0: + + default: + goto L + L: + } +} + +func _() { + switch i { + case 0: + goto L + L: + ; + default: + } +} + +func _() { + goto L /* ERROR "goto L jumps into block" */ + switch i { + case 0: + L: + } +} + +func _() { + goto L /* ERROR "goto L jumps into block" */ + switch i { + case 0: + L: + ; + default: + } +} + +func _() { + goto L /* ERROR "goto L jumps into block" */ + switch i { + case 0: + default: + L: + } +} + +func _() { + switch i { + default: + goto L /* ERROR "goto L jumps into block" */ + case 0: + L: + } +} + +func _() { + switch i { + case 0: + L: + ; + default: + goto L /* ERROR "goto L jumps into block" */ + } +} + +// select +// different from switch. the statement has no implicit block around it. + +func _() { +L: + select { + case <-c: + goto L + } +} + +func _() { +L: + select { + case c <- 1: + + default: + goto L + } +} + +func _() { + select { + case <-c: + + default: + L: + goto L + } +} + +func _() { + select { + case c <- 1: + + default: + goto L + L: + } +} + +func _() { + select { + case <-c: + goto L + L: + ; + default: + } +} + +func _() { + goto L /* ERROR "goto L jumps into block" */ + select { + case c <- 1: + L: + } +} + +func _() { + goto L /* ERROR "goto L jumps into block" */ + select { + case c <- 1: + L: + ; + default: + } +} + +func _() { + goto L /* ERROR "goto L jumps into block" */ + select { + case <-c: + default: + L: + } +} + +func _() { + select { + default: + goto L /* ERROR "goto L jumps into block" */ + case <-c: + L: + } +} + +func _() { + select { + case <-c: + L: + ; + default: + goto L /* ERROR "goto L jumps into block" */ + } +} diff --git a/src/go/types/testdata/importC.src b/src/go/types/testdata/importC.src new file mode 100644 index 0000000..f55be2d --- /dev/null +++ b/src/go/types/testdata/importC.src @@ -0,0 +1,54 @@ +// Copyright 2015 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 importC + +import "C" +import _ /* ERROR cannot rename import "C" */ "C" +import foo /* ERROR cannot rename import "C" */ "C" +import . /* ERROR cannot rename import "C" */ "C" + +// Test cases extracted from issue #22090. + +import "unsafe" + +const _ C.int = 0xff // no error due to invalid constant type + +type T struct { + Name string + Ordinal int +} + +func _(args []T) { + var s string + for i, v := range args { + cname := C.CString(v.Name) + args[i].Ordinal = int(C.sqlite3_bind_parameter_index(s, cname)) // no error due to i not being "used" + C.free(unsafe.Pointer(cname)) + } +} + +type CType C.Type + +const _ CType = C.X // no error due to invalid constant type +const _ = C.X + +// Test cases extracted from issue #23712. + +func _() { + var a [C.ArrayLength]byte + _ = a[0] // no index out of bounds error here +} + +// Additional tests to verify fix for #23712. + +func _() { + var a [C.ArrayLength1]byte + _ = 1 / len(a) // no division by zero error here and below + _ = 1 / cap(a) + _ = uint(unsafe.Sizeof(a)) // must not be negative + + var b [C.ArrayLength2]byte + a = b // should be valid +} diff --git a/src/go/types/testdata/importdecl0a.src b/src/go/types/testdata/importdecl0a.src new file mode 100644 index 0000000..e96fca3 --- /dev/null +++ b/src/go/types/testdata/importdecl0a.src @@ -0,0 +1,53 @@ +// Copyright 2013 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 importdecl0 + +import () + +import ( + // we can have multiple blank imports (was bug) + _ "math" + _ "net/rpc" + init /* ERROR "cannot declare init" */ "fmt" + // reflect defines a type "flag" which shows up in the gc export data + "reflect" + . /* ERROR "imported but not used" */ "reflect" +) + +import "math" /* ERROR "imported but not used" */ +import m /* ERROR "imported but not used as m" */ "math" +import _ "math" + +import ( + "math/big" /* ERROR "imported but not used" */ + b /* ERROR "imported but not used" */ "math/big" + _ "math/big" +) + +import "fmt" +import f1 "fmt" +import f2 "fmt" + +// reflect.flag must not be visible in this package +type flag int +type _ reflect.flag /* ERROR "not exported" */ + +// imported package name may conflict with local objects +type reflect /* ERROR "reflect already declared" */ int + +// dot-imported exported objects may conflict with local objects +type Value /* ERROR "Value already declared through dot-import of package reflect" */ struct{} + +var _ = fmt.Println // use "fmt" + +func _() { + f1.Println() // use "fmt" +} + +func _() { + _ = func() { + f2.Println() // use "fmt" + } +} diff --git a/src/go/types/testdata/importdecl0b.src b/src/go/types/testdata/importdecl0b.src new file mode 100644 index 0000000..6844e70 --- /dev/null +++ b/src/go/types/testdata/importdecl0b.src @@ -0,0 +1,33 @@ +// Copyright 2013 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 importdecl0 + +import "math" +import m "math" + +import . "testing" // declares T in file scope +import . /* ERROR "imported but not used" */ "unsafe" +import . "fmt" // declares Println in file scope + +import ( + // TODO(gri) At the moment, 2 errors are reported because both go/parser + // and the type checker report it. Eventually, this test should not be + // done by the parser anymore. + "" /* ERROR invalid import path */ /* ERROR invalid import path */ + "a!b" /* ERROR invalid import path */ /* ERROR invalid import path */ + "abc\xffdef" /* ERROR invalid import path */ /* ERROR invalid import path */ +) + +// using "math" in this file doesn't affect its use in other files +const Pi0 = math.Pi +const Pi1 = m.Pi + +type _ T // use "testing" + +func _() func() interface{} { + return func() interface{} { + return Println // use "fmt" + } +} diff --git a/src/go/types/testdata/importdecl1a.src b/src/go/types/testdata/importdecl1a.src new file mode 100644 index 0000000..d377c01 --- /dev/null +++ b/src/go/types/testdata/importdecl1a.src @@ -0,0 +1,22 @@ +// Copyright 2014 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. + +// Test case for issue 8969. + +package importdecl1 + +import "go/ast" +import . "unsafe" + +var _ Pointer // use dot-imported package unsafe + +// Test cases for issue 23914. + +type A interface { + // Methods m1, m2 must be type-checked in this file scope + // even when embedded in an interface in a different + // file of the same package. + m1() ast.Node + m2() Pointer +} diff --git a/src/go/types/testdata/importdecl1b.src b/src/go/types/testdata/importdecl1b.src new file mode 100644 index 0000000..ee70bbd --- /dev/null +++ b/src/go/types/testdata/importdecl1b.src @@ -0,0 +1,11 @@ +// Copyright 2014 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 importdecl1 + +import . /* ERROR "imported but not used" */ "unsafe" + +type B interface { + A +} diff --git a/src/go/types/testdata/init0.src b/src/go/types/testdata/init0.src new file mode 100644 index 0000000..6e8746a --- /dev/null +++ b/src/go/types/testdata/init0.src @@ -0,0 +1,106 @@ +// Copyright 2013 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. + +// initialization cycles + +package init0 + +// initialization cycles (we don't know the types) +const ( + s0 /* ERROR initialization cycle */ = s0 + + x0 /* ERROR initialization cycle */ = y0 + y0 = x0 + + a0 = b0 + b0 /* ERROR initialization cycle */ = c0 + c0 = d0 + d0 = b0 +) + +var ( + s1 /* ERROR initialization cycle */ = s1 + + x1 /* ERROR initialization cycle */ = y1 + y1 = x1 + + a1 = b1 + b1 /* ERROR initialization cycle */ = c1 + c1 = d1 + d1 = b1 +) + +// initialization cycles (we know the types) +const ( + s2 /* ERROR initialization cycle */ int = s2 + + x2 /* ERROR initialization cycle */ int = y2 + y2 = x2 + + a2 = b2 + b2 /* ERROR initialization cycle */ int = c2 + c2 = d2 + d2 = b2 +) + +var ( + s3 /* ERROR initialization cycle */ int = s3 + + x3 /* ERROR initialization cycle */ int = y3 + y3 = x3 + + a3 = b3 + b3 /* ERROR initialization cycle */ int = c3 + c3 = d3 + d3 = b3 +) + +// cycles via struct fields + +type S1 struct { + f int +} +const cx3 S1 /* ERROR invalid constant type */ = S1{cx3.f} +var vx3 /* ERROR initialization cycle */ S1 = S1{vx3.f} + +// cycles via functions + +var x4 = x5 +var x5 /* ERROR initialization cycle */ = f1() +func f1() int { return x5*10 } + +var x6, x7 /* ERROR initialization cycle */ = f2() +var x8 = x7 +func f2() (int, int) { return f3() + f3(), 0 } +func f3() int { return x8 } + +// cycles via function literals + +var x9 /* ERROR initialization cycle */ = func() int { return x9 }() + +var x10 /* ERROR initialization cycle */ = f4() + +func f4() int { + _ = func() { + _ = x10 + } + return 0 +} + +// cycles via method expressions + +type T1 struct{} + +func (T1) m() bool { _ = x11; return false } + +var x11 /* ERROR initialization cycle */ = T1.m(T1{}) + +// cycles via method values + +type T2 struct{} + +func (T2) m() bool { _ = x12; return false } + +var t1 T2 +var x12 /* ERROR initialization cycle */ = t1.m diff --git a/src/go/types/testdata/init1.src b/src/go/types/testdata/init1.src new file mode 100644 index 0000000..39ca314 --- /dev/null +++ b/src/go/types/testdata/init1.src @@ -0,0 +1,97 @@ +// Copyright 2013 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. + +// initialization cycles + +package init1 + +// issue 6683 (marked as WorkingAsIntended) + +type T0 struct{} + +func (T0) m() int { return y0 } + +var x0 = T0{} + +var y0 /* ERROR initialization cycle */ = x0.m() + +type T1 struct{} + +func (T1) m() int { return y1 } + +var x1 interface { + m() int +} = T1{} + +var y1 = x1.m() // no cycle reported, x1 is of interface type + +// issue 6703 (modified) + +var x2 /* ERROR initialization cycle */ = T2.m + +var y2 = x2 + +type T2 struct{} + +func (T2) m() int { + _ = y2 + return 0 +} + +var x3 /* ERROR initialization cycle */ = T3.m(T3{}) // <<<< added (T3{}) + +var y3 = x3 + +type T3 struct{} + +func (T3) m() int { + _ = y3 + return 0 +} + +var x4 /* ERROR initialization cycle */ = T4{}.m // <<<< added {} + +var y4 = x4 + +type T4 struct{} + +func (T4) m() int { + _ = y4 + return 0 +} + +var x5 /* ERROR initialization cycle */ = T5{}.m() // <<<< added () + +var y5 = x5 + +type T5 struct{} + +func (T5) m() int { + _ = y5 + return 0 +} + +// issue 4847 +// simplified test case + +var x6 = f6 +var y6 /* ERROR initialization cycle */ = f6 +func f6() { _ = y6 } + +// full test case + +type ( + E int + S int +) + +type matcher func(s *S) E + +func matchList(s *S) E { return matcher(matchAnyFn)(s) } + +var foo = matcher(matchList) + +var matchAny /* ERROR initialization cycle */ = matcher(matchList) + +func matchAnyFn(s *S) (err E) { return matchAny(s) } \ No newline at end of file diff --git a/src/go/types/testdata/init2.src b/src/go/types/testdata/init2.src new file mode 100644 index 0000000..614db6c --- /dev/null +++ b/src/go/types/testdata/init2.src @@ -0,0 +1,139 @@ +// Copyright 2014 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. + +// initialization cycles + +package init2 + +// cycles through functions + +func f1() int { _ = x1; return 0 } +var x1 /* ERROR initialization cycle */ = f1 + +func f2() int { _ = x2; return 0 } +var x2 /* ERROR initialization cycle */ = f2() + +// cycles through method expressions + +type T3 int +func (T3) m() int { _ = x3; return 0 } +var x3 /* ERROR initialization cycle */ = T3.m + +type T4 int +func (T4) m() int { _ = x4; return 0 } +var x4 /* ERROR initialization cycle */ = T4.m(0) + +type T3p int +func (*T3p) m() int { _ = x3p; return 0 } +var x3p /* ERROR initialization cycle */ = (*T3p).m + +type T4p int +func (*T4p) m() int { _ = x4p; return 0 } +var x4p /* ERROR initialization cycle */ = (*T4p).m(nil) + +// cycles through method expressions of embedded methods + +type T5 struct { E5 } +type E5 int +func (E5) m() int { _ = x5; return 0 } +var x5 /* ERROR initialization cycle */ = T5.m + +type T6 struct { E6 } +type E6 int +func (E6) m() int { _ = x6; return 0 } +var x6 /* ERROR initialization cycle */ = T6.m(T6{0}) + +type T5p struct { E5p } +type E5p int +func (*E5p) m() int { _ = x5p; return 0 } +var x5p /* ERROR initialization cycle */ = (*T5p).m + +type T6p struct { E6p } +type E6p int +func (*E6p) m() int { _ = x6p; return 0 } +var x6p /* ERROR initialization cycle */ = (*T6p).m(nil) + +// cycles through method values + +type T7 int +func (T7) m() int { _ = x7; return 0 } +var x7 /* ERROR initialization cycle */ = T7(0).m + +type T8 int +func (T8) m() int { _ = x8; return 0 } +var x8 /* ERROR initialization cycle */ = T8(0).m() + +type T7p int +func (*T7p) m() int { _ = x7p; return 0 } +var x7p /* ERROR initialization cycle */ = new(T7p).m + +type T8p int +func (*T8p) m() int { _ = x8p; return 0 } +var x8p /* ERROR initialization cycle */ = new(T8p).m() + +type T7v int +func (T7v) m() int { _ = x7v; return 0 } +var x7var T7v +var x7v /* ERROR initialization cycle */ = x7var.m + +type T8v int +func (T8v) m() int { _ = x8v; return 0 } +var x8var T8v +var x8v /* ERROR initialization cycle */ = x8var.m() + +type T7pv int +func (*T7pv) m() int { _ = x7pv; return 0 } +var x7pvar *T7pv +var x7pv /* ERROR initialization cycle */ = x7pvar.m + +type T8pv int +func (*T8pv) m() int { _ = x8pv; return 0 } +var x8pvar *T8pv +var x8pv /* ERROR initialization cycle */ = x8pvar.m() + +// cycles through method values of embedded methods + +type T9 struct { E9 } +type E9 int +func (E9) m() int { _ = x9; return 0 } +var x9 /* ERROR initialization cycle */ = T9{0}.m + +type T10 struct { E10 } +type E10 int +func (E10) m() int { _ = x10; return 0 } +var x10 /* ERROR initialization cycle */ = T10{0}.m() + +type T9p struct { E9p } +type E9p int +func (*E9p) m() int { _ = x9p; return 0 } +var x9p /* ERROR initialization cycle */ = new(T9p).m + +type T10p struct { E10p } +type E10p int +func (*E10p) m() int { _ = x10p; return 0 } +var x10p /* ERROR initialization cycle */ = new(T10p).m() + +type T9v struct { E9v } +type E9v int +func (E9v) m() int { _ = x9v; return 0 } +var x9var T9v +var x9v /* ERROR initialization cycle */ = x9var.m + +type T10v struct { E10v } +type E10v int +func (E10v) m() int { _ = x10v; return 0 } +var x10var T10v +var x10v /* ERROR initialization cycle */ = x10var.m() + +type T9pv struct { E9pv } +type E9pv int +func (*E9pv) m() int { _ = x9pv; return 0 } +var x9pvar *T9pv +var x9pv /* ERROR initialization cycle */ = x9pvar.m + +type T10pv struct { E10pv } +type E10pv int +func (*E10pv) m() int { _ = x10pv; return 0 } +var x10pvar *T10pv +var x10pv /* ERROR initialization cycle */ = x10pvar.m() diff --git a/src/go/types/testdata/issue25008a.src b/src/go/types/testdata/issue25008a.src new file mode 100644 index 0000000..cf71ca1 --- /dev/null +++ b/src/go/types/testdata/issue25008a.src @@ -0,0 +1,15 @@ +// Copyright 2018 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 p + +import "io" + +type A interface { + io.Reader +} + +func f(a A) { + a.Read(nil) +} diff --git a/src/go/types/testdata/issue25008b.src b/src/go/types/testdata/issue25008b.src new file mode 100644 index 0000000..f132b7f --- /dev/null +++ b/src/go/types/testdata/issue25008b.src @@ -0,0 +1,9 @@ +// Copyright 2018 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 p + +type B interface { + A +} diff --git a/src/go/types/testdata/issues.src b/src/go/types/testdata/issues.src new file mode 100644 index 0000000..e0c5d7a --- /dev/null +++ b/src/go/types/testdata/issues.src @@ -0,0 +1,365 @@ +// Copyright 2014 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 issues + +import ( + "fmt" + syn "cmd/compile/internal/syntax" + t1 "text/template" + t2 "html/template" +) + +func issue7035() { + type T struct{ X int } + _ = func() { + fmt.Println() // must refer to imported fmt rather than the fmt below + } + fmt := new(T) + _ = fmt.X +} + +func issue8066() { + const ( + _ = float32(340282356779733661637539395458142568447) + _ = float32(340282356779733661637539395458142568448 /* ERROR cannot convert */ ) + ) +} + +// Check that a missing identifier doesn't lead to a spurious error cascade. +func issue8799a() { + x, ok := missing /* ERROR undeclared */ () + _ = !ok + _ = x +} + +func issue8799b(x int, ok bool) { + x, ok = missing /* ERROR undeclared */ () + _ = !ok + _ = x +} + +func issue9182() { + type Point C /* ERROR undeclared */ .Point + // no error for composite literal based on unknown type + _ = Point{x: 1, y: 2} +} + +func f0() (a []int) { return } +func f1() (a []int, b int) { return } +func f2() (a, b []int) { return } + +func append_([]int, ...int) {} + +func issue9473(a []int, b ...int) { + // variadic builtin function + _ = append(f0()) + _ = append(f0(), f0()...) + _ = append(f1()) + _ = append(f2 /* ERROR cannot use .* in argument */ ()) + _ = append(f2()... /* ERROR cannot use ... */ ) + _ = append(f0(), f1 /* ERROR 2-valued f1 */ ()) + _ = append(f0(), f2 /* ERROR 2-valued f2 */ ()) + _ = append(f0(), f1 /* ERROR 2-valued f1 */ ()...) + _ = append(f0(), f2 /* ERROR 2-valued f2 */ ()...) + + // variadic user-defined function + append_(f0()) + append_(f0(), f0()...) + append_(f1()) + append_(f2 /* ERROR cannot use .* in argument */ ()) + append_(f2()... /* ERROR cannot use ... */ ) + append_(f0(), f1 /* ERROR 2-valued f1 */ ()) + append_(f0(), f2 /* ERROR 2-valued f2 */ ()) + append_(f0(), f1 /* ERROR 2-valued f1 */ ()...) + append_(f0(), f2 /* ERROR 2-valued f2 */ ()...) +} + +// Check that embedding a non-interface type in an interface results in a good error message. +func issue10979() { + type _ interface { + int /* ERROR int is not an interface */ + } + type T struct{} + type _ interface { + T /* ERROR T is not an interface */ + } + type _ interface { + nosuchtype /* ERROR undeclared name: nosuchtype */ + } + type _ interface { + fmt.Nosuchtype /* ERROR Nosuchtype not declared by package fmt */ + } + type _ interface { + nosuchpkg /* ERROR undeclared name: nosuchpkg */ .Nosuchtype + } + type I interface { + I.m /* ERROR no field or method m */ + m() + } +} + +// issue11347 +// These should not crash. +var a1, b1 /* ERROR cycle */ , c1 /* ERROR cycle */ b1 = 0 > 0<<""[""[c1]]>c1 +var a2, b2 /* ERROR cycle */ = 0 /* ERROR cannot initialize */ /* ERROR cannot initialize */ > 0<<""[b2] +var a3, b3 /* ERROR cycle */ = int /* ERROR cannot initialize */ /* ERROR cannot initialize */ (1<<""[b3]) + +// issue10260 +// Check that error messages explain reason for interface assignment failures. +type ( + I0 interface{} + I1 interface{ foo() } + I2 interface{ foo(x int) } + T0 struct{} + T1 struct{} + T2 struct{} +) + +func (*T1) foo() {} +func (*T2) foo(x int) {} + +func issue10260() { + var ( + i0 I0 + i1 I1 + i2 I2 + t0 *T0 + t1 *T1 + t2 *T2 + ) + + var x I1 + x = T1 /* ERROR cannot use .*: missing method foo \(foo has pointer receiver\) */ {} + _ = x /* ERROR .* cannot have dynamic type T1 \(missing method foo \(foo has pointer receiver\)\) */ .(T1) + + T1{}.foo /* ERROR cannot call pointer method foo on T1 */ () + x.Foo /* ERROR "x.Foo undefined \(type I1 has no field or method Foo, but does have foo\)" */ () + + _ = i2 /* ERROR i2 .* cannot have dynamic type \*T1 \(wrong type for method foo \(have func\(\), want func\(x int\)\)\) */ .(*T1) + + i1 = i0 /* ERROR cannot use .* missing method foo */ + i1 = t0 /* ERROR cannot use .* missing method foo */ + i1 = i2 /* ERROR cannot use .* wrong type for method foo */ + i1 = t2 /* ERROR cannot use .* wrong type for method foo */ + i2 = i1 /* ERROR cannot use .* wrong type for method foo */ + i2 = t1 /* ERROR cannot use .* wrong type for method foo */ + + _ = func() I1 { return i0 /* ERROR cannot use .* missing method foo */ } + _ = func() I1 { return t0 /* ERROR cannot use .* missing method foo */ } + _ = func() I1 { return i2 /* ERROR cannot use .* wrong type for method foo */ } + _ = func() I1 { return t2 /* ERROR cannot use .* wrong type for method foo */ } + _ = func() I2 { return i1 /* ERROR cannot use .* wrong type for method foo */ } + _ = func() I2 { return t1 /* ERROR cannot use .* wrong type for method foo */ } + + // a few more - less exhaustive now + + f := func(I1, I2){} + f(i0 /* ERROR cannot use .* missing method foo */ , i1 /* ERROR cannot use .* wrong type for method foo \(have func\(\), want func\(x int\)\) */ ) + + _ = [...]I1{i0 /* ERROR cannot use .* missing method foo */ } + _ = [...]I1{i2 /* ERROR cannot use .* wrong type for method foo */ } + _ = []I1{i0 /* ERROR cannot use .* missing method foo */ } + _ = []I1{i2 /* ERROR cannot use .* wrong type for method foo */ } + _ = map[int]I1{0: i0 /* ERROR cannot use .* missing method foo */ } + _ = map[int]I1{0: i2 /* ERROR cannot use .* wrong type for method foo */ } + + make(chan I1) <- i0 /* ERROR cannot use .* in send: missing method foo */ + make(chan I1) <- i2 /* ERROR cannot use .* in send: wrong type for method foo */ +} + +// Check that constants representable as integers are in integer form +// before being used in operations that are only defined on integers. +func issue14229() { + // from the issue + const _ = int64(-1<<63) % 1e6 + + // related + const ( + a int = 3 + b = 4.0 + _ = a / b + _ = a % b + _ = b / a + _ = b % a + ) +} + +// Check that in a n:1 variable declaration with type and initialization +// expression the type is distributed to all variables of the lhs before +// the initialization expression assignment is checked. +func issue15755() { + // from issue + var i interface{} + type b bool + var x, y b = i.(b) + _ = x == y + + // related: we should see an error since the result of f1 is ([]int, int) + var u, v []int = f1 /* ERROR cannot use f1 */ () + _ = u + _ = v +} + +// Test that we don't get "declared but not used" +// errors in the context of invalid/C objects. +func issue20358() { + var F C /* ERROR "undeclared" */ .F + var A C /* ERROR "undeclared" */ .A + var S C /* ERROR "undeclared" */ .S + type T C /* ERROR "undeclared" */ .T + type P C /* ERROR "undeclared" */ .P + + // these variables must be "used" even though + // the LHS expressions/types below in which + // context they are used are unknown/invalid + var f, a, s1, s2, s3, t, p int + + _ = F(f) + _ = A[a] + _ = S[s1:s2:s3] + _ = T{t} + _ = P{f: p} +} + +// Test that we don't declare lhs variables in short variable +// declarations before we type-check function literals on the +// rhs. +func issue24026() { + f := func() int { f(0) /* must refer to outer f */; return 0 } + _ = f + + _ = func() { + f := func() { _ = f() /* must refer to outer f */ } + _ = f + } + + // b and c must not be visible inside function literal + a := 0 + a, b, c := func() (int, int, int) { + return a, b /* ERROR undeclared */ , c /* ERROR undeclared */ + }() + _, _ = b, c +} + +func f(int) {} // for issue24026 + +// Test that we don't report a "missing return statement" error +// (due to incorrect context when type-checking interfaces). +func issue24140(x interface{}) int { + switch x.(type) { + case interface{}: + return 0 + default: + panic(0) + } +} + +// Test that we don't crash when the 'if' condition is missing. +func issue25438() { + if { /* ERROR missing condition */ } + if x := 0; /* ERROR missing condition */ { _ = x } + if + { /* ERROR missing condition */ } +} + +// Test that we can embed alias type names in interfaces. +type issue25301 interface { + E +} + +type E = interface { + m() +} + +// Test case from issue. +// cmd/compile reports a cycle as well. +type issue25301b /* ERROR cycle */ = interface { + m() interface{ issue25301b } +} + +type issue25301c interface { + notE // ERROR struct\{\} is not an interface +} + +type notE = struct{} + +// Test that method declarations don't introduce artificial cycles +// (issue #26124). +const CC TT = 1 +type TT int +func (TT) MM() [CC]TT + +// Reduced test case from issue #26124. +const preloadLimit LNumber = 128 +type LNumber float64 +func (LNumber) assertFunction() *LFunction +type LFunction struct { + GFunction LGFunction +} +type LGFunction func(*LState) +type LState struct { + reg *registry +} +type registry struct { + alloc *allocator +} +type allocator struct { + _ [int(preloadLimit)]int +} + +// Test that we don't crash when type-checking composite literals +// containing errors in the type. +var issue27346 = [][n /* ERROR undeclared */ ]int{ + 0: {}, +} + +var issue22467 = map[int][... /* ERROR invalid use of ... */ ]int{0: {}} + +// Test that invalid use of ... in parameter lists is recognized +// (issue #28281). +func issue28281a(int, int, ...int) +func issue28281b(a, b int, c ...int) +func issue28281c(a, b, c ... /* ERROR can only use ... with final parameter */ int) +func issue28281d(... /* ERROR can only use ... with final parameter */ int, int) +func issue28281e(a, b, c ... /* ERROR can only use ... with final parameter */ int, d int) +func issue28281f(... /* ERROR can only use ... with final parameter */ int, ... /* ERROR can only use ... with final parameter */ int, int) +func (... /* ERROR expected type */ TT) f() +func issue28281g() (... /* ERROR expected type */ TT) + +// Issue #26234: Make various field/method lookup errors easier to read by matching cmd/compile's output +func issue26234a(f *syn.File) { + // The error message below should refer to the actual package name (syntax) + // not the local package name (syn). + f.foo /* ERROR f.foo undefined \(type \*syntax.File has no field or method foo\) */ +} + +type T struct { + x int + E1 + E2 +} + +type E1 struct{ f int } +type E2 struct{ f int } + +func issue26234b(x T) { + _ = x.f /* ERROR ambiguous selector x.f */ +} + +func issue26234c() { + T.x /* ERROR T.x undefined \(type T has no method x\) */ () +} + +func issue35895() { + // T is defined in this package, don't qualify its name with the package name. + var _ T = 0 // ERROR cannot use 0 \(untyped int constant\) as T + + // There is only one package with name syntax imported, only use the (global) package name in error messages. + var _ *syn.File = 0 // ERROR cannot use 0 \(untyped int constant\) as \*syntax.File + + // Because both t1 and t2 have the same global package name (template), + // qualify packages with full path name in this case. + var _ t1.Template = t2 /* ERROR cannot use .* \(value of type "html/template".Template\) as "text/template".Template */ .Template{} +} diff --git a/src/go/types/testdata/labels.src b/src/go/types/testdata/labels.src new file mode 100644 index 0000000..9f42406 --- /dev/null +++ b/src/go/types/testdata/labels.src @@ -0,0 +1,207 @@ +// Copyright 2011 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. + +// This file is a modified concatenation of the files +// $GOROOT/test/label.go and $GOROOT/test/label1.go. + +package labels + +var x int + +func f0() { +L1 /* ERROR "label L1 declared but not used" */ : + for { + } +L2 /* ERROR "label L2 declared but not used" */ : + select { + } +L3 /* ERROR "label L3 declared but not used" */ : + switch { + } +L4 /* ERROR "label L4 declared but not used" */ : + if true { + } +L5 /* ERROR "label L5 declared but not used" */ : + f0() +L6: + f0() +L6 /* ERROR "label L6 already declared" */ : + f0() + if x == 20 { + goto L6 + } + +L7: + for { + break L7 + break L8 /* ERROR "invalid break label L8" */ + } + +// A label must be directly associated with a switch, select, or +// for statement; it cannot be the label of a labeled statement. + +L7a /* ERROR "declared but not used" */ : L7b: + for { + break L7a /* ERROR "invalid break label L7a" */ + continue L7a /* ERROR "invalid continue label L7a" */ + continue L7b + } + +L8: + for { + if x == 21 { + continue L8 + continue L7 /* ERROR "invalid continue label L7" */ + } + } + +L9: + switch { + case true: + break L9 + defalt /* ERROR "label defalt declared but not used" */ : + } + +L10: + select { + default: + break L10 + break L9 /* ERROR "invalid break label L9" */ + } + + goto L10a +L10a: L10b: + select { + default: + break L10a /* ERROR "invalid break label L10a" */ + break L10b + continue L10b /* ERROR "invalid continue label L10b" */ + } +} + +func f1() { +L1: + for { + if x == 0 { + break L1 + } + if x == 1 { + continue L1 + } + goto L1 + } + +L2: + select { + default: + if x == 0 { + break L2 + } + if x == 1 { + continue L2 /* ERROR "invalid continue label L2" */ + } + goto L2 + } + +L3: + switch { + case x > 10: + if x == 11 { + break L3 + } + if x == 12 { + continue L3 /* ERROR "invalid continue label L3" */ + } + goto L3 + } + +L4: + if true { + if x == 13 { + break L4 /* ERROR "invalid break label L4" */ + } + if x == 14 { + continue L4 /* ERROR "invalid continue label L4" */ + } + if x == 15 { + goto L4 + } + } + +L5: + f1() + if x == 16 { + break L5 /* ERROR "invalid break label L5" */ + } + if x == 17 { + continue L5 /* ERROR "invalid continue label L5" */ + } + if x == 18 { + goto L5 + } + + for { + if x == 19 { + break L1 /* ERROR "invalid break label L1" */ + } + if x == 20 { + continue L1 /* ERROR "invalid continue label L1" */ + } + if x == 21 { + goto L1 + } + } +} + +// Additional tests not in the original files. + +func f2() { +L1 /* ERROR "label L1 declared but not used" */ : + if x == 0 { + for { + continue L1 /* ERROR "invalid continue label L1" */ + } + } +} + +func f3() { +L1: +L2: +L3: + for { + break L1 /* ERROR "invalid break label L1" */ + break L2 /* ERROR "invalid break label L2" */ + break L3 + continue L1 /* ERROR "invalid continue label L1" */ + continue L2 /* ERROR "invalid continue label L2" */ + continue L3 + goto L1 + goto L2 + goto L3 + } +} + +// Blank labels are never declared. + +func f4() { +_: +_: // multiple blank labels are ok + goto _ /* ERROR "label _ not declared" */ +} + +func f5() { +_: + for { + break _ /* ERROR "invalid break label _" */ + continue _ /* ERROR "invalid continue label _" */ + } +} + +func f6() { +_: + switch { + default: + break _ /* ERROR "invalid break label _" */ + } +} diff --git a/src/go/types/testdata/literals.src b/src/go/types/testdata/literals.src new file mode 100644 index 0000000..494a465 --- /dev/null +++ b/src/go/types/testdata/literals.src @@ -0,0 +1,111 @@ +// Copyright 2019 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. + +// This file tests various representations of literals +// and compares them with literals or constant expressions +// of equal values. + +package literals + +func _() { + // 0-octals + assert(0_123 == 0123) + assert(0123_456 == 0123456) + + // decimals + assert(1_234 == 1234) + assert(1_234_567 == 1234567) + + // hexadecimals + assert(0X_0 == 0) + assert(0X_1234 == 0x1234) + assert(0X_CAFE_f00d == 0xcafef00d) + + // octals + assert(0o0 == 0) + assert(0o1234 == 01234) + assert(0o01234567 == 01234567) + + assert(0O0 == 0) + assert(0O1234 == 01234) + assert(0O01234567 == 01234567) + + assert(0o_0 == 0) + assert(0o_1234 == 01234) + assert(0o0123_4567 == 01234567) + + assert(0O_0 == 0) + assert(0O_1234 == 01234) + assert(0O0123_4567 == 01234567) + + // binaries + assert(0b0 == 0) + assert(0b1011 == 0xb) + assert(0b00101101 == 0x2d) + + assert(0B0 == 0) + assert(0B1011 == 0xb) + assert(0B00101101 == 0x2d) + + assert(0b_0 == 0) + assert(0b10_11 == 0xb) + assert(0b_0010_1101 == 0x2d) + + // decimal floats + assert(1_2_3. == 123.) + assert(0_123. == 123.) + + assert(0_0e0 == 0.) + assert(1_2_3e0 == 123.) + assert(0_123e0 == 123.) + + assert(0e-0_0 == 0.) + assert(1_2_3E+0 == 123.) + assert(0123E1_2_3 == 123e123) + + assert(0.e+1 == 0.) + assert(123.E-1_0 == 123e-10) + assert(01_23.e123 == 123e123) + + assert(.0e-1 == .0) + assert(.123E+10 == .123e10) + assert(.0123E123 == .0123e123) + + assert(1_2_3.123 == 123.123) + assert(0123.01_23 == 123.0123) + + // hexadecimal floats + assert(0x0.p+0 == 0.) + assert(0Xdeadcafe.p-10 == 0xdeadcafe/1024.0) + assert(0x1234.P84 == 0x1234000000000000000000000) + + assert(0x.1p-0 == 1./16) + assert(0X.deadcafep4 == 1.0*0xdeadcafe/0x10000000) + assert(0x.1234P+12 == 1.0*0x1234/0x10) + + assert(0x0p0 == 0.) + assert(0Xdeadcafep+1 == 0x1bd5b95fc) + assert(0x1234P-10 == 0x1234/1024.0) + + assert(0x0.0p0 == 0.) + assert(0Xdead.cafep+1 == 1.0*0x1bd5b95fc/0x10000) + assert(0x12.34P-10 == 1.0*0x1234/0x40000) + + assert(0Xdead_cafep+1 == 0xdeadcafep+1) + assert(0x_1234P-10 == 0x1234p-10) + + assert(0X_dead_cafe.p-10 == 0xdeadcafe.p-10) + assert(0x12_34.P1_2_3 == 0x1234.p123) + + assert(1_234i == 1234i) + assert(1_234_567i == 1234567i) + + assert(0.i == 0i) + assert(123.i == 123i) + assert(0123.i == 123i) + + assert(0.e+1i == 0i) + assert(123.E-1_0i == 123e-10i) + assert(01_23.e123i == 123e123i) +} diff --git a/src/go/types/testdata/methodsets.src b/src/go/types/testdata/methodsets.src new file mode 100644 index 0000000..9fb10de --- /dev/null +++ b/src/go/types/testdata/methodsets.src @@ -0,0 +1,214 @@ +// Copyright 2013 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 methodsets + +type T0 struct {} + +func (T0) v0() {} +func (*T0) p0() {} + +type T1 struct {} // like T0 with different method names + +func (T1) v1() {} +func (*T1) p1() {} + +type T2 interface { + v2() + p2() +} + +type T3 struct { + T0 + *T1 + T2 +} + +// Method expressions +func _() { + var ( + _ func(T0) = T0.v0 + _ = T0.p0 /* ERROR "cannot call pointer method p0 on T0" */ + + _ func (*T0) = (*T0).v0 + _ func (*T0) = (*T0).p0 + + // T1 is like T0 + + _ func(T2) = T2.v2 + _ func(T2) = T2.p2 + + _ func(T3) = T3.v0 + _ func(T3) = T3.p0 /* ERROR "cannot call pointer method p0 on T3" */ + _ func(T3) = T3.v1 + _ func(T3) = T3.p1 + _ func(T3) = T3.v2 + _ func(T3) = T3.p2 + + _ func(*T3) = (*T3).v0 + _ func(*T3) = (*T3).p0 + _ func(*T3) = (*T3).v1 + _ func(*T3) = (*T3).p1 + _ func(*T3) = (*T3).v2 + _ func(*T3) = (*T3).p2 + ) +} + +// Method values with addressable receivers +func _() { + var ( + v0 T0 + _ func() = v0.v0 + _ func() = v0.p0 + ) + + var ( + p0 *T0 + _ func() = p0.v0 + _ func() = p0.p0 + ) + + // T1 is like T0 + + var ( + v2 T2 + _ func() = v2.v2 + _ func() = v2.p2 + ) + + var ( + v4 T3 + _ func() = v4.v0 + _ func() = v4.p0 + _ func() = v4.v1 + _ func() = v4.p1 + _ func() = v4.v2 + _ func() = v4.p2 + ) + + var ( + p4 *T3 + _ func() = p4.v0 + _ func() = p4.p0 + _ func() = p4.v1 + _ func() = p4.p1 + _ func() = p4.v2 + _ func() = p4.p2 + ) +} + +// Method calls with addressable receivers +func _() { + var v0 T0 + v0.v0() + v0.p0() + + var p0 *T0 + p0.v0() + p0.p0() + + // T1 is like T0 + + var v2 T2 + v2.v2() + v2.p2() + + var v4 T3 + v4.v0() + v4.p0() + v4.v1() + v4.p1() + v4.v2() + v4.p2() + + var p4 *T3 + p4.v0() + p4.p0() + p4.v1() + p4.p1() + p4.v2() + p4.p2() +} + +// Method values with value receivers +func _() { + var ( + _ func() = T0{}.v0 + _ func() = T0{}.p0 /* ERROR "cannot call pointer method p0 on T0" */ + + _ func() = (&T0{}).v0 + _ func() = (&T0{}).p0 + + // T1 is like T0 + + // no values for T2 + + _ func() = T3{}.v0 + _ func() = T3{}.p0 /* ERROR "cannot call pointer method p0 on T3" */ + _ func() = T3{}.v1 + _ func() = T3{}.p1 + _ func() = T3{}.v2 + _ func() = T3{}.p2 + + _ func() = (&T3{}).v0 + _ func() = (&T3{}).p0 + _ func() = (&T3{}).v1 + _ func() = (&T3{}).p1 + _ func() = (&T3{}).v2 + _ func() = (&T3{}).p2 + ) +} + +// Method calls with value receivers +func _() { + T0{}.v0() + T0{}.p0 /* ERROR "cannot call pointer method p0 on T0" */ () + + (&T0{}).v0() + (&T0{}).p0() + + // T1 is like T0 + + // no values for T2 + + T3{}.v0() + T3{}.p0 /* ERROR "cannot call pointer method p0 on T3" */ () + T3{}.v1() + T3{}.p1() + T3{}.v2() + T3{}.p2() + + (&T3{}).v0() + (&T3{}).p0() + (&T3{}).v1() + (&T3{}).p1() + (&T3{}).v2() + (&T3{}).p2() +} + +// *T has no methods if T is an interface type +func issue5918() { + var ( + err error + _ = err.Error() + _ func() string = err.Error + _ func(error) string = error.Error + + perr = &err + _ = perr.Error /* ERROR "no field or method" */ () + _ func() string = perr.Error /* ERROR "no field or method" */ + _ func(*error) string = (*error).Error /* ERROR "no field or method" */ + ) + + type T *interface{ m() int } + var ( + x T + _ = (*x).m() + _ = (*x).m + + _ = x.m /* ERROR "no field or method" */ () + _ = x.m /* ERROR "no field or method" */ + _ = T.m /* ERROR "no field or method" */ + ) +} diff --git a/src/go/types/testdata/shifts.src b/src/go/types/testdata/shifts.src new file mode 100644 index 0000000..c9a38ae --- /dev/null +++ b/src/go/types/testdata/shifts.src @@ -0,0 +1,395 @@ +// Copyright 2013 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 shifts + +func shifts0() { + // basic constant shifts + const ( + s = 10 + _ = 0<<0 + _ = 1<> s) + _, _, _ = u, v, x +} + +func shifts4() { + // shifts in comparisons w/ untyped operands + var s uint + + _ = 1<> 1.1 /* ERROR "truncated to uint" */ // example from issue 11325 + _ = 0 >> 1.1 /* ERROR "truncated to uint" */ + _ = 0 << 1.1 /* ERROR "truncated to uint" */ + _ = 0 >> 1. + _ = 1 >> 1.1 /* ERROR "truncated to uint" */ + _ = 1 >> 1. + _ = 1. >> 1 + _ = 1. >> 1. + _ = 1.1 /* ERROR "must be integer" */ >> 1 +} + +func issue11594() { + var _ = complex64 /* ERROR "must be integer" */ (1) << 2 // example from issue 11594 + _ = float32 /* ERROR "must be integer" */ (0) << 1 + _ = float64 /* ERROR "must be integer" */ (0) >> 2 + _ = complex64 /* ERROR "must be integer" */ (0) << 3 + _ = complex64 /* ERROR "must be integer" */ (0) >> 4 +} + +func issue21727() { + var s uint + var a = make([]int, 1< len(fields) { + panic("more tags than fields") + } + return &Struct{fields: fields, tags: tags} +} + +// NumFields returns the number of fields in the struct (including blank and embedded fields). +func (s *Struct) NumFields() int { return len(s.fields) } + +// Field returns the i'th field for 0 <= i < NumFields(). +func (s *Struct) Field(i int) *Var { return s.fields[i] } + +// Tag returns the i'th field tag for 0 <= i < NumFields(). +func (s *Struct) Tag(i int) string { + if i < len(s.tags) { + return s.tags[i] + } + return "" +} + +// A Pointer represents a pointer type. +type Pointer struct { + base Type // element type +} + +// NewPointer returns a new pointer type for the given element (base) type. +func NewPointer(elem Type) *Pointer { return &Pointer{base: elem} } + +// Elem returns the element type for the given pointer p. +func (p *Pointer) Elem() Type { return p.base } + +// A Tuple represents an ordered list of variables; a nil *Tuple is a valid (empty) tuple. +// Tuples are used as components of signatures and to represent the type of multiple +// assignments; they are not first class types of Go. +type Tuple struct { + vars []*Var +} + +// NewTuple returns a new tuple for the given variables. +func NewTuple(x ...*Var) *Tuple { + if len(x) > 0 { + return &Tuple{x} + } + return nil +} + +// Len returns the number variables of tuple t. +func (t *Tuple) Len() int { + if t != nil { + return len(t.vars) + } + return 0 +} + +// At returns the i'th variable of tuple t. +func (t *Tuple) At(i int) *Var { return t.vars[i] } + +// A Signature represents a (non-builtin) function or method type. +// The receiver is ignored when comparing signatures for identity. +type Signature struct { + // We need to keep the scope in Signature (rather than passing it around + // and store it in the Func Object) because when type-checking a function + // literal we call the general type checker which returns a general Type. + // We then unpack the *Signature and use the scope for the literal body. + scope *Scope // function scope, present for package-local signatures + recv *Var // nil if not a method + params *Tuple // (incoming) parameters from left to right; or nil + results *Tuple // (outgoing) results from left to right; or nil + variadic bool // true if the last parameter's type is of the form ...T (or string, for append built-in only) +} + +// NewSignature returns a new function type for the given receiver, parameters, +// and results, either of which may be nil. If variadic is set, the function +// is variadic, it must have at least one parameter, and the last parameter +// must be of unnamed slice type. +func NewSignature(recv *Var, params, results *Tuple, variadic bool) *Signature { + if variadic { + n := params.Len() + if n == 0 { + panic("types.NewSignature: variadic function must have at least one parameter") + } + if _, ok := params.At(n - 1).typ.(*Slice); !ok { + panic("types.NewSignature: variadic parameter must be of unnamed slice type") + } + } + return &Signature{nil, recv, params, results, variadic} +} + +// Recv returns the receiver of signature s (if a method), or nil if a +// function. It is ignored when comparing signatures for identity. +// +// For an abstract method, Recv returns the enclosing interface either +// as a *Named or an *Interface. Due to embedding, an interface may +// contain methods whose receiver type is a different interface. +func (s *Signature) Recv() *Var { return s.recv } + +// Params returns the parameters of signature s, or nil. +func (s *Signature) Params() *Tuple { return s.params } + +// Results returns the results of signature s, or nil. +func (s *Signature) Results() *Tuple { return s.results } + +// Variadic reports whether the signature s is variadic. +func (s *Signature) Variadic() bool { return s.variadic } + +// An Interface represents an interface type. +type Interface struct { + methods []*Func // ordered list of explicitly declared methods + embeddeds []Type // ordered list of explicitly embedded types + + allMethods []*Func // ordered list of methods declared with or embedded in this interface (TODO(gri): replace with mset) +} + +// emptyInterface represents the empty (completed) interface +var emptyInterface = Interface{allMethods: markComplete} + +// markComplete is used to mark an empty interface as completely +// set up by setting the allMethods field to a non-nil empty slice. +var markComplete = make([]*Func, 0) + +// NewInterface returns a new (incomplete) interface for the given methods and embedded types. +// Each embedded type must have an underlying type of interface type. +// NewInterface takes ownership of the provided methods and may modify their types by setting +// missing receivers. To compute the method set of the interface, Complete must be called. +// +// Deprecated: Use NewInterfaceType instead which allows any (even non-defined) interface types +// to be embedded. This is necessary for interfaces that embed alias type names referring to +// non-defined (literal) interface types. +func NewInterface(methods []*Func, embeddeds []*Named) *Interface { + tnames := make([]Type, len(embeddeds)) + for i, t := range embeddeds { + tnames[i] = t + } + return NewInterfaceType(methods, tnames) +} + +// NewInterfaceType returns a new (incomplete) interface for the given methods and embedded types. +// Each embedded type must have an underlying type of interface type (this property is not +// verified for defined types, which may be in the process of being set up and which don't +// have a valid underlying type yet). +// NewInterfaceType takes ownership of the provided methods and may modify their types by setting +// missing receivers. To compute the method set of the interface, Complete must be called. +func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface { + if len(methods) == 0 && len(embeddeds) == 0 { + return &emptyInterface + } + + // set method receivers if necessary + typ := new(Interface) + for _, m := range methods { + if sig := m.typ.(*Signature); sig.recv == nil { + sig.recv = NewVar(m.pos, m.pkg, "", typ) + } + } + + // All embedded types should be interfaces; however, defined types + // may not yet be fully resolved. Only verify that non-defined types + // are interfaces. This matches the behavior of the code before the + // fix for #25301 (issue #25596). + for _, t := range embeddeds { + if _, ok := t.(*Named); !ok && !IsInterface(t) { + panic("embedded type is not an interface") + } + } + + // sort for API stability + sort.Sort(byUniqueMethodName(methods)) + sort.Stable(byUniqueTypeName(embeddeds)) + + typ.methods = methods + typ.embeddeds = embeddeds + return typ +} + +// NumExplicitMethods returns the number of explicitly declared methods of interface t. +func (t *Interface) NumExplicitMethods() int { return len(t.methods) } + +// ExplicitMethod returns the i'th explicitly declared method of interface t for 0 <= i < t.NumExplicitMethods(). +// The methods are ordered by their unique Id. +func (t *Interface) ExplicitMethod(i int) *Func { return t.methods[i] } + +// NumEmbeddeds returns the number of embedded types in interface t. +func (t *Interface) NumEmbeddeds() int { return len(t.embeddeds) } + +// Embedded returns the i'th embedded defined (*Named) type of interface t for 0 <= i < t.NumEmbeddeds(). +// The result is nil if the i'th embedded type is not a defined type. +// +// Deprecated: Use EmbeddedType which is not restricted to defined (*Named) types. +func (t *Interface) Embedded(i int) *Named { tname, _ := t.embeddeds[i].(*Named); return tname } + +// EmbeddedType returns the i'th embedded type of interface t for 0 <= i < t.NumEmbeddeds(). +func (t *Interface) EmbeddedType(i int) Type { return t.embeddeds[i] } + +// NumMethods returns the total number of methods of interface t. +// The interface must have been completed. +func (t *Interface) NumMethods() int { t.assertCompleteness(); return len(t.allMethods) } + +func (t *Interface) assertCompleteness() { + if t.allMethods == nil { + panic("interface is incomplete") + } +} + +// Method returns the i'th method of interface t for 0 <= i < t.NumMethods(). +// The methods are ordered by their unique Id. +// The interface must have been completed. +func (t *Interface) Method(i int) *Func { t.assertCompleteness(); return t.allMethods[i] } + +// Empty reports whether t is the empty interface. +// The interface must have been completed. +func (t *Interface) Empty() bool { t.assertCompleteness(); return len(t.allMethods) == 0 } + +// Complete computes the interface's method set. It must be called by users of +// NewInterfaceType and NewInterface after the interface's embedded types are +// fully defined and before using the interface type in any way other than to +// form other types. The interface must not contain duplicate methods or a +// panic occurs. Complete returns the receiver. +func (t *Interface) Complete() *Interface { + // TODO(gri) consolidate this method with Checker.completeInterface + if t.allMethods != nil { + return t + } + + t.allMethods = markComplete // avoid infinite recursion + + var todo []*Func + var methods []*Func + var seen objset + addMethod := func(m *Func, explicit bool) { + switch other := seen.insert(m); { + case other == nil: + methods = append(methods, m) + case explicit: + panic("duplicate method " + m.name) + default: + // check method signatures after all locally embedded interfaces are computed + todo = append(todo, m, other.(*Func)) + } + } + + for _, m := range t.methods { + addMethod(m, true) + } + + for _, typ := range t.embeddeds { + typ := typ.Underlying().(*Interface) + typ.Complete() + for _, m := range typ.allMethods { + addMethod(m, false) + } + } + + for i := 0; i < len(todo); i += 2 { + m := todo[i] + other := todo[i+1] + if !Identical(m.typ, other.typ) { + panic("duplicate method " + m.name) + } + } + + if methods != nil { + sort.Sort(byUniqueMethodName(methods)) + t.allMethods = methods + } + + return t +} + +// A Map represents a map type. +type Map struct { + key, elem Type +} + +// NewMap returns a new map for the given key and element types. +func NewMap(key, elem Type) *Map { + return &Map{key, elem} +} + +// Key returns the key type of map m. +func (m *Map) Key() Type { return m.key } + +// Elem returns the element type of map m. +func (m *Map) Elem() Type { return m.elem } + +// A Chan represents a channel type. +type Chan struct { + dir ChanDir + elem Type +} + +// A ChanDir value indicates a channel direction. +type ChanDir int + +// The direction of a channel is indicated by one of these constants. +const ( + SendRecv ChanDir = iota + SendOnly + RecvOnly +) + +// NewChan returns a new channel type for the given direction and element type. +func NewChan(dir ChanDir, elem Type) *Chan { + return &Chan{dir, elem} +} + +// Dir returns the direction of channel c. +func (c *Chan) Dir() ChanDir { return c.dir } + +// Elem returns the element type of channel c. +func (c *Chan) Elem() Type { return c.elem } + +// A Named represents a named type. +type Named struct { + info typeInfo // for cycle detection + obj *TypeName // corresponding declared object + orig Type // type (on RHS of declaration) this *Named type is derived of (for cycle reporting) + underlying Type // possibly a *Named during setup; never a *Named once set up completely + methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily +} + +// NewNamed returns a new named type for the given type name, underlying type, and associated methods. +// If the given type name obj doesn't have a type yet, its type is set to the returned named type. +// The underlying type must not be a *Named. +func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { + if _, ok := underlying.(*Named); ok { + panic("types.NewNamed: underlying type must not be *Named") + } + typ := &Named{obj: obj, orig: underlying, underlying: underlying, methods: methods} + if obj.typ == nil { + obj.typ = typ + } + return typ +} + +// Obj returns the type name for the named type t. +func (t *Named) Obj() *TypeName { return t.obj } + +// NumMethods returns the number of explicit methods whose receiver is named type t. +func (t *Named) NumMethods() int { return len(t.methods) } + +// Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). +func (t *Named) Method(i int) *Func { return t.methods[i] } + +// SetUnderlying sets the underlying type and marks t as complete. +func (t *Named) SetUnderlying(underlying Type) { + if underlying == nil { + panic("types.Named.SetUnderlying: underlying type must not be nil") + } + if _, ok := underlying.(*Named); ok { + panic("types.Named.SetUnderlying: underlying type must not be *Named") + } + t.underlying = underlying +} + +// AddMethod adds method m unless it is already in the method list. +func (t *Named) AddMethod(m *Func) { + if i, _ := lookupMethod(t.methods, m.pkg, m.name); i < 0 { + t.methods = append(t.methods, m) + } +} + +// Implementations for Type methods. + +func (b *Basic) Underlying() Type { return b } +func (a *Array) Underlying() Type { return a } +func (s *Slice) Underlying() Type { return s } +func (s *Struct) Underlying() Type { return s } +func (p *Pointer) Underlying() Type { return p } +func (t *Tuple) Underlying() Type { return t } +func (s *Signature) Underlying() Type { return s } +func (t *Interface) Underlying() Type { return t } +func (m *Map) Underlying() Type { return m } +func (c *Chan) Underlying() Type { return c } +func (t *Named) Underlying() Type { return t.underlying } + +func (b *Basic) String() string { return TypeString(b, nil) } +func (a *Array) String() string { return TypeString(a, nil) } +func (s *Slice) String() string { return TypeString(s, nil) } +func (s *Struct) String() string { return TypeString(s, nil) } +func (p *Pointer) String() string { return TypeString(p, nil) } +func (t *Tuple) String() string { return TypeString(t, nil) } +func (s *Signature) String() string { return TypeString(s, nil) } +func (t *Interface) String() string { return TypeString(t, nil) } +func (m *Map) String() string { return TypeString(m, nil) } +func (c *Chan) String() string { return TypeString(c, nil) } +func (t *Named) String() string { return TypeString(t, nil) } diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go new file mode 100644 index 0000000..31c572f --- /dev/null +++ b/src/go/types/typestring.go @@ -0,0 +1,307 @@ +// Copyright 2013 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. + +// This file implements printing of types. + +package types + +import ( + "bytes" + "fmt" +) + +// A Qualifier controls how named package-level objects are printed in +// calls to TypeString, ObjectString, and SelectionString. +// +// These three formatting routines call the Qualifier for each +// package-level object O, and if the Qualifier returns a non-empty +// string p, the object is printed in the form p.O. +// If it returns an empty string, only the object name O is printed. +// +// Using a nil Qualifier is equivalent to using (*Package).Path: the +// object is qualified by the import path, e.g., "encoding/json.Marshal". +// +type Qualifier func(*Package) string + +// RelativeTo returns a Qualifier that fully qualifies members of +// all packages other than pkg. +func RelativeTo(pkg *Package) Qualifier { + if pkg == nil { + return nil + } + return func(other *Package) string { + if pkg == other { + return "" // same package; unqualified + } + return other.Path() + } +} + +// If gcCompatibilityMode is set, printing of types is modified +// to match the representation of some types in the gc compiler: +// +// - byte and rune lose their alias name and simply stand for +// uint8 and int32 respectively +// - embedded interfaces get flattened (the embedding info is lost, +// and certain recursive interface types cannot be printed anymore) +// +// This makes it easier to compare packages computed with the type- +// checker vs packages imported from gc export data. +// +// Caution: This flag affects all uses of WriteType, globally. +// It is only provided for testing in conjunction with +// gc-generated data. +// +// This flag is exported in the x/tools/go/types package. We don't +// need it at the moment in the std repo and so we don't export it +// anymore. We should eventually try to remove it altogether. +// TODO(gri) remove this +var gcCompatibilityMode bool + +// TypeString returns the string representation of typ. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +func TypeString(typ Type, qf Qualifier) string { + var buf bytes.Buffer + WriteType(&buf, typ, qf) + return buf.String() +} + +// WriteType writes the string representation of typ to buf. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +func WriteType(buf *bytes.Buffer, typ Type, qf Qualifier) { + writeType(buf, typ, qf, make([]Type, 0, 8)) +} + +func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) { + // Theoretically, this is a quadratic lookup algorithm, but in + // practice deeply nested composite types with unnamed component + // types are uncommon. This code is likely more efficient than + // using a map. + for _, t := range visited { + if t == typ { + fmt.Fprintf(buf, "○%T", typ) // cycle to typ + return + } + } + visited = append(visited, typ) + + switch t := typ.(type) { + case nil: + buf.WriteString("") + + case *Basic: + if t.kind == UnsafePointer { + buf.WriteString("unsafe.") + } + if gcCompatibilityMode { + // forget the alias names + switch t.kind { + case Byte: + t = Typ[Uint8] + case Rune: + t = Typ[Int32] + } + } + buf.WriteString(t.name) + + case *Array: + fmt.Fprintf(buf, "[%d]", t.len) + writeType(buf, t.elem, qf, visited) + + case *Slice: + buf.WriteString("[]") + writeType(buf, t.elem, qf, visited) + + case *Struct: + buf.WriteString("struct{") + for i, f := range t.fields { + if i > 0 { + buf.WriteString("; ") + } + if !f.embedded { + buf.WriteString(f.name) + buf.WriteByte(' ') + } + writeType(buf, f.typ, qf, visited) + if tag := t.Tag(i); tag != "" { + fmt.Fprintf(buf, " %q", tag) + } + } + buf.WriteByte('}') + + case *Pointer: + buf.WriteByte('*') + writeType(buf, t.base, qf, visited) + + case *Tuple: + writeTuple(buf, t, false, qf, visited) + + case *Signature: + buf.WriteString("func") + writeSignature(buf, t, qf, visited) + + case *Interface: + // We write the source-level methods and embedded types rather + // than the actual method set since resolved method signatures + // may have non-printable cycles if parameters have embedded + // interface types that (directly or indirectly) embed the + // current interface. For instance, consider the result type + // of m: + // + // type T interface{ + // m() interface{ T } + // } + // + buf.WriteString("interface{") + empty := true + if gcCompatibilityMode { + // print flattened interface + // (useful to compare against gc-generated interfaces) + for i, m := range t.allMethods { + if i > 0 { + buf.WriteString("; ") + } + buf.WriteString(m.name) + writeSignature(buf, m.typ.(*Signature), qf, visited) + empty = false + } + } else { + // print explicit interface methods and embedded types + for i, m := range t.methods { + if i > 0 { + buf.WriteString("; ") + } + buf.WriteString(m.name) + writeSignature(buf, m.typ.(*Signature), qf, visited) + empty = false + } + for i, typ := range t.embeddeds { + if i > 0 || len(t.methods) > 0 { + buf.WriteString("; ") + } + writeType(buf, typ, qf, visited) + empty = false + } + } + if t.allMethods == nil || len(t.methods) > len(t.allMethods) { + if !empty { + buf.WriteByte(' ') + } + buf.WriteString("/* incomplete */") + } + buf.WriteByte('}') + + case *Map: + buf.WriteString("map[") + writeType(buf, t.key, qf, visited) + buf.WriteByte(']') + writeType(buf, t.elem, qf, visited) + + case *Chan: + var s string + var parens bool + switch t.dir { + case SendRecv: + s = "chan " + // chan (<-chan T) requires parentheses + if c, _ := t.elem.(*Chan); c != nil && c.dir == RecvOnly { + parens = true + } + case SendOnly: + s = "chan<- " + case RecvOnly: + s = "<-chan " + default: + panic("unreachable") + } + buf.WriteString(s) + if parens { + buf.WriteByte('(') + } + writeType(buf, t.elem, qf, visited) + if parens { + buf.WriteByte(')') + } + + case *Named: + s := "" + if obj := t.obj; obj != nil { + if obj.pkg != nil { + writePackage(buf, obj.pkg, qf) + } + // TODO(gri): function-local named types should be displayed + // differently from named types at package level to avoid + // ambiguity. + s = obj.name + } + buf.WriteString(s) + + default: + // For externally defined implementations of Type. + buf.WriteString(t.String()) + } +} + +func writeTuple(buf *bytes.Buffer, tup *Tuple, variadic bool, qf Qualifier, visited []Type) { + buf.WriteByte('(') + if tup != nil { + for i, v := range tup.vars { + if i > 0 { + buf.WriteString(", ") + } + if v.name != "" { + buf.WriteString(v.name) + buf.WriteByte(' ') + } + typ := v.typ + if variadic && i == len(tup.vars)-1 { + if s, ok := typ.(*Slice); ok { + buf.WriteString("...") + typ = s.elem + } else { + // special case: + // append(s, "foo"...) leads to signature func([]byte, string...) + if t, ok := typ.Underlying().(*Basic); !ok || t.kind != String { + panic("internal error: string type expected") + } + writeType(buf, typ, qf, visited) + buf.WriteString("...") + continue + } + } + writeType(buf, typ, qf, visited) + } + } + buf.WriteByte(')') +} + +// WriteSignature writes the representation of the signature sig to buf, +// without a leading "func" keyword. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +func WriteSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier) { + writeSignature(buf, sig, qf, make([]Type, 0, 8)) +} + +func writeSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier, visited []Type) { + writeTuple(buf, sig.params, sig.variadic, qf, visited) + + n := sig.results.Len() + if n == 0 { + // no result + return + } + + buf.WriteByte(' ') + if n == 1 && sig.results.vars[0].name == "" { + // single unnamed result + writeType(buf, sig.results.vars[0].typ, qf, visited) + return + } + + // multiple or named result(s) + writeTuple(buf, sig.results, false, qf, visited) +} diff --git a/src/go/types/typestring_test.go b/src/go/types/typestring_test.go new file mode 100644 index 0000000..5d9db39 --- /dev/null +++ b/src/go/types/typestring_test.go @@ -0,0 +1,221 @@ +// Copyright 2012 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 types_test + +import ( + "go/ast" + "go/importer" + "go/parser" + "go/token" + "internal/testenv" + "testing" + + . "go/types" +) + +const filename = "" + +func makePkg(src string) (*Package, error) { + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors) + if err != nil { + return nil, err + } + // use the package name as package path + conf := Config{Importer: importer.Default()} + return conf.Check(file.Name.Name, fset, []*ast.File{file}, nil) +} + +type testEntry struct { + src, str string +} + +// dup returns a testEntry where both src and str are the same. +func dup(s string) testEntry { + return testEntry{s, s} +} + +// types that don't depend on any other type declarations +var independentTestTypes = []testEntry{ + // basic types + dup("int"), + dup("float32"), + dup("string"), + + // arrays + dup("[10]int"), + + // slices + dup("[]int"), + dup("[][]int"), + + // structs + dup("struct{}"), + dup("struct{x int}"), + {`struct { + x, y int + z float32 "foo" + }`, `struct{x int; y int; z float32 "foo"}`}, + {`struct { + string + elems []complex128 + }`, `struct{string; elems []complex128}`}, + + // pointers + dup("*int"), + dup("***struct{}"), + dup("*struct{a int; b float32}"), + + // functions + dup("func()"), + dup("func(x int)"), + {"func(x, y int)", "func(x int, y int)"}, + {"func(x, y int, z string)", "func(x int, y int, z string)"}, + dup("func(int)"), + {"func(int, string, byte)", "func(int, string, byte)"}, + + dup("func() int"), + {"func() (string)", "func() string"}, + dup("func() (u int)"), + {"func() (u, v int, w string)", "func() (u int, v int, w string)"}, + + dup("func(int) string"), + dup("func(x int) string"), + dup("func(x int) (u string)"), + {"func(x, y int) (u string)", "func(x int, y int) (u string)"}, + + dup("func(...int) string"), + dup("func(x ...int) string"), + dup("func(x ...int) (u string)"), + {"func(x int, y ...int) (u string)", "func(x int, y ...int) (u string)"}, + + // interfaces + dup("interface{}"), + dup("interface{m()}"), + dup(`interface{String() string; m(int) float32}`), + + // maps + dup("map[string]int"), + {"map[struct{x, y int}][]byte", "map[struct{x int; y int}][]byte"}, + + // channels + dup("chan<- chan int"), + dup("chan<- <-chan int"), + dup("<-chan <-chan int"), + dup("chan (<-chan int)"), + dup("chan<- func()"), + dup("<-chan []func() int"), +} + +// types that depend on other type declarations (src in TestTypes) +var dependentTestTypes = []testEntry{ + // interfaces + dup(`interface{io.Reader; io.Writer}`), + dup(`interface{m() int; io.Writer}`), + {`interface{m() interface{T}}`, `interface{m() interface{p.T}}`}, +} + +func TestTypeString(t *testing.T) { + testenv.MustHaveGoBuild(t) + + var tests []testEntry + tests = append(tests, independentTestTypes...) + tests = append(tests, dependentTestTypes...) + + for _, test := range tests { + src := `package p; import "io"; type _ io.Writer; type T ` + test.src + pkg, err := makePkg(src) + if err != nil { + t.Errorf("%s: %s", src, err) + continue + } + typ := pkg.Scope().Lookup("T").Type().Underlying() + if got := typ.String(); got != test.str { + t.Errorf("%s: got %s, want %s", test.src, got, test.str) + } + } +} + +func TestIncompleteInterfaces(t *testing.T) { + sig := NewSignature(nil, nil, nil, false) + m := NewFunc(token.NoPos, nil, "m", sig) + for _, test := range []struct { + typ *Interface + want string + }{ + {new(Interface), "interface{/* incomplete */}"}, + {new(Interface).Complete(), "interface{}"}, + + {NewInterface(nil, nil), "interface{}"}, + {NewInterface(nil, nil).Complete(), "interface{}"}, + {NewInterface([]*Func{}, nil), "interface{}"}, + {NewInterface([]*Func{}, nil).Complete(), "interface{}"}, + {NewInterface(nil, []*Named{}), "interface{}"}, + {NewInterface(nil, []*Named{}).Complete(), "interface{}"}, + {NewInterface([]*Func{m}, nil), "interface{m() /* incomplete */}"}, + {NewInterface([]*Func{m}, nil).Complete(), "interface{m()}"}, + {NewInterface(nil, []*Named{newDefined(new(Interface).Complete())}), "interface{T /* incomplete */}"}, + {NewInterface(nil, []*Named{newDefined(new(Interface).Complete())}).Complete(), "interface{T}"}, + {NewInterface(nil, []*Named{newDefined(NewInterface([]*Func{m}, nil))}), "interface{T /* incomplete */}"}, + {NewInterface(nil, []*Named{newDefined(NewInterface([]*Func{m}, nil).Complete())}), "interface{T /* incomplete */}"}, + {NewInterface(nil, []*Named{newDefined(NewInterface([]*Func{m}, nil).Complete())}).Complete(), "interface{T}"}, + + {NewInterfaceType(nil, nil), "interface{}"}, + {NewInterfaceType(nil, nil).Complete(), "interface{}"}, + {NewInterfaceType([]*Func{}, nil), "interface{}"}, + {NewInterfaceType([]*Func{}, nil).Complete(), "interface{}"}, + {NewInterfaceType(nil, []Type{}), "interface{}"}, + {NewInterfaceType(nil, []Type{}).Complete(), "interface{}"}, + {NewInterfaceType([]*Func{m}, nil), "interface{m() /* incomplete */}"}, + {NewInterfaceType([]*Func{m}, nil).Complete(), "interface{m()}"}, + {NewInterfaceType(nil, []Type{new(Interface).Complete()}), "interface{interface{} /* incomplete */}"}, + {NewInterfaceType(nil, []Type{new(Interface).Complete()}).Complete(), "interface{interface{}}"}, + {NewInterfaceType(nil, []Type{NewInterfaceType([]*Func{m}, nil)}), "interface{interface{m() /* incomplete */} /* incomplete */}"}, + {NewInterfaceType(nil, []Type{NewInterfaceType([]*Func{m}, nil).Complete()}), "interface{interface{m()} /* incomplete */}"}, + {NewInterfaceType(nil, []Type{NewInterfaceType([]*Func{m}, nil).Complete()}).Complete(), "interface{interface{m()}}"}, + } { + got := test.typ.String() + if got != test.want { + t.Errorf("got: %s, want: %s", got, test.want) + } + } +} + +// newDefined creates a new defined type named T with the given underlying type. +// Helper function for use with TestIncompleteInterfaces only. +func newDefined(underlying Type) *Named { + tname := NewTypeName(token.NoPos, nil, "T", nil) + return NewNamed(tname, underlying, nil) +} + +func TestQualifiedTypeString(t *testing.T) { + p, _ := pkgFor("p.go", "package p; type T int", nil) + q, _ := pkgFor("q.go", "package q", nil) + + pT := p.Scope().Lookup("T").Type() + for _, test := range []struct { + typ Type + this *Package + want string + }{ + {nil, nil, ""}, + {pT, nil, "p.T"}, + {pT, p, "T"}, + {pT, q, "p.T"}, + {NewPointer(pT), p, "*T"}, + {NewPointer(pT), q, "*p.T"}, + } { + qualifier := func(pkg *Package) string { + if pkg != test.this { + return pkg.Name() + } + return "" + } + if got := TypeString(test.typ, qualifier); got != test.want { + t.Errorf("TypeString(%s, %s) = %s, want %s", + test.this, test.typ, got, test.want) + } + } +} diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go new file mode 100644 index 0000000..2b39801 --- /dev/null +++ b/src/go/types/typexpr.go @@ -0,0 +1,764 @@ +// Copyright 2013 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. + +// This file implements type-checking of identifiers and type expressions. + +package types + +import ( + "go/ast" + "go/constant" + "go/token" + "sort" + "strconv" +) + +// ident type-checks identifier e and initializes x with the value or type of e. +// If an error occurred, x.mode is set to invalid. +// For the meaning of def, see Checker.definedType, below. +// If wantType is set, the identifier e is expected to denote a type. +// +func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, wantType bool) { + x.mode = invalid + x.expr = e + + // Note that we cannot use check.lookup here because the returned scope + // may be different from obj.Parent(). See also Scope.LookupParent doc. + scope, obj := check.scope.LookupParent(e.Name, check.pos) + if obj == nil { + if e.Name == "_" { + check.errorf(e, _InvalidBlank, "cannot use _ as value or type") + } else { + check.errorf(e, _UndeclaredName, "undeclared name: %s", e.Name) + } + return + } + check.recordUse(e, obj) + + // Type-check the object. + // Only call Checker.objDecl if the object doesn't have a type yet + // (in which case we must actually determine it) or the object is a + // TypeName and we also want a type (in which case we might detect + // a cycle which needs to be reported). Otherwise we can skip the + // call and avoid a possible cycle error in favor of the more + // informative "not a type/value" error that this function's caller + // will issue (see issue #25790). + typ := obj.Type() + if _, gotType := obj.(*TypeName); typ == nil || gotType && wantType { + check.objDecl(obj, def) + typ = obj.Type() // type must have been assigned by Checker.objDecl + } + assert(typ != nil) + + // The object may be dot-imported: If so, remove its package from + // the map of unused dot imports for the respective file scope. + // (This code is only needed for dot-imports. Without them, + // we only have to mark variables, see *Var case below). + if pkg := obj.Pkg(); pkg != check.pkg && pkg != nil { + delete(check.unusedDotImports[scope], pkg) + } + + switch obj := obj.(type) { + case *PkgName: + check.errorf(e, _InvalidPkgUse, "use of package %s not in selector", obj.name) + return + + case *Const: + check.addDeclDep(obj) + if typ == Typ[Invalid] { + return + } + if obj == universeIota { + if check.iota == nil { + check.errorf(e, _InvalidIota, "cannot use iota outside constant declaration") + return + } + x.val = check.iota + } else { + x.val = obj.val + } + assert(x.val != nil) + x.mode = constant_ + + case *TypeName: + x.mode = typexpr + + case *Var: + // It's ok to mark non-local variables, but ignore variables + // from other packages to avoid potential race conditions with + // dot-imported variables. + if obj.pkg == check.pkg { + obj.used = true + } + check.addDeclDep(obj) + if typ == Typ[Invalid] { + return + } + x.mode = variable + + case *Func: + check.addDeclDep(obj) + x.mode = value + + case *Builtin: + x.id = obj.id + x.mode = builtin + + case *Nil: + x.mode = value + + default: + unreachable() + } + + x.typ = typ +} + +// typ type-checks the type expression e and returns its type, or Typ[Invalid]. +func (check *Checker) typ(e ast.Expr) Type { + return check.definedType(e, nil) +} + +// definedType is like typ but also accepts a type name def. +// If def != nil, e is the type specification for the defined type def, declared +// in a type declaration, and def.underlying will be set to the type of e before +// any components of e are type-checked. +// +func (check *Checker) definedType(e ast.Expr, def *Named) (T Type) { + if trace { + check.trace(e.Pos(), "%s", e) + check.indent++ + defer func() { + check.indent-- + check.trace(e.Pos(), "=> %s", T) + }() + } + + T = check.typInternal(e, def) + assert(isTyped(T)) + check.recordTypeAndValue(e, typexpr, T, nil) + + return +} + +// funcType type-checks a function or method type. +func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast.FuncType) { + scope := NewScope(check.scope, token.NoPos, token.NoPos, "function") + scope.isFunc = true + check.recordScope(ftyp, scope) + + recvList, _ := check.collectParams(scope, recvPar, false) + params, variadic := check.collectParams(scope, ftyp.Params, true) + results, _ := check.collectParams(scope, ftyp.Results, false) + + if recvPar != nil { + // recv parameter list present (may be empty) + // spec: "The receiver is specified via an extra parameter section preceding the + // method name. That parameter section must declare a single parameter, the receiver." + var recv *Var + switch len(recvList) { + case 0: + check.error(recvPar, _BadRecv, "method is missing receiver") + recv = NewParam(0, nil, "", Typ[Invalid]) // ignore recv below + default: + // more than one receiver + check.error(recvList[len(recvList)-1], _BadRecv, "method must have exactly one receiver") + fallthrough // continue with first receiver + case 1: + recv = recvList[0] + } + // spec: "The receiver type must be of the form T or *T where T is a type name." + // (ignore invalid types - error was reported before) + if t, _ := deref(recv.typ); t != Typ[Invalid] { + var err string + if T, _ := t.(*Named); T != nil { + // spec: "The type denoted by T is called the receiver base type; it must not + // be a pointer or interface type and it must be declared in the same package + // as the method." + if T.obj.pkg != check.pkg { + err = "type not defined in this package" + } else { + // TODO(gri) This is not correct if the underlying type is unknown yet. + switch u := T.underlying.(type) { + case *Basic: + // unsafe.Pointer is treated like a regular pointer + if u.kind == UnsafePointer { + err = "unsafe.Pointer" + } + case *Pointer, *Interface: + err = "pointer or interface type" + } + } + } else { + err = "basic or unnamed type" + } + if err != "" { + check.errorf(recv, _InvalidRecv, "invalid receiver %s (%s)", recv.typ, err) + // ok to continue + } + } + sig.recv = recv + } + + sig.scope = scope + sig.params = NewTuple(params...) + sig.results = NewTuple(results...) + sig.variadic = variadic +} + +// typInternal drives type checking of types. +// Must only be called by definedType. +// +func (check *Checker) typInternal(e ast.Expr, def *Named) Type { + switch e := e.(type) { + case *ast.BadExpr: + // ignore - error reported before + + case *ast.Ident: + var x operand + check.ident(&x, e, def, true) + + switch x.mode { + case typexpr: + typ := x.typ + def.setUnderlying(typ) + return typ + case invalid: + // ignore - error reported before + case novalue: + check.errorf(&x, _NotAType, "%s used as type", &x) + default: + check.errorf(&x, _NotAType, "%s is not a type", &x) + } + + case *ast.SelectorExpr: + var x operand + check.selector(&x, e) + + switch x.mode { + case typexpr: + typ := x.typ + def.setUnderlying(typ) + return typ + case invalid: + // ignore - error reported before + case novalue: + check.errorf(&x, _NotAType, "%s used as type", &x) + default: + check.errorf(&x, _NotAType, "%s is not a type", &x) + } + + case *ast.ParenExpr: + return check.definedType(e.X, def) + + case *ast.ArrayType: + if e.Len != nil { + typ := new(Array) + def.setUnderlying(typ) + typ.len = check.arrayLength(e.Len) + typ.elem = check.typ(e.Elt) + return typ + + } else { + typ := new(Slice) + def.setUnderlying(typ) + typ.elem = check.typ(e.Elt) + return typ + } + + case *ast.StructType: + typ := new(Struct) + def.setUnderlying(typ) + check.structType(typ, e) + return typ + + case *ast.StarExpr: + typ := new(Pointer) + def.setUnderlying(typ) + typ.base = check.typ(e.X) + return typ + + case *ast.FuncType: + typ := new(Signature) + def.setUnderlying(typ) + check.funcType(typ, nil, e) + return typ + + case *ast.InterfaceType: + typ := new(Interface) + def.setUnderlying(typ) + check.interfaceType(typ, e, def) + return typ + + case *ast.MapType: + typ := new(Map) + def.setUnderlying(typ) + + typ.key = check.typ(e.Key) + typ.elem = check.typ(e.Value) + + // spec: "The comparison operators == and != must be fully defined + // for operands of the key type; thus the key type must not be a + // function, map, or slice." + // + // Delay this check because it requires fully setup types; + // it is safe to continue in any case (was issue 6667). + check.atEnd(func() { + if !Comparable(typ.key) { + check.errorf(e.Key, _IncomparableMapKey, "incomparable map key type %s", typ.key) + } + }) + + return typ + + case *ast.ChanType: + typ := new(Chan) + def.setUnderlying(typ) + + dir := SendRecv + switch e.Dir { + case ast.SEND | ast.RECV: + // nothing to do + case ast.SEND: + dir = SendOnly + case ast.RECV: + dir = RecvOnly + default: + check.invalidAST(e, "unknown channel direction %d", e.Dir) + // ok to continue + } + + typ.dir = dir + typ.elem = check.typ(e.Value) + return typ + + default: + check.errorf(e, _NotAType, "%s is not a type", e) + } + + typ := Typ[Invalid] + def.setUnderlying(typ) + return typ +} + +// typeOrNil type-checks the type expression (or nil value) e +// and returns the typ of e, or nil. +// If e is neither a type nor nil, typOrNil returns Typ[Invalid]. +// +func (check *Checker) typOrNil(e ast.Expr) Type { + var x operand + check.rawExpr(&x, e, nil) + switch x.mode { + case invalid: + // ignore - error reported before + case novalue: + check.errorf(&x, _NotAType, "%s used as type", &x) + case typexpr: + return x.typ + case value: + if x.isNil() { + return nil + } + fallthrough + default: + check.errorf(&x, _NotAType, "%s is not a type", &x) + } + return Typ[Invalid] +} + +// arrayLength type-checks the array length expression e +// and returns the constant length >= 0, or a value < 0 +// to indicate an error (and thus an unknown length). +func (check *Checker) arrayLength(e ast.Expr) int64 { + var x operand + check.expr(&x, e) + if x.mode != constant_ { + if x.mode != invalid { + check.errorf(&x, _InvalidArrayLen, "array length %s must be constant", &x) + } + return -1 + } + if isUntyped(x.typ) || isInteger(x.typ) { + if val := constant.ToInt(x.val); val.Kind() == constant.Int { + if representableConst(val, check, Typ[Int], nil) { + if n, ok := constant.Int64Val(val); ok && n >= 0 { + return n + } + check.errorf(&x, _InvalidArrayLen, "invalid array length %s", &x) + return -1 + } + } + } + check.errorf(&x, _InvalidArrayLen, "array length %s must be integer", &x) + return -1 +} + +func (check *Checker) collectParams(scope *Scope, list *ast.FieldList, variadicOk bool) (params []*Var, variadic bool) { + if list == nil { + return + } + + var named, anonymous bool + for i, field := range list.List { + ftype := field.Type + if t, _ := ftype.(*ast.Ellipsis); t != nil { + ftype = t.Elt + if variadicOk && i == len(list.List)-1 && len(field.Names) <= 1 { + variadic = true + } else { + check.softErrorf(t, _MisplacedDotDotDot, "can only use ... with final parameter in list") + // ignore ... and continue + } + } + typ := check.typ(ftype) + // The parser ensures that f.Tag is nil and we don't + // care if a constructed AST contains a non-nil tag. + if len(field.Names) > 0 { + // named parameter + for _, name := range field.Names { + if name.Name == "" { + check.invalidAST(name, "anonymous parameter") + // ok to continue + } + par := NewParam(name.Pos(), check.pkg, name.Name, typ) + check.declare(scope, name, par, scope.pos) + params = append(params, par) + } + named = true + } else { + // anonymous parameter + par := NewParam(ftype.Pos(), check.pkg, "", typ) + check.recordImplicit(field, par) + params = append(params, par) + anonymous = true + } + } + + if named && anonymous { + check.invalidAST(list, "list contains both named and anonymous parameters") + // ok to continue + } + + // For a variadic function, change the last parameter's type from T to []T. + // Since we type-checked T rather than ...T, we also need to retro-actively + // record the type for ...T. + if variadic { + last := params[len(params)-1] + last.typ = &Slice{elem: last.typ} + check.recordTypeAndValue(list.List[len(list.List)-1].Type, typexpr, last.typ, nil) + } + + return +} + +func (check *Checker) declareInSet(oset *objset, pos token.Pos, obj Object) bool { + if alt := oset.insert(obj); alt != nil { + check.errorf(atPos(pos), _DuplicateDecl, "%s redeclared", obj.Name()) + check.reportAltDecl(alt) + return false + } + return true +} + +func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, def *Named) { + for _, f := range iface.Methods.List { + if len(f.Names) > 0 { + // We have a method with name f.Names[0]. + // (The parser ensures that there's only one method + // and we don't care if a constructed AST has more.) + name := f.Names[0] + if name.Name == "_" { + check.errorf(name, _BlankIfaceMethod, "invalid method name _") + continue // ignore + } + + typ := check.typ(f.Type) + sig, _ := typ.(*Signature) + if sig == nil { + if typ != Typ[Invalid] { + check.invalidAST(f.Type, "%s is not a method signature", typ) + } + continue // ignore + } + + // use named receiver type if available (for better error messages) + var recvTyp Type = ityp + if def != nil { + recvTyp = def + } + sig.recv = NewVar(name.Pos(), check.pkg, "", recvTyp) + + m := NewFunc(name.Pos(), check.pkg, name.Name, sig) + check.recordDef(name, m) + ityp.methods = append(ityp.methods, m) + } else { + // We have an embedded interface and f.Type is its + // (possibly qualified) embedded type name. Collect + // it if it's a valid interface. + typ := check.typ(f.Type) + + utyp := check.underlying(typ) + if _, ok := utyp.(*Interface); !ok { + if utyp != Typ[Invalid] { + check.errorf(f.Type, _InvalidIfaceEmbed, "%s is not an interface", typ) + } + continue + } + + ityp.embeddeds = append(ityp.embeddeds, typ) + check.posMap[ityp] = append(check.posMap[ityp], f.Type.Pos()) + } + } + + if len(ityp.methods) == 0 && len(ityp.embeddeds) == 0 { + // empty interface + ityp.allMethods = markComplete + return + } + + // sort for API stability + sort.Sort(byUniqueMethodName(ityp.methods)) + sort.Stable(byUniqueTypeName(ityp.embeddeds)) + + check.later(func() { check.completeInterface(ityp) }) +} + +func (check *Checker) completeInterface(ityp *Interface) { + if ityp.allMethods != nil { + return + } + + // completeInterface may be called via the LookupFieldOrMethod, + // MissingMethod, Identical, or IdenticalIgnoreTags external API + // in which case check will be nil. In this case, type-checking + // must be finished and all interfaces should have been completed. + if check == nil { + panic("internal error: incomplete interface") + } + + if trace { + check.trace(token.NoPos, "complete %s", ityp) + check.indent++ + defer func() { + check.indent-- + check.trace(token.NoPos, "=> %s", ityp) + }() + } + + // An infinitely expanding interface (due to a cycle) is detected + // elsewhere (Checker.validType), so here we simply assume we only + // have valid interfaces. Mark the interface as complete to avoid + // infinite recursion if the validType check occurs later for some + // reason. + ityp.allMethods = markComplete + + // Methods of embedded interfaces are collected unchanged; i.e., the identity + // of a method I.m's Func Object of an interface I is the same as that of + // the method m in an interface that embeds interface I. On the other hand, + // if a method is embedded via multiple overlapping embedded interfaces, we + // don't provide a guarantee which "original m" got chosen for the embedding + // interface. See also issue #34421. + // + // If we don't care to provide this identity guarantee anymore, instead of + // reusing the original method in embeddings, we can clone the method's Func + // Object and give it the position of a corresponding embedded interface. Then + // we can get rid of the mpos map below and simply use the cloned method's + // position. + + var seen objset + var methods []*Func + mpos := make(map[*Func]token.Pos) // method specification or method embedding position, for good error messages + addMethod := func(pos token.Pos, m *Func, explicit bool) { + switch other := seen.insert(m); { + case other == nil: + methods = append(methods, m) + mpos[m] = pos + case explicit: + check.errorf(atPos(pos), _DuplicateDecl, "duplicate method %s", m.name) + check.errorf(atPos(mpos[other.(*Func)]), _DuplicateDecl, "\tother declaration of %s", m.name) // secondary error, \t indented + default: + // check method signatures after all types are computed (issue #33656) + check.atEnd(func() { + if !check.identical(m.typ, other.Type()) { + check.errorf(atPos(pos), _DuplicateDecl, "duplicate method %s", m.name) + check.errorf(atPos(mpos[other.(*Func)]), _DuplicateDecl, "\tother declaration of %s", m.name) // secondary error, \t indented + } + }) + } + } + + for _, m := range ityp.methods { + addMethod(m.pos, m, true) + } + + posList := check.posMap[ityp] + for i, typ := range ityp.embeddeds { + pos := posList[i] // embedding position + typ, ok := check.underlying(typ).(*Interface) + if !ok { + // An error was reported when collecting the embedded types. + // Ignore it. + continue + } + check.completeInterface(typ) + for _, m := range typ.allMethods { + addMethod(pos, m, false) // use embedding position pos rather than m.pos + } + } + + if methods != nil { + sort.Sort(byUniqueMethodName(methods)) + ityp.allMethods = methods + } +} + +// byUniqueTypeName named type lists can be sorted by their unique type names. +type byUniqueTypeName []Type + +func (a byUniqueTypeName) Len() int { return len(a) } +func (a byUniqueTypeName) Less(i, j int) bool { return sortName(a[i]) < sortName(a[j]) } +func (a byUniqueTypeName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +func sortName(t Type) string { + if named, _ := t.(*Named); named != nil { + return named.obj.Id() + } + return "" +} + +// byUniqueMethodName method lists can be sorted by their unique method names. +type byUniqueMethodName []*Func + +func (a byUniqueMethodName) Len() int { return len(a) } +func (a byUniqueMethodName) Less(i, j int) bool { return a[i].Id() < a[j].Id() } +func (a byUniqueMethodName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +func (check *Checker) tag(t *ast.BasicLit) string { + if t != nil { + if t.Kind == token.STRING { + if val, err := strconv.Unquote(t.Value); err == nil { + return val + } + } + check.invalidAST(t, "incorrect tag syntax: %q", t.Value) + } + return "" +} + +func (check *Checker) structType(styp *Struct, e *ast.StructType) { + list := e.Fields + if list == nil { + return + } + + // struct fields and tags + var fields []*Var + var tags []string + + // for double-declaration checks + var fset objset + + // current field typ and tag + var typ Type + var tag string + add := func(ident *ast.Ident, embedded bool, pos token.Pos) { + if tag != "" && tags == nil { + tags = make([]string, len(fields)) + } + if tags != nil { + tags = append(tags, tag) + } + + name := ident.Name + fld := NewField(pos, check.pkg, name, typ, embedded) + // spec: "Within a struct, non-blank field names must be unique." + if name == "_" || check.declareInSet(&fset, pos, fld) { + fields = append(fields, fld) + check.recordDef(ident, fld) + } + } + + // addInvalid adds an embedded field of invalid type to the struct for + // fields with errors; this keeps the number of struct fields in sync + // with the source as long as the fields are _ or have different names + // (issue #25627). + addInvalid := func(ident *ast.Ident, pos token.Pos) { + typ = Typ[Invalid] + tag = "" + add(ident, true, pos) + } + + for _, f := range list.List { + typ = check.typ(f.Type) + tag = check.tag(f.Tag) + if len(f.Names) > 0 { + // named fields + for _, name := range f.Names { + add(name, false, name.Pos()) + } + } else { + // embedded field + // spec: "An embedded type must be specified as a type name T or as a pointer + // to a non-interface type name *T, and T itself may not be a pointer type." + pos := f.Type.Pos() + name := embeddedFieldIdent(f.Type) + if name == nil { + check.invalidAST(f.Type, "embedded field type %s has no name", f.Type) + name = ast.NewIdent("_") + name.NamePos = pos + addInvalid(name, pos) + continue + } + t, isPtr := deref(typ) + // Because we have a name, typ must be of the form T or *T, where T is the name + // of a (named or alias) type, and t (= deref(typ)) must be the type of T. + switch t := t.Underlying().(type) { + case *Basic: + if t == Typ[Invalid] { + // error was reported before + addInvalid(name, pos) + continue + } + + // unsafe.Pointer is treated like a regular pointer + if t.kind == UnsafePointer { + check.errorf(f.Type, _InvalidPtrEmbed, "embedded field type cannot be unsafe.Pointer") + addInvalid(name, pos) + continue + } + + case *Pointer: + check.errorf(f.Type, _InvalidPtrEmbed, "embedded field type cannot be a pointer") + addInvalid(name, pos) + continue + + case *Interface: + if isPtr { + check.errorf(f.Type, _InvalidPtrEmbed, "embedded field type cannot be a pointer to an interface") + addInvalid(name, pos) + continue + } + } + add(name, true, pos) + } + } + + styp.fields = fields + styp.tags = tags +} + +func embeddedFieldIdent(e ast.Expr) *ast.Ident { + switch e := e.(type) { + case *ast.Ident: + return e + case *ast.StarExpr: + // *T is valid, but **T is not + if _, ok := e.X.(*ast.StarExpr); !ok { + return embeddedFieldIdent(e.X) + } + case *ast.SelectorExpr: + return e.Sel + } + return nil // invalid embedded field +} diff --git a/src/go/types/universe.go b/src/go/types/universe.go new file mode 100644 index 0000000..ff5b891 --- /dev/null +++ b/src/go/types/universe.go @@ -0,0 +1,238 @@ +// Copyright 2011 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. + +// This file sets up the universe scope and the unsafe package. + +package types + +import ( + "go/constant" + "go/token" + "strings" +) + +// The Universe scope contains all predeclared objects of Go. +// It is the outermost scope of any chain of nested scopes. +var Universe *Scope + +// The Unsafe package is the package returned by an importer +// for the import path "unsafe". +var Unsafe *Package + +var ( + universeIota *Const + universeByte *Basic // uint8 alias, but has name "byte" + universeRune *Basic // int32 alias, but has name "rune" + universeError *Named +) + +// Typ contains the predeclared *Basic types indexed by their +// corresponding BasicKind. +// +// The *Basic type for Typ[Byte] will have the name "uint8". +// Use Universe.Lookup("byte").Type() to obtain the specific +// alias basic type named "byte" (and analogous for "rune"). +var Typ = []*Basic{ + Invalid: {Invalid, 0, "invalid type"}, + + Bool: {Bool, IsBoolean, "bool"}, + Int: {Int, IsInteger, "int"}, + Int8: {Int8, IsInteger, "int8"}, + Int16: {Int16, IsInteger, "int16"}, + Int32: {Int32, IsInteger, "int32"}, + Int64: {Int64, IsInteger, "int64"}, + Uint: {Uint, IsInteger | IsUnsigned, "uint"}, + Uint8: {Uint8, IsInteger | IsUnsigned, "uint8"}, + Uint16: {Uint16, IsInteger | IsUnsigned, "uint16"}, + Uint32: {Uint32, IsInteger | IsUnsigned, "uint32"}, + Uint64: {Uint64, IsInteger | IsUnsigned, "uint64"}, + Uintptr: {Uintptr, IsInteger | IsUnsigned, "uintptr"}, + Float32: {Float32, IsFloat, "float32"}, + Float64: {Float64, IsFloat, "float64"}, + Complex64: {Complex64, IsComplex, "complex64"}, + Complex128: {Complex128, IsComplex, "complex128"}, + String: {String, IsString, "string"}, + UnsafePointer: {UnsafePointer, 0, "Pointer"}, + + UntypedBool: {UntypedBool, IsBoolean | IsUntyped, "untyped bool"}, + UntypedInt: {UntypedInt, IsInteger | IsUntyped, "untyped int"}, + UntypedRune: {UntypedRune, IsInteger | IsUntyped, "untyped rune"}, + UntypedFloat: {UntypedFloat, IsFloat | IsUntyped, "untyped float"}, + UntypedComplex: {UntypedComplex, IsComplex | IsUntyped, "untyped complex"}, + UntypedString: {UntypedString, IsString | IsUntyped, "untyped string"}, + UntypedNil: {UntypedNil, IsUntyped, "untyped nil"}, +} + +var aliases = [...]*Basic{ + {Byte, IsInteger | IsUnsigned, "byte"}, + {Rune, IsInteger, "rune"}, +} + +func defPredeclaredTypes() { + for _, t := range Typ { + def(NewTypeName(token.NoPos, nil, t.name, t)) + } + for _, t := range aliases { + def(NewTypeName(token.NoPos, nil, t.name, t)) + } + + // Error has a nil package in its qualified name since it is in no package + res := NewVar(token.NoPos, nil, "", Typ[String]) + sig := &Signature{results: NewTuple(res)} + err := NewFunc(token.NoPos, nil, "Error", sig) + typ := &Named{underlying: NewInterfaceType([]*Func{err}, nil).Complete()} + sig.recv = NewVar(token.NoPos, nil, "", typ) + def(NewTypeName(token.NoPos, nil, "error", typ)) +} + +var predeclaredConsts = [...]struct { + name string + kind BasicKind + val constant.Value +}{ + {"true", UntypedBool, constant.MakeBool(true)}, + {"false", UntypedBool, constant.MakeBool(false)}, + {"iota", UntypedInt, constant.MakeInt64(0)}, +} + +func defPredeclaredConsts() { + for _, c := range predeclaredConsts { + def(NewConst(token.NoPos, nil, c.name, Typ[c.kind], c.val)) + } +} + +func defPredeclaredNil() { + def(&Nil{object{name: "nil", typ: Typ[UntypedNil], color_: black}}) +} + +// A builtinId is the id of a builtin function. +type builtinId int + +const ( + // universe scope + _Append builtinId = iota + _Cap + _Close + _Complex + _Copy + _Delete + _Imag + _Len + _Make + _New + _Panic + _Print + _Println + _Real + _Recover + + // package unsafe + _Alignof + _Offsetof + _Sizeof + + // testing support + _Assert + _Trace +) + +var predeclaredFuncs = [...]struct { + name string + nargs int + variadic bool + kind exprKind +}{ + _Append: {"append", 1, true, expression}, + _Cap: {"cap", 1, false, expression}, + _Close: {"close", 1, false, statement}, + _Complex: {"complex", 2, false, expression}, + _Copy: {"copy", 2, false, statement}, + _Delete: {"delete", 2, false, statement}, + _Imag: {"imag", 1, false, expression}, + _Len: {"len", 1, false, expression}, + _Make: {"make", 1, true, expression}, + _New: {"new", 1, false, expression}, + _Panic: {"panic", 1, false, statement}, + _Print: {"print", 0, true, statement}, + _Println: {"println", 0, true, statement}, + _Real: {"real", 1, false, expression}, + _Recover: {"recover", 0, false, statement}, + + _Alignof: {"Alignof", 1, false, expression}, + _Offsetof: {"Offsetof", 1, false, expression}, + _Sizeof: {"Sizeof", 1, false, expression}, + + _Assert: {"assert", 1, false, statement}, + _Trace: {"trace", 0, true, statement}, +} + +func defPredeclaredFuncs() { + for i := range predeclaredFuncs { + id := builtinId(i) + if id == _Assert || id == _Trace { + continue // only define these in testing environment + } + def(newBuiltin(id)) + } +} + +// DefPredeclaredTestFuncs defines the assert and trace built-ins. +// These built-ins are intended for debugging and testing of this +// package only. +func DefPredeclaredTestFuncs() { + if Universe.Lookup("assert") != nil { + return // already defined + } + def(newBuiltin(_Assert)) + def(newBuiltin(_Trace)) +} + +func init() { + Universe = NewScope(nil, token.NoPos, token.NoPos, "universe") + Unsafe = NewPackage("unsafe", "unsafe") + Unsafe.complete = true + + defPredeclaredTypes() + defPredeclaredConsts() + defPredeclaredNil() + defPredeclaredFuncs() + + universeIota = Universe.Lookup("iota").(*Const) + universeByte = Universe.Lookup("byte").(*TypeName).typ.(*Basic) + universeRune = Universe.Lookup("rune").(*TypeName).typ.(*Basic) + universeError = Universe.Lookup("error").(*TypeName).typ.(*Named) +} + +// Objects with names containing blanks are internal and not entered into +// a scope. Objects with exported names are inserted in the unsafe package +// scope; other objects are inserted in the universe scope. +// +func def(obj Object) { + assert(obj.color() == black) + name := obj.Name() + if strings.Contains(name, " ") { + return // nothing to do + } + // fix Obj link for named types + if typ, ok := obj.Type().(*Named); ok { + typ.obj = obj.(*TypeName) + } + // exported identifiers go into package unsafe + scope := Universe + if obj.Exported() { + scope = Unsafe.scope + // set Pkg field + switch obj := obj.(type) { + case *TypeName: + obj.pkg = Unsafe + case *Builtin: + obj.pkg = Unsafe + default: + unreachable() + } + } + if scope.Insert(obj) != nil { + panic("internal error: double declaration") + } +} -- cgit v1.2.3