From 4ead70ccceb8a2c574f74c1b3b06f9c8158f8e67 Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Wed, 28 Aug 2024 20:09:02 +0000 Subject: [PATCH] gopls: report semantic tokens of top-level type constructor modifiers These new semantic type modifiers will be reported in places of type definition, type embedding, type alias, variable definition, parameter type, return type and comments link. Fixes golang/go#68975 Change-Id: I21ebd758351ee4c6e7bd7a0d34a093b3a6ad4ac8 GitHub-Last-Rev: f825f8aeb77448470baee6070957d0f906c271b0 GitHub-Pull-Request: golang/tools#511 Reviewed-on: https://go-review.googlesource.com/c/tools/+/608156 Reviewed-by: Alan Donovan LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley Auto-Submit: Robert Findley --- gopls/doc/features/passive.md | 2 + gopls/doc/release/v0.17.0.md | 7 +++ gopls/doc/semantictokens.md | 8 +-- gopls/internal/cmd/integration_test.go | 2 +- gopls/internal/golang/semtok.go | 63 +++++++++++++++---- gopls/internal/protocol/semantic.go | 2 + .../internal/test/integration/fake/editor.go | 2 + .../integration/misc/semantictokens_test.go | 2 +- .../test/marker/testdata/token/comment.txt | 4 +- 9 files changed, 72 insertions(+), 20 deletions(-) diff --git a/gopls/doc/features/passive.md b/gopls/doc/features/passive.md index dc9c1382ac7..92ae929ad5e 100644 --- a/gopls/doc/features/passive.md +++ b/gopls/doc/features/passive.md @@ -207,6 +207,8 @@ a portion of it. The client may use this information to provide syntax highlighting that conveys semantic distinctions between, for example, functions and types, constants and variables, or library functions and built-ins. +Gopls also reports a modifier for the top-level constructor of each symbols's type, one of: +`interface`, `struct`, `signature`, `pointer`, `array`, `map`, `slice`, `chan`, `string`, `number`, `bool`, `invalid`. The client specifies the sets of types and modifiers it is interested in. Settings: diff --git a/gopls/doc/release/v0.17.0.md b/gopls/doc/release/v0.17.0.md index 65b835d6737..dba85fef46c 100644 --- a/gopls/doc/release/v0.17.0.md +++ b/gopls/doc/release/v0.17.0.md @@ -28,3 +28,10 @@ of paritial selection of a declration cannot invoke this code action. Hovering over a standard library symbol now displays information about the first Go release containing the symbol. For example, hovering over `errors.As` shows "Added in go1.13". + +## Semantic token modifiers of top-level constructor of types +The semantic tokens response now includes additional modifiers for the top-level +constructor of the type of each symbol: +`interface`, `struct`, `signature`, `pointer`, `array`, `map`, `slice`, `chan`, `string`, `number`, `bool`, and `invalid`. +Editors may use this for syntax coloring. + diff --git a/gopls/doc/semantictokens.md b/gopls/doc/semantictokens.md index 761d94a02d1..f17ea7f06d8 100644 --- a/gopls/doc/semantictokens.md +++ b/gopls/doc/semantictokens.md @@ -72,9 +72,9 @@ The references to *object* refer to the 1. __`keyword`__ All Go [keywords](https://golang.org/ref/spec#Keywords) are marked `keyword`. 1. __`namespace`__ All package names are marked `namespace`. In an import, if there is an alias, it would be marked. Otherwise the last component of the import path is marked. -1. __`type`__ Objects of type ```types.TypeName``` are marked `type`. -If they are also ```types.Basic``` -the modifier is `defaultLibrary`. (And in ```type B struct{C}```, ```B``` has modifier `definition`.) +1. __`type`__ Objects of type ```types.TypeName``` are marked `type`. It also reports +a modifier for the top-level constructor of the object's type, one of: +`interface`, `struct`, `signature`, `pointer`, `array`, `map`, `slice`, `chan`, `string`, `number`, `bool`, `invalid`. 1. __`parameter`__ The formal arguments in ```ast.FuncDecl``` and ```ast.FuncType``` nodes are marked `parameter`. 1. __`variable`__ Identifiers in the scope of ```const``` are modified with `readonly`. ```nil``` is usually a `variable` modified with both @@ -121,4 +121,4 @@ While a file is being edited it may temporarily contain either parsing errors or type errors. In this case gopls cannot determine some (or maybe any) of the semantic tokens. To avoid weird flickering it is the responsibility of clients to maintain the semantic token information -in the unedited part of the file, and they do. \ No newline at end of file +in the unedited part of the file, and they do. diff --git a/gopls/internal/cmd/integration_test.go b/gopls/internal/cmd/integration_test.go index f4d76b90b27..0bc066b02e0 100644 --- a/gopls/internal/cmd/integration_test.go +++ b/gopls/internal/cmd/integration_test.go @@ -819,7 +819,7 @@ const c = 0 want := ` /*⇒7,keyword,[]*/package /*⇒1,namespace,[]*/a /*⇒4,keyword,[]*/func /*⇒1,function,[definition]*/f() -/*⇒3,keyword,[]*/var /*⇒1,variable,[definition]*/v /*⇒3,type,[defaultLibrary]*/int +/*⇒3,keyword,[]*/var /*⇒1,variable,[definition]*/v /*⇒3,type,[defaultLibrary number]*/int /*⇒5,keyword,[]*/const /*⇒1,variable,[definition readonly]*/c = /*⇒1,number,[]*/0 `[1:] if got != want { diff --git a/gopls/internal/golang/semtok.go b/gopls/internal/golang/semtok.go index 99a0ba335f7..9fd093fe5fc 100644 --- a/gopls/internal/golang/semtok.go +++ b/gopls/internal/golang/semtok.go @@ -210,18 +210,18 @@ func (tv *tokenVisitor) comment(c *ast.Comment, importByName map[string]*types.P } } - tokenTypeByObject := func(obj types.Object) semtok.TokenType { + tokenTypeByObject := func(obj types.Object) (semtok.TokenType, []string) { switch obj.(type) { case *types.PkgName: - return semtok.TokNamespace + return semtok.TokNamespace, nil case *types.Func: - return semtok.TokFunction + return semtok.TokFunction, nil case *types.TypeName: - return semtok.TokType + return semtok.TokType, appendTypeModifiers(nil, obj) case *types.Const, *types.Var: - return semtok.TokVariable + return semtok.TokVariable, nil default: - return semtok.TokComment + return semtok.TokComment, nil } } @@ -244,7 +244,8 @@ func (tv *tokenVisitor) comment(c *ast.Comment, importByName map[string]*types.P } id, rest, _ := strings.Cut(name, ".") name = rest - tv.token(offset, len(id), tokenTypeByObject(obj), nil) + tok, mods := tokenTypeByObject(obj) + tv.token(offset, len(id), tok, mods) offset += token.Pos(len(id)) } last = idx[3] @@ -483,6 +484,46 @@ func (tv *tokenVisitor) inspect(n ast.Node) (descend bool) { return true } +// appendTypeModifiers appends optional modifiers that describe the top-level +// type constructor of obj.Type(): "pointer", "map", etc. +func appendTypeModifiers(mods []string, obj types.Object) []string { + switch t := obj.Type().Underlying().(type) { + case *types.Interface: + mods = append(mods, "interface") + case *types.Struct: + mods = append(mods, "struct") + case *types.Signature: + mods = append(mods, "signature") + case *types.Pointer: + mods = append(mods, "pointer") + case *types.Array: + mods = append(mods, "array") + case *types.Map: + mods = append(mods, "map") + case *types.Slice: + mods = append(mods, "slice") + case *types.Chan: + mods = append(mods, "chan") + case *types.Basic: + mods = append(mods, "defaultLibrary") + switch t.Kind() { + case types.Invalid: + mods = append(mods, "invalid") + case types.String: + mods = append(mods, "string") + case types.Bool: + mods = append(mods, "bool") + case types.UnsafePointer: + mods = append(mods, "pointer") + default: + if t.Info()&types.IsNumeric != 0 { + mods = append(mods, "number") + } + } + } + return mods +} + func (tv *tokenVisitor) ident(id *ast.Ident) { var obj types.Object @@ -535,10 +576,8 @@ func (tv *tokenVisitor) ident(id *ast.Ident) { case *types.TypeName: // could be a TypeParam if is[*types.TypeParam](aliases.Unalias(obj.Type())) { emit(semtok.TokTypeParam) - } else if is[*types.Basic](obj.Type()) { - emit(semtok.TokType, "defaultLibrary") } else { - emit(semtok.TokType) + emit(semtok.TokType, appendTypeModifiers(nil, obj)...) } case *types.Var: if is[*types.Signature](aliases.Unalias(obj.Type())) { @@ -795,11 +834,11 @@ func (tv *tokenVisitor) definitionFor(id *ast.Ident, obj types.Object) (semtok.T if fld, ok := fldm.(*ast.Field); ok { // if len(fld.names) == 0 this is a semtok.TokType, being used if len(fld.Names) == 0 { - return semtok.TokType, nil + return semtok.TokType, appendTypeModifiers(nil, obj) } return semtok.TokVariable, modifiers } - return semtok.TokType, modifiers + return semtok.TokType, appendTypeModifiers(modifiers, obj) } } // can't happen diff --git a/gopls/internal/protocol/semantic.go b/gopls/internal/protocol/semantic.go index 03407899b57..23356dd8ef2 100644 --- a/gopls/internal/protocol/semantic.go +++ b/gopls/internal/protocol/semantic.go @@ -52,5 +52,7 @@ var ( semanticModifiers = [...]string{ "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary", + // Additional modifiers + "interface", "struct", "signature", "pointer", "array", "map", "slice", "chan", "string", "number", "bool", "invalid", } ) diff --git a/gopls/internal/test/integration/fake/editor.go b/gopls/internal/test/integration/fake/editor.go index 8d1c0307a1a..76c9545430d 100644 --- a/gopls/internal/test/integration/fake/editor.go +++ b/gopls/internal/test/integration/fake/editor.go @@ -354,6 +354,8 @@ func clientCapabilities(cfg EditorConfig) (protocol.ClientCapabilities, error) { capabilities.TextDocument.SemanticTokens.TokenModifiers = []string{ "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary", + // Additional modifiers supported by this client: + "interface", "struct", "signature", "pointer", "array", "map", "slice", "chan", "string", "number", "bool", "invalid", } // The LSP tests have historically enabled this flag, // but really we should test both ways for older editors. diff --git a/gopls/internal/test/integration/misc/semantictokens_test.go b/gopls/internal/test/integration/misc/semantictokens_test.go index e688be50946..b8d8729c63a 100644 --- a/gopls/internal/test/integration/misc/semantictokens_test.go +++ b/gopls/internal/test/integration/misc/semantictokens_test.go @@ -57,7 +57,7 @@ func TestSemantic_2527(t *testing.T) { {Token: "func", TokenType: "keyword"}, {Token: "Add", TokenType: "function", Mod: "definition deprecated"}, {Token: "T", TokenType: "typeParameter", Mod: "definition"}, - {Token: "int", TokenType: "type", Mod: "defaultLibrary"}, + {Token: "int", TokenType: "type", Mod: "defaultLibrary number"}, {Token: "target", TokenType: "parameter", Mod: "definition"}, {Token: "T", TokenType: "typeParameter"}, {Token: "l", TokenType: "parameter", Mod: "definition"}, diff --git a/gopls/internal/test/marker/testdata/token/comment.txt b/gopls/internal/test/marker/testdata/token/comment.txt index 082e95491dd..a5ce9139c4e 100644 --- a/gopls/internal/test/marker/testdata/token/comment.txt +++ b/gopls/internal/test/marker/testdata/token/comment.txt @@ -21,7 +21,7 @@ var B = 2 type Foo int -// [F] accept a [Foo], and print it. //@token("F", "function", ""),token("Foo", "type", "") +// [F] accept a [Foo], and print it. //@token("F", "function", ""),token("Foo", "type", "defaultLibrary number") func F(v Foo) { println(v) @@ -44,7 +44,7 @@ func F2(s string) { -- b.go -- package p -// [F3] accept [*Foo] //@token("F3", "function", ""),token("Foo", "type", "") +// [F3] accept [*Foo] //@token("F3", "function", ""),token("Foo", "type", "defaultLibrary number") func F3(v *Foo) { println(*v) }