diff --git a/.travis.yml b/.travis.yml index ae19babd..2cc4948f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: go go: - - 1.8.x - 1.9.x - 1.10.x + - 1.11.x go_import_path: github.com/sourcegraph/go-langserver diff --git a/langserver/completion.go b/langserver/completion.go index 85c92399..f776178c 100644 --- a/langserver/completion.go +++ b/langserver/completion.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/sourcegraph/go-langserver/langserver/internal/gocode" + "github.com/sourcegraph/go-langserver/langserver/internal/gocode/gbimporter" "github.com/sourcegraph/go-langserver/langserver/util" "github.com/sourcegraph/go-langserver/pkg/lsp" "github.com/sourcegraph/jsonrpc2" @@ -46,11 +47,21 @@ func (h *LangHandler) handleTextDocumentCompletion(ctx context.Context, conn jso return nil, fmt.Errorf("invalid position: %s:%d:%d (%s)", filename, params.Position.Line, params.Position.Character, why) } - ca, rangelen := gocode.AutoComplete(contents, filename, offset) - citems := make([]lsp.CompletionItem, len(ca)) - for i, it := range ca { + ac, err := gocode.AutoComplete(&gocode.AutoCompleteRequest{ + Filename: filename, + Data: contents, + Cursor: offset, + Builtin: true, + Source: !h.config.UseBinaryPkgCache, + Context: gbimporter.PackContext(h.BuildContext(ctx)), + }) + if err != nil { + return nil, fmt.Errorf("could not autocomplete %s: %v", filename, err) + } + citems := make([]lsp.CompletionItem, len(ac.Candidates)) + for i, it := range ac.Candidates { var kind lsp.CompletionItemKind - switch it.Class.String() { + switch it.Class { case "const": kind = CIKConstantSupported case "func": @@ -75,7 +86,7 @@ func (h *LangHandler) handleTextDocumentCompletion(ctx context.Context, conn jso InsertText: newText, TextEdit: &lsp.TextEdit{ Range: lsp.Range{ - Start: lsp.Position{Line: params.Position.Line, Character: params.Position.Character - rangelen}, + Start: lsp.Position{Line: params.Position.Line, Character: params.Position.Character - ac.Len}, End: lsp.Position{Line: params.Position.Line, Character: params.Position.Character}, }, NewText: newText, diff --git a/langserver/handler.go b/langserver/handler.go index a52ac4c0..0411cca8 100644 --- a/langserver/handler.go +++ b/langserver/handler.go @@ -15,7 +15,6 @@ import ( opentracing "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" - "github.com/sourcegraph/go-langserver/langserver/internal/gocode" "github.com/sourcegraph/go-langserver/pkg/lsp" "github.com/sourcegraph/go-langserver/pkg/lspext" "github.com/sourcegraph/jsonrpc2" @@ -222,9 +221,6 @@ func (h *LangHandler) Handle(ctx context.Context, conn jsonrpc2.JSONRPC2, req *j if err := h.reset(¶ms); err != nil { return nil, err } - if h.config.GocodeCompletionEnabled { - gocode.InitDaemon(h.BuildContext(ctx)) - } // PERF: Kick off a workspace/symbol in the background to warm up the server if yes, _ := strconv.ParseBool(envWarmupOnInitialize); yes { diff --git a/langserver/internal/gocode/autocompletecontext.go b/langserver/internal/gocode/autocompletecontext.go deleted file mode 100644 index 916aae1f..00000000 --- a/langserver/internal/gocode/autocompletecontext.go +++ /dev/null @@ -1,793 +0,0 @@ -package gocode - -import ( - "bytes" - "fmt" - "go/ast" - "go/parser" - "go/token" - "os" - "path/filepath" - "runtime" - "sort" - "strings" - "time" -) - -//------------------------------------------------------------------------- -// out_buffers -// -// Temporary structure for writing autocomplete response. -//------------------------------------------------------------------------- - -// fields must be exported for RPC -type candidate struct { - Name string - Type string - Class decl_class - Package string -} - -type out_buffers struct { - tmpbuf *bytes.Buffer - candidates []candidate - canonical_aliases map[string]string - ctx *auto_complete_context - tmpns map[string]bool - ignorecase bool -} - -func new_out_buffers(ctx *auto_complete_context) *out_buffers { - b := new(out_buffers) - b.tmpbuf = bytes.NewBuffer(make([]byte, 0, 1024)) - b.candidates = make([]candidate, 0, 64) - b.ctx = ctx - b.canonical_aliases = make(map[string]string) - for _, imp := range b.ctx.current.packages { - b.canonical_aliases[imp.abspath] = imp.alias - } - return b -} - -func (b *out_buffers) Len() int { - return len(b.candidates) -} - -func (b *out_buffers) Less(i, j int) bool { - x := b.candidates[i] - y := b.candidates[j] - if x.Class == y.Class { - return x.Name < y.Name - } - return x.Class < y.Class -} - -func (b *out_buffers) Swap(i, j int) { - b.candidates[i], b.candidates[j] = b.candidates[j], b.candidates[i] -} - -func (b *out_buffers) append_decl(p, name, pkg string, decl *decl, class decl_class) { - c1 := !g_config.ProposeBuiltins && decl.scope == g_universe_scope && decl.name != "Error" - c2 := class != decl_invalid && decl.class != class - c3 := class == decl_invalid && !has_prefix(name, p, b.ignorecase) - c4 := !decl.matches() - c5 := !check_type_expr(decl.typ) - - if c1 || c2 || c3 || c4 || c5 { - return - } - - decl.pretty_print_type(b.tmpbuf, b.canonical_aliases) - b.candidates = append(b.candidates, candidate{ - Name: name, - Type: b.tmpbuf.String(), - Class: decl.class, - Package: pkg, - }) - b.tmpbuf.Reset() -} - -func (b *out_buffers) append_embedded(p string, decl *decl, pkg string, class decl_class) { - if decl.embedded == nil { - return - } - - first_level := false - if b.tmpns == nil { - // first level, create tmp namespace - b.tmpns = make(map[string]bool) - first_level = true - - // add all children of the current decl to the namespace - for _, c := range decl.children { - b.tmpns[c.name] = true - } - } - - for _, emb := range decl.embedded { - typedecl := type_to_decl(emb, decl.scope) - if typedecl == nil { - continue - } - - // could be type alias - if typedecl.is_alias() { - typedecl = typedecl.type_dealias() - } - - // prevent infinite recursion here - if typedecl.is_visited() { - continue - } - typedecl.set_visited() - defer typedecl.clear_visited() - - for _, c := range typedecl.children { - if _, has := b.tmpns[c.name]; has { - continue - } - b.append_decl(p, c.name, pkg, c, class) - b.tmpns[c.name] = true - } - b.append_embedded(p, typedecl, pkg, class) - } - - if first_level { - // remove tmp namespace - b.tmpns = nil - } -} - -//------------------------------------------------------------------------- -// auto_complete_context -// -// Context that holds cache structures for autocompletion needs. It -// includes cache for packages and for main package files. -//------------------------------------------------------------------------- - -type auto_complete_context struct { - current *auto_complete_file // currently edited file - others []*decl_file_cache // other files of the current package - pkg *scope - - pcache package_cache // packages cache - declcache *decl_cache // top-level declarations cache -} - -func new_auto_complete_context(pcache package_cache, declcache *decl_cache) *auto_complete_context { - c := new(auto_complete_context) - c.current = new_auto_complete_file("", declcache.context) - c.pcache = pcache - c.declcache = declcache - return c -} - -func (c *auto_complete_context) update_caches() { - // temporary map for packages that we need to check for a cache expiration - // map is used as a set of unique items to prevent double checks - ps := make(map[string]*package_file_cache) - - // collect import information from all of the files - c.pcache.append_packages(ps, c.current.packages) - c.others = get_other_package_files(c.current.name, c.current.package_name, c.declcache) - for _, other := range c.others { - c.pcache.append_packages(ps, other.packages) - } - - update_packages(ps) - - // fix imports for all files - fixup_packages(c.current.filescope, c.current.packages, c.pcache) - for _, f := range c.others { - fixup_packages(f.filescope, f.packages, c.pcache) - } - - // At this point we have collected all top level declarations, now we need to - // merge them in the common package block. - c.merge_decls() -} - -func (c *auto_complete_context) merge_decls() { - c.pkg = new_scope(g_universe_scope) - merge_decls(c.current.filescope, c.pkg, c.current.decls) - merge_decls_from_packages(c.pkg, c.current.packages, c.pcache) - for _, f := range c.others { - merge_decls(f.filescope, c.pkg, f.decls) - merge_decls_from_packages(c.pkg, f.packages, c.pcache) - } - - // special pass for type aliases which also have methods, while this is - // valid code, it shouldn't happen a lot in practice, so, whatever - // let's move all type alias methods to their first non-alias type down in - // the chain - propagate_type_alias_methods(c.pkg) -} - -func (c *auto_complete_context) make_decl_set(scope *scope) map[string]*decl { - set := make(map[string]*decl, len(c.pkg.entities)*2) - make_decl_set_recursive(set, scope) - return set -} - -func (c *auto_complete_context) get_candidates_from_set(set map[string]*decl, partial string, class decl_class, b *out_buffers) { - for key, value := range set { - if value == nil { - continue - } - value.infer_type() - pkgname := "" - if pkg, ok := c.pcache[value.name]; ok { - pkgname = pkg.import_name - } - b.append_decl(partial, key, pkgname, value, class) - } -} - -func (c *auto_complete_context) get_candidates_from_decl_alias(cc cursor_context, class decl_class, b *out_buffers) { - if cc.decl.is_visited() { - return - } - - cc.decl = cc.decl.type_dealias() - if cc.decl == nil { - return - } - - cc.decl.set_visited() - defer cc.decl.clear_visited() - - c.get_candidates_from_decl(cc, class, b) - return -} - -func (c *auto_complete_context) decl_package_import_path(decl *decl) string { - if decl == nil || decl.scope == nil { - return "" - } - if pkg, ok := c.pcache[decl.scope.pkgname]; ok { - return pkg.import_name - } - return "" -} - -func (c *auto_complete_context) get_candidates_from_decl(cc cursor_context, class decl_class, b *out_buffers) { - if cc.decl.is_alias() { - c.get_candidates_from_decl_alias(cc, class, b) - return - } - - // propose all children of a subject declaration and - for _, decl := range cc.decl.children { - if cc.decl.class == decl_package && !ast.IsExported(decl.name) { - continue - } - if cc.struct_field { - // if we're autocompleting struct field init, skip all methods - if _, ok := decl.typ.(*ast.FuncType); ok { - continue - } - } - b.append_decl(cc.partial, decl.name, c.decl_package_import_path(decl), decl, class) - } - // propose all children of an underlying struct/interface type - adecl := advance_to_struct_or_interface(cc.decl) - if adecl != nil && adecl != cc.decl { - for _, decl := range adecl.children { - if decl.class == decl_var { - b.append_decl(cc.partial, decl.name, c.decl_package_import_path(decl), decl, class) - } - } - } - // propose all children of its embedded types - b.append_embedded(cc.partial, cc.decl, c.decl_package_import_path(cc.decl), class) -} - -func (c *auto_complete_context) get_import_candidates(partial string, b *out_buffers) { - currentPackagePath, pkgdirs := g_daemon.context.pkg_dirs() - resultSet := map[string]struct{}{} - for _, pkgdir := range pkgdirs { - // convert srcpath to pkgpath and get candidates - get_import_candidates_dir(pkgdir, filepath.FromSlash(partial), b.ignorecase, currentPackagePath, resultSet) - } - for k := range resultSet { - b.candidates = append(b.candidates, candidate{Name: k, Class: decl_import}) - } -} - -func get_import_candidates_dir(root, partial string, ignorecase bool, currentPackagePath string, r map[string]struct{}) { - var fpath string - var match bool - if strings.HasSuffix(partial, "/") { - fpath = filepath.Join(root, partial) - } else { - fpath = filepath.Join(root, filepath.Dir(partial)) - match = true - } - fi := readdir(fpath) - for i := range fi { - name := fi[i].Name() - rel, err := filepath.Rel(root, filepath.Join(fpath, name)) - if err != nil { - panic(err) - } - if match && !has_prefix(rel, partial, ignorecase) { - continue - } else if fi[i].IsDir() { - get_import_candidates_dir(root, rel+string(filepath.Separator), ignorecase, currentPackagePath, r) - } else { - ext := filepath.Ext(name) - if ext != ".a" { - continue - } else { - rel = rel[0 : len(rel)-2] - } - if ipath, ok := vendorlessImportPath(filepath.ToSlash(rel), currentPackagePath); ok { - r[ipath] = struct{}{} - } - } - } -} - -// returns three slices of the same length containing: -// 1. apropos names -// 2. apropos types (pretty-printed) -// 3. apropos classes -// and length of the part that should be replaced (if any) -func (c *auto_complete_context) apropos(file []byte, filename string, cursor int) ([]candidate, int) { - c.current.cursor = cursor - c.current.name = filename - - // Update caches and parse the current file. - // This process is quite complicated, because I was trying to design it in a - // concurrent fashion. Apparently I'm not really good at that. Hopefully - // will be better in future. - - // Ugly hack, but it actually may help in some cases. Insert a - // semicolon right at the cursor location. - filesemi := make([]byte, len(file)+1) - copy(filesemi, file[:cursor]) - filesemi[cursor] = ';' - copy(filesemi[cursor+1:], file[cursor:]) - - // Does full processing of the currently edited file (top-level declarations plus - // active function). - c.current.process_data(filesemi) - - // Updates cache of other files and packages. See the function for details of - // the process. At the end merges all the top-level declarations into the package - // block. - c.update_caches() - - // And we're ready to Go. ;) - - b := new_out_buffers(c) - - partial := 0 - cc, ok := c.deduce_cursor_context(file, cursor) - if !ok { - var d *decl - if ident, ok := cc.expr.(*ast.Ident); ok && g_config.UnimportedPackages { - p := resolveKnownPackageIdent(ident.Name, c.current.name, c.current.context) - c.pcache[p.name] = p - d = p.main - } - if d == nil { - return nil, 0 - } - cc.decl = d - } - - class := decl_invalid - switch cc.partial { - case "const": - class = decl_const - case "var": - class = decl_var - case "type": - class = decl_type - case "func": - class = decl_func - case "package": - class = decl_package - } - - if cc.decl_import { - c.get_import_candidates(cc.partial, b) - if cc.partial != "" && len(b.candidates) == 0 { - // as a fallback, try case insensitive approach - b.ignorecase = true - c.get_import_candidates(cc.partial, b) - } - } else if cc.decl == nil { - // In case if no declaraion is a subject of completion, propose all: - set := c.make_decl_set(c.current.scope) - c.get_candidates_from_set(set, cc.partial, class, b) - if cc.partial != "" && len(b.candidates) == 0 { - // as a fallback, try case insensitive approach - b.ignorecase = true - c.get_candidates_from_set(set, cc.partial, class, b) - } - } else { - c.get_candidates_from_decl(cc, class, b) - if cc.partial != "" && len(b.candidates) == 0 { - // as a fallback, try case insensitive approach - b.ignorecase = true - c.get_candidates_from_decl(cc, class, b) - } - } - partial = len(cc.partial) - - if len(b.candidates) == 0 { - return nil, 0 - } - - sort.Sort(b) - return b.candidates, partial -} - -func update_packages(ps map[string]*package_file_cache) { - // initiate package cache update - done := make(chan bool) - for _, p := range ps { - go func(p *package_file_cache) { - defer func() { - if err := recover(); err != nil { - print_backtrace(err) - done <- false - } - }() - p.update_cache() - done <- true - }(p) - } - - // wait for its completion - for range ps { - if !<-done { - panic("One of the package cache updaters panicked") - } - } -} - -func collect_type_alias_methods(d *decl) map[string]*decl { - if d == nil || d.is_visited() || !d.is_alias() { - return nil - } - d.set_visited() - defer d.clear_visited() - - // add own methods - m := map[string]*decl{} - for k, v := range d.children { - m[k] = v - } - - // recurse into more aliases - dd := type_to_decl(d.typ, d.scope) - for k, v := range collect_type_alias_methods(dd) { - m[k] = v - } - - return m -} - -func propagate_type_alias_methods(s *scope) { - for _, e := range s.entities { - if !e.is_alias() { - continue - } - - methods := collect_type_alias_methods(e) - if len(methods) == 0 { - continue - } - - dd := e.type_dealias() - if dd == nil { - continue - } - - decl := dd.deep_copy() - for _, v := range methods { - decl.add_child(v) - } - s.entities[decl.name] = decl - } -} - -func merge_decls(filescope *scope, pkg *scope, decls map[string]*decl) { - for _, d := range decls { - pkg.merge_decl(d) - } - filescope.parent = pkg -} - -func merge_decls_from_packages(pkgscope *scope, pkgs []package_import, pcache package_cache) { - for _, p := range pkgs { - path, alias := p.abspath, p.alias - if alias != "." { - continue - } - p := pcache[path].main - if p == nil { - continue - } - for _, d := range p.children { - if ast.IsExported(d.name) { - pkgscope.merge_decl(d) - } - } - } -} - -func fixup_packages(filescope *scope, pkgs []package_import, pcache package_cache) { - for _, p := range pkgs { - path, alias := p.abspath, p.alias - if alias == "" { - alias = pcache[path].defalias - } - // skip packages that will be merged to the package scope - if alias == "." { - continue - } - filescope.replace_decl(alias, pcache[path].main) - } -} - -func get_other_package_files(filename, packageName string, declcache *decl_cache) []*decl_file_cache { - others := find_other_package_files(filename, packageName) - - ret := make([]*decl_file_cache, len(others)) - done := make(chan *decl_file_cache) - - for _, nm := range others { - go func(name string) { - defer func() { - if err := recover(); err != nil { - print_backtrace(err) - done <- nil - } - }() - done <- declcache.get_and_update(name) - }(nm) - } - - for i := range others { - ret[i] = <-done - if ret[i] == nil { - panic("One of the decl cache updaters panicked") - } - } - - return ret -} - -func find_other_package_files(filename, package_name string) []string { - if filename == "" { - return nil - } - - dir, file := filepath.Split(filename) - files_in_dir, err := readdir_lstat(dir) - if err != nil { - panic(err) - } - - count := 0 - for _, stat := range files_in_dir { - ok, _ := filepath.Match("*.go", stat.Name()) - if !ok || stat.Name() == file { - continue - } - count++ - } - - out := make([]string, 0, count) - for _, stat := range files_in_dir { - const non_regular = os.ModeDir | os.ModeSymlink | - os.ModeDevice | os.ModeNamedPipe | os.ModeSocket - - ok, _ := filepath.Match("*.go", stat.Name()) - if !ok || stat.Name() == file || stat.Mode()&non_regular != 0 { - continue - } - - abspath := filepath.Join(dir, stat.Name()) - if file_package_name(abspath) == package_name { - n := len(out) - out = out[:n+1] - out[n] = abspath - } - } - - return out -} - -func file_package_name(filename string) string { - file, _ := parser.ParseFile(token.NewFileSet(), filename, nil, parser.PackageClauseOnly) - return file.Name.Name -} - -func make_decl_set_recursive(set map[string]*decl, scope *scope) { - for name, ent := range scope.entities { - if _, ok := set[name]; !ok { - set[name] = ent - } - } - if scope.parent != nil { - make_decl_set_recursive(set, scope.parent) - } -} - -func check_func_field_list(f *ast.FieldList) bool { - if f == nil { - return true - } - - for _, field := range f.List { - if !check_type_expr(field.Type) { - return false - } - } - return true -} - -// checks for a type expression correctness, it the type expression has -// ast.BadExpr somewhere, returns false, otherwise true -func check_type_expr(e ast.Expr) bool { - switch t := e.(type) { - case *ast.StarExpr: - return check_type_expr(t.X) - case *ast.ArrayType: - return check_type_expr(t.Elt) - case *ast.SelectorExpr: - return check_type_expr(t.X) - case *ast.FuncType: - a := check_func_field_list(t.Params) - b := check_func_field_list(t.Results) - return a && b - case *ast.MapType: - a := check_type_expr(t.Key) - b := check_type_expr(t.Value) - return a && b - case *ast.Ellipsis: - return check_type_expr(t.Elt) - case *ast.ChanType: - return check_type_expr(t.Value) - case *ast.BadExpr: - return false - default: - return true - } - return true -} - -//------------------------------------------------------------------------- -// Status output -//------------------------------------------------------------------------- - -type decl_slice []*decl - -func (s decl_slice) Less(i, j int) bool { - if s[i].class != s[j].class { - return s[i].name < s[j].name - } - return s[i].class < s[j].class -} -func (s decl_slice) Len() int { return len(s) } -func (s decl_slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -const ( - color_red = "\033[0;31m" - color_red_bold = "\033[1;31m" - color_green = "\033[0;32m" - color_green_bold = "\033[1;32m" - color_yellow = "\033[0;33m" - color_yellow_bold = "\033[1;33m" - color_blue = "\033[0;34m" - color_blue_bold = "\033[1;34m" - color_magenta = "\033[0;35m" - color_magenta_bold = "\033[1;35m" - color_cyan = "\033[0;36m" - color_cyan_bold = "\033[1;36m" - color_white = "\033[0;37m" - color_white_bold = "\033[1;37m" - color_none = "\033[0m" -) - -var g_decl_class_to_color = [...]string{ - decl_const: color_white_bold, - decl_var: color_magenta, - decl_type: color_cyan, - decl_func: color_green, - decl_package: color_red, - decl_methods_stub: color_red, -} - -var g_decl_class_to_string_status = [...]string{ - decl_const: " const", - decl_var: " var", - decl_type: " type", - decl_func: " func", - decl_package: "package", - decl_methods_stub: " stub", -} - -func (c *auto_complete_context) status() string { - - buf := bytes.NewBuffer(make([]byte, 0, 4096)) - fmt.Fprintf(buf, "Server's GOMAXPROCS == %d\n", runtime.GOMAXPROCS(0)) - fmt.Fprintf(buf, "\nPackage cache contains %d entries\n", len(c.pcache)) - fmt.Fprintf(buf, "\nListing these entries:\n") - for _, mod := range c.pcache { - fmt.Fprintf(buf, "\tname: %s (default alias: %s)\n", mod.name, mod.defalias) - fmt.Fprintf(buf, "\timports %d declarations and %d packages\n", len(mod.main.children), len(mod.others)) - if mod.mtime == -1 { - fmt.Fprintf(buf, "\tthis package stays in cache forever (built-in package)\n") - } else { - mtime := time.Unix(0, mod.mtime) - fmt.Fprintf(buf, "\tlast modification time: %s\n", mtime) - } - fmt.Fprintf(buf, "\n") - } - if c.current.name != "" { - fmt.Fprintf(buf, "Last edited file: %s (package: %s)\n", c.current.name, c.current.package_name) - if len(c.others) > 0 { - fmt.Fprintf(buf, "\nOther files from the current package:\n") - } - for _, f := range c.others { - fmt.Fprintf(buf, "\t%s\n", f.name) - } - fmt.Fprintf(buf, "\nListing declarations from files:\n") - - const status_decls = "\t%s%s" + color_none + " " + color_yellow + "%s" + color_none + "\n" - const status_decls_children = "\t%s%s" + color_none + " " + color_yellow + "%s" + color_none + " (%d)\n" - - fmt.Fprintf(buf, "\n%s:\n", c.current.name) - ds := make(decl_slice, len(c.current.decls)) - i := 0 - for _, d := range c.current.decls { - ds[i] = d - i++ - } - sort.Sort(ds) - for _, d := range ds { - if len(d.children) > 0 { - fmt.Fprintf(buf, status_decls_children, - g_decl_class_to_color[d.class], - g_decl_class_to_string_status[d.class], - d.name, len(d.children)) - } else { - fmt.Fprintf(buf, status_decls, - g_decl_class_to_color[d.class], - g_decl_class_to_string_status[d.class], - d.name) - } - } - - for _, f := range c.others { - fmt.Fprintf(buf, "\n%s:\n", f.name) - ds = make(decl_slice, len(f.decls)) - i = 0 - for _, d := range f.decls { - ds[i] = d - i++ - } - sort.Sort(ds) - for _, d := range ds { - if len(d.children) > 0 { - fmt.Fprintf(buf, status_decls_children, - g_decl_class_to_color[d.class], - g_decl_class_to_string_status[d.class], - d.name, len(d.children)) - } else { - fmt.Fprintf(buf, status_decls, - g_decl_class_to_color[d.class], - g_decl_class_to_string_status[d.class], - d.name) - } - } - } - } - return buf.String() -} diff --git a/langserver/internal/gocode/autocompletefile.go b/langserver/internal/gocode/autocompletefile.go deleted file mode 100644 index 6d47dcdb..00000000 --- a/langserver/internal/gocode/autocompletefile.go +++ /dev/null @@ -1,420 +0,0 @@ -package gocode - -import ( - "bytes" - "go/ast" - "go/parser" - "go/scanner" - "go/token" - "log" -) - -func parse_decl_list(fset *token.FileSet, data []byte) ([]ast.Decl, error) { - var buf bytes.Buffer - buf.WriteString("package p;") - buf.Write(data) - file, err := parser.ParseFile(fset, "", buf.Bytes(), parser.AllErrors) - if err != nil { - return file.Decls, err - } - return file.Decls, nil -} - -func log_parse_error(intro string, err error) { - if el, ok := err.(scanner.ErrorList); ok { - log.Printf("%s:", intro) - for _, er := range el { - log.Printf(" %s", er) - } - } else { - log.Printf("%s: %s", intro, err) - } -} - -//------------------------------------------------------------------------- -// auto_complete_file -//------------------------------------------------------------------------- - -type auto_complete_file struct { - name string - package_name string - - decls map[string]*decl - packages []package_import - filescope *scope - scope *scope - - cursor int // for current file buffer only - fset *token.FileSet - context *package_lookup_context -} - -func new_auto_complete_file(name string, context *package_lookup_context) *auto_complete_file { - p := new(auto_complete_file) - p.name = name - p.cursor = -1 - p.fset = token.NewFileSet() - p.context = context - return p -} - -func (f *auto_complete_file) offset(p token.Pos) int { - const fixlen = len("package p;") - return f.fset.Position(p).Offset - fixlen -} - -// this one is used for current file buffer exclusively -func (f *auto_complete_file) process_data(data []byte) { - cur, filedata, block := rip_off_decl(data, f.cursor) - file, err := parser.ParseFile(f.fset, "", filedata, parser.AllErrors) - if err != nil && *g_debug { - log_parse_error("Error parsing input file (outer block)", err) - } - f.package_name = package_name(file) - - f.decls = make(map[string]*decl) - f.packages = collect_package_imports(f.name, file.Decls, f.context) - f.filescope = new_scope(nil) - f.scope = f.filescope - - for _, d := range file.Decls { - anonymify_ast(d, 0, f.filescope) - } - - // process all top-level declarations - for _, decl := range file.Decls { - append_to_top_decls(f.decls, decl, f.scope) - } - if block != nil { - // process local function as top-level declaration - decls, err := parse_decl_list(f.fset, block) - if err != nil && *g_debug { - log_parse_error("Error parsing input file (inner block)", err) - } - - for _, d := range decls { - anonymify_ast(d, 0, f.filescope) - } - - for _, decl := range decls { - append_to_top_decls(f.decls, decl, f.scope) - } - - // process function internals - f.cursor = cur - for _, decl := range decls { - f.process_decl_locals(decl) - } - } - -} - -func (f *auto_complete_file) process_decl_locals(decl ast.Decl) { - switch t := decl.(type) { - case *ast.FuncDecl: - if f.cursor_in(t.Body) { - s := f.scope - f.scope = new_scope(f.scope) - - f.process_field_list(t.Recv, s) - f.process_field_list(t.Type.Params, s) - f.process_field_list(t.Type.Results, s) - f.process_block_stmt(t.Body) - } - default: - v := new(func_lit_visitor) - v.ctx = f - ast.Walk(v, decl) - } -} - -func (f *auto_complete_file) process_decl(decl ast.Decl) { - if t, ok := decl.(*ast.GenDecl); ok && f.offset(t.TokPos) > f.cursor { - return - } - prevscope := f.scope - foreach_decl(decl, func(data *foreach_decl_struct) { - class := ast_decl_class(data.decl) - if class != decl_type { - f.scope, prevscope = advance_scope(f.scope) - } - for i, name := range data.names { - typ, v, vi := data.type_value_index(i) - - d := new_decl_full(name.Name, class, ast_decl_flags(data.decl), typ, v, vi, prevscope) - if d == nil { - return - } - - f.scope.add_named_decl(d) - } - }) -} - -func (f *auto_complete_file) process_block_stmt(block *ast.BlockStmt) { - if block != nil && f.cursor_in(block) { - f.scope, _ = advance_scope(f.scope) - - for _, stmt := range block.List { - f.process_stmt(stmt) - } - - // hack to process all func literals - v := new(func_lit_visitor) - v.ctx = f - ast.Walk(v, block) - } -} - -type func_lit_visitor struct { - ctx *auto_complete_file -} - -func (v *func_lit_visitor) Visit(node ast.Node) ast.Visitor { - if t, ok := node.(*ast.FuncLit); ok && v.ctx.cursor_in(t.Body) { - s := v.ctx.scope - v.ctx.scope = new_scope(v.ctx.scope) - - v.ctx.process_field_list(t.Type.Params, s) - v.ctx.process_field_list(t.Type.Results, s) - v.ctx.process_block_stmt(t.Body) - - return nil - } - return v -} - -func (f *auto_complete_file) process_stmt(stmt ast.Stmt) { - switch t := stmt.(type) { - case *ast.DeclStmt: - f.process_decl(t.Decl) - case *ast.AssignStmt: - f.process_assign_stmt(t) - case *ast.IfStmt: - if f.cursor_in_if_head(t) { - f.process_stmt(t.Init) - } else if f.cursor_in_if_stmt(t) { - f.scope, _ = advance_scope(f.scope) - f.process_stmt(t.Init) - f.process_block_stmt(t.Body) - f.process_stmt(t.Else) - } - case *ast.BlockStmt: - f.process_block_stmt(t) - case *ast.RangeStmt: - f.process_range_stmt(t) - case *ast.ForStmt: - if f.cursor_in_for_head(t) { - f.process_stmt(t.Init) - } else if f.cursor_in(t.Body) { - f.scope, _ = advance_scope(f.scope) - - f.process_stmt(t.Init) - f.process_block_stmt(t.Body) - } - case *ast.SwitchStmt: - f.process_switch_stmt(t) - case *ast.TypeSwitchStmt: - f.process_type_switch_stmt(t) - case *ast.SelectStmt: - f.process_select_stmt(t) - case *ast.LabeledStmt: - f.process_stmt(t.Stmt) - } -} - -func (f *auto_complete_file) process_select_stmt(a *ast.SelectStmt) { - if !f.cursor_in(a.Body) { - return - } - var prevscope *scope - f.scope, prevscope = advance_scope(f.scope) - - var last_cursor_after *ast.CommClause - for _, s := range a.Body.List { - if cc := s.(*ast.CommClause); f.cursor > f.offset(cc.Colon) { - last_cursor_after = cc - } - } - - if last_cursor_after != nil { - if last_cursor_after.Comm != nil { - //if lastCursorAfter.Lhs != nil && lastCursorAfter.Tok == token.DEFINE { - if astmt, ok := last_cursor_after.Comm.(*ast.AssignStmt); ok && astmt.Tok == token.DEFINE { - vname := astmt.Lhs[0].(*ast.Ident).Name - v := new_decl_var(vname, nil, astmt.Rhs[0], -1, prevscope) - if v != nil { - f.scope.add_named_decl(v) - } - } - } - for _, s := range last_cursor_after.Body { - f.process_stmt(s) - } - } -} - -func (f *auto_complete_file) process_type_switch_stmt(a *ast.TypeSwitchStmt) { - if !f.cursor_in(a.Body) { - return - } - var prevscope *scope - f.scope, prevscope = advance_scope(f.scope) - - f.process_stmt(a.Init) - // type var - var tv *decl - if a, ok := a.Assign.(*ast.AssignStmt); ok { - lhs := a.Lhs - rhs := a.Rhs - if lhs != nil && len(lhs) == 1 { - tvname := lhs[0].(*ast.Ident).Name - tv = new_decl_var(tvname, nil, rhs[0], -1, prevscope) - } - } - - var last_cursor_after *ast.CaseClause - for _, s := range a.Body.List { - if cc := s.(*ast.CaseClause); f.cursor > f.offset(cc.Colon) { - last_cursor_after = cc - } - } - - if last_cursor_after != nil { - if tv != nil { - if last_cursor_after.List != nil && len(last_cursor_after.List) == 1 { - tv.typ = last_cursor_after.List[0] - tv.value = nil - } - f.scope.add_named_decl(tv) - } - for _, s := range last_cursor_after.Body { - f.process_stmt(s) - } - } -} - -func (f *auto_complete_file) process_switch_stmt(a *ast.SwitchStmt) { - if !f.cursor_in(a.Body) { - return - } - f.scope, _ = advance_scope(f.scope) - - f.process_stmt(a.Init) - var last_cursor_after *ast.CaseClause - for _, s := range a.Body.List { - if cc := s.(*ast.CaseClause); f.cursor > f.offset(cc.Colon) { - last_cursor_after = cc - } - } - if last_cursor_after != nil { - for _, s := range last_cursor_after.Body { - f.process_stmt(s) - } - } -} - -func (f *auto_complete_file) process_range_stmt(a *ast.RangeStmt) { - if !f.cursor_in(a.Body) { - return - } - var prevscope *scope - f.scope, prevscope = advance_scope(f.scope) - - if a.Tok == token.DEFINE { - if t, ok := a.Key.(*ast.Ident); ok { - d := new_decl_var(t.Name, nil, a.X, 0, prevscope) - if d != nil { - d.flags |= decl_rangevar - f.scope.add_named_decl(d) - } - } - - if a.Value != nil { - if t, ok := a.Value.(*ast.Ident); ok { - d := new_decl_var(t.Name, nil, a.X, 1, prevscope) - if d != nil { - d.flags |= decl_rangevar - f.scope.add_named_decl(d) - } - } - } - } - - f.process_block_stmt(a.Body) -} - -func (f *auto_complete_file) process_assign_stmt(a *ast.AssignStmt) { - if a.Tok != token.DEFINE || f.offset(a.TokPos) > f.cursor { - return - } - - names := make([]*ast.Ident, len(a.Lhs)) - for i, name := range a.Lhs { - id, ok := name.(*ast.Ident) - if !ok { - // something is wrong, just ignore the whole stmt - return - } - names[i] = id - } - - var prevscope *scope - f.scope, prevscope = advance_scope(f.scope) - - pack := decl_pack{names, nil, a.Rhs} - for i, name := range pack.names { - typ, v, vi := pack.type_value_index(i) - d := new_decl_var(name.Name, typ, v, vi, prevscope) - if d == nil { - continue - } - - f.scope.add_named_decl(d) - } -} - -func (f *auto_complete_file) process_field_list(field_list *ast.FieldList, s *scope) { - if field_list != nil { - decls := ast_field_list_to_decls(field_list, decl_var, 0, s, false) - for _, d := range decls { - f.scope.add_named_decl(d) - } - } -} - -func (f *auto_complete_file) cursor_in_if_head(s *ast.IfStmt) bool { - if f.cursor > f.offset(s.If) && f.cursor <= f.offset(s.Body.Lbrace) { - return true - } - return false -} - -func (f *auto_complete_file) cursor_in_if_stmt(s *ast.IfStmt) bool { - if f.cursor > f.offset(s.If) { - // magic -10 comes from auto_complete_file.offset method, see - // len() expr in there - if f.offset(s.End()) == -10 || f.cursor < f.offset(s.End()) { - return true - } - } - return false -} - -func (f *auto_complete_file) cursor_in_for_head(s *ast.ForStmt) bool { - if f.cursor > f.offset(s.For) && f.cursor <= f.offset(s.Body.Lbrace) { - return true - } - return false -} - -func (f *auto_complete_file) cursor_in(block *ast.BlockStmt) bool { - if f.cursor == -1 || block == nil { - return false - } - - if f.cursor > f.offset(block.Lbrace) && f.cursor <= f.offset(block.Rbrace) { - return true - } - return false -} diff --git a/langserver/internal/gocode/config.go b/langserver/internal/gocode/config.go deleted file mode 100644 index 8bc0ddaf..00000000 --- a/langserver/internal/gocode/config.go +++ /dev/null @@ -1,177 +0,0 @@ -package gocode - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "os" - "reflect" - "strconv" -) - -//------------------------------------------------------------------------- -// config -// -// Structure represents persistent config storage of the gocode daemon. Usually -// the config is located somewhere in ~/.config/gocode directory. -//------------------------------------------------------------------------- - -type config struct { - ProposeBuiltins bool `json:"propose-builtins"` - LibPath string `json:"lib-path"` - CustomPkgPrefix string `json:"custom-pkg-prefix"` - CustomVendorDir string `json:"custom-vendor-dir"` - Autobuild bool `json:"autobuild"` - ForceDebugOutput string `json:"force-debug-output"` - PackageLookupMode string `json:"package-lookup-mode"` - CloseTimeout int `json:"close-timeout"` - UnimportedPackages bool `json:"unimported-packages"` -} - -var g_config = config{ - ProposeBuiltins: false, - LibPath: "", - CustomPkgPrefix: "", - Autobuild: false, - ForceDebugOutput: "", - PackageLookupMode: "go", - CloseTimeout: 1800, - UnimportedPackages: false, -} - -var g_string_to_bool = map[string]bool{ - "t": true, - "true": true, - "y": true, - "yes": true, - "on": true, - "1": true, - "f": false, - "false": false, - "n": false, - "no": false, - "off": false, - "0": false, -} - -func set_value(v reflect.Value, value string) { - switch t := v; t.Kind() { - case reflect.Bool: - v, ok := g_string_to_bool[value] - if ok { - t.SetBool(v) - } - case reflect.String: - t.SetString(value) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - v, err := strconv.ParseInt(value, 10, 64) - if err == nil { - t.SetInt(v) - } - case reflect.Float32, reflect.Float64: - v, err := strconv.ParseFloat(value, 64) - if err == nil { - t.SetFloat(v) - } - } -} - -func list_value(v reflect.Value, name string, w io.Writer) { - switch t := v; t.Kind() { - case reflect.Bool: - fmt.Fprintf(w, "%s %v\n", name, t.Bool()) - case reflect.String: - fmt.Fprintf(w, "%s \"%v\"\n", name, t.String()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - fmt.Fprintf(w, "%s %v\n", name, t.Int()) - case reflect.Float32, reflect.Float64: - fmt.Fprintf(w, "%s %v\n", name, t.Float()) - } -} - -func (this *config) list() string { - str, typ := this.value_and_type() - buf := bytes.NewBuffer(make([]byte, 0, 256)) - for i := 0; i < str.NumField(); i++ { - v := str.Field(i) - name := typ.Field(i).Tag.Get("json") - list_value(v, name, buf) - } - return buf.String() -} - -func (this *config) list_option(name string) string { - str, typ := this.value_and_type() - buf := bytes.NewBuffer(make([]byte, 0, 256)) - for i := 0; i < str.NumField(); i++ { - v := str.Field(i) - nm := typ.Field(i).Tag.Get("json") - if nm == name { - list_value(v, name, buf) - } - } - return buf.String() -} - -func (this *config) set_option(name, value string) string { - str, typ := this.value_and_type() - buf := bytes.NewBuffer(make([]byte, 0, 256)) - for i := 0; i < str.NumField(); i++ { - v := str.Field(i) - nm := typ.Field(i).Tag.Get("json") - if nm == name { - set_value(v, value) - list_value(v, name, buf) - } - } - this.write() - return buf.String() - -} - -func (this *config) value_and_type() (reflect.Value, reflect.Type) { - v := reflect.ValueOf(this).Elem() - return v, v.Type() -} - -func (this *config) write() error { - data, err := json.Marshal(this) - if err != nil { - return err - } - - // make sure config dir exists - dir := config_dir() - if !file_exists(dir) { - os.MkdirAll(dir, 0755) - } - - f, err := os.Create(config_file()) - if err != nil { - return err - } - defer f.Close() - - _, err = f.Write(data) - if err != nil { - return err - } - - return nil -} - -func (this *config) read() error { - data, err := ioutil.ReadFile(config_file()) - if err != nil { - return err - } - - err = json.Unmarshal(data, this) - if err != nil { - return err - } - - return nil -} diff --git a/langserver/internal/gocode/cursorcontext.go b/langserver/internal/gocode/cursorcontext.go deleted file mode 100644 index 76f610a9..00000000 --- a/langserver/internal/gocode/cursorcontext.go +++ /dev/null @@ -1,584 +0,0 @@ -package gocode - -import ( - "bytes" - "go/ast" - "go/parser" - "go/scanner" - "go/token" - "log" -) - -type cursor_context struct { - decl *decl - partial string - struct_field bool - decl_import bool - - // store expression that was supposed to be deduced to "decl", however - // if decl is nil, then deduction failed, we could try to resolve it to - // unimported package instead - expr ast.Expr -} - -type token_iterator struct { - tokens []token_item - token_index int -} - -type token_item struct { - off int - tok token.Token - lit string -} - -func (i token_item) literal() string { - if i.tok.IsLiteral() { - return i.lit - } else { - return i.tok.String() - } - return "" -} - -func new_token_iterator(src []byte, cursor int) token_iterator { - tokens := make([]token_item, 0, 1000) - var s scanner.Scanner - fset := token.NewFileSet() - file := fset.AddFile("", fset.Base(), len(src)) - s.Init(file, src, nil, 0) - for { - pos, tok, lit := s.Scan() - off := fset.Position(pos).Offset - if tok == token.EOF || cursor <= off { - break - } - tokens = append(tokens, token_item{ - off: off, - tok: tok, - lit: lit, - }) - } - return token_iterator{ - tokens: tokens, - token_index: len(tokens) - 1, - } -} - -func (this *token_iterator) token() token_item { - return this.tokens[this.token_index] -} - -func (this *token_iterator) go_back() bool { - if this.token_index <= 0 { - return false - } - this.token_index-- - return true -} - -var bracket_pairs_map = map[token.Token]token.Token{ - token.RPAREN: token.LPAREN, - token.RBRACK: token.LBRACK, - token.RBRACE: token.LBRACE, -} - -func (ti *token_iterator) skip_to_left(left, right token.Token) bool { - if ti.token().tok == left { - return true - } - balance := 1 - for balance != 0 { - if !ti.go_back() { - return false - } - switch ti.token().tok { - case right: - balance++ - case left: - balance-- - } - } - return true -} - -// when the cursor is at the ')' or ']' or '}', move the cursor to an opposite -// bracket pair, this functions takes nested bracket pairs into account -func (this *token_iterator) skip_to_balanced_pair() bool { - right := this.token().tok - left := bracket_pairs_map[right] - return this.skip_to_left(left, right) -} - -// Move the cursor to the open brace of the current block, taking nested blocks -// into account. -func (this *token_iterator) skip_to_left_curly() bool { - return this.skip_to_left(token.LBRACE, token.RBRACE) -} - -func (ti *token_iterator) extract_type_alike() string { - if ti.token().tok != token.IDENT { // not Foo, return nothing - return "" - } - b := ti.token().literal() - if !ti.go_back() { // just Foo - return b - } - if ti.token().tok != token.PERIOD { // not .Foo, return Foo - return b - } - if !ti.go_back() { // just .Foo, return Foo (best choice recovery) - return b - } - if ti.token().tok != token.IDENT { // not lib.Foo, return Foo - return b - } - out := ti.token().literal() + "." + b // lib.Foo - ti.go_back() - return out -} - -// Extract the type expression right before the enclosing curly bracket block. -// Examples (# - the cursor): -// &lib.Struct{Whatever: 1, Hel#} // returns "lib.Struct" -// X{#} // returns X -// The idea is that we check if this type expression is a type and it is, we -// can apply special filtering for autocompletion results. -// Sadly, this doesn't cover anonymous structs. -func (ti *token_iterator) extract_struct_type() string { - if !ti.skip_to_left_curly() { - return "" - } - if !ti.go_back() { - return "" - } - if ti.token().tok == token.LBRACE { // Foo{#{}} - if !ti.go_back() { - return "" - } - } else if ti.token().tok == token.COMMA { // Foo{abc,#{}} - return ti.extract_struct_type() - } - typ := ti.extract_type_alike() - if typ == "" { - return "" - } - if ti.token().tok == token.RPAREN || ti.token().tok == token.MUL { - return "" - } - return typ -} - -// Starting from the token under the cursor move back and extract something -// that resembles a valid Go primary expression. Examples of primary expressions -// from Go spec: -// x -// 2 -// (s + ".txt") -// f(3.1415, true) -// Point{1, 2} -// m["foo"] -// s[i : j + 1] -// obj.color -// f.p[i].x() -// -// As you can see we can move through all of them using balanced bracket -// matching and applying simple rules -// E.g. -// Point{1, 2}.m["foo"].s[i : j + 1].MethodCall(a, func(a, b int) int { return a + b }). -// Can be seen as: -// Point{ }.m[ ].s[ ].MethodCall( ). -// Which boils the rules down to these connected via dots: -// ident -// ident[] -// ident{} -// ident() -// Of course there are also slightly more complicated rules for brackets: -// ident{}.ident()[5][4](), etc. -func (this *token_iterator) extract_go_expr() string { - orig := this.token_index - - // Contains the type of the previously scanned token (initialized with - // the token right under the cursor). This is the token to the *right* of - // the current one. - prev := this.token().tok -loop: - for { - if !this.go_back() { - return token_items_to_string(this.tokens[:orig]) - } - switch this.token().tok { - case token.PERIOD: - // If the '.' is not followed by IDENT, it's invalid. - if prev != token.IDENT { - break loop - } - case token.IDENT: - // Valid tokens after IDENT are '.', '[', '{' and '('. - switch prev { - case token.PERIOD, token.LBRACK, token.LBRACE, token.LPAREN: - // all ok - default: - break loop - } - case token.RBRACE: - // This one can only be a part of type initialization, like: - // Dummy{}.Hello() - // It is valid Go if Hello method is defined on a non-pointer receiver. - if prev != token.PERIOD { - break loop - } - this.skip_to_balanced_pair() - case token.RPAREN, token.RBRACK: - // After ']' and ')' their opening counterparts are valid '[', '(', - // as well as the dot. - switch prev { - case token.PERIOD, token.LBRACK, token.LPAREN: - // all ok - default: - break loop - } - this.skip_to_balanced_pair() - default: - break loop - } - prev = this.token().tok - } - expr := token_items_to_string(this.tokens[this.token_index+1 : orig]) - if *g_debug { - log.Printf("extracted expression tokens: %s", expr) - } - return expr -} - -// Given a slice of token_item, reassembles them into the original literal -// expression. -func token_items_to_string(tokens []token_item) string { - var buf bytes.Buffer - for _, t := range tokens { - buf.WriteString(t.literal()) - } - return buf.String() -} - -// this function is called when the cursor is at the '.' and you need to get the -// declaration before that dot -func (c *auto_complete_context) deduce_cursor_decl(iter *token_iterator) (*decl, ast.Expr) { - expr, err := parser.ParseExpr(iter.extract_go_expr()) - if err != nil { - return nil, nil - } - return expr_to_decl(expr, c.current.scope), expr -} - -// try to find and extract the surrounding struct literal type -func (c *auto_complete_context) deduce_struct_type_decl(iter *token_iterator) *decl { - typ := iter.extract_struct_type() - if typ == "" { - return nil - } - - expr, err := parser.ParseExpr(typ) - if err != nil { - return nil - } - decl := type_to_decl(expr, c.current.scope) - if decl == nil { - return nil - } - - // we allow only struct types here, but also support type aliases - if decl.is_alias() { - dd := decl.type_dealias() - if _, ok := dd.typ.(*ast.StructType); !ok { - return nil - } - } else if _, ok := decl.typ.(*ast.StructType); !ok { - return nil - } - return decl -} - -// Entry point from autocompletion, the function looks at text before the cursor -// and figures out the declaration the cursor is on. This declaration is -// used in filtering the resulting set of autocompletion suggestions. -func (c *auto_complete_context) deduce_cursor_context(file []byte, cursor int) (cursor_context, bool) { - if cursor <= 0 { - return cursor_context{}, true - } - - iter := new_token_iterator(file, cursor) - if len(iter.tokens) == 0 { - return cursor_context{}, false - } - - // figure out what is just before the cursor - switch tok := iter.token(); tok.tok { - case token.STRING: - // make sure cursor is inside the string - s := tok.literal() - if len(s) > 1 && s[len(s)-1] == '"' && tok.off+len(s) <= cursor { - return cursor_context{}, true - } - // now figure out if inside an import declaration - var ptok = token.STRING - for iter.go_back() { - itok := iter.token().tok - switch itok { - case token.STRING: - switch ptok { - case token.SEMICOLON, token.IDENT, token.PERIOD: - default: - return cursor_context{}, true - } - case token.LPAREN, token.SEMICOLON: - switch ptok { - case token.STRING, token.IDENT, token.PERIOD: - default: - return cursor_context{}, true - } - case token.IDENT, token.PERIOD: - switch ptok { - case token.STRING: - default: - return cursor_context{}, true - } - case token.IMPORT: - switch ptok { - case token.STRING, token.IDENT, token.PERIOD, token.LPAREN: - path_len := cursor - tok.off - path := s[1:path_len] - return cursor_context{decl_import: true, partial: path}, true - default: - return cursor_context{}, true - } - default: - return cursor_context{}, true - } - ptok = itok - } - case token.PERIOD: - // we're '.' - // figure out decl, Partial is "" - decl, expr := c.deduce_cursor_decl(&iter) - return cursor_context{decl: decl, expr: expr}, decl != nil - case token.IDENT, token.TYPE, token.CONST, token.VAR, token.FUNC, token.PACKAGE: - // we're '.' - // parse as Partial and figure out decl - var partial string - if tok.tok == token.IDENT { - // Calculate the offset of the cursor position within the identifier. - // For instance, if we are 'ab#c', we want partial_len = 2 and partial = ab. - partial_len := cursor - tok.off - - // If it happens that the cursor is past the end of the literal, - // means there is a space between the literal and the cursor, think - // of it as no context, because that's what it really is. - if partial_len > len(tok.literal()) { - return cursor_context{}, true - } - partial = tok.literal()[0:partial_len] - } else { - // Do not try to truncate if it is not an identifier. - partial = tok.literal() - } - - iter.go_back() - switch iter.token().tok { - case token.PERIOD: - decl, expr := c.deduce_cursor_decl(&iter) - return cursor_context{decl: decl, partial: partial, expr: expr}, decl != nil - case token.COMMA, token.LBRACE: - // This can happen for struct fields: - // &Struct{Hello: 1, Wor#} // (# - the cursor) - // Let's try to find the struct type - decl := c.deduce_struct_type_decl(&iter) - return cursor_context{ - decl: decl, - partial: partial, - struct_field: decl != nil, - }, true - default: - return cursor_context{partial: partial}, true - } - case token.COMMA, token.LBRACE: - // Try to parse the current expression as a structure initialization. - decl := c.deduce_struct_type_decl(&iter) - return cursor_context{ - decl: decl, - partial: "", - struct_field: decl != nil, - }, true - } - - return cursor_context{}, true -} - -// Decl deduction failed, but we're on ".", this ident can be an -// unexported package, let's try to match the ident against a set of known -// packages and if it matches try to import it. -// TODO: Right now I've made a static list of built-in packages, but in theory -// we could scan all GOPATH packages as well. Now, don't forget that default -// package name has nothing to do with package file name, that's why we need to -// scan the packages. And many of them will have conflicts. Can we make a smart -// prediction algorithm which will prefer certain packages over another ones? -func resolveKnownPackageIdent(ident string, filename string, context *package_lookup_context) *package_file_cache { - importPath, ok := knownPackageIdents[ident] - if !ok { - return nil - } - - path, ok := abs_path_for_package(filename, importPath, context) - if !ok { - return nil - } - - p := new_package_file_cache(path, importPath) - p.update_cache() - return p -} - -var knownPackageIdents = map[string]string{ - "adler32": "hash/adler32", - "aes": "crypto/aes", - "ascii85": "encoding/ascii85", - "asn1": "encoding/asn1", - "ast": "go/ast", - "atomic": "sync/atomic", - "base32": "encoding/base32", - "base64": "encoding/base64", - "big": "math/big", - "binary": "encoding/binary", - "bufio": "bufio", - "build": "go/build", - "bytes": "bytes", - "bzip2": "compress/bzip2", - "cgi": "net/http/cgi", - "cgo": "runtime/cgo", - "cipher": "crypto/cipher", - "cmplx": "math/cmplx", - "color": "image/color", - "constant": "go/constant", - "context": "context", - "cookiejar": "net/http/cookiejar", - "crc32": "hash/crc32", - "crc64": "hash/crc64", - "crypto": "crypto", - "csv": "encoding/csv", - "debug": "runtime/debug", - "des": "crypto/des", - "doc": "go/doc", - "draw": "image/draw", - "driver": "database/sql/driver", - "dsa": "crypto/dsa", - "dwarf": "debug/dwarf", - "ecdsa": "crypto/ecdsa", - "elf": "debug/elf", - "elliptic": "crypto/elliptic", - "encoding": "encoding", - "errors": "errors", - "exec": "os/exec", - "expvar": "expvar", - "fcgi": "net/http/fcgi", - "filepath": "path/filepath", - "flag": "flag", - "flate": "compress/flate", - "fmt": "fmt", - "fnv": "hash/fnv", - "format": "go/format", - "gif": "image/gif", - "gob": "encoding/gob", - "gosym": "debug/gosym", - "gzip": "compress/gzip", - "hash": "hash", - "heap": "container/heap", - "hex": "encoding/hex", - "hmac": "crypto/hmac", - "hpack": "vendor/golang_org/x/net/http2/hpack", - "html": "html", - "http": "net/http", - "httplex": "vendor/golang_org/x/net/lex/httplex", - "httptest": "net/http/httptest", - "httptrace": "net/http/httptrace", - "httputil": "net/http/httputil", - "image": "image", - "importer": "go/importer", - "io": "io", - "iotest": "testing/iotest", - "ioutil": "io/ioutil", - "jpeg": "image/jpeg", - "json": "encoding/json", - "jsonrpc": "net/rpc/jsonrpc", - "list": "container/list", - "log": "log", - "lzw": "compress/lzw", - "macho": "debug/macho", - "mail": "net/mail", - "math": "math", - "md5": "crypto/md5", - "mime": "mime", - "multipart": "mime/multipart", - "net": "net", - "os": "os", - "palette": "image/color/palette", - "parse": "text/template/parse", - "parser": "go/parser", - "path": "path", - "pe": "debug/pe", - "pem": "encoding/pem", - "pkix": "crypto/x509/pkix", - "plan9obj": "debug/plan9obj", - "png": "image/png", - "pprof": "net/http/pprof", - "printer": "go/printer", - "quick": "testing/quick", - "quotedprintable": "mime/quotedprintable", - "race": "runtime/race", - "rand": "math/rand", - "rc4": "crypto/rc4", - "reflect": "reflect", - "regexp": "regexp", - "ring": "container/ring", - "rpc": "net/rpc", - "rsa": "crypto/rsa", - "runtime": "runtime", - "scanner": "text/scanner", - "sha1": "crypto/sha1", - "sha256": "crypto/sha256", - "sha512": "crypto/sha512", - "signal": "os/signal", - "smtp": "net/smtp", - "sort": "sort", - "sql": "database/sql", - "strconv": "strconv", - "strings": "strings", - "subtle": "crypto/subtle", - "suffixarray": "index/suffixarray", - "sync": "sync", - "syntax": "regexp/syntax", - "syscall": "syscall", - "syslog": "log/syslog", - "tabwriter": "text/tabwriter", - "tar": "archive/tar", - "template": "html/template", - "testing": "testing", - "textproto": "net/textproto", - "time": "time", - "tls": "crypto/tls", - "token": "go/token", - "trace": "runtime/trace", - "types": "go/types", - "unicode": "unicode", - "url": "net/url", - "user": "os/user", - "utf16": "unicode/utf16", - "utf8": "unicode/utf8", - "x509": "crypto/x509", - "xml": "encoding/xml", - "zip": "archive/zip", - "zlib": "compress/zlib", - //"scanner": "go/scanner", // DUP: prefer text/scanner - //"template": "text/template", // DUP: prefer html/template - //"pprof": "runtime/pprof", // DUP: prefer net/http/pprof - //"rand": "crypto/rand", // DUP: prefer math/rand -} diff --git a/langserver/internal/gocode/decl.go b/langserver/internal/gocode/decl.go deleted file mode 100644 index 3e6b8189..00000000 --- a/langserver/internal/gocode/decl.go +++ /dev/null @@ -1,1473 +0,0 @@ -package gocode - -import ( - "bytes" - "fmt" - "go/ast" - "go/token" - "io" - "reflect" - "strings" - "sync" -) - -// decl.class -type decl_class int16 - -const ( - decl_invalid = decl_class(-1 + iota) - - // these are in a sorted order - decl_const - decl_func - decl_import - decl_package - decl_type - decl_var - - // this one serves as a temporary type for those methods that were - // declared before their actual owner - decl_methods_stub -) - -func (this decl_class) String() string { - switch this { - case decl_invalid: - return "PANIC" - case decl_const: - return "const" - case decl_func: - return "func" - case decl_import: - return "import" - case decl_package: - return "package" - case decl_type: - return "type" - case decl_var: - return "var" - case decl_methods_stub: - return "IF YOU SEE THIS, REPORT A BUG" // :D - } - panic("unreachable") -} - -// decl.flags -type decl_flags int16 - -const ( - decl_foreign decl_flags = 1 << iota // imported from another package - - // means that the decl is a part of the range statement - // its type is inferred in a special way - decl_rangevar - - // decl of decl_type class is a type alias - decl_alias - - // for preventing infinite recursions and loops in type inference code - decl_visited -) - -//------------------------------------------------------------------------- -// decl -// -// The most important data structure of the whole gocode project. It -// describes a single declaration and its children. -//------------------------------------------------------------------------- - -type decl struct { - // Name starts with '$' if the declaration describes an anonymous type. - // '$s_%d' for anonymous struct types - // '$i_%d' for anonymous interface types - name string - typ ast.Expr - class decl_class - flags decl_flags - - // functions for interface type, fields+methods for struct type - children map[string]*decl - - // embedded types - embedded []ast.Expr - - // if the type is unknown at AST building time, I'm using these - value ast.Expr - - // if it's a multiassignment and the Value is a CallExpr, it is being set - // to an index into the return value tuple, otherwise it's a -1 - value_index int - - // scope where this Decl was declared in (not its visibilty scope!) - // Decl uses it for type inference - scope *scope -} - -func ast_decl_type(d ast.Decl) ast.Expr { - switch t := d.(type) { - case *ast.GenDecl: - switch t.Tok { - case token.CONST, token.VAR: - c := t.Specs[0].(*ast.ValueSpec) - return c.Type - case token.TYPE: - t := t.Specs[0].(*ast.TypeSpec) - return t.Type - } - case *ast.FuncDecl: - return t.Type - } - panic("unreachable") - return nil -} - -func ast_decl_flags(d ast.Decl) decl_flags { - switch t := d.(type) { - case *ast.GenDecl: - switch t.Tok { - case token.TYPE: - if isAliasTypeSpec(t.Specs[0].(*ast.TypeSpec)) { - return decl_alias - } - } - } - return 0 -} - -func ast_decl_class(d ast.Decl) decl_class { - switch t := d.(type) { - case *ast.GenDecl: - switch t.Tok { - case token.VAR: - return decl_var - case token.CONST: - return decl_const - case token.TYPE: - return decl_type - } - case *ast.FuncDecl: - return decl_func - } - panic("unreachable") -} - -func ast_decl_convertable(d ast.Decl) bool { - switch t := d.(type) { - case *ast.GenDecl: - switch t.Tok { - case token.VAR, token.CONST, token.TYPE: - return true - } - case *ast.FuncDecl: - return true - } - return false -} - -func ast_field_list_to_decls(f *ast.FieldList, class decl_class, flags decl_flags, scope *scope, add_anonymous bool) map[string]*decl { - count := 0 - for _, field := range f.List { - count += len(field.Names) - } - - decls := make(map[string]*decl, count) - for _, field := range f.List { - for _, name := range field.Names { - if flags&decl_foreign != 0 && !ast.IsExported(name.Name) { - continue - } - d := &decl{ - name: name.Name, - typ: field.Type, - class: class, - flags: flags, - scope: scope, - value_index: -1, - } - decls[d.name] = d - } - - // add anonymous field as a child (type embedding) - if class == decl_var && field.Names == nil && add_anonymous { - tp := get_type_path(field.Type) - if flags&decl_foreign != 0 && !ast.IsExported(tp.name) { - continue - } - d := &decl{ - name: tp.name, - typ: field.Type, - class: class, - flags: flags, - scope: scope, - value_index: -1, - } - decls[d.name] = d - } - } - return decls -} - -func ast_field_list_to_embedded(f *ast.FieldList) []ast.Expr { - count := 0 - for _, field := range f.List { - if field.Names == nil || field.Names[0].Name == "?" { - count++ - } - } - - if count == 0 { - return nil - } - - embedded := make([]ast.Expr, count) - i := 0 - for _, field := range f.List { - if field.Names == nil || field.Names[0].Name == "?" { - embedded[i] = field.Type - i++ - } - } - - return embedded -} - -func ast_type_to_embedded(ty ast.Expr) []ast.Expr { - switch t := ty.(type) { - case *ast.StructType: - return ast_field_list_to_embedded(t.Fields) - case *ast.InterfaceType: - return ast_field_list_to_embedded(t.Methods) - } - return nil -} - -func ast_type_to_children(ty ast.Expr, flags decl_flags, scope *scope) map[string]*decl { - switch t := ty.(type) { - case *ast.StructType: - return ast_field_list_to_decls(t.Fields, decl_var, flags, scope, true) - case *ast.InterfaceType: - return ast_field_list_to_decls(t.Methods, decl_func, flags, scope, false) - } - return nil -} - -//------------------------------------------------------------------------- -// anonymous_id_gen -// -// ID generator for anonymous types (thread-safe) -//------------------------------------------------------------------------- - -type anonymous_id_gen struct { - sync.Mutex - i int -} - -func (a *anonymous_id_gen) gen() (id int) { - a.Lock() - defer a.Unlock() - id = a.i - a.i++ - return -} - -var g_anon_gen anonymous_id_gen - -//------------------------------------------------------------------------- - -func check_for_anon_type(t ast.Expr, flags decl_flags, s *scope) ast.Expr { - if t == nil { - return nil - } - var name string - - switch t.(type) { - case *ast.StructType: - name = fmt.Sprintf("$s_%d", g_anon_gen.gen()) - case *ast.InterfaceType: - name = fmt.Sprintf("$i_%d", g_anon_gen.gen()) - } - - if name != "" { - anonymify_ast(t, flags, s) - d := new_decl_full(name, decl_type, flags, t, nil, -1, s) - s.add_named_decl(d) - return ast.NewIdent(name) - } - return t -} - -//------------------------------------------------------------------------- - -func new_decl_full(name string, class decl_class, flags decl_flags, typ, v ast.Expr, vi int, s *scope) *decl { - if name == "_" { - return nil - } - d := new(decl) - d.name = name - d.class = class - d.flags = flags - d.typ = typ - d.value = v - d.value_index = vi - d.scope = s - d.children = ast_type_to_children(d.typ, flags, s) - d.embedded = ast_type_to_embedded(d.typ) - return d -} - -func new_decl(name string, class decl_class, scope *scope) *decl { - decl := new(decl) - decl.name = name - decl.class = class - decl.value_index = -1 - decl.scope = scope - return decl -} - -func new_decl_var(name string, typ ast.Expr, value ast.Expr, vindex int, scope *scope) *decl { - if name == "_" { - return nil - } - decl := new(decl) - decl.name = name - decl.class = decl_var - decl.typ = typ - decl.value = value - decl.value_index = vindex - decl.scope = scope - return decl -} - -func method_of(d ast.Decl) string { - if t, ok := d.(*ast.FuncDecl); ok { - if t.Recv != nil && len(t.Recv.List) != 0 { - switch t := t.Recv.List[0].Type.(type) { - case *ast.StarExpr: - if se, ok := t.X.(*ast.SelectorExpr); ok { - return se.Sel.Name - } - if ident, ok := t.X.(*ast.Ident); ok { - return ident.Name - } - return "" - case *ast.Ident: - return t.Name - default: - return "" - } - } - } - return "" -} - -func (other *decl) deep_copy() *decl { - d := new(decl) - d.name = other.name - d.class = other.class - d.flags = other.flags - d.typ = other.typ - d.value = other.value - d.value_index = other.value_index - d.children = make(map[string]*decl, len(other.children)) - for key, value := range other.children { - d.children[key] = value - } - if other.embedded != nil { - d.embedded = make([]ast.Expr, len(other.embedded)) - copy(d.embedded, other.embedded) - } - d.scope = other.scope - return d -} - -func (d *decl) is_rangevar() bool { - return d.flags&decl_rangevar != 0 -} - -func (d *decl) is_alias() bool { - return d.flags&decl_alias != 0 -} - -func (d *decl) is_visited() bool { - return d.flags&decl_visited != 0 -} - -func (d *decl) set_visited() { - d.flags |= decl_visited -} - -func (d *decl) clear_visited() { - d.flags &^= decl_visited -} - -func (d *decl) expand_or_replace(other *decl) { - // expand only if it's a methods stub, otherwise simply keep it as is - if d.class != decl_methods_stub && other.class != decl_methods_stub { - return - } - - if d.class == decl_methods_stub { - d.typ = other.typ - d.class = other.class - d.flags = other.flags - } - - if other.children != nil { - for _, c := range other.children { - d.add_child(c) - } - } - - if other.embedded != nil { - d.embedded = other.embedded - d.scope = other.scope - } -} - -func (d *decl) matches() bool { - if strings.HasPrefix(d.name, "$") || d.class == decl_methods_stub { - return false - } - return true -} - -func (d *decl) pretty_print_type(out io.Writer, canonical_aliases map[string]string) { - switch d.class { - case decl_type: - switch d.typ.(type) { - case *ast.StructType: - // TODO: not used due to anonymify? - fmt.Fprintf(out, "struct") - case *ast.InterfaceType: - // TODO: not used due to anonymify? - fmt.Fprintf(out, "interface") - default: - if d.typ != nil { - pretty_print_type_expr(out, d.typ, canonical_aliases) - } - } - case decl_var: - if d.typ != nil { - pretty_print_type_expr(out, d.typ, canonical_aliases) - } - case decl_func: - pretty_print_type_expr(out, d.typ, canonical_aliases) - } -} - -func (d *decl) add_child(cd *decl) { - if d.children == nil { - d.children = make(map[string]*decl) - } - d.children[cd.name] = cd -} - -func check_for_builtin_funcs(typ *ast.Ident, c *ast.CallExpr, scope *scope) (ast.Expr, *scope) { - if strings.HasPrefix(typ.Name, "func(") { - if t, ok := c.Fun.(*ast.Ident); ok { - switch t.Name { - case "new": - if len(c.Args) > 0 { - e := new(ast.StarExpr) - e.X = c.Args[0] - return e, scope - } - case "make": - if len(c.Args) > 0 { - return c.Args[0], scope - } - case "append": - if len(c.Args) > 0 { - t, scope, _ := infer_type(c.Args[0], scope, -1) - return t, scope - } - case "complex": - // TODO: fix it - return ast.NewIdent("complex"), g_universe_scope - case "closed": - return ast.NewIdent("bool"), g_universe_scope - case "cap": - return ast.NewIdent("int"), g_universe_scope - case "copy": - return ast.NewIdent("int"), g_universe_scope - case "len": - return ast.NewIdent("int"), g_universe_scope - } - // TODO: - // func recover() interface{} - // func imag(c ComplexType) FloatType - // func real(c ComplexType) FloatType - } - } - return nil, nil -} - -func func_return_type(f *ast.FuncType, index int) ast.Expr { - if f.Results == nil { - return nil - } - - if index == -1 { - return f.Results.List[0].Type - } - - i := 0 - var field *ast.Field - for _, field = range f.Results.List { - n := 1 - if field.Names != nil { - n = len(field.Names) - } - if i <= index && index < i+n { - return field.Type - } - i += n - } - return nil -} - -type type_path struct { - pkg string - name string -} - -func (tp *type_path) is_nil() bool { - return tp.pkg == "" && tp.name == "" -} - -// converts type expressions like: -// ast.Expr -// *ast.Expr -// $ast$go/ast.Expr -// to a path that can be used to lookup a type related Decl -func get_type_path(e ast.Expr) (r type_path) { - if e == nil { - return type_path{"", ""} - } - - switch t := e.(type) { - case *ast.Ident: - r.name = t.Name - case *ast.StarExpr: - r = get_type_path(t.X) - case *ast.SelectorExpr: - if ident, ok := t.X.(*ast.Ident); ok { - r.pkg = ident.Name - } - r.name = t.Sel.Name - } - return -} - -func lookup_path(tp type_path, scope *scope) *decl { - if tp.is_nil() { - return nil - } - var decl *decl - if tp.pkg != "" { - decl = scope.lookup(tp.pkg) - // return nil early if the package wasn't found but it's part - // of the type specification - if decl == nil { - return nil - } - } - - if decl != nil { - if tp.name != "" { - return decl.find_child(tp.name) - } else { - return decl - } - } - - return scope.lookup(tp.name) -} - -func lookup_pkg(tp type_path, scope *scope) string { - if tp.is_nil() { - return "" - } - if tp.pkg == "" { - return "" - } - decl := scope.lookup(tp.pkg) - if decl == nil { - return "" - } - return decl.name -} - -func type_to_decl(t ast.Expr, scope *scope) *decl { - tp := get_type_path(t) - d := lookup_path(tp, scope) - if d != nil && d.class == decl_var { - // weird variable declaration pointing to itself - return nil - } - return d -} - -func expr_to_decl(e ast.Expr, scope *scope) *decl { - t, scope, _ := infer_type(e, scope, -1) - return type_to_decl(t, scope) -} - -//------------------------------------------------------------------------- -// Type inference -//------------------------------------------------------------------------- - -type type_predicate func(ast.Expr) bool - -func advance_to_type(pred type_predicate, v ast.Expr, scope *scope) (ast.Expr, *scope) { - if pred(v) { - return v, scope - } - - decl := type_to_decl(v, scope) - if decl == nil { - return nil, nil - } - - if decl.is_visited() { - return nil, nil - } - decl.set_visited() - defer decl.clear_visited() - - return advance_to_type(pred, decl.typ, decl.scope) -} - -func advance_to_struct_or_interface(decl *decl) *decl { - if decl.is_visited() { - return nil - } - decl.set_visited() - defer decl.clear_visited() - - if struct_interface_predicate(decl.typ) { - return decl - } - - decl = type_to_decl(decl.typ, decl.scope) - if decl == nil { - return nil - } - return advance_to_struct_or_interface(decl) -} - -func struct_interface_predicate(v ast.Expr) bool { - switch v.(type) { - case *ast.StructType, *ast.InterfaceType: - return true - } - return false -} - -func chan_predicate(v ast.Expr) bool { - _, ok := v.(*ast.ChanType) - return ok -} - -func index_predicate(v ast.Expr) bool { - switch v.(type) { - case *ast.ArrayType, *ast.MapType, *ast.Ellipsis: - return true - } - return false -} - -func star_predicate(v ast.Expr) bool { - _, ok := v.(*ast.StarExpr) - return ok -} - -func func_predicate(v ast.Expr) bool { - _, ok := v.(*ast.FuncType) - return ok -} - -func range_predicate(v ast.Expr) bool { - switch t := v.(type) { - case *ast.Ident: - if t.Name == "string" { - return true - } - case *ast.ArrayType, *ast.MapType, *ast.ChanType, *ast.Ellipsis: - return true - } - return false -} - -type anonymous_typer struct { - flags decl_flags - scope *scope -} - -func (a *anonymous_typer) Visit(node ast.Node) ast.Visitor { - switch t := node.(type) { - case *ast.CompositeLit: - t.Type = check_for_anon_type(t.Type, a.flags, a.scope) - case *ast.MapType: - t.Key = check_for_anon_type(t.Key, a.flags, a.scope) - t.Value = check_for_anon_type(t.Value, a.flags, a.scope) - case *ast.ArrayType: - t.Elt = check_for_anon_type(t.Elt, a.flags, a.scope) - case *ast.Ellipsis: - t.Elt = check_for_anon_type(t.Elt, a.flags, a.scope) - case *ast.ChanType: - t.Value = check_for_anon_type(t.Value, a.flags, a.scope) - case *ast.Field: - t.Type = check_for_anon_type(t.Type, a.flags, a.scope) - case *ast.CallExpr: - t.Fun = check_for_anon_type(t.Fun, a.flags, a.scope) - case *ast.ParenExpr: - t.X = check_for_anon_type(t.X, a.flags, a.scope) - case *ast.StarExpr: - t.X = check_for_anon_type(t.X, a.flags, a.scope) - case *ast.GenDecl: - switch t.Tok { - case token.VAR: - for _, s := range t.Specs { - vs := s.(*ast.ValueSpec) - vs.Type = check_for_anon_type(vs.Type, a.flags, a.scope) - } - } - } - return a -} - -func anonymify_ast(node ast.Node, flags decl_flags, scope *scope) { - v := anonymous_typer{flags, scope} - ast.Walk(&v, node) -} - -// RETURNS: -// - type expression which represents a full name of a type -// - bool whether a type expression is actually a type (used internally) -// - scope in which type makes sense -func infer_type(v ast.Expr, scope *scope, index int) (ast.Expr, *scope, bool) { - switch t := v.(type) { - case *ast.CompositeLit: - return t.Type, scope, true - case *ast.Ident: - if d := scope.lookup(t.Name); d != nil { - if d.class == decl_package { - return ast.NewIdent(t.Name), scope, false - } - typ, scope := d.infer_type() - return typ, scope, d.class == decl_type - } - case *ast.UnaryExpr: - switch t.Op { - case token.AND: - // &a makes sense only with values, don't even check for type - it, s, _ := infer_type(t.X, scope, -1) - if it == nil { - break - } - - e := new(ast.StarExpr) - e.X = it - return e, s, false - case token.ARROW: - // <-a makes sense only with values - it, s, _ := infer_type(t.X, scope, -1) - if it == nil { - break - } - switch index { - case -1, 0: - it, s = advance_to_type(chan_predicate, it, s) - return it.(*ast.ChanType).Value, s, false - case 1: - // technically it's a value, but in case of index == 1 - // it is always the last infer operation - return ast.NewIdent("bool"), g_universe_scope, false - } - case token.ADD, token.NOT, token.SUB, token.XOR: - it, s, _ := infer_type(t.X, scope, -1) - if it == nil { - break - } - return it, s, false - } - case *ast.BinaryExpr: - switch t.Op { - case token.EQL, token.NEQ, token.LSS, token.LEQ, - token.GTR, token.GEQ, token.LOR, token.LAND: - // logic operations, the result is a bool, always - return ast.NewIdent("bool"), g_universe_scope, false - case token.ADD, token.SUB, token.MUL, token.QUO, token.OR, - token.XOR, token.REM, token.AND, token.AND_NOT: - // try X, then Y, they should be the same anyway - it, s, _ := infer_type(t.X, scope, -1) - if it == nil { - it, s, _ = infer_type(t.Y, scope, -1) - if it == nil { - break - } - } - return it, s, false - case token.SHL, token.SHR: - // try only X for shifts, Y is always uint - it, s, _ := infer_type(t.X, scope, -1) - if it == nil { - break - } - return it, s, false - } - case *ast.IndexExpr: - // something[another] always returns a value and it works on a value too - it, s, _ := infer_type(t.X, scope, -1) - if it == nil { - break - } - it, s = advance_to_type(index_predicate, it, s) - switch t := it.(type) { - case *ast.ArrayType: - return t.Elt, s, false - case *ast.Ellipsis: - return t.Elt, s, false - case *ast.MapType: - switch index { - case -1, 0: - return t.Value, s, false - case 1: - return ast.NewIdent("bool"), g_universe_scope, false - } - } - case *ast.SliceExpr: - // something[start : end] always returns a value - it, s, _ := infer_type(t.X, scope, -1) - if it == nil { - break - } - it, s = advance_to_type(index_predicate, it, s) - switch t := it.(type) { - case *ast.ArrayType: - e := new(ast.ArrayType) - e.Elt = t.Elt - return e, s, false - } - case *ast.StarExpr: - it, s, is_type := infer_type(t.X, scope, -1) - if it == nil { - break - } - if is_type { - // if it's a type, add * modifier, make it a 'pointer of' type - e := new(ast.StarExpr) - e.X = it - return e, s, true - } else { - it, s := advance_to_type(star_predicate, it, s) - if se, ok := it.(*ast.StarExpr); ok { - return se.X, s, false - } - } - case *ast.CallExpr: - // this is a function call or a type cast: - // myFunc(1,2,3) or int16(myvar) - it, s, is_type := infer_type(t.Fun, scope, -1) - if it == nil { - break - } - - if is_type { - // a type cast - return it, scope, false - } else { - // it must be a function call or a built-in function - // first check for built-in - if ct, ok := it.(*ast.Ident); ok { - ty, s := check_for_builtin_funcs(ct, t, scope) - if ty != nil { - return ty, s, false - } - } - - // then check for an ordinary function call - it, scope = advance_to_type(func_predicate, it, s) - if ct, ok := it.(*ast.FuncType); ok { - return func_return_type(ct, index), s, false - } - } - case *ast.ParenExpr: - it, s, is_type := infer_type(t.X, scope, -1) - if it == nil { - break - } - return it, s, is_type - case *ast.SelectorExpr: - it, s, _ := infer_type(t.X, scope, -1) - if it == nil { - break - } - - if d := type_to_decl(it, s); d != nil { - c := d.find_child_and_in_embedded(t.Sel.Name) - if c != nil { - if c.class == decl_type { - return t, scope, true - } else { - typ, s := c.infer_type() - return typ, s, false - } - } - } - case *ast.FuncLit: - // it's a value, but I think most likely we don't even care, cause we can only - // call it, and CallExpr uses the type itself to figure out - return t.Type, scope, false - case *ast.TypeAssertExpr: - if t.Type == nil { - return infer_type(t.X, scope, -1) - } - switch index { - case -1, 0: - // converting a value to a different type, but return thing is a value - it, _, _ := infer_type(t.Type, scope, -1) - return it, scope, false - case 1: - return ast.NewIdent("bool"), g_universe_scope, false - } - case *ast.ArrayType, *ast.MapType, *ast.ChanType, *ast.Ellipsis, - *ast.FuncType, *ast.StructType, *ast.InterfaceType: - return t, scope, true - default: - _ = reflect.TypeOf(v) - //log.Println(ty) - } - return nil, nil, false -} - -// Uses Value, ValueIndex and Scope to infer the type of this -// declaration. Returns the type itself and the scope where this type -// makes sense. -func (d *decl) infer_type() (ast.Expr, *scope) { - // special case for range vars - if d.is_rangevar() { - var scope *scope - d.typ, scope = infer_range_type(d.value, d.scope, d.value_index) - return d.typ, scope - } - - switch d.class { - case decl_package: - // package is handled specially in inferType - return nil, nil - case decl_type: - return ast.NewIdent(d.name), d.scope - } - - // shortcut - if d.typ != nil && d.value == nil { - return d.typ, d.scope - } - - // prevent loops - if d.is_visited() { - return nil, nil - } - d.set_visited() - defer d.clear_visited() - - var scope *scope - d.typ, scope, _ = infer_type(d.value, d.scope, d.value_index) - return d.typ, scope -} - -func (d *decl) type_dealias() *decl { - if d.is_visited() { - return nil - } - d.set_visited() - defer d.clear_visited() - - dd := type_to_decl(d.typ, d.scope) - if dd != nil && dd.is_alias() { - return dd.type_dealias() - } - return dd -} - -func (d *decl) find_child(name string) *decl { - // type aliases don't really have any children on their own, but they - // point to a different type, let's try to find one - if d.is_alias() { - dd := d.type_dealias() - if dd != nil { - return dd.find_child(name) - } - - // note that type alias can also point to a type literal, something like - // type A = struct { A int } - // in this case we rely on "advance_to_struct_or_interface" below - } - - if d.children != nil { - if c, ok := d.children[name]; ok { - return c - } - } - - decl := advance_to_struct_or_interface(d) - if decl != nil && decl != d { - if d.is_visited() { - return nil - } - d.set_visited() - defer d.clear_visited() - - return decl.find_child(name) - } - return nil -} - -func (d *decl) find_child_and_in_embedded(name string) *decl { - if d == nil { - return nil - } - - c := d.find_child(name) - if c == nil { - for _, e := range d.embedded { - typedecl := type_to_decl(e, d.scope) - c = typedecl.find_child_and_in_embedded(name) - if c != nil { - break - } - } - } - return c -} - -// Special type inference for range statements. -// [int], [int] := range [string] -// [int], [value] := range [slice or array] -// [key], [value] := range [map] -// [value], [nil] := range [chan] -func infer_range_type(e ast.Expr, sc *scope, valueindex int) (ast.Expr, *scope) { - t, s, _ := infer_type(e, sc, -1) - t, s = advance_to_type(range_predicate, t, s) - if t != nil { - var t1, t2 ast.Expr - var s1, s2 *scope - s1 = s - s2 = s - - switch t := t.(type) { - case *ast.Ident: - // string - if t.Name == "string" { - t1 = ast.NewIdent("int") - t2 = ast.NewIdent("rune") - s1 = g_universe_scope - s2 = g_universe_scope - } else { - t1, t2 = nil, nil - } - case *ast.ArrayType: - t1 = ast.NewIdent("int") - s1 = g_universe_scope - t2 = t.Elt - case *ast.Ellipsis: - t1 = ast.NewIdent("int") - s1 = g_universe_scope - t2 = t.Elt - case *ast.MapType: - t1 = t.Key - t2 = t.Value - case *ast.ChanType: - t1 = t.Value - t2 = nil - default: - t1, t2 = nil, nil - } - - switch valueindex { - case 0: - return t1, s1 - case 1: - return t2, s2 - } - } - return nil, nil -} - -//------------------------------------------------------------------------- -// Pretty printing -//------------------------------------------------------------------------- - -func get_array_len(e ast.Expr) string { - switch t := e.(type) { - case *ast.BasicLit: - return string(t.Value) - case *ast.Ellipsis: - return "..." - } - return "" -} - -func pretty_print_type_expr(out io.Writer, e ast.Expr, canonical_aliases map[string]string) { - switch t := e.(type) { - case *ast.StarExpr: - fmt.Fprintf(out, "*") - pretty_print_type_expr(out, t.X, canonical_aliases) - case *ast.Ident: - if strings.HasPrefix(t.Name, "$") { - // beautify anonymous types - switch t.Name[1] { - case 's': - fmt.Fprintf(out, "struct") - case 'i': - // ok, in most cases anonymous interface is an - // empty interface, I'll just pretend that - // it's always true - fmt.Fprintf(out, "interface{}") - } - } else if !*g_debug && strings.HasPrefix(t.Name, "!") { - // these are full package names for disambiguating and pretty - // printing packages within packages, e.g. - // !go/ast!ast vs. !github.com/nsf/my/ast!ast - // another ugly hack, if people are punished in hell for ugly hacks - // I'm screwed... - emarkIdx := strings.LastIndex(t.Name, "!") - path := t.Name[1:emarkIdx] - alias := canonical_aliases[path] - if alias == "" { - alias = t.Name[emarkIdx+1:] - } - fmt.Fprintf(out, alias) - } else { - fmt.Fprintf(out, t.Name) - } - case *ast.ArrayType: - al := "" - if t.Len != nil { - al = get_array_len(t.Len) - } - if al != "" { - fmt.Fprintf(out, "[%s]", al) - } else { - fmt.Fprintf(out, "[]") - } - pretty_print_type_expr(out, t.Elt, canonical_aliases) - case *ast.SelectorExpr: - pretty_print_type_expr(out, t.X, canonical_aliases) - fmt.Fprintf(out, ".%s", t.Sel.Name) - case *ast.FuncType: - fmt.Fprintf(out, "func(") - pretty_print_func_field_list(out, t.Params, canonical_aliases) - fmt.Fprintf(out, ")") - - buf := bytes.NewBuffer(make([]byte, 0, 256)) - nresults := pretty_print_func_field_list(buf, t.Results, canonical_aliases) - if nresults > 0 { - results := buf.String() - if strings.IndexAny(results, ", ") != -1 { - results = "(" + results + ")" - } - fmt.Fprintf(out, " %s", results) - } - case *ast.MapType: - fmt.Fprintf(out, "map[") - pretty_print_type_expr(out, t.Key, canonical_aliases) - fmt.Fprintf(out, "]") - pretty_print_type_expr(out, t.Value, canonical_aliases) - case *ast.InterfaceType: - fmt.Fprintf(out, "interface{}") - case *ast.Ellipsis: - fmt.Fprintf(out, "...") - pretty_print_type_expr(out, t.Elt, canonical_aliases) - case *ast.StructType: - fmt.Fprintf(out, "struct") - case *ast.ChanType: - switch t.Dir { - case ast.RECV: - fmt.Fprintf(out, "<-chan ") - case ast.SEND: - fmt.Fprintf(out, "chan<- ") - case ast.SEND | ast.RECV: - fmt.Fprintf(out, "chan ") - } - pretty_print_type_expr(out, t.Value, canonical_aliases) - case *ast.ParenExpr: - fmt.Fprintf(out, "(") - pretty_print_type_expr(out, t.X, canonical_aliases) - fmt.Fprintf(out, ")") - case *ast.BadExpr: - // TODO: probably I should check that in a separate function - // and simply discard declarations with BadExpr as a part of their - // type - default: - // the element has some weird type, just ignore it - } -} - -func pretty_print_func_field_list(out io.Writer, f *ast.FieldList, canonical_aliases map[string]string) int { - count := 0 - if f == nil { - return count - } - for i, field := range f.List { - // names - if field.Names != nil { - hasNonblank := false - for j, name := range field.Names { - if name.Name != "?" { - hasNonblank = true - fmt.Fprintf(out, "%s", name.Name) - if j != len(field.Names)-1 { - fmt.Fprintf(out, ", ") - } - } - count++ - } - if hasNonblank { - fmt.Fprintf(out, " ") - } - } else { - count++ - } - - // type - pretty_print_type_expr(out, field.Type, canonical_aliases) - - // , - if i != len(f.List)-1 { - fmt.Fprintf(out, ", ") - } - } - return count -} - -func ast_decl_names(d ast.Decl) []*ast.Ident { - var names []*ast.Ident - - switch t := d.(type) { - case *ast.GenDecl: - switch t.Tok { - case token.CONST: - c := t.Specs[0].(*ast.ValueSpec) - names = make([]*ast.Ident, len(c.Names)) - for i, name := range c.Names { - names[i] = name - } - case token.TYPE: - t := t.Specs[0].(*ast.TypeSpec) - names = make([]*ast.Ident, 1) - names[0] = t.Name - case token.VAR: - v := t.Specs[0].(*ast.ValueSpec) - names = make([]*ast.Ident, len(v.Names)) - for i, name := range v.Names { - names[i] = name - } - } - case *ast.FuncDecl: - names = make([]*ast.Ident, 1) - names[0] = t.Name - } - - return names -} - -func ast_decl_values(d ast.Decl) []ast.Expr { - // TODO: CONST values here too - switch t := d.(type) { - case *ast.GenDecl: - switch t.Tok { - case token.VAR: - v := t.Specs[0].(*ast.ValueSpec) - if v.Values != nil { - return v.Values - } - } - } - return nil -} - -func ast_decl_split(d ast.Decl) []ast.Decl { - var decls []ast.Decl - if t, ok := d.(*ast.GenDecl); ok { - decls = make([]ast.Decl, len(t.Specs)) - for i, s := range t.Specs { - decl := new(ast.GenDecl) - *decl = *t - decl.Specs = make([]ast.Spec, 1) - decl.Specs[0] = s - decls[i] = decl - } - } else { - decls = make([]ast.Decl, 1) - decls[0] = d - } - return decls -} - -//------------------------------------------------------------------------- -// decl_pack -//------------------------------------------------------------------------- - -type decl_pack struct { - names []*ast.Ident - typ ast.Expr - values []ast.Expr -} - -type foreach_decl_struct struct { - decl_pack - decl ast.Decl -} - -func (f *decl_pack) value(i int) ast.Expr { - if f.values == nil { - return nil - } - if len(f.values) > 1 { - return f.values[i] - } - return f.values[0] -} - -func (f *decl_pack) value_index(i int) (v ast.Expr, vi int) { - // default: nil value - v = nil - vi = -1 - - if f.values != nil { - // A = B, if there is only one name, the value is solo too - if len(f.names) == 1 { - return f.values[0], -1 - } - - if len(f.values) > 1 { - // in case if there are multiple values, it's a usual - // multiassignment - if i >= len(f.values) { - i = len(f.values) - 1 - } - v = f.values[i] - } else { - // in case if there is one value, but many names, it's - // a tuple unpack.. use index here - v = f.values[0] - vi = i - } - } - return -} - -func (f *decl_pack) type_value_index(i int) (ast.Expr, ast.Expr, int) { - if f.typ != nil { - // If there is a type, we don't care about value, just return the type - // and zero value. - return f.typ, nil, -1 - } - - // And otherwise we simply return nil type and a valid value for later inferring. - v, vi := f.value_index(i) - return nil, v, vi -} - -type foreach_decl_func func(data *foreach_decl_struct) - -func foreach_decl(decl ast.Decl, do foreach_decl_func) { - decls := ast_decl_split(decl) - var data foreach_decl_struct - for _, decl := range decls { - if !ast_decl_convertable(decl) { - continue - } - data.names = ast_decl_names(decl) - data.typ = ast_decl_type(decl) - data.values = ast_decl_values(decl) - data.decl = decl - - do(&data) - } -} - -//------------------------------------------------------------------------- -// Built-in declarations -//------------------------------------------------------------------------- - -var g_universe_scope = new_scope(nil) - -func init() { - builtin := ast.NewIdent("built-in") - - add_type := func(name string) { - d := new_decl(name, decl_type, g_universe_scope) - d.typ = builtin - g_universe_scope.add_named_decl(d) - } - add_type("bool") - add_type("byte") - add_type("complex64") - add_type("complex128") - add_type("float32") - add_type("float64") - add_type("int8") - add_type("int16") - add_type("int32") - add_type("int64") - add_type("string") - add_type("uint8") - add_type("uint16") - add_type("uint32") - add_type("uint64") - add_type("int") - add_type("uint") - add_type("uintptr") - add_type("rune") - - add_const := func(name string) { - d := new_decl(name, decl_const, g_universe_scope) - d.typ = builtin - g_universe_scope.add_named_decl(d) - } - add_const("true") - add_const("false") - add_const("iota") - add_const("nil") - - add_func := func(name, typ string) { - d := new_decl(name, decl_func, g_universe_scope) - d.typ = ast.NewIdent(typ) - g_universe_scope.add_named_decl(d) - } - add_func("append", "func([]type, ...type) []type") - add_func("cap", "func(container) int") - add_func("close", "func(channel)") - add_func("complex", "func(real, imag) complex") - add_func("copy", "func(dst, src)") - add_func("delete", "func(map[typeA]typeB, typeA)") - add_func("imag", "func(complex)") - add_func("len", "func(container) int") - add_func("make", "func(type, len[, cap]) type") - add_func("new", "func(type) *type") - add_func("panic", "func(interface{})") - add_func("print", "func(...interface{})") - add_func("println", "func(...interface{})") - add_func("real", "func(complex)") - add_func("recover", "func() interface{}") - - // built-in error interface - d := new_decl("error", decl_type, g_universe_scope) - d.typ = &ast.InterfaceType{} - d.children = make(map[string]*decl) - d.children["Error"] = new_decl("Error", decl_func, g_universe_scope) - d.children["Error"].typ = &ast.FuncType{ - Results: &ast.FieldList{ - List: []*ast.Field{ - { - Type: ast.NewIdent("string"), - }, - }, - }, - } - g_universe_scope.add_named_decl(d) -} diff --git a/langserver/internal/gocode/declcache.go b/langserver/internal/gocode/declcache.go deleted file mode 100644 index 9841b731..00000000 --- a/langserver/internal/gocode/declcache.go +++ /dev/null @@ -1,532 +0,0 @@ -package gocode - -import ( - "fmt" - "go/ast" - "go/build" - "go/parser" - "go/token" - "log" - "os" - "os/exec" - "path/filepath" - "strings" - "sync" -) - -//------------------------------------------------------------------------- -// []package_import -//------------------------------------------------------------------------- - -type package_import struct { - alias string - abspath string - path string -} - -// Parses import declarations until the first non-import declaration and fills -// `packages` array with import information. -func collect_package_imports(filename string, decls []ast.Decl, context *package_lookup_context) []package_import { - pi := make([]package_import, 0, 16) - for _, decl := range decls { - if gd, ok := decl.(*ast.GenDecl); ok && gd.Tok == token.IMPORT { - for _, spec := range gd.Specs { - imp := spec.(*ast.ImportSpec) - path, alias := path_and_alias(imp) - abspath, ok := abs_path_for_package(filename, path, context) - if ok && alias != "_" { - pi = append(pi, package_import{alias, abspath, path}) - } - } - } else { - break - } - } - return pi -} - -//------------------------------------------------------------------------- -// decl_file_cache -// -// Contains cache for top-level declarations of a file as well as its -// contents, AST and import information. -//------------------------------------------------------------------------- - -type decl_file_cache struct { - name string // file name - mtime int64 // last modification time - - decls map[string]*decl // top-level declarations - error error // last error - packages []package_import // import information - filescope *scope - - fset *token.FileSet - context *package_lookup_context -} - -func new_decl_file_cache(name string, context *package_lookup_context) *decl_file_cache { - return &decl_file_cache{ - name: name, - context: context, - } -} - -func (f *decl_file_cache) update() { - stat, err := os.Stat(f.name) - if err != nil { - f.decls = nil - f.error = err - f.fset = nil - return - } - - statmtime := stat.ModTime().UnixNano() - if f.mtime == statmtime { - return - } - - f.mtime = statmtime - f.read_file() -} - -func (f *decl_file_cache) read_file() { - var data []byte - data, f.error = file_reader.read_file(f.name) - if f.error != nil { - return - } - data, _ = filter_out_shebang(data) - - f.process_data(data) -} - -func (f *decl_file_cache) process_data(data []byte) { - var file *ast.File - f.fset = token.NewFileSet() - file, f.error = parser.ParseFile(f.fset, "", data, 0) - f.filescope = new_scope(nil) - for _, d := range file.Decls { - anonymify_ast(d, 0, f.filescope) - } - f.packages = collect_package_imports(f.name, file.Decls, f.context) - f.decls = make(map[string]*decl, len(file.Decls)) - for _, decl := range file.Decls { - append_to_top_decls(f.decls, decl, f.filescope) - } -} - -func append_to_top_decls(decls map[string]*decl, decl ast.Decl, scope *scope) { - foreach_decl(decl, func(data *foreach_decl_struct) { - class := ast_decl_class(data.decl) - for i, name := range data.names { - typ, v, vi := data.type_value_index(i) - - d := new_decl_full(name.Name, class, ast_decl_flags(data.decl), typ, v, vi, scope) - if d == nil { - return - } - - methodof := method_of(decl) - if methodof != "" { - decl, ok := decls[methodof] - if ok { - decl.add_child(d) - } else { - decl = new_decl(methodof, decl_methods_stub, scope) - decls[methodof] = decl - decl.add_child(d) - } - } else { - decl, ok := decls[d.name] - if ok { - decl.expand_or_replace(d) - } else { - decls[d.name] = d - } - } - } - }) -} - -func abs_path_for_package(filename, p string, context *package_lookup_context) (string, bool) { - dir, _ := filepath.Split(filename) - if len(p) == 0 { - return "", false - } - if p[0] == '.' { - return fmt.Sprintf("%s.a", filepath.Join(dir, p)), true - } - pkg, ok := find_go_dag_package(p, dir) - if ok { - return pkg, true - } - return find_global_file(p, context) -} - -func path_and_alias(imp *ast.ImportSpec) (string, string) { - path := "" - if imp.Path != nil && len(imp.Path.Value) > 0 { - path = string(imp.Path.Value) - path = path[1 : len(path)-1] - } - alias := "" - if imp.Name != nil { - alias = imp.Name.Name - } - return path, alias -} - -func find_go_dag_package(imp, filedir string) (string, bool) { - // Support godag directory structure - dir, pkg := filepath.Split(imp) - godag_pkg := filepath.Join(filedir, "..", dir, "_obj", pkg+".a") - if file_exists(godag_pkg) { - return godag_pkg, true - } - return "", false -} - -// autobuild compares the mod time of the source files of the package, and if any of them is newer -// than the package object file will rebuild it. -func autobuild(p *build.Package) error { - if p.Dir == "" { - return fmt.Errorf("no files to build") - } - ps, err := os.Stat(p.PkgObj) - if err != nil { - // Assume package file does not exist and build for the first time. - return build_package(p) - } - pt := ps.ModTime() - fs, err := readdir_lstat(p.Dir) - if err != nil { - return err - } - for _, f := range fs { - if f.IsDir() { - continue - } - if f.ModTime().After(pt) { - // Source file is newer than package file; rebuild. - return build_package(p) - } - } - return nil -} - -// build_package builds the package by calling `go install package/import`. If everything compiles -// correctly, the newly compiled package should then be in the usual place in the `$GOPATH/pkg` -// directory, and gocode will pick it up from there. -func build_package(p *build.Package) error { - if *g_debug { - log.Printf("-------------------") - log.Printf("rebuilding package %s", p.Name) - log.Printf("package import: %s", p.ImportPath) - log.Printf("package object: %s", p.PkgObj) - log.Printf("package source dir: %s", p.Dir) - log.Printf("package source files: %v", p.GoFiles) - log.Printf("GOPATH: %v", g_daemon.context.GOPATH) - log.Printf("GOROOT: %v", g_daemon.context.GOROOT) - } - env := os.Environ() - for i, v := range env { - if strings.HasPrefix(v, "GOPATH=") { - env[i] = "GOPATH=" + g_daemon.context.GOPATH - } else if strings.HasPrefix(v, "GOROOT=") { - env[i] = "GOROOT=" + g_daemon.context.GOROOT - } - } - - cmd := exec.Command("go", "install", p.ImportPath) - cmd.Env = env - - // TODO: Should read STDERR rather than STDOUT. - out, err := cmd.CombinedOutput() - if err != nil { - return err - } - if *g_debug { - log.Printf("build out: %s\n", string(out)) - } - return nil -} - -// executes autobuild function if autobuild option is enabled, logs error and -// ignores it -func try_autobuild(p *build.Package) { - if g_config.Autobuild { - err := autobuild(p) - if err != nil && *g_debug { - log.Printf("Autobuild error: %s\n", err) - } - } -} - -func log_found_package_maybe(imp, pkgpath string) { - if *g_debug { - log.Printf("Found %q at %q\n", imp, pkgpath) - } -} - -func log_build_context(context *package_lookup_context) { - log.Printf(" GOROOT: %s\n", context.GOROOT) - log.Printf(" GOPATH: %s\n", context.GOPATH) - log.Printf(" GOOS: %s\n", context.GOOS) - log.Printf(" GOARCH: %s\n", context.GOARCH) - log.Printf(" BzlProjectRoot: %q\n", context.BzlProjectRoot) - log.Printf(" GBProjectRoot: %q\n", context.GBProjectRoot) - log.Printf(" lib-path: %q\n", g_config.LibPath) -} - -// find_global_file returns the file path of the compiled package corresponding to the specified -// import, and a boolean stating whether such path is valid. -// TODO: Return only one value, possibly empty string if not found. -func find_global_file(imp string, context *package_lookup_context) (string, bool) { - // gocode synthetically generates the builtin package - // "unsafe", since the "unsafe.a" package doesn't really exist. - // Thus, when the user request for the package "unsafe" we - // would return synthetic global file that would be used - // just as a key name to find this synthetic package - if imp == "unsafe" { - return "unsafe", true - } - - pkgfile := fmt.Sprintf("%s.a", imp) - - // if lib-path is defined, use it - if g_config.LibPath != "" { - for _, p := range filepath.SplitList(g_config.LibPath) { - pkg_path := filepath.Join(p, pkgfile) - if file_exists(pkg_path) { - log_found_package_maybe(imp, pkg_path) - return pkg_path, true - } - // Also check the relevant pkg/OS_ARCH dir for the libpath, if provided. - pkgdir := fmt.Sprintf("%s_%s", context.GOOS, context.GOARCH) - pkg_path = filepath.Join(p, "pkg", pkgdir, pkgfile) - if file_exists(pkg_path) { - log_found_package_maybe(imp, pkg_path) - return pkg_path, true - } - } - } - - // gb-specific lookup mode, only if the root dir was found - if g_config.PackageLookupMode == "gb" && context.GBProjectRoot != "" { - root := context.GBProjectRoot - pkgdir := filepath.Join(root, "pkg", context.GOOS+"-"+context.GOARCH) - if !is_dir(pkgdir) { - pkgdir = filepath.Join(root, "pkg", context.GOOS+"-"+context.GOARCH+"-race") - } - pkg_path := filepath.Join(pkgdir, pkgfile) - if file_exists(pkg_path) { - log_found_package_maybe(imp, pkg_path) - return pkg_path, true - } - } - - // bzl-specific lookup mode, only if the root dir was found - if g_config.PackageLookupMode == "bzl" && context.BzlProjectRoot != "" { - var root, impath string - if strings.HasPrefix(imp, g_config.CustomPkgPrefix+"/") { - root = filepath.Join(context.BzlProjectRoot, "bazel-bin") - impath = imp[len(g_config.CustomPkgPrefix)+1:] - } else if g_config.CustomVendorDir != "" { - // Try custom vendor dir. - root = filepath.Join(context.BzlProjectRoot, "bazel-bin", g_config.CustomVendorDir) - impath = imp - } - - if root != "" && impath != "" { - // There might be more than one ".a" files in the pkg path with bazel. - // But the best practice is to keep one go_library build target in each - // pakcage directory so that it follows the standard Go package - // structure. Thus here we assume there is at most one ".a" file existing - // in the pkg path. - if d, err := os.Open(filepath.Join(root, impath)); err == nil { - defer d.Close() - - if fis, err := d.Readdir(-1); err == nil { - for _, fi := range fis { - if !fi.IsDir() && filepath.Ext(fi.Name()) == ".a" { - pkg_path := filepath.Join(root, impath, fi.Name()) - log_found_package_maybe(imp, pkg_path) - return pkg_path, true - } - } - } - } - } - } - - if context.CurrentPackagePath != "" { - // Try vendor path first, see GO15VENDOREXPERIMENT. - // We don't check this environment variable however, seems like there is - // almost no harm in doing so (well.. if you experiment with vendoring, - // gocode will fail after enabling/disabling the flag, and you'll be - // forced to get rid of vendor binaries). But asking users to set this - // env var is up will bring more trouble. Because we also need to pass - // it from client to server, make sure their editors set it, etc. - // So, whatever, let's just pretend it's always on. - package_path := context.CurrentPackagePath - for { - limp := filepath.Join(package_path, "vendor", imp) - if p, err := context.Import(limp, "", build.AllowBinary|build.FindOnly); err == nil { - try_autobuild(p) - if file_exists(p.PkgObj) { - log_found_package_maybe(imp, p.PkgObj) - return p.PkgObj, true - } - } - if package_path == "" { - break - } - next_path := filepath.Dir(package_path) - // let's protect ourselves from inf recursion here - if next_path == package_path { - break - } - package_path = next_path - } - } - - if p, err := context.Import(imp, "", build.AllowBinary|build.FindOnly); err == nil { - try_autobuild(p) - if file_exists(p.PkgObj) { - log_found_package_maybe(imp, p.PkgObj) - return p.PkgObj, true - } - } - - if *g_debug { - log.Printf("Import path %q was not resolved\n", imp) - log.Println("Gocode's build context is:") - log_build_context(context) - } - return "", false -} - -func package_name(file *ast.File) string { - if file.Name != nil { - return file.Name.Name - } - return "" -} - -//------------------------------------------------------------------------- -// decl_cache -// -// Thread-safe collection of DeclFileCache entities. -//------------------------------------------------------------------------- - -type package_lookup_context struct { - build.Context - BzlProjectRoot string - GBProjectRoot string - CurrentPackagePath string -} - -// gopath returns the list of Go path directories. -func (ctxt *package_lookup_context) gopath() []string { - var all []string - for _, p := range filepath.SplitList(ctxt.GOPATH) { - if p == "" || p == ctxt.GOROOT { - // Empty paths are uninteresting. - // If the path is the GOROOT, ignore it. - // People sometimes set GOPATH=$GOROOT. - // Do not get confused by this common mistake. - continue - } - if strings.HasPrefix(p, "~") { - // Path segments starting with ~ on Unix are almost always - // users who have incorrectly quoted ~ while setting GOPATH, - // preventing it from expanding to $HOME. - // The situation is made more confusing by the fact that - // bash allows quoted ~ in $PATH (most shells do not). - // Do not get confused by this, and do not try to use the path. - // It does not exist, and printing errors about it confuses - // those users even more, because they think "sure ~ exists!". - // The go command diagnoses this situation and prints a - // useful error. - // On Windows, ~ is used in short names, such as c:\progra~1 - // for c:\program files. - continue - } - all = append(all, p) - } - return all -} - -func (ctxt *package_lookup_context) pkg_dirs() (string, []string) { - pkgdir := fmt.Sprintf("%s_%s", ctxt.GOOS, ctxt.GOARCH) - - var currentPackagePath string - var all []string - if ctxt.GOROOT != "" { - dir := filepath.Join(ctxt.GOROOT, "pkg", pkgdir) - if is_dir(dir) { - all = append(all, dir) - } - } - - switch g_config.PackageLookupMode { - case "go": - currentPackagePath = ctxt.CurrentPackagePath - for _, p := range ctxt.gopath() { - dir := filepath.Join(p, "pkg", pkgdir) - if is_dir(dir) { - all = append(all, dir) - } - dir = filepath.Join(dir, currentPackagePath, "vendor") - if is_dir(dir) { - all = append(all, dir) - } - } - case "gb": - if ctxt.GBProjectRoot != "" { - pkgdir := fmt.Sprintf("%s-%s", ctxt.GOOS, ctxt.GOARCH) - if !is_dir(pkgdir) { - pkgdir = fmt.Sprintf("%s-%s-race", ctxt.GOOS, ctxt.GOARCH) - } - dir := filepath.Join(ctxt.GBProjectRoot, "pkg", pkgdir) - if is_dir(dir) { - all = append(all, dir) - } - } - case "bzl": - // TODO: Support bazel mode - } - return currentPackagePath, all -} - -type decl_cache struct { - cache map[string]*decl_file_cache - context *package_lookup_context - sync.Mutex -} - -func new_decl_cache(context *package_lookup_context) *decl_cache { - return &decl_cache{ - cache: make(map[string]*decl_file_cache), - context: context, - } -} - -func (c *decl_cache) get(filename string) *decl_file_cache { - c.Lock() - defer c.Unlock() - - f, ok := c.cache[filename] - if !ok { - f = new_decl_file_cache(filename, c.context) - c.cache[filename] = f - } - return f -} - -func (c *decl_cache) get_and_update(filename string) *decl_file_cache { - f := c.get(filename) - f.update() - return f -} diff --git a/langserver/internal/gocode/export.go b/langserver/internal/gocode/export.go deleted file mode 100644 index 8a5e5200..00000000 --- a/langserver/internal/gocode/export.go +++ /dev/null @@ -1,39 +0,0 @@ -package gocode - -import ( - "go/build" -) - -var bctx go_build_context - -func InitDaemon(bc *build.Context) { - bctx = pack_build_context(bc) - g_config.ProposeBuiltins = true - g_config.Autobuild = true - g_daemon = new(daemon) - g_daemon.drop_cache() -} - -func SetBuildContext(bc *build.Context) { - bctx = pack_build_context(bc) -} - -func AutoComplete(file []byte, filename string, offset int) ([]candidate, int) { - return server_auto_complete(file, filename, offset, bctx) -} - -// dumb vars for unused parts of the package -var ( - g_sock *string - g_addr *string - fals = false - g_debug = &fals - get_socket_filename func() string - config_dir func() string - config_file func() string -) - -// dumb types for unused parts of the package -type ( - RPC struct{} -) diff --git a/langserver/internal/gocode/gbimporter/context.go b/langserver/internal/gocode/gbimporter/context.go new file mode 100644 index 00000000..58b775bb --- /dev/null +++ b/langserver/internal/gocode/gbimporter/context.go @@ -0,0 +1,34 @@ +package gbimporter + +import "go/build" + +// PackedContext is a copy of build.Context without the func fields. +// +// TODO(mdempsky): Not sure this belongs here. +type PackedContext struct { + GOARCH string + GOOS string + GOROOT string + GOPATH string + CgoEnabled bool + UseAllFiles bool + Compiler string + BuildTags []string + ReleaseTags []string + InstallSuffix string +} + +func PackContext(ctx *build.Context) PackedContext { + return PackedContext{ + GOARCH: ctx.GOARCH, + GOOS: ctx.GOOS, + GOROOT: ctx.GOROOT, + GOPATH: ctx.GOPATH, + CgoEnabled: ctx.CgoEnabled, + UseAllFiles: ctx.UseAllFiles, + Compiler: ctx.Compiler, + BuildTags: ctx.BuildTags, + ReleaseTags: ctx.ReleaseTags, + InstallSuffix: ctx.InstallSuffix, + } +} diff --git a/langserver/internal/gocode/gbimporter/gbimporter.go b/langserver/internal/gocode/gbimporter/gbimporter.go new file mode 100644 index 00000000..ed787168 --- /dev/null +++ b/langserver/internal/gocode/gbimporter/gbimporter.go @@ -0,0 +1,124 @@ +package gbimporter + +import ( + "fmt" + "go/build" + "go/types" + "path/filepath" + "strings" + "sync" +) + +// We need to mangle go/build.Default to make gcimporter work as +// intended, so use a lock to protect against concurrent accesses. +var buildDefaultLock sync.Mutex + +// importer implements types.ImporterFrom and provides transparent +// support for gb-based projects. +type importer struct { + underlying types.ImporterFrom + ctx *PackedContext + gbroot string + gbpaths []string +} + +func New(ctx *PackedContext, filename string, underlying types.ImporterFrom) types.ImporterFrom { + imp := &importer{ + ctx: ctx, + underlying: underlying, + } + + slashed := filepath.ToSlash(filename) + i := strings.LastIndex(slashed, "/vendor/src/") + if i < 0 { + i = strings.LastIndex(slashed, "/src/") + } + if i > 0 { + paths := filepath.SplitList(imp.ctx.GOPATH) + + gbroot := filepath.FromSlash(slashed[:i]) + gbvendor := filepath.Join(gbroot, "vendor") + if samePath(gbroot, imp.ctx.GOROOT) { + goto Found + } + for _, path := range paths { + if samePath(path, gbroot) || samePath(path, gbvendor) { + goto Found + } + } + + imp.gbroot = gbroot + imp.gbpaths = append(paths, gbroot, gbvendor) + Found: + } + + return imp +} + +func (i *importer) Import(path string) (*types.Package, error) { + return i.ImportFrom(path, "", 0) +} + +func (i *importer) ImportFrom(path, srcDir string, mode types.ImportMode) (*types.Package, error) { + buildDefaultLock.Lock() + defer buildDefaultLock.Unlock() + + origDef := build.Default + defer func() { build.Default = origDef }() + + def := &build.Default + def.GOARCH = i.ctx.GOARCH + def.GOOS = i.ctx.GOOS + def.GOROOT = i.ctx.GOROOT + def.GOPATH = i.ctx.GOPATH + def.CgoEnabled = i.ctx.CgoEnabled + def.UseAllFiles = i.ctx.UseAllFiles + def.Compiler = i.ctx.Compiler + def.BuildTags = i.ctx.BuildTags + def.ReleaseTags = i.ctx.ReleaseTags + def.InstallSuffix = i.ctx.InstallSuffix + + def.SplitPathList = i.splitPathList + def.JoinPath = i.joinPath + + return i.underlying.ImportFrom(path, srcDir, mode) +} + +func (i *importer) splitPathList(list string) []string { + if i.gbroot != "" { + return i.gbpaths + } + return filepath.SplitList(list) +} + +func (i *importer) joinPath(elem ...string) string { + res := filepath.Join(elem...) + + if i.gbroot != "" { + // Want to rewrite "$GBROOT/(vendor/)?pkg/$GOOS_$GOARCH(_)?" + // into "$GBROOT/pkg/$GOOS-$GOARCH(-)?". + // Note: gb doesn't use vendor/pkg. + if gbrel, err := filepath.Rel(i.gbroot, res); err == nil { + gbrel = filepath.ToSlash(gbrel) + gbrel, _ = match(gbrel, "vendor/") + if gbrel, ok := match(gbrel, fmt.Sprintf("pkg/%s_%s", i.ctx.GOOS, i.ctx.GOARCH)); ok { + gbrel, hasSuffix := match(gbrel, "_") + + // Reassemble into result. + if hasSuffix { + gbrel = "-" + gbrel + } + gbrel = fmt.Sprintf("pkg/%s_%s/", i.ctx.GOOS, i.ctx.GOARCH) + gbrel + gbrel = filepath.FromSlash(gbrel) + res = filepath.Join(i.gbroot, gbrel) + } + } + } + + return res +} + +func match(s, prefix string) (string, bool) { + rest := strings.TrimPrefix(s, prefix) + return rest, len(rest) < len(s) +} diff --git a/langserver/internal/gocode/gbimporter/samepath_unix.go b/langserver/internal/gocode/gbimporter/samepath_unix.go new file mode 100644 index 00000000..5d78680e --- /dev/null +++ b/langserver/internal/gocode/gbimporter/samepath_unix.go @@ -0,0 +1,8 @@ +// +build !windows + +package gbimporter + +// samePath checks two file paths for their equality based on the current filesystem +func samePath(a, b string) bool { + return a == b +} diff --git a/langserver/internal/gocode/gbimporter/samepath_windows.go b/langserver/internal/gocode/gbimporter/samepath_windows.go new file mode 100644 index 00000000..e7040b2c --- /dev/null +++ b/langserver/internal/gocode/gbimporter/samepath_windows.go @@ -0,0 +1,12 @@ +// +build windows + +package gbimporter + +import ( + "strings" +) + +// samePath checks two file paths for their equality based on the current filesystem +func samePath(a, b string) bool { + return strings.EqualFold(a, b) +} diff --git a/langserver/internal/gocode/gocode.go b/langserver/internal/gocode/gocode.go new file mode 100644 index 00000000..fcf8bffc --- /dev/null +++ b/langserver/internal/gocode/gocode.go @@ -0,0 +1,55 @@ +package gocode + +import ( + "fmt" + "go/importer" + "go/types" + + "github.com/sourcegraph/go-langserver/langserver/internal/gocode/gbimporter" + "github.com/sourcegraph/go-langserver/langserver/internal/gocode/suggest" +) + +type AutoCompleteRequest struct { + Filename string + Data []byte + Cursor int + Context gbimporter.PackedContext + Source bool + Builtin bool +} + +type AutoCompleteReply struct { + Candidates []suggest.Candidate + Len int +} + +func AutoComplete(req *AutoCompleteRequest) (res *AutoCompleteReply, err error) { + res = &AutoCompleteReply{} + defer func() { + if err := recover(); err != nil { + fmt.Printf("gocode panic: %s\n\n", err) + + res.Candidates = []suggest.Candidate{ + {Class: "PANIC", Name: "PANIC", Type: "PANIC"}, + } + } + }() + + var underlying types.ImporterFrom + if req.Source { + underlying = importer.For("source", nil).(types.ImporterFrom) + } else { + underlying = importer.Default().(types.ImporterFrom) + } + cfg := suggest.Config{ + Importer: gbimporter.New(&req.Context, req.Filename, underlying), + Builtin: req.Builtin, + } + + candidates, d, err := cfg.Suggest(req.Filename, req.Data, req.Cursor) + if err != nil { + return nil, err + } + res.Candidates, res.Len = candidates, d + return res, nil +} diff --git a/langserver/internal/gocode/lookdot/lookdot.go b/langserver/internal/gocode/lookdot/lookdot.go new file mode 100644 index 00000000..31ab1ab1 --- /dev/null +++ b/langserver/internal/gocode/lookdot/lookdot.go @@ -0,0 +1,154 @@ +package lookdot + +import "go/types" + +type Visitor func(obj types.Object) + +func Walk(tv *types.TypeAndValue, v Visitor) bool { + switch { + case tv.IsType(): + walk(tv.Type, false, false, v) + case tv.IsValue(): + walk(tv.Type, tv.Addressable(), true, v) + default: + return false + } + return true +} + +func walk(typ0 types.Type, addable0, value bool, v Visitor) { + // Enumerating valid selector expression identifiers is + // surprisingly nuanced. + + // found is a map from selector identifiers to the objects + // they select. Nil entries are used to track objects that + // have already been reported to the visitor and to indicate + // ambiguous identifiers. + found := make(map[string]types.Object) + + addObj := func(obj types.Object, valid bool) { + id := obj.Id() + switch otherObj, isPresent := found[id]; { + case !isPresent: + if valid { + found[id] = obj + } else { + found[id] = nil + } + case otherObj != nil: + // Ambiguous selector. + found[id] = nil + } + } + + // visited keeps track of named types that we've already + // visited. We only need to track named types, because + // recursion can only happen through embedded struct fields, + // which must be either a named type or a pointer to a named + // type. + visited := make(map[*types.Named]bool) + + type todo struct { + typ types.Type + addable bool + } + + var cur, next []todo + cur = []todo{{typ0, addable0}} + + for { + if len(cur) == 0 { + // Flush discovered objects to visitor function. + for id, obj := range found { + if obj != nil { + v(obj) + found[id] = nil + } + } + + // Move unvisited types from next to cur. + // It's important to check between levels to + // ensure that ambiguous selections are + // correctly handled. + cur = next[:0] + for _, t := range next { + nt := namedOf(t.typ) + if nt == nil { + panic("embedded struct field without name?") + } + if !visited[nt] { + cur = append(cur, t) + } + } + next = nil + + if len(cur) == 0 { + break + } + } + + now := cur[0] + cur = cur[1:] + + // Look for methods declared on a named type. + { + typ, addable := chasePointer(now.typ) + if !addable { + addable = now.addable + } + if typ, ok := typ.(*types.Named); ok { + visited[typ] = true + for i, n := 0, typ.NumMethods(); i < n; i++ { + m := typ.Method(i) + addObj(m, addable || !hasPtrRecv(m)) + } + } + } + + // Look for interface methods. + if typ, ok := now.typ.Underlying().(*types.Interface); ok { + for i, n := 0, typ.NumMethods(); i < n; i++ { + addObj(typ.Method(i), true) + } + } + + // Look for struct fields. + { + typ, addable := chasePointer(now.typ.Underlying()) + if !addable { + addable = now.addable + } + if typ, ok := typ.Underlying().(*types.Struct); ok { + for i, n := 0, typ.NumFields(); i < n; i++ { + f := typ.Field(i) + addObj(f, value) + if f.Anonymous() { + next = append(next, todo{f.Type(), addable}) + } + } + } + } + } +} + +// namedOf returns the named type T when given T or *T. +// Otherwise, it returns nil. +func namedOf(typ types.Type) *types.Named { + if ptr, isPtr := typ.(*types.Pointer); isPtr { + typ = ptr.Elem() + } + res, _ := typ.(*types.Named) + return res +} + +func hasPtrRecv(m *types.Func) bool { + _, ok := m.Type().(*types.Signature).Recv().Type().(*types.Pointer) + return ok +} + +func chasePointer(typ types.Type) (types.Type, bool) { + if ptr, isPtr := typ.(*types.Pointer); isPtr { + return ptr.Elem(), true + } + return typ, false +} diff --git a/langserver/internal/gocode/lookdot/lookdot_test.go b/langserver/internal/gocode/lookdot/lookdot_test.go new file mode 100644 index 00000000..f9406ca6 --- /dev/null +++ b/langserver/internal/gocode/lookdot/lookdot_test.go @@ -0,0 +1,133 @@ +package lookdot_test + +import ( + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "reflect" + "sort" + "testing" + + "github.com/sourcegraph/go-langserver/langserver/internal/gocode/lookdot" +) + +const src = ` +package p + +import "time" + +type S struct { x int; y int } +func (S) Sv() +func (*S) Sp() +var s S + +type I interface { f(); g() } + +type P *S + +type T1 struct { *T2 } +type T2 struct { *T1 } +func (*T1) t1() +func (*T2) t2() + +type X int +func (*X) x() +type X1 struct { X } +type X2 struct { *X } +type X12 struct { X1; X2 } + +type A1 int +func (A1) A() int +type A2 int +func (A2) A() int +type A struct { A1; A2; } + +type B1 int +func (B1) b() +type B2 struct { b int; B1 } + +var loc time.Location +` + +var tests = [...]struct { + lhs string + want []string +}{ + {"S", []string{"Sv"}}, + {"*S", []string{"Sv", "Sp"}}, + {"S{}", []string{"Sv", "x", "y"}}, + {"s", []string{"Sv", "Sp", "x", "y"}}, + + {"I", []string{"f", "g"}}, + {"I(nil)", []string{"f", "g"}}, + {"(*I)(nil)", nil}, + + // golang.org/issue/15708 + {"*T1", []string{"t1", "t2"}}, + {"T1", []string{"t2"}}, + + // golang.org/issue/9060 + {"error", []string{"Error"}}, + {"struct { error }", []string{"Error"}}, + {"interface { error }", []string{"Error"}}, + + // golang.org/issue/15722 + {"P", nil}, + + {"X1", nil}, + {"X2", []string{"x"}}, + {"X12", nil}, + + {"A", nil}, + + {"B2", nil}, + + {"loc", []string{"String"}}, +} + +func TestWalk(t *testing.T) { + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "src.go", src, 0) + if err != nil { + t.Fatal(err) + } + + var cfg types.Config + cfg.Importer = importer.Default() + pkg, err := cfg.Check("p", fset, []*ast.File{file}, nil) + if err != nil { + t.Fatal(err) + } + + for _, test := range tests { + tv, err := types.Eval(fset, pkg, token.NoPos, test.lhs) + if err != nil { + t.Errorf("Eval(%q) failed: %v", test.lhs, err) + continue + } + + var got []string + visitor := func(obj types.Object) { + // TODO(mdempsky): Should Walk be responsible + // for filtering out inaccessible objects? + if obj.Exported() || obj.Pkg() == pkg { + got = append(got, obj.Name()) + } + } + + if !lookdot.Walk(&tv, visitor) { + t.Errorf("Walk(%q) returned false", test.lhs) + continue + } + + sort.Strings(got) + sort.Strings(test.want) + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Look(%q): got %v, want %v", test.lhs, got, test.want) + continue + } + } +} diff --git a/langserver/internal/gocode/package.go b/langserver/internal/gocode/package.go deleted file mode 100644 index ebf35a5a..00000000 --- a/langserver/internal/gocode/package.go +++ /dev/null @@ -1,254 +0,0 @@ -package gocode - -import ( - "bytes" - "fmt" - "go/ast" - "os" - "strings" -) - -type package_parser interface { - parse_export(callback func(pkg string, decl ast.Decl)) -} - -//------------------------------------------------------------------------- -// package_file_cache -// -// Structure that represents a cache for an imported package. In other words -// these are the contents of an archive (*.a) file. -//------------------------------------------------------------------------- - -type package_file_cache struct { - name string // file name - import_name string - mtime int64 - defalias string - - scope *scope - main *decl // package declaration - others map[string]*decl -} - -func new_package_file_cache(absname, name string) *package_file_cache { - m := new(package_file_cache) - m.name = absname - m.import_name = name - m.mtime = 0 - m.defalias = "" - return m -} - -// Creates a cache that stays in cache forever. Useful for built-in packages. -func new_package_file_cache_forever(name, defalias string) *package_file_cache { - m := new(package_file_cache) - m.name = name - m.mtime = -1 - m.defalias = defalias - return m -} - -func (m *package_file_cache) find_file() string { - if file_exists(m.name) { - return m.name - } - - n := len(m.name) - filename := m.name[:n-1] + "6" - if file_exists(filename) { - return filename - } - - filename = m.name[:n-1] + "8" - if file_exists(filename) { - return filename - } - - filename = m.name[:n-1] + "5" - if file_exists(filename) { - return filename - } - return m.name -} - -func (m *package_file_cache) update_cache() { - if m.mtime == -1 { - return - } - fname := m.find_file() - stat, err := os.Stat(fname) - if err != nil { - return - } - - statmtime := stat.ModTime().UnixNano() - if m.mtime != statmtime { - m.mtime = statmtime - - data, err := file_reader.read_file(fname) - if err != nil { - return - } - m.process_package_data(data) - } -} - -func (m *package_file_cache) process_package_data(data []byte) { - m.scope = new_named_scope(g_universe_scope, m.name) - - // find import section - i := bytes.Index(data, []byte{'\n', '$', '$'}) - if i == -1 { - panic(fmt.Sprintf("Can't find the import section in the package file %s", m.name)) - } - data = data[i+len("\n$$"):] - - // main package - m.main = new_decl(m.name, decl_package, nil) - // create map for other packages - m.others = make(map[string]*decl) - - var pp package_parser - if data[0] == 'B' { - // binary format, skip 'B\n' - data = data[2:] - var p gc_bin_parser - p.init(data, m) - pp = &p - } else { - // textual format, find the beginning of the package clause - i = bytes.Index(data, []byte{'p', 'a', 'c', 'k', 'a', 'g', 'e'}) - if i == -1 { - panic("Can't find the package clause") - } - data = data[i:] - - var p gc_parser - p.init(data, m) - pp = &p - } - - prefix := "!" + m.name + "!" - pp.parse_export(func(pkg string, decl ast.Decl) { - anonymify_ast(decl, decl_foreign, m.scope) - if pkg == "" || strings.HasPrefix(pkg, prefix) { - // main package - add_ast_decl_to_package(m.main, decl, m.scope) - } else { - // others - if _, ok := m.others[pkg]; !ok { - m.others[pkg] = new_decl(pkg, decl_package, nil) - } - add_ast_decl_to_package(m.others[pkg], decl, m.scope) - } - }) - - // hack, add ourselves to the package scope - mainName := "!" + m.name + "!" + m.defalias - m.add_package_to_scope(mainName, m.name) - - // replace dummy package decls in package scope to actual packages - for key := range m.scope.entities { - if !strings.HasPrefix(key, "!") { - continue - } - pkg, ok := m.others[key] - if !ok && key == mainName { - pkg = m.main - } - m.scope.replace_decl(key, pkg) - } -} - -func (m *package_file_cache) add_package_to_scope(alias, realname string) { - d := new_decl(realname, decl_package, nil) - m.scope.add_decl(alias, d) -} - -func add_ast_decl_to_package(pkg *decl, decl ast.Decl, scope *scope) { - foreach_decl(decl, func(data *foreach_decl_struct) { - class := ast_decl_class(data.decl) - for i, name := range data.names { - typ, v, vi := data.type_value_index(i) - - d := new_decl_full(name.Name, class, decl_foreign|ast_decl_flags(data.decl), typ, v, vi, scope) - if d == nil { - return - } - - if !name.IsExported() && d.class != decl_type { - return - } - - methodof := method_of(data.decl) - if methodof != "" { - decl := pkg.find_child(methodof) - if decl != nil { - decl.add_child(d) - } else { - decl = new_decl(methodof, decl_methods_stub, scope) - decl.add_child(d) - pkg.add_child(decl) - } - } else { - decl := pkg.find_child(d.name) - if decl != nil { - decl.expand_or_replace(d) - } else { - pkg.add_child(d) - } - } - } - }) -} - -//------------------------------------------------------------------------- -// package_cache -//------------------------------------------------------------------------- - -type package_cache map[string]*package_file_cache - -func new_package_cache() package_cache { - m := make(package_cache) - - // add built-in "unsafe" package - m.add_builtin_unsafe_package() - - return m -} - -// Function fills 'ps' set with packages from 'packages' import information. -// In case if package is not in the cache, it creates one and adds one to the cache. -func (c package_cache) append_packages(ps map[string]*package_file_cache, pkgs []package_import) { - for _, m := range pkgs { - if _, ok := ps[m.abspath]; ok { - continue - } - - if mod, ok := c[m.abspath]; ok { - ps[m.abspath] = mod - } else { - mod = new_package_file_cache(m.abspath, m.path) - ps[m.abspath] = mod - c[m.abspath] = mod - } - } -} - -var g_builtin_unsafe_package = []byte(` -import -$$ -package unsafe - type @"".Pointer uintptr - func @"".Offsetof (? any) uintptr - func @"".Sizeof (? any) uintptr - func @"".Alignof (? any) uintptr - -$$ -`) - -func (c package_cache) add_builtin_unsafe_package() { - pkg := new_package_file_cache_forever("unsafe", "unsafe") - pkg.process_package_data(g_builtin_unsafe_package) - c["unsafe"] = pkg -} diff --git a/langserver/internal/gocode/package_bin.go b/langserver/internal/gocode/package_bin.go deleted file mode 100644 index 576f1ba0..00000000 --- a/langserver/internal/gocode/package_bin.go +++ /dev/null @@ -1,829 +0,0 @@ -package gocode - -import ( - "encoding/binary" - "fmt" - "go/ast" - "go/token" - "strconv" - "strings" - "unicode" - "unicode/utf8" -) - -//------------------------------------------------------------------------- -// gc_bin_parser -// -// The following part of the code may contain portions of the code from the Go -// standard library, which tells me to retain their copyright notice: -// -// Copyright (c) 2012 The Go Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -//------------------------------------------------------------------------- - -type gc_bin_parser struct { - data []byte - buf []byte // for reading strings - version int // export format version - - // object lists - strList []string // in order of appearance - pathList []string // in order of appearance - pkgList []string // in order of appearance - typList []ast.Expr // in order of appearance - callback func(pkg string, decl ast.Decl) - pfc *package_file_cache - trackAllTypes bool - - // position encoding - posInfoFormat bool - prevFile string - prevLine int - - // debugging support - debugFormat bool - read int // bytes read - -} - -func (p *gc_bin_parser) init(data []byte, pfc *package_file_cache) { - p.data = data - p.version = -1 // unknown version - p.strList = []string{""} // empty string is mapped to 0 - p.pathList = []string{""} // empty string is mapped to 0 - p.pfc = pfc -} - -func (p *gc_bin_parser) parse_export(callback func(string, ast.Decl)) { - p.callback = callback - - // read version info - var versionstr string - if b := p.rawByte(); b == 'c' || b == 'd' { - // Go1.7 encoding; first byte encodes low-level - // encoding format (compact vs debug). - // For backward-compatibility only (avoid problems with - // old installed packages). Newly compiled packages use - // the extensible format string. - // TODO(gri) Remove this support eventually; after Go1.8. - if b == 'd' { - p.debugFormat = true - } - p.trackAllTypes = p.rawByte() == 'a' - p.posInfoFormat = p.int() != 0 - versionstr = p.string() - if versionstr == "v1" { - p.version = 0 - } - } else { - // Go1.8 extensible encoding - // read version string and extract version number (ignore anything after the version number) - versionstr = p.rawStringln(b) - if s := strings.SplitN(versionstr, " ", 3); len(s) >= 2 && s[0] == "version" { - if v, err := strconv.Atoi(s[1]); err == nil && v > 0 { - p.version = v - } - } - } - - // read version specific flags - extend as necessary - switch p.version { - // case 6: - // ... - // fallthrough - case 5, 4, 3, 2, 1: - p.debugFormat = p.rawStringln(p.rawByte()) == "debug" - p.trackAllTypes = p.int() != 0 - p.posInfoFormat = p.int() != 0 - case 0: - // Go1.7 encoding format - nothing to do here - default: - panic(fmt.Errorf("unknown export format version %d (%q)", p.version, versionstr)) - } - - // --- generic export data --- - - // populate typList with predeclared "known" types - p.typList = append(p.typList, predeclared...) - - // read package data - pkgName := p.pkg() - p.pfc.defalias = pkgName[strings.LastIndex(pkgName, "!")+1:] - - // read objects of phase 1 only (see cmd/compiler/internal/gc/bexport.go) - objcount := 0 - for { - tag := p.tagOrIndex() - if tag == endTag { - break - } - p.obj(tag) - objcount++ - } - - // self-verification - if count := p.int(); count != objcount { - panic(fmt.Sprintf("got %d objects; want %d", objcount, count)) - } -} - -func (p *gc_bin_parser) pkg() string { - // if the package was seen before, i is its index (>= 0) - i := p.tagOrIndex() - if i >= 0 { - return p.pkgList[i] - } - - // otherwise, i is the package tag (< 0) - if i != packageTag { - panic(fmt.Sprintf("unexpected package tag %d version %d", i, p.version)) - } - - // read package data - name := p.string() - var path string - if p.version >= 5 { - path = p.path() - } else { - path = p.string() - } - - // we should never see an empty package name - if name == "" { - panic("empty package name in import") - } - - // an empty path denotes the package we are currently importing; - // it must be the first package we see - if (path == "") != (len(p.pkgList) == 0) { - panic(fmt.Sprintf("package path %q for pkg index %d", path, len(p.pkgList))) - } - - var fullName string - if path != "" { - fullName = "!" + path + "!" + name - p.pfc.add_package_to_scope(fullName, path) - } else { - fullName = "!" + p.pfc.name + "!" + name - } - - // if the package was imported before, use that one; otherwise create a new one - p.pkgList = append(p.pkgList, fullName) - return p.pkgList[len(p.pkgList)-1] -} - -func (p *gc_bin_parser) obj(tag int) { - switch tag { - case constTag: - p.pos() - pkg, name := p.qualifiedName() - typ := p.typ("") - p.skipValue() // ignore const value, gocode's not interested - p.callback(pkg, &ast.GenDecl{ - Tok: token.CONST, - Specs: []ast.Spec{ - &ast.ValueSpec{ - Names: []*ast.Ident{ast.NewIdent(name)}, - Type: typ, - Values: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: "0"}}, - }, - }, - }) - - case aliasTag: - // TODO(gri) verify type alias hookup is correct - p.pos() - pkg, name := p.qualifiedName() - typ := p.typ("") - p.callback(pkg, &ast.GenDecl{ - Tok: token.TYPE, - Specs: []ast.Spec{typeAliasSpec(name, typ)}, - }) - - case typeTag: - _ = p.typ("") - - case varTag: - p.pos() - pkg, name := p.qualifiedName() - typ := p.typ("") - p.callback(pkg, &ast.GenDecl{ - Tok: token.VAR, - Specs: []ast.Spec{ - &ast.ValueSpec{ - Names: []*ast.Ident{ast.NewIdent(name)}, - Type: typ, - }, - }, - }) - - case funcTag: - p.pos() - pkg, name := p.qualifiedName() - params := p.paramList() - results := p.paramList() - p.callback(pkg, &ast.FuncDecl{ - Name: ast.NewIdent(name), - Type: &ast.FuncType{Params: params, Results: results}, - }) - - default: - panic(fmt.Sprintf("unexpected object tag %d", tag)) - } -} - -const deltaNewFile = -64 // see cmd/compile/internal/gc/bexport.go - -func (p *gc_bin_parser) pos() { - if !p.posInfoFormat { - return - } - - file := p.prevFile - line := p.prevLine - delta := p.int() - line += delta - if p.version >= 5 { - if delta == deltaNewFile { - if n := p.int(); n >= 0 { - // file changed - file = p.path() - line = n - } - } - } else { - if delta == 0 { - if n := p.int(); n >= 0 { - // file changed - file = p.prevFile[:n] + p.string() - line = p.int() - } - } - } - p.prevFile = file - p.prevLine = line - - // TODO(gri) register new position -} - -func (p *gc_bin_parser) qualifiedName() (pkg string, name string) { - name = p.string() - pkg = p.pkg() - return pkg, name -} - -func (p *gc_bin_parser) reserveMaybe() int { - if p.trackAllTypes { - p.typList = append(p.typList, nil) - return len(p.typList) - 1 - } else { - return -1 - } -} - -func (p *gc_bin_parser) recordMaybe(idx int, t ast.Expr) ast.Expr { - if idx == -1 { - return t - } - p.typList[idx] = t - return t -} - -func (p *gc_bin_parser) record(t ast.Expr) { - p.typList = append(p.typList, t) -} - -// parent is the package which declared the type; parent == nil means -// the package currently imported. The parent package is needed for -// exported struct fields and interface methods which don't contain -// explicit package information in the export data. -func (p *gc_bin_parser) typ(parent string) ast.Expr { - // if the type was seen before, i is its index (>= 0) - i := p.tagOrIndex() - if i >= 0 { - return p.typList[i] - } - - // otherwise, i is the type tag (< 0) - switch i { - case namedTag: - // read type object - p.pos() - parent, name := p.qualifiedName() - tdecl := &ast.GenDecl{ - Tok: token.TYPE, - Specs: []ast.Spec{ - &ast.TypeSpec{ - Name: ast.NewIdent(name), - }, - }, - } - - // record it right away (underlying type can contain refs to t) - t := &ast.SelectorExpr{X: ast.NewIdent(parent), Sel: ast.NewIdent(name)} - p.record(t) - - // parse underlying type - t0 := p.typ(parent) - tdecl.Specs[0].(*ast.TypeSpec).Type = t0 - - p.callback(parent, tdecl) - - // interfaces have no methods - if _, ok := t0.(*ast.InterfaceType); ok { - return t - } - - // read associated methods - for i := p.int(); i > 0; i-- { - // TODO(gri) replace this with something closer to fieldName - p.pos() - name := p.string() - if !exported(name) { - p.pkg() - } - - recv := p.paramList() - params := p.paramList() - results := p.paramList() - p.int() // go:nointerface pragma - discarded - - strip_method_receiver(recv) - p.callback(parent, &ast.FuncDecl{ - Recv: recv, - Name: ast.NewIdent(name), - Type: &ast.FuncType{Params: params, Results: results}, - }) - } - return t - case arrayTag: - i := p.reserveMaybe() - n := p.int64() - elt := p.typ(parent) - return p.recordMaybe(i, &ast.ArrayType{ - Len: &ast.BasicLit{Kind: token.INT, Value: fmt.Sprint(n)}, - Elt: elt, - }) - - case sliceTag: - i := p.reserveMaybe() - elt := p.typ(parent) - return p.recordMaybe(i, &ast.ArrayType{Len: nil, Elt: elt}) - - case dddTag: - i := p.reserveMaybe() - elt := p.typ(parent) - return p.recordMaybe(i, &ast.Ellipsis{Elt: elt}) - - case structTag: - i := p.reserveMaybe() - return p.recordMaybe(i, p.structType(parent)) - - case pointerTag: - i := p.reserveMaybe() - elt := p.typ(parent) - return p.recordMaybe(i, &ast.StarExpr{X: elt}) - - case signatureTag: - i := p.reserveMaybe() - params := p.paramList() - results := p.paramList() - return p.recordMaybe(i, &ast.FuncType{Params: params, Results: results}) - - case interfaceTag: - i := p.reserveMaybe() - var embeddeds []*ast.SelectorExpr - for n := p.int(); n > 0; n-- { - p.pos() - if named, ok := p.typ(parent).(*ast.SelectorExpr); ok { - embeddeds = append(embeddeds, named) - } - } - methods := p.methodList(parent) - for _, field := range embeddeds { - methods = append(methods, &ast.Field{Type: field}) - } - return p.recordMaybe(i, &ast.InterfaceType{Methods: &ast.FieldList{List: methods}}) - - case mapTag: - i := p.reserveMaybe() - key := p.typ(parent) - val := p.typ(parent) - return p.recordMaybe(i, &ast.MapType{Key: key, Value: val}) - - case chanTag: - i := p.reserveMaybe() - dir := ast.SEND | ast.RECV - switch d := p.int(); d { - case 1: - dir = ast.RECV - case 2: - dir = ast.SEND - case 3: - // already set - default: - panic(fmt.Sprintf("unexpected channel dir %d", d)) - } - elt := p.typ(parent) - return p.recordMaybe(i, &ast.ChanType{Dir: dir, Value: elt}) - - default: - panic(fmt.Sprintf("unexpected type tag %d", i)) - } -} - -func (p *gc_bin_parser) structType(parent string) *ast.StructType { - var fields []*ast.Field - if n := p.int(); n > 0 { - fields = make([]*ast.Field, n) - for i := range fields { - fields[i], _ = p.field(parent) // (*ast.Field, tag), not interested in tags - } - } - return &ast.StructType{Fields: &ast.FieldList{List: fields}} -} - -func (p *gc_bin_parser) field(parent string) (*ast.Field, string) { - p.pos() - _, name, _ := p.fieldName(parent) - typ := p.typ(parent) - tag := p.string() - - var names []*ast.Ident - if name != "" { - names = []*ast.Ident{ast.NewIdent(name)} - } - return &ast.Field{ - Names: names, - Type: typ, - }, tag -} - -func (p *gc_bin_parser) methodList(parent string) (methods []*ast.Field) { - if n := p.int(); n > 0 { - methods = make([]*ast.Field, n) - for i := range methods { - methods[i] = p.method(parent) - } - } - return -} - -func (p *gc_bin_parser) method(parent string) *ast.Field { - p.pos() - _, name, _ := p.fieldName(parent) - params := p.paramList() - results := p.paramList() - return &ast.Field{ - Names: []*ast.Ident{ast.NewIdent(name)}, - Type: &ast.FuncType{Params: params, Results: results}, - } -} - -func (p *gc_bin_parser) fieldName(parent string) (string, string, bool) { - name := p.string() - pkg := parent - if p.version == 0 && name == "_" { - // version 0 didn't export a package for _ fields - return pkg, name, false - } - var alias bool - switch name { - case "": - // 1) field name matches base type name and is exported: nothing to do - case "?": - // 2) field name matches base type name and is not exported: need package - name = "" - pkg = p.pkg() - case "@": - // 3) field name doesn't match type name (alias) - name = p.string() - alias = true - fallthrough - default: - if !exported(name) { - pkg = p.pkg() - } - } - return pkg, name, alias -} - -func (p *gc_bin_parser) paramList() *ast.FieldList { - n := p.int() - if n == 0 { - return nil - } - // negative length indicates unnamed parameters - named := true - if n < 0 { - n = -n - named = false - } - // n > 0 - flds := make([]*ast.Field, n) - for i := range flds { - flds[i] = p.param(named) - } - return &ast.FieldList{List: flds} -} - -func (p *gc_bin_parser) param(named bool) *ast.Field { - t := p.typ("") - - name := "?" - if named { - name = p.string() - if name == "" { - panic("expected named parameter") - } - if name != "_" { - p.pkg() - } - if i := strings.Index(name, "·"); i > 0 { - name = name[:i] // cut off gc-specific parameter numbering - } - } - - // read and discard compiler-specific info - p.string() - - return &ast.Field{ - Names: []*ast.Ident{ast.NewIdent(name)}, - Type: t, - } -} - -func exported(name string) bool { - ch, _ := utf8.DecodeRuneInString(name) - return unicode.IsUpper(ch) -} - -func (p *gc_bin_parser) skipValue() { - switch tag := p.tagOrIndex(); tag { - case falseTag, trueTag: - case int64Tag: - p.int64() - case floatTag: - p.float() - case complexTag: - p.float() - p.float() - case stringTag: - p.string() - default: - panic(fmt.Sprintf("unexpected value tag %d", tag)) - } -} - -func (p *gc_bin_parser) float() { - sign := p.int() - if sign == 0 { - return - } - - p.int() // exp - p.string() // mant -} - -// ---------------------------------------------------------------------------- -// Low-level decoders - -func (p *gc_bin_parser) tagOrIndex() int { - if p.debugFormat { - p.marker('t') - } - - return int(p.rawInt64()) -} - -func (p *gc_bin_parser) int() int { - x := p.int64() - if int64(int(x)) != x { - panic("exported integer too large") - } - return int(x) -} - -func (p *gc_bin_parser) int64() int64 { - if p.debugFormat { - p.marker('i') - } - - return p.rawInt64() -} - -func (p *gc_bin_parser) path() string { - if p.debugFormat { - p.marker('p') - } - // if the path was seen before, i is its index (>= 0) - // (the empty string is at index 0) - i := p.rawInt64() - if i >= 0 { - return p.pathList[i] - } - // otherwise, i is the negative path length (< 0) - a := make([]string, -i) - for n := range a { - a[n] = p.string() - } - s := strings.Join(a, "/") - p.pathList = append(p.pathList, s) - return s -} - -func (p *gc_bin_parser) string() string { - if p.debugFormat { - p.marker('s') - } - // if the string was seen before, i is its index (>= 0) - // (the empty string is at index 0) - i := p.rawInt64() - if i >= 0 { - return p.strList[i] - } - // otherwise, i is the negative string length (< 0) - if n := int(-i); n <= cap(p.buf) { - p.buf = p.buf[:n] - } else { - p.buf = make([]byte, n) - } - for i := range p.buf { - p.buf[i] = p.rawByte() - } - s := string(p.buf) - p.strList = append(p.strList, s) - return s -} - -func (p *gc_bin_parser) marker(want byte) { - if got := p.rawByte(); got != want { - panic(fmt.Sprintf("incorrect marker: got %c; want %c (pos = %d)", got, want, p.read)) - } - - pos := p.read - if n := int(p.rawInt64()); n != pos { - panic(fmt.Sprintf("incorrect position: got %d; want %d", n, pos)) - } -} - -// rawInt64 should only be used by low-level decoders. -func (p *gc_bin_parser) rawInt64() int64 { - i, err := binary.ReadVarint(p) - if err != nil { - panic(fmt.Sprintf("read error: %v", err)) - } - return i -} - -// rawStringln should only be used to read the initial version string. -func (p *gc_bin_parser) rawStringln(b byte) string { - p.buf = p.buf[:0] - for b != '\n' { - p.buf = append(p.buf, b) - b = p.rawByte() - } - return string(p.buf) -} - -// needed for binary.ReadVarint in rawInt64 -func (p *gc_bin_parser) ReadByte() (byte, error) { - return p.rawByte(), nil -} - -// byte is the bottleneck interface for reading p.data. -// It unescapes '|' 'S' to '$' and '|' '|' to '|'. -// rawByte should only be used by low-level decoders. -func (p *gc_bin_parser) rawByte() byte { - b := p.data[0] - r := 1 - if b == '|' { - b = p.data[1] - r = 2 - switch b { - case 'S': - b = '$' - case '|': - // nothing to do - default: - panic("unexpected escape sequence in export data") - } - } - p.data = p.data[r:] - p.read += r - return b - -} - -// ---------------------------------------------------------------------------- -// Export format - -// Tags. Must be < 0. -const ( - // Objects - packageTag = -(iota + 1) - constTag - typeTag - varTag - funcTag - endTag - - // Types - namedTag - arrayTag - sliceTag - dddTag - structTag - pointerTag - signatureTag - interfaceTag - mapTag - chanTag - - // Values - falseTag - trueTag - int64Tag - floatTag - fractionTag // not used by gc - complexTag - stringTag - nilTag // only used by gc (appears in exported inlined function bodies) - unknownTag // not used by gc (only appears in packages with errors) - - // Type aliases - aliasTag -) - -var predeclared = []ast.Expr{ - // basic types - ast.NewIdent("bool"), - ast.NewIdent("int"), - ast.NewIdent("int8"), - ast.NewIdent("int16"), - ast.NewIdent("int32"), - ast.NewIdent("int64"), - ast.NewIdent("uint"), - ast.NewIdent("uint8"), - ast.NewIdent("uint16"), - ast.NewIdent("uint32"), - ast.NewIdent("uint64"), - ast.NewIdent("uintptr"), - ast.NewIdent("float32"), - ast.NewIdent("float64"), - ast.NewIdent("complex64"), - ast.NewIdent("complex128"), - ast.NewIdent("string"), - - // basic type aliases - ast.NewIdent("byte"), - ast.NewIdent("rune"), - - // error - ast.NewIdent("error"), - - // TODO(nsf): don't think those are used in just package type info, - // maybe for consts, but we are not interested in that - // untyped types - ast.NewIdent(">_<"), // TODO: types.Typ[types.UntypedBool], - ast.NewIdent(">_<"), // TODO: types.Typ[types.UntypedInt], - ast.NewIdent(">_<"), // TODO: types.Typ[types.UntypedRune], - ast.NewIdent(">_<"), // TODO: types.Typ[types.UntypedFloat], - ast.NewIdent(">_<"), // TODO: types.Typ[types.UntypedComplex], - ast.NewIdent(">_<"), // TODO: types.Typ[types.UntypedString], - ast.NewIdent(">_<"), // TODO: types.Typ[types.UntypedNil], - - // package unsafe - &ast.SelectorExpr{X: ast.NewIdent("unsafe"), Sel: ast.NewIdent("Pointer")}, - - // invalid type - ast.NewIdent(">_<"), // TODO: types.Typ[types.Invalid], // only appears in packages with errors - - // used internally by gc; never used by this package or in .a files - ast.NewIdent("any"), -} diff --git a/langserver/internal/gocode/package_text.go b/langserver/internal/gocode/package_text.go deleted file mode 100644 index 9d4b5629..00000000 --- a/langserver/internal/gocode/package_text.go +++ /dev/null @@ -1,678 +0,0 @@ -package gocode - -import ( - "bytes" - "errors" - "fmt" - "go/ast" - "go/token" - "strconv" - "text/scanner" -) - -//------------------------------------------------------------------------- -// gc_parser -// -// The following part of the code may contain portions of the code from the Go -// standard library, which tells me to retain their copyright notice: -// -// Copyright (c) 2009 The Go Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -//------------------------------------------------------------------------- - -type gc_parser struct { - scanner scanner.Scanner - tok rune - lit string - path_to_name map[string]string - beautify bool - pfc *package_file_cache -} - -func (p *gc_parser) init(data []byte, pfc *package_file_cache) { - p.scanner.Init(bytes.NewReader(data)) - p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) } - p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanStrings | - scanner.ScanComments | scanner.ScanChars | scanner.SkipComments - p.scanner.Whitespace = 1<<'\t' | 1<<' ' | 1<<'\r' | 1<<'\v' | 1<<'\f' - p.scanner.Filename = "package.go" - p.next() - // and the built-in "unsafe" package to the path_to_name map - p.path_to_name = map[string]string{"unsafe": "unsafe"} - p.pfc = pfc -} - -func (p *gc_parser) next() { - p.tok = p.scanner.Scan() - switch p.tok { - case scanner.Ident, scanner.Int, scanner.String: - p.lit = p.scanner.TokenText() - default: - p.lit = "" - } -} - -func (p *gc_parser) error(msg string) { - panic(errors.New(msg)) -} - -func (p *gc_parser) errorf(format string, args ...interface{}) { - p.error(fmt.Sprintf(format, args...)) -} - -func (p *gc_parser) expect(tok rune) string { - lit := p.lit - if p.tok != tok { - p.errorf("expected %s, got %s (%q)", scanner.TokenString(tok), - scanner.TokenString(p.tok), lit) - } - p.next() - return lit -} - -func (p *gc_parser) expect_keyword(keyword string) { - lit := p.expect(scanner.Ident) - if lit != keyword { - p.errorf("expected keyword: %s, got: %q", keyword, lit) - } -} - -func (p *gc_parser) expect_special(what string) { - i := 0 - for i < len(what) { - if p.tok != rune(what[i]) { - break - } - - nc := p.scanner.Peek() - if i != len(what)-1 && nc <= ' ' { - break - } - - p.next() - i++ - } - - if i < len(what) { - p.errorf("expected: %q, got: %q", what, what[0:i]) - } -} - -// dotIdentifier = "?" | ( ident | '·' ) { ident | int | '·' } . -// we're doing lexer job here, kind of -func (p *gc_parser) parse_dot_ident() string { - if p.tok == '?' { - p.next() - return "?" - } - - ident := "" - sep := 'x' - i, j := 0, -1 - for (p.tok == scanner.Ident || p.tok == scanner.Int || p.tok == '·') && sep > ' ' { - ident += p.lit - if p.tok == '·' { - ident += "·" - j = i - i++ - } - i += len(p.lit) - sep = p.scanner.Peek() - p.next() - } - // middot = \xc2\xb7 - if j != -1 && i > j+1 { - c := ident[j+2] - if c >= '0' && c <= '9' { - ident = ident[0:j] - } - } - return ident -} - -// ImportPath = string_lit . -// quoted name of the path, but we return it as an identifier, taking an alias -// from 'pathToAlias' map, it is filled by import statements -func (p *gc_parser) parse_package() *ast.Ident { - path, err := strconv.Unquote(p.expect(scanner.String)) - if err != nil { - panic(err) - } - - return ast.NewIdent(path) -} - -// ExportedName = "@" ImportPath "." dotIdentifier . -func (p *gc_parser) parse_exported_name() *ast.SelectorExpr { - p.expect('@') - pkg := p.parse_package() - if pkg.Name == "" { - pkg.Name = "!" + p.pfc.name + "!" + p.pfc.defalias - } else { - pkg.Name = p.path_to_name[pkg.Name] - } - p.expect('.') - name := ast.NewIdent(p.parse_dot_ident()) - return &ast.SelectorExpr{X: pkg, Sel: name} -} - -// Name = identifier | "?" | ExportedName . -func (p *gc_parser) parse_name() (string, ast.Expr) { - switch p.tok { - case scanner.Ident: - name := p.lit - p.next() - return name, ast.NewIdent(name) - case '?': - p.next() - return "?", ast.NewIdent("?") - case '@': - en := p.parse_exported_name() - return en.Sel.Name, en - } - p.error("name expected") - return "", nil -} - -// Field = Name Type [ string_lit ] . -func (p *gc_parser) parse_field() *ast.Field { - var tag string - name, _ := p.parse_name() - typ := p.parse_type() - if p.tok == scanner.String { - tag = p.expect(scanner.String) - } - - var names []*ast.Ident - if name != "?" { - names = []*ast.Ident{ast.NewIdent(name)} - } - - return &ast.Field{ - Names: names, - Type: typ, - Tag: &ast.BasicLit{Kind: token.STRING, Value: tag}, - } -} - -// Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] . -func (p *gc_parser) parse_parameter() *ast.Field { - // name - name, _ := p.parse_name() - - // type - var typ ast.Expr - if p.tok == '.' { - p.expect_special("...") - typ = &ast.Ellipsis{Elt: p.parse_type()} - } else { - typ = p.parse_type() - } - - var tag string - if p.tok == scanner.String { - tag = p.expect(scanner.String) - } - - return &ast.Field{ - Names: []*ast.Ident{ast.NewIdent(name)}, - Type: typ, - Tag: &ast.BasicLit{Kind: token.STRING, Value: tag}, - } -} - -// Parameters = "(" [ ParameterList ] ")" . -// ParameterList = { Parameter "," } Parameter . -func (p *gc_parser) parse_parameters() *ast.FieldList { - flds := []*ast.Field{} - parse_parameter := func() { - par := p.parse_parameter() - flds = append(flds, par) - } - - p.expect('(') - if p.tok != ')' { - parse_parameter() - for p.tok == ',' { - p.next() - parse_parameter() - } - } - p.expect(')') - return &ast.FieldList{List: flds} -} - -// Signature = Parameters [ Result ] . -// Result = Type | Parameters . -func (p *gc_parser) parse_signature() *ast.FuncType { - var params *ast.FieldList - var results *ast.FieldList - - params = p.parse_parameters() - switch p.tok { - case scanner.Ident, '[', '*', '<', '@': - fld := &ast.Field{Type: p.parse_type()} - results = &ast.FieldList{List: []*ast.Field{fld}} - case '(': - results = p.parse_parameters() - } - return &ast.FuncType{Params: params, Results: results} -} - -// MethodOrEmbedSpec = Name [ Signature ] . -func (p *gc_parser) parse_method_or_embed_spec() *ast.Field { - name, nameexpr := p.parse_name() - if p.tok == '(' { - typ := p.parse_signature() - return &ast.Field{ - Names: []*ast.Ident{ast.NewIdent(name)}, - Type: typ, - } - } - - return &ast.Field{ - Type: nameexpr, - } -} - -// int_lit = [ "-" | "+" ] { "0" ... "9" } . -func (p *gc_parser) parse_int() { - switch p.tok { - case '-', '+': - p.next() - } - p.expect(scanner.Int) -} - -// number = int_lit [ "p" int_lit ] . -func (p *gc_parser) parse_number() { - p.parse_int() - if p.lit == "p" { - p.next() - p.parse_int() - } -} - -//------------------------------------------------------------------------------- -// gc_parser.types -//------------------------------------------------------------------------------- - -// InterfaceType = "interface" "{" [ MethodOrEmbedList ] "}" . -// MethodOrEmbedList = MethodOrEmbedSpec { ";" MethodOrEmbedSpec } . -func (p *gc_parser) parse_interface_type() ast.Expr { - var methods []*ast.Field - parse_method := func() { - meth := p.parse_method_or_embed_spec() - methods = append(methods, meth) - } - - p.expect_keyword("interface") - p.expect('{') - if p.tok != '}' { - parse_method() - for p.tok == ';' { - p.next() - parse_method() - } - } - p.expect('}') - return &ast.InterfaceType{Methods: &ast.FieldList{List: methods}} -} - -// StructType = "struct" "{" [ FieldList ] "}" . -// FieldList = Field { ";" Field } . -func (p *gc_parser) parse_struct_type() ast.Expr { - var fields []*ast.Field - parse_field := func() { - fld := p.parse_field() - fields = append(fields, fld) - } - - p.expect_keyword("struct") - p.expect('{') - if p.tok != '}' { - parse_field() - for p.tok == ';' { - p.next() - parse_field() - } - } - p.expect('}') - return &ast.StructType{Fields: &ast.FieldList{List: fields}} -} - -// MapType = "map" "[" Type "]" Type . -func (p *gc_parser) parse_map_type() ast.Expr { - p.expect_keyword("map") - p.expect('[') - key := p.parse_type() - p.expect(']') - elt := p.parse_type() - return &ast.MapType{Key: key, Value: elt} -} - -// ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type . -func (p *gc_parser) parse_chan_type() ast.Expr { - dir := ast.SEND | ast.RECV - if p.tok == scanner.Ident { - p.expect_keyword("chan") - if p.tok == '<' { - p.expect_special("<-") - dir = ast.SEND - } - } else { - p.expect_special("<-") - p.expect_keyword("chan") - dir = ast.RECV - } - - elt := p.parse_type() - return &ast.ChanType{Dir: dir, Value: elt} -} - -// ArrayOrSliceType = ArrayType | SliceType . -// ArrayType = "[" int_lit "]" Type . -// SliceType = "[" "]" Type . -func (p *gc_parser) parse_array_or_slice_type() ast.Expr { - p.expect('[') - if p.tok == ']' { - // SliceType - p.next() // skip ']' - return &ast.ArrayType{Len: nil, Elt: p.parse_type()} - } - - // ArrayType - lit := p.expect(scanner.Int) - p.expect(']') - return &ast.ArrayType{ - Len: &ast.BasicLit{Kind: token.INT, Value: lit}, - Elt: p.parse_type(), - } -} - -// Type = -// BasicType | TypeName | ArrayType | SliceType | StructType | -// PointerType | FuncType | InterfaceType | MapType | ChanType | -// "(" Type ")" . -// BasicType = ident . -// TypeName = ExportedName . -// SliceType = "[" "]" Type . -// PointerType = "*" Type . -// FuncType = "func" Signature . -func (p *gc_parser) parse_type() ast.Expr { - switch p.tok { - case scanner.Ident: - switch p.lit { - case "struct": - return p.parse_struct_type() - case "func": - p.next() - return p.parse_signature() - case "interface": - return p.parse_interface_type() - case "map": - return p.parse_map_type() - case "chan": - return p.parse_chan_type() - default: - lit := p.lit - p.next() - return ast.NewIdent(lit) - } - case '@': - return p.parse_exported_name() - case '[': - return p.parse_array_or_slice_type() - case '*': - p.next() - return &ast.StarExpr{X: p.parse_type()} - case '<': - return p.parse_chan_type() - case '(': - p.next() - typ := p.parse_type() - p.expect(')') - return typ - } - p.errorf("unexpected token: %s", scanner.TokenString(p.tok)) - return nil -} - -//------------------------------------------------------------------------------- -// gc_parser.declarations -//------------------------------------------------------------------------------- - -// ImportDecl = "import" identifier string_lit . -func (p *gc_parser) parse_import_decl() { - p.expect_keyword("import") - alias := p.expect(scanner.Ident) - path := p.parse_package() - fullName := "!" + path.Name + "!" + alias - p.path_to_name[path.Name] = fullName - p.pfc.add_package_to_scope(fullName, path.Name) -} - -// ConstDecl = "const" ExportedName [ Type ] "=" Literal . -// Literal = bool_lit | int_lit | float_lit | complex_lit | string_lit . -// bool_lit = "true" | "false" . -// complex_lit = "(" float_lit "+" float_lit ")" . -// rune_lit = "(" int_lit "+" int_lit ")" . -// string_lit = `"` { unicode_char } `"` . -func (p *gc_parser) parse_const_decl() (string, *ast.GenDecl) { - // TODO: do we really need actual const value? gocode doesn't use this - p.expect_keyword("const") - name := p.parse_exported_name() - - var typ ast.Expr - if p.tok != '=' { - typ = p.parse_type() - } - - p.expect('=') - - // skip the value - switch p.tok { - case scanner.Ident: - // must be bool, true or false - p.next() - case '-', '+', scanner.Int: - // number - p.parse_number() - case '(': - // complex_lit or rune_lit - p.next() // skip '(' - if p.tok == scanner.Char { - p.next() - } else { - p.parse_number() - } - p.expect('+') - p.parse_number() - p.expect(')') - case scanner.Char: - p.next() - case scanner.String: - p.next() - default: - p.error("expected literal") - } - - return name.X.(*ast.Ident).Name, &ast.GenDecl{ - Tok: token.CONST, - Specs: []ast.Spec{ - &ast.ValueSpec{ - Names: []*ast.Ident{name.Sel}, - Type: typ, - Values: []ast.Expr{&ast.BasicLit{Kind: token.INT, Value: "0"}}, - }, - }, - } -} - -// TypeDecl = "type" ExportedName Type . -func (p *gc_parser) parse_type_decl() (string, *ast.GenDecl) { - p.expect_keyword("type") - name := p.parse_exported_name() - typ := p.parse_type() - return name.X.(*ast.Ident).Name, &ast.GenDecl{ - Tok: token.TYPE, - Specs: []ast.Spec{ - &ast.TypeSpec{ - Name: name.Sel, - Type: typ, - }, - }, - } -} - -// VarDecl = "var" ExportedName Type . -func (p *gc_parser) parse_var_decl() (string, *ast.GenDecl) { - p.expect_keyword("var") - name := p.parse_exported_name() - typ := p.parse_type() - return name.X.(*ast.Ident).Name, &ast.GenDecl{ - Tok: token.VAR, - Specs: []ast.Spec{ - &ast.ValueSpec{ - Names: []*ast.Ident{name.Sel}, - Type: typ, - }, - }, - } -} - -// FuncBody = "{" ... "}" . -func (p *gc_parser) parse_func_body() { - p.expect('{') - for i := 1; i > 0; p.next() { - switch p.tok { - case '{': - i++ - case '}': - i-- - } - } -} - -// FuncDecl = "func" ExportedName Signature [ FuncBody ] . -func (p *gc_parser) parse_func_decl() (string, *ast.FuncDecl) { - // "func" was already consumed by lookahead - name := p.parse_exported_name() - typ := p.parse_signature() - if p.tok == '{' { - p.parse_func_body() - } - return name.X.(*ast.Ident).Name, &ast.FuncDecl{ - Name: name.Sel, - Type: typ, - } -} - -func strip_method_receiver(recv *ast.FieldList) string { - var sel *ast.SelectorExpr - - // find selector expression - typ := recv.List[0].Type - switch t := typ.(type) { - case *ast.StarExpr: - sel = t.X.(*ast.SelectorExpr) - case *ast.SelectorExpr: - sel = t - } - - // extract package path - pkg := sel.X.(*ast.Ident).Name - - // write back stripped type - switch t := typ.(type) { - case *ast.StarExpr: - t.X = sel.Sel - case *ast.SelectorExpr: - recv.List[0].Type = sel.Sel - } - - return pkg -} - -// MethodDecl = "func" Receiver Name Signature . -// Receiver = "(" ( identifier | "?" ) [ "*" ] ExportedName ")" [ FuncBody ] . -func (p *gc_parser) parse_method_decl() (string, *ast.FuncDecl) { - recv := p.parse_parameters() - pkg := strip_method_receiver(recv) - name, _ := p.parse_name() - typ := p.parse_signature() - if p.tok == '{' { - p.parse_func_body() - } - return pkg, &ast.FuncDecl{ - Recv: recv, - Name: ast.NewIdent(name), - Type: typ, - } -} - -// Decl = [ ImportDecl | ConstDecl | TypeDecl | VarDecl | FuncDecl | MethodDecl ] "\n" . -func (p *gc_parser) parse_decl() (pkg string, decl ast.Decl) { - switch p.lit { - case "import": - p.parse_import_decl() - case "const": - pkg, decl = p.parse_const_decl() - case "type": - pkg, decl = p.parse_type_decl() - case "var": - pkg, decl = p.parse_var_decl() - case "func": - p.next() - if p.tok == '(' { - pkg, decl = p.parse_method_decl() - } else { - pkg, decl = p.parse_func_decl() - } - } - p.expect('\n') - return -} - -// Export = PackageClause { Decl } "$$" . -// PackageClause = "package" identifier [ "safe" ] "\n" . -func (p *gc_parser) parse_export(callback func(string, ast.Decl)) { - p.expect_keyword("package") - p.pfc.defalias = p.expect(scanner.Ident) - if p.tok != '\n' { - p.expect_keyword("safe") - } - p.expect('\n') - - for p.tok != '$' && p.tok != scanner.EOF { - pkg, decl := p.parse_decl() - if decl != nil { - callback(pkg, decl) - } - } -} diff --git a/langserver/internal/gocode/pre_go17.go b/langserver/internal/gocode/pre_go17.go deleted file mode 100644 index d961a0c5..00000000 --- a/langserver/internal/gocode/pre_go17.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build !go1.7,!go1.8 - -package gocode - -func init() { - knownPackageIdents["context"] = "golang.org/x/net/context" -} diff --git a/langserver/internal/gocode/ripper.go b/langserver/internal/gocode/ripper.go deleted file mode 100644 index 05310572..00000000 --- a/langserver/internal/gocode/ripper.go +++ /dev/null @@ -1,141 +0,0 @@ -package gocode - -import ( - "go/scanner" - "go/token" -) - -// All the code in this file serves single purpose: -// It separates a function with the cursor inside and the rest of the code. I'm -// doing that, because sometimes parser is not able to recover itself from an -// error and the autocompletion results become less complete. - -type tok_pos_pair struct { - tok token.Token - pos token.Pos -} - -type tok_collection struct { - tokens []tok_pos_pair - fset *token.FileSet -} - -func (this *tok_collection) next(s *scanner.Scanner) bool { - pos, tok, _ := s.Scan() - if tok == token.EOF { - return false - } - - this.tokens = append(this.tokens, tok_pos_pair{tok, pos}) - return true -} - -func (this *tok_collection) find_decl_beg(pos int) int { - lowest := 0 - lowpos := -1 - lowi := -1 - cur := 0 - for i := pos; i >= 0; i-- { - t := this.tokens[i] - switch t.tok { - case token.RBRACE: - cur++ - case token.LBRACE: - cur-- - } - - if cur < lowest { - lowest = cur - lowpos = this.fset.Position(t.pos).Offset - lowi = i - } - } - - cur = lowest - for i := lowi - 1; i >= 0; i-- { - t := this.tokens[i] - switch t.tok { - case token.RBRACE: - cur++ - case token.LBRACE: - cur-- - } - if t.tok == token.SEMICOLON && cur == lowest { - lowpos = this.fset.Position(t.pos).Offset - break - } - } - - return lowpos -} - -func (this *tok_collection) find_decl_end(pos int) int { - highest := 0 - highpos := -1 - cur := 0 - - if this.tokens[pos].tok == token.LBRACE { - pos++ - } - - for i := pos; i < len(this.tokens); i++ { - t := this.tokens[i] - switch t.tok { - case token.RBRACE: - cur++ - case token.LBRACE: - cur-- - } - - if cur > highest { - highest = cur - highpos = this.fset.Position(t.pos).Offset - } - } - - return highpos -} - -func (this *tok_collection) find_outermost_scope(cursor int) (int, int) { - pos := 0 - - for i, t := range this.tokens { - if cursor <= this.fset.Position(t.pos).Offset { - break - } - pos = i - } - - return this.find_decl_beg(pos), this.find_decl_end(pos) -} - -// return new cursor position, file without ripped part and the ripped part itself -// variants: -// new-cursor, file-without-ripped-part, ripped-part -// old-cursor, file, nil -func (this *tok_collection) rip_off_decl(file []byte, cursor int) (int, []byte, []byte) { - this.fset = token.NewFileSet() - var s scanner.Scanner - s.Init(this.fset.AddFile("", this.fset.Base(), len(file)), file, nil, scanner.ScanComments) - for this.next(&s) { - } - - beg, end := this.find_outermost_scope(cursor) - if beg == -1 || end == -1 { - return cursor, file, nil - } - - ripped := make([]byte, end+1-beg) - copy(ripped, file[beg:end+1]) - - newfile := make([]byte, len(file)-len(ripped)) - copy(newfile, file[:beg]) - copy(newfile[beg:], file[end+1:]) - - return cursor - beg, newfile, ripped -} - -func rip_off_decl(file []byte, cursor int) (int, []byte, []byte) { - var tc tok_collection - return tc.rip_off_decl(file, cursor) -} diff --git a/langserver/internal/gocode/scope.go b/langserver/internal/gocode/scope.go deleted file mode 100644 index 14527148..00000000 --- a/langserver/internal/gocode/scope.go +++ /dev/null @@ -1,77 +0,0 @@ -package gocode - -//------------------------------------------------------------------------- -// scope -//------------------------------------------------------------------------- - -type scope struct { - // the package name that this scope resides in - pkgname string - parent *scope // nil for universe scope - entities map[string]*decl -} - -func new_named_scope(outer *scope, name string) *scope { - s := new_scope(outer) - s.pkgname = name - return s -} - -func new_scope(outer *scope) *scope { - s := new(scope) - if outer != nil { - s.pkgname = outer.pkgname - } - s.parent = outer - s.entities = make(map[string]*decl) - return s -} - -// returns: new, prev -func advance_scope(s *scope) (*scope, *scope) { - if len(s.entities) == 0 { - return s, s.parent - } - return new_scope(s), s -} - -// adds declaration or returns an existing one -func (s *scope) add_named_decl(d *decl) *decl { - return s.add_decl(d.name, d) -} - -func (s *scope) add_decl(name string, d *decl) *decl { - decl, ok := s.entities[name] - if !ok { - s.entities[name] = d - return d - } - return decl -} - -func (s *scope) replace_decl(name string, d *decl) { - s.entities[name] = d -} - -func (s *scope) merge_decl(d *decl) { - decl, ok := s.entities[d.name] - if !ok { - s.entities[d.name] = d - } else { - decl := decl.deep_copy() - decl.expand_or_replace(d) - s.entities[d.name] = decl - } -} - -func (s *scope) lookup(name string) *decl { - decl, ok := s.entities[name] - if !ok { - if s.parent != nil { - return s.parent.lookup(name) - } else { - return nil - } - } - return decl -} diff --git a/langserver/internal/gocode/server.go b/langserver/internal/gocode/server.go deleted file mode 100644 index 7bc2a7ca..00000000 --- a/langserver/internal/gocode/server.go +++ /dev/null @@ -1,243 +0,0 @@ -package gocode - -import ( - "bytes" - "fmt" - "go/build" - "log" - "net" - "net/rpc" - "os" - "path/filepath" - "reflect" - "runtime" - "time" -) - -func do_server() int { - g_config.read() - if g_config.ForceDebugOutput != "" { - // forcefully enable debugging and redirect logging into the - // specified file - *g_debug = true - f, err := os.Create(g_config.ForceDebugOutput) - if err != nil { - panic(err) - } - log.SetOutput(f) - } - - addr := *g_addr - if *g_sock == "unix" { - addr = get_socket_filename() - if file_exists(addr) { - log.Printf("unix socket: '%s' already exists\n", addr) - return 1 - } - } - g_daemon = new_daemon(*g_sock, addr) - if *g_sock == "unix" { - // cleanup unix socket file - defer os.Remove(addr) - } - - rpc.Register(new(RPC)) - - g_daemon.loop() - return 0 -} - -//------------------------------------------------------------------------- -// daemon -//------------------------------------------------------------------------- - -type daemon struct { - listener net.Listener - cmd_in chan int - autocomplete *auto_complete_context - pkgcache package_cache - declcache *decl_cache - context package_lookup_context -} - -func new_daemon(network, address string) *daemon { - var err error - - d := new(daemon) - d.listener, err = net.Listen(network, address) - if err != nil { - panic(err) - } - - d.cmd_in = make(chan int, 1) - d.pkgcache = new_package_cache() - d.declcache = new_decl_cache(&d.context) - d.autocomplete = new_auto_complete_context(d.pkgcache, d.declcache) - return d -} - -func (this *daemon) drop_cache() { - this.pkgcache = new_package_cache() - this.declcache = new_decl_cache(&this.context) - this.autocomplete = new_auto_complete_context(this.pkgcache, this.declcache) -} - -const ( - daemon_close = iota -) - -func (this *daemon) loop() { - conn_in := make(chan net.Conn) - go func() { - for { - c, err := this.listener.Accept() - if err != nil { - panic(err) - } - conn_in <- c - } - }() - - timeout := time.Duration(g_config.CloseTimeout) * time.Second - countdown := time.NewTimer(timeout) - - for { - // handle connections or server CMDs (currently one CMD) - select { - case c := <-conn_in: - rpc.ServeConn(c) - countdown.Reset(timeout) - runtime.GC() - case cmd := <-this.cmd_in: - switch cmd { - case daemon_close: - return - } - case <-countdown.C: - return - } - } -} - -func (this *daemon) close() { - this.cmd_in <- daemon_close -} - -var g_daemon *daemon - -//------------------------------------------------------------------------- -// server_* functions -// -// Corresponding client_* functions are autogenerated by goremote. -//------------------------------------------------------------------------- - -func server_auto_complete(file []byte, filename string, cursor int, context_packed go_build_context) (c []candidate, d int) { - context := unpack_build_context(&context_packed) - defer func() { - if err := recover(); err != nil { - print_backtrace(err) - c = []candidate{ - {"PANIC", "PANIC", decl_invalid, "panic"}, - } - - // drop cache - g_daemon.drop_cache() - } - }() - // TODO: Probably we don't care about comparing all the fields, checking GOROOT and GOPATH - // should be enough. - if !reflect.DeepEqual(g_daemon.context.Context, context.Context) { - g_daemon.context = context - g_daemon.drop_cache() - } - switch g_config.PackageLookupMode { - case "bzl": - // when package lookup mode is bzl, we set GOPATH to "" explicitly and - // BzlProjectRoot becomes valid (or empty) - var err error - g_daemon.context.GOPATH = "" - g_daemon.context.BzlProjectRoot, err = find_bzl_project_root(g_config.LibPath, filename) - if *g_debug && err != nil { - log.Printf("Bzl project root not found: %s", err) - } - case "gb": - // when package lookup mode is gb, we set GOPATH to "" explicitly and - // GBProjectRoot becomes valid (or empty) - var err error - g_daemon.context.GOPATH = "" - g_daemon.context.GBProjectRoot, err = find_gb_project_root(filename) - if *g_debug && err != nil { - log.Printf("Gb project root not found: %s", err) - } - case "go": - // get current package path for GO15VENDOREXPERIMENT hack - g_daemon.context.CurrentPackagePath = "" - pkg, err := g_daemon.context.ImportDir(filepath.Dir(filename), build.FindOnly) - if err == nil { - if *g_debug { - log.Printf("Go project path: %s", pkg.ImportPath) - } - g_daemon.context.CurrentPackagePath = pkg.ImportPath - } else if *g_debug { - log.Printf("Go project path not found: %s", err) - } - } - if *g_debug { - var buf bytes.Buffer - log.Printf("Got autocompletion request for '%s'\n", filename) - log.Printf("Cursor at: %d\n", cursor) - if cursor > len(file) || cursor < 0 { - log.Println("ERROR! Cursor is outside of the boundaries of the buffer, " + - "this is most likely a text editor plugin bug. Text editor is responsible " + - "for passing the correct cursor position to gocode.") - } else { - buf.WriteString("-------------------------------------------------------\n") - buf.Write(file[:cursor]) - buf.WriteString("#") - buf.Write(file[cursor:]) - log.Print(buf.String()) - log.Println("-------------------------------------------------------") - } - } - candidates, d := g_daemon.autocomplete.apropos(file, filename, cursor) - if *g_debug { - log.Printf("Offset: %d\n", d) - log.Printf("Number of candidates found: %d\n", len(candidates)) - log.Printf("Candidates are:\n") - for _, c := range candidates { - abbr := fmt.Sprintf("%s %s %s", c.Class, c.Name, c.Type) - if c.Class == decl_func { - abbr = fmt.Sprintf("%s %s%s", c.Class, c.Name, c.Type[len("func"):]) - } - log.Printf(" %s\n", abbr) - } - log.Println("=======================================================") - } - return candidates, d -} - -func server_close(notused int) int { - g_daemon.close() - return 0 -} - -func server_status(notused int) string { - return g_daemon.autocomplete.status() -} - -func server_drop_cache(notused int) int { - // drop cache - g_daemon.drop_cache() - return 0 -} - -func server_set(key, value string) string { - if key == "\x00" { - return g_config.list() - } else if value == "\x00" { - return g_config.list_option(key) - } - // drop cache on settings changes - g_daemon.drop_cache() - return g_config.set_option(key, value) -} diff --git a/langserver/internal/gocode/suggest/candidate.go b/langserver/internal/gocode/suggest/candidate.go new file mode 100644 index 00000000..d83bda90 --- /dev/null +++ b/langserver/internal/gocode/suggest/candidate.go @@ -0,0 +1,189 @@ +package suggest + +import ( + "fmt" + "go/types" + "sort" + "strings" +) + +type Candidate struct { + Class string `json:"class"` + PkgPath string `json:"package"` + Name string `json:"name"` + Type string `json:"type"` +} + +func (c Candidate) Suggestion() string { + switch { + case c.Class != "func": + return c.Name + case strings.HasPrefix(c.Type, "func()"): + return c.Name + "()" + default: + return c.Name + "(" + } +} + +func (c Candidate) String() string { + if c.Class == "func" { + return fmt.Sprintf("%s %s%s", c.Class, c.Name, strings.TrimPrefix(c.Type, "func")) + } + return fmt.Sprintf("%s %s %s", c.Class, c.Name, c.Type) +} + +type candidatesByClassAndName []Candidate + +func (s candidatesByClassAndName) Len() int { return len(s) } +func (s candidatesByClassAndName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s candidatesByClassAndName) Less(i, j int) bool { + if s[i].Class != s[j].Class { + return s[i].Class < s[j].Class + } + return s[i].Name < s[j].Name +} + +type objectFilter func(types.Object) bool + +var objectFilters = map[string]objectFilter{ + "const": func(obj types.Object) bool { _, ok := obj.(*types.Const); return ok }, + "func": func(obj types.Object) bool { _, ok := obj.(*types.Func); return ok }, + "package": func(obj types.Object) bool { _, ok := obj.(*types.PkgName); return ok }, + "type": func(obj types.Object) bool { _, ok := obj.(*types.TypeName); return ok }, + "var": func(obj types.Object) bool { _, ok := obj.(*types.Var); return ok }, +} + +func classifyObject(obj types.Object) string { + switch obj.(type) { + case *types.Builtin: + return "func" + case *types.Const: + return "const" + case *types.Func: + return "func" + case *types.Nil: + return "const" + case *types.PkgName: + return "package" + case *types.TypeName: + return "type" + case *types.Var: + return "var" + } + panic(fmt.Sprintf("unhandled types.Object: %T", obj)) +} + +type candidateCollector struct { + exact []types.Object + badcase []types.Object + localpkg *types.Package + partial string + filter objectFilter + builtin bool +} + +func (b *candidateCollector) getCandidates() []Candidate { + objs := b.exact + if objs == nil { + objs = b.badcase + } + + var res []Candidate + for _, obj := range objs { + res = append(res, b.asCandidate(obj)) + } + sort.Sort(candidatesByClassAndName(res)) + return res +} + +func (b *candidateCollector) asCandidate(obj types.Object) Candidate { + objClass := classifyObject(obj) + var typ types.Type + switch objClass { + case "const", "func", "var": + typ = obj.Type() + case "type": + typ = obj.Type().Underlying() + } + + var typStr string + switch t := typ.(type) { + case *types.Interface: + typStr = "interface" + case *types.Struct: + typStr = "struct" + default: + if _, isBuiltin := obj.(*types.Builtin); isBuiltin { + typStr = builtinTypes[obj.Name()] + } else if t != nil { + typStr = types.TypeString(t, b.qualify) + } + } + + path := "builtin" + if pkg := obj.Pkg(); pkg != nil { + path = pkg.Path() + } + + return Candidate{ + Class: objClass, + PkgPath: path, + Name: obj.Name(), + Type: typStr, + } +} + +var builtinTypes = map[string]string{ + // Universe. + "append": "func(slice []Type, elems ..Type) []Type", + "cap": "func(v Type) int", + "close": "func(c chan<- Type)", + "complex": "func(real FloatType, imag FloatType) ComplexType", + "copy": "func(dst []Type, src []Type) int", + "delete": "func(m map[Key]Type, key Key)", + "imag": "func(c ComplexType) FloatType", + "len": "func(v Type) int", + "make": "func(Type, size IntegerType) Type", + "new": "func(Type) *Type", + "panic": "func(v interface{})", + "print": "func(args ...Type)", + "println": "func(args ...Type)", + "real": "func(c ComplexType) FloatType", + "recover": "func() interface{}", + + // Package unsafe. + "Alignof": "func(x Type) uintptr", + "Sizeof": "func(x Type) uintptr", + "Offsetof": "func(x Type) uintptr", +} + +func (b *candidateCollector) qualify(pkg *types.Package) string { + if pkg == b.localpkg { + return "" + } + return pkg.Name() +} + +func (b *candidateCollector) appendObject(obj types.Object) { + if obj.Pkg() != b.localpkg { + if obj.Parent() == types.Universe { + if !b.builtin { + return + } + } else if !obj.Exported() { + return + } + } + + // TODO(mdempsky): Reconsider this functionality. + if b.filter != nil && !b.filter(obj) { + return + } + + if b.filter != nil || strings.HasPrefix(obj.Name(), b.partial) { + b.exact = append(b.exact, obj) + } else if strings.HasPrefix(strings.ToLower(obj.Name()), strings.ToLower(b.partial)) { + b.badcase = append(b.badcase, obj) + } +} diff --git a/langserver/internal/gocode/suggest/cursorcontext.go b/langserver/internal/gocode/suggest/cursorcontext.go new file mode 100644 index 00000000..63ce9f9b --- /dev/null +++ b/langserver/internal/gocode/suggest/cursorcontext.go @@ -0,0 +1,305 @@ +package suggest + +import ( + "bytes" + "go/scanner" + "go/token" +) + +type tokenIterator struct { + tokens []tokenItem + pos int +} + +type tokenItem struct { + tok token.Token + lit string +} + +func (i tokenItem) String() string { + if i.tok.IsLiteral() { + return i.lit + } + return i.tok.String() +} + +func newTokenIterator(src []byte, cursor int) (tokenIterator, int) { + fset := token.NewFileSet() + file := fset.AddFile("", fset.Base(), len(src)) + cursorPos := file.Pos(cursor) + + var s scanner.Scanner + s.Init(file, src, nil, 0) + tokens := make([]tokenItem, 0, 1000) + lastPos := token.NoPos + for { + pos, tok, lit := s.Scan() + if tok == token.EOF || pos >= cursorPos { + break + } + tokens = append(tokens, tokenItem{ + tok: tok, + lit: lit, + }) + lastPos = pos + } + return tokenIterator{ + tokens: tokens, + pos: len(tokens) - 1, + }, int(cursorPos - lastPos) +} + +func (ti *tokenIterator) token() tokenItem { + return ti.tokens[ti.pos] +} + +func (ti *tokenIterator) prev() bool { + if ti.pos <= 0 { + return false + } + ti.pos-- + return true +} + +var bracket_pairs_map = map[token.Token]token.Token{ + token.RPAREN: token.LPAREN, + token.RBRACK: token.LBRACK, + token.RBRACE: token.LBRACE, +} + +func (ti *tokenIterator) skipToLeft(left, right token.Token) bool { + if ti.token().tok == left { + return true + } + balance := 1 + for balance != 0 { + if !ti.prev() { + return false + } + switch ti.token().tok { + case right: + balance++ + case left: + balance-- + } + } + return true +} + +// when the cursor is at the ')' or ']' or '}', move the cursor to an opposite +// bracket pair, this functions takes nested bracket pairs into account +func (ti *tokenIterator) skipToBalancedPair() bool { + right := ti.token().tok + left := bracket_pairs_map[right] + return ti.skipToLeft(left, right) +} + +// Move the cursor to the open brace of the current block, taking nested blocks +// into account. +func (ti *tokenIterator) skipToLeftCurly() bool { + return ti.skipToLeft(token.LBRACE, token.RBRACE) +} + +// Extract the type expression right before the enclosing curly bracket block. +// Examples (# - the cursor): +// &lib.Struct{Whatever: 1, Hel#} // returns "lib.Struct" +// X{#} // returns X +// The idea is that we check if this type expression is a type and it is, we +// can apply special filtering for autocompletion results. +func (ti *tokenIterator) extractLiteralType() (res string) { + if !ti.skipToLeftCurly() { + return "" + } + origPos := ti.pos + if !ti.prev() { + return "" + } + + // A composite literal type must end with either "ident", + // "ident.ident", or "struct { ... }". + switch ti.token().tok { + case token.IDENT: + if !ti.prev() { + return "" + } + if ti.token().tok == token.PERIOD { + if !ti.prev() { + return "" + } + if ti.token().tok != token.IDENT { + return "" + } + if !ti.prev() { + return "" + } + } + case token.RBRACE: + ti.skipToBalancedPair() + if !ti.prev() { + return "" + } + if ti.token().tok != token.STRUCT { + return "" + } + if !ti.prev() { + return "" + } + } + + // Continuing backwards, we might see "[]", "[...]", "[expr]", + // or "map[T]". + for ti.token().tok == token.RBRACK { + ti.skipToBalancedPair() + if !ti.prev() { + return "" + } + if ti.token().tok == token.MAP { + if !ti.prev() { + return "" + } + } + } + + return joinTokens(ti.tokens[ti.pos+1 : origPos]) +} + +// Starting from the token under the cursor move back and extract something +// that resembles a valid Go primary expression. Examples of primary expressions +// from Go spec: +// x +// 2 +// (s + ".txt") +// f(3.1415, true) +// Point{1, 2} +// m["foo"] +// s[i : j + 1] +// obj.color +// f.p[i].x() +// +// As you can see we can move through all of them using balanced bracket +// matching and applying simple rules +// E.g. +// Point{1, 2}.m["foo"].s[i : j + 1].MethodCall(a, func(a, b int) int { return a + b }). +// Can be seen as: +// Point{ }.m[ ].s[ ].MethodCall( ). +// Which boils the rules down to these connected via dots: +// ident +// ident[] +// ident{} +// ident() +// Of course there are also slightly more complicated rules for brackets: +// ident{}.ident()[5][4](), etc. +func (ti *tokenIterator) extractExpr() string { + orig := ti.pos + + // Contains the type of the previously scanned token (initialized with + // the token right under the cursor). This is the token to the *right* of + // the current one. + prev := ti.token().tok +loop: + for { + if !ti.prev() { + return joinTokens(ti.tokens[:orig]) + } + switch ti.token().tok { + case token.PERIOD: + // If the '.' is not followed by IDENT, it's invalid. + if prev != token.IDENT { + break loop + } + case token.IDENT: + // Valid tokens after IDENT are '.', '[', '{' and '('. + switch prev { + case token.PERIOD, token.LBRACK, token.LBRACE, token.LPAREN: + // all ok + default: + break loop + } + case token.RBRACE: + // This one can only be a part of type initialization, like: + // Dummy{}.Hello() + // It is valid Go if Hello method is defined on a non-pointer receiver. + if prev != token.PERIOD { + break loop + } + ti.skipToBalancedPair() + case token.RPAREN, token.RBRACK: + // After ']' and ')' their opening counterparts are valid '[', '(', + // as well as the dot. + switch prev { + case token.PERIOD, token.LBRACK, token.LPAREN: + // all ok + default: + break loop + } + ti.skipToBalancedPair() + default: + break loop + } + prev = ti.token().tok + } + return joinTokens(ti.tokens[ti.pos+1 : orig]) +} + +// Given a slice of token_item, reassembles them into the original literal +// expression. +func joinTokens(tokens []tokenItem) string { + var buf bytes.Buffer + for i, tok := range tokens { + if i > 0 { + buf.WriteByte(' ') + } + buf.WriteString(tok.String()) + } + return buf.String() +} + +type cursorContext int + +const ( + unknownContext cursorContext = iota + selectContext + compositeLiteralContext +) + +func deduceCursorContext(file []byte, cursor int) (cursorContext, string, string) { + iter, off := newTokenIterator(file, cursor) + if len(iter.tokens) == 0 { + return unknownContext, "", "" + } + + // See if we have a partial identifier to work with. + var partial string + switch tok := iter.token(); tok.tok { + case token.IDENT, token.TYPE, token.CONST, token.VAR, token.FUNC, token.PACKAGE: + // we're '.' + // parse as Partial and figure out decl + + partial = tok.String() + if tok.tok == token.IDENT { + // If it happens that the cursor is past the end of the literal, + // means there is a space between the literal and the cursor, think + // of it as no context, because that's what it really is. + if off > len(tok.String()) { + return unknownContext, "", "" + } + partial = partial[:off] + } + + if !iter.prev() { + return unknownContext, "", partial + } + } + + switch iter.token().tok { + case token.PERIOD: + return selectContext, iter.extractExpr(), partial + case token.COMMA, token.LBRACE: + // This can happen for struct fields: + // &Struct{Hello: 1, Wor#} // (# - the cursor) + // Let's try to find the struct type + return compositeLiteralContext, iter.extractLiteralType(), partial + } + + return unknownContext, "", partial +} diff --git a/langserver/internal/gocode/suggest/formatters.go b/langserver/internal/gocode/suggest/formatters.go new file mode 100644 index 00000000..ed0ebd9f --- /dev/null +++ b/langserver/internal/gocode/suggest/formatters.go @@ -0,0 +1,86 @@ +package suggest + +import ( + "encoding/json" + "fmt" + "io" +) + +type Formatter func(w io.Writer, candidates []Candidate, num int) + +var Formatters = map[string]Formatter{ + "csv": csvFormat, + "csv-with-package": csvFormat, + "emacs": emacsFormat, + "godit": goditFormat, + "json": jsonFormat, + "nice": NiceFormat, + "vim": vimFormat, +} + +func NiceFormat(w io.Writer, candidates []Candidate, num int) { + if candidates == nil { + fmt.Fprintf(w, "Nothing to complete.\n") + return + } + + fmt.Fprintf(w, "Found %d candidates:\n", len(candidates)) + for _, c := range candidates { + fmt.Fprintf(w, " %s\n", c.String()) + } +} + +func vimFormat(w io.Writer, candidates []Candidate, num int) { + if candidates == nil { + fmt.Fprint(w, "[0, []]") + return + } + + fmt.Fprintf(w, "[%d, [", num) + for i, c := range candidates { + if i != 0 { + fmt.Fprintf(w, ", ") + } + + word := c.Suggestion() + abbr := c.String() + fmt.Fprintf(w, "{'word': '%s', 'abbr': '%s', 'info': '%s'}", word, abbr, abbr) + } + fmt.Fprintf(w, "]]") +} + +func goditFormat(w io.Writer, candidates []Candidate, num int) { + fmt.Fprintf(w, "%d,,%d\n", num, len(candidates)) + for _, c := range candidates { + fmt.Fprintf(w, "%s,,%s\n", c.String(), c.Suggestion()) + } +} + +func emacsFormat(w io.Writer, candidates []Candidate, num int) { + for _, c := range candidates { + var hint string + switch { + case c.Class == "func": + hint = c.Type + case c.Type == "": + hint = c.Class + default: + hint = c.Class + " " + c.Type + } + fmt.Fprintf(w, "%s,,%s\n", c.Name, hint) + } +} + +func csvFormat(w io.Writer, candidates []Candidate, num int) { + for _, c := range candidates { + fmt.Fprintf(w, "%s,,%s,,%s,,%s\n", c.Class, c.Name, c.Type, c.PkgPath) + } +} + +func jsonFormat(w io.Writer, candidates []Candidate, num int) { + var x []interface{} + if candidates != nil { + x = []interface{}{num, candidates} + } + json.NewEncoder(w).Encode(x) +} diff --git a/langserver/internal/gocode/suggest/formatters_test.go b/langserver/internal/gocode/suggest/formatters_test.go new file mode 100644 index 00000000..1a697059 --- /dev/null +++ b/langserver/internal/gocode/suggest/formatters_test.go @@ -0,0 +1,104 @@ +package suggest_test + +import ( + "bytes" + "testing" + + "github.com/sourcegraph/go-langserver/langserver/internal/gocode/suggest" +) + +func TestFormatters(t *testing.T) { + // TODO(mdempsky): More comprehensive test. + + num := len("client") + candidates := []suggest.Candidate{{ + Class: "func", + PkgPath: "gocode", + Name: "client_auto_complete", + Type: "func(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int, Arg3 gocode_env) (c []candidate, d int)", + }, { + Class: "func", + PkgPath: "gocode", + Name: "client_close", + Type: "func(cli *rpc.Client, Arg0 int) int", + }, { + Class: "func", + PkgPath: "gocode", + Name: "client_cursor_type_pkg", + Type: "func(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int) (typ, pkg string)", + }, { + Class: "func", + PkgPath: "gocode", + Name: "client_drop_cache", + Type: "func(cli *rpc.Client, Arg0 int) int", + }, { + Class: "func", + PkgPath: "gocode", + Name: "client_highlight", + Type: "func(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 gocode_env) (c []highlight_range, d int)", + }, { + Class: "func", + PkgPath: "gocode", + Name: "client_set", + Type: "func(cli *rpc.Client, Arg0, Arg1 string) string", + }, { + Class: "func", + PkgPath: "gocode", + Name: "client_status", + Type: "func(cli *rpc.Client, Arg0 int) string", + }} + + var tests = [...]struct { + name string + want string + }{ + {"json", `[6,[{"class":"func","package":"gocode","name":"client_auto_complete","type":"func(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int, Arg3 gocode_env) (c []candidate, d int)"},{"class":"func","package":"gocode","name":"client_close","type":"func(cli *rpc.Client, Arg0 int) int"},{"class":"func","package":"gocode","name":"client_cursor_type_pkg","type":"func(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int) (typ, pkg string)"},{"class":"func","package":"gocode","name":"client_drop_cache","type":"func(cli *rpc.Client, Arg0 int) int"},{"class":"func","package":"gocode","name":"client_highlight","type":"func(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 gocode_env) (c []highlight_range, d int)"},{"class":"func","package":"gocode","name":"client_set","type":"func(cli *rpc.Client, Arg0, Arg1 string) string"},{"class":"func","package":"gocode","name":"client_status","type":"func(cli *rpc.Client, Arg0 int) string"}]] +`}, + {"nice", `Found 7 candidates: + func client_auto_complete(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int, Arg3 gocode_env) (c []candidate, d int) + func client_close(cli *rpc.Client, Arg0 int) int + func client_cursor_type_pkg(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int) (typ, pkg string) + func client_drop_cache(cli *rpc.Client, Arg0 int) int + func client_highlight(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 gocode_env) (c []highlight_range, d int) + func client_set(cli *rpc.Client, Arg0, Arg1 string) string + func client_status(cli *rpc.Client, Arg0 int) string +`}, + {"vim", `[6, [{'word': 'client_auto_complete(', 'abbr': 'func client_auto_complete(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int, Arg3 gocode_env) (c []candidate, d int)', 'info': 'func client_auto_complete(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int, Arg3 gocode_env) (c []candidate, d int)'}, {'word': 'client_close(', 'abbr': 'func client_close(cli *rpc.Client, Arg0 int) int', 'info': 'func client_close(cli *rpc.Client, Arg0 int) int'}, {'word': 'client_cursor_type_pkg(', 'abbr': 'func client_cursor_type_pkg(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int) (typ, pkg string)', 'info': 'func client_cursor_type_pkg(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int) (typ, pkg string)'}, {'word': 'client_drop_cache(', 'abbr': 'func client_drop_cache(cli *rpc.Client, Arg0 int) int', 'info': 'func client_drop_cache(cli *rpc.Client, Arg0 int) int'}, {'word': 'client_highlight(', 'abbr': 'func client_highlight(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 gocode_env) (c []highlight_range, d int)', 'info': 'func client_highlight(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 gocode_env) (c []highlight_range, d int)'}, {'word': 'client_set(', 'abbr': 'func client_set(cli *rpc.Client, Arg0, Arg1 string) string', 'info': 'func client_set(cli *rpc.Client, Arg0, Arg1 string) string'}, {'word': 'client_status(', 'abbr': 'func client_status(cli *rpc.Client, Arg0 int) string', 'info': 'func client_status(cli *rpc.Client, Arg0 int) string'}]]`}, + {"godit", `6,,7 +func client_auto_complete(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int, Arg3 gocode_env) (c []candidate, d int),,client_auto_complete( +func client_close(cli *rpc.Client, Arg0 int) int,,client_close( +func client_cursor_type_pkg(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int) (typ, pkg string),,client_cursor_type_pkg( +func client_drop_cache(cli *rpc.Client, Arg0 int) int,,client_drop_cache( +func client_highlight(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 gocode_env) (c []highlight_range, d int),,client_highlight( +func client_set(cli *rpc.Client, Arg0, Arg1 string) string,,client_set( +func client_status(cli *rpc.Client, Arg0 int) string,,client_status( +`}, + {"emacs", ` +client_auto_complete,,func(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int, Arg3 gocode_env) (c []candidate, d int) +client_close,,func(cli *rpc.Client, Arg0 int) int +client_cursor_type_pkg,,func(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int) (typ, pkg string) +client_drop_cache,,func(cli *rpc.Client, Arg0 int) int +client_highlight,,func(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 gocode_env) (c []highlight_range, d int) +client_set,,func(cli *rpc.Client, Arg0, Arg1 string) string +client_status,,func(cli *rpc.Client, Arg0 int) string +`[1:]}, + {"csv", ` +func,,client_auto_complete,,func(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int, Arg3 gocode_env) (c []candidate, d int),,gocode +func,,client_close,,func(cli *rpc.Client, Arg0 int) int,,gocode +func,,client_cursor_type_pkg,,func(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 int) (typ, pkg string),,gocode +func,,client_drop_cache,,func(cli *rpc.Client, Arg0 int) int,,gocode +func,,client_highlight,,func(cli *rpc.Client, Arg0 []byte, Arg1 string, Arg2 gocode_env) (c []highlight_range, d int),,gocode +func,,client_set,,func(cli *rpc.Client, Arg0, Arg1 string) string,,gocode +func,,client_status,,func(cli *rpc.Client, Arg0 int) string,,gocode +`[1:]}, + } + + for _, test := range tests { + var out bytes.Buffer + suggest.Formatters[test.name](&out, candidates, num) + + if got := out.String(); got != test.want { + t.Errorf("Format %s:\nGot:\n%q\nWant:\n%q\n", test.name, got, test.want) + } + } +} diff --git a/langserver/internal/gocode/suggest/suggest.go b/langserver/internal/gocode/suggest/suggest.go new file mode 100644 index 00000000..a35cb586 --- /dev/null +++ b/langserver/internal/gocode/suggest/suggest.go @@ -0,0 +1,224 @@ +package suggest + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/scanner" + "go/token" + "go/types" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/sourcegraph/go-langserver/langserver/internal/gocode/lookdot" +) + +type Config struct { + Importer types.Importer + Logf func(fmt string, args ...interface{}) + Builtin bool +} + +type packageAnalysis struct { + fset *token.FileSet + pos token.Pos + pkg *types.Package +} + +// Suggest returns a list of suggestion candidates and the length of +// the text that should be replaced, if any. +func (c *Config) Suggest(filename string, data []byte, cursor int) ([]Candidate, int, error) { + if cursor < 0 { + return nil, 0, nil + } + + a, err := c.analyzePackage(filename, data, cursor) + if err != nil { + return nil, 0, err + } + fset := a.fset + pos := a.pos + pkg := a.pkg + if pkg == nil { + return nil, 0, nil + } + scope := pkg.Scope().Innermost(pos) + + ctx, expr, partial := deduceCursorContext(data, cursor) + b := candidateCollector{ + localpkg: pkg, + partial: partial, + filter: objectFilters[partial], + builtin: ctx != selectContext && c.Builtin, + } + + switch ctx { + case selectContext: + tv, _ := types.Eval(fset, pkg, pos, expr) + if lookdot.Walk(&tv, b.appendObject) { + break + } + + _, obj := scope.LookupParent(expr, pos) + if pkgName, isPkg := obj.(*types.PkgName); isPkg { + c.packageCandidates(pkgName.Imported(), &b) + break + } + + return nil, 0, nil + + case compositeLiteralContext: + tv, _ := types.Eval(fset, pkg, pos, expr) + if tv.IsType() { + if _, isStruct := tv.Type.Underlying().(*types.Struct); isStruct { + c.fieldNameCandidates(tv.Type, &b) + break + } + } + + fallthrough + default: + c.scopeCandidates(scope, pos, &b) + } + + res := b.getCandidates() + if len(res) == 0 { + return nil, 0, nil + } + return res, len(partial), nil +} + +func (c *Config) analyzePackage(filename string, data []byte, cursor int) (*packageAnalysis, error) { + // If we're in trailing white space at the end of a scope, + // sometimes go/types doesn't recognize that variables should + // still be in scope there. + filesemi := bytes.Join([][]byte{data[:cursor], []byte(";"), data[cursor:]}, nil) + + fset := token.NewFileSet() + fileAST, err := parser.ParseFile(fset, filename, filesemi, parser.AllErrors) + if err != nil { + c.logParseError("Error parsing input file (outer block)", err) + } + astPos := fileAST.Pos() + if astPos == 0 { + return &packageAnalysis{fset: nil, pos: token.NoPos, pkg: nil}, nil + } + pos := fset.File(astPos).Pos(cursor) + + files := []*ast.File{fileAST} + otherPkgFiles, err := c.findOtherPackageFiles(filename, fileAST.Name.Name) + if err != nil { + return nil, err + } + for _, otherName := range otherPkgFiles { + ast, err := parser.ParseFile(fset, otherName, nil, 0) + if err != nil { + c.logParseError("Error parsing other file", err) + } + files = append(files, ast) + } + + // Clear any function bodies other than where the cursor + // is. They're not relevant to suggestions and only slow down + // typechecking. + for _, file := range files { + for _, decl := range file.Decls { + if fd, ok := decl.(*ast.FuncDecl); ok && (pos < fd.Pos() || pos >= fd.End()) { + fd.Body = nil + } + } + } + + cfg := types.Config{ + Importer: c.Importer, + Error: func(err error) {}, + } + pkg, _ := cfg.Check("", fset, files, nil) + + return &packageAnalysis{fset: fset, pos: pos, pkg: pkg}, nil +} + +func (c *Config) fieldNameCandidates(typ types.Type, b *candidateCollector) { + s := typ.Underlying().(*types.Struct) + for i, n := 0, s.NumFields(); i < n; i++ { + b.appendObject(s.Field(i)) + } +} + +func (c *Config) packageCandidates(pkg *types.Package, b *candidateCollector) { + c.scopeCandidates(pkg.Scope(), token.NoPos, b) +} + +func (c *Config) scopeCandidates(scope *types.Scope, pos token.Pos, b *candidateCollector) { + seen := make(map[string]bool) + for scope != nil { + for _, name := range scope.Names() { + if seen[name] { + continue + } + seen[name] = true + _, obj := scope.LookupParent(name, pos) + if obj != nil { + b.appendObject(obj) + } + } + scope = scope.Parent() + } +} + +func (c *Config) logParseError(intro string, err error) { + if c.Logf == nil { + return + } + if el, ok := err.(scanner.ErrorList); ok { + c.Logf("%s:", intro) + for _, er := range el { + c.Logf(" %s", er) + } + } else { + c.Logf("%s: %s", intro, err) + } +} + +func (c *Config) findOtherPackageFiles(filename, pkgName string) ([]string, error) { + if filename == "" { + return nil, nil + } + + dir, file := filepath.Split(filename) + dents, err := ioutil.ReadDir(dir) + if err != nil { + return nil, fmt.Errorf("could not read dir: %v", err) + } + isTestFile := strings.HasSuffix(file, "_test.go") + + // TODO(mdempsky): Use go/build.(*Context).MatchFile or + // something to properly handle build tags? + var out []string + for _, dent := range dents { + name := dent.Name() + if strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") { + continue + } + if name == file || !strings.HasSuffix(name, ".go") { + continue + } + if !isTestFile && strings.HasSuffix(name, "_test.go") { + continue + } + + abspath := filepath.Join(dir, name) + if pkgNameFor(abspath) == pkgName { + out = append(out, abspath) + } + } + + return out, nil +} + +func pkgNameFor(filename string) string { + file, _ := parser.ParseFile(token.NewFileSet(), filename, nil, parser.PackageClauseOnly) + return file.Name.Name +} diff --git a/langserver/internal/gocode/suggest/suggest_test.go b/langserver/internal/gocode/suggest/suggest_test.go new file mode 100644 index 00000000..34232c15 --- /dev/null +++ b/langserver/internal/gocode/suggest/suggest_test.go @@ -0,0 +1,81 @@ +package suggest_test + +import ( + "bytes" + "encoding/json" + "go/importer" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/sourcegraph/go-langserver/langserver/internal/gocode/suggest" +) + +func TestRegress(t *testing.T) { + testDirs, err := filepath.Glob("testdata/test.*") + if err != nil { + t.Fatal(err) + } + + for _, testDir := range testDirs { + testDir := testDir // capture + name := strings.TrimPrefix(testDir, "testdata/") + t.Run(name, func(t *testing.T) { + t.Parallel() + testRegress(t, testDir) + }) + } +} + +func testRegress(t *testing.T, testDir string) { + testDir, err := filepath.Abs(testDir) + if err != nil { + t.Errorf("Abs failed: %v", err) + return + } + + filename := filepath.Join(testDir, "test.go.in") + data, err := ioutil.ReadFile(filename) + if err != nil { + t.Errorf("ReadFile failed: %v", err) + return + } + + cursor := bytes.IndexByte(data, '@') + if cursor < 0 { + t.Errorf("Missing @") + return + } + data = append(data[:cursor], data[cursor+1:]...) + + cfg := suggest.Config{ + Importer: importer.Default(), + } + if testing.Verbose() { + cfg.Logf = t.Logf + } + if cfgJSON, err := os.Open(filepath.Join(testDir, "config.json")); err == nil { + if err := json.NewDecoder(cfgJSON).Decode(&cfg); err != nil { + t.Errorf("Decode failed: %v", err) + return + } + } else if !os.IsNotExist(err) { + t.Errorf("Open failed: %v", err) + return + } + candidates, prefixLen, err := cfg.Suggest(filename, data, cursor) + if err != nil { + t.Fatalf("could not get suggestions: %v", err) + } + + var out bytes.Buffer + suggest.NiceFormat(&out, candidates, prefixLen) + + want, _ := ioutil.ReadFile(filepath.Join(testDir, "out.expected")) + if got := out.Bytes(); !bytes.Equal(got, want) { + t.Errorf("%s:\nGot:\n%s\nWant:\n%s\n", testDir, got, want) + return + } +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0001/out.expected b/langserver/internal/gocode/suggest/testdata/test.0001/out.expected new file mode 100644 index 00000000..662c4e5b --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0001/out.expected @@ -0,0 +1,26 @@ +Found 25 candidates: + func Errorf(format string, a ...interface{}) error + func Fprint(w io.Writer, a ...interface{}) (n int, err error) + func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) + func Fprintln(w io.Writer, a ...interface{}) (n int, err error) + func Fscan(r io.Reader, a ...interface{}) (n int, err error) + func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) + func Fscanln(r io.Reader, a ...interface{}) (n int, err error) + func Print(a ...interface{}) (n int, err error) + func Printf(format string, a ...interface{}) (n int, err error) + func Println(a ...interface{}) (n int, err error) + func Scan(a ...interface{}) (n int, err error) + func Scanf(format string, a ...interface{}) (n int, err error) + func Scanln(a ...interface{}) (n int, err error) + func Sprint(a ...interface{}) string + func Sprintf(format string, a ...interface{}) string + func Sprintln(a ...interface{}) string + func Sscan(str string, a ...interface{}) (n int, err error) + func Sscanf(str string, format string, a ...interface{}) (n int, err error) + func Sscanln(str string, a ...interface{}) (n int, err error) + type Formatter interface + type GoStringer interface + type ScanState interface + type Scanner interface + type State interface + type Stringer interface diff --git a/langserver/internal/gocode/suggest/testdata/test.0001/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0001/test.go.in new file mode 100644 index 00000000..cc8ad00f --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0001/test.go.in @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0002/out.expected b/langserver/internal/gocode/suggest/testdata/test.0002/out.expected new file mode 100644 index 00000000..7b689e31 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0002/out.expected @@ -0,0 +1,6 @@ +Found 5 candidates: + func main() + package os + var key string + var test map[string]invalid type + var value invalid type diff --git a/langserver/internal/gocode/suggest/testdata/test.0002/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0002/test.go.in new file mode 100644 index 00000000..81fba290 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0002/test.go.in @@ -0,0 +1,11 @@ +package main + +import "os" + +var test map[string]os.Error + +func main() { + for key, value := range test { + @ + } +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0003/out.expected b/langserver/internal/gocode/suggest/testdata/test.0003/out.expected new file mode 100644 index 00000000..883d48ed --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0003/out.expected @@ -0,0 +1,8 @@ +Found 7 candidates: + func End() token.Pos + func IsExported() bool + func Pos() token.Pos + func String() string + var Name string + var NamePos token.Pos + var Obj *ast.Object diff --git a/langserver/internal/gocode/suggest/testdata/test.0003/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0003/test.go.in new file mode 100644 index 00000000..79d9455f --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0003/test.go.in @@ -0,0 +1,58 @@ +package main + +import ( + "go/ast" + "io" +) + +func PrettyPrintTypeExpr(out io.Writer, e ast.Expr) { + switch t := e.(type) { + case *ast.StarExpr: + fmt.Fprintf(out, "*") + PrettyPrintTypeExpr(out, t.X) + case *ast.Ident: + // ast.Ident type decl as a reminder (note embedded type): + // + // type Ident struct { + // token.Position // identifier position + // Obj *Object // denoted object + // } + // + // Correct type inference in complex type switch statements + + // support for type embedding + fmt.Fprintf(out, t.Name()) + t.@ + case *ast.ArrayType: + fmt.Fprintf(out, "[]") + PrettyPrintTypeExpr(out, t.Elt) + case *ast.SelectorExpr: + PrettyPrintTypeExpr(out, t.X) + fmt.Fprintf(out, ".%s", t.Sel.Name()) + case *ast.FuncType: + // SKIP THIS FOR DEMO + case *ast.MapType: + fmt.Fprintf(out, "map[") + PrettyPrintTypeExpr(out, t.Key) + fmt.Fprintf(out, "]") + PrettyPrintTypeExpr(out, t.Value) + case *ast.InterfaceType: + fmt.Fprintf(out, "interface{}") + case *ast.Ellipsis: + fmt.Fprintf(out, "...") + PrettyPrintTypeExpr(out, t.Elt) + case *ast.StructType: + fmt.Fprintf(out, "struct") + case *ast.ChanType: + switch t.Dir { + case ast.RECV: + fmt.Fprintf(out, "<-chan ") + case ast.SEND: + fmt.Fprintf(out, "chan<- ") + case ast.SEND | ast.RECV: + fmt.Fprintf(out, "chan ") + } + PrettyPrintTypeExpr(out, t.Value) + default: + panic("OMGWTFBBQ!") + } +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0004/out.expected b/langserver/internal/gocode/suggest/testdata/test.0004/out.expected new file mode 100644 index 00000000..be930d4b --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0004/out.expected @@ -0,0 +1,7 @@ +Found 6 candidates: + func PrettyPrintTypeExpr(out io.Writer, e ast.Expr) + package ast + package io + var e ast.Expr + var out io.Writer + var t ast.Expr diff --git a/langserver/internal/gocode/suggest/testdata/test.0004/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0004/test.go.in new file mode 100644 index 00000000..008d2174 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0004/test.go.in @@ -0,0 +1,58 @@ +package main + +import ( + "go/ast" + "io" +) + +func PrettyPrintTypeExpr(out io.Writer, e ast.Expr) { + switch t := e.(type) { + case *ast.StarExpr: + fmt.Fprintf(out, "*") + PrettyPrintTypeExpr(out, t.X) + case *ast.Ident: + // ast.Ident type decl as a reminder (note embedded type): + // + // type Ident struct { + // token.Position // identifier position + // Obj *Object // denoted object + // } + // + // Correct type inference in complex type switch statements + + // support for type embedding + fmt.Fprintf(out, t.Name()) + case *ast.ArrayType: + fmt.Fprintf(out, "[]") + PrettyPrintTypeExpr(out, t.Elt) + case *ast.SelectorExpr: + PrettyPrintTypeExpr(out, t.X) + fmt.Fprintf(out, ".%s", t.Sel.Name()) + case *ast.FuncType: + // SKIP THIS FOR DEMO + case *ast.MapType: + fmt.Fprintf(out, "map[") + PrettyPrintTypeExpr(out, t.Key) + fmt.Fprintf(out, "]") + PrettyPrintTypeExpr(out, t.Value) + case *ast.InterfaceType: + fmt.Fprintf(out, "interface{}") + case *ast.Ellipsis: + fmt.Fprintf(out, "...") + PrettyPrintTypeExpr(out, t.Elt) + case *ast.StructType: + fmt.Fprintf(out, "struct") + case *ast.ChanType: + switch t.Dir { + case ast.RECV: + fmt.Fprintf(out, "<-chan ") + case ast.SEND: + fmt.Fprintf(out, "chan<- ") + case ast.SEND | ast.RECV: + fmt.Fprintf(out, "chan ") + } + PrettyPrintTypeExpr(out, t.Value) + default: + @ + panic("OMGWTFBBQ!") + } +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0005/b.go b/langserver/internal/gocode/suggest/testdata/test.0005/b.go new file mode 100644 index 00000000..ffbd7674 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0005/b.go @@ -0,0 +1,24 @@ +package main + +// this is a file 'a.go' + +import ( + superos "os" +) + +func B() superos.Error { + return nil +} + +// notice how changing type of a return function in one file, +// the inferred type of a variable in another file changes also + +func (t *Tester) SetC() { + t.c = 31337 +} + +func (t *Tester) SetD() { + t.d = 31337 +} + +// support for multifile packages, including correct namespace handling diff --git a/langserver/internal/gocode/suggest/testdata/test.0005/out.expected b/langserver/internal/gocode/suggest/testdata/test.0005/out.expected new file mode 100644 index 00000000..580f9b2a --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0005/out.expected @@ -0,0 +1,6 @@ +Found 5 candidates: + func A() invalid type + func B() invalid type + package localos + type Tester struct + var test invalid type diff --git a/langserver/internal/gocode/suggest/testdata/test.0005/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0005/test.go.in new file mode 100644 index 00000000..dc1ea225 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0005/test.go.in @@ -0,0 +1,30 @@ +package main + +// this is a file 'a.go' + +import ( + localos "os" +) + +func A() localos.Error { + return nil +} + +// B() is defined in file 'b.go' +var test = B() + +type Tester struct { + a, b, c, d int +} + +func (t *Tester) SetA() { + t.a = 31337 +} + +func (t *Tester) SetB() { + t.b = 31337 +} + +// methods SetC and SetD are defined in 'b.go' + +@ \ No newline at end of file diff --git a/langserver/internal/gocode/suggest/testdata/test.0006/b.go b/langserver/internal/gocode/suggest/testdata/test.0006/b.go new file mode 100644 index 00000000..ffbd7674 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0006/b.go @@ -0,0 +1,24 @@ +package main + +// this is a file 'a.go' + +import ( + superos "os" +) + +func B() superos.Error { + return nil +} + +// notice how changing type of a return function in one file, +// the inferred type of a variable in another file changes also + +func (t *Tester) SetC() { + t.c = 31337 +} + +func (t *Tester) SetD() { + t.d = 31337 +} + +// support for multifile packages, including correct namespace handling diff --git a/langserver/internal/gocode/suggest/testdata/test.0006/out.expected b/langserver/internal/gocode/suggest/testdata/test.0006/out.expected new file mode 100644 index 00000000..25a78e98 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0006/out.expected @@ -0,0 +1 @@ +Nothing to complete. diff --git a/langserver/internal/gocode/suggest/testdata/test.0006/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0006/test.go.in new file mode 100644 index 00000000..239d4101 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0006/test.go.in @@ -0,0 +1,30 @@ +package main + +// this is a file 'a.go' + +import ( + localos "os" +) + +func A() localos.Error { + return nil +} + +// B() is defined in file 'b.go' +var test = B() + +type Tester struct { + a, b, c, d int +} + +func (t *Tester) SetA() { + t.a = 31337 +} + +func (t *Tester) SetB() { + t.b = 31337 +} + +Tester.@ + +// methods SetC and SetD are defined in 'b.go' diff --git a/langserver/internal/gocode/suggest/testdata/test.0007/out.expected b/langserver/internal/gocode/suggest/testdata/test.0007/out.expected new file mode 100644 index 00000000..c8c4e360 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0007/out.expected @@ -0,0 +1,6 @@ +Found 5 candidates: + var ForkLock sync.RWMutex + var SocketDisableIPv6 bool + var Stderr int + var Stdin int + var Stdout int diff --git a/langserver/internal/gocode/suggest/testdata/test.0007/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0007/test.go.in new file mode 100644 index 00000000..eba34ac8 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0007/test.go.in @@ -0,0 +1,7 @@ +package main + +import "syscall" + +func main() { + syscall.var@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0008/out.expected b/langserver/internal/gocode/suggest/testdata/test.0008/out.expected new file mode 100644 index 00000000..0f46ccc9 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0008/out.expected @@ -0,0 +1,7 @@ +Found 6 candidates: + func Lock() + func Unlock() + var Mutex sync.Mutex + var data map[string][]string + var path string + var time int64 diff --git a/langserver/internal/gocode/suggest/testdata/test.0008/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0008/test.go.in new file mode 100644 index 00000000..97a7db91 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0008/test.go.in @@ -0,0 +1,15 @@ +package main + +import "sync" + +var hosts struct { + sync.Mutex + data map[string][]string + time int64 + path string +} + +hosts.@ + +func main() { +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0009/out.expected b/langserver/internal/gocode/suggest/testdata/test.0009/out.expected new file mode 100644 index 00000000..e7f998c1 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0009/out.expected @@ -0,0 +1,3 @@ +Found 2 candidates: + func Lock() + func Unlock() diff --git a/langserver/internal/gocode/suggest/testdata/test.0009/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0009/test.go.in new file mode 100644 index 00000000..421e9cc9 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0009/test.go.in @@ -0,0 +1,15 @@ +package main + +import "sync" + +var hosts struct { + sync.Mutex + data map[string][]string + time int64 + path string +} + +hosts.Mutex.@ + +func main() { +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0010/out.expected b/langserver/internal/gocode/suggest/testdata/test.0010/out.expected new file mode 100644 index 00000000..7a59a11e --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0010/out.expected @@ -0,0 +1,8 @@ +Found 7 candidates: + func End() token.Pos + func Pos() token.Pos + var Comment *ast.CommentGroup + var Doc *ast.CommentGroup + var Names []*ast.Ident + var Type ast.Expr + var Values []ast.Expr diff --git a/langserver/internal/gocode/suggest/testdata/test.0010/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0010/test.go.in new file mode 100644 index 00000000..308baaf1 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0010/test.go.in @@ -0,0 +1,9 @@ +package main + +import "go/ast" + +func main() { + var gd *ast.GenDecl + v := gd.Specs[0].(*ast.ValueSpec) + v.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0011/out.expected b/langserver/internal/gocode/suggest/testdata/test.0011/out.expected new file mode 100644 index 00000000..7de665e1 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0011/out.expected @@ -0,0 +1,60 @@ +Found 59 candidates: + func Addr() reflect.Value + func Bool() bool + func Bytes() []byte + func Call(in []reflect.Value) []reflect.Value + func CallSlice(in []reflect.Value) []reflect.Value + func CanAddr() bool + func CanInterface() bool + func CanSet() bool + func Cap() int + func Close() + func Complex() complex128 + func Convert(t reflect.Type) reflect.Value + func Elem() reflect.Value + func Field(i int) reflect.Value + func FieldByIndex(index []int) reflect.Value + func FieldByName(name string) reflect.Value + func FieldByNameFunc(match func(string) bool) reflect.Value + func Float() float64 + func Index(i int) reflect.Value + func Int() int64 + func Interface() (i interface{}) + func InterfaceData() [2]uintptr + func IsNil() bool + func IsValid() bool + func Kind() reflect.Kind + func Len() int + func MapIndex(key reflect.Value) reflect.Value + func MapKeys() []reflect.Value + func Method(i int) reflect.Value + func MethodByName(name string) reflect.Value + func NumField() int + func NumMethod() int + func OverflowComplex(x complex128) bool + func OverflowFloat(x float64) bool + func OverflowInt(x int64) bool + func OverflowUint(x uint64) bool + func Pointer() uintptr + func Recv() (x reflect.Value, ok bool) + func Send(x reflect.Value) + func Set(x reflect.Value) + func SetBool(x bool) + func SetBytes(x []byte) + func SetCap(n int) + func SetComplex(x complex128) + func SetFloat(x float64) + func SetInt(x int64) + func SetLen(n int) + func SetMapIndex(key reflect.Value, val reflect.Value) + func SetPointer(x unsafe.Pointer) + func SetString(x string) + func SetUint(x uint64) + func Slice(i int, j int) reflect.Value + func Slice3(i int, j int, k int) reflect.Value + func String() string + func TryRecv() (x reflect.Value, ok bool) + func TrySend(x reflect.Value) bool + func Type() reflect.Type + func Uint() uint64 + func UnsafeAddr() uintptr diff --git a/langserver/internal/gocode/suggest/testdata/test.0011/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0011/test.go.in new file mode 100644 index 00000000..cb88eef7 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0011/test.go.in @@ -0,0 +1,8 @@ +package main + +import "reflect" + +func main() { + var test reflect.Value + test.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0012/out.expected b/langserver/internal/gocode/suggest/testdata/test.0012/out.expected new file mode 100644 index 00000000..ac8c201b --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0012/out.expected @@ -0,0 +1,7 @@ +Found 6 candidates: + func main() + type MyMap map[string]int + var key string + var m MyMap + var value int + var z int diff --git a/langserver/internal/gocode/suggest/testdata/test.0012/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0012/test.go.in new file mode 100644 index 00000000..3aac0d3f --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0012/test.go.in @@ -0,0 +1,11 @@ +package main + +type MyMap map[string]int + +func main() { + var m MyMap + for key, value := range m { + z := m["15"] + @ + } +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0013/out.expected b/langserver/internal/gocode/suggest/testdata/test.0013/out.expected new file mode 100644 index 00000000..3f58a85f --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0013/out.expected @@ -0,0 +1,25 @@ +Found 24 candidates: + func main() + var a int + var add int + var and int + var andnot int + var b int + var c bool + var d bool + var div int + var eq bool + var geq bool + var greater bool + var leq bool + var less bool + var logand bool + var logor bool + var mul int + var neq bool + var or int + var rem int + var shl int + var shr int + var sub int + var xor int diff --git a/langserver/internal/gocode/suggest/testdata/test.0013/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0013/test.go.in new file mode 100644 index 00000000..b1989cf4 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0013/test.go.in @@ -0,0 +1,28 @@ +package main + +func main() { + var a, b int + add := a + b + sub := a - b + mul := a * b + div := a / b + or := a | b + xor := a ^ b + rem := a % b + shr := a >> uint(b) + shl := a << uint(b) + and := a & b + andnot := a &^ b + + eq := a == b + neq := a != b + less := a < b + leq := a <= b + greater := a > b + geq := a >= b + + var c, d bool + logor := c || d + logand := c && d + @ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0014/out.expected b/langserver/internal/gocode/suggest/testdata/test.0014/out.expected new file mode 100644 index 00000000..3b255410 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0014/out.expected @@ -0,0 +1,12 @@ +Found 11 candidates: + func main() + type MyPtrInt *int + var a *int + var aa int + var b int + var bb *MyPtrInt + var c *int + var d **int + var megaptr **int + var superint int + var typeptr MyPtrInt diff --git a/langserver/internal/gocode/suggest/testdata/test.0014/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0014/test.go.in new file mode 100644 index 00000000..2412db12 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0014/test.go.in @@ -0,0 +1,18 @@ +package main + +type MyPtrInt *int + +func main() { + var megaptr **int + a := *megaptr + b := *a + + var superint int + c := &superint + d := &c + + var typeptr MyPtrInt + aa := *typeptr + bb := &typeptr + @ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0015/out.expected b/langserver/internal/gocode/suggest/testdata/test.0015/out.expected new file mode 100644 index 00000000..724d5b49 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0015/out.expected @@ -0,0 +1,10 @@ +Found 9 candidates: + func main() + var a int + var arro bool + var b bool + var c chan bool + var uadd int + var unot bool + var usub int + var uxor invalid type diff --git a/langserver/internal/gocode/suggest/testdata/test.0015/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0015/test.go.in new file mode 100644 index 00000000..59c521ad --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0015/test.go.in @@ -0,0 +1,13 @@ +package main + +func main() { + var a int + var b bool + var c chan bool + uadd := +a + usub := -a + unot := !b + uxor := ^b + arro := <-c + @ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0016/out.expected b/langserver/internal/gocode/suggest/testdata/test.0016/out.expected new file mode 100644 index 00000000..30cf8975 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0016/out.expected @@ -0,0 +1,5 @@ +Found 4 candidates: + func main() + var a int + var b string + var d bool diff --git a/langserver/internal/gocode/suggest/testdata/test.0016/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0016/test.go.in new file mode 100644 index 00000000..b7f623d3 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0016/test.go.in @@ -0,0 +1,14 @@ +package main + +func main() { + var a int + for { + var b string + if a == len(b) { + var c bool + } else { + var d bool + @ + } + } +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0017/out.expected b/langserver/internal/gocode/suggest/testdata/test.0017/out.expected new file mode 100644 index 00000000..ea734bec --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0017/out.expected @@ -0,0 +1,4 @@ +Found 3 candidates: + func a(a int, b int, c int) int + func b(a string, b string, c string) string + func main() diff --git a/langserver/internal/gocode/suggest/testdata/test.0017/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0017/test.go.in new file mode 100644 index 00000000..f88a8bd8 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0017/test.go.in @@ -0,0 +1,14 @@ +package main + +func a(a, b, c int) int { + return 123 +} + +func main() { + @ +} + +func b(a, b, c string) string { + return "abc" +} + diff --git a/langserver/internal/gocode/suggest/testdata/test.0018/out.expected b/langserver/internal/gocode/suggest/testdata/test.0018/out.expected new file mode 100644 index 00000000..061930b6 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0018/out.expected @@ -0,0 +1,3 @@ +Found 2 candidates: + func Bark() + var Legs int diff --git a/langserver/internal/gocode/suggest/testdata/test.0018/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0018/test.go.in new file mode 100644 index 00000000..2a0cc8cd --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0018/test.go.in @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" +) + +// just a dummy example +type Dog struct { + Legs int +} + +func (d *Dog) Bark() { + fmt.Printf("Bark!\n") +} + +// another one +type Test struct { + // map of slices of pointer to a *Dog + MoreTests map[string][]**Dog +} + +func (t *Test) GetMe() *Test { + return t +} + +func main() { + t := new(Test) + (*t.GetMe().MoreTests["blabla"][10]).@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0019/out.expected b/langserver/internal/gocode/suggest/testdata/test.0019/out.expected new file mode 100644 index 00000000..0698f864 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0019/out.expected @@ -0,0 +1,16 @@ +Found 15 candidates: + const BestCompression untyped int + const BestSpeed untyped int + const DefaultCompression untyped int + const HuffmanOnly untyped int + const NoCompression untyped int + func NewReader(r io.Reader) (io.ReadCloser, error) + func NewReaderDict(r io.Reader, dict []byte) (io.ReadCloser, error) + func NewWriter(w io.Writer) *zlib.Writer + func NewWriterLevel(w io.Writer, level int) (*zlib.Writer, error) + func NewWriterLevelDict(w io.Writer, level int, dict []byte) (*zlib.Writer, error) + type Resetter interface + type Writer struct + var ErrChecksum error + var ErrDictionary error + var ErrHeader error diff --git a/langserver/internal/gocode/suggest/testdata/test.0019/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0019/test.go.in new file mode 100644 index 00000000..2673f4b1 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0019/test.go.in @@ -0,0 +1,7 @@ +package main + +import "compress/zlib" + +func main() { + var b map[int]zlib.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0020/out.expected b/langserver/internal/gocode/suggest/testdata/test.0020/out.expected new file mode 100644 index 00000000..d5d01e90 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0020/out.expected @@ -0,0 +1,5 @@ +Found 4 candidates: + func Lock() + func Unlock() + var Dummy Dummy + var Mutex sync.Mutex diff --git a/langserver/internal/gocode/suggest/testdata/test.0020/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0020/test.go.in new file mode 100644 index 00000000..236b54b5 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0020/test.go.in @@ -0,0 +1,22 @@ +package main + +import "sync" + +type Dummy struct { + sync.Mutex +} + +type Bar struct { + Dummy +} + +func (b *Bar) Lock() { +} + +func (b *Bar) Unlock() { +} + +func main() { + var b Bar + b.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0021/out.expected b/langserver/internal/gocode/suggest/testdata/test.0021/out.expected new file mode 100644 index 00000000..883d48ed --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0021/out.expected @@ -0,0 +1,8 @@ +Found 7 candidates: + func End() token.Pos + func IsExported() bool + func Pos() token.Pos + func String() string + var Name string + var NamePos token.Pos + var Obj *ast.Object diff --git a/langserver/internal/gocode/suggest/testdata/test.0021/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0021/test.go.in new file mode 100644 index 00000000..18eecbdb --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0021/test.go.in @@ -0,0 +1,8 @@ +package main + +import "go/ast" + +func main() { + var test *ast.ImportSpec + test.Name.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0022/out.expected b/langserver/internal/gocode/suggest/testdata/test.0022/out.expected new file mode 100644 index 00000000..6fa05015 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0022/out.expected @@ -0,0 +1,6 @@ +Found 5 candidates: + func getMap() map[string]invalid type + func main() + package os + var key string + var value invalid type diff --git a/langserver/internal/gocode/suggest/testdata/test.0022/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0022/test.go.in new file mode 100644 index 00000000..eb1e710a --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0022/test.go.in @@ -0,0 +1,14 @@ +package main + +import "os" + +func main() { + for key, value := range getMap() { + @ + } +} + +func getMap() map[string]os.Error { + // simply to trick type inference + return nil +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0023/out.expected b/langserver/internal/gocode/suggest/testdata/test.0023/out.expected new file mode 100644 index 00000000..662c4e5b --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0023/out.expected @@ -0,0 +1,26 @@ +Found 25 candidates: + func Errorf(format string, a ...interface{}) error + func Fprint(w io.Writer, a ...interface{}) (n int, err error) + func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) + func Fprintln(w io.Writer, a ...interface{}) (n int, err error) + func Fscan(r io.Reader, a ...interface{}) (n int, err error) + func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) + func Fscanln(r io.Reader, a ...interface{}) (n int, err error) + func Print(a ...interface{}) (n int, err error) + func Printf(format string, a ...interface{}) (n int, err error) + func Println(a ...interface{}) (n int, err error) + func Scan(a ...interface{}) (n int, err error) + func Scanf(format string, a ...interface{}) (n int, err error) + func Scanln(a ...interface{}) (n int, err error) + func Sprint(a ...interface{}) string + func Sprintf(format string, a ...interface{}) string + func Sprintln(a ...interface{}) string + func Sscan(str string, a ...interface{}) (n int, err error) + func Sscanf(str string, format string, a ...interface{}) (n int, err error) + func Sscanln(str string, a ...interface{}) (n int, err error) + type Formatter interface + type GoStringer interface + type ScanState interface + type Scanner interface + type State interface + type Stringer interface diff --git a/langserver/internal/gocode/suggest/testdata/test.0023/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0023/test.go.in new file mode 100644 index 00000000..039be836 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0023/test.go.in @@ -0,0 +1,9 @@ +package main + +// Simple check for unicode awareness + +import ъ "fmt" + +func main() { + ъ.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0024/out.expected b/langserver/internal/gocode/suggest/testdata/test.0024/out.expected new file mode 100644 index 00000000..3757fb51 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0024/out.expected @@ -0,0 +1,4 @@ +Found 3 candidates: + var a int + var b int + var c int diff --git a/langserver/internal/gocode/suggest/testdata/test.0024/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0024/test.go.in new file mode 100644 index 00000000..2777c0db --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0024/test.go.in @@ -0,0 +1,10 @@ +package main + +type X struct { + a,b,c int +} + +func main() { + var X *X + X.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0025/out.expected b/langserver/internal/gocode/suggest/testdata/test.0025/out.expected new file mode 100644 index 00000000..8d3e54fa --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0025/out.expected @@ -0,0 +1,5 @@ +Found 4 candidates: + func Alignof(x Type) uintptr + func Offsetof(x Type) uintptr + func Sizeof(x Type) uintptr + type Pointer unsafe.Pointer diff --git a/langserver/internal/gocode/suggest/testdata/test.0025/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0025/test.go.in new file mode 100644 index 00000000..e9332e72 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0025/test.go.in @@ -0,0 +1,7 @@ +package main + +import "unsafe" + +func main() { + unsafe.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0026/out.expected b/langserver/internal/gocode/suggest/testdata/test.0026/out.expected new file mode 100644 index 00000000..cfeb50a2 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0026/out.expected @@ -0,0 +1,8 @@ +Found 7 candidates: + func main() + var A struct + var B struct + var C struct + var a int + var d int + var g int diff --git a/langserver/internal/gocode/suggest/testdata/test.0026/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0026/test.go.in new file mode 100644 index 00000000..7580d39b --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0026/test.go.in @@ -0,0 +1,13 @@ +package main + +var A = struct { a, b, c int }{1,2,3} +var B struct { d, e, f int } + +func main() { + C := struct { g, h, i int }{1,2,3} + + a := A.a + d := B.d + g := C.g + @ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0027/out.expected b/langserver/internal/gocode/suggest/testdata/test.0027/out.expected new file mode 100644 index 00000000..0a0026c6 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0027/out.expected @@ -0,0 +1,3 @@ +Found 2 candidates: + func String() + var name string diff --git a/langserver/internal/gocode/suggest/testdata/test.0027/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0027/test.go.in new file mode 100644 index 00000000..3e0a7ace --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0027/test.go.in @@ -0,0 +1,14 @@ +package main + +type file struct { + name string +} + +func (file *file) String() { + file.@ + return file.name +} + +func main() { + +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0028/out.expected b/langserver/internal/gocode/suggest/testdata/test.0028/out.expected new file mode 100644 index 00000000..2fa8895a --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0028/out.expected @@ -0,0 +1,2 @@ +Found 1 candidates: + func PowerOfTwo() int diff --git a/langserver/internal/gocode/suggest/testdata/test.0028/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0028/test.go.in new file mode 100644 index 00000000..c382336c --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0028/test.go.in @@ -0,0 +1,14 @@ +package main + +type X int + +func (x X) PowerOfTwo() int { + return int(x * x) +} + +func main() { + a := X(56) + i := a + 67 + j := ^i + j.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0029/out.expected b/langserver/internal/gocode/suggest/testdata/test.0029/out.expected new file mode 100644 index 00000000..533258f2 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0029/out.expected @@ -0,0 +1,28 @@ +Found 27 candidates: + func Errorf(format string, a ...interface{}) error + func Fprint(w io.Writer, a ...interface{}) (n int, err error) + func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) + func Fprintln(w io.Writer, a ...interface{}) (n int, err error) + func Fscan(r io.Reader, a ...interface{}) (n int, err error) + func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) + func Fscanln(r io.Reader, a ...interface{}) (n int, err error) + func Print(a ...interface{}) (n int, err error) + func Printf(format string, a ...interface{}) (n int, err error) + func Println(a ...interface{}) (n int, err error) + func Scan(a ...interface{}) (n int, err error) + func Scanf(format string, a ...interface{}) (n int, err error) + func Scanln(a ...interface{}) (n int, err error) + func Sprint(a ...interface{}) string + func Sprintf(format string, a ...interface{}) string + func Sprintln(a ...interface{}) string + func Sscan(str string, a ...interface{}) (n int, err error) + func Sscanf(str string, format string, a ...interface{}) (n int, err error) + func Sscanln(str string, a ...interface{}) (n int, err error) + func main() + type Formatter interface + type GoStringer interface + type ScanState interface + type Scanner interface + type State interface + type Stringer interface + var a fmt.Formatter diff --git a/langserver/internal/gocode/suggest/testdata/test.0029/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0029/test.go.in new file mode 100644 index 00000000..2eefc5a3 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0029/test.go.in @@ -0,0 +1,8 @@ +package main + +import . "fmt" + +func main() { + var a Formatter + @ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0030/out.expected b/langserver/internal/gocode/suggest/testdata/test.0030/out.expected new file mode 100644 index 00000000..0b1884c4 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0030/out.expected @@ -0,0 +1,4 @@ +Found 3 candidates: + var F1 int + var F2 int + var F3 int diff --git a/langserver/internal/gocode/suggest/testdata/test.0030/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0030/test.go.in new file mode 100644 index 00000000..8d4ea25a --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0030/test.go.in @@ -0,0 +1,12 @@ +package main + +type A struct { + F1, F2, F3 int +} + +type B A + +func main() { + var b B + b.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0031/out.expected b/langserver/internal/gocode/suggest/testdata/test.0031/out.expected new file mode 100644 index 00000000..2ea646e1 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0031/out.expected @@ -0,0 +1,3 @@ +Found 2 candidates: + func main() + var c int diff --git a/langserver/internal/gocode/suggest/testdata/test.0031/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0031/test.go.in new file mode 100644 index 00000000..d89464e9 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0031/test.go.in @@ -0,0 +1,11 @@ +package main + +func main() { + var c chan int + select { + case c := <-c: + _ = c + @ + default: + } +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0032/out.expected b/langserver/internal/gocode/suggest/testdata/test.0032/out.expected new file mode 100644 index 00000000..700fe99c --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0032/out.expected @@ -0,0 +1,125 @@ +Found 124 candidates: + func main() + package adler32 + package aes + package ascii85 + package asn1 + package ast + package atomic + package base32 + package base64 + package big + package binary + package bufio + package build + package bytes + package bzip2 + package cgi + package cgo + package cipher + package cmplx + package color + package crc32 + package crc64 + package crypto + package csv + package debug + package des + package doc + package draw + package driver + package dsa + package dwarf + package ecdsa + package elf + package elliptic + package errors + package exec + package expvar + package fcgi + package filepath + package flag + package flate + package fmt + package fnv + package gif + package gob + package gosym + package gzip + package hash + package heap + package hex + package hmac + package html + package http + package httptest + package httputil + package image + package io + package iotest + package ioutil + package jpeg + package json + package jsonrpc + package list + package log + package lzw + package macho + package mail + package math + package md5 + package mime + package multipart + package net + package os + package parse + package parser + package path + package pe + package pem + package pkix + package png + package pprof + package printer + package quick + package rand + package rc4 + package reflect + package regexp + package ring + package rpc + package rsa + package runtime + package scanner + package sha1 + package sha256 + package sha512 + package signal + package smtp + package sort + package sql + package strconv + package strings + package subtle + package suffixarray + package sync + package syntax + package syscall + package syslog + package tabwriter + package tar + package template + package testing + package textproto + package time + package tls + package token + package unicode + package url + package user + package utf16 + package utf8 + package x509 + package xml + package zip + package zlib diff --git a/langserver/internal/gocode/suggest/testdata/test.0032/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0032/test.go.in new file mode 100644 index 00000000..92978918 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0032/test.go.in @@ -0,0 +1,135 @@ +package main + +import ( + "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/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/parser" + "go/printer" + "go/scanner" + "go/token" + "hash" + "hash/adler32" + "hash/crc32" + "hash/crc64" + "hash/fnv" + "html" + "html/template" + "image" + "image/color" + "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/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" + "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" +) + +func main() { + +@} diff --git a/langserver/internal/gocode/suggest/testdata/test.0033/out.expected b/langserver/internal/gocode/suggest/testdata/test.0033/out.expected new file mode 100644 index 00000000..95da52eb --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0033/out.expected @@ -0,0 +1,7 @@ +Found 6 candidates: + func testEllipsis(dummies ...*Dummy) + type Dummy struct + var d *Dummy + var dummies []*Dummy + var i int + var x *Dummy diff --git a/langserver/internal/gocode/suggest/testdata/test.0033/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0033/test.go.in new file mode 100644 index 00000000..f605c903 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0033/test.go.in @@ -0,0 +1,12 @@ +package main + +type Dummy struct { + a, b, c int +} + +func testEllipsis(dummies ...*Dummy) { + for i, d := range dummies { + x := dummies[0] + @ + } +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0034/out.expected b/langserver/internal/gocode/suggest/testdata/test.0034/out.expected new file mode 100644 index 00000000..e01c666c --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0034/out.expected @@ -0,0 +1,4 @@ +Found 3 candidates: + func At(x int, y int) color.Color + func Bounds() image.Rectangle + func ColorModel() color.Model diff --git a/langserver/internal/gocode/suggest/testdata/test.0034/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0034/test.go.in new file mode 100644 index 00000000..5e50dd2c --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0034/test.go.in @@ -0,0 +1,8 @@ +package main + +import "image/png" + +func test() { + img, err := png.Decode(nil) + img.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0035/out.expected b/langserver/internal/gocode/suggest/testdata/test.0035/out.expected new file mode 100644 index 00000000..7496c7b2 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0035/out.expected @@ -0,0 +1,6 @@ +Found 5 candidates: + func main() + var err error + var offset int + var r rune + var s string diff --git a/langserver/internal/gocode/suggest/testdata/test.0035/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0035/test.go.in new file mode 100644 index 00000000..7c67c07a --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0035/test.go.in @@ -0,0 +1,9 @@ +package main + +func main() { + var err error + s := err.Error() + for offset, r := range s { + @ + } +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0036/out.expected b/langserver/internal/gocode/suggest/testdata/test.0036/out.expected new file mode 100644 index 00000000..41ebe727 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0036/out.expected @@ -0,0 +1,22 @@ +Found 21 candidates: + func Close() error + func DWARF() (*dwarf.Data, error) + func DynString(tag elf.DynTag) ([]string, error) + func DynamicSymbols() ([]elf.Symbol, error) + func ImportedLibraries() ([]string, error) + func ImportedSymbols() ([]elf.ImportedSymbol, error) + func Section(name string) *elf.Section + func SectionByType(typ elf.SectionType) *elf.Section + func Symbols() ([]elf.Symbol, error) + var ABIVersion uint8 + var ByteOrder binary.ByteOrder + var Class elf.Class + var Data elf.Data + var Entry uint64 + var FileHeader elf.FileHeader + var Machine elf.Machine + var OSABI elf.OSABI + var Progs []*elf.Prog + var Sections []*elf.Section + var Type elf.Type + var Version elf.Version diff --git a/langserver/internal/gocode/suggest/testdata/test.0036/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0036/test.go.in new file mode 100644 index 00000000..e8168071 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0036/test.go.in @@ -0,0 +1,8 @@ +package main + +import "debug/elf" + +func main() { + var f elf.File + f.@ +} \ No newline at end of file diff --git a/langserver/internal/gocode/suggest/testdata/test.0037/out.expected b/langserver/internal/gocode/suggest/testdata/test.0037/out.expected new file mode 100644 index 00000000..8acc25e0 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0037/out.expected @@ -0,0 +1,8 @@ +Found 7 candidates: + func main() + type Array [5]int + var a Array + var s []string + var s1 []string + var s2 []int + var s3 invalid type diff --git a/langserver/internal/gocode/suggest/testdata/test.0037/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0037/test.go.in new file mode 100644 index 00000000..06bc4688 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0037/test.go.in @@ -0,0 +1,12 @@ +package main + +type Array [5]int + +func main() { + var s []string + var a Array + s1 := append(s[:], "123") + s2 := a[0:100500] + s3 := append() + @ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0038/out.expected b/langserver/internal/gocode/suggest/testdata/test.0038/out.expected new file mode 100644 index 00000000..0cace8ca --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0038/out.expected @@ -0,0 +1,3 @@ +Found 2 candidates: + func main() + var x int diff --git a/langserver/internal/gocode/suggest/testdata/test.0038/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0038/test.go.in new file mode 100644 index 00000000..40a591d2 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0038/test.go.in @@ -0,0 +1,8 @@ +package main + +func main() { + if y := 20; y == 0 { + } + if x := 10; x == 0 { + } else if +@} diff --git a/langserver/internal/gocode/suggest/testdata/test.0039/out.expected b/langserver/internal/gocode/suggest/testdata/test.0039/out.expected new file mode 100644 index 00000000..4919bff8 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0039/out.expected @@ -0,0 +1,3 @@ +Found 2 candidates: + func main() + var z int diff --git a/langserver/internal/gocode/suggest/testdata/test.0039/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0039/test.go.in new file mode 100644 index 00000000..5ce95f96 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0039/test.go.in @@ -0,0 +1,10 @@ +package main + +func main() { + z := 5 + if y := 20; y == 0 { + } + if x := 10; x == 0 { + } + +@} diff --git a/langserver/internal/gocode/suggest/testdata/test.0040/out.expected b/langserver/internal/gocode/suggest/testdata/test.0040/out.expected new file mode 100644 index 00000000..033a05be --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0040/out.expected @@ -0,0 +1,4 @@ +Found 3 candidates: + func create_foo() Foo + type Foo struct + var t Foo diff --git a/langserver/internal/gocode/suggest/testdata/test.0040/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0040/test.go.in new file mode 100644 index 00000000..20a38ce1 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0040/test.go.in @@ -0,0 +1,10 @@ +package main + +type Foo struct {} + +func (f *Foo) Bar() {} + +func create_foo() Foo { + t := Foo{} + +@} diff --git a/langserver/internal/gocode/suggest/testdata/test.0041/out.expected b/langserver/internal/gocode/suggest/testdata/test.0041/out.expected new file mode 100644 index 00000000..5745c3a9 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0041/out.expected @@ -0,0 +1,4 @@ +Found 3 candidates: + var Xa int + var Xb int + var Xy Y diff --git a/langserver/internal/gocode/suggest/testdata/test.0041/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0041/test.go.in new file mode 100644 index 00000000..80a20f76 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0041/test.go.in @@ -0,0 +1,20 @@ +package main + +type X struct { + Xa int + Xb int + Xy Y +} + +type Y struct { + Ya int + Yb int +} + +func main() { + x := X{ + Xy: Y{ + Ya: 1, + }, + +@} diff --git a/langserver/internal/gocode/suggest/testdata/test.0042/out.expected b/langserver/internal/gocode/suggest/testdata/test.0042/out.expected new file mode 100644 index 00000000..6e17e66c --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0042/out.expected @@ -0,0 +1,3 @@ +Found 2 candidates: + var Ya int + var Yb int diff --git a/langserver/internal/gocode/suggest/testdata/test.0042/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0042/test.go.in new file mode 100644 index 00000000..dcbead25 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0042/test.go.in @@ -0,0 +1,18 @@ +package main + +type X struct { + Xa int + Xb int + Xy Y +} + +type Y struct { + Ya int + Yb int +} + +func main() { + x := X{ + Xy: Y{ + +@} diff --git a/langserver/internal/gocode/suggest/testdata/test.0043/out.expected b/langserver/internal/gocode/suggest/testdata/test.0043/out.expected new file mode 100644 index 00000000..a97752f1 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0043/out.expected @@ -0,0 +1,5 @@ +Found 4 candidates: + func foo() + var Xa int + var Xb int + var Xy Y diff --git a/langserver/internal/gocode/suggest/testdata/test.0043/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0043/test.go.in new file mode 100644 index 00000000..e5c02e0a --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0043/test.go.in @@ -0,0 +1,25 @@ +package main + +type X struct { + Xa int + Xb int + Xy Y +} + +type Y struct { + Ya int + Yb int +} + +func (x X) foo() { + return +} + +func main() { + x := X{ + Xa: 1, + Xb: 2, + } + y := Y{ + Ya: x. +@} diff --git a/langserver/internal/gocode/suggest/testdata/test.0044/out.expected b/langserver/internal/gocode/suggest/testdata/test.0044/out.expected new file mode 100644 index 00000000..04a4631c --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0044/out.expected @@ -0,0 +1,3 @@ +Found 2 candidates: + var Xa int + var Xb int diff --git a/langserver/internal/gocode/suggest/testdata/test.0044/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0044/test.go.in new file mode 100644 index 00000000..c7e9c488 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0044/test.go.in @@ -0,0 +1,15 @@ +package main + +type X struct { + Xa int + Xb int +} + +func (x X) foo() { + return +} + +func main() { + x := X{ + +@} diff --git a/langserver/internal/gocode/suggest/testdata/test.0045/out.expected b/langserver/internal/gocode/suggest/testdata/test.0045/out.expected new file mode 100644 index 00000000..662c4e5b --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0045/out.expected @@ -0,0 +1,26 @@ +Found 25 candidates: + func Errorf(format string, a ...interface{}) error + func Fprint(w io.Writer, a ...interface{}) (n int, err error) + func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) + func Fprintln(w io.Writer, a ...interface{}) (n int, err error) + func Fscan(r io.Reader, a ...interface{}) (n int, err error) + func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) + func Fscanln(r io.Reader, a ...interface{}) (n int, err error) + func Print(a ...interface{}) (n int, err error) + func Printf(format string, a ...interface{}) (n int, err error) + func Println(a ...interface{}) (n int, err error) + func Scan(a ...interface{}) (n int, err error) + func Scanf(format string, a ...interface{}) (n int, err error) + func Scanln(a ...interface{}) (n int, err error) + func Sprint(a ...interface{}) string + func Sprintf(format string, a ...interface{}) string + func Sprintln(a ...interface{}) string + func Sscan(str string, a ...interface{}) (n int, err error) + func Sscanf(str string, format string, a ...interface{}) (n int, err error) + func Sscanln(str string, a ...interface{}) (n int, err error) + type Formatter interface + type GoStringer interface + type ScanState interface + type Scanner interface + type State interface + type Stringer interface diff --git a/langserver/internal/gocode/suggest/testdata/test.0045/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0045/test.go.in new file mode 100644 index 00000000..b84cad6c --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0045/test.go.in @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + if fmt. +@} diff --git a/langserver/internal/gocode/suggest/testdata/test.0046/out.expected b/langserver/internal/gocode/suggest/testdata/test.0046/out.expected new file mode 100644 index 00000000..662c4e5b --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0046/out.expected @@ -0,0 +1,26 @@ +Found 25 candidates: + func Errorf(format string, a ...interface{}) error + func Fprint(w io.Writer, a ...interface{}) (n int, err error) + func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) + func Fprintln(w io.Writer, a ...interface{}) (n int, err error) + func Fscan(r io.Reader, a ...interface{}) (n int, err error) + func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) + func Fscanln(r io.Reader, a ...interface{}) (n int, err error) + func Print(a ...interface{}) (n int, err error) + func Printf(format string, a ...interface{}) (n int, err error) + func Println(a ...interface{}) (n int, err error) + func Scan(a ...interface{}) (n int, err error) + func Scanf(format string, a ...interface{}) (n int, err error) + func Scanln(a ...interface{}) (n int, err error) + func Sprint(a ...interface{}) string + func Sprintf(format string, a ...interface{}) string + func Sprintln(a ...interface{}) string + func Sscan(str string, a ...interface{}) (n int, err error) + func Sscanf(str string, format string, a ...interface{}) (n int, err error) + func Sscanln(str string, a ...interface{}) (n int, err error) + type Formatter interface + type GoStringer interface + type ScanState interface + type Scanner interface + type State interface + type Stringer interface diff --git a/langserver/internal/gocode/suggest/testdata/test.0046/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0046/test.go.in new file mode 100644 index 00000000..b7c6291c --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0046/test.go.in @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + x := fmt. +@} diff --git a/langserver/internal/gocode/suggest/testdata/test.0047/out.expected b/langserver/internal/gocode/suggest/testdata/test.0047/out.expected new file mode 100644 index 00000000..25a78e98 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0047/out.expected @@ -0,0 +1 @@ +Nothing to complete. diff --git a/langserver/internal/gocode/suggest/testdata/test.0047/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0047/test.go.in new file mode 100644 index 00000000..6cfdde88 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0047/test.go.in @@ -0,0 +1,5 @@ +package p + +type T struct{ X } + +var _ = T.x.@ diff --git a/langserver/internal/gocode/suggest/testdata/test.0048/out.expected b/langserver/internal/gocode/suggest/testdata/test.0048/out.expected new file mode 100644 index 00000000..1d4f4b4e --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0048/out.expected @@ -0,0 +1,8 @@ +Found 7 candidates: + func Fprint(w io.Writer, a ...interface{}) (n int, err error) + func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) + func Fprintln(w io.Writer, a ...interface{}) (n int, err error) + func Fscan(r io.Reader, a ...interface{}) (n int, err error) + func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) + func Fscanln(r io.Reader, a ...interface{}) (n int, err error) + type Formatter interface diff --git a/langserver/internal/gocode/suggest/testdata/test.0048/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0048/test.go.in new file mode 100644 index 00000000..6bfadba1 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0048/test.go.in @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + x := fmt.F@pr +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0049/out.expected b/langserver/internal/gocode/suggest/testdata/test.0049/out.expected new file mode 100644 index 00000000..aa2bc2e6 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0049/out.expected @@ -0,0 +1,2 @@ +Found 1 candidates: + var i int diff --git a/langserver/internal/gocode/suggest/testdata/test.0049/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0049/test.go.in new file mode 100644 index 00000000..845517fb --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0049/test.go.in @@ -0,0 +1,7 @@ +package p + +func t(a struct { + i int +}) { + a.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0050/out.expected b/langserver/internal/gocode/suggest/testdata/test.0050/out.expected new file mode 100644 index 00000000..aa2bc2e6 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0050/out.expected @@ -0,0 +1,2 @@ +Found 1 candidates: + var i int diff --git a/langserver/internal/gocode/suggest/testdata/test.0050/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0050/test.go.in new file mode 100644 index 00000000..02561028 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0050/test.go.in @@ -0,0 +1,7 @@ +package p + +func t(a *struct { + i int +}) { + a.@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0051/out.expected b/langserver/internal/gocode/suggest/testdata/test.0051/out.expected new file mode 100644 index 00000000..01d261fb --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0051/out.expected @@ -0,0 +1,4 @@ +Found 3 candidates: + var NumApples int + var NumBananas int + var NumOranges int diff --git a/langserver/internal/gocode/suggest/testdata/test.0051/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0051/test.go.in new file mode 100644 index 00000000..bceb9aec --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0051/test.go.in @@ -0,0 +1,32 @@ +package main + +type Item struct { + Quantity int + Name string +} + +type Config struct { + NumApples int + NumOranges int + NumBananas int +} + +func main() { + var cfg Config + items := []Item{ + {cfg.NumApples, "apple"}, + {cfg.NumOranges, "orange"}, + {cfg.@NumApples, "banana"}, + } + + var a int + var b int + var c int + var d int + var e int + var f int + var g int + var h int + var i int + var j int +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0052/out.expected b/langserver/internal/gocode/suggest/testdata/test.0052/out.expected new file mode 100644 index 00000000..3757fb51 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0052/out.expected @@ -0,0 +1,4 @@ +Found 3 candidates: + var a int + var b int + var c int diff --git a/langserver/internal/gocode/suggest/testdata/test.0052/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0052/test.go.in new file mode 100644 index 00000000..ee5b0ed4 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0052/test.go.in @@ -0,0 +1,11 @@ +package main + +type Dummy struct { + a, b, c int +} + +func testEllipsis(dummies ...*Dummy) { + for _, d := range dummies { + d.@ + } +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0053/b.go b/langserver/internal/gocode/suggest/testdata/test.0053/b.go new file mode 100644 index 00000000..6c6d96b3 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0053/b.go @@ -0,0 +1,24 @@ +package main + +// this is a file 'a.go' + +import ( + superos "os" +) + +func B() superos.Error { + return nil +} + +// notice how changing type of a return function in one file, +// the inferred type of a variable in another file changes also + +func (t Tester) SetC() { + t.c = 31337 +} + +func (t Tester) SetD() { + t.d = 31337 +} + +// support for multifile packages, including correct namespace handling diff --git a/langserver/internal/gocode/suggest/testdata/test.0053/out.expected b/langserver/internal/gocode/suggest/testdata/test.0053/out.expected new file mode 100644 index 00000000..49805ba9 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0053/out.expected @@ -0,0 +1,3 @@ +Found 2 candidates: + func SetC() + func SetD() diff --git a/langserver/internal/gocode/suggest/testdata/test.0053/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0053/test.go.in new file mode 100644 index 00000000..239d4101 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0053/test.go.in @@ -0,0 +1,30 @@ +package main + +// this is a file 'a.go' + +import ( + localos "os" +) + +func A() localos.Error { + return nil +} + +// B() is defined in file 'b.go' +var test = B() + +type Tester struct { + a, b, c, d int +} + +func (t *Tester) SetA() { + t.a = 31337 +} + +func (t *Tester) SetB() { + t.b = 31337 +} + +Tester.@ + +// methods SetC and SetD are defined in 'b.go' diff --git a/langserver/internal/gocode/suggest/testdata/test.0054/b.go b/langserver/internal/gocode/suggest/testdata/test.0054/b.go new file mode 100644 index 00000000..6c6d96b3 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0054/b.go @@ -0,0 +1,24 @@ +package main + +// this is a file 'a.go' + +import ( + superos "os" +) + +func B() superos.Error { + return nil +} + +// notice how changing type of a return function in one file, +// the inferred type of a variable in another file changes also + +func (t Tester) SetC() { + t.c = 31337 +} + +func (t Tester) SetD() { + t.d = 31337 +} + +// support for multifile packages, including correct namespace handling diff --git a/langserver/internal/gocode/suggest/testdata/test.0054/out.expected b/langserver/internal/gocode/suggest/testdata/test.0054/out.expected new file mode 100644 index 00000000..412e2f25 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0054/out.expected @@ -0,0 +1,5 @@ +Found 4 candidates: + func SetA() + func SetB() + func SetC() + func SetD() diff --git a/langserver/internal/gocode/suggest/testdata/test.0054/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0054/test.go.in new file mode 100644 index 00000000..8dc60cd1 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0054/test.go.in @@ -0,0 +1,30 @@ +package main + +// this is a file 'a.go' + +import ( + localos "os" +) + +func A() localos.Error { + return nil +} + +// B() is defined in file 'b.go' +var test = B() + +type Tester struct { + a, b, c, d int +} + +func (t *Tester) SetA() { + t.a = 31337 +} + +func (t *Tester) SetB() { + t.b = 31337 +} + +(*Tester).@ + +// methods SetC and SetD are defined in 'b.go' diff --git a/langserver/internal/gocode/suggest/testdata/test.0055/out.expected b/langserver/internal/gocode/suggest/testdata/test.0055/out.expected new file mode 100644 index 00000000..a575b145 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0055/out.expected @@ -0,0 +1,3 @@ +Found 2 candidates: + var x int + var y int diff --git a/langserver/internal/gocode/suggest/testdata/test.0055/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0055/test.go.in new file mode 100644 index 00000000..a8f45703 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0055/test.go.in @@ -0,0 +1,8 @@ +package p + +var _ = struct { + x int + y int +}{ + @ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0056/out.expected b/langserver/internal/gocode/suggest/testdata/test.0056/out.expected new file mode 100644 index 00000000..773a0756 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0056/out.expected @@ -0,0 +1,2 @@ +Found 1 candidates: + var L sync.Locker diff --git a/langserver/internal/gocode/suggest/testdata/test.0056/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0056/test.go.in new file mode 100644 index 00000000..320f1936 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0056/test.go.in @@ -0,0 +1,5 @@ +package p + +import "sync" + +var _ = sync.Cond{@ diff --git a/langserver/internal/gocode/suggest/testdata/test.0057/out.expected b/langserver/internal/gocode/suggest/testdata/test.0057/out.expected new file mode 100644 index 00000000..25a78e98 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0057/out.expected @@ -0,0 +1 @@ +Nothing to complete. diff --git a/langserver/internal/gocode/suggest/testdata/test.0057/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0057/test.go.in new file mode 100644 index 00000000..27ea4dc9 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0057/test.go.in @@ -0,0 +1,3 @@ +package p + +var _ = (*[]int).@ diff --git a/langserver/internal/gocode/suggest/testdata/test.0058/out.expected b/langserver/internal/gocode/suggest/testdata/test.0058/out.expected new file mode 100644 index 00000000..b798b64b --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0058/out.expected @@ -0,0 +1,2 @@ +Found 1 candidates: + var str func(myint) string diff --git a/langserver/internal/gocode/suggest/testdata/test.0058/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0058/test.go.in new file mode 100644 index 00000000..bf5bea02 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0058/test.go.in @@ -0,0 +1,12 @@ +package p + +type myint int + +func (i myint) String() string { + return "int" +} + +func main() { + str := myint.String + st@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0059/out.expected b/langserver/internal/gocode/suggest/testdata/test.0059/out.expected new file mode 100644 index 00000000..ebbab185 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0059/out.expected @@ -0,0 +1,2 @@ +Found 1 candidates: + func B() diff --git a/langserver/internal/gocode/suggest/testdata/test.0059/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0059/test.go.in new file mode 100644 index 00000000..fce07fb7 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0059/test.go.in @@ -0,0 +1,12 @@ +package p + +type A int +type B []A + +func (A) A() {} +func (B) B() {} + +func test(x B) { + for _, x := range x.@ { + } +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0060/out.expected b/langserver/internal/gocode/suggest/testdata/test.0060/out.expected new file mode 100644 index 00000000..193f97ff --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0060/out.expected @@ -0,0 +1,2 @@ +Found 1 candidates: + func M2() diff --git a/langserver/internal/gocode/suggest/testdata/test.0060/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0060/test.go.in new file mode 100644 index 00000000..4f9e85de --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0060/test.go.in @@ -0,0 +1,9 @@ +package p + +type S1 struct { *S2 } +type S2 struct { *S1 } + +func (*S1) M1() {} +func (*S2) M2() {} + +var _ = (S1).@ diff --git a/langserver/internal/gocode/suggest/testdata/test.0061/out.expected b/langserver/internal/gocode/suggest/testdata/test.0061/out.expected new file mode 100644 index 00000000..05cc27b9 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0061/out.expected @@ -0,0 +1,2 @@ +Found 1 candidates: + var buf int diff --git a/langserver/internal/gocode/suggest/testdata/test.0061/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0061/test.go.in new file mode 100644 index 00000000..f043c2bf --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0061/test.go.in @@ -0,0 +1,10 @@ +package p + +import "bytes" + +type S struct { buf int } +type T struct { S; bytes.Buffer } + +func _(t T) { + _ = t.b@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0062/out.expected b/langserver/internal/gocode/suggest/testdata/test.0062/out.expected new file mode 100644 index 00000000..7706b156 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0062/out.expected @@ -0,0 +1,2 @@ +Found 1 candidates: + type S struct diff --git a/langserver/internal/gocode/suggest/testdata/test.0062/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0062/test.go.in new file mode 100644 index 00000000..618bc2c7 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0062/test.go.in @@ -0,0 +1,5 @@ +package p + +type S struct{ x int } + +var _ = []S{ @ } diff --git a/langserver/internal/gocode/suggest/testdata/test.0063/out.expected b/langserver/internal/gocode/suggest/testdata/test.0063/out.expected new file mode 100644 index 00000000..25a78e98 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0063/out.expected @@ -0,0 +1 @@ +Nothing to complete. diff --git a/langserver/internal/gocode/suggest/testdata/test.0063/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0063/test.go.in new file mode 100644 index 00000000..17d280a8 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0063/test.go.in @@ -0,0 +1 @@ +pa@ diff --git a/langserver/internal/gocode/suggest/testdata/test.0064/config.json b/langserver/internal/gocode/suggest/testdata/test.0064/config.json new file mode 100644 index 00000000..8cb611a3 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0064/config.json @@ -0,0 +1 @@ +{"Builtin": true} diff --git a/langserver/internal/gocode/suggest/testdata/test.0064/out.expected b/langserver/internal/gocode/suggest/testdata/test.0064/out.expected new file mode 100644 index 00000000..dc07445d --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0064/out.expected @@ -0,0 +1,2 @@ +Found 1 candidates: + type string string diff --git a/langserver/internal/gocode/suggest/testdata/test.0064/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0064/test.go.in new file mode 100644 index 00000000..3614966e --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0064/test.go.in @@ -0,0 +1,5 @@ +package p + +func f() { + var s str@ +} diff --git a/langserver/internal/gocode/suggest/testdata/test.0065/config.json b/langserver/internal/gocode/suggest/testdata/test.0065/config.json new file mode 100644 index 00000000..8cb611a3 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0065/config.json @@ -0,0 +1 @@ +{"Builtin": true} diff --git a/langserver/internal/gocode/suggest/testdata/test.0065/out.expected b/langserver/internal/gocode/suggest/testdata/test.0065/out.expected new file mode 100644 index 00000000..33630698 --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0065/out.expected @@ -0,0 +1,2 @@ +Found 1 candidates: + func New(text string) error diff --git a/langserver/internal/gocode/suggest/testdata/test.0065/test.go.in b/langserver/internal/gocode/suggest/testdata/test.0065/test.go.in new file mode 100644 index 00000000..74646e9c --- /dev/null +++ b/langserver/internal/gocode/suggest/testdata/test.0065/test.go.in @@ -0,0 +1,7 @@ +package p + +import "errors" + +func f() { + errors.@ +} diff --git a/langserver/internal/gocode/type_alias_build_hack_18.go b/langserver/internal/gocode/type_alias_build_hack_18.go deleted file mode 100644 index 945e6ba7..00000000 --- a/langserver/internal/gocode/type_alias_build_hack_18.go +++ /dev/null @@ -1,18 +0,0 @@ -// +build !go1.9,!go1.8.typealias - -package gocode - -import ( - "go/ast" -) - -func typeAliasSpec(name string, typ ast.Expr) *ast.TypeSpec { - return &ast.TypeSpec{ - Name: ast.NewIdent(name), - Type: typ, - } -} - -func isAliasTypeSpec(t *ast.TypeSpec) bool { - return false -} diff --git a/langserver/internal/gocode/type_alias_build_hack_19.go b/langserver/internal/gocode/type_alias_build_hack_19.go deleted file mode 100644 index 4fc034d2..00000000 --- a/langserver/internal/gocode/type_alias_build_hack_19.go +++ /dev/null @@ -1,19 +0,0 @@ -// +build go1.9 go1.8.typealias - -package gocode - -import ( - "go/ast" -) - -func typeAliasSpec(name string, typ ast.Expr) *ast.TypeSpec { - return &ast.TypeSpec{ - Name: ast.NewIdent(name), - Assign: 1, - Type: typ, - } -} - -func isAliasTypeSpec(t *ast.TypeSpec) bool { - return t.Assign != 0 -} diff --git a/langserver/internal/gocode/utils.go b/langserver/internal/gocode/utils.go deleted file mode 100644 index 29e133f1..00000000 --- a/langserver/internal/gocode/utils.go +++ /dev/null @@ -1,297 +0,0 @@ -package gocode - -import ( - "bytes" - "fmt" - "go/build" - "io/ioutil" - "log" - "os" - "path/filepath" - "runtime" - "strings" - "sync" - "unicode/utf8" -) - -// our own readdir, which skips the files it cannot lstat -func readdir_lstat(name string) ([]os.FileInfo, error) { - f, err := os.Open(name) - if err != nil { - return nil, err - } - defer f.Close() - - names, err := f.Readdirnames(-1) - if err != nil { - return nil, err - } - - out := make([]os.FileInfo, 0, len(names)) - for _, lname := range names { - s, err := os.Lstat(filepath.Join(name, lname)) - if err != nil { - continue - } - out = append(out, s) - } - return out, nil -} - -// our other readdir function, only opens and reads -func readdir(dirname string) []os.FileInfo { - f, err := os.Open(dirname) - if err != nil { - return nil - } - fi, err := f.Readdir(-1) - f.Close() - if err != nil { - panic(err) - } - return fi -} - -// returns truncated 'data' and amount of bytes skipped (for cursor pos adjustment) -func filter_out_shebang(data []byte) ([]byte, int) { - if len(data) > 2 && data[0] == '#' && data[1] == '!' { - newline := bytes.Index(data, []byte("\n")) - if newline != -1 && len(data) > newline+1 { - return data[newline+1:], newline + 1 - } - } - return data, 0 -} - -func file_exists(filename string) bool { - _, err := os.Stat(filename) - if err != nil { - return false - } - return true -} - -func is_dir(path string) bool { - fi, err := os.Stat(path) - return err == nil && fi.IsDir() -} - -func char_to_byte_offset(s []byte, offset_c int) (offset_b int) { - for offset_b = 0; offset_c > 0 && offset_b < len(s); offset_b++ { - if utf8.RuneStart(s[offset_b]) { - offset_c-- - } - } - return offset_b -} - -func xdg_home_dir() string { - xdghome := os.Getenv("XDG_CONFIG_HOME") - if xdghome == "" { - xdghome = filepath.Join(os.Getenv("HOME"), ".config") - } - return xdghome -} - -func has_prefix(s, prefix string, ignorecase bool) bool { - if ignorecase { - s = strings.ToLower(s) - prefix = strings.ToLower(prefix) - } - return strings.HasPrefix(s, prefix) -} - -func find_bzl_project_root(libpath, path string) (string, error) { - if libpath == "" { - return "", fmt.Errorf("could not find project root, libpath is empty") - } - - pathMap := map[string]struct{}{} - for _, lp := range strings.Split(libpath, ":") { - lp := strings.TrimSpace(lp) - pathMap[filepath.Clean(lp)] = struct{}{} - } - - path = filepath.Dir(path) - if path == "" { - return "", fmt.Errorf("project root is blank") - } - - start := path - for path != "/" { - if _, ok := pathMap[filepath.Clean(path)]; ok { - return path, nil - } - path = filepath.Dir(path) - } - return "", fmt.Errorf("could not find project root in %q or its parents", start) -} - -// Code taken directly from `gb`, I hope author doesn't mind. -func find_gb_project_root(path string) (string, error) { - path = filepath.Dir(path) - if path == "" { - return "", fmt.Errorf("project root is blank") - } - start := path - for path != "/" { - root := filepath.Join(path, "src") - if _, err := os.Stat(root); err != nil { - if os.IsNotExist(err) { - path = filepath.Dir(path) - continue - } - return "", err - } - path, err := filepath.EvalSymlinks(path) - if err != nil { - return "", err - } - return path, nil - } - return "", fmt.Errorf("could not find project root in %q or its parents", start) -} - -// vendorlessImportPath returns the devendorized version of the provided import path. -// e.g. "foo/bar/vendor/a/b" => "a/b" -func vendorlessImportPath(ipath string, currentPackagePath string) (string, bool) { - split := strings.Split(ipath, "vendor/") - // no vendor in path - if len(split) == 1 { - return ipath, true - } - // this import path does not belong to the current package - if currentPackagePath != "" && !strings.Contains(currentPackagePath, split[0]) { - return "", false - } - // Devendorize for use in import statement. - if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 { - return ipath[i+len("/vendor/"):], true - } - if strings.HasPrefix(ipath, "vendor/") { - return ipath[len("vendor/"):], true - } - return ipath, true -} - -//------------------------------------------------------------------------- -// print_backtrace -// -// a nicer backtrace printer than the default one -//------------------------------------------------------------------------- - -var g_backtrace_mutex sync.Mutex - -func print_backtrace(err interface{}) { - g_backtrace_mutex.Lock() - defer g_backtrace_mutex.Unlock() - log.Printf("panic: %v\n", err) - i := 2 - for { - pc, file, line, ok := runtime.Caller(i) - if !ok { - break - } - f := runtime.FuncForPC(pc) - log.Printf("%d(%s): %s:%d\n", i-1, f.Name(), file, line) - i++ - } - log.Println("") -} - -//------------------------------------------------------------------------- -// File reader goroutine -// -// It's a bad idea to block multiple goroutines on file I/O. Creates many -// threads which fight for HDD. Therefore only single goroutine should read HDD -// at the same time. -//------------------------------------------------------------------------- - -type file_read_request struct { - filename string - out chan file_read_response -} - -type file_read_response struct { - data []byte - error error -} - -type file_reader_type struct { - in chan file_read_request -} - -func new_file_reader() *file_reader_type { - this := new(file_reader_type) - this.in = make(chan file_read_request) - go func() { - var rsp file_read_response - for { - req := <-this.in - rsp.data, rsp.error = ioutil.ReadFile(req.filename) - req.out <- rsp - } - }() - return this -} - -func (this *file_reader_type) read_file(filename string) ([]byte, error) { - req := file_read_request{ - filename, - make(chan file_read_response), - } - this.in <- req - rsp := <-req.out - return rsp.data, rsp.error -} - -var file_reader = new_file_reader() - -//------------------------------------------------------------------------- -// copy of the build.Context without func fields -//------------------------------------------------------------------------- - -type go_build_context struct { - GOARCH string - GOOS string - GOROOT string - GOPATH string - CgoEnabled bool - UseAllFiles bool - Compiler string - BuildTags []string - ReleaseTags []string - InstallSuffix string -} - -func pack_build_context(ctx *build.Context) go_build_context { - return go_build_context{ - GOARCH: ctx.GOARCH, - GOOS: ctx.GOOS, - GOROOT: ctx.GOROOT, - GOPATH: ctx.GOPATH, - CgoEnabled: ctx.CgoEnabled, - UseAllFiles: ctx.UseAllFiles, - Compiler: ctx.Compiler, - BuildTags: ctx.BuildTags, - ReleaseTags: ctx.ReleaseTags, - InstallSuffix: ctx.InstallSuffix, - } -} - -func unpack_build_context(ctx *go_build_context) package_lookup_context { - return package_lookup_context{ - Context: build.Context{ - GOARCH: ctx.GOARCH, - GOOS: ctx.GOOS, - GOROOT: ctx.GOROOT, - GOPATH: ctx.GOPATH, - CgoEnabled: ctx.CgoEnabled, - UseAllFiles: ctx.UseAllFiles, - Compiler: ctx.Compiler, - BuildTags: ctx.BuildTags, - ReleaseTags: ctx.ReleaseTags, - InstallSuffix: ctx.InstallSuffix, - }, - } -} diff --git a/langserver/langserver_test.go b/langserver/langserver_test.go index e27b8eb7..efcf4350 100644 --- a/langserver/langserver_test.go +++ b/langserver/langserver_test.go @@ -21,7 +21,6 @@ import ( "testing" "github.com/sourcegraph/ctxvfs" - "github.com/sourcegraph/go-langserver/langserver/internal/gocode" "github.com/sourcegraph/go-langserver/langserver/util" "github.com/sourcegraph/go-langserver/pkg/lsp" "github.com/sourcegraph/go-langserver/pkg/lspext" @@ -210,7 +209,7 @@ var serverTestCases = map[string]serverTestCase{ "a_test.go:1:20": "var A int", }, wantCompletion: map[string]string{ - "x_test.go:1:45": "1:44-1:45 panic function func(interface{}), print function func(...interface{}), println function func(...interface{}), p module ", + "x_test.go:1:45": "1:44-1:45 panic function func(v interface{}), print function func(args ...Type), println function func(args ...Type), p module ", "x_test.go:1:46": "1:46-1:46 A variable int", "b_test.go:1:35": "1:34-1:35 X variable int", }, @@ -463,8 +462,8 @@ package main; import "test/pkg"; func B() { p.A(); B() }`, }, wantCompletion: map[string]string{ // use default GOROOT, since gocode needs package binaries - "a.go:1:21": "1:20-1:21 flag module , fmt module ", - "a.go:1:44": "1:38-1:44 Println function func(a ...interface{}) (n int, err error)", + // "a.go:1:21": "1:21-1:21 x variable int", TODO(anjmao): bug in gocode, it returns all builtins when adding . in pkg import path + // "a.go:1:44": "1:38-1:44 Println function func(a ...interface{}) (n int, err error)", // TODO(anjmao): check this test }, wantSymbols: map[string][]string{ "a.go": { @@ -508,7 +507,7 @@ package main; import "test/pkg"; func B() { p.A(); B() }`, "b/b.go:1:43": "/src/test/pkg/a/a.go:1:17 id:test/pkg/a/-/A name:A package:test/pkg/a packageName:a recv: vendor:false", }, wantCompletion: map[string]string{ - "b/b.go:1:26": "1:20-1:26 test/pkg/a module , test/pkg/b module ", + // "b/b.go:1:26": "1:20-1:26 test/pkg/a module , test/pkg/b module ", // TODO(anjmao): gocode doesn't support package autocomplete yet "b/b.go:1:43": "1:43-1:43 A function func()", }, wantReferences: map[string][]string{ @@ -559,7 +558,7 @@ package main; import "test/pkg"; func B() { p.A(); B() }`, "a.go:1:61": "/src/test/pkg/vendor/github.com/v/vendored/v.go:1:24 id:test/pkg/vendor/github.com/v/vendored/-/V name:V package:test/pkg/vendor/github.com/v/vendored packageName:vendored recv: vendor:true", }, wantCompletion: map[string]string{ - "a.go:1:34": "1:20-1:34 github.com/v/vendored module ", + // "a.go:1:34": "1:20-1:34 github.com/v/vendored module ", // TODO(anjmao): gocode doesn't support package autocomplete yet "a.go:1:61": "1:61-1:61 V function func()", }, wantReferences: map[string][]string{ @@ -646,7 +645,7 @@ package main; import "test/pkg"; func B() { p.A(); B() }`, "a.go:1:51": "/src/github.com/d/dep/d.go:1:19 id:github.com/d/dep/-/D name:D package:github.com/d/dep packageName:dep recv: vendor:false", }, wantCompletion: map[string]string{ - "a.go:1:34": "1:20-1:34 github.com/d/dep module ", + // "a.go:1:34": "1:20-1:34 github.com/d/dep module ", // TODO(anjmao): gocode doesn't support package autocomplete yet "a.go:1:51": "1:51-1:51 D function func()", }, wantReferences: map[string][]string{ @@ -718,7 +717,7 @@ package main; import "test/pkg"; func B() { p.A(); B() }`, "a.go:1:57": "/src/github.com/d/dep/subp/d.go:1:20 id:github.com/d/dep/subp/-/D name:D package:github.com/d/dep/subp packageName:subp recv: vendor:false", }, wantCompletion: map[string]string{ - "a.go:1:34": "1:20-1:34 github.com/d/dep/subp module ", + // "a.go:1:34": "1:20-1:34 github.com/d/dep/subp module ", // TODO(anjmao): gocode doesn't support package autocomplete yet "a.go:1:57": "1:57-1:57 D function func()", }, wantWorkspaceReferences: map[*lspext.WorkspaceReferencesParams][]string{ @@ -1054,15 +1053,15 @@ var s4 func()`, }, cases: lspTestCases{ wantCompletion: map[string]string{ - "a.go:6:7": "6:6-6:7 s1 constant , s2 function func(), strings module , string class built-in, s3 variable int, s4 variable func()", - "a.go:7:7": "7:6-7:7 nil constant , new function func(type) *type", - "a.go:12:11": "12:8-12:11 int class built-in, int16 class built-in, int32 class built-in, int64 class built-in, int8 class built-in", + "a.go:6:7": "6:6-6:7 s1 constant untyped int, s2 function func(), strings module , string class string, s3 variable int, s4 variable func()", + "a.go:7:7": "7:6-7:7 nil constant untyped nil, new function func(Type) *Type", + "a.go:12:11": "12:8-12:11 int class int, int16 class int16, int32 class int32, int64 class int64, int8 class int8", }, }, }, "unexpected paths": { - // notice the : and @ symbol - rootURI: "file:///src/t:est/@hello/pkg", + // notice the : symbol + rootURI: "file:///src/t:est/hello/pkg", skip: runtime.GOOS == "windows", // this test is not supported on windows fs: map[string]string{ "a.go": "package p; func A() { A() }", @@ -1073,12 +1072,12 @@ var s4 func()`, }, wantReferences: map[string][]string{ "a.go:1:17": { - "/src/t:est/@hello/pkg/a.go:1:17", - "/src/t:est/@hello/pkg/a.go:1:23", + "/src/t:est/hello/pkg/a.go:1:17", + "/src/t:est/hello/pkg/a.go:1:23", }, }, wantSymbols: map[string][]string{ - "a.go": {"/src/t:est/@hello/pkg/a.go:function:A:1:17"}, + "a.go": {"/src/t:est/hello/pkg/a.go:function:A:1:17"}, }, }, }, @@ -1225,8 +1224,8 @@ func TestServer(t *testing.T) { NoOSFileSystemAccess: true, RootImportPath: strings.TrimPrefix(rootFSPath, "/src/"), BuildContext: &InitializeBuildContextParams{ - GOOS: "linux", - GOARCH: "amd64", + GOOS: runtime.GOOS, + GOARCH: runtime.GOARCH, GOPATH: "/", GOROOT: "/goroot", Compiler: runtime.Compiler, @@ -1397,7 +1396,6 @@ func lspTests(t testing.TB, ctx context.Context, h *LangHandler, c *jsonrpc2.Con // look for $GOPATH/pkg .a files inside the $GOPATH that was set during // 'go test' instead of our tmp directory. build.Default.GOPATH = tmpDir - gocode.SetBuildContext(&build.Default) tmpRootPath := filepath.Join(tmpDir, util.UriToPath(rootURI)) // Install all Go packages in the $GOPATH. @@ -1428,6 +1426,7 @@ func lspTests(t testing.TB, ctx context.Context, h *LangHandler, c *jsonrpc2.Con hoverTest(t, ctx, c, util.PathToURI(tmpRootPath), pos, want) }) } + for pos, want := range cases.wantCompletion { tbRun(t, fmt.Sprintf("completion-%s", strings.Replace(pos, "/", "-", -1)), func(t testing.TB) { completionTest(t, ctx, c, util.PathToURI(tmpRootPath), pos, want)