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, } }