diff --git a/tools/spxls/internal/pkgdata/gen/main.go b/tools/spxls/internal/pkgdata/gen/main.go
index 9a65327b0..f229b066c 100644
--- a/tools/spxls/internal/pkgdata/gen/main.go
+++ b/tools/spxls/internal/pkgdata/gen/main.go
@@ -14,7 +14,6 @@ import (
"os/exec"
"path"
"path/filepath"
- "runtime"
"slices"
"strings"
@@ -31,30 +30,22 @@ var modulePaths = []string{
// generate generates the pkgdata.zip file containing the exported symbols of
// the given packages.
func generate() error {
- var pkgPaths []string
+ pkgPaths := []string{"builtin"}
// Scan the stdlib packages.
- gorootSrcDir := filepath.Join(runtime.GOROOT(), "src")
- if err := filepath.Walk(gorootSrcDir, func(p string, fi os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- if !fi.IsDir() {
- return nil
- }
-
- importPath := strings.TrimPrefix(p, gorootSrcDir+string(os.PathSeparator))
+ stdlibImportPaths, err := execGo("list", "-f", "{{.ImportPath}}", "std")
+ if err != nil {
+ return err
+ }
+ for _, importPath := range strings.Split(string(stdlibImportPaths), "\n") {
if !isSkippable(importPath) {
pkgPaths = append(pkgPaths, importPath)
}
- return nil
- }); err != nil {
- return fmt.Errorf("failed to walk goroot src dir: %w", err)
}
// Scan the modules.
for _, modulePath := range modulePaths {
- importPaths, err := execGo("list", "-f", "{{.ImportPath}}", modulePath+"/...")
+ importPaths, err := execGo("list", "-deps", "-f", "{{if not .Standard}}{{.ImportPath}}{{end}}", modulePath+"/...")
if err != nil {
return err
}
@@ -199,7 +190,7 @@ func generate() error {
if err := zw.Close(); err != nil {
return err
}
- return os.WriteFile("pkgdata.zip", zipBuf.Bytes(), 0644)
+ return os.WriteFile("pkgdata.zip", zipBuf.Bytes(), 0o644)
}
// isSkippable reports whether the import path should be skipped.
diff --git a/tools/spxls/internal/pkgdata/pkgdata.zip b/tools/spxls/internal/pkgdata/pkgdata.zip
index a7609cc21..230a4904d 100644
Binary files a/tools/spxls/internal/pkgdata/pkgdata.zip and b/tools/spxls/internal/pkgdata/pkgdata.zip differ
diff --git a/tools/spxls/internal/server/compile.go b/tools/spxls/internal/server/compile.go
index d2a1eeb1b..34225d1f9 100644
--- a/tools/spxls/internal/server/compile.go
+++ b/tools/spxls/internal/server/compile.go
@@ -28,17 +28,17 @@ import (
)
const (
- spxPkgPath = "github.com/goplus/spx"
- spxGameTypeName = spxPkgPath + ".Game"
- spxBackdropNameTypeName = spxPkgPath + ".BackdropName"
- spxSpriteTypeName = spxPkgPath + ".Sprite"
- spxSpriteImplTypeName = spxPkgPath + ".SpriteImpl"
- spxSpriteNameTypeName = spxPkgPath + ".SpriteName"
- spxSpriteCostumeNameTypeName = spxPkgPath + ".SpriteCostumeName"
- spxSpriteAnimationNameTypeName = spxPkgPath + ".SpriteAnimationName"
- spxSoundTypeName = spxPkgPath + ".Sound"
- spxSoundNameTypeName = spxPkgPath + ".SoundName"
- spxWidgetNameTypeName = spxPkgPath + ".WidgetName"
+ spxPkgPath = "github.com/goplus/spx"
+ spxGameTypeFullName = spxPkgPath + ".Game"
+ spxBackdropNameTypeFullName = spxPkgPath + ".BackdropName"
+ spxSpriteTypeFullName = spxPkgPath + ".Sprite"
+ spxSpriteImplTypeFullName = spxPkgPath + ".SpriteImpl"
+ spxSpriteNameTypeFullName = spxPkgPath + ".SpriteName"
+ spxSpriteCostumeNameTypeFullName = spxPkgPath + ".SpriteCostumeName"
+ spxSpriteAnimationNameTypeFullName = spxPkgPath + ".SpriteAnimationName"
+ spxSoundTypeFullName = spxPkgPath + ".Sound"
+ spxSoundNameTypeFullName = spxPkgPath + ".SoundName"
+ spxWidgetNameTypeFullName = spxPkgPath + ".WidgetName"
)
var (
@@ -169,9 +169,15 @@ func (r *compileResult) identsAtASTFileLine(astFile *gopast.File, line int) (ide
}
}
for ident := range r.typeInfo.Defs {
+ if funcDecl, ok := r.mainASTPkgIdentToFuncDecl[ident]; ok && funcDecl.Shadow {
+ continue
+ }
collectIdentAtLine(ident)
}
- for ident := range r.typeInfo.Uses {
+ for ident, obj := range r.typeInfo.Uses {
+ if funcDecl, ok := r.mainASTPkgIdentToFuncDecl[r.defIdentFor(obj)]; ok && funcDecl.Shadow {
+ continue
+ }
collectIdentAtLine(ident)
}
return
@@ -354,7 +360,9 @@ func (r *compileResult) isDefinedInFirstVarBlock(obj types.Object) bool {
return defIdent.Pos() >= firstVarBlock.Pos() && defIdent.End() <= firstVarBlock.End()
}
-// spxDefinitionsFor returns all spx definitions for the given object.
+// spxDefinitionsFor returns all spx definitions for the given object. It
+// returns multiple definitions only if the object is a Go+ overloadable
+// function.
func (r *compileResult) spxDefinitionsFor(obj types.Object, selectorTypeName string) []SpxDefinition {
if obj == nil {
return nil
@@ -383,7 +391,14 @@ func (r *compileResult) spxDefinitionsFor(obj types.Object, selectorTypeName str
if funcDecl, ok := r.mainASTPkgIdentToFuncDecl[r.defIdentFor(obj)]; ok && funcDecl.Shadow {
return nil
}
- return NewSpxDefinitionsForFunc(obj, selectorTypeName, pkgDoc)
+ if funcOverloads := expandGopOverloadableFunc(obj); funcOverloads != nil {
+ defs := make([]SpxDefinition, 0, len(funcOverloads))
+ for _, funcOverload := range funcOverloads {
+ defs = append(defs, NewSpxDefinitionForFunc(funcOverload, selectorTypeName, pkgDoc))
+ }
+ return defs
+ }
+ return []SpxDefinition{NewSpxDefinitionForFunc(obj, selectorTypeName, pkgDoc)}
case *types.PkgName:
return []SpxDefinition{NewSpxDefinitionForPkg(obj, pkgDoc)}
}
@@ -391,11 +406,14 @@ func (r *compileResult) spxDefinitionsFor(obj types.Object, selectorTypeName str
}
// spxDefinitionsForIdent returns all spx definitions for the given identifier.
+// It returns multiple definitions only if the identifier is a Go+ overloadable
+// function.
func (r *compileResult) spxDefinitionsForIdent(ident *gopast.Ident) []SpxDefinition {
return r.spxDefinitionsFor(r.typeInfo.ObjectOf(ident), r.selectorTypeNameForIdent(ident))
}
-// spxDefinitionsForNamedStruct returns all spx definitions for the given named struct type.
+// spxDefinitionsForNamedStruct returns all spx definitions for the given named
+// struct type.
func (r *compileResult) spxDefinitionsForNamedStruct(named *types.Named) (defs []SpxDefinition) {
if defsIface, ok := r.computedCache.spxDefinitionsForNamedStructs.Load(named); ok {
return defsIface.([]SpxDefinition)
@@ -579,7 +597,7 @@ func (s *Server) compileUncached(snapshot *vfs.MapFS, spxFiles []string) (*compi
result.diagnostics[documentURI] = []Diagnostic{}
astFile, err := gopparser.ParseFSEntry(result.fset, gpfs, spxFile, nil, gopparser.Config{
- Mode: gopparser.ParseComments | gopparser.AllErrors,
+ Mode: gopparser.ParseComments | gopparser.AllErrors | gopparser.ParseGoPlusClass,
})
if err != nil {
var (
@@ -796,9 +814,9 @@ func (s *Server) inspectForSpxResourceAutoBindingsAndRefsAtDecls(result *compile
isSpxSpriteResourceAutoBinding bool
)
switch objTypeName := objType.String(); objTypeName {
- case spxSoundTypeName:
+ case spxSoundTypeFullName:
isSpxSoundResourceAutoBinding = true
- case spxSpriteTypeName:
+ case spxSpriteTypeFullName:
isSpxSpriteResourceAutoBinding = true
default:
for _, spxSpriteName := range result.spxSpriteNames {
@@ -874,7 +892,7 @@ func (s *Server) inspectForSpxResourceAutoBindingsAndRefsAtDecls(result *compile
func (s *Server) inspectForSpxResourceRefs(result *compileResult) {
// Check all type-checked expressions.
for expr, tv := range result.typeInfo.Types {
- if expr == nil || !expr.Pos().IsValid() || tv.IsType() {
+ if expr == nil || !expr.Pos().IsValid() || tv.IsType() || tv.Type == nil {
continue // Skip type identifiers.
}
@@ -893,7 +911,7 @@ func (s *Server) inspectForSpxResourceRefs(result *compileResult) {
if recv := funcSig.Recv(); recv != nil {
recvType := unwrapPointerType(recv.Type())
switch recvType.String() {
- case spxSpriteTypeName, spxSpriteImplTypeName:
+ case spxSpriteTypeFullName, spxSpriteImplTypeFullName:
spxSpriteResource = s.inspectSpxSpriteResourceRefAtExpr(result, expr, recvType)
}
}
@@ -909,34 +927,34 @@ func (s *Server) inspectForSpxResourceRefs(result *compileResult) {
paramType = lastParamType
}
switch paramType.String() {
- case spxBackdropNameTypeName:
+ case spxBackdropNameTypeFullName:
s.inspectSpxBackdropResourceRefAtExpr(result, arg, paramType)
- case spxSpriteNameTypeName, spxSpriteTypeName:
+ case spxSpriteNameTypeFullName, spxSpriteTypeFullName:
s.inspectSpxSpriteResourceRefAtExpr(result, arg, paramType)
- case spxSpriteCostumeNameTypeName:
+ case spxSpriteCostumeNameTypeFullName:
if spxSpriteResource != nil {
s.inspectSpxSpriteCostumeResourceRefAtExpr(result, spxSpriteResource, arg, paramType)
}
- case spxSpriteAnimationNameTypeName:
+ case spxSpriteAnimationNameTypeFullName:
if spxSpriteResource != nil {
s.inspectSpxSpriteAnimationResourceRefAtExpr(result, spxSpriteResource, arg, paramType)
}
- case spxSoundNameTypeName, spxSoundTypeName:
+ case spxSoundNameTypeFullName, spxSoundTypeFullName:
s.inspectSpxSoundResourceRefAtExpr(result, arg, paramType)
- case spxWidgetNameTypeName:
+ case spxWidgetNameTypeFullName:
s.inspectSpxWidgetResourceRefAtExpr(result, arg, paramType)
}
}
default:
typ := unwrapPointerType(tv.Type)
switch typ.String() {
- case spxBackdropNameTypeName:
+ case spxBackdropNameTypeFullName:
s.inspectSpxBackdropResourceRefAtExpr(result, expr, typ)
- case spxSpriteNameTypeName, spxSpriteTypeName:
+ case spxSpriteNameTypeFullName, spxSpriteTypeFullName:
s.inspectSpxSpriteResourceRefAtExpr(result, expr, typ)
- case spxSoundNameTypeName, spxSoundTypeName:
+ case spxSoundNameTypeFullName, spxSoundTypeFullName:
s.inspectSpxSoundResourceRefAtExpr(result, expr, typ)
- case spxWidgetNameTypeName:
+ case spxWidgetNameTypeFullName:
s.inspectSpxWidgetResourceRefAtExpr(result, expr, typ)
}
}
@@ -946,11 +964,11 @@ func (s *Server) inspectForSpxResourceRefs(result *compileResult) {
for ident, obj := range result.typeInfo.Uses {
objType := unwrapPointerType(obj.Type())
switch objType.String() {
- case spxSpriteTypeName:
+ case spxSpriteTypeFullName:
if _, ok := result.spxSpriteResourceAutoBindings[obj]; ok {
s.inspectSpxSpriteResourceRefAtExpr(result, ident, objType)
}
- case spxSoundTypeName:
+ case spxSoundTypeFullName:
if _, ok := result.spxSoundResourceAutoBindings[obj]; ok {
s.inspectSpxSoundResourceRefAtExpr(result, ident, objType)
}
@@ -961,7 +979,7 @@ func (s *Server) inspectForSpxResourceRefs(result *compileResult) {
for node, obj := range result.typeInfo.Implicits {
objType := unwrapPointerType(obj.Type())
switch objType.String() {
- case spxSpriteTypeName, spxSpriteImplTypeName:
+ case spxSpriteTypeFullName, spxSpriteImplTypeFullName:
if typeAssert, ok := node.(*gopast.TypeAssertExpr); ok {
s.inspectSpxSpriteResourceRefAtExpr(result, typeAssert, objType)
}
@@ -974,14 +992,14 @@ func (s *Server) inspectForSpxResourceRefs(result *compileResult) {
var spxSpriteResource *SpxSpriteResource
switch recv.String() {
- case spxSpriteTypeName, spxSpriteImplTypeName:
+ case spxSpriteTypeFullName, spxSpriteImplTypeFullName:
spxSpriteResource = s.inspectSpxSpriteResourceRefAtExpr(result, sel.X, recv)
}
if spxSpriteResource != nil {
switch selection.Type().String() {
- case spxSpriteCostumeNameTypeName:
+ case spxSpriteCostumeNameTypeFullName:
s.inspectSpxSpriteCostumeResourceRefAtExpr(result, spxSpriteResource, sel, selection.Type())
- case spxSpriteAnimationNameTypeName:
+ case spxSpriteAnimationNameTypeFullName:
s.inspectSpxSpriteAnimationResourceRefAtExpr(result, spxSpriteResource, sel, selection.Type())
}
}
@@ -1088,7 +1106,7 @@ func (s *Server) inspectSpxSpriteResourceRefAtExpr(result *compileResult, expr g
var spxResourceRefKind SpxResourceRefKind
switch typeName {
- case spxSpriteNameTypeName:
+ case spxSpriteNameTypeFullName:
var ok bool
spxSpriteName, ok = getStringLitOrConstValue(expr, exprTV)
if !ok {
@@ -1098,7 +1116,7 @@ func (s *Server) inspectSpxSpriteResourceRefAtExpr(result *compileResult, expr g
if _, ok := expr.(*gopast.Ident); ok {
spxResourceRefKind = SpxResourceRefKindConstantReference
}
- case spxSpriteTypeName:
+ case spxSpriteTypeFullName:
ident, ok := expr.(*gopast.Ident)
if !ok {
return nil
@@ -1165,7 +1183,7 @@ func (s *Server) inspectSpxSpriteCostumeResourceRefAtExpr(result *compileResult,
spxResourceRefKind SpxResourceRefKind
)
switch typeName {
- case spxSpriteCostumeNameTypeName:
+ case spxSpriteCostumeNameTypeFullName:
var ok bool
spxSpriteCostumeName, ok = getStringLitOrConstValue(expr, exprTV)
if !ok {
@@ -1227,7 +1245,7 @@ func (s *Server) inspectSpxSpriteAnimationResourceRefAtExpr(result *compileResul
spxResourceRefKind SpxResourceRefKind
)
switch typeName {
- case spxSpriteAnimationNameTypeName:
+ case spxSpriteAnimationNameTypeFullName:
var ok bool
spxSpriteAnimationName, ok = getStringLitOrConstValue(expr, exprTV)
if !ok {
@@ -1289,7 +1307,7 @@ func (s *Server) inspectSpxSoundResourceRefAtExpr(result *compileResult, expr go
spxResourceRefKind SpxResourceRefKind
)
switch typeName {
- case spxSoundNameTypeName:
+ case spxSoundNameTypeFullName:
var ok bool
spxSoundName, ok = getStringLitOrConstValue(expr, exprTV)
if !ok {
@@ -1299,7 +1317,7 @@ func (s *Server) inspectSpxSoundResourceRefAtExpr(result *compileResult, expr go
if _, ok := expr.(*gopast.Ident); ok {
spxResourceRefKind = SpxResourceRefKindConstantReference
}
- case spxSoundTypeName:
+ case spxSoundTypeFullName:
ident, ok := expr.(*gopast.Ident)
if !ok {
return nil
@@ -1365,7 +1383,7 @@ func (s *Server) inspectSpxWidgetResourceRefAtExpr(result *compileResult, expr g
spxResourceRefKind SpxResourceRefKind
)
switch typeName {
- case spxWidgetNameTypeName:
+ case spxWidgetNameTypeFullName:
var ok bool
spxWidgetName, ok = getStringLitOrConstValue(expr, exprTV)
if !ok {
diff --git a/tools/spxls/internal/server/completion.go b/tools/spxls/internal/server/completion.go
index 0f6e8ac08..975aebf31 100644
--- a/tools/spxls/internal/server/completion.go
+++ b/tools/spxls/internal/server/completion.go
@@ -318,7 +318,7 @@ func (ctx *completionContext) collectDotCompletions() (completionItemSet, error)
}
recvTypeName := ctx.result.selectorTypeNameForIdent(ctx.result.defIdentFor(method))
- itemSet.addSpxDefs(NewSpxDefinitionsForFunc(method, recvTypeName, ctx.result.mainPkgDoc)...)
+ itemSet.addSpxDefs(NewSpxDefinitionForFunc(method, recvTypeName, ctx.result.mainPkgDoc))
}
} else if named, ok := typ.(*types.Named); ok && isNamedStructType(named) {
itemSet.addSpxDefs(ctx.result.spxDefinitionsForNamedStruct(named)...)
diff --git a/tools/spxls/internal/server/completion_test.go b/tools/spxls/internal/server/completion_test.go
index e63e4eb45..76a3c3b99 100644
--- a/tools/spxls/internal/server/completion_test.go
+++ b/tools/spxls/internal/server/completion_test.go
@@ -40,7 +40,8 @@ onStart => {
assert.Contains(t, emptyLineItems, SpxDefinition{
ID: SpxDefinitionIdentifier{
Package: util.ToPtr("main"),
- Name: util.ToPtr("MySprite")},
+ Name: util.ToPtr("MySprite"),
+ },
Overview: "type MySprite struct{SpriteImpl; *main.Game}",
CompletionItemLabel: "MySprite",
diff --git a/tools/spxls/internal/server/diagnostic_test.go b/tools/spxls/internal/server/diagnostic_test.go
index 438a8e818..ec519af44 100644
--- a/tools/spxls/internal/server/diagnostic_test.go
+++ b/tools/spxls/internal/server/diagnostic_test.go
@@ -673,4 +673,56 @@ onStart => {
}
}
})
+
+ t.Run("WithNonBasicTypeAliases", func(t *testing.T) {
+ s := New(newMapFSWithoutModTime(map[string][]byte{
+ "main.spx": []byte(`
+run "assets", {Title: "My Game"}
+`),
+ "MySprite.spx": []byte(`
+import "image/color"
+
+onStart => {
+ touchingColor color.RGBA{0, 0, 0, 0}
+}
+`),
+ "assets/index.json": []byte(`{}`),
+ "assets/sprites/MySprite/index.json": []byte(`{}`),
+ }), nil)
+
+ report, err := s.workspaceDiagnostic(&WorkspaceDiagnosticParams{})
+ require.NoError(t, err)
+ require.NotNil(t, report)
+ assert.Len(t, report.Items, 2)
+ for _, item := range report.Items {
+ fullReport := item.Value.(WorkspaceFullDocumentDiagnosticReport)
+ assert.Equal(t, string(DiagnosticFull), fullReport.Kind)
+ assert.Empty(t, fullReport.Items)
+ }
+ })
+
+ t.Run("OnKey", func(t *testing.T) {
+ s := New(newMapFSWithoutModTime(map[string][]byte{
+ "main.spx": []byte(`
+onKey KeyLeft, => {}
+
+// FIXME: Multi-key bindings currently fail because goplus/gogen lacks support for types.Alias.
+// See https://github.com/goplus/gogen/issues/457 for details.
+// onKey [KeyRight, KeyUp, KeyDown], => {}
+
+run "assets", {Title: "My Game"}
+`),
+ "assets/index.json": []byte(`{}`),
+ }), nil)
+
+ report, err := s.workspaceDiagnostic(&WorkspaceDiagnosticParams{})
+ require.NoError(t, err)
+ require.NotNil(t, report)
+ assert.Len(t, report.Items, 1)
+ for _, item := range report.Items {
+ fullReport := item.Value.(WorkspaceFullDocumentDiagnosticReport)
+ assert.Equal(t, string(DiagnosticFull), fullReport.Kind)
+ assert.Empty(t, fullReport.Items)
+ }
+ })
}
diff --git a/tools/spxls/internal/server/document_test.go b/tools/spxls/internal/server/document_test.go
index 97282859c..4120e30c5 100644
--- a/tools/spxls/internal/server/document_test.go
+++ b/tools/spxls/internal/server/document_test.go
@@ -105,7 +105,7 @@ onStart => {
}
linksForMySpriteSpx, err := s.textDocumentDocumentLink(paramsForMySpriteSpx)
require.NoError(t, err)
- require.Len(t, linksForMySpriteSpx, 25)
+ require.Len(t, linksForMySpriteSpx, 16)
assert.Contains(t, linksForMySpriteSpx, DocumentLink{
Range: Range{
Start: Position{Line: 3, Character: 12},
diff --git a/tools/spxls/internal/server/format.go b/tools/spxls/internal/server/format.go
index 3901f8c69..5dda9d74d 100644
--- a/tools/spxls/internal/server/format.go
+++ b/tools/spxls/internal/server/format.go
@@ -27,11 +27,7 @@ func (s *Server) textDocumentFormatting(params *DocumentFormattingParams) ([]Tex
return nil, err
}
- formatted, err := gopfmt.Source(content, false, spxFile)
- if err != nil {
- return nil, fmt.Errorf("failed to format document %q: %w", spxFile, err)
- }
- formatted, err = formatSpx(formatted)
+ formatted, err := formatSpx(content)
if err != nil {
return nil, fmt.Errorf("failed to format spx source file: %w", err)
}
@@ -62,11 +58,13 @@ func (s *Server) textDocumentFormatting(params *DocumentFormattingParams) ([]Tex
func formatSpx(src []byte) ([]byte, error) {
// Parse the source into AST.
fset := goptoken.NewFileSet()
- f, err := gopparser.ParseFile(fset, "main.spx", src, gopparser.ParseComments)
+ f, err := gopparser.ParseFile(fset, "main.spx", src, gopparser.ParseComments|gopparser.ParseGoPlusClass)
if err != nil {
return nil, err
}
+ gopast.SortImports(fset, f)
+
// Find all var blocks and function declarations.
var (
varBlocks []*gopast.GenDecl
diff --git a/tools/spxls/internal/server/format_test.go b/tools/spxls/internal/server/format_test.go
index 1839313e8..a6820d7a5 100644
--- a/tools/spxls/internal/server/format_test.go
+++ b/tools/spxls/internal/server/format_test.go
@@ -91,7 +91,7 @@ run "assets", {Title: "Bullet (by Go+)"}
edits, err := s.textDocumentFormatting(params)
require.Error(t, err)
- require.Contains(t, err.Error(), "failed to format document")
+ require.Contains(t, err.Error(), "failed to format spx source file")
require.Nil(t, edits)
})
@@ -168,4 +168,27 @@ var (
`,
})
})
+
+ t.Run("NoTypeSpriteVarDeclaration", func(t *testing.T) {
+ s := New(newMapFSWithoutModTime(map[string][]byte{
+ "main.spx": []byte(`// A spx game.
+
+var (
+ MySprite
+)
+
+run "assets", {Title: "My Game"}
+`),
+ "MySprite.spx": []byte(``),
+ "assets/index.json": []byte(`{}`),
+ "assets/sprites/MySprite/index.json": []byte(`{}`),
+ }), nil)
+ params := &DocumentFormattingParams{
+ TextDocument: TextDocumentIdentifier{URI: "file:///main.spx"},
+ }
+
+ edits, err := s.textDocumentFormatting(params)
+ require.NoError(t, err)
+ require.Nil(t, edits)
+ })
}
diff --git a/tools/spxls/internal/server/hover_test.go b/tools/spxls/internal/server/hover_test.go
index 024522469..34f1b01da 100644
--- a/tools/spxls/internal/server/hover_test.go
+++ b/tools/spxls/internal/server/hover_test.go
@@ -416,7 +416,7 @@ onStart => {
assert.Equal(t, &Hover{
Contents: MarkupContent{
Kind: Markdown,
- Value: "\n\n\n\n",
+ Value: "\n\n",
},
Range: Range{
Start: Position{Line: 5, Character: 1},
diff --git a/tools/spxls/internal/server/spx_definition.go b/tools/spxls/internal/server/spx_definition.go
index 8a4d5e742..0f53fd2d8 100644
--- a/tools/spxls/internal/server/spx_definition.go
+++ b/tools/spxls/internal/server/spx_definition.go
@@ -259,6 +259,12 @@ var GetSpxPkg = sync.OnceValue(func() *types.Package {
return spxPkg
})
+// GetSpxGameType returns the [spx.Game] type.
+var GetSpxGameType = sync.OnceValue(func() types.Type {
+ spxPkg := GetSpxPkg()
+ return spxPkg.Scope().Lookup("Game").Type()
+})
+
// GetSpxSpriteImplType returns the [spx.SpriteImpl] type.
var GetSpxSpriteImplType = sync.OnceValue(func() types.Type {
spxPkg := GetSpxPkg()
@@ -285,7 +291,13 @@ var GetSpxPkgDefinitions = sync.OnceValue(func() []SpxDefinition {
case *types.TypeName:
defs = append(defs, NewSpxDefinitionForType(obj, spxPkgDoc))
case *types.Func:
- defs = append(defs, NewSpxDefinitionsForFunc(obj, "", spxPkgDoc)...)
+ if funcOverloads := expandGopOverloadableFunc(obj); funcOverloads != nil {
+ for _, funcOverload := range funcOverloads {
+ defs = append(defs, NewSpxDefinitionForFunc(funcOverload, "", spxPkgDoc))
+ }
+ } else {
+ defs = append(defs, NewSpxDefinitionForFunc(obj, "", spxPkgDoc))
+ }
case *types.PkgName:
defs = append(defs, NewSpxDefinitionForPkg(obj, spxPkgDoc))
}
@@ -375,7 +387,7 @@ func NewSpxDefinitionForVar(v *types.Var, selectorTypeName string, forceVar bool
// for constants.
var nonMainPkgSpxDefCacheForConsts sync.Map // map[*types.Const]SpxDefinition
-// NewSpxDefinitionForConst makes a new [SpxDefinition] for the provided constant.
+// NewSpxDefinitionForConst creates a new [SpxDefinition] for the provided constant.
func NewSpxDefinitionForConst(c *types.Const, pkgDoc *pkgdoc.PkgDoc) (def SpxDefinition) {
if !isMainPkgObject(c) {
if defIface, ok := nonMainPkgSpxDefCacheForConsts.Load(c); ok {
@@ -417,7 +429,7 @@ func NewSpxDefinitionForConst(c *types.Const, pkgDoc *pkgdoc.PkgDoc) (def SpxDef
// for types.
var nonMainPkgSpxDefCacheForTypes sync.Map // map[*types.TypeName]SpxDefinition
-// NewSpxDefinitionForType makes a new [SpxDefinition] for the provided type.
+// NewSpxDefinitionForType creates a new [SpxDefinition] for the provided type.
func NewSpxDefinitionForType(typeName *types.TypeName, pkgDoc *pkgdoc.PkgDoc) (def SpxDefinition) {
if !isMainPkgObject(typeName) {
if defIface, ok := nonMainPkgSpxDefCacheForTypes.Load(typeName); ok {
@@ -462,41 +474,32 @@ func NewSpxDefinitionForType(typeName *types.TypeName, pkgDoc *pkgdoc.PkgDoc) (d
return
}
-// nonMainPkgSpxDefsCacheForFuncs is a cache of non-main package spx definitions
+// nonMainPkgSpxDefCacheForFuncs is a cache of non-main package spx definitions
// for functions.
-var nonMainPkgSpxDefsCacheForFuncs sync.Map // map[nonMainPkgSpxDefsCacheForFuncsKey][]SpxDefinition
+var nonMainPkgSpxDefCacheForFuncs sync.Map // map[nonMainPkgSpxDefCacheForFuncsKey]SpxDefinition
-// nonMainPkgSpxDefsCacheForFuncsKey is the key for the non-main package spx
+// nonMainPkgSpxDefCacheForFuncsKey is the key for the non-main package spx
// definition cache for functions.
-type nonMainPkgSpxDefsCacheForFuncsKey struct {
+type nonMainPkgSpxDefCacheForFuncsKey struct {
fun *types.Func
recvTypeName string
}
-// NewSpxDefinitionsForFunc creates new [SpxDefinition]s for the provided
-// function. It returns multiple definitions if the function has overloaded
-// variants.
-func NewSpxDefinitionsForFunc(fun *types.Func, recvTypeName string, pkgDoc *pkgdoc.PkgDoc) (defs []SpxDefinition) {
+// NewSpxDefinitionForFunc creates a new [SpxDefinition] for the provided function.
+func NewSpxDefinitionForFunc(fun *types.Func, recvTypeName string, pkgDoc *pkgdoc.PkgDoc) (def SpxDefinition) {
if !isMainPkgObject(fun) {
- cacheKey := nonMainPkgSpxDefsCacheForFuncsKey{
+ cacheKey := nonMainPkgSpxDefCacheForFuncsKey{
fun: fun,
recvTypeName: recvTypeName,
}
- if defsIface, ok := nonMainPkgSpxDefsCacheForFuncs.Load(cacheKey); ok {
- return defsIface.([]SpxDefinition)
+ if defIface, ok := nonMainPkgSpxDefCacheForFuncs.Load(cacheKey); ok {
+ return defIface.(SpxDefinition)
}
defer func() {
- nonMainPkgSpxDefsCacheForFuncs.Store(cacheKey, slices.Clip(defs))
+ nonMainPkgSpxDefCacheForFuncs.Store(cacheKey, def)
}()
}
- if funcOverloads := expandGopOverloadedFunc(fun); len(funcOverloads) > 0 {
- // When encountering a overload signature like `func(__gop_overload_args__ interface{_()})`,
- // we expand it to concrete overloads and use the first one as the default representation.
- // All overload variants will still be included in the returned definitions.
- fun = funcOverloads[0]
- }
-
if isSpxPkgObject(fun) && recvTypeName == "Sprite" {
recvTypeName = "SpriteImpl"
}
@@ -515,8 +518,6 @@ func NewSpxDefinitionsForFunc(fun *types.Func, recvTypeName string, pkgDoc *pkgd
}
}
- pkg := fun.Pkg()
- pkgPath := pkg.Path()
idName := parsedName
if recvTypeName != "" {
recvTypeDisplayName := recvTypeName
@@ -525,104 +526,19 @@ func NewSpxDefinitionsForFunc(fun *types.Func, recvTypeName string, pkgDoc *pkgd
}
idName = recvTypeDisplayName + "." + idName
}
- defs = []SpxDefinition{
- {
- ID: SpxDefinitionIdentifier{
- Package: &pkgPath,
- Name: &idName,
- OverloadID: overloadID,
- },
- Overview: overview,
- Detail: detail,
-
- CompletionItemLabel: parsedName,
- CompletionItemKind: FunctionCompletion,
- CompletionItemInsertText: parsedName,
- CompletionItemInsertTextFormat: PlainTextTextFormat,
+ def = SpxDefinition{
+ ID: SpxDefinitionIdentifier{
+ Package: util.ToPtr(fun.Pkg().Path()),
+ Name: &idName,
+ OverloadID: overloadID,
},
- }
-
- if overloadID == nil {
- return
- }
-
- seenOverloadNames := map[string]struct{}{fun.Name(): {}}
- handleOverloads := func(overloads []*types.Func, funcDocs map[string]string) {
- for _, overload := range overloads {
- overloadName := overload.Name()
- if _, ok := seenOverloadNames[overloadName]; ok {
- continue
- }
- seenOverloadNames[overloadName] = struct{}{}
-
- if _, methodName, ok := util.SplitGoptMethod(overloadName); ok {
- overloadName = methodName
- }
-
- var detail string
- if funcDocs != nil {
- detail = funcDocs[overloadName]
- }
-
- _, oID := parseGopFuncName(overloadName)
- overview, _, _, _ := makeSpxDefinitionOverviewForFunc(overload)
- defs = append(defs, SpxDefinition{
- ID: SpxDefinitionIdentifier{
- Package: &pkgPath,
- Name: &idName,
- OverloadID: oID,
- },
- Overview: overview,
- Detail: detail,
-
- CompletionItemLabel: parsedName,
- CompletionItemKind: FunctionCompletion,
- CompletionItemInsertText: parsedName,
- CompletionItemInsertTextFormat: PlainTextTextFormat,
- })
- }
- }
-
- if recvTypeName != "" {
- recvType := pkg.Scope().Lookup(recvTypeName).Type()
- if recvType == nil {
- return
- }
- recvNamed, ok := recvType.(*types.Named)
- if !ok || !isNamedStructType(recvNamed) {
- return
- }
-
- var methodDocs map[string]string
- if pkgDoc != nil {
- recvTypeDoc, ok := pkgDoc.Types[recvTypeName]
- if ok {
- methodDocs = recvTypeDoc.Methods
- }
- }
-
- walkStruct(recvNamed, func(member types.Object, selector *types.Named) bool {
- method, ok := member.(*types.Func)
- if !ok {
- return true
- }
- if pn, _ := parseGopFuncName(method.Name()); pn != parsedName {
- return true
- }
+ Overview: overview,
+ Detail: detail,
- overloads := expandGopOverloadedFunc(method)
- if len(overloads) == 0 {
- overloads = []*types.Func{method}
- }
- handleOverloads(overloads, methodDocs)
- return true
- })
- } else {
- var funcDocs map[string]string
- if pkgDoc != nil {
- funcDocs = pkgDoc.Funcs
- }
- handleOverloads(expandGopOverloadedFunc(fun), funcDocs)
+ CompletionItemLabel: parsedName,
+ CompletionItemKind: FunctionCompletion,
+ CompletionItemInsertText: parsedName,
+ CompletionItemInsertTextFormat: PlainTextTextFormat,
}
return
}
diff --git a/tools/spxls/internal/server/util.go b/tools/spxls/internal/server/util.go
index 55b0eff7f..ed418a075 100644
--- a/tools/spxls/internal/server/util.go
+++ b/tools/spxls/internal/server/util.go
@@ -122,8 +122,7 @@ func walkStruct(named *types.Named, onMember func(member types.Object, selector
}
selector = named
- typeName := selector.Obj().Name()
- if isSpxPkgObject(selector.Obj()) && (typeName == "Game" || typeName == "SpriteImpl") {
+ if isSpxPkgObject(selector.Obj()) && (selector == GetSpxGameType() || selector == GetSpxSpriteImplType()) {
break
}
}
@@ -197,9 +196,17 @@ func parseGopFuncName(name string) (parsedName string, overloadID *string) {
return
}
-// expandGopOverloadedFunc expands the given Go+ function to all its overloads.
-// It returns nil if the function is not overloaded.
-func expandGopOverloadedFunc(fun *types.Func) []*types.Func {
+// isGopOverloadableFunc reports whether the given function is a Go+ overloadable
+// function with a signature like `func(__gop_overload_args__ interface{_()})`.
+func isGopOverloadableFunc(fun *types.Func) bool {
+ typ, _ := gogen.CheckSigFuncExObjects(fun.Type().(*types.Signature))
+ return typ != nil
+}
+
+// expandGopOverloadableFunc expands the given Go+ function with a signature
+// like `func(__gop_overload_args__ interface{_()})` to all its overloads. It
+// returns nil if the function is not qualified for overload expansion.
+func expandGopOverloadableFunc(fun *types.Func) []*types.Func {
typ, objs := gogen.CheckSigFuncExObjects(fun.Type().(*types.Signature))
if typ == nil {
return nil
diff --git a/tools/spxls/internal/util/enclosing.go b/tools/spxls/internal/util/enclosing.go
index 2f2588860..64dcb7e55 100644
--- a/tools/spxls/internal/util/enclosing.go
+++ b/tools/spxls/internal/util/enclosing.go
@@ -481,9 +481,11 @@ type byPos []ast.Node
func (sl byPos) Len() int {
return len(sl)
}
+
func (sl byPos) Less(i, j int) bool {
return sl[i].Pos() < sl[j].Pos()
}
+
func (sl byPos) Swap(i, j int) {
sl[i], sl[j] = sl[j], sl[i]
}
diff --git a/tools/spxls/internal/vfs/mapfs.go b/tools/spxls/internal/vfs/mapfs.go
index 0bb56ab5f..5e55e9537 100644
--- a/tools/spxls/internal/vfs/mapfs.go
+++ b/tools/spxls/internal/vfs/mapfs.go
@@ -30,8 +30,8 @@ type MapFS struct {
func NewMapFS(getFileMap GetFileMapFunc) *MapFS {
return &MapFS{
getFileMap: getFileMap,
- fileMode: 0444,
- dirMode: 0444 | fs.ModeDir,
+ fileMode: 0o444,
+ dirMode: 0o444 | fs.ModeDir,
}
}