diff options
author | Robert Griesemer <gri@golang.org> | 2020-10-19 15:28:22 -0700 |
---|---|---|
committer | Robert Griesemer <gri@golang.org> | 2020-10-21 00:51:12 +0000 |
commit | ca36ba83ab86b9eb1ddc076f0ebfda648ce31d6b (patch) | |
tree | c061b8a1ffa5064e361e9ba58ea6693fab6ab0e0 /src | |
parent | 6ff16fe3ee46f8e35c18226d04bd38a396eb4175 (diff) | |
download | go-ca36ba83ab86b9eb1ddc076f0ebfda648ce31d6b.tar.gz go-ca36ba83ab86b9eb1ddc076f0ebfda648ce31d6b.zip |
[dev.typeparams] cmd/compile/internal/importer, types2: initial check-in of types2 and importer
This is a copy of the importer and types2 (unreviewed) prototype version
excluding the testdata directory containing tests (see below). Each file
is marked with the comment
// UNREVIEWED
on the first line. The plan is to check in this code wholesale (it runs and
passes all tests) and then review the code file-by-file via subsequent CLs
and remove the "// UNREVIEWED" comments as we review the files.
Since most tests are unchanged from the original go/types, the next CL will
commit those tests as they don't need to be reviewed again. (Eventually we
may want to factor them out and share them from a single place, e.g. the
test directory.)
The existing file fmtmap_test.go was updated.
Change-Id: I9bd0ad1a7e7188b501423483a44d18e623c0fe71
Reviewed-on: https://go-review.googlesource.com/c/go/+/263624
Trust: Robert Griesemer <gri@golang.org>
Trust: Keith Randall <khr@golang.org>
Run-TryBot: Robert Griesemer <gri@golang.org>
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Diffstat (limited to 'src')
90 files changed, 24300 insertions, 3 deletions
diff --git a/src/cmd/compile/fmtmap_test.go b/src/cmd/compile/fmtmap_test.go index a3d09576a7..bcc82dda2f 100644 --- a/src/cmd/compile/fmtmap_test.go +++ b/src/cmd/compile/fmtmap_test.go @@ -42,6 +42,10 @@ var knownFormats = map[string]string{ "*cmd/compile/internal/ssa.Value %s": "", "*cmd/compile/internal/ssa.Value %v": "", "*cmd/compile/internal/ssa.sparseTreeMapEntry %v": "", + "*cmd/compile/internal/syntax.CallExpr %s": "", + "*cmd/compile/internal/syntax.CallExpr %v": "", + "*cmd/compile/internal/syntax.FuncLit %s": "", + "*cmd/compile/internal/syntax.IndexExpr %s": "", "*cmd/compile/internal/types.Field %p": "", "*cmd/compile/internal/types.Field %v": "", "*cmd/compile/internal/types.Sym %0S": "", @@ -58,6 +62,25 @@ var knownFormats = map[string]string{ "*cmd/compile/internal/types.Type %p": "", "*cmd/compile/internal/types.Type %s": "", "*cmd/compile/internal/types.Type %v": "", + "*cmd/compile/internal/types2.Basic %s": "", + "*cmd/compile/internal/types2.Chan %s": "", + "*cmd/compile/internal/types2.Func %s": "", + "*cmd/compile/internal/types2.Initializer %s": "", + "*cmd/compile/internal/types2.Interface %s": "", + "*cmd/compile/internal/types2.MethodSet %s": "", + "*cmd/compile/internal/types2.Named %s": "", + "*cmd/compile/internal/types2.Named %v": "", + "*cmd/compile/internal/types2.Package %s": "", + "*cmd/compile/internal/types2.Package %v": "", + "*cmd/compile/internal/types2.Scope %p": "", + "*cmd/compile/internal/types2.Selection %s": "", + "*cmd/compile/internal/types2.Signature %s": "", + "*cmd/compile/internal/types2.TypeName %s": "", + "*cmd/compile/internal/types2.TypeName %v": "", + "*cmd/compile/internal/types2.TypeParam %s": "", + "*cmd/compile/internal/types2.Var %s": "", + "*cmd/compile/internal/types2.operand %s": "", + "*cmd/compile/internal/types2.substMap %s": "", "*cmd/internal/obj.Addr %v": "", "*cmd/internal/obj.LSym %v": "", "*math/big.Float %f": "", @@ -67,6 +90,8 @@ var knownFormats = map[string]string{ "[16]byte %x": "", "[]*cmd/compile/internal/ssa.Block %v": "", "[]*cmd/compile/internal/ssa.Value %v": "", + "[]*cmd/compile/internal/types2.Func %v": "", + "[]*cmd/compile/internal/types2.TypeName %s": "", "[][]string %q": "", "[]byte %s": "", "[]byte %x": "", @@ -75,6 +100,8 @@ var knownFormats = map[string]string{ "[]cmd/compile/internal/ssa.posetNode %v": "", "[]cmd/compile/internal/ssa.posetUndo %v": "", "[]cmd/compile/internal/syntax.token %s": "", + "[]cmd/compile/internal/types2.Type %s": "", + "[]int %v": "", "[]string %v": "", "[]uint32 %v": "", "bool %v": "", @@ -100,6 +127,7 @@ var knownFormats = map[string]string{ "cmd/compile/internal/gc.fmtMode %d": "", "cmd/compile/internal/gc.initKind %d": "", "cmd/compile/internal/gc.itag %v": "", + "cmd/compile/internal/importer.itag %v": "", "cmd/compile/internal/ssa.BranchPrediction %d": "", "cmd/compile/internal/ssa.Edge %v": "", "cmd/compile/internal/ssa.GCNode %v": "", @@ -122,9 +150,13 @@ var knownFormats = map[string]string{ "cmd/compile/internal/ssa.regMask %d": "", "cmd/compile/internal/ssa.register %d": "", "cmd/compile/internal/ssa.relation %s": "", + "cmd/compile/internal/syntax.ChanDir %d": "", + "cmd/compile/internal/syntax.Decl %T": "", "cmd/compile/internal/syntax.Error %q": "", "cmd/compile/internal/syntax.Error %v": "", "cmd/compile/internal/syntax.Expr %#v": "", + "cmd/compile/internal/syntax.Expr %T": "", + "cmd/compile/internal/syntax.Expr %s": "", "cmd/compile/internal/syntax.LitKind %d": "", "cmd/compile/internal/syntax.Node %T": "", "cmd/compile/internal/syntax.Operator %s": "", @@ -136,12 +168,22 @@ var knownFormats = map[string]string{ "cmd/compile/internal/types.EType %d": "", "cmd/compile/internal/types.EType %s": "", "cmd/compile/internal/types.EType %v": "", + "cmd/compile/internal/types2.Object %T": "", + "cmd/compile/internal/types2.Object %p": "", + "cmd/compile/internal/types2.Object %s": "", + "cmd/compile/internal/types2.Object %v": "", + "cmd/compile/internal/types2.Type %T": "", + "cmd/compile/internal/types2.Type %s": "", + "cmd/compile/internal/types2.Type %v": "", + "cmd/compile/internal/types2.color %s": "", "cmd/internal/obj.ABI %v": "", + "error %s": "", "error %v": "", "float64 %.2f": "", "float64 %.3f": "", "float64 %.6g": "", "float64 %g": "", + "go/constant.Value %s": "", "int %#x": "", "int %-12d": "", "int %-6d": "", @@ -174,9 +216,10 @@ var knownFormats = map[string]string{ "interface{} %q": "", "interface{} %s": "", "interface{} %v": "", - "map[*cmd/compile/internal/gc.Node]*cmd/compile/internal/ssa.Value %v": "", - "map[*cmd/compile/internal/gc.Node][]*cmd/compile/internal/gc.Node %v": "", - "map[cmd/compile/internal/ssa.ID]uint32 %v": "", + "map[*cmd/compile/internal/gc.Node]*cmd/compile/internal/ssa.Value %v": "", + "map[*cmd/compile/internal/gc.Node][]*cmd/compile/internal/gc.Node %v": "", + "map[*cmd/compile/internal/types2.TypeParam]cmd/compile/internal/types2.Type %s": "", + "map[cmd/compile/internal/ssa.ID]uint32 %v": "", "map[int64]uint32 %v": "", "math/big.Accuracy %s": "", "reflect.Type %s": "", @@ -186,6 +229,7 @@ var knownFormats = map[string]string{ "string %-*s": "", "string %-16s": "", "string %-6s": "", + "string %T": "", "string %q": "", "string %s": "", "string %v": "", @@ -205,6 +249,7 @@ var knownFormats = map[string]string{ "uint64 %08x": "", "uint64 %b": "", "uint64 %d": "", + "uint64 %v": "", "uint64 %x": "", "uint8 %#x": "", "uint8 %d": "", diff --git a/src/cmd/compile/internal/importer/exportdata.go b/src/cmd/compile/internal/importer/exportdata.go new file mode 100644 index 0000000000..3925a64314 --- /dev/null +++ b/src/cmd/compile/internal/importer/exportdata.go @@ -0,0 +1,92 @@ +// UNREVIEWED +// 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 importer + +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) == "!<arch>\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/cmd/compile/internal/importer/gcimporter.go b/src/cmd/compile/internal/importer/gcimporter.go new file mode 100644 index 0000000000..feb18cf2c9 --- /dev/null +++ b/src/cmd/compile/internal/importer/gcimporter.go @@ -0,0 +1,175 @@ +// UNREVIEWED +// 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 importer implements Import for gc-generated object files. +package importer + +import ( + "bufio" + "cmd/compile/internal/types2" + "fmt" + "go/build" + "io" + "io/ioutil" + "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(packages map[string]*types2.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types2.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 types2.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 types2.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 = ioutil.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(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 []*types2.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/cmd/compile/internal/importer/gcimporter_test.go b/src/cmd/compile/internal/importer/gcimporter_test.go new file mode 100644 index 0000000000..a275524484 --- /dev/null +++ b/src/cmd/compile/internal/importer/gcimporter_test.go @@ -0,0 +1,612 @@ +// UNREVIEWED +// 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 importer + +import ( + "bytes" + "cmd/compile/internal/types2" + "fmt" + "internal/testenv" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + "time" +) + +// 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) *types2.Package { + t0 := time.Now() + pkg, err := Import(make(map[string]*types2.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 := ioutil.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 := ioutil.TempDir("", "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 := ioutil.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) + } + + 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(make(map[string]*types2.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 := ioutil.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" + ioutil.WriteFile(filename, data, 0666) + + // test that importing the corrupted file results in an error + _, err = Import(make(map[string]*types2.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 has grown much larger - excluded for now + // {"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) + } + + 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(make(map[string]*types2.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 := types2.ObjectString(obj, types2.RelativeTo(pkg)) + if got != test.want { + t.Errorf("%s: got %q; want %q", test.name, got, test.want) + } + + if named, _ := obj.Type().(*types2.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 *types2.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().(*types2.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().(*types2.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).(*types2.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.(*types2.TypeName); tname != nil { + named := tname.Type().(*types2.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]*types2.Package) + _, err := Import(imports, "net/http", ".", nil) + if err != nil { + t.Fatal(err) + } + + mutex := imports["sync"].Scope().Lookup("Mutex").(*types2.TypeName).Type() + mset := types2.NewMethodSet(types2.NewPointer(mutex)) // methods of *sync.Mutex + sel := mset.Lookup(nil, "Lock") + lock := sel.Obj().(*types2.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 + imports := make(map[string]*types2.Package) + _, err := Import(imports, "go/internal/gcimporter", ".", nil) + if err != nil { + t.Fatal(err) + } + + // look for go/types package + var goTypesPkg *types2.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/types2.Object type + obj := lookupObj(t, goTypesPkg.Scope(), "Object") + typ, ok := obj.Type().(*types2.Named) + if !ok { + t.Fatalf("go/types2.Object type is %v; wanted named type", typ) + } + + // lookup go/types2.Object.Pkg method + m, index, indirect := types2.LookupFieldOrMethod(typ, false, nil, "Pkg") + if m == nil { + t.Fatalf("go/types2.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]*types2.Package) + for i := 0; i < 3; i++ { + if _, err := Import(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 := types2.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) *types2.Package { + pkg, err := Import(make(map[string]*types2.Package), path, srcDir, nil) + if err != nil { + t.Fatal(err) + } + return pkg +} + +func compileAndImportPkg(t *testing.T, name string) *types2.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 *types2.Scope, name string) types2.Object { + if obj := scope.Lookup(name); obj != nil { + return obj + } + t.Fatalf("%s not found", name) + return nil +} diff --git a/src/cmd/compile/internal/importer/iimport.go b/src/cmd/compile/internal/importer/iimport.go new file mode 100644 index 0000000000..b9c1ccfb66 --- /dev/null +++ b/src/cmd/compile/internal/importer/iimport.go @@ -0,0 +1,616 @@ +// UNREVIEWED +// 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 importer + +import ( + "bytes" + "cmd/compile/internal/syntax" + "cmd/compile/internal/types2" + "encoding/binary" + "fmt" + "go/constant" + "go/token" + "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(imports map[string]*types2.Package, data []byte, path string) (_ int, pkg *types2.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]*types2.Package), + + declData: declData, + pkgIndex: make(map[*types2.Package]map[string]uint64), + typCache: make(map[uint64]types2.Type), + } + + for i, pt := range predeclared { + p.typCache[uint64(i)] = pt + } + + pkgList := make([]*types2.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 = types2.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(([]*types2.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]*types2.Package + + declData []byte + pkgIndex map[*types2.Package]map[string]uint64 + typCache map[uint64]types2.Type + + interfaceList []*types2.Interface +} + +func (p *iimporter) doDecl(pkg *types2.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) *types2.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 *types2.Named) types2.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 *types2.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(types2.NewTypeName(pos, r.currPkg, name, typ)) + + case 'C': + typ, val := r.value() + + r.declare(types2.NewConst(pos, r.currPkg, name, typ, val)) + + case 'F': + sig := r.signature(nil) + + r.declare(types2.NewFunc(pos, r.currPkg, name, sig)) + + case 'T': + // Types can be recursive. We need to setup a stub + // declaration before recursing. + obj := types2.NewTypeName(pos, r.currPkg, name, nil) + named := types2.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(types2.NewFunc(mpos, r.currPkg, mname, msig)) + } + } + + case 'V': + typ := r.typ() + + r.declare(types2.NewVar(pos, r.currPkg, name, typ)) + + default: + errorf("unexpected tag: %v", tag) + } +} + +func (r *importReader) declare(obj types2.Object) { + obj.Pkg().Scope().Insert(obj) +} + +func (r *importReader) value() (typ types2.Type, val constant.Value) { + typ = r.typ() + + switch b := typ.Underlying().(*types2.Basic); b.Info() & types2.IsConstType { + case types2.IsBoolean: + val = constant.MakeBool(r.bool()) + + case types2.IsString: + val = constant.MakeString(r.string()) + + case types2.IsInteger: + val = r.mpint(b) + + case types2.IsFloat: + val = r.mpfloat(b) + + case types2.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 *types2.Basic) (signed bool, maxBytes uint) { + if (b.Info() & types2.IsUntyped) != 0 { + return true, 64 + } + + switch b.Kind() { + case types2.Float32, types2.Complex64: + return true, 3 + case types2.Float64, types2.Complex128: + return true, 7 + } + + signed = (b.Info() & types2.IsUnsigned) == 0 + switch b.Kind() { + case types2.Int8, types2.Uint8: + maxBytes = 1 + case types2.Int16, types2.Uint16: + maxBytes = 2 + case types2.Int32, types2.Uint32: + maxBytes = 4 + default: + maxBytes = 8 + } + + return +} + +func (r *importReader) mpint(b *types2.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 *types2.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() (*types2.Package, string) { + name := r.string() + pkg := r.pkg() + return pkg, name +} + +func (r *importReader) pos() syntax.Pos { + if r.p.version >= 1 { + r.posv1() + } else { + r.posv0() + } + + if r.prevFile == "" && r.prevLine == 0 && r.prevColumn == 0 { + return syntax.Pos{} + } + // TODO(gri) fix this + // return r.p.fake.pos(r.prevFile, int(r.prevLine), int(r.prevColumn)) + return syntax.Pos{} +} + +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() types2.Type { + return r.p.typAt(r.uint64(), nil) +} + +func isInterface(t types2.Type) bool { + _, ok := t.(*types2.Interface) + return ok +} + +func (r *importReader) pkg() *types2.Package { return r.p.pkgAt(r.uint64()) } +func (r *importReader) string() string { return r.p.stringAt(r.uint64()) } + +func (r *importReader) doType(base *types2.Named) types2.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).(*types2.TypeName).Type() + case pointerType: + return types2.NewPointer(r.typ()) + case sliceType: + return types2.NewSlice(r.typ()) + case arrayType: + n := r.uint64() + return types2.NewArray(r.typ(), int64(n)) + case chanType: + dir := chanDir(int(r.uint64())) + return types2.NewChan(dir, r.typ()) + case mapType: + return types2.NewMap(r.typ(), r.typ()) + case signatureType: + r.currPkg = r.pkg() + return r.signature(nil) + + case structType: + r.currPkg = r.pkg() + + fields := make([]*types2.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] = types2.NewField(fpos, r.currPkg, fname, ftyp, emb) + tags[i] = tag + } + return types2.NewStruct(fields, tags) + + case interfaceType: + r.currPkg = r.pkg() + + embeddeds := make([]types2.Type, r.uint64()) + for i := range embeddeds { + _ = r.pos() + embeddeds[i] = r.typ() + } + + methods := make([]*types2.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 *types2.Var + if base != nil { + recv = types2.NewVar(syntax.Pos{}, r.currPkg, "", base) + } + + msig := r.signature(recv) + methods[i] = types2.NewFunc(mpos, r.currPkg, mname, msig) + } + + typ := types2.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 *types2.Var) *types2.Signature { + params := r.paramList() + results := r.paramList() + variadic := params.Len() > 0 && r.bool() + return types2.NewSignature(recv, params, results, variadic) +} + +func (r *importReader) paramList() *types2.Tuple { + xs := make([]*types2.Var, r.uint64()) + for i := range xs { + xs[i] = r.param() + } + return types2.NewTuple(xs...) +} + +func (r *importReader) param() *types2.Var { + pos := r.pos() + name := r.ident() + typ := r.typ() + return types2.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/cmd/compile/internal/importer/support.go b/src/cmd/compile/internal/importer/support.go new file mode 100644 index 0000000000..4f013f4a51 --- /dev/null +++ b/src/cmd/compile/internal/importer/support.go @@ -0,0 +1,144 @@ +// UNREVIEWED +// 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 importer + +import ( + "cmd/compile/internal/types2" + "fmt" + "go/token" + "sync" +) + +func errorf(format string, args ...interface{}) { + panic(fmt.Sprintf(format, args...)) +} + +const deltaNewFile = -64 // see cmd/compile/internal/gc/bexport.go + +// 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) types2.ChanDir { + // tag values must match the constants in cmd/compile/internal/gc/go.go + switch d { + case 1 /* Crecv */ : + return types2.RecvOnly + case 2 /* Csend */ : + return types2.SendOnly + case 3 /* Cboth */ : + return types2.SendRecv + default: + errorf("unexpected channel dir %d", d) + return 0 + } +} + +var predeclared = []types2.Type{ + // basic types + types2.Typ[types2.Bool], + types2.Typ[types2.Int], + types2.Typ[types2.Int8], + types2.Typ[types2.Int16], + types2.Typ[types2.Int32], + types2.Typ[types2.Int64], + types2.Typ[types2.Uint], + types2.Typ[types2.Uint8], + types2.Typ[types2.Uint16], + types2.Typ[types2.Uint32], + types2.Typ[types2.Uint64], + types2.Typ[types2.Uintptr], + types2.Typ[types2.Float32], + types2.Typ[types2.Float64], + types2.Typ[types2.Complex64], + types2.Typ[types2.Complex128], + types2.Typ[types2.String], + + // basic type aliases + types2.Universe.Lookup("byte").Type(), + types2.Universe.Lookup("rune").Type(), + + // error + types2.Universe.Lookup("error").Type(), + + // untyped types + types2.Typ[types2.UntypedBool], + types2.Typ[types2.UntypedInt], + types2.Typ[types2.UntypedRune], + types2.Typ[types2.UntypedFloat], + types2.Typ[types2.UntypedComplex], + types2.Typ[types2.UntypedString], + types2.Typ[types2.UntypedNil], + + // package unsafe + types2.Typ[types2.UnsafePointer], + + // invalid type + types2.Typ[types2.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() types2.Type { return t } +func (t anyType) Under() types2.Type { return t } +func (t anyType) String() string { return "any" } + +// types2.aType is not exported for now so we need to implemented these here. +func (anyType) Basic() *types2.Basic { return nil } +func (anyType) Array() *types2.Array { return nil } +func (anyType) Slice() *types2.Slice { return nil } +func (anyType) Struct() *types2.Struct { return nil } +func (anyType) Pointer() *types2.Pointer { return nil } +func (anyType) Tuple() *types2.Tuple { return nil } +func (anyType) Signature() *types2.Signature { return nil } +func (anyType) Sum() *types2.Sum { return nil } +func (anyType) Interface() *types2.Interface { return nil } +func (anyType) Map() *types2.Map { return nil } +func (anyType) Chan() *types2.Chan { return nil } +func (anyType) Named() *types2.Named { return nil } +func (anyType) TypeParam() *types2.TypeParam { return nil } diff --git a/src/cmd/compile/internal/importer/testdata/a.go b/src/cmd/compile/internal/importer/testdata/a.go new file mode 100644 index 0000000000..06dafee98c --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/a.go @@ -0,0 +1,15 @@ +// UNREVIEWED +// 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/cmd/compile/internal/importer/testdata/b.go b/src/cmd/compile/internal/importer/testdata/b.go new file mode 100644 index 0000000000..a601dbccc5 --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/b.go @@ -0,0 +1,12 @@ +// UNREVIEWED +// 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/cmd/compile/internal/importer/testdata/exports.go b/src/cmd/compile/internal/importer/testdata/exports.go new file mode 100644 index 0000000000..2a720fd2c1 --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/exports.go @@ -0,0 +1,89 @@ +// UNREVIEWED +// 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/cmd/compile/internal/importer/testdata/issue15920.go b/src/cmd/compile/internal/importer/testdata/issue15920.go new file mode 100644 index 0000000000..b402026162 --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/issue15920.go @@ -0,0 +1,12 @@ +// UNREVIEWED +// 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/cmd/compile/internal/importer/testdata/issue20046.go b/src/cmd/compile/internal/importer/testdata/issue20046.go new file mode 100644 index 0000000000..e412f353ad --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/issue20046.go @@ -0,0 +1,10 @@ +// UNREVIEWED +// 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/cmd/compile/internal/importer/testdata/issue25301.go b/src/cmd/compile/internal/importer/testdata/issue25301.go new file mode 100644 index 0000000000..a9dc1d7f08 --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/issue25301.go @@ -0,0 +1,18 @@ +// UNREVIEWED +// 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/cmd/compile/internal/importer/testdata/issue25596.go b/src/cmd/compile/internal/importer/testdata/issue25596.go new file mode 100644 index 0000000000..95bef42280 --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/issue25596.go @@ -0,0 +1,14 @@ +// UNREVIEWED +// 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/cmd/compile/internal/importer/testdata/p.go b/src/cmd/compile/internal/importer/testdata/p.go new file mode 100644 index 0000000000..34a20eaa14 --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/p.go @@ -0,0 +1,14 @@ +// UNREVIEWED +// 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/cmd/compile/internal/importer/testdata/versions/test.go b/src/cmd/compile/internal/importer/testdata/versions/test.go new file mode 100644 index 0000000000..2f8eb5ced0 --- /dev/null +++ b/src/cmd/compile/internal/importer/testdata/versions/test.go @@ -0,0 +1,29 @@ +// UNREVIEWED +// 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/cmd/compile/internal/types2/api.go b/src/cmd/compile/internal/types2/api.go new file mode 100644 index 0000000000..eff90d4cdf --- /dev/null +++ b/src/cmd/compile/internal/types2/api.go @@ -0,0 +1,426 @@ +// UNREVIEWED +// 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 (syntax.Name) 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 (syntax.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 (syntax.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 types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "go/constant" +) + +// 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 { + Pos syntax.Pos // error position + Msg string // default error message, user-friendly + Full string // full error message, for debugging (may contain internal details) + Soft bool // if set, error is "soft" +} + +// Error returns an error string formatted as follows: +// filename:line:column: message +func (err Error) Error() string { + return fmt.Sprintf("%s: %s", err.Pos, err.Msg) +} + +// FullError returns an error string like Error, buy it may contain +// type-checker internal details such as subscript indices for type +// parameters and more. Useful for debugging. +func (err Error) FullError() string { + return fmt.Sprintf("%s: %s", err.Pos, err.Full) +} + +// 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 AcceptMethodTypeParams is set, methods may have type parameters. + AcceptMethodTypeParams bool + + // If InferFromConstraints is set, constraint type inference is used + // if some function type arguments are missing. + InferFromConstraints 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 Trace is set, a debug trace is printed to stdout. + Trace 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[syntax.Expr]TypeAndValue + + // Inferred maps calls of parameterized functions that use + // type inference to the inferred type arguments and signature + // of the function called. The recorded "call" expression may be + // an *ast.CallExpr (as in f(x)), or an *ast.IndexExpr (s in f[T]). + Inferred map[syntax.Expr]Inferred + + // 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[*syntax.Name]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[*syntax.Name]Object + + // Implicits maps nodes to their implicitly declared objects, if any. + // The following node and object types may appear: + // + // node declared object + // + // *syntax.ImportDecl *PkgName for imports without renames + // *syntax.CaseClause type-specific *Var for each type switch case clause (incl. default) + // *syntax.Field anonymous parameter *Var (incl. unnamed results) + // + Implicits map[syntax.Node]Object + + // Selections maps selector expressions (excluding qualified identifiers) + // to their corresponding selections. + Selections map[*syntax.SelectorExpr]*Selection + + // Scopes maps syntax.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: + // + // *syntax.File + // *syntax.FuncType + // *syntax.BlockStmt + // *syntax.IfStmt + // *syntax.SwitchStmt + // *syntax.CaseClause + // *syntax.CommClause + // *syntax.ForStmt + // + Scopes map[syntax.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 syntax.Expr) Type { + if t, ok := info.Types[e]; ok { + return t.Type + } + if id, _ := e.(*syntax.Name); 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 *syntax.Name) 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 +} + +// Inferred reports the inferred type arguments and signature +// for a parameterized function call that uses type inference. +type Inferred struct { + Targs []Type + Sig *Signature +} + +// 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 syntax.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 *syntax.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, files []*syntax.File, info *Info) (*Package, error) { + pkg := NewPackage(path, "") + return pkg, NewChecker(conf, 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, false) + 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} + return x.assignableTo(nil, T, nil) // check not needed for non-constant x +} + +// 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/cmd/compile/internal/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go new file mode 100644 index 0000000000..403df3f941 --- /dev/null +++ b/src/cmd/compile/internal/types2/api_test.go @@ -0,0 +1,1741 @@ +// UNREVIEWED +// 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 types2_test + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "internal/testenv" + "reflect" + "regexp" + "strings" + "testing" + + . "cmd/compile/internal/types2" +) + +func unimplemented() { + panic("unimplemented") +} + +func parseSrc(path, src string) (*syntax.File, error) { + errh := func(error) {} // dummy error handler so that parsing continues in presence of errors + return syntax.Parse(syntax.NewFileBase(path), strings.NewReader(src), errh, nil, syntax.AllowGenerics) +} + +func pkgFor(path, source string, info *Info) (*Package, error) { + f, err := parseSrc(path, source) + if err != nil { + return nil, err + } + conf := Config{Importer: defaultImporter()} + return conf.Check(f.PkgName.Value, []*syntax.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, error) { + f, err := parseSrc(path, source) + if f == nil { // ignore errors unless f is nil + t.Fatalf("%s: unable to parse: %s", path, err) + } + conf := Config{ + AcceptMethodTypeParams: true, + InferFromConstraints: true, + Error: func(err error) {}, + Importer: defaultImporter(), + } + pkg, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, info) + return pkg.Name(), err +} + +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[syntax.Expr]TypeAndValue), + } + name := mustTypecheck(t, "ValuesInfo", test.src, &info) + + // look for expression + var expr syntax.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`}, + + // parameterized functions + {`package p0; func f[T any](T); var _ = f[int]`, `f`, `func[T₁ any](T₁)`}, + {`package p1; func f[T any](T); var _ = f[int]`, `f[int]`, `func(int)`}, + {`package p2; func f[T any](T); var _ = f(42)`, `f`, `func[T₁ any](T₁)`}, + {`package p2; func f[T any](T); var _ = f(42)`, `f(42)`, `()`}, + + // type parameters + {`package t0; type t[] int; var _ t`, `t`, `t0.t`}, // t[] is a syntax error that is ignored in this test in favor of t + {`package t1; type t[P any] int; var _ t[int]`, `t`, `t1.t[P₁ any]`}, + {`package t2; type t[P interface{}] int; var _ t[int]`, `t`, `t2.t[P₁ interface{}]`}, + {`package t3; type t[P, Q interface{}] int; var _ t[int, int]`, `t`, `t3.t[P₁, Q₂ interface{}]`}, + {`package t4; type t[P, Q interface{ m() }] int; var _ t[int, int]`, `t`, `t4.t[P₁, Q₂ interface{m()}]`}, + + // instantiated types must be sanitized + {`package g0; type t[P any] int; var x struct{ f t[int] }; var _ = x.f`, `x.f`, `g0.t[int]`}, + } + + for _, test := range tests { + info := Info{Types: make(map[syntax.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 TestInferredInfo(t *testing.T) { + var tests = []struct { + src string + fun string + targs []string + sig string + }{ + {`package p0; func f[T any](T); func _() { f(42) }`, + `f`, + []string{`int`}, + `func(int)`, + }, + {`package p1; func f[T any](T) T; func _() { f('@') }`, + `f`, + []string{`rune`}, + `func(rune) rune`, + }, + {`package p2; func f[T any](...T) T; func _() { f(0i) }`, + `f`, + []string{`complex128`}, + `func(...complex128) complex128`, + }, + {`package p3; func f[A, B, C any](A, *B, []C); func _() { f(1.2, new(string), []byte{}) }`, + `f`, + []string{`float64`, `string`, `byte`}, + `func(float64, *string, []byte)`, + }, + {`package p4; func f[A, B any](A, *B, ...[]B); func _() { f(1.2, new(byte)) }`, + `f`, + []string{`float64`, `byte`}, + `func(float64, *byte, ...[]byte)`, + }, + + // we don't know how to translate these but we can type-check them + {`package q0; type T struct{}; func (T) m[P any](P); func _(x T) { x.m(42) }`, + `x.m`, + []string{`int`}, + `func(int)`, + }, + {`package q1; type T struct{}; func (T) m[P any](P) P; func _(x T) { x.m(42) }`, + `x.m`, + []string{`int`}, + `func(int) int`, + }, + {`package q2; type T struct{}; func (T) m[P any](...P) P; func _(x T) { x.m(42) }`, + `x.m`, + []string{`int`}, + `func(...int) int`, + }, + {`package q3; type T struct{}; func (T) m[A, B, C any](A, *B, []C); func _(x T) { x.m(1.2, new(string), []byte{}) }`, + `x.m`, + []string{`float64`, `string`, `byte`}, + `func(float64, *string, []byte)`, + }, + {`package q4; type T struct{}; func (T) m[A, B any](A, *B, ...[]B); func _(x T) { x.m(1.2, new(byte)) }`, + `x.m`, + []string{`float64`, `byte`}, + `func(float64, *byte, ...[]byte)`, + }, + + {`package r0; type T[P any] struct{}; func (_ T[P]) m[Q any](Q); func _[P any](x T[P]) { x.m(42) }`, + `x.m`, + []string{`int`}, + `func(int)`, + }, + // TODO(gri) record method type parameters in syntax.FuncType so we can check this + // {`package r1; type T interface{ m[P any](P) }; func _(x T) { x.m(4.2) }`, + // `x.m`, + // []string{`float64`}, + // `func(float64)`, + // }, + + {`package s1; func f[T any, P interface{type *T}](x T); func _(x string) { f(x) }`, + `f`, + []string{`string`, `*string`}, + `func(x string)`, + }, + {`package s2; func f[T any, P interface{type *T}](x []T); func _(x []int) { f(x) }`, + `f`, + []string{`int`, `*int`}, + `func(x []int)`, + }, + {`package s3; type C[T any] interface{type chan<- T}; func f[T any, P C[T]](x []T); func _(x []int) { f(x) }`, + `f`, + []string{`int`, `chan<- int`}, + `func(x []int)`, + }, + {`package s4; type C[T any] interface{type chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T); func _(x []int) { f(x) }`, + `f`, + []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, + `func(x []int)`, + }, + + {`package t1; func f[T any, P interface{type *T}]() T; func _() { _ = f[string] }`, + `f`, + []string{`string`, `*string`}, + `func() string`, + }, + {`package t2; type C[T any] interface{type chan<- T}; func f[T any, P C[T]]() []T; func _() { _ = f[int] }`, + `f`, + []string{`int`, `chan<- int`}, + `func() []int`, + }, + {`package t3; type C[T any] interface{type chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T; func _() { _ = f[int] }`, + `f`, + []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, + `func() []int`, + }, + } + + for _, test := range tests { + info := Info{Inferred: make(map[syntax.Expr]Inferred)} + name, err := mayTypecheck(t, "InferredInfo", test.src, &info) + if err != nil { + t.Errorf("package %s: %v", name, err) + continue + } + + // look for inferred type arguments and signature + var targs []Type + var sig *Signature + for call, inf := range info.Inferred { + var fun syntax.Expr + switch x := call.(type) { + case *syntax.CallExpr: + fun = x.Fun + case *syntax.IndexExpr: + fun = x.X + default: + panic(fmt.Sprintf("unexpected call expression type %T", call)) + } + if ExprString(fun) == test.fun { + targs = inf.Targs + sig = inf.Sig + break + } + } + if targs == nil { + t.Errorf("package %s: no inferred information found for %s", name, test.fun) + continue + } + + // check that type arguments are correct + if len(targs) != len(test.targs) { + t.Errorf("package %s: got %d type arguments; want %d", name, len(targs), len(test.targs)) + continue + } + for i, targ := range targs { + if got := targ.String(); got != test.targs[i] { + t.Errorf("package %s, %d. type argument: got %s; want %s", name, i, got, test.targs[i]) + continue + } + } + + // check that signature is correct + if got := sig.String(); got != test.sig { + t.Errorf("package %s: got %s; want %s", name, got, test.sig) + } + } +} + +func TestDefsInfo(t *testing.T) { + var tests = []struct { + src string + obj string + want string + }{ + {`package p0; const x = 42`, `x`, `const p0.x untyped int`}, + {`package p1; const x int = 42`, `x`, `const p1.x int`}, + {`package p2; var x int`, `x`, `var p2.x int`}, + {`package p3; type x int`, `x`, `type p3.x int`}, + {`package p4; func f()`, `f`, `func p4.f()`}, + + // generic types must be sanitized + // (need to use sufficiently nested types to provoke unexpanded types) + {`package g0; type t[P any] P; const x = t[int](42)`, `x`, `const g0.x g0.t[int]`}, + {`package g1; type t[P any] P; var x = t[int](42)`, `x`, `var g1.x g1.t[int]`}, + {`package g2; type t[P any] P; type x struct{ f t[int] }`, `x`, `type g2.x struct{f g2.t[int]}`}, + {`package g3; type t[P any] P; func f(x struct{ f t[string] }); var g = f`, `g`, `var g3.g func(x struct{f g3.t[string]})`}, + } + + for _, test := range tests { + info := Info{ + Defs: make(map[*syntax.Name]Object), + } + name := mustTypecheck(t, "DefsInfo", test.src, &info) + + // find object + var def Object + for id, obj := range info.Defs { + if id.Value == test.obj { + def = obj + break + } + } + if def == nil { + t.Errorf("package %s: %s not found", name, test.obj) + continue + } + + if got := def.String(); got != test.want { + t.Errorf("package %s: got %s; want %s", name, got, test.want) + } + } +} + +func TestUsesInfo(t *testing.T) { + var tests = []struct { + src string + obj string + want string + }{ + {`package p0; func _() { _ = x }; const x = 42`, `x`, `const p0.x untyped int`}, + {`package p1; func _() { _ = x }; const x int = 42`, `x`, `const p1.x int`}, + {`package p2; func _() { _ = x }; var x int`, `x`, `var p2.x int`}, + {`package p3; func _() { type _ x }; type x int`, `x`, `type p3.x int`}, + {`package p4; func _() { _ = f }; func f()`, `f`, `func p4.f()`}, + + // generic types must be sanitized + // (need to use sufficiently nested types to provoke unexpanded types) + {`package g0; func _() { _ = x }; type t[P any] P; const x = t[int](42)`, `x`, `const g0.x g0.t[int]`}, + {`package g1; func _() { _ = x }; type t[P any] P; var x = t[int](42)`, `x`, `var g1.x g1.t[int]`}, + {`package g2; func _() { type _ x }; type t[P any] P; type x struct{ f t[int] }`, `x`, `type g2.x struct{f g2.t[int]}`}, + {`package g3; func _() { _ = f }; type t[P any] P; func f(x struct{ f t[string] })`, `f`, `func g3.f(x struct{f g3.t[string]})`}, + } + + for _, test := range tests { + info := Info{ + Uses: make(map[*syntax.Name]Object), + } + name := mustTypecheck(t, "UsesInfo", test.src, &info) + + // find object + var use Object + for id, obj := range info.Uses { + if id.Value == test.obj { + use = obj + break + } + } + if use == nil { + t.Errorf("package %s: %s not found", name, test.obj) + continue + } + + if got := use.String(); got != test.want { + t.Errorf("package %s: got %s; want %s", name, got, test.want) + } + } +} + +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[syntax.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 *syntax.ImportDecl: + got = "importSpec" + case *syntax.CaseClause: + got = "caseClause" + case *syntax.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`, `<missing>`}, + {`package m1; import p "os"; func _() { _ = p.Stdout }`, `p`, `<missing>`}, + {`package m2; const c = 0`, `c`, `<missing>`}, + {`package m3; type T int`, `T`, `<missing>`}, + {`package m4; var v int`, `v`, `<missing>`}, + {`package m5; func f() {}`, `f`, `<missing>`}, + {`package m6; func _(x int) {}`, `x`, `<missing>`}, + {`package m6; func _()(x int) { return }`, `x`, `<missing>`}, + {`package m6; type T int; func (x T) _() {}`, `x`, `<missing>`}, + } + + for _, test := range tests { + info := Info{Types: make(map[syntax.Expr]TypeAndValue)} + name := mustTypecheck(t, "PredicatesInfo", test.src, &info) + + // look for expression predicates + got := "<missing>" + 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", "switch:", + }}, + {`package p12; func _(t interface{}) { switch t := t; t.(type) {} }`, []string{ + "file:", "func:t", "switch:t", + }}, + {`package p13; func _(t interface{}) { switch x := t.(type) { case int: _ = x } }`, []string{ + "file:", "func:t", "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", "select:", + }}, + {`package p16; func _(c chan int) { select{ case i := <-c: x := i; _ = x} }`, []string{ + "file:", "func:c", "select: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", "for:i", "block:", + }}, + {`package p20; var s int; func _(a []int) { for i, x := range a { s += x; _ = i } }`, []string{ + "file:", "func:a", "for:i x", "block:", + }}, + } + + for _, test := range tests { + info := Info{Scopes: make(map[syntax.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 { + var kind string + switch node.(type) { + case *syntax.File: + kind = "file" + case *syntax.FuncType: + kind = "func" + case *syntax.BlockStmt: + kind = "block" + case *syntax.IfStmt: + kind = "if" + case *syntax.SwitchStmt: + kind = "switch" + case *syntax.SelectStmt: + kind = "select" + case *syntax.CaseClause: + kind = "case" + case *syntax.CommClause: + kind = "comm" + case *syntax.ForStmt: + kind = "for" + default: + kind = fmt.Sprintf("%T", node) + } + + // 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) { + mustParse := func(src string) *syntax.File { + f, err := parseSrc("main", src) + 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 []*syntax.File + want string + }{ + {[]*syntax.File{fileA, fileB}, "[a = 1 b = 2]"}, + {[]*syntax.File{fileB, fileA}, "[b = 2 a = 1]"}, + } { + var info Info + if _, err := new(Config).Check("main", 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 + pkg := NewPackage("p", "p") + var info Info + check := NewChecker(&conf, pkg, &info) + + for i, src := range sources { + filename := fmt.Sprintf("sources%d", i) + f, err := parseSrc(filename, src) + if err != nil { + t.Fatal(err) + } + if err := check.Files([]*syntax.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) { + t.Skip("requires fixes around source positions") + + selections := make(map[*syntax.SelectorExpr]*Selection) + + imports := make(testImporter) + conf := Config{Importer: imports} + makePkg := func(path, src string) { + f, err := parseSrc(path+".go", src) + if err != nil { + t.Fatal(err) + } + pkg, err := conf.Check(path, []*syntax.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 + + unimplemented() + _ = e + // 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()), + } + unimplemented() + _ = got + // 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) { + unimplemented() + // 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 syntax.Selection found with syntax %q", syntax) + } +} + +func TestIssue8518(t *testing.T) { + 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 := parseSrc(path, src) + if err != nil { + t.Fatal(err) + } + pkg, _ := conf.Check(path, []*syntax.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 within the source. +func TestScopeLookupParent(t *testing.T) { + imports := make(testImporter) + conf := Config{Importer: imports} + var info Info + makePkg := func(path, src string) { + f, err := parseSrc(path, src) + if err != nil { + t.Fatal(err) + } + imports[path], err = conf.Check(path, []*syntax.File{f}, &info) + if err != nil { + t.Fatal(err) + } + } + + makePkg("lib", "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[*syntax.Name]Object) + makePkg("main", mainSrc) + mainScope := imports["main"].Scope() + + rx := regexp.MustCompile(`^/\*(\w*)=([\w:]*)\*/$`) + + base := syntax.NewFileBase("main") + syntax.CommentsDo(strings.NewReader(mainSrc), func(line, col uint, text string) { + pos := syntax.MakePos(base, line, col) + + // Syntax errors are not comments. + if text[0] != '/' { + t.Errorf("%s: %s", pos, text) + return + } + + // Parse the assertion in the comment. + m := rx.FindStringSubmatch(text) + if m == nil { + t.Errorf("%s: bad comment: %s", pos, text) + return + } + name, want := m[1], m[2] + + // Look up the name in the innermost enclosing scope. + inner := mainScope.Innermost(pos) + if inner == nil { + t.Errorf("%s: at %s: can't find innermost scope", pos, text) + return + } + got := "undef" + if _, obj := inner.LookupParent(name, pos); obj != nil { + kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types2.")) + got = fmt.Sprintf("%s:%d", kind, obj.Pos().Line()) + } + if got != want { + t.Errorf("%s: at %s: %s resolved to %s, want %s", pos, 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", id.Pos(), id.Value) + 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.Value == "X" { + continue + } + + _, gotObj := inner.LookupParent(id.Value, id.Pos()) + if gotObj != wantObj { + t.Errorf("%s: got %v, want %v", id.Pos(), gotObj, wantObj) + continue + } + } +} + +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)" + f, err := parseSrc("issue15305.go", src) + if err != nil { + t.Fatal(err) + } + conf := Config{ + Error: func(err error) {}, // allow errors + } + info := &Info{ + Types: make(map[syntax.Expr]TypeAndValue), + } + conf.Check("p", []*syntax.File{f}, info) // ignore result + for e, tv := range info.Types { + if _, ok := e.(*syntax.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}`}, + } { + f, err := parseSrc(test.lit, "package p; var _ = "+test.lit) + if err != nil { + t.Fatalf("%s: %v", test.lit, err) + } + + info := &Info{ + Types: make(map[syntax.Expr]TypeAndValue), + } + if _, err = new(Config).Check("p", []*syntax.File{f}, info); err != nil { + t.Fatalf("%s: %v", test.lit, err) + } + + cmptype := func(x syntax.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.DeclList[0].(*syntax.VarDecl).Values + cmptype(rhs, test.typ) + + // test type of composite literal type expression + cmptype(rhs.(*syntax.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) } +` + + f, err := parseSrc("src", src) + if err != nil { + t.Fatal(err) + } + + info := &Info{ + Defs: make(map[*syntax.Name]Object), + } + if _, err = new(Config).Check("p", []*syntax.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.Value != "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) } +` + f, err := parseSrc("src", src) + if err != nil { + t.Fatal(err) + } + files := []*syntax.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[*syntax.Name]Object), + } + pkg, _ := conf.Check("p", 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.Value == "foo" { + if obj, ok := obj.(*PkgName); ok { + if obj.Imported() != imp { + t.Errorf("%s resolved to %v; want %v", ident.Value, obj.Imported(), imp) + } + } else { + t.Errorf("%s resolved to %v; want package name", ident.Value, obj) + } + } + } + } +} diff --git a/src/cmd/compile/internal/types2/assignments.go b/src/cmd/compile/internal/types2/assignments.go new file mode 100644 index 0000000000..93d3255686 --- /dev/null +++ b/src/cmd/compile/internal/types2/assignments.go @@ -0,0 +1,359 @@ +// UNREVIEWED +// 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 types2 + +import "cmd/compile/internal/syntax" + +// 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: + // we may get here because of other problems (issue #39634, crash 12) + check.errorf(x, "cannot assign %s to %s in %s", x, T, context) + return + } + + 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, "use of untyped nil in %s", context) + x.mode = invalid + return + } + target = Default(x.typ) + } + check.convertUntyped(x, target) + if x.mode == invalid { + return + } + } + // x.typ is typed + + // A generic (non-instantiated) function value cannot be assigned to a variable. + if sig := x.typ.Signature(); sig != nil && len(sig.tparams) > 0 { + check.errorf(x, "cannot use generic function %s without instantiation in %s", x, context) + } + + // 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 + } + + if reason := ""; !x.assignableTo(check, T, &reason) { + if reason != "" { + check.errorf(x, "cannot use %s as %s value in %s: %s", x, T, context, reason) + } else { + check.errorf(x, "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, "%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, "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 syntax.Expr, x *operand) Type { + if x.mode == invalid || x.typ == Typ[Invalid] { + check.useLHS(lhs) + return nil + } + + // Determine if the lhs is a (possibly parenthesized) identifier. + ident, _ := unparen(lhs).(*syntax.Name) + + // Don't evaluate lhs if it is the blank identifier. + if ident != nil && ident.Value == "_" { + 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.Value); 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.(*syntax.SelectorExpr); ok { + var op operand + check.expr(&op, sel.X) + if op.mode == mapindex { + check.errorf(&z, "cannot assign to struct field %s in map", ExprString(z.expr)) + return nil + } + } + check.errorf(&z, "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, orig_rhs []syntax.Expr, returnPos syntax.Pos) { + rhs, commaOk := check.exprList(orig_rhs, len(lhs) == 2 && !returnPos.IsKnown()) + + if len(lhs) != len(rhs) { + // invalidate lhs + for _, obj := range lhs { + if obj.typ == nil { + obj.typ = Typ[Invalid] + } + } + // don't report an error if we already reported one + for _, x := range rhs { + if x.mode == invalid { + return + } + } + if returnPos.IsKnown() { + check.errorf(returnPos, "wrong number of return values (want %d, got %d)", len(lhs), len(rhs)) + return + } + check.errorf(rhs[0], "cannot initialize %d variables with %d values", len(lhs), len(rhs)) + return + } + + context := "assignment" + if returnPos.IsKnown() { + context = "return statement" + } + + if commaOk { + var a [2]Type + for i := range a { + a[i] = check.initVar(lhs[i], rhs[i], context) + } + check.recordCommaOkTypes(orig_rhs[0], a) + return + } + + for i, lhs := range lhs { + check.initVar(lhs, rhs[i], context) + } +} + +func (check *Checker) assignVars(lhs, orig_rhs []syntax.Expr) { + rhs, commaOk := check.exprList(orig_rhs, len(lhs) == 2) + + if len(lhs) != len(rhs) { + check.useLHS(lhs...) + // don't report an error if we already reported one + for _, x := range rhs { + if x.mode == invalid { + return + } + } + check.errorf(rhs[0], "cannot assign %d values to %d variables", len(rhs), len(lhs)) + return + } + + if commaOk { + var a [2]Type + for i := range a { + a[i] = check.assignVar(lhs[i], rhs[i]) + } + check.recordCommaOkTypes(orig_rhs[0], a) + return + } + + for i, lhs := range lhs { + check.assignVar(lhs, rhs[i]) + } +} + +// unpack unpacks a *syntax.ListExpr into a list of syntax.Expr. +// Helper introduced for the go/types -> types2 port. +// TODO(gri) Should find a more efficient solution that doesn't +// require introduction of a new slice for simple +// expressions. +func unpackExpr(x syntax.Expr) []syntax.Expr { + if x, _ := x.(*syntax.ListExpr); x != nil { + return x.ElemList + } + if x != nil { + return []syntax.Expr{x} + } + return nil +} + +func (check *Checker) shortVarDecl(pos syntax.Pos, lhs, rhs []syntax.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.(*syntax.Name); 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.Value + 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, "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.errorf(lhs, "cannot declare %s", lhs) + } + if obj == nil { + obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable + } + lhsVars[i] = obj + } + + check.initVars(lhsVars, rhs, 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 := endPos(rhs[len(rhs)-1]) + for _, obj := range newVars { + check.declare(scope, nil, obj, scopePos) // recordObject already called + } + } else { + check.softErrorf(pos, "no new variables on left side of :=") + } +} diff --git a/src/cmd/compile/internal/types2/builtins.go b/src/cmd/compile/internal/types2/builtins.go new file mode 100644 index 0000000000..c1706fd873 --- /dev/null +++ b/src/cmd/compile/internal/types2/builtins.go @@ -0,0 +1,777 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" + "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 *syntax.CallExpr, id builtinId) (_ bool) { + // append is the only built-in that permits the use of ... for the last argument + bin := predeclaredFuncs[id] + if call.HasDots && id != _Append { + //check.invalidOpf(call.Ellipsis, "invalid use of ... with built-in %s", bin.name) + check.invalidOpf(call, "invalid use of ... with built-in %s", bin.name) + check.use(call.ArgList...) + 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 exprList because exprList evaluates + // all arguments. + if id == _Len || id == _Cap { + defer func(b bool) { + check.hasCallOrRecv = b + }(check.hasCallOrRecv) + check.hasCallOrRecv = false + } + + // determine actual arguments + var arg func(*operand, int) // TODO(gri) remove use of arg getter in favor of using xlist directly + nargs := len(call.ArgList) + switch id { + default: + // make argument getter + xlist, _ := check.exprList(call.ArgList, false) + arg = func(x *operand, i int) { *x = *xlist[i]; x.typ = expand(x.typ) } + nargs = len(xlist) + // 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.invalidOpf(call, "%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.Slice(); s != nil { + T = s.elem + } else { + check.invalidArgf(x, "%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.HasDots && x.assignableTo(check, NewSlice(universeByte), nil) { + 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 + var xlist []*operand + // convert []operand to []*operand + for i := range alist { + xlist = append(xlist, &alist[i]) + } + for i := len(alist); i < nargs; i++ { + var x operand + arg(&x, i) + xlist = append(xlist, &x) + } + check.arguments(call, sig, xlist) // discard result (we know the result type) + // 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(optype(x.typ.Under())); 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 + } + + case *Sum: + if t.is(func(t Type) bool { + switch t := t.Under().(type) { + case *Basic: + if isString(t) && id == _Len { + return true + } + case *Array, *Slice, *Chan: + return true + case *Map: + if id == _Len { + return true + } + } + return false + }) { + mode = value + } + } + + if mode == invalid && typ != Typ[Invalid] { + check.invalidArgf(x, "%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.Chan() + if c == nil { + check.invalidArgf(x, "%s is not a channel", x) + return + } + if c.dir == RecvOnly { + check.invalidArgf(x, "%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.invalidArgf(x, "mismatched types %s and %s", x.typ, y.typ) + return + } + + // the argument types must be of floating-point type + f := func(x Type) Type { + if t := x.Basic(); t != nil { + switch t.kind { + case Float32: + return Typ[Complex64] + case Float64: + return Typ[Complex128] + case UntypedFloat: + return Typ[UntypedComplex] + } + } + return nil + } + resTyp := check.applyTypeFunc(f, x.typ) + if resTyp == nil { + check.invalidArgf(x, "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 + } + + 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.Slice(); t != nil { + dst = t.elem + } + + var y operand + arg(&y, 1) + if y.mode == invalid { + return + } + var src Type + switch t := optype(y.typ.Under()).(type) { + case *Basic: + if isString(y.typ) { + src = universeByte + } + case *Slice: + src = t.elem + } + + if dst == nil || src == nil { + check.invalidArgf(x, "copy expects slice arguments; found %s and %s", x, &y) + return + } + + if !check.identical(dst, src) { + check.invalidArgf(x, "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.Map() + if m == nil { + check.invalidArgf(x, "%s is not a map", x) + return + } + arg(x, 1) // k + if x.mode == invalid { + return + } + + if !x.assignableTo(check, m.key, nil) { + check.invalidArgf(x, "%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 + f := func(x Type) Type { + if t := x.Basic(); t != nil { + switch t.kind { + case Complex64: + return Typ[Float32] + case Complex128: + return Typ[Float64] + case UntypedComplex: + return Typ[UntypedFloat] + } + } + return nil + } + resTyp := check.applyTypeFunc(f, x.typ) + if resTyp == nil { + check.invalidArgf(x, "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 + } + + 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.ArgList[0] + T := check.varType(arg0) + if T == Typ[Invalid] { + return + } + + min, max := -1, 10 + var valid func(t Type) bool + valid = func(t Type) bool { + var m int + switch t := optype(t.Under()).(type) { + case *Slice: + m = 2 + case *Map, *Chan: + m = 1 + case *Sum: + return t.is(valid) + default: + return false + } + if m > min { + min = m + } + if m+1 < max { + max = m + 1 + } + return true + } + + if !valid(T) { + check.invalidArgf(arg0, "cannot make %s; type must be slice, map, or channel", arg0) + return + } + if nargs < min || max < nargs { + if min == max { + check.errorf(call, "%v expects %d arguments; found %d", call, min, nargs) + } else { + check.errorf(call, "%v expects %d or %d arguments; found %d", call, min, max, nargs) + } + return + } + + types := []Type{T} + var sizes []int64 // constant integer arguments, if any + for _, arg := range call.ArgList[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.invalidArgf(call.ArgList[1], "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.varType(call.ArgList[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[*syntax.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 + if x.typ.TypeParam() != nil { + check.invalidOpf(call, "unsafe.Alignof undefined for %s", x) + return + } + 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.ArgList[0] + selx, _ := unparen(arg0).(*syntax.SelectorExpr) + if selx == nil { + check.invalidArgf(arg0, "%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.Value + obj, index, indirect := check.lookupFieldOrMethod(base, false, check.pkg, sel) + switch obj.(type) { + case nil: + check.invalidArgf(x, "%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.invalidArgf(arg0, "%s is a method value", arg0) + return + } + if indirect { + check.invalidArgf(x, "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 + if x.typ.TypeParam() != nil { + check.invalidOpf(call, "unsafe.Sizeof undefined for %s", x) + return + } + 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.invalidArgf(x, "%s is not a boolean constant", x) + return + } + if x.val.Kind() != constant.Bool { + check.errorf(x, "internal error: value of %s should be a boolean constant", x) + return + } + if !constant.BoolVal(x.val) { + check.errorf(call, "%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", posFor(call)) + x.mode = novalue + break + } + var t operand + x1 := x + for _, arg := range call.ArgList { + check.rawExpr(x1, arg, nil) // permit trace for types, e.g.: new(trace(T)) + check.dump("%v: %s", posFor(x1), 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 +} + +// applyTypeFunc applies f to x. If x is a type parameter, +// the result is a type parameter constrained by an new +// interface bound. The type bounds for that interface +// are computed by applying f to each of the type bounds +// of x. If any of these applications of f return nil, +// applyTypeFunc returns nil. +// If x is not a type parameter, the result is f(x). +func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type { + if tp := x.TypeParam(); tp != nil { + // Test if t satisfies the requirements for the argument + // type and collect possible result types at the same time. + var rtypes []Type + if !tp.Bound().is(func(x Type) bool { + if r := f(x); r != nil { + rtypes = append(rtypes, r) + return true + } + return false + }) { + return nil + } + + // TODO(gri) Would it be ok to return just the one type + // if len(rtypes) == 1? What about top-level + // uses of real() where the result is used to + // define type and initialize a variable? + + // construct a suitable new type parameter + tpar := NewTypeName(nopos, nil /* = Universe pkg */, "<type parameter>", nil) + ptyp := check.NewTypeParam(tp.ptr, tpar, 0, &emptyInterface) // assigns type to tpar as a side-effect + tsum := NewSum(rtypes) + ptyp.bound = &Interface{types: tsum, allMethods: markComplete, allTypes: tsum} + + return ptyp + } + + return f(x) +} + +// 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(nopos, nil, "", Default(param)) + } + params := NewTuple(list...) + var result *Tuple + if res != nil { + assert(!isUntyped(res)) + result = NewTuple(NewVar(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 := p.base.Array(); a != nil { + return a + } + } + return typ +} + +// unparen returns e with any enclosing parentheses stripped. +func unparen(e syntax.Expr) syntax.Expr { + for { + p, ok := e.(*syntax.ParenExpr) + if !ok { + return e + } + e = p.X + } +} diff --git a/src/cmd/compile/internal/types2/builtins_test.go b/src/cmd/compile/internal/types2/builtins_test.go new file mode 100644 index 0000000000..9f737bc9bb --- /dev/null +++ b/src/cmd/compile/internal/types2/builtins_test.go @@ -0,0 +1,219 @@ +// UNREVIEWED +// 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 types2_test + +import ( + "cmd/compile/internal/syntax" + "fmt" + "testing" + + . "cmd/compile/internal/types2" +) + +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 := parseSrc("", src) + if err != nil { + t.Errorf("%s: %s", src0, err) + return + } + + conf := Config{Importer: defaultImporter()} + uses := make(map[*syntax.Name]Object) + types := make(map[syntax.Expr]TypeAndValue) + _, err = conf.Check(f.PkgName.Value, []*syntax.File{f}, &Info{Uses: uses, Types: types}) + if err != nil { + t.Errorf("%s: %s", src0, err) + return + } + + // find called function + n := 0 + var fun syntax.Expr + for x := range types { + if call, _ := x.(*syntax.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 *syntax.Name: + obj := uses[p] + if obj == nil { + t.Errorf("%s: no object found for %s", src0, p.Value) + return + } + bin, _ := obj.(*Builtin) + if bin == nil { + t.Errorf("%s: %s does not denote a built-in", src0, p.Value) + return + } + if bin.Name() != name { + t.Errorf("%s: got built-in %s; want %s", src0, bin.Name(), name) + return + } + return // we're done + + case *syntax.ParenExpr: + fun = p.X // unpack + + case *syntax.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/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go new file mode 100644 index 0000000000..a29096322a --- /dev/null +++ b/src/cmd/compile/internal/types2/call.go @@ -0,0 +1,808 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" + "strings" + "unicode" +) + +// funcInst type-checks a function instantiaton inst and returns the result in x. +// The operand x must be the evaluation of inst.X and its type must be a signature. +func (check *Checker) funcInst(x *operand, inst *syntax.IndexExpr) { + args, ok := check.exprOrTypeList(unpackExpr(inst.Index)) + if ok && len(args) > 0 && args[0].mode != typexpr { + check.errorf(args[0], "%s is not a type", args[0]) + ok = false + } + if !ok { + x.mode = invalid + x.expr = inst + return + } + + // check number of type arguments + n := len(args) + sig := x.typ.(*Signature) + if !check.conf.InferFromConstraints && n != len(sig.tparams) || n > len(sig.tparams) { + check.errorf(args[n-1], "got %d type arguments but want %d", n, len(sig.tparams)) + x.mode = invalid + x.expr = inst + return + } + + // collect types + targs := make([]Type, n) + poslist := make([]syntax.Pos, n) + for i, a := range args { + if a.mode != typexpr { + // error was reported earlier + x.mode = invalid + x.expr = inst + return + } + targs[i] = a.typ + poslist[i] = a.Pos() + } + + // if we don't have enough type arguments, use constraint type inference + var inferred bool + if n < len(sig.tparams) { + var failed int + targs, failed = check.inferB(sig.tparams, targs) + if targs == nil { + // error was already reported + x.mode = invalid + x.expr = inst + return + } + if failed >= 0 { + // at least one type argument couldn't be inferred + assert(targs[failed] == nil) + tpar := sig.tparams[failed] + check.errorf(inst, "cannot infer %s (%s) (%s)", tpar.name, tpar.pos, targs) + x.mode = invalid + x.expr = inst + return + } + // all type arguments were inferred sucessfully + if debug { + for _, targ := range targs { + assert(targ != nil) + } + } + //check.dump("### inferred targs = %s", targs) + n = len(targs) + inferred = true + } + assert(n == len(sig.tparams)) + + // instantiate function signature + for i, typ := range targs { + // some positions may be missing if types are inferred + var pos syntax.Pos + if i < len(poslist) { + pos = poslist[i] + } + check.ordinaryType(pos, typ) + } + res := check.instantiate(x.Pos(), sig, targs, poslist).(*Signature) + assert(res.tparams == nil) // signature is not generic anymore + if inferred { + check.recordInferred(inst, targs, res) + } + x.typ = res + x.mode = value + x.expr = inst +} + +func (check *Checker) call(x *operand, call *syntax.CallExpr) exprKind { + check.exprOrType(x, call.Fun) + + switch x.mode { + case invalid: + check.use(call.ArgList...) + x.expr = call + return statement + + case typexpr: + // conversion + T := x.typ + x.mode = invalid + switch n := len(call.ArgList); n { + case 0: + check.errorf(call, "missing argument in conversion to %s", T) + case 1: + check.expr(x, call.ArgList[0]) + if x.mode != invalid { + if t := T.Interface(); t != nil { + check.completeInterface(nopos, t) + if t.IsConstraint() { + check.errorf(call, "cannot use interface %s in conversion (contains type list or is comparable)", T) + break + } + } + check.conversion(x, T) + } + default: + check.use(call.ArgList...) + check.errorf(call.ArgList[n-1], "too many arguments in conversion to %s", T) + } + x.expr = call + return conversion + + case builtin: + id := x.id + if !check.builtin(x, call, id) { + x.mode = invalid + } + x.expr = call + // 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.Signature() + if sig == nil { + check.invalidOpf(x, "cannot call non-function %s", x) + x.mode = invalid + x.expr = call + return statement + } + + // evaluate arguments + args, ok := check.exprOrTypeList(call.ArgList) + if !ok { + x.mode = invalid + x.expr = call + return expression + } + + sig = check.arguments(call, sig, args) + + // 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 = call + check.hasCallOrRecv = true + + // if type inference failed, a parametrized result must be invalidated + // (operands cannot have a parametrized type) + if x.mode == value && len(sig.tparams) > 0 && isParameterized(sig.tparams, x.typ) { + x.mode = invalid + } + + return statement + } +} + +// exprOrTypeList returns a list of operands and reports an error if the +// list contains a mix of values and types (ignoring invalid operands). +// TODO(gri) Now we can split this into exprList and typeList. +func (check *Checker) exprOrTypeList(elist []syntax.Expr) (xlist []*operand, ok bool) { + ok = true + + switch len(elist) { + case 0: + // nothing to do + + case 1: + // single (possibly comma-ok) value or type, or function returning multiple values + e := elist[0] + var x operand + check.multiExprOrType(&x, e) + if t, ok := x.typ.(*Tuple); ok && x.mode != invalid && x.mode != typexpr { + // multiple values + xlist = make([]*operand, t.Len()) + for i, v := range t.vars { + xlist[i] = &operand{mode: value, expr: e, typ: v.typ} + } + break + } + + check.instantiatedOperand(&x) + + // exactly one (possibly invalid or comma-ok) value or type + xlist = []*operand{&x} + + default: + // multiple (possibly invalid) values or types + xlist = make([]*operand, len(elist)) + ntypes := 0 + for i, e := range elist { + var x operand + check.exprOrType(&x, e) + xlist[i] = &x + switch x.mode { + case invalid: + ntypes = len(xlist) // make 'if' condition fail below (no additional error in this case) + case typexpr: + ntypes++ + check.instantiatedOperand(&x) + } + } + if 0 < ntypes && ntypes < len(xlist) { + check.errorf(xlist[0], "mix of value and type expressions") + ok = false + } + } + + return +} + +func (check *Checker) exprList(elist []syntax.Expr, allowCommaOk bool) (xlist []*operand, commaOk bool) { + switch len(elist) { + case 0: + // nothing to do + + case 1: + // single (possibly comma-ok) value, or function returning multiple values + e := elist[0] + var x operand + check.multiExpr(&x, e) + if t, ok := x.typ.(*Tuple); ok && x.mode != invalid { + // multiple values + xlist = make([]*operand, t.Len()) + for i, v := range t.vars { + xlist[i] = &operand{mode: value, expr: e, typ: v.typ} + } + break + } + + // exactly one (possibly invalid or comma-ok) value + xlist = []*operand{&x} + if allowCommaOk && (x.mode == mapindex || x.mode == commaok || x.mode == commaerr) { + x.mode = value + xlist = append(xlist, &operand{mode: value, expr: e, typ: Typ[UntypedBool]}) + commaOk = true + } + + default: + // multiple (possibly invalid) values + xlist = make([]*operand, len(elist)) + for i, e := range elist { + var x operand + check.expr(&x, e) + xlist[i] = &x + } + } + + return +} + +func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, args []*operand) (rsig *Signature) { + rsig = sig + + // TODO(gri) try to eliminate this extra verification loop + for _, a := range args { + switch a.mode { + case typexpr: + check.errorf(a, "%s used as value", a) + return + case invalid: + return + } + } + + // Function call argument/parameter count requirements + // + // | standard call | dotdotdot call | + // --------------+------------------+----------------+ + // standard func | nargs == npars | invalid | + // --------------+------------------+----------------+ + // variadic func | nargs >= npars-1 | nargs == npars | + // --------------+------------------+----------------+ + + nargs := len(args) + npars := sig.params.Len() + ddd := call.HasDots + + // set up parameters + sig_params := sig.params // adjusted for variadic functions (may be nil for empty parameter lists!) + adjusted := false // indicates if sig_params is different from t.params + if sig.variadic { + if ddd { + // variadic_func(a, b, c...) + if len(call.ArgList) == 1 && nargs > 1 { + // f()... is not permitted if f() is multi-valued + //check.errorf(call.Ellipsis, "cannot use ... with %d-valued %s", nargs, call.ArgList[0]) + check.errorf(call, "cannot use ... with %d-valued %s", nargs, call.ArgList[0]) + return + } + } else { + // variadic_func(a, b, c) + if nargs >= npars-1 { + // Create custom parameters for arguments: keep + // the first npars-1 parameters and add one for + // each argument mapping to the ... parameter. + vars := make([]*Var, npars-1) // npars > 0 for variadic functions + copy(vars, sig.params.vars) + last := sig.params.vars[npars-1] + typ := last.typ.(*Slice).elem + for len(vars) < nargs { + vars = append(vars, NewParam(last.pos, last.pkg, last.name, typ)) + } + sig_params = NewTuple(vars...) // possibly nil! + adjusted = true + npars = nargs + } else { + // nargs < npars-1 + npars-- // for correct error message below + } + } + } else { + if ddd { + // standard_func(a, b, c...) + //check.errorf(call.Ellipsis, "cannot use ... in call to non-variadic %s", call.Fun) + check.errorf(call, "cannot use ... in call to non-variadic %s", call.Fun) + return + } + // standard_func(a, b, c) + } + + // check argument count + switch { + case nargs < npars: + check.errorf(call, "not enough arguments in call to %s", call.Fun) + return + case nargs > npars: + check.errorf(args[npars], "too many arguments in call to %s", call.Fun) // report at first extra argument + return + } + + // infer type arguments and instantiate signature if necessary + if len(sig.tparams) > 0 { + // TODO(gri) provide position information for targs so we can feed + // it to the instantiate call for better error reporting + targs, failed := check.infer(sig.tparams, sig_params, args) + if targs == nil { + return // error already reported + } + if failed >= 0 { + // Some type arguments couldn't be inferred. Use + // bounds type inference to try to make progress. + if check.conf.InferFromConstraints { + targs, failed = check.inferB(sig.tparams, targs) + if targs == nil { + return // error already reported + } + } + if failed >= 0 { + // at least one type argument couldn't be inferred + assert(targs[failed] == nil) + tpar := sig.tparams[failed] + // TODO(gri) here we'd like to use the position of the call's ')' + check.errorf(call.Pos(), "cannot infer %s (%s) (%s)", tpar.name, tpar.pos, targs) + return + } + } + // all type arguments were inferred sucessfully + if debug { + for _, targ := range targs { + assert(targ != nil) + } + } + //check.dump("### inferred targs = %s", targs) + + // compute result signature + rsig = check.instantiate(call.Pos(), sig, targs, nil).(*Signature) + assert(rsig.tparams == nil) // signature is not generic anymore + check.recordInferred(call, targs, rsig) + + // Optimization: Only if the parameter list was adjusted do we + // need to compute it from the adjusted list; otherwise we can + // simply use the result signature's parameter list. + if adjusted { + sig_params = check.subst(call.Pos(), sig_params, makeSubstMap(sig.tparams, targs)).(*Tuple) + } else { + sig_params = rsig.params + } + } + + // check arguments + for i, a := range args { + check.assignment(a, sig_params.vars[i].typ, "argument") + } + + return +} + +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 *syntax.SelectorExpr) { + // these must be declared before the "goto Error" statements + var ( + obj Object + index []int + indirect bool + ) + + sel := e.Sel.Value + // 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.(*syntax.Name); ok { + obj := check.lookup(ident.Value) + 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, "%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, "%s not declared by package %s", sel, pkg.name) + } + goto Error + } + if !exp.Exported() { + check.errorf(e.Sel, "%s not exported by package %s", sel, pkg.name) + // ok to continue + } + } + check.recordUse(e.Sel, exp) + + // Simplified version of the code for *syntax.Names: + // - 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("%v: unexpected object %v", posFor(e.Sel), exp) + unreachable() + } + x.expr = e + return + } + } + + check.exprOrType(x, e.X) + if x.mode == invalid { + goto Error + } + + check.instantiatedOperand(x) + + 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, "ambiguous selector %s.%s", x.expr, sel) + case indirect: + check.errorf(e.Sel, "cannot call pointer method %s on %s", sel, x.typ) + default: + var why string + if tpar := x.typ.TypeParam(); tpar != nil { + // Type parameter bounds don't specify fields, so don't mention "field". + switch obj := tpar.Bound().obj.(type) { + case nil: + why = check.sprintf("type bound for %s has no method %s", x.typ, sel) + case *TypeName: + why = check.sprintf("interface %s has no method %s", obj.name, sel) + } + } else { + why = check.sprintf("type %s has no field or method %s", x.typ, sel) + } + + // 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 { + why += ", but does have " + changeCase + } + } + + check.errorf(e.Sel, "%s.%s undefined (%s)", x.expr, sel, why) + + } + goto Error + } + + // methods may not have a fully set up signature yet + if m, _ := obj.(*Func); m != nil { + // check.dump("### found method %s", m) + check.objDecl(m, nil) + // If m has a parameterized receiver type, infer the type parameter + // values from the actual receiver provided and then substitute the + // type parameters in the signature accordingly. + // TODO(gri) factor this code out + sig := m.typ.(*Signature) + if len(sig.rparams) > 0 { + //check.dump("### recv typ = %s", x.typ) + //check.dump("### method = %s rparams = %s tparams = %s", m, sig.rparams, sig.tparams) + // The method may have a pointer receiver, but the actually provided receiver + // may be a (hopefully addressable) non-pointer value, or vice versa. Here we + // only care about inferring receiver type parameters; to make the inference + // work, match up pointer-ness of receiver and argument. + arg := x + if ptrRecv := isPointer(sig.recv.typ); ptrRecv != isPointer(arg.typ) { + copy := *arg + if ptrRecv { + copy.typ = NewPointer(arg.typ) + } else { + copy.typ = arg.typ.(*Pointer).base + } + arg = © + } + targs, failed := check.infer(sig.rparams, NewTuple(sig.recv), []*operand{arg}) + //check.dump("### inferred targs = %s", targs) + if failed >= 0 { + // We may reach here if there were other errors (see issue #40056). + // check.infer will report a follow-up error. + // TODO(gri) avoid the follow-up error or provide better explanation. + goto Error + } + // Don't modify m. Instead - for now - make a copy of m and use that instead. + // (If we modify m, some tests will fail; possibly because the m is in use.) + // TODO(gri) investigate and provide a correct explanation here + copy := *m + copy.typ = check.subst(e.Pos(), m.typ, makeSubstMap(sig.rparams, targs)) + obj = © + } + // TODO(gri) we also need to do substitution for parameterized interface methods + // (this breaks code in testdata/linalg.go2 at the moment) + // 12/20/2019: Is this TODO still correct? + } + + 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, "%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{ + tparams: sig.tparams, + params: NewTuple(append([]*Var{NewVar(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) + + // TODO(gri) The verification pass below is disabled for now because + // method sets don't match method lookup in some cases. + // For instance, if we made a copy above when creating a + // custom method for a parameterized received type, the + // method set method doesn't match (no copy there). There + /// may be other situations. + disabled := true + if !disabled && 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", posFor(e), 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 +} + +// 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. +// TODO(gri) make this accept a []syntax.Expr and use an unpack function when we have a ListExpr? +func (check *Checker) use(arg ...syntax.Expr) { + var x operand + for _, e := range arg { + // Certain AST fields may legally be nil (e.g., the ast.SliceExpr.High field). + if e == nil { + continue + } + if l, _ := e.(*syntax.ListExpr); l != nil { + check.use(l.ElemList...) + continue + } + 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 ...syntax.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).(*syntax.Name); ident != nil { + // never type-check the blank name on the lhs + if ident.Value == "_" { + continue + } + if _, obj := check.scope.LookupParent(ident.Value, 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 + } + } +} + +// instantiatedOperand reports an error of x is an uninstantiated (generic) type and sets x.typ to Typ[Invalid]. +func (check *Checker) instantiatedOperand(x *operand) { + if x.mode == typexpr && isGeneric(x.typ) { + check.errorf(x, "cannot use generic type %s without instantiation", x.typ) + x.typ = Typ[Invalid] + } +} diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go new file mode 100644 index 0000000000..4504586545 --- /dev/null +++ b/src/cmd/compile/internal/types2/check.go @@ -0,0 +1,449 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" + "errors" + "fmt" + "go/constant" +) + +var nopos syntax.Pos + +// debugging/development support +const debug = true // leave on during development + +// If forceStrict 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 forceStrict = false + +// If methodTypeParamsOk is set, type parameters are +// permitted in method declarations (in interfaces, too). +// Generalization and experimental feature. +const methodTypeParamsOk = true + +// 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 syntax.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 + sig *Signature // function signature if inside a function; nil otherwise + isPanic map[*syntax.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 + pkg *Package + *Info + nextId uint64 // unique Id for type parameters (first valid Id is 1) + 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][]syntax.Pos // maps interface types to lists of embedded interface positions + typMap map[string]*Named // maps an instantiated named type hash to a *Named type + 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 []*syntax.File // package files + unusedDotImports map[*Scope]map[*Package]syntax.Pos // positions of unused dot-imported packages for each file scope + + firstErr error // first error encountered + methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods + untyped map[syntax.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, pos syntax.Pos) { + mm := check.unusedDotImports + if mm == nil { + mm = make(map[*Scope]map[*Package]syntax.Pos) + check.unusedDotImports = mm + } + m := mm[scope] + if m == nil { + m = make(map[*Package]syntax.Pos) + mm[scope] = m + } + m[pkg] = pos +} + +// 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 syntax.Expr, lhs bool, mode operandMode, typ *Basic, val constant.Value) { + m := check.untyped + if m == nil { + m = make(map[syntax.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, 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, + pkg: pkg, + Info: info, + nextId: 1, + objMap: make(map[Object]*declInfo), + impMap: make(map[importKey]*Package), + posMap: make(map[*Interface][]syntax.Pos), + typMap: make(map[string]*Named), + 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 []*syntax.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.PkgName.Value; pkg.name { + case "": + if name != "_" { + pkg.name = name + } else { + check.errorf(file.PkgName, "invalid package name _") + } + fallthrough + + case name: + check.files = append(check.files, file) + + default: + check.errorf(file, "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 []*syntax.File) error { return check.checkFiles(files) } + +var errBadCgo = errors.New("cannot use FakeImportC and go115UsesCgo together") + +func (check *Checker) checkFiles(files []*syntax.File) (err error) { + if check.conf.FakeImportC && check.conf.go115UsesCgo { + return errBadCgo + } + + defer check.handleBailout(&err) + + print := func(msg string) { + if check.conf.Trace { + fmt.Println(msg) + } + } + + print("== initFiles ==") + check.initFiles(files) + + print("== collectObjects ==") + check.collectObjects() + + print("== packageObjects ==") + check.packageObjects() + + print("== processDelayed ==") + check.processDelayed(0) // incl. all functions + check.processFinals() + + print("== initOrder ==") + check.initOrder() + + if !check.conf.DisableUnusedImportCheck { + print("== unusedImports ==") + check.unusedImports() + } + + print("== recordUntyped ==") + check.recordUntyped() + + if check.Info != nil { + print("== sanitizeInfo ==") + sanitizeInfo(check.Info) + } + + 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", posFor(x), x, info.typ) + unreachable() + } + check.recordTypeAndValue(x, info.mode, info.typ, info.val) + } +} + +func (check *Checker) recordTypeAndValue(x syntax.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 syntax.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 *syntax.Name: + return // we're done + case *syntax.ParenExpr: + f = p.X + default: + unreachable() + } + } +} + +func (check *Checker) recordCommaOkTypes(x syntax.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.(*syntax.ParenExpr) + if p == nil { + break + } + x = p.X + } + } +} + +func (check *Checker) recordInferred(call syntax.Expr, targs []Type, sig *Signature) { + assert(call != nil) + assert(sig != nil) + if m := check.Inferred; m != nil { + m[call] = Inferred{targs, sig} + } +} + +func (check *Checker) recordDef(id *syntax.Name, obj Object) { + assert(id != nil) + if m := check.Defs; m != nil { + m[id] = obj + } +} + +func (check *Checker) recordUse(id *syntax.Name, obj Object) { + assert(id != nil) + assert(obj != nil) + if m := check.Uses; m != nil { + m[id] = obj + } +} + +func (check *Checker) recordImplicit(node syntax.Node, obj Object) { + assert(node != nil) + assert(obj != nil) + if m := check.Implicits; m != nil { + m[node] = obj + } +} + +func (check *Checker) recordSelection(x *syntax.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 syntax.Node, scope *Scope) { + assert(node != nil) + assert(scope != nil) + if m := check.Scopes; m != nil { + m[node] = scope + } +} diff --git a/src/cmd/compile/internal/types2/check_test.go b/src/cmd/compile/internal/types2/check_test.go new file mode 100644 index 0000000000..4dac76ea80 --- /dev/null +++ b/src/cmd/compile/internal/types2/check_test.go @@ -0,0 +1,269 @@ +// UNREVIEWED +// 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 types2_test + +import ( + "cmd/compile/internal/syntax" + "flag" + "fmt" + "internal/testenv" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + + . "cmd/compile/internal/types2" +) + +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") +) + +func parseFiles(t *testing.T, filenames []string) ([]*syntax.File, []error) { + var files []*syntax.File + var errlist []error + errh := func(err error) { errlist = append(errlist, err) } + for _, filename := range filenames { + file, err := syntax.ParseFile(filename, errh, nil, syntax.AllowGenerics) + if file == nil { + t.Fatalf("%s: %s", filename, err) + } + files = append(files, file) + } + return files, errlist +} + +func unpackError(err error) syntax.Error { + switch err := err.(type) { + case syntax.Error: + return err + case Error: + return syntax.Error{Pos: err.Pos, Msg: err.Msg} + default: + return syntax.Error{Msg: err.Error()} + } +} + +func delta(x, y uint) uint { + switch { + case x < y: + return y - x + case x > y: + return x - y + default: + return 0 + } +} + +func checkFiles(t *testing.T, sources []string, colDelta uint, trace bool) { + // parse files and collect parser errors + files, errlist := parseFiles(t, sources) + + pkgName := "<no package>" + if len(files) > 0 { + pkgName = files[0].PkgName.Value + } + + if *listErrors && len(errlist) > 0 { + t.Errorf("--- %s:", pkgName) + for _, err := range errlist { + t.Error(err) + } + } + + // typecheck and collect typechecker errors + var conf Config + conf.AcceptMethodTypeParams = true + conf.InferFromConstraints = true + // special case for importC.src + if len(sources) == 1 && strings.HasSuffix(sources[0], "importC.src") { + conf.FakeImportC = true + } + conf.Trace = trace + conf.Importer = defaultImporter() + 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, files, nil) + + if *listErrors { + return + } + + // collect expected errors + errmap := make(map[string]map[uint][]syntax.Error) + for _, filename := range sources { + f, err := os.Open(filename) + if err != nil { + t.Error(err) + continue + } + if m := syntax.ErrorMap(f); len(m) > 0 { + errmap[filename] = m + } + f.Close() + } + + // match against found errors + for _, err := range errlist { + got := unpackError(err) + + // find list of errors for the respective error line + filename := got.Pos.Base().Filename() + filemap := errmap[filename] + var line uint + var list []syntax.Error + if filemap != nil { + line = got.Pos.Line() + list = filemap[line] + } + // list may be nil + + // one of errors in list should match the current error + index := -1 // list index of matching message, if any + for i, want := range list { + rx, err := regexp.Compile(want.Msg) + if err != nil { + t.Errorf("%s:%d:%d: %v", filename, line, want.Pos.Col(), err) + continue + } + if rx.MatchString(got.Msg) { + index = i + break + } + } + if index < 0 { + t.Errorf("%s: no error expected: %q", got.Pos, got.Msg) + continue + } + + // column position must be within expected colDelta + want := list[index] + if delta(got.Pos.Col(), want.Pos.Col()) > colDelta { + t.Errorf("%s: got col = %d; want %d", got.Pos, got.Pos.Col(), want.Pos.Col()) + } + + // 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] + filemap[line] = list[:n] + } else { + // last entry - remove list from filemap + delete(filemap, line) + } + + // if filemap is empty, eliminate from errmap + if len(filemap) == 0 { + delete(errmap, filename) + } + } + + // there should be no expected errors left + if len(errmap) > 0 { + t.Errorf("--- %s: unreported errors:", pkgName) + for filename, filemap := range errmap { + for line, list := range filemap { + for _, err := range list { + t.Errorf("%s:%d:%d: %s", filename, line, err.Pos.Col(), err.Msg) + } + } + } + } +} + +// TestCheck is for manual testing of selected input files, provided with -files. +func TestCheck(t *testing.T) { + if *testFiles == "" { + return + } + testenv.MustHaveGoBuild(t) + DefPredeclaredTestFuncs() + checkFiles(t, strings.Split(*testFiles, " "), 0, testing.Verbose()) +} + +// TODO(gri) Enable once we have added the testdata tests. +// func TestTestdata(t *testing.T) { DefPredeclaredTestFuncs(); testDir(t, 75, "testdata") } // TODO(gri) narrow column tolerance +func TestExamples(t *testing.T) { testDir(t, 0, "examples") } +func TestFixedbugs(t *testing.T) { testDir(t, 0, "fixedbugs") } + +func testDir(t *testing.T, colDelta uint, dir string) { + testenv.MustHaveGoBuild(t) + + fis, err := ioutil.ReadDir(dir) + if err != nil { + t.Error(err) + return + } + + for count, fi := range fis { + path := filepath.Join(dir, fi.Name()) + + // if fi is a directory, its files make up a single package + if fi.IsDir() { + if testing.Verbose() { + fmt.Printf("%3d %s\n", count, path) + } + fis, err := ioutil.ReadDir(path) + if err != nil { + t.Error(err) + continue + } + files := make([]string, len(fis)) + for i, fi := range fis { + // if fi is a directory, checkFiles below will complain + files[i] = filepath.Join(path, fi.Name()) + if testing.Verbose() { + fmt.Printf("\t%s\n", files[i]) + } + } + checkFiles(t, files, colDelta, false) + continue + } + + // otherwise, fi is a stand-alone file + if testing.Verbose() { + fmt.Printf("%3d %s\n", count, path) + } + checkFiles(t, []string{path}, colDelta, false) + } +} diff --git a/src/cmd/compile/internal/types2/conversions.go b/src/cmd/compile/internal/types2/conversions.go new file mode 100644 index 0000000000..9ff548593f --- /dev/null +++ b/src/cmd/compile/internal/types2/conversions.go @@ -0,0 +1,164 @@ +// UNREVIEWED +// 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 types2 + +import "go/constant" + +// 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.Basic(); { + case representableConst(x.val, check, t, &x.val): + ok = true + case isInteger(x.typ) && isString(t): + codepoint := int64(-1) + if i, ok := constant.Int64Val(x.val); ok { + codepoint = i + } + // If codepoint < 0 the absolute value is too large (or unknown) for + // conversion. This is the same as converting any other out-of-range + // value - let string(codepoint) do the work. + x.val = constant.MakeString(string(rune(codepoint))) + ok = true + } + case x.convertibleTo(check, T): + // non-constant conversion + x.mode = value + ok = true + } + + if !ok { + check.errorf(x, "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<<s + 1.0): +// Here, the type of 1<<s + 1.0 will be UntypedFloat which will lead to the +// (correct!) refusal of the conversion. But the reported error is essentially +// "cannot convert untyped float value to string", yet the correct error (per +// the spec) is that we cannot shift a floating-point value: 1 in 1<<s should +// be converted to UntypedFloat because of the addition of 1.0. Fixing this +// is tricky because we'd have to run updateExprType on the argument first. +// (Issue #21982.) + +// convertibleTo reports whether T(x) is valid. +// The check parameter may be nil if convertibleTo is invoked through an +// exported API call, i.e., when all methods have been type-checked. +func (x *operand) convertibleTo(check *Checker, T Type) bool { + // "x is assignable to T" + if x.assignableTo(check, T, nil) { + return true + } + + // "x's type and T have identical underlying types if tags are ignored" + V := x.typ + Vu := V.Under() + Tu := T.Under() + if check.identicalIgnoreTags(Vu, Tu) { + return true + } + + // "x's type and T are unnamed pointer types and their pointer base types + // have identical underlying types if tags are ignored" + if V, ok := V.(*Pointer); ok { + if T, ok := T.(*Pointer); ok { + if check.identicalIgnoreTags(V.base.Under(), T.base.Under()) { + return true + } + } + } + + // "x's type and T are both integer or floating point types" + if isIntegerOrFloat(V) && isIntegerOrFloat(T) { + return true + } + + // "x's type and T are both complex types" + if isComplex(V) && isComplex(T) { + return true + } + + // "x is an integer or a slice of bytes or runes and T is a string type" + if (isInteger(V) || isBytesOrRunes(Vu)) && isString(T) { + return true + } + + // "x is a string and T is a slice of bytes or runes" + if isString(V) && isBytesOrRunes(Tu) { + return true + } + + // package unsafe: + // "any pointer or value of underlying type uintptr can be converted into a unsafe.Pointer" + if (isPointer(Vu) || isUintptr(Vu)) && isUnsafePointer(T) { + return true + } + // "and vice versa" + if isUnsafePointer(V) && (isPointer(Tu) || isUintptr(Tu)) { + return true + } + + return false +} + +func isUintptr(typ Type) bool { + t := typ.Basic() + return t != nil && t.kind == Uintptr +} + +func isUnsafePointer(typ Type) bool { + // TODO(gri): Is this typ.Basic() instead of typ.(*Basic) correct? + // (The former calls typ.Under(), while the latter doesn't.) + // The spec does not say so, but gc claims it is. See also + // issue 6326. + t := typ.Basic() + return t != nil && t.kind == UnsafePointer +} + +func isPointer(typ Type) bool { + return typ.Pointer() != nil +} + +func isBytesOrRunes(typ Type) bool { + if s := typ.Slice(); s != nil { + t := s.elem.Basic() + return t != nil && (t.kind == Byte || t.kind == Rune) + } + return false +} diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go new file mode 100644 index 0000000000..ef8dc7a245 --- /dev/null +++ b/src/cmd/compile/internal/types2/decl.go @@ -0,0 +1,981 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" + "go/constant" +) + +func (check *Checker) reportAltDecl(obj Object) { + if pos := obj.Pos(); pos.IsKnown() { + // We use "other" rather than "previous" here because + // the first declaration seen may not be textually + // earlier in the source. + check.errorf(pos, "\tother declaration of %s", obj.Name()) // secondary error, \t indented + } +} + +func (check *Checker) declare(scope *Scope, id *syntax.Name, obj Object, pos syntax.Pos) { + // spec: "The blank identifier, represented by the underscore + // character _, may be used in a declaration like any other + // identifier but the declaration does not introduce a new + // binding." + if obj.Name() != "_" { + if alt := scope.Insert(obj); alt != nil { + check.errorf(obj.Pos(), "%s redeclared in this block", obj.Name()) + check.reportAltDecl(alt) + return + } + obj.setScopePos(pos) + } + if id != nil { + check.recordDef(id, obj) + } +} + +// pathString returns a string of the form a->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 check.conf.Trace && obj.Type() == nil { + if check.indent == 0 { + fmt.Println() // empty line between top-level objects for readability + } + 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.vtyp, d.init) + case *Var: + check.decl = d // new package-level var decl + check.varDecl(obj, d.lhs, d.vtyp, d.init) + case *TypeName: + // invalid recursive types are detected via path + check.typeDecl(obj, d.tdecl, def) + check.collectMethods(obj) // methods can only be added to top-level types + 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.tdecl.Alias // package-level object + } else { + alias = obj.IsAlias() // function local object + } + if !alias { + ndef++ + } + case *Func: + // ignored for now + default: + unreachable() + } + } + + if check.conf.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 + return t.info + } + } + panic("internal error: cycle start not found") + } + return t.info + + case *instance: + return check.validType(t.expand(), path) + } + + 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.Pos(), "illegal cycle in declaration of %s", obj.Name()) + for range cycle { + check.errorf(obj.Pos(), "\t%s refers to", obj.Name()) // secondary error, \t indented + i++ + if i >= len(cycle) { + i = 0 + } + obj = cycle[i] + } + check.errorf(obj.Pos(), "\t%s", obj.Name()) +} + +// TODO(gri) This functionality should probably be with the Pos implementation. +func cmpPos(p, q syntax.Pos) int { + // TODO(gri) is RelFilename correct here? + pname := p.RelFilename() + qname := q.RelFilename() + switch { + case pname < qname: + return -1 + case pname > qname: + return +1 + } + + pline := p.Line() + qline := q.Line() + switch { + case pline < qline: + return -1 + case pline > qline: + return +1 + } + + pcol := p.Col() + qcol := q.Col() + switch { + case pcol < qcol: + return -1 + case pcol > qcol: + return +1 + } + + return 0 +} + +// 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 cmpPos(t.Pos(), pos) < 0 { + fst, pos = i+1, t.Pos() + } + } + return fst +} + +func (check *Checker) constDecl(obj *Const, typ, init syntax.Expr) { + assert(obj.typ == nil) + + // use the correct value of iota + defer func(iota constant.Value) { check.iota = iota }(check.iota) + check.iota = obj.val + + // 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.Under() != Typ[Invalid] { + check.errorf(typ, "invalid constant type %s", t) + } + obj.typ = Typ[Invalid] + return + } + obj.typ = t + } + + // check initialization + var x operand + if init != nil { + check.expr(&x, init) + } + check.initConst(obj, &x) +} + +func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init syntax.Expr) { + assert(obj.typ == nil) + + // determine type, if any + if typ != nil { + obj.typ = check.varType(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, []syntax.Expr{init}, nopos) +} + +// Under returns the expanded underlying type of n0; possibly by following +// forward chains of named types. 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 or a cycle +// is detected, the result is Typ[Invalid]. If a cycle is detected and +// n0.check != nil, the cycle is reported. +func (n0 *Named) Under() Type { + u := n0.underlying + if u == nil { + return Typ[Invalid] + } + + // If the underlying type of a defined type is not a defined + // type, then that is the desired underlying type. + n := u.Named() + if n == nil { + return u // common case + } + + // Otherwise, follow the forward chain. + seen := map[*Named]int{n0: 0} + path := []Object{n0.obj} + for { + u = n.underlying + if u == nil { + u = Typ[Invalid] + break + } + n1 := u.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 + if n0.check != nil { + n0.check.cycleError(path[i:]) + } + u = 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). + // Do this check always, not just in debug more (it's cheap). + if n0.check != nil && n.obj.pkg != n0.check.pkg { + panic("internal error: imported type with unresolved underlying type") + } + n.underlying = u + } + + return u +} + +func (n *Named) setUnderlying(typ Type) { + if n != nil { + n.underlying = typ + } +} + +func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named) { + assert(obj.typ == nil) + + check.later(func() { + check.validType(obj.typ, nil) + }) + + alias := tdecl.Alias + if alias && tdecl.TParamList != nil { + // The parser will ensure this but we may still get an invalid AST. + // Complain and continue as regular type definition. + check.errorf(tdecl, "generic type cannot be alias") + alias = false + } + + if alias { + // type alias declaration + + obj.typ = Typ[Invalid] + obj.typ = check.anyType(tdecl.Type) + + } else { + // defined type declaration + + named := &Named{check: check, obj: obj} + def.setUnderlying(named) + obj.typ = named // make sure recursive type declarations terminate + + if tdecl.TParamList != nil { + check.openScope(tdecl, "type parameters") + defer check.closeScope() + named.tparams = check.collectTypeParams(tdecl.TParamList) + } + + // determine underlying type of named + named.orig = check.definedType(tdecl.Type, 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. + // TODO(gri) Investigate if we can just use named.origin here + // and rely on lazy computation of the underlying type. + named.underlying = named.Under() + } + +} + +func (check *Checker) collectTypeParams(list []*syntax.Field) (tparams []*TypeName) { + // Type parameter lists should not be empty. The parser will + // complain but we still may get an incorrect AST: ignore it. + if len(list) == 0 { + return + } + + // Declare type parameters up-front, with empty interface as type bound. + // The scope of type parameters starts at the beginning of the type parameter + // list (so we can have mutually recursive parameterized interfaces). + for _, f := range list { + tparams = check.declareTypeParam(tparams, f.Name) + } + + var bound Type + for i, j := 0, 0; i < len(list); i = j { + f := list[i] + ftype := f.Type + + // determine the range of type parameters list[i:j] with identical type bound + // (declared as in (type a, b, c B)) + j = i + 1 + for j < len(list) && list[j].Type == ftype { + j++ + } + + // this should never be the case, but be careful + if ftype == nil { + continue + } + + // If the type bound expects exactly one type argument, permit leaving + // it away and use the corresponding type parameter as implicit argument. + // This allows us to write (type p b(p), q b(q), r b(r)) as (type p, q, r b). + // Enabled if enableImplicitTParam is set. + const enableImplicitTParam = false + + // The predeclared identifier "any" is visible only as a constraint + // in a type parameter list. Look for it before general constraint + // resolution. + if tident, _ := f.Type.(*syntax.Name); tident != nil && tident.Value == "any" && check.lookup("any") == nil { + bound = universeAny + } else if enableImplicitTParam { + bound = check.anyType(f.Type) + } else { + bound = check.typ(f.Type) + } + + // type bound must be an interface + // TODO(gri) We should delay the interface check because + // we may not have a complete interface yet: + // type C(type T C) interface {} + // (issue #39724). + if _, ok := bound.Under().(*Interface); ok { + if enableImplicitTParam && isGeneric(bound) { + base := bound.(*Named) // only a *Named type can be generic + if j-i != 1 || len(base.tparams) != 1 { + // TODO(gri) make this error message better + check.errorf(ftype, "cannot use generic type %s without instantiation (more than one type parameter)", bound) + bound = Typ[Invalid] + continue + } + // We have exactly one type parameter. + // "Manually" instantiate the bound with each type + // parameter the bound applies to. + // TODO(gri) this code (in more general form) is also in + // checker.typInternal for the *ast.CallExpr case. Factor? + typ := new(instance) + typ.check = check + typ.pos = ftype.Pos() + typ.base = base + typ.targs = []Type{tparams[i].typ} + typ.poslist = []syntax.Pos{f.Name.Pos()} + // Make sure we check instantiation works at least once + // and that the resulting type is valid. + check.atEnd(func() { + check.validType(typ.expand(), nil) + }) + // update bound and recorded type + bound = typ + check.recordTypeAndValue(ftype, typexpr, typ, nil) + } + // set the type bounds + for i < j { + tparams[i].typ.(*TypeParam).bound = bound + i++ + } + } else if bound != Typ[Invalid] { + check.errorf(f.Type, "%s is not an interface", bound) + } + } + + return +} + +func (check *Checker) declareTypeParam(tparams []*TypeName, name *syntax.Name) []*TypeName { + var ptr bool + nstr := name.Value + if len(nstr) > 0 && nstr[0] == '*' { + ptr = true + nstr = nstr[1:] + } + tpar := NewTypeName(name.Pos(), check.pkg, nstr, nil) + check.NewTypeParam(ptr, tpar, len(tparams), &emptyInterface) // assigns type to tpar as a side-effect + check.declare(check.scope, name, tpar, check.scope.pos) // TODO(gri) check scope position + tparams = append(tparams, tpar) + + if check.conf.Trace { + check.trace(name.Pos(), "type param = %v", tparams[len(tparams)-1]) + } + + return tparams +} + +func (check *Checker) collectMethods(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].tdecl.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.pos, "field and method with the same name %s", m.name) + case *Func: + check.errorf(m.pos, "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 + + // Avoid cycle error when referring to method while type-checking the signature. + // This avoids a nuisance in the best case (non-parameterized receiver type) and + // since the method is not a type, we get an error. If we have a parameterized + // receiver type, instantiating the receiver type leads to the instantiation of + // its methods, and we don't want a cycle error in that case. + // TODO(gri) review if this is correct and/or whether we still need this? + saved := obj.color_ + obj.color_ = black + fdecl := decl.fdecl + check.funcType(sig, fdecl.Recv, fdecl.TParamList, fdecl.Type) + obj.color_ = saved + + // 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(list []syntax.Decl) { + pkg := check.pkg + + first := -1 // index of first ConstDecl in the current group, or -1 + var last *syntax.ConstDecl // last ConstDecl with init expressions, or nil + for index, decl := range list { + if _, ok := decl.(*syntax.ConstDecl); !ok { + first = -1 // we're not in a constant declaration + } + + switch s := decl.(type) { + case *syntax.ConstDecl: + top := len(check.delayed) + + // iota is the index of the current constDecl within the group + if first < 0 || list[index-1].(*syntax.ConstDecl).Group != s.Group { + first = index + last = nil + } + iota := constant.MakeInt64(int64(index - first)) + + // determine which initialization expressions to use + inherited := true + switch { + case s.Type != nil || s.Values != nil: + last = s + inherited = false + case last == nil: + last = new(syntax.ConstDecl) // make sure last exists + inherited = false + } + + // declare all constants + lhs := make([]*Const, len(s.NameList)) + values := unpackExpr(last.Values) + for i, name := range s.NameList { + obj := NewConst(name.Pos(), pkg, name.Value, nil, iota) + lhs[i] = obj + + var init syntax.Expr + if i < len(values) { + init = values[i] + } + + check.constDecl(obj, last.Type, init) + } + + // Constants must always have init values. + check.arity(s.Pos(), s.NameList, values, true, 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 := endPos(s) + for i, name := range s.NameList { + check.declare(check.scope, name, lhs[i], scopePos) + } + + case *syntax.VarDecl: + top := len(check.delayed) + + lhs0 := make([]*Var, len(s.NameList)) + for i, name := range s.NameList { + lhs0[i] = NewVar(name.Pos(), pkg, name.Value, nil) + } + + // initialize all variables + values := unpackExpr(s.Values) + for i, obj := range lhs0 { + var lhs []*Var + var init syntax.Expr + switch len(values) { + case len(s.NameList): + // lhs and rhs match + init = values[i] + case 1: + // rhs is expected to be a multi-valued expression + lhs = lhs0 + init = values[0] + default: + if i < len(values) { + init = values[i] + } + } + check.varDecl(obj, lhs, s.Type, init) + if len(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 + } + } + + // If we have no type, we must have values. + if s.Type == nil || values != nil { + check.arity(s.Pos(), s.NameList, values, false, false) + } + + // 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 := endPos(s) // see constant declarations + for i, name := range s.NameList { + // see constant declarations + check.declare(check.scope, name, lhs0[i], scopePos) + } + + case *syntax.TypeDecl: + obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Value, 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 := s.Name.Pos() + check.declare(check.scope, s.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, s, nil) + check.pop().setColor(black) + + default: + check.invalidASTf(s, "unknown syntax.Decl node %T", s) + } + } +} diff --git a/src/cmd/compile/internal/types2/errors.go b/src/cmd/compile/internal/types2/errors.go new file mode 100644 index 0000000000..5211439f89 --- /dev/null +++ b/src/cmd/compile/internal/types2/errors.go @@ -0,0 +1,159 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" + "strconv" + "strings" +) + +func unimplemented() { + panic("unimplemented") +} + +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 = "<nil>" + case operand: + panic("internal error: should always pass *operand") + case *operand: + arg = operandString(a, check.qualifier) + case syntax.Pos: + arg = a.String() + case syntax.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 syntax.Pos, format string, args ...interface{}) { + fmt.Printf("%s:\t%s%s\n", + 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(pos syntax.Pos, msg string, soft bool) { + // 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. + if check.firstErr != nil && (strings.Index(msg, "invalid operand") > 0 || strings.Index(msg, "invalid type") > 0) { + return + } + + err := Error{pos, stripAnnotations(msg), msg, soft} + if check.firstErr == nil { + check.firstErr = err + } + + if check.conf.Trace { + check.trace(pos, "ERROR: %s", msg) + } + + f := check.conf.Error + if f == nil { + panic(bailout{}) // report only first error + } + f(err) +} + +type poser interface { + Pos() syntax.Pos +} + +func (check *Checker) error(at poser, msg string) { + check.err(posFor(at), msg, false) +} + +func (check *Checker) errorf(at poser, format string, args ...interface{}) { + check.err(posFor(at), check.sprintf(format, args...), false) +} + +func (check *Checker) softErrorf(at poser, format string, args ...interface{}) { + check.err(posFor(at), check.sprintf(format, args...), true) +} + +func (check *Checker) invalidASTf(at poser, format string, args ...interface{}) { + check.errorf(at, "invalid AST: "+format, args...) +} + +func (check *Checker) invalidArgf(at poser, format string, args ...interface{}) { + check.errorf(at, "invalid argument: "+format, args...) +} + +func (check *Checker) invalidOpf(at poser, format string, args ...interface{}) { + check.errorf(at, "invalid operation: "+format, args...) +} + +// posFor reports the left (= start) position of at. +func posFor(at poser) syntax.Pos { + switch x := at.(type) { + case *operand: + if x.expr != nil { + return startPos(x.expr) + } + case syntax.Node: + return startPos(x) + } + return at.Pos() +} + +// stripAnnotations removes internal (type) annotations from s. +func stripAnnotations(s string) string { + var b strings.Builder + for _, r := range s { + // strip #'s and subscript digits + if r != instanceMarker && !('₀' <= r && r < '₀'+10) { // '₀' == U+2080 + b.WriteRune(r) + } + } + if b.Len() < len(s) { + return b.String() + } + return s +} diff --git a/src/cmd/compile/internal/types2/errors_test.go b/src/cmd/compile/internal/types2/errors_test.go new file mode 100644 index 0000000000..51ae5fdb73 --- /dev/null +++ b/src/cmd/compile/internal/types2/errors_test.go @@ -0,0 +1,26 @@ +// UNREVIEWED +// 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 types2 + +import "testing" + +func TestStripAnnotations(t *testing.T) { + for _, test := range []struct { + in, want string + }{ + {"", ""}, + {" ", " "}, + {"foo", "foo"}, + {"foo₀", "foo"}, + {"foo(T₀)", "foo(T)"}, + {"#foo(T₀)", "foo(T)"}, + } { + got := stripAnnotations(test.in) + if got != test.want { + t.Errorf("%q: got %q; want %q", test.in, got, test.want) + } + } +} diff --git a/src/cmd/compile/internal/types2/example_test.go b/src/cmd/compile/internal/types2/example_test.go new file mode 100644 index 0000000000..dcdeaca0c0 --- /dev/null +++ b/src/cmd/compile/internal/types2/example_test.go @@ -0,0 +1,324 @@ +// UNREVIEWED +// 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 types2_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" + "cmd/compile/internal/syntax" + "cmd/compile/internal/types2" + "fmt" + "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. + var files []*syntax.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 := parseSrc(file.name, file.input) + 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 := types2.Config{Importer: defaultImporter()} + pkg, err := conf.Check("temperature", 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 } +` + f, err := parseSrc("celsius.go", input) + 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 := types2.Config{Importer: defaultImporter()} + pkg, err := conf.Check("temperature", []*syntax.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 []types2.Type{celsius, types2.NewPointer(celsius)} { + fmt.Printf("Method set of %s:\n", t) + mset := types2.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(types2.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 +// types2.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) +}` + f, err := parseSrc("fib.go", input) + 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 := types2.Info{ + Types: make(map[syntax.Expr]types2.TypeAndValue), + Defs: make(map[*syntax.Name]types2.Object), + Uses: make(map[*syntax.Name]types2.Object), + } + var conf types2.Config + pkg, err := conf.Check("fib", []*syntax.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[types2.Object][]string) + for id, obj := range info.Uses { + posn := id.Pos() + lineCol := fmt.Sprintf("%d:%d", posn.Line(), posn.Col()) + 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", + types2.ObjectString(obj, types2.RelativeTo(pkg)), + 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() + + // TODO(gri) Enable once positions are updated/verified + // fmt.Println("Types and Values of each expression:") + // items = nil + // for expr, tv := range info.Types { + // var buf bytes.Buffer + // posn := 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.Col(), types2.ExprString(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 <unknown position> + // 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 <unknown position> + // used at 8:12, 8:17 + // type string: + // defined at <unknown position> + // 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 + + // TODO(gri) Enable once positions are updated/verified + // 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 types2.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" + } +} diff --git a/src/cmd/compile/internal/types2/examples/functions.go2 b/src/cmd/compile/internal/types2/examples/functions.go2 new file mode 100644 index 0000000000..ab4c192c00 --- /dev/null +++ b/src/cmd/compile/internal/types2/examples/functions.go2 @@ -0,0 +1,216 @@ +// UNREVIEWED +// 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 shows some examples of type-parameterized functions. + +package p + +// Reverse is a generic function that takes a []T argument and +// reverses that slice in place. +func Reverse[T any](list []T) { + i := 0 + j := len(list)-1 + for i < j { + list[i], list[j] = list[j], list[i] + i++ + j-- + } +} + +func _() { + // Reverse can be called with an explicit type argument. + Reverse[int](nil) + Reverse[string]([]string{"foo", "bar"}) + Reverse[struct{x, y int}]([]struct{x, y int}{{1, 2}, {2, 3}, {3, 4}}) + + // Since the type parameter is used for an incoming argument, + // it can be inferred from the provided argument's type. + Reverse([]string{"foo", "bar"}) + Reverse([]struct{x, y int}{{1, 2}, {2, 3}, {3, 4}}) + + // But the incoming argument must have a type, even if it's a + // default type. An untyped nil won't work. + // Reverse(nil) // this won't type-check + + // A typed nil will work, though. + Reverse([]int(nil)) +} + +// Certain functions, such as the built-in `new` could be written using +// type parameters. +func new[T any]() *T { + var x T + return &x +} + +// When calling our own `new`, we need to pass the type parameter +// explicitly since there is no (value) argument from which the +// result type could be inferred. We don't try to infer the +// result type from the assignment to keep things simple and +// easy to understand. +var _ = new[int]() +var _ *float64 = new[float64]() // the result type is indeed *float64 + +// A function may have multiple type parameters, of course. +func foo[A, B, C any](a A, b []B, c *C) B { + // do something here + return b[0] +} + +// As before, we can pass type parameters explicitly. +var s = foo[int, string, float64](1, []string{"first"}, new[float64]()) + +// Or we can use type inference. +var _ float64 = foo(42, []float64{1.0}, &s) + +// Type inference works in a straight-forward manner even +// for variadic functions. +func variadic[A, B any](A, B, ...B) int + +// var _ = variadic(1) // ERROR not enough arguments +var _ = variadic(1, 2.3) +var _ = variadic(1, 2.3, 3.4, 4.5) +var _ = variadic[int, float64](1, 2.3, 3.4, 4) + +// Type inference also works in recursive function calls where +// the inferred type is the type parameter of the caller. +func f1[T any](x T) { + f1(x) +} + +func f2a[T any](x, y T) { + f2a(x, y) +} + +func f2b[T any](x, y T) { + f2b(y, x) +} + +func g2a[P, Q any](x P, y Q) { + g2a(x, y) +} + +func g2b[P, Q any](x P, y Q) { + g2b(y, x) +} + +// Here's an example of a recursive function call with variadic +// arguments and type inference inferring the type parameter of +// the caller (i.e., itself). +func max[T interface{ type int }](x ...T) T { + var x0 T + if len(x) > 0 { + x0 = x[0] + } + if len(x) > 1 { + x1 := max(x[1:]...) + if x1 > x0 { + return x1 + } + } + return x0 +} + +// When inferring channel types, the channel direction is ignored +// for the purpose of type inference. Once the type has been in- +// fered, the usual parameter passing rules are applied. +// Thus even if a type can be inferred successfully, the function +// call may not be valid. + +func fboth[T any](chan T) +func frecv[T any](<-chan T) +func fsend[T any](chan<- T) + +func _() { + var both chan int + var recv <-chan int + var send chan<-int + + fboth(both) + fboth(recv /* ERROR cannot use */ ) + fboth(send /* ERROR cannot use */ ) + + frecv(both) + frecv(recv) + frecv(send /* ERROR cannot use */ ) + + fsend(both) + fsend(recv /* ERROR cannot use */) + fsend(send) +} + +func ffboth[T any](func(chan T)) +func ffrecv[T any](func(<-chan T)) +func ffsend[T any](func(chan<- T)) + +func _() { + var both func(chan int) + var recv func(<-chan int) + var send func(chan<- int) + + ffboth(both) + ffboth(recv /* ERROR cannot use */ ) + ffboth(send /* ERROR cannot use */ ) + + ffrecv(both /* ERROR cannot use */ ) + ffrecv(recv) + ffrecv(send /* ERROR cannot use */ ) + + ffsend(both /* ERROR cannot use */ ) + ffsend(recv /* ERROR cannot use */ ) + ffsend(send) +} + +// When inferring elements of unnamed composite parameter types, +// if the arguments are defined types, use their underlying types. +// Even though the matching types are not exactly structurally the +// same (one is a type literal, the other a named type), because +// assignment is permitted, parameter passing is permitted as well, +// so type inference should be able to handle these cases well. + +func g1[T any]([]T) +func g2[T any]([]T, T) +func g3[T any](*T, ...T) + +func _() { + type intSlize []int + g1([]int{}) + g1(intSlize{}) + g2(nil, 0) + + type myString string + var s1 string + g3(nil, "1", myString("2"), "3") + g3(&s1, "1", myString /* ERROR does not match */ ("2"), "3") + _ = s1 + + type myStruct struct{x int} + var s2 myStruct + g3(nil, struct{x int}{}, myStruct{}) + g3(&s2, struct{x int}{}, myStruct{}) + g3(nil, myStruct{}, struct{x int}{}) + g3(&s2, myStruct{}, struct{x int}{}) +} + +// Here's a realistic example. + +func append[T any](s []T, t ...T) []T + +func _() { + var f func() + type Funcs []func() + var funcs Funcs + _ = append(funcs, f) +} + +// Generic type declarations cannot have empty type parameter lists +// (that would indicate a slice type). Thus, generic functions cannot +// have empty type parameter lists, either. This is a syntax error. + +func h[] /* ERROR empty type parameter list */ () + +func _() { + h[] /* ERROR operand */ () +} diff --git a/src/cmd/compile/internal/types2/examples/methods.go2 b/src/cmd/compile/internal/types2/examples/methods.go2 new file mode 100644 index 0000000000..52f835f80e --- /dev/null +++ b/src/cmd/compile/internal/types2/examples/methods.go2 @@ -0,0 +1,97 @@ +// UNREVIEWED +// 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 shows some examples of methods on type-parameterized types. + +package p + +// Parameterized types may have methods. +type T1[A any] struct{ a A } + +// When declaring a method for a parameterized type, the "instantiated" +// receiver type acts as an implicit declaration of the type parameters +// for the receiver type. In the example below, method m1 on type T1 has +// the receiver type T1[A] which declares the type parameter A for use +// with this method. That is, within the method m1, A stands for the +// actual type argument provided to an instantiated T1. +func (t T1[A]) m1() A { return t.a } + +// For instance, if T1 is instantiated with the type int, the type +// parameter A in m1 assumes that type (int) as well and we can write +// code like this: +var x T1[int] +var _ int = x.m1() + +// Because the type parameter provided to a parameterized receiver type +// is declared through that receiver declaration, it must be an identifier. +// It cannot possibly be some other type because the receiver type is not +// instantiated with concrete types, it is standing for the parameterized +// receiver type. +func (t T1[[ /* ERROR must be an identifier */ ]int]) m2() {} + +// Note that using what looks like a predeclared identifier, say int, +// as type parameter in this situation is deceptive and considered bad +// style. In m3 below, int is the name of the local receiver type parameter +// and it shadows the predeclared identifier int which then cannot be used +// anymore as expected. +// This is no different from locally redelaring a predeclared identifier +// and usually should be avoided. There are some notable exceptions; e.g., +// sometimes it makes sense to use the identifier "copy" which happens to +// also be the name of a predeclared built-in function. +func (t T1[int]) m3() { var _ int = 42 /* ERROR cannot convert 42 .* to int */ } + +// The names of the type parameters used in a parameterized receiver +// type don't have to match the type parameter names in the the declaration +// of the type used for the receiver. In our example, even though T1 is +// declared with type parameter named A, methods using that receiver type +// are free to use their own name for that type parameter. That is, the +// name of type parameters is always local to the declaration where they +// are introduced. In our example we can write a method m2 and use the +// name X instead of A for the type parameter w/o any difference. +func (t T1[X]) m4() X { return t.a } + +// If the receiver type is parameterized, type parameters must always be +// provided: this simply follows from the general rule that a parameterized +// type must be instantiated before it can be used. A method receiver +// declaration using a parameterized receiver type is no exception. It is +// simply that such receiver type expressions perform two tasks simultaneously: +// they declare the (local) type parameters and then use them to instantiate +// the receiver type. Forgetting to provide a type parameter leads to an error. +func (t T1 /* ERROR generic type .* without instantiation */ ) m5() {} + +// However, sometimes we don't need the type parameter, and thus it is +// inconvenient to have to choose a name. Since the receiver type expression +// serves as a declaration for its type parameters, we are free to choose the +// blank identifier: +func (t T1[_]) m6() {} + +// Naturally, these rules apply to any number of type parameters on the receiver +// type. Here are some more complex examples. +type T2[A, B, C any] struct { + a A + b B + c C +} + +// Naming of the type parameters is local and has no semantic impact: +func (t T2[A, B, C]) m1() (A, B, C) { return t.a, t.b, t.c } +func (t T2[C, B, A]) m2() (C, B, A) { return t.a, t.b, t.c } +func (t T2[X, Y, Z]) m3() (X, Y, Z) { return t.a, t.b, t.c } + +// Type parameters may be left blank if they are not needed: +func (t T2[A, _, C]) m4() (A, C) { return t.a, t.c } +func (t T2[_, _, X]) m5() X { return t.c } +func (t T2[_, _, _]) m6() {} + +// As usual, blank names may be used for any object which we don't care about +// using later. For instance, we may write an unnamed method with a receiver +// that cannot be accessed: +func (_ T2[_, _, _]) _() int { return 42 } + +// Because a receiver parameter list is simply a parameter list, we can +// leave the receiver argument away for receiver types. +type T0 struct{} +func (T0) _() {} +func (T1[A]) _() {} diff --git a/src/cmd/compile/internal/types2/examples/types.go2 b/src/cmd/compile/internal/types2/examples/types.go2 new file mode 100644 index 0000000000..be8d44e599 --- /dev/null +++ b/src/cmd/compile/internal/types2/examples/types.go2 @@ -0,0 +1,261 @@ +// UNREVIEWED +// 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 shows some examples of generic types. + +package p + +// List is just what it says - a slice of E elements. +type List[E any] []E + +// A generic (parameterized) type must always be instantiated +// before it can be used to designate the type of a variable +// (including a struct field, or function parameter); though +// for the latter cases, the provided type may be another type +// parameter. So: +var _ List[byte] = []byte{} + +// A generic binary tree might be declared as follows. +type Tree[E any] struct { + left, right *Tree[E] + payload E +} + +// A simple instantiation of Tree: +var root1 Tree[int] + +// The actual type parameter provided may be a generic type itself: +var root2 Tree[List[int]] + +// A couple of more complex examples. +// We don't need extra parentheses around the element type of the slices on +// the right (unlike when we use ()'s rather than []'s for type parameters). +var _ List[List[int]] = []List[int]{} +var _ List[List[List[Tree[int]]]] = []List[List[Tree[int]]]{} + +// Type parameters act like type aliases when used in generic types +// in the sense that we can "emulate" a specific type instantiation +// with type aliases. +type T1[P any] struct { + f P +} + +type T2[P any] struct { + f struct { + g P + } +} + +var x1 T1[struct{ g int }] +var x2 T2[int] + +func _() { + // This assignment is invalid because the types of x1, x2 are T1(...) + // and T2(...) respectively, which are two different defined types. + x1 = x2 // ERROR assignment + + // This assignment is valid because the types of x1.f and x2.f are + // both struct { g int }; the type parameters act like type aliases + // and their actual names don't come into play here. + x1.f = x2.f +} + +// We can verify this behavior using type aliases instead: +type T1a struct { + f A1 +} +type A1 = struct { g int } + +type T2a struct { + f struct { + g A2 + } +} +type A2 = int + +var x1a T1a +var x2a T2a + +func _() { + x1a = x2a // ERROR assignment + x1a.f = x2a.f +} + +// Another interesting corner case are generic types that don't use +// their type arguments. For instance: +type T[P any] struct{} + +var xint T[int] +var xbool T[bool] + +// Are these two variables of the same type? After all, their underlying +// types are identical. We consider them to be different because each type +// instantiation creates a new named type, in this case T<int> and T<bool> +// even if their underlying types are identical. This is sensible because +// we might still have methods that have different signatures or behave +// differently depending on the type arguments, and thus we can't possibly +// consider such types identical. Consequently: +func _() { + xint = xbool // ERROR assignment +} + +// Generic types cannot be used without instantiation. +var _ T // ERROR cannot use generic type T + +// In type context, generic (parameterized) types cannot be parenthesized before +// being instantiated. See also NOTES entry from 12/4/2019. +var _ (T /* ERROR cannot use generic type T */ )[ /* ERROR unexpected \[ */ int] + +// All types may be parameterized, including interfaces. +type I1[T any] interface{ + m1(T) +} + +// Generic interfaces may be embedded as one would expect. +type I2 interface { + I1(int) // method! + I1[string] // embedded I1 +} + +func _() { + var x I2 + x.I1(0) + x.m1("foo") +} + +type I0 interface { + m0() +} + +type I3 interface { + I0 + I1[bool] + m(string) +} + +func _() { + var x I3 + x.m0() + x.m1(true) + x.m("foo") +} + +type _ struct { + ( /* ERROR cannot parenthesize */ int8) + ( /* ERROR cannot parenthesize */ *int16) + *( /* ERROR cannot parenthesize */ int32) + List[int] + + int8 /* ERROR int8 redeclared */ + * /* ERROR int16 redeclared */ int16 + List /* ERROR List redeclared */ [int] +} + +// It's possible to declare local types whose underlying types +// are type parameters. As with ordinary type definitions, the +// types underlying properties are "inherited" but the methods +// are not. +func _[T interface{ m(); type int }]() { + type L T + var x L + + // m is not defined on L (it is not "inherited" from + // its underlying type). + x.m /* ERROR x.m undefined */ () + + // But the properties of T, such that as that it supports + // the operations of the types given by its type bound, + // are also the properties of L. + x++ + _ = x - x + + // On the other hand, if we define a local alias for T, + // that alias stands for T as expected. + type A = T + var y A + y.m() + _ = y < 0 +} + +// As a special case, an explicit type argument may be omitted +// from a type parameter bound if the type bound expects exactly +// one type argument. In that case, the type argument is the +// respective type parameter to which the type bound applies. +// Note: We may not permit this syntactic sugar at first. +// Note: This is now disabled. All examples below are adjusted. +type Adder[T any] interface { + Add(T) T +} + +// We don't need to explicitly instantiate the Adder bound +// if we have exactly one type parameter. +func Sum[T Adder[T]](list []T) T { + var sum T + for _, x := range list { + sum = sum.Add(x) + } + return sum +} + +// Valid and invalid variations. +type B0 interface {} +type B1[_ any] interface{} +type B2[_, _ any] interface{} + +func _[T1 B0]() +func _[T1 B1[T1]]() +func _[T1 B2 /* ERROR cannot use generic type .* without instantiation */ ]() + +func _[T1, T2 B0]() +func _[T1 B1[T1], T2 B1[T2]]() +func _[T1, T2 B2 /* ERROR cannot use generic type .* without instantiation */ ]() + +func _[T1 B0, T2 B1[T2]]() // here B1 applies to T2 + +// When the type argument is left away, the type bound is +// instantiated for each type parameter with that type +// parameter. +// Note: We may not permit this syntactic sugar at first. +func _[A Adder[A], B Adder[B], C Adder[A]]() { + var a A // A's type bound is Adder[A] + a = a.Add(a) + var b B // B's type bound is Adder[B] + b = b.Add(b) + var c C // C's type bound is Adder[A] + a = c.Add(a) +} + +// The type of variables (incl. parameters and return values) cannot +// be an interface with type constraints or be/embed comparable. +type I interface { + type int +} + +var ( + _ interface /* ERROR contains type constraints */ {type int} + _ I /* ERROR contains type constraints */ +) + +func _(I /* ERROR contains type constraints */ ) +func _(x, y, z I /* ERROR contains type constraints */ ) +func _() I /* ERROR contains type constraints */ + +func _() { + var _ I /* ERROR contains type constraints */ +} + +type C interface { + comparable +} + +var _ comparable /* ERROR comparable */ +var _ C /* ERROR comparable */ + +func _(_ comparable /* ERROR comparable */ , _ C /* ERROR comparable */ ) + +func _() { + var _ comparable /* ERROR comparable */ + var _ C /* ERROR comparable */ +}
\ No newline at end of file diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go new file mode 100644 index 0000000000..f83aa86f6e --- /dev/null +++ b/src/cmd/compile/internal/types2/expr.go @@ -0,0 +1,1906 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" + "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 *syntax.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[syntax.Operator]func(Type) bool + +var unaryOpPredicates = opPredicates{ + syntax.Add: isNumeric, + syntax.Sub: isNumeric, + syntax.Xor: isInteger, + syntax.Not: isBoolean, +} + +func (check *Checker) op(m opPredicates, x *operand, op syntax.Operator) bool { + if pred := m[op]; pred != nil { + if !pred(x.typ) { + check.invalidOpf(x, "operator %s not defined for %s", op, x) + return false + } + } else { + check.invalidASTf(x, "unknown operator %s", op) + return false + } + return true +} + +func op2token(op syntax.Operator) token.Token { + switch op { + case syntax.Def: // : + unreachable() + case syntax.Not: // ! + return token.NOT + case syntax.Recv: // <- + unreachable() + + case syntax.OrOr: // || + return token.LOR + case syntax.AndAnd: // && + return token.LAND + + case syntax.Eql: // == + return token.EQL + case syntax.Neq: // != + return token.NEQ + case syntax.Lss: // < + return token.LSS + case syntax.Leq: // <= + return token.LEQ + case syntax.Gtr: // > + return token.GTR + case syntax.Geq: // >= + return token.GEQ + + case syntax.Add: // + + return token.ADD + case syntax.Sub: // - + return token.SUB + case syntax.Or: // | + return token.OR + case syntax.Xor: // ^ + return token.XOR + + case syntax.Mul: // * + return token.MUL + case syntax.Div: // / + return token.QUO + case syntax.Rem: // % + return token.REM + case syntax.And: // & + return token.AND + case syntax.AndNot: // &^ + return token.AND_NOT + case syntax.Shl: // << + return token.SHL + case syntax.Shr: // >> + return token.SHR + } + + return token.ILLEGAL +} + +// The unary expression e may be nil. It's passed in for better error messages only. +func (check *Checker) unary(x *operand, e *syntax.Operation, op syntax.Operator) { + switch op { + case syntax.And: + // spec: "As an exception to the addressability + // requirement x may also be a composite literal." + if _, ok := unparen(x.expr).(*syntax.CompositeLit); !ok && x.mode != variable { + check.invalidOpf(x, "cannot take address of %s", x) + x.mode = invalid + return + } + x.mode = value + x.typ = &Pointer{base: x.typ} + return + + case syntax.Recv: + typ := x.typ.Chan() + if typ == nil { + check.invalidOpf(x, "cannot receive from non-channel %s", x) + x.mode = invalid + return + } + if typ.dir == SendOnly { + check.invalidOpf(x, "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.Basic() + var prec uint + if isUnsigned(typ) { + prec = uint(check.conf.sizeof(typ) * 8) + } + x.val = constant.UnaryOp(op2token(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 syntax.Operator) bool { + return op == syntax.Shl || op == syntax.Shr +} + +func isComparison(op syntax.Operator) bool { + // Note: tokens are not ordered well to make this much easier + switch op { + case syntax.Eql, syntax.Neq, syntax.Lss, syntax.Leq, syntax.Gtr, syntax.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)<<s-1 + } + return 0 <= x + case Uint8: + const s = 8 + return 0 <= x && x <= 1<<s-1 + case Uint16: + const s = 16 + return 0 <= x && x <= 1<<s-1 + case Uint32: + const s = 32 + return 0 <= x && x <= 1<<s-1 + case Uint64: + return 0 <= x + default: + unreachable() + } + } + // x does not fit into int64 + switch n := constant.BitLen(x); typ.kind { + case Uint, Uintptr: + var s = uint(conf.sizeof(typ)) * 8 + return constant.Sign(x) >= 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) { + assert(x.mode == constant_) + if !representableConst(x.val, check, typ, &x.val) { + var msg string + 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" + } else { + msg = "%s overflows %s" + } + } else { + msg = "cannot convert %s to %s" + } + check.errorf(x, msg, x, typ) + x.mode = invalid + } +} + +// 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 syntax.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 *syntax.BadExpr, + *syntax.FuncLit, + *syntax.CompositeLit, + *syntax.IndexExpr, + *syntax.SliceExpr, + *syntax.AssertExpr, + //*syntax.StarExpr, + *syntax.KeyValueExpr, + *syntax.ArrayType, + *syntax.StructType, + *syntax.FuncType, + *syntax.InterfaceType, + *syntax.MapType, + *syntax.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)", posFor(x), x, old.typ, typ) + unreachable() + } + return + + case *syntax.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 *syntax.Name, *syntax.BasicLit, *syntax.SelectorExpr: + // An identifier denoting a constant, a constant literal, + // or a qualified identifier (imported untyped constant). + // No operands to take care of. + + case *syntax.ParenExpr: + check.updateExprType(x.X, typ, final) + + // case *syntax.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 *syntax.Operation: + if x.Y == nil { + // unary expression + if x.Op == syntax.Mul { + // see commented out code for StarExpr above + // TODO(gri) needs cleanup + if debug { + unimplemented() + } + return + } + // 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) + break + } + + // binary expression + 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.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.invalidOpf(x, "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 syntax.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) { + target = expand(target) + if x.mode == invalid || isTyped(x.typ) || target == Typ[Invalid] { + return + } + + // TODO(gri) Sloppy code - clean up. This function is central + // to assignment and expression checking. + + 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 { + goto Error + } + return + } + + // In case of a type parameter, conversion must succeed against + // all types enumerated by the type parameter bound. + // TODO(gri) We should not need this because we have the code + // for Sum types in convertUntypedInternal. But at least one + // test fails. Investigate. + if t := target.TypeParam(); t != nil { + types := t.Bound().allTypes + if types == nil { + goto Error + } + + for _, t := range unpack(types) { + check.convertUntypedInternal(x, t) + if x.mode == invalid { + goto Error + } + } + + // keep nil untyped (was bug #39755) + if x.isNil() { + target = Typ[UntypedNil] + } + x.typ = target + check.updateExprType(x.expr, target, true) // UntypedNils are final + return + } + + check.convertUntypedInternal(x, target) + return + +Error: + // TODO(gri) better error message (explain cause) + check.errorf(x, "cannot convert %s to %s", x, target) + x.mode = invalid +} + +// convertUntypedInternal should only be called by convertUntyped. +func (check *Checker) convertUntypedInternal(x *operand, target Type) { + assert(isTyped(target)) + + // typed target + switch t := optype(target.Under()).(type) { + case *Basic: + if x.mode == constant_ { + check.representable(x, t) + if x.mode == invalid { + return + } + // expression value may have been rounded - update if needed + check.updateExprVal(x.expr, x.val) + } else { + // 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) { + goto Error + } + case UntypedInt, UntypedRune, UntypedFloat, UntypedComplex: + if !isNumeric(target) { + goto Error + } + case UntypedString: + // Non-constant untyped string values are not + // permitted by the spec and should not occur. + unreachable() + case UntypedNil: + // Unsafe.Pointer is a basic type that includes nil. + if !hasNil(target) { + goto Error + } + default: + goto Error + } + } + case *Sum: + t.is(func(t Type) bool { + check.convertUntypedInternal(x, t) + return x.mode != invalid + }) + case *Interface: + // Update operand types to the default type rather then + // the target (interface) type: 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() { + target = Typ[UntypedNil] + } else { + // cannot assign untyped values to non-empty interfaces + check.completeInterface(nopos, t) + if !t.Empty() { + goto Error + } + target = Default(x.typ) + } + case *Pointer, *Signature, *Slice, *Map, *Chan: + if !x.isNil() { + goto Error + } + // keep nil untyped - see comment for interfaces, above + target = Typ[UntypedNil] + default: + goto Error + } + + x.typ = target + check.updateExprType(x.expr, target, true) // UntypedNils are final + return + +Error: + check.errorf(x, "cannot convert %s to %s", x, target) + x.mode = invalid +} + +func (check *Checker) comparison(x, y *operand, op syntax.Operator) { + // spec: "In any comparison, the first operand must be assignable + // to the type of the second operand, or vice versa." + err := "" + if x.assignableTo(check, y.typ, nil) || y.assignableTo(check, x.typ, nil) { + defined := false + switch op { + case syntax.Eql, syntax.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 syntax.Lss, syntax.Leq, syntax.Gtr, syntax.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) + } + } else { + err = check.sprintf("mismatched types %s and %s", x.typ, y.typ) + } + + if err != "" { + check.errorf(x, "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, op2token(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 *syntax.Operation, op syntax.Operator) { + 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.invalidOpf(x, "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.invalidOpf(y, "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.invalidOpf(y, "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.invalidOpf(y, "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, op2token(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.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.invalidOpf(x, "shifted operand %s must be integer", x) + x.mode = invalid + return + } + + x.mode = value +} + +var binaryOpPredicates = opPredicates{ + syntax.Add: isNumericOrString, + syntax.Sub: isNumeric, + syntax.Mul: isNumeric, + syntax.Div: isNumeric, + syntax.Rem: isInteger, + + syntax.And: isInteger, + syntax.Or: isInteger, + syntax.Xor: isInteger, + syntax.AndNot: isInteger, + + syntax.AndAnd: isBoolean, + syntax.OrOr: isBoolean, +} + +// The binary expression e may be nil. It's passed in for better error messages only. +func (check *Checker) binary(x *operand, e *syntax.Operation, lhs, rhs syntax.Expr, op syntax.Operator) { + 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] { + check.invalidOpf(x, "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 == syntax.Div || op == syntax.Rem { + // check for zero divisor + if (x.mode == constant_ || isInteger(x.typ)) && y.mode == constant_ && constant.Sign(y.val) == 0 { + check.invalidOpf(&y, "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.invalidOpf(&y, "division by zero") + x.mode = invalid + return + } + } + } + + if x.mode == constant_ && y.mode == constant_ { + xval := x.val + yval := y.val + typ := x.typ.Basic() + // force integer division of integer operands + tok := op2token(op) + if op == syntax.Div && isInteger(typ) { + tok = token.QUO_ASSIGN + } + x.val = constant.BinaryOp(xval, tok, yval) + // 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 syntax.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.invalidArgf(&x, "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.invalidArgf(&x, "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, "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 []syntax.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.(*syntax.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, "index %s must be integer constant", kv.Key) + } + } + eval = kv.Value + } else if length >= 0 && index >= length { + check.errorf(e, "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, "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 syntax.Expr, hint Type) exprKind { + if check.conf.Trace { + check.trace(e.Pos(), "expr %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 syntax.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 nil: + unreachable() + + case *syntax.BadExpr: + goto Error // error was reported before + + case *syntax.Name: + check.ident(x, e, nil, false) + + case *syntax.DotsType: + // ellipses are handled explicitly where they are legal + // (array composite literals and parameter lists) + check.error(e, "invalid use of '...'") + goto Error + + case *syntax.BasicLit: + x.setConst(e.Kind, e.Value) + if x.mode == invalid { + check.invalidASTf(e, "invalid literal %v", e.Value) + goto Error + } + + case *syntax.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, "<function literal>", sig, e.Body, iota) + }) + x.mode = value + x.typ = sig + } else { + check.invalidASTf(e, "invalid function literal %s", e) + goto Error + } + + case *syntax.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.(*syntax.ArrayType); atyp != nil && atyp.Len == 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.varType(atyp.Elem)} + 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.Under()) // *T implies &T{} + + default: + // TODO(gri) provide better error messages depending on context + check.error(e, "missing type in composite literal") + goto Error + } + + switch utyp := optype(base.Under()).(type) { + case *Struct: + if len(e.ElemList) == 0 { + break + } + fields := utyp.fields + if _, ok := e.ElemList[0].(*syntax.KeyValueExpr); ok { + // all elements must have keys + visited := make([]bool, len(fields)) + for _, e := range e.ElemList { + kv, _ := e.(*syntax.KeyValueExpr) + if kv == nil { + check.error(e, "mixture of field:value and value elements in struct literal") + continue + } + key, _ := kv.Key.(*syntax.Name) + // 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, "invalid field name %s in struct literal", kv.Key) + continue + } + i := fieldIndex(utyp.fields, check.pkg, key.Value) + if i < 0 { + check.errorf(kv, "unknown field %s in struct literal", key.Value) + 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, "duplicate field name %s in struct literal", key.Value) + continue + } + visited[i] = true + } + } else { + // no element must have a key + for i, e := range e.ElemList { + if kv, _ := e.(*syntax.KeyValueExpr); kv != nil { + check.error(kv, "mixture of field:value and value elements in struct literal") + continue + } + check.expr(x, e) + if i >= len(fields) { + check.error(x, "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, "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.ElemList) < len(fields) { + check.error(e.Rbrace, "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, "illegal cycle in type declaration") + goto Error + } + n := check.indexedElts(e.ElemList, 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, "illegal cycle in type declaration") + goto Error + } + check.indexedElts(e.ElemList, 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, "illegal cycle in type declaration") + goto Error + } + visited := make(map[interface{}][]Type, len(e.ElemList)) + for _, e := range e.ElemList { + kv, _ := e.(*syntax.KeyValueExpr) + if kv == nil { + check.error(e, "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 utyp.key.Interface() != nil { + 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, "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.ElemList { + if kv, _ := e.(*syntax.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, "invalid composite literal type %s", typ) + goto Error + } + } + + x.mode = value + x.typ = typ + + case *syntax.ParenExpr: + kind := check.rawExpr(x, e.X, nil) + x.expr = e + return kind + + case *syntax.SelectorExpr: + check.selector(x, e) + + case *syntax.IndexExpr: + check.exprOrType(x, e.X) + if x.mode == invalid { + check.use(e.Index) + goto Error + } + + if x.mode == typexpr { + // type instantiation + x.mode = invalid + x.typ = check.varType(e) + if x.typ != Typ[Invalid] { + x.mode = typexpr + } + return expression + } + + if x.mode == value { + if sig := x.typ.Signature(); sig != nil && len(sig.tparams) > 0 { + // function instantiation + check.funcInst(x, e) + return expression + } + } + + // ordinary index expression + valid := false + length := int64(-1) // valid if >= 0 + switch typ := optype(x.typ.Under()).(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.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") + if x.mode == invalid { + goto Error + } + x.mode = mapindex + x.typ = typ.elem + x.expr = e + return expression + + case *Sum: + // A sum type can be indexed if all the sum's types + // support indexing and have the same element type. + var elem Type + if typ.is(func(t Type) bool { + var e Type + switch t := t.Under().(type) { + case *Basic: + if isString(t) { + e = universeByte + } + case *Array: + e = t.elem + case *Pointer: + if t := t.base.Array(); t != nil { + e = t.elem + } + case *Slice: + e = t.elem + case *Map: + e = t.elem + case *TypeParam: + check.errorf(x, "type of %s contains a type parameter - cannot index (implementation restriction)", x) + case *instance: + unimplemented() + } + if e != nil && (e == elem || elem == nil) { + elem = e + return true + } + return false + }) { + valid = true + x.mode = variable + x.typ = elem + } + } + + if !valid { + check.invalidOpf(x, "cannot index %s", x) + goto Error + } + + if e.Index == nil { + check.invalidASTf(e, "missing index for %s", x) + goto Error + } + + index := e.Index + if l, _ := index.(*syntax.ListExpr); l != nil { + if n := len(l.ElemList); n <= 1 { + check.invalidASTf(e, "invalid use of ListExpr for index expression %s with %d indices", e, n) + goto Error + } + // len(l.ElemList) > 1 + check.invalidOpf(l.ElemList[1], "more than one index") + index = l.ElemList[0] // continue with first index + } + + // In pathological (invalid) cases (e.g.: type T1 [][[]T1{}[0][0]]T0) + // the element type may be accessed before it's set. Make sure we have + // a valid type. + if x.typ == nil { + x.typ = Typ[Invalid] + } + + check.index(index, length) + // ok to continue + + case *syntax.SliceExpr: + 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 := optype(x.typ.Under()).(type) { + case *Basic: + if isString(typ) { + if e.Full { + check.invalidOpf(x, "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.invalidOpf(x, "cannot slice %s (value not addressable)", x) + goto Error + } + x.typ = &Slice{elem: typ.elem} + + case *Pointer: + if typ := typ.base.Array(); typ != nil { + valid = true + length = typ.len + x.typ = &Slice{elem: typ.elem} + } + + case *Slice: + valid = true + // x.typ doesn't change + + case *Sum, *TypeParam: + check.errorf(x, "generic slice expressions not yet implemented") + goto Error + } + + if !valid { + check.invalidOpf(x, "cannot slice %s", x) + goto Error + } + + x.mode = value + + // spec: "Only the first index may be omitted; it defaults to 0." + if e.Full && (e.Index[1] == nil || e.Index[2] == nil) { + check.invalidASTf(e, "2nd and 3rd index required in 3-index slice") + goto Error + } + + // check indices + var ind [3]int64 + for i, expr := range e.Index { + 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(e, "invalid slice indices: %d > %d", x, y) + break L // only report one error, ok to continue + } + } + } + } + + case *syntax.AssertExpr: + check.expr(x, e.X) + if x.mode == invalid { + goto Error + } + var xtyp *Interface + var strict bool + switch t := optype(x.typ.Under()).(type) { + case *Interface: + xtyp = t + // Disabled for now. It is not clear what the right approach is + // here. Also, the implementation below is inconsistent because + // the underlying type of a type parameter is either itself or + // a sum type if the corresponding type bound contains a type list. + // case *TypeParam: + // xtyp = t.Bound() + // strict = true + default: + check.invalidOpf(x, "%s is not an interface type", x) + goto Error + } + // x.(type) expressions are encoded via TypeSwitchGuards + if e.Type == nil { + check.invalidASTf(e, "invalid use of AssertExpr") + goto Error + } + T := check.varType(e.Type) + if T == Typ[Invalid] { + goto Error + } + check.typeAssertion(posFor(x), x, xtyp, T, strict) + x.mode = commaok + x.typ = T + + case *syntax.TypeSwitchGuard: + // x.(type) expressions are handled explicitly in type switches + check.invalidASTf(e, "use of .(type) outside type switch") + goto Error + + case *syntax.CallExpr: + return check.call(x, e) + + // case *syntax.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 *syntax.BinaryExpr: + // check.binary(x, e, e.X, e.Y, e.Op) + // if x.mode == invalid { + // goto Error + // } + + case *syntax.Operation: + if e.Y == nil { + // unary expression + if e.Op == syntax.Mul { + // pointer indirection + check.exprOrType(x, e.X) + switch x.mode { + case invalid: + goto Error + case typexpr: + x.typ = &Pointer{base: x.typ} + default: + if typ := x.typ.Pointer(); typ != nil { + x.mode = variable + x.typ = typ.base + } else { + check.invalidOpf(x, "cannot indirect %s", x) + goto Error + } + } + break + } + + 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 == syntax.Recv { + x.expr = e + return statement // receive operations may appear in statement context + } + break + } + + // binary expression + check.binary(x, e, e.X, e.Y, e.Op) + if x.mode == invalid { + goto Error + } + + case *syntax.KeyValueExpr: + // key:value expressions are handled in composite literals + check.invalidASTf(e, "no key:value expected") + goto Error + + case *syntax.ArrayType, *syntax.SliceType, *syntax.StructType, *syntax.FuncType, + *syntax.InterfaceType, *syntax.MapType, *syntax.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", posFor(e), 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(pos syntax.Pos, x *operand, xtyp *Interface, T Type, strict bool) { + method, wrongType := check.assertableTo(xtyp, T, strict) + 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(pos, "%s cannot have dynamic type %s (%s)", x, T, msg) +} + +// 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 syntax.Expr) { + check.rawExpr(x, e, nil) + check.exclude(x, 1<<novalue|1<<builtin|1<<typexpr) + check.singleValue(x) +} + +// multiExpr is like expr but the result may also be a multi-value. +func (check *Checker) multiExpr(x *operand, e syntax.Expr) { + check.rawExpr(x, e, nil) + check.exclude(x, 1<<novalue|1<<builtin|1<<typexpr) +} + +// multiExprOrType is like multiExpr but the result may also be a type. +func (check *Checker) multiExprOrType(x *operand, e syntax.Expr) { + check.rawExpr(x, e, nil) + check.exclude(x, 1<<novalue|1<<builtin) +} + +// 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 syntax.Expr, hint Type) { + assert(hint != nil) + check.rawExpr(x, e, hint) + check.exclude(x, 1<<novalue|1<<builtin|1<<typexpr) + check.singleValue(x) +} + +// 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 syntax.Expr) { + check.rawExpr(x, e, nil) + check.exclude(x, 1<<novalue) + check.singleValue(x) +} + +// exclude reports an error if x.mode is in modeset and sets x.mode to invalid. +// The modeset may contain any of 1<<novalue, 1<<builtin, 1<<typexpr. +func (check *Checker) exclude(x *operand, modeset uint) { + if modeset&(1<<x.mode) != 0 { + var msg string + switch x.mode { + case novalue: + if modeset&(1<<typexpr) != 0 { + msg = "%s used as value" + } else { + msg = "%s used as value or type" + } + case builtin: + msg = "%s must be called" + case typexpr: + msg = "%s is not an expression" + default: + unreachable() + } + check.errorf(x, msg, x) + x.mode = invalid + } +} + +// singleValue reports an error if x describes a tuple and sets x.mode to invalid. +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, "%d-valued %s where single value is expected", t.Len(), x) + x.mode = invalid + } + } +} diff --git a/src/cmd/compile/internal/types2/exprstring.go b/src/cmd/compile/internal/types2/exprstring.go new file mode 100644 index 0000000000..e187d050e6 --- /dev/null +++ b/src/cmd/compile/internal/types2/exprstring.go @@ -0,0 +1,287 @@ +// UNREVIEWED +// 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 types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" +) + +// 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 syntax.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 syntax.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.) + + // TODO(gri): This assumption is not correct - we need to recreate + // parentheses in expressions. + + switch x := x.(type) { + default: + buf.WriteString("(ast: bad expr)") // nil, syntax.BadExpr, syntax.KeyValueExpr + + case *syntax.Name: + buf.WriteString(x.Value) + + case *syntax.DotsType: + buf.WriteString("...") + if x.Elem != nil { + WriteExpr(buf, x.Elem) + } + + case *syntax.BasicLit: + buf.WriteString(x.Value) + + case *syntax.FuncLit: + buf.WriteByte('(') + WriteExpr(buf, x.Type) + buf.WriteString(" literal)") // shortened + + case *syntax.CompositeLit: + buf.WriteByte('(') + WriteExpr(buf, x.Type) + buf.WriteString(" literal)") // shortened + + case *syntax.ParenExpr: + buf.WriteByte('(') + WriteExpr(buf, x.X) + buf.WriteByte(')') + + case *syntax.SelectorExpr: + WriteExpr(buf, x.X) + buf.WriteByte('.') + buf.WriteString(x.Sel.Value) + + case *syntax.IndexExpr: + WriteExpr(buf, x.X) + buf.WriteByte('[') + WriteExpr(buf, x.Index) // x.Index may be a *ListExpr + buf.WriteByte(']') + + case *syntax.SliceExpr: + WriteExpr(buf, x.X) + buf.WriteByte('[') + if x.Index[0] != nil { + WriteExpr(buf, x.Index[0]) + } + buf.WriteByte(':') + if x.Index[1] != nil { + WriteExpr(buf, x.Index[1]) + } + if x.Full { + buf.WriteByte(':') + if x.Index[2] != nil { + WriteExpr(buf, x.Index[2]) + } + } + buf.WriteByte(']') + + case *syntax.AssertExpr: + WriteExpr(buf, x.X) + buf.WriteString(".(") + WriteExpr(buf, x.Type) + buf.WriteByte(')') + + case *syntax.CallExpr: + WriteExpr(buf, x.Fun) + buf.WriteByte('(') + writeExprList(buf, x.ArgList) + if x.HasDots { + buf.WriteString("...") + } + buf.WriteByte(')') + + case *syntax.ListExpr: + writeExprList(buf, x.ElemList) + + case *syntax.Operation: + // TODO(gri) This would be simpler if x.X == nil meant unary expression. + if x.Y == nil { + // unary expression + buf.WriteString(x.Op.String()) + WriteExpr(buf, x.X) + } else { + // binary expression + WriteExpr(buf, x.X) + buf.WriteByte(' ') + buf.WriteString(x.Op.String()) + buf.WriteByte(' ') + WriteExpr(buf, x.Y) + } + + // 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 *syntax.ArrayType: + if x.Len == nil { + buf.WriteString("[...]") + } else { + buf.WriteByte('[') + WriteExpr(buf, x.Len) + buf.WriteByte(']') + } + WriteExpr(buf, x.Elem) + + case *syntax.SliceType: + buf.WriteString("[]") + WriteExpr(buf, x.Elem) + + case *syntax.StructType: + buf.WriteString("struct{") + writeFieldList(buf, x.FieldList, "; ", false) + buf.WriteByte('}') + + case *syntax.FuncType: + buf.WriteString("func") + writeSigExpr(buf, x) + + case *syntax.InterfaceType: + // separate type list types from method list + // TODO(gri) we can get rid of this extra code if writeExprList does the separation + var types []syntax.Expr + var methods []*syntax.Field + for _, f := range x.MethodList { + if f.Name != nil && f.Name.Value == "type" { + // type list type + types = append(types, f.Type) + } else { + // method or embedded interface + methods = append(methods, f) + } + } + + buf.WriteString("interface{") + writeFieldList(buf, methods, "; ", true) + if len(types) > 0 { + if len(methods) > 0 { + buf.WriteString("; ") + } + buf.WriteString("type ") + writeExprList(buf, types) + } + buf.WriteByte('}') + + case *syntax.MapType: + buf.WriteString("map[") + WriteExpr(buf, x.Key) + buf.WriteByte(']') + WriteExpr(buf, x.Value) + + case *syntax.ChanType: + var s string + switch x.Dir { + case syntax.SendOnly: + s = "chan<- " + case syntax.RecvOnly: + s = "<-chan " + default: + s = "chan " + } + buf.WriteString(s) + if e, _ := x.Elem.(*syntax.ChanType); x.Dir != syntax.SendOnly && e != nil && e.Dir == syntax.RecvOnly { + // don't print chan (<-chan T) as chan <-chan T (but chan<- <-chan T is ok) + buf.WriteByte('(') + WriteExpr(buf, x.Elem) + buf.WriteByte(')') + } else { + WriteExpr(buf, x.Elem) + } + } +} + +func writeSigExpr(buf *bytes.Buffer, sig *syntax.FuncType) { + buf.WriteByte('(') + writeFieldList(buf, sig.ParamList, ", ", false) + buf.WriteByte(')') + + res := sig.ResultList + n := len(res) + if n == 0 { + // no result + return + } + + buf.WriteByte(' ') + if n == 1 && res[0].Name == nil { + // single unnamed result + WriteExpr(buf, res[0].Type) + return + } + + // multiple or named result(s) + buf.WriteByte('(') + writeFieldList(buf, res, ", ", false) + buf.WriteByte(')') +} + +func writeFieldList(buf *bytes.Buffer, list []*syntax.Field, sep string, iface bool) { + for i := 0; i < len(list); { + f := list[i] + if i > 0 { + buf.WriteString(sep) + } + + // if we don't have a name, we have an embedded type + if f.Name == nil { + WriteExpr(buf, f.Type) + i++ + continue + } + + // types of interface methods consist of signatures only + if sig, _ := f.Type.(*syntax.FuncType); sig != nil && iface { + buf.WriteString(f.Name.Value) + writeSigExpr(buf, sig) + i++ + continue + } + + // write the type only once for a sequence of fields with the same type + t := f.Type + buf.WriteString(f.Name.Value) + for i++; i < len(list) && list[i].Type == t; i++ { + buf.WriteString(", ") + buf.WriteString(list[i].Name.Value) + } + buf.WriteByte(' ') + WriteExpr(buf, t) + } +} + +func writeExprList(buf *bytes.Buffer, list []syntax.Expr) { + for i, x := range list { + if i > 0 { + buf.WriteString(", ") + } + WriteExpr(buf, x) + } +} diff --git a/src/cmd/compile/internal/types2/exprstring_test.go b/src/cmd/compile/internal/types2/exprstring_test.go new file mode 100644 index 0000000000..d7b9d5b2ef --- /dev/null +++ b/src/cmd/compile/internal/types2/exprstring_test.go @@ -0,0 +1,97 @@ +// UNREVIEWED +// 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 types2_test + +import ( + "testing" + + "cmd/compile/internal/syntax" + . "cmd/compile/internal/types2" +) + +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 { + src := "package p; var _ = " + test.src + f, err := parseSrc("expr", src) + if err != nil { + t.Errorf("%s: %s", test.src, err) + continue + } + x := f.DeclList[0].(*syntax.VarDecl).Values + if got := ExprString(x); got != test.str { + t.Errorf("%s: got %s, want %s", test.src, got, test.str) + } + } +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39634.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39634.go2 new file mode 100644 index 0000000000..f37930d0e8 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39634.go2 @@ -0,0 +1,92 @@ +// UNREVIEWED +// 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. + +// Examples adjusted to match new [T any] syntax for type parameters. +// Also, previously permitted empty type parameter lists and instantiations +// are now syntax errors. + +package p + +// crash 1 +type nt1[_ any]interface{g /* ERROR undeclared name */ } +type ph1[e nt1[e],g(d /* ERROR undeclared name */ )]s /* ERROR undeclared name */ +func(*ph1[e,e /* ERROR redeclared */ ])h(d /* ERROR undeclared name */ ) + +// crash 2 +// Disabled: empty []'s are now syntax errors. This example leads to too many follow-on errors. +// type Numeric2 interface{t2 /* ERROR not a type */ } +// func t2[T Numeric2](s[]T){0 /* ERROR not a type */ []{s /* ERROR cannot index */ [0][0]}} + +// crash 3 +type t3 *interface{ t3.p /* ERROR no field or method p */ } + +// crash 4 +type Numeric4 interface{t4 /* ERROR not a type */ } +func t4[T Numeric4](s[]T){if( /* ERROR non-boolean */ 0){*s /* ERROR cannot indirect */ [0]}} + +// crash 7 +type foo7 interface { bar() } +type x7[A any] struct{ foo7 } +func main7() { var _ foo7 = x7[int]{} } + +// crash 8 +type foo8[A any] interface { type A } +func bar8[A foo8[A]](a A) {} +func main8() {} + +// crash 9 +type foo9[A any] interface { type foo9 /* ERROR interface contains type constraints */ [A] } +func _() { var _ = new(foo9 /* ERROR interface contains type constraints */ [int]) } + +// crash 12 +var u /* ERROR cycle */ , i [func /* ERROR used as value */ /* ERROR used as value */ (u, c /* ERROR undeclared */ /* ERROR undeclared */ ) {}(0, len)]c /* ERROR undeclared */ /* ERROR undeclared */ + +// crash 15 +func y15() { var a /* ERROR declared but not used */ interface{ p() } = G15[string]{} } +type G15[X any] s /* ERROR undeclared name */ +func (G15 /* ERROR generic type .* without instantiation */ ) p() + +// crash 16 +type Foo16[T any] r16 /* ERROR not a type */ +func r16[T any]() Foo16[Foo16[T]] + +// crash 17 +type Y17 interface{ c() } +type Z17 interface { + c() Y17 + Y17 /* ERROR duplicate method */ +} +func F17[T Z17](T) + +// crash 18 +type o18[T any] []func(_ o18[[]_ /* ERROR cannot use _ */ ]) + +// crash 19 +type Z19 [][[]Z19{}[0][0]]c19 /* ERROR undeclared */ + +// crash 20 +type Z20 /* ERROR illegal cycle */ interface{ Z20 } +func F20[t Z20]() { F20(t /* ERROR invalid composite literal type */ {}) } + +// crash 21 +type Z21 /* ERROR illegal cycle */ interface{ Z21 } +func F21[T Z21]() { ( /* ERROR not used */ F21[Z21]) } + +// crash 24 +type T24[P any] P +func (r T24[P]) m() { T24 /* ERROR without instantiation */ .m() } + +// crash 25 +type T25[A any] int +func (t T25[A]) m1() {} +var x T25 /* ERROR without instantiation */ .m1 + +// crash 26 +type T26 = interface{ F26[ /* ERROR cannot have type parameters */ Z any]() } +func F26[Z any]() T26 { return F26 /* ERROR without instantiation */ /* ERROR missing method */ [] /* ERROR operand */ } + +// crash 27 +func e27[T any]() interface{ x27 /* ERROR not a type */ } +func x27() { e27( /* ERROR cannot infer T */ ) }
\ No newline at end of file diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39664.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39664.go2 new file mode 100644 index 0000000000..cf566c3e24 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39664.go2 @@ -0,0 +1,16 @@ +// UNREVIEWED +// 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 T[_ any] struct {} + +func (T /* ERROR instantiation */ ) m() + +func _() { + var x interface { m() } + x = T[int]{} + _ = x +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39680.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39680.go2 new file mode 100644 index 0000000000..3239c57d43 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39680.go2 @@ -0,0 +1,28 @@ +// UNREVIEWED +// 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 + +import "fmt" + +// Minimal test case. +func _[T interface{type T}](x T) T{ + return x +} + +// Test case from issue. +type constr[T any] interface { + type T +} + +func Print[T constr[T]](s []T) { + for _, v := range s { + fmt.Print(v) + } +} + +func f() { + Print([]string{"Hello, ", "playground\n"}) +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39693.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39693.go2 new file mode 100644 index 0000000000..6f4d701185 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39693.go2 @@ -0,0 +1,15 @@ +// UNREVIEWED +// 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 Number interface { + int /* ERROR int is not an interface */ + float64 /* ERROR float64 is not an interface */ +} + +func Add[T Number](a, b T) T { + return a /* ERROR not defined */ + b +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39699.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39699.go2 new file mode 100644 index 0000000000..c8655efee5 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39699.go2 @@ -0,0 +1,30 @@ +// UNREVIEWED +// 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 T0 interface{ +} + +type T1 interface{ + type int +} + +type T2 interface{ + comparable +} + +type T3 interface { + T0 + T1 + T2 +} + +func _() { + _ = T0(0) + _ = T1 /* ERROR cannot use interface T1 in conversion */ (1) + _ = T2 /* ERROR cannot use interface T2 in conversion */ (2) + _ = T3 /* ERROR cannot use interface T3 in conversion */ (3) +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39711.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39711.go2 new file mode 100644 index 0000000000..8edce78c10 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39711.go2 @@ -0,0 +1,12 @@ +// UNREVIEWED +// 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 + +// Do not report a duplicate type error for this type list. +// (Check types after interfaces have been completed.) +type _ interface { + type interface{ Error() string }, interface{ String() string } +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39723.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39723.go2 new file mode 100644 index 0000000000..8a4006ef84 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39723.go2 @@ -0,0 +1,10 @@ +// UNREVIEWED +// 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 + +// A constraint must be an interface; it cannot +// be a type parameter, for instance. +func _[A interface{ type interface{} }, B A /* ERROR not an interface */ ]() diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39725.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39725.go2 new file mode 100644 index 0000000000..6de661a38e --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39725.go2 @@ -0,0 +1,17 @@ +// UNREVIEWED +// 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 + +func f1[T1, T2 any](T1, T2, struct{a T1; b T2}) +func _() { + f1(42, string("foo"), struct /* ERROR does not match inferred type struct\{a int; b string\} */ {a, b int}{}) +} + +// simplified test case from issue +func f2[T any](_ []T, _ func(T)) +func _() { + f2([]string{}, func /* ERROR does not match inferred type func\(string\) */ (f []byte) {}) +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39754.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39754.go2 new file mode 100644 index 0000000000..36b774faaf --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39754.go2 @@ -0,0 +1,21 @@ +// UNREVIEWED +// 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 Optional[T any] struct {}
+
+func (_ Optional[T]) Val() (T, bool)
+
+type Box[T any] interface {
+ Val() (T, bool)
+}
+
+func f[V interface{}, A, B Box[V]]() {}
+
+func _() {
+ f[int, Optional[int], Optional[int]]()
+ f[int, Optional[int], Optional /* ERROR does not satisfy Box */ [string]]()
+}
diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39755.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39755.go2 new file mode 100644 index 0000000000..93aea85215 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39755.go2 @@ -0,0 +1,24 @@ +// UNREVIEWED +// 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 + +func _[T interface{type map[string]int}](x T) { + _ = x == nil +} + +// simplified test case from issue + +type PathParamsConstraint interface { + type map[string]string, []struct{key, value string} +} + +type PathParams[T PathParamsConstraint] struct { + t T +} + +func (pp *PathParams[T]) IsNil() bool { + return pp.t == nil // this must succeed +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39768.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39768.go2 new file mode 100644 index 0000000000..81b4a91f2c --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39768.go2 @@ -0,0 +1,21 @@ +// UNREVIEWED +// 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 T[P any] P +type A = T +var x A[int] +var _ A /* ERROR cannot use generic type */ + +type B = T[int] +var y B = x +var _ B /* ERROR not a generic type */ [int] + +// test case from issue + +type Vector[T any] []T +type VectorAlias = Vector +var v Vector[int] diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39938.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39938.go2 new file mode 100644 index 0000000000..19e69e6486 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39938.go2 @@ -0,0 +1,51 @@ +// UNREVIEWED +// 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. + +// Check "infinite expansion" cycle errors across instantiated types. + +package p + +type E0[P any] P +type E1[P any] *P +type E2[P any] struct{ P } +type E3[P any] struct{ *P } + +type T0 /* ERROR illegal cycle */ struct { + _ E0[T0] +} + +type T0_ /* ERROR illegal cycle */ struct { + E0[T0_] +} + +type T1 struct { + _ E1[T1] +} + +type T2 /* ERROR illegal cycle */ struct { + _ E2[T2] +} + +type T3 struct { + _ E3[T3] +} + +// some more complex cases + +type T4 /* ERROR illegal cycle */ struct { + _ E0[E2[T4]] +} + +type T5 struct { + _ E0[E2[E0[E1[E2[[10]T5]]]]] +} + +type T6 /* ERROR illegal cycle */ struct { + _ E0[[10]E2[E0[E2[E2[T6]]]]] +} + +type T7 struct { + _ E0[[]E2[E0[E2[E2[T6]]]]] +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39948.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39948.go2 new file mode 100644 index 0000000000..dede9c5797 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39948.go2 @@ -0,0 +1,10 @@ +// UNREVIEWED +// 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 T[P any] interface{ + P // ERROR P is a type parameter, not an interface +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39976.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39976.go2 new file mode 100644 index 0000000000..2ab9664f88 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39976.go2 @@ -0,0 +1,17 @@ +// UNREVIEWED +// 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 policy[K, V any] interface{} +type LRU[K, V any] struct{} + +func NewCache[K, V any](p policy[K, V]) + +func _() { + var lru LRU[int, string] + NewCache[int, string](&lru) + NewCache(& /* ERROR does not match policy\[K, V\] \(cannot infer K and V\) */ lru) +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue39982.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue39982.go2 new file mode 100644 index 0000000000..3abdfcb1b0 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue39982.go2 @@ -0,0 +1,37 @@ +// UNREVIEWED +// 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 ( + T[_ any] struct{} + S[_ any] struct { + data T[*T[int]] + } +) + +func _() { + _ = S[int]{ + data: T[*T[int]]{}, + } +} + +// full test case from issue + +type ( + Element[TElem any] struct{} + + entry[K comparable] struct{} + + Cache[K comparable] struct { + data map[K]*Element[*entry[K]] + } +) + +func _() { + _ = Cache[int]{ + data: make(map[int](*Element[*entry[int]])), + } +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue40038.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue40038.go2 new file mode 100644 index 0000000000..fe3963aac2 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue40038.go2 @@ -0,0 +1,16 @@ +// UNREVIEWED +// 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 A[T any] int + +func (A[T]) m(A[T]) + +func f[P interface{m(P)}]() + +func _() { + _ = f[A[int]] +}
\ No newline at end of file diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue40056.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue40056.go2 new file mode 100644 index 0000000000..0c78c3f289 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue40056.go2 @@ -0,0 +1,16 @@ +// UNREVIEWED +// 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 + +func _() { + NewS( /* ERROR cannot infer T */ ) .M() +} + +type S struct {} + +func NewS[T any]() *S + +func (_ *S /* ERROR S is not a generic type */ [T]) M()
\ No newline at end of file diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue40057.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue40057.go2 new file mode 100644 index 0000000000..b2ff11e4bf --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue40057.go2 @@ -0,0 +1,18 @@ +// UNREVIEWED +// 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 + +func _() { + var x interface{} + switch t := x.(type) { + case S /* ERROR cannot use generic type */ : + t.m() + } +} + +type S[T any] struct {} + +func (_ S[T]) m() diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue40301.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue40301.go2 new file mode 100644 index 0000000000..6a3dfc741e --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue40301.go2 @@ -0,0 +1,13 @@ +// UNREVIEWED +// 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 + +import "unsafe" + +func _[T any](x T) { + _ = unsafe /* ERROR undefined */ .Alignof(x) + _ = unsafe /* ERROR undefined */ .Sizeof(x) +} diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue40684.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue40684.go2 new file mode 100644 index 0000000000..001c6d7b99 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue40684.go2 @@ -0,0 +1,16 @@ +// UNREVIEWED +// 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 T[_ any] int + +func f[_ any]() +func g[_, _ any]() + +func _() { + _ = f[T /* ERROR without instantiation */ ] + _ = g[T /* ERROR without instantiation */ , T /* ERROR without instantiation */ ] +}
\ No newline at end of file diff --git a/src/cmd/compile/internal/types2/fixedbugs/issue41124.go2 b/src/cmd/compile/internal/types2/fixedbugs/issue41124.go2 new file mode 100644 index 0000000000..3098f44948 --- /dev/null +++ b/src/cmd/compile/internal/types2/fixedbugs/issue41124.go2 @@ -0,0 +1,92 @@ +// UNREVIEWED +// 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 + +// Test case from issue. + +type Nat interface { + type Zero, Succ +} + +type Zero struct{} +type Succ struct{ + Nat // ERROR interface contains type constraints +} + +// Struct tests. + +type I1 interface { + comparable +} + +type I2 interface { + type int +} + +type I3 interface { + I1 + I2 +} + +type _ struct { + f I1 // ERROR interface is .* comparable +} + +type _ struct { + comparable // ERROR interface is .* comparable +} + +type _ struct{ + I1 // ERROR interface is .* comparable +} + +type _ struct{ + I2 // ERROR interface contains type constraints +} + +type _ struct{ + I3 // ERROR interface contains type constraints +} + +// General composite types. + +type ( + _ [10]I1 // ERROR interface is .* comparable + _ [10]I2 // ERROR interface contains type constraints + + _ []I1 // ERROR interface is .* comparable + _ []I2 // ERROR interface contains type constraints + + _ *I3 // ERROR interface contains type constraints + _ map[I1 /* ERROR interface is .* comparable */ ]I2 // ERROR interface contains type constraints + _ chan I3 // ERROR interface contains type constraints + _ func(I1 /* ERROR interface is .* comparable */ ) + _ func() I2 // ERROR interface contains type constraints +) + +// Other cases. + +var _ = [...]I3 /* ERROR interface contains type constraints */ {} + +func _(x interface{}) { + _ = x.(I3 /* ERROR interface contains type constraints */ ) +} + +type T1[_ any] struct{} +type T3[_, _, _ any] struct{} +var _ T1[I2 /* ERROR interface contains type constraints */ ] +var _ T3[int, I2 /* ERROR interface contains type constraints */ , float32] + +func f1[_ any]() int +var _ = f1[I2 /* ERROR interface contains type constraints */ ]() +func f3[_, _, _ any]() int +var _ = f3[int, I2 /* ERROR interface contains type constraints */ , float32]() + +func _(x interface{}) { + switch x.(type) { + case I2 /* ERROR interface contains type constraints */ : + } +} diff --git a/src/cmd/compile/internal/types2/gccgosizes.go b/src/cmd/compile/internal/types2/gccgosizes.go new file mode 100644 index 0000000000..d3c79745a2 --- /dev/null +++ b/src/cmd/compile/internal/types2/gccgosizes.go @@ -0,0 +1,41 @@ +// UNREVIEWED +// 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 types2 + +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/cmd/compile/internal/types2/hilbert_test.go b/src/cmd/compile/internal/types2/hilbert_test.go new file mode 100644 index 0000000000..ee0c4daea6 --- /dev/null +++ b/src/cmd/compile/internal/types2/hilbert_test.go @@ -0,0 +1,220 @@ +// UNREVIEWED +// 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 types2_test + +import ( + "bytes" + "cmd/compile/internal/syntax" + "flag" + "fmt" + "io/ioutil" + "testing" + + . "cmd/compile/internal/types2" +) + +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 != "" { + ioutil.WriteFile(*out, src, 0666) + return + } + + // parse source + // TODO(gri) get rid of []bytes to string conversion below + f, err := parseSrc("hilbert.go", string(src)) + if err != nil { + t.Fatal(err) + } + + // type-check file + DefPredeclaredTestFuncs() // define assert built-in + conf := Config{Importer: defaultImporter()} + _, err = conf.Check(f.PkgName.Value, []*syntax.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/cmd/compile/internal/types2/importer_test.go b/src/cmd/compile/internal/types2/importer_test.go new file mode 100644 index 0000000000..90476c4269 --- /dev/null +++ b/src/cmd/compile/internal/types2/importer_test.go @@ -0,0 +1,36 @@ +// UNREVIEWED +// 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. + +// This file implements the (temporary) plumbing to get importing to work. + +package types2_test + +import ( + gcimporter "cmd/compile/internal/importer" + "cmd/compile/internal/types2" + "io" +) + +func defaultImporter() types2.Importer { + return &gcimports{ + packages: make(map[string]*types2.Package), + } +} + +type gcimports struct { + packages map[string]*types2.Package + lookup func(path string) (io.ReadCloser, error) +} + +func (m *gcimports) Import(path string) (*types2.Package, error) { + return m.ImportFrom(path, "" /* no vendoring */, 0) +} + +func (m *gcimports) ImportFrom(path, srcDir string, mode types2.ImportMode) (*types2.Package, error) { + if mode != 0 { + panic("mode must be 0") + } + return gcimporter.Import(m.packages, path, srcDir, m.lookup) +} diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go new file mode 100644 index 0000000000..b52a834e5a --- /dev/null +++ b/src/cmd/compile/internal/types2/infer.go @@ -0,0 +1,359 @@ +// UNREVIEWED +// 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 implements type parameter inference given +// a list of concrete arguments and a parameter list. + +package types2 + +import "strings" + +// infer returns the list of actual type arguments for the given list of type parameters tparams +// by inferring them from the actual arguments args for the parameters params. If type inference +// is impossible because unification fails, an error is reported and the resulting types list is +// nil, and index is 0. Otherwise, types is the list of inferred type arguments, and index is +// the index of the first type argument in that list that couldn't be inferred (and thus is nil). +// If all type arguments where inferred successfully, index is < 0. +func (check *Checker) infer(tparams []*TypeName, params *Tuple, args []*operand) (types []Type, index int) { + assert(params.Len() == len(args)) + + u := newUnifier(check, false) + u.x.init(tparams) + + errorf := func(kind string, tpar, targ Type, arg *operand) { + // provide a better error message if we can + targs, failed := u.x.types() + if failed == 0 { + // The first type parameter couldn't be inferred. + // If none of them could be inferred, don't try + // to provide the inferred type in the error msg. + allFailed := true + for _, targ := range targs { + if targ != nil { + allFailed = false + break + } + } + if allFailed { + check.errorf(arg, "%s %s of %s does not match %s (cannot infer %s)", kind, targ, arg.expr, tpar, typeNamesString(tparams)) + return + } + } + smap := makeSubstMap(tparams, targs) + inferred := check.subst(arg.Pos(), tpar, smap) + if inferred != tpar { + check.errorf(arg, "%s %s of %s does not match inferred type %s for %s", kind, targ, arg.expr, inferred, tpar) + } else { + check.errorf(arg, "%s %s of %s does not match %s", kind, targ, arg.expr, tpar) + } + } + + // Terminology: generic parameter = function parameter with a type-parameterized type + + // 1st pass: Unify parameter and argument types for generic parameters with typed arguments + // and collect the indices of generic parameters with untyped arguments. + var indices []int + for i, arg := range args { + par := params.At(i) + // If we permit bidirectional unification, this conditional code needs to be + // executed even if par.typ is not parameterized since the argument may be a + // generic function (for which we want to infer // its type arguments). + if isParameterized(tparams, par.typ) { + if arg.mode == invalid { + // An error was reported earlier. Ignore this targ + // and continue, we may still be able to infer all + // targs resulting in fewer follon-on errors. + continue + } + if targ := arg.typ; isTyped(targ) { + // If we permit bidirectional unification, and targ is + // a generic function, we need to initialize u.y with + // the respectice type parameters of targ. + if !u.unify(par.typ, targ) { + errorf("type", par.typ, targ, arg) + return nil, 0 + } + } else { + indices = append(indices, i) + } + } + } + + // Some generic parameters with untyped arguments may have been given a type + // indirectly through another generic parameter with a typed argument; we can + // ignore those now. (This only means that we know the types for those generic + // parameters; it doesn't mean untyped arguments can be passed safely. We still + // need to verify that assignment of those arguments is valid when we check + // function parameter passing external to infer.) + j := 0 + for _, i := range indices { + par := params.At(i) + // Since untyped types are all basic (i.e., non-composite) types, an + // untyped argument will never match a composite parameter type; the + // only parameter type it can possibly match against is a *TypeParam. + // Thus, only keep the indices of generic parameters that are not of + // composite types and which don't have a type inferred yet. + if tpar, _ := par.typ.(*TypeParam); tpar != nil && u.x.at(tpar.index) == nil { + indices[j] = i + j++ + } + } + indices = indices[:j] + + // 2nd pass: Unify parameter and default argument types for remaining generic parameters. + for _, i := range indices { + par := params.At(i) + arg := args[i] + targ := Default(arg.typ) + // The default type for an untyped nil is untyped nil. We must not + // infer an untyped nil type as type parameter type. Ignore untyped + // nil by making sure all default argument types are typed. + if isTyped(targ) && !u.unify(par.typ, targ) { + errorf("default type", par.typ, targ, arg) + return nil, 0 + } + } + + return u.x.types() +} + +// typeNamesString produces a string containing all the +// type names in list suitable for human consumption. +func typeNamesString(list []*TypeName) string { + // common cases + n := len(list) + switch n { + case 0: + return "" + case 1: + return list[0].name + case 2: + return list[0].name + " and " + list[1].name + } + + // general case (n > 2) + var b strings.Builder + for i, tname := range list[:n-1] { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(tname.name) + } + b.WriteString(", and ") + b.WriteString(list[n-1].name) + return b.String() +} + +// IsParameterized reports whether typ contains any of the type parameters of tparams. +func isParameterized(tparams []*TypeName, typ Type) bool { + w := tpWalker{ + seen: make(map[Type]bool), + tparams: tparams, + } + return w.isParameterized(typ) +} + +type tpWalker struct { + seen map[Type]bool + tparams []*TypeName +} + +func (w *tpWalker) isParameterized(typ Type) (res bool) { + // detect cycles + if x, ok := w.seen[typ]; ok { + return x + } + w.seen[typ] = false + defer func() { + w.seen[typ] = res + }() + + switch t := typ.(type) { + case nil, *Basic: // TODO(gri) should nil be handled here? + break + + case *Array: + return w.isParameterized(t.elem) + + case *Slice: + return w.isParameterized(t.elem) + + case *Struct: + for _, fld := range t.fields { + if w.isParameterized(fld.typ) { + return true + } + } + + case *Pointer: + return w.isParameterized(t.base) + + case *Tuple: + n := t.Len() + for i := 0; i < n; i++ { + if w.isParameterized(t.At(i).typ) { + return true + } + } + + case *Sum: + return w.isParameterizedList(t.types) + + case *Signature: + // t.tparams may not be nil if we are looking at a signature + // of a generic function type (or an interface method) that is + // part of the type we're testing. We don't care about these type + // parameters. + // Similarly, the receiver of a method may declare (rather then + // use) type parameters, we don't care about those either. + // Thus, we only need to look at the input and result parameters. + return w.isParameterized(t.params) || w.isParameterized(t.results) + + case *Interface: + if t.allMethods != nil { + // interface is complete - quick test + for _, m := range t.allMethods { + if w.isParameterized(m.typ) { + return true + } + } + return w.isParameterizedList(unpack(t.allTypes)) + } + + return t.iterate(func(t *Interface) bool { + for _, m := range t.methods { + if w.isParameterized(m.typ) { + return true + } + } + return w.isParameterizedList(unpack(t.types)) + }, nil) + + case *Map: + return w.isParameterized(t.key) || w.isParameterized(t.elem) + + case *Chan: + return w.isParameterized(t.elem) + + case *Named: + return w.isParameterizedList(t.targs) + + case *TypeParam: + // t must be one of w.tparams + return t.index < len(w.tparams) && w.tparams[t.index].typ == t + + case *instance: + return w.isParameterizedList(t.targs) + + default: + unreachable() + } + + return false +} + +func (w *tpWalker) isParameterizedList(list []Type) bool { + for _, t := range list { + if w.isParameterized(t) { + return true + } + } + return false +} + +// inferB returns the list of actual type arguments inferred from the type parameters' +// bounds and an initial set of type arguments. If type inference is impossible because +// unification fails, an error is reported, the resulting types list is nil, and index is 0. +// Otherwise, types is the list of inferred type arguments, and index is the index of the +// first type argument in that list that couldn't be inferred (and thus is nil). If all +// type arguments where inferred successfully, index is < 0. The number of type arguments +// provided may be less than the number of type parameters, but there must be at least one. +func (check *Checker) inferB(tparams []*TypeName, targs []Type) (types []Type, index int) { + assert(len(tparams) >= len(targs) && len(targs) > 0) + + // Setup bidirectional unification between those structural bounds + // and the corresponding type arguments (which may be nil!). + u := newUnifier(check, false) + u.x.init(tparams) + u.y = u.x // type parameters between LHS and RHS of unification are identical + + // Set the type arguments which we know already. + for i, targ := range targs { + if targ != nil { + u.x.set(i, targ) + } + } + + // Unify type parameters with their structural constraints, if any. + for _, tpar := range tparams { + typ := tpar.typ.(*TypeParam) + sbound := check.structuralType(typ.bound.Under()) + if sbound != nil { + //check.dump(">>> unify(%s, %s)", tpar, sbound) + if !u.unify(typ, sbound) { + check.errorf(tpar.pos, "%s does not match %s", tpar, sbound) + return nil, 0 + } + //check.dump(">>> => indices = %v, types = %s", u.x.indices, u.types) + } + } + + // u.x.types() now contains the incoming type arguments plus any additional type + // arguments for which there were structural constraints. The newly inferred non- + // nil entries may still contain references to other type parameters. For instance, + // for [type A interface{}, B interface{type []C}, C interface{type *A}], if A == int + // was given, unification produced the type list [int, []C, *A]. We eliminate the + // remaining type parameters by substituting the type parameters in this type list + // until nothing changes anymore. + types, index = u.x.types() + if debug { + for i, targ := range targs { + assert(targ == nil || types[i] == targ) + } + } + + // dirty tracks the indices of all types that may still contain type parameters. + // We know that nil types entries and entries corresponding to provided (non-nil) + // type arguments are clean, so exclude them from the start. + var dirty []int + for i, typ := range types { + if typ != nil && (i >= len(targs) || targs[i] == nil) { + dirty = append(dirty, i) + } + } + + for len(dirty) > 0 { + // TODO(gri) Instead of creating a new smap for each iteration, + // provide an update operation for smaps and only change when + // needed. Optimization. + smap := makeSubstMap(tparams, types) + n := 0 + for _, index := range dirty { + t0 := types[index] + if t1 := check.subst(nopos, t0, smap); t1 != t0 { + types[index] = t1 + dirty[n] = index + n++ + } + } + dirty = dirty[:n] + } + //check.dump(">>> inferred types = %s", types) + + return +} + +// structuralType returns the structural type of a constraint, if any. +func (check *Checker) structuralType(constraint Type) Type { + if iface, _ := constraint.(*Interface); iface != nil { + check.completeInterface(nopos, iface) + types := unpack(iface.allTypes) + if len(types) == 1 { + return types[0] + } + return nil + } + return constraint +} diff --git a/src/cmd/compile/internal/types2/initorder.go b/src/cmd/compile/internal/types2/initorder.go new file mode 100644 index 0000000000..3bb92d9622 --- /dev/null +++ b/src/cmd/compile/internal/types2/initorder.go @@ -0,0 +1,298 @@ +// UNREVIEWED +// 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 types2 + +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, "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, "\t%s refers to", obj.Name()) // secondary error, \t indented + obj = cycle[i] + } + // print cycle[0] again to close the cycle + check.errorf(obj, "\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/cmd/compile/internal/types2/issues_test.go b/src/cmd/compile/internal/types2/issues_test.go new file mode 100644 index 0000000000..6d39f99424 --- /dev/null +++ b/src/cmd/compile/internal/types2/issues_test.go @@ -0,0 +1,533 @@ +// UNREVIEWED +// 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 types2_test + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "internal/testenv" + "sort" + "strings" + "testing" + + . "cmd/compile/internal/types2" +) + +func mustParse(t *testing.T, src string) *syntax.File { + f, err := parseSrc("", src) + if err != nil { + t.Fatal(err) + } + return f +} +func TestIssue5770(t *testing.T) { + f := mustParse(t, `package p; type S struct{T}`) + var conf Config + // conf := Config{Importer: importer.Default()} + _, err := conf.Check(f.PkgName.Value, []*syntax.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[syntax.Expr]TypeAndValue) + _, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, &Info{Types: types}) + if err != nil { + t.Fatal(err) + } + + for x, tv := range types { + var want Type + switch x := x.(type) { + case *syntax.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 *syntax.Name: + if x.Value == "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[syntax.Expr]TypeAndValue) + _, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, &Info{Types: types}) + if err != nil { + t.Fatal(err) + } + + want := Typ[Int] + n := 0 + for x, tv := range types { + if _, ok := x.(*syntax.CallExpr); ok { + if tv.Type != want { + t.Errorf("%s: got %s; want %s", 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[*syntax.Name]Object) + _, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, &Info{Defs: defs}) + if err != nil { + t.Fatal(err) + } + + m := f.DeclList[0].(*syntax.FuncDecl) + res1 := defs[m.Name].(*Func).Type().(*Signature).Results().At(0) + res2 := defs[m.Type.ResultList[0].Name].(*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[*syntax.Name]Object) + uses := make(map[*syntax.Name]Object) + _, err := conf.Check(f.PkgName.Value, []*syntax.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", id.Pos().Line(), obj) + facts = append(facts, fact) + } + } + for id, obj := range uses { + fact := fmt.Sprintf("L%d uses %s", 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) + conf := Config{Importer: defaultImporter()} + info := Info{Uses: make(map[*syntax.Name]Object)} + _, err := conf.Check("main", []*syntax.File{f}, &info) + if err != nil { + t.Fatal(err) + } + + var pkg *Package + count := 0 + for id, obj := range info.Uses { + if id.Value == "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 types2", 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.PkgName.Value, []*syntax.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) { + t.Skip("requires syntax tree inspection") + + 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) + + conf := Config{Importer: defaultImporter(), Error: func(err error) {}} + info := &Info{Types: make(map[syntax.Expr]TypeAndValue)} + _, err := conf.Check(f.PkgName.Value, []*syntax.File{f}, info) + if err != nil { + if _, ok := err.(Error); !ok { + t.Fatal(err) + } + } + + unimplemented() + /* + ast.Inspect(f, func(n syntax.Node) bool { + if spec, _ := n.(*syntax.TypeDecl); spec != nil { + if tv, ok := info.Types[spec.Type]; ok && spec.Name.Value == "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)]*syntax.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([]*syntax.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[*syntax.Name]Object)} + _, err := conf.Check("", files, info) + if err != nil { + t.Fatal(err) + } + + // look for interface object X + var obj Object + for name, def := range info.Defs { + if name.Value == "X" { + obj = def + break + } + } + if obj == nil { + t.Fatal("object X not found") + } + iface := obj.Type().Interface() // object X must be an interface + if iface == nil { + t.Fatalf("%s is not an interface", obj) + } + + // 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.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[*syntax.Name]Object)} + check := NewChecker(&conf, NewPackage("", "p"), info) + if err := check.Files([]*syntax.File{f1, f2}); err != nil { + t.Fatal(err) + } + want := printInfo(info) + + // type-check incrementally + info = &Info{Defs: make(map[*syntax.Name]Object)} + check = NewChecker(&conf, NewPackage("", "p"), info) + if err := check.Files([]*syntax.File{f1}); err != nil { + t.Fatal(err) + } + if err := check.Files([]*syntax.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.PkgName.Value, []*syntax.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.PkgName.Value, []*syntax.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 + } +} diff --git a/src/cmd/compile/internal/types2/labels.go b/src/cmd/compile/internal/types2/labels.go new file mode 100644 index 0000000000..ca5fe6b389 --- /dev/null +++ b/src/cmd/compile/internal/types2/labels.go @@ -0,0 +1,260 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" +) + +// labels checks correct label use in body. +func (check *Checker) labels(body *syntax.BlockStmt) { + // set of all labels in this body + all := NewScope(nil, body.Pos(), endPos(body), "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 + name := jmp.Label.Value + if alt := all.Lookup(name); alt != nil { + msg = "goto %s jumps into block" + alt.(*Label).used = true // avoid another error + } else { + msg = "label %s not declared" + } + check.errorf(jmp.Label, 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.pos, "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 *syntax.LabeledStmt // labeled statement to which this block belongs, or nil + labels map[string]*syntax.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 *syntax.LabeledStmt) { + name := s.Label.Value + if debug { + assert(b.gotoTarget(name) == nil) + } + labels := b.labels + if labels == nil { + labels = make(map[string]*syntax.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) *syntax.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) *syntax.LabeledStmt { + for s := b; s != nil; s = s.parent { + if t := s.lstmt; t != nil && t.Label.Value == 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 *syntax.LabeledStmt, list []syntax.Stmt) []*syntax.BranchStmt { + b := &block{parent, lstmt, nil} + + var ( + varDeclPos syntax.Pos + fwdJumps, badJumps []*syntax.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 syntax.Pos) { + varDeclPos = pos + badJumps = append(badJumps[:0], fwdJumps...) // copy fwdJumps to badJumps + } + + jumpsOverVarDecl := func(jmp *syntax.BranchStmt) bool { + if varDeclPos.IsKnown() { + for _, bad := range badJumps { + if jmp == bad { + return true + } + } + } + return false + } + + var stmtBranches func(syntax.Stmt) + stmtBranches = func(s syntax.Stmt) { + switch s := s.(type) { + case *syntax.DeclStmt: + for _, d := range s.DeclList { + if d, _ := d.(*syntax.VarDecl); d != nil { + recordVarDecl(d.Pos()) + } + } + + case *syntax.LabeledStmt: + // declare non-blank label + if name := s.Label.Value; name != "_" { + lbl := NewLabel(s.Label.Pos(), check.pkg, name) + if alt := all.Insert(lbl); alt != nil { + check.softErrorf(lbl.pos, "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.Value == name { + // match + lbl.used = true + check.recordUse(jmp.Label, lbl) + if jumpsOverVarDecl(jmp) { + check.softErrorf( + jmp.Label, + "goto %s jumps over variable declaration at line %d", + name, + 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 *syntax.BranchStmt: + if s.Label == nil { + return // checked in 1st pass (check.stmt) + } + + // determine and validate target + name := s.Label.Value + switch s.Tok { + case syntax.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 *syntax.SwitchStmt, *syntax.SelectStmt, *syntax.ForStmt: + valid = true + } + } + if !valid { + check.errorf(s.Label, "invalid break label %s", name) + return + } + + case syntax.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 *syntax.ForStmt: + valid = true + } + } + if !valid { + check.errorf(s.Label, "invalid continue label %s", name) + return + } + + case syntax.Goto: + if b.gotoTarget(name) == nil { + // label may be declared later - add branch to forward jumps + fwdJumps = append(fwdJumps, s) + return + } + + default: + check.invalidASTf(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 *syntax.AssignStmt: + if s.Op == syntax.Def { + recordVarDecl(s.Pos()) + } + + case *syntax.BlockStmt: + // Unresolved forward jumps inside the nested block + // become forward jumps in the current block. + fwdJumps = append(fwdJumps, check.blockBranches(all, b, lstmt, s.List)...) + + case *syntax.IfStmt: + stmtBranches(s.Then) + if s.Else != nil { + stmtBranches(s.Else) + } + + case *syntax.SwitchStmt: + b := &block{b, lstmt, nil} + for _, s := range s.Body { + fwdJumps = append(fwdJumps, check.blockBranches(all, b, nil, s.Body)...) + } + + case *syntax.SelectStmt: + b := &block{b, lstmt, nil} + for _, s := range s.Body { + fwdJumps = append(fwdJumps, check.blockBranches(all, b, nil, s.Body)...) + } + + case *syntax.ForStmt: + stmtBranches(s.Body) + } + } + + for _, s := range list { + stmtBranches(s) + } + + return fwdJumps +} diff --git a/src/cmd/compile/internal/types2/lookup.go b/src/cmd/compile/internal/types2/lookup.go new file mode 100644 index 0000000000..277212c568 --- /dev/null +++ b/src/cmd/compile/internal/types2/lookup.go @@ -0,0 +1,493 @@ +// UNREVIEWED +// 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 types2 + +// 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. + // Be cautious: typ may be nil (issue 39634, crash #3). + if typ == nil || 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 + var tpar *TypeParam // set if obj receiver is a type parameter + 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, but only if it's not a type parameter + // TODO(gri) is this what we want to do for type parameters? (spec question) + typ = named.Under() + if typ.TypeParam() != nil { + continue + } + } + + tpar = nil + 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(nopos, 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 + } + + case *TypeParam: + if i, m := lookupMethod(t.Bound().allMethods, pkg, name); m != nil { + assert(m.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + tpar = t + obj = m + indirect = e.indirect + } + if obj == nil { + // At this point we're not (yet) looking into methods + // that any underlyng type of the types in the type list + // migth have. + // TODO(gri) Do we want to specify the language that way? + } + } + } + + 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 { + // determine if method has a pointer receiver + hasPtrRecv := tpar == nil && ptrRecv(f) || tpar != nil && tpar.ptr + if hasPtrRecv && !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 *Checker as +// receiver and an addressable flag. +// 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(nopos, T) + + // fast path for common case + if T.Empty() { + return + } + + if ityp := V.Interface(); ityp != nil { + check.completeInterface(nopos, ityp) + // TODO(gri) allMethods is sorted - can do this more efficiently + for _, m := range T.allMethods { + _, f := lookupMethod(ityp.allMethods, m.pkg, m.name) + + if f == nil { + // if m is the magic method == we're ok (interfaces are comparable) + if m.name == "==" || !static { + continue + } + return m, f + } + + // both methods must have the same number of type parameters + ftyp := f.typ.(*Signature) + mtyp := m.typ.(*Signature) + if len(ftyp.tparams) != len(mtyp.tparams) { + return m, f + } + + // If the methods have type parameters we don't care whether they + // are the same or not, as long as they match up. Use unification + // to see if they can be made to match. + // TODO(gri) is this always correct? what about type bounds? + // (Alternative is to rename/subst type parameters and compare.) + u := newUnifier(check, true) + u.x.init(ftyp.tparams) + if !u.unify(ftyp, mtyp) { + return m, f + } + } + + return + } + + // A concrete type implements T if it implements all methods of T. + Vd, _ := deref(V) + Vn := Vd.Named() + for _, m := range T.allMethods { + // TODO(gri) should this be calling lookupFieldOrMethod instead (and why not)? + 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 { + // if m is the magic method == and V is comparable, we're ok + if m.name == "==" && Comparable(V) { + continue + } + return m, nil + } + + // methods may not have a fully set up signature yet + if check != nil { + check.objDecl(f, nil) + } + + // both methods must have the same number of type parameters + ftyp := f.typ.(*Signature) + mtyp := m.typ.(*Signature) + if len(ftyp.tparams) != len(mtyp.tparams) { + return m, f + } + + // If V is a (instantiated) generic type, its methods are still + // parameterized using the original (declaration) receiver type + // parameters (subst simply copies the existing method list, it + // does not instantiate the methods). + // In order to compare the signatures, substitute the receiver + // type parameters of ftyp with V's instantiation type arguments. + // This lazily instantiates the signature of method f. + if Vn != nil && len(Vn.tparams) > 0 { + // Be careful: The number of type arguments may not match + // the number of receiver parameters. If so, an error was + // reported earlier but the length discrepancy is still + // here. Exit early in this case to prevent an assertion + // failure in makeSubstMap. + // TODO(gri) Can we avoid this check by fixing the lengths? + if len(ftyp.rparams) != len(Vn.targs) { + return + } + ftyp = check.subst(nopos, ftyp, makeSubstMap(ftyp.rparams, Vn.targs)).(*Signature) + } + + // If the methods have type parameters we don't care whether they + // are the same or not, as long as they match up. Use unification + // to see if they can be made to match. + // TODO(gri) is this always correct? what about type bounds? + // (Alternative is to rename/subst type parameters and compare.) + u := newUnifier(check, true) + u.x.init(ftyp.tparams) + if !u.unify(ftyp, mtyp) { + 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. +// If strict (or the global constant forceStrict) is set, assertions that +// are known to fail are not permitted. +func (check *Checker) assertableTo(V *Interface, T Type, strict bool) (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 T.Interface() != nil && !(strict || forceStrict) { + 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.Pointer(); p != nil { + if p.base.Struct() != nil { + 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/cmd/compile/internal/types2/methodset.go b/src/cmd/compile/internal/types2/methodset.go new file mode 100644 index 0000000000..9f7315a0fa --- /dev/null +++ b/src/cmd/compile/internal/types2/methodset.go @@ -0,0 +1,261 @@ +// UNREVIEWED +// 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 types2 + +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/cmd/compile/internal/types2/object.go b/src/cmd/compile/internal/types2/object.go new file mode 100644 index 0000000000..6e6f48c036 --- /dev/null +++ b/src/cmd/compile/internal/types2/object.go @@ -0,0 +1,492 @@ +// UNREVIEWED +// 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 types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" + "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() syntax.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 + + // setType sets the type of the object. + setType(Type) + + // 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() syntax.Pos + + // setScopePos sets the start position of the scope for this Object. + setScopePos(pos syntax.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 syntax.Pos + pkg *Package + name string + typ Type + order_ uint32 + color_ color + scopePos_ syntax.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() syntax.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() syntax.Pos { return obj.scopePos_ } + +func (obj *object) setParent(parent *Scope) { obj.parent = parent } +func (obj *object) setType(typ Type) { obj.typ = typ } +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 syntax.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 syntax.Pos, pkg *Package, name string, imported *Package) *PkgName { + return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, 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 syntax.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const { + return &Const{object{nil, pos, pkg, name, typ, 0, colorFor(typ), 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 syntax.Pos, pkg *Package, name string, typ Type) *TypeName { + return &TypeName{object{nil, pos, pkg, name, typ, 0, colorFor(typ), 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 syntax.Pos, pkg *Package, name string, typ Type) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}} +} + +// NewParam returns a new variable representing a function parameter. +func NewParam(pos syntax.Pos, pkg *Package, name string, typ Type) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), 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 syntax.Pos, pkg *Package, name string, typ Type, embedded bool) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), 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 syntax.Pos, pkg *Package, name string, sig *Signature) *Func { + // don't store a (typed) nil signature + var typ Type + if sig != nil { + typ = sig + } + return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), 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 syntax.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.Under() + } + } + + 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/cmd/compile/internal/types2/object_test.go b/src/cmd/compile/internal/types2/object_test.go new file mode 100644 index 0000000000..8f11c87451 --- /dev/null +++ b/src/cmd/compile/internal/types2/object_test.go @@ -0,0 +1,89 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" + "strings" + "testing" +) + +func parseSrc(path, src string) (*syntax.File, error) { + return syntax.Parse(syntax.NewFileBase(path), strings.NewReader(src), nil, nil, 0) +} + +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(nopos, pkg, "t1", nil) + n1 := NewNamed(t1, new(Struct), nil) + for _, test := range []struct { + name *TypeName + alias bool + }{ + {NewTypeName(nopos, nil, "t0", nil), false}, // no type yet + {NewTypeName(nopos, pkg, "t0", nil), false}, // no type yet + {t1, false}, // type name refers to named type and vice versa + {NewTypeName(nopos, nil, "t2", &emptyInterface), true}, // type name refers to unnamed type + {NewTypeName(nopos, pkg, "t3", n1), true}, // type name refers to named type with different type name + {NewTypeName(nopos, nil, "t4", Typ[Int32]), true}, // type name refers to basic type with different name + {NewTypeName(nopos, nil, "int32", Typ[Int32]), false}, // type name refers to basic type with same name + {NewTypeName(nopos, pkg, "int32", Typ[Int32]), true}, // type name is declared in user-defined package (outside Universe) + {NewTypeName(nopos, 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 + f, err := parseSrc("", src) + if err != nil { + t.Fatalf("parse failed: %s", err) + } + var conf Config + pkg, err := conf.Check(f.PkgName.Value, []*syntax.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/cmd/compile/internal/types2/objset.go b/src/cmd/compile/internal/types2/objset.go new file mode 100644 index 0000000000..ef06315705 --- /dev/null +++ b/src/cmd/compile/internal/types2/objset.go @@ -0,0 +1,32 @@ +// UNREVIEWED +// 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 types2 + +// 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/cmd/compile/internal/types2/operand.go b/src/cmd/compile/internal/types2/operand.go new file mode 100644 index 0000000000..fe88921893 --- /dev/null +++ b/src/cmd/compile/internal/types2/operand.go @@ -0,0 +1,315 @@ +// UNREVIEWED +// 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 types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "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 syntax.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 nopos. +// +func (x *operand) Pos() syntax.Pos { + // x.expr may not be set if x is invalid + if x.expr == nil { + return 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 <expr> ( <mode> ) +// novalue <expr> ( <mode> ) +// builtin <expr> ( <mode> ) +// typexpr <expr> ( <mode> ) +// +// constant <expr> (<untyped kind> <mode> ) +// constant <expr> ( <mode> of type <typ>) +// constant <expr> (<untyped kind> <mode> <val> ) +// constant <expr> ( <mode> <val> of type <typ>) +// +// variable <expr> (<untyped kind> <mode> ) +// variable <expr> ( <mode> of type <typ>) +// +// mapindex <expr> (<untyped kind> <mode> ) +// mapindex <expr> ( <mode> of type <typ>) +// +// value <expr> (<untyped kind> <mode> ) +// value <expr> ( <mode> of type <typ>) +// +// commaok <expr> (<untyped kind> <mode> ) +// commaok <expr> ( <mode> of type <typ>) +// +// commaerr <expr> (<untyped kind> <mode> ) +// commaerr <expr> ( <mode> of type <typ>) +// +// cgofunc <expr> (<untyped kind> <mode> ) +// cgofunc <expr> ( <mode> of type <typ>) +// +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() + } + } + + // <expr> ( + if expr != "" { + buf.WriteString(expr) + buf.WriteString(" (") + } + + // <untyped kind> + 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 + } + } + + // <mode> + buf.WriteString(operandModeString[x.mode]) + + // <val> + if x.mode == constant_ { + if s := x.val.String(); s != expr { + buf.WriteByte(' ') + buf.WriteString(s) + } + } + + // <typ> + if hasType { + if x.typ != Typ[Invalid] { + var intro string + switch { + case isGeneric(x.typ): + intro = " of generic type " + case x.typ.TypeParam() != nil: + intro = " of type parameter type " + default: + intro = " of type " + } + buf.WriteString(intro) + 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(k syntax.LitKind, lit string) { + var tok token.Token + var kind BasicKind + switch k { + case syntax.IntLit: + tok = token.INT + kind = UntypedInt + case syntax.FloatLit: + tok = token.FLOAT + kind = UntypedFloat + case syntax.ImagLit: + tok = token.IMAG + kind = UntypedComplex + case syntax.RuneLit: + tok = token.CHAR + kind = UntypedRune + case syntax.StringLit: + tok = token.STRING + kind = UntypedString + default: + unreachable() + } + + x.mode = constant_ + x.typ = Typ[kind] + x.val = constant.MakeFromLiteral(lit, tok, 0) +} + +// isNil reports whether x is the nil value. +func (x *operand) isNil() bool { + return x.mode == value && x.typ == Typ[UntypedNil] +} + +// TODO(gri) The functions operand.assignableTo, checker.convertUntyped, +// checker.representable, and checker.assignment are +// overlapping in functionality. Need to simplify and clean up. + +// 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 { + if x.mode == invalid || T == Typ[Invalid] { + return true // avoid spurious errors + } + + V := x.typ + + // x's type is identical to T + if check.identical(V, T) { + return true + } + + Vu := optype(V.Under()) + Tu := optype(T.Under()) + + // x is an untyped value representable by a value of type T + // TODO(gri) This is borrowing from checker.convertUntyped and + // checker.representable. Need to clean up. + if isUntyped(Vu) { + switch t := Tu.(type) { + case *Basic: + if x.isNil() && t.kind == UnsafePointer { + return true + } + if x.mode == constant_ { + return representableConst(x.val, check, t, nil) + } + // The result of a comparison is an untyped boolean, + // but may not be a constant. + if Vb, _ := Vu.(*Basic); Vb != nil { + return Vb.kind == UntypedBool && isBoolean(Tu) + } + case *Sum: + return t.is(func(t Type) bool { + // TODO(gri) this could probably be more efficient + return x.assignableTo(check, t, reason) + }) + case *Interface: + check.completeInterface(nopos, t) + return x.isNil() || t.Empty() + case *Pointer, *Signature, *Slice, *Map, *Chan: + return x.isNil() + } + } + // 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 + } + + // 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 + } + return true + } + + // 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) { + return !isNamed(V) || !isNamed(T) + } + } + + return false +} diff --git a/src/cmd/compile/internal/types2/package.go b/src/cmd/compile/internal/types2/package.go new file mode 100644 index 0000000000..03ae6ff5b7 --- /dev/null +++ b/src/cmd/compile/internal/types2/package.go @@ -0,0 +1,65 @@ +// UNREVIEWED +// 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 types2 + +import ( + "fmt" +) + +// 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, nopos, 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/cmd/compile/internal/types2/pos.go b/src/cmd/compile/internal/types2/pos.go new file mode 100644 index 0000000000..4dd839b7dc --- /dev/null +++ b/src/cmd/compile/internal/types2/pos.go @@ -0,0 +1,364 @@ +// UNREVIEWED +// 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 helper functions for scope position computations. + +package types2 + +import "cmd/compile/internal/syntax" + +// startPos returns the start position of n. +func startPos(n syntax.Node) syntax.Pos { + // Cases for nodes which don't need a correction are commented out. + for m := n; ; { + switch n := m.(type) { + case nil: + panic("internal error: nil") + + // packages + case *syntax.File: + // file block starts at the beginning of the file + return syntax.MakePos(n.Pos().Base(), 1, 1) + + // declarations + // case *syntax.ImportDecl: + // case *syntax.ConstDecl: + // case *syntax.TypeDecl: + // case *syntax.VarDecl: + // case *syntax.FuncDecl: + + // expressions + // case *syntax.BadExpr: + // case *syntax.Name: + // case *syntax.BasicLit: + case *syntax.CompositeLit: + if n.Type != nil { + m = n.Type + continue + } + return n.Pos() + // case *syntax.KeyValueExpr: + // case *syntax.FuncLit: + // case *syntax.ParenExpr: + case *syntax.SelectorExpr: + m = n.X + case *syntax.IndexExpr: + m = n.X + // case *syntax.SliceExpr: + case *syntax.AssertExpr: + m = n.X + case *syntax.TypeSwitchGuard: + if n.Lhs != nil { + m = n.Lhs + continue + } + m = n.X + case *syntax.Operation: + if n.Y != nil { + m = n.X + continue + } + return n.Pos() + case *syntax.CallExpr: + m = n.Fun + case *syntax.ListExpr: + if len(n.ElemList) > 0 { + m = n.ElemList[0] + continue + } + return n.Pos() + // types + // case *syntax.ArrayType: + // case *syntax.SliceType: + // case *syntax.DotsType: + // case *syntax.StructType: + // case *syntax.Field: + // case *syntax.InterfaceType: + // case *syntax.FuncType: + // case *syntax.MapType: + // case *syntax.ChanType: + + // statements + // case *syntax.EmptyStmt: + // case *syntax.LabeledStmt: + // case *syntax.BlockStmt: + // case *syntax.ExprStmt: + case *syntax.SendStmt: + m = n.Chan + // case *syntax.DeclStmt: + case *syntax.AssignStmt: + m = n.Lhs + // case *syntax.BranchStmt: + // case *syntax.CallStmt: + // case *syntax.ReturnStmt: + // case *syntax.IfStmt: + // case *syntax.ForStmt: + // case *syntax.SwitchStmt: + // case *syntax.SelectStmt: + + // helper nodes + case *syntax.RangeClause: + if n.Lhs != nil { + m = n.Lhs + continue + } + m = n.X + // case *syntax.CaseClause: + // case *syntax.CommClause: + + default: + return n.Pos() + } + } +} + +// endPos returns the approximate end position of n in the source. +// For some nodes (*syntax.Name, *syntax.BasicLit) it returns +// the position immediately following the node; for others +// (*syntax.BlockStmt, *syntax.SwitchStmt, etc.) it returns +// the position of the closing '}'; and for some (*syntax.ParenExpr) +// the returned position is the end position of the last enclosed +// expression. +// Thus, endPos should not be used for exact demarcation of the +// end of a node in the source; it is mostly useful to determine +// scope ranges where there is some leeway. +func endPos(n syntax.Node) syntax.Pos { + for m := n; ; { + switch n := m.(type) { + case nil: + panic("internal error: nil") + + // packages + case *syntax.File: + return n.EOF + + // declarations + case *syntax.ImportDecl: + m = n.Path + case *syntax.ConstDecl: + if n.Values != nil { + m = n.Values + continue + } + if n.Type != nil { + m = n.Type + continue + } + if l := len(n.NameList); l > 0 { + m = n.NameList[l-1] + continue + } + return n.Pos() + case *syntax.TypeDecl: + m = n.Type + case *syntax.VarDecl: + if n.Values != nil { + m = n.Values + continue + } + if n.Type != nil { + m = n.Type + continue + } + if l := len(n.NameList); l > 0 { + m = n.NameList[l-1] + continue + } + return n.Pos() + case *syntax.FuncDecl: + if n.Body != nil { + m = n.Body + continue + } + m = n.Type + + // expressions + case *syntax.BadExpr: + return n.Pos() + case *syntax.Name: + p := n.Pos() + return syntax.MakePos(p.Base(), p.Line(), p.Col()+uint(len(n.Value))) + case *syntax.BasicLit: + p := n.Pos() + return syntax.MakePos(p.Base(), p.Line(), p.Col()+uint(len(n.Value))) + case *syntax.CompositeLit: + return n.Rbrace + case *syntax.KeyValueExpr: + m = n.Value + case *syntax.FuncLit: + m = n.Body + case *syntax.ParenExpr: + m = n.X + case *syntax.SelectorExpr: + m = n.Sel + case *syntax.IndexExpr: + m = n.Index + case *syntax.SliceExpr: + for i := len(n.Index) - 1; i >= 0; i-- { + if x := n.Index[i]; x != nil { + m = x + continue + } + } + m = n.X + case *syntax.AssertExpr: + m = n.Type + case *syntax.TypeSwitchGuard: + m = n.X + case *syntax.Operation: + if n.Y != nil { + m = n.Y + continue + } + m = n.X + case *syntax.CallExpr: + if l := lastExpr(n.ArgList); l != nil { + m = l + continue + } + m = n.Fun + case *syntax.ListExpr: + if l := lastExpr(n.ElemList); l != nil { + m = l + continue + } + return n.Pos() + + // types + case *syntax.ArrayType: + m = n.Elem + case *syntax.SliceType: + m = n.Elem + case *syntax.DotsType: + m = n.Elem + case *syntax.StructType: + if l := lastField(n.FieldList); l != nil { + m = l + continue + } + return n.Pos() + // TODO(gri) need to take TagList into account + case *syntax.Field: + if n.Type != nil { + m = n.Type + continue + } + m = n.Name + case *syntax.InterfaceType: + if l := lastField(n.MethodList); l != nil { + m = l + continue + } + return n.Pos() + case *syntax.FuncType: + if l := lastField(n.ResultList); l != nil { + m = l + continue + } + if l := lastField(n.ParamList); l != nil { + m = l + continue + } + return n.Pos() + case *syntax.MapType: + m = n.Value + case *syntax.ChanType: + m = n.Elem + + // statements + case *syntax.EmptyStmt: + return n.Pos() + case *syntax.LabeledStmt: + m = n.Stmt + case *syntax.BlockStmt: + return n.Rbrace + case *syntax.ExprStmt: + m = n.X + case *syntax.SendStmt: + m = n.Value + case *syntax.DeclStmt: + if l := lastDecl(n.DeclList); l != nil { + m = l + continue + } + return n.Pos() + case *syntax.AssignStmt: + m = n.Rhs + case *syntax.BranchStmt: + if n.Label != nil { + m = n.Label + continue + } + return n.Pos() + case *syntax.CallStmt: + m = n.Call + case *syntax.ReturnStmt: + if n.Results != nil { + m = n.Results + continue + } + return n.Pos() + case *syntax.IfStmt: + if n.Else != nil { + m = n.Else + continue + } + m = n.Then + case *syntax.ForStmt: + m = n.Body + case *syntax.SwitchStmt: + return n.Rbrace + case *syntax.SelectStmt: + return n.Rbrace + + // helper nodes + case *syntax.RangeClause: + m = n.X + case *syntax.CaseClause: + if l := lastStmt(n.Body); l != nil { + m = l + continue + } + return n.Colon + case *syntax.CommClause: + if l := lastStmt(n.Body); l != nil { + m = l + continue + } + return n.Colon + + default: + return n.Pos() + } + } +} + +func lastDecl(list []syntax.Decl) syntax.Decl { + if l := len(list); l > 0 { + return list[l-1] + } + return nil +} + +func lastExpr(list []syntax.Expr) syntax.Expr { + if l := len(list); l > 0 { + return list[l-1] + } + return nil +} + +func lastStmt(list []syntax.Stmt) syntax.Stmt { + if l := len(list); l > 0 { + return list[l-1] + } + return nil +} + +func lastField(list []*syntax.Field) *syntax.Field { + if l := len(list); l > 0 { + return list[l-1] + } + return nil +} diff --git a/src/cmd/compile/internal/types2/predicates.go b/src/cmd/compile/internal/types2/predicates.go new file mode 100644 index 0000000000..f3a5818b3f --- /dev/null +++ b/src/cmd/compile/internal/types2/predicates.go @@ -0,0 +1,413 @@ +// UNREVIEWED +// 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 types2 + +import ( + "sort" +) + +// isNamed reports whether typ has a name. +// isNamed may be called with types that are not fully set up. +func isNamed(typ Type) bool { + switch typ.(type) { + case *Basic, *Named, *TypeParam, *instance: + return true + } + return false +} + +// isGeneric reports whether a type is a generic, uninstantiated type (generic signatures are not included). +func isGeneric(typ Type) bool { + // A parameterized type is only instantiated if it doesn't have an instantiation already. + named, _ := typ.(*Named) + return named != nil && named.obj != nil && named.tparams != nil && named.targs == nil +} + +func is(typ Type, what BasicInfo) bool { + switch t := optype(typ.Under()).(type) { + case *Basic: + return t.info&what != 0 + case *Sum: + return t.is(func(typ Type) bool { return is(typ, what) }) + } + return false +} + +func isBoolean(typ Type) bool { return is(typ, IsBoolean) } +func isInteger(typ Type) bool { return is(typ, IsInteger) } +func isUnsigned(typ Type) bool { return is(typ, IsUnsigned) } +func isFloat(typ Type) bool { return is(typ, IsFloat) } +func isComplex(typ Type) bool { return is(typ, IsComplex) } +func isNumeric(typ Type) bool { return is(typ, IsNumeric) } +func isString(typ Type) bool { return is(typ, IsString) } + +// Note that if typ is a type parameter, isInteger(typ) || isFloat(typ) does not +// produce the expected result because a type list that contains both an integer +// and a floating-point type is neither (all) integers, nor (all) floats. +// Use isIntegerOrFloat instead. +func isIntegerOrFloat(typ Type) bool { return is(typ, IsInteger|IsFloat) } + +// isNumericOrString is the equivalent of isIntegerOrFloat for isNumeric(typ) || isString(typ). +func isNumericOrString(typ Type) bool { return is(typ, IsNumeric|IsString) } + +// isTyped reports whether typ is typed; i.e., not an untyped +// constant or boolean. isTyped may be called with types that +// are not fully set up. +func isTyped(typ Type) bool { + // isTyped is called with types that are not fully + // set up. Must not call Basic()! + // A *Named or *instance type is always typed, so + // we only need to check if we have a true *Basic + // type. + t, _ := typ.(*Basic) + return t == nil || t.info&IsUntyped == 0 +} + +// isUntyped(typ) is the same as !isTyped(typ). +func isUntyped(typ Type) bool { + return !isTyped(typ) +} + +func isOrdered(typ Type) bool { return is(typ, IsOrdered) } + +func isConstType(typ Type) bool { + t := typ.Basic() + return t != nil && t.info&IsConstType != 0 +} + +// IsInterface reports whether typ is an interface type. +func IsInterface(typ Type) bool { + return typ.Interface() != nil +} + +// Comparable reports whether values of type T are comparable. +func Comparable(T Type) bool { + // If T is a type parameter not constraint by any type + // list (i.e., it's underlying type is the top type), + // T is comparable if it has the == method. Otherwise, + // the underlying type "wins". For instance + // + // interface{ comparable; type []byte } + // + // is not comparable because []byte is not comparable. + if t := T.TypeParam(); t != nil && optype(t) == theTop { + return t.Bound().IsComparable() + } + + switch t := optype(T.Under()).(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) { + return false + } + } + return true + case *Array: + return Comparable(t.elem) + case *Sum: + return t.is(Comparable) + case *TypeParam: + return t.Bound().IsComparable() + } + return false +} + +// hasNil reports whether a type includes the nil value. +func hasNil(typ Type) bool { + switch t := optype(typ.Under()).(type) { + case *Basic: + return t.kind == UnsafePointer + case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan: + return true + case *Sum: + return t.is(hasNil) + } + 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 +} + +// For changes to this code the corresponding changes should be made to unifier.nify. +func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool { + // types must be expanded for comparison + x = expandf(x) + y = expandf(y) + + 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. + // Generic functions must also have matching type parameter lists, but for the + // parameter names. + if y, ok := y.(*Signature); ok { + return x.variadic == y.variadic && + check.identicalTParams(x.tparams, y.tparams, cmpTags, p) && + check.identical0(x.params, y.params, cmpTags, p) && + check.identical0(x.results, y.results, cmpTags, p) + } + + case *Sum: + // Two sum types are identical if they contain the same types. + // (Sum types always consist of at least two types. Also, the + // the set (list) of types in a sum type consists of unique + // types - each type appears exactly once. Thus, two sum types + // must contain the same number of types to have chance of + // being equal. + if y, ok := y.(*Sum); ok && len(x.types) == len(y.types) { + // Every type in x.types must be in y.types. + // Quadratic algorithm, but probably good enough for now. + // TODO(gri) we need a fast quick type ID/hash for all types. + L: + for _, x := range x.types { + for _, y := range y.types { + if Identical(x, y) { + continue L // x is in y.types + } + } + return false // x is not in y.types + } + return true + } + + 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(nopos, x) + check.completeInterface(nopos, 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 { + // TODO(gri) Why is x == y not sufficient? And if it is, + // we can just return false here because x == y + // is caught in the very beginning of this function. + return x.obj == y.obj + } + + case *TypeParam: + // nothing to do (x and y being equal is caught in the very beginning of this function) + + // case *instance: + // unreachable since types are expanded + + case *bottom, *top: + // Either both types are theBottom, or both are theTop in which + // case the initial x == y check will have caught them. Otherwise + // they are not identical. + + case nil: + // avoid a crash in case of nil type + + default: + unreachable() + } + + return false +} + +func (check *Checker) identicalTParams(x, y []*TypeName, cmpTags bool, p *ifacePair) bool { + if len(x) != len(y) { + return false + } + for i, x := range x { + y := y[i] + if !check.identical0(x.typ.(*TypeParam).bound, y.typ.(*TypeParam).bound, cmpTags, p) { + return false + } + } + return true +} + +// 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/cmd/compile/internal/types2/resolver.go b/src/cmd/compile/internal/types2/resolver.go new file mode 100644 index 0000000000..b116888bf2 --- /dev/null +++ b/src/cmd/compile/internal/types2/resolver.go @@ -0,0 +1,714 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" + "go/constant" + "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 + vtyp syntax.Expr // type, or nil (for const and var declarations only) + init syntax.Expr // init/orig expression, or nil (for const and var declarations only) + tdecl *syntax.TypeDecl // type declaration, or nil + fdecl *syntax.FuncDecl // func declaration, or nil + + // 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 +} + +// arity checks that the lhs and rhs of a const or var decl +// have a matching number of names and initialization values. +// If inherited is set, the initialization values are from +// another (constant) declaration. +func (check *Checker) arity(pos syntax.Pos, names []*syntax.Name, inits []syntax.Expr, constDecl, inherited bool) { + l := len(names) + r := len(inits) + + switch { + case l < r: + n := inits[l] + if inherited { + check.errorf(pos, "extra init expr at %s", n.Pos()) + } else { + check.errorf(n, "extra init expr %s", n) + } + case l > r && (constDecl || r != 1): // if r == 1 it may be a multi-valued function and we can't say anything yet + n := names[r] + check.errorf(n, "missing init expr for %s", n.Value) + } +} + +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 *syntax.Name, obj Object, d *declInfo) { + assert(ident.Value == 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.Value == "init" { + check.errorf(ident, "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.Value == "main" && check.pkg.name == "main" { + check.errorf(ident, "cannot declare main - must be func") + return + } + + check.declare(check.pkg.scope, ident, obj, 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.IsKnown() { + // return check.fset.File(pos).Name() + // TODO(gri) do we need the actual file name here? + return pos.RelFilename() + } + return fmt.Sprintf("file[%d]", fileNo) +} + +func (check *Checker) importPackage(pos syntax.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(pos, "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 + } + + type methodInfo struct { + obj *Func // method + ptr bool // true if pointer receiver + recv *syntax.Name // receiver type name + } + var methods []methodInfo // collected methods with valid receivers and non-blank _ names + var fileScopes []*Scope + for fileNo, file := range check.files { + // The package identifier denotes the current package, + // but there is no corresponding package object. + check.recordDef(file.PkgName, nil) + + fileScope := NewScope(check.pkg.scope, startPos(file), endPos(file), check.filename(fileNo)) + fileScopes = append(fileScopes, fileScope) + 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(file.PkgName.Pos().RelFilename()) // TODO(gri) should this be filename? + + first := -1 // index of first ConstDecl in the current group, or -1 + var last *syntax.ConstDecl // last ConstDecl with init expressions, or nil + for index, decl := range file.DeclList { + if _, ok := decl.(*syntax.ConstDecl); !ok { + first = -1 // we're not in a constant declaration + } + + switch s := decl.(type) { + case *syntax.ImportDecl: + // import package + path, err := validatedImportPath(s.Path.Value) + if err != nil { + check.errorf(s.Path, "invalid import path (%s)", err) + continue + } + + imp := check.importPackage(s.Path.Pos(), path, fileDir) + if imp == nil { + continue + } + + // 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 s.LocalPkgName != nil { + name = s.LocalPkgName.Value + if path == "C" { + // match cmd/compile (not prescribed by spec) + check.errorf(s.LocalPkgName, `cannot rename import "C"`) + continue + } + if name == "init" { + check.errorf(s.LocalPkgName, "cannot declare init - must be func") + continue + } + } + + obj := NewPkgName(s.Pos(), pkg, name, imp) + if s.LocalPkgName != nil { + // in a dot-import, the dot represents the package + check.recordDef(s.LocalPkgName, obj) + } else { + check.recordImplicit(s, 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(s.LocalPkgName, "%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, s.Pos()) + } else { + // declare imported package object in file scope + // (no need to provide s.LocalPkgName since we called check.recordDef earlier) + check.declare(fileScope, nil, obj, nopos) + } + + case *syntax.ConstDecl: + // iota is the index of the current constDecl within the group + if first < 0 || file.DeclList[index-1].(*syntax.ConstDecl).Group != s.Group { + first = index + last = nil + } + iota := constant.MakeInt64(int64(index - first)) + + // determine which initialization expressions to use + inherited := true + switch { + case s.Type != nil || s.Values != nil: + last = s + inherited = false + case last == nil: + last = new(syntax.ConstDecl) // make sure last exists + inherited = false + } + + // declare all constants + values := unpackExpr(last.Values) + for i, name := range s.NameList { + obj := NewConst(name.Pos(), pkg, name.Value, nil, iota) + + var init syntax.Expr + if i < len(values) { + init = values[i] + } + + d := &declInfo{file: fileScope, vtyp: last.Type, init: init} + check.declarePkgObj(name, obj, d) + } + + // Constants must always have init values. + check.arity(s.Pos(), s.NameList, values, true, inherited) + + case *syntax.VarDecl: + lhs := make([]*Var, len(s.NameList)) + // 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 _, ok := s.Values.(*syntax.ListExpr); !ok { + // The lhs elements are only set up after the for loop below, + // but that's ok because declarePkgObj only collects the declInfo + // for a later phase. + d1 = &declInfo{file: fileScope, lhs: lhs, vtyp: s.Type, init: s.Values} + } + + // declare all variables + values := unpackExpr(s.Values) + for i, name := range s.NameList { + obj := NewVar(name.Pos(), pkg, name.Value, nil) + lhs[i] = obj + + d := d1 + if d == nil { + // individual assignments + var init syntax.Expr + if i < len(values) { + init = values[i] + } + d = &declInfo{file: fileScope, vtyp: s.Type, init: init} + } + + check.declarePkgObj(name, obj, d) + } + + // If we have no type, we must have values. + if s.Type == nil || values != nil { + check.arity(s.Pos(), s.NameList, values, false, false) + } + + case *syntax.TypeDecl: + obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Value, nil) + check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, tdecl: s}) + + case *syntax.FuncDecl: + d := s // TODO(gri) get rid of this + name := d.Name.Value + obj := NewFunc(d.Name.Pos(), pkg, name, nil) + if d.Recv == nil { + // regular function + if name == "init" { + if d.TParamList != nil { + //check.softErrorf(d.TParamList.Pos(), "func init must have no type parameters") + check.softErrorf(d.Name, "func init must have no type parameters") + } + if t := d.Type; len(t.ParamList) != 0 || len(t.ResultList) != 0 { + check.softErrorf(d, "func init must have no arguments and no return values") + } + // don't declare init functions in the package scope - they are invisible + obj.parent = pkg.scope + check.recordDef(d.Name, obj) + // init functions must have a body + if d.Body == nil { + // TODO(gri) make this error message consistent with the others above + check.softErrorf(obj.pos, "missing function body") + } + } else { + check.declare(pkg.scope, d.Name, obj, nopos) + } + } else { + // method + // d.Recv != nil + if !methodTypeParamsOk && len(d.TParamList) != 0 { + //check.invalidASTf(d.TParamList.Pos(), "method must have no type parameters") + check.invalidASTf(d, "method must have no type parameters") + } + ptr, recv, _ := check.unpackRecv(d.Recv.Type, false) + // (Methods with invalid receiver cannot be associated to a type, and + // methods with blank _ names are never found; no need to collect any + // of them. They will still be type-checked with all the other functions.) + if recv != nil && name != "_" { + methods = append(methods, methodInfo{obj, ptr, recv}) + } + check.recordDef(d.Name, obj) + } + info := &declInfo{file: fileScope, fdecl: d} + // 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))) + + default: + check.invalidASTf(s, "unknown syntax.Decl node %T", s) + } + } + } + + // verify that objects in package and file scopes have different names + for _, scope := range fileScopes { + for _, obj := range scope.elems { + if alt := pkg.scope.Lookup(obj.Name()); alt != nil { + if pkg, ok := obj.(*PkgName); ok { + check.errorf(alt, "%s already declared through import of %s", alt.Name(), pkg.Imported()) + check.reportAltDecl(pkg) + } else { + check.errorf(alt, "%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 { + check.methods = make(map[*TypeName][]*Func) + for i := range methods { + m := &methods[i] + // Determine the receiver base type and associate m with it. + ptr, base := check.resolveBaseTypeName(m.ptr, m.recv) + if base != nil { + m.obj.hasPtrRecv = ptr + check.methods[base] = append(check.methods[base], m.obj) + } + } + } +} + +// unpackRecv unpacks a receiver type and returns its components: ptr indicates whether +// rtyp is a pointer receiver, rname is the receiver type name, and tparams are its +// type parameters, if any. The type parameters are only unpacked if unpackParams is +// set. If rname is nil, the receiver is unusable (i.e., the source has a bug which we +// cannot easily work around). +func (check *Checker) unpackRecv(rtyp syntax.Expr, unpackParams bool) (ptr bool, rname *syntax.Name, tparams []*syntax.Name) { +L: // unpack receiver type + // This accepts invalid receivers such as ***T and does not + // work for other invalid receivers, but we don't care. The + // validity of receiver expressions is checked elsewhere. + for { + switch t := rtyp.(type) { + case *syntax.ParenExpr: + rtyp = t.X + // case *ast.StarExpr: + // rtyp = t.X + case *syntax.Operation: + if t.Op != syntax.Mul || t.Y != nil { + break + } + rtyp = t.X + default: + break L + } + } + + // unpack type parameters, if any + if ptyp, _ := rtyp.(*syntax.IndexExpr); ptyp != nil { + rtyp = ptyp.X + if unpackParams { + for _, arg := range unpackExpr(ptyp.Index) { + var par *syntax.Name + switch arg := arg.(type) { + case *syntax.Name: + par = arg + case *syntax.BadExpr: + // ignore - error already reported by parser + case nil: + check.invalidASTf(ptyp, "parameterized receiver contains nil parameters") + default: + check.errorf(arg, "receiver type parameter %s must be an identifier", arg) + } + if par == nil { + par = newName(arg.Pos(), "_") + } + tparams = append(tparams, par) + } + + } + } + + // unpack receiver name + if name, _ := rtyp.(*syntax.Name); name != nil { + rname = name + } + + return +} + +// 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(seenPtr bool, typ syntax.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. + ptr = seenPtr + var seen map[*TypeName]bool + for { + typ = unparen(typ) + + // check if we have a pointer type + // if pexpr, _ := typ.(*ast.StarExpr); pexpr != nil { + if pexpr, _ := typ.(*syntax.Operation); pexpr != nil && pexpr.Op == syntax.Mul && pexpr.Y == 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.(*syntax.Name) + 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.Value) + 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].tdecl // must exist for objects in package scope + if !tdecl.Alias { + return ptr, tname + } + + // otherwise, continue resolving + typ = tdecl.Type + 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.collectMethods(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].tdecl.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.pos, "%q imported but not used", path) + } else { + check.softErrorf(obj.pos, "%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, "%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/cmd/compile/internal/types2/resolver_test.go b/src/cmd/compile/internal/types2/resolver_test.go new file mode 100644 index 0000000000..cdfdba6b43 --- /dev/null +++ b/src/cmd/compile/internal/types2/resolver_test.go @@ -0,0 +1,223 @@ +// UNREVIEWED +// 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 types2_test + +import ( + "cmd/compile/internal/syntax" + "fmt" + "internal/testenv" + "sort" + "testing" + + . "cmd/compile/internal/types2" +) + +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 = defaultImporter().(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 + var files []*syntax.File + for i, src := range sources { + f, err := parseSrc(fmt.Sprintf("sources[%d]", i), src) + 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[*syntax.Name]Object) + defs := make(map[*syntax.Name]Object) + _, err := conf.Check("testResolveIdents", 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 { + Walk(f, func(n syntax.Node) bool { + if s, ok := n.(*syntax.SelectorExpr); ok { + if x, ok := s.X.(*syntax.Name); ok { + obj := uses[x] + if obj == nil { + t.Errorf("%s: unresolved qualified identifier %s", x.Pos(), x.Value) + return true + } + if _, ok := obj.(*PkgName); ok && uses[s.Sel] == nil { + t.Errorf("%s: unresolved selector %s", s.Sel.Pos(), s.Sel.Value) + return true + } + return true + } + return true + } + return false + }) + } + + for id, obj := range uses { + if obj == nil { + t.Errorf("%s: Uses[%s] == nil", id.Pos(), id.Value) + } + } + + // Check that each identifier in the source is found in uses or defs or both. + // We need the foundUses/Defs maps (rather then just deleting the found objects + // from the uses and defs maps) because Walk traverses shared nodes multiple + // times (e.g. types in field lists such as "a, b, c int"). + foundUses := make(map[*syntax.Name]bool) + foundDefs := make(map[*syntax.Name]bool) + var both []string + for _, f := range files { + Walk(f, func(n syntax.Node) bool { + if x, ok := n.(*syntax.Name); ok { + var objects int + if _, found := uses[x]; found { + objects |= 1 + foundUses[x] = true + } + if _, found := defs[x]; found { + objects |= 2 + foundDefs[x] = true + } + switch objects { + case 0: + t.Errorf("%s: unresolved identifier %s", x.Pos(), x.Value) + case 3: + both = append(both, x.Value) + } + return true + } + return false + }) + } + + // 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 { + if !foundUses[x] { + t.Errorf("%s: identifier %s not present in source", x.Pos(), x.Value) + } + } + for x := range defs { + if !foundDefs[x] { + t.Errorf("%s: identifier %s not present in source", x.Pos(), x.Value) + } + } + + // TODO(gri) add tests to check ImplicitObj callbacks +} diff --git a/src/cmd/compile/internal/types2/return.go b/src/cmd/compile/internal/types2/return.go new file mode 100644 index 0000000000..88234b1723 --- /dev/null +++ b/src/cmd/compile/internal/types2/return.go @@ -0,0 +1,180 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" +) + +// 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 syntax.Stmt, label string) bool { + switch s := s.(type) { + default: + unreachable() + + case *syntax.DeclStmt, *syntax.EmptyStmt, *syntax.SendStmt, + *syntax.AssignStmt, *syntax.CallStmt: + // no chance + + case *syntax.LabeledStmt: + return check.isTerminating(s.Stmt, s.Label.Value) + + case *syntax.ExprStmt: + // calling the predeclared (possibly parenthesized) panic() function is terminating + if call, ok := unparen(s.X).(*syntax.CallExpr); ok && check.isPanic[call] { + return true + } + + case *syntax.ReturnStmt: + return true + + case *syntax.BranchStmt: + if s.Tok == syntax.Goto || s.Tok == syntax.Fallthrough { + return true + } + + case *syntax.BlockStmt: + return check.isTerminatingList(s.List, "") + + case *syntax.IfStmt: + if s.Else != nil && + check.isTerminating(s.Then, "") && + check.isTerminating(s.Else, "") { + return true + } + + case *syntax.SwitchStmt: + return check.isTerminatingSwitch(s.Body, label) + + case *syntax.SelectStmt: + for _, cc := range s.Body { + if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { + return false + } + + } + return true + + case *syntax.ForStmt: + if s.Cond == nil && !hasBreak(s.Body, label, true) { + return true + } + } + + return false +} + +func (check *Checker) isTerminatingList(list []syntax.Stmt, label string) bool { + // trailing empty statements are permitted - skip them + for i := len(list) - 1; i >= 0; i-- { + if _, ok := list[i].(*syntax.EmptyStmt); !ok { + return check.isTerminating(list[i], label) + } + } + return false // all statements are empty +} + +func (check *Checker) isTerminatingSwitch(body []*syntax.CaseClause, label string) bool { + hasDefault := false + for _, cc := range body { + if cc.Cases == 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 syntax.Stmt, label string, implicit bool) bool { + switch s := s.(type) { + default: + unreachable() + + case *syntax.DeclStmt, *syntax.EmptyStmt, *syntax.ExprStmt, + *syntax.SendStmt, *syntax.AssignStmt, *syntax.CallStmt, + *syntax.ReturnStmt: + // no chance + + case *syntax.LabeledStmt: + return hasBreak(s.Stmt, label, implicit) + + case *syntax.BranchStmt: + if s.Tok == syntax.Break { + if s.Label == nil { + return implicit + } + if s.Label.Value == label { + return true + } + } + + case *syntax.BlockStmt: + return hasBreakList(s.List, label, implicit) + + case *syntax.IfStmt: + if hasBreak(s.Then, label, implicit) || + s.Else != nil && hasBreak(s.Else, label, implicit) { + return true + } + + case *syntax.SwitchStmt: + if label != "" && hasBreakCaseList(s.Body, label, false) { + return true + } + + case *syntax.SelectStmt: + if label != "" && hasBreakCommList(s.Body, label, false) { + return true + } + + case *syntax.ForStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + } + + return false +} + +func hasBreakList(list []syntax.Stmt, label string, implicit bool) bool { + for _, s := range list { + if hasBreak(s, label, implicit) { + return true + } + } + return false +} + +func hasBreakCaseList(list []*syntax.CaseClause, label string, implicit bool) bool { + for _, s := range list { + if hasBreakList(s.Body, label, implicit) { + return true + } + } + return false +} + +func hasBreakCommList(list []*syntax.CommClause, label string, implicit bool) bool { + for _, s := range list { + if hasBreakList(s.Body, label, implicit) { + return true + } + } + return false +} diff --git a/src/cmd/compile/internal/types2/sanitize.go b/src/cmd/compile/internal/types2/sanitize.go new file mode 100644 index 0000000000..bac569416b --- /dev/null +++ b/src/cmd/compile/internal/types2/sanitize.go @@ -0,0 +1,149 @@ +// UNREVIEWED +// 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 types2 + +func sanitizeInfo(info *Info) { + var s sanitizer = make(map[Type]Type) + + // Note: Some map entries are not references. + // If modified, they must be assigned back. + + for e, tv := range info.Types { + tv.Type = s.typ(tv.Type) + info.Types[e] = tv + } + + for e, inf := range info.Inferred { + for i, targ := range inf.Targs { + inf.Targs[i] = s.typ(targ) + } + inf.Sig = s.typ(inf.Sig).(*Signature) + info.Inferred[e] = inf + } + + for _, obj := range info.Defs { + if obj != nil { + obj.setType(s.typ(obj.Type())) + } + } + + for _, obj := range info.Uses { + if obj != nil { + obj.setType(s.typ(obj.Type())) + } + } + + // TODO(gri) sanitize as needed + // - info.Implicits + // - info.Selections + // - info.Scopes + // - info.InitOrder +} + +type sanitizer map[Type]Type + +func (s sanitizer) typ(typ Type) Type { + if t, found := s[typ]; found { + return t + } + s[typ] = typ + + switch t := typ.(type) { + case nil, *Basic, *bottom, *top: + // nothing to do + + case *Array: + t.elem = s.typ(t.elem) + + case *Slice: + t.elem = s.typ(t.elem) + + case *Struct: + s.varList(t.fields) + + case *Pointer: + t.base = s.typ(t.base) + + case *Tuple: + s.tuple(t) + + case *Signature: + s.var_(t.recv) + s.tuple(t.params) + s.tuple(t.results) + + case *Sum: + s.typeList(t.types) + + case *Interface: + s.funcList(t.methods) + s.typ(t.types) + s.typeList(t.embeddeds) + s.funcList(t.allMethods) + s.typ(t.allTypes) + + case *Map: + t.key = s.typ(t.key) + t.elem = s.typ(t.elem) + + case *Chan: + t.elem = s.typ(t.elem) + + case *Named: + t.orig = s.typ(t.orig) + t.underlying = s.typ(t.underlying) + s.typeList(t.targs) + s.funcList(t.methods) + + case *TypeParam: + t.bound = s.typ(t.bound) + + case *instance: + typ = t.expand() + s[t] = typ + + default: + unimplemented() + } + + return typ +} + +func (s sanitizer) var_(v *Var) { + if v != nil { + v.typ = s.typ(v.typ) + } +} + +func (s sanitizer) varList(list []*Var) { + for _, v := range list { + s.var_(v) + } +} + +func (s sanitizer) tuple(t *Tuple) { + if t != nil { + s.varList(t.vars) + } +} + +func (s sanitizer) func_(f *Func) { + if f != nil { + f.typ = s.typ(f.typ) + } +} + +func (s sanitizer) funcList(list []*Func) { + for _, f := range list { + s.func_(f) + } +} + +func (s sanitizer) typeList(list []Type) { + for i, t := range list { + list[i] = s.typ(t) + } +} diff --git a/src/cmd/compile/internal/types2/scope.go b/src/cmd/compile/internal/types2/scope.go new file mode 100644 index 0000000000..c8243ac36c --- /dev/null +++ b/src/cmd/compile/internal/types2/scope.go @@ -0,0 +1,217 @@ +// UNREVIEWED +// 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 types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "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 syntax.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 syntax.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 syntax.Pos) (*Scope, Object) { + for ; s != nil; s = s.parent { + if obj := s.elems[name]; obj != nil && (!pos.IsKnown() || cmpPos(obj.scopePos(), pos) <= 0) { + 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 +} + +// Squash merges s with its parent scope p by adding all +// objects of s to p, adding all children of s to the +// children of p, and removing s from p's children. +// The function f is called for each object obj in s which +// has an object alt in p. s should be discarded after +// having been squashed. +func (s *Scope) Squash(err func(obj, alt Object)) { + p := s.parent + assert(p != nil) + for _, obj := range s.elems { + obj.setParent(nil) + if alt := p.Insert(obj); alt != nil { + err(obj, alt) + } + } + + j := -1 // index of s in p.children + for i, ch := range p.children { + if ch == s { + j = i + break + } + } + assert(j >= 0) + k := len(p.children) - 1 + p.children[j] = p.children[k] + p.children = p.children[:k] + + p.children = append(p.children, s.children...) + + s.children = nil + s.elems = 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() syntax.Pos { return s.pos } +func (s *Scope) End() syntax.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 syntax.Pos) bool { + return cmpPos(s.pos, pos) <= 0 && cmpPos(pos, s.end) < 0 +} + +// 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 syntax.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/cmd/compile/internal/types2/selection.go b/src/cmd/compile/internal/types2/selection.go new file mode 100644 index 0000000000..da0e9ab526 --- /dev/null +++ b/src/cmd/compile/internal/types2/selection.go @@ -0,0 +1,144 @@ +// UNREVIEWED +// 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 types2 + +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/cmd/compile/internal/types2/self_test.go b/src/cmd/compile/internal/types2/self_test.go new file mode 100644 index 0000000000..6d7971e50f --- /dev/null +++ b/src/cmd/compile/internal/types2/self_test.go @@ -0,0 +1,97 @@ +// UNREVIEWED +// 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 types2_test + +import ( + "cmd/compile/internal/syntax" + "flag" + "fmt" + "path/filepath" + "testing" + "time" + + . "cmd/compile/internal/types2" +) + +var benchmark = flag.Bool("b", false, "run benchmarks") + +func TestSelf(t *testing.T) { + files, err := pkgFiles(".") + if err != nil { + t.Fatal(err) + } + + conf := Config{Importer: defaultImporter()} + _, err = conf.Check("go/types", 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 TestBenchmark(t *testing.T) { + if !*benchmark { + return + } + + // We're not using testing's benchmarking mechanism directly + // because we want custom output. + + for _, p := range []string{"types", "constant", filepath.Join("internal", "gcimporter")} { + path := filepath.Join("..", p) + runbench(t, path, false) + runbench(t, path, true) + fmt.Println() + } +} + +func runbench(t *testing.T, path string, ignoreFuncBodies bool) { + files, err := pkgFiles(path) + if err != nil { + t.Fatal(err) + } + + b := testing.Benchmark(func(b *testing.B) { + for i := 0; i < b.N; i++ { + conf := Config{IgnoreFuncBodies: ignoreFuncBodies} + conf.Check(path, files, nil) + } + }) + + // determine line count + var lines uint + for _, f := range files { + lines += f.EOF.Line() + } + + d := time.Duration(b.NsPerOp()) + fmt.Printf( + "%s: %s for %d lines (%d lines/s), ignoreFuncBodies = %v\n", + filepath.Base(path), d, lines, int64(float64(lines)/d.Seconds()), ignoreFuncBodies, + ) +} + +func pkgFiles(path string) ([]*syntax.File, error) { + filenames, err := pkgFilenames(path) // from stdlib_test.go + if err != nil { + return nil, err + } + + var files []*syntax.File + for _, filename := range filenames { + file, err := syntax.ParseFile(filename, nil, nil, 0) + if err != nil { + return nil, err + } + files = append(files, file) + } + + return files, nil +} diff --git a/src/cmd/compile/internal/types2/sizes.go b/src/cmd/compile/internal/types2/sizes.go new file mode 100644 index 0000000000..cae71c139c --- /dev/null +++ b/src/cmd/compile/internal/types2/sizes.go @@ -0,0 +1,266 @@ +// UNREVIEWED +// 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 types2 + +// 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 := optype(T.Under()).(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 := optype(T.Under()).(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 *Sum: + panic("Sizeof unimplemented for type sum") + 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.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/cmd/compile/internal/types2/sizes_test.go b/src/cmd/compile/internal/types2/sizes_test.go new file mode 100644 index 0000000000..b246909d2a --- /dev/null +++ b/src/cmd/compile/internal/types2/sizes_test.go @@ -0,0 +1,108 @@ +// UNREVIEWED +// 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 types2_test + +import ( + "cmd/compile/internal/syntax" + "cmd/compile/internal/types2" + "testing" +) + +// findStructType typechecks src and returns the first struct type encountered. +func findStructType(t *testing.T, src string) *types2.Struct { + f, err := parseSrc("x.go", src) + if err != nil { + t.Fatal(err) + } + info := types2.Info{Types: make(map[syntax.Expr]types2.TypeAndValue)} + var conf types2.Config + _, err = conf.Check("x", []*syntax.File{f}, &info) + if err != nil { + t.Fatal(err) + } + for _, tv := range info.Types { + if ts, ok := tv.Type.(*types2.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 := types2.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 = types2.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 := &types2.StdSizes{WordSize: 4, MaxAlign: 8} + var fields []*types2.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) +` + f, err := parseSrc("x.go", src) + if err != nil { + t.Fatal(err) + } + info := types2.Info{Types: make(map[syntax.Expr]types2.TypeAndValue)} + conf := types2.Config{ + Importer: defaultImporter(), + Sizes: &types2.StdSizes{WordSize: 8, MaxAlign: 8}, + } + _, err = conf.Check("x", []*syntax.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/cmd/compile/internal/types2/stdlib_test.go b/src/cmd/compile/internal/types2/stdlib_test.go new file mode 100644 index 0000000000..bc8b81998a --- /dev/null +++ b/src/cmd/compile/internal/types2/stdlib_test.go @@ -0,0 +1,320 @@ +// UNREVIEWED +// 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 types2_test + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" + "go/build" + "internal/testenv" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + . "cmd/compile/internal/types2" +) + +var stdLibImporter = defaultImporter() + +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 within the first 4K of the file are considered. +// TODO(gri) should only read until we see "package" token. +func firstComment(filename string) (first string) { + f, err := os.Open(filename) + if err != nil { + return "" + } + defer f.Close() + + // read at most 4KB + var buf [4 << 10]byte + n, _ := f.Read(buf[:]) + src := bytes.NewBuffer(buf[:n]) + + // TODO(gri) we need a better way to terminate CommentsDo + defer func() { + if p := recover(); p != nil { + if s, ok := p.(string); ok { + first = s + } + } + }() + + syntax.CommentsDo(src, func(_, _ uint, text string) { + if text[0] != '/' { + return // not a comment + } + + // extract comment text + if text[1] == '*' { + text = text[:len(text)-2] + } + text = strings.TrimSpace(text[2:]) + + if strings.HasPrefix(text, "+build ") { + panic("skip") + } + if first == "" { + first = text // text may be "" but that's ok + } + // continue as we may still see build tags + }) + + return +} + +func testTestDir(t *testing.T, path string, ignore ...string) { + files, err := ioutil.ReadDir(path) + if err != nil { + t.Fatal(err) + } + + excluded := make(map[string]bool) + for _, filename := range ignore { + excluded[filename] = true + } + + 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 + if testing.Verbose() { + fmt.Println("\t", filename) + } + file, err := syntax.ParseFile(filename, nil, nil, 0) + if err == nil { + conf := Config{Importer: stdLibImporter} + _, err = conf.Check(filename, []*syntax.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 + ) +} + +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 + ) +} + +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) { + // parse package files + var files []*syntax.File + for _, filename := range filenames { + errh := func(err error) { t.Error(err) } + file, err := syntax.ParseFile(filename, errh, nil, 0) + if err != nil { + return + } + + if testing.Verbose() { + if len(files) == 0 { + fmt.Println("package", file.PkgName.Value) + } + 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[*syntax.Name]Object)} + conf.Check(path, files, &info) + + // Perform checks of API invariants. + + // All Objects have a package, except predeclared ones. + errorError := Universe.Lookup("error").Type().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 := 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 + } + + fis, err := ioutil.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 _, fi := range fis { + if fi.IsDir() && fi.Name() != "testdata" { + w.walk(filepath.Join(dir, fi.Name())) + } + } +} diff --git a/src/cmd/compile/internal/types2/stmt.go b/src/cmd/compile/internal/types2/stmt.go new file mode 100644 index 0000000000..2f1347faf4 --- /dev/null +++ b/src/cmd/compile/internal/types2/stmt.go @@ -0,0 +1,919 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" + "go/constant" + "sort" +) + +func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body *syntax.BlockStmt, iota constant.Value) { + if check.conf.Trace { + check.trace(body.Pos(), "--- %s: %s", name, sig) + defer func() { + check.trace(endPos(body), "--- <end>") + }() + } + + // set function scope extent + sig.scope.pos = body.Pos() + sig.scope.end = endPos(body) + + // 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(body.Rbrace, "missing return") + } + + // TODO(gri) Should we make it an error to declare generic functions + // where the type parameters are not used? + // 12/19/2018: Probably not - it can make sense to have an API with + // all functions uniformly sharing the same type parameters. + + // 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 cmpPos(unused[i].pos, unused[j].pos) < 0 + }) + for _, v := range unused { + check.softErrorf(v.pos, "%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 syntax.Stmt) { + if s != nil { + check.stmt(0, s) + } +} + +func trimTrailingEmptyStmts(list []syntax.Stmt) []syntax.Stmt { + for i := len(list); i > 0; i-- { + if _, ok := list[i-1].(*syntax.EmptyStmt); !ok { + return list[:i] + } + } + return nil +} + +func (check *Checker) stmtList(ctxt stmtContext, list []syntax.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) multipleSwitchDefaults(list []*syntax.CaseClause) { + var first *syntax.CaseClause + for _, c := range list { + if c.Cases == nil { + if first != nil { + check.errorf(c, "multiple defaults (first at %s)", first.Pos()) + // TODO(gri) probably ok to bail out after first error (and simplify this code) + } else { + first = c + } + } + } +} + +func (check *Checker) multipleSelectDefaults(list []*syntax.CommClause) { + var first *syntax.CommClause + for _, c := range list { + if c.Comm == nil { + if first != nil { + check.errorf(c, "multiple defaults (first at %s)", first.Pos()) + // TODO(gri) probably ok to bail out after first error (and simplify this code) + } else { + first = c + } + } + } +} + +func (check *Checker) openScope(node syntax.Node, comment string) { + scope := NewScope(check.scope, node.Pos(), endPos(node), comment) + check.recordScope(node, scope) + check.scope = scope +} + +func (check *Checker) closeScope() { + check.scope = check.scope.Parent() +} + +func (check *Checker) suspendedCall(keyword string, call *syntax.CallExpr) { + var x operand + var msg string + switch check.rawExpr(&x, call, nil) { + case conversion: + msg = "requires function call, not conversion" + case expression: + msg = "discards result of" + case statement: + return + default: + unreachable() + } + check.errorf(&x, "%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 syntax.Pos + typ Type + } +) + +func (check *Checker) caseValues(x *operand, values []syntax.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, syntax.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, "duplicate case %s in expression switch", &v) + check.error(vt.pos, "\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 []syntax.Expr, seen map[Type]syntax.Pos, strict bool) (T Type) { +L: + for _, e := range types { + T = check.typOrNil(e) + if T == Typ[Invalid] { + continue L + } + if T != nil { + check.ordinaryType(e.Pos(), T) + } + // look for duplicate types + // (quadratic algorithm, but type switches tend to be reasonably small) + for t, pos := 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, "duplicate case %s in type switch", Ts) + check.error(pos, "\tprevious case") // secondary error, \t indented + continue L + } + } + seen[T] = e.Pos() + if T != nil { + check.typeAssertion(e.Pos(), x, xtyp, T, strict) + } + } + return +} + +// stmt typechecks statement s. +func (check *Checker) stmt(ctxt stmtContext, s syntax.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 *syntax.EmptyStmt: + // ignore + + case *syntax.DeclStmt: + check.declStmt(s.DeclList) + + case *syntax.LabeledStmt: + check.hasLabel = true + check.stmt(ctxt, s.Stmt) + + case *syntax.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 + switch x.mode { + default: + if kind == statement { + return + } + msg = "is not used" + case builtin: + msg = "must be called" + case typexpr: + msg = "is not an expression" + } + check.errorf(&x, "%s %s", &x, msg) + + case *syntax.SendStmt: + var ch, x operand + check.expr(&ch, s.Chan) + check.expr(&x, s.Value) + if ch.mode == invalid || x.mode == invalid { + return + } + + tch := ch.typ.Chan() + if tch == nil { + check.invalidOpf(s, "cannot send to non-chan type %s", ch.typ) + return + } + + if tch.dir == RecvOnly { + check.invalidOpf(s, "cannot send to receive-only type %s", tch) + return + } + + check.assignment(&x, tch.elem, "send") + + case *syntax.AssignStmt: + lhs := unpackExpr(s.Lhs) + rhs := unpackExpr(s.Rhs) + if s.Op == 0 || s.Op == syntax.Def { + // regular assignment or short variable declaration + if len(lhs) == 0 { + check.invalidASTf(s, "missing lhs in assignment") + return + } + if s.Op == syntax.Def { + check.shortVarDecl(s.Pos(), lhs, rhs) + } else { + // regular assignment + check.assignVars(lhs, rhs) + } + } else { + // assignment operations + if len(lhs) != 1 || len(rhs) != 1 { + check.errorf(s, "assignment operation %s requires single-valued expressions", s.Op) + return + } + + // provide better error messages for x++ and x-- + if rhs[0] == syntax.ImplicitOne { + var x operand + check.expr(&x, lhs[0]) + if x.mode == invalid { + return + } + if !isNumeric(x.typ) { + check.invalidOpf(lhs[0], "%s%s%s (non-numeric type %s)", lhs[0], s.Op, s.Op, x.typ) + return + } + } + + var x operand + check.binary(&x, nil, lhs[0], rhs[0], s.Op) + if x.mode == invalid { + return + } + check.assignVar(lhs[0], &x) + } + + // case *syntax.GoStmt: + // check.suspendedCall("go", s.Call) + + // case *syntax.DeferStmt: + // check.suspendedCall("defer", s.Call) + case *syntax.CallStmt: + // TODO(gri) get rid of this conversion to string + kind := "go" + if s.Tok == syntax.Defer { + kind = "defer" + } + check.suspendedCall(kind, s.Call) + + case *syntax.ReturnStmt: + res := check.sig.results + results := unpackExpr(s.Results) + if res.Len() > 0 { + // function returns results + // (if one, say the first, result parameter is named, all of them are named) + if len(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, "result parameter %s not in scope at return", obj.name) + check.errorf(alt, "\tinner declaration of %s", obj) + // ok to continue + } + } + } else { + // return has results or result parameters are unnamed + check.initVars(res.vars, results, s.Pos()) + } + } else if len(results) > 0 { + check.error(results[0], "no result values expected") + check.use(results...) + } + + case *syntax.BranchStmt: + if s.Label != nil { + check.hasLabel = true + return // checked in 2nd pass (check.labels) + } + switch s.Tok { + case syntax.Break: + if ctxt&breakOk == 0 { + check.error(s, "break not in for, switch, or select statement") + } + case syntax.Continue: + if ctxt&continueOk == 0 { + check.error(s, "continue not in for statement") + } + case syntax.Fallthrough: + if ctxt&fallthroughOk == 0 { + msg := "fallthrough statement out of place" + if ctxt&finalSwitchCase != 0 { + msg = "cannot fallthrough final case in switch" + } + check.error(s, msg) + } + default: + check.invalidASTf(s, "branch statement: %s", s.Tok) + } + + case *syntax.BlockStmt: + check.openScope(s, "block") + defer check.closeScope() + + check.stmtList(inner, s.List) + + case *syntax.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, "non-boolean condition in if statement") + } + check.stmt(inner, s.Then) + // 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: + // valid or error already reported + case *syntax.IfStmt, *syntax.BlockStmt: + check.stmt(inner, s.Else) + default: + check.error(s.Else, "invalid else branch in if statement") + } + + case *syntax.SwitchStmt: + inner |= breakOk + check.openScope(s, "switch") + defer check.closeScope() + + check.simpleStmt(s.Init) + + if g, _ := s.Tag.(*syntax.TypeSwitchGuard); g != nil { + check.typeSwitchStmt(inner, s, g) + } else { + check.switchStmt(inner, s) + } + + case *syntax.SelectStmt: + inner |= breakOk + + check.multipleSelectDefaults(s.Body) + + for _, clause := range s.Body { + if clause == nil { + continue // error reported before + } + + // clause.Comm must be a SendStmt, RecvStmt, or default case + valid := false + var rhs syntax.Expr // rhs of RecvStmt, or nil + switch s := clause.Comm.(type) { + case nil, *syntax.SendStmt: + valid = true + case *syntax.AssignStmt: + if _, ok := s.Rhs.(*syntax.ListExpr); !ok { + rhs = s.Rhs + } + case *syntax.ExprStmt: + rhs = s.X + } + + // if present, rhs must be a receive operation + if rhs != nil { + if x, _ := unparen(rhs).(*syntax.Operation); x != nil && x.Y == nil && x.Op == syntax.Recv { + valid = true + } + } + + if !valid { + check.error(clause.Comm, "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 *syntax.ForStmt: + inner |= breakOk | continueOk + check.openScope(s, "for") + defer check.closeScope() + + if rclause, _ := s.Init.(*syntax.RangeClause); rclause != nil { + check.rangeStmt(inner, s, rclause) + break + } + + 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, "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.(*syntax.AssignStmt); s != nil && s.Op == syntax.Def { + // The parser already reported an error. + // 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) + + default: + check.error(s, "invalid statement") + } +} + +func newName(pos syntax.Pos, value string) *syntax.Name { + n := new(syntax.Name) + // TODO(gri) why does this not work? + //n.pos = pos + n.Value = value + return n +} + +func (check *Checker) switchStmt(inner stmtContext, s *syntax.SwitchStmt) { + // init statement already handled + + 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") + } 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) + // TODO(gri) should have a better position here + pos := s.Rbrace + if len(s.Body) > 0 { + pos = s.Body[0].Pos() + } + x.expr = newName(pos, "true") + } + + check.multipleSwitchDefaults(s.Body) + + seen := make(valueMap) // map of seen case values to positions and types + for i, clause := range s.Body { + if clause == nil { + check.invalidASTf(clause, "incorrect expression switch case") + continue + } + check.caseValues(&x, unpackExpr(clause.Cases), seen) + check.openScope(clause, "case") + inner := inner + if i+1 < len(s.Body) { + inner |= fallthroughOk + } else { + inner |= finalSwitchCase + } + check.stmtList(inner, clause.Body) + check.closeScope() + } +} + +func (check *Checker) typeSwitchStmt(inner stmtContext, s *syntax.SwitchStmt, guard *syntax.TypeSwitchGuard) { + // init statement already handled + + // A type switch guard must be of the form: + // + // TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . + // \__lhs__/ \___rhs___/ + + // check lhs, if any + lhs := guard.Lhs + if lhs != nil { + if lhs.Value == "_" { + // _ := x.(type) is an invalid short variable declaration + check.softErrorf(lhs, "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 + } + } + + // check rhs + var x operand + check.expr(&x, guard.X) + if x.mode == invalid { + return + } + var xtyp *Interface + var strict bool + switch t := x.typ.Under().(type) { + case *Interface: + xtyp = t + // Disabled for now. See comment in the implementation of type assertions (expr.go). + // case *TypeParam: + // xtyp = t.Bound() + // strict = true + default: + check.errorf(&x, "%s is not an interface or generic type", &x) + return + } + + check.multipleSwitchDefaults(s.Body) + + var lhsVars []*Var // list of implicitly declared lhs variables + seen := make(map[Type]syntax.Pos) // map of seen types to positions + for _, clause := range s.Body { + if clause == nil { + check.invalidASTf(s, "incorrect type switch case") + continue + } + // Check each type in this type switch case. + cases := unpackExpr(clause.Cases) + T := check.caseTypes(&x, xtyp, cases, seen, strict) + 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(cases) != 1 || T == nil { + T = x.typ + } + obj := NewVar(lhs.Pos(), check.pkg, lhs.Value, T) + scopePos := clause.Pos() // for default clause (len(List) == 0) + if n := len(cases); n > 0 { + scopePos = endPos(cases[n-1]) + } + 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, "%s declared but not used", lhs.Value) + } + } +} + +func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *syntax.RangeClause) { + // scope already opened + + // check expression to iterate over + var x operand + check.expr(&x, rclause.X) + + // determine lhs, if any + sKey := rclause.Lhs // possibly nil + var sValue syntax.Expr + if p, _ := sKey.(*syntax.ListExpr); p != nil { + if len(p.ElemList) != 2 { + check.invalidASTf(s, "invalid lhs in range clause") + return + } + sKey = p.ElemList[0] + sValue = p.ElemList[1] + } + + // determine key/value types + var key, val Type + if x.mode != invalid { + typ := optype(x.typ.Under()) + if _, ok := typ.(*Chan); ok && sValue != nil { + // TODO(gri) this also needs to happen for channels in generic variables + check.softErrorf(sValue, "range over %s permits only one iteration variable", &x) + // ok to continue + } + var msg string + key, val, msg = rangeKeyVal(typ, isVarName(sKey), isVarName(sValue)) + if key == nil || msg != "" { + if msg != "" { + msg = ": " + msg + } + check.softErrorf(&x, "cannot range over %s%s", &x, msg) + // 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]syntax.Expr{sKey, sValue} + rhs := [2]Type{key, val} // key, val may be nil + + if rclause.Def { + // 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.(*syntax.Name); ident != nil { + // declare new variable + name := ident.Value + 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.errorf(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 := endPos(rclause.X) // TODO(gri) should this just be s.Body.Pos (spec clarification)? + 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(s, "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) +} + +// isVarName reports whether x is a non-nil, non-blank (_) expression. +func isVarName(x syntax.Expr) bool { + if x == nil { + return false + } + ident, _ := unparen(x).(*syntax.Name) + return ident == nil || ident.Value != "_" +} + +// rangeKeyVal returns the key and value type produced by a range clause +// over an expression of type typ, and possibly an error message. If the +// range clause is not permitted the returned key is nil or msg is not +// empty (in that case we still may have a non-nil key type which can be +// used to reduce the chance for follow-on errors). +// The wantKey, wantVal, and hasVal flags indicate which of the iteration +// variables are used or present; this matters if we range over a generic +// type where not all keys or values are of the same type. +func rangeKeyVal(typ Type, wantKey, wantVal bool) (Type, Type, string) { + switch typ := typ.(type) { + case *Basic: + if isString(typ) { + return Typ[Int], universeRune, "" // use 'rune' name + } + case *Array: + return Typ[Int], typ.elem, "" + case *Slice: + return Typ[Int], typ.elem, "" + case *Pointer: + if typ := typ.base.Array(); typ != nil { + return Typ[Int], typ.elem, "" + } + case *Map: + return typ.key, typ.elem, "" + case *Chan: + var msg string + if typ.dir == SendOnly { + msg = "send-only channel" + } + return typ.elem, Typ[Invalid], msg + case *Sum: + first := true + var key, val Type + var msg string + typ.is(func(t Type) bool { + k, v, m := rangeKeyVal(t.Under(), wantKey, wantVal) + if k == nil || m != "" { + key, val, msg = k, v, m + return false + } + if first { + key, val, msg = k, v, m + first = false + return true + } + if wantKey && !Identical(key, k) { + key, val, msg = nil, nil, "all possible values must have the same key type" + return false + } + if wantVal && !Identical(val, v) { + key, val, msg = nil, nil, "all possible values must have the same element type" + return false + } + return true + }) + return key, val, msg + } + return nil, nil, "" +} diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go new file mode 100644 index 0000000000..27c907de10 --- /dev/null +++ b/src/cmd/compile/internal/types2/subst.go @@ -0,0 +1,554 @@ +// UNREVIEWED +// 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 implements instantiation of generic types +// through substitution of type parameters by actual +// types. + +package types2 + +import ( + "bytes" + "cmd/compile/internal/syntax" + "fmt" +) + +type substMap struct { + // The targs field is currently needed for *Named type substitution. + // TODO(gri) rewrite that code, get rid of this field, and make this + // struct just the map (proj) + targs []Type + proj map[*TypeParam]Type +} + +// makeSubstMap creates a new substitution map mapping tpars[i] to targs[i]. +// If targs[i] is nil, tpars[i] is not substituted. +func makeSubstMap(tpars []*TypeName, targs []Type) *substMap { + assert(len(tpars) == len(targs)) + proj := make(map[*TypeParam]Type, len(tpars)) + for i, tpar := range tpars { + // We must expand type arguments otherwise *Instance + // types end up as components in composite types. + // TODO(gri) explain why this causes problems, if it does + targ := expand(targs[i]) // possibly nil + targs[i] = targ + proj[tpar.typ.(*TypeParam)] = targ + } + return &substMap{targs, proj} +} + +func (m *substMap) String() string { + return fmt.Sprintf("%s", m.proj) +} + +func (m *substMap) empty() bool { + return len(m.proj) == 0 +} + +func (m *substMap) lookup(tpar *TypeParam) Type { + if t := m.proj[tpar]; t != nil { + return t + } + return tpar +} + +func (check *Checker) instantiate(pos syntax.Pos, typ Type, targs []Type, poslist []syntax.Pos) (res Type) { + if check.conf.Trace { + check.trace(pos, "-- instantiating %s with %s", typ, typeListString(targs)) + check.indent++ + defer func() { + check.indent-- + var under Type + if res != nil { + // Calling Under() here may lead to endless instantiations. + // Test case: type T[P any] T[P] + // TODO(gri) investigate if that's a bug or to be expected. + under = res.Underlying() + } + check.trace(pos, "=> %s (under = %s)", res, under) + }() + } + + assert(poslist == nil || len(poslist) <= len(targs)) + + // TODO(gri) What is better here: work with TypeParams, or work with TypeNames? + var tparams []*TypeName + switch t := typ.(type) { + case *Named: + tparams = t.tparams + case *Signature: + tparams = t.tparams + defer func() { + // If we had an unexpected failure somewhere don't + // panic below when asserting res.(*Signature). + if res == nil { + return + } + // If the signature doesn't use its type parameters, subst + // will not make a copy. In that case, make a copy now (so + // we can set tparams to nil w/o causing side-effects). + if t == res { + copy := *t + res = © + } + // After instantiating a generic signature, it is not generic + // anymore; we need to set tparams to nil. + res.(*Signature).tparams = nil + }() + + default: + check.dump("%v: cannot instantiate %v", pos, typ) + unreachable() // only defined types and (defined) functions can be generic + + } + + // the number of supplied types must match the number of type parameters + if len(targs) != len(tparams) { + // TODO(gri) provide better error message + check.errorf(pos, "got %d arguments but %d type parameters", len(targs), len(tparams)) + return Typ[Invalid] + } + + if len(tparams) == 0 { + return typ // nothing to do (minor optimization) + } + + smap := makeSubstMap(tparams, targs) + + // check bounds + for i, tname := range tparams { + tpar := tname.typ.(*TypeParam) + iface := tpar.Bound() + if iface.Empty() { + continue // no type bound + } + + targ := targs[i] + + // best position for error reporting + pos := pos + if i < len(poslist) { + pos = poslist[i] + } + + // The type parameter bound is parameterized with the same type parameters + // as the instantiated type; before we can use it for bounds checking we + // need to instantiate it with the type arguments with which we instantiate + // the parameterized type. + iface = check.subst(pos, iface, smap).(*Interface) + + // targ must implement iface (methods) + // - check only if we have methods + check.completeInterface(nopos, iface) + if len(iface.allMethods) > 0 { + // If the type argument is a type parameter itself, its pointer designation + // must match the pointer designation of the callee's type parameter. + // If the type argument is a pointer to a type parameter, the type argument's + // method set is empty. + // TODO(gri) is this what we want? (spec question) + if tparg := targ.TypeParam(); tparg != nil { + if tparg.ptr != tpar.ptr { + check.errorf(pos, "pointer designation mismatch") + break + } + } else if base, isPtr := deref(targ); isPtr && base.TypeParam() != nil { + check.errorf(pos, "%s has no methods", targ) + break + } + // If a type parameter is marked as a pointer type, the type bound applies + // to a pointer of the type argument. + actual := targ + if tpar.ptr { + actual = NewPointer(targ) + } + if m, wrong := check.missingMethod(actual, iface, true); m != nil { + // TODO(gri) needs to print updated name to avoid major confusion in error message! + // (print warning for now) + // check.softErrorf(pos, "%s does not satisfy %s (warning: name not updated) = %s (missing method %s)", targ, tpar.bound, iface, m) + if m.name == "==" { + // We don't want to report "missing method ==". + check.softErrorf(pos, "%s does not satisfy comparable", targ) + } else if wrong != nil { + // TODO(gri) This can still report uninstantiated types which makes the error message + // more difficult to read then necessary. + check.softErrorf(pos, + "%s does not satisfy %s: wrong method signature\n\tgot %s\n\twant %s", + targ, tpar.bound, wrong, m, + ) + } else { + check.softErrorf(pos, "%s does not satisfy %s (missing method %s)", targ, tpar.bound, m.name) + } + break + } + } + + // targ's underlying type must also be one of the interface types listed, if any + if iface.allTypes == nil { + continue // nothing to do + } + // iface.allTypes != nil + + // If targ is itself a type parameter, each of its possible types, but at least one, must be in the + // list of iface types (i.e., the targ type list must be a non-empty subset of the iface types). + if targ := targ.TypeParam(); targ != nil { + targBound := targ.Bound() + if targBound.allTypes == nil { + check.softErrorf(pos, "%s does not satisfy %s (%s has no type constraints)", targ, tpar.bound, targ) + break + } + for _, t := range unpack(targBound.allTypes) { + if !iface.isSatisfiedBy(t.Under()) { + // TODO(gri) match this error message with the one below (or vice versa) + check.softErrorf(pos, "%s does not satisfy %s (%s type constraint %s not found in %s)", targ, tpar.bound, targ, t, iface.allTypes) + break + } + } + break + } + + // Otherwise, targ's type or underlying type must also be one of the interface types listed, if any. + if !iface.isSatisfiedBy(targ) { + check.softErrorf(pos, "%s does not satisfy %s (%s not found in %s)", targ, tpar.bound, targ.Under(), iface.allTypes) + break + } + } + + return check.subst(pos, typ, smap) +} + +// subst returns the type typ with its type parameters tpars replaced by +// the corresponding type arguments targs, recursively. +// subst is functional in the sense that it doesn't modify the incoming +// type. If a substitution took place, the result type is different from +// from the incoming type. +func (check *Checker) subst(pos syntax.Pos, typ Type, smap *substMap) Type { + if smap.empty() { + return typ + } + + // common cases + switch t := typ.(type) { + case *Basic: + return typ // nothing to do + case *TypeParam: + return smap.lookup(t) + } + + // general case + subst := subster{check, pos, make(map[Type]Type), smap} + return subst.typ(typ) +} + +type subster struct { + check *Checker + pos syntax.Pos + cache map[Type]Type + smap *substMap +} + +func (subst *subster) typ(typ Type) Type { + switch t := typ.(type) { + case nil: + // Call typOrNil if it's possible that typ is nil. + panic("nil typ") + + case *Basic, *bottom, *top: + // nothing to do + + case *Array: + elem := subst.typOrNil(t.elem) + if elem != t.elem { + return &Array{len: t.len, elem: elem} + } + + case *Slice: + elem := subst.typOrNil(t.elem) + if elem != t.elem { + return &Slice{elem: elem} + } + + case *Struct: + if fields, copied := subst.varList(t.fields); copied { + return &Struct{fields: fields, tags: t.tags} + } + + case *Pointer: + base := subst.typ(t.base) + if base != t.base { + return &Pointer{base: base} + } + + case *Tuple: + return subst.tuple(t) + + case *Signature: + // TODO(gri) rethink the recv situation with respect to methods on parameterized types + // recv := subst.var_(t.recv) // TODO(gri) this causes a stack overflow - explain + recv := t.recv + params := subst.tuple(t.params) + results := subst.tuple(t.results) + if recv != t.recv || params != t.params || results != t.results { + return &Signature{ + rparams: t.rparams, + tparams: t.tparams, + scope: t.scope, + recv: recv, + params: params, + results: results, + variadic: t.variadic, + } + } + + case *Sum: + types, copied := subst.typeList(t.types) + if copied { + // Don't do it manually, with a Sum literal: the new + // types list may not be unique and NewSum may remove + // duplicates. + return NewSum(types) + } + + case *Interface: + methods, mcopied := subst.funcList(t.methods) + types := t.types + if t.types != nil { + types = subst.typ(t.types) + } + embeddeds, ecopied := subst.typeList(t.embeddeds) + if mcopied || types != t.types || ecopied { + iface := &Interface{methods: methods, types: types, embeddeds: embeddeds} + subst.check.posMap[iface] = subst.check.posMap[t] // satisfy completeInterface requirement + subst.check.completeInterface(nopos, iface) + return iface + } + + case *Map: + key := subst.typ(t.key) + elem := subst.typ(t.elem) + if key != t.key || elem != t.elem { + return &Map{key: key, elem: elem} + } + + case *Chan: + elem := subst.typ(t.elem) + if elem != t.elem { + return &Chan{dir: t.dir, elem: elem} + } + + case *Named: + subst.check.indent++ + defer func() { + subst.check.indent-- + }() + dump := func(format string, args ...interface{}) { + if subst.check.conf.Trace { + subst.check.trace(subst.pos, format, args...) + } + } + + if t.tparams == nil { + dump(">>> %s is not parameterized", t) + return t // type is not parameterized + } + + var new_targs []Type + + if len(t.targs) > 0 { + + // already instantiated + dump(">>> %s already instantiated", t) + assert(len(t.targs) == len(t.tparams)) + // For each (existing) type argument targ, determine if it needs + // to be substituted; i.e., if it is or contains a type parameter + // that has a type argument for it. + for i, targ := range t.targs { + dump(">>> %d targ = %s", i, targ) + new_targ := subst.typ(targ) + if new_targ != targ { + dump(">>> substituted %d targ %s => %s", i, targ, new_targ) + if new_targs == nil { + new_targs = make([]Type, len(t.tparams)) + copy(new_targs, t.targs) + } + new_targs[i] = new_targ + } + } + + if new_targs == nil { + dump(">>> nothing to substitute in %s", t) + return t // nothing to substitute + } + + } else { + + // not yet instantiated + dump(">>> first instantiation of %s", t) + new_targs = subst.smap.targs + + } + + // before creating a new named type, check if we have this one already + h := instantiatedHash(t, new_targs) + dump(">>> new type hash: %s", h) + if named, found := subst.check.typMap[h]; found { + dump(">>> found %s", named) + subst.cache[t] = named + return named + } + + // create a new named type and populate caches to avoid endless recursion + tname := NewTypeName(subst.pos, t.obj.pkg, t.obj.name, nil) + named := subst.check.NewNamed(tname, t.underlying, t.methods) // method signatures are updated lazily + named.tparams = t.tparams // new type is still parameterized + named.targs = new_targs + subst.check.typMap[h] = named + subst.cache[t] = named + + // do the substitution + dump(">>> subst %s with %s (new: %s)", t.underlying, subst.smap, new_targs) + named.underlying = subst.typOrNil(t.underlying) + named.orig = named.underlying // for cycle detection (Checker.validType) + + return named + + case *TypeParam: + return subst.smap.lookup(t) + + case *instance: + // TODO(gri) can we avoid the expansion here and just substitute the type parameters? + return subst.typ(t.expand()) + + default: + unimplemented() + } + + return typ +} + +// TODO(gri) Eventually, this should be more sophisticated. +// It won't work correctly for locally declared types. +func instantiatedHash(typ *Named, targs []Type) string { + var buf bytes.Buffer + writeTypeName(&buf, typ.obj, nil) + buf.WriteByte('(') + writeTypeList(&buf, targs, nil, nil) + buf.WriteByte(')') + + // With respect to the represented type, whether a + // type is fully expanded or stored as instance + // does not matter - they are the same types. + // Remove the instanceMarkers printed for instances. + res := buf.Bytes() + i := 0 + for _, b := range res { + if b != instanceMarker { + res[i] = b + i++ + } + } + + return string(res[:i]) +} + +func typeListString(list []Type) string { + var buf bytes.Buffer + writeTypeList(&buf, list, nil, nil) + return buf.String() +} + +// typOrNil is like typ but if the argument is nil it is replaced with Typ[Invalid]. +// A nil type may appear in pathological cases such as type T[P any] []func(_ T([]_)) +// where an array/slice element is accessed before it is set up. +func (subst *subster) typOrNil(typ Type) Type { + if typ == nil { + return Typ[Invalid] + } + return subst.typ(typ) +} + +func (subst *subster) var_(v *Var) *Var { + if v != nil { + if typ := subst.typ(v.typ); typ != v.typ { + copy := *v + copy.typ = typ + return © + } + } + return v +} + +func (subst *subster) tuple(t *Tuple) *Tuple { + if t != nil { + if vars, copied := subst.varList(t.vars); copied { + return &Tuple{vars: vars} + } + } + return t +} + +func (subst *subster) varList(in []*Var) (out []*Var, copied bool) { + out = in + for i, v := range in { + if w := subst.var_(v); w != v { + if !copied { + // first variable that got substituted => allocate new out slice + // and copy all variables + new := make([]*Var, len(in)) + copy(new, out) + out = new + copied = true + } + out[i] = w + } + } + return +} + +func (subst *subster) func_(f *Func) *Func { + if f != nil { + if typ := subst.typ(f.typ); typ != f.typ { + copy := *f + copy.typ = typ + return © + } + } + return f +} + +func (subst *subster) funcList(in []*Func) (out []*Func, copied bool) { + out = in + for i, f := range in { + if g := subst.func_(f); g != f { + if !copied { + // first function that got substituted => allocate new out slice + // and copy all functions + new := make([]*Func, len(in)) + copy(new, out) + out = new + copied = true + } + out[i] = g + } + } + return +} + +func (subst *subster) typeList(in []Type) (out []Type, copied bool) { + out = in + for i, t := range in { + if u := subst.typ(t); u != t { + if !copied { + // first function that got substituted => allocate new out slice + // and copy all functions + new := make([]Type, len(in)) + copy(new, out) + out = new + copied = true + } + out[i] = u + } + } + return +} diff --git a/src/cmd/compile/internal/types2/type.go b/src/cmd/compile/internal/types2/type.go new file mode 100644 index 0000000000..c26d243f3c --- /dev/null +++ b/src/cmd/compile/internal/types2/type.go @@ -0,0 +1,1061 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" + "sort" +) + +// A Type represents a type of Go. +// All types implement the Type interface. +type Type interface { + // Underlying returns the underlying type of a type + // w/o following forwarding chains. Only used by + // client packages (here for backward-compatibility). + Underlying() Type + + // Under returns the true expanded underlying type. + // If it doesn't exist, the result is Typ[Invalid]. + // Under must only be called when a type is known + // to be fully set up. + Under() Type + + // String returns a string representation of a type. + String() string + + // Converters + // A converter must only be called when a type is + // known to be fully set up. A converter returns + // a type's operational type (see comment for optype) + // or nil if the type is receiver is not of the + // respective type. + Basic() *Basic + Array() *Array + Slice() *Slice + Struct() *Struct + Pointer() *Pointer + Tuple() *Tuple + Signature() *Signature + Sum() *Sum + Interface() *Interface + Map() *Map + Chan() *Chan + + // If the receiver for Named and TypeParam is of + // the respective type (possibly after unpacking + // an instance type), these methods return that + // type. Otherwise the result is nil. + Named() *Named + TypeParam() *TypeParam +} + +// aType implements default type behavior +type aType struct{} + +// These methods must be implemented by each type. +func (aType) Underlying() Type { panic("unreachable") } +func (aType) Under() Type { panic("unreachable") } +func (aType) String() string { panic("unreachable") } + +// Each type is implementing its version of these methods +// (Basic must implement Basic, etc.), the other methods +// are inherited. +func (aType) Basic() *Basic { return nil } +func (aType) Array() *Array { return nil } +func (aType) Slice() *Slice { return nil } +func (aType) Struct() *Struct { return nil } +func (aType) Pointer() *Pointer { return nil } +func (aType) Tuple() *Tuple { return nil } +func (aType) Signature() *Signature { return nil } +func (aType) Sum() *Sum { return nil } +func (aType) Interface() *Interface { return nil } +func (aType) Map() *Map { return nil } +func (aType) Chan() *Chan { return nil } + +func (aType) Named() *Named { return nil } +func (aType) TypeParam() *TypeParam { return nil } + +// BasicKind describes the kind of basic type. +type BasicKind int + +const ( + Invalid BasicKind = iota // type is invalid + + // predeclared types + Bool + Int + Int8 + Int16 + Int32 + Int64 + Uint + Uint8 + Uint16 + Uint32 + Uint64 + Uintptr + Float32 + Float64 + Complex64 + Complex128 + String + UnsafePointer + + // types for untyped values + UntypedBool + UntypedInt + UntypedRune + UntypedFloat + UntypedComplex + UntypedString + UntypedNil + + // aliases + Byte = Uint8 + Rune = Int32 +) + +// BasicInfo is a set of flags describing properties of a basic type. +type BasicInfo int + +// Properties of basic types. +const ( + IsBoolean BasicInfo = 1 << iota + IsInteger + IsUnsigned + IsFloat + IsComplex + IsString + IsUntyped + + IsOrdered = IsInteger | IsFloat | IsString + IsNumeric = IsInteger | IsFloat | IsComplex + IsConstType = IsBoolean | IsNumeric | IsString +) + +// A Basic represents a basic type. +type Basic struct { + kind BasicKind + info BasicInfo + name string + aType +} + +// Kind returns the kind of basic type b. +func (b *Basic) Kind() BasicKind { return b.kind } + +// Info returns information about properties of basic type b. +func (b *Basic) Info() BasicInfo { return b.info } + +// Name returns the name of basic type b. +func (b *Basic) Name() string { return b.name } + +// An Array represents an array type. +type Array struct { + len int64 + elem Type + aType +} + +// NewArray returns a new array type for the given element type and length. +// A negative length indicates an unknown length. +func NewArray(elem Type, len int64) *Array { return &Array{len: len, elem: elem} } + +// Len returns the length of array a. +// A negative result indicates an unknown length. +func (a *Array) Len() int64 { return a.len } + +// Elem returns element type of array a. +func (a *Array) Elem() Type { return a.elem } + +// A Slice represents a slice type. +type Slice struct { + elem Type + aType +} + +// NewSlice returns a new slice type for the given element type. +func NewSlice(elem Type) *Slice { return &Slice{elem: elem} } + +// Elem returns the element type of slice s. +func (s *Slice) Elem() Type { return s.elem } + +// A Struct represents a struct type. +type Struct struct { + fields []*Var + tags []string // field tags; nil if there are no tags + aType +} + +// NewStruct returns a new struct with the given fields and corresponding field tags. +// If a field with index i has a tag, tags[i] must be that tag, but len(tags) may be +// only as long as required to hold the tag with the largest index i. Consequently, +// if no field has a tag, tags may be nil. +func NewStruct(fields []*Var, tags []string) *Struct { + var fset objset + for _, f := range fields { + if f.name != "_" && fset.insert(f) != nil { + panic("multiple fields with the same name") + } + } + if len(tags) > 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 + aType +} + +// 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 + aType +} + +// NewTuple returns a new tuple for the given variables. +func NewTuple(x ...*Var) *Tuple { + if len(x) > 0 { + return &Tuple{vars: x} + } + return nil +} + +// We cannot rely on the embedded X() *X methods because (*Tuple)(nil) +// is a valid *Tuple value but (*Tuple)(nil).X() would panic without +// these implementations. At the moment we only need X = Basic, Named, +// but add all because missing one leads to very confusing bugs. +// TODO(gri) Don't represent empty tuples with a (*Tuple)(nil) pointer; +// it's too subtle and causes problems. +func (*Tuple) Basic() *Basic { return nil } +func (*Tuple) Array() *Array { return nil } +func (*Tuple) Slice() *Slice { return nil } +func (*Tuple) Struct() *Struct { return nil } +func (*Tuple) Pointer() *Pointer { return nil } + +// func (*Tuple) Tuple() *Tuple // implemented below +func (*Tuple) Signature() *Signature { return nil } +func (*Tuple) Sum() *Sum { return nil } +func (*Tuple) Interface() *Interface { return nil } +func (*Tuple) Map() *Map { return nil } +func (*Tuple) Chan() *Chan { return nil } + +func (*Tuple) Named() *Named { return nil } +func (*Tuple) TypeParam() *TypeParam { 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. + rparams []*TypeName // reveiver type parameters from left to right; or nil + tparams []*TypeName // type parameters from left to right; or nil + 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) + aType +} + +// 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{recv: recv, params: params, results: results, variadic: 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 } + +// TParams returns the type parameters of signature s, or nil. +func (s *Signature) TParams() []*TypeName { return s.tparams } + +// SetTParams sets the type parameters of signature s. +func (s *Signature) SetTParams(tparams []*TypeName) { s.tparams = tparams } + +// 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 } + +// A Sum represents a set of possible types. +// Sums are currently used to represent type lists of interfaces +// and thus the underlying types of type parameters; they are not +// first class types of Go. +type Sum struct { + types []Type // types are unique + aType +} + +// NewSum returns a new Sum type consisting of the provided +// types if there are more than one. If there is exactly one +// type, it returns that type. If the list of types is empty +// the result is nil. +func NewSum(types []Type) Type { + if len(types) == 0 { + return nil + } + + // What should happen if types contains a sum type? + // Do we flatten the types list? For now we check + // and panic. This should not be possible for the + // current use case of type lists. + // TODO(gri) Come up with the rules for sum types. + for _, t := range types { + if _, ok := t.(*Sum); ok { + panic("sum type contains sum type - unimplemented") + } + } + + if len(types) == 1 { + return types[0] + } + return &Sum{types: types} +} + +// is reports whether all types in t satisfy pred. +func (s *Sum) is(pred func(Type) bool) bool { + if s == nil { + return false + } + for _, t := range s.types { + if !pred(t) { + return false + } + } + return true +} + +// An Interface represents an interface type. +type Interface struct { + methods []*Func // ordered list of explicitly declared methods + types Type // (possibly a Sum) type declared with a type list (TODO(gri) need better field name) + 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) + allTypes Type // intersection of all embedded and locally declared types (TODO(gri) need better field name) + + obj Object // type declaration defining this interface; or nil (for better error messages) + + aType +} + +// unpack unpacks a type into a list of types. +// TODO(gri) Try to eliminate the need for this function. +func unpack(typ Type) []Type { + if typ == nil { + return nil + } + if sum := typ.Sum(); sum != nil { + return sum.types + } + return []Type{typ} +} + +// is reports whether interface t represents types that all satisfy pred. +func (t *Interface) is(pred func(Type) bool) bool { + if t.allTypes == nil { + return false // we must have at least one type! (was bug) + } + for _, t := range unpack(t.allTypes) { + if !pred(t) { + return false + } + } + return true +} + +// 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. +func (t *Interface) Empty() bool { + if t.allMethods != nil { + // interface is complete - quick test + // A non-nil allTypes may still be empty and represents the bottom type. + return len(t.allMethods) == 0 && t.allTypes == nil + } + return !t.iterate(func(t *Interface) bool { + if len(t.methods) > 0 || t.types != nil { + return true + } + return false + }, nil) +} + +// HasTypeList reports whether interface t has a type list, possibly from an embedded type. +func (t *Interface) HasTypeList() bool { + if t.allMethods != nil { + // interface is complete - quick test + return t.allTypes != nil + } + + return t.iterate(func(t *Interface) bool { + if t.types != nil { + return true + } + return false + }, nil) +} + +// IsComparable reports whether interface t is or embeds the predeclared interface "comparable". +func (t *Interface) IsComparable() bool { + if t.allMethods != nil { + // interface is complete - quick test + _, m := lookupMethod(t.allMethods, nil, "==") + return m != nil + } + + return t.iterate(func(t *Interface) bool { + _, m := lookupMethod(t.methods, nil, "==") + return m != nil + }, nil) +} + +// IsConstraint reports t.HasTypeList() || t.IsComparable(). +func (t *Interface) IsConstraint() bool { + if t.allMethods != nil { + // interface is complete - quick test + if t.allTypes != nil { + return true + } + _, m := lookupMethod(t.allMethods, nil, "==") + return m != nil + } + + return t.iterate(func(t *Interface) bool { + if t.types != nil { + return true + } + _, m := lookupMethod(t.methods, nil, "==") + return m != nil + }, nil) +} + +// iterate calls f with t and then with any embedded interface of t, recursively, until f returns true. +// iterate reports whether any call to f returned true. +func (t *Interface) iterate(f func(*Interface) bool, seen map[*Interface]bool) bool { + if f(t) { + return true + } + for _, e := range t.embeddeds { + // e should be an interface but be careful (it may be invalid) + if e := e.Interface(); e != nil { + // Cyclic interfaces such as "type E interface { E }" are not permitted + // but they are still constructed and we need to detect such cycles. + if seen[e] { + continue + } + if seen == nil { + seen = make(map[*Interface]bool) + } + seen[e] = true + if e.iterate(f, seen) { + return true + } + } + } + return false +} + +// isSatisfiedBy reports whether interface t's type list is satisfied by the type typ. +// If the the type list is empty (absent), typ trivially satisfies the interface. +// TODO(gri) This is not a great name. Eventually, we should have a more comprehensive +// "implements" predicate. +func (t *Interface) isSatisfiedBy(typ Type) bool { + t.Complete() + if t.allTypes == nil { + return true + } + types := unpack(t.allTypes) + return includes(types, typ) || includes(types, typ.Under()) +} + +// 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) + } + + allTypes := t.types + + for _, typ := range t.embeddeds { + utyp := typ.Under() + etyp := utyp.Interface() + if etyp == nil { + if utyp != Typ[Invalid] { + panic(fmt.Sprintf("%s is not an interface", typ)) + } + continue + } + etyp.Complete() + for _, m := range etyp.allMethods { + addMethod(m, false) + } + allTypes = intersect(allTypes, etyp.allTypes) + } + + 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 + } + t.allTypes = allTypes + + return t +} + +// A Map represents a map type. +type Map struct { + key, elem Type + aType +} + +// NewMap returns a new map for the given key and element types. +func NewMap(key, elem Type) *Map { + return &Map{key: key, elem: 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 + aType +} + +// 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: dir, elem: 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 (defined) type. +type Named struct { + check *Checker // for Named.Under implementation + 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 + tparams []*TypeName // type parameters, or nil + targs []Type // type arguments (after instantiation), or nil + methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily + aType +} + +// 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 +} + +func (check *Checker) NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { + typ := &Named{check: check, 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 } + +// Converter methods +func (t *Named) Basic() *Basic { return t.Under().Basic() } +func (t *Named) Array() *Array { return t.Under().Array() } +func (t *Named) Slice() *Slice { return t.Under().Slice() } +func (t *Named) Struct() *Struct { return t.Under().Struct() } +func (t *Named) Pointer() *Pointer { return t.Under().Pointer() } +func (t *Named) Tuple() *Tuple { return t.Under().Tuple() } +func (t *Named) Signature() *Signature { return t.Under().Signature() } +func (t *Named) Interface() *Interface { return t.Under().Interface() } +func (t *Named) Map() *Map { return t.Under().Map() } +func (t *Named) Chan() *Chan { return t.Under().Chan() } + +// func (t *Named) Named() *Named // declared below +func (t *Named) TypeParam() *TypeParam { return t.Under().TypeParam() } + +// TODO(gri) Come up with a better representation and API to distinguish +// between parameterized instantiated and non-instantiated types. + +// TParams returns the type parameters of the named type t, or nil. +// The result is non-nil for an (originally) parameterized type even if it is instantiated. +func (t *Named) TParams() []*TypeName { return t.tparams } + +// TArgs returns the type arguments after instantiation of the named type t, or nil if not instantiated. +func (t *Named) TArgs() []Type { return t.targs } + +// SetTArgs sets the type arguments of Named. +func (t *Named) SetTArgs(args []Type) { t.targs = args } + +// 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) + } +} + +// A TypeParam represents a type parameter type. +type TypeParam struct { + check *Checker // for lazy type bound completion + id uint64 // unique id + ptr bool // pointer designation + obj *TypeName // corresponding type name + index int // parameter index + bound Type // *Named or *Interface; underlying type is always *Interface + aType +} + +// NewTypeParam returns a new TypeParam. +func (check *Checker) NewTypeParam(ptr bool, obj *TypeName, index int, bound Type) *TypeParam { + assert(bound != nil) + typ := &TypeParam{check: check, id: check.nextId, ptr: ptr, obj: obj, index: index, bound: bound} + check.nextId++ + if obj.typ == nil { + obj.typ = typ + } + return typ +} + +func (t *TypeParam) Bound() *Interface { + iface := t.bound.Interface() + // use the type bound position if we have one + pos := nopos + if n, _ := t.bound.(*Named); n != nil { + pos = n.obj.pos + } + t.check.completeInterface(pos, iface) + return iface +} + +// optype returns a type's operational type. Except for +// type parameters, the operational type is the same +// as the underlying type (as returned by Under). For +// Type parameters, the operational type is determined +// by the corresponding type bound's type list. The +// result may be the bottom or top type, but it is never +// the incoming type parameter. +func optype(typ Type) Type { + if t := typ.TypeParam(); t != nil { + // If the optype is typ, return the top type as we have + // no information. It also prevents infinite recursion + // via the TypeParam converter methods. This can happen + // for a type parameter list of the form: + // (type T interface { type T }). + // See also issue #39680. + if u := t.Bound().allTypes; u != nil && u != typ { + // u != typ and u is a type parameter => u.Under() != typ, so this is ok + return u.Under() + } + return theTop + } + return typ +} + +// Converter methods +func (t *TypeParam) Basic() *Basic { return optype(t).Basic() } +func (t *TypeParam) Array() *Array { return optype(t).Array() } +func (t *TypeParam) Slice() *Slice { return optype(t).Slice() } +func (t *TypeParam) Struct() *Struct { return optype(t).Struct() } +func (t *TypeParam) Pointer() *Pointer { return optype(t).Pointer() } +func (t *TypeParam) Tuple() *Tuple { return optype(t).Tuple() } +func (t *TypeParam) Signature() *Signature { return optype(t).Signature() } +func (t *TypeParam) Sum() *Sum { return optype(t).Sum() } +func (t *TypeParam) Interface() *Interface { return optype(t).Interface() } +func (t *TypeParam) Map() *Map { return optype(t).Map() } +func (t *TypeParam) Chan() *Chan { return optype(t).Chan() } + +// func (t *TypeParam) Named() *Named // Named does not unpack type parameters +// func (t *TypeParam) TypeParam() *TypeParam // declared below + +// An instance represents an instantiated generic type syntactically +// (without expanding the instantiation). Type instances appear only +// during type-checking and are replaced by their fully instantiated +// (expanded) types before the end of type-checking. +type instance struct { + check *Checker // for lazy instantiation + pos syntax.Pos // position of type instantiation; for error reporting only + base *Named // parameterized type to be instantiated + targs []Type // type arguments + poslist []syntax.Pos // position of each targ; for error reporting only + value Type // base(targs...) after instantiation or Typ[Invalid]; nil if not yet set + aType +} + +// Converter methods +func (t *instance) Basic() *Basic { return t.Under().Basic() } +func (t *instance) Array() *Array { return t.Under().Array() } +func (t *instance) Slice() *Slice { return t.Under().Slice() } +func (t *instance) Struct() *Struct { return t.Under().Struct() } +func (t *instance) Pointer() *Pointer { return t.Under().Pointer() } +func (t *instance) Tuple() *Tuple { return t.Under().Tuple() } +func (t *instance) Signature() *Signature { return t.Under().Signature() } +func (t *instance) Sum() *Sum { return t.Under().Sum() } +func (t *instance) Interface() *Interface { return t.Under().Interface() } +func (t *instance) Map() *Map { return t.Under().Map() } +func (t *instance) Chan() *Chan { return t.Under().Chan() } + +func (t *instance) Named() *Named { return t.expand().Named() } +func (t *instance) TypeParam() *TypeParam { return t.expand().TypeParam() } + +// expand returns the instantiated (= expanded) type of t. +// The result is either an instantiated *Named type, or +// Typ[Invalid] if there was an error. +func (t *instance) expand() Type { + v := t.value + if v == nil { + v = t.check.instantiate(t.pos, t.base, t.targs, t.poslist) + if v == nil { + v = Typ[Invalid] + } + t.value = v + } + // After instantiation we must have an invalid or a *Named type. + if debug && v != Typ[Invalid] { + _ = v.(*Named) + } + return v +} + +// expand expands a type instance into its instantiated +// type and leaves all other types alone. expand does +// not recurse. +func expand(typ Type) Type { + if t, _ := typ.(*instance); t != nil { + return t.expand() + } + return typ +} + +// expandf is set to expand. +// Call expandf when calling expand causes compile-time cycle error. +var expandf func(Type) Type + +func init() { expandf = expand } + +// bottom represents the bottom of the type lattice. +// It is the underlying type of a type parameter that +// cannot be satisfied by any type, usually because +// the intersection of type constraints left nothing). +type bottom struct { + aType +} + +// theBottom is the singleton bottom type. +var theBottom = &bottom{} + +// top represents the top of the type lattice. +// It is the underlying type of a type parameter that +// can be satisfied by any type (ignoring methods), +// usually because the type constraint has no type +// list. +type top struct { + aType +} + +// theTop is the singleton top type. +var theTop = &top{} + +// Type-specific implementations of type converters. +func (t *Basic) Basic() *Basic { return t } +func (t *Array) Array() *Array { return t } +func (t *Slice) Slice() *Slice { return t } +func (t *Struct) Struct() *Struct { return t } +func (t *Pointer) Pointer() *Pointer { return t } +func (t *Tuple) Tuple() *Tuple { return t } +func (t *Signature) Signature() *Signature { return t } +func (t *Sum) Sum() *Sum { return t } +func (t *Interface) Interface() *Interface { return t } +func (t *Map) Map() *Map { return t } +func (t *Chan) Chan() *Chan { return t } + +func (t *Named) Named() *Named { return t } +func (t *TypeParam) TypeParam() *TypeParam { return t } + +// Type-specific implementations of Underlying. +func (t *Basic) Underlying() Type { return t } +func (t *Array) Underlying() Type { return t } +func (t *Slice) Underlying() Type { return t } +func (t *Struct) Underlying() Type { return t } +func (t *Pointer) Underlying() Type { return t } +func (t *Tuple) Underlying() Type { return t } +func (t *Signature) Underlying() Type { return t } +func (t *Sum) Underlying() Type { return t } +func (t *Interface) Underlying() Type { return t } +func (t *Map) Underlying() Type { return t } +func (t *Chan) Underlying() Type { return t } +func (t *Named) Underlying() Type { return t.underlying } +func (t *TypeParam) Underlying() Type { return t } +func (t *instance) Underlying() Type { return t } +func (t *bottom) Underlying() Type { return t } +func (t *top) Underlying() Type { return t } + +// Type-specific implementations of Under. +func (t *Basic) Under() Type { return t } +func (t *Array) Under() Type { return t } +func (t *Slice) Under() Type { return t } +func (t *Struct) Under() Type { return t } +func (t *Pointer) Under() Type { return t } +func (t *Tuple) Under() Type { return t } +func (t *Signature) Under() Type { return t } +func (t *Sum) Under() Type { return t } // TODO(gri) is this correct? +func (t *Interface) Under() Type { return t } +func (t *Map) Under() Type { return t } +func (t *Chan) Under() Type { return t } + +// see decl.go for implementation of Named.Under +func (t *TypeParam) Under() Type { return t } +func (t *instance) Under() Type { return t.expand().Under() } +func (t *bottom) Under() Type { return t } +func (t *top) Under() Type { return t } + +// Type-specific implementations of String. +func (t *Basic) String() string { return TypeString(t, nil) } +func (t *Array) String() string { return TypeString(t, nil) } +func (t *Slice) String() string { return TypeString(t, nil) } +func (t *Struct) String() string { return TypeString(t, nil) } +func (t *Pointer) String() string { return TypeString(t, nil) } +func (t *Tuple) String() string { return TypeString(t, nil) } +func (t *Signature) String() string { return TypeString(t, nil) } +func (t *Sum) String() string { return TypeString(t, nil) } +func (t *Interface) String() string { return TypeString(t, nil) } +func (t *Map) String() string { return TypeString(t, nil) } +func (t *Chan) String() string { return TypeString(t, nil) } +func (t *Named) String() string { return TypeString(t, nil) } +func (t *TypeParam) String() string { return TypeString(t, nil) } +func (t *instance) String() string { return TypeString(t, nil) } +func (t *bottom) String() string { return TypeString(t, nil) } +func (t *top) String() string { return TypeString(t, nil) } diff --git a/src/cmd/compile/internal/types2/typestring.go b/src/cmd/compile/internal/types2/typestring.go new file mode 100644 index 0000000000..98021797a1 --- /dev/null +++ b/src/cmd/compile/internal/types2/typestring.go @@ -0,0 +1,480 @@ +// UNREVIEWED +// 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 types2 + +import ( + "bytes" + "fmt" + "unicode/utf8" +) + +// 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)) +} + +// instanceMarker is the prefix for an instantiated type +// in "non-evaluated" instance form. +const instanceMarker = '#' + +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", goTypeName(typ)) // cycle to typ + return + } + } + visited = append(visited, typ) + + switch t := typ.(type) { + case nil: + buf.WriteString("<nil>") + + 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("; ") + } + buf.WriteString(f.name) + if f.embedded { + // emphasize that the embedded field's name + // doesn't match the field's type name + if f.name != embeddedFieldName(f.typ) { + buf.WriteString(" /* = ") + writeType(buf, f.typ, qf, visited) + buf.WriteString(" */") + } + } else { + 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 *Sum: + for i, t := range t.types { + if i > 0 { + buf.WriteString(", ") + } + writeType(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 + } + if !empty && t.allTypes != nil { + buf.WriteString("; ") + } + if t.allTypes != nil { + buf.WriteString("type ") + writeType(buf, t.allTypes, qf, visited) + } + } 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 + } + if !empty && t.types != nil { + buf.WriteString("; ") + } + if t.types != nil { + buf.WriteString("type ") + writeType(buf, t.types, qf, visited) + empty = false + } + if !empty && len(t.embeddeds) > 0 { + buf.WriteString("; ") + } + for i, typ := range t.embeddeds { + if i > 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: + writeTypeName(buf, t.obj, qf) + if t.targs != nil { + // instantiated type + buf.WriteByte('[') + writeTypeList(buf, t.targs, qf, visited) + buf.WriteByte(']') + } else if t.tparams != nil { + // parameterized type + writeTParamList(buf, t.tparams, qf, visited) + } + + case *TypeParam: + s := "?" + if t.obj != nil { + s = t.obj.name + } + buf.WriteString(s + subscript(t.id)) + + case *instance: + buf.WriteByte(instanceMarker) // indicate "non-evaluated" syntactic instance + writeTypeName(buf, t.base.obj, qf) + buf.WriteByte('[') + writeTypeList(buf, t.targs, qf, visited) + buf.WriteByte(']') + + case *bottom: + buf.WriteString("⊥") + + case *top: + buf.WriteString("⊤") + + default: + // For externally defined implementations of Type. + buf.WriteString(t.String()) + } +} + +func writeTypeList(buf *bytes.Buffer, list []Type, qf Qualifier, visited []Type) { + for i, typ := range list { + if i > 0 { + buf.WriteString(", ") + } + writeType(buf, typ, qf, visited) + } +} + +func writeTParamList(buf *bytes.Buffer, list []*TypeName, qf Qualifier, visited []Type) { + // bound returns the type bound for tname. The result is never nil. + bound := func(tname *TypeName) Type { + // be careful to avoid crashes in case of inconsistencies + if t, _ := tname.typ.(*TypeParam); t != nil && t.bound != nil { + return t.bound + } + return &emptyInterface + } + + // If a single type bound is not the empty interface, we have to write them all. + var writeBounds bool + for _, p := range list { + // bound(p) should be an interface but be careful (it may be invalid) + b := bound(p).Interface() + if b != nil && !b.Empty() { + writeBounds = true + break + } + } + writeBounds = true // always write the bounds for new type parameter list syntax + + buf.WriteString("[") + var prev Type + for i, p := range list { + b := bound(p) + if i > 0 { + if writeBounds && b != prev { + // type bound changed - write previous one before advancing + buf.WriteByte(' ') + writeType(buf, prev, qf, visited) + } + buf.WriteString(", ") + } + prev = b + + if t, _ := p.typ.(*TypeParam); t != nil { + if t.ptr { + buf.WriteByte('*') + } + writeType(buf, t, qf, visited) + } else { + buf.WriteString(p.name) + } + } + if writeBounds && prev != nil { + buf.WriteByte(' ') + writeType(buf, prev, qf, visited) + } + buf.WriteByte(']') +} + +func writeTypeName(buf *bytes.Buffer, obj *TypeName, qf Qualifier) { + s := "<Named w/o object>" + if 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) +} + +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 := typ.Basic(); t == nil || 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) { + if sig.tparams != nil { + writeTParamList(buf, sig.tparams, qf, visited) + } + + 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) +} + +// embeddedFieldName returns an embedded field's name given its type. +// The result is "" if the type doesn't have an embedded field name. +func embeddedFieldName(typ Type) string { + switch t := typ.(type) { + case *Basic: + return t.name + case *Named: + return t.obj.name + case *Pointer: + // *T is ok, but **T is not + if _, ok := t.base.(*Pointer); !ok { + return embeddedFieldName(t.base) + } + case *instance: + return t.base.obj.name + } + return "" // not a (pointer to) a defined type +} + +// subscript returns the decimal (utf8) representation of x using subscript digits. +func subscript(x uint64) string { + const w = len("₀") // all digits 0...9 have the same utf8 width + var buf [32 * w]byte + i := len(buf) + for { + i -= w + utf8.EncodeRune(buf[i:], '₀'+rune(x%10)) // '₀' == U+2080 + x /= 10 + if x == 0 { + break + } + } + return string(buf[i:]) +} diff --git a/src/cmd/compile/internal/types2/typestring_test.go b/src/cmd/compile/internal/types2/typestring_test.go new file mode 100644 index 0000000000..f1f7e34bf8 --- /dev/null +++ b/src/cmd/compile/internal/types2/typestring_test.go @@ -0,0 +1,221 @@ +// UNREVIEWED +// 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 types2_test + +import ( + "internal/testenv" + "testing" + + "cmd/compile/internal/syntax" + . "cmd/compile/internal/types2" +) + +const filename = "<src>" + +func makePkg(src string) (*Package, error) { + file, err := parseSrc(filename, src) + if err != nil { + return nil, err + } + // use the package name as package path + conf := Config{Importer: defaultImporter()} + return conf.Check(file.PkgName.Value, []*syntax.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}`), + dup(`interface{type int, float32, complex128}`), + + // 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) + } + } +} + +var nopos syntax.Pos + +func TestIncompleteInterfaces(t *testing.T) { + sig := NewSignature(nil, nil, nil, false) + m := NewFunc(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(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, "<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/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go new file mode 100644 index 0000000000..ae5ea669f5 --- /dev/null +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -0,0 +1,1280 @@ +// UNREVIEWED +// 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 types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" + "go/constant" + "sort" + "strconv" + "strings" +) + +// 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 *syntax.Name, 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.Value, check.pos) + if obj == nil { + if e.Value == "_" { + check.errorf(e, "cannot use _ as value or type") + } else { + check.errorf(e, "undeclared name: %s", e.Value) + } + 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, "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, "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]. +// The type must not be an (uninstantiated) generic type. +func (check *Checker) typ(e syntax.Expr) Type { + return check.definedType(e, nil) +} + +// varType type-checks the type expression e and returns its type, or Typ[Invalid]. +// The type must not be an (uninstantiated) generic type and it must be ordinary +// (see ordinaryType). +func (check *Checker) varType(e syntax.Expr) Type { + typ := check.definedType(e, nil) + check.ordinaryType(startPos(e), typ) + return typ +} + +// ordinaryType reports an error if typ is an interface type containing +// type lists or is (or embeds) the predeclared type comparable. +func (check *Checker) ordinaryType(pos syntax.Pos, typ Type) { + // We don't want to call Under() (via Interface) or complete interfaces while we + // are in the middle of type-checking parameter declarations that might belong to + // interface methods. Delay this check to the end of type-checking. + check.atEnd(func() { + if t := typ.Interface(); t != nil { + check.completeInterface(pos, t) // TODO(gri) is this the correct position? + if t.allTypes != nil { + check.softErrorf(pos, "interface contains type constraints (%s)", t.allTypes) + return + } + if t.IsComparable() { + check.softErrorf(pos, "interface is (or embeds) comparable") + } + } + }) +} + +// anyType type-checks the type expression e and returns its type, or Typ[Invalid]. +// The type may be generic or instantiated. +func (check *Checker) anyType(e syntax.Expr) Type { + typ := check.typInternal(e, nil) + assert(isTyped(typ)) + check.recordTypeAndValue(e, typexpr, typ, nil) + return typ +} + +// 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 syntax.Expr, def *Named) Type { + typ := check.typInternal(e, def) + assert(isTyped(typ)) + if isGeneric(typ) { + check.errorf(e, "cannot use generic type %s without instantiation", typ) + typ = Typ[Invalid] + } + check.recordTypeAndValue(e, typexpr, typ, nil) + return typ +} + +// genericType is like typ but the type must be an (uninstantiated) generic type. +func (check *Checker) genericType(e syntax.Expr, reportErr bool) Type { + typ := check.typInternal(e, nil) + assert(isTyped(typ)) + if typ != Typ[Invalid] && !isGeneric(typ) { + if reportErr { + check.errorf(e, "%s is not a generic type", typ) + } + typ = Typ[Invalid] + } + // TODO(gri) what is the correct call below? + check.recordTypeAndValue(e, typexpr, typ, nil) + return typ +} + +// isubst returns an x with identifiers substituted per the substitution map smap. +// isubst only handles the case of (valid) method receiver type expressions correctly. +func isubst(x syntax.Expr, smap map[*syntax.Name]*syntax.Name) syntax.Expr { + switch n := x.(type) { + case *syntax.Name: + if alt := smap[n]; alt != nil { + return alt + } + // case *syntax.StarExpr: + // X := isubst(n.X, smap) + // if X != n.X { + // new := *n + // new.X = X + // return &new + // } + case *syntax.Operation: + if n.Op == syntax.Mul && n.Y == nil { + X := isubst(n.X, smap) + if X != n.X { + new := *n + new.X = X + return &new + } + } + case *syntax.IndexExpr: + Index := isubst(n.Index, smap) + if Index != n.Index { + new := *n + new.Index = Index + return &new + } + case *syntax.ListExpr: + var elems []syntax.Expr + for i, elem := range n.ElemList { + Elem := isubst(elem, smap) + if Elem != elem { + if elems == nil { + elems = make([]syntax.Expr, len(n.ElemList)) + copy(elems, n.ElemList) + } + elems[i] = Elem + } + } + if elems != nil { + new := *n + new.ElemList = elems + return &new + } + case *syntax.ParenExpr: + return isubst(n.X, smap) // no need to keep parentheses + default: + // Other receiver type expressions are invalid. + // It's fine to ignore those here as they will + // be checked elsewhere. + } + return x +} + +// funcType type-checks a function or method type. +func (check *Checker) funcType(sig *Signature, recvPar *syntax.Field, tparams []*syntax.Field, ftyp *syntax.FuncType) { + check.openScope(ftyp, "function") + check.scope.isFunc = true + check.recordScope(ftyp, check.scope) + sig.scope = check.scope + defer check.closeScope() + + var recvTyp syntax.Expr // rewritten receiver type; valid if != nil + if recvPar != nil { + // collect generic receiver type parameters, if any + // - a receiver type parameter is like any other type parameter, except that it is declared implicitly + // - the receiver specification acts as local declaration for its type parameters, which may be blank + _, rname, rparams := check.unpackRecv(recvPar.Type, true) + if len(rparams) > 0 { + // Blank identifiers don't get declared and regular type-checking of the instantiated + // parameterized receiver type expression fails in Checker.collectParams of receiver. + // Identify blank type parameters and substitute each with a unique new identifier named + // "n_" (where n is the parameter index) and which cannot conflict with any user-defined + // name. + var smap map[*syntax.Name]*syntax.Name // substitution map from "_" to "!n" identifiers + for i, p := range rparams { + if p.Value == "_" { + new := *p + new.Value = fmt.Sprintf("%d_", i) + rparams[i] = &new // use n_ identifier instead of _ so it can be looked up + if smap == nil { + smap = make(map[*syntax.Name]*syntax.Name) + } + smap[p] = &new + } + } + if smap != nil { + // blank identifiers were found => use rewritten receiver type + recvTyp = isubst(recvPar.Type, smap) + } + // TODO(gri) rework declareTypeParams + sig.rparams = nil + for _, rparam := range rparams { + sig.rparams = check.declareTypeParam(sig.rparams, rparam) + } + // determine receiver type to get its type parameters + // and the respective type parameter bounds + var recvTParams []*TypeName + if rname != nil { + // recv should be a Named type (otherwise an error is reported elsewhere) + // Also: Don't report an error via genericType since it will be reported + // again when we type-check the signature. + // TODO(gri) maybe the receiver should be marked as invalid instead? + if recv := check.genericType(rname, false).Named(); recv != nil { + recvTParams = recv.tparams + } + } + // provide type parameter bounds + // - only do this if we have the right number (otherwise an error is reported elsewhere) + if len(sig.rparams) == len(recvTParams) { + // We have a list of *TypeNames but we need a list of Types. + // While creating this list, also update type parameter pointer designation + // for each (*TypeParam) list entry, by copying the information from the + // receiver base type's type parameters. + list := make([]Type, len(sig.rparams)) + for i, t := range sig.rparams { + t.typ.(*TypeParam).ptr = recvTParams[i].typ.(*TypeParam).ptr + list[i] = t.typ + } + for i, tname := range sig.rparams { + bound := recvTParams[i].typ.(*TypeParam).bound + // bound is (possibly) parameterized in the context of the + // receiver type declaration. Substitute parameters for the + // current context. + // TODO(gri) should we assume now that bounds always exist? + // (no bound == empty interface) + if bound != nil { + bound = check.subst(tname.pos, bound, makeSubstMap(recvTParams, list)) + tname.typ.(*TypeParam).bound = bound + } + } + } + } + } + + if tparams != nil { + sig.tparams = check.collectTypeParams(tparams) + // Always type-check method type parameters but complain if they are not enabled. + // (A separate check is needed when type-checking interface method signatures because + // they don't have a receiver specification.) + if recvPar != nil && !check.conf.AcceptMethodTypeParams { + check.errorf(ftyp, "methods cannot have type parameters") + } + } + + // Value (non-type) parameters' scope starts in the function body. Use a temporary scope for their + // declarations and then squash that scope into the parent scope (and report any redeclarations at + // that time). + scope := NewScope(check.scope, nopos, nopos, "function body (temp. scope)") + var recvList []*Var // TODO(gri) remove the need for making a list here + if recvPar != nil { + recvList, _ = check.collectParams(scope, []*syntax.Field{recvPar}, recvTyp, false) // use rewritten receiver type, if any + } + params, variadic := check.collectParams(scope, ftyp.ParamList, nil, true) + results, _ := check.collectParams(scope, ftyp.ResultList, nil, false) + scope.Squash(func(obj, alt Object) { + check.errorf(obj, "%s redeclared in this block", obj.Name()) + check.reportAltDecl(alt) + }) + + 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: + // error reported by resolver + recv = NewParam(nopos, nil, "", Typ[Invalid]) // ignore recv below + default: + // more than one receiver + check.error(recvList[len(recvList)-1].Pos(), "method must have exactly one receiver") + fallthrough // continue with first receiver + case 1: + recv = recvList[0] + } + + // TODO(gri) We should delay rtyp expansion to when we actually need the + // receiver; thus all checks here should be delayed to later. + rtyp, _ := deref(recv.typ) + rtyp = expand(rtyp) + + // 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 := rtyp; 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 { + switch u := optype(T.Under()).(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.pos, "invalid receiver %s (%s)", recv.typ, err) + // ok to continue + } + } + sig.recv = recv + } + + sig.params = NewTuple(params...) + sig.results = NewTuple(results...) + sig.variadic = variadic +} + +// goTypeName returns the Go type name for typ and +// removes any occurences of "types." from that name. +func goTypeName(typ Type) string { + return strings.ReplaceAll(fmt.Sprintf("%T", typ), "types.", "") +} + +// typInternal drives type checking of types. +// Must only be called by definedType or genericType. +// +func (check *Checker) typInternal(e0 syntax.Expr, def *Named) (T Type) { + if check.conf.Trace { + check.trace(e0.Pos(), "type %s", e0) + check.indent++ + defer func() { + check.indent-- + var under Type + if T != nil { + // Calling Under() here may lead to endless instantiations. + // Test case: type T[P any] *T[P] + // TODO(gri) investigate if that's a bug or to be expected + // (see also analogous comment in Checker.instantiate). + under = T.Underlying() + } + if T == under { + check.trace(e0.Pos(), "=> %s // %s", T, goTypeName(T)) + } else { + check.trace(e0.Pos(), "=> %s (under = %s) // %s", T, under, goTypeName(T)) + } + }() + } + + switch e := e0.(type) { + case *syntax.BadExpr: + // ignore - error reported before + + case *syntax.Name: + 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, "%s used as type", &x) + default: + check.errorf(&x, "%s is not a type", &x) + } + + case *syntax.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, "%s used as type", &x) + default: + check.errorf(&x, "%s is not a type", &x) + } + + case *syntax.IndexExpr: + return check.instantiatedType(e.X, unpackExpr(e.Index), def) + + case *syntax.ParenExpr: + // Generic types must be instantiated before they can be used in any form. + // Consequently, generic types cannot be parenthesized. + return check.definedType(e.X, def) + + case *syntax.ArrayType: + typ := new(Array) + def.setUnderlying(typ) + if e.Len != nil { + typ.len = check.arrayLength(e.Len) + } else { + // [...]array + check.errorf(e, "invalid use of [...] array (outside a composite literal)") + typ.len = -1 + } + typ.elem = check.varType(e.Elem) + return typ + + case *syntax.SliceType: + typ := new(Slice) + def.setUnderlying(typ) + typ.elem = check.varType(e.Elem) + return typ + + case *syntax.StructType: + typ := new(Struct) + def.setUnderlying(typ) + check.structType(typ, e) + return typ + + case *syntax.Operation: + if e.Op == syntax.Mul && e.Y == nil { + typ := new(Pointer) + def.setUnderlying(typ) + typ.base = check.varType(e.X) + return typ + } + + case *syntax.FuncType: + typ := new(Signature) + def.setUnderlying(typ) + check.funcType(typ, nil, nil, e) + return typ + + case *syntax.InterfaceType: + typ := new(Interface) + def.setUnderlying(typ) + if def != nil { + typ.obj = def.obj + } + check.interfaceType(typ, e, def) + return typ + + case *syntax.MapType: + typ := new(Map) + def.setUnderlying(typ) + + typ.key = check.varType(e.Key) + typ.elem = check.varType(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) { + var why string + if typ.key.TypeParam() != nil { + why = " (missing comparable constraint)" + } + check.errorf(e.Key, "invalid map key type %s%s", typ.key, why) + } + }) + + return typ + + case *syntax.ChanType: + typ := new(Chan) + def.setUnderlying(typ) + + dir := SendRecv + switch e.Dir { + case 0: + // nothing to do + case syntax.SendOnly: + dir = SendOnly + case syntax.RecvOnly: + dir = RecvOnly + default: + check.invalidASTf(e, "unknown channel direction %d", e.Dir) + // ok to continue + } + + typ.dir = dir + typ.elem = check.varType(e.Elem) + return typ + + default: + check.errorf(e0, "%s is not a type", e0) + check.use(e0) + } + + typ := Typ[Invalid] + def.setUnderlying(typ) + return typ +} + +// typeOrNil type-checks the type expression (or nil value) e +// and returns the type of e, or nil. If e is a type, it must +// not be an (uninstantiated) generic type. +// If e is neither a type nor nil, typeOrNil returns Typ[Invalid]. +// TODO(gri) should we also disallow non-var types? +func (check *Checker) typOrNil(e syntax.Expr) Type { + var x operand + check.rawExpr(&x, e, nil) + switch x.mode { + case invalid: + // ignore - error reported before + case novalue: + check.errorf(&x, "%s used as type", &x) + case typexpr: + check.instantiatedOperand(&x) + return x.typ + case value: + if x.isNil() { + return nil + } + fallthrough + default: + check.errorf(&x, "%s is not a type", &x) + } + return Typ[Invalid] +} + +func (check *Checker) instantiatedType(x syntax.Expr, targs []syntax.Expr, def *Named) Type { + b := check.genericType(x, true) // TODO(gri) what about cycles? + if b == Typ[Invalid] { + return b // error already reported + } + base := b.Named() + if base == nil { + unreachable() // should have been caught by genericType + } + + // create a new type Instance rather than instantiate the type + // TODO(gri) should do argument number check here rather than + // when instantiating the type? + typ := new(instance) + def.setUnderlying(typ) + + typ.check = check + typ.pos = x.Pos() + typ.base = base + + // evaluate arguments (always) + typ.targs = check.typeList(targs) + if typ.targs == nil { + def.setUnderlying(Typ[Invalid]) // avoid later errors due to lazy instantiation + return Typ[Invalid] + } + + // determine argument positions (for error reporting) + typ.poslist = make([]syntax.Pos, len(targs)) + for i, arg := range targs { + typ.poslist[i] = arg.Pos() + } + + // make sure we check instantiation works at least once + // and that the resulting type is valid + check.atEnd(func() { + t := typ.expand() + check.validType(t, nil) + }) + + return typ +} + +// 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 syntax.Expr) int64 { + var x operand + check.expr(&x, e) + if x.mode != constant_ { + if x.mode != invalid { + check.errorf(&x, "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, "invalid array length %s", &x) + return -1 + } + } + } + check.errorf(&x, "array length %s must be integer", &x) + return -1 +} + +// typeList provides the list of types corresponding to the incoming expression list. +// If an error occured, the result is nil, but all list elements were type-checked. +func (check *Checker) typeList(list []syntax.Expr) []Type { + res := make([]Type, len(list)) // res != nil even if len(list) == 0 + for i, x := range list { + t := check.varType(x) + if t == Typ[Invalid] { + res = nil + } + if res != nil { + res[i] = t + } + } + return res +} + +// collectParams declares the parameters of list in scope and returns the corresponding +// variable list. If type0 != nil, it is used instead of the first type in list. +func (check *Checker) collectParams(scope *Scope, list []*syntax.Field, type0 syntax.Expr, variadicOk bool) (params []*Var, variadic bool) { + if list == nil { + return + } + + var named, anonymous bool + + var typ Type + var prev syntax.Expr + for i, field := range list { + ftype := field.Type + // type-check type of grouped fields only once + if ftype != prev { + prev = ftype + if i == 0 && type0 != nil { + ftype = type0 + } + if t, _ := ftype.(*syntax.DotsType); t != nil { + ftype = t.Elem + if variadicOk && i == len(list)-1 { + variadic = true + } else { + check.softErrorf(t, "can only use ... with final parameter in list") + // ignore ... and continue + } + } + typ = check.varType(ftype) + } + // The parser ensures that f.Tag is nil and we don't + // care if a constructed AST contains a non-nil tag. + if field.Name != nil { + // named parameter + name := field.Name.Value + if name == "" { + check.invalidASTf(field.Name, "anonymous parameter") + // ok to continue + } + par := NewParam(field.Name.Pos(), check.pkg, name, typ) + check.declare(scope, field.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.invalidASTf(list[0], "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[len(list)-1].Type, typexpr, last.typ, nil) + } + + return +} + +func (check *Checker) declareInSet(oset *objset, pos syntax.Pos, obj Object) bool { + if alt := oset.insert(obj); alt != nil { + check.errorf(pos, "%s redeclared", obj.Name()) + check.reportAltDecl(alt) + return false + } + return true +} + +func (check *Checker) interfaceType(ityp *Interface, iface *syntax.InterfaceType, def *Named) { + var tname *syntax.Name // most recent "type" name + var types []syntax.Expr + for _, f := range iface.MethodList { + if f.Name != nil { + // We have a method with name f.Name, or a type + // of a type list (f.Name.Value == "type"). + name := f.Name.Value + if name == "_" { + check.errorf(f.Name, "invalid method name _") + continue // ignore + } + + if name == "type" { + // Always collect all type list entries, even from + // different type lists, under the assumption that + // the author intended to include all types. + types = append(types, f.Type) + if tname != nil && tname != f.Name { + check.errorf(f.Name, "cannot have multiple type lists in an interface") + } + tname = f.Name + continue + } + + typ := check.typ(f.Type) + sig, _ := typ.(*Signature) + if sig == nil { + if typ != Typ[Invalid] { + check.invalidASTf(f.Type, "%s is not a method signature", typ) + } + continue // ignore + } + + // Always type-check method type parameters but complain if they are not enabled. + // (This extra check is needed here because interface method signatures don't have + // a receiver specification.) + if sig.tparams != nil && !check.conf.AcceptMethodTypeParams { + check.errorf(f.Type, "methods cannot have type parameters") + } + + // use named receiver type if available (for better error messages) + var recvTyp Type = ityp + if def != nil { + recvTyp = def + } + sig.recv = NewVar(f.Name.Pos(), check.pkg, "", recvTyp) + + m := NewFunc(f.Name.Pos(), check.pkg, name, sig) + check.recordDef(f.Name, m) + ityp.methods = append(ityp.methods, m) + } else { + // We have an embedded type. completeInterface will + // eventually verify that we have an interface. + ityp.embeddeds = append(ityp.embeddeds, check.typ(f.Type)) + check.posMap[ityp] = append(check.posMap[ityp], f.Type.Pos()) + } + } + + // type constraints + ityp.types = NewSum(check.collectTypeConstraints(iface.Pos(), types)) + + if len(ityp.methods) == 0 && ityp.types == nil && 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(iface.Pos(), ityp) }) +} + +func (check *Checker) completeInterface(pos syntax.Pos, 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 check.conf.Trace { + // Types don't generally have position information. + // If we don't have a valid pos provided, try to use + // one close enough. + if !pos.IsKnown() && len(ityp.methods) > 0 { + pos = ityp.methods[0].pos + } + + check.trace(pos, "complete %s", ityp) + check.indent++ + defer func() { + check.indent-- + check.trace(pos, "=> %s (methods = %v, types = %v)", ityp, ityp.allMethods, ityp.allTypes) + }() + } + + // 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]syntax.Pos) // method specification or method embedding position, for good error messages + addMethod := func(pos syntax.Pos, m *Func, explicit bool) { + switch other := seen.insert(m); { + case other == nil: + methods = append(methods, m) + mpos[m] = pos + case explicit: + check.errorf(pos, "duplicate method %s", m.name) + check.errorf(mpos[other.(*Func)], "\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(pos, "duplicate method %s", m.name) + check.errorf(mpos[other.(*Func)], "\tother declaration of %s", m.name) // secondary error, \t indented + } + }) + } + } + + for _, m := range ityp.methods { + addMethod(m.pos, m, true) + } + + // collect types + allTypes := ityp.types + + posList := check.posMap[ityp] + for i, typ := range ityp.embeddeds { + pos := posList[i] // embedding position + utyp := typ.Under() + etyp := utyp.Interface() + if etyp == nil { + if utyp != Typ[Invalid] { + var format string + if _, ok := utyp.(*TypeParam); ok { + format = "%s is a type parameter, not an interface" + } else { + format = "%s is not an interface" + } + check.errorf(pos, format, typ) + } + continue + } + check.completeInterface(pos, etyp) + for _, m := range etyp.allMethods { + addMethod(pos, m, false) // use embedding position pos rather than m.pos + } + allTypes = intersect(allTypes, etyp.allTypes) + } + + if methods != nil { + sort.Sort(byUniqueMethodName(methods)) + ityp.allMethods = methods + } + ityp.allTypes = allTypes +} + +// intersect computes the intersection of the types x and y. +// Note: A incomming nil type stands for the top type. A top +// type result is returned as nil. +func intersect(x, y Type) (r Type) { + defer func() { + if r == theTop { + r = nil + } + }() + + switch { + case x == theBottom || y == theBottom: + return theBottom + case x == nil || x == theTop: + return y + case y == nil || x == theTop: + return x + } + + xtypes := unpack(x) + ytypes := unpack(y) + // Compute the list rtypes which includes only + // types that are in both xtypes and ytypes. + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix this + var rtypes []Type + for _, x := range xtypes { + if includes(ytypes, x) { + rtypes = append(rtypes, x) + } + } + + if rtypes == nil { + return theBottom + } + return NewSum(rtypes) +} + +// 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 *syntax.BasicLit) string { + if t != nil { + if t.Kind == syntax.StringLit { + if val, err := strconv.Unquote(t.Value); err == nil { + return val + } + } + check.invalidASTf(t, "incorrect tag syntax: %q", t.Value) + } + return "" +} + +func (check *Checker) structType(styp *Struct, e *syntax.StructType) { + if e.FieldList == 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 *syntax.Name, embedded bool, pos syntax.Pos) { + if tag != "" && tags == nil { + tags = make([]string, len(fields)) + } + if tags != nil { + tags = append(tags, tag) + } + + name := ident.Value + 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 *syntax.Name, pos syntax.Pos) { + typ = Typ[Invalid] + tag = "" + add(ident, true, pos) + } + + var prev syntax.Expr + for i, f := range e.FieldList { + // Fields declared syntactically with the same type (e.g.: a, b, c T) + // share the same type expression. Only check type if it's a new type. + if i == 0 || f.Type != prev { + typ = check.varType(f.Type) + prev = f.Type + } + if i < len(e.TagList) { + tag = check.tag(e.TagList[i]) + } + if f.Name != nil { + // named field + add(f.Name, false, f.Name.Pos()) + } else { + // embedded field + // spec: "An embedded type must be specified as a (possibly parenthesized) type name T or + // as a pointer to a non-interface type name *T, and T itself may not be a pointer type." + pos := startPos(f.Type) + name := embeddedFieldIdent(f.Type) + if name == nil { + check.errorf(pos, "invalid embedded field type %s", f.Type) + name = &syntax.Name{Value: "_"} // TODO(gri) need to set position to pos + addInvalid(name, pos) + continue + } + add(name, true, pos) + // 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. + // We must delay this check to the end because we don't want to instantiate + // (via t.Under()) a possibly incomplete type. + embeddedTyp := typ // for closure below + embeddedPos := pos + check.atEnd(func() { + t, isPtr := deref(embeddedTyp) + switch t := optype(t.Under()).(type) { + case *Basic: + if t == Typ[Invalid] { + // error was reported before + return + } + // unsafe.Pointer is treated like a regular pointer + if t.kind == UnsafePointer { + check.errorf(embeddedPos, "embedded field type cannot be unsafe.Pointer") + } + case *Pointer: + check.errorf(embeddedPos, "embedded field type cannot be a pointer") + case *Interface: + if isPtr { + check.errorf(embeddedPos, "embedded field type cannot be a pointer to an interface") + } + } + }) + } + } + + styp.fields = fields + styp.tags = tags +} + +func embeddedFieldIdent(e syntax.Expr) *syntax.Name { + switch e := e.(type) { + case *syntax.Name: + return e + case *syntax.Operation: + if base := ptrBase(e); base != nil { + // *T is valid, but **T is not + if op, _ := base.(*syntax.Operation); op == nil || ptrBase(op) == nil { + return embeddedFieldIdent(e.X) + } + } + case *syntax.SelectorExpr: + return e.Sel + case *syntax.IndexExpr: + return embeddedFieldIdent(e.X) + case *syntax.ParenExpr: + return embeddedFieldIdent(e.X) + } + return nil // invalid embedded field +} + +func (check *Checker) collectTypeConstraints(pos syntax.Pos, types []syntax.Expr) []Type { + list := make([]Type, 0, len(types)) // assume all types are correct + for _, texpr := range types { + if texpr == nil { + check.invalidASTf(pos, "missing type constraint") + continue + } + typ := check.varType(texpr) + // A type constraint may be a predeclared type or a + // composite type composed of only predeclared types. + // TODO(gri) If we enable this again it also must run + // at the end. + const restricted = false + var why string + if restricted && !check.typeConstraint(typ, &why) { + check.errorf(texpr, "invalid type constraint %s (%s)", typ, why) + continue + } + list = append(list, typ) + } + + // Ensure that each type is only present once in the type list. + // Types may be interfaces, which may not be complete yet. It's + // ok to do this check at the end because it's not a requirement + // for correctness of the code. + check.atEnd(func() { + uniques := make([]Type, 0, len(list)) // assume all types are unique + for i, t := range list { + if t := t.Interface(); t != nil { + check.completeInterface(types[i].Pos(), t) + } + if includes(uniques, t) { + check.softErrorf(types[i], "duplicate type %s in type list", t) + } + uniques = append(uniques, t) + } + }) + + return list +} + +// includes reports whether typ is in list +func includes(list []Type, typ Type) bool { + for _, e := range list { + if Identical(typ, e) { + return true + } + } + return false +} + +// typeConstraint checks that typ may be used in a type list. +// For now this just checks for the absence of defined (*Named) types. +func (check *Checker) typeConstraint(typ Type, why *string) bool { + switch t := typ.(type) { + case *Basic: + // ok + case *Array: + return check.typeConstraint(t.elem, why) + case *Slice: + return check.typeConstraint(t.elem, why) + case *Struct: + for _, f := range t.fields { + if !check.typeConstraint(f.typ, why) { + return false + } + } + case *Pointer: + return check.typeConstraint(t.base, why) + case *Tuple: + if t == nil { + return true + } + for _, v := range t.vars { + if !check.typeConstraint(v.typ, why) { + return false + } + } + case *Signature: + if len(t.tparams) != 0 { + panic("type parameter in function type") + } + return (t.recv == nil || check.typeConstraint(t.recv.typ, why)) && + check.typeConstraint(t.params, why) && + check.typeConstraint(t.results, why) + case *Interface: + t.assertCompleteness() + for _, m := range t.allMethods { + if !check.typeConstraint(m.typ, why) { + return false + } + } + case *Map: + return check.typeConstraint(t.key, why) && check.typeConstraint(t.elem, why) + case *Chan: + return check.typeConstraint(t.elem, why) + case *Named: + *why = check.sprintf("contains defined type %s", t) + return false + case *TypeParam: + // ok, e.g.: func f (type T interface { type T }) () + default: + unreachable() + } + return true +} + +func ptrBase(x *syntax.Operation) syntax.Expr { + if x.Op == syntax.Mul && x.Y == nil { + return x.X + } + return nil +} diff --git a/src/cmd/compile/internal/types2/unify.go b/src/cmd/compile/internal/types2/unify.go new file mode 100644 index 0000000000..7c639366ef --- /dev/null +++ b/src/cmd/compile/internal/types2/unify.go @@ -0,0 +1,454 @@ +// UNREVIEWED +// 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. + +// This file implements type unification. + +package types2 + +import "sort" + +// The unifier maintains two separate sets of type parameters x and y +// which are used to resolve type parameters in the x and y arguments +// provided to the unify call. For unidirectional unification, only +// one of these sets (say x) is provided, and then type parameters are +// only resolved for the x argument passed to unify, not the y argument +// (even if that also contains possibly the same type parameters). This +// is crucial to infer the type parameters of self-recursive calls: +// +// func f[type P](a P) { f(a) } +// +// For the call f(a) we want to infer that the type argument for P is P. +// During unification, the parameter type P must be resolved to the type +// parameter P ("x" side), but the argument type P must be left alone so +// that unification resolves the type parameter P to P. +// +// For bidirection unification, both sets are provided. This enables +// unification to go from argument to parameter type and vice versa. +// For constraint type inference, we use bidirectional unification +// where both the x and y type parameters are identical. This is done +// by setting up one of them (using init) and then assigning its value +// to the other. + +// A unifier maintains the current type parameters for x and y +// and the respective types inferred for each type parameter. +// A unifier is created by calling newUnifier. +type unifier struct { + check *Checker + exact bool + x, y tparamsList // x and y must initialized via tparamsList.init + types []Type // inferred types, shared by x and y +} + +// newUnifier returns a new unifier. +// If exact is set, unification requires unified types to match +// exactly. If exact is not set, a named type's underlying type +// is considered if unification would fail otherwise, and the +// direction of channels is ignored. +func newUnifier(check *Checker, exact bool) *unifier { + u := &unifier{check: check, exact: exact} + u.x.unifier = u + u.y.unifier = u + return u +} + +// unify attempts to unify x and y and reports whether it succeeded. +func (u *unifier) unify(x, y Type) bool { + return u.nify(x, y, nil) +} + +// A tparamsList describes a list of type parameters and the types inferred for them. +type tparamsList struct { + unifier *unifier + tparams []*TypeName + // For each tparams element, there is a corresponding type slot index in indices. + // index < 0: unifier.types[-index] == nil + // index == 0: no type slot allocated yet + // index > 0: unifier.types[index] == typ + // Joined tparams elements share the same type slot and thus have the same index. + // By using a negative index for nil types we don't need to check unifier.types + // to see if we have a type or not. + indices []int // len(d.indices) == len(d.tparams) +} + +// init initializes d with the given type parameters. +// The type parameters must be in the order in which they appear in their declaration +// (this ensures that the tparams indices match the respective type parameter index). +func (d *tparamsList) init(tparams []*TypeName) { + if len(tparams) == 0 { + return + } + if debug { + for i, tpar := range tparams { + assert(i == tpar.typ.(*TypeParam).index) + } + } + d.tparams = tparams + d.indices = make([]int, len(tparams)) +} + +// join unifies the i'th type parameter of x with the j'th type parameter of y. +// If both type parameters already have a type associated with them and they are +// not joined, join fails and return false. +func (u *unifier) join(i, j int) bool { + ti := u.x.indices[i] + tj := u.y.indices[j] + switch { + case ti == 0 && tj == 0: + // Neither type parameter has a type slot associated with them. + // Allocate a new joined nil type slot (negative index). + u.types = append(u.types, nil) + u.x.indices[i] = -len(u.types) + u.y.indices[j] = -len(u.types) + case ti == 0: + // The type parameter for x has no type slot yet. Use slot of y. + u.x.indices[i] = tj + case tj == 0: + // The type parameter for y has no type slot yet. Use slot of x. + u.y.indices[j] = ti + + // Both type parameters have a slot: ti != 0 && tj != 0. + case ti == tj: + // Both type parameters already share the same slot. Nothing to do. + break + case ti > 0 && tj > 0: + // Both type parameters have (possibly different) inferred types. Cannot join. + return false + case ti > 0: + // Only the type parameter for x has an inferred type. Use x slot for y. + u.y.setIndex(j, ti) + // This case is handled like the default case. + // case tj > 0: + // // Only the type parameter for y has an inferred type. Use y slot for x. + // u.x.setIndex(i, tj) + default: + // Neither type parameter has an inferred type. Use y slot for x + // (or x slot for y, it doesn't matter). + u.x.setIndex(i, tj) + } + return true +} + +// If typ is a type parameter of d, index returns the type parameter index. +// Otherwise, the result is < 0. +func (d *tparamsList) index(typ Type) int { + if t, ok := typ.(*TypeParam); ok { + if i := t.index; i < len(d.tparams) && d.tparams[i].typ == t { + return i + } + } + return -1 +} + +// setIndex sets the type slot index for the i'th type parameter +// (and all its joined parameters) to tj. The type parameter +// must have a (possibly nil) type slot associated with it. +func (d *tparamsList) setIndex(i, tj int) { + ti := d.indices[i] + assert(ti != 0 && tj != 0) + for k, tk := range d.indices { + if tk == ti { + d.indices[k] = tj + } + } +} + +// at returns the type set for the i'th type parameter; or nil. +func (d *tparamsList) at(i int) Type { + if ti := d.indices[i]; ti > 0 { + return d.unifier.types[ti-1] + } + return nil +} + +// set sets the type typ for the i'th type parameter; +// typ must not be nil and it must not have been set before. +func (d *tparamsList) set(i int, typ Type) { + assert(typ != nil) + u := d.unifier + switch ti := d.indices[i]; { + case ti < 0: + u.types[-ti-1] = typ + d.setIndex(i, -ti) + case ti == 0: + u.types = append(u.types, typ) + d.indices[i] = len(u.types) + default: + panic("type already set") + } +} + +// types returns the list of inferred types (via unification) for the type parameters +// described by d, and an index. If all types were inferred, the returned index is < 0. +// Otherwise, it is the index of the first type parameter which couldn't be inferred; +// i.e., for which list[index] is nil. +func (d *tparamsList) types() (list []Type, index int) { + list = make([]Type, len(d.tparams)) + index = -1 + for i := range d.tparams { + t := d.at(i) + list[i] = t + if index < 0 && t == nil { + index = i + } + } + return +} + +func (u *unifier) nifyEq(x, y Type, p *ifacePair) bool { + return x == y || u.nify(x, y, p) +} + +// nify implements the core unification algorithm which is an +// adapted version of Checker.identical0. For changes to that +// code the corresponding changes should be made here. +// Must not be called directly from outside the unifier. +func (u *unifier) nify(x, y Type, p *ifacePair) bool { + // types must be expanded for comparison + x = expand(x) + y = expand(y) + + if !u.exact { + // If exact unification is known to fail because we attempt to + // match a type name against an unnamed type literal, consider + // the underlying type of the named type. + // (Subtle: We use isNamed to include any type with a name (incl. + // basic types and type parameters. We use Named() because we only + // want *Named types.) + switch { + case !isNamed(x) && y != nil && y.Named() != nil: + return u.nify(x, y.Under(), p) + case x != nil && x.Named() != nil && !isNamed(y): + return u.nify(x.Under(), y, p) + } + } + + // Cases where at least one of x or y is a type parameter. + switch i, j := u.x.index(x), u.y.index(y); { + case i >= 0 && j >= 0: + // both x and y are type parameters + if u.join(i, j) { + return true + } + // both x and y have an inferred type - they must match + return u.nifyEq(u.x.at(i), u.y.at(j), p) + + case i >= 0: + // x is a type parameter, y is not + if tx := u.x.at(i); tx != nil { + return u.nifyEq(tx, y, p) + } + // otherwise, infer type from y + u.x.set(i, y) + return true + + case j >= 0: + // y is a type parameter, x is not + if ty := u.y.at(j); ty != nil { + return u.nifyEq(x, ty, p) + } + // otherwise, infer type from x + u.y.set(j, x) + return true + } + + // For type unification, do not shortcut (x == y) for identical + // types. Instead keep comparing them element-wise to unify the + // matching (and equal type parameter types). A simple test case + // where this matters is: func f[P any](a P) { f(a) } . + + 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) && u.nify(x.elem, y.elem, p) + } + + case *Slice: + // Two slice types are identical if they have identical element types. + if y, ok := y.(*Slice); ok { + return u.nify(x.elem, y.elem, 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 || + x.Tag(i) != y.Tag(i) || + !f.sameId(g.pkg, g.name) || + !u.nify(f.typ, g.typ, 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 u.nify(x.base, y.base, 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 !u.nify(v.typ, w.typ, 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. + // TODO(gri) handle type parameters or document why we can ignore them. + if y, ok := y.(*Signature); ok { + return x.variadic == y.variadic && + u.nify(x.params, y.params, p) && + u.nify(x.results, y.results, p) + } + + case *Sum: + // This should not happen with the current internal use of sum types. + panic("type inference across sum types not implemented") + + 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 u.check != nil { + u.check.completeInterface(nopos, x) + u.check.completeInterface(nopos, 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() || !u.nify(f.typ, g.typ, 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 u.nify(x.key, y.key, p) && u.nify(x.elem, y.elem, p) + } + + case *Chan: + // Two channel types are identical if they have identical value types. + if y, ok := y.(*Chan); ok { + return (!u.exact || x.dir == y.dir) && u.nify(x.elem, y.elem, 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 + // } + if y, ok := y.(*Named); ok { + // TODO(gri) This is not always correct: two types may have the same names + // in the same package if one of them is nested in a function. + // Extremely unlikely but we need an always correct solution. + if x.obj.pkg == y.obj.pkg && x.obj.name == y.obj.name { + assert(len(x.targs) == len(y.targs)) + for i, x := range x.targs { + if !u.nify(x, y.targs[i], p) { + return false + } + } + return true + } + } + + case *TypeParam: + // Two type parameters (which are not part of the type parameters of the + // enclosing type as those are handled in the beginning of this function) + // are identical if they originate in the same declaration. + return x == y + + // case *instance: + // unreachable since types are expanded + + case nil: + // avoid a crash in case of nil type + + default: + u.check.dump("### u.nify(%s, %s), u.x.tparams = %s", x, y, u.x.tparams) + unreachable() + } + + return false +} diff --git a/src/cmd/compile/internal/types2/universe.go b/src/cmd/compile/internal/types2/universe.go new file mode 100644 index 0000000000..c1961d7455 --- /dev/null +++ b/src/cmd/compile/internal/types2/universe.go @@ -0,0 +1,282 @@ +// UNREVIEWED +// 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 types2 + +import ( + "go/constant" + "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" + universeAny *Named + 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", aType{}}, + + Bool: {Bool, IsBoolean, "bool", aType{}}, + Int: {Int, IsInteger, "int", aType{}}, + Int8: {Int8, IsInteger, "int8", aType{}}, + Int16: {Int16, IsInteger, "int16", aType{}}, + Int32: {Int32, IsInteger, "int32", aType{}}, + Int64: {Int64, IsInteger, "int64", aType{}}, + Uint: {Uint, IsInteger | IsUnsigned, "uint", aType{}}, + Uint8: {Uint8, IsInteger | IsUnsigned, "uint8", aType{}}, + Uint16: {Uint16, IsInteger | IsUnsigned, "uint16", aType{}}, + Uint32: {Uint32, IsInteger | IsUnsigned, "uint32", aType{}}, + Uint64: {Uint64, IsInteger | IsUnsigned, "uint64", aType{}}, + Uintptr: {Uintptr, IsInteger | IsUnsigned, "uintptr", aType{}}, + Float32: {Float32, IsFloat, "float32", aType{}}, + Float64: {Float64, IsFloat, "float64", aType{}}, + Complex64: {Complex64, IsComplex, "complex64", aType{}}, + Complex128: {Complex128, IsComplex, "complex128", aType{}}, + String: {String, IsString, "string", aType{}}, + UnsafePointer: {UnsafePointer, 0, "Pointer", aType{}}, + + UntypedBool: {UntypedBool, IsBoolean | IsUntyped, "untyped bool", aType{}}, + UntypedInt: {UntypedInt, IsInteger | IsUntyped, "untyped int", aType{}}, + UntypedRune: {UntypedRune, IsInteger | IsUntyped, "untyped rune", aType{}}, + UntypedFloat: {UntypedFloat, IsFloat | IsUntyped, "untyped float", aType{}}, + UntypedComplex: {UntypedComplex, IsComplex | IsUntyped, "untyped complex", aType{}}, + UntypedString: {UntypedString, IsString | IsUntyped, "untyped string", aType{}}, + UntypedNil: {UntypedNil, IsUntyped, "untyped nil", aType{}}, +} + +var aliases = [...]*Basic{ + {Byte, IsInteger | IsUnsigned, "byte", aType{}}, + {Rune, IsInteger, "rune", aType{}}, +} + +func defPredeclaredTypes() { + for _, t := range Typ { + def(NewTypeName(nopos, nil, t.name, t)) + } + for _, t := range aliases { + def(NewTypeName(nopos, nil, t.name, t)) + } + + // any + // (Predeclared and entered into universe scope so we do all the + // usual checks; but removed again from scope later since it's + // only visible as constraint in a type parameter list.) + { + typ := &Named{underlying: &emptyInterface} + def(NewTypeName(nopos, nil, "any", typ)) + } + + // Error has a nil package in its qualified name since it is in no package + { + res := NewVar(nopos, nil, "", Typ[String]) + sig := &Signature{results: NewTuple(res)} + err := NewFunc(nopos, nil, "Error", sig) + typ := &Named{underlying: NewInterfaceType([]*Func{err}, nil).Complete()} + sig.recv = NewVar(nopos, nil, "", typ) + def(NewTypeName(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(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 defPredeclaredComparable() { + // The "comparable" interface can be imagined as defined like + // + // type comparable interface { + // == () untyped bool + // != () untyped bool + // } + // + // == and != cannot be user-declared but we can declare + // a magic method == and check for its presence when needed. + + // Define interface { == () }. We don't care about the signature + // for == so leave it empty except for the receiver, which is + // set up later to match the usual interface method assumptions. + sig := new(Signature) + eql := NewFunc(nopos, nil, "==", sig) + iface := NewInterfaceType([]*Func{eql}, nil).Complete() + + // set up the defined type for the interface + obj := NewTypeName(nopos, nil, "comparable", nil) + named := NewNamed(obj, iface, nil) + obj.color_ = black + sig.recv = NewVar(nopos, nil, "", named) // complete == signature + + def(obj) +} + +func init() { + Universe = NewScope(nil, nopos, nopos, "universe") + Unsafe = NewPackage("unsafe", "unsafe") + Unsafe.complete = true + + defPredeclaredTypes() + defPredeclaredConsts() + defPredeclaredNil() + defPredeclaredFuncs() + defPredeclaredComparable() + + universeIota = Universe.Lookup("iota").(*Const) + universeByte = Universe.Lookup("byte").(*TypeName).typ.(*Basic) + universeRune = Universe.Lookup("rune").(*TypeName).typ.(*Basic) + universeAny = Universe.Lookup("any").(*TypeName).typ.(*Named) + universeError = Universe.Lookup("error").(*TypeName).typ.(*Named) + + // "any" is only visible as constraint in a type parameter list + delete(Universe.elems, "any") +} + +// 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 := obj.Type().Named(); typ != nil { + 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") + } +} diff --git a/src/cmd/compile/internal/types2/walk.go b/src/cmd/compile/internal/types2/walk.go new file mode 100644 index 0000000000..18cfb28ade --- /dev/null +++ b/src/cmd/compile/internal/types2/walk.go @@ -0,0 +1,322 @@ +// UNREVIEWED +// 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 syntax tree walking. +// TODO(gri) A more general API should probably be in +// the syntax package. + +package types2 + +import ( + "cmd/compile/internal/syntax" + "fmt" +) + +// Walk traverses a syntax in pre-order: It starts by calling f(root); +// root must not be nil. If f returns false (== "continue"), Walk calls +// f recursively for each of the non-nil children of that node; if f +// returns true (== "stop"), Walk does not traverse the respective node's +// children. +// Some nodes may be shared among multiple parent nodes (e.g., types in +// field lists such as type T in "a, b, c T"). Such shared nodes are +// walked multiple times. +// TODO(gri) Revisit this design. It may make sense to walk those nodes +// only once. A place where this matters is TestResolveIdents. +func Walk(root syntax.Node, f func(syntax.Node) bool) { + w := walker{f} + w.node(root) +} + +type walker struct { + f func(syntax.Node) bool +} + +func (w *walker) node(n syntax.Node) { + if n == nil { + panic("invalid syntax tree: nil node") + } + + if w.f(n) { + return + } + + switch n := n.(type) { + // packages + case *syntax.File: + w.node(n.PkgName) + w.declList(n.DeclList) + + // declarations + case *syntax.ImportDecl: + if n.LocalPkgName != nil { + w.node(n.LocalPkgName) + } + w.node(n.Path) + + case *syntax.ConstDecl: + w.nameList(n.NameList) + if n.Type != nil { + w.node(n.Type) + } + if n.Values != nil { + w.node(n.Values) + } + + case *syntax.TypeDecl: + w.node(n.Name) + w.fieldList(n.TParamList) + w.node(n.Type) + + case *syntax.VarDecl: + w.nameList(n.NameList) + if n.Type != nil { + w.node(n.Type) + } + if n.Values != nil { + w.node(n.Values) + } + + case *syntax.FuncDecl: + if n.Recv != nil { + w.node(n.Recv) + } + w.node(n.Name) + w.fieldList(n.TParamList) + w.node(n.Type) + if n.Body != nil { + w.node(n.Body) + } + + // expressions + case *syntax.BadExpr: // nothing to do + case *syntax.Name: + case *syntax.BasicLit: // nothing to do + + case *syntax.CompositeLit: + if n.Type != nil { + w.node(n.Type) + } + w.exprList(n.ElemList) + + case *syntax.KeyValueExpr: + w.node(n.Key) + w.node(n.Value) + + case *syntax.FuncLit: + w.node(n.Type) + w.node(n.Body) + + case *syntax.ParenExpr: + w.node(n.X) + + case *syntax.SelectorExpr: + w.node(n.X) + w.node(n.Sel) + + case *syntax.IndexExpr: + w.node(n.X) + w.node(n.Index) + + case *syntax.SliceExpr: + w.node(n.X) + for _, x := range n.Index { + if x != nil { + w.node(x) + } + } + + case *syntax.AssertExpr: + w.node(n.X) + w.node(n.Type) + + case *syntax.TypeSwitchGuard: + if n.Lhs != nil { + w.node(n.Lhs) + } + w.node(n.X) + + case *syntax.Operation: + w.node(n.X) + if n.Y != nil { + w.node(n.Y) + } + + case *syntax.CallExpr: + w.node(n.Fun) + w.exprList(n.ArgList) + + case *syntax.ListExpr: + w.exprList(n.ElemList) + + // types + case *syntax.ArrayType: + if n.Len != nil { + w.node(n.Len) + } + w.node(n.Elem) + + case *syntax.SliceType: + w.node(n.Elem) + + case *syntax.DotsType: + w.node(n.Elem) + + case *syntax.StructType: + w.fieldList(n.FieldList) + for _, t := range n.TagList { + if t != nil { + w.node(t) + } + } + + case *syntax.Field: + if n.Name != nil { + w.node(n.Name) + } + w.node(n.Type) + + case *syntax.InterfaceType: + w.fieldList(n.MethodList) + + case *syntax.FuncType: + w.fieldList(n.ParamList) + w.fieldList(n.ResultList) + + case *syntax.MapType: + w.node(n.Key) + w.node(n.Value) + + case *syntax.ChanType: + w.node(n.Elem) + + // statements + case *syntax.EmptyStmt: // nothing to do + + case *syntax.LabeledStmt: + w.node(n.Label) + w.node(n.Stmt) + + case *syntax.BlockStmt: + w.stmtList(n.List) + + case *syntax.ExprStmt: + w.node(n.X) + + case *syntax.SendStmt: + w.node(n.Chan) + w.node(n.Value) + + case *syntax.DeclStmt: + w.declList(n.DeclList) + + case *syntax.AssignStmt: + w.node(n.Lhs) + w.node(n.Rhs) + + case *syntax.BranchStmt: + if n.Label != nil { + w.node(n.Label) + } + // Target points to nodes elsewhere in the syntax tree + + case *syntax.CallStmt: + w.node(n.Call) + + case *syntax.ReturnStmt: + if n.Results != nil { + w.node(n.Results) + } + + case *syntax.IfStmt: + if n.Init != nil { + w.node(n.Init) + } + w.node(n.Cond) + w.node(n.Then) + if n.Else != nil { + w.node(n.Else) + } + + case *syntax.ForStmt: + if n.Init != nil { + w.node(n.Init) + } + if n.Cond != nil { + w.node(n.Cond) + } + if n.Post != nil { + w.node(n.Post) + } + w.node(n.Body) + + case *syntax.SwitchStmt: + if n.Init != nil { + w.node(n.Init) + } + if n.Tag != nil { + w.node(n.Tag) + } + for _, s := range n.Body { + w.node(s) + } + + case *syntax.SelectStmt: + for _, s := range n.Body { + w.node(s) + } + + // helper nodes + case *syntax.RangeClause: + if n.Lhs != nil { + w.node(n.Lhs) + } + w.node(n.X) + + case *syntax.CaseClause: + if n.Cases != nil { + w.node(n.Cases) + } + w.stmtList(n.Body) + + case *syntax.CommClause: + if n.Comm != nil { + w.node(n.Comm) + } + w.stmtList(n.Body) + + default: + panic(fmt.Sprintf("internal error: unknown node type %T", n)) + } +} + +func (w *walker) declList(list []syntax.Decl) { + for _, n := range list { + w.node(n) + } +} + +func (w *walker) exprList(list []syntax.Expr) { + for _, n := range list { + w.node(n) + } +} + +func (w *walker) stmtList(list []syntax.Stmt) { + for _, n := range list { + w.node(n) + } +} + +func (w *walker) nameList(list []*syntax.Name) { + for _, n := range list { + w.node(n) + } +} + +func (w *walker) fieldList(list []*syntax.Field) { + for _, n := range list { + w.node(n) + } +} |