From ef7ef876d5b1d85c9b2b49ec0d1e4c9294bb2f90 Mon Sep 17 00:00:00 2001 From: Justin Israel Date: Sun, 23 Nov 2014 11:01:15 +1300 Subject: [PATCH 1/9] Fixes #114 - Integrate GoRename command --- GoSublime.sublime-commands | 4 +++ gscommands.py | 59 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/GoSublime.sublime-commands b/GoSublime.sublime-commands index 02a631f5..fa6f95f2 100644 --- a/GoSublime.sublime-commands +++ b/GoSublime.sublime-commands @@ -65,6 +65,10 @@ "caption": "GoSublime: Fmt the current file (without saving it)", "command": "gs_fmt" }, + { + "caption": "GoSublime: Rename the current selection (gorename)", + "command": "gs_gorename" + }, { "caption": "GoSublime: New Go File", "command": "gs_new_go_file" diff --git a/gscommands.py b/gscommands.py index 0686d68d..5479c053 100644 --- a/gscommands.py +++ b/gscommands.py @@ -2,6 +2,7 @@ from gosubl import gspatch from gosubl import mg9 import datetime +import subprocess import os import sublime import sublime_plugin @@ -201,3 +202,61 @@ def run(self, edit, pos, content, added_path=''): gs.set_attr(k, added_path) else: gs.del_attr(k) + +class GsGorenameCommand(sublime_plugin.TextCommand): + def is_enabled(self): + fn = self.view.file_name() + if fn: + scope_ok = fn.lower().endswith('.go') + else: + scope_ok = gs.is_go_source_view(self.view) + + return scope_ok and gs.setting('fmt_enabled') is True + + def run(self, edit): + view = self.view + + # if view.is_dirty(): + # sublime.error_message("{0}: GoRename failure: Unsaved file".format(DOMAIN)) + # return + + region = view.sel()[0] + + # If the current selection is empty, try to expand + # it to the word under the cursor + if region.empty(): + region = view.word(region) + + if region.empty(): + sublime.message_dialog('Select an identifier you would like to rename and try again.') + return + + current_selection = view.substr(region) + filename = view.file_name() + + def on_done(new_name): + if new_name == current_selection: + return + + gs.println(DOMAIN, 'Requested New Name: {0}'.format(new_name)) + + offset = '{0}:#{1}'.format(filename, region.begin()) + command = ['gorename', '-offset', offset, '-to', new_name] + + gs.println(DOMAIN, 'CMD: {0}'.format(' '.join(command))) + + out = "" + try: + p = gs.popen(command, stderr=subprocess.STDOUT) + out = p.communicate()[0] + if p.returncode != 0: + raise OSError("GoRename failed") + + except Exception as e: + msg = gs.tbck.format_exc() + if out: + msg = '{0}\n{1}'.format(msg, gs.ustr(out)) + gs.show_output('GsGorename', msg, replace=False, merge_domain=False) + + view.window().show_input_panel("New name:", current_selection, on_done, None, None) + From bc8c0a321e772fb47459b3ec5d0445b720c723e5 Mon Sep 17 00:00:00 2001 From: Justin Israel Date: Sun, 23 Nov 2014 15:06:06 +1300 Subject: [PATCH 2/9] Removed extraneous "fmt_enabled" check from GsGoRename --- gscommands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gscommands.py b/gscommands.py index 5479c053..cf55989e 100644 --- a/gscommands.py +++ b/gscommands.py @@ -211,7 +211,7 @@ def is_enabled(self): else: scope_ok = gs.is_go_source_view(self.view) - return scope_ok and gs.setting('fmt_enabled') is True + return scope_ok def run(self, edit): view = self.view From 561958b874b28e7dc4c0073f36c9e7884e687ac7 Mon Sep 17 00:00:00 2001 From: Douglas Clark Date: Sat, 29 Nov 2014 22:02:12 -0600 Subject: [PATCH 3/9] Switch Margo to GoType and added Find Usages --- gosubl/mg9.py | 8 +- gsdoc.py | 35 +- src/gosubli.me/margo/m_doc.go | 261 +------- src/gosubli.me/margo/m_doc_test.go | 118 ++++ src/gosubli.me/margo/m_doc_types.go | 861 +++++++++++++++++++++++++ src/gosubli.me/margo/testing/simple.go | 13 + 6 files changed, 1040 insertions(+), 256 deletions(-) create mode 100644 src/gosubli.me/margo/m_doc_test.go create mode 100644 src/gosubli.me/margo/m_doc_types.go create mode 100644 src/gosubli.me/margo/testing/simple.go diff --git a/gosubl/mg9.py b/gosubl/mg9.py index fb751006..e6d97f24 100644 --- a/gosubl/mg9.py +++ b/gosubl/mg9.py @@ -390,13 +390,17 @@ def imports(fn, src, toggle): 'tabWidth': gs.setting('fmt_tab_width'), }) -def doc(fn, src, offset, f): +def doc(fn, src, offset, f, mode='doc'): tid = gs.begin(DOMAIN, 'Fetching doc info') def cb(res, err): gs.end(tid) f(res, err) - acall('doc', { + #default to doc + if mode not in ['usage', 'doc']: + mode = 'doc' + + acall(mode, { 'fn': fn or '', 'src': src or '', 'offset': offset or 0, diff --git a/gsdoc.py b/gsdoc.py index eb6c3059..ec33d2cf 100644 --- a/gsdoc.py +++ b/gsdoc.py @@ -25,7 +25,7 @@ def show_output(self, s): def run(self, _, mode=''): view = self.view - if (not gs.is_go_source_view(view)) or (mode not in ['goto', 'hint']): + if (not gs.is_go_source_view(view)) or (mode not in ['goto', 'hint', 'usage']): return pt = gs.sel(view).begin() @@ -36,11 +36,12 @@ def f(docs, err): if err: self.show_output('// Error: %s' % err) elif docs: - if mode == "goto": + if mode == "goto" or mode == "usage": fn = '' flags = 0 - if len(docs) > 0: - d = docs[0] + + #method to open doc + def open(d): fn = d.get('fn', '') row = d.get('row', 0) col = d.get('col', 0) @@ -48,7 +49,29 @@ def f(docs, err): gs.println('opening %s:%s:%s' % (fn, row, col)) gs.focus(fn, row, col) return - self.show_output("%s: cannot find definition" % DOMAIN) + self.show_output("%s: cannot find definition" % DOMAIN) + + if len(docs) > 1: + def callback(idx): + open(docs[idx]) + + def highlight(idx): + d = docs[idx] + fn = d.get('fn', '') + row = d.get('row', 0) + 1 + col = d.get('col', 0) + 1 + sublime.active_window().open_file('%s:%s:%s' % (fn, row or 0, col or 0), sublime.TRANSIENT | sublime.ENCODED_POSITION) + + #list of usages + lines = [] + for d in docs: + lines.append(d.get('fn', '') + ':' + str(d.get('row', 0) + 1) + ':' + str(d.get('col', 0) + 1)) + + sublime.active_window().show_quick_panel(lines, callback, on_highlight=highlight) + + elif len(docs) == 1: + open(docs[0]) + elif mode == "hint": s = [] for d in docs: @@ -67,7 +90,7 @@ def f(docs, err): doc = '\n\n\n'.join(s).strip() self.show_output(doc or "// %s: no docs found" % DOMAIN) - mg9.doc(view.file_name(), src, pt, f) + mg9.doc(view.file_name(), src, pt, f, mode) class GsBrowseDeclarationsCommand(sublime_plugin.WindowCommand): def run(self, dir=''): diff --git a/src/gosubli.me/margo/m_doc.go b/src/gosubli.me/margo/m_doc.go index 2647fdfc..b9e0c002 100644 --- a/src/gosubli.me/margo/m_doc.go +++ b/src/gosubli.me/margo/m_doc.go @@ -1,15 +1,6 @@ package main -import ( - "go/ast" - "go/parser" - "go/token" - "path" - "path/filepath" - "runtime" - "sort" - "strings" -) +import "path" type Doc struct { Src string `json:"src"` @@ -23,260 +14,34 @@ type Doc struct { type mDoc struct { Fn string - Src string + Src interface{} Env map[string]string Offset int TabIndent bool TabWidth int + FindDef bool + FindUse bool + FindInfo bool } func (m *mDoc) Call() (interface{}, string) { - res := []*Doc{} - - fset, af, err := parseAstFile(m.Fn, m.Src, parser.ParseComments) - if err != nil { - return res, err.Error() - } - - sel, id := identAt(fset, af, m.Offset) - if id == nil { - return res, "" - } - - pkgs, _ := parser.ParseDir(fset, filepath.Dir(m.Fn), fiHasGoExt, parser.ParseComments) - if pkgs == nil { - pkgs = map[string]*ast.Package{} - } - - pkgName := af.Name.Name - files := map[string]*ast.File{} - pkg, _ := pkgs[pkgName] - if pkg != nil { - files = pkg.Files - } - files[m.Fn] = af - pkg, _ = ast.NewPackage(fset, files, nil, nil) - if pkg == nil { - return res, "" - } - pkgs[pkg.Name] = pkg - - obj, pkg, objPkgs := findUnderlyingObj(fset, af, pkg, pkgs, rootDirs(m.Env), sel, id) - if obj != nil { - res = append(res, objDoc(fset, pkg, m.TabIndent, m.TabWidth, obj)) - if objPkgs != nil { - xName := "Example" + obj.Name - xPrefix := xName + "_" - for _, objPkg := range objPkgs { - xPkg, _ := ast.NewPackage(fset, objPkg.Files, nil, nil) - if xPkg == nil || xPkg.Scope == nil { - continue - } - - for _, xObj := range xPkg.Scope.Objects { - if xObj.Name == xName || strings.HasPrefix(xObj.Name, xPrefix) { - res = append(res, objDoc(fset, xPkg, m.TabIndent, m.TabWidth, xObj)) - } - } - } - } - } + // get the path from our current filename + res := m.findCode([]string{path.Dir(m.Fn)}) return res, "" } func init() { registry.Register("doc", func(_ *Broker) Caller { return &mDoc{ - Env: map[string]string{}, + Env: map[string]string{}, + FindDef: true, } }) -} - -func objDoc(fset *token.FileSet, pkg *ast.Package, tabIndent bool, tabWidth int, obj *ast.Object) *Doc { - decl := obj.Decl - kind := obj.Kind.String() - tp := fset.Position(obj.Pos()) - objSrc := "" - pkgName := "" - if pkg != nil && pkg.Name != "builtin" { - pkgName = pkg.Name - } - - if obj.Kind == ast.Pkg { - pkgName = "" - doc := "" - // special-case `package name` is generated as a TypeSpec - if v, ok := obj.Decl.(*ast.TypeSpec); ok && v.Doc != nil { - doc = "/*\n" + v.Doc.Text() + "\n*/\n" - } - objSrc = doc + "package " + obj.Name - } else if af, ok := pkg.Files[tp.Filename]; ok { - switch decl.(type) { - case *ast.TypeSpec, *ast.ValueSpec, *ast.Field: - line := tp.Line - 1 - for _, cg := range af.Comments { - cgp := fset.Position(cg.End()) - if cgp.Filename == tp.Filename && cgp.Line == line { - switch v := decl.(type) { - case *ast.TypeSpec: - v.Doc = cg - case *ast.ValueSpec: - v.Doc = cg - case *ast.Field: - pkgName = "" - kind = "field" - } - break - } - } - } - } - - if objSrc == "" { - objSrc, _ = printSrc(fset, decl, tabIndent, tabWidth) - } - - return &Doc{ - Src: objSrc, - Pkg: pkgName, - Name: obj.Name, - Kind: kind, - Fn: tp.Filename, - Row: tp.Line - 1, - Col: tp.Column - 1, - } -} -func isBetween(n, start, end int) bool { - return (n >= start && n <= end) -} - -func identAt(fset *token.FileSet, af *ast.File, offset int) (sel *ast.SelectorExpr, id *ast.Ident) { - ast.Inspect(af, func(n ast.Node) bool { - if n != nil { - start := fset.Position(n.Pos()) - end := fset.Position(n.End()) - if isBetween(offset, start.Offset, end.Offset) { - switch v := n.(type) { - case *ast.SelectorExpr: - sel = v - case *ast.Ident: - id = v - } - } + registry.Register("usage", func(_ *Broker) Caller { + return &mDoc{ + Env: map[string]string{}, + FindUse: true, } - return true }) - return -} - -func findUnderlyingObj(fset *token.FileSet, af *ast.File, pkg *ast.Package, pkgs map[string]*ast.Package, srcRootDirs []string, sel *ast.SelectorExpr, id *ast.Ident) (*ast.Object, *ast.Package, map[string]*ast.Package) { - if id != nil && id.Obj != nil { - return id.Obj, pkg, pkgs - } - - if id == nil { - // can this ever happen? - return nil, pkg, pkgs - } - - if sel == nil { - if obj := pkg.Scope.Lookup(id.Name); obj != nil { - return obj, pkg, pkgs - } - fn := filepath.Join(runtime.GOROOT(), "src", "pkg", "builtin") - if pkgBuiltin, _, err := parsePkg(fset, fn, parser.ParseComments); err == nil { - if obj := pkgBuiltin.Scope.Lookup(id.Name); obj != nil { - return obj, pkgBuiltin, pkgs - } - } - } - - if sel == nil { - return nil, pkg, pkgs - } - - switch x := sel.X.(type) { - case *ast.Ident: - if x.Obj != nil { - // todo: resolve type - } else { - if v := pkg.Scope.Lookup(id.Name); v != nil { - // todo: found a type? - } else { - // it's most likely a package - // todo: handle .dot imports - for _, ispec := range af.Imports { - importPath := unquote(ispec.Path.Value) - pkgAlias := "" - if ispec.Name == nil { - _, pkgAlias = path.Split(importPath) - } else { - pkgAlias = ispec.Name.Name - } - if pkgAlias == x.Name { - if id == x { - // where do we go as the first place of a package? - pkg, pkgs, _ = findPkg(fset, importPath, srcRootDirs, parser.ParseComments|parser.PackageClauseOnly) - if pkg != nil { - // we'll just match the behaviour of package browsing - // we will visit some file within the package - // but which file, or where is undefined - var f *ast.File - ok := false - if len(pkg.Files) > 0 { - basedir := "" - for fn, _ := range pkg.Files { - basedir = filepath.Dir(fn) - break - } - baseFn := func(fn string) string { - return filepath.Join(basedir, fn) - } - if f, ok = pkg.Files[baseFn("doc.go")]; !ok { - if f, ok = pkg.Files[baseFn("main.go")]; !ok { - if f, ok = pkg.Files[baseFn(pkgAlias+".go")]; !ok { - // try to keep things consistent - filenames := sort.StringSlice{} - for filename, _ := range pkg.Files { - filenames = append(filenames, filename) - } - sort.Sort(filenames) - f = pkg.Files[filenames[0]] - } - } - } - } - - if f != nil && f.Name != nil { - doc := f.Doc - if len(f.Comments) > 0 && f.Doc == nil { - doc = f.Comments[len(f.Comments)-1] - } - o := &ast.Object{ - Kind: ast.Pkg, - Name: f.Name.Name, - Decl: &ast.TypeSpec{ - Name: f.Name, - Doc: doc, - Type: f.Name, - }, - } - return o, pkg, pkgs - } - } - // in-case we don't find a pkg decl - return nil, pkg, pkgs - } - - if pkg, pkgs, _ = findPkg(fset, importPath, srcRootDirs, parser.ParseComments); pkg != nil { - obj := pkg.Scope.Lookup(id.Name) - return obj, pkg, pkgs - } - } - } - } - } - } - return nil, pkg, pkgs } diff --git a/src/gosubli.me/margo/m_doc_test.go b/src/gosubli.me/margo/m_doc_test.go new file mode 100644 index 00000000..9dc3a6ba --- /dev/null +++ b/src/gosubli.me/margo/m_doc_test.go @@ -0,0 +1,118 @@ +package main + +import ( + "os" + "path" + "testing" + + "github.com/kr/pretty" + "github.com/stretchr/testify/assert" +) + +func usageSimple(t *testing.T, offset int, output []*Doc) { + // get current dir + wd, _ := os.Getwd() + fn := path.Join(wd, "testing/simple.go") + dec := &mDoc{ + Fn: fn, + Src: nil, + Env: map[string]string{}, + Offset: offset, + TabIndent: true, + TabWidth: 4, + FindUse: true, + } + + raw, _ := dec.Call() + assertOut(t, raw, output) +} +func findSimple(t *testing.T, offset int, output []*Doc) { + // get current dir + wd, _ := os.Getwd() + fn := path.Join(wd, "testing/simple.go") + dec := &mDoc{ + Fn: fn, + Src: nil, + Env: map[string]string{}, + Offset: offset, + TabIndent: true, + TabWidth: 4, + FindDef: true, + } + + raw, _ := dec.Call() + assertOut(t, raw, output) +} + +func assertOut(t *testing.T, raw interface{}, output []*Doc) { + if assert.Len(t, raw, len(output)) { + docs := raw.([]*Doc) + + for i := 0; i < len(docs); i++ { + assert.Equal(t, output[i].Col, docs[i].Col, "Col") + assert.Equal(t, output[i].Kind, docs[i].Kind, "Kind") + assert.Equal(t, output[i].Name, docs[i].Name, "Name") + assert.Equal(t, output[i].Pkg, docs[i].Pkg, "Pkg") + assert.Equal(t, output[i].Row, docs[i].Row, "Row") + assert.Equal(t, output[i].Src, docs[i].Src, "Src") + assert.Regexp(t, output[i].Fn+"$", docs[i].Fn) + } + } else { + pretty.Logf("Found: %# v", raw) + } +} + +func TestCallBasics_Struct(t *testing.T) { + findSimple( + t, + 85, + //[]*Doc{&Doc{Src: "a struct {\n\tvalue string\n}", Pkg: "tstm", Name: "a", Kind: "type", Fn: "testing.go", Row: 4, Col: 5}}, + []*Doc{&Doc{Src: "", Pkg: "testing", Name: "a", Kind: "struct", Fn: "simple.go", Row: 4, Col: 5}}, + ) +} + +func TestCallBasics_StructFieldSet(t *testing.T) { + findSimple( + t, + 95, + //[]*Doc{&Doc{Src: "value string", Pkg: "tstm", Name: "value", Kind: "field", Fn: "testing.go", Row: 5, Col: 1}}, + []*Doc{&Doc{Src: "", Pkg: "testing", Name: "value", Kind: "field", Fn: "simple.go", Row: 5, Col: 1}}, + ) +} + +func TestCallBasics_Framework(t *testing.T) { + findSimple( + t, + 115, + //[]*Doc{&Doc{Src: "func Println()", Pkg: "fmt", Name: "Println", Kind: "func", Fn: "/usr/local/go/src/pkg/fmt/print.go", Row: 262, Col: 5}}, + []*Doc{&Doc{Src: "", Pkg: "fmt", Name: "Println", Kind: "func", Fn: "/fmt/print.go", Row: 262, Col: 5}}, + ) +} + +func TestUsages_StructField(t *testing.T) { + usageSimple( + t, + 95, + []*Doc{ + &Doc{Src: "", Pkg: "testing", Name: "value", Kind: "field", Fn: "testing/simple.go", Row: 5, Col: 1}, + &Doc{Src: "", Pkg: "testing", Name: "value", Kind: "field", Fn: "testing/simple.go", Row: 10, Col: 3}, + &Doc{Src: "", Pkg: "testing", Name: "value", Kind: "field", Fn: "testing/simple.go", Row: 11, Col: 15}, + }, + ) +} + +func TestUsages_Framework(t *testing.T) { + usageSimple( + t, + 115, + []*Doc{&Doc{Src: "", Pkg: "fmt", Name: "Println", Kind: "func", Fn: "testing/simple.go", Row: 11, Col: 5}}, + ) +} + +func TestUsages_Import(t *testing.T) { + usageSimple( + t, + 26, + []*Doc{&Doc{Src: "", Pkg: "testing", Name: "fmt", Kind: "package", Fn: "testing/simple.go", Row: 11, Col: 1}}, + ) +} diff --git a/src/gosubli.me/margo/m_doc_types.go b/src/gosubli.me/margo/m_doc_types.go new file mode 100644 index 00000000..3dee1d2a --- /dev/null +++ b/src/gosubli.me/margo/m_doc_types.go @@ -0,0 +1,861 @@ +// Most of this file comes from LiteIDE. The LICENSE file is LGPL. +// The original source file is ./liteidex/src/liteide_stub/type.go +// It has been simplified and re-organized a bit to work within margo. + +// Copyright 2011-2014 visualfc . All rights reserved. + +package main + +import ( + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "log" + "os" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + "time" + + "golang.org/x/tools/go/gcimporter" + "golang.org/x/tools/go/types" +) + +var ( + typeVerbose bool = false + typeAllowBinary bool +) + +func (m *mDoc) findCode(packages []string) []*Doc { + res := []*Doc{} + if typeVerbose { + now := time.Now() + defer func() { + log.Println("time", time.Now().Sub(now)) + }() + } + w := NewPkgWalker(&build.Default, m.FindDef, m.FindUse, m.FindInfo) + cursor := &FileCursor{ + src: m.Src, + cursorPos: m.Offset, + fileName: filepath.Base(m.Fn), + fileDir: filepath.Dir(m.Fn), + } + + for _, pkgName := range packages { + if pkgName == "." { + pkgPath, err := os.Getwd() + if err != nil { + log.Fatalln(err) + } + pkgName = pkgPath + } + conf := &PkgConfig{IgnoreFuncBodies: true, AllowBinary: true, WithTestFiles: true} + if cursor != nil { + conf.Cursor = cursor + conf.IgnoreFuncBodies = false + conf.Info = &types.Info{ + Uses: make(map[*ast.Ident]types.Object), + Defs: make(map[*ast.Ident]types.Object), + //Types : make(map[ast.Expr]types.TypeAndValue) + Selections: make(map[*ast.SelectorExpr]*types.Selection), + //Scopes : make(map[ast.Node]*types.Scope) + //Implicits : make(map[ast.Node]types.Object) + } + } + pkg, err := w.Import("", pkgName, conf) + if pkg == nil { + log.Fatalln("error import path", err) + } + if cursor != nil && (m.FindInfo || m.FindDef || m.FindUse) { + res = w.LookupCursor(pkg, conf.Info, cursor) + } + } + + return res +} + +func simpleType(src string) string { + re, _ := regexp.Compile("[\\w\\./]+") + return re.ReplaceAllStringFunc(src, func(s string) string { + r := s + if i := strings.LastIndex(s, "/"); i != -1 { + r = s[i+1:] + } + if strings.Count(r, ".") > 1 { + r = r[strings.Index(r, ".")+1:] + } + return r + }) +} + +type FileCursor struct { + pkg string + fileName string + fileDir string + cursorPos int + pos token.Pos + src interface{} +} + +type PkgConfig struct { + IgnoreFuncBodies bool + AllowBinary bool + WithTestFiles bool + Cursor *FileCursor + Info *types.Info + Files map[string]*ast.File + TestFiles map[string]*ast.File + XTestFiles map[string]*ast.File +} + +func NewPkgWalker(context *build.Context, findDef, findUse, findInfo bool) *PkgWalker { + return &PkgWalker{ + context: context, + fset: token.NewFileSet(), + parsedFileCache: map[string]*ast.File{}, + imported: map[string]*types.Package{"unsafe": types.Unsafe}, + gcimporter: map[string]*types.Package{"unsafe": types.Unsafe}, + findDef: findDef, + findUse: findUse, + findInfo: findInfo, + } +} + +type PkgWalker struct { + fset *token.FileSet + context *build.Context + current *types.Package + importing types.Package + parsedFileCache map[string]*ast.File + imported map[string]*types.Package // packages already imported + gcimporter map[string]*types.Package + + findDef bool + findUse bool + findInfo bool +} + +func contains(list []string, s string) bool { + for _, t := range list { + if t == s { + return true + } + } + return false +} + +func (w *PkgWalker) isBinaryPkg(pkg string) bool { + return isStdPkg(pkg) +} + +func (w *PkgWalker) Import(parentDir string, name string, conf *PkgConfig) (pkg *types.Package, err error) { + /*defer func() { + err := recover() + if err != nil && typeVerbose { + log.Println(err) + } + }()*/ + + if strings.HasPrefix(name, ".") && parentDir != "" { + name = filepath.Join(parentDir, name) + } + pkg = w.imported[name] + if pkg != nil { + if pkg == &w.importing { + return nil, fmt.Errorf("cycle importing package %q", name) + } + return pkg, nil + } + + if typeVerbose { + log.Println("parser pkg", name) + } + + var bp *build.Package + if filepath.IsAbs(name) { + bp, err = w.context.ImportDir(name, 0) + } else { + bp, err = w.context.Import(name, "", 0) + } + + checkName := name + + if bp.ImportPath == "." { + checkName = bp.Name + } else { + checkName = bp.ImportPath + } + + if err != nil { + return nil, err + //if _, nogo := err.(*build.NoGoError); nogo { + // return + //} + //return + //log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, info.Dir, err) + } + + filenames := append(append([]string{}, bp.GoFiles...), bp.CgoFiles...) + if conf.WithTestFiles { + filenames = append(filenames, bp.TestGoFiles...) + } + + if name == "runtime" { + n := fmt.Sprintf("zgoos_%s.go", w.context.GOOS) + if !contains(filenames, n) { + filenames = append(filenames, n) + } + + n = fmt.Sprintf("zgoarch_%s.go", w.context.GOARCH) + if !contains(filenames, n) { + filenames = append(filenames, n) + } + } + + parserFiles := func(filenames []string, cursor *FileCursor, includeDefault bool) (files []*ast.File) { + foundCursor := false + for _, file := range filenames { + var f *ast.File + if cursor != nil && cursor.fileName == file { + f, err = w.parseFile(bp.Dir, file, cursor.src) + cursor.pos = token.Pos(w.fset.File(f.Pos()).Base()) + token.Pos(cursor.cursorPos) + cursor.fileDir = bp.Dir + foundCursor = true + } else { + f, err = w.parseFile(bp.Dir, file, nil) + } + if err != nil && typeVerbose { + log.Printf("error parsing package %s: %s\n", name, err) + } + files = append(files, f) + } + + if cursor != nil && includeDefault && !foundCursor { + f, err := w.parseFile(bp.Dir, cursor.fileName, cursor.src) + + if err != nil { + log.Printf("error parsing cursor package %s: %s\n", cursor.fileName, err) + } else { + cursor.pos = token.Pos(w.fset.File(f.Pos()).Base()) + token.Pos(cursor.cursorPos) + cursor.fileDir = bp.Dir + } + } + return + } + + files := parserFiles(filenames, conf.Cursor, true) + xfiles := parserFiles(bp.XTestGoFiles, conf.Cursor, false) + + typesConf := types.Config{ + IgnoreFuncBodies: conf.IgnoreFuncBodies, + FakeImportC: true, + Packages: w.gcimporter, + Import: func(imports map[string]*types.Package, name string) (pkg *types.Package, err error) { + if pkg != nil { + return pkg, nil + } + if conf.AllowBinary && w.isBinaryPkg(name) { + pkg = w.gcimporter[name] + if pkg != nil && pkg.Complete() { + return + } + pkg, err = gcimporter.Import(imports, name) + if pkg != nil && pkg.Complete() { + w.gcimporter[name] = pkg + return + } + } + return w.Import(bp.Dir, name, &PkgConfig{IgnoreFuncBodies: true, AllowBinary: true, WithTestFiles: false}) + }, + Error: func(err error) { + if typeVerbose { + log.Println(err) + } + }, + } + if pkg == nil { + pkg, err = typesConf.Check(checkName, w.fset, files, conf.Info) + } + w.imported[name] = pkg + + if len(xfiles) > 0 { + xpkg, _ := typesConf.Check(checkName+"_test", w.fset, xfiles, conf.Info) + w.imported[checkName+"_test"] = xpkg + } + return +} + +func (w *PkgWalker) parseFile(dir, file string, src interface{}) (*ast.File, error) { + filename := filepath.Join(dir, filepath.Base(file)) + f, _ := w.parsedFileCache[filename] + if f != nil { + return f, nil + } + + var err error + + // generate missing context-dependent files. + if w.context != nil && file == fmt.Sprintf("zgoos_%s.go", w.context.GOOS) { + src := fmt.Sprintf("package runtime; const theGoos = `%s`", w.context.GOOS) + f, err = parser.ParseFile(w.fset, filename, src, 0) + if err != nil { + log.Fatalf("incorrect generated file: %s", err) + } + } + + if w.context != nil && file == fmt.Sprintf("zgoarch_%s.go", w.context.GOARCH) { + src := fmt.Sprintf("package runtime; const theGoarch = `%s`", w.context.GOARCH) + f, err = parser.ParseFile(w.fset, filename, src, 0) + if err != nil { + log.Fatalf("incorrect generated file: %s", err) + } + } + + if f == nil { + f, err = parser.ParseFile(w.fset, filename, src, parser.AllErrors) //|parser.ParseComments) + if err != nil { + return f, err + } + } + + w.parsedFileCache[filename] = f + return f, nil +} + +func (w *PkgWalker) LookupCursor(pkg *types.Package, pkgInfo *types.Info, cursor *FileCursor) []*Doc { + is := w.CheckIsImport(cursor) + if is != nil { + return w.LookupImport(pkg, pkgInfo, cursor, is) + } else { + return w.LookupObjects(pkg, pkgInfo, cursor) + } +} + +func (w *PkgWalker) LookupImport(pkg *types.Package, pkgInfo *types.Info, cursor *FileCursor, is *ast.ImportSpec) []*Doc { + fpath, err := strconv.Unquote(is.Path.Value) + if err != nil { + return []*Doc{} + } + + ret := []*Doc{} + + if w.findDef { + fpos := w.fset.Position(is.Pos()) + ret = append(ret, &Doc{ + Pkg: pkg.Name(), + Src: "", + Name: is.Name.Name, + Kind: "package", + Fn: fpos.Filename, + Row: fpos.Line - 1, + Col: fpos.Column - 1, + }) + fmt.Println(fpos) + } + + fbase := fpath + pos := strings.LastIndexAny(fpath, "./-\\") + if pos != -1 { + fbase = fpath[pos+1:] + } + + var fname string + if is.Name != nil { + fname = is.Name.Name + } else { + fname = fbase + } + + if w.findInfo { + if fname == fpath { + fmt.Printf("package %s\n", fname) + } else { + fmt.Printf("package %s (\"%s\")\n", fname, fpath) + } + } + + if !w.findUse { + return ret + } + + fid := pkg.Path() + "." + fname + var usages []int + for id, obj := range pkgInfo.Uses { + if obj != nil && obj.Id() == fid { //!= nil && cursorObj.Pos() == obj.Pos() { + usages = append(usages, int(id.Pos())) + } + } + (sort.IntSlice(usages)).Sort() + for _, pos := range usages { + fpos := w.fset.Position(token.Pos(pos)) + ret = append(ret, &Doc{ + Pkg: pkg.Name(), + Src: "", + Name: fname, + Kind: "package", + Fn: fpos.Filename, + Row: fpos.Line - 1, + Col: fpos.Column - 1, + }) + log.Println(fpos) + } + + return ret +} + +func parserObjKind(obj types.Object) (ObjKind, error) { + var kind ObjKind + switch t := obj.(type) { + case *types.PkgName: + kind = ObjPkgName + case *types.Const: + kind = ObjConst + case *types.TypeName: + kind = ObjTypeName + switch t.Type().Underlying().(type) { + case *types.Interface: + kind = ObjInterface + case *types.Struct: + kind = ObjStruct + } + case *types.Var: + kind = ObjVar + if t.IsField() { + kind = ObjField + } + case *types.Func: + kind = ObjFunc + if sig, ok := t.Type().(*types.Signature); ok { + if sig.Recv() != nil { + kind = ObjMethod + } + } + case *types.Label: + kind = ObjLabel + case *types.Builtin: + kind = ObjBuiltin + case *types.Nil: + kind = ObjNil + default: + return ObjNone, fmt.Errorf("unknown obj type %T", obj) + } + return kind, nil +} + +func (w *PkgWalker) LookupStructFromField(info *types.Info, cursorPkg *types.Package, cursorObj types.Object, cursorPos token.Pos) types.Object { + if info == nil { + conf := &PkgConfig{ + IgnoreFuncBodies: true, + AllowBinary: true, + WithTestFiles: true, + Info: &types.Info{ + Defs: make(map[*ast.Ident]types.Object), + }, + } + w.imported[cursorPkg.Path()] = nil + pkg, _ := w.Import("", cursorPkg.Path(), conf) + if pkg != nil { + info = conf.Info + } + } + if info == nil { + return nil + } + for _, obj := range info.Defs { + if obj == nil { + continue + } + if _, ok := obj.(*types.TypeName); ok { + if t, ok := obj.Type().Underlying().(*types.Struct); ok { + for i := 0; i < t.NumFields(); i++ { + if t.Field(i).Pos() == cursorPos { + return obj + } + } + } + } + } + return nil +} + +func (w *PkgWalker) lookupNamedMethod(named *types.Named, name string) (types.Object, *types.Named) { + if iface, ok := named.Underlying().(*types.Interface); ok { + for i := 0; i < iface.NumMethods(); i++ { + fn := iface.Method(i) + if fn.Name() == name { + return fn, named + } + } + for i := 0; i < iface.NumEmbeddeds(); i++ { + if obj, na := w.lookupNamedMethod(iface.Embedded(i), name); obj != nil { + return obj, na + } + } + return nil, nil + } + if istruct, ok := named.Underlying().(*types.Struct); ok { + for i := 0; i < named.NumMethods(); i++ { + fn := named.Method(i) + if fn.Name() == name { + return fn, named + } + } + for i := 0; i < istruct.NumFields(); i++ { + field := istruct.Field(i) + if !field.Anonymous() { + continue + } + if typ, ok := field.Type().(*types.Named); ok { + if obj, na := w.lookupNamedMethod(typ, name); obj != nil { + return obj, na + } + } + } + } + return nil, nil +} + +func (w *PkgWalker) LookupObjects(pkg *types.Package, pkgInfo *types.Info, cursor *FileCursor) []*Doc { + var cursorObj types.Object + var cursorSelection *types.Selection + var cursorObjIsDef bool + //lookup defs + + _ = cursorObjIsDef + if cursorObj == nil { + for sel, obj := range pkgInfo.Selections { + if cursor.pos >= sel.Sel.Pos() && cursor.pos <= sel.Sel.End() { + cursorObj = obj.Obj() + cursorSelection = obj + break + } + } + } + if cursorObj == nil { + for id, obj := range pkgInfo.Defs { + if cursor.pos >= id.Pos() && cursor.pos <= id.End() { + cursorObj = obj + cursorObjIsDef = true + break + } + } + } + _ = cursorSelection + if cursorObj == nil { + for id, obj := range pkgInfo.Uses { + if cursor.pos >= id.Pos() && cursor.pos <= id.End() { + cursorObj = obj + break + } + } + } + if cursorObj == nil { + log.Println("exit 1") + return []*Doc{} + } + kind, err := parserObjKind(cursorObj) + if err != nil { + log.Fatalln(err) + } + if kind == ObjField { + if cursorObj.(*types.Var).Anonymous() { + if named, ok := cursorObj.Type().(*types.Named); ok { + cursorObj = named.Obj() + } + } + } + cursorPkg := cursorObj.Pkg() + cursorPos := cursorObj.Pos() + var fieldTypeInfo *types.Info + var fieldTypeObj types.Object + if cursorPkg == pkg { + fieldTypeInfo = pkgInfo + } + cursorIsInterfaceMethod := false + var cursorInterfaceTypeName string + if kind == ObjMethod && cursorSelection != nil && cursorSelection.Recv() != nil { + sig := cursorObj.(*types.Func).Type().Underlying().(*types.Signature) + if _, ok := sig.Recv().Type().Underlying().(*types.Interface); ok { + named := cursorSelection.Recv().(*types.Named) + obj, typ := w.lookupNamedMethod(named, cursorObj.Name()) + if obj != nil { + cursorObj = obj + } + if typ != nil { + cursorPkg = typ.Obj().Pkg() + cursorInterfaceTypeName = typ.Obj().Name() + } + cursorIsInterfaceMethod = true + } + } + + if cursorPkg != nil && cursorPkg != pkg && + kind != ObjPkgName && w.isBinaryPkg(cursorPkg.Path()) { + conf := &PkgConfig{ + IgnoreFuncBodies: true, + AllowBinary: true, + WithTestFiles: true, + Info: &types.Info{ + Defs: make(map[*ast.Ident]types.Object), + }, + } + pkg, _ := w.Import("", cursorPkg.Path(), conf) + if pkg != nil { + if cursorIsInterfaceMethod { + for _, obj := range conf.Info.Defs { + if obj == nil { + continue + } + if fn, ok := obj.(*types.Func); ok { + if fn.Name() == cursorObj.Name() { + if sig, ok := fn.Type().Underlying().(*types.Signature); ok { + if named, ok := sig.Recv().Type().(*types.Named); ok { + if named.Obj() != nil && named.Obj().Name() == cursorInterfaceTypeName { + cursorPos = obj.Pos() + break + } + } + } + } + } + } + } else { + for _, obj := range conf.Info.Defs { + if obj != nil && obj.String() == cursorObj.String() { + cursorPos = obj.Pos() + break + } + } + } + } + if kind == ObjField || cursorIsInterfaceMethod { + fieldTypeInfo = conf.Info + } + } + if kind == ObjField { + fieldTypeObj = w.LookupStructFromField(fieldTypeInfo, cursorPkg, cursorObj, cursorPos) + } + + ret := []*Doc{} + + if w.findDef { + fpos := w.fset.Position(cursorPos) + + ret = append(ret, &Doc{ + Pkg: cursorObj.Pkg().Name(), + Src: "", + Name: cursorObj.Name(), + Kind: kind.String(), + Fn: fpos.Filename, + Row: fpos.Line - 1, + Col: fpos.Column - 1, + }) + log.Println(fpos) + } + if w.findInfo { + if kind == ObjField && fieldTypeObj != nil { + typeName := fieldTypeObj.Name() + if fieldTypeObj.Pkg() != nil && fieldTypeObj.Pkg() != pkg { + typeName = fieldTypeObj.Pkg().Name() + "." + fieldTypeObj.Name() + } + fmt.Println(typeName, simpleType(cursorObj.String())) + } else if kind == ObjBuiltin { + fmt.Println(builtinInfo(cursorObj.Name())) + } else if kind == ObjPkgName { + fmt.Println(cursorObj.String()) + } else if cursorIsInterfaceMethod { + fmt.Println(strings.Replace(simpleType(cursorObj.String()), "(interface)", cursorPkg.Name()+"."+cursorInterfaceTypeName, 1)) + } else { + fmt.Println(simpleType(cursorObj.String())) + } + } + //if f, ok := w.parsedFileCache[w.fset.Position(cursorPos).Filename]; ok { + // for _, d := range f.Decls { + // if inRange(d, cursorPos) { + // if fd, ok := d.(*ast.FuncDecl); ok { + // fd.Body = nil + // } + // commentMap := ast.NewCommentMap(w.fset, f, f.Comments) + // commentedNode := printer.CommentedNode{Node: d} + // if comments := commentMap.Filter(d).Comments(); comments != nil { + // commentedNode.Comments = comments + // } + // var b bytes.Buffer + // printer.Fprint(&b, w.fset, &commentedNode) + // b.Write([]byte("\n\n")) // Add a blank line between entries if we print documentation. + // log.Println(w.nodeString(d)) + // } + // } + //} + if !w.findUse { + return ret + } + var usages []int + if kind == ObjPkgName { + for id, obj := range pkgInfo.Uses { + if obj != nil && obj.Id() == cursorObj.Id() { //!= nil && cursorObj.Pos() == obj.Pos() { + usages = append(usages, int(id.Pos())) + } + } + } else { + for id, obj := range pkgInfo.Defs { + if obj == cursorObj { //!= nil && cursorObj.Pos() == obj.Pos() { + usages = append(usages, int(id.Pos())) + } + } + for id, obj := range pkgInfo.Uses { + if obj == cursorObj { //!= nil && cursorObj.Pos() == obj.Pos() { + usages = append(usages, int(id.Pos())) + } + } + } + (sort.IntSlice(usages)).Sort() + for _, pos := range usages { + fpos := w.fset.Position(token.Pos(pos)) + + ret = append(ret, &Doc{ + Pkg: cursorObj.Pkg().Name(), + Src: "", + Name: cursorObj.Name(), + Kind: kind.String(), + Fn: fpos.Filename, + Row: fpos.Line - 1, + Col: fpos.Column - 1, + }) + log.Println(fpos) + } + + return ret +} + +func (w *PkgWalker) CheckIsImport(cursor *FileCursor) *ast.ImportSpec { + if cursor.fileDir == "" { + return nil + } + file, _ := w.parseFile(cursor.fileDir, cursor.fileName, cursor.src) + if file == nil { + return nil + } + for _, is := range file.Imports { + if inRange(is, cursor.pos) { + return is + } + } + return nil +} + +type ObjKind int + +const ( + ObjNone ObjKind = iota + ObjPkgName + ObjTypeName + ObjInterface + ObjStruct + ObjConst + ObjVar + ObjField + ObjFunc + ObjMethod + ObjLabel + ObjBuiltin + ObjNil +) + +var stdPkg = []string{ + "cmd/cgo", "cmd/fix", "cmd/go", "cmd/gofmt", + "cmd/yacc", "archive/tar", "archive/zip", "bufio", + "bytes", "compress/bzip2", "compress/flate", "compress/gzip", + "compress/lzw", "compress/zlib", "container/heap", "container/list", + "container/ring", "crypto", "crypto/aes", "crypto/cipher", + "crypto/des", "crypto/dsa", "crypto/ecdsa", "crypto/elliptic", + "crypto/hmac", "crypto/md5", "crypto/rand", "crypto/rc4", + "crypto/rsa", "crypto/sha1", "crypto/sha256", "crypto/sha512", + "crypto/subtle", "crypto/tls", "crypto/x509", "crypto/x509/pkix", + "database/sql", "database/sql/driver", "debug/dwarf", "debug/elf", + "debug/gosym", "debug/macho", "debug/pe", "encoding", + "encoding/ascii85", "encoding/asn1", "encoding/base32", "encoding/base64", + "encoding/binary", "encoding/csv", "encoding/gob", "encoding/hex", + "encoding/json", "encoding/pem", "encoding/xml", "errors", + "expvar", "flag", "fmt", "go/ast", + "go/build", "go/doc", "go/format", "go/parser", + "go/printer", "go/scanner", "go/token", "hash", + "hash/adler32", "hash/crc32", "hash/crc64", "hash/fnv", + "html", "html/template", "image", "image/color", + "image/color/palette", "image/draw", "image/gif", "image/jpeg", + "image/png", "index/suffixarray", "io", "io/ioutil", + "log", "log/syslog", "math", "math/big", + "math/cmplx", "math/rand", "mime", "mime/multipart", + "net", "net/http", "net/http/cgi", "net/http/cookiejar", + "net/http/fcgi", "net/http/httptest", "net/http/httputil", "net/http/pprof", + "net/mail", "net/rpc", "net/rpc/jsonrpc", "net/smtp", + "net/textproto", "net/url", "os", "os/exec", + "os/signal", "os/user", "path", "path/filepath", + "reflect", "regexp", "regexp/syntax", "runtime", + "runtime/cgo", "runtime/debug", "runtime/pprof", "runtime/race", + "sort", "strconv", "strings", "sync", + "sync/atomic", "syscall", "testing", "testing/iotest", + "testing/quick", "text/scanner", "text/tabwriter", "text/template", + "text/template/parse", "time", "unicode", "unicode/utf16", + "unicode/utf8", "unsafe", +} + +func isStdPkg(pkg string) bool { + for _, v := range stdPkg { + if v == pkg { + return true + } + } + return false +} + +var ObjKindName = []string{"none", "package", + "type", "interface", "struct", + "const", "var", "field", + "func", "method", + "label", "builtin", "nil"} + +func (k ObjKind) String() string { + if k >= 0 && int(k) < len(ObjKindName) { + return ObjKindName[k] + } + return "unkwnown" +} + +var builtinInfoMap = map[string]string{ + "append": "func append(slice []Type, elems ...Type) []Type", + "copy": "func copy(dst, src []Type) int", + "delete": "func delete(m map[Type]Type1, key Type)", + "len": "func len(v Type) int", + "cap": "func cap(v Type) int", + "make": "func make(Type, size IntegerType) Type", + "new": "func new(Type) *Type", + "complex": "func complex(r, i FloatType) ComplexType", + "real": "func real(c ComplexType) FloatType", + "imag": "func imag(c ComplexType) FloatType", + "close": "func close(c chan<- Type)", + "panic": "func panic(v interface{})", + "recover": "func recover() interface{}", + "print": "func print(args ...Type)", + "println": "func println(args ...Type)", + "error": "type error interface {Error() string}", +} + +func builtinInfo(id string) string { + if info, ok := builtinInfoMap[id]; ok { + return "builtin " + info + } + return "builtin " + id +} + +func inRange(node ast.Node, p token.Pos) bool { + if node == nil { + return false + } + return p >= node.Pos() && p <= node.End() +} diff --git a/src/gosubli.me/margo/testing/simple.go b/src/gosubli.me/margo/testing/simple.go new file mode 100644 index 00000000..ba028a86 --- /dev/null +++ b/src/gosubli.me/margo/testing/simple.go @@ -0,0 +1,13 @@ +package testing + +import "fmt" + +type a struct { + value string +} + +func main() { + t := &a{} + t.value = "test" + fmt.Println(t.value) +} From ec8f880916fcb02bbf125b079e85cc9c3888112b Mon Sep 17 00:00:00 2001 From: Douglas Clark Date: Sun, 30 Nov 2014 13:45:16 -0600 Subject: [PATCH 4/9] Updated vendored code for types, gcimporter, and exact --- src/gosubli.me/margo/m_doc_test.go | 2 + src/gosubli.me/margo/m_doc_types.go | 4 +- src/gosubli.me/margo/m_lint.go | 7 +- .../something-borrowed/exact/exact.go | 920 +++++++++ .../something-borrowed/exact/exact_test.go | 375 ++++ .../something-borrowed/exact/go13.go | 24 + .../something-borrowed/exact/go14.go | 13 + .../{types => gcimporter}/exportdata.go | 49 +- .../{types => gcimporter}/gcimporter.go | 522 +++--- .../gcimporter/gcimporter_test.go | 216 +++ .../{types => gcimporter}/testdata/exports.go | 0 .../something-borrowed/types/api.go | 418 ++++- .../something-borrowed/types/api_test.go | 936 ++++++++++ .../something-borrowed/types/assignments.go | 323 ++++ .../something-borrowed/types/builtins.go | 565 ++++-- .../something-borrowed/types/builtins_test.go | 204 ++ .../something-borrowed/types/call.go | 427 +++++ .../something-borrowed/types/check.go | 677 +++---- .../something-borrowed/types/check_test.go | 294 +++ .../something-borrowed/types/const.go | 718 ------- .../something-borrowed/types/conversions.go | 109 +- .../something-borrowed/types/decl.go | 419 +++++ .../something-borrowed/types/errors.go | 321 +--- .../something-borrowed/types/eval.go | 109 ++ .../something-borrowed/types/eval_test.go | 148 ++ .../something-borrowed/types/expr.go | 1651 +++++++++-------- .../something-borrowed/types/exprstring.go | 220 +++ .../types/exprstring_test.go | 94 + .../something-borrowed/types/go11.go | 17 + .../something-borrowed/types/go12.go | 17 + .../something-borrowed/types/hilbert_test.go | 232 +++ .../something-borrowed/types/initorder.go | 222 +++ .../something-borrowed/types/issues_test.go | 205 ++ .../something-borrowed/types/labels.go | 268 +++ .../something-borrowed/types/lookup.go | 341 ++++ .../something-borrowed/types/methodset.go | 271 +++ .../types/methodsetcache.go | 69 + .../something-borrowed/types/object.go | 340 ++++ .../something-borrowed/types/objects.go | 186 -- .../something-borrowed/types/objset.go | 31 + .../something-borrowed/types/operand.go | 396 ++-- .../something-borrowed/types/ordering.go | 127 ++ .../something-borrowed/types/package.go | 58 + .../something-borrowed/types/predicates.go | 303 +-- .../something-borrowed/types/resolve.go | 197 -- .../something-borrowed/types/resolver.go | 446 +++++ .../something-borrowed/types/resolver_test.go | 150 +- .../something-borrowed/types/return.go | 185 ++ .../something-borrowed/types/scope.go | 159 +- .../something-borrowed/types/selection.go | 143 ++ .../something-borrowed/types/self_test.go | 101 + .../something-borrowed/types/sizes.go | 249 ++- .../something-borrowed/types/stdlib_test.go | 254 +++ .../something-borrowed/types/stmt.go | 931 +++++----- .../types/testdata/blank.src | 5 + .../types/testdata/builtins.src | 746 ++++++-- .../types/testdata/const0.src | 83 +- .../types/testdata/const1.src | 314 ++++ .../types/testdata/constdecl.src | 94 + .../types/testdata/conversions.src | 82 +- .../types/testdata/cycles.src | 143 ++ .../types/testdata/cycles1.src | 77 + .../types/testdata/cycles2.src | 118 ++ .../types/testdata/cycles3.src | 60 + .../types/testdata/cycles4.src | 110 ++ .../types/testdata/decls0.src | 77 +- .../types/testdata/decls1.src | 40 +- .../types/testdata/decls2a.src | 68 +- .../types/testdata/decls2b.src | 41 +- .../types/testdata/decls3.src | 80 +- .../types/testdata/errors.src | 55 + .../types/testdata/expr0.src | 15 +- .../types/testdata/expr2.src | 226 ++- .../types/testdata/expr3.src | 319 ++-- .../types/testdata/gotos.src | 560 ++++++ .../types/testdata/importdecl0a.src | 53 + .../types/testdata/importdecl0b.src | 33 + .../types/testdata/importdecl1a.src | 11 + .../types/testdata/importdecl1b.src | 7 + .../types/testdata/init0.src | 106 ++ .../types/testdata/init1.src | 97 + .../types/testdata/init2.src | 139 ++ .../types/testdata/issues.src | 37 + .../types/testdata/labels.src | 207 +++ .../types/testdata/methodsets.src | 214 +++ .../types/testdata/shifts.src | 321 ++++ .../types/testdata/stmt0.src | 595 +++++- .../types/testdata/stmt1.src | 165 ++ .../types/testdata/vardecl.src | 183 ++ .../something-borrowed/types/token_test.go | 47 + .../something-borrowed/types/type.go | 452 +++++ .../something-borrowed/types/types.go | 236 --- .../something-borrowed/types/typestring.go | 266 +++ .../{types_test.go => typestring_test.go} | 121 +- .../types/typeutil/example_test.go | 64 + .../types/typeutil/imports.go | 27 + .../types/typeutil/imports_test.go | 75 + .../something-borrowed/types/typeutil/map.go | 314 ++++ .../types/typeutil/map_test.go | 174 ++ .../something-borrowed/types/typeutil/ui.go | 38 + .../something-borrowed/types/typexpr.go | 721 +++++++ .../something-borrowed/types/universe.go | 248 ++- 102 files changed, 18936 insertions(+), 4991 deletions(-) create mode 100644 src/gosubli.me/something-borrowed/exact/exact.go create mode 100644 src/gosubli.me/something-borrowed/exact/exact_test.go create mode 100644 src/gosubli.me/something-borrowed/exact/go13.go create mode 100644 src/gosubli.me/something-borrowed/exact/go14.go rename src/gosubli.me/something-borrowed/{types => gcimporter}/exportdata.go (71%) rename src/gosubli.me/something-borrowed/{types => gcimporter}/gcimporter.go (60%) create mode 100644 src/gosubli.me/something-borrowed/gcimporter/gcimporter_test.go rename src/gosubli.me/something-borrowed/{types => gcimporter}/testdata/exports.go (100%) create mode 100644 src/gosubli.me/something-borrowed/types/api_test.go create mode 100644 src/gosubli.me/something-borrowed/types/assignments.go create mode 100644 src/gosubli.me/something-borrowed/types/builtins_test.go create mode 100644 src/gosubli.me/something-borrowed/types/call.go create mode 100644 src/gosubli.me/something-borrowed/types/check_test.go delete mode 100644 src/gosubli.me/something-borrowed/types/const.go create mode 100644 src/gosubli.me/something-borrowed/types/decl.go create mode 100644 src/gosubli.me/something-borrowed/types/eval.go create mode 100644 src/gosubli.me/something-borrowed/types/eval_test.go create mode 100644 src/gosubli.me/something-borrowed/types/exprstring.go create mode 100644 src/gosubli.me/something-borrowed/types/exprstring_test.go create mode 100644 src/gosubli.me/something-borrowed/types/go11.go create mode 100644 src/gosubli.me/something-borrowed/types/go12.go create mode 100644 src/gosubli.me/something-borrowed/types/hilbert_test.go create mode 100644 src/gosubli.me/something-borrowed/types/initorder.go create mode 100644 src/gosubli.me/something-borrowed/types/issues_test.go create mode 100644 src/gosubli.me/something-borrowed/types/labels.go create mode 100644 src/gosubli.me/something-borrowed/types/lookup.go create mode 100644 src/gosubli.me/something-borrowed/types/methodset.go create mode 100644 src/gosubli.me/something-borrowed/types/methodsetcache.go create mode 100644 src/gosubli.me/something-borrowed/types/object.go delete mode 100644 src/gosubli.me/something-borrowed/types/objects.go create mode 100644 src/gosubli.me/something-borrowed/types/objset.go create mode 100644 src/gosubli.me/something-borrowed/types/ordering.go create mode 100644 src/gosubli.me/something-borrowed/types/package.go delete mode 100644 src/gosubli.me/something-borrowed/types/resolve.go create mode 100644 src/gosubli.me/something-borrowed/types/resolver.go create mode 100644 src/gosubli.me/something-borrowed/types/return.go create mode 100644 src/gosubli.me/something-borrowed/types/selection.go create mode 100644 src/gosubli.me/something-borrowed/types/self_test.go create mode 100644 src/gosubli.me/something-borrowed/types/stdlib_test.go create mode 100644 src/gosubli.me/something-borrowed/types/testdata/blank.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/const1.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/constdecl.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/cycles.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/cycles1.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/cycles2.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/cycles3.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/cycles4.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/errors.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/gotos.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/importdecl0a.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/importdecl0b.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/importdecl1a.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/importdecl1b.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/init0.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/init1.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/init2.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/issues.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/labels.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/methodsets.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/shifts.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/stmt1.src create mode 100644 src/gosubli.me/something-borrowed/types/testdata/vardecl.src create mode 100644 src/gosubli.me/something-borrowed/types/token_test.go create mode 100644 src/gosubli.me/something-borrowed/types/type.go delete mode 100644 src/gosubli.me/something-borrowed/types/types.go create mode 100644 src/gosubli.me/something-borrowed/types/typestring.go rename src/gosubli.me/something-borrowed/types/{types_test.go => typestring_test.go} (50%) create mode 100644 src/gosubli.me/something-borrowed/types/typeutil/example_test.go create mode 100644 src/gosubli.me/something-borrowed/types/typeutil/imports.go create mode 100644 src/gosubli.me/something-borrowed/types/typeutil/imports_test.go create mode 100644 src/gosubli.me/something-borrowed/types/typeutil/map.go create mode 100644 src/gosubli.me/something-borrowed/types/typeutil/map_test.go create mode 100644 src/gosubli.me/something-borrowed/types/typeutil/ui.go create mode 100644 src/gosubli.me/something-borrowed/types/typexpr.go diff --git a/src/gosubli.me/margo/m_doc_test.go b/src/gosubli.me/margo/m_doc_test.go index 9dc3a6ba..e0b81d79 100644 --- a/src/gosubli.me/margo/m_doc_test.go +++ b/src/gosubli.me/margo/m_doc_test.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "os" "path" "testing" @@ -110,6 +111,7 @@ func TestUsages_Framework(t *testing.T) { } func TestUsages_Import(t *testing.T) { + fmt.Println("Test: %v") usageSimple( t, 26, diff --git a/src/gosubli.me/margo/m_doc_types.go b/src/gosubli.me/margo/m_doc_types.go index 3dee1d2a..a80019f2 100644 --- a/src/gosubli.me/margo/m_doc_types.go +++ b/src/gosubli.me/margo/m_doc_types.go @@ -21,8 +21,8 @@ import ( "strings" "time" - "golang.org/x/tools/go/gcimporter" - "golang.org/x/tools/go/types" + "gosubli.me/something-borrowed/gcimporter" //"golang.org/x/tools/go/gcimporter" + "gosubli.me/something-borrowed/types" //"golang.org/x/tools/go/types" ) var ( diff --git a/src/gosubli.me/margo/m_lint.go b/src/gosubli.me/margo/m_lint.go index a0b043b4..9390a4fe 100644 --- a/src/gosubli.me/margo/m_lint.go +++ b/src/gosubli.me/margo/m_lint.go @@ -5,9 +5,10 @@ import ( "go/parser" "go/scanner" "go/token" - "gosubli.me/something-borrowed/types" "regexp" "strconv" + + "gosubli.me/something-borrowed/types" ) type mLintReport struct { @@ -150,7 +151,7 @@ func mLintCheckTypes(kind string, m *mLint) { } } - ctx := types.Context{ + ctx := types.Config{ Error: func(err error) { s := mLintErrPat.FindStringSubmatch(err.Error()) if len(s) == 5 { @@ -168,5 +169,5 @@ func mLintCheckTypes(kind string, m *mLint) { }, } - ctx.Check(m.fset, files) + ctx.Check(m.v.dir, m.fset, files, nil) } diff --git a/src/gosubli.me/something-borrowed/exact/exact.go b/src/gosubli.me/something-borrowed/exact/exact.go new file mode 100644 index 00000000..e8fbfe97 --- /dev/null +++ b/src/gosubli.me/something-borrowed/exact/exact.go @@ -0,0 +1,920 @@ +// 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 exact implements Values representing untyped +// Go constants and the corresponding operations. Values +// and operations have unlimited precision. +// +// A special Unknown value may be used when a value +// is unknown due to an error. Operations on unknown +// values produce unknown values unless specified +// otherwise. +// +package exact + +import ( + "fmt" + "go/token" + "math/big" + "strconv" +) + +// Kind specifies the kind of value represented by a Value. +type Kind int + +// Implementation note: Kinds must be enumerated in +// order of increasing "complexity" (used by match). + +const ( + // unknown values + Unknown Kind = iota + + // non-numeric values + Bool + String + + // numeric values + Int + Float + Complex +) + +// A Value represents a mathematically exact value of a given Kind. +type Value interface { + // Kind returns the value kind; it is always the smallest + // kind in which the value can be represented exactly. + Kind() Kind + + // String returns a human-readable form of the value. + String() string + + // Prevent external implementations. + implementsValue() +} + +// ---------------------------------------------------------------------------- +// Implementations + +type ( + unknownVal struct{} + boolVal bool + stringVal string + int64Val int64 + intVal struct{ val *big.Int } + floatVal struct{ val *big.Rat } + complexVal struct{ re, im *big.Rat } +) + +func (unknownVal) Kind() Kind { return Unknown } +func (boolVal) Kind() Kind { return Bool } +func (stringVal) Kind() Kind { return String } +func (int64Val) Kind() Kind { return Int } +func (intVal) Kind() Kind { return Int } +func (floatVal) Kind() Kind { return Float } +func (complexVal) Kind() Kind { return Complex } + +func (unknownVal) String() string { return "unknown" } +func (x boolVal) String() string { return fmt.Sprintf("%v", bool(x)) } +func (x stringVal) String() string { return strconv.Quote(string(x)) } +func (x int64Val) String() string { return strconv.FormatInt(int64(x), 10) } +func (x intVal) String() string { return x.val.String() } +func (x floatVal) String() string { return x.val.String() } +func (x complexVal) String() string { return fmt.Sprintf("(%s + %si)", x.re, x.im) } + +func (unknownVal) implementsValue() {} +func (boolVal) implementsValue() {} +func (stringVal) implementsValue() {} +func (int64Val) implementsValue() {} +func (intVal) implementsValue() {} +func (floatVal) implementsValue() {} +func (complexVal) implementsValue() {} + +// int64 bounds +var ( + minInt64 = big.NewInt(-1 << 63) + maxInt64 = big.NewInt(1<<63 - 1) +) + +func normInt(x *big.Int) Value { + if minInt64.Cmp(x) <= 0 && x.Cmp(maxInt64) <= 0 { + return int64Val(x.Int64()) + } + return intVal{x} +} + +func normFloat(x *big.Rat) Value { + if x.IsInt() { + return normInt(x.Num()) + } + return floatVal{x} +} + +func normComplex(re, im *big.Rat) Value { + if im.Sign() == 0 { + return normFloat(re) + } + return complexVal{re, im} +} + +// ---------------------------------------------------------------------------- +// Factories + +// MakeUnknown returns the Unknown value. +func MakeUnknown() Value { return unknownVal{} } + +// MakeBool returns the Bool value for x. +func MakeBool(b bool) Value { return boolVal(b) } + +// MakeString returns the String value for x. +func MakeString(s string) Value { return stringVal(s) } + +// MakeInt64 returns the Int value for x. +func MakeInt64(x int64) Value { return int64Val(x) } + +// MakeUint64 returns the Int value for x. +func MakeUint64(x uint64) Value { return normInt(new(big.Int).SetUint64(x)) } + +// MakeFloat64 returns the numeric value for x. +// If x is not finite, the result is unknown. +func MakeFloat64(x float64) Value { + if f := new(big.Rat).SetFloat64(x); f != nil { + return normFloat(f) + } + return unknownVal{} +} + +// MakeFromLiteral returns the corresponding integer, floating-point, +// imaginary, character, or string value for a Go literal string. The +// result is nil if the literal string is invalid. +func MakeFromLiteral(lit string, tok token.Token) Value { + switch tok { + case token.INT: + if x, err := strconv.ParseInt(lit, 0, 64); err == nil { + return int64Val(x) + } + if x, ok := new(big.Int).SetString(lit, 0); ok { + return intVal{x} + } + + case token.FLOAT: + if x, ok := new(big.Rat).SetString(lit); ok { + return normFloat(x) + } + + case token.IMAG: + if n := len(lit); n > 0 && lit[n-1] == 'i' { + if im, ok := new(big.Rat).SetString(lit[0 : n-1]); ok { + return normComplex(big.NewRat(0, 1), im) + } + } + + case token.CHAR: + if n := len(lit); n >= 2 { + if code, _, _, err := strconv.UnquoteChar(lit[1:n-1], '\''); err == nil { + return int64Val(code) + } + } + + case token.STRING: + if s, err := strconv.Unquote(lit); err == nil { + return stringVal(s) + } + } + + return nil +} + +// ---------------------------------------------------------------------------- +// Accessors +// +// For unknown arguments the result is the zero value for the respective +// accessor type, except for Sign, where the result is 1. + +// BoolVal returns the Go boolean value of x, which must be a Bool or an Unknown. +// If x is Unknown, the result is false. +func BoolVal(x Value) bool { + switch x := x.(type) { + case boolVal: + return bool(x) + case unknownVal: + return false + } + panic(fmt.Sprintf("%v not a Bool", x)) +} + +// StringVal returns the Go string value of x, which must be a String or an Unknown. +// If x is Unknown, the result is "". +func StringVal(x Value) string { + switch x := x.(type) { + case stringVal: + return string(x) + case unknownVal: + return "" + } + panic(fmt.Sprintf("%v not a String", x)) +} + +// Int64Val returns the Go int64 value of x and whether the result is exact; +// x must be an Int or an Unknown. If the result is not exact, its value is undefined. +// If x is Unknown, the result is (0, false). +func Int64Val(x Value) (int64, bool) { + switch x := x.(type) { + case int64Val: + return int64(x), true + case intVal: + return x.val.Int64(), x.val.BitLen() <= 63 + case unknownVal: + return 0, false + } + panic(fmt.Sprintf("%v not an Int", x)) +} + +// Uint64Val returns the Go uint64 value of x and whether the result is exact; +// x must be an Int or an Unknown. If the result is not exact, its value is undefined. +// If x is Unknown, the result is (0, false). +func Uint64Val(x Value) (uint64, bool) { + switch x := x.(type) { + case int64Val: + return uint64(x), x >= 0 + case intVal: + return x.val.Uint64(), x.val.Sign() >= 0 && x.val.BitLen() <= 64 + case unknownVal: + return 0, false + } + panic(fmt.Sprintf("%v not an Int", x)) +} + +// Float32Val is like Float64Val but for float32 instead of float64. +func Float32Val(x Value) (float32, bool) { + switch x := x.(type) { + case int64Val: + f := float32(x) + return f, int64Val(f) == x + case intVal: + return ratToFloat32(new(big.Rat).SetFrac(x.val, int1)) + case floatVal: + return ratToFloat32(x.val) + case unknownVal: + return 0, false + } + panic(fmt.Sprintf("%v not a Float", x)) +} + +// Float64Val returns the nearest Go float64 value of x and whether the result is exact; +// x must be numeric but not Complex, or Unknown. For values too small (too close to 0) +// to represent as float64, Float64Val silently underflows to 0. The result sign always +// matches the sign of x, even for 0. +// If x is Unknown, the result is (0, false). +func Float64Val(x Value) (float64, bool) { + switch x := x.(type) { + case int64Val: + f := float64(int64(x)) + return f, int64Val(f) == x + case intVal: + return new(big.Rat).SetFrac(x.val, int1).Float64() + case floatVal: + return x.val.Float64() + case unknownVal: + return 0, false + } + panic(fmt.Sprintf("%v not a Float", x)) +} + +// BitLen returns the number of bits required to represent +// the absolute value x in binary representation; x must be an Int or an Unknown. +// If x is Unknown, the result is 0. +func BitLen(x Value) int { + switch x := x.(type) { + case int64Val: + return new(big.Int).SetInt64(int64(x)).BitLen() + case intVal: + return x.val.BitLen() + case unknownVal: + return 0 + } + panic(fmt.Sprintf("%v not an Int", x)) +} + +// Sign returns -1, 0, or 1 depending on whether x < 0, x == 0, or x > 0; +// x must be numeric or Unknown. For complex values x, the sign is 0 if x == 0, +// otherwise it is != 0. If x is Unknown, the result is 1. +func Sign(x Value) int { + switch x := x.(type) { + case int64Val: + switch { + case x < 0: + return -1 + case x > 0: + return 1 + } + return 0 + case intVal: + return x.val.Sign() + case floatVal: + return x.val.Sign() + case complexVal: + return x.re.Sign() | x.im.Sign() + case unknownVal: + return 1 // avoid spurious division by zero errors + } + panic(fmt.Sprintf("%v not numeric", x)) +} + +// ---------------------------------------------------------------------------- +// Support for serializing/deserializing integers + +const ( + // Compute the size of a Word in bytes. + _m = ^big.Word(0) + _log = _m>>8&1 + _m>>16&1 + _m>>32&1 + wordSize = 1 << _log +) + +// Bytes returns the bytes for the absolute value of x in little- +// endian binary representation; x must be an Int. +func Bytes(x Value) []byte { + var val *big.Int + switch x := x.(type) { + case int64Val: + val = new(big.Int).SetInt64(int64(x)) + case intVal: + val = x.val + default: + panic(fmt.Sprintf("%v not an Int", x)) + } + + words := val.Bits() + bytes := make([]byte, len(words)*wordSize) + + i := 0 + for _, w := range words { + for j := 0; j < wordSize; j++ { + bytes[i] = byte(w) + w >>= 8 + i++ + } + } + // remove leading 0's + for i > 0 && bytes[i-1] == 0 { + i-- + } + + return bytes[:i] +} + +// MakeFromBytes returns the Int value given the bytes of its little-endian +// binary representation. An empty byte slice argument represents 0. +func MakeFromBytes(bytes []byte) Value { + words := make([]big.Word, (len(bytes)+(wordSize-1))/wordSize) + + i := 0 + var w big.Word + var s uint + for _, b := range bytes { + w |= big.Word(b) << s + if s += 8; s == wordSize*8 { + words[i] = w + i++ + w = 0 + s = 0 + } + } + // store last word + if i < len(words) { + words[i] = w + i++ + } + // remove leading 0's + for i > 0 && words[i-1] == 0 { + i-- + } + + return normInt(new(big.Int).SetBits(words[:i])) +} + +// ---------------------------------------------------------------------------- +// Support for disassembling fractions + +// Num returns the numerator of x; x must be Int, Float, or Unknown. +// If x is Unknown, the result is Unknown, otherwise it is an Int +// with the same sign as x. +func Num(x Value) Value { + switch x := x.(type) { + case unknownVal, int64Val, intVal: + return x + case floatVal: + return normInt(x.val.Num()) + } + panic(fmt.Sprintf("%v not Int or Float", x)) +} + +// Denom returns the denominator of x; x must be Int, Float, or Unknown. +// If x is Unknown, the result is Unknown, otherwise it is an Int >= 1. +func Denom(x Value) Value { + switch x := x.(type) { + case unknownVal: + return x + case int64Val, intVal: + return int64Val(1) + case floatVal: + return normInt(x.val.Denom()) + } + panic(fmt.Sprintf("%v not Int or Float", x)) +} + +// ---------------------------------------------------------------------------- +// Support for assembling/disassembling complex numbers + +// MakeImag returns the numeric value x*i (possibly 0); +// x must be Int, Float, or Unknown. +// If x is Unknown, the result is Unknown. +func MakeImag(x Value) Value { + var im *big.Rat + switch x := x.(type) { + case unknownVal: + return x + case int64Val: + im = big.NewRat(int64(x), 1) + case intVal: + im = new(big.Rat).SetFrac(x.val, int1) + case floatVal: + im = x.val + default: + panic(fmt.Sprintf("%v not Int or Float", x)) + } + return normComplex(rat0, im) +} + +// Real returns the real part of x, which must be a numeric or unknown value. +// If x is Unknown, the result is Unknown. +func Real(x Value) Value { + switch x := x.(type) { + case unknownVal, int64Val, intVal, floatVal: + return x + case complexVal: + return normFloat(x.re) + } + panic(fmt.Sprintf("%v not numeric", x)) +} + +// Imag returns the imaginary part of x, which must be a numeric or unknown value. +// If x is Unknown, the result is Unknown. +func Imag(x Value) Value { + switch x := x.(type) { + case unknownVal: + return x + case int64Val, intVal, floatVal: + return int64Val(0) + case complexVal: + return normFloat(x.im) + } + panic(fmt.Sprintf("%v not numeric", x)) +} + +// ---------------------------------------------------------------------------- +// Operations + +// is32bit reports whether x can be represented using 32 bits. +func is32bit(x int64) bool { + const s = 32 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 +} + +// is63bit reports whether x can be represented using 63 bits. +func is63bit(x int64) bool { + const s = 63 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 +} + +// UnaryOp returns the result of the unary expression op y. +// The operation must be defined for the operand. +// If size >= 0 it specifies the ^ (xor) result size in bytes. +// If y is Unknown, the result is Unknown. +// +func UnaryOp(op token.Token, y Value, size int) Value { + switch op { + case token.ADD: + switch y.(type) { + case unknownVal, int64Val, intVal, floatVal, complexVal: + return y + } + + case token.SUB: + switch y := y.(type) { + case unknownVal: + return y + case int64Val: + if z := -y; z != y { + return z // no overflow + } + return normInt(new(big.Int).Neg(big.NewInt(int64(y)))) + case intVal: + return normInt(new(big.Int).Neg(y.val)) + case floatVal: + return normFloat(new(big.Rat).Neg(y.val)) + case complexVal: + return normComplex(new(big.Rat).Neg(y.re), new(big.Rat).Neg(y.im)) + } + + case token.XOR: + var z big.Int + switch y := y.(type) { + case unknownVal: + return y + case int64Val: + z.Not(big.NewInt(int64(y))) + case intVal: + z.Not(y.val) + default: + goto Error + } + // For unsigned types, the result will be negative and + // thus "too large": We must limit the result size to + // the type's size. + if size >= 0 { + s := uint(size) * 8 + z.AndNot(&z, new(big.Int).Lsh(big.NewInt(-1), s)) // z &^= (-1)< ord(y) { + y, x = match(y, x) + return x, y + } + // ord(x) <= ord(y) + + switch x := x.(type) { + case unknownVal: + return x, x + + case boolVal, stringVal, complexVal: + return x, y + + case int64Val: + switch y := y.(type) { + case int64Val: + return x, y + case intVal: + return intVal{big.NewInt(int64(x))}, y + case floatVal: + return floatVal{big.NewRat(int64(x), 1)}, y + case complexVal: + return complexVal{big.NewRat(int64(x), 1), rat0}, y + } + + case intVal: + switch y := y.(type) { + case intVal: + return x, y + case floatVal: + return floatVal{new(big.Rat).SetFrac(x.val, int1)}, y + case complexVal: + return complexVal{new(big.Rat).SetFrac(x.val, int1), rat0}, y + } + + case floatVal: + switch y := y.(type) { + case floatVal: + return x, y + case complexVal: + return complexVal{x.val, rat0}, y + } + } + + panic("unreachable") +} + +// BinaryOp returns the result of the binary expression x op y. +// The operation must be defined for the operands. If one of the +// operands is Unknown, the result is Unknown. +// To force integer division of Int operands, use op == token.QUO_ASSIGN +// instead of token.QUO; the result is guaranteed to be Int in this case. +// Division by zero leads to a run-time panic. +// +func BinaryOp(x Value, op token.Token, y Value) Value { + x, y = match(x, y) + + switch x := x.(type) { + case unknownVal: + return x + + case boolVal: + y := y.(boolVal) + switch op { + case token.LAND: + return x && y + case token.LOR: + return x || y + } + + case int64Val: + a := int64(x) + b := int64(y.(int64Val)) + var c int64 + switch op { + case token.ADD: + if !is63bit(a) || !is63bit(b) { + return normInt(new(big.Int).Add(big.NewInt(a), big.NewInt(b))) + } + c = a + b + case token.SUB: + if !is63bit(a) || !is63bit(b) { + return normInt(new(big.Int).Sub(big.NewInt(a), big.NewInt(b))) + } + c = a - b + case token.MUL: + if !is32bit(a) || !is32bit(b) { + return normInt(new(big.Int).Mul(big.NewInt(a), big.NewInt(b))) + } + c = a * b + case token.QUO: + return normFloat(new(big.Rat).SetFrac(big.NewInt(a), big.NewInt(b))) + case token.QUO_ASSIGN: // force integer division + c = a / b + case token.REM: + c = a % b + case token.AND: + c = a & b + case token.OR: + c = a | b + case token.XOR: + c = a ^ b + case token.AND_NOT: + c = a &^ b + default: + goto Error + } + return int64Val(c) + + case intVal: + a := x.val + b := y.(intVal).val + var c big.Int + switch op { + case token.ADD: + c.Add(a, b) + case token.SUB: + c.Sub(a, b) + case token.MUL: + c.Mul(a, b) + case token.QUO: + return normFloat(new(big.Rat).SetFrac(a, b)) + case token.QUO_ASSIGN: // force integer division + c.Quo(a, b) + case token.REM: + c.Rem(a, b) + case token.AND: + c.And(a, b) + case token.OR: + c.Or(a, b) + case token.XOR: + c.Xor(a, b) + case token.AND_NOT: + c.AndNot(a, b) + default: + goto Error + } + return normInt(&c) + + case floatVal: + a := x.val + b := y.(floatVal).val + var c big.Rat + switch op { + case token.ADD: + c.Add(a, b) + case token.SUB: + c.Sub(a, b) + case token.MUL: + c.Mul(a, b) + case token.QUO: + c.Quo(a, b) + default: + goto Error + } + return normFloat(&c) + + case complexVal: + y := y.(complexVal) + a, b := x.re, x.im + c, d := y.re, y.im + var re, im big.Rat + switch op { + case token.ADD: + // (a+c) + i(b+d) + re.Add(a, c) + im.Add(b, d) + case token.SUB: + // (a-c) + i(b-d) + re.Sub(a, c) + im.Sub(b, d) + case token.MUL: + // (ac-bd) + i(bc+ad) + var ac, bd, bc, ad big.Rat + ac.Mul(a, c) + bd.Mul(b, d) + bc.Mul(b, c) + ad.Mul(a, d) + re.Sub(&ac, &bd) + im.Add(&bc, &ad) + case token.QUO: + // (ac+bd)/s + i(bc-ad)/s, with s = cc + dd + var ac, bd, bc, ad, s, cc, dd big.Rat + ac.Mul(a, c) + bd.Mul(b, d) + bc.Mul(b, c) + ad.Mul(a, d) + cc.Mul(c, c) + dd.Mul(d, d) + s.Add(&cc, &dd) + re.Add(&ac, &bd) + re.Quo(&re, &s) + im.Sub(&bc, &ad) + im.Quo(&im, &s) + default: + goto Error + } + return normComplex(&re, &im) + + case stringVal: + if op == token.ADD { + return x + y.(stringVal) + } + } + +Error: + panic(fmt.Sprintf("invalid binary operation %v %s %v", x, op, y)) +} + +// Shift returns the result of the shift expression x op s +// with op == token.SHL or token.SHR (<< or >>). x must be +// an Int or an Unknown. If x is Unknown, the result is x. +// +func Shift(x Value, op token.Token, s uint) Value { + switch x := x.(type) { + case unknownVal: + return x + + case int64Val: + if s == 0 { + return x + } + switch op { + case token.SHL: + z := big.NewInt(int64(x)) + return normInt(z.Lsh(z, s)) + case token.SHR: + return x >> s + } + + case intVal: + if s == 0 { + return x + } + var z big.Int + switch op { + case token.SHL: + return normInt(z.Lsh(x.val, s)) + case token.SHR: + return normInt(z.Rsh(x.val, s)) + } + } + + panic(fmt.Sprintf("invalid shift %v %s %d", x, op, s)) +} + +func cmpZero(x int, op token.Token) bool { + switch op { + case token.EQL: + return x == 0 + case token.NEQ: + return x != 0 + case token.LSS: + return x < 0 + case token.LEQ: + return x <= 0 + case token.GTR: + return x > 0 + case token.GEQ: + return x >= 0 + } + panic("unreachable") +} + +// Compare returns the result of the comparison x op y. +// The comparison must be defined for the operands. +// If one of the operands is Unknown, the result is +// false. +// +func Compare(x Value, op token.Token, y Value) bool { + x, y = match(x, y) + + switch x := x.(type) { + case unknownVal: + return false + + case boolVal: + y := y.(boolVal) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + } + + case int64Val: + y := y.(int64Val) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + case token.LSS: + return x < y + case token.LEQ: + return x <= y + case token.GTR: + return x > y + case token.GEQ: + return x >= y + } + + case intVal: + return cmpZero(x.val.Cmp(y.(intVal).val), op) + + case floatVal: + return cmpZero(x.val.Cmp(y.(floatVal).val), op) + + case complexVal: + y := y.(complexVal) + re := x.re.Cmp(y.re) + im := x.im.Cmp(y.im) + switch op { + case token.EQL: + return re == 0 && im == 0 + case token.NEQ: + return re != 0 || im != 0 + } + + case stringVal: + y := y.(stringVal) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + case token.LSS: + return x < y + case token.LEQ: + return x <= y + case token.GTR: + return x > y + case token.GEQ: + return x >= y + } + } + + panic(fmt.Sprintf("invalid comparison %v %s %v", x, op, y)) +} diff --git a/src/gosubli.me/something-borrowed/exact/exact_test.go b/src/gosubli.me/something-borrowed/exact/exact_test.go new file mode 100644 index 00000000..aa38a896 --- /dev/null +++ b/src/gosubli.me/something-borrowed/exact/exact_test.go @@ -0,0 +1,375 @@ +// 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 exact + +import ( + "go/token" + "strings" + "testing" +) + +// TODO(gri) expand this test framework + +var opTests = []string{ + // unary operations + `+ 0 = 0`, + `+ ? = ?`, + `- 1 = -1`, + `- ? = ?`, + `^ 0 = -1`, + `^ ? = ?`, + + `! true = false`, + `! false = true`, + `! ? = ?`, + + // etc. + + // binary operations + `"" + "" = ""`, + `"foo" + "" = "foo"`, + `"" + "bar" = "bar"`, + `"foo" + "bar" = "foobar"`, + + `0 + 0 = 0`, + `0 + 0.1 = 0.1`, + `0 + 0.1i = 0.1i`, + `0.1 + 0.9 = 1`, + `1e100 + 1e100 = 2e100`, + `? + 0 = ?`, + `0 + ? = ?`, + + `0 - 0 = 0`, + `0 - 0.1 = -0.1`, + `0 - 0.1i = -0.1i`, + `1e100 - 1e100 = 0`, + `? - 0 = ?`, + `0 - ? = ?`, + + `0 * 0 = 0`, + `1 * 0.1 = 0.1`, + `1 * 0.1i = 0.1i`, + `1i * 1i = -1`, + `? * 0 = ?`, + `0 * ? = ?`, + + `0 / 0 = "division_by_zero"`, + `10 / 2 = 5`, + `5 / 3 = 5/3`, + `5i / 3i = 5/3`, + `? / 0 = ?`, + `0 / ? = ?`, + + `0 % 0 = "runtime_error:_integer_divide_by_zero"`, // TODO(gri) should be the same as for / + `10 % 3 = 1`, + `? % 0 = ?`, + `0 % ? = ?`, + + `0 & 0 = 0`, + `12345 & 0 = 0`, + `0xff & 0xf = 0xf`, + `? & 0 = ?`, + `0 & ? = ?`, + + `0 | 0 = 0`, + `12345 | 0 = 12345`, + `0xb | 0xa0 = 0xab`, + `? | 0 = ?`, + `0 | ? = ?`, + + `0 ^ 0 = 0`, + `1 ^ -1 = -2`, + `? ^ 0 = ?`, + `0 ^ ? = ?`, + + `0 &^ 0 = 0`, + `0xf &^ 1 = 0xe`, + `1 &^ 0xf = 0`, + // etc. + + // shifts + `0 << 0 = 0`, + `1 << 10 = 1024`, + `0 >> 0 = 0`, + `1024 >> 10 == 1`, + `? << 0 == ?`, + `? >> 10 == ?`, + // etc. + + // comparisons + `false == false = true`, + `false == true = false`, + `true == false = false`, + `true == true = true`, + + `false != false = false`, + `false != true = true`, + `true != false = true`, + `true != true = false`, + + `"foo" == "bar" = false`, + `"foo" != "bar" = true`, + `"foo" < "bar" = false`, + `"foo" <= "bar" = false`, + `"foo" > "bar" = true`, + `"foo" >= "bar" = true`, + + `0 == 0 = true`, + `0 != 0 = false`, + `0 < 10 = true`, + `10 <= 10 = true`, + `0 > 10 = false`, + `10 >= 10 = true`, + + `1/123456789 == 1/123456789 == true`, + `1/123456789 != 1/123456789 == false`, + `1/123456789 < 1/123456788 == true`, + `1/123456788 <= 1/123456789 == false`, + `0.11 > 0.11 = false`, + `0.11 >= 0.11 = true`, + + `? == 0 = false`, + `? != 0 = false`, + `? < 10 = false`, + `? <= 10 = false`, + `? > 10 = false`, + `? >= 10 = false`, + + `0 == ? = false`, + `0 != ? = false`, + `0 < ? = false`, + `10 <= ? = false`, + `0 > ? = false`, + `10 >= ? = false`, + + // etc. +} + +func TestOps(t *testing.T) { + for _, test := range opTests { + a := strings.Split(test, " ") + i := 0 // operator index + + var x, x0 Value + switch len(a) { + case 4: + // unary operation + case 5: + // binary operation + x, x0 = val(a[0]), val(a[0]) + i = 1 + default: + t.Errorf("invalid test case: %s", test) + continue + } + + op, ok := optab[a[i]] + if !ok { + panic("missing optab entry for " + a[i]) + } + + y, y0 := val(a[i+1]), val(a[i+1]) + + got := doOp(x, op, y) + want := val(a[i+3]) + if !eql(got, want) { + t.Errorf("%s: got %s; want %s", test, got, want) + } + if x0 != nil && !eql(x, x0) { + t.Errorf("%s: x changed to %s", test, x) + } + if !eql(y, y0) { + t.Errorf("%s: y changed to %s", test, y) + } + } +} + +func eql(x, y Value) bool { + _, ux := x.(unknownVal) + _, uy := y.(unknownVal) + if ux || uy { + return ux == uy + } + return Compare(x, token.EQL, y) +} + +// ---------------------------------------------------------------------------- +// Support functions + +func val(lit string) Value { + if len(lit) == 0 { + return MakeUnknown() + } + + switch lit { + case "?": + return MakeUnknown() + case "true": + return MakeBool(true) + case "false": + return MakeBool(false) + } + + tok := token.INT + switch first, last := lit[0], lit[len(lit)-1]; { + case first == '"' || first == '`': + tok = token.STRING + lit = strings.Replace(lit, "_", " ", -1) + case first == '\'': + tok = token.CHAR + case last == 'i': + tok = token.IMAG + default: + if !strings.HasPrefix(lit, "0x") && strings.ContainsAny(lit, "./Ee") { + tok = token.FLOAT + } + } + + return MakeFromLiteral(lit, tok) +} + +var optab = map[string]token.Token{ + "!": token.NOT, + + "+": token.ADD, + "-": token.SUB, + "*": token.MUL, + "/": token.QUO, + "%": token.REM, + + "<<": token.SHL, + ">>": token.SHR, + + "&": token.AND, + "|": token.OR, + "^": token.XOR, + "&^": token.AND_NOT, + + "==": token.EQL, + "!=": token.NEQ, + "<": token.LSS, + "<=": token.LEQ, + ">": token.GTR, + ">=": token.GEQ, +} + +func panicHandler(v *Value) { + switch p := recover().(type) { + case nil: + // nothing to do + case string: + *v = MakeString(p) + case error: + *v = MakeString(p.Error()) + default: + panic(p) + } +} + +func doOp(x Value, op token.Token, y Value) (z Value) { + defer panicHandler(&z) + + if x == nil { + return UnaryOp(op, y, -1) + } + + switch op { + case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ: + return MakeBool(Compare(x, op, y)) + case token.SHL, token.SHR: + s, _ := Int64Val(y) + return Shift(x, op, uint(s)) + default: + return BinaryOp(x, op, y) + } +} + +// ---------------------------------------------------------------------------- +// Other tests + +var fracTests = []string{ + "0 0 1", + "1 1 1", + "-1 -1 1", + "1.2 6 5", + "-0.991 -991 1000", + "1e100 1e100 1", +} + +func TestFractions(t *testing.T) { + for _, test := range fracTests { + a := strings.Split(test, " ") + if len(a) != 3 { + t.Errorf("invalid test case: %s", test) + continue + } + + x := val(a[0]) + n := val(a[1]) + d := val(a[2]) + + if got := Num(x); !eql(got, n) { + t.Errorf("%s: got num = %s; want %s", test, got, n) + } + + if got := Denom(x); !eql(got, d) { + t.Errorf("%s: got denom = %s; want %s", test, got, d) + } + } +} + +var bytesTests = []string{ + "0", + "1", + "123456789", + "123456789012345678901234567890123456789012345678901234567890", +} + +func TestBytes(t *testing.T) { + for _, test := range bytesTests { + x := val(test) + bytes := Bytes(x) + + // special case 0 + if Sign(x) == 0 && len(bytes) != 0 { + t.Errorf("%s: got %v; want empty byte slice", test, bytes) + } + + if n := len(bytes); n > 0 && bytes[n-1] == 0 { + t.Errorf("%s: got %v; want no leading 0 byte", test, bytes) + } + + if got := MakeFromBytes(bytes); !eql(got, x) { + t.Errorf("%s: got %s; want %s (bytes = %v)", test, got, x, bytes) + } + } +} + +func TestUnknown(t *testing.T) { + u := MakeUnknown() + var values = []Value{ + u, + MakeBool(false), // token.ADD ok below, operation is never considered + MakeString(""), + MakeInt64(1), + MakeFromLiteral("-1234567890123456789012345678901234567890", token.INT), + MakeFloat64(1.2), + MakeImag(MakeFloat64(1.2)), + } + for _, val := range values { + x, y := val, u + for i := range [2]int{} { + if i == 1 { + x, y = y, x + } + if got := BinaryOp(x, token.ADD, y); got.Kind() != Unknown { + t.Errorf("%s + %s: got %s; want %s", x, y, got, u) + } + if got := Compare(x, token.EQL, y); got { + t.Errorf("%s == %s: got true; want false", x, y) + } + } + } +} diff --git a/src/gosubli.me/something-borrowed/exact/go13.go b/src/gosubli.me/something-borrowed/exact/go13.go new file mode 100644 index 00000000..1016c141 --- /dev/null +++ b/src/gosubli.me/something-borrowed/exact/go13.go @@ -0,0 +1,24 @@ +// 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. + +// +build !go1.4 + +package exact + +import ( + "math" + "math/big" +) + +func ratToFloat32(x *big.Rat) (float32, bool) { + // Before 1.4, there's no Rat.Float32. + // Emulate it, albeit at the cost of + // imprecision in corner cases. + x64, exact := x.Float64() + x32 := float32(x64) + if math.IsInf(float64(x32), 0) { + exact = false + } + return x32, exact +} diff --git a/src/gosubli.me/something-borrowed/exact/go14.go b/src/gosubli.me/something-borrowed/exact/go14.go new file mode 100644 index 00000000..b86e5d26 --- /dev/null +++ b/src/gosubli.me/something-borrowed/exact/go14.go @@ -0,0 +1,13 @@ +// 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. + +// +build go1.4 + +package exact + +import "math/big" + +func ratToFloat32(x *big.Rat) (float32, bool) { + return x.Float32() +} diff --git a/src/gosubli.me/something-borrowed/types/exportdata.go b/src/gosubli.me/something-borrowed/gcimporter/exportdata.go similarity index 71% rename from src/gosubli.me/something-borrowed/types/exportdata.go rename to src/gosubli.me/something-borrowed/gcimporter/exportdata.go index 1f6a3c72..657742bb 100644 --- a/src/gosubli.me/something-borrowed/types/exportdata.go +++ b/src/gosubli.me/something-borrowed/gcimporter/exportdata.go @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file implements FindGcExportData. +// This file implements FindExportData. -package types +package gcimporter import ( "bufio" @@ -36,50 +36,47 @@ func readGopackHeader(r *bufio.Reader) (name string, size int, err error) { return } -// FindGcExportData positions the reader r at the beginning of the +// 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. // -func FindGcExportData(r *bufio.Reader) (err error) { +func FindExportData(r *bufio.Reader) (err error) { // Read first line to make sure this is an object file. line, err := r.ReadSlice('\n') if err != nil { return } if string(line) == "!\n" { - // Archive file. Scan to __.PKGDEF, which should - // be second archive entry. + // Archive file. Scan to __.PKGDEF. var name string var size int - - // First entry should be __.GOSYMDEF. - // Older archives used __.SYMDEF, so allow that too. - // Read and discard. if name, size, err = readGopackHeader(r); err != nil { return } - if name != "__.SYMDEF" && name != "__.GOSYMDEF" { - err = errors.New("go archive does not begin with __.SYMDEF or __.GOSYMDEF") - return - } - const block = 4096 - tmp := make([]byte, block) - for size > 0 { - n := size - if n > block { - n = block + + // Optional leading __.GOSYMDEF or __.SYMDEF. + // Read and discard. + if name == "__.SYMDEF" || name == "__.GOSYMDEF" { + const block = 4096 + tmp := make([]byte, block) + for size > 0 { + n := size + if n > block { + n = block + } + if _, err = io.ReadFull(r, tmp[:n]); err != nil { + return + } + size -= n } - if _, err = io.ReadFull(r, tmp[:n]); err != nil { + + if name, size, err = readGopackHeader(r); err != nil { return } - size -= n } - // Second entry should be __.PKGDEF. - if name, size, err = readGopackHeader(r); err != nil { - return - } + // First real entry should be __.PKGDEF. if name != "__.PKGDEF" { err = errors.New("go archive is missing __.PKGDEF") return diff --git a/src/gosubli.me/something-borrowed/types/gcimporter.go b/src/gosubli.me/something-borrowed/gcimporter/gcimporter.go similarity index 60% rename from src/gosubli.me/something-borrowed/types/gcimporter.go rename to src/gosubli.me/something-borrowed/gcimporter/gcimporter.go index 49d1aa55..1574c70b 100644 --- a/src/gosubli.me/something-borrowed/types/gcimporter.go +++ b/src/gosubli.me/something-borrowed/gcimporter/gcimporter.go @@ -2,31 +2,32 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file implements an Importer for gc-generated object files. - -package types +// Package gcimporter implements Import for gc-generated object files. +// Importing this package installs Import as go/types.DefaultImport. +package gcimporter import ( "bufio" "errors" "fmt" - "go/ast" "go/build" "go/token" "io" - "math/big" "os" "path/filepath" "strconv" "strings" "text/scanner" + + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" + "gosubli.me/something-borrowed/types" //"golang.org/x/tools/go/types" ) -func _gosublime_strings_TrimSuffix(s, suffix string) string { - if strings.HasSuffix(s, suffix) { - return s[:len(s)-len(suffix)] - } - return s +// debugging/development support +const debug = false + +func init() { + types.DefaultImport = Import } var pkgExts = [...]string{".a", ".5", ".6", ".8"} @@ -51,7 +52,7 @@ func FindPkg(path, srcDir string) (filename, id string) { if bp.PkgObj == "" { return } - noext = _gosublime_strings_TrimSuffix(bp.PkgObj, ".a") + noext = strings.TrimSuffix(bp.PkgObj, ".a") case build.IsLocalImport(path): // "./x" -> "/this/directory/x.ext", "/this/directory/x" @@ -77,7 +78,7 @@ func FindPkg(path, srcDir string) (filename, id string) { return } -// GcImportData imports a package by reading the gc-generated export data, +// ImportData imports a package by reading the gc-generated export data, // adds the corresponding package object to the imports map indexed by id, // and returns the object. // @@ -89,30 +90,34 @@ func FindPkg(path, srcDir string) (filename, id string) { // can be used directly, and there is no need to call this function (but // there is also no harm but for extra time used). // -func GcImportData(imports map[string]*Package, filename, id string, data *bufio.Reader) (pkg *Package, err error) { - // support for gcParser error handling +func ImportData(imports map[string]*types.Package, filename, id string, data io.Reader) (pkg *types.Package, err error) { + // support for parser error handling defer func() { - if r := recover(); r != nil { - err = r.(importError) // will re-panic if r is not an importError + switch r := recover().(type) { + case nil: + // nothing to do + case importError: + err = r + default: + panic(r) // internal error } }() - var p gcParser + var p parser p.init(filename, id, data, imports) pkg = p.parseExport() return } -// GcImport imports a gc-generated package given its import path, adds the +// Import imports a gc-generated package given its import path, adds the // corresponding package object to the imports map, and returns the object. // Local import paths are interpreted relative to the current working directory. // The imports map must contains all packages already imported. -// GcImport satisfies the ast.Importer signature. // -func GcImport(imports map[string]*Package, path string) (pkg *Package, err error) { +func Import(imports map[string]*types.Package, path string) (pkg *types.Package, err error) { if path == "unsafe" { - return Unsafe, nil + return types.Unsafe, nil } srcDir := "." @@ -125,12 +130,12 @@ func GcImport(imports map[string]*Package, path string) (pkg *Package, err error filename, id := FindPkg(path, srcDir) if filename == "" { - err = errors.New("can't find import: " + id) + err = fmt.Errorf("can't find import: %s", id) return } // no need to re-import if the package was imported completely before - if pkg = imports[id]; pkg != nil && pkg.Complete { + if pkg = imports[id]; pkg != nil && pkg.Complete() { return } @@ -148,29 +153,35 @@ func GcImport(imports map[string]*Package, path string) (pkg *Package, err error }() buf := bufio.NewReader(f) - if err = FindGcExportData(buf); err != nil { + if err = FindExportData(buf); err != nil { return } - pkg, err = GcImportData(imports, filename, id, buf) + pkg, err = ImportData(imports, filename, id, buf) return } // ---------------------------------------------------------------------------- -// gcParser +// Parser + +// TODO(gri) Imported objects don't have position information. +// Ideally use the debug table line info; alternatively +// create some fake position (or the position of the +// import). That way error messages referring to imported +// objects can print meaningful information. -// gcParser parses the exports inside a gc compiler-produced +// parser parses the exports inside a gc compiler-produced // object/archive file and populates its scope with the results. -type gcParser struct { +type parser struct { scanner scanner.Scanner - tok rune // current token - lit string // literal string; only valid for Ident, Int, String tokens - id string // package id of imported package - imports map[string]*Package // package id -> package object + tok rune // current token + lit string // literal string; only valid for Ident, Int, String tokens + id string // package id of imported package + imports map[string]*types.Package // package id -> package object } -func (p *gcParser) init(filename, id string, src io.Reader, imports map[string]*Package) { +func (p *parser) init(filename, id string, src io.Reader, imports map[string]*types.Package) { p.scanner.Init(src) p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) } p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments @@ -179,18 +190,17 @@ func (p *gcParser) init(filename, id string, src io.Reader, imports map[string]* p.next() p.id = id p.imports = imports - // leave for debugging - if false { + if debug { // check consistency of imports map for _, pkg := range imports { - if pkg.Name == "" { - fmt.Printf("no package name for %s\n", pkg.Path) + if pkg.Name() == "" { + fmt.Printf("no package name for %s\n", pkg.Path()) } } } } -func (p *gcParser) next() { +func (p *parser) next() { p.tok = p.scanner.Scan() switch p.tok { case scanner.Ident, scanner.Int, scanner.Char, scanner.String, '·': @@ -198,54 +208,20 @@ func (p *gcParser) next() { default: p.lit = "" } - // leave for debugging - if false { + if debug { fmt.Printf("%s: %q -> %q\n", scanner.TokenString(p.tok), p.scanner.TokenText(), p.lit) } } -func declConst(pkg *Package, name string) *Const { - // the constant may have been imported before - if it exists - // already in the respective scope, return that constant - scope := pkg.Scope - if obj := scope.Lookup(name); obj != nil { - return obj.(*Const) - } - // otherwise create a new constant and insert it into the scope - obj := &Const{Pkg: pkg, Name: name} - scope.Insert(obj) - return obj -} - -func declTypeName(pkg *Package, name string) *TypeName { - scope := pkg.Scope +func declTypeName(pkg *types.Package, name string) *types.TypeName { + scope := pkg.Scope() if obj := scope.Lookup(name); obj != nil { - return obj.(*TypeName) + return obj.(*types.TypeName) } - obj := &TypeName{Pkg: pkg, Name: name} + obj := types.NewTypeName(token.NoPos, pkg, name, nil) // a named type may be referred to before the underlying type // is known - set it up - obj.Type = &NamedType{Obj: obj} - scope.Insert(obj) - return obj -} - -func declVar(pkg *Package, name string) *Var { - scope := pkg.Scope - if obj := scope.Lookup(name); obj != nil { - return obj.(*Var) - } - obj := &Var{Pkg: pkg, Name: name} - scope.Insert(obj) - return obj -} - -func declFunc(pkg *Package, name string) *Func { - scope := pkg.Scope - if obj := scope.Lookup(name); obj != nil { - return obj.(*Func) - } - obj := &Func{Pkg: pkg, Name: name} + types.NewNamed(obj, nil, nil) scope.Insert(obj) return obj } @@ -263,7 +239,7 @@ func (e importError) Error() string { return fmt.Sprintf("import error %s (byte offset = %d): %s", e.pos, e.pos.Offset, e.err) } -func (p *gcParser) error(err interface{}) { +func (p *parser) error(err interface{}) { if s, ok := err.(string); ok { err = errors.New(s) } @@ -271,11 +247,11 @@ func (p *gcParser) error(err interface{}) { panic(importError{p.scanner.Pos(), err.(error)}) } -func (p *gcParser) errorf(format string, args ...interface{}) { +func (p *parser) errorf(format string, args ...interface{}) { p.error(fmt.Sprintf(format, args...)) } -func (p *gcParser) expect(tok rune) string { +func (p *parser) expect(tok rune) string { lit := p.lit if p.tok != tok { p.errorf("expected %s, got %s (%s)", scanner.TokenString(tok), scanner.TokenString(p.tok), lit) @@ -284,7 +260,7 @@ func (p *gcParser) expect(tok rune) string { return lit } -func (p *gcParser) expectSpecial(tok string) { +func (p *parser) expectSpecial(tok string) { sep := 'x' // not white space i := 0 for i < len(tok) && p.tok == rune(tok[i]) && sep > ' ' { @@ -297,7 +273,7 @@ func (p *gcParser) expectSpecial(tok string) { } } -func (p *gcParser) expectKeyword(keyword string) { +func (p *parser) expectKeyword(keyword string) { lit := p.expect(scanner.Ident) if lit != keyword { p.errorf("expected keyword %s, got %q", keyword, lit) @@ -309,7 +285,7 @@ func (p *gcParser) expectKeyword(keyword string) { // PackageId = string_lit . // -func (p *gcParser) parsePackageId() string { +func (p *parser) parsePackageId() string { id, err := strconv.Unquote(p.expect(scanner.String)) if err != nil { p.error(err) @@ -324,12 +300,12 @@ func (p *gcParser) parsePackageId() string { // PackageName = ident . // -func (p *gcParser) parsePackageName() string { +func (p *parser) parsePackageName() string { return p.expect(scanner.Ident) } // dotIdentifier = ( ident | '·' ) { ident | int | '·' } . -func (p *gcParser) parseDotIdent() string { +func (p *parser) parseDotIdent() string { ident := "" if p.tok != scanner.Int { sep := 'x' // not white space @@ -345,13 +321,18 @@ func (p *gcParser) parseDotIdent() string { return ident } -// QualifiedName = "@" PackageId "." dotIdentifier . +// QualifiedName = "@" PackageId "." ( "?" | dotIdentifier ) . // -func (p *gcParser) parseQualifiedName() (id, name string) { +func (p *parser) parseQualifiedName() (id, name string) { p.expect('@') id = p.parsePackageId() p.expect('.') - name = p.parseDotIdent() + // Per rev f280b8a485fd (10/2/2013), qualified names may be used for anonymous fields. + if p.tok == '?' { + p.next() + } else { + name = p.parseDotIdent() + } return } @@ -359,23 +340,23 @@ func (p *gcParser) parseQualifiedName() (id, name string) { // not found but we have a package name, create the package and // add it to the p.imports map. // -func (p *gcParser) getPkg(id, name string) *Package { +func (p *parser) getPkg(id, name string) *types.Package { // package unsafe is not in the imports map - handle explicitly if id == "unsafe" { - return Unsafe + return types.Unsafe } pkg := p.imports[id] if pkg == nil && name != "" { - pkg = &Package{Name: name, Path: id, Scope: new(Scope)} + pkg = types.NewPackage(id, name) p.imports[id] = pkg } return pkg } // parseExportedName is like parseQualifiedName, but -// the package id is resolved to an imported *Package. +// the package id is resolved to an imported *types.Package. // -func (p *gcParser) parseExportedName() (pkg *Package, name string) { +func (p *parser) parseExportedName() (pkg *types.Package, name string) { id, name := p.parseQualifiedName() pkg = p.getPkg(id, "") if pkg == nil { @@ -389,11 +370,11 @@ func (p *gcParser) parseExportedName() (pkg *Package, name string) { // BasicType = identifier . // -func (p *gcParser) parseBasicType() Type { +func (p *parser) parseBasicType() types.Type { id := p.expect(scanner.Ident) - obj := Universe.Lookup(id) - if obj, ok := obj.(*TypeName); ok { - return obj.Type + obj := types.Universe.Lookup(id) + if obj, ok := obj.(*types.TypeName); ok { + return obj.Type() } p.errorf("not a basic type: %s", id) return nil @@ -401,48 +382,48 @@ func (p *gcParser) parseBasicType() Type { // ArrayType = "[" int_lit "]" Type . // -func (p *gcParser) parseArrayType() Type { +func (p *parser) parseArrayType() types.Type { // "[" already consumed and lookahead known not to be "]" lit := p.expect(scanner.Int) p.expect(']') - elt := p.parseType() + elem := p.parseType() n, err := strconv.ParseInt(lit, 10, 64) if err != nil { p.error(err) } - return &Array{Len: n, Elt: elt} + return types.NewArray(elem, n) } // MapType = "map" "[" Type "]" Type . // -func (p *gcParser) parseMapType() Type { +func (p *parser) parseMapType() types.Type { p.expectKeyword("map") p.expect('[') key := p.parseType() p.expect(']') - elt := p.parseType() - return &Map{Key: key, Elt: elt} + elem := p.parseType() + return types.NewMap(key, elem) } // Name = identifier | "?" | QualifiedName . // -// If materializePkg is set, a package is returned for fully qualified names. -// That package may be a fake package (without name, scope, and not in the -// p.imports map), created for the sole purpose of providing a package path -// for QualifiedNames. Fake packages are created when the package id is not -// found in the p.imports map; we cannot create a real package in that case -// because we don't have a package name. -// -// TODO(gri): consider changing QualifiedIdents to (path, name) pairs to -// simplify this code. +// If materializePkg is set, the returned package is guaranteed to be set. +// For fully qualified names, the returned package may be a fake package +// (without name, scope, and not in the p.imports map), created for the +// sole purpose of providing a package path. Fake packages are created +// when the package id is not found in the p.imports map; in that case +// we cannot create a real package because we don't have a package name. +// For non-qualified names, the returned package is the imported package. // -func (p *gcParser) parseName(materializePkg bool) (pkg *Package, name string) { +func (p *parser) parseName(materializePkg bool) (pkg *types.Package, name string) { switch p.tok { case scanner.Ident: + pkg = p.imports[p.id] name = p.lit p.next() case '?': // anonymous + pkg = p.imports[p.id] p.next() case '@': // exported name prefixed with package path @@ -453,7 +434,7 @@ func (p *gcParser) parseName(materializePkg bool) (pkg *Package, name string) { // doesn't exist yet, create a fake package instead pkg = p.getPkg(id, "") if pkg == nil { - pkg = &Package{Path: id} + pkg = types.NewPackage(id, "") } } default: @@ -462,72 +443,102 @@ func (p *gcParser) parseName(materializePkg bool) (pkg *Package, name string) { return } +func deref(typ types.Type) types.Type { + if p, _ := typ.(*types.Pointer); p != nil { + return p.Elem() + } + return typ +} + // Field = Name Type [ string_lit ] . // -func (p *gcParser) parseField() *Field { - var f Field - f.Pkg, f.Name = p.parseName(true) - f.Type = p.parseType() - if p.tok == scanner.String { - f.Tag = p.expect(scanner.String) - } - if f.Name == "" { +func (p *parser) parseField() (*types.Var, string) { + pkg, name := p.parseName(true) + typ := p.parseType() + anonymous := false + if name == "" { // anonymous field - typ must be T or *T and T must be a type name - if typ, ok := deref(f.Type).(*NamedType); ok && typ.Obj != nil { - f.Name = typ.Obj.GetName() - f.IsAnonymous = true - } else { + switch typ := deref(typ).(type) { + case *types.Basic: // basic types are named types + pkg = nil + name = typ.Name() + case *types.Named: + name = typ.Obj().Name() + default: p.errorf("anonymous field expected") } + anonymous = true + } + tag := "" + if p.tok == scanner.String { + s := p.expect(scanner.String) + var err error + tag, err = strconv.Unquote(s) + if err != nil { + p.errorf("invalid struct tag %s: %s", s, err) + } } - return &f + return types.NewField(token.NoPos, pkg, name, typ, anonymous), tag } // StructType = "struct" "{" [ FieldList ] "}" . // FieldList = Field { ";" Field } . // -func (p *gcParser) parseStructType() Type { - var fields []*Field +func (p *parser) parseStructType() types.Type { + var fields []*types.Var + var tags []string p.expectKeyword("struct") p.expect('{') - for p.tok != '}' { - if len(fields) > 0 { + for i := 0; p.tok != '}' && p.tok != scanner.EOF; i++ { + if i > 0 { p.expect(';') } - fields = append(fields, p.parseField()) + fld, tag := p.parseField() + if tag != "" && tags == nil { + tags = make([]string, i) + } + if tags != nil { + tags = append(tags, tag) + } + fields = append(fields, fld) } p.expect('}') - return &Struct{Fields: fields} + return types.NewStruct(fields, tags) } // Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] . // -func (p *gcParser) parseParameter() (par *Var, isVariadic bool) { +func (p *parser) parseParameter() (par *types.Var, isVariadic bool) { _, name := p.parseName(false) - if name == "" { - name = "_" // cannot access unnamed identifiers + // remove gc-specific parameter numbering + if i := strings.Index(name, "·"); i >= 0 { + name = name[:i] } if p.tok == '.' { p.expectSpecial("...") isVariadic = true } typ := p.parseType() + if isVariadic { + typ = types.NewSlice(typ) + } // ignore argument tag (e.g. "noescape") if p.tok == scanner.String { p.next() } - par = &Var{Name: name, Type: typ} // Pkg == nil + // TODO(gri) should we provide a package? + par = types.NewVar(token.NoPos, nil, name, typ) return } // Parameters = "(" [ ParameterList ] ")" . // ParameterList = { Parameter "," } Parameter . // -func (p *gcParser) parseParameters() (list []*Var, isVariadic bool) { +func (p *parser) parseParameters() (list []*types.Var, isVariadic bool) { p.expect('(') - for p.tok != ')' { + for p.tok != ')' && p.tok != scanner.EOF { if len(list) > 0 { p.expect(',') } @@ -548,11 +559,11 @@ func (p *gcParser) parseParameters() (list []*Var, isVariadic bool) { // Signature = Parameters [ Result ] . // Result = Type | Parameters . // -func (p *gcParser) parseSignature() *Signature { +func (p *parser) parseSignature(recv *types.Var) *types.Signature { params, isVariadic := p.parseParameters() // optional result type - var results []*Var + var results []*types.Var if p.tok == '(' { var variadic bool results, variadic = p.parseParameters() @@ -561,7 +572,7 @@ func (p *gcParser) parseSignature() *Signature { } } - return &Signature{Params: params, Results: results, IsVariadic: isVariadic} + return types.NewSignature(nil, recv, types.NewTuple(params...), types.NewTuple(results...), isVariadic) } // InterfaceType = "interface" "{" [ MethodList ] "}" . @@ -572,41 +583,43 @@ func (p *gcParser) parseSignature() *Signature { // by the compiler and thus embedded interfaces are never // visible in the export data. // -func (p *gcParser) parseInterfaceType() Type { - var methods []*Method +func (p *parser) parseInterfaceType() types.Type { + var methods []*types.Func p.expectKeyword("interface") p.expect('{') - for p.tok != '}' { - if len(methods) > 0 { + for i := 0; p.tok != '}' && p.tok != scanner.EOF; i++ { + if i > 0 { p.expect(';') } pkg, name := p.parseName(true) - typ := p.parseSignature() - methods = append(methods, &Method{QualifiedName{pkg, name}, typ}) + sig := p.parseSignature(nil) + methods = append(methods, types.NewFunc(token.NoPos, pkg, name, sig)) } p.expect('}') - return &Interface{Methods: methods} + // Complete requires the type's embedded interfaces to be fully defined, + // but we do not define any + return types.NewInterface(methods, nil).Complete() } // ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type . // -func (p *gcParser) parseChanType() Type { - dir := ast.SEND | ast.RECV +func (p *parser) parseChanType() types.Type { + dir := types.SendRecv if p.tok == scanner.Ident { p.expectKeyword("chan") if p.tok == '<' { p.expectSpecial("<-") - dir = ast.SEND + dir = types.SendOnly } } else { p.expectSpecial("<-") p.expectKeyword("chan") - dir = ast.RECV + dir = types.RecvOnly } - elt := p.parseType() - return &Chan{Dir: dir, Elt: elt} + elem := p.parseType() + return types.NewChan(dir, elem) } // Type = @@ -620,7 +633,7 @@ func (p *gcParser) parseChanType() Type { // PointerType = "*" Type . // FuncType = "func" Signature . // -func (p *gcParser) parseType() Type { +func (p *parser) parseType() types.Type { switch p.tok { case scanner.Ident: switch p.lit { @@ -631,7 +644,7 @@ func (p *gcParser) parseType() Type { case "func": // FuncType p.next() - return p.parseSignature() + return p.parseSignature(nil) case "interface": return p.parseInterfaceType() case "map": @@ -642,19 +655,19 @@ func (p *gcParser) parseType() Type { case '@': // TypeName pkg, name := p.parseExportedName() - return declTypeName(pkg, name).Type + return declTypeName(pkg, name).Type() case '[': p.next() // look ahead if p.tok == ']' { // SliceType p.next() - return &Slice{Elt: p.parseType()} + return types.NewSlice(p.parseType()) } return p.parseArrayType() case '*': // PointerType p.next() - return &Pointer{Base: p.parseType()} + return types.NewPointer(p.parseType()) case '<': return p.parseChanType() case '(': @@ -673,7 +686,7 @@ func (p *gcParser) parseType() Type { // ImportDecl = "import" PackageName PackageId . // -func (p *gcParser) parseImportDecl() { +func (p *parser) parseImportDecl() { p.expectKeyword("import") name := p.parsePackageName() p.getPkg(p.parsePackageId(), name) @@ -681,57 +694,51 @@ func (p *gcParser) parseImportDecl() { // int_lit = [ "+" | "-" ] { "0" ... "9" } . // -func (p *gcParser) parseInt() (neg bool, val string) { +func (p *parser) parseInt() string { + s := "" switch p.tok { case '-': - neg = true - fallthrough + s = "-" + p.next() case '+': p.next() } - val = p.expect(scanner.Int) - return + return s + p.expect(scanner.Int) } // number = int_lit [ "p" int_lit ] . // -func (p *gcParser) parseNumber() (x operand) { - x.mode = constant - +func (p *parser) parseNumber() (typ *types.Basic, val exact.Value) { // mantissa - neg, val := p.parseInt() - mant, ok := new(big.Int).SetString(val, 0) - assert(ok) - if neg { - mant.Neg(mant) + mant := exact.MakeFromLiteral(p.parseInt(), token.INT) + if mant == nil { + panic("invalid mantissa") } if p.lit == "p" { // exponent (base 2) p.next() - neg, val = p.parseInt() - exp64, err := strconv.ParseUint(val, 10, 0) + exp, err := strconv.ParseInt(p.parseInt(), 10, 0) if err != nil { p.error(err) } - exp := uint(exp64) - if neg { - denom := big.NewInt(1) - denom.Lsh(denom, exp) - x.typ = Typ[UntypedFloat] - x.val = normalizeRatConst(new(big.Rat).SetFrac(mant, denom)) + if exp < 0 { + denom := exact.MakeInt64(1) + denom = exact.Shift(denom, token.SHL, uint(-exp)) + typ = types.Typ[types.UntypedFloat] + val = exact.BinaryOp(mant, token.QUO, denom) return } if exp > 0 { - mant.Lsh(mant, exp) + mant = exact.Shift(mant, token.SHL, uint(exp)) } - x.typ = Typ[UntypedFloat] - x.val = normalizeIntConst(mant) + typ = types.Typ[types.UntypedFloat] + val = mant return } - x.typ = Typ[UntypedInt] - x.val = normalizeIntConst(mant) + typ = types.Typ[types.UntypedInt] + val = mant return } @@ -742,28 +749,31 @@ func (p *gcParser) parseNumber() (x operand) { // rune_lit = "(" int_lit "+" int_lit ")" . // string_lit = `"` { unicode_char } `"` . // -func (p *gcParser) parseConstDecl() { +func (p *parser) parseConstDecl() { p.expectKeyword("const") pkg, name := p.parseExportedName() - obj := declConst(pkg, name) - var x operand + + var typ0 types.Type if p.tok != '=' { - obj.Type = p.parseType() + typ0 = p.parseType() } + p.expect('=') + var typ types.Type + var val exact.Value switch p.tok { case scanner.Ident: // bool_lit if p.lit != "true" && p.lit != "false" { p.error("expected true or false") } - x.typ = Typ[UntypedBool] - x.val = p.lit == "true" + typ = types.Typ[types.UntypedBool] + val = exact.MakeBool(p.lit == "true") p.next() case '-', scanner.Int: // int_lit - x = p.parseNumber() + typ, val = p.parseNumber() case '(': // complex_lit or rune_lit @@ -771,44 +781,45 @@ func (p *gcParser) parseConstDecl() { if p.tok == scanner.Char { p.next() p.expect('+') - x = p.parseNumber() - x.typ = Typ[UntypedRune] + typ = types.Typ[types.UntypedRune] + _, val = p.parseNumber() p.expect(')') break } - re := p.parseNumber() + _, re := p.parseNumber() p.expect('+') - im := p.parseNumber() + _, im := p.parseNumber() p.expectKeyword("i") p.expect(')') - x.typ = Typ[UntypedComplex] - // TODO(gri) fix this - _, _ = re, im - x.val = zeroConst + typ = types.Typ[types.UntypedComplex] + val = exact.BinaryOp(re, token.ADD, exact.MakeImag(im)) case scanner.Char: // rune_lit - x.setConst(token.CHAR, p.lit) + typ = types.Typ[types.UntypedRune] + val = exact.MakeFromLiteral(p.lit, token.CHAR) p.next() case scanner.String: // string_lit - x.setConst(token.STRING, p.lit) + typ = types.Typ[types.UntypedString] + val = exact.MakeFromLiteral(p.lit, token.STRING) p.next() default: p.errorf("expected literal got %s", scanner.TokenString(p.tok)) } - if obj.Type == nil { - obj.Type = x.typ + + if typ0 == nil { + typ0 = typ } - assert(x.val != nil) - obj.Val = x.val + + pkg.Scope().Insert(types.NewConst(token.NoPos, pkg, name, typ0, val)) } // TypeDecl = "type" ExportedName Type . // -func (p *gcParser) parseTypeDecl() { +func (p *parser) parseTypeDecl() { p.expectKeyword("type") pkg, name := p.parseExportedName() obj := declTypeName(pkg, name) @@ -820,25 +831,25 @@ func (p *gcParser) parseTypeDecl() { // a given type declaration. typ := p.parseType() - if name := obj.Type.(*NamedType); name.Underlying == nil { - name.Underlying = typ + if name := obj.Type().(*types.Named); name.Underlying() == nil { + name.SetUnderlying(typ) } } // VarDecl = "var" ExportedName Type . // -func (p *gcParser) parseVarDecl() { +func (p *parser) parseVarDecl() { p.expectKeyword("var") pkg, name := p.parseExportedName() - obj := declVar(pkg, name) - obj.Type = p.parseType() + typ := p.parseType() + pkg.Scope().Insert(types.NewVar(token.NoPos, pkg, name, typ)) } // Func = Signature [ Body ] . // Body = "{" ... "}" . // -func (p *gcParser) parseFunc() *Signature { - sig := p.parseSignature() +func (p *parser) parseFunc(recv *types.Var) *types.Signature { + sig := p.parseSignature(recv) if p.tok == '{' { p.next() for i := 1; i > 0; p.next() { @@ -856,62 +867,57 @@ func (p *gcParser) parseFunc() *Signature { // MethodDecl = "func" Receiver Name Func . // Receiver = "(" ( identifier | "?" ) [ "*" ] ExportedName ")" . // -func (p *gcParser) parseMethodDecl() { +func (p *parser) parseMethodDecl() { // "func" already consumed p.expect('(') recv, _ := p.parseParameter() // receiver p.expect(')') // determine receiver base type object - typ := recv.Type - if ptr, ok := typ.(*Pointer); ok { - typ = ptr.Base - } - base := typ.(*NamedType) + base := deref(recv.Type()).(*types.Named) // parse method name, signature, and possibly inlined body - pkg, name := p.parseName(true) // unexported method names in imports are qualified with their package. - sig := p.parseFunc() - sig.Recv = recv + _, name := p.parseName(true) + sig := p.parseFunc(recv) + + // methods always belong to the same package as the base type object + pkg := base.Obj().Pkg() // add method to type unless type was imported before // and method exists already - // TODO(gri) investigate if this can be avoided - for _, m := range base.Methods { - if m.Name == name { - return // method was added before - } - } - base.Methods = append(base.Methods, &Method{QualifiedName{pkg, name}, sig}) + // TODO(gri) This leads to a quadratic algorithm - ok for now because method counts are small. + base.AddMethod(types.NewFunc(token.NoPos, pkg, name, sig)) } // FuncDecl = "func" ExportedName Func . // -func (p *gcParser) parseFuncDecl() { +func (p *parser) parseFuncDecl() { // "func" already consumed pkg, name := p.parseExportedName() - typ := p.parseFunc() - declFunc(pkg, name).Type = typ + typ := p.parseFunc(nil) + pkg.Scope().Insert(types.NewFunc(token.NoPos, pkg, name, typ)) } // Decl = [ ImportDecl | ConstDecl | TypeDecl | VarDecl | FuncDecl | MethodDecl ] "\n" . // -func (p *gcParser) parseDecl() { - switch p.lit { - case "import": - p.parseImportDecl() - case "const": - p.parseConstDecl() - case "type": - p.parseTypeDecl() - case "var": - p.parseVarDecl() - case "func": - p.next() // look ahead - if p.tok == '(' { - p.parseMethodDecl() - } else { - p.parseFuncDecl() +func (p *parser) parseDecl() { + if p.tok == scanner.Ident { + switch p.lit { + case "import": + p.parseImportDecl() + case "const": + p.parseConstDecl() + case "type": + p.parseTypeDecl() + case "var": + p.parseVarDecl() + case "func": + p.next() // look ahead + if p.tok == '(' { + p.parseMethodDecl() + } else { + p.parseFuncDecl() + } } } p.expect('\n') @@ -923,14 +929,12 @@ func (p *gcParser) parseDecl() { // Export = "PackageClause { Decl } "$$" . // PackageClause = "package" PackageName [ "safe" ] "\n" . // -func (p *gcParser) parseExport() *Package { +func (p *parser) parseExport() *types.Package { p.expectKeyword("package") name := p.parsePackageName() - if p.tok != '\n' { - // A package is safe if it was compiled with the -u flag, - // which disables the unsafe package. - // TODO(gri) remember "safe" package - p.expectKeyword("safe") + if p.tok == scanner.Ident && p.lit == "safe" { + // package was compiled with -u option - ignore + p.next() } p.expect('\n') @@ -951,7 +955,7 @@ func (p *gcParser) parseExport() *Package { } // package was imported completely and without errors - pkg.Complete = true + pkg.MarkComplete() return pkg } diff --git a/src/gosubli.me/something-borrowed/gcimporter/gcimporter_test.go b/src/gosubli.me/something-borrowed/gcimporter/gcimporter_test.go new file mode 100644 index 00000000..a20853ac --- /dev/null +++ b/src/gosubli.me/something-borrowed/gcimporter/gcimporter_test.go @@ -0,0 +1,216 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gcimporter + +import ( + "go/build" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "golang.org/x/tools/go/types" +) + +var gcPath string // Go compiler path + +func init() { + // determine compiler + var gc string + switch runtime.GOARCH { + case "386": + gc = "8g" + case "amd64": + gc = "6g" + case "arm": + gc = "5g" + default: + gcPath = "unknown-GOARCH-compiler" + return + } + gcPath = filepath.Join(build.ToolDir, gc) +} + +func compile(t *testing.T, dirname, filename string) string { + cmd := exec.Command(gcPath, filename) + cmd.Dir = dirname + out, err := cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("%s %s failed: %s", gcPath, filename, err) + } + archCh, _ := build.ArchChar(runtime.GOARCH) + // filename should end with ".go" + return filepath.Join(dirname, filename[:len(filename)-2]+archCh) +} + +// Use the same global imports map for all tests. The effect is +// as if all tested packages were imported into a single package. +var imports = make(map[string]*types.Package) + +func testPath(t *testing.T, path string) bool { + t0 := time.Now() + _, err := Import(imports, path) + if err != nil { + t.Errorf("testPath(%s): %s", path, err) + return false + } + t.Logf("testPath(%s): %v", path, time.Since(t0)) + return true +} + +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)) { + nimports++ + } + } + } + case f.IsDir(): + nimports += testDir(t, filepath.Join(dir, f.Name()), endTime) + } + } + return +} + +func TestImport(t *testing.T) { + // This package does not handle gccgo export data. + if runtime.Compiler == "gccgo" { + return + } + + // On cross-compile builds, the path will not exist. + // Need to use GOHOSTOS, which is not available. + if _, err := os.Stat(gcPath); err != nil { + t.Skipf("skipping test: %v", err) + } + + if outFn := compile(t, "testdata", "exports.go"); outFn != "" { + defer os.Remove(outFn) + } + + nimports := 0 + if testPath(t, "./testdata/exports") { + nimports++ + } + nimports += testDir(t, "", time.Now().Add(maxTime)) // installed packages + t.Logf("tested %d imports", nimports) +} + +var importedObjectTests = []struct { + name string + want string +}{ + {"unsafe.Pointer", "type Pointer unsafe.Pointer"}, + {"math.Pi", "const Pi untyped float"}, + {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"}, + {"io.ReadWriter", "type ReadWriter interface{Read(p []byte) (n int, err error); Write(p []byte) (n int, err error)}"}, + {"math.Sin", "func Sin(x float64) float64"}, + // TODO(gri) add more tests +} + +func TestImportedTypes(t *testing.T) { + // This package does not handle gccgo export data. + if runtime.Compiler == "gccgo" { + return + } + 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(imports, importPath) + if err != nil { + t.Error(err) + continue + } + + obj := pkg.Scope().Lookup(objName) + if obj == nil { + t.Errorf("%s: object not found", test.name) + continue + } + + got := types.ObjectString(pkg, obj) + if got != test.want { + t.Errorf("%s: got %q; want %q", test.name, got, test.want) + } + } +} + +func TestIssue5815(t *testing.T) { + // This package does not handle gccgo export data. + if runtime.Compiler == "gccgo" { + return + } + + pkg, err := Import(make(map[string]*types.Package), "strings") + if err != nil { + t.Fatal(err) + } + + scope := pkg.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + if obj.Pkg() == nil { + t.Errorf("no pkg for %s", obj) + } + if tname, _ := obj.(*types.TypeName); tname != nil { + named := tname.Type().(*types.Named) + for i := 0; i < named.NumMethods(); i++ { + m := named.Method(i) + if m.Pkg() == nil { + t.Errorf("no pkg for %s", m) + } + } + } + } +} + +// Smoke test to ensure that imported methods get the correct package. +func TestCorrectMethodPackage(t *testing.T) { + // This package does not handle gccgo export data. + if runtime.Compiler == "gccgo" { + return + } + + imports := make(map[string]*types.Package) + _, err := Import(imports, "net/http") + if err != nil { + t.Fatal(err) + } + + mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type() + mset := types.NewMethodSet(types.NewPointer(mutex)) // methods of *sync.Mutex + sel := mset.Lookup(nil, "Lock") + lock := sel.Obj().(*types.Func) + if got, want := lock.Pkg().Path(), "sync"; got != want { + t.Errorf("got package path %q; want %q", got, want) + } +} diff --git a/src/gosubli.me/something-borrowed/types/testdata/exports.go b/src/gosubli.me/something-borrowed/gcimporter/testdata/exports.go similarity index 100% rename from src/gosubli.me/something-borrowed/types/testdata/exports.go rename to src/gosubli.me/something-borrowed/gcimporter/testdata/exports.go diff --git a/src/gosubli.me/something-borrowed/types/api.go b/src/gosubli.me/something-borrowed/types/api.go index 536f0c6f..edc8a4fe 100644 --- a/src/gosubli.me/something-borrowed/types/api.go +++ b/src/gosubli.me/something-borrowed/types/api.go @@ -2,100 +2,360 @@ // 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 structures for representing -// Go types and implements typechecking of package files. +// Package types declares the data types and implements +// the algorithms for type-checking of Go packages. +// Use Check and Config.Check to invoke the type-checker. // -// WARNING: THE TYPES API IS SUBJECT TO CHANGE. +// Type-checking consists of several interdependent phases: +// +// Name resolution maps each identifier (ast.Ident) in the program to the +// language object (Object) it denotes. +// Use Info.{Defs,Uses,Implicits} for the results of name resolution. +// +// Constant folding computes the exact constant value (exact.Value) for +// every expression (ast.Expr) that is a compile-time constant. +// Use Info.Types[expr].Value for the results of constant folding. +// +// Type inference computes the type (Type) of every expression (ast.Expr) +// and checks for compliance with the language specification. +// Use Info.Types[expr].Type for the results of type inference. // package types import ( + "bytes" + "fmt" "go/ast" "go/token" + + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" ) -// A Context specifies the supporting context for type checking. -// An empty Context is a ready-to-use default context. -type Context struct { +// Check type-checks a package and returns the resulting complete package +// object, or a nil package and the first error. The package is specified +// by a list of *ast.Files and corresponding file set, and the import path +// the package is identified with. The clean path must not be empty or dot ("."). +// +// For more control over type-checking and results, use Config.Check. +func Check(path string, fset *token.FileSet, files []*ast.File) (*Package, error) { + var conf Config + pkg, err := conf.Check(path, fset, files, nil) + if err != nil { + return nil, err + } + return pkg, nil +} + +// An Error describes a type-checking error; it implements the error interface. +// A "soft" error is an error that still permits a valid interpretation of a +// package (such as "unused variable"); "hard" errors may lead to unpredictable +// behavior if ignored. +type Error struct { + Fset *token.FileSet // file set for interpretation of Pos + Pos token.Pos // error position + Msg string // error message + Soft bool // if set, error is "soft" +} + +// Error returns an error string formatted as follows: +// filename:line:column: message +func (err Error) Error() string { + return fmt.Sprintf("%s: %s", err.Fset.Position(err.Pos), err.Msg) +} + +// An importer resolves import paths to Packages. +// The imports map records packages already known, +// indexed by package path. The type-checker +// will invoke Import with Config.Packages. +// An importer must determine the canonical package path and +// check imports to see if it is already present in the map. +// If so, the Importer can return the map entry. Otherwise, +// the importer must load the package data for the given path +// into a new *Package, record it in imports map, and return +// the package. +// TODO(gri) Need to be clearer about requirements of completeness. +type Importer func(map[string]*Package, string) (*Package, error) + +// A Config specifies the configuration for type checking. +// The zero value for Config is a ready-to-use default configuration. +type Config struct { + // If IgnoreFuncBodies is set, function bodies are not + // type-checked. + IgnoreFuncBodies bool + + // If FakeImportC is set, `import "C"` (for packages requiring Cgo) + // declares an empty "C" package and errors are omitted for qualified + // identifiers referring to package C (which won't find an object). + // This feature is intended for the standard library cmd/api tool. + // + // Caution: Effects may be unpredictable due to follow-up errors. + // Do not use casually! + FakeImportC bool + + // Packages is used to look up (and thus canonicalize) packages by + // package path. If Packages is nil, it is set to a new empty map. + // During type-checking, imported packages are added to the map. + Packages map[string]*Package + // If Error != nil, it is called with each error found - // during type checking. The error strings of errors with - // detailed position information are formatted as follows: - // filename:line:column: message + // 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) - // If Ident != nil, it is called for each identifier id - // denoting an Object in the files provided to Check, and - // obj is the denoted object. - // Ident is not called for fields and methods in struct or - // interface types or composite literals, or for blank (_) - // or dot (.) identifiers in dot-imports. - // TODO(gri) Consider making Fields and Methods ordinary - // Objects - than we could lift this restriction. - Ident func(id *ast.Ident, obj Object) - - // If Expr != nil, it is called for each expression x that is - // type-checked: typ is the expression type, and val is the value - // if x is constant, val is nil otherwise. + // If Import != nil, it is called for each imported package. + // Otherwise, DefaultImport is called. + Import Importer + + // If Sizes != nil, it provides the sizing functions for package unsafe. + // Otherwise &StdSizes{WordSize: 8, MaxAlign: 8} is used instead. + Sizes Sizes +} + +// DefaultImport is the default importer invoked if Config.Import == nil. +// The declaration: +// +// import _ "golang.org/x/tools/go/gcimporter" +// +// in a client of go/types will initialize DefaultImport to gcimporter.Import. +var DefaultImport Importer + +// 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, 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. + // + // Identifiers on the lhs of declarations (i.e., the identifiers + // which are being declared) are collected in the Defs map. + // Identifiers denoting packages are collected in the Uses maps. + Types map[ast.Expr]TypeAndValue + + // Defs maps identifiers to the objects they define (including + // package names, dots "." of dot-imports, and blank "_" identifiers). + // For identifiers that do not denote objects (e.g., the package name + // in package clauses, or symbolic variables t in t := x.(type) of + // type switch headers), the corresponding objects are nil. + // + // For an anonymous field, Defs returns the field *Var it defines. // - // Constants are represented as follows: + // Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos() + Defs map[*ast.Ident]Object + + // Uses maps identifiers to the objects they denote. // - // bool -> bool - // numeric -> int64, *big.Int, *big.Rat, Complex - // string -> string - // nil -> NilType + // For an anonymous field, Uses returns the *TypeName it denotes. // - // Constant values are normalized, that is, they are represented - // using the "smallest" possible type that can represent the value. - // For instance, 1.0 is represented as an int64 because it can be - // represented accurately as an int64. - Expr func(x ast.Expr, typ Type, val interface{}) + // Invariant: Uses[id].Pos() != id.Pos() + Uses map[*ast.Ident]Object - // If Import != nil, it is called for each imported package. - // Otherwise, GcImporter is called. - Import Importer + // Implicits maps nodes to their implicitly declared objects, if any. + // The following node and object types may appear: + // + // node declared object + // + // *ast.ImportSpec *PkgName for dot-imports and imports without renames + // *ast.CaseClause type-specific *Var for each type switch case clause (incl. default) + // *ast.Field anonymous struct field or parameter *Var + // + Implicits map[ast.Node]Object + + // Selections maps selector expressions (excluding qualified identifiers) + // to their corresponding selections. + Selections map[*ast.SelectorExpr]*Selection + + // Scopes maps ast.Nodes to the scopes they define. Package scopes are not + // associated with a specific node but with all files belonging to a package. + // Thus, the package scope can be found in the type-checked Package object. + // Scopes nest, with the Universe scope being the outermost scope, enclosing + // the package scope, which contains (one or more) files scopes, which enclose + // function scopes which in turn enclose statement and function literal scopes. + // Note that even though package-level functions are declared in the package + // scope, the function scopes are embedded in the file scope of the file + // containing the function declaration. + // + // The following node types may appear in Scopes: + // + // *ast.File + // *ast.FuncType + // *ast.BlockStmt + // *ast.IfStmt + // *ast.SwitchStmt + // *ast.TypeSwitchStmt + // *ast.CaseClause + // *ast.CommClause + // *ast.ForStmt + // *ast.RangeStmt + // + Scopes map[ast.Node]*Scope + + // InitOrder is the list of package-level initializers in the order in which + // they must be executed. Initializers referring to variables related by an + // initialization dependency appear in topological order, the others appear + // in source order. Variables without an initialization expression do not + // appear in this list. + InitOrder []*Initializer +} + +// TypeOf returns the type of expression e, or nil if not found. +// Precondition: the Types, Uses and Defs maps are populated. +// +func (info *Info) TypeOf(e ast.Expr) Type { + if t, ok := info.Types[e]; ok { + return t.Type + } + if id, _ := e.(*ast.Ident); id != nil { + if obj := info.ObjectOf(id); obj != nil { + return obj.Type() + } + } + return nil +} + +// ObjectOf returns the object denoted by the specified id, +// or nil if not found. +// +// If id is an anonymous struct field, ObjectOf returns the field (*Var) +// it uses, not the type (*TypeName) it defines. +// +// Precondition: the Uses and Defs maps are populated. +// +func (info *Info) ObjectOf(id *ast.Ident) Object { + if obj, _ := info.Defs[id]; obj != nil { + return obj + } + return info.Uses[id] +} + +// TypeAndValue reports the type and value (for constants) +// of the corresponding expression. +type TypeAndValue struct { + mode operandMode + Type Type + Value exact.Value +} + +// TODO(gri) Consider eliminating the IsVoid predicate. Instead, report +// "void" values as regular values but with the empty tuple type. + +// 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: + 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 (http://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 lhs of a comma-ok assignment. +func (tv TypeAndValue) HasOk() bool { + return tv.mode == commaok || tv.mode == mapindex +} + +// An Initializer describes a package-level variable, or a list of variables in case +// of a multi-valued initialization expression, and the corresponding initialization +// expression. +type Initializer struct { + Lhs []*Var // var Lhs = Rhs + Rhs ast.Expr +} + +func (init *Initializer) String() string { + var buf bytes.Buffer + for i, lhs := range init.Lhs { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(lhs.Name()) + } + buf.WriteString(" = ") + WriteExpr(&buf, init.Rhs) + return buf.String() +} + +// Check type-checks a package and returns the resulting package object, +// the first error if any, and if info != nil, additional type information. +// The package is marked as complete if no errors occurred, otherwise it is +// incomplete. See Config.Error for controlling behavior in the presence of +// errors. +// +// The package is specified by a list of *ast.Files and corresponding +// file set, and the package path the package is identified with. +// The clean path must not be empty or dot ("."). +func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, info *Info) (*Package, error) { + pkg := NewPackage(path, "") + return pkg, NewChecker(conf, fset, pkg, info).Files(files) +} + +// AssertableTo reports whether a value of type V can be asserted to have type T. +func AssertableTo(V *Interface, T Type) bool { + m, _ := assertableTo(V, T) + return m == nil +} + +// AssignableTo reports whether a value of type V is assignable to a variable of type T. +func AssignableTo(V, T Type) bool { + x := operand{mode: value, typ: V} + return x.assignableTo(nil, T) // config 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) // config not needed for non-constant x +} - // If Alignof != nil, it is called to determine the alignment - // of the given type. Otherwise DefaultAlignmentof is called. - // Alignof must implement the alignment guarantees required by - // the spec. - Alignof func(Type) int64 - - // If Offsetsof != nil, it is called to determine the offsets - // of the given struct fields, in bytes. Otherwise DefaultOffsetsof - // is called. Offsetsof must implement the offset guarantees - // required by the spec. - Offsetsof func(fields []*Field) []int64 - - // If Sizeof != nil, it is called to determine the size of the - // given type. Otherwise, DefaultSizeof is called. Sizeof must - // implement the size guarantees required by the spec. - Sizeof func(Type) int64 -} - -// An Importer resolves import paths to Package objects. -// The imports map records the packages already imported, -// indexed by package id (canonical import path). -// An Importer must determine the canonical import path and -// check the map to see if it is already present in the imports map. -// If so, the Importer can return the map entry. Otherwise, the -// Importer should load the package data for the given path into -// a new *Package, record pkg in the imports map, and then -// return pkg. -type Importer func(imports map[string]*Package, path string) (pkg *Package, err error) - -// Check resolves and typechecks a set of package files within the given -// context. It returns the package and the first error encountered, if -// any. If the context's Error handler is nil, Check terminates as soon -// as the first error is encountered; otherwise it continues until the -// entire package is checked. If there are errors, the package may be -// only partially type-checked, and the resulting package may be incomplete -// (missing objects, imports, etc.). -func (ctxt *Context) Check(fset *token.FileSet, files []*ast.File) (*Package, error) { - return check(ctxt, fset, files) -} - -// Check is shorthand for ctxt.Check where ctxt is a default (empty) context. -func Check(fset *token.FileSet, files []*ast.File) (*Package, error) { - var ctxt Context - return ctxt.Check(fset, files) +// Implements reports whether type V implements interface T. +func Implements(V Type, T *Interface) bool { + f, _ := MissingMethod(V, T, true) + return f == nil } diff --git a/src/gosubli.me/something-borrowed/types/api_test.go b/src/gosubli.me/something-borrowed/types/api_test.go new file mode 100644 index 00000000..ad3d30b8 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/api_test.go @@ -0,0 +1,936 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types_test + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/token" + "strings" + "testing" + + _ "golang.org/x/tools/go/gcimporter" + . "golang.org/x/tools/go/types" +) + +func pkgFor(path, source string, info *Info) (*Package, error) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, path, source, 0) + if err != nil { + return nil, err + } + + var conf Config + return conf.Check(f.Name.Name, fset, []*ast.File{f}, info) +} + +func mustTypecheck(t *testing.T, path, source string, info *Info) string { + pkg, err := pkgFor(path, source, info) + if err != nil { + name := path + if pkg != nil { + name = "package " + pkg.Name() + } + t.Fatalf("%s: didn't type-check (%s)", name, err) + } + return pkg.Name() +} + +func 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`}, + {`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`}, + {`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`}, + {`package c4b; var _ = complex64(0i)`, `complex64(0i)`, `complex64`, `0`}, + {`package c4c; type T complex64; var _ = T(0i)`, `T(0i)`, `c4c.T`, `0`}, + + {`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 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`}, + {`package e5; const _ = complex64(-1e-200)`, `complex64(-1e-200)`, `complex64`, `0`}, + {`package e6; const _ = complex128( 1e-2000)`, `complex128(1e-2000)`, `complex128`, `0`}, + {`package e7; const _ = complex128(-1e-2000)`, `complex128(-1e-2000)`, `complex128`, `0`}, + + {`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`}, + {`package f5 ; var _ complex64 = -1e-200 `, `-1e-200`, `complex64`, `0`}, + {`package f6a; var _ complex128 = 1e-2000i`, `1e-2000i`, `complex128`, `0`}, + {`package f7a; var _ complex128 = -1e-2000i`, `-1e-2000i`, `complex128`, `0`}, + {`package f6b; var _ = 1e-2000i`, `1e-2000i`, `complex128`, `0`}, + {`package f7b; var _ = -1e-2000i`, `-1e-2000i`, `complex128`, `0`}, + } + + for _, test := range tests { + info := Info{ + Types: make(map[ast.Expr]TypeAndValue), + } + name := mustTypecheck(t, "ValuesInfo", test.src, &info) + + // look for constant expression + var expr ast.Expr + for e := range info.Types { + if ExprString(e) == test.expr { + expr = e + break + } + } + if expr == nil { + t.Errorf("package %s: no expression found for %s", name, test.expr) + continue + } + tv := info.Types[expr] + + // check that type is correct + if got := tv.Type.String(); got != test.typ { + t.Errorf("package %s: got type %s; want %s", name, got, test.typ) + continue + } + + // check that value is correct + if got := tv.Value.String(); got != test.val { + t.Errorf("package %s: got value %s; want %s", name, got, 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)`, + }, + // TODO(gri): uncomment if we accept issue 8189. + // {`package p2; type mybool bool; var m map[string]complex128; var b mybool; func _() { _, b = m["foo"] }`, + // `m["foo"]`, + // `(complex128, p2.mybool)`, + // }, + // TODO(gri): remove if we accept issue 8189. + {`package p2; 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)`, + }, + } + + for _, test := range tests { + info := Info{Types: make(map[ast.Expr]TypeAndValue)} + name := mustTypecheck(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 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) { + 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`}, + + // built-ins + {`package b0; var _ = len("")`, `len`, `builtin`}, + {`package b1; var _ = (len)("")`, `(len)`, `builtin`}, + + // constants + {`package c0; var _ = 42`, `42`, `const`}, + {`package c1; var _ = "foo" + "bar"`, `"foo" + "bar"`, `const`}, + {`package c2; const (i = 1i; _ = i)`, `i`, `const`}, + + // values + {`package v0; var (a, b int; _ = a + b)`, `a + b`, `value`}, + {`package v1; var _ = &[]int{1}`, `([]int literal)`, `value`}, + {`package v2; var _ = func(){}`, `(func() literal)`, `value`}, + {`package v4; func f() { _ = f }`, `f`, `value`}, + {`package v3; var _ *int = nil`, `nil`, `value, nil`}, + {`package v3; var _ *int = (nil)`, `(nil)`, `value, nil`}, + + // addressable (and thus assignable) operands + {`package a0; var (x int; _ = x)`, `x`, `value, addressable, assignable`}, + {`package a1; var (p *int; _ = *p)`, `*p`, `value, addressable, assignable`}, + {`package a2; var (s []int; _ = s[0])`, `s[0]`, `value, addressable, assignable`}, + {`package a3; var (s struct{f int}; _ = s.f)`, `s.f`, `value, addressable, assignable`}, + {`package a4; var (a [10]int; _ = a[0])`, `a[0]`, `value, addressable, assignable`}, + {`package a5; func _(x int) { _ = x }`, `x`, `value, addressable, assignable`}, + {`package a6; func _()(x int) { _ = x; return }`, `x`, `value, addressable, assignable`}, + {`package a7; type T int; func (x T) _() { _ = x }`, `x`, `value, addressable, assignable`}, + // composite literals are not addressable + + // assignable but not addressable values + {`package s0; var (m map[int]int; _ = m[0])`, `m[0]`, `value, assignable, hasOk`}, + {`package s1; var (m map[int]int; _, _ = m[0])`, `m[0]`, `value, assignable, hasOk`}, + + // hasOk expressions + {`package k0; var (ch chan int; _ = <-ch)`, `<-ch`, `value, hasOk`}, + {`package k1; var (ch chan int; _, _ = <-ch)`, `<-ch`, `value, hasOk`}, + + // missing entries + // - package names are collected in the Uses map + // - identifiers being declared are collected in the Defs map + {`package m0; import "os"; func _() { _ = os.Stdout }`, `os`, ``}, + {`package m1; import p "os"; func _() { _ = p.Stdout }`, `p`, ``}, + {`package m2; const c = 0`, `c`, ``}, + {`package m3; type T int`, `T`, ``}, + {`package m4; var v int`, `v`, ``}, + {`package m5; func f() {}`, `f`, ``}, + {`package m6; func _(x int) {}`, `x`, ``}, + {`package m6; func _()(x int) { return }`, `x`, ``}, + {`package m6; type T int; func (x T) _() {}`, `x`, ``}, + } + + for _, test := range tests { + info := Info{Types: make(map[ast.Expr]TypeAndValue)} + name := mustTypecheck(t, "PredicatesInfo", test.src, &info) + + // look for expression predicates + got := "" + for e, tv := range info.Types { + //println(name, ExprString(e)) + if ExprString(e) == test.expr { + got = predString(tv) + break + } + } + + if got != test.pred { + t.Errorf("package %s: got %s; want %s", name, got, test.pred) + } + } +} + +func TestScopesInfo(t *testing.T) { + var tests = []struct { + src string + scopes []string // list of scope descriptors of the form kind:varlist + }{ + {`package p0`, []string{ + "file:", + }}, + {`package p1; import ( "fmt"; m "math"; _ "os" ); var ( _ = fmt.Println; _ = m.Pi )`, []string{ + "file:fmt m", + }}, + {`package p2; func _() {}`, []string{ + "file:", "func:", + }}, + {`package p3; func _(x, y int) {}`, []string{ + "file:", "func:x y", + }}, + {`package p4; func _(x, y int) { x, z := 1, 2; _ = z }`, []string{ + "file:", "func:x y z", // redeclaration of x + }}, + {`package p5; func _(x, y int) (u, _ int) { return }`, []string{ + "file:", "func:u x y", + }}, + {`package p6; func _() { { var x int; _ = x } }`, []string{ + "file:", "func:", "block:x", + }}, + {`package p7; func _() { if true {} }`, []string{ + "file:", "func:", "if:", "block:", + }}, + {`package p8; func _() { if x := 0; x < 0 { y := x; _ = y } }`, []string{ + "file:", "func:", "if:x", "block:y", + }}, + {`package p9; func _() { switch x := 0; x {} }`, []string{ + "file:", "func:", "switch:x", + }}, + {`package p10; func _() { switch x := 0; x { case 1: y := x; _ = y; default: }}`, []string{ + "file:", "func:", "switch:x", "case:y", "case:", + }}, + {`package p11; func _(t interface{}) { switch t.(type) {} }`, []string{ + "file:", "func:t", "type switch:", + }}, + {`package p12; func _(t interface{}) { switch t := t; t.(type) {} }`, []string{ + "file:", "func:t", "type switch:t", + }}, + {`package p13; func _(t interface{}) { switch x := t.(type) { case int: _ = x } }`, []string{ + "file:", "func:t", "type switch:", "case:x", // x implicitly declared + }}, + {`package p14; func _() { select{} }`, []string{ + "file:", "func:", + }}, + {`package p15; func _(c chan int) { select{ case <-c: } }`, []string{ + "file:", "func:c", "comm:", + }}, + {`package p16; func _(c chan int) { select{ case i := <-c: x := i; _ = x} }`, []string{ + "file:", "func:c", "comm:i x", + }}, + {`package p17; func _() { for{} }`, []string{ + "file:", "func:", "for:", "block:", + }}, + {`package p18; func _(n int) { for i := 0; i < n; i++ { _ = i } }`, []string{ + "file:", "func:n", "for:i", "block:", + }}, + {`package p19; func _(a []int) { for i := range a { _ = i} }`, []string{ + "file:", "func:a", "range:i", "block:", + }}, + {`package p20; var s int; func _(a []int) { for i, x := range a { s += x; _ = i } }`, []string{ + "file:", "func:a", "range:i x", "block:", + }}, + } + + for _, test := range tests { + info := Info{Scopes: make(map[ast.Node]*Scope)} + name := mustTypecheck(t, "ScopesInfo", test.src, &info) + + // number of scopes must match + if len(info.Scopes) != len(test.scopes) { + t.Errorf("package %s: got %d scopes; want %d", name, len(info.Scopes), len(test.scopes)) + } + + // scope descriptions must match + for node, scope := range info.Scopes { + kind := "" + switch node.(type) { + case *ast.File: + kind = "file" + case *ast.FuncType: + kind = "func" + case *ast.BlockStmt: + kind = "block" + case *ast.IfStmt: + kind = "if" + case *ast.SwitchStmt: + kind = "switch" + case *ast.TypeSwitchStmt: + kind = "type switch" + case *ast.CaseClause: + kind = "case" + case *ast.CommClause: + kind = "comm" + case *ast.ForStmt: + kind = "for" + case *ast.RangeStmt: + kind = "range" + } + + // look for matching scope description + desc := kind + ":" + strings.Join(scope.Names(), " ") + found := false + for _, d := range test.scopes { + if desc == d { + found = true + break + } + } + if !found { + t.Errorf("package %s: no matching scope found for %s", name, desc) + } + } + } +} + +func TestInitOrderInfo(t *testing.T) { + var tests = []struct { + src string + inits []string + }{ + {`package p0; var (x = 1; y = x)`, []string{ + "x = 1", "y = x", + }}, + {`package p1; var (a = 1; b = 2; c = 3)`, []string{ + "a = 1", "b = 2", "c = 3", + }}, + {`package p2; var (a, b, c = 1, 2, 3)`, []string{ + "a = 1", "b = 2", "c = 3", + }}, + {`package p3; var _ = f(); func f() int { return 1 }`, []string{ + "_ = f()", // blank var + }}, + {`package p4; var (a = 0; x = y; y = z; z = 0)`, []string{ + "a = 0", "z = 0", "y = z", "x = y", + }}, + {`package p5; var (a, _ = m[0]; m map[int]string)`, []string{ + "a, _ = m[0]", // blank var + }}, + {`package p6; var a, b = f(); func f() (_, _ int) { return z, z }; var z = 0`, []string{ + "z = 0", "a, b = f()", + }}, + {`package p7; var (a = func() int { return b }(); b = 1)`, []string{ + "b = 1", "a = (func() int literal)()", + }}, + {`package p8; var (a, b = func() (_, _ int) { return c, c }(); c = 1)`, []string{ + "c = 1", "a, b = (func() (_, _ int) literal)()", + }}, + {`package p9; type T struct{}; func (T) m() int { _ = y; return 0 }; var x, y = T.m, 1`, []string{ + "y = 1", "x = T.m", + }}, + {`package p10; var (d = c + b; a = 0; b = 0; c = 0)`, []string{ + "a = 0", "b = 0", "c = 0", "d = c + b", + }}, + {`package p11; var (a = e + c; b = d + c; c = 0; d = 0; e = 0)`, []string{ + "c = 0", "d = 0", "b = d + c", "e = 0", "a = e + c", + }}, + // emit an initializer for n:1 initializations only once (not for each node + // on the lhs which may appear in different order in the dependency graph) + {`package p12; var (a = x; b = 0; x, y = m[0]; m map[int]int)`, []string{ + "b = 0", "x, y = m[0]", "a = x", + }}, + // test case from spec section on package initialization + {`package p12 + + var ( + a = c + b + b = f() + c = f() + d = 3 + ) + + func f() int { + d++ + return d + }`, []string{ + "d = 3", "b = f()", "c = f()", "a = c + b", + }}, + // test case for issue 7131 + {`package main + + var counter int + func next() int { counter++; return counter } + + var _ = makeOrder() + func makeOrder() []int { return []int{f, b, d, e, c, a} } + + var a = next() + var b, c = next(), next() + var d, e, f = next(), next(), next() + `, []string{ + "a = next()", "b = next()", "c = next()", "d = next()", "e = next()", "f = next()", "_ = makeOrder()", + }}, + } + + for _, test := range tests { + info := Info{} + name := mustTypecheck(t, "InitOrderInfo", test.src, &info) + + // number of initializers must match + if len(info.InitOrder) != len(test.inits) { + t.Errorf("package %s: got %d initializers; want %d", name, len(info.InitOrder), len(test.inits)) + continue + } + + // initializers must match + for i, want := range test.inits { + got := info.InitOrder[i].String() + if got != want { + t.Errorf("package %s, init %d: got %s; want %s", name, i, got, want) + continue + } + } + } +} + +func TestMultiFileInitOrder(t *testing.T) { + fset := token.NewFileSet() + mustParse := func(src string) *ast.File { + f, err := parser.ParseFile(fset, "main", src, 0) + if err != nil { + t.Fatal(err) + } + return f + } + + fileA := mustParse(`package main; var a = 1`) + fileB := mustParse(`package main; var b = 2`) + + // The initialization order must not depend on the parse + // order of the files, only on the presentation order to + // the type-checker. + for _, test := range []struct { + files []*ast.File + want string + }{ + {[]*ast.File{fileA, fileB}, "[a = 1 b = 2]"}, + {[]*ast.File{fileB, fileA}, "[b = 2 a = 1]"}, + } { + var info Info + if _, err := new(Config).Check("main", fset, test.files, &info); err != nil { + t.Fatal(err) + } + if got := fmt.Sprint(info.InitOrder); got != test.want { + t.Fatalf("got %s; want %s", got, test.want) + } + } +} + +func TestFiles(t *testing.T) { + var sources = []string{ + "package p; type T struct{}; func (T) m1() {}", + "package p; func (T) m2() {}; var x interface{ m1(); m2() } = T{}", + "package p; func (T) m3() {}; var y interface{ m1(); m2(); m3() } = T{}", + "package p", + } + + var conf Config + fset := token.NewFileSet() + pkg := NewPackage("p", "p") + var info Info + check := NewChecker(&conf, fset, pkg, &info) + + for i, src := range sources { + filename := fmt.Sprintf("sources%d", i) + f, err := parser.ParseFile(fset, filename, src, 0) + if err != nil { + t.Fatal(err) + } + if err := check.Files([]*ast.File{f}); err != nil { + t.Error(err) + } + } + + // check InitOrder is [x y] + var vars []string + for _, init := range info.InitOrder { + for _, v := range init.Lhs { + vars = append(vars, v.Name()) + } + } + if got, want := fmt.Sprint(vars), "[x y]"; got != want { + t.Errorf("InitOrder == %s, want %s", got, want) + } +} + +func TestSelection(t *testing.T) { + selections := make(map[*ast.SelectorExpr]*Selection) + + fset := token.NewFileSet() + conf := Config{ + Packages: make(map[string]*Package), + Import: func(imports map[string]*Package, path string) (*Package, error) { + return imports[path], nil + }, + } + makePkg := func(path, src string) { + f, err := parser.ParseFile(fset, path+".go", src, 0) + if err != nil { + t.Fatal(err) + } + pkg, err := conf.Check(path, fset, []*ast.File{f}, &Info{Selections: selections}) + if err != nil { + t.Fatal(err) + } + conf.Packages[path] = pkg + } + + const libSrc = ` +package lib +type T float64 +const C T = 3 +var V T +func F() {} +func (T) M() {} +` + const mainSrc = ` +package main +import "lib" + +type A struct { + *B + C +} + +type B struct { + b int +} + +func (B) f(int) + +type C struct { + c int +} + +func (C) g() +func (*C) h() + +func main() { + // qualified identifiers + var _ lib.T + _ = lib.C + _ = lib.F + _ = lib.V + _ = lib.T.M + + // fields + _ = A{}.B + _ = new(A).B + + _ = A{}.C + _ = new(A).C + + _ = A{}.b + _ = new(A).b + + _ = A{}.c + _ = new(A).c + + // methods + _ = A{}.f + _ = new(A).f + _ = A{}.g + _ = new(A).g + _ = new(A).h + + _ = B{}.f + _ = new(B).f + + _ = C{}.g + _ = new(C).g + _ = new(C).h + + // method expressions + _ = A.f + _ = (*A).f + _ = B.f + _ = (*B).f +}` + + wantOut := map[string][2]string{ + "lib.T.M": {"method expr (lib.T) M(lib.T)", ".[0]"}, + + "A{}.B": {"field (main.A) B *main.B", ".[0]"}, + "new(A).B": {"field (*main.A) B *main.B", "->[0]"}, + "A{}.C": {"field (main.A) C main.C", ".[1]"}, + "new(A).C": {"field (*main.A) C main.C", "->[1]"}, + "A{}.b": {"field (main.A) b int", "->[0 0]"}, + "new(A).b": {"field (*main.A) b int", "->[0 0]"}, + "A{}.c": {"field (main.A) c int", ".[1 0]"}, + "new(A).c": {"field (*main.A) c int", "->[1 0]"}, + + "A{}.f": {"method (main.A) f(int)", "->[0 0]"}, + "new(A).f": {"method (*main.A) f(int)", "->[0 0]"}, + "A{}.g": {"method (main.A) g()", ".[1 0]"}, + "new(A).g": {"method (*main.A) g()", "->[1 0]"}, + "new(A).h": {"method (*main.A) h()", "->[1 1]"}, // TODO(gri) should this report .[1 1] ? + "B{}.f": {"method (main.B) f(int)", ".[0]"}, + "new(B).f": {"method (*main.B) f(int)", "->[0]"}, + "C{}.g": {"method (main.C) g()", ".[0]"}, + "new(C).g": {"method (*main.C) g()", "->[0]"}, + "new(C).h": {"method (*main.C) h()", "->[1]"}, // TODO(gri) should this report .[1] ? + + "A.f": {"method expr (main.A) f(main.A, int)", "->[0 0]"}, + "(*A).f": {"method expr (*main.A) f(*main.A, int)", "->[0 0]"}, + "B.f": {"method expr (main.B) f(main.B, int)", ".[0]"}, + "(*B).f": {"method expr (*main.B) f(*main.B, int)", "->[0]"}, + } + + makePkg("lib", libSrc) + makePkg("main", mainSrc) + + for e, sel := range selections { + sel.String() // assertion: must not panic + + start := fset.Position(e.Pos()).Offset + end := fset.Position(e.End()).Offset + syntax := mainSrc[start:end] // (all SelectorExprs are in main, not lib) + + direct := "." + if sel.Indirect() { + direct = "->" + } + got := [2]string{ + sel.String(), + fmt.Sprintf("%s%v", direct, sel.Index()), + } + want := wantOut[syntax] + if want != got { + t.Errorf("%s: got %q; want %q", syntax, got, want) + } + delete(wantOut, syntax) + + // We must explicitly assert properties of the + // Signature's receiver since it doesn't participate + // in Identical() or String(). + sig, _ := sel.Type().(*Signature) + if sel.Kind() == MethodVal { + got := sig.Recv().Type() + want := sel.Recv() + if !Identical(got, want) { + t.Errorf("%s: Recv() = %s, want %s", got, want) + } + } else if sig != nil && sig.Recv() != nil { + t.Error("%s: signature has receiver %s", sig, sig.Recv().Type()) + } + } + // Assert that all wantOut entries were used exactly once. + for syntax := range wantOut { + t.Errorf("no ast.Selection found with syntax %q", syntax) + } +} + +func TestIssue8518(t *testing.T) { + fset := token.NewFileSet() + conf := Config{ + Packages: make(map[string]*Package), + Error: func(err error) { t.Log(err) }, // don't exit after first error + Import: func(imports map[string]*Package, path string) (*Package, error) { + return imports[path], nil + }, + } + makePkg := func(path, src string) { + f, err := parser.ParseFile(fset, path, src, 0) + if err != nil { + t.Fatal(err) + } + pkg, _ := conf.Check(path, fset, []*ast.File{f}, nil) // errors logged via conf.Error + conf.Packages[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 +} diff --git a/src/gosubli.me/something-borrowed/types/assignments.go b/src/gosubli.me/something-borrowed/types/assignments.go new file mode 100644 index 00000000..7ee1abcc --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/assignments.go @@ -0,0 +1,323 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements initialization and assignment checks. + +package types + +import ( + "go/ast" + "go/token" +) + +// assignment reports whether x can be assigned to a variable of type T, +// if necessary by attempting to convert untyped values to the appropriate +// type. If x.mode == invalid upon return, then assignment has already +// issued an error message and the caller doesn't have to report another. +// Use T == nil to indicate assignment to an untyped blank identifier. +// +// TODO(gri) Should find a better way to handle in-band errors. +// +func (check *Checker) assignment(x *operand, T Type) bool { + switch x.mode { + case invalid: + return true // error reported before + case constant, variable, mapindex, value, commaok: + // ok + default: + unreachable() + } + + // x must be a single value + // (tuple types are never named - no need for underlying type) + if t, _ := x.typ.(*Tuple); t != nil { + assert(t.Len() > 1) + check.errorf(x.pos(), "%d-valued expression %s used as single value", t.Len(), x) + x.mode = invalid + return false + } + + 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.pos(), "use of untyped nil") + x.mode = invalid + return false + } + target = defaultType(x.typ) + } + check.convertUntyped(x, target) + if x.mode == invalid { + return false + } + } + + // 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." + return T == nil || x.assignableTo(check.conf, T) +} + +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.pos(), "%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 + } + + if !check.assignment(x, lhs.typ) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot define constant %s (type %s) as %s", lhs.Name(), lhs.typ, x) + } + return + } + + lhs.val = x.val +} + +// If result is set, lhs is a function result parameter and x is a return result. +func (check *Checker) initVar(lhs *Var, x *operand, result bool) 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.pos(), "use of untyped nil") + lhs.typ = Typ[Invalid] + return nil + } + typ = defaultType(typ) + } + lhs.typ = typ + } + + if !check.assignment(x, lhs.typ) { + if x.mode != invalid { + if result { + // don't refer to lhs.name because it may be an anonymous result parameter + check.errorf(x.pos(), "cannot return %s as value of type %s", x, lhs.typ) + } else { + check.errorf(x.pos(), "cannot initialize %s with %s", lhs, x) + } + } + return nil + } + + return x.typ +} + +func (check *Checker) assignVar(lhs ast.Expr, x *operand) Type { + if x.mode == invalid || x.typ == Typ[Invalid] { + return nil + } + + // Determine if the lhs is a (possibly parenthesized) identifier. + ident, _ := unparen(lhs).(*ast.Ident) + + // Don't evaluate lhs if it is the blank identifier. + if ident != nil && ident.Name == "_" { + check.recordDef(ident, nil) + if !check.assignment(x, nil) { + assert(x.mode == invalid) + x.typ = 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.scope.LookupParent(ident.Name); obj != nil { + v, _ = obj.(*Var) + if v != nil { + 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: + check.errorf(z.pos(), "cannot assign to %s", &z) + return nil + } + + if !check.assignment(x, z.typ) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot assign %s to %s", x, &z) + } + return nil + } + + return x.typ +} + +// If returnPos is valid, initVars is called to type-check the assignment of +// return expressions, and returnPos is the position of the return statement. +func (check *Checker) initVars(lhs []*Var, rhs []ast.Expr, returnPos token.Pos) { + l := len(lhs) + get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2 && !returnPos.IsValid()) + if get == nil || l != r { + // invalidate lhs and use rhs + for _, obj := range lhs { + if obj.typ == nil { + obj.typ = Typ[Invalid] + } + } + if get == nil { + return // error reported by unpack + } + check.useGetter(get, r) + if returnPos.IsValid() { + check.errorf(returnPos, "wrong number of return values (want %d, got %d)", l, r) + return + } + check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r) + return + } + + var x operand + if commaOk { + var a [2]Type + for i := range a { + get(&x, i) + a[i] = check.initVar(lhs[i], &x, returnPos.IsValid()) + } + check.recordCommaOkTypes(rhs[0], a) + return + } + + for i, lhs := range lhs { + get(&x, i) + check.initVar(lhs, &x, returnPos.IsValid()) + } +} + +func (check *Checker) assignVars(lhs, rhs []ast.Expr) { + l := len(lhs) + get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2) + if get == nil { + return // error reported by unpack + } + if l != r { + check.useGetter(get, r) + check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r) + return + } + + var x operand + if commaOk { + var a [2]Type + for i := range a { + get(&x, i) + a[i] = check.assignVar(lhs[i], &x) + } + check.recordCommaOkTypes(rhs[0], a) + return + } + + for i, lhs := range lhs { + get(&x, i) + check.assignVar(lhs, &x) + } +} + +func (check *Checker) shortVarDecl(pos token.Pos, lhs, rhs []ast.Expr) { + scope := check.scope + + // collect lhs variables + var newVars []*Var + var lhsVars = make([]*Var, len(lhs)) + for i, lhs := range lhs { + var obj *Var + if ident, _ := lhs.(*ast.Ident); ident != nil { + // Use the correct obj if the ident is redeclared. The + // variable's scope starts after the declaration; so we + // must use Scope.Lookup here and call Scope.Insert + // (via check.declare) later. + name := ident.Name + if alt := scope.Lookup(name); alt != nil { + // redeclared object must be a variable + if alt, _ := alt.(*Var); alt != nil { + obj = alt + } else { + check.errorf(lhs.Pos(), "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.errorf(lhs.Pos(), "cannot declare %s", lhs) + } + if obj == nil { + obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable + } + lhsVars[i] = obj + } + + check.initVars(lhsVars, rhs, token.NoPos) + + // declare new variables + if len(newVars) > 0 { + for _, obj := range newVars { + check.declare(scope, nil, obj) // recordObject already called + } + } else { + check.softErrorf(pos, "no new variables on left side of :=") + } +} diff --git a/src/gosubli.me/something-borrowed/types/builtins.go b/src/gosubli.me/something-borrowed/types/builtins.go index fd796ee7..b482e7bf 100644 --- a/src/gosubli.me/something-borrowed/types/builtins.go +++ b/src/gosubli.me/something-borrowed/types/builtins.go @@ -9,75 +9,144 @@ package types import ( "go/ast" "go/token" + + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" ) -// builtin typechecks a built-in call. The built-in type is bin, and iota is the current -// value of iota or -1 if iota doesn't have a value in the current context. The result -// of the call is returned via x. If the call has type errors, the returned x is marked -// as invalid (x.mode == invalid). +// builtin type-checks a call to the built-in specified by id and +// returns true if the call is valid, with *x holding the result; +// but x.expr is not set. If the call is invalid, the result is +// false, and *x is undefined. // -func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota int) { - args := call.Args - id := bin.id - - // declare before goto's - var arg0 ast.Expr // first argument, if present - - // check argument count - n := len(args) - msg := "" - if n < bin.nargs { - msg = "not enough" - } else if !bin.isVariadic && n > bin.nargs { - msg = "too many" +func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ bool) { + // append is the only built-in that permits the use of ... for the last argument + bin := predeclaredFuncs[id] + if call.Ellipsis.IsValid() && id != _Append { + check.invalidOp(call.Ellipsis, "invalid use of ... with built-in %s", bin.name) + check.use(call.Args...) + return } - if msg != "" { - check.invalidOp(call.Pos(), msg+" arguments for %s (expected %d, found %d)", call, bin.nargs, n) - goto Error + + // For len(x) and cap(x) we need to know if x contains any function calls or + // receive operations. Save/restore current setting and set hasCallOrRecv to + // false for the evaluation of x so that we can check it afterwards. + // Note: We must do this _before_ calling unpack because unpack evaluates the + // first argument before we even call arg(x, 0)! + if id == _Len || id == _Cap { + defer func(b bool) { + check.hasCallOrRecv = b + }(check.hasCallOrRecv) + check.hasCallOrRecv = false } - // common case: evaluate first argument if present; - // if it is an expression, x has the expression value - if n > 0 { - arg0 = args[0] - switch id { - case _Make, _New, _Print, _Println, _Offsetof, _Trace: - // respective cases below do the work - default: - // argument must be an expression - check.expr(x, arg0, nil, iota) + // determine actual arguments + var arg getter + nargs := len(call.Args) + switch id { + default: + // make argument getter + arg, nargs, _ = unpack(func(x *operand, i int) { check.expr(x, call.Args[i]) }, nargs, false) + if arg == nil { + x.mode = invalid + return + } + // evaluate first argument, if present + if nargs > 0 { + arg(x, 0) if x.mode == invalid { - goto Error + return } } + case _Make, _New, _Offsetof, _Trace: + // arguments require special handling + } + + // check argument count + { + msg := "" + if nargs < bin.nargs { + msg = "not enough" + } else if !bin.variadic && nargs > bin.nargs { + msg = "too many" + } + if msg != "" { + check.invalidOp(call.Rparen, "%s arguments for %s (expected %d, found %d)", msg, call, bin.nargs, nargs) + return + } } switch id { case _Append: - if _, ok := underlying(x.typ).(*Slice); !ok { - check.invalidArg(x.pos(), "%s is not a typed slice", x) - goto Error + // append(s S, x ...T) S, where T is the element type of S + // spec: "The variadic function append appends zero or more values x to s of type + // S, which must be a slice type, and returns the resulting slice, also of type S. + // The values x are passed to a parameter of type ...T where T is the element type + // of S and the respective parameter passing rules apply." + S := x.typ + var T Type + if s, _ := S.Underlying().(*Slice); s != nil { + T = s.elem + } else { + check.invalidArg(x.pos(), "%s is not a slice", x) + return } - resultTyp := x.typ - for _, arg := range args[1:] { - check.expr(x, arg, nil, iota) + + // remember arguments that have been evaluated already + alist := []operand{*x} + + // spec: "As a special case, append also accepts a first argument assignable + // to type []byte with a second argument of string type followed by ... . + // This form appends the bytes of the string. + if nargs == 2 && call.Ellipsis.IsValid() && x.assignableTo(check.conf, NewSlice(UniverseByte)) { + arg(x, 1) if x.mode == invalid { - goto Error + return } - // TODO(gri) check assignability - } + if isString(x.typ) { + if check.Types != nil { + sig := makeSig(S, S, x.typ) + sig.variadic = true + check.recordBuiltinType(call.Fun, sig) + } + x.mode = value + x.typ = S + break + } + alist = append(alist, *x) + // fallthrough + } + + // check general case by creating custom signature + sig := makeSig(S, S, NewSlice(T)) // []T required for variadic signature + sig.variadic = true + check.arguments(x, call, sig, func(x *operand, i int) { + // only evaluate arguments that have not been evaluated before + if i < len(alist) { + *x = alist[i] + return + } + arg(x, i) + }, nargs) + // ok to continue even if check.arguments reported errors + x.mode = value - x.typ = resultTyp + x.typ = S + if check.Types != nil { + check.recordBuiltinType(call.Fun, sig) + } case _Cap, _Len: + // cap(x) + // len(x) mode := invalid - var val interface{} - switch typ := implicitArrayDeref(underlying(x.typ)).(type) { + var typ Type + var val exact.Value + switch typ = implicitArrayDeref(x.typ.Underlying()); t := typ.(type) { case *Basic: - if isString(typ) && id == _Len { + if isString(t) && id == _Len { if x.mode == constant { mode = constant - val = int64(len(x.val.(string))) + val = exact.MakeInt64(int64(len(exact.StringVal(x.val)))) } else { mode = value } @@ -89,9 +158,9 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota // 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.containsCallsOrReceives(arg0) { + if !check.hasCallOrRecv { mode = constant - val = typ.Len + val = exact.MakeInt64(t.len) } case *Slice, *Chan: @@ -105,140 +174,184 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota if mode == invalid { check.invalidArg(x.pos(), "%s for %s", x, bin.name) - goto Error + 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: - ch, ok := underlying(x.typ).(*Chan) - if !ok { + // close(c) + c, _ := x.typ.Underlying().(*Chan) + if c == nil { check.invalidArg(x.pos(), "%s is not a channel", x) - goto Error + return } - if ch.Dir&ast.SEND == 0 { + if c.dir == RecvOnly { check.invalidArg(x.pos(), "%s must not be a receive-only channel", x) - goto Error + return } + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, c)) + } case _Complex: + // complex(x, y realT) complexT if !check.complexArg(x) { - goto Error + return } var y operand - check.expr(&y, args[1], nil, iota) + arg(&y, 1) if y.mode == invalid { - goto Error + return } if !check.complexArg(&y) { - goto Error + return } check.convertUntyped(x, y.typ) if x.mode == invalid { - goto Error + return } check.convertUntyped(&y, x.typ) if y.mode == invalid { - goto Error + return } - if !IsIdentical(x.typ, y.typ) { + if !Identical(x.typ, y.typ) { check.invalidArg(x.pos(), "mismatched types %s and %s", x.typ, y.typ) - goto Error + return } - typ := underlying(x.typ).(*Basic) if x.mode == constant && y.mode == constant { - x.val = binaryOpConst(x.val, toImagConst(y.val), token.ADD, typ) + x.val = exact.BinaryOp(x.val, token.ADD, exact.MakeImag(y.val)) } else { x.mode = value } - switch typ.Kind { + realT := x.typ + complexT := Typ[Invalid] + switch realT.Underlying().(*Basic).kind { case Float32: - x.typ = Typ[Complex64] + complexT = Typ[Complex64] case Float64: - x.typ = Typ[Complex128] + complexT = Typ[Complex128] case UntypedInt, UntypedRune, UntypedFloat: - x.typ = Typ[UntypedComplex] + if x.mode == constant { + realT = defaultType(realT).(*Basic) + complexT = Typ[UntypedComplex] + } else { + // untyped but not constant; probably because one + // operand is a non-constant shift of untyped lhs + realT = Typ[Float64] + complexT = Typ[Complex128] + } default: check.invalidArg(x.pos(), "float32 or float64 arguments expected") - goto Error + return + } + + x.typ = complexT + if check.Types != nil && x.mode != constant { + check.recordBuiltinType(call.Fun, makeSig(complexT, realT, realT)) + } + + if x.mode != constant { + // The arguments 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. + // (If the result is constant, the arguments are never + // materialized and there is nothing to do.) + check.updateExprType(x.expr, realT, true) + check.updateExprType(y.expr, realT, true) } case _Copy: - var y operand - check.expr(&y, args[1], nil, iota) - if y.mode == invalid { - goto Error + // copy(x, y []T) int + var dst Type + if t, _ := x.typ.Underlying().(*Slice); t != nil { + dst = t.elem } - var dst, src Type - if t, ok := underlying(x.typ).(*Slice); ok { - dst = t.Elt + var y operand + arg(&y, 1) + if y.mode == invalid { + return } - switch t := underlying(y.typ).(type) { + var src Type + switch t := y.typ.Underlying().(type) { case *Basic: if isString(y.typ) { - src = Typ[Byte] + src = UniverseByte } case *Slice: - src = t.Elt + src = t.elem } if dst == nil || src == nil { check.invalidArg(x.pos(), "copy expects slice arguments; found %s and %s", x, &y) - goto Error + return } - if !IsIdentical(dst, src) { + if !Identical(dst, src) { check.invalidArg(x.pos(), "arguments to copy %s and %s have different element types %s and %s", x, &y, dst, src) - goto Error + 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: - m, ok := underlying(x.typ).(*Map) - if !ok { + // delete(m, k) + m, _ := x.typ.Underlying().(*Map) + if m == nil { check.invalidArg(x.pos(), "%s is not a map", x) - goto Error + return } - check.expr(x, args[1], nil, iota) + arg(x, 1) // k if x.mode == invalid { - goto Error + return } - if !x.isAssignable(check.ctxt, m.Key) { - check.invalidArg(x.pos(), "%s is not assignable to %s", x, m.Key) - goto Error + + if !x.assignableTo(check.conf, m.key) { + check.invalidArg(x.pos(), "%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) realT + // real(complexT) realT if !isComplex(x.typ) { check.invalidArg(x.pos(), "%s must be a complex number", x) - goto Error + return } if x.mode == constant { - // nothing to do for x.val == 0 - if !isZeroConst(x.val) { - c := x.val.(Complex) - if id == _Real { - x.val = c.Re - } else { - x.val = c.Im - } + if id == _Real { + x.val = exact.Real(x.val) + } else { + x.val = exact.Imag(x.val) } } else { x.mode = value } - k := Invalid - switch underlying(x.typ).(*Basic).Kind { + var k BasicKind + switch x.typ.Underlying().(*Basic).kind { case Complex64: k = Float32 case Complex128: @@ -248,109 +361,179 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota default: unreachable() } + + if check.Types != nil && x.mode != constant { + check.recordBuiltinType(call.Fun, makeSig(Typ[k], x.typ)) + } x.typ = Typ[k] case _Make: - resultTyp := check.typ(arg0, false) - if resultTyp == Typ[Invalid] { - goto Error + // make(T, n) + // make(T, n, m) + // (no argument evaluated yet) + arg0 := call.Args[0] + T := check.typ(arg0) + if T == Typ[Invalid] { + return } + var min int // minimum number of arguments - switch underlying(resultTyp).(type) { + switch T.Underlying().(type) { case *Slice: min = 2 case *Map, *Chan: min = 1 default: check.invalidArg(arg0.Pos(), "cannot make %s; type must be slice, map, or channel", arg0) - goto Error + return } - if n := len(args); n < min || min+1 < n { - check.errorf(call.Pos(), "%s expects %d or %d arguments; found %d", call, min, min+1, n) - goto Error + if nargs < min || min+1 < nargs { + check.errorf(call.Pos(), "%s expects %d or %d arguments; found %d", call, min, min+1, nargs) + return } - var sizes []interface{} // constant integer arguments, if any - for _, arg := range args[1:] { - check.expr(x, arg, nil, iota) - if x.isInteger(check.ctxt) { - if x.mode == constant { - if isNegConst(x.val) { - check.invalidArg(x.pos(), "%s must not be negative", x) - // safe to continue - } else { - sizes = append(sizes, x.val) // x.val >= 0 - } - } - } else { - check.invalidArg(x.pos(), "%s must be an integer", x) - // safe to continue + var sizes []int64 // constant integer arguments, if any + for _, arg := range call.Args[1:] { + if s, ok := check.index(arg, -1); ok && s >= 0 { + sizes = append(sizes, s) } } - if len(sizes) == 2 && compareConst(sizes[0], sizes[1], token.GTR) { - check.invalidArg(args[1].Pos(), "length and capacity swapped") + if len(sizes) == 2 && sizes[0] > sizes[1] { + check.invalidArg(call.Args[1].Pos(), "length and capacity swapped") // safe to continue } - x.mode = variable - x.typ = resultTyp + x.mode = value + x.typ = T + if check.Types != nil { + params := [...]Type{T, Typ[Int], Typ[Int]} + check.recordBuiltinType(call.Fun, makeSig(x.typ, params[:1+len(sizes)]...)) + } case _New: - resultTyp := check.typ(arg0, false) - if resultTyp == Typ[Invalid] { - goto Error + // new(T) + // (no argument evaluated yet) + T := check.typ(call.Args[0]) + if T == Typ[Invalid] { + return + } + + x.mode = value + x.typ = &Pointer{base: T} + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ, T)) } - x.mode = variable - x.typ = &Pointer{Base: resultTyp} case _Panic: + // panic(x) + T := new(Interface) + if !check.assignment(x, T) { + assert(x.mode == invalid) + return + } + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, T)) + } case _Print, _Println: - for _, arg := range args { - check.expr(x, arg, nil, -1) - if x.mode == invalid { - goto Error + // 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 + } + if !check.assignment(x, nil) { + assert(x.mode == invalid) + 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 = new(Interface) + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ)) + } case _Alignof: + // unsafe.Alignof(x T) uintptr + if !check.assignment(x, nil) { + assert(x.mode == invalid) + return + } + x.mode = constant - x.val = check.ctxt.alignof(x.typ) + x.val = exact.MakeInt64(check.conf.alignof(x.typ)) x.typ = Typ[Uintptr] + // result is constant - no need to record signature case _Offsetof: - arg, ok := unparen(arg0).(*ast.SelectorExpr) - if !ok { + // unsafe.Offsetof(x T) uintptr, where x must be a selector + // (no argument evaluated yet) + arg0 := call.Args[0] + selx, _ := unparen(arg0).(*ast.SelectorExpr) + if selx == nil { check.invalidArg(arg0.Pos(), "%s is not a selector expression", arg0) - goto Error + check.use(arg0) + return } - check.expr(x, arg.X, nil, -1) + + check.expr(x, selx.X) if x.mode == invalid { - goto Error + return } - sel := arg.Sel.Name - res := lookupField(x.typ, QualifiedName{check.pkg, arg.Sel.Name}) - if res.index == nil { - check.invalidArg(x.pos(), "%s has no single field %s", x, sel) - goto Error + + base := derefStructPtr(x.typ) + sel := selx.Sel.Name + obj, index, indirect := LookupFieldOrMethod(base, false, check.pkg, sel) + switch obj.(type) { + case nil: + check.invalidArg(x.pos(), "%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: http://play.golang.org/p/al75v23kUy , + // but go/types reports: "invalid argument: x.m is a method value". + check.invalidArg(arg0.Pos(), "%s is a method value", arg0) + return } - offs := check.ctxt.offsetof(x.typ, res.index) - if offs < 0 { - check.invalidArg(x.pos(), "field %s is embedded via a pointer in %s", sel, x) - goto Error + if indirect { + check.invalidArg(x.pos(), "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 = offs + x.val = exact.MakeInt64(offs) x.typ = Typ[Uintptr] + // result is constant - no need to record signature case _Sizeof: + // unsafe.Sizeof(x T) uintptr + if !check.assignment(x, nil) { + assert(x.mode == invalid) + return + } + x.mode = constant - x.val = check.ctxt.sizeof(x.typ) + x.val = exact.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. @@ -358,48 +541,59 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota // Note: assert is only available in self-test mode. if x.mode != constant || !isBoolean(x.typ) { check.invalidArg(x.pos(), "%s is not a boolean constant", x) - goto Error + return } - pred, ok := x.val.(bool) - if !ok { + if x.val.Kind() != exact.Bool { check.errorf(x.pos(), "internal error: value of %s should be a boolean constant", x) - goto Error + return } - if !pred { + if !exact.BoolVal(x.val) { check.errorf(call.Pos(), "%s 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. - if len(args) == 0 { + // (no argument evaluated yet) + if nargs == 0 { check.dump("%s: trace() without arguments", call.Pos()) x.mode = novalue - x.expr = call - return + break } var t operand x1 := x - for _, arg := range args { - check.rawExpr(x1, arg, nil, iota, true) // permit trace for types, e.g.: new(trace(T)) + for _, arg := range call.Args { + check.rawExpr(x1, arg, nil) // permit trace for types, e.g.: new(trace(T)) check.dump("%s: %s", x1.pos(), x1) x1 = &t // use incoming x only for first argument } + // trace is only available in test mode - no need to record signature default: - check.invalidAST(call.Pos(), "unknown builtin id %d", id) - goto Error + unreachable() } - x.expr = call - return + return true +} -Error: - x.mode = invalid - x.expr = call +// makeSig makes a signature for the given argument and result types. +// Default types are used for untyped arguments, and res may be nil. +func makeSig(res Type, args ...Type) *Signature { + list := make([]*Var, len(args)) + for i, param := range args { + list[i] = NewVar(token.NoPos, nil, "", defaultType(param)) + } + params := NewTuple(list...) + var result *Tuple + if res != nil { + assert(!isUntyped(res)) + result = NewTuple(NewVar(token.NoPos, nil, "", res)) + } + return &Signature{params: params, results: result} } // implicitArrayDeref returns A if typ is of the form *A and A is an array; @@ -407,34 +601,13 @@ Error: // func implicitArrayDeref(typ Type) Type { if p, ok := typ.(*Pointer); ok { - if a, ok := underlying(p.Base).(*Array); ok { + if a, ok := p.base.Underlying().(*Array); ok { return a } } return typ } -// containsCallsOrReceives reports if x contains function calls or channel receives. -// Expects that x was type-checked already. -// -func (check *checker) containsCallsOrReceives(x ast.Expr) (found bool) { - ast.Inspect(x, func(x ast.Node) bool { - switch x := x.(type) { - case *ast.CallExpr: - // calls and conversions look the same - if !check.conversions[x] { - found = true - } - case *ast.UnaryExpr: - if x.Op == token.ARROW { - found = true - } - } - return !found // no need to continue if found - }) - return -} - // unparen removes any parentheses surrounding an expression and returns // the naked expression. // @@ -445,9 +618,9 @@ func unparen(x ast.Expr) ast.Expr { return x } -func (check *checker) complexArg(x *operand) bool { - t, _ := underlying(x.typ).(*Basic) - if t != nil && (t.Info&IsFloat != 0 || t.Kind == UntypedInt || t.Kind == UntypedRune) { +func (check *Checker) complexArg(x *operand) bool { + t, _ := x.typ.Underlying().(*Basic) + if t != nil && (t.info&IsFloat != 0 || t.kind == UntypedInt || t.kind == UntypedRune) { return true } check.invalidArg(x.pos(), "%s must be a float32, float64, or an untyped non-complex numeric constant", x) diff --git a/src/gosubli.me/something-borrowed/types/builtins_test.go b/src/gosubli.me/something-borrowed/types/builtins_test.go new file mode 100644 index 00000000..e7857994 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/builtins_test.go @@ -0,0 +1,204 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types_test + +import ( + "fmt" + "go/ast" + "go/parser" + "testing" + + _ "golang.org/x/tools/go/gcimporter" + . "golang.org/x/tools/go/types" +) + +var builtinCalls = []struct { + name, src, sig string +}{ + {"append", `var s []int; _ = append(s)`, `func([]int, ...int) []int`}, + {"append", `var s []int; _ = append(s, 0)`, `func([]int, ...int) []int`}, + {"append", `var s []int; _ = (append)(s, 0)`, `func([]int, ...int) []int`}, + {"append", `var s []byte; _ = ((append))(s, 0)`, `func([]byte, ...byte) []byte`}, + {"append", `var s []byte; _ = append(s, "foo"...)`, `func([]byte, string...) []byte`}, + {"append", `type T []byte; var s T; var str string; _ = append(s, str...)`, `func(p.T, string...) p.T`}, + {"append", `type T []byte; type U string; var s T; var str U; _ = append(s, str...)`, `func(p.T, p.U...) p.T`}, + + {"cap", `var s [10]int; _ = cap(s)`, `invalid type`}, // constant + {"cap", `var s [10]int; _ = cap(&s)`, `invalid type`}, // constant + {"cap", `var s []int64; _ = cap(s)`, `func([]int64) int`}, + {"cap", `var c chan<-bool; _ = cap(c)`, `func(chan<- bool) int`}, + + {"len", `_ = len("foo")`, `invalid type`}, // constant + {"len", `var s string; _ = len(s)`, `func(string) int`}, + {"len", `var s [10]int; _ = len(s)`, `invalid type`}, // constant + {"len", `var s [10]int; _ = len(&s)`, `invalid type`}, // constant + {"len", `var s []int64; _ = len(s)`, `func([]int64) int`}, + {"len", `var c chan<-bool; _ = len(c)`, `func(chan<- bool) int`}, + {"len", `var m map[string]float32; _ = len(m)`, `func(map[string]float32) int`}, + + {"close", `var c chan int; close(c)`, `func(chan int)`}, + {"close", `var c chan<- chan string; close(c)`, `func(chan<- chan string)`}, + + {"complex", `_ = complex(1, 0)`, `invalid type`}, // constant + {"complex", `var re float32; _ = complex(re, 1.0)`, `func(float32, float32) complex64`}, + {"complex", `var im float64; _ = complex(1, im)`, `func(float64, float64) complex128`}, + {"complex", `type F32 float32; var re, im F32; _ = complex(re, im)`, `func(p.F32, p.F32) complex64`}, + {"complex", `type F64 float64; var re, im F64; _ = complex(re, im)`, `func(p.F64, p.F64) complex128`}, + + {"copy", `var src, dst []byte; copy(dst, src)`, `func([]byte, []byte) int`}, + {"copy", `type T [][]int; var src, dst T; _ = copy(dst, src)`, `func(p.T, p.T) int`}, + {"copy", `var src string; var dst []byte; copy(dst, src)`, `func([]byte, string) int`}, + {"copy", `type T string; type U []byte; var src T; var dst U; copy(dst, src)`, `func(p.U, p.T) int`}, + {"copy", `var dst []byte; copy(dst, "hello")`, `func([]byte, string) int`}, + + {"delete", `var m map[string]bool; delete(m, "foo")`, `func(map[string]bool, string)`}, + {"delete", `type (K string; V int); var m map[K]V; delete(m, "foo")`, `func(map[p.K]p.V, p.K)`}, + + {"imag", `_ = imag(1i)`, `invalid type`}, // constant + {"imag", `var c complex64; _ = imag(c)`, `func(complex64) float32`}, + {"imag", `var c complex128; _ = imag(c)`, `func(complex128) float64`}, + {"imag", `type C64 complex64; var c C64; _ = imag(c)`, `func(p.C64) float32`}, + {"imag", `type C128 complex128; var c C128; _ = imag(c)`, `func(p.C128) float64`}, + + {"real", `_ = real(1i)`, `invalid type`}, // constant + {"real", `var c complex64; _ = real(c)`, `func(complex64) float32`}, + {"real", `var c complex128; _ = real(c)`, `func(complex128) float64`}, + {"real", `type C64 complex64; var c C64; _ = real(c)`, `func(p.C64) float32`}, + {"real", `type C128 complex128; var c C128; _ = real(c)`, `func(p.C128) float64`}, + + {"make", `_ = make([]int, 10)`, `func([]int, int) []int`}, + {"make", `type T []byte; _ = make(T, 10, 20)`, `func(p.T, int, int) p.T`}, + + {"new", `_ = new(int)`, `func(int) *int`}, + {"new", `type T struct{}; _ = new(T)`, `func(p.T) *p.T`}, + + {"panic", `panic(0)`, `func(interface{})`}, + {"panic", `panic("foo")`, `func(interface{})`}, + + {"print", `print()`, `func()`}, + {"print", `print(0)`, `func(int)`}, + {"print", `print(1, 2.0, "foo", true)`, `func(int, float64, string, bool)`}, + + {"println", `println()`, `func()`}, + {"println", `println(0)`, `func(int)`}, + {"println", `println(1, 2.0, "foo", true)`, `func(int, float64, string, bool)`}, + + {"recover", `recover()`, `func() interface{}`}, + {"recover", `_ = recover()`, `func() interface{}`}, + + {"Alignof", `_ = unsafe.Alignof(0)`, `invalid type`}, // constant + {"Alignof", `var x struct{}; _ = unsafe.Alignof(x)`, `invalid type`}, // constant + + {"Offsetof", `var x struct{f bool}; _ = unsafe.Offsetof(x.f)`, `invalid type`}, // constant + {"Offsetof", `var x struct{_ int; f bool}; _ = unsafe.Offsetof((&x).f)`, `invalid type`}, // constant + + {"Sizeof", `_ = unsafe.Sizeof(0)`, `invalid type`}, // constant + {"Sizeof", `var x struct{}; _ = unsafe.Sizeof(x)`, `invalid type`}, // constant + + {"assert", `assert(true)`, `invalid type`}, // constant + {"assert", `type B bool; const pred B = 1 < 2; assert(pred)`, `invalid type`}, // constant + + // no tests for trace since it produces output as a side-effect +} + +func TestBuiltinSignatures(t *testing.T) { + DefPredeclaredTestFuncs() + + seen := map[string]bool{"trace": true} // no test for trace built-in; add it manually + for _, call := range builtinCalls { + testBuiltinSignature(t, call.name, call.src, call.sig) + seen[call.name] = true + } + + // make sure we didn't miss one + for _, name := range Universe.Names() { + if _, ok := Universe.Lookup(name).(*Builtin); ok && !seen[name] { + t.Errorf("missing test for %s", name) + } + } + for _, name := range Unsafe.Scope().Names() { + if _, ok := Unsafe.Scope().Lookup(name).(*Builtin); ok && !seen[name] { + t.Errorf("missing test for unsafe.%s", name) + } + } +} + +func testBuiltinSignature(t *testing.T, name, src0, want string) { + src := fmt.Sprintf(`package p; import "unsafe"; type _ unsafe.Pointer /* use unsafe */; func _() { %s }`, src0) + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Errorf("%s: %s", src0, err) + return + } + + var conf Config + uses := make(map[*ast.Ident]Object) + types := make(map[ast.Expr]TypeAndValue) + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Uses: uses, Types: types}) + if err != nil { + t.Errorf("%s: %s", src0, err) + return + } + + // find called function + n := 0 + var fun ast.Expr + for x := range types { + if call, _ := x.(*ast.CallExpr); call != nil { + fun = call.Fun + n++ + } + } + if n != 1 { + t.Errorf("%s: got %d CallExprs; want 1", src0, n) + return + } + + // check recorded types for fun and descendents (may be parenthesized) + for { + // the recorded type for the built-in must match the wanted signature + typ := types[fun].Type + if typ == nil { + t.Errorf("%s: no type recorded for %s", src0, ExprString(fun)) + return + } + if got := typ.String(); got != want { + t.Errorf("%s: got type %s; want %s", src0, got, want) + return + } + + // called function must be a (possibly parenthesized, qualified) + // identifier denoting the expected built-in + switch p := fun.(type) { + case *ast.Ident: + obj := uses[p] + if obj == nil { + t.Errorf("%s: no object found for %s", src0, p) + return + } + bin, _ := obj.(*Builtin) + if bin == nil { + t.Errorf("%s: %s does not denote a built-in", src0, p) + return + } + if bin.Name() != name { + t.Errorf("%s: got built-in %s; want %s", src0, bin.Name(), name) + return + } + return // we're done + + case *ast.ParenExpr: + fun = p.X // unpack + + case *ast.SelectorExpr: + // built-in from package unsafe - ignore details + return // we're done + + default: + t.Errorf("%s: invalid function call", src0) + return + } + } +} diff --git a/src/gosubli.me/something-borrowed/types/call.go b/src/gosubli.me/something-borrowed/types/call.go new file mode 100644 index 00000000..a392d910 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/call.go @@ -0,0 +1,427 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements typechecking of call and selector expressions. + +package types + +import ( + "go/ast" + "go/token" +) + +func (check *Checker) call(x *operand, e *ast.CallExpr) exprKind { + check.exprOrType(x, e.Fun) + + switch x.mode { + case invalid: + check.use(e.Args...) + x.mode = invalid + x.expr = e + return statement + + case typexpr: + // conversion + T := x.typ + x.mode = invalid + switch n := len(e.Args); n { + case 0: + check.errorf(e.Rparen, "missing argument in conversion to %s", T) + case 1: + check.expr(x, e.Args[0]) + if x.mode != invalid { + check.conversion(x, T) + } + default: + check.errorf(e.Args[n-1].Pos(), "too many arguments in conversion to %s", T) + } + x.expr = e + return conversion + + case builtin: + id := x.id + if !check.builtin(x, e, id) { + x.mode = invalid + } + x.expr = e + // a non-constant result implies a function call + if x.mode != invalid && x.mode != constant { + check.hasCallOrRecv = true + } + return predeclaredFuncs[id].kind + + default: + // function/method call + sig, _ := x.typ.Underlying().(*Signature) + if sig == nil { + check.invalidOp(x.pos(), "cannot call non-function %s", x) + x.mode = invalid + x.expr = e + return statement + } + + arg, n, _ := unpack(func(x *operand, i int) { check.expr(x, e.Args[i]) }, len(e.Args), false) + if arg == nil { + x.mode = invalid + x.expr = e + return statement + } + + check.arguments(x, e, sig, arg, n) + + // determine result + switch sig.results.Len() { + case 0: + x.mode = novalue + case 1: + x.mode = value + x.typ = sig.results.vars[0].typ // unpack tuple + default: + x.mode = value + x.typ = sig.results + } + x.expr = e + check.hasCallOrRecv = true + + return statement + } +} + +// use type-checks each argument. +// Useful to make sure expressions are evaluated +// (and variables are "used") in the presence of other errors. +func (check *Checker) use(arg ...ast.Expr) { + var x operand + for _, e := range arg { + check.rawExpr(&x, e, nil) + } +} + +// useGetter is like use, but takes a getter instead of a list of expressions. +// It should be called instead of use if a getter is present to avoid repeated +// evaluation of the first argument (since the getter was likely obtained via +// unpack, which may have evaluated the first argument already). +func (check *Checker) useGetter(get getter, n int) { + var x operand + for i := 0; i < n; i++ { + get(&x, i) + } +} + +// A getter sets x as the i'th operand, where 0 <= i < n and n is the total +// number of operands (context-specific, and maintained elsewhere). A getter +// type-checks the i'th operand; the details of the actual check are getter- +// specific. +type getter func(x *operand, i int) + +// unpack takes a getter get and a number of operands n. If n == 1, unpack +// calls the incoming getter for the first operand. If that operand is +// invalid, unpack returns (nil, 0, false). Otherwise, if that operand is a +// function call, or a comma-ok expression and allowCommaOk is set, the result +// is a new getter and operand count providing access to the function results, +// or comma-ok values, respectively. The third result value reports if it +// is indeed the comma-ok case. In all other cases, the incoming getter and +// operand count are returned unchanged, and the third result value is false. +// +// In other words, if there's exactly one operand that - after type-checking +// by calling get - stands for multiple operands, the resulting getter provides +// access to those operands instead. +// +// If the returned getter is called at most once for a given operand index i +// (including i == 0), that operand is guaranteed to cause only one call of +// the incoming getter with that i. +// +func unpack(get getter, n int, allowCommaOk bool) (getter, int, bool) { + if n == 1 { + // possibly result of an n-valued function call or comma,ok value + var x0 operand + get(&x0, 0) + if x0.mode == invalid { + return nil, 0, false + } + + if t, ok := x0.typ.(*Tuple); ok { + // result of an n-valued function call + return func(x *operand, i int) { + x.mode = value + x.expr = x0.expr + x.typ = t.At(i).typ + }, t.Len(), false + } + + if x0.mode == mapindex || x0.mode == commaok { + // comma-ok value + if allowCommaOk { + a := [2]Type{x0.typ, Typ[UntypedBool]} + return func(x *operand, i int) { + x.mode = value + x.expr = x0.expr + x.typ = a[i] + }, 2, true + } + x0.mode = value + } + + // single value + return func(x *operand, i int) { + if i != 0 { + unreachable() + } + *x = x0 + }, 1, false + } + + // zero or multiple values + return get, n, false +} + +// arguments checks argument passing for the call with the given signature. +// The arg function provides the operand for the i'th argument. +func (check *Checker) arguments(x *operand, call *ast.CallExpr, sig *Signature, arg getter, n int) { + passSlice := false + if call.Ellipsis.IsValid() { + // last argument is of the form x... + if sig.variadic { + passSlice = true + } else { + check.errorf(call.Ellipsis, "cannot use ... in call to non-variadic %s", call.Fun) + // ok to continue + } + } + + // evaluate arguments + for i := 0; i < n; i++ { + arg(x, i) + if x.mode != invalid { + check.argument(sig, i, x, passSlice && i == n-1) + } + } + + // check argument count + if sig.variadic { + // a variadic function accepts an "empty" + // last argument: count one extra + n++ + } + if n < sig.params.Len() { + check.errorf(call.Rparen, "too few arguments in call to %s", call.Fun) + // ok to continue + } +} + +// argument checks passing of argument x to the i'th parameter of the given signature. +// If passSlice is set, the argument is followed by ... in the call. +func (check *Checker) argument(sig *Signature, i int, x *operand, passSlice bool) { + n := sig.params.Len() + + // determine parameter type + var typ Type + switch { + case i < n: + typ = sig.params.vars[i].typ + case sig.variadic: + typ = sig.params.vars[n-1].typ + if debug { + if _, ok := typ.(*Slice); !ok { + check.dump("%s: expected unnamed slice type, got %s", sig.params.vars[n-1].Pos(), typ) + } + } + default: + check.errorf(x.pos(), "too many arguments") + return + } + + if passSlice { + // argument is of the form x... + if i != n-1 { + check.errorf(x.pos(), "can only use ... with matching parameter") + return + } + if _, ok := x.typ.Underlying().(*Slice); !ok { + check.errorf(x.pos(), "cannot use %s as parameter of type %s", x, typ) + return + } + } else if sig.variadic && i >= n-1 { + // use the variadic parameter slice's element type + typ = typ.(*Slice).elem + } + + if !check.assignment(x, typ) && x.mode != invalid { + check.errorf(x.pos(), "cannot pass argument %s to parameter of type %s", x, typ) + } +} + +func (check *Checker) selector(x *operand, e *ast.SelectorExpr) { + // these must be declared before the "goto Error" statements + var ( + obj Object + index []int + indirect bool + ) + + sel := e.Sel.Name + // If the identifier refers to a package, handle everything here + // so we don't need a "package" mode for operands: package names + // can only appear in qualified identifiers which are mapped to + // selector expressions. + if ident, ok := e.X.(*ast.Ident); ok { + _, obj := check.scope.LookupParent(ident.Name) + if pkg, _ := obj.(*PkgName); pkg != nil { + assert(pkg.pkg == check.pkg) + check.recordUse(ident, pkg) + pkg.used = true + exp := pkg.imported.scope.Lookup(sel) + if exp == nil { + if !pkg.imported.fake { + check.errorf(e.Pos(), "%s not declared by package %s", sel, ident) + } + goto Error + } + if !exp.Exported() { + check.errorf(e.Pos(), "%s not exported by package %s", sel, ident) + // ok to continue + } + check.recordUse(e.Sel, exp) + // Simplified version of the code for *ast.Idents: + // - imported objects are always fully initialized + switch exp := exp.(type) { + case *Const: + assert(exp.Val() != nil) + x.mode = constant + x.typ = exp.typ + x.val = exp.val + case *TypeName: + x.mode = typexpr + x.typ = exp.typ + case *Var: + x.mode = variable + x.typ = exp.typ + case *Func: + x.mode = value + x.typ = exp.typ + case *Builtin: + x.mode = builtin + x.typ = exp.typ + x.id = exp.id + default: + unreachable() + } + x.expr = e + return + } + } + + check.exprOrType(x, e.X) + if x.mode == invalid { + goto Error + } + + obj, index, indirect = 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.invalidOp(e.Pos(), "ambiguous selector %s", sel) + case indirect: + check.invalidOp(e.Pos(), "%s is not in method set of %s", sel, x.typ) + default: + check.invalidOp(e.Pos(), "%s has no field or method %s", x, sel) + } + goto Error + } + + if x.mode == typexpr { + // method expression + m, _ := obj.(*Func) + if m == nil { + check.invalidOp(e.Pos(), "%s has no method %s", x, sel) + goto Error + } + + check.recordSelection(e, MethodExpr, x.typ, m, index, indirect) + + // the receiver type becomes the type of the first function + // argument of the method expression's function type + var params []*Var + sig := m.typ.(*Signature) + if sig.params != nil { + params = sig.params.vars + } + x.mode = value + x.typ = &Signature{ + params: NewTuple(append([]*Var{NewVar(token.NoPos, check.pkg, "", x.typ)}, params...)...), + results: sig.results, + variadic: sig.variadic, + } + + check.addDeclDep(m) + + } else { + // regular selector + switch obj := obj.(type) { + case *Var: + check.recordSelection(e, FieldVal, x.typ, obj, index, indirect) + if x.mode == variable || indirect { + x.mode = variable + } else { + x.mode = value + } + x.typ = obj.typ + + case *Func: + // TODO(gri) If we needed to take into account the receiver's + // addressability, should we report the type &(x.typ) instead? + check.recordSelection(e, MethodVal, x.typ, obj, index, indirect) + + if debug { + // Verify that LookupFieldOrMethod and MethodSet.Lookup agree. + 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("%s: (%s).%v -> %s", e.Pos(), typ, obj.name, m) + check.dump("%s\n", mset) + panic("method sets and lookup don't agree") + } + } + + x.mode = value + + // remove receiver + sig := *obj.typ.(*Signature) + sig.recv = nil + x.typ = &sig + + check.addDeclDep(obj) + + default: + unreachable() + } + } + + // everything went well + x.expr = e + return + +Error: + x.mode = invalid + x.expr = e +} diff --git a/src/gosubli.me/something-borrowed/types/check.go b/src/gosubli.me/something-borrowed/types/check.go index cf8d20de..aee4d6a2 100644 --- a/src/gosubli.me/something-borrowed/types/check.go +++ b/src/gosubli.me/something-borrowed/types/check.go @@ -2,471 +2,360 @@ // 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 typechecks a package. +// This file implements the Check function, which drives type-checking. package types import ( - "fmt" "go/ast" "go/token" + + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" +) + +// debugging/development support +const ( + debug = false // leave on during development + trace = false // turn on for detailed type resolution traces ) -// enable for debugging -const trace = false - -type checker struct { - ctxt *Context - fset *token.FileSet - files []*ast.File - - // lazily initialized - pkg *Package // current package - firsterr error // first error encountered - idents map[*ast.Ident]Object // maps identifiers to their unique object - objects map[*ast.Object]Object // maps *ast.Objects to their unique object - initspecs map[*ast.ValueSpec]*ast.ValueSpec // "inherited" type and initialization expressions for constant declarations - methods map[*TypeName]*Scope // maps type names to associated methods - conversions map[*ast.CallExpr]bool // set of type-checked conversions (to distinguish from calls) - funclist []function // list of functions/methods with correct signatures and non-empty bodies - funcsig *Signature // signature of currently typechecked function - pos []token.Pos // stack of expr positions; debugging support, used if trace is set +// If Strict is set, the type-checker enforces additional +// rules not specified by the Go 1 spec, but which will +// catch guaranteed run-time errors if the respective +// code is executed. In other words, programs passing in +// Strict mode are Go 1 compliant, but not all Go 1 programs +// will pass in Strict mode. The additional rules are: +// +// - A type assertion x.(T) where T is an interface type +// is invalid if any (statically known) method that exists +// for both x and T have different signatures. +// +const strict = false + +// exprInfo stores information about an untyped expression. +type exprInfo struct { + isLhs bool // expression is lhs operand of a shift with delayed type-check + mode operandMode + typ *Basic + val exact.Value // constant value; or nil (if not a constant) } -func (check *checker) register(id *ast.Ident, obj Object) { - // When an expression is evaluated more than once (happens - // in rare cases, e.g. for statement expressions, see - // comment in stmt.go), the object has been registered - // before. Don't do anything in that case. - if alt := check.idents[id]; alt != nil { - assert(alt == obj) - return - } - check.idents[id] = obj - if f := check.ctxt.Ident; f != nil { - f(id, obj) - } +// funcInfo stores the information required for type-checking a function. +type funcInfo struct { + name string // for debugging/tracing only + decl *declInfo // for cycle detection + sig *Signature + body *ast.BlockStmt } -// lookup returns the unique Object denoted by the identifier. -// For identifiers without assigned *ast.Object, it uses the -// checker.idents map; for identifiers with an *ast.Object it -// uses the checker.objects map. -// -// TODO(gri) Once identifier resolution is done entirely by -// the typechecker, only the idents map is needed. -// -func (check *checker) lookup(ident *ast.Ident) Object { - obj := check.idents[ident] - astObj := ident.Obj +// 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 + iota exact.Value // value of iota in a constant declaration; nil otherwise + sig *Signature // function signature if inside a function; nil otherwise + 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 +} - if obj != nil { - assert(astObj == nil || check.objects[astObj] == nil || check.objects[astObj] == obj) - return obj - } +// A Checker maintains the state of the type checker. +// It must be created with NewChecker. +type Checker struct { + // package information + // (initialized by NewChecker, valid for the life-time of checker) + conf *Config + fset *token.FileSet + pkg *Package + *Info + objMap map[Object]*declInfo // maps package-level object to declaration info + + // information collected during type-checking of a set of package files + // (initialized by Files, valid only for the duration of check.Files; + // maps and lists are allocated on demand) + files []*ast.File // package files + unusedDotImports map[*Scope]map[*Package]token.Pos // positions of unused dot-imported packages for each file scope + + firstErr error // first error encountered + methods map[string][]*Func // maps type names to associated methods + untyped map[ast.Expr]exprInfo // map of expressions without final type + funcs []funcInfo // list of functions to type-check + delayed []func() // delayed checks requiring fully setup types + + // 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 +} - if astObj == nil { - return nil +// 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 token.Pos) { + mm := check.unusedDotImports + if mm == nil { + mm = make(map[*Scope]map[*Package]token.Pos) + check.unusedDotImports = mm } - - if obj = check.objects[astObj]; obj == nil { - obj = newObj(check.pkg, astObj) - check.objects[astObj] = obj + m := mm[scope] + if m == nil { + m = make(map[*Package]token.Pos) + mm[scope] = m } - check.register(ident, obj) - - return obj + m[pkg] = pos } -type function struct { - obj *Func // for debugging/tracing only - sig *Signature - body *ast.BlockStmt +// 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) } -// later adds a function with non-empty body to the list of functions -// that need to be processed after all package-level declarations -// are typechecked. -// -func (check *checker) later(f *Func, sig *Signature, body *ast.BlockStmt) { - // functions implemented elsewhere (say in assembly) have no body - if body != nil { - check.funclist = append(check.funclist, function{f, sig, body}) +func (check *Checker) assocMethod(tname string, meth *Func) { + m := check.methods + if m == nil { + m = make(map[string][]*Func) + check.methods = m } + m[tname] = append(m[tname], meth) } -func (check *checker) declareIdent(scope *Scope, ident *ast.Ident, obj Object) { - assert(check.lookup(ident) == nil) // identifier already declared or resolved - check.register(ident, obj) - if ident.Name != "_" { - if alt := scope.Insert(obj); alt != nil { - prevDecl := "" - if pos := alt.GetPos(); pos.IsValid() { - prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", check.fset.Position(pos)) - } - check.errorf(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl)) - } +func (check *Checker) rememberUntyped(e ast.Expr, lhs bool, mode operandMode, typ *Basic, val exact.Value) { + m := check.untyped + if m == nil { + m = make(map[ast.Expr]exprInfo) + check.untyped = m } + m[e] = exprInfo{lhs, mode, typ, val} } -func (check *checker) valueSpec(pos token.Pos, obj Object, lhs []*ast.Ident, spec *ast.ValueSpec, iota int) { - if len(lhs) == 0 { - check.invalidAST(pos, "missing lhs in declaration") - return +func (check *Checker) later(name string, decl *declInfo, sig *Signature, body *ast.BlockStmt) { + check.funcs = append(check.funcs, funcInfo{name, decl, sig, body}) +} + +func (check *Checker) delay(f func()) { + check.delayed = append(check.delayed, f) +} + +// NewChecker returns a new Checker instance for a given package. +// Package files may be added incrementally via checker.Files. +func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Checker { + // make sure we have a configuration + if conf == nil { + conf = new(Config) } - // determine type for all of lhs, if any - // (but only set it for the object we typecheck!) - var typ Type - if spec.Type != nil { - typ = check.typ(spec.Type, false) + // make sure we have a package canonicalization map + if conf.Packages == nil { + conf.Packages = make(map[string]*Package) } - // len(lhs) > 0 - rhs := spec.Values - if len(lhs) == len(rhs) { - // check only lhs and rhs corresponding to obj - var l, r ast.Expr - for i, name := range lhs { - if check.lookup(name) == obj { - l = lhs[i] - r = rhs[i] - break - } - } - assert(l != nil) - switch obj := obj.(type) { - case *Const: - obj.Type = typ - case *Var: - obj.Type = typ - default: - unreachable() - } - check.assign1to1(l, r, nil, true, iota) - return + // make sure we have an info struct + if info == nil { + info = new(Info) } - // there must be a type or initialization expressions - if typ == nil && len(rhs) == 0 { - check.invalidAST(pos, "missing type or initialization expression") - typ = Typ[Invalid] + return &Checker{ + conf: conf, + fset: fset, + pkg: pkg, + Info: info, + objMap: make(map[Object]*declInfo), } +} - // if we have a type, mark all of lhs - if typ != nil { - for _, name := range lhs { - switch obj := check.lookup(name).(type) { - case *Const: - obj.Type = typ - case *Var: - obj.Type = typ - default: - unreachable() +// initFiles initializes the files-specific portion of checker. +// The provided files must all belong to the same package. +func (check *Checker) initFiles(files []*ast.File) { + // start with a clean slate (check.Files may be called multiple times) + check.files = nil + check.unusedDotImports = nil + + check.firstErr = nil + check.methods = nil + check.untyped = nil + check.funcs = nil + check.delayed = nil + + // determine package name and collect valid files + pkg := check.pkg + for _, file := range files { + switch name := file.Name.Name; pkg.name { + case "": + if name != "_" { + pkg.name = name + } else { + check.errorf(file.Name.Pos(), "invalid package name _") } + fallthrough + + case name: + check.files = append(check.files, file) + + default: + check.errorf(file.Package, "package %s; expected %s", name, pkg.name) + // ignore this file } } +} - // check initial values, if any - if len(rhs) > 0 { - // TODO(gri) should try to avoid this conversion - lhx := make([]ast.Expr, len(lhs)) - for i, e := range lhs { - lhx[i] = e - } - check.assignNtoM(lhx, rhs, true, iota) +// 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) } } -// object typechecks an object by assigning it a type. -// -func (check *checker) object(obj Object, cycleOk bool) { - switch obj := obj.(type) { - case *Package: - // nothing to do - case *Const: - if obj.Type != nil { - return // already checked - } - // The obj.Val field for constants is initialized to its respective - // iota value by the parser. - // The object's fields can be in one of the following states: - // Type != nil => the constant value is Val - // Type == nil => the constant is not typechecked yet, and Val can be: - // Val is int => Val is the value of iota for this declaration - // Val == nil => the object's expression is being evaluated - if obj.Val == nil { - check.errorf(obj.GetPos(), "illegal cycle in initialization of %s", obj.Name) - obj.Type = Typ[Invalid] - return - } - spec := obj.spec - iota := obj.Val.(int) - obj.Val = nil // mark obj as "visited" for cycle detection - // determine spec for type and initialization expressions - init := spec - if len(init.Values) == 0 { - init = check.initspecs[spec] - } - check.valueSpec(spec.Pos(), obj, spec.Names, init, iota) +// Files checks the provided files as part of the checker's package. +func (check *Checker) Files(files []*ast.File) (err error) { + defer check.handleBailout(&err) - case *Var: - if obj.Type != nil { - return // already checked - } - if obj.visited { - check.errorf(obj.GetPos(), "illegal cycle in initialization of %s", obj.Name) - obj.Type = Typ[Invalid] - return - } - switch d := obj.decl.(type) { - case *ast.Field: - unreachable() // function parameters are always typed when collected - case *ast.ValueSpec: - obj.visited = true - check.valueSpec(d.Pos(), obj, d.Names, d, 0) - case *ast.AssignStmt: - // If we reach here, we have a short variable declaration - // where the rhs didn't typecheck and thus the lhs has no - // types. - obj.visited = true - obj.Type = Typ[Invalid] - default: - unreachable() // see also function newObj - } + check.initFiles(files) - case *TypeName: - if obj.Type != nil { - return // already checked - } - typ := &NamedType{Obj: obj} - obj.Type = typ // "mark" object so recursion terminates - typ.Underlying = underlying(check.typ(obj.spec.Type, cycleOk)) - // typecheck associated method signatures - if scope := check.methods[obj]; scope != nil { - switch t := typ.Underlying.(type) { - case *Struct: - // struct fields must not conflict with methods - for _, f := range t.Fields { - if m := scope.Lookup(f.Name); m != nil { - check.errorf(m.GetPos(), "type %s has both field and method named %s", obj.Name, f.Name) - // ok to continue - } - } - case *Interface: - // methods cannot be associated with an interface type - for _, m := range scope.Entries { - recv := m.(*Func).decl.Recv.List[0].Type - check.errorf(recv.Pos(), "invalid receiver type %s (%s is an interface type)", obj.Name, obj.Name) - // ok to continue - } - } - // typecheck method signatures - var methods []*Method - for _, obj := range scope.Entries { - m := obj.(*Func) - sig := check.typ(m.decl.Type, cycleOk).(*Signature) - params, _ := check.collectParams(m.decl.Recv, false) - sig.Recv = params[0] // the parser/assocMethod ensure there is exactly one parameter - m.Type = sig - methods = append(methods, &Method{QualifiedName{check.pkg, m.Name}, sig}) - check.later(m, sig, m.decl.Body) - } - typ.Methods = methods - delete(check.methods, obj) // we don't need this scope anymore - } + check.collectObjects() - case *Func: - if obj.Type != nil { - return // already checked - } - fdecl := obj.decl - // methods are typechecked when their receivers are typechecked - if fdecl.Recv == nil { - sig := check.typ(fdecl.Type, cycleOk).(*Signature) - if obj.Name == "init" && (len(sig.Params) != 0 || len(sig.Results) != 0) { - check.errorf(fdecl.Pos(), "func init must have no arguments and no return values") - // ok to continue - } - obj.Type = sig - check.later(obj, sig, fdecl.Body) - } + check.packageObjects(check.resolveOrder()) - default: - unreachable() + check.functionBodies() + + check.initOrder() + + check.unusedImports() + + // perform delayed checks + for _, f := range check.delayed { + f() } + + check.recordUntyped() + + check.pkg.complete = true + return } -// assocInitvals associates "inherited" initialization expressions -// with the corresponding *ast.ValueSpec in the check.initspecs map -// for constant declarations without explicit initialization expressions. -// -func (check *checker) assocInitvals(decl *ast.GenDecl) { - var last *ast.ValueSpec - for _, s := range decl.Specs { - if s, ok := s.(*ast.ValueSpec); ok { - if len(s.Values) > 0 { - last = s - } else { - check.initspecs[s] = last - } - } +func (check *Checker) recordUntyped() { + if !debug && check.Types == nil { + return // nothing to do } - if last == nil { - check.invalidAST(decl.Pos(), "no initialization values provided") + + for x, info := range check.untyped { + if debug && isTyped(info.typ) { + check.dump("%s: %s (type %s) is typed", x.Pos(), x, info.typ) + unreachable() + } + check.recordTypeAndValue(x, info.mode, info.typ, info.val) } } -// assocMethod associates a method declaration with the respective -// receiver base type. meth.Recv must exist. -// -func (check *checker) assocMethod(meth *ast.FuncDecl) { - // The receiver type is one of the following (enforced by parser): - // - *ast.Ident - // - *ast.StarExpr{*ast.Ident} - // - *ast.BadExpr (parser error) - typ := meth.Recv.List[0].Type - if ptr, ok := typ.(*ast.StarExpr); ok { - typ = ptr.X +func (check *Checker) recordTypeAndValue(x ast.Expr, mode operandMode, typ Type, val exact.Value) { + assert(x != nil) + assert(typ != nil) + if mode == invalid { + return // omit } - // determine receiver base type name - ident, ok := typ.(*ast.Ident) - if !ok { - // not an identifier - parser reported error already - return // ignore this method + assert(typ != nil) + if mode == constant { + assert(val != nil) + assert(typ == Typ[Invalid] || isConstType(typ)) } - // determine receiver base type object - var tname *TypeName - if obj := check.lookup(ident); obj != nil { - obj, ok := obj.(*TypeName) - if !ok { - check.errorf(ident.Pos(), "%s is not a type", ident.Name) - return // ignore this method - } - if obj.spec == nil { - check.errorf(ident.Pos(), "cannot define method on non-local type %s", ident.Name) - return // ignore this method - } - tname = obj - } else { - // identifier not declared/resolved - parser reported error already - return // ignore this method - } - // declare method in receiver base type scope - scope := check.methods[tname] - if scope == nil { - scope = new(Scope) - check.methods[tname] = scope + if m := check.Types; m != nil { + m[x] = TypeAndValue{mode, typ, val} } - check.declareIdent(scope, meth.Name, &Func{Pkg: check.pkg, Name: meth.Name.Name, decl: meth}) } -func (check *checker) decl(decl ast.Decl) { - switch d := decl.(type) { - case *ast.BadDecl: - // ignore - case *ast.GenDecl: - for _, spec := range d.Specs { - switch s := spec.(type) { - case *ast.ImportSpec: - // nothing to do (handled by check.resolve) - case *ast.ValueSpec: - for _, name := range s.Names { - check.object(check.lookup(name), false) - } - case *ast.TypeSpec: - check.object(check.lookup(s.Name), false) - default: - check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s) - } - } - case *ast.FuncDecl: - // methods are checked when their respective base types are checked - if d.Recv != nil { - return - } - obj := check.lookup(d.Name) - // Initialization functions don't have an object associated with them - // since they are not in any scope. Create a dummy object for them. - if d.Name.Name == "init" { - assert(obj == nil) // all other functions should have an object - obj = &Func{Pkg: check.pkg, Name: d.Name.Name, decl: d} - check.register(d.Name, obj) +func (check *Checker) recordBuiltinType(f ast.Expr, sig *Signature) { + // f must be a (possibly parenthesized) identifier denoting a built-in + // (built-ins in package unsafe always produce a constant result and + // we don't record their signatures, so we don't see qualified idents + // here): record the signature for f and possible children. + for { + check.recordTypeAndValue(f, builtin, sig, nil) + switch p := f.(type) { + case *ast.Ident: + return // we're done + case *ast.ParenExpr: + f = p.X + default: + unreachable() } - check.object(obj, false) - default: - check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d) } } -// A bailout panic is raised to indicate early termination. -type bailout struct{} - -func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (pkg *Package, err error) { - // initialize checker - check := checker{ - ctxt: ctxt, - fset: fset, - files: files, - idents: make(map[*ast.Ident]Object), - objects: make(map[*ast.Object]Object), - initspecs: make(map[*ast.ValueSpec]*ast.ValueSpec), - methods: make(map[*TypeName]*Scope), - conversions: make(map[*ast.CallExpr]bool), +func (check *Checker) recordCommaOkTypes(x ast.Expr, a [2]Type) { + assert(x != nil) + if a[0] == nil || a[1] == nil { + return } - - // set results and handle panics - defer func() { - pkg = check.pkg - switch p := recover().(type) { - case nil, bailout: - // normal return or early exit - err = check.firsterr - default: - // unexpected panic: don't crash clients - const debug = true - if debug { - check.dump("INTERNAL PANIC: %v", p) - panic(p) + assert(isTyped(a[0]) && isTyped(a[1]) && isBoolean(a[1])) + if m := check.Types; m != nil { + for { + tv := m[x] + assert(tv.Type != nil) // should have been recorded already + pos := x.Pos() + tv.Type = NewTuple( + NewVar(pos, check.pkg, "", a[0]), + NewVar(pos, check.pkg, "", a[1]), + ) + m[x] = tv + // if x is a parenthesized expression (p.X), update p.X + p, _ := x.(*ast.ParenExpr) + if p == nil { + break } - // TODO(gri) add a test case for this scenario - err = fmt.Errorf("types internal error: %v", p) + x = p.X } - }() + } +} - // resolve identifiers - imp := ctxt.Import - if imp == nil { - imp = GcImport +func (check *Checker) recordDef(id *ast.Ident, obj Object) { + assert(id != nil) + if m := check.Defs; m != nil { + m[id] = obj } - methods := check.resolve(imp) +} - // associate methods with types - for _, m := range methods { - check.assocMethod(m) +func (check *Checker) recordUse(id *ast.Ident, obj Object) { + assert(id != nil) + assert(obj != nil) + if m := check.Uses; m != nil { + m[id] = obj } +} - // typecheck all declarations - for _, f := range check.files { - for _, d := range f.Decls { - check.decl(d) - } +func (check *Checker) recordImplicit(node ast.Node, obj Object) { + assert(node != nil) + assert(obj != nil) + if m := check.Implicits; m != nil { + m[node] = obj } +} - // typecheck all function/method bodies - // (funclist may grow when checking statements - do not use range clause!) - for i := 0; i < len(check.funclist); i++ { - f := check.funclist[i] - if trace { - s := "" - if f.obj != nil { - s = f.obj.Name - } - fmt.Println("---", s) - } - check.funcsig = f.sig - check.stmtList(f.body.List) +func (check *Checker) recordSelection(x *ast.SelectorExpr, kind SelectionKind, recv Type, obj Object, index []int, indirect bool) { + assert(obj != nil && (recv == nil || len(index) > 0)) + check.recordUse(x.Sel, obj) + // TODO(gri) Should we also call recordTypeAndValue? + if m := check.Selections; m != nil { + m[x] = &Selection{kind, recv, obj, index, indirect} } +} - return +func (check *Checker) recordScope(node ast.Node, scope *Scope) { + assert(node != nil) + assert(scope != nil) + if m := check.Scopes; m != nil { + m[node] = scope + } } diff --git a/src/gosubli.me/something-borrowed/types/check_test.go b/src/gosubli.me/something-borrowed/types/check_test.go new file mode 100644 index 00000000..aefaa5a6 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/check_test.go @@ -0,0 +1,294 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements a typechecker test harness. The packages specified +// in tests are typechecked. Error messages reported by the typechecker are +// compared against the error messages expected in the test files. +// +// Expected errors are indicated in the test files by putting a comment +// of the form /* ERROR "rx" */ immediately following an offending token. +// The harness will verify that an error matching the regular expression +// rx is reported at that source position. Consecutive comments may be +// used to indicate multiple errors for the same token position. +// +// For instance, the following test file indicates that a "not declared" +// error should be reported for the undeclared variable x: +// +// package p +// func f() { +// _ = x /* ERROR "not declared" */ + 1 +// } + +// TODO(gri) Also collect strict mode errors of the form /* STRICT ... */ +// and test against strict mode. + +package types_test + +import ( + "flag" + "go/ast" + "go/parser" + "go/scanner" + "go/token" + "io/ioutil" + "regexp" + "strings" + "testing" + + _ "golang.org/x/tools/go/gcimporter" + . "golang.org/x/tools/go/types" +) + +var ( + listErrors = flag.Bool("list", false, "list errors") + testFiles = flag.String("files", "", "space-separated list of test files") +) + +// The test filenames do not end in .go so that they are invisible +// to gofmt since they contain comments that must not change their +// positions relative to surrounding tokens. + +// Each tests entry is list of files belonging to the same package. +var tests = [][]string{ + {"testdata/errors.src"}, + {"testdata/importdecl0a.src", "testdata/importdecl0b.src"}, + {"testdata/importdecl1a.src", "testdata/importdecl1b.src"}, + {"testdata/cycles.src"}, + {"testdata/cycles1.src"}, + {"testdata/cycles2.src"}, + {"testdata/cycles3.src"}, + {"testdata/cycles4.src"}, + {"testdata/init0.src"}, + {"testdata/init1.src"}, + {"testdata/init2.src"}, + {"testdata/decls0.src"}, + {"testdata/decls1.src"}, + {"testdata/decls2a.src", "testdata/decls2b.src"}, + {"testdata/decls3.src"}, + {"testdata/const0.src"}, + {"testdata/const1.src"}, + {"testdata/constdecl.src"}, + {"testdata/vardecl.src"}, + {"testdata/expr0.src"}, + {"testdata/expr1.src"}, + {"testdata/expr2.src"}, + {"testdata/expr3.src"}, + {"testdata/methodsets.src"}, + {"testdata/shifts.src"}, + {"testdata/builtins.src"}, + {"testdata/conversions.src"}, + {"testdata/stmt0.src"}, + {"testdata/stmt1.src"}, + {"testdata/gotos.src"}, + {"testdata/labels.src"}, + {"testdata/issues.src"}, + {"testdata/blank.src"}, +} + +var fset = token.NewFileSet() + +// Positioned errors are of the form filename:line:column: message . +var posMsgRx = regexp.MustCompile(`^(.*:[0-9]+:[0-9]+): *(.*)`) + +// splitError splits an error's error message into a position string +// and the actual error message. If there's no position information, +// pos is the empty string, and msg is the entire error message. +// +func splitError(err error) (pos, msg string) { + msg = err.Error() + if m := posMsgRx.FindStringSubmatch(msg); len(m) == 3 { + pos = m[1] + msg = m[2] + } + return +} + +func parseFiles(t *testing.T, filenames []string) ([]*ast.File, []error) { + var files []*ast.File + var errlist []error + for _, filename := range filenames { + file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) + if file == nil { + t.Fatalf("%s: %s", filename, err) + } + files = append(files, file) + if err != nil { + if list, _ := err.(scanner.ErrorList); len(list) > 0 { + for _, err := range list { + errlist = append(errlist, err) + } + } else { + errlist = append(errlist, err) + } + } + } + return files, errlist +} + +// ERROR comments must start with text `ERROR "rx"` or `ERROR rx` where +// rx is a regular expression that matches the expected error message. +// Space around "rx" or rx is ignored. Use the form `ERROR HERE "rx"` +// for error messages that are located immediately after rather than +// at a token's position. +// +var errRx = regexp.MustCompile(`^ *ERROR *(HERE)? *"?([^"]*)"?`) + +// errMap collects the regular expressions of ERROR comments found +// in files and returns them as a map of error positions to error messages. +// +func errMap(t *testing.T, testname string, files []*ast.File) map[string][]string { + // map of position strings to lists of error message patterns + errmap := make(map[string][]string) + + for _, file := range files { + filename := fset.Position(file.Package).Filename + src, err := ioutil.ReadFile(filename) + if err != nil { + t.Fatalf("%s: could not read %s", testname, filename) + } + + var s scanner.Scanner + s.Init(fset.AddFile(filename, -1, len(src)), src, nil, scanner.ScanComments) + var prev token.Pos // position of last non-comment, non-semicolon token + var here token.Pos // position immediately after the token at position prev + + scanFile: + for { + pos, tok, lit := s.Scan() + switch tok { + case token.EOF: + break scanFile + case token.COMMENT: + if lit[1] == '*' { + lit = lit[:len(lit)-2] // strip trailing */ + } + if s := errRx.FindStringSubmatch(lit[2:]); len(s) == 3 { + pos := prev + if s[1] == "HERE" { + pos = here + } + p := fset.Position(pos).String() + errmap[p] = append(errmap[p], strings.TrimSpace(s[2])) + } + case token.SEMICOLON: + // ignore automatically inserted semicolon + if lit == "\n" { + continue scanFile + } + fallthrough + default: + prev = pos + var l int // token length + if tok.IsLiteral() { + l = len(lit) + } else { + l = len(tok.String()) + } + here = prev + token.Pos(l) + } + } + } + + return errmap +} + +func eliminate(t *testing.T, errmap map[string][]string, errlist []error) { + for _, err := range errlist { + pos, gotMsg := splitError(err) + list := errmap[pos] + index := -1 // list index of matching message, if any + // we expect one of the messages in list to match the error at pos + for i, wantRx := range list { + rx, err := regexp.Compile(wantRx) + if err != nil { + t.Errorf("%s: %v", pos, err) + continue + } + if rx.MatchString(gotMsg) { + index = i + break + } + } + if index >= 0 { + // eliminate from list + if n := len(list) - 1; n > 0 { + // not the last entry - swap in last element and shorten list by 1 + list[index] = list[n] + errmap[pos] = list[:n] + } else { + // last entry - remove list from map + delete(errmap, pos) + } + } else { + t.Errorf("%s: no error expected: %q", pos, gotMsg) + } + } +} + +func checkFiles(t *testing.T, testfiles []string) { + // parse files and collect parser errors + files, errlist := parseFiles(t, testfiles) + + pkgName := "" + if len(files) > 0 { + pkgName = files[0].Name.Name + } + + if *listErrors && len(errlist) > 0 { + t.Errorf("--- %s:", pkgName) + for _, err := range errlist { + t.Error(err) + } + } + + // typecheck and collect typechecker errors + var conf Config + conf.Error = func(err error) { + if *listErrors { + t.Error(err) + return + } + // Ignore secondary error messages starting with "\t"; + // they are clarifying messages for a primary error. + if !strings.Contains(err.Error(), ": \t") { + errlist = append(errlist, err) + } + } + conf.Check(pkgName, fset, files, nil) + + if *listErrors { + return + } + + // match and eliminate errors; + // we are expecting the following errors + errmap := errMap(t, pkgName, files) + eliminate(t, errmap, errlist) + + // there should be no expected errors left + if len(errmap) > 0 { + t.Errorf("--- %s: %d source positions with expected (but not reported) errors:", pkgName, len(errmap)) + for pos, list := range errmap { + for _, rx := range list { + t.Errorf("%s: %q", pos, rx) + } + } + } +} + +func TestCheck(t *testing.T) { + // Declare builtins for testing. + DefPredeclaredTestFuncs() + + // If explicit test files are specified, only check those. + if files := *testFiles; files != "" { + checkFiles(t, strings.Split(files, " ")) + return + } + + // Otherwise, run all the tests. + for _, files := range tests { + checkFiles(t, files) + } +} diff --git a/src/gosubli.me/something-borrowed/types/const.go b/src/gosubli.me/something-borrowed/types/const.go deleted file mode 100644 index 503652e7..00000000 --- a/src/gosubli.me/something-borrowed/types/const.go +++ /dev/null @@ -1,718 +0,0 @@ -// 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 operations on constant values. - -package types - -import ( - "fmt" - "go/token" - "math/big" - "strconv" -) - -// TODO(gri) At the moment, constants are different types -// passed around as interface{} values. Introduce a Const -// interface and use methods instead of xConst functions. - -// Representation of constant values. -// -// bool -> bool (true, false) -// numeric -> int64, *big.Int, *big.Rat, Complex (ordered by increasing data structure "size") -// string -> string -// nil -> NilType (nilConst) -// -// Numeric constants are normalized after each operation such -// that they are represented by the "smallest" data structure -// required to represent the constant, independent of actual -// type. Non-numeric constants are always normalized. - -// Representation of complex numbers. -type Complex struct { - Re, Im *big.Rat -} - -func (c Complex) String() string { - if c.Re.Sign() == 0 { - return fmt.Sprintf("%si", c.Im) - } - // normalized complex values always have an imaginary part - return fmt.Sprintf("(%s + %si)", c.Re, c.Im) -} - -// Representation of nil. -type NilType struct{} - -func (NilType) String() string { - return "nil" -} - -// Frequently used values. -var ( - nilConst = NilType{} - zeroConst = int64(0) -) - -// int64 bounds -var ( - minInt64 = big.NewInt(-1 << 63) - maxInt64 = big.NewInt(1<<63 - 1) -) - -// normalizeIntConst returns the smallest constant representation -// for the specific value of x; either an int64 or a *big.Int value. -// -func normalizeIntConst(x *big.Int) interface{} { - if minInt64.Cmp(x) <= 0 && x.Cmp(maxInt64) <= 0 { - return x.Int64() - } - return x -} - -// normalizeRatConst returns the smallest constant representation -// for the specific value of x; either an int64, *big.Int, -// or *big.Rat value. -// -func normalizeRatConst(x *big.Rat) interface{} { - if x.IsInt() { - return normalizeIntConst(x.Num()) - } - return x -} - -// newComplex returns the smallest constant representation -// for the specific value re + im*i; either an int64, *big.Int, -// *big.Rat, or complex value. -// -func newComplex(re, im *big.Rat) interface{} { - if im.Sign() == 0 { - return normalizeRatConst(re) - } - return Complex{re, im} -} - -// makeRuneConst returns the int64 code point for the rune literal -// lit. The result is nil if lit is not a correct rune literal. -// -func makeRuneConst(lit string) interface{} { - if n := len(lit); n >= 2 { - if code, _, _, err := strconv.UnquoteChar(lit[1:n-1], '\''); err == nil { - return int64(code) - } - } - return nil -} - -// makeRuneConst returns the smallest integer constant representation -// (int64, *big.Int) for the integer literal lit. The result is nil if -// lit is not a correct integer literal. -// -func makeIntConst(lit string) interface{} { - if x, err := strconv.ParseInt(lit, 0, 64); err == nil { - return x - } - if x, ok := new(big.Int).SetString(lit, 0); ok { - return x - } - return nil -} - -// makeFloatConst returns the smallest floating-point constant representation -// (int64, *big.Int, *big.Rat) for the floating-point literal lit. The result -// is nil if lit is not a correct floating-point literal. -// -func makeFloatConst(lit string) interface{} { - if x, ok := new(big.Rat).SetString(lit); ok { - return normalizeRatConst(x) - } - return nil -} - -// makeComplexConst returns the complex constant representation (Complex) for -// the imaginary literal lit. The result is nil if lit is not a correct imaginary -// literal. -// -func makeComplexConst(lit string) interface{} { - n := len(lit) - if n > 0 && lit[n-1] == 'i' { - if im, ok := new(big.Rat).SetString(lit[0 : n-1]); ok { - return newComplex(big.NewRat(0, 1), im) - } - } - return nil -} - -// makeStringConst returns the string constant representation (string) for -// the string literal lit. The result is nil if lit is not a correct string -// literal. -// -func makeStringConst(lit string) interface{} { - if s, err := strconv.Unquote(lit); err == nil { - return s - } - return nil -} - -// toImagConst returns the constant Complex(0, x) for a non-complex x. -func toImagConst(x interface{}) interface{} { - var im *big.Rat - switch x := x.(type) { - case int64: - im = big.NewRat(x, 1) - case *big.Int: - im = new(big.Rat).SetFrac(x, int1) - case *big.Rat: - im = x - default: - unreachable() - } - return Complex{rat0, im} -} - -// isZeroConst reports whether the value of constant x is 0. -// x must be normalized. -// -func isZeroConst(x interface{}) bool { - i, ok := x.(int64) // good enough since constants are normalized - return ok && i == 0 -} - -// isNegConst reports whether the value of constant x is < 0. -// x must be a non-complex numeric value. -// -func isNegConst(x interface{}) bool { - switch x := x.(type) { - case int64: - return x < 0 - case *big.Int: - return x.Sign() < 0 - case *big.Rat: - return x.Sign() < 0 - } - unreachable() - return false -} - -// isRepresentableConst reports whether the value of constant x can -// be represented as a value of the basic type Typ[as] without loss -// of precision. -// -func isRepresentableConst(x interface{}, ctxt *Context, as BasicKind) bool { - switch x := x.(type) { - case bool: - return as == Bool || as == UntypedBool - - case int64: - switch as { - case Int: - var s = uint(ctxt.sizeof(Typ[as])) * 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: - return true - case Uint, Uintptr: - var s = uint(ctxt.sizeof(Typ[as])) * 8 - return 0 <= x && x <= int64(1)<<(s-1)-1 - case Uint8: - const s = 8 - return 0 <= x && x <= 1<= 0 && x.BitLen() <= int(s) - case Uint64: - return x.Sign() >= 0 && x.BitLen() <= 64 - case Float32: - return true // TODO(gri) fix this - case Float64: - return true // TODO(gri) fix this - case Complex64: - return true // TODO(gri) fix this - case Complex128: - return true // TODO(gri) fix this - case UntypedInt, UntypedFloat, UntypedComplex: - return true - } - - case *big.Rat: - switch as { - case Float32: - return true // TODO(gri) fix this - case Float64: - return true // TODO(gri) fix this - case Complex64: - return true // TODO(gri) fix this - case Complex128: - return true // TODO(gri) fix this - case UntypedFloat, UntypedComplex: - return true - } - - case Complex: - switch as { - case Complex64: - return true // TODO(gri) fix this - case Complex128: - return true // TODO(gri) fix this - case UntypedComplex: - return true - } - - case string: - return as == String || as == UntypedString - - case NilType: - return as == UntypedNil || as == UnsafePointer - - default: - unreachable() - } - - return false -} - -var ( - int1 = big.NewInt(1) - rat0 = big.NewRat(0, 1) -) - -// complexity returns a measure of representation complexity for constant x. -func complexity(x interface{}) int { - switch x.(type) { - case bool, string, NilType: - return 1 - case int64: - return 2 - case *big.Int: - return 3 - case *big.Rat: - return 4 - case Complex: - return 5 - } - unreachable() - return 0 -} - -// matchConst returns the matching representation (same type) with the -// smallest complexity for two constant values x and y. They must be -// of the same "kind" (boolean, numeric, string, or NilType). -// -func matchConst(x, y interface{}) (_, _ interface{}) { - if complexity(x) > complexity(y) { - y, x = matchConst(y, x) - return x, y - } - // complexity(x) <= complexity(y) - - switch x := x.(type) { - case bool, Complex, string, NilType: - return x, y - - case int64: - switch y := y.(type) { - case int64: - return x, y - case *big.Int: - return big.NewInt(x), y - case *big.Rat: - return big.NewRat(x, 1), y - case Complex: - return Complex{big.NewRat(x, 1), rat0}, y - } - - case *big.Int: - switch y := y.(type) { - case *big.Int: - return x, y - case *big.Rat: - return new(big.Rat).SetFrac(x, int1), y - case Complex: - return Complex{new(big.Rat).SetFrac(x, int1), rat0}, y - } - - case *big.Rat: - switch y := y.(type) { - case *big.Rat: - return x, y - case Complex: - return Complex{x, rat0}, y - } - } - - unreachable() - return nil, nil -} - -// is32bit reports whether x can be represented using 32 bits. -func is32bit(x int64) bool { - return -1<<31 <= x && x <= 1<<31-1 -} - -// is63bit reports whether x can be represented using 63 bits. -func is63bit(x int64) bool { - return -1<<62 <= x && x <= 1<<62-1 -} - -// unaryOpConst returns the result of the constant evaluation op x where x is of the given type. -func unaryOpConst(x interface{}, ctxt *Context, op token.Token, typ *Basic) interface{} { - switch op { - case token.ADD: - return x // nothing to do - case token.SUB: - switch x := x.(type) { - case int64: - if z := -x; z != x { - return z // no overflow - } - // overflow - need to convert to big.Int - return normalizeIntConst(new(big.Int).Neg(big.NewInt(x))) - case *big.Int: - return normalizeIntConst(new(big.Int).Neg(x)) - case *big.Rat: - return normalizeRatConst(new(big.Rat).Neg(x)) - case Complex: - return newComplex(new(big.Rat).Neg(x.Re), new(big.Rat).Neg(x.Im)) - } - case token.XOR: - var z big.Int - switch x := x.(type) { - case int64: - z.Not(big.NewInt(x)) - case *big.Int: - z.Not(x) - default: - unreachable() - } - // For unsigned types, the result will be negative and - // thus "too large": We must limit the result size to - // the type's size. - if typ.Info&IsUnsigned != 0 { - s := uint(ctxt.sizeof(typ)) * 8 - z.AndNot(&z, new(big.Int).Lsh(big.NewInt(-1), s)) // z &^= (-1)<>). x must be an -// integer constant. -// -func shiftConst(x interface{}, s uint, op token.Token) interface{} { - switch x := x.(type) { - case int64: - switch op { - case token.SHL: - z := big.NewInt(x) - return normalizeIntConst(z.Lsh(z, s)) - case token.SHR: - return x >> s - } - - case *big.Int: - var z big.Int - switch op { - case token.SHL: - return normalizeIntConst(z.Lsh(x, s)) - case token.SHR: - return normalizeIntConst(z.Rsh(x, s)) - } - } - - unreachable() - return nil -} - -// compareConst returns the result of the constant comparison x op y; -// both operands must be of the same "kind" (boolean, numeric, string, -// or NilType). -// -func compareConst(x, y interface{}, op token.Token) (z bool) { - x, y = matchConst(x, y) - - // x == y => x == y - // x != y => x != y - // x > y => y < x - // x >= y => u <= x - swap := false - switch op { - case token.GTR: - swap = true - op = token.LSS - case token.GEQ: - swap = true - op = token.LEQ - } - - // x == y => x == y - // x != y => !(x == y) - // x < y => x < y - // x <= y => !(y < x) - negate := false - switch op { - case token.NEQ: - negate = true - op = token.EQL - case token.LEQ: - swap = !swap - negate = true - op = token.LSS - } - - if negate { - defer func() { z = !z }() - } - - if swap { - x, y = y, x - } - - switch x := x.(type) { - case bool: - if op == token.EQL { - return x == y.(bool) - } - - case int64: - y := y.(int64) - switch op { - case token.EQL: - return x == y - case token.LSS: - return x < y - } - - case *big.Int: - s := x.Cmp(y.(*big.Int)) - switch op { - case token.EQL: - return s == 0 - case token.LSS: - return s < 0 - } - - case *big.Rat: - s := x.Cmp(y.(*big.Rat)) - switch op { - case token.EQL: - return s == 0 - case token.LSS: - return s < 0 - } - - case Complex: - y := y.(Complex) - if op == token.EQL { - return x.Re.Cmp(y.Re) == 0 && x.Im.Cmp(y.Im) == 0 - } - - case string: - y := y.(string) - switch op { - case token.EQL: - return x == y - case token.LSS: - return x < y - } - - case NilType: - if op == token.EQL { - return x == y.(NilType) - } - } - - fmt.Printf("x = %s (%T), y = %s (%T)\n", x, x, y, y) - unreachable() - return -} diff --git a/src/gosubli.me/something-borrowed/types/conversions.go b/src/gosubli.me/something-borrowed/types/conversions.go index fcbaf771..7ad2096e 100644 --- a/src/gosubli.me/something-borrowed/types/conversions.go +++ b/src/gosubli.me/something-borrowed/types/conversions.go @@ -6,67 +6,81 @@ package types -import ( - "go/ast" -) - -// conversion typechecks the type conversion conv to type typ. iota is the current -// value of iota or -1 if iota doesn't have a value in the current context. The result -// of the conversion is returned via x. If the conversion has type errors, the returned -// x is marked as invalid (x.mode == invalid). -// -func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type, iota int) { - // all conversions have one argument - if len(conv.Args) != 1 { - check.invalidOp(conv.Pos(), "%s conversion requires exactly one argument", conv) - goto Error - } +import "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" - // evaluate argument - check.expr(x, conv.Args[0], nil, iota) - if x.mode == invalid { - goto Error - } +// Conversion type-checks the conversion T(x). +// The result is in x. +func (check *Checker) conversion(x *operand, T Type) { + constArg := x.mode == constant - if x.mode == constant && isConstType(typ) { + var ok bool + switch { + case constArg && isConstType(T): // constant conversion - // TODO(gri) implement this - } else { - // non-constant conversion - if !x.isConvertible(check.ctxt, typ) { - check.invalidOp(conv.Pos(), "cannot convert %s to %s", x, typ) - goto Error + switch t := T.Underlying().(*Basic); { + case representableConst(x.val, check.conf, t.kind, &x.val): + ok = true + case x.isInteger() && isString(t): + codepoint := int64(-1) + if i, ok := exact.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 = exact.MakeString(string(codepoint)) + ok = true } + case x.convertibleTo(check.conf, T): + // non-constant conversion x.mode = value + ok = true } - check.conversions[conv] = true // for cap/len checking - x.expr = conv - x.typ = typ - return + if !ok { + check.errorf(x.pos(), "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,...". + final := x.typ + 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. + if isInterface(T) || constArg && !isConstType(T) { + final = defaultType(x.typ) + } + check.updateExprType(x.expr, final, true) + } -Error: - x.mode = invalid + x.typ = T } -func (x *operand) isConvertible(ctxt *Context, T Type) bool { +func (x *operand) convertibleTo(conf *Config, T Type) bool { // "x is assignable to T" - if x.isAssignable(ctxt, T) { + if x.assignableTo(conf, T) { return true } // "x's type and T have identical underlying types" V := x.typ - Vu := underlying(V) - Tu := underlying(T) - if IsIdentical(Vu, Tu) { + Vu := V.Underlying() + Tu := T.Underlying() + if Identical(Vu, Tu) { return true } // "x's type and T are unnamed pointer types and their pointer base types have identical underlying types" if V, ok := V.(*Pointer); ok { if T, ok := T.(*Pointer); ok { - if IsIdentical(underlying(V.Base), underlying(T.Base)) { + if Identical(V.base.Underlying(), T.base.Underlying()) { return true } } @@ -106,24 +120,27 @@ func (x *operand) isConvertible(ctxt *Context, T Type) bool { } func isUintptr(typ Type) bool { - t, ok := typ.(*Basic) - return ok && t.Kind == Uintptr + t, ok := typ.Underlying().(*Basic) + return ok && t.kind == Uintptr } func isUnsafePointer(typ Type) bool { - t, ok := typ.(*Basic) - return ok && t.Kind == UnsafePointer + // TODO(gri): Is this (typ.Underlying() instead of just typ) correct? + // The spec does not say so, but gc claims it is. See also + // issue 6326. + t, ok := typ.Underlying().(*Basic) + return ok && t.kind == UnsafePointer } func isPointer(typ Type) bool { - _, ok := typ.(*Pointer) + _, ok := typ.Underlying().(*Pointer) return ok } func isBytesOrRunes(typ Type) bool { if s, ok := typ.(*Slice); ok { - t, ok := underlying(s.Elt).(*Basic) - return ok && (t.Kind == Byte || t.Kind == Rune) + t, ok := s.elem.Underlying().(*Basic) + return ok && (t.kind == Byte || t.kind == Rune) } return false } diff --git a/src/gosubli.me/something-borrowed/types/decl.go b/src/gosubli.me/something-borrowed/types/decl.go new file mode 100644 index 00000000..0cba571a --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/decl.go @@ -0,0 +1,419 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types + +import ( + "go/ast" + "go/token" + + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" +) + +func (check *Checker) reportAltDecl(obj Object) { + if pos := obj.Pos(); pos.IsValid() { + // 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 *ast.Ident, obj Object) { + // 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 + } + } + if id != nil { + check.recordDef(id, obj) + } +} + +// objDecl type-checks the declaration of obj in its respective (file) context. +// See check.typ for the details on def and path. +func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) { + if obj.Type() != nil { + return // already checked - nothing to do + } + + if trace { + check.trace(obj.Pos(), "-- declaring %s", obj.Name()) + check.indent++ + defer func() { + check.indent-- + check.trace(obj.Pos(), "=> %s", obj) + }() + } + + d := check.objMap[obj] + if d == nil { + check.dump("%s: %s should have been declared", obj.Pos(), obj.Name()) + unreachable() + } + + // save/restore current context and setup object context + defer func(ctxt context) { + check.context = ctxt + }(check.context) + check.context = context{ + scope: d.file, + } + + // Const and var declarations must not have initialization + // cycles. We track them by remembering the current declaration + // in check.decl. Initialization expressions depending on other + // consts, vars, or functions, add dependencies to the current + // check.decl. + switch obj := obj.(type) { + case *Const: + check.decl = d // new package-level const decl + check.constDecl(obj, d.typ, d.init) + case *Var: + check.decl = d // new package-level var decl + check.varDecl(obj, d.lhs, d.typ, d.init) + case *TypeName: + // invalid recursive types are detected via path + check.typeDecl(obj, d.typ, def, path) + case *Func: + // functions may be recursive - no need to track dependencies + check.funcDecl(obj, d) + default: + unreachable() + } +} + +func (check *Checker) constDecl(obj *Const, typ, init ast.Expr) { + assert(obj.typ == nil) + + if obj.visited { + obj.typ = Typ[Invalid] + return + } + obj.visited = true + + // use the correct value of iota + assert(check.iota == nil) + check.iota = obj.val + defer func() { check.iota = nil }() + + // provide valid constant value under all circumstances + obj.val = exact.MakeUnknown() + + // determine type, if any + if typ != nil { + t := check.typ(typ) + if !isConstType(t) { + check.errorf(typ.Pos(), "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 ast.Expr) { + assert(obj.typ == nil) + + if obj.visited { + obj.typ = Typ[Invalid] + return + } + obj.visited = true + + // var declarations cannot use iota + assert(check.iota == nil) + + // determine type, if any + if typ != nil { + obj.typ = check.typ(typ) + } + + // 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, false) + 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") + } + } + check.initVars(lhs, []ast.Expr{init}, token.NoPos) +} + +// underlying returns the underlying type of typ; possibly by following +// forward chains of named types. Such chains only exist while named types +// are incomplete. +func underlying(typ Type) Type { + for { + n, _ := typ.(*Named) + if n == nil { + break + } + typ = n.underlying + } + return typ +} + +func (n *Named) setUnderlying(typ Type) { + if n != nil { + n.underlying = typ + } +} + +func (check *Checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, path []*TypeName) { + assert(obj.typ == nil) + + // type declarations cannot use iota + assert(check.iota == nil) + + named := &Named{obj: obj} + def.setUnderlying(named) + obj.typ = named // make sure recursive type declarations terminate + + // determine underlying type of named + check.typExpr(typ, named, append(path, obj)) + + // 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 (they always end in an unnamed type). + named.underlying = underlying(named.underlying) + + // check and add associated methods + // TODO(gri) It's easy to create pathological cases where the + // current approach is incorrect: In general we need to know + // and add all methods _before_ type-checking the type. + // See http://play.golang.org/p/WMpE0q2wK8 + check.addMethodDecls(obj) +} + +func (check *Checker) addMethodDecls(obj *TypeName) { + // get associated methods + methods := check.methods[obj.name] + if len(methods) == 0 { + return // no methods + } + delete(check.methods, obj.name) + + // 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) + 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) + } + + // type-check methods + for _, m := range methods { + // spec: "For a base type, the non-blank names of methods bound + // to it must be unique." + if 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, base) + default: + unreachable() + } + check.reportAltDecl(alt) + continue + } + } + check.objDecl(m, nil, nil) + // methods with blank _ names cannot be found - don't keep them + if m.name != "_" { + base.methods = append(base.methods, m) + } + } +} + +func (check *Checker) funcDecl(obj *Func, decl *declInfo) { + assert(obj.typ == nil) + + // func declarations cannot use iota + assert(check.iota == nil) + + sig := new(Signature) + obj.typ = sig // guard against cycles + fdecl := decl.fdecl + check.funcType(sig, fdecl.Recv, fdecl.Type) + if sig.recv == nil && obj.name == "init" && (sig.params.Len() > 0 || sig.results.Len() > 0) { + check.errorf(fdecl.Pos(), "func init must have no arguments and no return values") + // ok to continue + } + + // function body must be type-checked after global declarations + // (functions implemented elsewhere have no body) + if !check.conf.IgnoreFuncBodies && fdecl.Body != nil { + check.later(obj.name, decl, sig, fdecl.Body) + } +} + +func (check *Checker) declStmt(decl ast.Decl) { + pkg := check.pkg + + switch d := decl.(type) { + case *ast.BadDecl: + // ignore + + case *ast.GenDecl: + var last *ast.ValueSpec // last ValueSpec with type or init exprs seen + for iota, spec := range d.Specs { + switch s := spec.(type) { + case *ast.ValueSpec: + switch d.Tok { + case token.CONST: + // determine which init exprs to use + switch { + case s.Type != nil || len(s.Values) > 0: + last = s + case last == nil: + last = new(ast.ValueSpec) // make sure last exists + } + + // declare all constants + lhs := make([]*Const, len(s.Names)) + for i, name := range s.Names { + obj := NewConst(name.Pos(), pkg, name.Name, nil, exact.MakeInt64(int64(iota))) + lhs[i] = obj + + var init ast.Expr + if i < len(last.Values) { + init = last.Values[i] + } + + check.constDecl(obj, last.Type, init) + } + + check.arityMatch(s, last) + + for i, name := range s.Names { + check.declare(check.scope, name, lhs[i]) + } + + case token.VAR: + lhs0 := make([]*Var, len(s.Names)) + for i, name := range s.Names { + lhs0[i] = NewVar(name.Pos(), pkg, name.Name, nil) + } + + // initialize all variables + for i, obj := range lhs0 { + var lhs []*Var + var init ast.Expr + switch len(s.Values) { + case len(s.Names): + // lhs and rhs match + init = s.Values[i] + case 1: + // rhs is expected to be a multi-valued expression + lhs = lhs0 + init = s.Values[0] + default: + if i < len(s.Values) { + init = s.Values[i] + } + } + check.varDecl(obj, lhs, s.Type, init) + if len(s.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 + } + } + + check.arityMatch(s, nil) + + // declare all variables + // (only at this point are the variable scopes (parents) set) + for i, name := range s.Names { + check.declare(check.scope, name, lhs0[i]) + } + + default: + check.invalidAST(s.Pos(), "invalid token %s", d.Tok) + } + + case *ast.TypeSpec: + obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil) + check.declare(check.scope, s.Name, obj) + check.typeDecl(obj, s.Type, nil, nil) + + default: + check.invalidAST(s.Pos(), "const, type, or var declaration expected") + } + } + + default: + check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d) + } +} diff --git a/src/gosubli.me/something-borrowed/types/errors.go b/src/gosubli.me/something-borrowed/types/errors.go index 62ee5479..0a9dd0e1 100644 --- a/src/gosubli.me/something-borrowed/types/errors.go +++ b/src/gosubli.me/something-borrowed/types/errors.go @@ -7,13 +7,12 @@ package types import ( - "bytes" "fmt" "go/ast" "go/token" + "strings" ) -// TODO(gri) eventually assert and unimplemented should disappear. func assert(p bool) { if !p { panic("assertion failed") @@ -24,312 +23,74 @@ func unreachable() { panic("unreachable") } -func (check *checker) printTrace(format string, args []interface{}) { - const dots = ". . . . . . . . . . . . . . . . . . . . " - n := len(check.pos) - 1 - i := 3 * n - for i > len(dots) { - fmt.Print(dots) - i -= len(dots) - } - // i <= len(dots) - fmt.Printf("%s:\t", check.fset.Position(check.pos[n])) - fmt.Print(dots[0:i]) - fmt.Println(check.formatMsg(format, args)) -} - -func (check *checker) trace(pos token.Pos, format string, args ...interface{}) { - check.pos = append(check.pos, pos) - check.printTrace(format, args) -} - -func (check *checker) untrace(format string, args ...interface{}) { - if len(format) > 0 { - check.printTrace(format, args) - } - check.pos = check.pos[:len(check.pos)-1] -} - -func (check *checker) formatMsg(format string, args []interface{}) string { +func (check *Checker) sprintf(format string, args ...interface{}) string { for i, arg := range args { switch a := arg.(type) { + case nil: + arg = "" + case operand: + panic("internal error: should always pass *operand") + case *operand: + arg = operandString(check.pkg, a) case token.Pos: - args[i] = check.fset.Position(a).String() + arg = check.fset.Position(a).String() case ast.Expr: - args[i] = exprString(a) + arg = ExprString(a) + case Object: + arg = ObjectString(check.pkg, a) case Type: - args[i] = typeString(a) - case operand: - panic("internal error: should always pass *operand") + arg = TypeString(check.pkg, a) } + args[i] = arg } return fmt.Sprintf(format, args...) } +func (check *Checker) trace(pos token.Pos, format string, args ...interface{}) { + fmt.Printf("%s:\t%s%s\n", + check.fset.Position(pos), + strings.Repeat(". ", check.indent), + check.sprintf(format, args...), + ) +} + // dump is only needed for debugging -func (check *checker) dump(format string, args ...interface{}) { - fmt.Println(check.formatMsg(format, args)) +func (check *Checker) dump(format string, args ...interface{}) { + fmt.Println(check.sprintf(format, args...)) } -func (check *checker) err(err error) { - if check.firsterr == nil { - check.firsterr = err +func (check *Checker) err(pos token.Pos, msg string, soft bool) { + err := Error{check.fset, pos, msg, soft} + if check.firstErr == nil { + check.firstErr = err } - f := check.ctxt.Error + f := check.conf.Error if f == nil { panic(bailout{}) // report only first error } f(err) } -func (check *checker) errorf(pos token.Pos, format string, args ...interface{}) { - check.err(fmt.Errorf("%s: %s", check.fset.Position(pos), check.formatMsg(format, args))) -} - -func (check *checker) invalidAST(pos token.Pos, format string, args ...interface{}) { - check.errorf(pos, "invalid AST: "+format, args...) -} - -func (check *checker) invalidArg(pos token.Pos, format string, args ...interface{}) { - check.errorf(pos, "invalid argument: "+format, args...) -} - -func (check *checker) invalidOp(pos token.Pos, format string, args ...interface{}) { - check.errorf(pos, "invalid operation: "+format, args...) +func (check *Checker) error(pos token.Pos, msg string) { + check.err(pos, msg, false) } -// exprString returns a (simplified) string representation for an expression. -func exprString(expr ast.Expr) string { - var buf bytes.Buffer - writeExpr(&buf, expr) - return buf.String() -} - -// TODO(gri) Need to merge with typeString since some expressions are types (try: ([]int)(a)) -func writeExpr(buf *bytes.Buffer, expr ast.Expr) { - switch x := expr.(type) { - case *ast.Ident: - buf.WriteString(x.Name) - - case *ast.BasicLit: - buf.WriteString(x.Value) - - case *ast.FuncLit: - buf.WriteString("(func literal)") - - case *ast.CompositeLit: - buf.WriteString("(composite literal)") - - case *ast.ParenExpr: - buf.WriteByte('(') - writeExpr(buf, x.X) - buf.WriteByte(')') - - case *ast.SelectorExpr: - writeExpr(buf, x.X) - buf.WriteByte('.') - buf.WriteString(x.Sel.Name) - - case *ast.IndexExpr: - writeExpr(buf, x.X) - buf.WriteByte('[') - writeExpr(buf, x.Index) - buf.WriteByte(']') - - case *ast.SliceExpr: - writeExpr(buf, x.X) - buf.WriteByte('[') - if x.Low != nil { - writeExpr(buf, x.Low) - } - buf.WriteByte(':') - if x.High != nil { - writeExpr(buf, x.High) - } - buf.WriteByte(']') - - case *ast.TypeAssertExpr: - writeExpr(buf, x.X) - buf.WriteString(".(...)") - - case *ast.CallExpr: - writeExpr(buf, x.Fun) - buf.WriteByte('(') - for i, arg := range x.Args { - if i > 0 { - buf.WriteString(", ") - } - writeExpr(buf, arg) - } - buf.WriteByte(')') - - case *ast.StarExpr: - buf.WriteByte('*') - writeExpr(buf, x.X) - - case *ast.UnaryExpr: - buf.WriteString(x.Op.String()) - writeExpr(buf, x.X) - - case *ast.BinaryExpr: - // The AST preserves source-level parentheses so there is - // no need to introduce parentheses here for correctness. - writeExpr(buf, x.X) - buf.WriteByte(' ') - buf.WriteString(x.Op.String()) - buf.WriteByte(' ') - writeExpr(buf, x.Y) - - default: - fmt.Fprintf(buf, "", x) - } +func (check *Checker) errorf(pos token.Pos, format string, args ...interface{}) { + check.err(pos, check.sprintf(format, args...), false) } -// typeString returns a string representation for typ. -func typeString(typ Type) string { - var buf bytes.Buffer - writeType(&buf, typ) - return buf.String() +func (check *Checker) softErrorf(pos token.Pos, format string, args ...interface{}) { + check.err(pos, check.sprintf(format, args...), true) } -func writeParams(buf *bytes.Buffer, params []*Var, isVariadic bool) { - buf.WriteByte('(') - for i, par := range params { - if i > 0 { - buf.WriteString(", ") - } - if par.Name != "" { - buf.WriteString(par.Name) - buf.WriteByte(' ') - } - if isVariadic && i == len(params)-1 { - buf.WriteString("...") - } - writeType(buf, par.Type) - } - buf.WriteByte(')') +func (check *Checker) invalidAST(pos token.Pos, format string, args ...interface{}) { + check.errorf(pos, "invalid AST: "+format, args...) } -func writeSignature(buf *bytes.Buffer, sig *Signature) { - writeParams(buf, sig.Params, sig.IsVariadic) - if len(sig.Results) == 0 { - // no result - return - } - - buf.WriteByte(' ') - if len(sig.Results) == 1 && sig.Results[0].Name == "" { - // single unnamed result - writeType(buf, sig.Results[0].Type.(Type)) - return - } - - // multiple or named result(s) - writeParams(buf, sig.Results, false) +func (check *Checker) invalidArg(pos token.Pos, format string, args ...interface{}) { + check.errorf(pos, "invalid argument: "+format, args...) } -func writeType(buf *bytes.Buffer, typ Type) { - switch t := typ.(type) { - case nil: - buf.WriteString("") - - case *Basic: - buf.WriteString(t.Name) - - case *Array: - fmt.Fprintf(buf, "[%d]", t.Len) - writeType(buf, t.Elt) - - case *Slice: - buf.WriteString("[]") - writeType(buf, t.Elt) - - case *Struct: - buf.WriteString("struct{") - for i, f := range t.Fields { - if i > 0 { - buf.WriteString("; ") - } - if !f.IsAnonymous { - buf.WriteString(f.Name) - buf.WriteByte(' ') - } - writeType(buf, f.Type) - if f.Tag != "" { - fmt.Fprintf(buf, " %q", f.Tag) - } - } - buf.WriteByte('}') - - case *Pointer: - buf.WriteByte('*') - writeType(buf, t.Base) - - case *Result: - writeParams(buf, t.Values, false) - - case *Signature: - buf.WriteString("func") - writeSignature(buf, t) - - case *builtin: - fmt.Fprintf(buf, "", t.name) - - case *Interface: - buf.WriteString("interface{") - for i, m := range t.Methods { - if i > 0 { - buf.WriteString("; ") - } - buf.WriteString(m.Name) - writeSignature(buf, m.Type) - } - buf.WriteByte('}') - - case *Map: - buf.WriteString("map[") - writeType(buf, t.Key) - buf.WriteByte(']') - writeType(buf, t.Elt) - - case *Chan: - var s string - switch t.Dir { - case ast.SEND: - s = "chan<- " - case ast.RECV: - s = "<-chan " - default: - s = "chan " - } - buf.WriteString(s) - writeType(buf, t.Elt) - - case *NamedType: - s := "" - if obj := t.Obj; obj != nil { - if obj.Pkg != nil && obj.Pkg.Path != "" { - buf.WriteString(obj.Pkg.Path) - buf.WriteString(".") - } - s = t.Obj.GetName() - } - buf.WriteString(s) - - default: - fmt.Fprintf(buf, "", t) - } +func (check *Checker) invalidOp(pos token.Pos, format string, args ...interface{}) { + check.errorf(pos, "invalid operation: "+format, args...) } - -func (t *Array) String() string { return typeString(t) } -func (t *Basic) String() string { return typeString(t) } -func (t *Chan) String() string { return typeString(t) } -func (t *Interface) String() string { return typeString(t) } -func (t *Map) String() string { return typeString(t) } -func (t *NamedType) String() string { return typeString(t) } -func (t *Pointer) String() string { return typeString(t) } -func (t *Result) String() string { return typeString(t) } -func (t *Signature) String() string { return typeString(t) } -func (t *Slice) String() string { return typeString(t) } -func (t *Struct) String() string { return typeString(t) } -func (t *builtin) String() string { return typeString(t) } diff --git a/src/gosubli.me/something-borrowed/types/eval.go b/src/gosubli.me/something-borrowed/types/eval.go new file mode 100644 index 00000000..22d3e9a3 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/eval.go @@ -0,0 +1,109 @@ +// 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 New, Eval and EvalNode. + +package types + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" +) + +// New is a convenience function to create a new type from a given +// expression or type literal string evaluated in Universe scope. +// New(str) is shorthand for Eval(str, nil, nil), but only returns +// the type result, and panics in case of an error. +// Position info for objects in the result type is undefined. +// +func New(str string) Type { + typ, _, err := Eval(str, nil, nil) + if err != nil { + panic(err) + } + return typ +} + +// Eval returns the type and, if constant, the value for the +// expression or type literal string str evaluated in scope. +// If the expression contains function literals, the function +// bodies are ignored (though they must be syntactically correct). +// +// If pkg == nil, the Universe scope is used and the provided +// scope is ignored. Otherwise, the scope must belong to the +// package (either the package scope, or nested within the +// package scope). +// +// An error is returned if the scope is incorrect, the string +// has syntax errors, or if it cannot be evaluated in the scope. +// Position info for objects in the result type is undefined. +// +// Note: Eval should not be used instead of running Check to compute +// types and values, but in addition to Check. Eval will re-evaluate +// its argument each time, and it also does not know about the context +// in which an expression is used (e.g., an assignment). Thus, top- +// level untyped constants will return an untyped type rather then the +// respective context-specific type. +// +func Eval(str string, pkg *Package, scope *Scope) (typ Type, val exact.Value, err error) { + node, err := parser.ParseExpr(str) + if err != nil { + return nil, nil, err + } + + // Create a file set that looks structurally identical to the + // one created by parser.ParseExpr for correct error positions. + fset := token.NewFileSet() + fset.AddFile("", len(str), fset.Base()).SetLinesForContent([]byte(str)) + + return EvalNode(fset, node, pkg, scope) +} + +// EvalNode is like Eval but instead of string it accepts +// an expression node and respective file set. +// +// An error is returned if the scope is incorrect +// if the node cannot be evaluated in the scope. +// +func EvalNode(fset *token.FileSet, node ast.Expr, pkg *Package, scope *Scope) (typ Type, val exact.Value, err error) { + // verify package/scope relationship + if pkg == nil { + scope = Universe + } else { + s := scope + for s != nil && s != pkg.scope { + s = s.parent + } + // s == nil || s == pkg.scope + if s == nil { + return nil, nil, fmt.Errorf("scope does not belong to package %s", pkg.name) + } + } + + // initialize checker + check := NewChecker(nil, fset, pkg, nil) + check.scope = scope + defer check.handleBailout(&err) + + // evaluate node + var x operand + check.exprOrType(&x, node) + switch x.mode { + case invalid, novalue: + fallthrough + default: + unreachable() // or bailed out with error + case constant: + val = x.val + fallthrough + case typexpr, variable, mapindex, value, commaok: + typ = x.typ + } + + return +} diff --git a/src/gosubli.me/something-borrowed/types/eval_test.go b/src/gosubli.me/something-borrowed/types/eval_test.go new file mode 100644 index 00000000..14d33273 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/eval_test.go @@ -0,0 +1,148 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for Eval. + +package types_test + +import ( + "go/ast" + "go/parser" + "go/token" + "strings" + "testing" + + _ "golang.org/x/tools/go/gcimporter" + . "golang.org/x/tools/go/types" +) + +func testEval(t *testing.T, pkg *Package, scope *Scope, str string, typ Type, typStr, valStr string) { + gotTyp, gotVal, err := Eval(str, pkg, scope) + if err != nil { + t.Errorf("Eval(%q) failed: %s", str, err) + return + } + if gotTyp == nil { + t.Errorf("Eval(%q) got nil type but no error", str) + return + } + + // compare types + if typ != nil { + // we have a type, check identity + if !Identical(gotTyp, typ) { + t.Errorf("Eval(%q) got type %s, want %s", str, gotTyp, typ) + return + } + } else { + // we have a string, compare type string + gotStr := gotTyp.String() + if gotStr != typStr { + t.Errorf("Eval(%q) got type %s, want %s", str, gotStr, typStr) + return + } + } + + // compare values + gotStr := "" + if gotVal != nil { + gotStr = gotVal.String() + } + if gotStr != valStr { + t.Errorf("Eval(%q) got value %s, want %s", str, gotStr, valStr) + } +} + +func TestEvalBasic(t *testing.T) { + for _, typ := range Typ[Bool : String+1] { + testEval(t, nil, nil, typ.Name(), typ, "", "") + } +} + +func TestEvalComposite(t *testing.T) { + for _, test := range independentTestTypes { + testEval(t, nil, nil, test.src, nil, test.str, "") + } +} + +func TestEvalArith(t *testing.T) { + var tests = []string{ + `true`, + `false == false`, + `12345678 + 87654321 == 99999999`, + `10 * 20 == 200`, + `(1<<1000)*2 >> 100 == 2<<900`, + `"foo" + "bar" == "foobar"`, + `"abc" <= "bcd"`, + `len([10]struct{}{}) == 2*5`, + } + for _, test := range tests { + testEval(t, nil, nil, test, Typ[UntypedBool], "", "true") + } +} + +func TestEvalContext(t *testing.T) { + src := ` +package p +import "fmt" +import m "math" +const c = 3.0 +type T []int +func f(a int, s string) float64 { + fmt.Println("calling f") + _ = m.Pi // use package math + const d int = c + 1 + var x int + x = a + len(s) + return float64(x) +} +` + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "p", src, 0) + if err != nil { + t.Fatal(err) + } + + pkg, err := Check("p", fset, []*ast.File{file}) + if err != nil { + t.Fatal(err) + } + + pkgScope := pkg.Scope() + if n := pkgScope.NumChildren(); n != 1 { + t.Fatalf("got %d file scopes, want 1", n) + } + + fileScope := pkgScope.Child(0) + if n := fileScope.NumChildren(); n != 1 { + t.Fatalf("got %d functions scopes, want 1", n) + } + + funcScope := fileScope.Child(0) + + var tests = []string{ + `true => true, untyped bool`, + `fmt.Println => , func(a ...interface{}) (n int, err error)`, + `c => 3, untyped float`, + `T => , p.T`, + `a => , int`, + `s => , string`, + `d => 4, int`, + `x => , int`, + `d/c => 1, int`, + `c/2 => 3/2, untyped float`, + `m.Pi < m.E => false, untyped bool`, + } + for _, test := range tests { + str, typ := split(test, ", ") + str, val := split(str, "=>") + testEval(t, pkg, funcScope, str, nil, typ, val) + } +} + +// split splits string s at the first occurrence of s. +func split(s, sep string) (string, string) { + i := strings.Index(s, sep) + return strings.TrimSpace(s[:i]), strings.TrimSpace(s[i+len(sep):]) +} diff --git a/src/gosubli.me/something-borrowed/types/expr.go b/src/gosubli.me/something-borrowed/types/expr.go index 8b645e4e..77a1f496 100644 --- a/src/gosubli.me/something-borrowed/types/expr.go +++ b/src/gosubli.me/something-borrowed/types/expr.go @@ -7,162 +7,55 @@ package types import ( + "fmt" "go/ast" "go/token" - "strconv" -) - -// TODO(gri) Cleanups -// - don't print error messages referring to invalid types (they are likely spurious errors) -// - simplify invalid handling: maybe just use Typ[Invalid] as marker, get rid of invalid Mode for values? -// - rethink error handling: should all callers check if x.mode == valid after making a call? -// - at the moment, iota is passed around almost everywhere - in many places we know it cannot be used -// - use "" or "_" consistently for anonymous identifiers? (e.g. reeceivers that have no name) - -// TODO(gri) API issues -// - clients need access to builtins type information -// - API tests are missing (e.g., identifiers should be handled as expressions in callbacks) - -func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params []*Var, isVariadic bool) { - if list == nil { - return - } - var last *Var - for i, field := range list.List { - ftype := field.Type - if t, _ := ftype.(*ast.Ellipsis); t != nil { - ftype = t.Elt - if variadicOk && i == len(list.List)-1 { - isVariadic = true - } else { - check.invalidAST(field.Pos(), "... not permitted") - // ok to continue - } - } - // the parser ensures that f.Tag is nil and we don't - // care if a constructed AST contains a non-nil tag - typ := check.typ(ftype, true) - if len(field.Names) > 0 { - // named parameter - for _, name := range field.Names { - par := check.lookup(name).(*Var) - par.Type = typ - last = par - copy := *par - params = append(params, ©) - } - } else { - // anonymous parameter - par := &Var{Type: typ} - last = nil // not accessible inside function - params = append(params, par) - } - } - // For a variadic function, change the last parameter's object type - // from T to []T (this is the type used inside the function), but - // keep the params list unchanged (this is the externally visible type). - if isVariadic && last != nil { - last.Type = &Slice{Elt: last.Type} - } - return -} - -func (check *checker) collectMethods(list *ast.FieldList) (methods []*Method) { - if list == nil { - return - } - for _, f := range list.List { - typ := check.typ(f.Type, len(f.Names) > 0) // cycles are not ok for embedded interfaces - // the parser ensures that f.Tag is nil and we don't - // care if a constructed AST contains a non-nil tag - if len(f.Names) > 0 { - // methods (the parser ensures that there's only one - // and we don't care if a constructed AST has more) - sig, ok := typ.(*Signature) - if !ok { - check.invalidAST(f.Type.Pos(), "%s is not a method signature", typ) - continue - } - for _, name := range f.Names { - methods = append(methods, &Method{QualifiedName{check.pkg, name.Name}, sig}) - } - } else { - // embedded interface - utyp := underlying(typ) - if ityp, ok := utyp.(*Interface); ok { - methods = append(methods, ityp.Methods...) - } else if utyp != Typ[Invalid] { - // if utyp is invalid, don't complain (the root cause was reported before) - check.errorf(f.Type.Pos(), "%s is not an interface type", typ) - } - } - } - // Check for double declarations. - // The parser inserts methods into an interface-local scope, so local - // double declarations are reported by the parser already. We need to - // check again for conflicts due to embedded interfaces. This will lead - // to a 2nd error message if the double declaration was reported before - // by the parser. - // TODO(gri) clean this up a bit - seen := make(map[string]bool) - for _, m := range methods { - if seen[m.Name] { - check.errorf(list.Pos(), "multiple methods named %s", m.Name) - return // keep multiple entries, lookup will only return the first entry - } - seen[m.Name] = true - } - return -} - -func (check *checker) tag(t *ast.BasicLit) string { - if t != nil { - if t.Kind == token.STRING { - if val, err := strconv.Unquote(t.Value); err == nil { - return val - } - } - check.invalidAST(t.Pos(), "incorrect tag syntax: %q", t.Value) - } - return "" -} + "math" -func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields []*Field) { - if list == nil { - return - } - - var typ Type // current field typ - var tag string // current field tag - add := func(name string, isAnonymous bool) { - fields = append(fields, &Field{QualifiedName{check.pkg, name}, typ, tag, isAnonymous}) - } - - for _, f := range list.List { - typ = check.typ(f.Type, cycleOk) - tag = check.tag(f.Tag) - if len(f.Names) > 0 { - // named fields - for _, name := range f.Names { - add(name.Name, false) - } - } else { - // anonymous field - switch t := deref(typ).(type) { - case *Basic: - add(t.Name, true) - case *NamedType: - add(t.Obj.GetName(), true) - default: - if typ != Typ[Invalid] { - check.invalidAST(f.Type.Pos(), "anonymous field type %s must be named", typ) - } - } - } - } + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" +) - return -} +/* +Basic algorithm: + +Expressions are checked recursively, top down. Expression checker functions +are generally of the form: + + func f(x *operand, e *ast.Expr, ...) + +where e is the expression to be checked, and x is the result of the check. +The check performed by f may fail in which case x.mode == invalid, and +related error messages will have been issued by f. + +If a hint argument is present, it is the composite literal element type +of an outer composite literal; it is used to type-check composite literal +elements that have no explicit type specification in the source +(e.g.: []T{{...}, {...}}, the hint is the type T in this case). + +All expressions are checked via rawExpr, which dispatches according +to expression kind. Upon returning, rawExpr is recording the types and +constant values for all expressions that have an untyped type (those types +may change on the way up in the expression tree). Usually these are constants, +but the results of comparisons or non-constant shifts of untyped constants +may also be untyped, but not constant. + +Untyped expressions may eventually become fully typed (i.e., not untyped), +typically when the value is assigned to a variable, or is used otherwise. +The updateExprType method is used to record this final type and update +the recorded types: the type-checked expression tree is again traversed down, +and the new type is propagated as needed. Untyped constant expression values +that become fully typed must now be representable by the full type (constant +sub-expression trees are left alone except for their roots). This mechanism +ensures that a client sees the actual (run-time) type an untyped value would +have. It also permits type-checking of lhs shift operands "as if the shift +were not present": when updateExprType visits an untyped lhs shift operand +and assigns it it's final type, that type must be an integer type, and a +constant lhs must be representable as an integer. + +When an expression gets its final type, either on the way out from rawExpr, +on the way down in updateExprType, or at the end of the type checker run, +the type (and constant value, if any) is recorded via Info.Types, if present. +*/ type opPredicates map[token.Token]func(Type) bool @@ -173,7 +66,7 @@ var unaryOpPredicates = opPredicates{ token.NOT: isBoolean, } -func (check *checker) op(m opPredicates, x *operand, op token.Token) bool { +func (check *Checker) op(m opPredicates, x *operand, op token.Token) bool { if pred := m[op]; pred != nil { if !pred(x.typ) { check.invalidOp(x.pos(), "operator %s not defined for %s", op, x) @@ -186,55 +79,60 @@ func (check *checker) op(m opPredicates, x *operand, op token.Token) bool { return true } -func (check *checker) unary(x *operand, op token.Token) { +func (check *Checker) unary(x *operand, op token.Token) { switch op { case token.AND: // spec: "As an exception to the addressability // requirement x may also be a composite literal." - if _, ok := unparen(x.expr).(*ast.CompositeLit); ok { - x.mode = variable - } - if x.mode != variable { + if _, ok := unparen(x.expr).(*ast.CompositeLit); !ok && x.mode != variable { check.invalidOp(x.pos(), "cannot take address of %s", x) - goto Error + x.mode = invalid + return } - x.typ = &Pointer{Base: x.typ} + x.mode = value + x.typ = &Pointer{base: x.typ} return case token.ARROW: - typ, ok := underlying(x.typ).(*Chan) + typ, ok := x.typ.Underlying().(*Chan) if !ok { check.invalidOp(x.pos(), "cannot receive from non-channel %s", x) - goto Error + x.mode = invalid + return } - if typ.Dir&ast.RECV == 0 { + if typ.dir == SendOnly { check.invalidOp(x.pos(), "cannot receive from send-only channel %s", x) - goto Error + x.mode = invalid + return } - x.mode = valueok - x.typ = typ.Elt + x.mode = commaok + x.typ = typ.elem + check.hasCallOrRecv = true return } if !check.op(unaryOpPredicates, x, op) { - goto Error + x.mode = invalid + return } if x.mode == constant { - typ := underlying(x.typ).(*Basic) - x.val = unaryOpConst(x.val, check.ctxt, op, typ) + typ := x.typ.Underlying().(*Basic) + size := -1 + if isUnsigned(typ) { + size = int(check.conf.sizeof(typ)) + } + x.val = exact.UnaryOp(op, x.val, size) // Typed constants must be representable in // their type after each constant operation. - check.isRepresentable(x, typ) + if isTyped(typ) { + check.representable(x, typ) + } return } x.mode = value // x.typ remains unchanged - return - -Error: - x.mode = invalid } func isShift(op token.Token) bool { @@ -250,16 +148,195 @@ func isComparison(op token.Token) bool { return false } -// isRepresentable checks that a constant operand is representable in the given type. -func (check *checker) isRepresentable(x *operand, typ *Basic) { - if x.mode != constant || isUntyped(typ) { - return +func fitsFloat32(x exact.Value) bool { + f32, _ := exact.Float32Val(x) + f := float64(f32) + return !math.IsInf(f, 0) +} + +func roundFloat32(x exact.Value) exact.Value { + f32, _ := exact.Float32Val(x) + f := float64(f32) + if !math.IsInf(f, 0) { + return exact.MakeFloat64(f) } + return nil +} - if !isRepresentableConst(x.val, check.ctxt, typ.Kind) { +func fitsFloat64(x exact.Value) bool { + f, _ := exact.Float64Val(x) + return !math.IsInf(f, 0) +} + +func roundFloat64(x exact.Value) exact.Value { + f, _ := exact.Float64Val(x) + if !math.IsInf(f, 0) { + return exact.MakeFloat64(f) + } + return nil +} + +// representableConst reports whether x can be represented as +// value of the given basic type kind 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 values; it is left alone otherwise. +// It is ok to provide the addressof the first argument for rounded. +func representableConst(x exact.Value, conf *Config, as BasicKind, rounded *exact.Value) bool { + switch x.Kind() { + case exact.Unknown: + return true + + case exact.Bool: + return as == Bool || as == UntypedBool + + case exact.Int: + if x, ok := exact.Int64Val(x); ok { + switch as { + case Int: + var s = uint(conf.sizeof(Typ[as])) * 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: + return true + case Uint, Uintptr: + if s := uint(conf.sizeof(Typ[as])) * 8; s < 64 { + return 0 <= x && x <= int64(1)<= 0 && n <= int(s) + case Uint64: + return exact.Sign(x) >= 0 && n <= 64 + case Float32, Complex64: + if rounded == nil { + return fitsFloat32(x) + } + r := roundFloat32(x) + if r != nil { + *rounded = r + return true + } + case Float64, Complex128: + if rounded == nil { + return fitsFloat64(x) + } + r := roundFloat64(x) + if r != nil { + *rounded = r + return true + } + case UntypedInt, UntypedFloat, UntypedComplex: + return true + } + + case exact.Float: + switch as { + case Float32, Complex64: + if rounded == nil { + return fitsFloat32(x) + } + r := roundFloat32(x) + if r != nil { + *rounded = r + return true + } + case Float64, Complex128: + if rounded == nil { + return fitsFloat64(x) + } + r := roundFloat64(x) + if r != nil { + *rounded = r + return true + } + case UntypedFloat, UntypedComplex: + return true + } + + case exact.Complex: + switch as { + case Complex64: + if rounded == nil { + return fitsFloat32(exact.Real(x)) && fitsFloat32(exact.Imag(x)) + } + re := roundFloat32(exact.Real(x)) + im := roundFloat32(exact.Imag(x)) + if re != nil && im != nil { + *rounded = exact.BinaryOp(re, token.ADD, exact.MakeImag(im)) + return true + } + case Complex128: + if rounded == nil { + return fitsFloat64(exact.Real(x)) && fitsFloat64(exact.Imag(x)) + } + re := roundFloat64(exact.Real(x)) + im := roundFloat64(exact.Imag(x)) + if re != nil && im != nil { + *rounded = exact.BinaryOp(re, token.ADD, exact.MakeImag(im)) + return true + } + case UntypedComplex: + return true + } + + case exact.String: + return as == String || as == UntypedString + + default: + unreachable() + } + + 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.conf, typ.kind, &x.val) { var msg string if isNumeric(x.typ) && isNumeric(typ) { - msg = "%s overflows %s" + // 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" } @@ -268,9 +345,126 @@ func (check *checker) isRepresentable(x *operand, typ *Basic) { } } +// updateExprType updates the type of x to typ and invokes itself +// recursively for the operands of x, depending on expression kind. +// If typ is still an untyped and not the final type, updateExprType +// only updates the recorded untyped type for x and possibly its +// operands. Otherwise (i.e., typ is not an untyped type anymore, +// or it is the final type for x), the type and value are recorded. +// Also, if x is a constant, it must be representable as a value of typ, +// and if x is the (formerly untyped) lhs operand of a non-constant +// shift, it must be an integer value. +// +func (check *Checker) updateExprType(x ast.Expr, typ Type, final bool) { + old, found := check.untyped[x] + if !found { + return // nothing to do + } + + // update operands of x if necessary + switch x := x.(type) { + case *ast.BadExpr, + *ast.FuncLit, + *ast.CompositeLit, + *ast.IndexExpr, + *ast.SliceExpr, + *ast.TypeAssertExpr, + *ast.StarExpr, + *ast.KeyValueExpr, + *ast.ArrayType, + *ast.StructType, + *ast.FuncType, + *ast.InterfaceType, + *ast.MapType, + *ast.ChanType: + // These expression are never untyped - nothing to do. + // The respective sub-expressions got their final types + // upon assignment or use. + if debug { + check.dump("%s: found old type(%s): %s (new: %s)", x.Pos(), x, old.typ, typ) + unreachable() + } + return + + case *ast.CallExpr: + // Resulting in an untyped constant (e.g., built-in complex). + // The respective calls take care of calling updateExprType + // for the arguments if necessary. + + case *ast.Ident, *ast.BasicLit, *ast.SelectorExpr: + // An identifier denoting a constant, a constant literal, + // or a qualified identifier (imported untyped constant). + // No operands to take care of. + + case *ast.ParenExpr: + check.updateExprType(x.X, typ, final) + + case *ast.UnaryExpr: + // If x is a constant, the operands were constants. + // They don't need to be updated since they never + // get "materialized" into a typed value; and they + // will be processed at the end of the type check. + if old.val != nil { + break + } + check.updateExprType(x.X, typ, final) + + case *ast.BinaryExpr: + if old.val != nil { + break // see comment for unary expressions + } + if isComparison(x.Op) { + // The result type is independent of operand types + // and the operand types must have final types. + } else if isShift(x.Op) { + // The result type depends only on lhs operand. + // The rhs type was updated when checking the shift. + check.updateExprType(x.X, typ, final) + } else { + // The operand types match the result type. + check.updateExprType(x.X, typ, final) + check.updateExprType(x.Y, typ, final) + } + + default: + unreachable() + } + + // If the new type is not final and still untyped, just + // update the recorded type. + if !final && isUntyped(typ) { + old.typ = typ.Underlying().(*Basic) + check.untyped[x] = old + return + } + + // Otherwise we have the final (typed or untyped type). + // Remove it from the map of yet untyped expressions. + delete(check.untyped, x) + + // If 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 old.isLhs && !isInteger(typ) { + check.invalidOp(x.Pos(), "shifted operand %s (type %s) must be integer", x, typ) + return + } + + // Everything's fine, record final type and value for x. + check.recordTypeAndValue(x, old.mode, typ, old.val) +} + +// updateExprVal updates the value of x to val. +func (check *Checker) updateExprVal(x ast.Expr, val exact.Value) { + if info, ok := check.untyped[x]; ok { + info.val = val + check.untyped[x] = info + } +} + // convertUntyped attempts to set the type of an untyped value to the target type. -func (check *checker) convertUntyped(x *operand, target Type) { - if x.mode == invalid || !isUntyped(x.typ) { +func (check *Checker) convertUntyped(x *operand, target Type) { + if x.mode == invalid || isTyped(x.typ) || target == Typ[Invalid] { return } @@ -279,11 +473,12 @@ func (check *checker) convertUntyped(x *operand, target Type) { if isUntyped(target) { // both x and target are untyped - xkind := x.typ.(*Basic).Kind - tkind := target.(*Basic).Kind + 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 @@ -292,28 +487,75 @@ func (check *checker) convertUntyped(x *operand, target Type) { } // typed target - switch t := underlying(target).(type) { - case nil: - // We may reach here due to previous type errors. - // Be conservative and don't crash. - x.mode = invalid - return + switch t := target.Underlying().(type) { case *Basic: - check.isRepresentable(x, t) + if x.mode == constant { + check.representable(x, t) + if x.mode == invalid { + return + } + // expression value may have been rounded - update if needed + // TODO(gri) A floating-point value may silently underflow to + // zero. If it was negative, the sign is lost. See issue 6898. + 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 *Interface: - if !x.isNil() && len(t.Methods) > 0 /* empty interfaces are ok */ { + if !x.isNil() && !t.Empty() /* empty interfaces are ok */ { goto Error } + // 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 + if !t.Empty() { + goto Error + } + target = defaultType(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: - check.dump("x = %v, target = %v", x, target) // leave for debugging - unreachable() + goto Error } x.typ = target + check.updateExprType(x.expr, target, true) // UntypedNils are final return Error: @@ -321,104 +563,141 @@ Error: x.mode = invalid } -func (check *checker) comparison(x, y *operand, op token.Token) { - // TODO(gri) deal with interface vs non-interface comparison - - valid := false - if x.isAssignable(check.ctxt, y.typ) || y.isAssignable(check.ctxt, x.typ) { +func (check *Checker) comparison(x, y *operand, op token.Token) { + // spec: "In any comparison, the first operand must be assignable + // to the type of the second operand, or vice versa." + err := "" + if x.assignableTo(check.conf, y.typ) || y.assignableTo(check.conf, x.typ) { + defined := false switch op { case token.EQL, token.NEQ: - valid = isComparable(x.typ) || - x.isNil() && hasNil(y.typ) || - y.isNil() && hasNil(x.typ) + // spec: "The equality operators == and != apply to operands that are comparable." + defined = Comparable(x.typ) || x.isNil() && hasNil(y.typ) || y.isNil() && hasNil(x.typ) case token.LSS, token.LEQ, token.GTR, token.GEQ: - valid = isOrdered(x.typ) + // spec: The ordering operators <, <=, >, and >= apply to operands that are ordered." + defined = isOrdered(x.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 !valid { - check.invalidOp(x.pos(), "cannot compare %s %s %s", x, op, y) + if err != "" { + check.errorf(x.pos(), "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 = compareConst(x.val, y.val, op) + x.val = exact.MakeBool(exact.Compare(x.val, op, y.val)) + // The operands are never materialized; no need to update + // their types. } else { x.mode = value + // The operands have now their final types, which at run- + // time will be materialized. Update the expression trees. + // If the current types are untyped, the materialized type + // is the respective default type. + check.updateExprType(x.expr, defaultType(x.typ), true) + check.updateExprType(y.expr, defaultType(y.typ), true) } + // spec: "Comparison operators compare two operands and yield + // an untyped boolean value." x.typ = Typ[UntypedBool] } -// untyped lhs shift operands convert to the hint type -func (check *checker) shift(x, y *operand, op token.Token, hint Type) { +func (check *Checker) shift(x, y *operand, op token.Token) { + untypedx := isUntyped(x.typ) + + // The lhs must be of integer type or be representable + // as an integer; otherwise the shift has no chance. + if !isInteger(x.typ) && (!untypedx || !representableConst(x.val, nil, UntypedInt, nil)) { + check.invalidOp(x.pos(), "shifted operand %s must be integer", x) + x.mode = invalid + return + } + // spec: "The right operand in a shift expression must have unsigned // integer type or be an untyped constant that can be converted to // unsigned integer type." switch { case isInteger(y.typ) && isUnsigned(y.typ): // nothing to do - case y.mode == constant && isUntyped(y.typ) && isRepresentableConst(y.val, check.ctxt, UntypedInt): - y.typ = Typ[UntypedInt] + case isUntyped(y.typ): + check.convertUntyped(y, Typ[UntypedInt]) + if y.mode == invalid { + x.mode = invalid + return + } default: check.invalidOp(y.pos(), "shift count %s must be unsigned integer", y) x.mode = invalid return } - // 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; - // the type is int if it cannot be determined from the context (for - // instance, if the shift expression is an operand in a comparison - // against an untyped constant)". - if x.mode == constant && isUntyped(x.typ) { + if x.mode == constant { if y.mode == constant { - // constant shift - accept values of any (untyped) type - // as long as the value is representable as an integer - if x.mode == constant && isUntyped(x.typ) { - if isRepresentableConst(x.val, check.ctxt, UntypedInt) { - x.typ = Typ[UntypedInt] - } - } - } else { - // non-constant shift - if hint == nil { - // TODO(gri) need to check for x.isNil (see other uses of defaultType) - hint = defaultType(x.typ) - } - check.convertUntyped(x, hint) - if x.mode == invalid { + // rhs must be within reasonable bounds + const stupidShift = 1023 - 1 + 52 // so we can express smallestFloat64 + s, ok := exact.Uint64Val(y.val) + if !ok || s > stupidShift { + check.invalidOp(y.pos(), "stupid 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.val = exact.Shift(x.val, op, uint(s)) + 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: + // The lhs expression must be in the untyped map, mark + // the entry as lhs shift operand. + info, found := check.untyped[x.expr] + assert(found) + info.isLhs = true + check.untyped[x.expr] = info + // keep x's type + x.mode = value + return } } + // constant rhs must be >= 0 + if y.mode == constant && exact.Sign(y.val) < 0 { + check.invalidOp(y.pos(), "shift count %s must not be negative", y) + } + + // non-constant shift - lhs must be an integer if !isInteger(x.typ) { check.invalidOp(x.pos(), "shifted operand %s must be integer", x) x.mode = invalid return } - if y.mode == constant { - const stupidShift = 1024 - s, ok := y.val.(int64) - if !ok || s < 0 || s >= stupidShift { - check.invalidOp(y.pos(), "%s: stupid shift", y) - x.mode = invalid - return - } - if x.mode == constant { - x.val = shiftConst(x.val, uint(s), op) - return - } - } - x.mode = value - // x.typ is already set } var binaryOpPredicates = opPredicates{ @@ -437,9 +716,23 @@ var binaryOpPredicates = opPredicates{ token.LOR: isBoolean, } -func (check *checker) binary(x, y *operand, op token.Token, hint Type) { +func (check *Checker) binary(x *operand, lhs, rhs ast.Expr, op token.Token) { + 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, op, hint) + check.shift(x, &y, op) return } @@ -447,19 +740,23 @@ func (check *checker) binary(x, y *operand, op token.Token, hint Type) { if x.mode == invalid { return } - check.convertUntyped(y, x.typ) + check.convertUntyped(&y, x.typ) if y.mode == invalid { x.mode = invalid return } if isComparison(op) { - check.comparison(x, y, op) + check.comparison(x, &y, op) return } - if !IsIdentical(x.typ, y.typ) { - check.invalidOp(x.pos(), "mismatched types %s and %s", x.typ, y.typ) + if !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.invalidOp(x.pos(), "mismatched types %s and %s", x.typ, y.typ) + } x.mode = invalid return } @@ -469,18 +766,24 @@ func (check *checker) binary(x, y *operand, op token.Token, hint Type) { return } - if (op == token.QUO || op == token.REM) && y.mode == constant && isZeroConst(y.val) { + if (op == token.QUO || op == token.REM) && (x.mode == constant || isInteger(x.typ)) && y.mode == constant && exact.Sign(y.val) == 0 { check.invalidOp(y.pos(), "division by zero") x.mode = invalid return } if x.mode == constant && y.mode == constant { - typ := underlying(x.typ).(*Basic) - x.val = binaryOpConst(x.val, y.val, op, typ) + typ := x.typ.Underlying().(*Basic) + // force integer division of integer operands + if op == token.QUO && isInteger(typ) { + op = token.QUO_ASSIGN + } + x.val = exact.BinaryOp(x.val, op, y.val) // Typed constants must be representable in // their type after each constant operation. - check.isRepresentable(x, typ) + if isTyped(typ) { + check.representable(x, typ) + } return } @@ -488,50 +791,44 @@ func (check *checker) binary(x, y *operand, op token.Token, hint Type) { // x.typ is unchanged } -// index checks an index expression for validity. If length >= 0, it is the upper -// bound for the index. The result is a valid index >= 0, or a negative value. -// -func (check *checker) index(index ast.Expr, length int64, iota int) int64 { +// index checks an index expression for validity. +// If max >= 0, it is the upper bound for index. +// If index is valid and the result i >= 0, then i is the constant value of index. +func (check *Checker) index(index ast.Expr, max int64) (i int64, valid bool) { var x operand - - check.expr(&x, index, nil, iota) - if !x.isInteger(check.ctxt) { - check.errorf(x.pos(), "index %s must be integer", &x) - return -1 - } - if x.mode != constant { - return -1 // we cannot check more - } - // The spec doesn't require int64 indices, but perhaps it should. - i, ok := x.val.(int64) - if !ok { - check.errorf(x.pos(), "stupid index %s", &x) - return -1 - } - if i < 0 { - check.errorf(x.pos(), "index %s must not be negative", &x) - return -1 + check.expr(&x, index) + if x.mode == invalid { + return } - if length >= 0 && i >= length { - check.errorf(x.pos(), "index %s is out of bounds (>= %d)", &x, length) - return -1 + + // an untyped constant must be representable as Int + check.convertUntyped(&x, Typ[Int]) + if x.mode == invalid { + return } - return i -} + // the index must be of integer type + if !isInteger(x.typ) { + check.invalidArg(x.pos(), "index %s must be integer", &x) + return + } -// compositeLitKey resolves unresolved composite literal keys. -// For details, see comment in go/parser/parser.go, method parseElement. -func (check *checker) compositeLitKey(key ast.Expr) { - if ident, ok := key.(*ast.Ident); ok && ident.Obj == nil { - if obj := check.pkg.Scope.Lookup(ident.Name); obj != nil { - check.register(ident, obj) - } else if obj := Universe.Lookup(ident.Name); obj != nil { - check.register(ident, obj) - } else { - check.errorf(ident.Pos(), "undeclared name: %s", ident.Name) + // a constant index i must be in bounds + if x.mode == constant { + if exact.Sign(x.val) < 0 { + check.invalidArg(x.pos(), "index %s must not be negative", &x) + return + } + i, valid = exact.Int64Val(x.val) + if !valid || max >= 0 && i >= max { + check.errorf(x.pos(), "index %s is out of bounds", &x) + return i, false } + // 0 <= i [ && i < max ] + return i, true } + + return -1, true } // indexElts checks the elements (elts) of an array or slice composite literal @@ -539,7 +836,7 @@ func (check *checker) compositeLitKey(key ast.Expr) { // the literal length if known (length >= 0). It returns the length of the // literal (maximum index value + 1). // -func (check *checker) indexedElts(elts []ast.Expr, typ Type, length int64, iota int) int64 { +func (check *Checker) indexedElts(elts []ast.Expr, typ Type, length int64) int64 { visited := make(map[int64]bool, len(elts)) var index, max int64 for _, e := range elts { @@ -547,10 +844,13 @@ func (check *checker) indexedElts(elts []ast.Expr, typ Type, length int64, iota validIndex := false eval := e if kv, _ := e.(*ast.KeyValueExpr); kv != nil { - check.compositeLitKey(kv.Key) - if i := check.index(kv.Key, length, iota); i >= 0 { - index = i - validIndex = true + if i, ok := check.index(kv.Key, length); ok { + if i >= 0 { + index = i + validIndex = true + } else { + check.errorf(e.Pos(), "index %s must be integer constant", kv.Key) + } } eval = kv.Value } else if length >= 0 && index >= length { @@ -573,151 +873,88 @@ func (check *checker) indexedElts(elts []ast.Expr, typ Type, length int64, iota // check element against composite literal element type var x operand - check.expr(&x, eval, typ, iota) - if !x.isAssignable(check.ctxt, typ) { + check.exprWithHint(&x, eval, typ) + if !check.assignment(&x, typ) && x.mode != invalid { check.errorf(x.pos(), "cannot use %s as %s value in array or slice literal", &x, typ) } } return max } -// argument typechecks passing an argument arg (if arg != nil) or -// x (if arg == nil) to the i'th parameter of the given signature. -// If passSlice is set, the argument is followed by ... in the call. -// -func (check *checker) argument(sig *Signature, i int, arg ast.Expr, x *operand, passSlice bool) { - // determine parameter - var par *Var - n := len(sig.Params) - if i < n { - par = sig.Params[i] - } else if sig.IsVariadic { - par = sig.Params[n-1] - } else { - check.errorf(arg.Pos(), "too many arguments") - return - } - - // determine argument - var z operand - z.mode = variable - z.expr = nil // TODO(gri) can we do better here? (for good error messages) - z.typ = par.Type +// exprKind describes the kind of an expression; the kind +// determines if an expression is valid in 'statement context'. +type exprKind int - if arg != nil { - check.expr(x, arg, z.typ, -1) - } - if x.mode == invalid { - return // ignore this argument - } +const ( + conversion exprKind = iota + expression + statement +) - // check last argument of the form x... - if passSlice { - if i+1 != n { - check.errorf(x.pos(), "can only use ... with matching parameter") - return // ignore this argument - } - // spec: "If the final argument is assignable to a slice type []T, - // it may be passed unchanged as the value for a ...T parameter if - // the argument is followed by ..." - z.typ = &Slice{Elt: z.typ} // change final parameter type to []T +// rawExpr typechecks expression e and initializes x with the expression +// value or type. If an error occurred, x.mode is set to invalid. +// If hint != nil, it is the type of a composite literal element. +// +func (check *Checker) rawExpr(x *operand, e ast.Expr, hint Type) exprKind { + if trace { + check.trace(e.Pos(), "%s", e) + check.indent++ + defer func() { + check.indent-- + check.trace(e.Pos(), "=> %s", x) + }() } - check.assignOperand(&z, x) -} + kind := check.exprInternal(x, e, hint) -var emptyResult Result - -func (check *checker) callExpr(x *operand) { + // convert x into a user-friendly set of values + // TODO(gri) this code can be simplified var typ Type - var val interface{} + var val exact.Value switch x.mode { case invalid: - return // nothing to do + typ = Typ[Invalid] case novalue: - typ = &emptyResult + typ = (*Tuple)(nil) case constant: typ = x.typ val = x.val default: typ = x.typ } - check.ctxt.Expr(x.expr, typ, val) -} + assert(x.expr != nil && typ != nil) -// rawExpr typechecks expression e and initializes x with the expression -// value or type. If an error occurred, x.mode is set to invalid. -// A hint != nil is used as operand type for untyped shifted operands; -// iota >= 0 indicates that the expression is part of a constant declaration. -// cycleOk indicates whether it is ok for a type expression to refer to itself. -// -func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycleOk bool) { - if trace { - c := "" - if cycleOk { - c = " ⨁" - } - check.trace(e.Pos(), "%s (%s, %d%s)", e, typeString(hint), iota, c) - defer check.untrace("=> %s", x) + 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) } - if check.ctxt.Expr != nil { - defer check.callExpr(x) - } + return kind +} + +// exprInternal contains the core of type checking of expressions. +// Must only be called by rawExpr. +// +func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { + // make sure x has a valid state in case of bailout + // (was issue 5770) + x.mode = invalid + x.typ = Typ[Invalid] switch e := e.(type) { case *ast.BadExpr: goto Error // error was reported before case *ast.Ident: - if e.Name == "_" { - check.invalidOp(e.Pos(), "cannot use _ as value or type") - goto Error - } - obj := check.lookup(e) - if obj == nil { - goto Error // error was reported before - } - check.object(obj, cycleOk) - switch obj := obj.(type) { - case *Package: - check.errorf(e.Pos(), "use of package %s not in selector", obj.Name) - goto Error - case *Const: - if obj.Val == nil { - goto Error // cycle detected - } - x.mode = constant - if obj == universeIota { - if iota < 0 { - check.invalidAST(e.Pos(), "cannot use iota outside constant declaration") - goto Error - } - x.val = int64(iota) - } else { - x.val = obj.Val - } - case *TypeName: - x.mode = typexpr - if !cycleOk && underlying(obj.Type) == nil { - check.errorf(obj.spec.Pos(), "illegal cycle in declaration of %s", obj.Name) - x.expr = e - x.typ = Typ[Invalid] - return // don't goto Error - need x.mode == typexpr - } - case *Var: - x.mode = variable - case *Func: - x.mode = value - default: - unreachable() - } - x.typ = obj.GetType() + check.ident(x, e, nil, nil) case *ast.Ellipsis: // ellipses are handled explicitly where they are legal // (array composite literals and parameter lists) - check.errorf(e.Pos(), "invalid use of '...'") + check.error(e.Pos(), "invalid use of '...'") goto Error case *ast.BasicLit: @@ -728,10 +965,13 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle } case *ast.FuncLit: - if sig, ok := check.typ(e.Type, false).(*Signature); ok { + 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. + check.funcBody(check.decl, "", sig, e.Body) x.mode = value x.typ = sig - check.later(nil, sig, e.Body) } else { check.invalidAST(e.Pos(), "invalid function literal %s", e) goto Error @@ -749,32 +989,32 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle // 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, Elt: check.typ(atyp.Elt, cycleOk)} + typ = &Array{len: -1, elem: check.typ(atyp.Elt)} openArray = true } } if typ == nil { - typ = check.typ(e.Type, false) + typ = check.typ(e.Type) } } if typ == nil { - check.errorf(e.Pos(), "missing type in composite literal") + check.error(e.Pos(), "missing type in composite literal") goto Error } - switch utyp := underlying(deref(typ)).(type) { + switch typ, _ := deref(typ); utyp := typ.Underlying().(type) { case *Struct: if len(e.Elts) == 0 { break } - fields := utyp.Fields + fields := utyp.fields if _, ok := e.Elts[0].(*ast.KeyValueExpr); ok { // all elements must have keys visited := make([]bool, len(fields)) for _, e := range e.Elts { kv, _ := e.(*ast.KeyValueExpr) if kv == nil { - check.errorf(e.Pos(), "mixture of field:value and value elements in struct literal") + check.error(e.Pos(), "mixture of field:value and value elements in struct literal") continue } key, _ := kv.Key.(*ast.Ident) @@ -782,21 +1022,25 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle check.errorf(kv.Pos(), "invalid field name %s in struct literal", kv.Key) continue } - i := utyp.fieldIndex(QualifiedName{check.pkg, key.Name}) + i := fieldIndex(utyp.fields, check.pkg, key.Name) if i < 0 { check.errorf(kv.Pos(), "unknown field %s in struct literal", key.Name) continue } + fld := fields[i] + check.recordUse(key, fld) // 0 <= i < len(fields) if visited[i] { check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name) continue } visited[i] = true - check.expr(x, kv.Value, nil, iota) - etyp := fields[i].Type - if !x.isAssignable(check.ctxt, etyp) { - check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp) + check.expr(x, kv.Value) + etyp := fld.typ + if !check.assignment(x, etyp) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp) + } continue } } @@ -804,67 +1048,85 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle // no element must have a key for i, e := range e.Elts { if kv, _ := e.(*ast.KeyValueExpr); kv != nil { - check.errorf(kv.Pos(), "mixture of field:value and value elements in struct literal") + check.error(kv.Pos(), "mixture of field:value and value elements in struct literal") continue } - check.expr(x, e, nil, iota) + check.expr(x, e) if i >= len(fields) { - check.errorf(x.pos(), "too many values in struct literal") + check.error(x.pos(), "too many values in struct literal") break // cannot continue } // i < len(fields) - etyp := fields[i].Type - if !x.isAssignable(check.ctxt, etyp) { - check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp) + etyp := fields[i].typ + if !check.assignment(x, etyp) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp) + } continue } } if len(e.Elts) < len(fields) { - check.errorf(e.Rbrace, "too few values in struct literal") + check.error(e.Rbrace, "too few values in struct literal") // ok to continue } } case *Array: - n := check.indexedElts(e.Elts, utyp.Elt, utyp.Len, iota) + n := check.indexedElts(e.Elts, utyp.elem, utyp.len) // if we have an "open" [...]T array, set the length now that we know it if openArray { - utyp.Len = n + utyp.len = n } case *Slice: - check.indexedElts(e.Elts, utyp.Elt, -1, iota) + check.indexedElts(e.Elts, utyp.elem, -1) case *Map: - visited := make(map[interface{}]bool, len(e.Elts)) + visited := make(map[interface{}][]Type, len(e.Elts)) for _, e := range e.Elts { kv, _ := e.(*ast.KeyValueExpr) if kv == nil { - check.errorf(e.Pos(), "missing key in map literal") + check.error(e.Pos(), "missing key in map literal") continue } - check.compositeLitKey(kv.Key) - check.expr(x, kv.Key, nil, iota) - if !x.isAssignable(check.ctxt, utyp.Key) { - check.errorf(x.pos(), "cannot use %s as %s key in map literal", x, utyp.Key) + check.expr(x, kv.Key) + if !check.assignment(x, utyp.key) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s key in map literal", x, utyp.key) + } continue } if x.mode == constant { - if visited[x.val] { + duplicate := false + // if the key is of interface type, the type is also significant when checking for duplicates + if _, ok := utyp.key.Underlying().(*Interface); ok { + for _, vtyp := range visited[x.val] { + if Identical(vtyp, x.typ) { + duplicate = true + break + } + } + visited[x.val] = append(visited[x.val], x.typ) + } else { + _, duplicate = visited[x.val] + visited[x.val] = nil + } + if duplicate { check.errorf(x.pos(), "duplicate key %s in map literal", x.val) continue } - visited[x.val] = true } - check.expr(x, kv.Value, utyp.Elt, iota) - if !x.isAssignable(check.ctxt, utyp.Elt) { - check.errorf(x.pos(), "cannot use %s as %s value in map literal", x, utyp.Elt) + check.exprWithHint(x, kv.Value, utyp.elem) + if !check.assignment(x, utyp.elem) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s value in map literal", x, utyp.elem) + } continue } } default: - check.errorf(e.Pos(), "%s is not a valid composite literal type", typ) + check.errorf(e.Pos(), "invalid composite literal type %s", typ) goto Error } @@ -872,134 +1134,69 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle x.typ = typ case *ast.ParenExpr: - check.rawExpr(x, e.X, hint, iota, cycleOk) + kind := check.rawExpr(x, e.X, nil) + x.expr = e + return kind case *ast.SelectorExpr: - sel := e.Sel.Name - // If the identifier refers to a package, handle everything here - // so we don't need a "package" mode for operands: package names - // can only appear in qualified identifiers which are mapped to - // selector expressions. - if ident, ok := e.X.(*ast.Ident); ok { - if pkg, ok := check.lookup(ident).(*Package); ok { - exp := pkg.Scope.Lookup(sel) - // gcimported package scopes contain non-exported - // objects such as types used in partially exported - // objects - do not accept them - if exp == nil || !ast.IsExported(exp.GetName()) { - check.errorf(e.Pos(), "cannot refer to unexported %s", e) - goto Error - } - check.register(e.Sel, exp) - // Simplified version of the code for *ast.Idents: - // - imported packages use types.Scope and types.Objects - // - imported objects are always fully initialized - switch exp := exp.(type) { - case *Const: - assert(exp.Val != nil) - x.mode = constant - x.typ = exp.Type - x.val = exp.Val - case *TypeName: - x.mode = typexpr - x.typ = exp.Type - case *Var: - x.mode = variable - x.typ = exp.Type - case *Func: - x.mode = value - x.typ = exp.Type - default: - unreachable() - } - x.expr = e - return - } - } + check.selector(x, e) - check.exprOrType(x, e.X, nil, iota, false) + case *ast.IndexExpr: + check.expr(x, e.X) if x.mode == invalid { goto Error } - res := lookupField(x.typ, QualifiedName{check.pkg, sel}) - if res.mode == invalid { - check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel) - goto Error - } - if x.mode == typexpr { - // method expression - sig, ok := res.typ.(*Signature) - if !ok { - check.invalidOp(e.Pos(), "%s has no method %s", x, sel) - goto Error - } - // the receiver type becomes the type of the first function - // argument of the method expression's function type - // TODO(gri) at the moment, method sets don't correctly track - // pointer vs non-pointer receivers => typechecker is too lenient - x.mode = value - x.typ = &Signature{ - Params: append([]*Var{{Type: x.typ}}, sig.Params...), - Results: sig.Results, - IsVariadic: sig.IsVariadic, - } - } else { - // regular selector - x.mode = res.mode - x.typ = res.typ - } - - case *ast.IndexExpr: - check.expr(x, e.X, hint, iota) valid := false length := int64(-1) // valid if >= 0 - switch typ := underlying(x.typ).(type) { + switch typ := x.typ.Underlying().(type) { case *Basic: if isString(typ) { valid = true if x.mode == constant { - length = int64(len(x.val.(string))) + length = int64(len(exact.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 = Typ[Byte] + x.typ = UniverseByte // use 'byte' name } case *Array: valid = true - length = typ.Len + length = typ.len if x.mode != variable { x.mode = value } - x.typ = typ.Elt + x.typ = typ.elem case *Pointer: - if typ, _ := underlying(typ.Base).(*Array); typ != nil { + if typ, _ := typ.base.Underlying().(*Array); typ != nil { valid = true - length = typ.Len + length = typ.len x.mode = variable - x.typ = typ.Elt + x.typ = typ.elem } case *Slice: valid = true x.mode = variable - x.typ = typ.Elt + x.typ = typ.elem case *Map: var key operand - check.expr(&key, e.Index, nil, iota) - if key.mode == invalid || !key.isAssignable(check.ctxt, typ.Key) { - check.invalidOp(x.pos(), "cannot use %s as map index of type %s", &key, typ.Key) + check.expr(&key, e.Index) + if !check.assignment(&key, typ.key) { + if key.mode != invalid { + check.invalidOp(key.pos(), "cannot use %s as map index of type %s", &key, typ.key) + } goto Error } - x.mode = valueok - x.typ = typ.Elt + x.mode = mapindex + x.typ = typ.elem x.expr = e - return + return expression } if !valid { @@ -1008,57 +1205,57 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle } if e.Index == nil { - check.invalidAST(e.Pos(), "missing index expression for %s", x) - return + check.invalidAST(e.Pos(), "missing index for %s", x) + goto Error } - check.index(e.Index, length, iota) + check.index(e.Index, length) // ok to continue case *ast.SliceExpr: - check.expr(x, e.X, hint, iota) + check.expr(x, e.X) + if x.mode == invalid { + goto Error + } valid := false length := int64(-1) // valid if >= 0 - switch typ := underlying(x.typ).(type) { + switch typ := x.typ.Underlying().(type) { case *Basic: if isString(typ) { + if slice3(e) { + check.invalidOp(x.pos(), "3-index slice of string") + goto Error + } valid = true if x.mode == constant { - length = int64(len(x.val.(string))) + 1 // +1 for slice + length = int64(len(exact.StringVal(x.val))) } - // a sliced string always yields a string value - // of the same type as the original string (not - // a constant) even if the string and the indices - // are constant - x.mode = value - // x.typ doesn't change, but if it is an untyped - // string it becomes string (see also issue 4913). - if typ.Kind == UntypedString { + // 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 + 1 // +1 for slice + length = typ.len if x.mode != variable { check.invalidOp(x.pos(), "cannot slice %s (value not addressable)", x) goto Error } - x.typ = &Slice{Elt: typ.Elt} + x.typ = &Slice{elem: typ.elem} case *Pointer: - if typ, _ := underlying(typ.Base).(*Array); typ != nil { + if typ, _ := typ.base.Underlying().(*Array); typ != nil { valid = true - length = typ.Len + 1 // +1 for slice - x.mode = variable - x.typ = &Slice{Elt: typ.Elt} + length = typ.len + x.typ = &Slice{elem: typ.elem} } case *Slice: valid = true - x.mode = variable // x.typ doesn't change } @@ -1067,158 +1264,91 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle goto Error } - lo := int64(0) - if e.Low != nil { - lo = check.index(e.Low, length, iota) + x.mode = value + + // spec: "Only the first index may be omitted; it defaults to 0." + if slice3(e) && (e.High == nil || sliceMax(e) == nil) { + check.error(e.Rbrack, "2nd and 3rd index required in 3-index slice") + goto Error } - hi := int64(-1) - if e.High != nil { - hi = check.index(e.High, length, iota) - } else if length >= 0 { - hi = length + // check indices + var ind [3]int64 + for i, expr := range []ast.Expr{e.Low, e.High, sliceMax(e)} { + 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 t, ok := check.index(expr, max); ok && t >= 0 { + x = t + } + 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 } - if lo >= 0 && hi >= 0 && lo > hi { - check.errorf(e.Low.Pos(), "inverted slice range: %d > %d", lo, hi) - // ok to continue + // 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.Rbrack, "invalid slice indices: %d > %d", x, y) + break L // only report one error, ok to continue + } + } + } } case *ast.TypeAssertExpr: - check.expr(x, e.X, hint, iota) + check.expr(x, e.X) if x.mode == invalid { goto Error } - var T *Interface - if T, _ = underlying(x.typ).(*Interface); T == nil { + xtyp, _ := x.typ.Underlying().(*Interface) + if xtyp == nil { check.invalidOp(x.pos(), "%s is not an interface", x) goto Error } // x.(type) expressions are handled explicitly in type switches if e.Type == nil { - check.errorf(e.Pos(), "use of .(type) outside type switch") + check.invalidAST(e.Pos(), "use of .(type) outside type switch") goto Error } - typ := check.typ(e.Type, false) - if typ == Typ[Invalid] { + T := check.typ(e.Type) + if T == Typ[Invalid] { goto Error } - if method, wrongType := missingMethod(typ, T); method != nil { - var msg string - if wrongType { - msg = "%s cannot have dynamic type %s (wrong type for method %s)" - } else { - msg = "%s cannot have dynamic type %s (missing method %s)" - } - check.errorf(e.Type.Pos(), msg, x, typ, method.Name) - // ok to continue - } - x.mode = valueok - x.expr = e - x.typ = typ + check.typeAssertion(x.pos(), x, xtyp, T) + x.mode = commaok + x.typ = T case *ast.CallExpr: - check.exprOrType(x, e.Fun, nil, iota, false) - if x.mode == invalid { - goto Error - } else if x.mode == typexpr { - check.conversion(x, e, x.typ, iota) - } else if sig, ok := underlying(x.typ).(*Signature); ok { - // check parameters - - // If we have a trailing ... at the end of the parameter - // list, the last argument must match the parameter type - // []T of a variadic function parameter x ...T. - passSlice := false - if e.Ellipsis.IsValid() { - if sig.IsVariadic { - passSlice = true - } else { - check.errorf(e.Ellipsis, "cannot use ... in call to %s", e.Fun) - // ok to continue - } - } - - // If we have a single argument that is a function call - // we need to handle it separately. Determine if this - // is the case without checking the argument. - var call *ast.CallExpr - if len(e.Args) == 1 { - call, _ = unparen(e.Args[0]).(*ast.CallExpr) - } - - n := 0 // parameter count - if call != nil { - // We have a single argument that is a function call. - check.expr(x, call, nil, -1) - if x.mode == invalid { - goto Error // TODO(gri): we can do better - } - if t, _ := x.typ.(*Result); t != nil { - // multiple result values - n = len(t.Values) - for i, obj := range t.Values { - x.mode = value - x.expr = nil // TODO(gri) can we do better here? (for good error messages) - x.typ = obj.Type - check.argument(sig, i, nil, x, passSlice && i+1 == n) - } - } else { - // single result value - n = 1 - check.argument(sig, 0, nil, x, passSlice) - } - - } else { - // We don't have a single argument or it is not a function call. - n = len(e.Args) - for i, arg := range e.Args { - check.argument(sig, i, arg, x, passSlice && i+1 == n) - } - } - - // determine if we have enough arguments - if sig.IsVariadic { - // a variadic function accepts an "empty" - // last argument: count one extra - n++ - } - if n < len(sig.Params) { - check.errorf(e.Fun.Pos(), "too few arguments in call to %s", e.Fun) - // ok to continue - } - - // determine result - switch len(sig.Results) { - case 0: - x.mode = novalue - case 1: - x.mode = value - x.typ = sig.Results[0].Type - default: - x.mode = value - x.typ = &Result{Values: sig.Results} - } - - } else if bin, ok := x.typ.(*builtin); ok { - check.builtin(x, e, bin, iota) - - } else { - check.invalidOp(x.pos(), "cannot call non-function %s", x) - goto Error - } + return check.call(x, e) case *ast.StarExpr: - check.exprOrType(x, e.X, hint, iota, true) + check.exprOrType(x, e.X) switch x.mode { case invalid: goto Error case typexpr: - x.typ = &Pointer{Base: x.typ} + x.typ = &Pointer{base: x.typ} default: - if typ, ok := underlying(x.typ).(*Pointer); ok { + if typ, ok := x.typ.Underlying().(*Pointer); ok { x.mode = variable - x.typ = typ.Base + x.typ = typ.base } else { check.invalidOp(x.pos(), "cannot indirect %s", x) goto Error @@ -1226,132 +1356,119 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle } case *ast.UnaryExpr: - check.expr(x, e.X, hint, iota) + check.expr(x, e.X) + if x.mode == invalid { + goto Error + } check.unary(x, e.Op) + if x.mode == invalid { + goto Error + } + if e.Op == token.ARROW { + x.expr = e + return statement // receive operations may appear in statement context + } case *ast.BinaryExpr: - var y operand - check.expr(x, e.X, hint, iota) - check.expr(&y, e.Y, hint, iota) - check.binary(x, &y, e.Op, hint) + check.binary(x, e.X, e.Y, e.Op) + if x.mode == invalid { + goto Error + } case *ast.KeyValueExpr: // key:value expressions are handled in composite literals check.invalidAST(e.Pos(), "no key:value expected") goto Error - case *ast.ArrayType: - if e.Len != nil { - check.expr(x, e.Len, nil, iota) - if x.mode == invalid { - goto Error - } - if x.mode != constant { - if x.mode != invalid { - check.errorf(x.pos(), "array length %s must be constant", x) - } - goto Error - } - n, ok := x.val.(int64) - if !ok || n < 0 { - check.errorf(x.pos(), "invalid array length %s", x) - goto Error - } - x.typ = &Array{Len: n, Elt: check.typ(e.Elt, cycleOk)} - } else { - x.typ = &Slice{Elt: check.typ(e.Elt, true)} - } - x.mode = typexpr - - case *ast.StructType: + case *ast.ArrayType, *ast.StructType, *ast.FuncType, + *ast.InterfaceType, *ast.MapType, *ast.ChanType: x.mode = typexpr - x.typ = &Struct{Fields: check.collectFields(e.Fields, cycleOk)} - - case *ast.FuncType: - params, isVariadic := check.collectParams(e.Params, true) - results, _ := check.collectParams(e.Results, false) - x.mode = typexpr - x.typ = &Signature{Recv: nil, Params: params, Results: results, IsVariadic: isVariadic} - - case *ast.InterfaceType: - x.mode = typexpr - x.typ = &Interface{Methods: check.collectMethods(e.Methods)} - - case *ast.MapType: - x.mode = typexpr - x.typ = &Map{Key: check.typ(e.Key, true), Elt: check.typ(e.Value, true)} - - case *ast.ChanType: - x.mode = typexpr - x.typ = &Chan{Dir: e.Dir, Elt: check.typ(e.Value, true)} + 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: - check.dump("e = %s", e) - unreachable() + panic(fmt.Sprintf("%s: unknown expression type %T", check.fset.Position(e.Pos()), e)) } // everything went well x.expr = e - return + return expression Error: x.mode = invalid x.expr = e + return statement // avoid follow-up errors } -// exprOrType is like rawExpr but reports an error if e doesn't represents a value or type. -func (check *checker) exprOrType(x *operand, e ast.Expr, hint Type, iota int, cycleOk bool) { - check.rawExpr(x, e, hint, iota, cycleOk) - if x.mode == novalue { - check.errorf(x.pos(), "%s used as value or type", x) - x.mode = invalid +// typeAssertion checks that x.(T) is legal; xtyp must be the type of x. +func (check *Checker) typeAssertion(pos token.Pos, x *operand, xtyp *Interface, T Type) { + method, wrongType := assertableTo(xtyp, T) + if method == nil { + return + } + + var msg string + if wrongType { + msg = "wrong type for method" + } else { + msg = "missing method" } + check.errorf(pos, "%s cannot have dynamic type %s (%s %s)", x, T, msg, method.name) } -// expr is like rawExpr but reports an error if e doesn't represents a value. -func (check *checker) expr(x *operand, e ast.Expr, hint Type, iota int) { - check.rawExpr(x, e, hint, iota, false) +// expr typechecks expression e and initializes x with the expression value. +// If an error occurred, x.mode is set to invalid. +// +func (check *Checker) expr(x *operand, e ast.Expr) { + check.rawExpr(x, e, nil) + var msg string switch x.mode { + default: + return case novalue: - check.errorf(x.pos(), "%s used as value", x) - x.mode = invalid + msg = "used as value" + case builtin: + msg = "must be called" case typexpr: - check.errorf(x.pos(), "%s is not an expression", x) - x.mode = invalid + msg = "is not an expression" } + check.errorf(x.pos(), "%s %s", x, msg) + x.mode = invalid } -func (check *checker) rawTyp(e ast.Expr, cycleOk, nilOk bool) Type { - var x operand - check.rawExpr(&x, e, nil, -1, cycleOk) +// exprWithHint typechecks expression e and initializes x with the expression value. +// 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) exprWithHint(x *operand, e ast.Expr, hint Type) { + assert(hint != nil) + check.rawExpr(x, e, hint) + var msg string switch x.mode { - case invalid: - // ignore - error reported before + default: + return case novalue: - check.errorf(x.pos(), "%s used as type", &x) + msg = "used as value" + case builtin: + msg = "must be called" case typexpr: - return x.typ - case constant: - if nilOk && x.isNil() { - return nil - } - fallthrough - default: - check.errorf(x.pos(), "%s is not a type", &x) + msg = "is not an expression" } - return Typ[Invalid] -} - -// typOrNil is like rawExpr but reports an error if e doesn't represents a type or the predeclared value nil. -// It returns e's type, nil, or Typ[Invalid] if an error occurred. -// -func (check *checker) typOrNil(e ast.Expr, cycleOk bool) Type { - return check.rawTyp(e, cycleOk, true) + check.errorf(x.pos(), "%s %s", x, msg) + x.mode = invalid } -// typ is like rawExpr but reports an error if e doesn't represents a type. -// It returns e's type, or Typ[Invalid] if an error occurred. +// 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) typ(e ast.Expr, cycleOk bool) Type { - return check.rawTyp(e, cycleOk, false) +func (check *Checker) exprOrType(x *operand, e ast.Expr) { + check.rawExpr(x, e, nil) + if x.mode == novalue { + check.errorf(x.pos(), "%s used as value or type", x) + x.mode = invalid + } } diff --git a/src/gosubli.me/something-borrowed/types/exprstring.go b/src/gosubli.me/something-borrowed/types/exprstring.go new file mode 100644 index 00000000..370bdf35 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/exprstring.go @@ -0,0 +1,220 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements printing of expressions. + +package types + +import ( + "bytes" + "go/ast" +) + +// ExprString returns the (possibly simplified) string representation for x. +func ExprString(x ast.Expr) string { + var buf bytes.Buffer + WriteExpr(&buf, x) + return buf.String() +} + +// WriteExpr writes the (possibly simplified) string representation for x to buf. +func WriteExpr(buf *bytes.Buffer, x ast.Expr) { + // The AST preserves source-level parentheses so there is + // no need to introduce them here to correct for different + // operator precedences. (This assumes that the AST was + // generated by a Go parser.) + + switch x := x.(type) { + default: + buf.WriteString("(bad expr)") // nil, ast.BadExpr, ast.KeyValueExpr + + case *ast.Ident: + buf.WriteString(x.Name) + + case *ast.Ellipsis: + buf.WriteString("...") + if x.Elt != nil { + WriteExpr(buf, x.Elt) + } + + case *ast.BasicLit: + buf.WriteString(x.Value) + + case *ast.FuncLit: + buf.WriteByte('(') + WriteExpr(buf, x.Type) + buf.WriteString(" literal)") // simplified + + case *ast.CompositeLit: + buf.WriteByte('(') + WriteExpr(buf, x.Type) + buf.WriteString(" literal)") // simplified + + case *ast.ParenExpr: + buf.WriteByte('(') + WriteExpr(buf, x.X) + buf.WriteByte(')') + + case *ast.SelectorExpr: + WriteExpr(buf, x.X) + buf.WriteByte('.') + buf.WriteString(x.Sel.Name) + + case *ast.IndexExpr: + WriteExpr(buf, x.X) + buf.WriteByte('[') + WriteExpr(buf, x.Index) + buf.WriteByte(']') + + case *ast.SliceExpr: + WriteExpr(buf, x.X) + buf.WriteByte('[') + if x.Low != nil { + WriteExpr(buf, x.Low) + } + buf.WriteByte(':') + if x.High != nil { + WriteExpr(buf, x.High) + } + if x.Slice3 { + buf.WriteByte(':') + if x.Max != nil { + WriteExpr(buf, x.Max) + } + } + buf.WriteByte(']') + + case *ast.TypeAssertExpr: + WriteExpr(buf, x.X) + buf.WriteString(".(") + WriteExpr(buf, x.Type) + buf.WriteByte(')') + + case *ast.CallExpr: + WriteExpr(buf, x.Fun) + buf.WriteByte('(') + for i, arg := range x.Args { + if i > 0 { + buf.WriteString(", ") + } + WriteExpr(buf, arg) + } + if x.Ellipsis.IsValid() { + buf.WriteString("...") + } + buf.WriteByte(')') + + case *ast.StarExpr: + buf.WriteByte('*') + WriteExpr(buf, x.X) + + case *ast.UnaryExpr: + buf.WriteString(x.Op.String()) + WriteExpr(buf, x.X) + + case *ast.BinaryExpr: + WriteExpr(buf, x.X) + buf.WriteByte(' ') + buf.WriteString(x.Op.String()) + buf.WriteByte(' ') + WriteExpr(buf, x.Y) + + case *ast.ArrayType: + buf.WriteByte('[') + if x.Len != nil { + WriteExpr(buf, x.Len) + } + buf.WriteByte(']') + WriteExpr(buf, x.Elt) + + case *ast.StructType: + buf.WriteString("struct{") + writeFieldList(buf, x.Fields, "; ", false) + buf.WriteByte('}') + + case *ast.FuncType: + buf.WriteString("func") + writeSigExpr(buf, x) + + case *ast.InterfaceType: + buf.WriteString("interface{") + writeFieldList(buf, x.Methods, "; ", true) + buf.WriteByte('}') + + case *ast.MapType: + buf.WriteString("map[") + WriteExpr(buf, x.Key) + buf.WriteByte(']') + WriteExpr(buf, x.Value) + + case *ast.ChanType: + var s string + switch x.Dir { + case ast.SEND: + s = "chan<- " + case ast.RECV: + s = "<-chan " + default: + s = "chan " + } + buf.WriteString(s) + WriteExpr(buf, x.Value) + } +} + +func writeSigExpr(buf *bytes.Buffer, sig *ast.FuncType) { + buf.WriteByte('(') + writeFieldList(buf, sig.Params, ", ", false) + buf.WriteByte(')') + + res := sig.Results + n := res.NumFields() + if n == 0 { + // no result + return + } + + buf.WriteByte(' ') + if n == 1 && len(res.List[0].Names) == 0 { + // single unnamed result + WriteExpr(buf, res.List[0].Type) + return + } + + // multiple or named result(s) + buf.WriteByte('(') + writeFieldList(buf, res, ", ", false) + buf.WriteByte(')') +} + +func writeFieldList(buf *bytes.Buffer, fields *ast.FieldList, sep string, iface bool) { + for i, f := range fields.List { + if i > 0 { + buf.WriteString(sep) + } + + // field list names + for i, name := range f.Names { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(name.Name) + } + + // types of interface methods consist of signatures only + if sig, _ := f.Type.(*ast.FuncType); sig != nil && iface { + writeSigExpr(buf, sig) + continue + } + + // named fields are separated with a blank from the field type + if len(f.Names) > 0 { + buf.WriteByte(' ') + } + + WriteExpr(buf, f.Type) + + // ignore tag + } +} diff --git a/src/gosubli.me/something-borrowed/types/exprstring_test.go b/src/gosubli.me/something-borrowed/types/exprstring_test.go new file mode 100644 index 00000000..cfd14722 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/exprstring_test.go @@ -0,0 +1,94 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types_test + +import ( + "go/parser" + "testing" + + . "golang.org/x/tools/go/types" +) + +var testExprs = []testEntry{ + // basic type literals + dup("x"), + dup("true"), + dup("42"), + dup("3.1415"), + dup("2.71828i"), + dup(`'a'`), + dup(`"foo"`), + dup("`bar`"), + + // func and composite literals + {"func(){}", "(func() literal)"}, + {"func(x int) complex128 {}", "(func(x int) complex128 literal)"}, + {"[]int{1, 2, 3}", "([]int literal)"}, + + // non-type expressions + dup("(x)"), + dup("x.f"), + dup("a[i]"), + + dup("s[:]"), + dup("s[i:]"), + dup("s[:j]"), + dup("s[i:j]"), + dup("s[:j:k]"), + dup("s[i:j:k]"), + + dup("x.(T)"), + + dup("x.([10]int)"), + dup("x.([...]int)"), + + dup("x.(struct{})"), + dup("x.(struct{x int; y, z float32; E})"), + + dup("x.(func())"), + dup("x.(func(x int))"), + dup("x.(func() int)"), + dup("x.(func(x, y int, z float32) (r int))"), + dup("x.(func(a, b, c int))"), + dup("x.(func(x ...T))"), + + dup("x.(interface{})"), + dup("x.(interface{m(); n(x int); E})"), + dup("x.(interface{m(); n(x int) T; E; F})"), + + dup("x.(map[K]V)"), + + dup("x.(chan E)"), + dup("x.(<-chan E)"), + dup("x.(chan<- chan int)"), + dup("x.(chan<- <-chan int)"), + dup("x.(<-chan chan int)"), + dup("x.(chan (<-chan int))"), + + dup("f()"), + dup("f(x)"), + dup("int(x)"), + dup("f(x, x + y)"), + dup("f(s...)"), + dup("f(a, s...)"), + + dup("*x"), + dup("&x"), + dup("x + y"), + dup("x + y << (2 * s)"), +} + +func TestExprString(t *testing.T) { + for _, test := range testExprs { + x, err := parser.ParseExpr(test.src) + if err != nil { + t.Errorf("%s: %s", test.src, err) + continue + } + if got := ExprString(x); got != test.str { + t.Errorf("%s: got %s, want %s", test.src, got, test.str) + } + } +} diff --git a/src/gosubli.me/something-borrowed/types/go11.go b/src/gosubli.me/something-borrowed/types/go11.go new file mode 100644 index 00000000..cf41cabe --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/go11.go @@ -0,0 +1,17 @@ +// 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. + +// +build !go1.2 + +package types + +import "go/ast" + +func slice3(x *ast.SliceExpr) bool { + return false +} + +func sliceMax(x *ast.SliceExpr) ast.Expr { + return nil +} diff --git a/src/gosubli.me/something-borrowed/types/go12.go b/src/gosubli.me/something-borrowed/types/go12.go new file mode 100644 index 00000000..20174421 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/go12.go @@ -0,0 +1,17 @@ +// 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. + +// +build go1.2 + +package types + +import "go/ast" + +func slice3(x *ast.SliceExpr) bool { + return x.Slice3 +} + +func sliceMax(x *ast.SliceExpr) ast.Expr { + return x.Max +} diff --git a/src/gosubli.me/something-borrowed/types/hilbert_test.go b/src/gosubli.me/something-borrowed/types/hilbert_test.go new file mode 100644 index 00000000..b5557218 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/hilbert_test.go @@ -0,0 +1,232 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types_test + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "testing" + + . "golang.org/x/tools/go/types" +) + +var ( + H = flag.Int("H", 5, "Hilbert matrix size") + out = flag.String("out", "", "write generated program to out") +) + +func TestHilbert(t *testing.T) { + // generate source + src := program(*H, *out) + if *out != "" { + ioutil.WriteFile(*out, src, 0666) + return + } + + // parse source + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "hilbert.go", src, 0) + if err != nil { + t.Fatal(err) + } + + // type-check file + DefPredeclaredTestFuncs() // define assert built-in + _, err = Check(f.Name.Name, fset, []*ast.File{f}) + if err != nil { + t.Fatal(err) + } +} + +func program(n int, out string) []byte { + var g gen + + g.p(`// WARNING: GENERATED FILE - DO NOT MODIFY MANUALLY! +// (To generate, in go/types directory: go test -run=Hilbert -H=%d -out=%q) + +// 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) mulRange(a, b int) { + if a > b { + g.p("1") + return + } + for i := a; i <= b; i++ { + if i > a { + g.p("*") + } + g.p("%d", i) + } +} + +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/gosubli.me/something-borrowed/types/initorder.go b/src/gosubli.me/something-borrowed/types/initorder.go new file mode 100644 index 00000000..0fd567b2 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/initorder.go @@ -0,0 +1,222 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types + +import ( + "container/heap" + "fmt" +) + +// initOrder computes the Info.InitOrder for package variables. +func (check *Checker) initOrder() { + // An InitOrder may already have been computed if a package is + // built from several calls to (*Checker).Files. Clear it. + check.Info.InitOrder = check.Info.InitOrder[:0] + + // compute the object dependency graph and + // initialize a priority queue with the list + // of graph nodes + pq := nodeQueue(dependencyGraph(check.objMap)) + heap.Init(&pq) + + const debug = false + if debug { + fmt.Printf("package %s: object dependency graph\n", check.pkg.Name()) + for _, n := range pq { + for _, o := range n.out { + fmt.Printf("\t%s -> %s\n", n.obj.Name(), o.obj.Name()) + } + } + fmt.Println() + fmt.Printf("package %s: initialization order\n", check.pkg.Name()) + } + + // 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. + mark := 0 + emitted := make(map[*declInfo]bool) + for len(pq) > 0 { + // get the next node + n := heap.Pop(&pq).(*objNode) + + // if n still depends on other nodes, we have a cycle + if n.in > 0 { + mark++ // mark nodes using a different value each time + cycle := findPath(n, n, mark) + if i := valIndex(cycle); i >= 0 { + check.reportCycle(cycle, i) + } + // 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 _, out := range n.out { + out.in-- + heap.Fix(&pq, out.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.Printf("\t%s\n", init) + } + } + + if debug { + fmt.Println() + } +} + +// findPath returns the (reversed) list of nodes z, ... c, b, a, +// such that there is a path (list of edges) from a to z. +// If there is no such path, the result is nil. +// Nodes marked with the value mark are considered "visited"; +// unvisited nodes are marked during the graph search. +func findPath(a, z *objNode, mark int) []*objNode { + if a.mark == mark { + return nil // node already seen + } + a.mark = mark + + for _, n := range a.out { + if n == z { + return []*objNode{z} + } + if P := findPath(n, z, mark); P != nil { + return append(P, n) + } + } + + return nil +} + +// valIndex returns the index of the first constant or variable in a, +// if any; or a value < 0. +func valIndex(a []*objNode) int { + for i, n := range a { + switch n.obj.(type) { + case *Const, *Var: + return i + } + } + return -1 +} + +// reportCycle reports an error for the cycle starting at i. +func (check *Checker) reportCycle(cycle []*objNode, i int) { + obj := cycle[i].obj + check.errorf(obj.Pos(), "initialization cycle for %s", obj.Name()) + // print cycle + 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].obj + } + check.errorf(obj.Pos(), "\t%s", obj.Name()) +} + +// An objNode represents a node in the object dependency graph. +// Each node b in a.out represents an edge a->b indicating that +// b depends on a. +// Nodes may be marked for cycle detection. A node n is marked +// if n.mark corresponds to the current mark value. +type objNode struct { + obj Object // object represented by this node + in int // number of nodes this node depends on + out []*objNode // list of nodes that depend on this node + index int // node index in list of nodes + mark int // for cycle detection +} + +// dependencyGraph computes the transposed object dependency graph +// from the given objMap. The transposed graph is returned as a list +// of nodes; an edge d->n indicates that node n depends on node d. +func dependencyGraph(objMap map[Object]*declInfo) []*objNode { + // M maps each object to its corresponding node + M := make(map[Object]*objNode, len(objMap)) + for obj := range objMap { + M[obj] = &objNode{obj: obj} + } + + // G is the graph of nodes n + G := make([]*objNode, len(M)) + i := 0 + for obj, n := range M { + deps := objMap[obj].deps + n.in = len(deps) + for d := range deps { + d := M[d] // node n depends on node d + d.out = append(d.out, n) // add edge d->n + } + + G[i] = n + n.index = i + i++ + } + + return G +} + +// nodeQueue implements the container/heap interface; +// a nodeQueue may be used as a priority queue. +type nodeQueue []*objNode + +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.in < y.in || x.in == y.in && 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/gosubli.me/something-borrowed/types/issues_test.go b/src/gosubli.me/something-borrowed/types/issues_test.go new file mode 100644 index 00000000..04d8b371 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/issues_test.go @@ -0,0 +1,205 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements tests for various issues. + +package types_test + +import ( + "fmt" + "go/ast" + "go/parser" + "sort" + "strings" + "testing" + + _ "golang.org/x/tools/go/gcimporter" + . "golang.org/x/tools/go/types" +) + +func TestIssue5770(t *testing.T) { + src := `package p; type S struct{T}` + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + _, err = Check(f.Name.Name, fset, []*ast.File{f}) // 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, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + var conf Config + types := make(map[ast.Expr]TypeAndValue) + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Types: types}) + if err != nil { + t.Fatal(err) + } + + for x, tv := range types { + var want Type + switch x := x.(type) { + case *ast.BasicLit: + switch x.Value { + case `8`: + want = Typ[Uint8] + case `16`: + want = Typ[Uint16] + case `32`: + want = Typ[Uint32] + case `64`: + want = Typ[Uint] // because of "+ s", s is of type uint + case `"foo"`: + want = Typ[String] + } + case *ast.Ident: + if x.Name == "nil" { + want = Typ[UntypedNil] + } + } + if want != nil && !Identical(tv.Type, want) { + t.Errorf("got %s; want %s", tv.Type, want) + } + } +} + +func TestIssue6413(t *testing.T) { + src := ` +package p +func f() int { + defer f() + go f() + return 0 +} +` + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + var conf Config + types := make(map[ast.Expr]TypeAndValue) + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Types: types}) + if err != nil { + t.Fatal(err) + } + + want := Typ[Int] + n := 0 + for x, tv := range types { + if _, ok := x.(*ast.CallExpr); ok { + if tv.Type != want { + t.Errorf("%s: got %s; want %s", fset.Position(x.Pos()), tv.Type, want) + } + n++ + } + } + + if n != 2 { + t.Errorf("got %d CallExprs; want 2", n) + } +} + +func TestIssue7245(t *testing.T) { + src := ` +package p +func (T) m() (res bool) { return } +type T struct{} // receiver type after method declaration +` + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + var conf Config + defs := make(map[*ast.Ident]Object) + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Defs: defs}) + if err != nil { + t.Fatal(err) + } + + m := f.Decls[0].(*ast.FuncDecl) + res1 := defs[m.Name].(*Func).Type().(*Signature).Results().At(0) + res2 := defs[m.Type.Results.List[0].Names[0]].(*Var) + + if res1 != res2 { + t.Errorf("got %s (%p) != %s (%p)", res1, res2, res1, res2) + } +} + +// This tests that uses of existing vars on the LHS of an assignment +// are Uses, not Defs; and also that the (illegal) use of a non-var on +// the LHS of an assignment is a Use nonetheless. +func TestIssue7827(t *testing.T) { + const src = ` +package p +func _() { + const w = 1 // defs w + x, y := 2, 3 // defs x, y + w, x, z := 4, 5, 6 // uses w, x, defs z; error: cannot assign to w + _, _, _ = x, y, z // uses x, y, z +} +` + 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` + + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + // don't abort at the first error + conf := Config{Error: func(err error) { t.Log(err) }} + defs := make(map[*ast.Ident]Object) + uses := make(map[*ast.Ident]Object) + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Defs: defs, Uses: uses}) + if s := fmt.Sprint(err); !strings.HasSuffix(s, "cannot assign to w") { + t.Errorf("Check: unexpected error: %s", s) + } + + var facts []string + for id, obj := range defs { + if obj != nil { + fact := fmt.Sprintf("L%d defs %s", fset.Position(id.Pos()).Line, obj) + facts = append(facts, fact) + } + } + for id, obj := range uses { + fact := fmt.Sprintf("L%d uses %s", fset.Position(id.Pos()).Line, obj) + facts = append(facts, fact) + } + sort.Strings(facts) + + got := strings.Join(facts, "\n") + if got != want { + t.Errorf("Unexpected defs/uses\ngot:\n%s\nwant:\n%s", got, want) + } +} diff --git a/src/gosubli.me/something-borrowed/types/labels.go b/src/gosubli.me/something-borrowed/types/labels.go new file mode 100644 index 00000000..d6ffc523 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/labels.go @@ -0,0 +1,268 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types + +import ( + "go/ast" + "go/token" +) + +// labels checks correct label use in body. +func (check *Checker) labels(body *ast.BlockStmt) { + // set of all labels in this body + all := NewScope(nil, "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.Name + 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.Pos(), 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 *ast.LabeledStmt // labeled statement to which this block belongs, or nil + labels map[string]*ast.LabeledStmt // allocated lazily +} + +// insert records a new label declaration for the current block. +// The label must not have been declared before in any block. +func (b *block) insert(s *ast.LabeledStmt) { + name := s.Label.Name + if debug { + assert(b.gotoTarget(name) == nil) + } + labels := b.labels + if labels == nil { + labels = make(map[string]*ast.LabeledStmt) + b.labels = labels + } + labels[name] = s +} + +// gotoTarget returns the labeled statement in the current +// or an enclosing block with the given label name, or nil. +func (b *block) gotoTarget(name string) *ast.LabeledStmt { + for s := b; s != nil; s = s.parent { + if t := s.labels[name]; t != nil { + return t + } + } + return nil +} + +// enclosingTarget returns the innermost enclosing labeled +// statement with the given label name, or nil. +func (b *block) enclosingTarget(name string) *ast.LabeledStmt { + for s := b; s != nil; s = s.parent { + if t := s.lstmt; t != nil && t.Label.Name == name { + return t + } + } + return nil +} + +// blockBranches processes a block's statement list and returns the set of outgoing forward jumps. +// all is the scope of all declared labels, parent the set of labels declared in the immediately +// enclosing block, and lstmt is the labeled statement this block is associated with (or nil). +func (check *Checker) blockBranches(all *Scope, parent *block, lstmt *ast.LabeledStmt, list []ast.Stmt) []*ast.BranchStmt { + b := &block{parent: parent, lstmt: lstmt} + + var ( + varDeclPos token.Pos + fwdJumps, badJumps []*ast.BranchStmt + ) + + // All forward jumps jumping over a variable declaration are possibly + // invalid (they may still jump out of the block and be ok). + // recordVarDecl records them for the given position. + recordVarDecl := func(pos token.Pos) { + varDeclPos = pos + badJumps = append(badJumps[:0], fwdJumps...) // copy fwdJumps to badJumps + } + + jumpsOverVarDecl := func(jmp *ast.BranchStmt) bool { + if varDeclPos.IsValid() { + for _, bad := range badJumps { + if jmp == bad { + return true + } + } + } + return false + } + + blockBranches := func(lstmt *ast.LabeledStmt, list []ast.Stmt) { + // Unresolved forward jumps inside the nested block + // become forward jumps in the current block. + fwdJumps = append(fwdJumps, check.blockBranches(all, b, lstmt, list)...) + } + + var stmtBranches func(ast.Stmt) + stmtBranches = func(s ast.Stmt) { + switch s := s.(type) { + case *ast.DeclStmt: + if d, _ := s.Decl.(*ast.GenDecl); d != nil && d.Tok == token.VAR { + recordVarDecl(d.Pos()) + } + + case *ast.LabeledStmt: + // declare non-blank label + if name := s.Label.Name; name != "_" { + lbl := NewLabel(s.Label.Pos(), check.pkg, name) + if alt := all.Insert(lbl); alt != nil { + check.softErrorf(lbl.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.Name == name { + // match + lbl.used = true + check.recordUse(jmp.Label, lbl) + if jumpsOverVarDecl(jmp) { + check.softErrorf( + jmp.Label.Pos(), + "goto %s jumps over variable declaration at line %d", + name, + check.fset.Position(varDeclPos).Line, + ) + // ok to continue + } + } else { + // no match - record new forward jump + fwdJumps[i] = jmp + i++ + } + } + fwdJumps = fwdJumps[:i] + lstmt = s + } + stmtBranches(s.Stmt) + + case *ast.BranchStmt: + if s.Label == nil { + return // checked in 1st pass (check.stmt) + } + + // determine and validate target + name := s.Label.Name + switch s.Tok { + case token.BREAK: + // spec: "If there is a label, it must be that of an enclosing + // "for", "switch", or "select" statement, and that is the one + // whose execution terminates." + valid := false + if t := b.enclosingTarget(name); t != nil { + switch t.Stmt.(type) { + case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.ForStmt, *ast.RangeStmt: + valid = true + } + } + if !valid { + check.errorf(s.Label.Pos(), "invalid break label %s", name) + return + } + + case token.CONTINUE: + // spec: "If there is a label, it must be that of an enclosing + // "for" statement, and that is the one whose execution advances." + valid := false + if t := b.enclosingTarget(name); t != nil { + switch t.Stmt.(type) { + case *ast.ForStmt, *ast.RangeStmt: + valid = true + } + } + if !valid { + check.errorf(s.Label.Pos(), "invalid continue label %s", name) + return + } + + case token.GOTO: + if b.gotoTarget(name) == nil { + // label may be declared later - add branch to forward jumps + fwdJumps = append(fwdJumps, s) + return + } + + default: + check.invalidAST(s.Pos(), "branch statement: %s %s", s.Tok, name) + return + } + + // record label use + obj := all.Lookup(name) + obj.(*Label).used = true + check.recordUse(s.Label, obj) + + case *ast.AssignStmt: + if s.Tok == token.DEFINE { + recordVarDecl(s.Pos()) + } + + case *ast.BlockStmt: + blockBranches(lstmt, s.List) + + case *ast.IfStmt: + stmtBranches(s.Body) + if s.Else != nil { + stmtBranches(s.Else) + } + + case *ast.CaseClause: + blockBranches(nil, s.Body) + + case *ast.SwitchStmt: + stmtBranches(s.Body) + + case *ast.TypeSwitchStmt: + stmtBranches(s.Body) + + case *ast.CommClause: + blockBranches(nil, s.Body) + + case *ast.SelectStmt: + stmtBranches(s.Body) + + case *ast.ForStmt: + stmtBranches(s.Body) + + case *ast.RangeStmt: + stmtBranches(s.Body) + } + } + + for _, s := range list { + stmtBranches(s) + } + + return fwdJumps +} diff --git a/src/gosubli.me/something-borrowed/types/lookup.go b/src/gosubli.me/something-borrowed/types/lookup.go new file mode 100644 index 00000000..3caca551 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/lookup.go @@ -0,0 +1,341 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements various field and method lookup functions. + +package types + +// LookupFieldOrMethod looks up a field or method with given package and name +// in T and returns the corresponding *Var or *Func, an index sequence, and a +// bool indicating if there were any pointer indirections on the path to the +// field or method. If addressable is set, T is the type of an addressable +// variable (only matters for method lookups). +// +// The last index entry is the field or method index in the (possibly embedded) +// type where the entry was found, either: +// +// 1) the list of declared methods of a named type; or +// 2) the list of all methods (method set) of an interface type; or +// 3) the list of fields of a struct type. +// +// The earlier index entries are the indices of the anonymous 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) { + // 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 = lookupFieldOrMethod(p, false, pkg, name) + if _, ok := obj.(*Func); ok { + return nil, nil, false + } + return + } + } + + return lookupFieldOrMethod(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.) + +func lookupFieldOrMethod(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) + named, _ := typ.(*Named) + + // *typ where typ is an interface has no methods. + if isPtr { + utyp := typ + if named != nil { + utyp = named.underlying + } + if _, ok := utyp.(*Interface); ok { + return + } + } + + // Start with typ as single entry at shallowest depth. + // If typ is not a named type, insert a nil type instead. + current := []embeddedType{{named, nil, isPtr, false}} + + // named types that we have seen already, allocated lazily + var seen map[*Named]bool + + // search current depth + for len(current) > 0 { + var next []embeddedType // embedded types found at current depth + + // look for (pkg, name) in all types at current depth + for _, e := range current { + // The very first time only, e.typ may be nil. + // In this case, we don't have a named type and + // we simply continue with the underlying type. + if e.typ != nil { + if seen[e.typ] { + // 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[e.typ] = true + + // look for a matching attached method + if i, m := lookupMethod(e.typ.methods, pkg, name); m != nil { + // potential match + assert(m.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + obj = m + indirect = e.indirect + continue // we can't have a matching field or interface method + } + + // continue with underlying type + typ = e.typ.underlying + } + + switch t := typ.(type) { + case *Struct: + // look for a matching field and collect embedded types + for i, f := range t.fields { + if f.sameId(pkg, name) { + assert(f.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + obj = f + indirect = e.indirect + continue // we can't have a matching interface method + } + // Collect embedded struct fields for searching the next + // lower depth, but only if we have not seen a match yet + // (if we have a match it is either the desired field or + // we have a name collision on the same depth; in either + // case we don't need to look further). + // Embedded fields are always of the form T or *T where + // T is a named type. If e.typ appeared multiple times at + // this depth, f.typ appears multiple times at the next + // depth. + if obj == nil && f.anonymous { + // Ignore embedded basic types - only user-defined + // named types can have methods or struct fields. + typ, isPtr := deref(f.typ) + if t, _ := typ.(*Named); t != nil { + next = append(next, embeddedType{t, 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 + if i, m := lookupMethod(t.allMethods, pkg, name); m != nil { + assert(m.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + obj = m + indirect = e.indirect + } + } + } + + if obj != nil { + // found a potential match + // spec: "A method call x.m() is valid if the method set of (the type of) x + // contains m and the argument list can be assigned to the parameter + // list of m. If x is addressable and &x's method set contains m, x.m() + // is shorthand for (&x).m()". + if f, _ := obj.(*Func); f != nil && ptrRecv(f) && !indirect && !addressable { + return nil, nil, true // pointer/addressable receiver required + } + return + } + + current = consolidateMultiples(next) + } + + return nil, nil, false // not found +} + +// embeddedType represents an embedded named type +type embeddedType struct { + typ *Named // nil means use the outer typ variable instead + 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 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[*Named]int) // index at which type was previously seen + for _, e := range list { + if i, found := prev[e.typ]; found { + list[i].multiples = true + // ignore this entry + } else { + prev[e.typ] = n + list[n] = e + n++ + } + } + return list[:n] +} + +// 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) { + // fast path for common case + if T.Empty() { + return + } + + // TODO(gri) Consider using method sets here. Might be more efficient. + + if ityp, _ := V.Underlying().(*Interface); ityp != nil { + // TODO(gri) allMethods is sorted - can do this more efficiently + for _, m := range T.allMethods { + _, obj := lookupMethod(ityp.allMethods, m.pkg, m.name) + switch { + case obj == nil: + if static { + return m, false + } + case !Identical(obj.Type(), m.typ): + return m, true + } + } + return + } + + // A concrete type implements T if it implements all methods of T. + for _, m := range T.allMethods { + obj, _, _ := lookupFieldOrMethod(V, false, m.pkg, m.name) + + f, _ := obj.(*Func) + if f == nil { + return m, false + } + + if !Identical(f.typ, m.typ) { + return m, true + } + } + + 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. +func assertableTo(V *Interface, T Type) (method *Func, wrongType bool) { + // no static check is required if T is an interface + // spec: "If T is an interface type, x.(T) asserts that the + // dynamic type of x implements the interface T." + if _, ok := T.Underlying().(*Interface); ok && !strict { + return + } + return MissingMethod(T, V, false) +} + +// deref dereferences typ if it is a *Pointer and returns its base and true. +// Otherwise it returns (typ, false). +func deref(typ Type) (Type, bool) { + if p, _ := typ.(*Pointer); p != nil { + return p.base, true + } + return typ, false +} + +// derefStructPtr dereferences typ if it is a (named or unnamed) pointer to a +// (named or unnamed) struct and returns its base. Otherwise it returns typ. +func derefStructPtr(typ Type) Type { + if p, _ := typ.Underlying().(*Pointer); p != nil { + if _, ok := p.base.Underlying().(*Struct); ok { + return p.base + } + } + return typ +} + +// concat returns the result of concatenating list and i. +// The result does not share its underlying array with list. +func concat(list []int, i int) []int { + var t []int + t = append(t, list...) + return append(t, i) +} + +// fieldIndex returns the index for the field with matching package and name, or a value < 0. +func fieldIndex(fields []*Var, pkg *Package, name string) int { + if name != "_" { + for i, f := range fields { + if f.sameId(pkg, name) { + return i + } + } + } + return -1 +} + +// lookupMethod returns the index of and method with matching package and name, or (-1, nil). +func lookupMethod(methods []*Func, pkg *Package, name string) (int, *Func) { + if name != "_" { + for i, m := range methods { + if m.sameId(pkg, name) { + return i, m + } + } + } + return -1, nil +} diff --git a/src/gosubli.me/something-borrowed/types/methodset.go b/src/gosubli.me/something-borrowed/types/methodset.go new file mode 100644 index 00000000..8aff6f9b --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/methodset.go @@ -0,0 +1,271 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements method sets. + +package types + +import ( + "bytes" + "fmt" + "sort" +) + +// 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 bytes.Buffer + 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 + +// NewMethodSet returns the method set for the given type T. It +// always returns a non-nil method set, even if it is empty. +// +// A MethodSetCache handles repeat queries more efficiently. +// +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) + named, _ := typ.(*Named) + + // *typ where typ is an interface has no methods. + if isPtr { + utyp := typ + if named != nil { + utyp = named.underlying + } + if _, ok := utyp.(*Interface); ok { + return &emptyMethodSet + } + } + + // Start with typ as single entry at shallowest depth. + // If typ is not a named type, insert a nil type instead. + current := []embeddedType{{named, nil, isPtr, false}} + + // named types that we have seen already, allocated lazily + 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, allocated lazily + var fset fieldSet + var mset methodSet + + for _, e := range current { + // The very first time only, e.typ may be nil. + // In this case, we don't have a named type and + // we simply continue with the underlying type. + if e.typ != nil { + if seen[e.typ] { + // 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[e.typ] = true + + mset = mset.add(e.typ.methods, e.index, e.indirect, e.multiples) + + // continue with underlying type + typ = e.typ.underlying + } + + switch t := typ.(type) { + case *Struct: + for i, f := range t.fields { + fset = fset.add(f, e.multiples) + + // Embedded fields are always of the form T or *T where + // T is a named type. If typ appeared multiple times at + // this depth, f.Type appears multiple times at the next + // depth. + if f.anonymous { + // Ignore embedded basic types - only user-defined + // named types can have methods or struct fields. + typ, isPtr := deref(f.typ) + if t, _ := typ.(*Named); t != nil { + next = append(next, embeddedType{t, 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 _, found := fset[k]; found { + m = nil // collision + } + if base == nil { + base = make(methodSet) + } + base[k] = m + } + } + + // Multiple fields with matching names collide at this depth and shadow all + // entries further down; add them as collisions to base if no entries with + // matching names exist already. + for k, f := range fset { + if f == nil { + if _, found := base[k]; !found { + if base == nil { + base = make(methodSet) + } + base[k] = nil // collision + } + } + } + + current = 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.Sort(byUniqueName(list)) + return &MethodSet{list} +} + +// A fieldSet is a set of fields and name collisions. +// A collision indicates that multiple fields with the +// same unique id appeared. +type fieldSet map[string]*Var // a nil entry indicates a name collision + +// Add adds field f to the field set s. +// If multiples is set, f appears multiple times +// and is treated as a collision. +func (s fieldSet) add(f *Var, multiples bool) fieldSet { + if s == nil { + s = make(fieldSet) + } + key := f.Id() + // if f is not in the set, add it + if !multiples { + if _, found := s[key]; !found { + s[key] = f + return s + } + } + s[key] = nil // collision + return s +} + +// A methodSet is a set of methods and name collisions. +// A collision indicates that multiple methods with the +// same unique 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. +// The receiver must exist. +func ptrRecv(f *Func) bool { + _, isPtr := deref(f.typ.(*Signature).recv.typ) + return isPtr +} + +// byUniqueName function lists can be sorted by their unique names. +type byUniqueName []*Selection + +func (a byUniqueName) Len() int { return len(a) } +func (a byUniqueName) Less(i, j int) bool { return a[i].obj.Id() < a[j].obj.Id() } +func (a byUniqueName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/src/gosubli.me/something-borrowed/types/methodsetcache.go b/src/gosubli.me/something-borrowed/types/methodsetcache.go new file mode 100644 index 00000000..5a482e95 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/methodsetcache.go @@ -0,0 +1,69 @@ +// 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. + +// This file implements a cache of method sets. + +package types + +import "sync" + +// A MethodSetCache records the method set of each type T for which +// MethodSet(T) is called so that repeat queries are fast. +// The zero value is a ready-to-use cache instance. +type MethodSetCache struct { + mu sync.Mutex + named map[*Named]struct{ value, pointer *MethodSet } // method sets for named N and *N + others map[Type]*MethodSet // all other types +} + +// MethodSet returns the method set of type T. It is thread-safe. +// +// If cache is nil, this function is equivalent to NewMethodSet(T). +// Utility functions can thus expose an optional *MethodSetCache +// parameter to clients that care about performance. +// +func (cache *MethodSetCache) MethodSet(T Type) *MethodSet { + if cache == nil { + return NewMethodSet(T) + } + cache.mu.Lock() + defer cache.mu.Unlock() + + switch T := T.(type) { + case *Named: + return cache.lookupNamed(T).value + + case *Pointer: + if N, ok := T.Elem().(*Named); ok { + return cache.lookupNamed(N).pointer + } + } + + // all other types + // (The map uses pointer equivalence, not type identity.) + mset := cache.others[T] + if mset == nil { + mset = NewMethodSet(T) + if cache.others == nil { + cache.others = make(map[Type]*MethodSet) + } + cache.others[T] = mset + } + return mset +} + +func (cache *MethodSetCache) lookupNamed(named *Named) struct{ value, pointer *MethodSet } { + if cache.named == nil { + cache.named = make(map[*Named]struct{ value, pointer *MethodSet }) + } + // Avoid recomputing mset(*T) for each distinct Pointer + // instance whose underlying type is a named type. + msets, ok := cache.named[named] + if !ok { + msets.value = NewMethodSet(named) + msets.pointer = NewMethodSet(NewPointer(named)) + cache.named[named] = msets + } + return msets +} diff --git a/src/gosubli.me/something-borrowed/types/object.go b/src/gosubli.me/something-borrowed/types/object.go new file mode 100644 index 00000000..69b0d470 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/object.go @@ -0,0 +1,340 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" +) + +// TODO(gri) Document factory, accessor methods, and fields. General clean-up. + +// 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 + Pos() token.Pos // position of object identifier in declaration + Pkg() *Package // nil for objects in the Universe scope and labels + Name() string // package local object name + Type() Type // object type + Exported() bool // reports whether the name starts with a capital letter + Id() string // object id (see Id below) + + // 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 + + // setOrder sets the order number of the object. It must be > 0. + setOrder(uint32) + + // 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 +} + +// 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 ast.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 := "_" + // TODO(gri): shouldn't !ast.IsExported(name) => pkg != nil be an precondition? + // if pkg == nil { + // panic("nil package in lookup of unexported name") + // } + if pkg != nil { + path = pkg.path + if path == "" { + path = "_" + } + } + return path + "." + name +} + +// An object implements the common parts of an Object. +type object struct { + parent *Scope + pos token.Pos + pkg *Package + name string + typ Type + order_ uint32 +} + +func (obj *object) Parent() *Scope { return obj.parent } +func (obj *object) Pos() token.Pos { return obj.pos } +func (obj *object) Pkg() *Package { return obj.pkg } +func (obj *object) Name() string { return obj.name } +func (obj *object) Type() Type { return obj.typ } +func (obj *object) Exported() bool { return ast.IsExported(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) setOrder(order uint32) { assert(order > 0); obj.order_ = order } +func (obj *object) setParent(parent *Scope) { obj.parent = parent } + +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. +type PkgName struct { + object + imported *Package + used bool // set if the package was used +} + +func NewPkgName(pos token.Pos, pkg *Package, name string, imported *Package) *PkgName { + return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0}, 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 exact.Value + visited bool // for initialization cycle detection +} + +func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val exact.Value) *Const { + return &Const{object{nil, pos, pkg, name, typ, 0}, val, false} +} + +func (obj *Const) Val() exact.Value { return obj.val } + +// A TypeName represents a declared type. +type TypeName struct { + object +} + +func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName { + return &TypeName{object{nil, pos, pkg, name, typ, 0}} +} + +// A Variable represents a declared variable (including function parameters and results, and struct fields). +type Var struct { + object + anonymous bool // if set, the variable is an anonymous struct field, and name is the type name + visited bool // for initialization cycle detection + isField bool // var is struct field + used bool // set if the variable was used +} + +func NewVar(pos token.Pos, pkg *Package, name string, typ Type) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0}} +} + +func NewParam(pos token.Pos, pkg *Package, name string, typ Type) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0}, used: true} // parameters are always 'used' +} + +func NewField(pos token.Pos, pkg *Package, name string, typ Type, anonymous bool) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0}, anonymous: anonymous, isField: true} +} + +func (obj *Var) Anonymous() bool { return obj.anonymous } + +func (obj *Var) IsField() bool { return obj.isField } + +// 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 +} + +func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func { + // don't store a nil signature + var typ Type + if sig != nil { + typ = sig + } + return &Func{object{nil, pos, pkg, name, typ, 0}} +} + +// 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, nil, obj) + return buf.String() +} + +func (obj *Func) Scope() *Scope { + return obj.typ.(*Signature).scope +} + +// A Label represents a declared label. +type Label struct { + object + used bool // set if the label was used +} + +func NewLabel(pos token.Pos, pkg *Package, name string) *Label { + return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid]}, 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]}, id} +} + +// Nil represents the predeclared value nil. +type Nil struct { + object +} + +func writeObject(buf *bytes.Buffer, this *Package, obj Object) { + 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: + buf.WriteString("type") + typ = typ.Underlying() + + case *Var: + if obj.isField { + buf.WriteString("field") + } else { + buf.WriteString("var") + } + + case *Func: + buf.WriteString("func ") + writeFuncName(buf, this, obj) + if typ != nil { + WriteSignature(buf, this, typ.(*Signature)) + } + 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, package-qualify the name, + // except for intra-package references (this != nil). + if pkg := obj.Pkg(); pkg != nil && this != pkg && pkg.scope.Lookup(obj.Name()) == obj { + buf.WriteString(pkg.path) + buf.WriteByte('.') + } + buf.WriteString(obj.Name()) + if typ != nil { + buf.WriteByte(' ') + WriteType(buf, this, typ) + } +} + +// ObjectString returns the string form of obj. +// Object and type names are printed package-qualified +// only if they do not belong to this package. +// +func ObjectString(this *Package, obj Object) string { + var buf bytes.Buffer + writeObject(&buf, this, obj) + return buf.String() +} + +func (obj *PkgName) String() string { return ObjectString(nil, obj) } +func (obj *Const) String() string { return ObjectString(nil, obj) } +func (obj *TypeName) String() string { return ObjectString(nil, obj) } +func (obj *Var) String() string { return ObjectString(nil, obj) } +func (obj *Func) String() string { return ObjectString(nil, obj) } +func (obj *Label) String() string { return ObjectString(nil, obj) } +func (obj *Builtin) String() string { return ObjectString(nil, obj) } +func (obj *Nil) String() string { return ObjectString(nil, obj) } + +func writeFuncName(buf *bytes.Buffer, this *Package, f *Func) { + 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, this, recv.Type()) + } + buf.WriteByte(')') + buf.WriteByte('.') + } else if f.pkg != nil && f.pkg != this { + buf.WriteString(f.pkg.path) + buf.WriteByte('.') + } + } + buf.WriteString(f.name) +} diff --git a/src/gosubli.me/something-borrowed/types/objects.go b/src/gosubli.me/something-borrowed/types/objects.go deleted file mode 100644 index 02291d34..00000000 --- a/src/gosubli.me/something-borrowed/types/objects.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package types - -import ( - "go/ast" - "go/token" -) - -// 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 { - GetPkg() *Package - GetName() string - GetType() Type - GetPos() token.Pos - - anObject() -} - -// A Package represents the contents (objects) of a Go package. -type Package struct { - Name string - Path string // import path, "" for current (non-imported) package - Scope *Scope // package-level scope - Imports map[string]*Package // map of import paths to imported packages - Complete bool // if set, this package was imported completely - - spec *ast.ImportSpec -} - -// A Const represents a declared constant. -type Const struct { - Pkg *Package - Name string - Type Type - Val interface{} - - spec *ast.ValueSpec -} - -// A TypeName represents a declared type. -type TypeName struct { - Pkg *Package - Name string - Type Type // *NamedType or *Basic - - spec *ast.TypeSpec -} - -// A Variable represents a declared variable (including function parameters and results). -type Var struct { - Pkg *Package // nil for parameters - Name string - Type Type - - visited bool // for initialization cycle detection - decl interface{} -} - -// A Func represents a declared function. -type Func struct { - Pkg *Package - Name string - Type Type // *Signature or *Builtin - - decl *ast.FuncDecl -} - -func (obj *Package) GetPkg() *Package { return obj } -func (obj *Const) GetPkg() *Package { return obj.Pkg } -func (obj *TypeName) GetPkg() *Package { return obj.Pkg } -func (obj *Var) GetPkg() *Package { return obj.Pkg } -func (obj *Func) GetPkg() *Package { return obj.Pkg } - -func (obj *Package) GetName() string { return obj.Name } -func (obj *Const) GetName() string { return obj.Name } -func (obj *TypeName) GetName() string { return obj.Name } -func (obj *Var) GetName() string { return obj.Name } -func (obj *Func) GetName() string { return obj.Name } - -func (obj *Package) GetType() Type { return Typ[Invalid] } -func (obj *Const) GetType() Type { return obj.Type } -func (obj *TypeName) GetType() Type { return obj.Type } -func (obj *Var) GetType() Type { return obj.Type } -func (obj *Func) GetType() Type { return obj.Type } - -func (obj *Package) GetPos() token.Pos { - if obj.spec != nil { - return obj.spec.Pos() - } - return token.NoPos -} - -func (obj *Const) GetPos() token.Pos { - for _, n := range obj.spec.Names { - if n.Name == obj.Name { - return n.Pos() - } - } - return token.NoPos -} -func (obj *TypeName) GetPos() token.Pos { - if obj.spec != nil { - return obj.spec.Pos() - } - return token.NoPos -} - -func (obj *Var) GetPos() token.Pos { - switch d := obj.decl.(type) { - case *ast.Field: - for _, n := range d.Names { - if n.Name == obj.Name { - return n.Pos() - } - } - case *ast.ValueSpec: - for _, n := range d.Names { - if n.Name == obj.Name { - return n.Pos() - } - } - case *ast.AssignStmt: - for _, x := range d.Lhs { - if ident, isIdent := x.(*ast.Ident); isIdent && ident.Name == obj.Name { - return ident.Pos() - } - } - } - return token.NoPos -} -func (obj *Func) GetPos() token.Pos { - if obj.decl != nil && obj.decl.Name != nil { - return obj.decl.Name.Pos() - } - return token.NoPos -} - -func (*Package) anObject() {} -func (*Const) anObject() {} -func (*TypeName) anObject() {} -func (*Var) anObject() {} -func (*Func) anObject() {} - -// newObj returns a new Object for a given *ast.Object. -// It does not canonicalize them (it always returns a new one). -// For canonicalization, see check.lookup. -// -// TODO(gri) Once we do identifier resolution completely in -// in the typechecker, this functionality can go. -// -func newObj(pkg *Package, astObj *ast.Object) Object { - assert(pkg != nil) - name := astObj.Name - typ, _ := astObj.Type.(Type) - switch astObj.Kind { - case ast.Bad: - // ignore - case ast.Pkg: - unreachable() - case ast.Con: - return &Const{Pkg: pkg, Name: name, Type: typ, Val: astObj.Data, spec: astObj.Decl.(*ast.ValueSpec)} - case ast.Typ: - return &TypeName{Pkg: pkg, Name: name, Type: typ, spec: astObj.Decl.(*ast.TypeSpec)} - case ast.Var: - switch astObj.Decl.(type) { - case *ast.Field: // function parameters - case *ast.ValueSpec: // proper variable declarations - case *ast.AssignStmt: // short variable declarations - default: - unreachable() // everything else is not ok - } - return &Var{Pkg: pkg, Name: name, Type: typ, decl: astObj.Decl} - case ast.Fun: - return &Func{Pkg: pkg, Name: name, Type: typ, decl: astObj.Decl.(*ast.FuncDecl)} - case ast.Lbl: - unreachable() // for now - } - unreachable() - return nil -} diff --git a/src/gosubli.me/something-borrowed/types/objset.go b/src/gosubli.me/something-borrowed/types/objset.go new file mode 100644 index 00000000..55eb74ad --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/objset.go @@ -0,0 +1,31 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements objsets. +// +// An objset is similar to a Scope but objset elements +// are identified by their unique id, instead of their +// object name. + +package types + +// An objset is a set of objects identified by their unique id. +// The zero value for objset is a ready-to-use empty objset. +type objset map[string]Object // initialized lazily + +// insert attempts to insert an object obj into objset s. +// If s already contains an alternative object alt with +// the same name, insert leaves s unchanged and returns alt. +// Otherwise it inserts obj and returns nil. +func (s *objset) insert(obj Object) Object { + id := obj.Id() + if alt := (*s)[id]; alt != nil { + return alt + } + if *s == nil { + *s = make(map[string]Object) + } + (*s)[id] = obj + return nil +} diff --git a/src/gosubli.me/something-borrowed/types/operand.go b/src/gosubli.me/something-borrowed/types/operand.go index 982ffef8..e29687fb 100644 --- a/src/gosubli.me/something-borrowed/types/operand.go +++ b/src/gosubli.me/something-borrowed/types/operand.go @@ -8,43 +8,51 @@ package types import ( "bytes" - "fmt" "go/ast" "go/token" + + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" ) // An operandMode specifies the (addressing) mode of an operand. -type operandMode int +type operandMode byte const ( - invalid operandMode = iota // operand is invalid (due to an earlier error) - ignore + 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 - valueok // like mode == value, but operand may be used in a comma,ok expression + commaok // like value, but operand may be used in a comma,ok expression ) var operandModeString = [...]string{ - invalid: "invalid", + invalid: "invalid operand", novalue: "no value", + builtin: "built-in", typexpr: "type", constant: "constant", variable: "variable", + mapindex: "map index expression", value: "value", - valueok: "value,ok", + commaok: "comma, ok expression", } // An operand represents an intermediate value during type checking. // Operands have an (addressing) mode, the expression evaluating to -// the operand, the operand's type, and for constants a constant value. +// the operand, the operand's type, a value for constants, and an id +// for built-in functions. +// The zero value of operand is a ready to use invalid operand. // type operand struct { mode operandMode expr ast.Expr typ Type - val interface{} + val exact.Value + id builtinId } // pos returns the position of the expression corresponding to x. @@ -58,78 +66,144 @@ func (x *operand) pos() token.Pos { return x.expr.Pos() } -func (x *operand) String() string { - if x.mode == invalid { - return "invalid operand" - } +// Operand string formats +// (not all "untyped" cases can appear due to the type system, +// but they fall out naturally here) +// +// mode format +// +// invalid ( ) +// novalue ( ) +// builtin ( ) +// typexpr ( ) +// +// constant ( ) +// constant ( of type ) +// constant ( ) +// constant ( of type ) +// +// variable ( ) +// variable ( of type ) +// +// mapindex ( ) +// mapindex ( of type ) +// +// value ( ) +// value ( of type ) +// +// commaok ( ) +// commaok ( of type ) +// +func operandString(this *Package, x *operand) string { var buf bytes.Buffer + + var expr string if x.expr != nil { - buf.WriteString(exprString(x.expr)) + expr = ExprString(x.expr) + } else { + switch x.mode { + case builtin: + expr = predeclaredFuncs[x.id].name + case typexpr: + expr = TypeString(this, x.typ) + case constant: + expr = x.val.String() + } + } + + // ( + if expr != "" { + buf.WriteString(expr) buf.WriteString(" (") } + + // + hasType := false + switch x.mode { + case invalid, novalue, builtin, typexpr: + // no type + default: + // has type + if isUntyped(x.typ) { + buf.WriteString(x.typ.(*Basic).name) + buf.WriteByte(' ') + break + } + hasType = true + } + + // buf.WriteString(operandModeString[x.mode]) + + // if x.mode == constant { - format := " %v" - if isString(x.typ) { - format = " %q" + if s := x.val.String(); s != expr { + buf.WriteByte(' ') + buf.WriteString(s) } - fmt.Fprintf(&buf, format, x.val) } - if x.mode != novalue && (x.mode != constant || !isUntyped(x.typ)) { - fmt.Fprintf(&buf, " of type %s", typeString(x.typ)) + + // + if hasType { + if x.typ != Typ[Invalid] { + buf.WriteString(" of type ") + WriteType(&buf, this, x.typ) + } else { + buf.WriteString(" with invalid type") + } } - if x.expr != nil { + + // ) + if expr != "" { buf.WriteByte(')') } + return buf.String() } +func (x *operand) String() string { + return operandString(nil, x) +} + // setConst sets x to the untyped constant for literal lit. func (x *operand) setConst(tok token.Token, lit string) { - x.mode = invalid + val := exact.MakeFromLiteral(lit, tok) + if val == nil { + // TODO(gri) Should we make it an unknown constant instead? + x.mode = invalid + return + } var kind BasicKind - var val interface{} switch tok { case token.INT: kind = UntypedInt - val = makeIntConst(lit) - case token.FLOAT: kind = UntypedFloat - val = makeFloatConst(lit) - case token.IMAG: kind = UntypedComplex - val = makeComplexConst(lit) - case token.CHAR: kind = UntypedRune - val = makeRuneConst(lit) - case token.STRING: kind = UntypedString - val = makeStringConst(lit) } - if val != nil { - x.mode = constant - x.typ = Typ[kind] - x.val = val - } + x.mode = constant + x.typ = Typ[kind] + x.val = val } -// isNil reports whether x is the predeclared nil constant. +// isNil reports whether x is the nil value. func (x *operand) isNil() bool { - return x.mode == constant && x.val == nilConst + return x.mode == value && x.typ == Typ[UntypedNil] } -// TODO(gri) The functions operand.isAssignable, checker.convertUntyped, -// checker.isRepresentable, and checker.assignOperand are +// TODO(gri) The functions operand.assignableTo, checker.convertUntyped, +// checker.representable, and checker.assignment are // overlapping in functionality. Need to simplify and clean up. -// isAssignable reports whether x is assignable to a variable of type T. -func (x *operand) isAssignable(ctxt *Context, T Type) bool { +// assignableTo reports whether x is assignable to a variable of type T. +func (x *operand) assignableTo(conf *Config, T Type) bool { if x.mode == invalid || T == Typ[Invalid] { return true // avoid spurious errors } @@ -137,31 +211,32 @@ func (x *operand) isAssignable(ctxt *Context, T Type) bool { V := x.typ // x's type is identical to T - if IsIdentical(V, T) { + if Identical(V, T) { return true } - Vu := underlying(V) - Tu := underlying(T) - - // x's type V and T have identical underlying types - // and at least one of V or T is not a named type - if IsIdentical(Vu, Tu) { - return !isNamed(V) || !isNamed(T) - } + Vu := V.Underlying() + Tu := T.Underlying() // T is an interface type and x implements T + // (Do this check first as it might succeed early.) if Ti, ok := Tu.(*Interface); ok { - if m, _ := missingMethod(x.typ, Ti); m == nil { + if Implements(x.typ, Ti) { return true } } + // x's type V and T have identical underlying types + // and at least one of V or T is not a named type + if Identical(Vu, Tu) && (!isNamed(V) || !isNamed(T)) { + 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 == ast.SEND|ast.RECV { - if Tc, ok := Tu.(*Chan); ok && IsIdentical(Vc.Elt, Tc.Elt) { + if Vc, ok := Vu.(*Chan); ok && Vc.dir == SendRecv { + if Tc, ok := Tu.(*Chan); ok && Identical(Vc.elem, Tc.elem) { return !isNamed(V) || !isNamed(T) } } @@ -171,7 +246,7 @@ func (x *operand) isAssignable(ctxt *Context, T Type) bool { if x.isNil() { switch t := Tu.(type) { case *Basic: - if t.Kind == UnsafePointer { + if t.kind == UnsafePointer { return true } case *Pointer, *Signature, *Slice, *Map, *Chan, *Interface: @@ -182,20 +257,20 @@ func (x *operand) isAssignable(ctxt *Context, T Type) bool { // x is an untyped constant representable by a value of type T // TODO(gri) This is borrowing from checker.convertUntyped and - // checker.isRepresentable. Need to clean up. + // checker.representable. Need to clean up. if isUntyped(Vu) { switch t := Tu.(type) { case *Basic: if x.mode == constant { - return isRepresentableConst(x.val, ctxt, t.Kind) + return representableConst(x.val, conf, t.kind, 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) + return Vb.kind == UntypedBool && isBoolean(Tu) } case *Interface: - return x.isNil() || len(t.Methods) == 0 + return x.isNil() || t.Empty() case *Pointer, *Signature, *Slice, *Map, *Chan: return x.isNil() } @@ -205,207 +280,8 @@ func (x *operand) isAssignable(ctxt *Context, T Type) bool { } // isInteger reports whether x is a (typed or untyped) integer value. -func (x *operand) isInteger(ctxt *Context) bool { +func (x *operand) isInteger() bool { return x.mode == invalid || isInteger(x.typ) || - x.mode == constant && isRepresentableConst(x.val, ctxt, UntypedInt) -} - -// lookupResult represents the result of a struct field/method lookup. -type lookupResult struct { - mode operandMode - typ Type - index []int // field index sequence; nil for methods -} - -type embeddedType struct { - typ *NamedType - index []int // field index sequence - multiples bool // if set, typ is embedded multiple times at the same level -} - -// lookupFieldBreadthFirst searches all types in list for a single entry (field -// or method) of the given name from the given package. If such a field is found, -// the result describes the field mode and type; otherwise the result mode is invalid. -// (This function is similar in structure to FieldByNameFunc in reflect/type.go) -// -func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res lookupResult) { - // visited records the types that have been searched already. - visited := make(map[*NamedType]bool) - - // embedded types of the next lower level - var next []embeddedType - - // potentialMatch is invoked every time a match is found. - potentialMatch := func(multiples bool, mode operandMode, typ Type) bool { - if multiples || res.mode != invalid { - // name appeared already at this level - annihilate - res.mode = invalid - return false - } - // first appearance of name - res.mode = mode - res.typ = typ - res.index = nil - return true - } - - // Search the current level if there is any work to do and collect - // embedded types of the next lower level in the next list. - for len(list) > 0 { - // The res.mode indicates whether we have found a match already - // on this level (mode != invalid), or not (mode == invalid). - assert(res.mode == invalid) - - // start with empty next list (don't waste underlying array) - next = next[:0] - - // look for name in all types at this level - for _, e := range list { - typ := e.typ - if visited[typ] { - continue - } - visited[typ] = true - - // look for a matching attached method - for _, m := range typ.Methods { - if name.IsSame(m.QualifiedName) { - assert(m.Type != nil) - if !potentialMatch(e.multiples, value, m.Type) { - return // name collision - } - } - } - - switch t := typ.Underlying.(type) { - case *Struct: - // look for a matching field and collect embedded types - for i, f := range t.Fields { - if name.IsSame(f.QualifiedName) { - assert(f.Type != nil) - if !potentialMatch(e.multiples, variable, f.Type) { - return // name collision - } - var index []int - index = append(index, e.index...) // copy e.index - index = append(index, i) - res.index = index - continue - } - // Collect embedded struct fields for searching the next - // lower level, 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 level; in either - // case we don't need to look further). - // Embedded fields are always of the form T or *T where - // T is a named type. If typ appeared multiple times at - // this level, f.Type appears multiple times at the next - // level. - if f.IsAnonymous && res.mode == invalid { - // Ignore embedded basic types - only user-defined - // named types can have methods or have struct fields. - if t, _ := deref(f.Type).(*NamedType); t != nil { - var index []int - index = append(index, e.index...) // copy e.index - index = append(index, i) - next = append(next, embeddedType{t, index, e.multiples}) - } - } - } - - case *Interface: - // look for a matching method - for _, m := range t.Methods { - if name.IsSame(m.QualifiedName) { - assert(m.Type != nil) - if !potentialMatch(e.multiples, value, m.Type) { - return // name collision - } - } - } - } - } - - if res.mode != invalid { - // we found a single match on this level - return - } - - // No match and no collision so far. - // Compute the list to search for the next level. - list = list[:0] // don't waste underlying array - for _, e := range next { - // Instead of adding the same type multiple times, look for - // it in the list and mark it as multiple if it was added - // before. - // We use a sequential search (instead of a map for next) - // because the lists tend to be small, can easily be reused, - // and explicit search appears to be faster in this case. - if alt := findType(list, e.typ); alt != nil { - alt.multiples = true - } else { - list = append(list, e) - } - } - - } - - return -} - -func findType(list []embeddedType, typ *NamedType) *embeddedType { - for i := range list { - if p := &list[i]; p.typ == typ { - return p - } - } - return nil -} - -func lookupField(typ Type, name QualifiedName) lookupResult { - typ = deref(typ) - - if t, ok := typ.(*NamedType); ok { - for _, m := range t.Methods { - if name.IsSame(m.QualifiedName) { - assert(m.Type != nil) - return lookupResult{value, m.Type, nil} - } - } - typ = t.Underlying - } - - switch t := typ.(type) { - case *Struct: - var next []embeddedType - for i, f := range t.Fields { - if name.IsSame(f.QualifiedName) { - return lookupResult{variable, f.Type, []int{i}} - } - if f.IsAnonymous { - // Possible optimization: If the embedded type - // is a pointer to the current type we could - // ignore it. - // Ignore embedded basic types - only user-defined - // named types can have methods or have struct fields. - if t, _ := deref(f.Type).(*NamedType); t != nil { - next = append(next, embeddedType{t, []int{i}, false}) - } - } - } - if len(next) > 0 { - return lookupFieldBreadthFirst(next, name) - } - - case *Interface: - for _, m := range t.Methods { - if name.IsSame(m.QualifiedName) { - return lookupResult{value, m.Type, nil} - } - } - } - - // not found - return lookupResult{mode: invalid} + x.mode == constant && representableConst(x.val, nil, UntypedInt, nil) // no *Config required for UntypedInt } diff --git a/src/gosubli.me/something-borrowed/types/ordering.go b/src/gosubli.me/something-borrowed/types/ordering.go new file mode 100644 index 00000000..6bb98f2d --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/ordering.go @@ -0,0 +1,127 @@ +// 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. + +// This file implements resolveOrder. + +package types + +import ( + "go/ast" + "sort" +) + +// resolveOrder computes the order in which package-level objects +// must be type-checked. +// +// Interface types appear first in the list, sorted topologically +// by dependencies on embedded interfaces that are also declared +// in this package, followed by all other objects sorted in source +// order. +// +// TODO(gri) Consider sorting all types by dependencies here, and +// in the process check _and_ report type cycles. This may simplify +// the full type-checking phase. +// +func (check *Checker) resolveOrder() []Object { + var ifaces, others []Object + + // collect interface types with their dependencies, and all other objects + for obj := range check.objMap { + if ityp := check.interfaceFor(obj); ityp != nil { + ifaces = append(ifaces, obj) + // determine dependencies on embedded interfaces + for _, f := range ityp.Methods.List { + if len(f.Names) == 0 { + // Embedded interface: The type must be a (possibly + // qualified) identifier denoting another interface. + // Imported interfaces are already fully resolved, + // so we can ignore qualified identifiers. + if ident, _ := f.Type.(*ast.Ident); ident != nil { + embedded := check.pkg.scope.Lookup(ident.Name) + if check.interfaceFor(embedded) != nil { + check.objMap[obj].addDep(embedded) + } + } + } + } + } else { + others = append(others, obj) + } + } + + // final object order + var order []Object + + // sort interface types topologically by dependencies, + // and in source order if there are no dependencies + sort.Sort(inSourceOrder(ifaces)) + if debug { + for _, obj := range ifaces { + assert(check.objMap[obj].mark == 0) + } + } + for _, obj := range ifaces { + check.appendInPostOrder(&order, obj) + } + + // sort everything else in source order + sort.Sort(inSourceOrder(others)) + + return append(order, others...) +} + +// interfaceFor returns the AST interface denoted by obj, or nil. +func (check *Checker) interfaceFor(obj Object) *ast.InterfaceType { + tname, _ := obj.(*TypeName) + if tname == nil { + return nil // not a type + } + d := check.objMap[obj] + if d == nil { + check.dump("%s: %s should have been declared", obj.Pos(), obj.Name()) + unreachable() + } + if d.typ == nil { + return nil // invalid AST - ignore (will be handled later) + } + ityp, _ := d.typ.(*ast.InterfaceType) + return ityp +} + +func (check *Checker) appendInPostOrder(order *[]Object, obj Object) { + d := check.objMap[obj] + if d.mark != 0 { + // We've already seen this object; either because it's + // already added to order, or because we have a cycle. + // In both cases we stop. Cycle errors are reported + // when type-checking types. + return + } + d.mark = 1 + + for _, obj := range orderedSetObjects(d.deps) { + check.appendInPostOrder(order, obj) + } + + *order = append(*order, obj) +} + +func orderedSetObjects(set map[Object]bool) []Object { + list := make([]Object, len(set)) + i := 0 + for obj := range set { + // we don't care about the map element value + list[i] = obj + i++ + } + sort.Sort(inSourceOrder(list)) + return list +} + +// 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] } diff --git a/src/gosubli.me/something-borrowed/types/package.go b/src/gosubli.me/something-borrowed/types/package.go new file mode 100644 index 00000000..366ca394 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/package.go @@ -0,0 +1,58 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types + +import "fmt" + +// 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) +} + +// NewPackage returns a new Package for the given package path and name; +// the name must not be the blank identifier. +// The package is not complete and contains no explicit imports. +func NewPackage(path, name string) *Package { + if name == "_" { + panic("invalid package name _") + } + scope := NewScope(Universe, 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 } + +// 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 explicitly imported by +// pkg; the list is in source order. Package unsafe is excluded. +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/gosubli.me/something-borrowed/types/predicates.go b/src/gosubli.me/something-borrowed/types/predicates.go index a99c91a4..2e36a729 100644 --- a/src/gosubli.me/something-borrowed/types/predicates.go +++ b/src/gosubli.me/something-borrowed/types/predicates.go @@ -6,94 +6,125 @@ package types +import "sort" + func isNamed(typ Type) bool { if _, ok := typ.(*Basic); ok { return ok } - _, ok := typ.(*NamedType) + _, ok := typ.(*Named) return ok } func isBoolean(typ Type) bool { - t, ok := underlying(typ).(*Basic) - return ok && t.Info&IsBoolean != 0 + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsBoolean != 0 } func isInteger(typ Type) bool { - t, ok := underlying(typ).(*Basic) - return ok && t.Info&IsInteger != 0 + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsInteger != 0 } func isUnsigned(typ Type) bool { - t, ok := underlying(typ).(*Basic) - return ok && t.Info&IsUnsigned != 0 + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsUnsigned != 0 } func isFloat(typ Type) bool { - t, ok := underlying(typ).(*Basic) - return ok && t.Info&IsFloat != 0 + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsFloat != 0 } func isComplex(typ Type) bool { - t, ok := underlying(typ).(*Basic) - return ok && t.Info&IsComplex != 0 + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsComplex != 0 } func isNumeric(typ Type) bool { - t, ok := underlying(typ).(*Basic) - return ok && t.Info&IsNumeric != 0 + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsNumeric != 0 } func isString(typ Type) bool { - t, ok := underlying(typ).(*Basic) - return ok && t.Info&IsString != 0 + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsString != 0 +} + +func isTyped(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return !ok || t.info&IsUntyped == 0 } func isUntyped(typ Type) bool { - t, ok := underlying(typ).(*Basic) - return ok && t.Info&IsUntyped != 0 + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsUntyped != 0 } func isOrdered(typ Type) bool { - t, ok := underlying(typ).(*Basic) - return ok && t.Info&IsOrdered != 0 + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsOrdered != 0 } func isConstType(typ Type) bool { - t, ok := underlying(typ).(*Basic) - return ok && t.Info&IsConstType != 0 + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsConstType != 0 +} + +func isInterface(typ Type) bool { + _, ok := typ.Underlying().(*Interface) + return ok } -func isComparable(typ Type) bool { - switch t := underlying(typ).(type) { +// Comparable reports whether values of type T are comparable. +func Comparable(T Type) bool { + switch t := T.Underlying().(type) { case *Basic: - return t.Kind != Invalid && t.Kind != UntypedNil + // assume invalid types to be comparable + // to avoid follow-up errors + return t.kind != UntypedNil case *Pointer, *Interface, *Chan: - // assumes types are equal for pointers and channels return true case *Struct: - for _, f := range t.Fields { - if !isComparable(f.Type) { + for _, f := range t.fields { + if !Comparable(f.typ) { return false } } return true case *Array: - return isComparable(t.Elt) + return Comparable(t.elem) } return false } +// hasNil reports whether a type includes the nil value. func hasNil(typ Type) bool { - switch underlying(typ).(type) { + switch t := typ.Underlying().(type) { + case *Basic: + return t.kind == UnsafePointer case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan: return true } return false } -// IsIdentical returns true if x and y are identical. -func IsIdentical(x, y Type) bool { +// Identical reports whether x and y are identical. +func Identical(x, y Type) bool { + return identical(x, y, nil) +} + +// An ifacePair is a node in a stack of interface type pairs compared for identity. +type ifacePair struct { + x, y *Interface + prev *ifacePair +} + +func (p *ifacePair) identical(q *ifacePair) bool { + return p.x == q.x && p.y == q.y || p.x == q.y && p.y == q.x +} + +func identical(x, y Type, p *ifacePair) bool { if x == y { return true } @@ -104,20 +135,20 @@ func IsIdentical(x, y Type) bool { // aliases, thus we cannot solely rely on the x == y check // above. if y, ok := y.(*Basic); ok { - return x.Kind == y.Kind + 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 { - return x.Len == y.Len && IsIdentical(x.Elt, y.Elt) + return x.len == y.len && identical(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 IsIdentical(x.Elt, y.Elt) + return identical(x.elem, y.elem, p) } case *Struct: @@ -126,13 +157,13 @@ func IsIdentical(x, y Type) bool { // and identical tags. Two anonymous 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 len(x.Fields) == len(y.Fields) { - for i, f := range x.Fields { - g := y.Fields[i] - if !f.QualifiedName.IsSame(g.QualifiedName) || - !IsIdentical(f.Type, g.Type) || - f.Tag != g.Tag || - f.IsAnonymous != g.IsAnonymous { + if x.NumFields() == y.NumFields() { + for i, f := range x.fields { + g := y.fields[i] + if f.anonymous != g.anonymous || + x.Tag(i) != y.Tag(i) || + !f.sameId(g.pkg, g.name) || + !identical(f.typ, g.typ, p) { return false } } @@ -143,7 +174,24 @@ func IsIdentical(x, y Type) bool { case *Pointer: // Two pointer types are identical if they have identical base types. if y, ok := y.(*Pointer); ok { - return IsIdentical(x.Base, y.Base) + return identical(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 !identical(v.typ, w.typ, p) { + return false + } + } + } + return true + } } case *Signature: @@ -152,9 +200,9 @@ func IsIdentical(x, y Type) bool { // and either both functions are variadic or neither is. Parameter and result // names are not required to match. if y, ok := y.(*Signature); ok { - return identicalTypes(x.Params, y.Params) && - identicalTypes(x.Results, y.Results) && - x.IsVariadic == y.IsVariadic + return x.variadic == y.variadic && + identical(x.params, y.params, p) && + identical(x.results, y.results, p) } case *Interface: @@ -162,142 +210,99 @@ func IsIdentical(x, y Type) bool { // 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 { - return identicalMethods(x.Methods, y.Methods) // methods are sorted + 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() || !identical(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 IsIdentical(x.Key, y.Key) && IsIdentical(x.Elt, y.Elt) + return identical(x.key, y.key, p) && identical(x.elem, y.elem, 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 && IsIdentical(x.Elt, y.Elt) + return x.dir == y.dir && identical(x.elem, y.elem, p) } - case *NamedType: + case *Named: // Two named types are identical if their type names originate // in the same type declaration. - if y, ok := y.(*NamedType); ok { - return x.Obj == y.Obj + if y, ok := y.(*Named); ok { + return x.obj == y.obj } - } - return false -} - -// identicalTypes returns true if both lists a and b have the -// same length and corresponding objects have identical types. -func identicalTypes(a, b []*Var) bool { - if len(a) != len(b) { - return false - } - for i, x := range a { - y := b[i] - if !IsIdentical(x.Type, y.Type) { - return false - } - } - return true -} - -// identicalMethods returns true if both lists a and b have the -// same length and corresponding methods have identical types. -// TODO(gri) make this more efficient -func identicalMethods(a, b []*Method) bool { - if len(a) != len(b) { - return false + default: + unreachable() } - m := make(map[QualifiedName]*Method) - for _, x := range a { - assert(m[x.QualifiedName] == nil) // method list must not have duplicate entries - m[x.QualifiedName] = x - } - for _, y := range b { - if x := m[y.QualifiedName]; x == nil || !IsIdentical(x.Type, y.Type) { - return false - } - } - return true -} - -// underlying returns the underlying type of typ. -func underlying(typ Type) Type { - // Basic types are representing themselves directly even though they are named. - if typ, ok := typ.(*NamedType); ok { - return typ.Underlying // underlying types are never NamedTypes - } - return typ -} -// deref returns a pointer's base type; otherwise it returns typ. -func deref(typ Type) Type { - if typ, ok := underlying(typ).(*Pointer); ok { - return typ.Base - } - return typ + return false } // defaultType returns the default "typed" type for an "untyped" type; -// it returns the incoming type for all other types. If there is no -// corresponding untyped type, the result is Typ[Invalid]. +// it returns the incoming type for all other types. The default type +// for untyped nil is untyped nil. // func defaultType(typ Type) Type { if t, ok := typ.(*Basic); ok { - k := Invalid - switch t.Kind { - // case UntypedNil: - // There is no default type for nil. For a good error message, - // catch this case before calling this function. + switch t.kind { case UntypedBool: - k = Bool + return Typ[Bool] case UntypedInt: - k = Int + return Typ[Int] case UntypedRune: - k = Rune + return UniverseRune // use 'rune' name case UntypedFloat: - k = Float64 + return Typ[Float64] case UntypedComplex: - k = Complex128 + return Typ[Complex128] case UntypedString: - k = String + return Typ[String] } - typ = Typ[k] } return typ } - -// missingMethod returns (nil, false) if typ implements T, otherwise -// it returns the first missing method required by T and whether it -// is missing or simply has the wrong type. -// -func missingMethod(typ Type, T *Interface) (method *Method, wrongType bool) { - // TODO(gri): this needs to correctly compare method names (taking package into account) - // TODO(gri): distinguish pointer and non-pointer receivers - // an interface type implements T if it has no methods with conflicting signatures - // Note: This is stronger than the current spec. Should the spec require this? - if ityp, _ := underlying(typ).(*Interface); ityp != nil { - for _, m := range T.Methods { - res := lookupField(ityp, m.QualifiedName) // TODO(gri) no need to go via lookupField - if res.mode != invalid && !IsIdentical(res.typ, m.Type) { - return m, true - } - } - return - } - - // a concrete type implements T if it implements all methods of T. - for _, m := range T.Methods { - res := lookupField(typ, m.QualifiedName) - if res.mode == invalid { - return m, false - } - if !IsIdentical(res.typ, m.Type) { - return m, true - } - } - return -} diff --git a/src/gosubli.me/something-borrowed/types/resolve.go b/src/gosubli.me/something-borrowed/types/resolve.go deleted file mode 100644 index 43db6070..00000000 --- a/src/gosubli.me/something-borrowed/types/resolve.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package types - -import ( - "fmt" - "go/ast" - "go/token" - "strconv" -) - -func (check *checker) declareObj(scope, altScope *Scope, obj Object, dotImport token.Pos) { - alt := scope.Insert(obj) - if alt == nil && altScope != nil { - // see if there is a conflicting declaration in altScope - alt = altScope.Lookup(obj.GetName()) - } - if alt != nil { - prevDecl := "" - - // for dot-imports, local declarations are declared first - swap messages - if dotImport.IsValid() { - if pos := alt.GetPos(); pos.IsValid() { - check.errorf(pos, fmt.Sprintf("%s redeclared in this block by dot-import at %s", - obj.GetName(), check.fset.Position(dotImport))) - return - } - - // get by w/o other position - check.errorf(dotImport, fmt.Sprintf("dot-import redeclares %s", obj.GetName())) - return - } - - if pos := alt.GetPos(); pos.IsValid() { - prevDecl = fmt.Sprintf("\n\tother declaration at %s", check.fset.Position(pos)) - } - check.errorf(obj.GetPos(), fmt.Sprintf("%s redeclared in this block%s", obj.GetName(), prevDecl)) - } -} - -func (check *checker) resolveIdent(scope *Scope, ident *ast.Ident) bool { - for ; scope != nil; scope = scope.Outer { - if obj := scope.Lookup(ident.Name); obj != nil { - check.register(ident, obj) - return true - } - } - return false -} - -func (check *checker) resolve(importer Importer) (methods []*ast.FuncDecl) { - pkg := &Package{Scope: &Scope{Outer: Universe}, Imports: make(map[string]*Package)} - check.pkg = pkg - - // complete package scope - i := 0 - for _, file := range check.files { - // package names must match - switch name := file.Name.Name; { - case pkg.Name == "": - pkg.Name = name - case name != pkg.Name: - check.errorf(file.Package, "package %s; expected %s", name, pkg.Name) - continue // ignore this file - } - - // keep this file - check.files[i] = file - i++ - - // the package identifier denotes the current package - check.register(file.Name, pkg) - - // insert top-level file objects in package scope - // (the parser took care of declaration errors) - for _, decl := range file.Decls { - switch d := decl.(type) { - case *ast.BadDecl: - // ignore - case *ast.GenDecl: - if d.Tok == token.CONST { - check.assocInitvals(d) - } - for _, spec := range d.Specs { - switch s := spec.(type) { - case *ast.ImportSpec: - // handled separately below - case *ast.ValueSpec: - for _, name := range s.Names { - if name.Name == "_" { - continue - } - pkg.Scope.Insert(check.lookup(name)) - } - case *ast.TypeSpec: - if s.Name.Name == "_" { - continue - } - pkg.Scope.Insert(check.lookup(s.Name)) - default: - check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s) - } - } - case *ast.FuncDecl: - if d.Recv != nil { - // collect method - methods = append(methods, d) - continue - } - if d.Name.Name == "_" || d.Name.Name == "init" { - continue // blank (_) and init functions are inaccessible - } - pkg.Scope.Insert(check.lookup(d.Name)) - default: - check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d) - } - } - } - check.files = check.files[0:i] - - // complete file scopes with imports and resolve identifiers - for _, file := range check.files { - // build file scope by processing all imports - importErrors := false - fileScope := &Scope{Outer: pkg.Scope} - for _, spec := range file.Imports { - if importer == nil { - importErrors = true - continue - } - path, _ := strconv.Unquote(spec.Path.Value) - imp, err := importer(pkg.Imports, path) - if err != nil { - check.errorf(spec.Path.Pos(), "could not import %s (%s)", path, err) - importErrors = true - continue - } - // TODO(gri) If a local package name != "." is provided, - // global identifier resolution could proceed even if the - // import failed. Consider adjusting the logic here a bit. - - // local name overrides imported package name - name := imp.Name - if spec.Name != nil { - name = spec.Name.Name - } - - // add import to file scope - if name == "." { - // merge imported scope with file scope - for _, obj := range imp.Scope.Entries { - // gcimported package scopes contain non-exported - // objects such as types used in partially exported - // objects - do not accept them - if ast.IsExported(obj.GetName()) { - check.declareObj(fileScope, pkg.Scope, obj, spec.Pos()) - } - } - // TODO(gri) consider registering the "." identifier - // if we have Context.Ident callbacks for say blank - // (_) identifiers - // check.register(spec.Name, pkg) - } else if name != "_" { - // declare imported package object in file scope - // (do not re-use imp in the file scope but create - // a new object instead; the Decl field is different - // for different files) - obj := &Package{Name: name, Scope: imp.Scope, spec: spec} - check.declareObj(fileScope, pkg.Scope, obj, token.NoPos) - } - } - - // resolve identifiers - if importErrors { - // don't use the universe scope without correct imports - // (objects in the universe may be shadowed by imports; - // with missing imports, identifiers might get resolved - // incorrectly to universe objects) - pkg.Scope.Outer = nil - } - i := 0 - for _, ident := range file.Unresolved { - if !check.resolveIdent(fileScope, ident) { - check.errorf(ident.Pos(), "undeclared name: %s", ident.Name) - file.Unresolved[i] = ident - i++ - } - - } - file.Unresolved = file.Unresolved[0:i] - pkg.Scope.Outer = Universe // reset outer scope (is nil if there were importErrors) - } - - return -} diff --git a/src/gosubli.me/something-borrowed/types/resolver.go b/src/gosubli.me/something-borrowed/types/resolver.go new file mode 100644 index 00000000..1b0fdb81 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/resolver.go @@ -0,0 +1,446 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types + +import ( + "errors" + "fmt" + "go/ast" + "go/token" + pathLib "path" + "strconv" + "strings" + "unicode" + + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" +) + +// A declInfo describes a package-level const, type, var, or func declaration. +type declInfo struct { + file *Scope // scope of file containing this declaration + lhs []*Var // lhs of n:1 variable declarations, or nil + typ ast.Expr // type, or nil + init ast.Expr // init expression, or nil + fdecl *ast.FuncDecl // func declaration, or nil + + deps map[Object]bool // type and init dependencies; lazily allocated + mark int // for dependency analysis +} + +// 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 as a dependency to d. +func (d *declInfo) addDep(obj Object) { + m := d.deps + if m == nil { + m = make(map[Object]bool) + d.deps = m + } + m[obj] = true +} + +// arityMatch checks that the lhs and rhs of a const or var decl +// have the appropriate number of names and init exprs. For const +// decls, init is the value spec providing the init exprs; for +// var decls, init is nil (the init exprs are in s in this case). +func (check *Checker) arityMatch(s, init *ast.ValueSpec) { + l := len(s.Names) + r := len(s.Values) + if init != nil { + r = len(init.Values) + } + + switch { + case init == nil && r == 0: + // var decl w/o init expr + if s.Type == nil { + check.errorf(s.Pos(), "missing type or init expr") + } + case l < r: + if l < len(s.Values) { + // init exprs from s + n := s.Values[l] + check.errorf(n.Pos(), "extra init expr %s", n) + // TODO(gri) avoid declared but not used error here + } else { + // init exprs "inherited" + check.errorf(s.Pos(), "extra init expr at %s", init.Pos()) + // TODO(gri) avoid declared but not used error here + } + case l > r && (init != nil || r != 1): + n := s.Names[r] + check.errorf(n.Pos(), "missing init expr for %s", n) + } +} + +func validatedImportPath(path string) (string, error) { + s, err := strconv.Unquote(path) + if err != nil { + return "", err + } + if s == "" { + return "", fmt.Errorf("empty string") + } + const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" + for _, r := range s { + if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { + return s, fmt.Errorf("invalid character %#U", r) + } + } + return s, nil +} + +// declarePkgObj declares obj in the package scope, records its ident -> obj mapping, +// and updates check.objMap. The object must not be a function or method. +func (check *Checker) declarePkgObj(ident *ast.Ident, obj Object, d *declInfo) { + assert(ident.Name == obj.Name()) + + // spec: "A package-scope or file-scope identifier with name init + // may only be declared to be a function with this (func()) signature." + if ident.Name == "init" { + check.errorf(ident.Pos(), "cannot declare init - must be func") + return + } + + check.declare(check.pkg.scope, ident, obj) + check.objMap[obj] = d + obj.setOrder(uint32(len(check.objMap))) +} + +// filename returns a filename suitable for debugging output. +func (check *Checker) filename(fileNo int) string { + file := check.files[fileNo] + if pos := file.Pos(); pos.IsValid() { + return check.fset.File(pos).Name() + } + return fmt.Sprintf("file[%d]", fileNo) +} + +// 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 + + importer := check.conf.Import + if importer == nil { + if DefaultImport != nil { + importer = DefaultImport + } else { + // Panic if we encounter an import. + importer = func(map[string]*Package, string) (*Package, error) { + panic(`no Config.Import or DefaultImport (missing import _ "golang.org/x/tools/go/gcimporter"?)`) + } + } + } + + // 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). + var pkgImports = make(map[*Package]bool) + for _, imp := range pkg.imports { + pkgImports[imp] = true + } + + for fileNo, file := range check.files { + // The package identifier denotes the current package, + // but there is no corresponding package object. + check.recordDef(file.Name, nil) + + fileScope := NewScope(check.pkg.scope, check.filename(fileNo)) + check.recordScope(file, fileScope) + + for _, decl := range file.Decls { + switch d := decl.(type) { + case *ast.BadDecl: + // ignore + + case *ast.GenDecl: + var last *ast.ValueSpec // last ValueSpec with type or init exprs seen + for iota, spec := range d.Specs { + switch s := spec.(type) { + case *ast.ImportSpec: + // import package + var imp *Package + path, err := validatedImportPath(s.Path.Value) + if err != nil { + check.errorf(s.Path.Pos(), "invalid import path (%s)", err) + continue + } + if path == "C" && check.conf.FakeImportC { + // TODO(gri) shouldn't create a new one each time + imp = NewPackage("C", "C") + imp.fake = true + } else { + var err error + imp, err = importer(check.conf.Packages, path) + if imp == nil && err == nil { + err = errors.New("Config.Import returned nil but no error") + } + if err != nil { + check.errorf(s.Path.Pos(), "could not import %s (%s)", path, err) + 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 + if imp != Unsafe { + pkg.imports = append(pkg.imports, imp) + } + } + + // local name overrides imported package name + name := imp.name + if s.Name != nil { + name = s.Name.Name + if name == "init" { + check.errorf(s.Name.Pos(), "cannot declare init - must be func") + continue + } + } + + obj := NewPkgName(s.Pos(), pkg, name, imp) + if s.Name != nil { + // in a dot-import, the dot represents the package + check.recordDef(s.Name, obj) + } else { + check.recordImplicit(s, obj) + } + + // 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() { + // TODO(gri) When we import a package, we create + // a new local package object. We should do the + // same for each dot-imported object. That way + // they can have correct position information. + // (We must not modify their existing position + // information because the same package - found + // via Config.Packages - may be dot-imported in + // another package!) + check.declare(fileScope, nil, obj) + check.recordImplicit(s, obj) + } + } + // 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 + check.declare(fileScope, nil, obj) + } + + case *ast.ValueSpec: + switch d.Tok { + case token.CONST: + // determine which initialization expressions to use + switch { + case s.Type != nil || len(s.Values) > 0: + last = s + case last == nil: + last = new(ast.ValueSpec) // make sure last exists + } + + // declare all constants + for i, name := range s.Names { + obj := NewConst(name.Pos(), pkg, name.Name, nil, exact.MakeInt64(int64(iota))) + + var init ast.Expr + if i < len(last.Values) { + init = last.Values[i] + } + + d := &declInfo{file: fileScope, typ: last.Type, init: init} + check.declarePkgObj(name, obj, d) + } + + check.arityMatch(s, last) + + case token.VAR: + lhs := make([]*Var, len(s.Names)) + // If there's exactly one rhs initializer, use + // the same declInfo d1 for all lhs variables + // so that each lhs variable depends on the same + // rhs initializer (n:1 var declaration). + var d1 *declInfo + if len(s.Values) == 1 { + // The lhs elements are only set up after the for loop below, + // but that's ok because declareVar only collects the declInfo + // for a later phase. + d1 = &declInfo{file: fileScope, lhs: lhs, typ: s.Type, init: s.Values[0]} + } + + // declare all variables + for i, name := range s.Names { + obj := NewVar(name.Pos(), pkg, name.Name, nil) + lhs[i] = obj + + d := d1 + if d == nil { + // individual assignments + var init ast.Expr + if i < len(s.Values) { + init = s.Values[i] + } + d = &declInfo{file: fileScope, typ: s.Type, init: init} + } + + check.declarePkgObj(name, obj, d) + } + + check.arityMatch(s, nil) + + default: + check.invalidAST(s.Pos(), "invalid token %s", d.Tok) + } + + case *ast.TypeSpec: + obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil) + check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, typ: s.Type}) + + default: + check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s) + } + } + + case *ast.FuncDecl: + name := d.Name.Name + obj := NewFunc(d.Name.Pos(), pkg, name, nil) + if d.Recv == nil { + // regular function + if name == "init" { + // don't declare init functions in the package scope - they are invisible + obj.parent = pkg.scope + check.recordDef(d.Name, obj) + // init functions must have a body + if d.Body == nil { + check.softErrorf(obj.pos, "missing function body") + } + } else { + check.declare(pkg.scope, d.Name, obj) + } + } else { + // method + check.recordDef(d.Name, obj) + // Associate method with receiver base type name, if possible. + // Ignore methods that have an invalid receiver, or a blank _ + // receiver name. They will be type-checked later, with regular + // functions. + if list := d.Recv.List; len(list) > 0 { + typ := list[0].Type + if ptr, _ := typ.(*ast.StarExpr); ptr != nil { + typ = ptr.X + } + if base, _ := typ.(*ast.Ident); base != nil && base.Name != "_" { + check.assocMethod(base.Name, obj) + } + } + } + info := &declInfo{file: fileScope, fdecl: d} + check.objMap[obj] = info + obj.setOrder(uint32(len(check.objMap))) + + default: + check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d) + } + } + } + + // verify that objects in package and file scopes have different names + for _, scope := range check.pkg.scope.children /* file scopes */ { + for _, obj := range scope.elems { + if alt := pkg.scope.Lookup(obj.Name()); alt != nil { + if pkg, ok := obj.(*PkgName); ok { + check.errorf(alt.Pos(), "%s already declared through import of %s", alt.Name(), pkg.Imported()) + check.reportAltDecl(pkg) + } else { + check.errorf(alt.Pos(), "%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) + } + } + } + } +} + +// packageObjects typechecks all package objects in objList, but not function bodies. +func (check *Checker) packageObjects(objList []Object) { + // add new methods to already type-checked types (from a prior Checker.Files call) + for _, obj := range objList { + if obj, _ := obj.(*TypeName); obj != nil && obj.typ != nil { + check.addMethodDecls(obj) + } + } + + // pre-allocate space for type declaration paths so that the underlying array is reused + typePath := make([]*TypeName, 0, 8) + + for _, obj := range objList { + check.objDecl(obj, nil, typePath) + } + + // 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 +} + +// functionBodies typechecks all function bodies. +func (check *Checker) functionBodies() { + for _, f := range check.funcs { + check.funcBody(f.decl, f.name, f.sig, f.body) + } +} + +// 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 := pathLib.Base(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) + } + } +} diff --git a/src/gosubli.me/something-borrowed/types/resolver_test.go b/src/gosubli.me/something-borrowed/types/resolver_test.go index d4e36445..d8af7a01 100644 --- a/src/gosubli.me/something-borrowed/types/resolver_test.go +++ b/src/gosubli.me/something-borrowed/types/resolver_test.go @@ -2,13 +2,18 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package types +package types_test import ( + "fmt" "go/ast" "go/parser" "go/token" + "sort" "testing" + + _ "golang.org/x/tools/go/gcimporter" + . "golang.org/x/tools/go/types" ) var sources = []string{ @@ -25,6 +30,7 @@ var sources = []string{ ` package p import "fmt" + type errorStringer struct { fmt.Stringer; error } func f() string { _ = "foo" return fmt.Sprintf("%d", g()) @@ -35,13 +41,45 @@ var sources = []string{ package p import . "go/parser" import "sync" - func g() Mode { return ImportsOnly } + func h() Mode { return ImportsOnly } var _, x int = 1, 2 func init() {} - type T struct{ sync.Mutex; a, b, c int} + 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: + } `, } @@ -50,12 +88,12 @@ var pkgnames = []string{ "math", } -func TestResolveQualifiedIdents(t *testing.T) { +func TestResolveIdents(t *testing.T) { // parse package files fset := token.NewFileSet() var files []*ast.File - for _, src := range sources { - f, err := parser.ParseFile(fset, "", src, parser.DeclarationErrors) + for i, src := range sources { + f, err := parser.ParseFile(fset, fmt.Sprintf("sources[%d]", i), src, parser.DeclarationErrors) if err != nil { t.Fatal(err) } @@ -63,39 +101,32 @@ func TestResolveQualifiedIdents(t *testing.T) { } // resolve and type-check package AST - idents := make(map[*ast.Ident]Object) - var ctxt Context - ctxt.Ident = func(id *ast.Ident, obj Object) { idents[id] = obj } - pkg, err := ctxt.Check(fset, files) + var conf Config + uses := make(map[*ast.Ident]Object) + defs := make(map[*ast.Ident]Object) + _, err := conf.Check("testResolveIdents", fset, files, &Info{Defs: defs, Uses: uses}) if err != nil { t.Fatal(err) } // check that all packages were imported for _, name := range pkgnames { - if pkg.Imports[name] == nil { + if conf.Packages[name] == nil { t.Errorf("package %s not imported", name) } } - // check that there are no top-level unresolved identifiers - for _, f := range files { - for _, x := range f.Unresolved { - t.Errorf("%s: unresolved global identifier %s", fset.Position(x.Pos()), x.Name) - } - } - // check that qualified identifiers are resolved for _, f := range files { ast.Inspect(f, func(n ast.Node) bool { if s, ok := n.(*ast.SelectorExpr); ok { if x, ok := s.X.(*ast.Ident); ok { - obj := idents[x] + obj := uses[x] if obj == nil { t.Errorf("%s: unresolved qualified identifier %s", fset.Position(x.Pos()), x.Name) return false } - if _, ok := obj.(*Package); ok && idents[s.Sel] == nil { + if _, ok := obj.(*PkgName); ok && uses[s.Sel] == nil { t.Errorf("%s: unresolved selector %s", fset.Position(s.Sel.Pos()), s.Sel.Name) return false } @@ -107,48 +138,30 @@ func TestResolveQualifiedIdents(t *testing.T) { }) } - // Currently, the Check API doesn't call Ident for fields, methods, and composite literal keys. - // Introduce them artifically so that we can run the check below. - for _, f := range files { - ast.Inspect(f, func(n ast.Node) bool { - switch x := n.(type) { - case *ast.StructType: - for _, list := range x.Fields.List { - for _, f := range list.Names { - assert(idents[f] == nil) - idents[f] = &Var{Pkg: pkg, Name: f.Name} - } - } - case *ast.InterfaceType: - for _, list := range x.Methods.List { - for _, f := range list.Names { - assert(idents[f] == nil) - idents[f] = &Func{Pkg: pkg, Name: f.Name} - } - } - case *ast.CompositeLit: - for _, e := range x.Elts { - if kv, ok := e.(*ast.KeyValueExpr); ok { - if k, ok := kv.Key.(*ast.Ident); ok { - assert(idents[k] == nil) - idents[k] = &Var{Pkg: pkg, Name: k.Name} - } - } - } - } - return true - }) + for id, obj := range uses { + if obj == nil { + t.Errorf("%s: Uses[%s] == nil", fset.Position(id.Pos()), id.Name) + } } - // check that each identifier in the source is enumerated by the Context.Ident callback + // check that each identifier in the source is found in uses or defs or both + var both []string for _, f := range files { ast.Inspect(f, func(n ast.Node) bool { - if x, ok := n.(*ast.Ident); ok && x.Name != "_" && x.Name != "." { - obj := idents[x] - if obj == nil { + if x, ok := n.(*ast.Ident); ok { + var objects int + if _, found := uses[x]; found { + objects |= 1 + delete(uses, x) + } + if _, found := defs[x]; found { + objects |= 2 + delete(defs, x) + } + if objects == 0 { t.Errorf("%s: unresolved identifier %s", fset.Position(x.Pos()), x.Name) - } else { - delete(idents, x) + } else if objects == 3 { + both = append(both, x.Name) } return false } @@ -156,12 +169,19 @@ func TestResolveQualifiedIdents(t *testing.T) { }) } - // TODO(gri) enable code below - // At the moment, the type checker introduces artifical identifiers which are not - // present in the source. Once it doesn't do that anymore, enable the checks below. - /* - for x := range idents { - t.Errorf("%s: identifier %s not present in source", fset.Position(x.Pos()), x.Name) - } - */ + // check the expected set of idents that are simultaneously uses and defs + sort.Strings(both) + if got, want := fmt.Sprint(both), "[Mutex Stringer error]"; got != want { + t.Errorf("simultaneous uses/defs = %s, want %s", got, want) + } + + // any left-over identifiers didn't exist in the source + for x := range uses { + t.Errorf("%s: identifier %s not present in source", fset.Position(x.Pos()), x.Name) + } + for x := range defs { + t.Errorf("%s: identifier %s not present in source", fset.Position(x.Pos()), x.Name) + } + + // TODO(gri) add tests to check ImplicitObj callbacks } diff --git a/src/gosubli.me/something-borrowed/types/return.go b/src/gosubli.me/something-borrowed/types/return.go new file mode 100644 index 00000000..df5a482a --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/return.go @@ -0,0 +1,185 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements isTerminating. + +package types + +import ( + "go/ast" + "go/token" +) + +// isTerminating reports if s is a terminating statement. +// If s is labeled, label is the label name; otherwise s +// is "". +func (check *Checker) isTerminating(s ast.Stmt, label string) bool { + switch s := s.(type) { + default: + unreachable() + + case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.SendStmt, + *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, *ast.DeferStmt, + *ast.RangeStmt: + // no chance + + case *ast.LabeledStmt: + return check.isTerminating(s.Stmt, s.Label.Name) + + case *ast.ExprStmt: + // the predeclared (possibly parenthesized) panic() function is terminating + if call, _ := unparen(s.X).(*ast.CallExpr); call != nil { + if id, _ := call.Fun.(*ast.Ident); id != nil { + if _, obj := check.scope.LookupParent(id.Name); obj != nil { + if b, _ := obj.(*Builtin); b != nil && b.id == _Panic { + return true + } + } + } + } + + case *ast.ReturnStmt: + return true + + case *ast.BranchStmt: + if s.Tok == token.GOTO || s.Tok == token.FALLTHROUGH { + return true + } + + case *ast.BlockStmt: + return check.isTerminatingList(s.List, "") + + case *ast.IfStmt: + if s.Else != nil && + check.isTerminating(s.Body, "") && + check.isTerminating(s.Else, "") { + return true + } + + case *ast.SwitchStmt: + return check.isTerminatingSwitch(s.Body, label) + + case *ast.TypeSwitchStmt: + return check.isTerminatingSwitch(s.Body, label) + + case *ast.SelectStmt: + for _, s := range s.Body.List { + cc := s.(*ast.CommClause) + if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { + return false + } + + } + return true + + case *ast.ForStmt: + if s.Cond == nil && !hasBreak(s.Body, label, true) { + return true + } + } + + return false +} + +func (check *Checker) isTerminatingList(list []ast.Stmt, label string) bool { + n := len(list) + return n > 0 && check.isTerminating(list[n-1], label) +} + +func (check *Checker) isTerminatingSwitch(body *ast.BlockStmt, label string) bool { + hasDefault := false + for _, s := range body.List { + cc := s.(*ast.CaseClause) + if cc.List == nil { + hasDefault = true + } + if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { + return false + } + } + return hasDefault +} + +// TODO(gri) For nested breakable statements, the current implementation of hasBreak +// will traverse the same subtree repeatedly, once for each label. Replace +// with a single-pass label/break matching phase. + +// hasBreak reports if s is or contains a break statement +// referring to the label-ed statement or implicit-ly the +// closest outer breakable statement. +func hasBreak(s ast.Stmt, label string, implicit bool) bool { + switch s := s.(type) { + default: + unreachable() + + case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.ExprStmt, + *ast.SendStmt, *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, + *ast.DeferStmt, *ast.ReturnStmt: + // no chance + + case *ast.LabeledStmt: + return hasBreak(s.Stmt, label, implicit) + + case *ast.BranchStmt: + if s.Tok == token.BREAK { + if s.Label == nil { + return implicit + } + if s.Label.Name == label { + return true + } + } + + case *ast.BlockStmt: + return hasBreakList(s.List, label, implicit) + + case *ast.IfStmt: + if hasBreak(s.Body, label, implicit) || + s.Else != nil && hasBreak(s.Else, label, implicit) { + return true + } + + case *ast.CaseClause: + return hasBreakList(s.Body, label, implicit) + + case *ast.SwitchStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.TypeSwitchStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.CommClause: + return hasBreakList(s.Body, label, implicit) + + case *ast.SelectStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.ForStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.RangeStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + } + + return false +} + +func hasBreakList(list []ast.Stmt, label string, implicit bool) bool { + for _, s := range list { + if hasBreak(s, label, implicit) { + return true + } + } + return false +} diff --git a/src/gosubli.me/something-borrowed/types/scope.go b/src/gosubli.me/something-borrowed/types/scope.go index 463ee40c..8ab0f64f 100644 --- a/src/gosubli.me/something-borrowed/types/scope.go +++ b/src/gosubli.me/something-borrowed/types/scope.go @@ -2,77 +2,144 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// This file implements Scopes. + package types import ( "bytes" "fmt" + "io" + "sort" + "strings" ) -// A Scope maintains the set of named language entities declared -// in the scope and a link to the immediately surrounding (outer) -// scope. -// +// TODO(gri) Provide scopes with a name or other mechanism so that +// objects can use that information for better printing. + +// 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 { - Outer *Scope - Entries []Object // scope entries in insertion order - large map[string]Object // for fast lookup - only used for larger scopes + parent *Scope + children []*Scope + comment string // for debugging only + elems map[string]Object // lazily allocated } -// Lookup returns the object with the given name if it is -// found in scope s, otherwise it returns nil. Outer scopes -// are ignored. -// -func (s *Scope) Lookup(name string) Object { - if s.large != nil { - return s.large[name] +// NewScope returns a new, empty scope contained in the given parent +// scope, if any. The comment is for debugging only. +func NewScope(parent *Scope, comment string) *Scope { + s := &Scope{parent: parent, comment: comment} + // don't add children to Universe scope! + if parent != nil && parent != Universe { + parent.children = append(parent.children, s) } - for _, obj := range s.Entries { - if obj.GetName() == name { - return obj + 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 no such scope 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, below). 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) (*Scope, Object) { + for ; s != nil; s = s.parent { + if obj := s.elems[name]; obj != nil { + return s, obj } } - return nil + return nil, nil } // Insert attempts to insert an object obj into scope s. -// If s already contains an object with the same name, -// Insert leaves s unchanged and returns that object. -// Otherwise it inserts obj and returns nil. -// +// 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.GetName() - if alt := s.Lookup(name); alt != nil { + name := obj.Name() + if alt := s.elems[name]; alt != nil { return alt } - s.Entries = append(s.Entries, obj) - - // If the scope size reaches a threshold, use a map for faster lookups. - const threshold = 20 - if len(s.Entries) > threshold { - if s.large == nil { - m := make(map[string]Object, len(s.Entries)) - for _, obj := range s.Entries { - m[obj.GetName()] = obj - } - s.large = m + if s.elems == nil { + s.elems = make(map[string]Object) + } + s.elems[name] = obj + if obj.Parent() == nil { + obj.setParent(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 {", indn, s.comment, s) + if len(s.elems) == 0 { + fmt.Fprintf(w, "}\n") + return + } + + fmt.Fprintln(w) + 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 { + fmt.Fprintln(w) + s.WriteTo(w, n+1, recurse) } - s.large[name] = obj } - return nil + fmt.Fprintf(w, "%s}", indn) } -// Debugging support +// String returns a string representation of the scope, for debugging. func (s *Scope) String() string { var buf bytes.Buffer - fmt.Fprintf(&buf, "scope %p {", s) - if s != nil && len(s.Entries) > 0 { - fmt.Fprintln(&buf) - for _, obj := range s.Entries { - fmt.Fprintf(&buf, "\t%s\t%T\n", obj.GetName(), obj) - } - } - fmt.Fprintf(&buf, "}\n") + s.WriteTo(&buf, 0, false) return buf.String() } diff --git a/src/gosubli.me/something-borrowed/types/selection.go b/src/gosubli.me/something-borrowed/types/selection.go new file mode 100644 index 00000000..1c701655 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/selection.go @@ -0,0 +1,143 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements Selections. + +package types + +import ( + "bytes" + "fmt" +) + +// SelectionKind describes the kind of a selector expression x.f +// (excluding qualified identifiers). +type SelectionKind int + +const ( + FieldVal SelectionKind = iota // x.f is a struct field selector + MethodVal // x.f is a method selector + MethodExpr // x.f is a method expression +) + +// A Selection describes a selector expression x.f. +// For the declarations: +// +// type T struct{ x int; E } +// type E struct{} +// func (e E) m() {} +// var p *T +// +// the following relations exist: +// +// Selector Kind Recv Obj Type Index Indirect +// +// p.x FieldVal T x int {0} true +// p.m MethodVal *T m func (e *T) m() {1, 0} true +// T.m MethodExpr T m func m(_ 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(nil, s) } + +// SelectionString returns the string form of s. +// Type names are printed package-qualified +// only if they do not belong to this package. +// +// Examples: +// "field (T) f int" +// "method (T) f(X) Y" +// "method expr (T) f(X) Y" +// +func SelectionString(this *Package, s *Selection) 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, this, s.Recv()) + fmt.Fprintf(&buf, ") %s", s.obj.Name()) + if T := s.Type(); s.kind == FieldVal { + buf.WriteByte(' ') + WriteType(&buf, this, T) + } else { + WriteSignature(&buf, this, T.(*Signature)) + } + return buf.String() +} diff --git a/src/gosubli.me/something-borrowed/types/self_test.go b/src/gosubli.me/something-borrowed/types/self_test.go new file mode 100644 index 00000000..01d12c71 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/self_test.go @@ -0,0 +1,101 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types_test + +import ( + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "path/filepath" + "testing" + "time" + + _ "golang.org/x/tools/go/gcimporter" + . "golang.org/x/tools/go/types" +) + +var benchmark = flag.Bool("b", false, "run benchmarks") + +func TestSelf(t *testing.T) { + fset := token.NewFileSet() + files, err := pkgFiles(fset, ".") + if err != nil { + t.Fatal(err) + } + + _, err = Check("go/types", fset, files) + if err != nil { + // Importing go.tools/go/exact doensn'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", "exact", "gcimporter"} { + path := filepath.Join("..", p) + runbench(t, path, false) + runbench(t, path, true) + fmt.Println() + } +} + +func runbench(t *testing.T, path string, ignoreFuncBodies bool) { + fset := token.NewFileSet() + files, err := pkgFiles(fset, 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, fset, files, nil) + } + }) + + // determine line count + lines := 0 + fset.Iterate(func(f *token.File) bool { + lines += f.LineCount() + return true + }) + + 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(fset *token.FileSet, path string) ([]*ast.File, error) { + filenames, err := pkgFilenames(path) // from stdlib_test.go + if err != nil { + return nil, err + } + + var files []*ast.File + for _, filename := range filenames { + file, err := parser.ParseFile(fset, filename, nil, 0) + if err != nil { + return nil, err + } + files = append(files, file) + } + + return files, nil +} diff --git a/src/gosubli.me/something-borrowed/types/sizes.go b/src/gosubli.me/something-borrowed/types/sizes.go index ef6499ba..56fb310c 100644 --- a/src/gosubli.me/something-borrowed/types/sizes.go +++ b/src/gosubli.me/something-borrowed/types/sizes.go @@ -2,161 +2,210 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file implements support for (unsafe) Alignof, Offsetof, and Sizeof. +// This file implements Sizes. package types -func (ctxt *Context) alignof(typ Type) int64 { - if f := ctxt.Alignof; f != nil { - if a := f(typ); a >= 1 { - return a - } - panic("Context.Alignof returned an alignment < 1") - } - return DefaultAlignof(typ) -} +// 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 -func (ctxt *Context) offsetsof(s *Struct) []int64 { - offsets := s.offsets - if offsets == nil { - // compute offsets on demand - if f := ctxt.Offsetsof; f != nil { - offsets = f(s.Fields) - // sanity checks - if len(offsets) != len(s.Fields) { - panic("Context.Offsetsof returned the wrong number of offsets") - } - for _, o := range offsets { - if o < 0 { - panic("Context.Offsetsof returned an offset < 0") - } - } - } else { - offsets = DefaultOffsetsof(s.Fields) - } - s.offsets = offsets - } - return offsets -} + // 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 -// offsetof returns the offset of the field specified via -// the index sequence relative to typ. It returns a value -// < 0 if the field is in an embedded pointer type. -func (ctxt *Context) offsetof(typ Type, index []int) int64 { - var o int64 - for _, i := range index { - s, _ := underlying(typ).(*Struct) - if s == nil { - return -1 - } - o += ctxt.offsetsof(s)[i] - typ = s.Fields[i].Type - } - return o + // Sizeof returns the size of a variable of type T. + // Sizeof must implement the size guarantees required by the spec. + Sizeof(T Type) int64 } -func (ctxt *Context) sizeof(typ Type) int64 { - if f := ctxt.Sizeof; f != nil { - if s := f(typ); s >= 0 { - return s - } - panic("Context.Sizeof returned a size < 0") - } - return DefaultSizeof(typ) +// 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 } -// DefaultMaxAlign is the default maximum alignment, in bytes, -// used by DefaultAlignof. -const DefaultMaxAlign = 8 - -// DefaultAlignof implements the default alignment computation -// for unsafe.Alignof. It is used if Context.Alignof == nil. -func DefaultAlignof(typ Type) int64 { +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 := underlying(typ).(type) { + switch t := T.Underlying().(type) { case *Array: // spec: "For a variable x of array type: unsafe.Alignof(x) // is the same as unsafe.Alignof(x[0]), but at least 1." - return DefaultAlignof(t.Elt) + 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 := DefaultAlignof(f.Type); a > max { + for _, f := range t.fields { + if a := s.Alignof(f.typ); a > max { max = a } } return max } - a := DefaultSizeof(typ) // may be 0 + 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 } - if a > DefaultMaxAlign { - return DefaultMaxAlign + if a > s.MaxAlign { + return s.MaxAlign } return a } -// 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 -} - -// DefaultOffsetsof implements the default field offset computation -// for unsafe.Offsetof. It is used if Context.Offsetsof == nil. -func DefaultOffsetsof(fields []*Field) []int64 { +func (s *StdSizes) Offsetsof(fields []*Var) []int64 { offsets := make([]int64, len(fields)) var o int64 for i, f := range fields { - a := DefaultAlignof(f.Type) + a := s.Alignof(f.typ) o = align(o, a) offsets[i] = o - o += DefaultSizeof(f.Type) + o += s.Sizeof(f.typ) } return offsets } -// DefaultPtrSize is the default size of ints, uint, and pointers, in bytes, -// used by DefaultSizeof. -const DefaultPtrSize = 8 +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, +} -// DefaultSizeof implements the default size computation -// for unsafe.Sizeof. It is used if Context.Sizeof == nil. -func DefaultSizeof(typ Type) int64 { - switch t := underlying(typ).(type) { +func (s *StdSizes) Sizeof(T Type) int64 { + switch t := T.Underlying().(type) { case *Basic: - if s := t.size; s > 0 { - return s + assert(isTyped(T)) + k := t.kind + if int(k) < len(basicSizes) { + if s := basicSizes[k]; s > 0 { + return int64(s) + } } - if t.Kind == String { - return DefaultPtrSize * 2 + if k == String { + return s.WordSize * 2 } case *Array: - a := DefaultAlignof(t.Elt) - s := DefaultSizeof(t.Elt) - return align(s, a) * t.Len // may be 0 + n := t.len + if n == 0 { + return 0 + } + a := s.Alignof(t.elem) + z := s.Sizeof(t.elem) + return align(z, a)*(n-1) + z case *Slice: - return DefaultPtrSize * 3 + return s.WordSize * 3 case *Struct: - n := len(t.Fields) + n := t.NumFields() if n == 0 { return 0 } offsets := t.offsets if t.offsets == nil { // compute offsets on demand - offsets = DefaultOffsetsof(t.Fields) + offsets = s.Offsetsof(t.fields) t.offsets = offsets } - return offsets[n-1] + DefaultSizeof(t.Fields[n-1].Type) - case *Signature: - return DefaultPtrSize * 2 + return offsets[n-1] + s.Sizeof(t.fields[n-1].typ) + case *Interface: + return s.WordSize * 2 } - return DefaultPtrSize // catch-all + return s.WordSize // catch-all +} + +// stdSizes is used if Config.Sizes == nil. +var stdSizes = StdSizes{8, 8} + +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 { + offsets := T.offsets + if offsets == nil && 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) + } + T.offsets = offsets + } + return offsets +} + +// offsetof returns the offset of the field specified via +// the index sequence relative to typ. All embedded fields +// must be structs (rather than pointer to structs). +func (conf *Config) offsetof(typ Type, index []int) int64 { + var o int64 + for _, i := range index { + s := typ.Underlying().(*Struct) + o += conf.offsetsof(s)[i] + typ = s.fields[i].typ + } + return o +} + +func (conf *Config) sizeof(T Type) int64 { + if s := conf.Sizes; s != nil { + if z := s.Sizeof(T); z >= 0 { + return z + } + panic("Config.Sizes.Sizeof returned a size < 0") + } + return stdSizes.Sizeof(T) +} + +// align returns the smallest y >= x such that y % a == 0. +func align(x, a int64) int64 { + y := x + a - 1 + return y - y%a } diff --git a/src/gosubli.me/something-borrowed/types/stdlib_test.go b/src/gosubli.me/something-borrowed/types/stdlib_test.go new file mode 100644 index 00000000..86290223 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/stdlib_test.go @@ -0,0 +1,254 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file tests types.Check by using it to +// typecheck the standard library and tests. + +package types_test + +import ( + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/scanner" + "go/token" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + _ "golang.org/x/tools/go/gcimporter" + . "golang.org/x/tools/go/types" +) + +var ( + pkgCount int // number of packages processed + start = time.Now() +) + +func TestStdlib(t *testing.T) { + walkDirs(t, filepath.Join(runtime.GOROOT(), "src")) + if testing.Verbose() { + fmt.Println(pkgCount, "packages typechecked in", time.Since(start)) + } +} + +// firstComment returns the contents of the first comment in +// the given file, assuming there's one within the first KB. +func firstComment(filename string) string { + f, err := os.Open(filename) + if err != nil { + return "" + } + defer f.Close() + + var src [1 << 10]byte // read at most 1KB + n, _ := f.Read(src[:]) + + var s scanner.Scanner + s.Init(fset.AddFile("", fset.Base(), n), src[:n], nil, scanner.ScanComments) + for { + _, tok, lit := s.Scan() + switch tok { + case token.COMMENT: + // remove trailing */ of multi-line comment + if lit[1] == '*' { + lit = lit[:len(lit)-2] + } + return strings.TrimSpace(lit[2:]) + case token.EOF: + 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 + } + + fset := token.NewFileSet() + for _, f := range files { + // filter directory contents + if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] { + continue + } + + // get per-file instructions + expectErrors := false + filename := filepath.Join(path, f.Name()) + if cmd := firstComment(filename); cmd != "" { + switch cmd { + case "skip", "compiledir": + continue // ignore this file + case "errorcheck": + expectErrors = true + } + } + + // parse and type-check file + file, err := parser.ParseFile(fset, filename, nil, 0) + if err == nil { + _, err = Check(filename, fset, []*ast.File{file}) + } + + 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) { + testTestDir(t, filepath.Join(runtime.GOROOT(), "test"), + "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore + "sigchld.go", // don't work on Windows; testTestDir should consult build tags + "float_lit2.go", // TODO(gri) enable for releases 1.4 and higher + ) +} + +func TestStdFixed(t *testing.T) { + testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "fixedbugs"), + "bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore + "bug459.go", // possibly incorrect test - see issue 6703 (pending spec clarification) + "issue3924.go", // possibly incorrect test - see issue 6671 (pending spec clarification) + "issue6889.go", // gc-specific test + ) +} + +func TestStdKen(t *testing.T) { + testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "ken")) +} + +// Package paths of excluded packages. +var excluded = map[string]bool{ + "builtin": true, +} + +// typecheck typechecks the given package files. +func typecheck(t *testing.T, path string, filenames []string) { + fset := token.NewFileSet() + + // parse package files + var files []*ast.File + for _, filename := range filenames { + file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) + if err != nil { + // the parser error may be a list of individual errors; report them all + if list, ok := err.(scanner.ErrorList); ok { + for _, err := range list { + t.Error(err) + } + return + } + t.Error(err) + return + } + + if testing.Verbose() { + if len(files) == 0 { + fmt.Println("package", file.Name.Name) + } + fmt.Println("\t", filename) + } + + files = append(files, file) + } + + // typecheck package files + var conf Config + conf.Error = func(err error) { t.Error(err) } + info := Info{Uses: make(map[*ast.Ident]Object)} + conf.Check(path, fset, files, &info) + pkgCount++ + + // Perform checks of API invariants. + + // All Objects have a package, except predeclared ones. + errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error + for id, obj := range info.Uses { + predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError + if predeclared == (obj.Pkg() != nil) { + posn := fset.Position(id.Pos()) + if predeclared { + t.Errorf("%s: predeclared object with package: %s", posn, obj) + } else { + t.Errorf("%s: user-defined object without package: %s", posn, obj) + } + } + } +} + +// pkgFilenames returns the list of package filenames for the given directory. +func pkgFilenames(dir string) ([]string, error) { + ctxt := build.Default + ctxt.CgoEnabled = false + pkg, err := ctxt.ImportDir(dir, 0) + if err != nil { + if _, nogo := err.(*build.NoGoError); nogo { + return nil, nil // no *.go files, not an error + } + return nil, err + } + if excluded[pkg.ImportPath] { + return nil, nil + } + var filenames []string + for _, name := range pkg.GoFiles { + filenames = append(filenames, filepath.Join(pkg.Dir, name)) + } + for _, name := range pkg.TestGoFiles { + filenames = append(filenames, filepath.Join(pkg.Dir, name)) + } + return filenames, nil +} + +// Note: Could use filepath.Walk instead of walkDirs but that wouldn't +// necessarily be shorter or clearer after adding the code to +// terminate early for -short tests. + +func walkDirs(t *testing.T, dir string) { + // limit run time for short tests + if testing.Short() && time.Since(start) >= 750*time.Millisecond { + return + } + + fis, err := ioutil.ReadDir(dir) + if err != nil { + t.Error(err) + return + } + + // typecheck package in directory + files, err := pkgFilenames(dir) + if err != nil { + t.Error(err) + return + } + if files != nil { + typecheck(t, dir, files) + } + + // traverse subdirectories, but don't walk into testdata + for _, fi := range fis { + if fi.IsDir() && fi.Name() != "testdata" { + walkDirs(t, filepath.Join(dir, fi.Name())) + } + } +} diff --git a/src/gosubli.me/something-borrowed/types/stmt.go b/src/gosubli.me/something-borrowed/types/stmt.go index 65b12a01..e0d5bb01 100644 --- a/src/gosubli.me/something-borrowed/types/stmt.go +++ b/src/gosubli.me/something-borrowed/types/stmt.go @@ -7,259 +7,92 @@ package types import ( + "fmt" "go/ast" "go/token" -) - -func (check *checker) assignOperand(z, x *operand) { - if t, ok := x.typ.(*Result); ok { - // TODO(gri) elsewhere we use "assignment count mismatch" (consolidate) - check.errorf(x.pos(), "%d-valued expression %s used as single value", len(t.Values), x) - x.mode = invalid - return - } - - check.convertUntyped(x, z.typ) - - if !x.isAssignable(check.ctxt, z.typ) { - check.errorf(x.pos(), "cannot assign %s to %s", x, z) - x.mode = invalid - } -} -// assign1to1 typechecks a single assignment of the form lhs = rhs (if rhs != nil), -// or lhs = x (if rhs == nil). If decl is set, the lhs operand must be an identifier. -// If its type is not set, it is deduced from the type or value of x. If lhs has a -// type it is used as a hint when evaluating rhs, if present. -// -func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota int) { - ident, _ := lhs.(*ast.Ident) - if x == nil { - assert(rhs != nil) - x = new(operand) - } + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" +) - if ident != nil && ident.Name == "_" { - // anything can be assigned to a blank identifier - check rhs only, if present - if rhs != nil { - check.expr(x, rhs, nil, iota) +func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body *ast.BlockStmt) { + if trace { + if name == "" { + name = "" } - return + fmt.Printf("--- %s: %s {\n", name, sig) + defer fmt.Println("--- ") } - if !decl { - // regular assignment - start with lhs to obtain a type hint - var z operand - check.expr(&z, lhs, nil, -1) - if z.mode == invalid { - z.typ = nil // so we can proceed with rhs - } - - if rhs != nil { - check.expr(x, rhs, z.typ, -1) - if x.mode == invalid { - return - } - } - - if x.mode == invalid || z.mode == invalid { - return - } - - check.assignOperand(&z, x) - if x.mode != invalid && z.mode == constant { - check.errorf(x.pos(), "cannot assign %s to %s", x, &z) - } - return + // 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, + sig: sig, } + check.indent = 0 - // declaration - lhs must be an identifier - if ident == nil { - check.errorf(lhs.Pos(), "cannot declare %s", lhs) - return - } + check.stmtList(0, body.List) - // lhs may or may not be typed yet - obj := check.lookup(ident) - var typ Type - if t := obj.GetType(); t != nil { - typ = t + if check.hasLabel { + check.labels(body) } - if rhs != nil { - check.expr(x, rhs, typ, iota) - // continue even if x.mode == invalid + if sig.results.Len() > 0 && !check.isTerminating(body, "") { + check.error(body.Rbrace, "missing return") } - if typ == nil { - // determine lhs type from rhs expression; - // for variables, convert untyped types to - // default types - typ = Typ[Invalid] - if x.mode != invalid { - typ = x.typ - if _, ok := obj.(*Var); ok && isUntyped(typ) { - if x.isNil() { - check.errorf(x.pos(), "use of untyped nil") - x.mode = invalid - } else { - typ = defaultType(typ) - } - } - } - switch obj := obj.(type) { - case *Const: - obj.Type = typ - case *Var: - obj.Type = typ - default: - unreachable() - } - } + // spec: "Implementation restriction: A compiler may make it illegal to + // declare a variable inside a function body if the variable is never used." + // (One could check each scope after use, but that distributes this check + // over several places because CloseScope is not always called explicitly.) + check.usage(sig.scope) +} - if x.mode != invalid { - var z operand - switch obj.(type) { - case *Const: - z.mode = constant - case *Var: - z.mode = variable - default: - unreachable() +func (check *Checker) usage(scope *Scope) { + for _, obj := range scope.elems { + if v, _ := obj.(*Var); v != nil && !v.used { + check.softErrorf(v.pos, "%s declared but not used", v.name) } - z.expr = ident - z.typ = typ - check.assignOperand(&z, x) } - - // for constants, set their value - if obj, ok := obj.(*Const); ok { - assert(obj.Val == nil) - if x.mode != invalid { - if x.mode == constant { - if isConstType(x.typ) { - obj.Val = x.val - } else { - check.errorf(x.pos(), "%s has invalid constant type", x) - } - } else { - check.errorf(x.pos(), "%s is not constant", x) - } - } - if obj.Val == nil { - // set the constant to its type's zero value to reduce spurious errors - switch typ := underlying(obj.Type); { - case typ == Typ[Invalid]: - // ignore - case isBoolean(typ): - obj.Val = false - case isNumeric(typ): - obj.Val = int64(0) - case isString(typ): - obj.Val = "" - case hasNil(typ): - obj.Val = nilConst - default: - // in all other cases just prevent use of the constant - // TODO(gri) re-evaluate this code - obj.Val = nilConst - } - } + for _, scope := range scope.children { + check.usage(scope) } } -// assignNtoM typechecks a general assignment. If decl is set, the lhs operands -// must be identifiers. If their types are not set, they are deduced from the -// types of the corresponding rhs expressions. iota >= 0 indicates that the -// "assignment" is part of a constant/variable declaration. -// Precondition: len(lhs) > 0 . -// -func (check *checker) assignNtoM(lhs, rhs []ast.Expr, decl bool, iota int) { - assert(len(lhs) > 0) - - if len(lhs) == len(rhs) { - for i, e := range rhs { - check.assign1to1(lhs[i], e, nil, decl, iota) - } - return - } - - if len(rhs) == 1 { - // len(lhs) > 1, therefore a correct rhs expression - // cannot be a shift and we don't need a type hint; - // ok to evaluate rhs first - var x operand - check.expr(&x, rhs[0], nil, iota) - if x.mode == invalid { - // If decl is set, this leaves the lhs identifiers - // untyped. We catch this when looking up the respective - // object. - return - } - - if t, ok := x.typ.(*Result); ok && len(lhs) == len(t.Values) { - // function result - x.mode = value - for i, obj := range t.Values { - x.expr = nil // TODO(gri) should do better here - x.typ = obj.Type - check.assign1to1(lhs[i], nil, &x, decl, iota) - } - return - } - - if x.mode == valueok && len(lhs) == 2 { - // comma-ok expression - x.mode = value - check.assign1to1(lhs[0], nil, &x, decl, iota) +// stmtContext is a bitset describing which +// control-flow statements are permissible. +type stmtContext uint - x.mode = value - x.typ = Typ[UntypedBool] - check.assign1to1(lhs[1], nil, &x, decl, iota) - return - } - } - - check.errorf(lhs[0].Pos(), "assignment count mismatch: %d = %d", len(lhs), len(rhs)) - - // avoid checking the same declaration over and over - // again for each lhs identifier that has no type yet - if iota >= 0 { - // declaration - for _, e := range lhs { - if name, ok := e.(*ast.Ident); ok { - switch obj := check.lookup(name).(type) { - case *Const: - obj.Type = Typ[Invalid] - case *Var: - obj.Type = Typ[Invalid] - default: - unreachable() - } - } - } - } -} +const ( + breakOk stmtContext = 1 << iota + continueOk + fallthroughOk +) -func (check *checker) optionalStmt(s ast.Stmt) { +func (check *Checker) simpleStmt(s ast.Stmt) { if s != nil { - check.stmt(s) + check.stmt(0, s) } } -func (check *checker) stmtList(list []ast.Stmt) { - for _, s := range list { - check.stmt(s) +func (check *Checker) stmtList(ctxt stmtContext, list []ast.Stmt) { + ok := ctxt&fallthroughOk != 0 + inner := ctxt &^ fallthroughOk + for i, s := range list { + inner := inner + if ok && i+1 == len(list) { + inner |= fallthroughOk + } + check.stmt(inner, s) } } -func (check *checker) call(call *ast.CallExpr) { - var x operand - check.rawExpr(&x, call, nil, -1, false) // don't check if value is used - // TODO(gri) If a builtin is called, the builtin must be valid in statement context. -} - -func (check *checker) multipleDefaults(list []ast.Stmt) { +func (check *Checker) multipleDefaults(list []ast.Stmt) { var first ast.Stmt for _, s := range list { var d ast.Stmt @@ -285,68 +118,147 @@ func (check *checker) multipleDefaults(list []ast.Stmt) { } } +func (check *Checker) openScope(s ast.Stmt, comment string) { + scope := NewScope(check.scope, comment) + check.recordScope(s, scope) + check.scope = scope +} + +func (check *Checker) closeScope() { + check.scope = check.scope.Parent() +} + +func assignOp(op token.Token) token.Token { + // token_test.go verifies the token ordering this function relies on + if token.ADD_ASSIGN <= op && op <= token.AND_NOT_ASSIGN { + return op + (token.ADD - token.ADD_ASSIGN) + } + return token.ILLEGAL +} + +func (check *Checker) suspendedCall(keyword string, call *ast.CallExpr) { + var x operand + var msg string + 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.pos(), "%s %s %s", keyword, msg, &x) +} + +func (check *Checker) caseValues(x operand /* copy argument (not *operand!) */, values []ast.Expr) { + // No duplicate checking for now. See issue 4524. + for _, e := range values { + var y operand + check.expr(&y, e) + if y.mode == invalid { + return + } + // TODO(gri) The convertUntyped call pair below appears in other places. Factor! + // Order matters: By comparing y against x, error positions are at the case values. + check.convertUntyped(&y, x.typ) + if y.mode == invalid { + return + } + check.convertUntyped(&x, y.typ) + if x.mode == invalid { + return + } + check.comparison(&y, &x, token.EQL) + } +} + +func (check *Checker) caseTypes(x *operand, xtyp *Interface, types []ast.Expr, seen map[Type]token.Pos) (T Type) { +L: + for _, e := range types { + T = check.typOrNil(e) + if T == Typ[Invalid] { + continue + } + // complain about duplicate types + // TODO(gri) use a type hash to avoid quadratic algorithm + for t, pos := range seen { + if T == nil && t == nil || T != nil && t != nil && Identical(T, t) { + // talk about "case" rather than "type" because of nil case + check.error(e.Pos(), "duplicate case in type switch") + check.errorf(pos, "\tprevious case %s", T) // secondary error, \t indented + continue L + } + } + seen[T] = e.Pos() + if T != nil { + check.typeAssertion(e.Pos(), x, xtyp, T) + } + } + return +} + // stmt typechecks statement s. -func (check *checker) stmt(s ast.Stmt) { +func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { + // statements cannot use iota in general + // (constant declarations set it explicitly) + assert(check.iota == nil) + + // 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) + } + + inner := ctxt &^ fallthroughOk switch s := s.(type) { case *ast.BadStmt, *ast.EmptyStmt: // ignore case *ast.DeclStmt: - d, _ := s.Decl.(*ast.GenDecl) - if d == nil || (d.Tok != token.CONST && d.Tok != token.TYPE && d.Tok != token.VAR) { - check.invalidAST(token.NoPos, "const, type, or var declaration expected") - return - } - if d.Tok == token.CONST { - check.assocInitvals(d) - } - check.decl(d) + check.declStmt(s.Decl) case *ast.LabeledStmt: - // TODO(gri) anything to do with label itself? - check.stmt(s.Stmt) + check.hasLabel = true + check.stmt(ctxt, s.Stmt) case *ast.ExprStmt: + // spec: "With the exception of specific built-in functions, + // function and method calls and receive operations can appear + // in statement context. Such statements may be parenthesized." var x operand - used := false - switch e := unparen(s.X).(type) { - case *ast.CallExpr: - // function calls are permitted - used = true - // but some builtins are excluded - // (Caution: This evaluates e.Fun twice, once here and once - // below as part of s.X. This has consequences for - // check.register. Perhaps this can be avoided.) - check.expr(&x, e.Fun, nil, -1) - if x.mode != invalid { - if b, ok := x.typ.(*builtin); ok && !b.isStatement { - used = false - } - } - case *ast.UnaryExpr: - // receive operations are permitted - if e.Op == token.ARROW { - used = true + 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" } - if !used { - check.errorf(s.Pos(), "%s not used", s.X) - // ok to continue - } - check.rawExpr(&x, s.X, nil, -1, false) - if x.mode == typexpr { - check.errorf(x.pos(), "%s is not an expression", &x) - } + check.errorf(x.pos(), "%s %s", &x, msg) case *ast.SendStmt: var ch, x operand - check.expr(&ch, s.Chan, nil, -1) - check.expr(&x, s.Value, nil, -1) + check.expr(&ch, s.Chan) + check.expr(&x, s.Value) if ch.mode == invalid || x.mode == invalid { return } - if tch, ok := underlying(ch.typ).(*Chan); !ok || tch.Dir&ast.SEND == 0 || !x.isAssignable(check.ctxt, tch.Elt) { - check.invalidOp(ch.pos(), "cannot send %s to channel %s", &x, &ch) + if tch, ok := ch.typ.Underlying().(*Chan); !ok || tch.dir == RecvOnly || !check.assignment(&x, tch.elem) { + if x.mode != invalid { + check.invalidOp(ch.pos(), "cannot send %s to channel %s", &x, &ch) + } } case *ast.IncDecStmt: @@ -360,11 +272,13 @@ func (check *checker) stmt(s ast.Stmt) { check.invalidAST(s.TokPos, "unknown inc/dec operation %s", s.Tok) return } - var x, y operand - check.expr(&x, s.X, nil, -1) - check.expr(&y, &ast.BasicLit{ValuePos: x.pos(), Kind: token.INT, Value: "1"}, nil, -1) // use x's position - check.binary(&x, &y, op, nil) - check.assign1to1(s.X, nil, &x, false, -1) + var x operand + Y := &ast.BasicLit{ValuePos: s.X.Pos(), Kind: token.INT, Value: "1"} // use x's position + check.binary(&x, s.X, Y, op) + if x.mode == invalid { + return + } + check.assignVar(s.X, &x) case *ast.AssignStmt: switch s.Tok { @@ -373,168 +287,150 @@ func (check *checker) stmt(s ast.Stmt) { check.invalidAST(s.Pos(), "missing lhs in assignment") return } - check.assignNtoM(s.Lhs, s.Rhs, s.Tok == token.DEFINE, -1) + if s.Tok == token.DEFINE { + check.shortVarDecl(s.TokPos, s.Lhs, s.Rhs) + } else { + // regular assignment + check.assignVars(s.Lhs, s.Rhs) + } + default: // assignment operations if len(s.Lhs) != 1 || len(s.Rhs) != 1 { check.errorf(s.TokPos, "assignment operation %s requires single-valued expressions", s.Tok) return } - // TODO(gri) make this conversion more efficient - var op token.Token - switch s.Tok { - case token.ADD_ASSIGN: - op = token.ADD - case token.SUB_ASSIGN: - op = token.SUB - case token.MUL_ASSIGN: - op = token.MUL - case token.QUO_ASSIGN: - op = token.QUO - case token.REM_ASSIGN: - op = token.REM - case token.AND_ASSIGN: - op = token.AND - case token.OR_ASSIGN: - op = token.OR - case token.XOR_ASSIGN: - op = token.XOR - case token.SHL_ASSIGN: - op = token.SHL - case token.SHR_ASSIGN: - op = token.SHR - case token.AND_NOT_ASSIGN: - op = token.AND_NOT - default: + op := assignOp(s.Tok) + if op == token.ILLEGAL { check.invalidAST(s.TokPos, "unknown assignment operation %s", s.Tok) return } - var x, y operand - // The lhs operand's type doesn't need a hint (from the rhs operand), - // because it must be a fully typed variable in this case. - check.expr(&x, s.Lhs[0], nil, -1) + var x operand + check.binary(&x, s.Lhs[0], s.Rhs[0], op) if x.mode == invalid { return } - check.expr(&y, s.Rhs[0], x.typ, -1) - if y.mode == invalid { - return - } - check.binary(&x, &y, op, x.typ) - check.assign1to1(s.Lhs[0], nil, &x, false, -1) + check.assignVar(s.Lhs[0], &x) } case *ast.GoStmt: - check.call(s.Call) + check.suspendedCall("go", s.Call) case *ast.DeferStmt: - check.call(s.Call) + check.suspendedCall("defer", s.Call) case *ast.ReturnStmt: - sig := check.funcsig - if n := len(sig.Results); n > 0 { - // TODO(gri) should not have to compute lhs, named every single time - clean this up - lhs := make([]ast.Expr, n) - named := false // if set, function has named results - for i, res := range sig.Results { - if len(res.Name) > 0 { - // a blank (_) result parameter is a named result - named = true + res := check.sig.results + if res.Len() > 0 { + // function returns results + // (if one, say the first, result parameter is named, all of them are named) + if len(s.Results) == 0 && res.vars[0].name != "" { + // spec: "Implementation restriction: A compiler may disallow an empty expression + // list in a "return" statement if a different entity (constant, type, or variable) + // with the same name as a result parameter is in scope at the place of the return." + for _, obj := range res.vars { + if _, alt := check.scope.LookupParent(obj.name); alt != nil && alt != obj { + check.errorf(s.Pos(), "result parameter %s not in scope at return", obj.name) + check.errorf(alt.Pos(), "\tinner declaration of %s", obj) + // ok to continue + } } - name := ast.NewIdent(res.Name) - name.NamePos = s.Pos() - check.register(name, &Var{Name: res.Name, Type: res.Type}) // Pkg == nil - lhs[i] = name - } - if len(s.Results) > 0 || !named { - // TODO(gri) assignNtoM should perhaps not require len(lhs) > 0 - check.assignNtoM(lhs, s.Results, false, -1) + } else { + // return has results or result parameters are unnamed + check.initVars(res.vars, s.Results, s.Return) } } else if len(s.Results) > 0 { - check.errorf(s.Pos(), "no result values expected") + check.error(s.Results[0].Pos(), "no result values expected") + check.use(s.Results...) } case *ast.BranchStmt: - // TODO(gri) implement this + if s.Label != nil { + check.hasLabel = true + return // checked in 2nd pass (check.labels) + } + switch s.Tok { + case token.BREAK: + if ctxt&breakOk == 0 { + check.error(s.Pos(), "break not in for, switch, or select statement") + } + case token.CONTINUE: + if ctxt&continueOk == 0 { + check.error(s.Pos(), "continue not in for statement") + } + case token.FALLTHROUGH: + if ctxt&fallthroughOk == 0 { + check.error(s.Pos(), "fallthrough statement out of place") + } + default: + check.invalidAST(s.Pos(), "branch statement: %s", s.Tok) + } case *ast.BlockStmt: - check.stmtList(s.List) + check.openScope(s, "block") + defer check.closeScope() + + check.stmtList(inner, s.List) case *ast.IfStmt: - check.optionalStmt(s.Init) + check.openScope(s, "if") + defer check.closeScope() + + check.simpleStmt(s.Init) var x operand - check.expr(&x, s.Cond, nil, -1) - if !isBoolean(x.typ) { - check.errorf(s.Cond.Pos(), "non-boolean condition in if statement") + check.expr(&x, s.Cond) + if x.mode != invalid && !isBoolean(x.typ) { + check.error(s.Cond.Pos(), "non-boolean condition in if statement") + } + check.stmt(inner, s.Body) + if s.Else != nil { + check.stmt(inner, s.Else) } - check.stmt(s.Body) - check.optionalStmt(s.Else) case *ast.SwitchStmt: - check.optionalStmt(s.Init) + inner |= breakOk + check.openScope(s, "switch") + defer check.closeScope() + + check.simpleStmt(s.Init) var x operand - tag := s.Tag - if tag == nil { - // use fake true tag value and position it at the opening { of the switch - ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} - check.register(ident, Universe.Lookup("true")) - tag = ident + if s.Tag != nil { + check.expr(&x, s.Tag) + } else { + // spec: "A missing switch expression is + // equivalent to the boolean value true." + x.mode = constant + x.typ = Typ[Bool] + x.val = exact.MakeBool(true) + x.expr = &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} } - check.expr(&x, tag, nil, -1) check.multipleDefaults(s.Body.List) - seen := make(map[interface{}]token.Pos) - for _, s := range s.Body.List { - clause, _ := s.(*ast.CaseClause) + + for i, c := range s.Body.List { + clause, _ := c.(*ast.CaseClause) if clause == nil { - continue // error reported before + check.invalidAST(c.Pos(), "incorrect expression switch case") + continue } if x.mode != invalid { - for _, expr := range clause.List { - x := x // copy of x (don't modify original) - var y operand - check.expr(&y, expr, nil, -1) - if y.mode == invalid { - continue // error reported before - } - // If we have a constant case value, it must appear only - // once in the switch statement. Determine if there is a - // duplicate entry, but only report an error if there are - // no other errors. - var dupl token.Pos - var yy operand - if y.mode == constant { - // TODO(gri) This code doesn't work correctly for - // large integer, floating point, or - // complex values - the respective struct - // comparisons are shallow. Need to use a - // hash function to index the map. - dupl = seen[y.val] - seen[y.val] = y.pos() - yy = y // remember y - } - // TODO(gri) The convertUntyped call pair below appears in other places. Factor! - // Order matters: By comparing y against x, error positions are at the case values. - check.convertUntyped(&y, x.typ) - if y.mode == invalid { - continue // error reported before - } - check.convertUntyped(&x, y.typ) - if x.mode == invalid { - continue // error reported before - } - check.comparison(&y, &x, token.EQL) - if y.mode != invalid && dupl.IsValid() { - check.errorf(yy.pos(), "%s is duplicate case (previous at %s)", - &yy, check.fset.Position(dupl)) - } - } + check.caseValues(x, clause.List) + } + check.openScope(clause, "case") + inner := inner + if i+1 < len(s.Body.List) { + inner |= fallthroughOk } - check.stmtList(clause.Body) + check.stmtList(inner, clause.Body) + check.closeScope() } case *ast.TypeSwitchStmt: - check.optionalStmt(s.Init) + inner |= breakOk + check.openScope(s, "type switch") + defer check.closeScope() + + check.simpleStmt(s.Init) // A type switch guard must be of the form: // @@ -544,7 +440,7 @@ func (check *checker) stmt(s ast.Stmt) { // remaining syntactic errors are considered AST errors here. // TODO(gri) better factoring of error handling (invalid ASTs) // - var lhs *Var // lhs variable or nil + var lhs *ast.Ident // lhs identifier or nil var rhs ast.Expr switch guard := s.Assign.(type) { case *ast.ExprStmt: @@ -554,13 +450,16 @@ func (check *checker) stmt(s ast.Stmt) { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } - ident, _ := guard.Lhs[0].(*ast.Ident) - if ident == nil { + + lhs, _ = guard.Lhs[0].(*ast.Ident) + if lhs == nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } - lhs = check.lookup(ident).(*Var) + check.recordDef(lhs, nil) // lhs variable is implicitly declared in each cause clause + rhs = guard.Rhs[0] + default: check.invalidAST(s.Pos(), "incorrect form of type switch guard") return @@ -573,119 +472,176 @@ func (check *checker) stmt(s ast.Stmt) { return } var x operand - check.expr(&x, expr.X, nil, -1) + check.expr(&x, expr.X) if x.mode == invalid { return } - var T *Interface - if T, _ = underlying(x.typ).(*Interface); T == nil { + xtyp, _ := x.typ.Underlying().(*Interface) + if xtyp == nil { check.errorf(x.pos(), "%s is not an interface", &x) return } check.multipleDefaults(s.Body.List) + + var lhsVars []*Var // list of implicitly declared lhs variables + seen := make(map[Type]token.Pos) // map of seen types to positions for _, s := range s.Body.List { clause, _ := s.(*ast.CaseClause) if clause == nil { - continue // error reported before + check.invalidAST(s.Pos(), "incorrect type switch case") + continue } // Check each type in this type switch case. - var typ Type - for _, expr := range clause.List { - typ = check.typOrNil(expr, false) - if typ != nil && typ != Typ[Invalid] { - if method, wrongType := missingMethod(typ, T); method != nil { - var msg string - if wrongType { - msg = "%s cannot have dynamic type %s (wrong type for method %s)" - } else { - msg = "%s cannot have dynamic type %s (missing method %s)" - } - check.errorf(expr.Pos(), msg, &x, typ, method.Name) - // ok to continue - } - } - } - // If lhs exists, set its type for each clause. + T := check.caseTypes(&x, xtyp, clause.List, seen) + check.openScope(clause, "case") + // If lhs exists, declare a corresponding variable in the case-local scope. if lhs != nil { - // In clauses with a case listing exactly one type, the variable has that type; - // otherwise, the variable has the type of the expression in the TypeSwitchGuard. - if len(clause.List) != 1 || typ == nil { - typ = x.typ + // spec: "The TypeSwitchGuard may include a short variable declaration. + // When that form is used, the variable is declared at the beginning of + // the implicit block in each clause. In clauses with a case listing + // exactly one type, the variable has that type; otherwise, the variable + // has the type of the expression in the TypeSwitchGuard." + if len(clause.List) != 1 || T == nil { + T = x.typ } - lhs.Type = typ + obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, T) + check.declare(check.scope, nil, obj) + 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(clause.Body) + check.stmtList(inner, clause.Body) + check.closeScope() } - // There is only one object (lhs) associated with a lhs identifier, but that object - // assumes different types for different clauses. Set it back to the type of the - // TypeSwitchGuard expression so that that variable always has a valid type. + // If lhs exists, we must have at least one lhs variable that was used. if lhs != nil { - lhs.Type = x.typ + 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.Pos(), "%s declared but not used", lhs.Name) + } } case *ast.SelectStmt: + inner |= breakOk + check.multipleDefaults(s.Body.List) + for _, s := range s.Body.List { clause, _ := s.(*ast.CommClause) if clause == nil { continue // error reported before } - check.optionalStmt(clause.Comm) // TODO(gri) check correctness of c.Comm (must be Send/RecvStmt) - check.stmtList(clause.Body) + + // clause.Comm must be a SendStmt, RecvStmt, or default case + valid := false + var rhs ast.Expr // rhs of RecvStmt, or nil + switch s := clause.Comm.(type) { + case nil, *ast.SendStmt: + valid = true + case *ast.AssignStmt: + if len(s.Rhs) == 1 { + rhs = s.Rhs[0] + } + case *ast.ExprStmt: + rhs = s.X + } + + // if present, rhs must be a receive operation + if rhs != nil { + if x, _ := unparen(rhs).(*ast.UnaryExpr); x != nil && x.Op == token.ARROW { + valid = true + } + } + + if !valid { + check.error(clause.Comm.Pos(), "select case must be send or receive (possibly with assignment)") + continue + } + + check.openScope(s, "case") + defer check.closeScope() + if clause.Comm != nil { + check.stmt(inner, clause.Comm) + } + check.stmtList(inner, clause.Body) } case *ast.ForStmt: - check.optionalStmt(s.Init) + inner |= breakOk | continueOk + check.openScope(s, "for") + defer check.closeScope() + + check.simpleStmt(s.Init) if s.Cond != nil { var x operand - check.expr(&x, s.Cond, nil, -1) - if !isBoolean(x.typ) { - check.errorf(s.Cond.Pos(), "non-boolean condition in for statement") + check.expr(&x, s.Cond) + if x.mode != invalid && !isBoolean(x.typ) { + check.error(s.Cond.Pos(), "non-boolean condition in for statement") } } - check.optionalStmt(s.Post) - check.stmt(s.Body) + check.simpleStmt(s.Post) + // spec: "The init statement may be a short variable + // declaration, but the post statement must not." + if s, _ := s.Post.(*ast.AssignStmt); s != nil && s.Tok == token.DEFINE { + check.softErrorf(s.Pos(), "cannot declare in post statement") + check.use(s.Lhs...) // avoid follow-up errors + } + check.stmt(inner, s.Body) case *ast.RangeStmt: + inner |= breakOk | continueOk + check.openScope(s, "for") + defer check.closeScope() + // check expression to iterate over decl := s.Tok == token.DEFINE var x operand - check.expr(&x, s.X, nil, -1) + check.expr(&x, s.X) if x.mode == invalid { // if we don't have a declaration, we can still check the loop's body + // (otherwise we can't because we are missing the declared variables) if !decl { - check.stmt(s.Body) + check.stmt(inner, s.Body) } return } // determine key/value types var key, val Type - switch typ := underlying(x.typ).(type) { + switch typ := x.typ.Underlying().(type) { case *Basic: if isString(typ) { - key = Typ[UntypedInt] - val = Typ[UntypedRune] + key = Typ[Int] + val = UniverseRune // use 'rune' name } case *Array: - key = Typ[UntypedInt] - val = typ.Elt + key = Typ[Int] + val = typ.elem case *Slice: - key = Typ[UntypedInt] - val = typ.Elt + key = Typ[Int] + val = typ.elem case *Pointer: - if typ, _ := underlying(typ.Base).(*Array); typ != nil { - key = Typ[UntypedInt] - val = typ.Elt + if typ, _ := typ.base.Underlying().(*Array); typ != nil { + key = Typ[Int] + val = typ.elem } case *Map: - key = typ.Key - val = typ.Elt + key = typ.key + val = typ.elem case *Chan: - key = typ.Elt - if typ.Dir&ast.RECV == 0 { + key = typ.elem + val = Typ[Invalid] + if typ.dir == SendOnly { check.errorf(x.pos(), "cannot range over send-only channel %s", &x) // ok to continue } @@ -699,32 +655,75 @@ func (check *checker) stmt(s ast.Stmt) { check.errorf(x.pos(), "cannot range over %s", &x) // if we don't have a declaration, we can still check the loop's body if !decl { - check.stmt(s.Body) + check.stmt(inner, s.Body) } return } // check assignment to/declaration of iteration variables - // TODO(gri) The error messages/positions are not great here, - // they refer to the expression in the range clause. - // Should give better messages w/o too much code - // duplication (assignment checking). - x.mode = value - if s.Key != nil { - x.typ = key - check.assign1to1(s.Key, nil, &x, decl, -1) + // (irregular assignment, cannot easily map to existing assignment checks) + + // lhs expressions and initialization value (rhs) types + lhs := [2]ast.Expr{s.Key, s.Value} + rhs := [2]Type{key, val} + + if decl { + // short variable declaration; variable scope starts after the range clause + // (the for loop opens a new scope, so variables on the lhs never redeclare + // previously declared variables) + var vars []*Var + for i, lhs := range lhs { + if lhs == nil { + continue + } + + // determine lhs variable + var obj *Var + if ident, _ := lhs.(*ast.Ident); ident != nil { + // declare new variable + name := ident.Name + obj = NewVar(ident.Pos(), check.pkg, name, nil) + check.recordDef(ident, obj) + // _ variables don't count as new variables + if name != "_" { + vars = append(vars, obj) + } + } else { + check.errorf(lhs.Pos(), "cannot declare %s", lhs) + obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable + } + + // initialize lhs variable + x.mode = value + x.expr = lhs // we don't have a better rhs expression to use here + x.typ = rhs[i] + check.initVar(obj, &x, false) + } + + // declare variables + if len(vars) > 0 { + for _, obj := range vars { + check.declare(check.scope, nil /* recordDef already called */, obj) + } + } else { + check.error(s.TokPos, "no new variables on left side of :=") + } } else { - check.invalidAST(s.Pos(), "range clause requires index iteration variable") - // ok to continue - } - if s.Value != nil { - x.typ = val - check.assign1to1(s.Value, nil, &x, decl, -1) + // ordinary assignment + for i, lhs := range lhs { + if lhs == nil { + continue + } + x.mode = value + x.expr = lhs // we don't have a better rhs expression to use here + x.typ = rhs[i] + check.assignVar(lhs, &x) + } } - check.stmt(s.Body) + check.stmt(inner, s.Body) default: - check.errorf(s.Pos(), "invalid statement") + check.error(s.Pos(), "invalid statement") } } diff --git a/src/gosubli.me/something-borrowed/types/testdata/blank.src b/src/gosubli.me/something-borrowed/types/testdata/blank.src new file mode 100644 index 00000000..6a2507f4 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/testdata/blank.src @@ -0,0 +1,5 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package _ /* ERROR invalid package name */ diff --git a/src/gosubli.me/something-borrowed/types/testdata/builtins.src b/src/gosubli.me/something-borrowed/types/testdata/builtins.src index c08c442c..8b405c3f 100644 --- a/src/gosubli.me/something-borrowed/types/testdata/builtins.src +++ b/src/gosubli.me/something-borrowed/types/testdata/builtins.src @@ -8,62 +8,174 @@ package builtins import "unsafe" -func _append() { +func f0() {} + +func append1() { + var b byte var x int var s []byte - _0 := append /* ERROR "argument" */ () - _1 := append("foo" /* ERROR "not a typed slice" */) - _2 := append(nil /* ERROR "not a typed slice" */, s) - _3 := append(x /* ERROR "not a typed slice" */, s) - _4 := append(s) - append /* ERROR "not used" */ (s) + _ = append() // ERROR not enough arguments + _ = append("foo" /* ERROR not a slice */ ) + _ = append(nil /* ERROR not a slice */ , s) + _ = append(x /* ERROR not a slice */ , s) + _ = append(s) + append /* ERROR not used */ (s) + + _ = append(s, b) + _ = append(s, x /* ERROR cannot pass argument x */ ) + _ = append(s, s /* ERROR cannot pass argument s */ ) + _ = append(s /* ERROR can only use ... with matching parameter */ ...) + _ = append(s, b, s /* ERROR can only use ... with matching parameter */ ...) + _ = append(s, 1, 2, 3) + _ = append(s, 1, 2, 3, x /* ERROR cannot pass argument x */ , 5, 6, 6) + _ = append(s, 1, 2, s /* ERROR can only use ... with matching parameter */ ...) + _ = append([]interface{}(nil), 1, 2, "foo", x, 3.1425, false) + + type S []byte + type T string + var t T + _ = append(s, "foo" /* ERROR cannot convert */ ) + _ = append(s, "foo"...) + _ = append(S(s), "foo" /* ERROR cannot convert */ ) + _ = append(S(s), "foo"...) + _ = append(s, t /* ERROR cannot pass argument t */ ) + _ = append(s, t...) + _ = append(s, T("foo")...) + _ = append(S(s), t /* ERROR cannot pass argument t */ ) + _ = append(S(s), t...) + _ = append(S(s), T("foo")...) + _ = append([]string{}, t /* ERROR cannot pass argument t */ , "foo") + _ = append([]T{}, t, "foo") +} + +// from the spec +func append2() { + s0 := []int{0, 0} + s1 := append(s0, 2) // append a single element s1 == []int{0, 0, 2} + s2 := append(s1, 3, 5, 7) // append multiple elements s2 == []int{0, 0, 2, 3, 5, 7} + s3 := append(s2, s0...) // append a slice s3 == []int{0, 0, 2, 3, 5, 7, 0, 0} + s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0} + + var t []interface{} + t = append(t, 42, 3.1415, "foo") // t == []interface{}{42, 3.1415, "foo"} + + var b []byte + b = append(b, "bar"...) // append string contents b == []byte{'b', 'a', 'r' } + + _ = s4 } -func _cap() { +func append3() { + f1 := func() (s []int) { return } + f2 := func() (s []int, x int) { return } + f3 := func() (s []int, x, y int) { return } + f5 := func() (s []interface{}, x int, y float32, z string, b bool) { return } + ff := func() (int, float32) { return 0, 0 } + _ = append(f0 /* ERROR used as value */ ()) + _ = append(f1()) + _ = append(f2()) + _ = append(f3()) + _ = append(f5()) + _ = append(ff /* ERROR not a slice */ ()) // TODO(gri) better error message +} + +func cap1() { var a [10]bool var p *[20]int - var s []int var c chan string - _0 := cap /* ERROR "argument" */ () - _1 := cap /* ERROR "argument" */ (1, 2) - _2 := cap(42 /* ERROR "invalid" */) + _ = cap() // ERROR not enough arguments + _ = cap(1, 2) // ERROR too many arguments + _ = cap(42 /* ERROR invalid */) const _3 = cap(a) assert(_3 == 10) const _4 = cap(p) assert(_4 == 20) - _5 := cap(c) - cap /* ERROR "not used" */ (c) + _ = cap(c) + cap /* ERROR not used */ (c) // issue 4744 type T struct{ a [10]int } const _ = cap(((*T)(nil)).a) + + var s [][]byte + _ = cap(s) + _ = cap(s... /* ERROR invalid use of \.\.\. */ ) +} + +func cap2() { + f1a := func() (a [10]int) { return } + f1s := func() (s []int) { return } + f2 := func() (s []int, x int) { return } + _ = cap(f0 /* ERROR used as value */ ()) + _ = cap(f1a()) + _ = cap(f1s()) + _ = cap(f2()) // ERROR too many arguments } -func _close() { +// test cases for issue 7387 +func cap3() { + var f = func() int { return 0 } + var x = f() + const ( + _ = cap([4]int{}) + _ = cap([4]int{x}) + _ = cap /* ERROR not constant */ ([4]int{f()}) + _ = cap /* ERROR not constant */ ([4]int{cap([]int{})}) + _ = cap([4]int{cap([4]int{})}) + ) + var y float64 + var z complex128 + const ( + _ = cap([4]float64{}) + _ = cap([4]float64{y}) + _ = cap([4]float64{real(2i)}) + _ = cap /* ERROR not constant */ ([4]float64{real(z)}) + ) + var ch chan [10]int + const ( + _ = cap /* ERROR not constant */ (<-ch) + _ = cap /* ERROR not constant */ ([4]int{(<-ch)[0]}) + ) +} + +func close1() { var c chan int var r <-chan int - close /* ERROR "argument" */ () - close /* ERROR "argument" */ (1, 2) - close(42 /* ERROR "not a channel" */) - close(r /* ERROR "receive-only channel" */) + close() // ERROR not enough arguments + close(1, 2) // ERROR too many arguments + close(42 /* ERROR not a channel */) + close(r /* ERROR receive-only channel */) close(c) + _ = close /* ERROR used as value */ (c) + + var s []chan int + close(s... /* ERROR invalid use of \.\.\. */ ) } -func _complex() { +func close2() { + f1 := func() (ch chan int) { return } + f2 := func() (ch chan int, x int) { return } + close(f0 /* ERROR used as value */ ()) + close(f1()) + close(f2()) // ERROR too many arguments +} + +func complex1() { var i32 int32 var f32 float32 var f64 float64 var c64 complex64 - _ = complex /* ERROR "argument" */ () - _ = complex /* ERROR "argument" */ (1) - _ = complex(true /* ERROR "invalid argument" */ , 0) - _ = complex(i32 /* ERROR "invalid argument" */ , 0) - _ = complex("foo" /* ERROR "invalid argument" */ , 0) - _ = complex(c64 /* ERROR "invalid argument" */ , 0) - _ = complex(0, true /* ERROR "invalid argument" */ ) - _ = complex(0, i32 /* ERROR "invalid argument" */ ) - _ = complex(0, "foo" /* ERROR "invalid argument" */ ) - _ = complex(0, c64 /* ERROR "invalid argument" */ ) + var c128 complex128 + _ = complex() // ERROR not enough arguments + _ = complex(1) // ERROR not enough arguments + _ = complex(true /* ERROR invalid argument */ , 0) + _ = complex(i32 /* ERROR invalid argument */ , 0) + _ = complex("foo" /* ERROR invalid argument */ , 0) + _ = complex(c64 /* ERROR invalid argument */ , 0) + _ = complex(0, true /* ERROR invalid argument */ ) + _ = complex(0, i32 /* ERROR invalid argument */ ) + _ = complex(0, "foo" /* ERROR invalid argument */ ) + _ = complex(0, c64 /* ERROR invalid argument */ ) _ = complex(f32, f32) _ = complex(f32, 1) _ = complex(f32, 1.0) @@ -72,20 +184,67 @@ func _complex() { _ = complex(f64, 1) _ = complex(f64, 1.0) _ = complex(f64, 'a') - _ = complex(f32 /* ERROR "mismatched types" */, f64) - _ = complex(f64 /* ERROR "mismatched types" */, f32) + _ = complex(f32 /* ERROR mismatched types */ , f64) + _ = complex(f64 /* ERROR mismatched types */ , f32) _ = complex(1, 1) _ = complex(1, 1.1) _ = complex(1, 'a') - complex /* ERROR "not used" */ (1, 2) + complex /* ERROR not used */ (1, 2) + + var _ complex64 = complex(f32, f32) + var _ complex64 = complex /* ERROR cannot initialize */ (f64, f64) + + var _ complex128 = complex /* ERROR cannot initialize */ (f32, f32) + var _ complex128 = complex(f64, f64) + + // untyped constants + const _ int = complex(1, 0) + const _ float32 = complex(1, 0) + const _ complex64 = complex(1, 0) + const _ complex128 = complex(1, 0) + + const _ int = complex /* ERROR int */ (1.1, 0) + const _ float32 = complex /* ERROR float32 */ (1, 2) + + // untyped values + var s uint + _ = complex(1 /* ERROR integer */ <>8&1 + mi>>16&1 + mi>>32&1) + logSizeofUint = uint(mu>>8&1 + mu>>16&1 + mu>>32&1) + logSizeofUintptr = uint(mp>>8&1 + mp>>16&1 + mp>>32&1) +) + +const ( + minInt8 = -1<<(8< 0) + _ = assert(smallestFloat64 > 0) +) + +const ( + maxFloat32 = 1<<127 * (1<<24 - 1) / (1.0<<23) + maxFloat64 = 1<<1023 * (1<<53 - 1) / (1.0<<52) +) + +const ( + _ int8 = minInt8 /* ERROR "overflows" */ - 1 + _ int8 = minInt8 + _ int8 = maxInt8 + _ int8 = maxInt8 /* ERROR "overflows" */ + 1 + _ int8 = smallestFloat64 /* ERROR "truncated" */ + + _ = int8(minInt8 /* ERROR "cannot convert" */ - 1) + _ = int8(minInt8) + _ = int8(maxInt8) + _ = int8(maxInt8 /* ERROR "cannot convert" */ + 1) + _ = int8(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ int16 = minInt16 /* ERROR "overflows" */ - 1 + _ int16 = minInt16 + _ int16 = maxInt16 + _ int16 = maxInt16 /* ERROR "overflows" */ + 1 + _ int16 = smallestFloat64 /* ERROR "truncated" */ + + _ = int16(minInt16 /* ERROR "cannot convert" */ - 1) + _ = int16(minInt16) + _ = int16(maxInt16) + _ = int16(maxInt16 /* ERROR "cannot convert" */ + 1) + _ = int16(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ int32 = minInt32 /* ERROR "overflows" */ - 1 + _ int32 = minInt32 + _ int32 = maxInt32 + _ int32 = maxInt32 /* ERROR "overflows" */ + 1 + _ int32 = smallestFloat64 /* ERROR "truncated" */ + + _ = int32(minInt32 /* ERROR "cannot convert" */ - 1) + _ = int32(minInt32) + _ = int32(maxInt32) + _ = int32(maxInt32 /* ERROR "cannot convert" */ + 1) + _ = int32(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ int64 = minInt64 /* ERROR "overflows" */ - 1 + _ int64 = minInt64 + _ int64 = maxInt64 + _ int64 = maxInt64 /* ERROR "overflows" */ + 1 + _ int64 = smallestFloat64 /* ERROR "truncated" */ + + _ = int64(minInt64 /* ERROR "cannot convert" */ - 1) + _ = int64(minInt64) + _ = int64(maxInt64) + _ = int64(maxInt64 /* ERROR "cannot convert" */ + 1) + _ = int64(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ int = minInt /* ERROR "overflows" */ - 1 + _ int = minInt + _ int = maxInt + _ int = maxInt /* ERROR "overflows" */ + 1 + _ int = smallestFloat64 /* ERROR "truncated" */ + + _ = int(minInt /* ERROR "cannot convert" */ - 1) + _ = int(minInt) + _ = int(maxInt) + _ = int(maxInt /* ERROR "cannot convert" */ + 1) + _ = int(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ uint8 = 0 /* ERROR "overflows" */ - 1 + _ uint8 = 0 + _ uint8 = maxUint8 + _ uint8 = maxUint8 /* ERROR "overflows" */ + 1 + _ uint8 = smallestFloat64 /* ERROR "truncated" */ + + _ = uint8(0 /* ERROR "cannot convert" */ - 1) + _ = uint8(0) + _ = uint8(maxUint8) + _ = uint8(maxUint8 /* ERROR "cannot convert" */ + 1) + _ = uint8(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ uint16 = 0 /* ERROR "overflows" */ - 1 + _ uint16 = 0 + _ uint16 = maxUint16 + _ uint16 = maxUint16 /* ERROR "overflows" */ + 1 + _ uint16 = smallestFloat64 /* ERROR "truncated" */ + + _ = uint16(0 /* ERROR "cannot convert" */ - 1) + _ = uint16(0) + _ = uint16(maxUint16) + _ = uint16(maxUint16 /* ERROR "cannot convert" */ + 1) + _ = uint16(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ uint32 = 0 /* ERROR "overflows" */ - 1 + _ uint32 = 0 + _ uint32 = maxUint32 + _ uint32 = maxUint32 /* ERROR "overflows" */ + 1 + _ uint32 = smallestFloat64 /* ERROR "truncated" */ + + _ = uint32(0 /* ERROR "cannot convert" */ - 1) + _ = uint32(0) + _ = uint32(maxUint32) + _ = uint32(maxUint32 /* ERROR "cannot convert" */ + 1) + _ = uint32(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ uint64 = 0 /* ERROR "overflows" */ - 1 + _ uint64 = 0 + _ uint64 = maxUint64 + _ uint64 = maxUint64 /* ERROR "overflows" */ + 1 + _ uint64 = smallestFloat64 /* ERROR "truncated" */ + + _ = uint64(0 /* ERROR "cannot convert" */ - 1) + _ = uint64(0) + _ = uint64(maxUint64) + _ = uint64(maxUint64 /* ERROR "cannot convert" */ + 1) + _ = uint64(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ uint = 0 /* ERROR "overflows" */ - 1 + _ uint = 0 + _ uint = maxUint + _ uint = maxUint /* ERROR "overflows" */ + 1 + _ uint = smallestFloat64 /* ERROR "truncated" */ + + _ = uint(0 /* ERROR "cannot convert" */ - 1) + _ = uint(0) + _ = uint(maxUint) + _ = uint(maxUint /* ERROR "cannot convert" */ + 1) + _ = uint(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ uintptr = 0 /* ERROR "overflows" */ - 1 + _ uintptr = 0 + _ uintptr = maxUintptr + _ uintptr = maxUintptr /* ERROR "overflows" */ + 1 + _ uintptr = smallestFloat64 /* ERROR "truncated" */ + + _ = uintptr(0 /* ERROR "cannot convert" */ - 1) + _ = uintptr(0) + _ = uintptr(maxUintptr) + _ = uintptr(maxUintptr /* ERROR "cannot convert" */ + 1) + _ = uintptr(smallestFloat64 /* ERROR "cannot convert" */) +) + +const ( + _ float32 = minInt64 + _ float64 = minInt64 + _ complex64 = minInt64 + _ complex128 = minInt64 + + _ = float32(minInt64) + _ = float64(minInt64) + _ = complex64(minInt64) + _ = complex128(minInt64) +) + +const ( + _ float32 = maxUint64 + _ float64 = maxUint64 + _ complex64 = maxUint64 + _ complex128 = maxUint64 + + _ = float32(maxUint64) + _ = float64(maxUint64) + _ = complex64(maxUint64) + _ = complex128(maxUint64) +) + +// TODO(gri) find smaller deltas below + +const delta32 = maxFloat32/(1 << 23) + +const ( + _ float32 = - /* ERROR "overflow" */ (maxFloat32 + delta32) + _ float32 = -maxFloat32 + _ float32 = maxFloat32 + _ float32 = maxFloat32 /* ERROR "overflow" */ + delta32 + + _ = float32(- /* ERROR "cannot convert" */ (maxFloat32 + delta32)) + _ = float32(-maxFloat32) + _ = float32(maxFloat32) + _ = float32(maxFloat32 /* ERROR "cannot convert" */ + delta32) + + _ = assert(float32(smallestFloat32) == smallestFloat32) + _ = assert(float32(smallestFloat32/2) == 0) + _ = assert(float32(smallestFloat64) == 0) + _ = assert(float32(smallestFloat64/2) == 0) +) + +const delta64 = maxFloat64/(1 << 52) + +const ( + _ float64 = - /* ERROR "overflow" */ (maxFloat64 + delta64) + _ float64 = -maxFloat64 + _ float64 = maxFloat64 + _ float64 = maxFloat64 /* ERROR "overflow" */ + delta64 + + _ = float64(- /* ERROR "cannot convert" */ (maxFloat64 + delta64)) + _ = float64(-maxFloat64) + _ = float64(maxFloat64) + _ = float64(maxFloat64 /* ERROR "cannot convert" */ + delta64) + + _ = assert(float64(smallestFloat32) == smallestFloat32) + _ = assert(float64(smallestFloat32/2) == smallestFloat32/2) + _ = assert(float64(smallestFloat64) == smallestFloat64) + _ = assert(float64(smallestFloat64/2) == 0) +) + +const ( + _ complex64 = - /* ERROR "overflow" */ (maxFloat32 + delta32) + _ complex64 = -maxFloat32 + _ complex64 = maxFloat32 + _ complex64 = maxFloat32 /* ERROR "overflow" */ + delta32 + + _ = complex64(- /* ERROR "cannot convert" */ (maxFloat32 + delta32)) + _ = complex64(-maxFloat32) + _ = complex64(maxFloat32) + _ = complex64(maxFloat32 /* ERROR "cannot convert" */ + delta32) +) + +const ( + _ complex128 = - /* ERROR "overflow" */ (maxFloat64 + delta64) + _ complex128 = -maxFloat64 + _ complex128 = maxFloat64 + _ complex128 = maxFloat64 /* ERROR "overflow" */ + delta64 + + _ = complex128(- /* ERROR "cannot convert" */ (maxFloat64 + delta64)) + _ = complex128(-maxFloat64) + _ = complex128(maxFloat64) + _ = complex128(maxFloat64 /* ERROR "cannot convert" */ + delta64) +) + +// Initialization of typed constant and conversion are the same: +const ( + f32 = 1 + smallestFloat32 + x32 float32 = f32 + y32 = float32(f32) + _ = assert(x32 - y32 == 0) +) + +const ( + f64 = 1 + smallestFloat64 + x64 float64 = f64 + y64 = float64(f64) + _ = assert(x64 - y64 == 0) +) diff --git a/src/gosubli.me/something-borrowed/types/testdata/constdecl.src b/src/gosubli.me/something-borrowed/types/testdata/constdecl.src new file mode 100644 index 00000000..8577cb92 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/testdata/constdecl.src @@ -0,0 +1,94 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package constdecl + +import "math" + +var v int + +// Const decls must be initialized by constants. +const _ = v /* ERROR "not constant" */ +const _ = math /* ERROR "not constant" */ .Sin(0) +const _ = int /* ERROR "not an expression" */ + +func _() { + const _ = v /* ERROR "not constant" */ + const _ = math /* ERROR "not constant" */ .Sin(0) + const _ = int /* ERROR "not an expression" */ +} + +// Identifier and expression arity must match. +const _ /* ERROR "missing init expr for _" */ +const _ = 1, 2 /* ERROR "extra init expr 2" */ + +const _ /* ERROR "missing init expr for _" */ int +const _ int = 1, 2 /* ERROR "extra init expr 2" */ + +const ( + _ /* ERROR "missing init expr for _" */ + _ = 1, 2 /* ERROR "extra init expr 2" */ + + _ /* ERROR "missing init expr for _" */ int + _ int = 1, 2 /* ERROR "extra init expr 2" */ +) + +const ( + _ = 1 + _ + _, _ /* ERROR "missing init expr for _" */ + _ +) + +const ( + _, _ = 1, 2 + _, _ + _ /* ERROR "extra init expr at" */ + _, _ + _, _, _ /* ERROR "missing init expr for _" */ + _, _ +) + +func _() { + const _ /* ERROR "missing init expr for _" */ + const _ = 1, 2 /* ERROR "extra init expr 2" */ + + const _ /* ERROR "missing init expr for _" */ int + const _ int = 1, 2 /* ERROR "extra init expr 2" */ + + const ( + _ /* ERROR "missing init expr for _" */ + _ = 1, 2 /* ERROR "extra init expr 2" */ + + _ /* ERROR "missing init expr for _" */ int + _ int = 1, 2 /* ERROR "extra init expr 2" */ + ) + + const ( + _ = 1 + _ + _, _ /* ERROR "missing init expr for _" */ + _ + ) + + const ( + _, _ = 1, 2 + _, _ + _ /* ERROR "extra init expr at" */ + _, _ + _, _, _ /* ERROR "missing init expr for _" */ + _, _ + ) +} + +// Test case for constant with invalid initialization. +// Caused panic because the constant value was not set up (gri - 7/8/2014). +func _() { + const ( + x string = missing /* ERROR "undeclared name" */ + y = x + "" + ) +} + +// TODO(gri) move extra tests from testdata/const0.src into here diff --git a/src/gosubli.me/something-borrowed/types/testdata/conversions.src b/src/gosubli.me/something-borrowed/types/testdata/conversions.src index 1b151836..42514246 100644 --- a/src/gosubli.me/something-borrowed/types/testdata/conversions.src +++ b/src/gosubli.me/something-borrowed/types/testdata/conversions.src @@ -6,13 +6,83 @@ package conversions +import "unsafe" + // argument count var ( - _v0 = int /* ERROR "one argument" */ () - _v1 = int /* ERROR "one argument" */ (1, 2) + _ = int() /* ERROR "missing argument" */ + _ = int(1, 2 /* ERROR "too many arguments" */ ) ) -// -var ( - _v2 = int8(0) -) \ No newline at end of file +// numeric constant conversions are in const1.src. + +func string_conversions() { + const A = string(65) + assert(A == "A") + const E = string(-1) + assert(E == "\uFFFD") + assert(E == string(1234567890)) + + type myint int + assert(A == string(myint(65))) + + type mystring string + const _ mystring = mystring("foo") + + const _ = string(true /* ERROR "cannot convert" */ ) + const _ = string(1.2 /* ERROR "cannot convert" */ ) + const _ = string(nil /* ERROR "cannot convert" */ ) +} + +func interface_conversions() { + type E interface{} + + type I1 interface{ + m1() + } + + type I2 interface{ + m1() + m2(x int) + } + + type I3 interface{ + m1() + m2() int + } + + var e E + var i1 I1 + var i2 I2 + var i3 I3 + + _ = E(0) + _ = E(nil) + _ = E(e) + _ = E(i1) + _ = E(i2) + + _ = I1(0 /* ERROR "cannot convert" */ ) + _ = I1(nil) + _ = I1(i1) + _ = I1(e /* ERROR "cannot convert" */ ) + _ = I1(i2) + + _ = I2(nil) + _ = I2(i1 /* ERROR "cannot convert" */ ) + _ = I2(i2) + _ = I2(i3 /* ERROR "cannot convert" */ ) + + _ = I3(nil) + _ = I3(i1 /* ERROR "cannot convert" */ ) + _ = I3(i2 /* ERROR "cannot convert" */ ) + _ = I3(i3) + + // TODO(gri) add more tests, improve error message +} + +func issue6326() { + type T unsafe.Pointer + var x T + _ = uintptr(x) // see issue 6326 +} diff --git a/src/gosubli.me/something-borrowed/types/testdata/cycles.src b/src/gosubli.me/something-borrowed/types/testdata/cycles.src new file mode 100644 index 00000000..621d83c9 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/testdata/cycles.src @@ -0,0 +1,143 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cycles + +type ( + T0 int + T1 /* ERROR cycle */ T1 + T2 *T2 + + T3 /* ERROR cycle */ T4 + T4 T5 + T5 T3 + + T6 T7 + T7 *T8 + T8 T6 + + // arrays + A0 /* ERROR cycle */ [10]A0 + A1 [10]*A1 + + A2 /* ERROR cycle */ [10]A3 + A3 [10]A4 + A4 A2 + + A5 [10]A6 + A6 *A5 + + // slices + L0 []L0 + + // structs + S0 /* ERROR cycle */ struct{ _ S0 } + S1 /* ERROR cycle */ struct{ S1 } + S2 struct{ _ *S2 } + S3 struct{ *S3 } + + S4 /* ERROR cycle */ struct{ S5 } + S5 struct{ S6 } + S6 S4 + + // pointers + P0 *P0 + + // functions + F0 func(F0) + F1 func() F1 + F2 func(F2) F2 + + // interfaces + I0 /* ERROR cycle */ interface{ I0 } + + I1 interface{ I2 } + I2 interface{ I3 } + I3 /* ERROR cycle */ interface{ I1 } + + I4 interface{ f(I4) } + + // testcase for issue 5090 + I5 interface{ f(I6) } + I6 interface{ I5 } + + // maps + M0 map[M0 /* ERROR invalid map key */ ]M0 + + // channels + C0 chan C0 +) + +func _() { + type ( + t1 /* ERROR cycle */ t1 + t2 *t2 + + t3 t4 /* ERROR undeclared */ + t4 t5 /* ERROR undeclared */ + t5 t3 + + // arrays + a0 /* ERROR cycle */ [10]a0 + a1 [10]*a1 + + // slices + l0 []l0 + + // structs + s0 /* ERROR cycle */ struct{ _ s0 } + s1 /* ERROR cycle */ struct{ s1 } + s2 struct{ _ *s2 } + s3 struct{ *s3 } + + // pointers + p0 *p0 + + // functions + f0 func(f0) + f1 func() f1 + f2 func(f2) f2 + + // interfaces + i0 /* ERROR cycle */ interface{ i0 } + + // maps + m0 map[m0 /* ERROR invalid map key */ ]m0 + + // channels + c0 chan c0 + ) +} + +// test cases for issue 6667 + +type A [10]map[A /* ERROR invalid map key */ ]bool + +type S struct { + m map[S /* ERROR invalid map key */ ]bool +} + +// test cases for issue 7236 +// (cycle detection must not be dependent on starting point of resolution) + +type ( + P1 *T9 + T9 /* ERROR cycle */ T9 + + T10 /* ERROR cycle */ T10 + P2 *T10 +) + +func (T11) m() {} + +type T11 /* ERROR cycle */ struct{ T11 } + +type T12 /* ERROR cycle */ struct{ T12 } + +func (*T12) m() {} + +type ( + P3 *T13 + T13 /* ERROR cycle */ T13 +) \ No newline at end of file diff --git a/src/gosubli.me/something-borrowed/types/testdata/cycles1.src b/src/gosubli.me/something-borrowed/types/testdata/cycles1.src new file mode 100644 index 00000000..ae2b38eb --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/testdata/cycles1.src @@ -0,0 +1,77 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type ( + A interface { + a() interface { + ABC1 + } + } + B interface { + b() interface { + ABC2 + } + } + C interface { + c() interface { + ABC3 + } + } + + AB interface { + A + B + } + BC interface { + B + C + } + + ABC1 interface { + A + B + C + } + ABC2 interface { + AB + C + } + ABC3 interface { + A + BC + } +) + +var ( + x1 ABC1 + x2 ABC2 + x3 ABC3 +) + +func _() { + // all types have the same method set + x1 = x2 + x2 = x1 + + x1 = x3 + x3 = x1 + + x2 = x3 + x3 = x2 + + // all methods return the same type again + x1 = x1.a() + x1 = x1.b() + x1 = x1.c() + + x2 = x2.a() + x2 = x2.b() + x2 = x2.c() + + x3 = x3.a() + x3 = x3.b() + x3 = x3.c() +} diff --git a/src/gosubli.me/something-borrowed/types/testdata/cycles2.src b/src/gosubli.me/something-borrowed/types/testdata/cycles2.src new file mode 100644 index 00000000..345ab56e --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/testdata/cycles2.src @@ -0,0 +1,118 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +import "unsafe" + +// Test case for issue 5090 + +type t interface { + f(u) +} + +type u interface { + t +} + +func _() { + var t t + var u u + + t.f(t) + t.f(u) + + u.f(t) + u.f(u) +} + + +// Test case for issue 6589. + +type A interface { + a() interface { + AB + } +} + +type B interface { + a() interface { + AB + } +} + +type AB interface { + a() interface { + A + B /* ERROR a redeclared */ + } + b() interface { + A + B /* ERROR a redeclared */ + } +} + +var x AB +var y interface { + A + B /* ERROR a redeclared */ +} +var _ = x /* ERROR cannot compare */ == y + + +// Test case for issue 6638. + +type T interface { + m() [T /* ERROR no value */ (nil).m()[0]]int +} + +// Variations of this test case. + +type T1 interface { + m() [x1 /* ERROR no value */ .m()[0]]int +} + +var x1 T1 + +type T2 interface { + m() [len(x2 /* ERROR no value */ .m())]int +} + +var x2 T2 + +type T3 interface { + m() [unsafe.Sizeof(x3.m)]int +} + +var x3 T3 + +// The test case below should also report an error for +// the cast inside the T4 interface (like it does for the +// variable initialization). The reason why it does not is +// that inside T4, the method x4.m depends on T4 which is not +// fully set up yet. The x4.m method happens to have an empty +// signature which is why the cast is permitted. +// TODO(gri) Consider marking methods as incomplete and provide +// a better error message in that case. + +type T4 interface { + m() [unsafe.Sizeof(cast4(x4.m))]int +} + +var x4 T4 +var _ = cast4(x4 /* ERROR cannot convert */.m) + +type cast4 func() + +// This test is symmetric to the T4 case: Here the cast is +// "correct", but it doesn't work inside the T5 interface. + +type T5 interface { + m() [unsafe.Sizeof(cast5(x5 /* ERROR cannot convert */ .m))]int +} + +var x5 T5 +var _ = cast5(x5.m) + +type cast5 func() [0]int diff --git a/src/gosubli.me/something-borrowed/types/testdata/cycles3.src b/src/gosubli.me/something-borrowed/types/testdata/cycles3.src new file mode 100644 index 00000000..3da4fb57 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/testdata/cycles3.src @@ -0,0 +1,60 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +import "unsafe" + +var ( + _ A = A(nil).a().b().c().d().e().f() + _ A = A(nil).b().c().d().e().f() + _ A = A(nil).c().d().e().f() + _ A = A(nil).d().e().f() + _ A = A(nil).e().f() + _ A = A(nil).f() + _ A = A(nil) +) + +type ( + A interface { + a() B + B + } + + B interface { + b() C + C + } + + C interface { + c() D + D + } + + D interface { + d() E + E + } + + E interface { + e() F + F + } + + F interface { + f() A + } +) + +type ( + U interface { + V + } + + V interface { + v() [unsafe.Sizeof(u)]int + } +) + +var u U diff --git a/src/gosubli.me/something-borrowed/types/testdata/cycles4.src b/src/gosubli.me/something-borrowed/types/testdata/cycles4.src new file mode 100644 index 00000000..445babca --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/testdata/cycles4.src @@ -0,0 +1,110 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// Check that all methods of T are collected before +// determining the result type of m (which embeds +// all methods of T). + +type T interface { + m() interface {T} + E +} + +var _ = T.m(nil).m().e() + +type E interface { + e() int +} + +// Check that unresolved forward chains are followed +// (see also comment in resolver.go, checker.typeDecl). + +var _ = C.m(nil).m().e() + +type A B + +type B interface { + m() interface{C} + E +} + +type C A + +// Check that interface type comparison for identity +// does not recur endlessly. + +type T1 interface { + m() interface{T1} +} + +type T2 interface { + m() interface{T2} +} + +func _(x T1, y T2) { + // Checking for assignability of interfaces must check + // if all methods of x are present in y, and that they + // have identical signatures. The signatures recur via + // the result type, which is an interface that embeds + // a single method m that refers to the very interface + // that contains it. This requires cycle detection in + // identity checks for interface types. + x = y +} + +type T3 interface { + m() interface{T4} +} + +type T4 interface { + m() interface{T3} +} + +func _(x T1, y T3) { + x = y +} + +// Check that interfaces are type-checked in order of +// (embedded interface) dependencies (was issue 7158). + +var x1 T5 = T7(nil) + +type T5 interface { + T6 +} + +type T6 interface { + m() T7 +} +type T7 interface { + T5 +} + +// Actual test case from issue 7158. + +func wrapNode() Node { + return wrapElement() +} + +func wrapElement() Element { + return nil +} + +type EventTarget interface { + AddEventListener(Event) +} + +type Node interface { + EventTarget +} + +type Element interface { + Node +} + +type Event interface { + Target() Element +} diff --git a/src/gosubli.me/something-borrowed/types/testdata/decls0.src b/src/gosubli.me/something-borrowed/types/testdata/decls0.src index f0115bd9..f1df3ea7 100644 --- a/src/gosubli.me/something-borrowed/types/testdata/decls0.src +++ b/src/gosubli.me/something-borrowed/types/testdata/decls0.src @@ -6,22 +6,7 @@ package decls0 -import ( - "unsafe" - // we can have multiple blank imports (was bug) - _ "math" - _ "net/rpc" - // reflect defines a type "flag" which shows up in the gc export data - "reflect" - . "reflect" -) - -// reflect.flag must not be visible in this package -type flag int -type _ reflect /* ERROR "cannot refer to unexported" */ .flag - -// dot-imported exported objects may conflict with local objects -type Value /* ERROR "redeclared in this block by dot-import" */ struct{} +import "unsafe" const pi = 3.1415 @@ -50,17 +35,29 @@ type ( ) +// declarations of init +const _, init /* ERROR "cannot declare init" */ , _ = 0, 1, 2 +type init /* ERROR "cannot declare init" */ struct{} +var _, init /* ERROR "cannot declare init" */ int + +func init() {} +func init /* ERROR "missing function body" */ () + +func _() { const init = 0 } +func _() { type init int } +func _() { var init int; _ = init } + // invalid array types type ( iA0 [... /* ERROR "invalid use of '...'" */ ]byte iA1 [1 /* ERROR "invalid array length" */ <<100]int iA2 [- /* ERROR "invalid array length" */ 1]complex128 - iA3 ["foo" /* ERROR "invalid array length" */ ]string + iA3 ["foo" /* ERROR "must be integer" */ ]string ) type ( - p1 pi /* ERROR "no single field or method foo" */ .foo + p1 pi /* ERROR "no field or method foo" */ .foo p2 unsafe.Pointer ) @@ -96,9 +93,8 @@ type ( u, v, a /* ERROR "redeclared" */ float32 } S2 struct { - U // anonymous field - // TODO(gri) recognize double-declaration below - // U /* ERROR "redeclared" */ int + S0 // anonymous field + S0 /* ERROR "redeclared" */ int } S3 struct { x S2 @@ -137,7 +133,7 @@ type ( I2 interface { m1() } - I3 interface { /* ERROR "multiple methods named m1" */ + I3 interface { m1() m1 /* ERROR "redeclared" */ () } @@ -159,18 +155,14 @@ type ( I8 /* ERROR "illegal cycle" */ interface { I8 } - // Use I09 (rather than I9) because it appears lexically before - // I10 so that we get the illegal cycle here rather then in the - // declaration of I10. If the implementation sorts by position - // rather than name, the error message will still be here. - I09 /* ERROR "illegal cycle" */ interface { + I9 interface { I10 } I10 interface { I11 } - I11 interface { - I09 + I11 /* ERROR "illegal cycle" */ interface { + I9 } C1 chan int @@ -185,3 +177,30 @@ type ( Last int ) + +// cycles in function/method declarations +// (test cases for issue 5217 and variants) +func f1(x f1 /* ERROR "not a type" */ ) {} +func f2(x *f2 /* ERROR "not a type" */ ) {} +func f3() (x f3 /* ERROR "not a type" */ ) { return } +func f4() (x *f4 /* ERROR "not a type" */ ) { return } + +func (S0) m1(x S0 /* ERROR "field or method" */ .m1) {} +func (S0) m2(x *S0 /* ERROR "field or method" */ .m2) {} +func (S0) m3() (x S0 /* ERROR "field or method" */ .m3) { return } +func (S0) m4() (x *S0 /* ERROR "field or method" */ .m4) { return } + +// interfaces may not have any blank methods +type BlankI interface { + _ /* ERROR "invalid method name" */ () + _ /* ERROR "invalid method name" */ (float32) int + m() +} + +// non-interface types may have multiple blank methods +type BlankT struct{} + +func (BlankT) _() {} +func (BlankT) _(int) {} +func (BlankT) _() int { return 0 } +func (BlankT) _(int) int { return 0} diff --git a/src/gosubli.me/something-borrowed/types/testdata/decls1.src b/src/gosubli.me/something-borrowed/types/testdata/decls1.src index 2251f457..7855e461 100644 --- a/src/gosubli.me/something-borrowed/types/testdata/decls1.src +++ b/src/gosubli.me/something-borrowed/types/testdata/decls1.src @@ -37,7 +37,7 @@ var ( s4 = s + t s5 = s /* ERROR "invalid operation" */ / t s6 = array[t1] - s7 = array[x /* ERROR "index" */] + s7 = array[x /* ERROR "integer" */] s8 = &a s10 = &42 /* ERROR "cannot take address" */ s11 = &v @@ -48,27 +48,28 @@ var ( s19 = s1 /* ERROR "cannot call" */ () s20 = f0 /* ERROR "no value" */ () s21 = f6(1, s1, i) - s22 = f6(1, s1, uu /* ERROR "cannot assign" */ ) + s22 = f6(1, s1, uu /* ERROR "cannot pass argument" */ ) t1 int = i + j t2 int = i /* ERROR "mismatched types" */ + x - t3 int = c /* ERROR "cannot assign" */ + d + t3 int = c /* ERROR "cannot initialize" */ + d t4 string = s + t t5 string = s /* ERROR "invalid operation" */ / t t6 byte = array[t1] - t7 byte = array[x /* ERROR "index" */] - t8 *int = & /* ERROR "cannot assign" */ a + t7 byte = array[x /* ERROR "must be integer" */] + t8 *int = & /* ERROR "cannot initialize" */ a t10 *int = &42 /* ERROR "cannot take address" */ t11 *complex64 = &v t12 complex64 = -(u + *t11) / *&v t13 int = a /* ERROR "shifted operand" */ << d t14 int = i << j /* ERROR "must be unsigned" */ t15 math /* ERROR "not in selector" */ - t16 math /* ERROR "unexported" */ .xxx + t16 math /* ERROR "not declared" */ .xxx t17 math /* ERROR "not a type" */ .Pi t18 float64 = math.Pi * 10.0 t19 int = t1 /* ERROR "cannot call" */ () t20 int = f0 /* ERROR "no value" */ () + t21 int = a /* ERROR "cannot initialize" */ ) // Various more complex expressions @@ -77,7 +78,7 @@ var ( u2 = iface.([]int) u3 = iface.(a /* ERROR "not a type" */ ) u4, ok = iface.(int) - u5 /* ERROR "assignment count mismatch" */ , ok2, ok3 = iface.(int) + u5, ok2, ok3 = iface /* ERROR "assignment count mismatch" */ .(int) ) // Constant expression initializations @@ -100,18 +101,30 @@ var ( // Multiple assignment expressions var ( m1a, m1b = 1, 2 - m2a /* ERROR "assignment count mismatch" */ , m2b, m2c = 1, 2 - m3a /* ERROR "assignment count mismatch" */ , m3b = 1, 2, 3 + m2a, m2b, m2c /* ERROR "missing init expr for m2c" */ = 1, 2 + m3a, m3b = 1, 2, 3 /* ERROR "extra init expr 3" */ ) +func _() { + var ( + m1a, m1b = 1, 2 + m2a, m2b, m2c /* ERROR "missing init expr for m2c" */ = 1, 2 + m3a, m3b = 1, 2, 3 /* ERROR "extra init expr 3" */ + ) + + _, _ = m1a, m1b + _, _, _ = m2a, m2b, m2c + _, _ = m3a, m3b +} + // Declaration of parameters and results func f0() {} func f1(a /* ERROR "not a type" */) {} func f2(a, b, c d /* ERROR "not a type" */) {} -func f3() int {} -func f4() a /* ERROR "not a type" */ {} -func f5() (a, b, c d /* ERROR "not a type" */) {} +func f3() int { return 0 } +func f4() a /* ERROR "not a type" */ { return 0 } +func f5() (a, b, c d /* ERROR "not a type" */) { return } func f6(a, b, c int) complex128 { return 0 } @@ -123,10 +136,9 @@ func (*T) m1() {} func (x T) m2() {} func (x *T) m3() {} - // Initialization functions func init() {} func /* ERROR "no arguments and no return values" */ init(int) {} func /* ERROR "no arguments and no return values" */ init() int { return 0 } -func /* ERROR "no arguments and no return values" */ init(int) int {} +func /* ERROR "no arguments and no return values" */ init(int) int { return 0 } func (T) init(int) int { return 0 } diff --git a/src/gosubli.me/something-borrowed/types/testdata/decls2a.src b/src/gosubli.me/something-borrowed/types/testdata/decls2a.src index 3867be73..bdbecd9d 100644 --- a/src/gosubli.me/something-borrowed/types/testdata/decls2a.src +++ b/src/gosubli.me/something-borrowed/types/testdata/decls2a.src @@ -7,6 +7,7 @@ package decls2 import "time" +import "unsafe" // T1 declared before its methods. type T1 struct{ @@ -14,9 +15,30 @@ type T1 struct{ } func (T1) m() {} -func (T1) m /* ERROR "redeclared" */ () {} +func (T1) m /* ERROR "already declared" */ () {} func (x *T1) f /* ERROR "field and method" */ () {} +// Conflict between embedded field and method name, +// with the embedded field being a basic type. +type T1b struct { + int +} + +func (T1b) int /* ERROR "field and method" */ () {} + +type T1c struct { + time.Time +} + +func (T1c) Time /* ERROR "field and method" */ () int { return 0 } + +// Disabled for now: LookupFieldOrMethod will find Pointer even though +// it's double-declared (it would cost extra in the common case to verify +// this). But the MethodSet computation will not find it due to the name +// collision caused by the double-declaration, leading to an internal +// inconsistency while we are verifying one computation against the other. +// var _ = T1c{}.Pointer + // T2's method declared before the type. func (*T2) f /* ERROR "field and method" */ () {} @@ -28,18 +50,16 @@ type T2 struct { func (undeclared /* ERROR "undeclared" */) m() {} func (x *undeclared /* ERROR "undeclared" */) m() {} -// TODO(gri) try to get rid of double error reporting here func (pi /* ERROR "not a type" */) m1() {} func (x pi /* ERROR "not a type" */) m2() {} -func (x *pi /* ERROR "not a type" */ ) m3() {} // TODO(gri) not closing the last /* comment crashes the system +func (x *pi /* ERROR "not a type" */ ) m3() {} // Blank types. type _ struct { m int } type _ struct { m int } -// TODO(gri) blank idents not fully checked - disabled for now -// func (_ /* ERROR "cannot use _" */) m() {} -// func (_ /* ERROR "cannot use _" */) m() {} +func (_ /* ERROR "cannot use _" */) m() {} +func m(_ /* ERROR "cannot use _" */) {} // Methods with receiver base type declared in another file. func (T3) m1() {} @@ -57,11 +77,35 @@ type T5 interface { m() int } -func (T5 /* ERROR "invalid receiver" */) m1() {} -func (T5 /* ERROR "invalid receiver" */) m2() {} +func (T5 /* ERROR "invalid receiver" */ ) m1() {} +func (T5 /* ERROR "invalid receiver" */ ) m2() {} + +// Methods associated with a named pointer type. +type ptr *int +func (ptr /* ERROR "invalid receiver" */ ) _() {} +func (* /* ERROR "invalid receiver" */ ptr) _() {} + +// Methods with zero or multiple receivers. +func ( /* ERROR "missing receiver" */ ) _() {} +func (T3, * /* ERROR "exactly one receiver" */ T3) _() {} +func (T3, T3, T3 /* ERROR "exactly one receiver" */ ) _() {} +func (a, b /* ERROR "exactly one receiver" */ T3) _() {} +func (a, b, c /* ERROR "exactly one receiver" */ T3) _() {} // Methods associated with non-local or unnamed types. -func (int /* ERROR "non-local type" */ ) m() {} -func ([ /* ERROR "expected" */ ]int) m() {} -func (time /* ERROR "expected" */ .Time) m() {} -func (x interface /* ERROR "expected" */ {}) m() {} +func (int /* ERROR "invalid receiver" */ ) m() {} +func ([ /* ERROR "invalid receiver" */ ]int) m() {} +func (time /* ERROR "invalid receiver" */ .Time) m() {} +func (* /* ERROR "invalid receiver" */ time.Time) m() {} +func (x /* ERROR "invalid receiver" */ interface{}) m() {} + +// Unsafe.Pointer is treated like a pointer when used as receiver type. +type UP unsafe.Pointer +func (UP /* ERROR "invalid" */ ) m1() {} +func (* /* ERROR "invalid" */ UP) m2() {} + +// Double declarations across package files +const c_double = 0 +type t_double int +var v_double int +func f_double() {} diff --git a/src/gosubli.me/something-borrowed/types/testdata/decls2b.src b/src/gosubli.me/something-borrowed/types/testdata/decls2b.src index c7f9ddf0..e7bc3947 100644 --- a/src/gosubli.me/something-borrowed/types/testdata/decls2b.src +++ b/src/gosubli.me/something-borrowed/types/testdata/decls2b.src @@ -6,9 +6,12 @@ package decls2 +import "io" + const pi = 3.1415 -func (T1) m /* ERROR "redeclared" */ () {} +func (T1) m /* ERROR "already declared" */ () {} +func (T2) m(io.Writer) {} type T3 struct { f *T3 @@ -25,4 +28,38 @@ func (t *T6) m1() int { func f() { var t *T6 t.m1() -} \ No newline at end of file +} + +// Double declarations across package files +const c_double /* ERROR "redeclared" */ = 0 +type t_double /* ERROR "redeclared" */ int +var v_double /* ERROR "redeclared" */ int +func f_double /* ERROR "redeclared" */ () {} + +// Blank methods need to be type-checked. +// Verify by checking that errors are reported. +func (T /* ERROR "undeclared" */ ) _() {} +func (T1) _(undeclared /* ERROR "undeclared" */ ) {} +func (T1) _() int { return "foo" /* ERROR "cannot convert" */ } + +// Methods with undeclared receiver type can still be checked. +// Verify by checking that errors are reported. +func (Foo /* ERROR "undeclared" */ ) m() {} +func (Foo /* ERROR "undeclared" */ ) m(undeclared /* ERROR "undeclared" */ ) {} +func (Foo /* ERROR "undeclared" */ ) m() int { return "foo" /* ERROR "cannot convert" */ } + +func (Foo /* ERROR "undeclared" */ ) _() {} +func (Foo /* ERROR "undeclared" */ ) _(undeclared /* ERROR "undeclared" */ ) {} +func (Foo /* ERROR "undeclared" */ ) _() int { return "foo" /* ERROR "cannot convert" */ } + +// Receiver declarations are regular parameter lists; +// receiver types may use parentheses, and the list +// may have a trailing comma. +type T7 struct {} + +func (T7) m1() {} +func ((T7)) m2() {} +func ((*T7)) m3() {} +func (x *(T7),) m4() {} +func (x (*(T7)),) m5() {} +func (x ((*((T7)))),) m6() {} diff --git a/src/gosubli.me/something-borrowed/types/testdata/decls3.src b/src/gosubli.me/something-borrowed/types/testdata/decls3.src index 6aa9f90e..80d2bc8f 100644 --- a/src/gosubli.me/something-borrowed/types/testdata/decls3.src +++ b/src/gosubli.me/something-borrowed/types/testdata/decls3.src @@ -6,6 +6,9 @@ package decls3 +import "unsafe" +import "fmt" + // fields with the same name at the same level cancel each other out func _() { @@ -16,7 +19,7 @@ func _() { ) var t T3 - _ = t /* ERROR "no single field or method" */ .X + _ = t /* ERROR "ambiguous selector" */ .X } func _() { @@ -28,7 +31,7 @@ func _() { ) var t T4 - _ = t /* ERROR "no single field or method" */ .X + _ = t /* ERROR "ambiguous selector" */ .X } func issue4355() { @@ -41,7 +44,17 @@ func issue4355() { ) var t T5 - _ = t /* ERROR "no single field or method" */ .X + _ = t /* ERROR "ambiguous selector" */ .X +} + +func _() { + type State int + type A struct{ State } + type B struct{ fmt.State } + type T struct{ A; B } + + var t T + _ = t /* ERROR "ambiguous selector" */ .State } // Embedded fields can be predeclared types. @@ -66,6 +79,49 @@ func _() { _ = y.f } +// Restrictions on embedded field types. + +func _() { + type I1 interface{} + type I2 interface{} + type P1 *int + type P2 *int + type UP unsafe.Pointer + + type T1 struct { + I1 + * /* ERROR "cannot be a pointer to an interface" */ I2 + * /* ERROR "cannot be a pointer to an interface" */ error + P1 /* ERROR "cannot be a pointer" */ + * /* ERROR "cannot be a pointer" */ P2 + } + + // unsafe.Pointers are treated like regular pointers when embedded + type T2 struct { + unsafe /* ERROR "cannot be unsafe.Pointer" */ .Pointer + */* ERROR "cannot be unsafe.Pointer" */ unsafe.Pointer + UP /* ERROR "cannot be unsafe.Pointer" */ + * /* ERROR "cannot be unsafe.Pointer" */ UP + } +} + +// Named types that are pointers. + +type S struct{ x int } +func (*S) m() {} +type P *S + +func _() { + var s *S + _ = s.x + _ = s.m + + var p P + _ = p.x + _ = p /* ERROR "no field or method" */ .m + _ = P /* ERROR "no field or method" */ .m +} + // Borrowed from the FieldByName test cases in reflect/all_test.go. type D1 struct { @@ -153,9 +209,9 @@ type S13 struct { } func _() { - _ = struct /* ERROR "no single field or method" */ {}{}.Foo + _ = struct /* ERROR "no field or method" */ {}{}.Foo _ = S0{}.A - _ = S0 /* ERROR "no single field or method" */ {}.D + _ = S0 /* ERROR "no field or method" */ {}.D _ = S1{}.A _ = S1{}.B _ = S1{}.S0 @@ -164,17 +220,17 @@ func _() { _ = S2{}.S1 _ = S2{}.B _ = S2{}.C - _ = S2 /* ERROR "no single field or method" */ {}.D - _ = S3 /* ERROR "no single field or method" */ {}.S1 + _ = S2 /* ERROR "no field or method" */ {}.D + _ = S3 /* ERROR "ambiguous selector" */ {}.S1 _ = S3{}.A - _ = S3 /* ERROR "no single field or method" */ {}.B + _ = S3 /* ERROR "ambiguous selector" */ {}.B _ = S3{}.D _ = S3{}.E _ = S4{}.A - _ = S4 /* ERROR "no single field or method" */ {}.B - _ = S5 /* ERROR "no single field or method" */ {}.X + _ = S4 /* ERROR "no field or method" */ {}.B + _ = S5 /* ERROR "ambiguous selector" */ {}.X _ = S5{}.Y - _ = S10 /* ERROR "no single field or method" */ {}.X + _ = S10 /* ERROR "ambiguous selector" */ {}.X _ = S10{}.Y } @@ -250,4 +306,4 @@ type R22 R21 type R23 R21 type R24 R21 -var _ = R0 /* ERROR "no single field or method" */ {}.X \ No newline at end of file +var _ = R0 /* ERROR "ambiguous selector" */ {}.X \ No newline at end of file diff --git a/src/gosubli.me/something-borrowed/types/testdata/errors.src b/src/gosubli.me/something-borrowed/types/testdata/errors.src new file mode 100644 index 00000000..45bd45a1 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/testdata/errors.src @@ -0,0 +1,55 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package errors + +// Testing precise operand formatting in error messages +// (matching messages are regular expressions, hence the \'s). +func f(x int, m map[string]int) { + // no values + _ = f /* ERROR "f\(0, m\) \(no value\) used as value" */ (0, m) + + // built-ins + _ = println /* ERROR "println \(built-in\) must be called" */ + + // types + _ = complex128 /* ERROR "complex128 \(type\) is not an expression" */ + + // constants + const c1 = 991 + const c2 float32 = 0.5 + 0 /* ERROR "0 \(untyped int constant\) is not used" */ + c1 /* ERROR "c1 \(untyped int constant 991\) is not used" */ + c2 /* ERROR "c2 \(constant 1/2 of type float32\) is not used" */ + c1 /* ERROR "c1 \+ c2 \(constant 1983/2 of type float32\) is not used" */ + c2 + + // variables + x /* ERROR "x \(variable of type int\) is not used" */ + + // values + x /* ERROR "x != x \(untyped bool value\) is not used" */ != x + x /* ERROR "x \+ x \(value of type int\) is not used" */ + x + + // value, ok's + const s = "foo" + m /* ERROR "m\[s\] \(map index expression of type int\) is not used" */ [s] +} + +// Valid ERROR comments can have a variety of forms. +func _() { + 0 /* ERROR "0 .* is not used" */ + 0 /* ERROR 0 .* is not used */ + 0 // ERROR "0 .* is not used" + 0 // ERROR 0 .* is not used +} + +// Don't report spurious errors as a consequence of earlier errors. +// Add more tests as needed. +func _() { + if err := foo /* ERROR undeclared */ (); err != nil /* no error here */ {} +} + +// Use unqualified names for package-local objects. +type T struct{} +var _ int = T /* ERROR value of type T */ {} // use T in error message rather then errors.T diff --git a/src/gosubli.me/something-borrowed/types/testdata/expr0.src b/src/gosubli.me/something-borrowed/types/testdata/expr0.src index 8d057f63..5afb5d73 100644 --- a/src/gosubli.me/something-borrowed/types/testdata/expr0.src +++ b/src/gosubli.me/something-borrowed/types/testdata/expr0.src @@ -6,6 +6,8 @@ package expr0 +type mybool bool + var ( // bool b0 = true @@ -21,6 +23,7 @@ var ( b10 = &true /* ERROR "cannot take address" */ b11 = &b0 b12 = <-b0 /* ERROR "cannot receive" */ + b13 = & & /* ERROR "cannot take address" */ b0 // int i0 = 1 @@ -71,7 +74,7 @@ var ( f2 = +1 f3 = +f0 f4 float64 = +1 - f5 float64 = +f4 /* ERROR not defined */ + f5 float64 = +f4 f6 = -1 f7 = -f0 f8 float64 = -1 @@ -92,7 +95,7 @@ var ( c2 = +1 c3 = +c0 c4 complex128 = +1 - c5 complex128 = +c4 /* ERROR not defined */ + c5 complex128 = +c4 c6 = -1 c7 = -c0 c8 complex128 = -1 @@ -113,10 +116,10 @@ var ( s2 = -s0 /* ERROR "not defined" */ s3 = !s0 /* ERROR "not defined" */ s4 = ^s0 /* ERROR "not defined" */ - s5 = *s4 /* ERROR "cannot indirect" */ + s5 = *s4 s6 = &s4 s7 = *s6 - s8 = <-s7 /* ERROR "cannot receive" */ + s8 = <-s7 // channel ch chan int @@ -132,6 +135,10 @@ var ( ch7 = <-ch ch8 = <-rc ch9 = <-sc /* ERROR "cannot receive" */ + ch10, ok = <-ch + // ok is of type bool + ch11, myok = <-ch + _ mybool = myok /* ERROR "cannot initialize" */ ) // address of composite literals diff --git a/src/gosubli.me/something-borrowed/types/testdata/expr2.src b/src/gosubli.me/something-borrowed/types/testdata/expr2.src index 674be400..31dc5f02 100644 --- a/src/gosubli.me/something-borrowed/types/testdata/expr2.src +++ b/src/gosubli.me/something-borrowed/types/testdata/expr2.src @@ -14,10 +14,234 @@ func _bool() { var b bool var x, y float32 b = x < y + _ = b _ = struct{b bool}{x < y} } // corner cases var ( v0 = nil /* ERROR "cannot compare" */ == nil -) \ No newline at end of file +) + +func arrays() { + // basics + var a, b [10]int + _ = a == b + _ = a != b + _ = a /* ERROR < not defined */ < b + _ = a == nil /* ERROR cannot convert */ + + type C [10]int + var c C + _ = a == c + + type D [10]int + var d D + _ = c /* ERROR mismatched types */ == d + + var e [10]func() int + _ = e /* ERROR == not defined */ == e +} + +func structs() { + // basics + var s, t struct { + x int + a [10]float32 + _ bool + } + _ = s == t + _ = s != t + _ = s /* ERROR < not defined */ < t + _ = s == nil /* ERROR cannot convert */ + + type S struct { + x int + a [10]float32 + _ bool + } + type T struct { + x int + a [10]float32 + _ bool + } + var ss S + var tt T + _ = s == ss + _ = ss /* ERROR mismatched types */ == tt + + var u struct { + x int + a [10]map[string]int + } + _ = u /* ERROR cannot compare */ == u +} + +func pointers() { + // nil + _ = nil /* ERROR == not defined */ == nil + _ = nil /* ERROR != not defined */ != nil + _ = nil /* ERROR < not defined */ < nil + _ = nil /* ERROR <= not defined */ <= nil + _ = nil /* ERROR > not defined */ > nil + _ = nil /* ERROR >= not defined */ >= nil + + // basics + var p, q *int + _ = p == q + _ = p != q + + _ = p == nil + _ = p != nil + _ = nil == q + _ = nil != q + + _ = p /* ERROR < not defined */ < q + _ = p /* ERROR <= not defined */ <= q + _ = p /* ERROR > not defined */ > q + _ = p /* ERROR >= not defined */ >= q + + // various element types + type ( + S1 struct{} + S2 struct{} + P1 *S1 + P2 *S2 + ) + var ( + ps1 *S1 + ps2 *S2 + p1 P1 + p2 P2 + ) + _ = ps1 == ps1 + _ = ps1 /* ERROR mismatched types */ == ps2 + _ = ps2 /* ERROR mismatched types */ == ps1 + + _ = p1 == p1 + _ = p1 /* ERROR mismatched types */ == p2 + + _ = p1 == ps1 +} + +func channels() { + // basics + var c, d chan int + _ = c == d + _ = c != d + _ = c == nil + _ = c /* ERROR < not defined */ < d + + // various element types (named types) + type ( + C1 chan int + C1r <-chan int + C1s chan<- int + C2 chan float32 + ) + var ( + c1 C1 + c1r C1r + c1s C1s + c1a chan int + c2 C2 + ) + _ = c1 == c1 + _ = c1 /* ERROR mismatched types */ == c1r + _ = c1 /* ERROR mismatched types */ == c1s + _ = c1r /* ERROR mismatched types */ == c1s + _ = c1 == c1a + _ = c1a == c1 + _ = c1 /* ERROR mismatched types */ == c2 + _ = c1a /* ERROR mismatched types */ == c2 + + // various element types (unnamed types) + var ( + d1 chan int + d1r <-chan int + d1s chan<- int + d1a chan<- int + d2 chan float32 + ) + _ = d1 == d1 + _ = d1 == d1r + _ = d1 == d1s + _ = d1r /* ERROR mismatched types */ == d1s + _ = d1 == d1a + _ = d1a == d1 + _ = d1 /* ERROR mismatched types */ == d2 + _ = d1a /* ERROR mismatched types */ == d2 +} + +// for interfaces test +type S1 struct{} +type S11 struct{} +type S2 struct{} +func (*S1) m() int +func (*S11) m() int +func (*S11) n() +func (*S2) m() float32 + +func interfaces() { + // basics + var i, j interface{ m() int } + _ = i == j + _ = i != j + _ = i == nil + _ = i /* ERROR < not defined */ < j + + // various interfaces + var ii interface { m() int; n() } + var k interface { m() float32 } + _ = i == ii + _ = i /* ERROR mismatched types */ == k + + // interfaces vs values + var s1 S1 + var s11 S11 + var s2 S2 + + _ = i == 0 /* ERROR cannot convert */ + _ = i /* ERROR mismatched types */ == s1 + _ = i == &s1 + _ = i == &s11 + + _ = i /* ERROR mismatched types */ == s2 + _ = i /* ERROR mismatched types */ == &s2 +} + +func slices() { + // basics + var s []int + _ = s == nil + _ = s != nil + _ = s /* ERROR < not defined */ < nil + + // slices are not otherwise comparable + _ = s /* ERROR == not defined */ == s + _ = s /* ERROR < not defined */ < s +} + +func maps() { + // basics + var m map[string]int + _ = m == nil + _ = m != nil + _ = m /* ERROR < not defined */ < nil + + // maps are not otherwise comparable + _ = m /* ERROR == not defined */ == m + _ = m /* ERROR < not defined */ < m +} + +func funcs() { + // basics + var f func(int) float32 + _ = f == nil + _ = f != nil + _ = f /* ERROR < not defined */ < nil + + // funcs are not otherwise comparable + _ = f /* ERROR == not defined */ == f + _ = f /* ERROR < not defined */ < f +} diff --git a/src/gosubli.me/something-borrowed/types/testdata/expr3.src b/src/gosubli.me/something-borrowed/types/testdata/expr3.src index 1fae2640..50ae7c4c 100644 --- a/src/gosubli.me/something-borrowed/types/testdata/expr3.src +++ b/src/gosubli.me/something-borrowed/types/testdata/expr3.src @@ -2,89 +2,67 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// various expressions - package expr3 -func shifts1() { - var ( - i0 int - u0 uint - ) - - var ( - v0 = 1<<0 - v1 = 1< 10: + if x == 11 { + break L3 + } + if x == 12 { + continue L3 /* ERROR "invalid continue label L3" */ + } + goto L3 + } + +L4: + if true { + if x == 13 { + break L4 /* ERROR "invalid break label L4" */ + } + if x == 14 { + continue L4 /* ERROR "invalid continue label L4" */ + } + if x == 15 { + goto L4 + } + } + +L5: + f1() + if x == 16 { + break L5 /* ERROR "invalid break label L5" */ + } + if x == 17 { + continue L5 /* ERROR "invalid continue label L5" */ + } + if x == 18 { + goto L5 + } + + for { + if x == 19 { + break L1 /* ERROR "invalid break label L1" */ + } + if x == 20 { + continue L1 /* ERROR "invalid continue label L1" */ + } + if x == 21 { + goto L1 + } + } +} + +// Additional tests not in the original files. + +func f2() { +L1 /* ERROR "label L1 declared but not used" */ : + if x == 0 { + for { + continue L1 /* ERROR "invalid continue label L1" */ + } + } +} + +func f3() { +L1: +L2: +L3: + for { + break L1 /* ERROR "invalid break label L1" */ + break L2 /* ERROR "invalid break label L2" */ + break L3 + continue L1 /* ERROR "invalid continue label L1" */ + continue L2 /* ERROR "invalid continue label L2" */ + continue L3 + goto L1 + goto L2 + goto L3 + } +} + +// Blank labels are never declared. + +func f4() { +_: +_: // multiple blank labels are ok + goto _ /* ERROR "label _ not declared" */ +} + +func f5() { +_: + for { + break _ /* ERROR "invalid break label _" */ + continue _ /* ERROR "invalid continue label _" */ + } +} + +func f6() { +_: + switch { + default: + break _ /* ERROR "invalid break label _" */ + } +} diff --git a/src/gosubli.me/something-borrowed/types/testdata/methodsets.src b/src/gosubli.me/something-borrowed/types/testdata/methodsets.src new file mode 100644 index 00000000..89211468 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/testdata/methodsets.src @@ -0,0 +1,214 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package methodsets + +type T0 struct {} + +func (T0) v0() {} +func (*T0) p0() {} + +type T1 struct {} // like T0 with different method names + +func (T1) v1() {} +func (*T1) p1() {} + +type T2 interface { + v2() + p2() +} + +type T3 struct { + T0 + *T1 + T2 +} + +// Method expressions +func _() { + var ( + _ func(T0) = T0.v0 + _ = T0 /* ERROR "not in method set" */ .p0 + + _ func (*T0) = (*T0).v0 + _ func (*T0) = (*T0).p0 + + // T1 is like T0 + + _ func(T2) = T2.v2 + _ func(T2) = T2.p2 + + _ func(T3) = T3.v0 + _ func(T3) = T3 /* ERROR "not in method set" */ .p0 + _ func(T3) = T3.v1 + _ func(T3) = T3.p1 + _ func(T3) = T3.v2 + _ func(T3) = T3.p2 + + _ func(*T3) = (*T3).v0 + _ func(*T3) = (*T3).p0 + _ func(*T3) = (*T3).v1 + _ func(*T3) = (*T3).p1 + _ func(*T3) = (*T3).v2 + _ func(*T3) = (*T3).p2 + ) +} + +// Method values with addressable receivers +func _() { + var ( + v0 T0 + _ func() = v0.v0 + _ func() = v0.p0 + ) + + var ( + p0 *T0 + _ func() = p0.v0 + _ func() = p0.p0 + ) + + // T1 is like T0 + + var ( + v2 T2 + _ func() = v2.v2 + _ func() = v2.p2 + ) + + var ( + v4 T3 + _ func() = v4.v0 + _ func() = v4.p0 + _ func() = v4.v1 + _ func() = v4.p1 + _ func() = v4.v2 + _ func() = v4.p2 + ) + + var ( + p4 *T3 + _ func() = p4.v0 + _ func() = p4.p0 + _ func() = p4.v1 + _ func() = p4.p1 + _ func() = p4.v2 + _ func() = p4.p2 + ) +} + +// Method calls with addressable receivers +func _() { + var v0 T0 + v0.v0() + v0.p0() + + var p0 *T0 + p0.v0() + p0.p0() + + // T1 is like T0 + + var v2 T2 + v2.v2() + v2.p2() + + var v4 T3 + v4.v0() + v4.p0() + v4.v1() + v4.p1() + v4.v2() + v4.p2() + + var p4 *T3 + p4.v0() + p4.p0() + p4.v1() + p4.p1() + p4.v2() + p4.p2() +} + +// Method values with value receivers +func _() { + var ( + _ func() = T0{}.v0 + _ func() = T0 /* ERROR "not in method set" */ {}.p0 + + _ func() = (&T0{}).v0 + _ func() = (&T0{}).p0 + + // T1 is like T0 + + // no values for T2 + + _ func() = T3{}.v0 + _ func() = T3 /* ERROR "not in method set" */ {}.p0 + _ func() = T3{}.v1 + _ func() = T3{}.p1 + _ func() = T3{}.v2 + _ func() = T3{}.p2 + + _ func() = (&T3{}).v0 + _ func() = (&T3{}).p0 + _ func() = (&T3{}).v1 + _ func() = (&T3{}).p1 + _ func() = (&T3{}).v2 + _ func() = (&T3{}).p2 + ) +} + +// Method calls with value receivers +func _() { + T0{}.v0() + T0 /* ERROR "not in method set" */ {}.p0() + + (&T0{}).v0() + (&T0{}).p0() + + // T1 is like T0 + + // no values for T2 + + T3{}.v0() + T3 /* ERROR "not in method set" */ {}.p0() + T3{}.v1() + T3{}.p1() + T3{}.v2() + T3{}.p2() + + (&T3{}).v0() + (&T3{}).p0() + (&T3{}).v1() + (&T3{}).p1() + (&T3{}).v2() + (&T3{}).p2() +} + +// *T has no methods if T is an interface type +func issue5918() { + var ( + err error + _ = err.Error() + _ func() string = err.Error + _ func(error) string = error.Error + + perr = &err + _ = perr /* ERROR "no field or method" */ .Error() + _ func() string = perr /* ERROR "no field or method" */ .Error + _ func(*error) string = ( /* ERROR "no field or method" */ *error).Error + ) + + type T *interface{ m() int } + var ( + x T + _ = (*x).m() + _ = (*x).m + + _ = x /* ERROR "no field or method" */ .m() + _ = x /* ERROR "no field or method" */ .m + _ = T /* ERROR "no field or method" */ .m + ) +} diff --git a/src/gosubli.me/something-borrowed/types/testdata/shifts.src b/src/gosubli.me/something-borrowed/types/testdata/shifts.src new file mode 100644 index 00000000..7f8ed06f --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/testdata/shifts.src @@ -0,0 +1,321 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package shifts + +func shifts0() { + // basic constant shifts + const ( + s = 10 + _ = 0<<0 + _ = 1<> s) + _, _, _ = u, v, x +} + +func shifts4() { + // shifts in comparisons w/ untyped operands + var s uint + + _ = 1< 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 anonymous fields). +func (s *Struct) NumFields() int { return len(s.fields) } + +// Field returns the i'th field for 0 <= i < NumFields(). +func (s *Struct) Field(i int) *Var { return s.fields[i] } + +// Tag returns the i'th field tag for 0 <= i < NumFields(). +func (s *Struct) Tag(i int) string { + if i < len(s.tags) { + return s.tags[i] + } + return "" +} + +// A Pointer represents a pointer type. +type Pointer struct { + base Type // element type +} + +// NewPointer returns a new pointer type for the given element (base) type. +func NewPointer(elem Type) *Pointer { return &Pointer{base: elem} } + +// Elem returns the element type for the given pointer p. +func (p *Pointer) Elem() Type { return p.base } + +// A Tuple represents an ordered list of variables; a nil *Tuple is a valid (empty) tuple. +// Tuples are used as components of signatures and to represent the type of multiple +// assignments; they are not first class types of Go. +type Tuple struct { + vars []*Var +} + +// NewTuple returns a new tuple for the given variables. +func NewTuple(x ...*Var) *Tuple { + if len(x) > 0 { + return &Tuple{x} + } + return nil +} + +// Len returns the number variables of tuple t. +func (t *Tuple) Len() int { + if t != nil { + return len(t.vars) + } + return 0 +} + +// At returns the i'th variable of tuple t. +func (t *Tuple) At(i int) *Var { return t.vars[i] } + +// A Signature represents a (non-builtin) function or method type. +type Signature struct { + scope *Scope // function scope, always present + recv *Var // nil if not a method + params *Tuple // (incoming) parameters from left to right; or nil + results *Tuple // (outgoing) results from left to right; or nil + variadic bool // true if the last parameter's type is of the form ...T (or string, for append built-in only) +} + +// NewSignature returns a new function type for the given receiver, parameters, +// and results, either of which may be nil. If variadic is set, the function +// is variadic, it must have at least one parameter, and the last parameter +// must be of unnamed slice type. +func NewSignature(scope *Scope, recv *Var, params, results *Tuple, variadic bool) *Signature { + // TODO(gri) Should we rely on the correct (non-nil) incoming scope + // or should this function allocate and populate a scope? + 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{scope, recv, params, results, variadic} +} + +// Recv returns the receiver of signature s (if a method), or nil if a +// function. +// +// For an abstract method, Recv returns the enclosing interface either +// as a *Named or an *Interface. Due to embedding, an interface may +// contain methods whose receiver type is a different interface. +func (s *Signature) Recv() *Var { return s.recv } + +// Params returns the parameters of signature s, or nil. +func (s *Signature) Params() *Tuple { return s.params } + +// Results returns the results of signature s, or nil. +func (s *Signature) Results() *Tuple { return s.results } + +// Variadic reports whether the signature s is variadic. +func (s *Signature) Variadic() bool { return s.variadic } + +// An Interface represents an interface type. +type Interface struct { + methods []*Func // ordered list of explicitly declared methods + embeddeds []*Named // ordered list of explicitly embedded types + + allMethods []*Func // ordered list of methods declared with or embedded in this interface (TODO(gri): replace with mset) +} + +// NewInterface returns a new interface for the given methods and embedded types. +func NewInterface(methods []*Func, embeddeds []*Named) *Interface { + typ := new(Interface) + + var mset objset + for _, m := range methods { + if mset.insert(m) != nil { + panic("multiple methods with the same name") + } + // set receiver + // TODO(gri) Ideally, we should use a named type here instead of + // typ, for less verbose printing of interface method signatures. + m.typ.(*Signature).recv = NewVar(m.pos, m.pkg, "", typ) + } + sort.Sort(byUniqueMethodName(methods)) + + if embeddeds == nil { + sort.Sort(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 type of interface t for 0 <= i < t.NumEmbeddeds(). +// The types are ordered by the corresponding TypeName's unique Id. +func (t *Interface) Embedded(i int) *Named { return t.embeddeds[i] } + +// NumMethods returns the total number of methods of interface t. +func (t *Interface) NumMethods() int { return len(t.allMethods) } + +// Method returns the i'th method of interface t for 0 <= i < t.NumMethods(). +// The methods are ordered by their unique Id. +func (t *Interface) Method(i int) *Func { return t.allMethods[i] } + +// Empty returns true if t is the empty interface. +func (t *Interface) Empty() bool { return len(t.allMethods) == 0 } + +// Complete computes the interface's method set. It must be called by users of +// 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. +// Complete returns the receiver. +func (t *Interface) Complete() *Interface { + if t.allMethods != nil { + return t + } + + var allMethods []*Func + if t.embeddeds == nil { + if t.methods == nil { + allMethods = make([]*Func, 0, 1) + } else { + allMethods = t.methods + } + } else { + allMethods = append(allMethods, t.methods...) + for _, et := range t.embeddeds { + it := et.Underlying().(*Interface) + it.Complete() + for _, tm := range it.allMethods { + // Make a copy of the method and adjust its receiver type. + newm := *tm + newmtyp := *tm.typ.(*Signature) + newm.typ = &newmtyp + newmtyp.recv = NewVar(newm.pos, newm.pkg, "", t) + allMethods = append(allMethods, &newm) + } + } + sort.Sort(byUniqueMethodName(allMethods)) + } + t.allMethods = allMethods + + return t +} + +// A Map represents a map type. +type Map struct { + key, elem Type +} + +// NewMap returns a new map for the given key and element types. +func NewMap(key, elem Type) *Map { + return &Map{key, elem} +} + +// Key returns the key type of map m. +func (m *Map) Key() Type { return m.key } + +// Elem returns the element type of map m. +func (m *Map) Elem() Type { return m.elem } + +// A Chan represents a channel type. +type Chan struct { + dir ChanDir + elem Type +} + +// A ChanDir value indicates a channel direction. +type ChanDir int + +// The direction of a channel is indicated by one of the following constants. +const ( + SendRecv ChanDir = iota + SendOnly + RecvOnly +) + +// NewChan returns a new channel type for the given direction and element type. +func NewChan(dir ChanDir, elem Type) *Chan { + return &Chan{dir, elem} +} + +// Dir returns the direction of channel c. +func (c *Chan) Dir() ChanDir { return c.dir } + +// Elem returns the element type of channel c. +func (c *Chan) Elem() Type { return c.elem } + +// A Named represents a named type. +type Named struct { + obj *TypeName // corresponding declared object + underlying Type // possibly a *Named during setup; never a *Named once set up completely + methods []*Func // methods declared for this type (not the method set of this type) +} + +// NewNamed returns a new named type for the given type name, underlying type, and associated methods. +// 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, underlying: underlying, methods: methods} + if obj.typ == nil { + obj.typ = typ + } + return typ +} + +// TypeName returns the type name for the named type t. +func (t *Named) Obj() *TypeName { return t.obj } + +// NumMethods returns the number of explicit methods whose receiver is named type t. +func (t *Named) NumMethods() int { return len(t.methods) } + +// Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). +func (t *Named) Method(i int) *Func { return t.methods[i] } + +// SetUnderlying sets the underlying type and marks t as complete. +// TODO(gri) determine if there's a better solution rather than providing this function +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. +// TODO(gri) find a better solution instead of providing this function +func (t *Named) AddMethod(m *Func) { + if i, _ := lookupMethod(t.methods, m.pkg, m.name); i < 0 { + t.methods = append(t.methods, m) + } +} + +// Implementations for Type methods. + +func (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 *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 *Basic) String() string { return TypeString(nil, t) } +func (t *Array) String() string { return TypeString(nil, t) } +func (t *Slice) String() string { return TypeString(nil, t) } +func (t *Struct) String() string { return TypeString(nil, t) } +func (t *Pointer) String() string { return TypeString(nil, t) } +func (t *Tuple) String() string { return TypeString(nil, t) } +func (t *Signature) String() string { return TypeString(nil, t) } +func (t *Interface) String() string { return TypeString(nil, t) } +func (t *Map) String() string { return TypeString(nil, t) } +func (t *Chan) String() string { return TypeString(nil, t) } +func (t *Named) String() string { return TypeString(nil, t) } diff --git a/src/gosubli.me/something-borrowed/types/types.go b/src/gosubli.me/something-borrowed/types/types.go deleted file mode 100644 index 2f2e579b..00000000 --- a/src/gosubli.me/something-borrowed/types/types.go +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package types - -import "go/ast" - -// All types implement the Type interface. -type Type interface { - String() string - aType() -} - -// 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 - size int64 // use DefaultSizeof to get size - Name string -} - -// An Array represents an array type [Len]Elt. -type Array struct { - Len int64 - Elt Type -} - -// A Slice represents a slice type []Elt. -type Slice struct { - Elt Type -} - -// A QualifiedName is a name qualified with the package that declared the name. -// Note: Pkg may be a fake package (no name, no scope) because the GC compiler's -// export information doesn't provide full information in some cases. -// TODO(gri): Should change Pkg to PkgPath since it's the only thing we care about. -type QualifiedName struct { - Pkg *Package // nil only for predeclared error.Error (exported) - Name string // unqualified type name for anonymous fields -} - -// IsSame reports whether p and q are the same. -func (p QualifiedName) IsSame(q QualifiedName) 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 p.Name != q.Name { - return false - } - // p.Name == q.Name - return ast.IsExported(p.Name) || p.Pkg.Path == q.Pkg.Path -} - -// A Field represents a field of a struct. -type Field struct { - QualifiedName - Type Type - Tag string - IsAnonymous bool -} - -// A Struct represents a struct type struct{...}. -type Struct struct { - Fields []*Field - offsets []int64 // field offsets in bytes, lazily computed -} - -func (typ *Struct) fieldIndex(name QualifiedName) int { - for i, f := range typ.Fields { - if f.QualifiedName.IsSame(name) { - return i - } - } - return -1 -} - -// A Pointer represents a pointer type *Base. -type Pointer struct { - Base Type -} - -// A Result represents a (multi-value) function call result. -type Result struct { - Values []*Var // Signature.Results of the function called -} - -// A Signature represents a user-defined function type func(...) (...). -type Signature struct { - Recv *Var // nil if not a method - Params []*Var // (incoming) parameters from left to right; or nil - Results []*Var // (outgoing) results from left to right; or nil - IsVariadic bool // true if the last parameter's type is of the form ...T -} - -// builtinId is an id of a builtin function. -type builtinId int - -// Predeclared builtin functions. -const ( - // Universe scope - _Append builtinId = iota - _Cap - _Close - _Complex - _Copy - _Delete - _Imag - _Len - _Make - _New - _Panic - _Print - _Println - _Real - _Recover - - // Unsafe package - _Alignof - _Offsetof - _Sizeof - - // Testing support - _Assert - _Trace -) - -// A builtin represents the type of a built-in function. -type builtin struct { - id builtinId - name string - nargs int // number of arguments (minimum if variadic) - isVariadic bool - isStatement bool // true if the built-in is valid as an expression statement -} - -// A Method represents a method. -type Method struct { - QualifiedName - Type *Signature -} - -// An Interface represents an interface type interface{...}. -type Interface struct { - Methods []*Method // TODO(gri) consider keeping them in sorted order -} - -// A Map represents a map type map[Key]Elt. -type Map struct { - Key, Elt Type -} - -// A Chan represents a channel type chan Elt, <-chan Elt, or chan<-Elt. -type Chan struct { - Dir ast.ChanDir - Elt Type -} - -// A NamedType represents a named type as declared in a type declaration. -type NamedType struct { - Obj *TypeName // corresponding declared object - Underlying Type // nil if not fully declared yet; never a *NamedType - Methods []*Method // TODO(gri) consider keeping them in sorted order -} - -func (*Basic) aType() {} -func (*Array) aType() {} -func (*Slice) aType() {} -func (*Struct) aType() {} -func (*Pointer) aType() {} -func (*Result) aType() {} -func (*Signature) aType() {} -func (*builtin) aType() {} -func (*Interface) aType() {} -func (*Map) aType() {} -func (*Chan) aType() {} -func (*NamedType) aType() {} diff --git a/src/gosubli.me/something-borrowed/types/typestring.go b/src/gosubli.me/something-borrowed/types/typestring.go new file mode 100644 index 00000000..9a537e81 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/typestring.go @@ -0,0 +1,266 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements printing of types. + +package types + +import ( + "bytes" + "fmt" +) + +// 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. It may be removed at any time. +var GcCompatibilityMode bool + +// TypeString returns the string representation of typ. +// Named types are printed package-qualified if they +// do not belong to this package. +func TypeString(this *Package, typ Type) string { + var buf bytes.Buffer + WriteType(&buf, this, typ) + return buf.String() +} + +// WriteType writes the string representation of typ to buf. +// Named types are printed package-qualified if they +// do not belong to this package. +func WriteType(buf *bytes.Buffer, this *Package, typ Type) { + writeType(buf, this, typ, make([]Type, 8)) +} + +func writeType(buf *bytes.Buffer, this *Package, typ Type, visited []Type) { + // Theoretically, this is a quadratic lookup algorithm, but in + // practice deeply nested composite types with unnamed component + // types are uncommon. This code is likely more efficient than + // using a map. + for _, t := range visited { + if t == typ { + fmt.Fprintf(buf, "○%T", typ) // cycle to typ + return + } + } + visited = append(visited, typ) + + switch t := typ.(type) { + case nil: + buf.WriteString("") + + case *Basic: + if t.kind == UnsafePointer { + buf.WriteString("unsafe.") + } + if GcCompatibilityMode { + // forget the alias names + switch t.kind { + case Byte: + t = Typ[Uint8] + case Rune: + t = Typ[Int32] + } + } + buf.WriteString(t.name) + + case *Array: + fmt.Fprintf(buf, "[%d]", t.len) + writeType(buf, this, t.elem, visited) + + case *Slice: + buf.WriteString("[]") + writeType(buf, this, t.elem, visited) + + case *Struct: + buf.WriteString("struct{") + for i, f := range t.fields { + if i > 0 { + buf.WriteString("; ") + } + if !f.anonymous { + buf.WriteString(f.name) + buf.WriteByte(' ') + } + writeType(buf, this, f.typ, visited) + if tag := t.Tag(i); tag != "" { + fmt.Fprintf(buf, " %q", tag) + } + } + buf.WriteByte('}') + + case *Pointer: + buf.WriteByte('*') + writeType(buf, this, t.base, visited) + + case *Tuple: + writeTuple(buf, this, t, false, visited) + + case *Signature: + buf.WriteString("func") + writeSignature(buf, this, t, 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 anonymous + // 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{") + 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, this, m.typ.(*Signature), 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, this, m.typ.(*Signature), visited) + } + for i, typ := range t.embeddeds { + if i > 0 || len(t.methods) > 0 { + buf.WriteString("; ") + } + writeType(buf, this, typ, visited) + } + } + buf.WriteByte('}') + + case *Map: + buf.WriteString("map[") + writeType(buf, this, t.key, visited) + buf.WriteByte(']') + writeType(buf, this, t.elem, 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, this, t.elem, visited) + if parens { + buf.WriteByte(')') + } + + case *Named: + s := "" + if obj := t.obj; obj != nil { + if pkg := obj.pkg; pkg != nil && pkg != this { + buf.WriteString(pkg.path) + buf.WriteByte('.') + } + // TODO(gri): function-local named types should be displayed + // differently from named types at package level to avoid + // ambiguity. + s = obj.name + } + buf.WriteString(s) + + default: + // For externally defined implementations of Type. + buf.WriteString(t.String()) + } +} + +func writeTuple(buf *bytes.Buffer, this *Package, tup *Tuple, variadic bool, visited []Type) { + buf.WriteByte('(') + if tup != nil { + for i, v := range tup.vars { + if i > 0 { + buf.WriteString(", ") + } + if v.name != "" { + buf.WriteString(v.name) + buf.WriteByte(' ') + } + typ := v.typ + if variadic && i == len(tup.vars)-1 { + if s, ok := typ.(*Slice); ok { + buf.WriteString("...") + typ = s.elem + } else { + // special case: + // append(s, "foo"...) leads to signature func([]byte, string...) + if t, ok := typ.Underlying().(*Basic); !ok || t.kind != String { + panic("internal error: string type expected") + } + writeType(buf, this, typ, visited) + buf.WriteString("...") + continue + } + } + writeType(buf, this, typ, visited) + } + } + buf.WriteByte(')') +} + +// WriteSignature writes the representation of the signature sig to buf, +// without a leading "func" keyword. +// Named types are printed package-qualified if they +// do not belong to this package. +func WriteSignature(buf *bytes.Buffer, this *Package, sig *Signature) { + writeSignature(buf, this, sig, make([]Type, 8)) +} + +func writeSignature(buf *bytes.Buffer, this *Package, sig *Signature, visited []Type) { + writeTuple(buf, this, sig.params, sig.variadic, 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, this, sig.results.vars[0].typ, visited) + return + } + + // multiple or named result(s) + writeTuple(buf, this, sig.results, false, visited) +} diff --git a/src/gosubli.me/something-borrowed/types/types_test.go b/src/gosubli.me/something-borrowed/types/typestring_test.go similarity index 50% rename from src/gosubli.me/something-borrowed/types/types_test.go rename to src/gosubli.me/something-borrowed/types/typestring_test.go index 8e228fa6..9aacf855 100644 --- a/src/gosubli.me/something-borrowed/types/types_test.go +++ b/src/gosubli.me/something-borrowed/types/typestring_test.go @@ -2,26 +2,28 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file contains tests verifying the types associated with an AST after -// type checking. - -package types +package types_test import ( "go/ast" "go/parser" + "go/token" "testing" + + _ "golang.org/x/tools/go/gcimporter" + . "golang.org/x/tools/go/types" ) const filename = "" func makePkg(t *testing.T, src string) (*Package, error) { + fset := token.NewFileSet() file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors) if err != nil { return nil, err } - pkg, err := Check(fset, []*ast.File{file}) - return pkg, err + // use the package name as package path + return Check(file.Name.Name, fset, []*ast.File{file}) } type testEntry struct { @@ -33,7 +35,8 @@ func dup(s string) testEntry { return testEntry{s, s} } -var testTypes = []testEntry{ +// types that don't depend on any other type declarations +var independentTestTypes = []testEntry{ // basic types dup("int"), dup("float32"), @@ -55,8 +58,8 @@ var testTypes = []testEntry{ }`, `struct{x int; y int; z float32 "foo"}`}, {`struct { string - elems []T - }`, `struct{string; elems []T}`}, + elems []complex128 + }`, `struct{string; elems []complex128}`}, // pointers dup("*int"), @@ -89,83 +92,67 @@ var testTypes = []testEntry{ // interfaces dup("interface{}"), dup("interface{m()}"), - dup(`interface{m(int) float32; String() string}`), - // TODO(gri) add test for interface w/ anonymous field + dup(`interface{String() string; m(int) float32}`), // maps dup("map[string]int"), {"map[struct{x, y int}][]byte", "map[struct{x int; y int}][]byte"}, // channels - dup("chan int"), + dup("chan<- chan int"), + dup("chan<- <-chan int"), + dup("<-chan <-chan int"), + dup("chan (<-chan int)"), dup("chan<- func()"), dup("<-chan []func() int"), } -func TestTypes(t *testing.T) { - for _, test := range testTypes { - src := "package p; type T " + test.src +// 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) { + 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(t, src) if err != nil { t.Errorf("%s: %s", src, err) continue } - typ := underlying(pkg.Scope.Lookup("T").GetType()) - str := typeString(typ) - if str != test.str { - t.Errorf("%s: got %s, want %s", test.src, str, test.str) + 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 testExprs = []testEntry{ - // basic type literals - dup("x"), - dup("true"), - dup("42"), - dup("3.1415"), - dup("2.71828i"), - dup(`'a'`), - dup(`"foo"`), - dup("`bar`"), - - // arbitrary expressions - dup("&x"), - dup("*&x"), - dup("(x)"), - dup("x + y"), - dup("x + y * 10"), - dup("t.foo"), - dup("s[0]"), - dup("s[x:y]"), - dup("s[:y]"), - dup("s[x:]"), - dup("s[:]"), - dup("f(1, 2.3)"), - dup("-f(10, 20)"), - dup("f(x + y, +3.1415)"), - {"func(a, b int) {}", "(func literal)"}, - {"func(a, b int) []int {}(1, 2)[x]", "(func literal)(1, 2)[x]"}, - {"[]int{1, 2, 3}", "(composite literal)"}, - {"[]int{1, 2, 3}[x:]", "(composite literal)[x:]"}, - {"i.([]string)", "i.(...)"}, -} - -func TestExprs(t *testing.T) { - for _, test := range testExprs { - src := "package p; var _ = " + test.src + "; var (x, y int; s []string; f func(int, float32) int; i interface{}; t interface { foo() })" - file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors) - if err != nil { - t.Errorf("%s: %s", src, err) - continue - } - // TODO(gri) writing the code below w/o the decl variable will - // cause a 386 compiler error (out of fixed registers) - decl := file.Decls[0].(*ast.GenDecl) - expr := decl.Specs[0].(*ast.ValueSpec).Values[0] - str := exprString(expr) - if str != test.str { - t.Errorf("%s: got %s, want %s", test.src, str, test.str) +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 + }{ + {pT, nil, "p.T"}, + {pT, p, "T"}, + {pT, q, "p.T"}, + {NewPointer(pT), p, "*T"}, + {NewPointer(pT), q, "*p.T"}, + } { + if got := TypeString(test.this, test.typ); got != test.want { + t.Errorf("TypeString(%s, %s) = %s, want %s", + test.this, test.typ, got, test.want) } } } diff --git a/src/gosubli.me/something-borrowed/types/typeutil/example_test.go b/src/gosubli.me/something-borrowed/types/typeutil/example_test.go new file mode 100644 index 00000000..67ee34f7 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/typeutil/example_test.go @@ -0,0 +1,64 @@ +package typeutil_test + +import ( + "fmt" + "sort" + + "go/ast" + "go/parser" + "go/token" + + "golang.org/x/tools/go/types" + "golang.org/x/tools/go/types/typeutil" +) + +func ExampleMap() { + const source = `package P + +var X []string +var Y []string + +const p, q = 1.0, 2.0 + +func f(offset int32) (value byte, ok bool) +func g(rune) (uint8, bool) +` + + // Parse and type-check the package. + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "P.go", source, 0) + if err != nil { + panic(err) + } + pkg, err := new(types.Config).Check("P", fset, []*ast.File{f}, nil) + if err != nil { + panic(err) + } + + scope := pkg.Scope() + + // Group names of package-level objects by their type. + var namesByType typeutil.Map // value is []string + for _, name := range scope.Names() { + T := scope.Lookup(name).Type() + + names, _ := namesByType.At(T).([]string) + names = append(names, name) + namesByType.Set(T, names) + } + + // Format, sort, and print the map entries. + var lines []string + namesByType.Iterate(func(T types.Type, names interface{}) { + lines = append(lines, fmt.Sprintf("%s %s", names, T)) + }) + sort.Strings(lines) + for _, line := range lines { + fmt.Println(line) + } + + // Output: + // [X Y] []string + // [f g] func(offset int32) (value byte, ok bool) + // [p q] untyped float +} diff --git a/src/gosubli.me/something-borrowed/types/typeutil/imports.go b/src/gosubli.me/something-borrowed/types/typeutil/imports.go new file mode 100644 index 00000000..5fc4238e --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/typeutil/imports.go @@ -0,0 +1,27 @@ +package typeutil + +import "gosubli.me/something-borrowed/types" //"golang.org/x/tools/go/types" + +// Dependencies returns all dependencies of the specified packages. +// +// Dependent packages appear in topological order: if package P imports +// package Q, Q appears earlier than P in the result. +// The algorithm follows import statements in the order they +// appear in the source code, so the result is a total order. +// +func Dependencies(pkgs ...*types.Package) []*types.Package { + var result []*types.Package + seen := make(map[*types.Package]bool) + var visit func(pkgs []*types.Package) + visit = func(pkgs []*types.Package) { + for _, p := range pkgs { + if !seen[p] { + seen[p] = true + visit(p.Imports()) + result = append(result, p) + } + } + } + visit(pkgs) + return result +} diff --git a/src/gosubli.me/something-borrowed/types/typeutil/imports_test.go b/src/gosubli.me/something-borrowed/types/typeutil/imports_test.go new file mode 100644 index 00000000..6f207cf4 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/typeutil/imports_test.go @@ -0,0 +1,75 @@ +package typeutil_test + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "testing" + + "golang.org/x/tools/go/types" + "golang.org/x/tools/go/types/typeutil" +) + +func TestDependencies(t *testing.T) { + packages := make(map[string]*types.Package) + conf := types.Config{ + Packages: packages, + Import: func(_ map[string]*types.Package, path string) (*types.Package, error) { + return packages[path], nil + }, + } + fset := token.NewFileSet() + + // All edges go to the right. + // /--D--B--A + // F \_C_/ + // \__E_/ + for i, content := range []string{ + `package A`, + `package C; import (_ "A")`, + `package B; import (_ "A")`, + `package E; import (_ "C")`, + `package D; import (_ "B"; _ "C")`, + `package F; import (_ "D"; _ "E")`, + } { + f, err := parser.ParseFile(fset, fmt.Sprintf("%d.go", i), content, 0) + if err != nil { + t.Fatal(err) + } + pkg, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + packages[pkg.Path()] = pkg + } + + for _, test := range []struct { + roots, want string + }{ + {"A", "A"}, + {"B", "AB"}, + {"C", "AC"}, + {"D", "ABCD"}, + {"E", "ACE"}, + {"F", "ABCDEF"}, + + {"BE", "ABCE"}, + {"EB", "ACEB"}, + {"DE", "ABCDE"}, + {"ED", "ACEBD"}, + {"EF", "ACEBDF"}, + } { + var pkgs []*types.Package + for _, r := range test.roots { + pkgs = append(pkgs, conf.Packages[string(r)]) + } + var got string + for _, p := range typeutil.Dependencies(pkgs...) { + got += p.Path() + } + if got != test.want { + t.Errorf("Dependencies(%q) = %q, want %q", test.roots, got, test.want) + } + } +} diff --git a/src/gosubli.me/something-borrowed/types/typeutil/map.go b/src/gosubli.me/something-borrowed/types/typeutil/map.go new file mode 100644 index 00000000..ce8a5807 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/typeutil/map.go @@ -0,0 +1,314 @@ +// 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 typeutil defines various utilities for types, such as Map, +// a mapping from types.Type to interface{} values. +package typeutil + +import ( + "bytes" + "fmt" + "reflect" + + "gosubli.me/something-borrowed/types" //"golang.org/x/tools/go/types" +) + +// Map is a hash-table-based mapping from types (types.Type) to +// arbitrary interface{} values. The concrete types that implement +// the Type interface are pointers. Since they are not canonicalized, +// == cannot be used to check for equivalence, and thus we cannot +// simply use a Go map. +// +// Just as with map[K]V, a nil *Map is a valid empty map. +// +// Not thread-safe. +// +type Map struct { + hasher Hasher // shared by many Maps + table map[uint32][]entry // maps hash to bucket; entry.key==nil means unused + length int // number of map entries +} + +// entry is an entry (key/value association) in a hash bucket. +type entry struct { + key types.Type + value interface{} +} + +// SetHasher sets the hasher used by Map. +// +// All Hashers are functionally equivalent but contain internal state +// used to cache the results of hashing previously seen types. +// +// A single Hasher created by MakeHasher() may be shared among many +// Maps. This is recommended if the instances have many keys in +// common, as it will amortize the cost of hash computation. +// +// A Hasher may grow without bound as new types are seen. Even when a +// type is deleted from the map, the Hasher never shrinks, since other +// types in the map may reference the deleted type indirectly. +// +// Hashers are not thread-safe, and read-only operations such as +// Map.Lookup require updates to the hasher, so a full Mutex lock (not a +// read-lock) is require around all Map operations if a shared +// hasher is accessed from multiple threads. +// +// If SetHasher is not called, the Map will create a private hasher at +// the first call to Insert. +// +func (m *Map) SetHasher(hasher Hasher) { + m.hasher = hasher +} + +// Delete removes the entry with the given key, if any. +// It returns true if the entry was found. +// +func (m *Map) Delete(key types.Type) bool { + if m != nil && m.table != nil { + hash := m.hasher.Hash(key) + bucket := m.table[hash] + for i, e := range bucket { + if e.key != nil && types.Identical(key, e.key) { + // We can't compact the bucket as it + // would disturb iterators. + bucket[i] = entry{} + m.length-- + return true + } + } + } + return false +} + +// At returns the map entry for the given key. +// The result is nil if the entry is not present. +// +func (m *Map) At(key types.Type) interface{} { + if m != nil && m.table != nil { + for _, e := range m.table[m.hasher.Hash(key)] { + if e.key != nil && types.Identical(key, e.key) { + return e.value + } + } + } + return nil +} + +// Set sets the map entry for key to val, +// and returns the previous entry, if any. +func (m *Map) Set(key types.Type, value interface{}) (prev interface{}) { + if m.table != nil { + hash := m.hasher.Hash(key) + bucket := m.table[hash] + var hole *entry + for i, e := range bucket { + if e.key == nil { + hole = &bucket[i] + } else if types.Identical(key, e.key) { + prev = e.value + bucket[i].value = value + return + } + } + + if hole != nil { + *hole = entry{key, value} // overwrite deleted entry + } else { + m.table[hash] = append(bucket, entry{key, value}) + } + } else { + if m.hasher.memo == nil { + m.hasher = MakeHasher() + } + hash := m.hasher.Hash(key) + m.table = map[uint32][]entry{hash: {entry{key, value}}} + } + + m.length++ + return +} + +// Len returns the number of map entries. +func (m *Map) Len() int { + if m != nil { + return m.length + } + return 0 +} + +// Iterate calls function f on each entry in the map in unspecified order. +// +// If f should mutate the map, Iterate provides the same guarantees as +// Go maps: if f deletes a map entry that Iterate has not yet reached, +// f will not be invoked for it, but if f inserts a map entry that +// Iterate has not yet reached, whether or not f will be invoked for +// it is unspecified. +// +func (m *Map) Iterate(f func(key types.Type, value interface{})) { + if m != nil { + for _, bucket := range m.table { + for _, e := range bucket { + if e.key != nil { + f(e.key, e.value) + } + } + } + } +} + +// Keys returns a new slice containing the set of map keys. +// The order is unspecified. +func (m *Map) Keys() []types.Type { + keys := make([]types.Type, 0, m.Len()) + m.Iterate(func(key types.Type, _ interface{}) { + keys = append(keys, key) + }) + return keys +} + +func (m *Map) toString(values bool) string { + if m == nil { + return "{}" + } + var buf bytes.Buffer + fmt.Fprint(&buf, "{") + sep := "" + m.Iterate(func(key types.Type, value interface{}) { + fmt.Fprint(&buf, sep) + sep = ", " + fmt.Fprint(&buf, key) + if values { + fmt.Fprintf(&buf, ": %q", value) + } + }) + fmt.Fprint(&buf, "}") + return buf.String() +} + +// String returns a string representation of the map's entries. +// Values are printed using fmt.Sprintf("%v", v). +// Order is unspecified. +// +func (m *Map) String() string { + return m.toString(true) +} + +// KeysString returns a string representation of the map's key set. +// Order is unspecified. +// +func (m *Map) KeysString() string { + return m.toString(false) +} + +//////////////////////////////////////////////////////////////////////// +// Hasher + +// A Hasher maps each type to its hash value. +// For efficiency, a hasher uses memoization; thus its memory +// footprint grows monotonically over time. +// Hashers are not thread-safe. +// Hashers have reference semantics. +// Call MakeHasher to create a Hasher. +type Hasher struct { + memo map[types.Type]uint32 +} + +// MakeHasher returns a new Hasher instance. +func MakeHasher() Hasher { + return Hasher{make(map[types.Type]uint32)} +} + +// Hash computes a hash value for the given type t such that +// Identical(t, t') => Hash(t) == Hash(t'). +func (h Hasher) Hash(t types.Type) uint32 { + hash, ok := h.memo[t] + if !ok { + hash = h.hashFor(t) + h.memo[t] = hash + } + return hash +} + +// hashString computes the Fowler–Noll–Vo hash of s. +func hashString(s string) uint32 { + var h uint32 + for i := 0; i < len(s); i++ { + h ^= uint32(s[i]) + h *= 16777619 + } + return h +} + +// hashFor computes the hash of t. +func (h Hasher) hashFor(t types.Type) uint32 { + // See Identical for rationale. + switch t := t.(type) { + case *types.Basic: + return uint32(t.Kind()) + + case *types.Array: + return 9043 + 2*uint32(t.Len()) + 3*h.Hash(t.Elem()) + + case *types.Slice: + return 9049 + 2*h.Hash(t.Elem()) + + case *types.Struct: + var hash uint32 = 9059 + for i, n := 0, t.NumFields(); i < n; i++ { + f := t.Field(i) + if f.Anonymous() { + hash += 8861 + } + hash += hashString(t.Tag(i)) + hash += hashString(f.Name()) // (ignore f.Pkg) + hash += h.Hash(f.Type()) + } + return hash + + case *types.Pointer: + return 9067 + 2*h.Hash(t.Elem()) + + case *types.Signature: + var hash uint32 = 9091 + if t.Variadic() { + hash *= 8863 + } + return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results()) + + case *types.Interface: + var hash uint32 = 9103 + for i, n := 0, t.NumMethods(); i < n; i++ { + // See go/types.identicalMethods for rationale. + // Method order is not significant. + // Ignore m.Pkg(). + m := t.Method(i) + hash += 3*hashString(m.Name()) + 5*h.Hash(m.Type()) + } + return hash + + case *types.Map: + return 9109 + 2*h.Hash(t.Key()) + 3*h.Hash(t.Elem()) + + case *types.Chan: + return 9127 + 2*uint32(t.Dir()) + 3*h.Hash(t.Elem()) + + case *types.Named: + // Not safe with a copying GC; objects may move. + return uint32(reflect.ValueOf(t.Obj()).Pointer()) + + case *types.Tuple: + return h.hashTuple(t) + } + panic(t) +} + +func (h Hasher) hashTuple(tuple *types.Tuple) uint32 { + // See go/types.identicalTypes for rationale. + n := tuple.Len() + var hash uint32 = 9137 + 2*uint32(n) + for i := 0; i < n; i++ { + hash += 3 * h.Hash(tuple.At(i).Type()) + } + return hash +} diff --git a/src/gosubli.me/something-borrowed/types/typeutil/map_test.go b/src/gosubli.me/something-borrowed/types/typeutil/map_test.go new file mode 100644 index 00000000..776b5e2c --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/typeutil/map_test.go @@ -0,0 +1,174 @@ +// 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 typeutil_test + +// TODO(adonovan): +// - test use of explicit hasher across two maps. +// - test hashcodes are consistent with equals for a range of types +// (e.g. all types generated by type-checking some body of real code). + +import ( + "testing" + + "golang.org/x/tools/go/types" + "golang.org/x/tools/go/types/typeutil" +) + +var ( + tStr = types.Typ[types.String] // string + tPStr1 = types.NewPointer(tStr) // *string + tPStr2 = types.NewPointer(tStr) // *string, again + tInt = types.Typ[types.Int] // int + tChanInt1 = types.NewChan(types.RecvOnly, tInt) // <-chan int + tChanInt2 = types.NewChan(types.RecvOnly, tInt) // <-chan int, again +) + +func checkEqualButNotIdentical(t *testing.T, x, y types.Type, comment string) { + if !types.Identical(x, y) { + t.Errorf("%s: not equal: %s, %s", comment, x, y) + } + if x == y { + t.Errorf("%s: identical: %v, %v", comment, x, y) + } +} + +func TestAxioms(t *testing.T) { + checkEqualButNotIdentical(t, tPStr1, tPStr2, "tPstr{1,2}") + checkEqualButNotIdentical(t, tChanInt1, tChanInt2, "tChanInt{1,2}") +} + +func TestMap(t *testing.T) { + var tmap *typeutil.Map + + // All methods but Set are safe on on (*T)(nil). + tmap.Len() + tmap.At(tPStr1) + tmap.Delete(tPStr1) + tmap.KeysString() + tmap.String() + + tmap = new(typeutil.Map) + + // Length of empty map. + if l := tmap.Len(); l != 0 { + t.Errorf("Len() on empty Map: got %d, want 0", l) + } + // At of missing key. + if v := tmap.At(tPStr1); v != nil { + t.Errorf("At() on empty Map: got %v, want nil", v) + } + // Deletion of missing key. + if tmap.Delete(tPStr1) { + t.Errorf("Delete() on empty Map: got true, want false") + } + // Set of new key. + if prev := tmap.Set(tPStr1, "*string"); prev != nil { + t.Errorf("Set() on empty Map returned non-nil previous value %s", prev) + } + + // Now: {*string: "*string"} + + // Length of non-empty map. + if l := tmap.Len(); l != 1 { + t.Errorf("Len(): got %d, want 1", l) + } + // At via insertion key. + if v := tmap.At(tPStr1); v != "*string" { + t.Errorf("At(): got %q, want \"*string\"", v) + } + // At via equal key. + if v := tmap.At(tPStr2); v != "*string" { + t.Errorf("At(): got %q, want \"*string\"", v) + } + // Iteration over sole entry. + tmap.Iterate(func(key types.Type, value interface{}) { + if key != tPStr1 { + t.Errorf("Iterate: key: got %s, want %s", key, tPStr1) + } + if want := "*string"; value != want { + t.Errorf("Iterate: value: got %s, want %s", value, want) + } + }) + + // Setion with key equal to present one. + if prev := tmap.Set(tPStr2, "*string again"); prev != "*string" { + t.Errorf("Set() previous value: got %s, want \"*string\"", prev) + } + + // Setion of another association. + if prev := tmap.Set(tChanInt1, "<-chan int"); prev != nil { + t.Errorf("Set() previous value: got %s, want nil", prev) + } + + // Now: {*string: "*string again", <-chan int: "<-chan int"} + + want1 := "{*string: \"*string again\", <-chan int: \"<-chan int\"}" + want2 := "{<-chan int: \"<-chan int\", *string: \"*string again\"}" + if s := tmap.String(); s != want1 && s != want2 { + t.Errorf("String(): got %s, want %s", s, want1) + } + + want1 = "{*string, <-chan int}" + want2 = "{<-chan int, *string}" + if s := tmap.KeysString(); s != want1 && s != want2 { + t.Errorf("KeysString(): got %s, want %s", s, want1) + } + + // Keys(). + I := types.Identical + switch k := tmap.Keys(); { + case I(k[0], tChanInt1) && I(k[1], tPStr1): // ok + case I(k[1], tChanInt1) && I(k[0], tPStr1): // ok + default: + t.Errorf("Keys(): got %v, want %s", k, want2) + } + + if l := tmap.Len(); l != 2 { + t.Errorf("Len(): got %d, want 1", l) + } + // At via original key. + if v := tmap.At(tPStr1); v != "*string again" { + t.Errorf("At(): got %q, want \"*string again\"", v) + } + hamming := 1 + tmap.Iterate(func(key types.Type, value interface{}) { + switch { + case I(key, tChanInt1): + hamming *= 2 // ok + case I(key, tPStr1): + hamming *= 3 // ok + } + }) + if hamming != 6 { + t.Errorf("Iterate: hamming: got %d, want %d", hamming, 6) + } + + if v := tmap.At(tChanInt2); v != "<-chan int" { + t.Errorf("At(): got %q, want \"<-chan int\"", v) + } + // Deletion with key equal to present one. + if !tmap.Delete(tChanInt2) { + t.Errorf("Delete() of existing key: got false, want true") + } + + // Now: {*string: "*string again"} + + if l := tmap.Len(); l != 1 { + t.Errorf("Len(): got %d, want 1", l) + } + // Deletion again. + if !tmap.Delete(tPStr2) { + t.Errorf("Delete() of existing key: got false, want true") + } + + // Now: {} + + if l := tmap.Len(); l != 0 { + t.Errorf("Len(): got %d, want %d", l, 0) + } + if s := tmap.String(); s != "{}" { + t.Errorf("Len(): got %q, want %q", s, "") + } +} diff --git a/src/gosubli.me/something-borrowed/types/typeutil/ui.go b/src/gosubli.me/something-borrowed/types/typeutil/ui.go new file mode 100644 index 00000000..4ebd2040 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/typeutil/ui.go @@ -0,0 +1,38 @@ +// 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 typeutil + +// This file defines utilities for user interfaces that display types. + +import "gosubli.me/something-borrowed/types" //"golang.org/x/tools/go/types" + +// IntuitiveMethodSet returns the intuitive method set of a type, T. +// +// The result contains MethodSet(T) and additionally, if T is a +// concrete type, methods belonging to *T if there is no identically +// named method on T itself. This corresponds to user intuition about +// method sets; this function is intended only for user interfaces. +// +// The order of the result is as for types.MethodSet(T). +// +func IntuitiveMethodSet(T types.Type, msets *types.MethodSetCache) []*types.Selection { + var result []*types.Selection + mset := msets.MethodSet(T) + if _, ok := T.Underlying().(*types.Interface); ok { + for i, n := 0, mset.Len(); i < n; i++ { + result = append(result, mset.At(i)) + } + } else { + pmset := msets.MethodSet(types.NewPointer(T)) + for i, n := 0, pmset.Len(); i < n; i++ { + meth := pmset.At(i) + if m := mset.Lookup(meth.Obj().Pkg(), meth.Obj().Name()); m != nil { + meth = m + } + result = append(result, meth) + } + } + return result +} diff --git a/src/gosubli.me/something-borrowed/types/typexpr.go b/src/gosubli.me/something-borrowed/types/typexpr.go new file mode 100644 index 00000000..5ccde974 --- /dev/null +++ b/src/gosubli.me/something-borrowed/types/typexpr.go @@ -0,0 +1,721 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements type-checking of identifiers and type expressions. + +package types + +import ( + "go/ast" + "go/token" + "sort" + "strconv" + + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" +) + +// 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 and path, see check.typ, below. +// +func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, path []*TypeName) { + x.mode = invalid + x.expr = e + + scope, obj := check.scope.LookupParent(e.Name) + if obj == nil { + if e.Name == "_" { + check.errorf(e.Pos(), "cannot use _ as value or type") + } else { + check.errorf(e.Pos(), "undeclared name: %s", e.Name) + } + return + } + check.recordUse(e, obj) + + check.objDecl(obj, def, path) + typ := obj.Type() + 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.Pos(), "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.Pos(), "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 + // check for cycle + // (it's ok to iterate forward because each named type appears at most once in path) + for i, prev := range path { + if prev == obj { + check.errorf(obj.pos, "illegal cycle in declaration of %s", obj.name) + // print cycle + for _, obj := range path[i:] { + check.errorf(obj.Pos(), "\t%s refers to", obj.Name()) // secondary error, \t indented + } + check.errorf(obj.Pos(), "\t%s", obj.Name()) + // maintain x.mode == typexpr despite error + typ = Typ[Invalid] + break + } + } + + case *Var: + 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 +} + +// typExpr type-checks the type expression e and returns its type, or Typ[Invalid]. +// If def != nil, e is the type specification for the named 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. Path contains the path of named types +// referring to this type. +// +func (check *Checker) typExpr(e ast.Expr, def *Named, path []*TypeName) (T Type) { + if trace { + check.trace(e.Pos(), "%s", e) + check.indent++ + defer func() { + check.indent-- + check.trace(e.Pos(), "=> %s", T) + }() + } + + T = check.typExprInternal(e, def, path) + assert(isTyped(T)) + check.recordTypeAndValue(e, typexpr, T, nil) + + return +} + +func (check *Checker) typ(e ast.Expr) Type { + return check.typExpr(e, nil, nil) +} + +// funcType type-checks a function or method type and returns its signature. +func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast.FuncType) *Signature { + scope := NewScope(check.scope, "function") + check.recordScope(ftyp, scope) + + recvList, _ := check.collectParams(scope, recvPar, false) + params, variadic := check.collectParams(scope, ftyp.Params, true) + results, _ := check.collectParams(scope, ftyp.Results, false) + + if recvPar != nil { + // recv parameter list present (may be empty) + // spec: "The receiver is specified via an extra parameter section preceeding the + // method name. That parameter section must declare a single parameter, the receiver." + var recv *Var + switch len(recvList) { + case 0: + check.error(recvPar.Pos(), "method is missing receiver") + recv = NewParam(0, 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] + } + // spec: "The receiver type must be of the form T or *T where T is a type name." + // (ignore invalid types - error was reported before) + if t, _ := deref(recv.typ); t != Typ[Invalid] { + var err string + if T, _ := t.(*Named); T != nil { + // spec: "The type denoted by T is called the receiver base type; it must not + // be a pointer or interface type and it must be declared in the same package + // as the method." + if T.obj.pkg != check.pkg { + err = "type not defined in this package" + } else { + // TODO(gri) This is not correct if the underlying type is unknown yet. + switch u := T.underlying.(type) { + case *Basic: + // unsafe.Pointer is treated like a regular pointer + if u.kind == UnsafePointer { + err = "unsafe.Pointer" + } + case *Pointer, *Interface: + err = "pointer or interface type" + } + } + } else { + err = "basic or unnamed type" + } + if err != "" { + check.errorf(recv.pos, "invalid receiver %s (%s)", recv.typ, err) + // ok to continue + } + } + sig.recv = recv + } + + sig.scope = scope + sig.params = NewTuple(params...) + sig.results = NewTuple(results...) + sig.variadic = variadic + + return sig +} + +// typExprInternal drives type checking of types. +// Must only be called by typExpr. +// +func (check *Checker) typExprInternal(e ast.Expr, def *Named, path []*TypeName) Type { + switch e := e.(type) { + case *ast.BadExpr: + // ignore - error reported before + + case *ast.Ident: + var x operand + check.ident(&x, e, def, path) + + switch x.mode { + case typexpr: + typ := x.typ + def.setUnderlying(typ) + return typ + case invalid: + // ignore - error reported before + case novalue: + check.errorf(x.pos(), "%s used as type", &x) + default: + check.errorf(x.pos(), "%s is not a type", &x) + } + + case *ast.SelectorExpr: + var x operand + check.selector(&x, e) + + switch x.mode { + case typexpr: + typ := x.typ + def.setUnderlying(typ) + return typ + case invalid: + // ignore - error reported before + case novalue: + check.errorf(x.pos(), "%s used as type", &x) + default: + check.errorf(x.pos(), "%s is not a type", &x) + } + + case *ast.ParenExpr: + return check.typExpr(e.X, def, path) + + case *ast.ArrayType: + if e.Len != nil { + typ := new(Array) + def.setUnderlying(typ) + typ.len = check.arrayLength(e.Len) + typ.elem = check.typExpr(e.Elt, nil, path) + return typ + + } else { + typ := new(Slice) + def.setUnderlying(typ) + typ.elem = check.typ(e.Elt) + return typ + } + + case *ast.StructType: + typ := new(Struct) + def.setUnderlying(typ) + check.structType(typ, e, path) + return typ + + case *ast.StarExpr: + typ := new(Pointer) + def.setUnderlying(typ) + typ.base = check.typ(e.X) + return typ + + case *ast.FuncType: + typ := new(Signature) + def.setUnderlying(typ) + check.funcType(typ, nil, e) + return typ + + case *ast.InterfaceType: + typ := new(Interface) + def.setUnderlying(typ) + check.interfaceType(typ, e, def, path) + return typ + + case *ast.MapType: + typ := new(Map) + def.setUnderlying(typ) + + typ.key = check.typ(e.Key) + typ.elem = check.typ(e.Value) + + // spec: "The comparison operators == and != must be fully defined + // for operands of the key type; thus the key type must not be a + // function, map, or slice." + // + // Delay this check because it requires fully setup types; + // it is safe to continue in any case (was issue 6667). + check.delay(func() { + if !Comparable(typ.key) { + check.errorf(e.Key.Pos(), "invalid map key type %s", typ.key) + } + }) + + return typ + + case *ast.ChanType: + typ := new(Chan) + def.setUnderlying(typ) + + dir := SendRecv + switch e.Dir { + case ast.SEND | ast.RECV: + // nothing to do + case ast.SEND: + dir = SendOnly + case ast.RECV: + dir = RecvOnly + default: + check.invalidAST(e.Pos(), "unknown channel direction %d", e.Dir) + // ok to continue + } + + typ.dir = dir + typ.elem = check.typ(e.Value) + return typ + + default: + check.errorf(e.Pos(), "%s is not a type", e) + } + + typ := Typ[Invalid] + def.setUnderlying(typ) + return typ +} + +// typeOrNil type-checks the type expression (or nil value) e +// and returns the typ of e, or nil. +// If e is neither a type nor nil, typOrNil returns Typ[Invalid]. +// +func (check *Checker) typOrNil(e ast.Expr) Type { + var x operand + check.rawExpr(&x, e, nil) + switch x.mode { + case invalid: + // ignore - error reported before + case novalue: + check.errorf(x.pos(), "%s used as type", &x) + case typexpr: + return x.typ + case value: + if x.isNil() { + return nil + } + fallthrough + default: + check.errorf(x.pos(), "%s is not a type", &x) + } + return Typ[Invalid] +} + +func (check *Checker) arrayLength(e ast.Expr) int64 { + var x operand + check.expr(&x, e) + if x.mode != constant { + if x.mode != invalid { + check.errorf(x.pos(), "array length %s must be constant", &x) + } + return 0 + } + if !x.isInteger() { + check.errorf(x.pos(), "array length %s must be integer", &x) + return 0 + } + n, ok := exact.Int64Val(x.val) + if !ok || n < 0 { + check.errorf(x.pos(), "invalid array length %s", &x) + return 0 + } + return n +} + +func (check *Checker) collectParams(scope *Scope, list *ast.FieldList, variadicOk bool) (params []*Var, variadic bool) { + if list == nil { + return + } + + var named, anonymous bool + for i, field := range list.List { + ftype := field.Type + if t, _ := ftype.(*ast.Ellipsis); t != nil { + ftype = t.Elt + if variadicOk && i == len(list.List)-1 { + variadic = true + } else { + check.invalidAST(field.Pos(), "... not permitted") + // ignore ... and continue + } + } + typ := check.typ(ftype) + // The parser ensures that f.Tag is nil and we don't + // care if a constructed AST contains a non-nil tag. + if len(field.Names) > 0 { + // named parameter + for _, name := range field.Names { + if name.Name == "" { + check.invalidAST(name.Pos(), "anonymous parameter") + // ok to continue + } + par := NewParam(name.Pos(), check.pkg, name.Name, typ) + check.declare(scope, name, par) + params = append(params, par) + } + named = true + } else { + // anonymous parameter + par := NewParam(ftype.Pos(), check.pkg, "", typ) + check.recordImplicit(field, par) + params = append(params, par) + anonymous = true + } + } + + if named && anonymous { + check.invalidAST(list.Pos(), "list contains both named and anonymous parameters") + // ok to continue + } + + // For a variadic function, change the last parameter's type from T to []T. + if variadic && len(params) > 0 { + last := params[len(params)-1] + last.typ = &Slice{elem: last.typ} + } + + return +} + +func (check *Checker) declareInSet(oset *objset, pos token.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(iface *Interface, ityp *ast.InterfaceType, def *Named, path []*TypeName) { + // empty interface: common case + if ityp.Methods == nil { + return + } + + // The parser ensures that field tags are nil and we don't + // care if a constructed AST contains non-nil tags. + + // use named receiver type if available (for better error messages) + var recvTyp Type = iface + if def != nil { + recvTyp = def + } + + // Phase 1: Collect explicitly declared methods, the corresponding + // signature (AST) expressions, and the list of embedded + // type (AST) expressions. Do not resolve signatures or + // embedded types yet to avoid cycles referring to this + // interface. + + var ( + mset objset + signatures []ast.Expr // list of corresponding method signatures + embedded []ast.Expr // list of embedded types + ) + for _, f := range ityp.Methods.List { + if len(f.Names) > 0 { + // The parser ensures that there's only one method + // and we don't care if a constructed AST has more. + name := f.Names[0] + pos := name.Pos() + // spec: "As with all method sets, in an interface type, + // each method must have a unique non-blank name." + if name.Name == "_" { + check.errorf(pos, "invalid method name _") + continue + } + // Don't type-check signature yet - use an + // empty signature now and update it later. + // Since we know the receiver, set it up now + // (required to avoid crash in ptrRecv; see + // e.g. test case for issue 6638). + // TODO(gri) Consider marking methods signatures + // as incomplete, for better error messages. See + // also the T4 and T5 tests in testdata/cycles2.src. + sig := new(Signature) + sig.recv = NewVar(pos, check.pkg, "", recvTyp) + m := NewFunc(pos, check.pkg, name.Name, sig) + if check.declareInSet(&mset, pos, m) { + iface.methods = append(iface.methods, m) + iface.allMethods = append(iface.allMethods, m) + signatures = append(signatures, f.Type) + check.recordDef(name, m) + } + } else { + // embedded type + embedded = append(embedded, f.Type) + } + } + + // Phase 2: Resolve embedded interfaces. Because an interface must not + // embed itself (directly or indirectly), each embedded interface + // can be fully resolved without depending on any method of this + // interface (if there is a cycle or another error, the embedded + // type resolves to an invalid type and is ignored). + // In particular, the list of methods for each embedded interface + // must be complete (it cannot depend on this interface), and so + // those methods can be added to the list of all methods of this + // interface. + + for _, e := range embedded { + pos := e.Pos() + typ := check.typExpr(e, nil, path) + named, _ := typ.(*Named) + if named == nil { + if typ != Typ[Invalid] { + check.invalidAST(pos, "%s is not named type", typ) + } + continue + } + // determine underlying (possibly incomplete) type + // by following its forward chain + u := underlying(named) + embed, _ := u.(*Interface) + if embed == nil { + if u != Typ[Invalid] { + check.errorf(pos, "%s is not an interface", named) + } + continue + } + iface.embeddeds = append(iface.embeddeds, named) + // collect embedded methods + for _, m := range embed.allMethods { + if check.declareInSet(&mset, pos, m) { + iface.allMethods = append(iface.allMethods, m) + } + } + } + + // Phase 3: At this point all methods have been collected for this interface. + // It is now safe to type-check the signatures of all explicitly + // declared methods, even if they refer to this interface via a cycle + // and embed the methods of this interface in a parameter of interface + // type. + + for i, m := range iface.methods { + expr := signatures[i] + typ := check.typ(expr) + sig, _ := typ.(*Signature) + if sig == nil { + if typ != Typ[Invalid] { + check.invalidAST(expr.Pos(), "%s is not a method signature", typ) + } + continue // keep method with empty method signature + } + // update signature, but keep recv that was set up before + old := m.typ.(*Signature) + sig.recv = old.recv + *old = *sig // update signature (don't replace it!) + } + + // TODO(gri) The list of explicit methods is only sorted for now to + // produce the same Interface as NewInterface. We may be able to + // claim source order in the future. Revisit. + sort.Sort(byUniqueMethodName(iface.methods)) + + // TODO(gri) The list of embedded types is only sorted for now to + // produce the same Interface as NewInterface. We may be able to + // claim source order in the future. Revisit. + sort.Sort(byUniqueTypeName(iface.embeddeds)) + + sort.Sort(byUniqueMethodName(iface.allMethods)) +} + +// byUniqueTypeName named type lists can be sorted by their unique type names. +type byUniqueTypeName []*Named + +func (a byUniqueTypeName) Len() int { return len(a) } +func (a byUniqueTypeName) Less(i, j int) bool { return a[i].obj.Id() < a[j].obj.Id() } +func (a byUniqueTypeName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// byUniqueMethodName method lists can be sorted by their unique method names. +type byUniqueMethodName []*Func + +func (a byUniqueMethodName) Len() int { return len(a) } +func (a byUniqueMethodName) Less(i, j int) bool { return a[i].Id() < a[j].Id() } +func (a byUniqueMethodName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +func (check *Checker) tag(t *ast.BasicLit) string { + if t != nil { + if t.Kind == token.STRING { + if val, err := strconv.Unquote(t.Value); err == nil { + return val + } + } + check.invalidAST(t.Pos(), "incorrect tag syntax: %q", t.Value) + } + return "" +} + +func (check *Checker) structType(styp *Struct, e *ast.StructType, path []*TypeName) { + list := e.Fields + if list == nil { + return + } + + // struct fields and tags + var fields []*Var + var tags []string + + // for double-declaration checks + var fset objset + + // current field typ and tag + var typ Type + var tag string + // anonymous != nil indicates an anonymous field. + add := func(field *ast.Field, ident *ast.Ident, anonymous *TypeName, pos token.Pos) { + if tag != "" && tags == nil { + tags = make([]string, len(fields)) + } + if tags != nil { + tags = append(tags, tag) + } + + name := ident.Name + fld := NewField(pos, check.pkg, name, typ, anonymous != nil) + // 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) + } + if anonymous != nil { + check.recordUse(ident, anonymous) + } + } + + for _, f := range list.List { + typ = check.typExpr(f.Type, nil, path) + tag = check.tag(f.Tag) + if len(f.Names) > 0 { + // named fields + for _, name := range f.Names { + add(f, name, nil, name.Pos()) + } + } else { + // anonymous field + name := anonymousFieldIdent(f.Type) + pos := f.Type.Pos() + t, isPtr := deref(typ) + switch t := t.(type) { + case *Basic: + if t == Typ[Invalid] { + // error was reported before + continue + } + // unsafe.Pointer is treated like a regular pointer + if t.kind == UnsafePointer { + check.errorf(pos, "anonymous field type cannot be unsafe.Pointer") + continue + } + add(f, name, Universe.Lookup(t.name).(*TypeName), pos) + + case *Named: + // spec: "An embedded type must be specified as a type name + // T or as a pointer to a non-interface type name *T, and T + // itself may not be a pointer type." + switch u := t.underlying.(type) { + case *Basic: + // unsafe.Pointer is treated like a regular pointer + if u.kind == UnsafePointer { + check.errorf(pos, "anonymous field type cannot be unsafe.Pointer") + continue + } + case *Pointer: + check.errorf(pos, "anonymous field type cannot be a pointer") + continue + case *Interface: + if isPtr { + check.errorf(pos, "anonymous field type cannot be a pointer to an interface") + continue + } + } + add(f, name, t.obj, pos) + + default: + check.invalidAST(pos, "anonymous field type %s must be named", typ) + } + } + } + + styp.fields = fields + styp.tags = tags +} + +func anonymousFieldIdent(e ast.Expr) *ast.Ident { + switch e := e.(type) { + case *ast.Ident: + return e + case *ast.StarExpr: + return anonymousFieldIdent(e.X) + case *ast.SelectorExpr: + return e.Sel + } + return nil // invalid anonymous field +} diff --git a/src/gosubli.me/something-borrowed/types/universe.go b/src/gosubli.me/something-borrowed/types/universe.go index b218525c..d17f0b5d 100644 --- a/src/gosubli.me/something-borrowed/types/universe.go +++ b/src/gosubli.me/something-borrowed/types/universe.go @@ -7,110 +7,188 @@ package types import ( - "go/ast" + "go/token" "strings" + + "gosubli.me/something-borrowed/exact" //"golang.org/x/tools/go/exact" ) var ( Universe *Scope Unsafe *Package universeIota *Const + UniverseByte *Basic // uint8 alias, but has name "byte" + UniverseRune *Basic // int32 alias, but has name "rune" ) -// Predeclared types, indexed by BasicKind. var Typ = [...]*Basic{ - Invalid: {Invalid, 0, 0, "invalid type"}, - - Bool: {Bool, IsBoolean, 1, "bool"}, - Int: {Int, IsInteger, 0, "int"}, - Int8: {Int8, IsInteger, 1, "int8"}, - Int16: {Int16, IsInteger, 2, "int16"}, - Int32: {Int32, IsInteger, 4, "int32"}, - Int64: {Int64, IsInteger, 8, "int64"}, - Uint: {Uint, IsInteger | IsUnsigned, 0, "uint"}, - Uint8: {Uint8, IsInteger | IsUnsigned, 1, "uint8"}, - Uint16: {Uint16, IsInteger | IsUnsigned, 2, "uint16"}, - Uint32: {Uint32, IsInteger | IsUnsigned, 4, "uint32"}, - Uint64: {Uint64, IsInteger | IsUnsigned, 8, "uint64"}, - Uintptr: {Uintptr, IsInteger | IsUnsigned, 0, "uintptr"}, - Float32: {Float32, IsFloat, 4, "float32"}, - Float64: {Float64, IsFloat, 8, "float64"}, - Complex64: {Complex64, IsComplex, 8, "complex64"}, - Complex128: {Complex128, IsComplex, 16, "complex128"}, - String: {String, IsString, 0, "string"}, - UnsafePointer: {UnsafePointer, 0, 0, "Pointer"}, - - UntypedBool: {UntypedBool, IsBoolean | IsUntyped, 0, "untyped boolean"}, - UntypedInt: {UntypedInt, IsInteger | IsUntyped, 0, "untyped integer"}, - UntypedRune: {UntypedRune, IsInteger | IsUntyped, 0, "untyped rune"}, - UntypedFloat: {UntypedFloat, IsFloat | IsUntyped, 0, "untyped float"}, - UntypedComplex: {UntypedComplex, IsComplex | IsUntyped, 0, "untyped complex"}, - UntypedString: {UntypedString, IsString | IsUntyped, 0, "untyped string"}, - UntypedNil: {UntypedNil, IsUntyped, 0, "untyped nil"}, + Invalid: {Invalid, 0, "invalid type"}, + + Bool: {Bool, IsBoolean, "bool"}, + Int: {Int, IsInteger, "int"}, + Int8: {Int8, IsInteger, "int8"}, + Int16: {Int16, IsInteger, "int16"}, + Int32: {Int32, IsInteger, "int32"}, + Int64: {Int64, IsInteger, "int64"}, + Uint: {Uint, IsInteger | IsUnsigned, "uint"}, + Uint8: {Uint8, IsInteger | IsUnsigned, "uint8"}, + Uint16: {Uint16, IsInteger | IsUnsigned, "uint16"}, + Uint32: {Uint32, IsInteger | IsUnsigned, "uint32"}, + Uint64: {Uint64, IsInteger | IsUnsigned, "uint64"}, + Uintptr: {Uintptr, IsInteger | IsUnsigned, "uintptr"}, + Float32: {Float32, IsFloat, "float32"}, + Float64: {Float64, IsFloat, "float64"}, + Complex64: {Complex64, IsComplex, "complex64"}, + Complex128: {Complex128, IsComplex, "complex128"}, + String: {String, IsString, "string"}, + UnsafePointer: {UnsafePointer, 0, "Pointer"}, + + UntypedBool: {UntypedBool, IsBoolean | IsUntyped, "untyped bool"}, + UntypedInt: {UntypedInt, IsInteger | IsUntyped, "untyped int"}, + UntypedRune: {UntypedRune, IsInteger | IsUntyped, "untyped rune"}, + UntypedFloat: {UntypedFloat, IsFloat | IsUntyped, "untyped float"}, + UntypedComplex: {UntypedComplex, IsComplex | IsUntyped, "untyped complex"}, + UntypedString: {UntypedString, IsString | IsUntyped, "untyped string"}, + UntypedNil: {UntypedNil, IsUntyped, "untyped nil"}, } var aliases = [...]*Basic{ - {Byte, IsInteger | IsUnsigned, 1, "byte"}, - {Rune, IsInteger, 4, "rune"}, -} - -var predeclaredConstants = [...]*Const{ - {nil, "true", Typ[UntypedBool], true, nil}, - {nil, "false", Typ[UntypedBool], false, nil}, - {nil, "iota", Typ[UntypedInt], zeroConst, nil}, - {nil, "nil", Typ[UntypedNil], nilConst, nil}, + {Byte, IsInteger | IsUnsigned, "byte"}, + {Rune, IsInteger, "rune"}, } -var predeclaredFunctions = [...]*builtin{ - {_Append, "append", 1, true, false}, - {_Cap, "cap", 1, false, false}, - {_Close, "close", 1, false, true}, - {_Complex, "complex", 2, false, false}, - {_Copy, "copy", 2, false, true}, - {_Delete, "delete", 2, false, true}, - {_Imag, "imag", 1, false, false}, - {_Len, "len", 1, false, false}, - {_Make, "make", 1, true, false}, - {_New, "new", 1, false, false}, - {_Panic, "panic", 1, false, true}, - {_Print, "print", 0, true, true}, - {_Println, "println", 0, true, true}, - {_Real, "real", 1, false, false}, - {_Recover, "recover", 0, false, true}, - - {_Alignof, "Alignof", 1, false, false}, - {_Offsetof, "Offsetof", 1, false, false}, - {_Sizeof, "Sizeof", 1, false, false}, -} - -func init() { - Universe = new(Scope) - Unsafe = &Package{Name: "unsafe", Scope: new(Scope)} - - // predeclared types +func defPredeclaredTypes() { for _, t := range Typ { - def(&TypeName{Name: t.Name, Type: t}) + def(NewTypeName(token.NoPos, nil, t.name, t)) } for _, t := range aliases { - def(&TypeName{Name: t.Name, Type: t}) + def(NewTypeName(token.NoPos, nil, t.name, t)) } - // error type - { - // Error has a nil package in its qualified name since it is in no package - err := &Method{QualifiedName{nil, "Error"}, &Signature{Results: []*Var{{Name: "", Type: Typ[String]}}}} - def(&TypeName{Name: "error", Type: &NamedType{Underlying: &Interface{Methods: []*Method{err}}}}) + // Error has a nil package in its qualified name since it is in no package + res := NewVar(token.NoPos, nil, "", Typ[String]) + sig := &Signature{results: NewTuple(res)} + err := NewFunc(token.NoPos, nil, "Error", sig) + typ := &Named{underlying: NewInterface([]*Func{err}, nil).Complete()} + sig.recv = NewVar(token.NoPos, nil, "", typ) + def(NewTypeName(token.NoPos, nil, "error", typ)) +} + +var predeclaredConsts = [...]struct { + name string + kind BasicKind + val exact.Value +}{ + {"true", UntypedBool, exact.MakeBool(true)}, + {"false", UntypedBool, exact.MakeBool(false)}, + {"iota", UntypedInt, exact.MakeInt64(0)}, +} + +func defPredeclaredConsts() { + for _, c := range predeclaredConsts { + def(NewConst(token.NoPos, nil, c.name, Typ[c.kind], c.val)) } +} - for _, c := range predeclaredConstants { - def(c) +func defPredeclaredNil() { + def(&Nil{object{name: "nil", typ: Typ[UntypedNil]}}) +} + +// 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)) } +} - for _, f := range predeclaredFunctions { - def(&Func{Name: f.name, Type: f}) +// DefPredeclaredTestFuncs defines the assert and trace built-ins. +// These built-ins are intended for debugging and testing of this +// package only. +func DefPredeclaredTestFuncs() { + if Universe.Lookup("assert") != nil { + return // already defined } + def(newBuiltin(_Assert)) + def(newBuiltin(_Trace)) +} + +func init() { + Universe = NewScope(nil, "universe") + Unsafe = NewPackage("unsafe", "unsafe") + Unsafe.complete = true + + defPredeclaredTypes() + defPredeclaredConsts() + defPredeclaredNil() + defPredeclaredFuncs() universeIota = Universe.Lookup("iota").(*Const) + UniverseByte = Universe.Lookup("byte").(*TypeName).typ.(*Basic) + UniverseRune = Universe.Lookup("rune").(*TypeName).typ.(*Basic) } // Objects with names containing blanks are internal and not entered into @@ -118,24 +196,24 @@ func init() { // scope; other objects are inserted in the universe scope. // func def(obj Object) { - name := obj.GetName() + name := obj.Name() if strings.Index(name, " ") >= 0 { return // nothing to do } // fix Obj link for named types - if typ, ok := obj.GetType().(*NamedType); ok { - typ.Obj = obj.(*TypeName) + if typ, ok := obj.Type().(*Named); ok { + typ.obj = obj.(*TypeName) } // exported identifiers go into package unsafe scope := Universe - if ast.IsExported(name) { - scope = Unsafe.Scope + if obj.Exported() { + scope = Unsafe.scope // set Pkg field switch obj := obj.(type) { case *TypeName: - obj.Pkg = Unsafe - case *Func: - obj.Pkg = Unsafe + obj.pkg = Unsafe + case *Builtin: + obj.pkg = Unsafe default: unreachable() } From e5d748e88d51c1b89561a54241a21b93de5fcc1c Mon Sep 17 00:00:00 2001 From: Douglas Clark Date: Sun, 30 Nov 2014 18:22:50 -0600 Subject: [PATCH 5/9] Updated for windows paths --- src/gosubli.me/margo/m_doc.go | 7 +++++-- src/gosubli.me/margo/m_doc_test.go | 18 +++++++++--------- src/gosubli.me/margo/m_doc_types.go | 1 + 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/gosubli.me/margo/m_doc.go b/src/gosubli.me/margo/m_doc.go index b9e0c002..8dcdf2bf 100644 --- a/src/gosubli.me/margo/m_doc.go +++ b/src/gosubli.me/margo/m_doc.go @@ -1,6 +1,9 @@ package main -import "path" +import ( + "log" + "path/filepath" +) type Doc struct { Src string `json:"src"` @@ -26,7 +29,7 @@ type mDoc struct { func (m *mDoc) Call() (interface{}, string) { // get the path from our current filename - res := m.findCode([]string{path.Dir(m.Fn)}) + res := m.findCode([]string{filepath.Dir(m.Fn)}) return res, "" } diff --git a/src/gosubli.me/margo/m_doc_test.go b/src/gosubli.me/margo/m_doc_test.go index e0b81d79..d8a17ac3 100644 --- a/src/gosubli.me/margo/m_doc_test.go +++ b/src/gosubli.me/margo/m_doc_test.go @@ -3,7 +3,7 @@ package main import ( "fmt" "os" - "path" + "path/filepath" "testing" "github.com/kr/pretty" @@ -13,7 +13,7 @@ import ( func usageSimple(t *testing.T, offset int, output []*Doc) { // get current dir wd, _ := os.Getwd() - fn := path.Join(wd, "testing/simple.go") + fn := filepath.Join(wd, "testing", "simple.go") dec := &mDoc{ Fn: fn, Src: nil, @@ -30,7 +30,7 @@ func usageSimple(t *testing.T, offset int, output []*Doc) { func findSimple(t *testing.T, offset int, output []*Doc) { // get current dir wd, _ := os.Getwd() - fn := path.Join(wd, "testing/simple.go") + fn := filepath.Join(wd, "testing", "simple.go") dec := &mDoc{ Fn: fn, Src: nil, @@ -86,7 +86,7 @@ func TestCallBasics_Framework(t *testing.T) { t, 115, //[]*Doc{&Doc{Src: "func Println()", Pkg: "fmt", Name: "Println", Kind: "func", Fn: "/usr/local/go/src/pkg/fmt/print.go", Row: 262, Col: 5}}, - []*Doc{&Doc{Src: "", Pkg: "fmt", Name: "Println", Kind: "func", Fn: "/fmt/print.go", Row: 262, Col: 5}}, + []*Doc{&Doc{Src: "", Pkg: "fmt", Name: "Println", Kind: "func", Fn: "print.go", Row: 262, Col: 5}}, ) } @@ -95,9 +95,9 @@ func TestUsages_StructField(t *testing.T) { t, 95, []*Doc{ - &Doc{Src: "", Pkg: "testing", Name: "value", Kind: "field", Fn: "testing/simple.go", Row: 5, Col: 1}, - &Doc{Src: "", Pkg: "testing", Name: "value", Kind: "field", Fn: "testing/simple.go", Row: 10, Col: 3}, - &Doc{Src: "", Pkg: "testing", Name: "value", Kind: "field", Fn: "testing/simple.go", Row: 11, Col: 15}, + &Doc{Src: "", Pkg: "testing", Name: "value", Kind: "field", Fn: "simple.go", Row: 5, Col: 1}, + &Doc{Src: "", Pkg: "testing", Name: "value", Kind: "field", Fn: "simple.go", Row: 10, Col: 3}, + &Doc{Src: "", Pkg: "testing", Name: "value", Kind: "field", Fn: "simple.go", Row: 11, Col: 15}, }, ) } @@ -106,7 +106,7 @@ func TestUsages_Framework(t *testing.T) { usageSimple( t, 115, - []*Doc{&Doc{Src: "", Pkg: "fmt", Name: "Println", Kind: "func", Fn: "testing/simple.go", Row: 11, Col: 5}}, + []*Doc{&Doc{Src: "", Pkg: "fmt", Name: "Println", Kind: "func", Fn: "simple.go", Row: 11, Col: 5}}, ) } @@ -115,6 +115,6 @@ func TestUsages_Import(t *testing.T) { usageSimple( t, 26, - []*Doc{&Doc{Src: "", Pkg: "testing", Name: "fmt", Kind: "package", Fn: "testing/simple.go", Row: 11, Col: 1}}, + []*Doc{&Doc{Src: "", Pkg: "testing", Name: "fmt", Kind: "package", Fn: "simple.go", Row: 11, Col: 1}}, ) } diff --git a/src/gosubli.me/margo/m_doc_types.go b/src/gosubli.me/margo/m_doc_types.go index a80019f2..9cca4f14 100644 --- a/src/gosubli.me/margo/m_doc_types.go +++ b/src/gosubli.me/margo/m_doc_types.go @@ -69,6 +69,7 @@ func (m *mDoc) findCode(packages []string) []*Doc { } pkg, err := w.Import("", pkgName, conf) if pkg == nil { + log.Printf("pkgName: %v, file: %v, dir: %v\n", pkgName, cursor.fileName, cursor.fileDir) log.Fatalln("error import path", err) } if cursor != nil && (m.FindInfo || m.FindDef || m.FindUse) { From 2a804c38b06b3817d1518f148fe77de34466a5c3 Mon Sep 17 00:00:00 2001 From: Douglas Clark Date: Sun, 30 Nov 2014 18:54:39 -0600 Subject: [PATCH 6/9] Added find usages to command list and default keyboard mapping --- Default (Linux).sublime-keymap | 6 ++++++ Default (OSX).sublime-keymap | 6 ++++++ Default (Windows).sublime-keymap | 6 ++++++ GoSublime.sublime-commands | 5 +++++ 4 files changed, 23 insertions(+) diff --git a/Default (Linux).sublime-keymap b/Default (Linux).sublime-keymap index ec4d8b75..d74a1d36 100644 --- a/Default (Linux).sublime-keymap +++ b/Default (Linux).sublime-keymap @@ -87,6 +87,12 @@ "args": {"mode": "hint"}, "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] }, + { + "keys": ["ctrl+.", "ctrl+u"], + "command": "gs_doc", + "args": {"mode": "usage"}, + "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] + }, { "keys": ["ctrl+.", "ctrl+."], "command": "show_overlay", diff --git a/Default (OSX).sublime-keymap b/Default (OSX).sublime-keymap index 75f17af0..265b03cc 100644 --- a/Default (OSX).sublime-keymap +++ b/Default (OSX).sublime-keymap @@ -81,6 +81,12 @@ "args": {"mode": "hint"}, "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] }, + { + "keys": ["super+.", "super+u"], + "command": "gs_doc", + "args": {"mode": "usage"}, + "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] + }, { "keys": ["super+.", "super+."], "command": "show_overlay", diff --git a/Default (Windows).sublime-keymap b/Default (Windows).sublime-keymap index 1df8c7f8..a061300c 100644 --- a/Default (Windows).sublime-keymap +++ b/Default (Windows).sublime-keymap @@ -87,6 +87,12 @@ "args": {"mode": "hint"}, "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] }, + { + "keys": ["ctrl+.", "ctrl+u"], + "command": "gs_doc", + "args": {"mode": "usage"}, + "context": [{ "key": "selector", "operator": "equal", "operand": "source.go" }] + }, { "keys": ["ctrl+.", "ctrl+."], "command": "show_overlay", diff --git a/GoSublime.sublime-commands b/GoSublime.sublime-commands index 02a631f5..92e64f1f 100644 --- a/GoSublime.sublime-commands +++ b/GoSublime.sublime-commands @@ -61,6 +61,11 @@ "command": "gs_doc", "args": {"mode": "hint"} }, + { + "caption": "GoSublime: Show Usages", + "command": "gs_doc", + "args": {"mode": "usage"} + }, { "caption": "GoSublime: Fmt the current file (without saving it)", "command": "gs_fmt" From 2d5f41c6488a1036f5e0f0fb246dc54a32723c03 Mon Sep 17 00:00:00 2001 From: Douglas Clark Date: Sun, 30 Nov 2014 19:02:01 -0600 Subject: [PATCH 7/9] Remove unused imports --- src/gosubli.me/margo/m_doc.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gosubli.me/margo/m_doc.go b/src/gosubli.me/margo/m_doc.go index 8dcdf2bf..17bc953d 100644 --- a/src/gosubli.me/margo/m_doc.go +++ b/src/gosubli.me/margo/m_doc.go @@ -1,7 +1,6 @@ package main import ( - "log" "path/filepath" ) From ed64552829ab20cd14310d3599e6d6db21fcd57a Mon Sep 17 00:00:00 2001 From: Douglas Clark Date: Sun, 30 Nov 2014 19:15:08 -0600 Subject: [PATCH 8/9] Fix issue where output would show error even on successful goto definition or find usage --- gsdoc.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gsdoc.py b/gsdoc.py index ec33d2cf..6148b7fa 100644 --- a/gsdoc.py +++ b/gsdoc.py @@ -68,10 +68,12 @@ def highlight(idx): lines.append(d.get('fn', '') + ':' + str(d.get('row', 0) + 1) + ':' + str(d.get('col', 0) + 1)) sublime.active_window().show_quick_panel(lines, callback, on_highlight=highlight) - + return + elif len(docs) == 1: open(docs[0]) - + return + elif mode == "hint": s = [] for d in docs: From 8ac973100980d8e72dde0e19395578f5330247ca Mon Sep 17 00:00:00 2001 From: Douglas Clark Date: Mon, 1 Dec 2014 17:00:07 -0600 Subject: [PATCH 9/9] Reduce Margo goto logging in normal mode. Clean test file. --- src/gosubli.me/margo/m_doc_test.go | 2 -- src/gosubli.me/margo/m_doc_types.go | 15 ++++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/gosubli.me/margo/m_doc_test.go b/src/gosubli.me/margo/m_doc_test.go index d8a17ac3..af787074 100644 --- a/src/gosubli.me/margo/m_doc_test.go +++ b/src/gosubli.me/margo/m_doc_test.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" "path/filepath" "testing" @@ -111,7 +110,6 @@ func TestUsages_Framework(t *testing.T) { } func TestUsages_Import(t *testing.T) { - fmt.Println("Test: %v") usageSimple( t, 26, diff --git a/src/gosubli.me/margo/m_doc_types.go b/src/gosubli.me/margo/m_doc_types.go index 9cca4f14..5109c6c3 100644 --- a/src/gosubli.me/margo/m_doc_types.go +++ b/src/gosubli.me/margo/m_doc_types.go @@ -239,7 +239,7 @@ func (w *PkgWalker) Import(parentDir string, name string, conf *PkgConfig) (pkg if cursor != nil && includeDefault && !foundCursor { f, err := w.parseFile(bp.Dir, cursor.fileName, cursor.src) - if err != nil { + if err != nil && typeVerbose { log.Printf("error parsing cursor package %s: %s\n", cursor.fileName, err) } else { cursor.pos = token.Pos(w.fset.File(f.Pos()).Base()) + token.Pos(cursor.cursorPos) @@ -403,7 +403,9 @@ func (w *PkgWalker) LookupImport(pkg *types.Package, pkgInfo *types.Info, cursor Row: fpos.Line - 1, Col: fpos.Column - 1, }) - log.Println(fpos) + if typeVerbose { + log.Println(fpos) + } } return ret @@ -556,7 +558,6 @@ func (w *PkgWalker) LookupObjects(pkg *types.Package, pkgInfo *types.Info, curso } } if cursorObj == nil { - log.Println("exit 1") return []*Doc{} } kind, err := parserObjKind(cursorObj) @@ -656,7 +657,9 @@ func (w *PkgWalker) LookupObjects(pkg *types.Package, pkgInfo *types.Info, curso Row: fpos.Line - 1, Col: fpos.Column - 1, }) - log.Println(fpos) + if typeVerbose { + log.Println(fpos) + } } if w.findInfo { if kind == ObjField && fieldTypeObj != nil { @@ -728,7 +731,9 @@ func (w *PkgWalker) LookupObjects(pkg *types.Package, pkgInfo *types.Info, curso Row: fpos.Line - 1, Col: fpos.Column - 1, }) - log.Println(fpos) + if typeVerbose { + log.Println(fpos) + } } return ret