Skip to content

Commit

Permalink
gopls: report semantic tokens of top-level type constructor modifiers
Browse files Browse the repository at this point in the history
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: f825f8a
GitHub-Pull-Request: #511
Reviewed-on: https://go-review.googlesource.com/c/tools/+/608156
Reviewed-by: Alan Donovan <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
Auto-Submit: Robert Findley <[email protected]>
  • Loading branch information
xzbdmw authored and gopherbot committed Aug 28, 2024
1 parent 826d8d9 commit 4ead70c
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 20 deletions.
2 changes: 2 additions & 0 deletions gopls/doc/features/passive.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 7 additions & 0 deletions gopls/doc/release/v0.17.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

8 changes: 4 additions & 4 deletions gopls/doc/semantictokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
in the unedited part of the file, and they do.
2 changes: 1 addition & 1 deletion gopls/internal/cmd/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
63 changes: 51 additions & 12 deletions gopls/internal/golang/semtok.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand All @@ -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]
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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())) {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions gopls/internal/protocol/semantic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
)
2 changes: 2 additions & 0 deletions gopls/internal/test/integration/fake/editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down
4 changes: 2 additions & 2 deletions gopls/internal/test/marker/testdata/token/comment.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
}
Expand Down

0 comments on commit 4ead70c

Please sign in to comment.