From a2f18c786cf57392407fa75d2cfa35dd92ae1df2 Mon Sep 17 00:00:00 2001 From: Denys Sedchenko Date: Fri, 9 Aug 2024 13:15:24 -0400 Subject: [PATCH] feat: provide builtin types and intrinsics (#7) * feat: bake-in completion item for intrinsics * fix: fix codegen script * feat: make snippet syntax optional * feat: add builtin lookup table * feat: provide completions * chore: remove mention of unsupported channels and goroutines * feat: use "go:generate" for codegen * fix: use .txt for builtins to prevent build fails * chore: remove obsolete comment * fix: goimports * fix: skip detail generation for primitive types and literals * fix: goimports * fix: trim doc string * fix: use go/doc/comment as markdown generator * fix: avoid copy of *token.FileSet --- Makefile | 6 +- go.mod | 1 + go.sum | 2 + internal/builtin/builtin_gen.go | 323 ++++++++++++++++++++++++++++++ internal/builtin/lookup.go | 47 +++++ internal/lsp/check.go | 6 +- internal/lsp/completion.go | 14 +- internal/lsp/definition.go | 6 +- internal/lsp/hover.go | 6 +- tools/codegen-builtins/collect.go | 284 ++++++++++++++++++++++++++ tools/codegen-builtins/config.go | 186 +++++++++++++++++ tools/codegen-builtins/docfmt.go | 29 +++ tools/codegen-builtins/main.go | 102 ++++++++++ tools/codegen-builtins/parse.go | 64 ++++++ tools/codegen-builtins/write.go | 151 ++++++++++++++ tools/gendata/README.md | 5 + tools/gendata/builtin.go.txt | 221 ++++++++++++++++++++ 17 files changed, 1438 insertions(+), 15 deletions(-) create mode 100644 internal/builtin/builtin_gen.go create mode 100644 internal/builtin/lookup.go create mode 100644 tools/codegen-builtins/collect.go create mode 100644 tools/codegen-builtins/config.go create mode 100644 tools/codegen-builtins/docfmt.go create mode 100644 tools/codegen-builtins/main.go create mode 100644 tools/codegen-builtins/parse.go create mode 100644 tools/codegen-builtins/write.go create mode 100644 tools/gendata/README.md create mode 100644 tools/gendata/builtin.go.txt diff --git a/Makefile b/Makefile index 2ec3788..f431db3 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PROJECT_NAME = gnopls BUILD_FLAGS = -mod=readonly -ldflags='$(LD_FLAGS)' BUILD_FOLDER = ./build -.PHONY: install build clean +.PHONY: install build clean gen ## install: Install the binary. install: @@ -22,3 +22,7 @@ clean: @echo Cleaning build cache... @-rm -rf $(BUILD_FOLDER) 2> /dev/null @go clean ./... + +## gen: runs "go:generate" across all Go files +gen: + @find . -name '*.go' -print0 | xargs -0 grep -l '//go:generate' | xargs -I {} go generate {} diff --git a/go.mod b/go.mod index b9f600d..ce272aa 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/dave/jennifer v1.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dgraph-io/badger/v3 v3.2103.4 // indirect diff --git a/go.sum b/go.sum index 4ba0727..3e9bf68 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,8 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/dave/jennifer v1.7.0 h1:uRbSBH9UTS64yXbh4FrMHfgfY762RD+C7bUPKODpSJE= +github.com/dave/jennifer v1.7.0/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/internal/builtin/builtin_gen.go b/internal/builtin/builtin_gen.go new file mode 100644 index 0000000..d5b1c30 --- /dev/null +++ b/internal/builtin/builtin_gen.go @@ -0,0 +1,323 @@ +// This file was generated by codegen-builtins, DO NOT EDIT! +// See: /tools/codegen-builtins +// +// Source: ../../tools/gendata/builtin.go.txt +// Skipped: ComplexType,Type,Type1,IntegerType,FloatType + +package builtin + +import "go.lsp.dev/protocol" + +// buckets contains list of completions for builtin Gno functions grouped by a first letter. +var buckets = map[rune][]protocol.CompletionItem{ + int32(100): []protocol.CompletionItem{protocol.CompletionItem{ + Detail: "func delete(m map[Type]Type1, key Type)", + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "The delete built-in function deletes the element with the specified key (m\\[key]) from the map. If m is nil or there is no such element, delete is a no-op.", + }, + InsertText: "delete()", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindFunction, + Label: "delete", + }}, + int32(101): []protocol.CompletionItem{protocol.CompletionItem{ + Detail: "type error interface {\n\tError() string\n}", + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "The error built-in interface type is the conventional interface for representing an error condition, with the nil value representing no error.", + }, + InsertText: "error", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindInterface, + Label: "error", + }}, + int32(102): []protocol.CompletionItem{protocol.CompletionItem{ + InsertText: "false", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindConstant, + Label: "false", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "float32 is the set of all IEEE-754 32-bit floating-point numbers.", + }, + InsertText: "float32", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "float32", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "float64 is the set of all IEEE-754 64-bit floating-point numbers.", + }, + InsertText: "float64", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "float64", + }}, + int32(105): []protocol.CompletionItem{protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "int8 is the set of all signed 8-bit integers. Range: -128 through 127.", + }, + InsertText: "int8", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "int8", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "int16 is the set of all signed 16-bit integers. Range: -32768 through 32767.", + }, + InsertText: "int16", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "int16", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "int32 is the set of all signed 32-bit integers. Range: -2147483648 through 2147483647.", + }, + InsertText: "int32", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "int32", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "int64 is the set of all signed 64-bit integers. Range: -9223372036854775808 through 9223372036854775807.", + }, + InsertText: "int64", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "int64", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "int is a signed integer type that is at least 32 bits in size. It is a distinct type, however, and not an alias for, say, int32.", + }, + InsertText: "int", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "int", + }, protocol.CompletionItem{ + Detail: "const iota = 0", + InsertText: "iota", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindConstant, + Label: "iota", + }}, + int32(108): []protocol.CompletionItem{protocol.CompletionItem{ + Detail: "func len(v Type) int", + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "The len built-in function returns the length of v, according to its type:\n\n\tArray: the number of elements in v.\n\tPointer to array: the number of elements in *v (even if v is nil).\n\tSlice, or map: the number of elements in v; if v is nil, len(v) is zero.\n\tString: the number of bytes in v.\n\nFor some arguments, such as a string literal or a simple array expression, the result can be a constant.", + }, + InsertText: "len()", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindFunction, + Label: "len", + }}, + int32(109): []protocol.CompletionItem{protocol.CompletionItem{ + Detail: "func make(t Type, size ...IntegerType) Type", + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "The make built-in function allocates and initializes an object of type slice, map, or chan (only). Like new, the first argument is a type, not a value. Unlike new, make's return type is the same as the type of its argument, not a pointer to it. The specification of the result depends on the type:\n\n\tSlice: The size specifies the length. The capacity of the slice is\n\tequal to its length. A second integer argument may be provided to\n\tspecify a different capacity; it must be no smaller than the\n\tlength. For example, make([]int, 0, 10) allocates an underlying array\n\tof size 10 and returns a slice of length 0 and capacity 10 that is\n\tbacked by this underlying array.\n\tMap: An empty map is allocated with enough space to hold the\n\tspecified number of elements. The size may be omitted, in which case\n\ta small starting size is allocated.", + }, + InsertText: "make()", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindFunction, + Label: "make", + }}, + int32(110): []protocol.CompletionItem{protocol.CompletionItem{ + Detail: "func new(Type) *Type", + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "The new built-in function allocates memory. The first argument is a type, not a value, and the value returned is a pointer to a newly allocated zero value of that type.", + }, + InsertText: "new()", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindFunction, + Label: "new", + }, protocol.CompletionItem{ + Detail: "var nil Type", + InsertText: "nil", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindVariable, + Label: "nil", + }}, + int32(112): []protocol.CompletionItem{protocol.CompletionItem{ + Detail: "func panic(v interface{})", + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "The panic built-in function stops normal execution of the program. Any functions whose execution was deferred by F are run in the usual way, and then F returns to its caller.\n\nAt that point, the program is terminated with a non-zero exit code. This termination sequence is called panicking and can be controlled by the built-in function recover.", + }, + InsertText: "panic()", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindFunction, + Label: "panic", + }, protocol.CompletionItem{ + Detail: "func print(args ...Type)", + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "The print built-in function formats its arguments in an implementation-specific way and writes the result to standard error. Print is useful for bootstrapping and debugging; it is not guaranteed to stay in the language.", + }, + InsertText: "print()", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindFunction, + Label: "print", + }, protocol.CompletionItem{ + Detail: "func println(args ...Type)", + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "The println built-in function formats its arguments in an implementation-specific way and writes the result to standard error. Spaces are always added between arguments and a newline is appended. Println is useful for bootstrapping and debugging; it is not guaranteed to stay in the language.", + }, + InsertText: "println()", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindFunction, + Label: "println", + }}, + int32(114): []protocol.CompletionItem{protocol.CompletionItem{ + Detail: "func recover() interface{}", + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "The recover built-in function allows a program to manage behavior of a panicking goroutine. Executing a call to recover inside a deferred function (but not any function called by it) stops the panicking sequence by restoring normal execution and retrieves the error value passed to the call of panic. If recover is called outside the deferred function it will not stop a panicking sequence. In this case, or when the program is not panicking, recover returns nil.", + }, + InsertText: "recover()", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindFunction, + Label: "recover", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "rune is an alias for int32 and is equivalent to int32 in all ways. It is used, by convention, to distinguish character values from integer values.", + }, + InsertText: "rune", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "rune", + }}, + int32(115): []protocol.CompletionItem{protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. A string may be empty, but not nil. Values of string type are immutable.", + }, + InsertText: "string", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "string", + }}, + int32(116): []protocol.CompletionItem{protocol.CompletionItem{ + InsertText: "true", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindConstant, + Label: "true", + }}, + int32(117): []protocol.CompletionItem{protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "uint8 is the set of all unsigned 8-bit integers. Range: 0 through 255.", + }, + InsertText: "uint8", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "uint8", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "uint16 is the set of all unsigned 16-bit integers. Range: 0 through 65535.", + }, + InsertText: "uint16", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "uint16", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "uint32 is the set of all unsigned 32-bit integers. Range: 0 through 4294967295.", + }, + InsertText: "uint32", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "uint32", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "uint64 is the set of all unsigned 64-bit integers. Range: 0 through 18446744073709551615.", + }, + InsertText: "uint64", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "uint64", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "uint is an unsigned integer type that is at least 32 bits in size. It is a distinct type, however, and not an alias for, say, uint32.", + }, + InsertText: "uint", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "uint", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "uintptr is an integer type that is large enough to hold the bit pattern of any pointer.", + }, + InsertText: "uintptr", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "uintptr", + }}, + int32(97): []protocol.CompletionItem{protocol.CompletionItem{ + Detail: "func append(slice []Type, elems ...Type) []Type", + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "The append built-in function appends elements to the end of a slice. If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not, a new underlying array will be allocated. Append returns the updated slice. It is therefore necessary to store the result of append, often in the variable holding the slice itself:\n\n\tslice = append(slice, elem1, elem2)\n\tslice = append(slice, anotherSlice...)\n\nAs a special case, it is legal to append a string to a byte slice, like this:\n\n\tslice = append([]byte(\"hello \"), \"world\"...)", + }, + InsertText: "append()", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindFunction, + Label: "append", + }}, + int32(98): []protocol.CompletionItem{protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "bool is the set of boolean values, true and false.", + }, + InsertText: "bool", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "bool", + }, protocol.CompletionItem{ + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "byte is an alias for uint8 and is equivalent to uint8 in all ways. It is used, by convention, to distinguish byte values from 8-bit unsigned integer values.", + }, + InsertText: "byte", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindClass, + Label: "byte", + }}, + int32(99): []protocol.CompletionItem{protocol.CompletionItem{ + Detail: "func copy(dst, src []Type) int", + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "The copy built-in function copies elements from a source slice into a destination slice. (As a special case, it also will copy bytes from a string to a slice of bytes.) The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum of len(src) and len(dst).", + }, + InsertText: "copy()", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindFunction, + Label: "copy", + }, protocol.CompletionItem{ + Detail: "func cap(v Type) int", + Documentation: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: "The cap built-in function returns the capacity of v, according to its type:\n\n\tArray: the number of elements in v (same as len(v)).\n\tPointer to array: the number of elements in *v (same as len(v)).\n\tSlice: the maximum length the slice can reach when resliced;\n\tif v is nil, cap(v) is zero.\n\nFor some arguments, such as a simple array expression, the result can be a constant.", + }, + InsertText: "cap()", + InsertTextFormat: protocol.InsertTextFormatPlainText, + Kind: protocol.CompletionItemKindFunction, + Label: "cap", + }}, +} diff --git a/internal/builtin/lookup.go b/internal/builtin/lookup.go new file mode 100644 index 0000000..7a032da --- /dev/null +++ b/internal/builtin/lookup.go @@ -0,0 +1,47 @@ +package builtin + +import ( + "strings" + + "go.lsp.dev/protocol" +) + +//go:generate go run ../../tools/codegen-builtins -src ../../tools/gendata/builtin.go.txt -dest ./builtin_gen.go -omit Type,Type1,IntegerType,FloatType,ComplexType + +// GetCompletions provides list of builtin symbols that has passed prefix in a name. +func GetCompletions(prefix string) []protocol.CompletionItem { + prefix = strings.TrimSpace(prefix) + if prefix == "" { + return nil + } + + key, remainder := getBucketKey(prefix) + bucket, ok := buckets[key] + if !ok { + return nil + } + + if remainder == 0 { + return bucket + } + + // most buckets contain only single value + if len(bucket) == 1 && strings.HasPrefix(bucket[0].Label, prefix) { + return bucket + } + + var items []protocol.CompletionItem + for _, item := range bucket { + // TODO: use fuzzy find? + if strings.HasPrefix(item.Label, prefix) { + items = append(items, item) + } + } + + return items +} + +func getBucketKey(str string) (rune, int) { + runes := []rune(str) + return runes[0], len(runes) - 1 +} diff --git a/internal/lsp/check.go b/internal/lsp/check.go index 505b227..e354c60 100644 --- a/internal/lsp/check.go +++ b/internal/lsp/check.go @@ -193,7 +193,7 @@ func (tcr *TypeCheckResult) Errors() []ErrorInfo { // Prints types.Info in a tabular form // Kept only for debugging purpose. -func formatTypeInfo(fset token.FileSet, info *types.Info) string { +func formatTypeInfo(fset *token.FileSet, info *types.Info) string { var items []string = nil for expr, tv := range info.Types { var buf strings.Builder @@ -215,7 +215,7 @@ func formatTypeInfo(fset token.FileSet, info *types.Info) string { // Prints types.Info in a tabular form // Kept only for debugging purpose. func getTypeAndValue( - fset token.FileSet, + fset *token.FileSet, info *types.Info, tok string, line, offset int, @@ -248,7 +248,7 @@ func getTypeAndValue( // Use getTypeAndValue instead // TODO: should be removed func getTypeAndValueLight( - fset token.FileSet, + fset *token.FileSet, info *types.Info, tok string, line int, diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go index c1f0ff0..265a857 100644 --- a/internal/lsp/completion.go +++ b/internal/lsp/completion.go @@ -18,6 +18,7 @@ import ( "unicode" "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gnopls/internal/builtin" cmap "github.com/orcaman/concurrent-map/v2" "go.lsp.dev/jsonrpc2" "go.lsp.dev/protocol" @@ -199,7 +200,7 @@ func (s *server) Completion(ctx context.Context, reply jsonrpc2.Replier, req jso switch n := paths[0].(type) { case *ast.Ident: _, tv := getTypeAndValue( - *pgf.Fset, + pgf.Fset, pkg.TypeCheckResult.info, n.Name, int(line), offset, @@ -267,7 +268,7 @@ func (s *server) Completion(ctx context.Context, reply jsonrpc2.Replier, req jso return reply(ctx, nil, nil) case *ast.CallExpr: _, tv := getTypeAndValue( - *pgf.Fset, + pgf.Fset, pkg.TypeCheckResult.info, types.ExprString(n), int(line), offset, @@ -330,6 +331,10 @@ func (s *server) Completion(ctx context.Context, reply jsonrpc2.Replier, req jso } func completionPackageIdent(ctx context.Context, s *server, reply jsonrpc2.Replier, params protocol.CompletionParams, pgf *ParsedGnoFile, i *ast.Ident, includeFuncs bool) error { + // This function is called not just for packages but also as fallback for unresolved cases. + // So, let's also propose builtins first. + items := builtin.GetCompletions(i.Name) + for _, spec := range pgf.File.Imports { path := spec.Path.Value[1 : len(spec.Path.Value)-1] parts := strings.Split(path, "/") @@ -337,7 +342,6 @@ func completionPackageIdent(ctx context.Context, s *server, reply jsonrpc2.Repli if last == i.Name { pkg := s.completionStore.lookupPkg(last) if pkg != nil { - items := []protocol.CompletionItem{} if includeFuncs { for _, f := range pkg.Functions { if !f.IsExported() { @@ -367,12 +371,12 @@ func completionPackageIdent(ctx context.Context, s *server, reply jsonrpc2.Repli Documentation: s.Doc, }) } - return reply(ctx, items, nil) + break } } } - return reply(ctx, nil, nil) + return reply(ctx, items, nil) } // End diff --git a/internal/lsp/definition.go b/internal/lsp/definition.go index da64c23..e782f67 100644 --- a/internal/lsp/definition.go +++ b/internal/lsp/definition.go @@ -81,7 +81,7 @@ func (s *server) Definition(ctx context.Context, reply jsonrpc2.Replier, req jso switch n := paths[0].(type) { case *ast.Ident: _, tv := getTypeAndValue( - *pkg.TypeCheckResult.fset, + pkg.TypeCheckResult.fset, info, n.Name, int(line), offset, @@ -138,7 +138,7 @@ func definitionSelectorExpr(ctx context.Context, s *server, reply jsonrpc2.Repli parentStr := types.ExprString(parent) _, tv := getTypeAndValueLight( - *pkg.TypeCheckResult.fset, + pkg.TypeCheckResult.fset, pkg.TypeCheckResult.info, exprStr, int(line), @@ -149,7 +149,7 @@ func definitionSelectorExpr(ctx context.Context, s *server, reply jsonrpc2.Repli tvStr := tv.Type.String() _, tvParent := getTypeAndValueLight( - *pkg.TypeCheckResult.fset, + pkg.TypeCheckResult.fset, pkg.TypeCheckResult.info, parentStr, int(line), diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go index e9726a1..fcc6d7a 100644 --- a/internal/lsp/hover.go +++ b/internal/lsp/hover.go @@ -70,7 +70,7 @@ func (s *server) Hover(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2 switch n := paths[0].(type) { case *ast.Ident: _, tv := getTypeAndValue( - *pkg.TypeCheckResult.fset, + pkg.TypeCheckResult.fset, info, n.Name, int(line), offset, @@ -164,7 +164,7 @@ func hoverSelectorExpr(ctx context.Context, s *server, reply jsonrpc2.Replier, p parentStr := types.ExprString(parent) _, tv := getTypeAndValueLight( - *pkg.TypeCheckResult.fset, + pkg.TypeCheckResult.fset, pkg.TypeCheckResult.info, exprStr, int(line), @@ -175,7 +175,7 @@ func hoverSelectorExpr(ctx context.Context, s *server, reply jsonrpc2.Replier, p tvStr := tv.Type.String() _, tvParent := getTypeAndValueLight( - *pkg.TypeCheckResult.fset, + pkg.TypeCheckResult.fset, pkg.TypeCheckResult.info, parentStr, int(line), diff --git a/tools/codegen-builtins/collect.go b/tools/codegen-builtins/collect.go new file mode 100644 index 0000000..11187a3 --- /dev/null +++ b/tools/codegen-builtins/collect.go @@ -0,0 +1,284 @@ +package main + +import ( + "fmt" + "go/ast" + "go/printer" + "go/token" + "reflect" + "strconv" + "strings" + + "go.lsp.dev/protocol" +) + +const stringBuffSize = 128 + +var ( + astDocFields = []string{"Doc", "Comment"} + commentBlockType = reflect.TypeOf((*ast.CommentGroup)(nil)) + + token2KindMapping = map[token.Token]protocol.CompletionItemKind{ + token.VAR: protocol.CompletionItemKindVariable, + token.CONST: protocol.CompletionItemKindConstant, + token.TYPE: protocol.CompletionItemKindClass, + } +) + +func declToCompletionItem(fset *token.FileSet, filter Filter, specGroup *ast.GenDecl) ([]protocol.CompletionItem, error) { + if len(specGroup.Specs) == 0 { + return nil, nil + } + + blockKind, ok := token2KindMapping[specGroup.Tok] + if !ok { + return nil, fmt.Errorf("unsupported declaration token %q", specGroup.Tok) + } + + // single-spec block declaration have documentation at the root of a block. + // multi-spec blocks should use per-spec doc property. + isDeclGroup := len(specGroup.Specs) > 1 + + ctx := ParentCtx{ + decl: specGroup, + tokenKind: blockKind, + isDeclGroup: isDeclGroup, + filter: filter, + } + + completions := make([]protocol.CompletionItem, 0, len(specGroup.Specs)) + for _, spec := range specGroup.Specs { + switch t := spec.(type) { + case *ast.TypeSpec: + if !filter.allow(t.Name.String()) { + continue + } + + item, err := typeToCompletionItem(fset, ctx, t) + if err != nil { + return nil, err + } + + completions = append(completions, item) + case *ast.ValueSpec: + items, err := valueToCompletionItem(fset, ctx, t) + if err != nil { + return nil, err + } + + if len(items) == 0 { + continue + } + + completions = append(completions, items...) + default: + return nil, fmt.Errorf("unsupported declaration type %T", t) + } + } + return completions, nil +} + +type ParentCtx struct { + decl *ast.GenDecl + tokenKind protocol.CompletionItemKind + isDeclGroup bool + filter Filter +} + +func typeToCompletionItem(fset *token.FileSet, ctx ParentCtx, spec *ast.TypeSpec) (protocol.CompletionItem, error) { + declCommentGroup := spec.Comment + item := protocol.CompletionItem{ + Kind: ctx.tokenKind, + Label: spec.Name.Name, + InsertText: spec.Name.Name, + InsertTextFormat: protocol.InsertTextFormatPlainText, + } + + isPrimitive := false + switch spec.Type.(type) { + case *ast.InterfaceType: + item.Kind = protocol.CompletionItemKindInterface + case *ast.StructType: + item.InsertText = item.InsertText + "{}" + item.Kind = protocol.CompletionItemKindStruct + case *ast.Ident: + isPrimitive = true + } + + if !ctx.isDeclGroup { + declCommentGroup = ctx.decl.Doc + } + + if !isPrimitive { + signature, err := typeToString(fset, ctx.decl) + if err != nil { + return item, fmt.Errorf("%w (type: %q)", err, item.Label) + } + + item.Detail = signature + } + + item.Documentation = parseDocGroup(declCommentGroup) + return item, nil +} + +func valueToCompletionItem(fset *token.FileSet, ctx ParentCtx, spec *ast.ValueSpec) ([]protocol.CompletionItem, error) { + var blockDoc *protocol.MarkupContent + if !ctx.isDeclGroup { + blockDoc = parseDocGroup(spec.Doc) + } + + items := make([]protocol.CompletionItem, 0, len(spec.Values)) + for _, val := range spec.Names { + if !ctx.filter.allow(val.Name) { + continue + } + + item := protocol.CompletionItem{ + Kind: ctx.tokenKind, + Label: val.Name, + InsertText: val.Name, + InsertTextFormat: protocol.InsertTextFormatPlainText, + Documentation: blockDoc, + } + + switch val.Name { + case "true", "false": + default: + signature, err := typeToString(fset, val.Obj.Decl) + if err != nil { + return nil, fmt.Errorf("%w (value name: %s)", err, val.Name) + } + + // declaration type is not present in value block. + if signature != "" { + signature = ctx.decl.Tok.String() + " " + signature + } + + item.Detail = signature + } + + items = append(items, item) + } + + return items, nil +} + +func funcToCompletionItem(fset *token.FileSet, format protocol.InsertTextFormat, fn *ast.FuncDecl) (item protocol.CompletionItem, err error) { + isSnippet := format == protocol.InsertTextFormatSnippet + item = protocol.CompletionItem{ + Label: fn.Name.String(), + Kind: protocol.CompletionItemKindFunction, + InsertTextFormat: format, + InsertText: buildFuncInsertStatement(fn, isSnippet), + Documentation: parseDocGroup(fn.Doc), + } + + item.Detail, err = typeToString(fset, fn) + if err != nil { + return item, err + } + + return item, nil +} + +func buildFuncInsertStatement(decl *ast.FuncDecl, asSnippet bool) string { + if !asSnippet { + return decl.Name.String() + "()" + } + + // snippet offsets start at 1 + offset := 1 + + typ := decl.Type + sb := new(strings.Builder) + sb.Grow(stringBuffSize) + sb.WriteString(decl.Name.String()) + offset = writeTypeParams(sb, offset, typ.TypeParams) + sb.WriteString("(") + writeParamsList(sb, offset, typ.Params) + sb.WriteString(")") + return sb.String() +} + +func writeTypeParams(sb *strings.Builder, snippetOffset int, typeParams *ast.FieldList) int { + if typeParams == nil || len(typeParams.List) == 0 { + return snippetOffset + } + + sb.WriteRune('[') + offset := writeParamsList(sb, snippetOffset, typeParams) + sb.WriteRune(']') + return offset +} + +func writeParamsList(sb *strings.Builder, snippetOffset int, params *ast.FieldList) int { + if params == nil || len(params.List) == 0 { + return snippetOffset + } + + offset := snippetOffset + for i, arg := range params.List { + if i > 0 { + sb.WriteString(", ") + } + + for j, n := range arg.Names { + if j > 0 { + sb.WriteString(", ") + } + + sb.WriteString("${") + sb.WriteString(strconv.Itoa(offset)) + sb.WriteRune(':') + sb.WriteString(n.String()) + sb.WriteRune('}') + offset++ + } + } + + return offset +} + +func typeToString(fset *token.FileSet, decl any) (string, error) { + // Remove comments block from AST node to keep only node body + trimmedDecl := trimCommentBlock(decl) + + sb := new(strings.Builder) + sb.Grow(stringBuffSize) + err := printer.Fprint(sb, fset, trimmedDecl) + if err != nil { + return "", fmt.Errorf("can't generate type signature out of AST node %T: %w", trimmedDecl, err) + } + + return sb.String(), nil +} + +func trimCommentBlock(decl any) any { + val := reflect.ValueOf(decl) + isPtr := val.Kind() == reflect.Pointer + if isPtr { + val = val.Elem() + } + if val.Kind() != reflect.Struct { + return decl + } + + dst := reflect.New(val.Type()).Elem() + dst.Set(val) + + // *ast.FuncDecl, *ast.Object have Doc + // *ast.Object and *ast.Indent might have Comment + for _, fieldName := range astDocFields { + field, ok := val.Type().FieldByName(fieldName) + if ok && field.Type.AssignableTo(commentBlockType) { + dst.FieldByIndex(field.Index).SetZero() + } + } + + if isPtr { + dst = dst.Addr() + } + + return dst.Interface() +} diff --git a/tools/codegen-builtins/config.go b/tools/codegen-builtins/config.go new file mode 100644 index 0000000..f56011a --- /dev/null +++ b/tools/codegen-builtins/config.go @@ -0,0 +1,186 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "os" + "path/filepath" + "strings" +) + +type StringSet map[string]struct{} + +func (set StringSet) toList() []string { + if len(set) == 0 { + return nil + } + + list := make([]string, 0, len(set)) + for val := range set { + list = append(list, val) + } + + return list +} + +type Filter interface { + allow(v string) bool +} + +type NopFilter struct{} + +func (_ NopFilter) allow(_ string) bool { + return true +} + +type DictFilter struct { + isIgnoreList bool + dict StringSet +} + +func (f DictFilter) allow(v string) bool { + _, ok := f.dict[v] + if f.isIgnoreList { + ok = !ok + } + + return ok +} + +type Params struct { + // OutFile is destination where generated Go file will be written. + OutFile string + + // OutPackageName is package name specified in output Go file. + OutPackageName string + + // SourceFile is source Go file which contains predefined symbols declarations. + SourceFile string + + // Omit is list of symbols to skip. Opposite to Pick. + Omit StringSet + + // Pick is a list of symbols to process. Opposite to Omit. + Pick StringSet + + // EnableInsertSnippets enables snippet syntax for insertions in protocol.CompletionItem.InsertText. + // + // See: protocol.InsertTextFormat + EnableInsertSnippets bool +} + +func (p Params) GenContext() GenContext { + return GenContext{ + PackageName: p.OutPackageName, + SourcePath: p.SourceFile, + OmitSymbols: p.Omit.toList(), + PickSymbols: p.Pick.toList(), + } +} + +func (p Params) withDefaults() (Params, error) { + if p.OutFile == "" || p.OutPackageName != "" { + return p, nil + } + + absPath, err := filepath.Abs(p.OutFile) + if err != nil { + return p, fmt.Errorf("can't resolve absolute path: %w", err) + } + + p.OutFile = absPath + p.OutPackageName = filepath.Base(filepath.Dir(absPath)) + if p.OutPackageName == "" { + p.OutPackageName = "builtin" + } + + return p, nil +} + +func (p Params) validate() error { + if p.SourceFile == "" { + return errors.New("missing source file") + } + + if p.OutFile == "" { + return errors.New("missing destination file name") + } + + if _, err := os.Stat(p.SourceFile); err != nil { + return fmt.Errorf("source file %q doesn't exist: %w", p.SourceFile, err) + } + + if len(p.Omit) > 0 && len(p.Pick) > 0 { + return errors.New("can't use both -pick and -omit flags together") + } + + return nil +} + +func (p Params) getFilter() Filter { + if len(p.Omit) > 0 { + return DictFilter{ + isIgnoreList: true, + dict: p.Omit, + } + } + + if len(p.Pick) > 0 { + return DictFilter{ + dict: p.Pick, + } + } + + return NopFilter{} +} + +func paramsFromFlags() (Params, error) { + var params Params + flag.StringVar( + ¶ms.OutFile, "dest", "", + "Destination file name where generated Go file will be written.", + ) + flag.StringVar( + ¶ms.OutPackageName, "pkg", "", + "Package name specified in output Go file. By default is parent dir name.", + ) + flag.StringVar( + ¶ms.SourceFile, "src", "", + "Source Go file name with builtin definitions.", + ) + flag.BoolVar( + ¶ms.EnableInsertSnippets, "enable-snippets", false, + "Enables snippet syntax in InsertText. "+ + "Might not be supported by other LSP clients so use this with caution.", + ) + + var pickList, omitList string + flag.StringVar( + &pickList, "pick", "", + "Comma-separated list of symbols to process. Any other symbols will be ignored.", + ) + flag.StringVar( + &omitList, "omit", "", + "Comma-separated list of symbols to skip. Opposite to -pick flag.", + ) + + flag.Parse() + + params.Omit = setFromCSV(omitList) + params.Pick = setFromCSV(pickList) + return params.withDefaults() +} + +func setFromCSV(str string) StringSet { + if str == "" { + return nil + } + + dst := make(StringSet) + for _, elem := range strings.Split(str, ",") { + dst[strings.TrimSpace(elem)] = struct{}{} + } + + return dst +} diff --git a/tools/codegen-builtins/docfmt.go b/tools/codegen-builtins/docfmt.go new file mode 100644 index 0000000..0d0772e --- /dev/null +++ b/tools/codegen-builtins/docfmt.go @@ -0,0 +1,29 @@ +package main + +import ( + "bytes" + "go/ast" + "go/doc/comment" + + "go.lsp.dev/protocol" +) + +func parseDocGroup(group *ast.CommentGroup) *protocol.MarkupContent { + if group == nil || len(group.List) == 0 { + return nil + } + + var ( + parser comment.Parser + printer comment.Printer + ) + + str := group.Text() + parsedDoc := parser.Parse(str) + mdDoc := printer.Markdown(parsedDoc) + mdDoc = bytes.TrimSuffix(mdDoc, []byte("\n")) + return &protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: string(mdDoc), + } +} diff --git a/tools/codegen-builtins/main.go b/tools/codegen-builtins/main.go new file mode 100644 index 0000000..b3cdd30 --- /dev/null +++ b/tools/codegen-builtins/main.go @@ -0,0 +1,102 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + + "go.lsp.dev/protocol" +) + +func main() { + if err := mainErr(); err != nil { + log.Fatal("ERROR: ", err) + } +} + +func mainErr() error { + usageFunc := flag.Usage + flag.Usage = func() { + fmt.Print("codegen-builtin - Generates completion items for intrinsic Gno functions.\n\n") + usageFunc() + } + + params, err := paramsFromFlags() + if err != nil { + return err + } + + if err := params.validate(); err != nil { + return err + } + + return run(params) +} + +func run(p Params) (err error) { + pkg, err := loadPackage(p) + if err != nil { + return err + } + + items, err := collectCompletionItems(pkg) + if err != nil { + return err + } + + return saveFile(p, items) +} + +func collectCompletionItems(pkg PackageContext) ([]protocol.CompletionItem, error) { + symbols := make([]protocol.CompletionItem, 0, len(pkg.funcs)+len(pkg.decls)) + + for _, fn := range pkg.funcs { + if !pkg.filter.allow(fn.Name.Name) { + continue + } + + item, err := funcToCompletionItem(pkg.fset, pkg.insertTextFormat, fn) + if err != nil { + return nil, fmt.Errorf("failed to process func %q: %w", fn.Name, err) + } + + symbols = append(symbols, item) + } + + for _, typeDecl := range pkg.decls { + items, err := declToCompletionItem(pkg.fset, pkg.filter, typeDecl) + if err != nil { + return nil, err + } + + symbols = append(symbols, items...) + } + + return symbols, nil +} + +func saveFile(p Params, items []protocol.CompletionItem) error { + err := os.MkdirAll(filepath.Dir(p.OutFile), 0755) + if err != nil { + return fmt.Errorf("failed to create output parent directory: %w", err) + } + + src, err := generateSourceFile(p.GenContext(), items) + if err != nil { + return fmt.Errorf("cannot generate Go file: %w", err) + } + + f, err := os.OpenFile(p.OutFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("failed to create output file: %w", err) + } + + defer f.Close() + if err := src.Render(f); err != nil { + return fmt.Errorf("failed to write to file: %w", err) + } + + return nil +} diff --git a/tools/codegen-builtins/parse.go b/tools/codegen-builtins/parse.go new file mode 100644 index 0000000..218780f --- /dev/null +++ b/tools/codegen-builtins/parse.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + + "go.lsp.dev/protocol" +) + +type PackageContext struct { + fset *token.FileSet + decls []*ast.GenDecl + funcs []*ast.FuncDecl + insertTextFormat protocol.InsertTextFormat + filter Filter +} + +func loadPackage(p Params) (PackageContext, error) { + fset := token.NewFileSet() + ctx := PackageContext{ + fset: fset, + filter: p.getFilter(), + insertTextFormat: protocol.InsertTextFormatPlainText, + } + + if p.EnableInsertSnippets { + ctx.insertTextFormat = protocol.InsertTextFormatSnippet + } + + root, err := parser.ParseFile(fset, p.SourceFile, nil, parser.ParseComments) + if err != nil { + return ctx, fmt.Errorf("can't parse source Go file: %w", err) + } + + // Usually, "go/doc" can be used to collect funcs and type info but for some reason + // it ignores most of declared functions in "builin.go" + // + // Also "go/doc" requires to parse a whole directory, but we should be able to deal + // with non *.go files to keep them away from "go build". + if err := readFile(&ctx, root); err != nil { + return ctx, err + } + + return ctx, nil +} + +func readFile(dst *PackageContext, root *ast.File) error { + for _, decl := range root.Decls { + switch t := decl.(type) { + case *ast.FuncDecl: + dst.funcs = append(dst.funcs, t) + case *ast.GenDecl: + if t.Tok != token.IMPORT { + dst.decls = append(dst.decls, t) + } + default: + return fmt.Errorf("unsupported block type %T at %d", t, t.Pos()) + } + } + + return nil +} diff --git a/tools/codegen-builtins/write.go b/tools/codegen-builtins/write.go new file mode 100644 index 0000000..d830f86 --- /dev/null +++ b/tools/codegen-builtins/write.go @@ -0,0 +1,151 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/dave/jennifer/jen" + "go.lsp.dev/protocol" +) + +const ( + protoPkg = "go.lsp.dev/protocol" + completionItemType = "CompletionItem" +) + +type GenContext struct { + PackageName string + SourcePath string + OmitSymbols []string + PickSymbols []string +} + +// generateSourceFile constructs a Go source file with map that contains completion items. +func generateSourceFile(genCtx GenContext, src []protocol.CompletionItem) (*jen.File, error) { + f := jen.NewFile(genCtx.PackageName) + f.HeaderComment("This file was generated by codegen-builtins, DO NOT EDIT!") + f.HeaderComment("See: /tools/codegen-builtins") + f.HeaderComment("") + f.HeaderComment("Source: " + genCtx.SourcePath) + if len(genCtx.PickSymbols) > 0 { + f.HeaderComment("Picked: " + strings.Join(genCtx.PickSymbols, ",")) + } + if len(genCtx.OmitSymbols) > 0 { + f.HeaderComment("Skipped: " + strings.Join(genCtx.OmitSymbols, ",")) + } + + // disables named import caused by jen.Qual(). + f.ImportName(protoPkg, "protocol") + + bucketStmt, err := buildBucketMapStatement(src) + if err != nil { + return nil, err + } + + f.Comment("buckets contains list of completions for builtin Gno functions grouped by a first letter.") + f.Var().Id("buckets").Op("=").Map(jen.Rune()).Index().Qual(protoPkg, completionItemType).Values(bucketStmt) + return f, nil +} + +func buildBucketMapStatement(src []protocol.CompletionItem) (jen.Dict, error) { + grouped := make(map[rune][]jen.Code) + for _, item := range src { + key := []rune(item.Label)[0] + stmt, err := completionItemToStatement(item) + if err != nil { + return nil, err + } + + grouped[key] = append(grouped[key], stmt) + } + + dict := jen.Dict{} + for key, entries := range grouped { + lit := jen.Lit(key) + dict[lit] = jen.Index().Qual(protoPkg, completionItemType).Values(entries...) + } + + return dict, nil +} + +func markupContentToStatement(doc protocol.MarkupContent) *jen.Statement { + return jen.Qual(protoPkg, "MarkupContent").Values(jen.Dict{ + jen.Id("Kind"): jen.Qual(protoPkg, "Markdown"), + jen.Id("Value"): jen.Lit(doc.Value), + }) +} + +func completionItemToStatement(item protocol.CompletionItem) (jen.Code, error) { + var docStmt *jen.Statement + switch doc := item.Documentation.(type) { + case protocol.MarkupContent: + docStmt = markupContentToStatement(doc) + case *protocol.MarkupContent: + if doc != nil { + docStmt = markupContentToStatement(*doc) + } + case string: + docStmt = jen.Lit(doc) + default: + return nil, fmt.Errorf("unexpected documentation field type: %#v", doc) + } + + var insertTextFormatStmt *jen.Statement + switch v := item.InsertTextFormat; v { + case protocol.InsertTextFormatPlainText, + protocol.InsertTextFormatSnippet: + constId := "InsertTextFormat" + v.String() + insertTextFormatStmt = jen.Qual(protoPkg, constId) + default: + insertTextFormatStmt = jen.Lit(float64(v)) + } + + var itemKindStmt *jen.Statement + switch item.Kind { + case + protocol.CompletionItemKindText, + protocol.CompletionItemKindMethod, + protocol.CompletionItemKindFunction, + protocol.CompletionItemKindConstructor, + protocol.CompletionItemKindField, + protocol.CompletionItemKindVariable, + protocol.CompletionItemKindClass, + protocol.CompletionItemKindInterface, + protocol.CompletionItemKindModule, + protocol.CompletionItemKindProperty, + protocol.CompletionItemKindUnit, + protocol.CompletionItemKindValue, + protocol.CompletionItemKindEnum, + protocol.CompletionItemKindKeyword, + protocol.CompletionItemKindSnippet, + protocol.CompletionItemKindColor, + protocol.CompletionItemKindFile, + protocol.CompletionItemKindReference, + protocol.CompletionItemKindFolder, + protocol.CompletionItemKindEnumMember, + protocol.CompletionItemKindConstant, + protocol.CompletionItemKindStruct, + protocol.CompletionItemKindEvent, + protocol.CompletionItemKindOperator, + protocol.CompletionItemKindTypeParameter: + constId := "CompletionItemKind" + item.Kind.String() + itemKindStmt = jen.Qual(protoPkg, constId) + default: + // Unknown value, write as is. + itemKindStmt = jen.Lit(int(item.Kind)) + } + + dict := jen.Dict{ + jen.Id("Label"): jen.Lit(item.Label), + jen.Id("Kind"): itemKindStmt, + jen.Id("InsertTextFormat"): insertTextFormatStmt, + jen.Id("InsertText"): jen.Lit(item.InsertText), + jen.Id("Documentation"): docStmt, + } + + if item.Detail != "" { + dict[jen.Id("Detail")] = jen.Lit(item.Detail) + } + + return jen.Qual(protoPkg, completionItemType).Values(dict), nil +} diff --git a/tools/gendata/README.md b/tools/gendata/README.md new file mode 100644 index 0000000..30a917c --- /dev/null +++ b/tools/gendata/README.md @@ -0,0 +1,5 @@ +# gendata + +This directory contains source data for auto generated files. + +* [builtin.go.txt](./builtin.go.txt) - Gno intrinsic functions definitions, used by [genbuildin](../genbuiltin) tool. diff --git a/tools/gendata/builtin.go.txt b/tools/gendata/builtin.go.txt new file mode 100644 index 0000000..31060c4 --- /dev/null +++ b/tools/gendata/builtin.go.txt @@ -0,0 +1,221 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Types documentation is based on Go's "builtin" package. +// +// Builtins list based on https://github.com/gnolang/gno/blob/master/gnovm/pkg/gnolang/uverse.go + +package builtin + +// bool is the set of boolean values, true and false. +type bool bool + +// true and false are the two untyped boolean values. +const ( + true = 0 == 0 // Untyped bool. + false = 0 != 0 // Untyped bool. +) + +// uint8 is the set of all unsigned 8-bit integers. +// Range: 0 through 255. +type uint8 uint8 + +// uint16 is the set of all unsigned 16-bit integers. +// Range: 0 through 65535. +type uint16 uint16 + +// uint32 is the set of all unsigned 32-bit integers. +// Range: 0 through 4294967295. +type uint32 uint32 + +// uint64 is the set of all unsigned 64-bit integers. +// Range: 0 through 18446744073709551615. +type uint64 uint64 + +// int8 is the set of all signed 8-bit integers. +// Range: -128 through 127. +type int8 int8 + +// int16 is the set of all signed 16-bit integers. +// Range: -32768 through 32767. +type int16 int16 + +// int32 is the set of all signed 32-bit integers. +// Range: -2147483648 through 2147483647. +type int32 int32 + +// int64 is the set of all signed 64-bit integers. +// Range: -9223372036854775808 through 9223372036854775807. +type int64 int64 + +// float32 is the set of all IEEE-754 32-bit floating-point numbers. +type float32 float32 + +// float64 is the set of all IEEE-754 64-bit floating-point numbers. +type float64 float64 + +// string is the set of all strings of 8-bit bytes, conventionally but not +// necessarily representing UTF-8-encoded text. A string may be empty, but +// not nil. Values of string type are immutable. +type string string + +// int is a signed integer type that is at least 32 bits in size. It is a +// distinct type, however, and not an alias for, say, int32. +type int int + +// uint is an unsigned integer type that is at least 32 bits in size. It is a +// distinct type, however, and not an alias for, say, uint32. +type uint uint + +// uintptr is an integer type that is large enough to hold the bit pattern of +// any pointer. +type uintptr uintptr + +// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is +// used, by convention, to distinguish byte values from 8-bit unsigned +// integer values. +type byte = uint8 + +// rune is an alias for int32 and is equivalent to int32 in all ways. It is +// used, by convention, to distinguish character values from integer values. +type rune = int32 + +// iota is a predeclared identifier representing the untyped integer ordinal +// number of the current const specification in a (usually parenthesized) +// const declaration. It is zero-indexed. +const iota = 0 // Untyped int. + +// nil is a predeclared identifier representing the zero value for a +// pointer, channel, func, interface, map, or slice type. +var nil Type // Type must be a pointer, channel, func, interface, map, or slice type + +// Type is here for the purposes of documentation only. It is a stand-in +// for any Gno type, but represents the same type for any given function +// invocation. +type Type int + +// Type1 is here for the purposes of documentation only. It is a stand-in +// for any Gno type, but represents the same type for any given function +// invocation. +type Type1 int + +// IntegerType is here for the purposes of documentation only. It is a stand-in +// for any integer type: int, uint, int8 etc. +type IntegerType int + +// FloatType is here for the purposes of documentation only. It is a stand-in +// for either float type: float32 or float64. +type FloatType float32 + +// ComplexType is here for the purposes of documentation only. It is a +// stand-in for either complex type: complex64 or complex128. + +type ComplexType complex64 + +// The append built-in function appends elements to the end of a slice. If +// it has sufficient capacity, the destination is resliced to accommodate the +// new elements. If it does not, a new underlying array will be allocated. +// Append returns the updated slice. It is therefore necessary to store the +// result of append, often in the variable holding the slice itself: +// +// slice = append(slice, elem1, elem2) +// slice = append(slice, anotherSlice...) +// +// As a special case, it is legal to append a string to a byte slice, like this: +// +// slice = append([]byte("hello "), "world"...) +func append(slice []Type, elems ...Type) []Type + +// The copy built-in function copies elements from a source slice into a +// destination slice. (As a special case, it also will copy bytes from a +// string to a slice of bytes.) The source and destination may overlap. Copy +// returns the number of elements copied, which will be the minimum of +// len(src) and len(dst). +func copy(dst, src []Type) int + +// The delete built-in function deletes the element with the specified key +// (m[key]) from the map. If m is nil or there is no such element, delete +// is a no-op. +func delete(m map[Type]Type1, key Type) + +// The len built-in function returns the length of v, according to its type: +// +// Array: the number of elements in v. +// Pointer to array: the number of elements in *v (even if v is nil). +// Slice, or map: the number of elements in v; if v is nil, len(v) is zero. +// String: the number of bytes in v. +// +// For some arguments, such as a string literal or a simple array expression, the +// result can be a constant. +func len(v Type) int + +// The cap built-in function returns the capacity of v, according to its type: +// +// Array: the number of elements in v (same as len(v)). +// Pointer to array: the number of elements in *v (same as len(v)). +// Slice: the maximum length the slice can reach when resliced; +// if v is nil, cap(v) is zero. +// +// For some arguments, such as a simple array expression, the result can be a +// constant. +func cap(v Type) int + +// The make built-in function allocates and initializes an object of type +// slice, map, or chan (only). Like new, the first argument is a type, not a +// value. Unlike new, make's return type is the same as the type of its +// argument, not a pointer to it. The specification of the result depends on +// the type: +// +// Slice: The size specifies the length. The capacity of the slice is +// equal to its length. A second integer argument may be provided to +// specify a different capacity; it must be no smaller than the +// length. For example, make([]int, 0, 10) allocates an underlying array +// of size 10 and returns a slice of length 0 and capacity 10 that is +// backed by this underlying array. +// Map: An empty map is allocated with enough space to hold the +// specified number of elements. The size may be omitted, in which case +// a small starting size is allocated. +func make(t Type, size ...IntegerType) Type + +// The new built-in function allocates memory. The first argument is a type, +// not a value, and the value returned is a pointer to a newly +// allocated zero value of that type. +func new(Type) *Type + +// The panic built-in function stops normal execution of the program. +// Any functions whose execution was deferred by F are run in +// the usual way, and then F returns to its caller. +// +// At that point, the program is terminated with a non-zero exit code. This +// termination sequence is called panicking and can be controlled by the +// built-in function recover. +func panic(v interface{}) + +// The recover built-in function allows a program to manage behavior of a +// panicking goroutine. Executing a call to recover inside a deferred +// function (but not any function called by it) stops the panicking sequence +// by restoring normal execution and retrieves the error value passed to the +// call of panic. If recover is called outside the deferred function it will +// not stop a panicking sequence. In this case, or when the program is not +// panicking, recover returns nil. +func recover() interface{} + +// The print built-in function formats its arguments in an +// implementation-specific way and writes the result to standard error. +// Print is useful for bootstrapping and debugging; it is not guaranteed +// to stay in the language. +func print(args ...Type) + +// The println built-in function formats its arguments in an +// implementation-specific way and writes the result to standard error. +// Spaces are always added between arguments and a newline is appended. +// Println is useful for bootstrapping and debugging; it is not guaranteed +// to stay in the language. +func println(args ...Type) + +// The error built-in interface type is the conventional interface for +// representing an error condition, with the nil value representing no error. +type error interface { + Error() string +}