diff options
| -rw-r--r-- | README.md | 4 | ||||
| -rwxr-xr-x | build.sh | 4 | ||||
| -rw-r--r-- | editors/vscode/package-lock.json | 34 | ||||
| -rw-r--r-- | editors/vscode/package.json | 2 | ||||
| -rw-r--r-- | src/common/util.odin | 13 | ||||
| -rw-r--r-- | src/server/analysis.odin | 539 | ||||
| -rw-r--r-- | src/server/build.odin | 12 | ||||
| -rw-r--r-- | src/server/caches.odin | 2 | ||||
| -rw-r--r-- | src/server/collector.odin | 8 | ||||
| -rw-r--r-- | src/server/completion.odin | 54 | ||||
| -rw-r--r-- | src/server/definition.odin | 10 | ||||
| -rw-r--r-- | src/server/file_resolve.odin | 540 | ||||
| -rw-r--r-- | src/server/generics.odin | 21 | ||||
| -rw-r--r-- | src/server/imports.odin | 4 | ||||
| -rw-r--r-- | src/server/references.odin | 459 | ||||
| -rw-r--r-- | src/server/rename.odin | 99 | ||||
| -rw-r--r-- | src/server/requests.odin | 56 | ||||
| -rw-r--r-- | src/server/semantic_tokens.odin | 55 | ||||
| -rw-r--r-- | src/server/symbol.odin | 7 | ||||
| -rw-r--r-- | src/server/types.odin | 17 | ||||
| -rw-r--r-- | src/testing/testing.odin | 67 | ||||
| -rw-r--r-- | tests/completions_test.odin | 59 | ||||
| -rw-r--r-- | tests/definition_test.odin | 123 | ||||
| -rw-r--r-- | tests/objc_test.odin | 2 | ||||
| -rw-r--r-- | tests/references_test.odin | 254 | ||||
| -rw-r--r-- | tests/semantic_tokens_test.odin | 36 |
26 files changed, 1840 insertions, 641 deletions
@@ -125,8 +125,10 @@ Support Language server features: - Completion - Go to definition -- Semantic tokens(really unstable and unfinished) +- Semantic tokens - Document symbols +- Rename +- References - Signature help - Hover @@ -74,9 +74,9 @@ if [[ $1 == "debug" ]] then shift - odin build src/ -collection:src=src -out:ols -use-separate-modules -debug $@ + odin build src/ -show-timings -collection:src=src -out:ols -microarch:native -no-bounds-check -use-separate-modules -debug $@ exit 0 fi -odin build src/ -collection:src=src -out:ols -o:speed $@ +odin build src/ -show-timings -collection:src=src -out:ols -microarch:native -no-bounds-check -o:speed $@ diff --git a/editors/vscode/package-lock.json b/editors/vscode/package-lock.json index 404d3e5..2384838 100644 --- a/editors/vscode/package-lock.json +++ b/editors/vscode/package-lock.json @@ -11,7 +11,7 @@ "adm-zip": "^0.5.9", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.7", - "vscode-languageclient": "8.1.0" + "vscode-languageclient": "9.0.1" }, "devDependencies": { "@types/glob": "^7.2.0", @@ -3176,24 +3176,24 @@ } }, "node_modules/vscode-jsonrpc": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz", - "integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", "engines": { "node": ">=14.0.0" } }, "node_modules/vscode-languageclient": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.1.0.tgz", - "integrity": "sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", + "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", "dependencies": { "minimatch": "^5.1.0", "semver": "^7.3.7", - "vscode-languageserver-protocol": "3.17.3" + "vscode-languageserver-protocol": "3.17.5" }, "engines": { - "vscode": "^1.67.0" + "vscode": "^1.82.0" } }, "node_modules/vscode-languageclient/node_modules/brace-expansion": { @@ -3216,18 +3216,18 @@ } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz", - "integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==", + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", "dependencies": { - "vscode-jsonrpc": "8.1.0", - "vscode-languageserver-types": "3.17.3" + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" } }, "node_modules/vscode-languageserver-types": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", - "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" }, "node_modules/vscode-test": { "version": "1.6.1", diff --git a/editors/vscode/package.json b/editors/vscode/package.json index a017ed5..e7d0003 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -160,7 +160,7 @@ "adm-zip": "^0.5.9", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.7", - "vscode-languageclient": "8.1.0" + "vscode-languageclient": "9.0.1" }, "configurationDefaults": { "[odin]": { diff --git a/src/common/util.odin b/src/common/util.odin index 9700829..f0195ca 100644 --- a/src/common/util.odin +++ b/src/common/util.odin @@ -6,6 +6,7 @@ import "core:log" import "core:mem" import "core:os" import "core:path/filepath" +import "core:path/slashpath" import "core:strings" foreign import libc "system:c" @@ -120,3 +121,15 @@ when ODIN_OS == .Darwin || ODIN_OS == .Linux || ODIN_OS == .NetBSD { fgetc :: proc "cdecl" (stream: ^FILE) -> i32 --- } } + +get_executable_path :: proc(allocator := context.temp_allocator) -> string { + exe_path, ok := filepath.abs( + slashpath.dir(os.args[0], context.temp_allocator), + context.temp_allocator, + ) + if !ok { + log.error("Failed to resolve executable path") + return "" + } + return exe_path +} diff --git a/src/server/analysis.odin b/src/server/analysis.odin index 93ada47..d6592ae 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -31,9 +31,12 @@ DocumentPositionContext :: struct { function: ^ast.Proc_Lit, //used to help with type resolving in function scope functions: [dynamic]^ast.Proc_Lit, //stores all the functions that have been iterated through to find the position selector: ^ast.Expr, //used for completion - selector_expr: ^ast.Selector_Expr, + selector_expr: ^ast.Node, identifier: ^ast.Node, + label: ^ast.Ident, implicit_context: ^ast.Implicit, + index: ^ast.Index_Expr, + previous_index: ^ast.Index_Expr, tag: ^ast.Node, field: ^ast.Expr, //used for completion call: ^ast.Expr, //used for signature help @@ -76,7 +79,7 @@ DocumentLocal :: struct { parameter: bool, } -DeferredDepth :: 100 +DeferredDepth :: 35 AstContext :: struct { locals: map[int]map[string][dynamic]DocumentLocal, //locals all the way to the document position @@ -133,14 +136,17 @@ make_ast_context :: proc( } set_ast_package_deferred :: proc(ast_context: ^AstContext, pkg: string) { + assert(ast_context.deferred_count > 0) ast_context.deferred_count -= 1 ast_context.current_package = ast_context.deferred_package[ast_context.deferred_count] - assert(ast_context.deferred_count >= 0) } @(deferred_in = set_ast_package_deferred) set_ast_package_set_scoped :: proc(ast_context: ^AstContext, pkg: string) { + if ast_context.deferred_count >= DeferredDepth { + return + } ast_context.deferred_package[ast_context.deferred_count] = ast_context.current_package ast_context.deferred_count += 1 @@ -148,14 +154,17 @@ set_ast_package_set_scoped :: proc(ast_context: ^AstContext, pkg: string) { } set_ast_package_none_deferred :: proc(ast_context: ^AstContext) { + assert(ast_context.deferred_count > 0) ast_context.deferred_count -= 1 ast_context.current_package = ast_context.deferred_package[ast_context.deferred_count] - assert(ast_context.deferred_count >= 0) } @(deferred_in = set_ast_package_none_deferred) set_ast_package_scoped :: proc(ast_context: ^AstContext) { + if ast_context.deferred_count >= DeferredDepth { + return + } ast_context.deferred_package[ast_context.deferred_count] = ast_context.current_package ast_context.deferred_count += 1 @@ -165,10 +174,10 @@ set_ast_package_from_symbol_deferred :: proc( ast_context: ^AstContext, symbol: Symbol, ) { + assert(ast_context.deferred_count > 0) ast_context.deferred_count -= 1 ast_context.current_package = ast_context.deferred_package[ast_context.deferred_count] - assert(ast_context.deferred_count >= 0) } @(deferred_in = set_ast_package_from_symbol_deferred) @@ -176,6 +185,10 @@ set_ast_package_from_symbol_scoped :: proc( ast_context: ^AstContext, symbol: Symbol, ) { + if ast_context.deferred_count >= DeferredDepth { + return + } + ast_context.deferred_package[ast_context.deferred_count] = ast_context.current_package ast_context.deferred_count += 1 @@ -822,11 +835,6 @@ resolve_type_expression :: proc( Symbol, bool, ) { - //Try to prevent stack overflows and prevent indexing out of bounds. - if ast_context.deferred_count >= DeferredDepth { - return {}, false - } - clear(&ast_context.recursion_map) return internal_resolve_type_expression(ast_context, node) } @@ -842,6 +850,11 @@ internal_resolve_type_expression :: proc( return {}, false } + //Try to prevent stack overflows and prevent indexing out of bounds. + if ast_context.deferred_count >= DeferredDepth { + return {}, false + } + set_ast_package_scoped(ast_context) if check_node_recursion(ast_context, node) { @@ -1317,11 +1330,6 @@ resolve_type_identifier :: proc( Symbol, bool, ) { - //Try to prevent stack overflows and prevent indexing out of bounds. - if ast_context.deferred_count > DeferredDepth { - return {}, false - } - return internal_resolve_type_identifier(ast_context, node) } @@ -1338,6 +1346,11 @@ internal_resolve_type_identifier :: proc( return {}, false } + //Try to prevent stack overflows and prevent indexing out of bounds. + if ast_context.deferred_count >= DeferredDepth { + return {}, false + } + set_ast_package_scoped(ast_context) if v, ok := common.keyword_map[node.name]; ok { @@ -1853,6 +1866,34 @@ resolve_comp_literal :: proc( value.arg_types[arg_index].type, ) or_return } + } else if position_context.returns != nil { + return_index: int + + if position_context.returns.results == nil { + return {}, false + } + + for result, i in position_context.returns.results { + if position_in_node(result, position_context.position) { + return_index = i + break + } + } + + if position_context.function.type == nil { + return {}, false + } + + if position_context.function.type.results == nil { + return {}, false + } + + if len(position_context.function.type.results.list) > return_index { + symbol = resolve_type_expression( + ast_context, + position_context.function.type.results.list[return_index].type, + ) or_return + } } set_ast_package_set_scoped(ast_context, symbol.pkg) @@ -2212,6 +2253,7 @@ resolve_location_identifier :: proc( uri := common.create_uri(local.lhs.pos.file, ast_context.allocator) symbol.pkg = ast_context.document_package symbol.uri = uri.uri + symbol.flags |= {.Local} return symbol, true } else if global, ok := ast_context.globals[node.name]; ok { symbol.range = common.get_token_range( @@ -2278,6 +2320,8 @@ resolve_location_implicit_selector :: proc( symbol: Symbol, ok: bool, ) { + ok = true + reset_ast_context(ast_context) set_ast_package_set_scoped(ast_context, ast_context.document_package) @@ -2295,6 +2339,15 @@ resolve_location_implicit_selector :: proc( symbol.range = v.ranges[i] } } + case SymbolUnionValue: + enum_value := unwrap_super_enum(ast_context, v) or_return + for name, i in enum_value.names { + if strings.compare(name, implicit_selector.field.name) == 0 { + symbol.range = enum_value.ranges[i] + } + } + case: + ok = false } return symbol, ok @@ -2302,7 +2355,7 @@ resolve_location_implicit_selector :: proc( resolve_location_selector :: proc( ast_context: ^AstContext, - selector: ^ast.Selector_Expr, + selector_expr: ^ast.Node, ) -> ( symbol: Symbol, ok: bool, @@ -2311,40 +2364,45 @@ resolve_location_selector :: proc( set_ast_package_set_scoped(ast_context, ast_context.document_package) - symbol = resolve_type_expression(ast_context, selector.expr) or_return + if selector, ok := selector_expr.derived.(^ast.Selector_Expr); ok { - field: string + symbol = resolve_type_expression(ast_context, selector.expr) or_return - if selector.field != nil { - #partial switch v in selector.field.derived { - case ^ast.Ident: - field = v.name - } - } + field: string - #partial switch v in symbol.value { - case SymbolStructValue: - for name, i in v.names { - if strings.compare(name, field) == 0 { - symbol.range = v.ranges[i] + if selector.field != nil { + #partial switch v in selector.field.derived { + case ^ast.Ident: + field = v.name } } - case SymbolBitFieldValue: - for name, i in v.names { - if strings.compare(name, field) == 0 { - symbol.range = v.ranges[i] + + #partial switch v in symbol.value { + case SymbolStructValue: + for name, i in v.names { + if strings.compare(name, field) == 0 { + symbol.range = v.ranges[i] + } + } + case SymbolBitFieldValue: + for name, i in v.names { + if strings.compare(name, field) == 0 { + symbol.range = v.ranges[i] + } + } + case SymbolPackageValue: + if pkg, ok := lookup(field, symbol.pkg); ok { + symbol.range = pkg.range + symbol.uri = pkg.uri + } else { + return {}, false } } - case SymbolPackageValue: - if pkg, ok := lookup(field, symbol.pkg); ok { - symbol.range = pkg.range - symbol.uri = pkg.uri - } else { - return {}, false - } + + return symbol, true } - return symbol, true + return {}, false } @@ -3837,296 +3895,6 @@ clear_locals :: proc(ast_context: ^AstContext) { clear(&ast_context.usings) } -ResolveReferenceFlag :: enum { - None, - Variable, - Constant, - StructElement, - EnumElement, -} - -resolve_entire_file :: proc( - document: ^Document, - reference := "", - flag := ResolveReferenceFlag.None, - save_unresolved := false, - allocator := context.allocator, -) -> map[uintptr]SymbolAndNode { - ast_context := make_ast_context( - document.ast, - document.imports, - document.package_name, - document.uri.uri, - document.fullpath, - allocator, - ) - - get_globals(document.ast, &ast_context) - - set_ast_package_set_scoped(&ast_context, ast_context.document_package) - - symbols := make(map[uintptr]SymbolAndNode, 10000, allocator) - - for decl in document.ast.decls { - if _, is_value := decl.derived.(^ast.Value_Decl); !is_value { - continue - } - - resolve_entire_decl( - &ast_context, - document, - decl, - &symbols, - reference, - flag, - save_unresolved, - allocator, - ) - clear(&ast_context.locals) - } - - return symbols -} - -resolve_entire_decl :: proc( - ast_context: ^AstContext, - document: ^Document, - decl: ^ast.Node, - symbols: ^map[uintptr]SymbolAndNode, - reference := "", - flag := ResolveReferenceFlag.None, - save_unresolved := false, - allocator := context.allocator, -) { - Scope :: struct { - offset: int, - id: int, - } - - Visit_Data :: struct { - ast_context: ^AstContext, - symbols: ^map[uintptr]SymbolAndNode, - scopes: [dynamic]Scope, - id_counter: int, - last_visit: ^ast.Node, - resolve_flag: ResolveReferenceFlag, - reference: string, - save_unresolved: bool, - document: ^Document, - } - - data := Visit_Data { - ast_context = ast_context, - symbols = symbols, - scopes = make([dynamic]Scope, allocator), - resolve_flag = flag, - reference = reference, - document = document, - save_unresolved = save_unresolved, - } - - visit :: proc(visitor: ^ast.Visitor, node: ^ast.Node) -> ^ast.Visitor { - if node == nil || visitor == nil { - return nil - } - data := cast(^Visit_Data)visitor.data - ast_context := data.ast_context - - reset_ast_context(ast_context) - - data.last_visit = node - - //It's somewhat silly to check the scope everytime, but the alternative is to implement my own walker function. - if len(data.scopes) > 0 { - current_scope := data.scopes[len(data.scopes) - 1] - - if current_scope.offset < node.end.offset { - clear_local_group(ast_context, current_scope.id) - - pop(&data.scopes) - - if len(data.scopes) > 0 { - current_scope = data.scopes[len(data.scopes) - 1] - ast_context.local_id = current_scope.id - } else { - ast_context.local_id = 0 - } - } - } - - #partial switch v in node.derived { - case ^ast.Proc_Lit: - if v.body == nil { - break - } - - scope: Scope - scope.id = data.id_counter - scope.offset = node.end.offset - data.id_counter += 1 - ast_context.local_id = scope.id - - append(&data.scopes, scope) - add_local_group(ast_context, scope.id) - - position_context: DocumentPositionContext - position_context.position = node.end.offset - - get_locals_proc_param_and_results( - ast_context.file, - v^, - ast_context, - &position_context, - ) - get_locals_stmt( - ast_context.file, - cast(^ast.Stmt)node, - ast_context, - &position_context, - ) - case ^ast.If_Stmt, - ^ast.For_Stmt, - ^ast.Range_Stmt, - ^ast.Inline_Range_Stmt: - scope: Scope - scope.id = data.id_counter - scope.offset = node.end.offset - data.id_counter += 1 - ast_context.local_id = scope.id - - append(&data.scopes, scope) - add_local_group(ast_context, scope.id) - - position_context: DocumentPositionContext - position_context.position = node.end.offset - get_locals_stmt( - ast_context.file, - cast(^ast.Stmt)node, - ast_context, - &position_context, - ) - } - - if data.resolve_flag == .None { - #partial switch v in node.derived { - case ^ast.Ident: - if symbol, ok := resolve_type_identifier(ast_context, v^); ok { - data.symbols[cast(uintptr)node] = SymbolAndNode { - node = v, - symbol = symbol, - is_resolved = true, - } - } else if data.save_unresolved { - data.symbols[cast(uintptr)node] = SymbolAndNode { - node = v, - } - } - case ^ast.Selector_Expr: - if symbol, ok := resolve_type_expression(ast_context, &v.node); - ok { - data.symbols[cast(uintptr)node] = SymbolAndNode { - node = v, - symbol = symbol, - is_resolved = true, - } - } else if data.save_unresolved { - data.symbols[cast(uintptr)node] = SymbolAndNode { - node = v, - } - } - case ^ast.Call_Expr: - if symbol, ok := resolve_type_expression(ast_context, &v.node); - ok { - data.symbols[cast(uintptr)node] = SymbolAndNode { - node = v, - symbol = symbol, - is_resolved = true, - } - } else if data.save_unresolved { - data.symbols[cast(uintptr)node] = SymbolAndNode { - node = v, - } - } - } - } else { - #partial done: switch v in node.derived { - case ^ast.Selector_Expr: - document: ^Document = data.document - - position_context := DocumentPositionContext { - position = v.pos.offset, - } - - get_document_position_decls( - document.ast.decls[:], - &position_context, - ) - - if symbol, ok := resolve_location_selector(ast_context, v); - ok { - data.symbols[cast(uintptr)node] = SymbolAndNode { - node = v.field, - symbol = symbol, - } - } - - if _, is_ident := v.field.derived.(^ast.Ident); is_ident { - if data.resolve_flag == .Constant || - data.resolve_flag == .Variable { - return nil - } - } - case ^ast.Ident: - if data.resolve_flag == .Variable && v.name != data.reference { - break done - } - - document: ^Document = data.document - - position_context := DocumentPositionContext { - position = v.pos.offset, - } - - get_document_position_decls( - document.ast.decls[:], - &position_context, - ) - - if position_context.field_value != nil && - position_in_node( - position_context.field_value.field, - v.pos.offset, - ) { - break done - } else if position_context.struct_type != nil && - data.resolve_flag != .StructElement { - break done - } else if position_context.enum_type != nil && - data.resolve_flag != .EnumElement { - break done - } - - if symbol, ok := resolve_location_identifier(ast_context, v^); - ok { - data.symbols[cast(uintptr)node] = SymbolAndNode { - node = v, - symbol = symbol, - } - } - } - } - - return visitor - } - - visitor := ast.Visitor { - data = &data, - visit = visit, - } - - ast.walk(&visitor, decl) -} - concatenate_symbol_information :: proc { concatenate_raw_symbol_information, concatenate_raw_string_information, @@ -4246,12 +4014,37 @@ unwrap_enum :: proc( if enum_symbol, ok := resolve_type_expression(ast_context, node); ok { if enum_value, ok := enum_symbol.value.(SymbolEnumValue); ok { return enum_value, true + } else if union_value, ok := enum_symbol.value.(SymbolUnionValue); ok { + return unwrap_super_enum(ast_context, union_value) } } return {}, false } +unwrap_super_enum :: proc( + ast_context: ^AstContext, + symbol_union: SymbolUnionValue, +) -> ( + ret_value: SymbolEnumValue, + ret_ok: bool, +) { + names := make([dynamic]string, 0, 20, ast_context.allocator) + ranges := make([dynamic]common.Range, 0, 20, ast_context.allocator) + + for type in symbol_union.types { + symbol := resolve_type_expression(ast_context, type) or_return + value := symbol.value.(SymbolEnumValue) or_return + append(&names, ..value.names) + append(&ranges, ..value.ranges) + } + + ret_value.names = names[:] + ret_value.ranges = ranges[:] + + return ret_value, true +} + unwrap_union :: proc( ast_context: ^AstContext, node: ^ast.Expr, @@ -4284,6 +4077,9 @@ unwrap_bitset :: proc( ); ok { if enum_value, ok := enum_symbol.value.(SymbolEnumValue); ok { return enum_value, true + } else if union_value, ok := enum_symbol.value.(SymbolUnionValue); + ok { + return unwrap_super_enum(ast_context, union_value) } } } @@ -5045,6 +4841,19 @@ position_in_node :: proc( ) } +get_document_position_label :: proc( + label: ^ast.Expr, + position_context: ^DocumentPositionContext, +) { + if label == nil { + return + } + + if ident, ok := label.derived.(^ast.Ident); ok { + position_context.label = ident + } +} + get_document_position_node :: proc( node: ^ast.Node, position_context: ^DocumentPositionContext, @@ -5081,12 +4890,14 @@ get_document_position_node :: proc( case ^Ellipsis: get_document_position(n.expr, position_context) case ^Proc_Lit: - get_document_position(n.type, position_context) - if position_in_node(n.body, position_context.position) { + get_document_position(n.type, position_context) position_context.function = cast(^Proc_Lit)node append(&position_context.functions, position_context.function) get_document_position(n.body, position_context) + } else if position_in_node(n.type, position_context.position) { + position_context.function = cast(^Proc_Lit)node + get_document_position(n.type, position_context) } case ^Comp_Lit: //only set this for the parent comp literal, since we will need to walk through it to infer types. @@ -5104,28 +4915,25 @@ get_document_position_node :: proc( get_document_position(n.expr, position_context) case ^Binary_Expr: if position_context.parent_binary == nil { - position_context.parent_binary = cast(^Binary_Expr)node + position_context.parent_binary = n } - position_context.binary = cast(^Binary_Expr)node + position_context.binary = n get_document_position(n.left, position_context) get_document_position(n.right, position_context) case ^Paren_Expr: get_document_position(n.expr, position_context) case ^Call_Expr: - if position_context.hint == .SignatureHelp || - position_context.hint == .Completion || - position_context.hint == .Definition { - position_context.call = cast(^Expr)node - } + position_context.call = n get_document_position(n.expr, position_context) get_document_position(n.args, position_context) case ^Selector_Call_Expr: if position_context.hint == .Definition || position_context.hint == .Hover || - position_context.hint == .SignatureHelp { + position_context.hint == .SignatureHelp || + position_context.hint == .Completion { position_context.selector = n.expr position_context.field = n.call - position_context.selector_expr = cast(^Selector_Expr)node + position_context.selector_expr = node if _, ok := n.call.derived.(^ast.Call_Expr); ok { position_context.call = n.call @@ -5139,18 +4947,11 @@ get_document_position_node :: proc( } } case ^Selector_Expr: - if position_context.hint == .Completion { - if n.field != nil && - n.field.pos.line - 1 == position_context.line { - //The parser is not fault tolerant enough, relying on the fallback as the main completion parsing for now - //position_context.selector = n.expr; - //position_context.field = n.field; - } - } else if position_context.hint == .Definition || + if position_context.hint == .Definition || position_context.hint == .Hover && n.field != nil { position_context.selector = n.expr position_context.field = n.field - position_context.selector_expr = cast(^Selector_Expr)node + position_context.selector_expr = node get_document_position(n.expr, position_context) get_document_position(n.field, position_context) } else { @@ -5158,6 +4959,8 @@ get_document_position_node :: proc( get_document_position(n.field, position_context) } case ^Index_Expr: + position_context.previous_index = position_context.index + position_context.index = n get_document_position(n.expr, position_context) get_document_position(n.index, position_context) case ^Deref_Expr: @@ -5167,7 +4970,7 @@ get_document_position_node :: proc( get_document_position(n.low, position_context) get_document_position(n.high, position_context) case ^Field_Value: - position_context.field_value = cast(^Field_Value)node + position_context.field_value = n get_document_position(n.field, position_context) get_document_position(n.value, position_context) case ^Ternary_If_Expr: @@ -5191,17 +4994,17 @@ get_document_position_node :: proc( case ^Expr_Stmt: get_document_position(n.expr, position_context) case ^Tag_Stmt: - r := cast(^Tag_Stmt)node + r := n get_document_position(r.stmt, position_context) case ^Assign_Stmt: - position_context.assign = cast(^Assign_Stmt)node + position_context.assign = n get_document_position(n.lhs, position_context) get_document_position(n.rhs, position_context) case ^Block_Stmt: - get_document_position(n.label, position_context) + get_document_position_label(n.label, position_context) get_document_position(n.stmts, position_context) case ^If_Stmt: - get_document_position(n.label, position_context) + get_document_position_label(n.label, position_context) get_document_position(n.init, position_context) get_document_position(n.cond, position_context) get_document_position(n.body, position_context) @@ -5211,18 +5014,18 @@ get_document_position_node :: proc( get_document_position(n.body, position_context) get_document_position(n.else_stmt, position_context) case ^Return_Stmt: - position_context.returns = cast(^Return_Stmt)node + position_context.returns = n get_document_position(n.results, position_context) case ^Defer_Stmt: get_document_position(n.stmt, position_context) case ^For_Stmt: - get_document_position(n.label, position_context) + get_document_position_label(n.label, position_context) get_document_position(n.init, position_context) get_document_position(n.cond, position_context) get_document_position(n.post, position_context) get_document_position(n.body, position_context) case ^Range_Stmt: - get_document_position(n.label, position_context) + get_document_position_label(n.label, position_context) get_document_position(n.vals, position_context) get_document_position(n.expr, position_context) get_document_position(n.body, position_context) @@ -5238,18 +5041,18 @@ get_document_position_node :: proc( get_document_position(n.body, position_context) case ^Switch_Stmt: position_context.switch_stmt = cast(^Switch_Stmt)node - get_document_position(n.label, position_context) + get_document_position_label(n.label, position_context) get_document_position(n.init, position_context) get_document_position(n.cond, position_context) get_document_position(n.body, position_context) case ^Type_Switch_Stmt: position_context.switch_type_stmt = cast(^Type_Switch_Stmt)node - get_document_position(n.label, position_context) + get_document_position_label(n.label, position_context) get_document_position(n.tag, position_context) get_document_position(n.expr, position_context) get_document_position(n.body, position_context) case ^Branch_Stmt: - get_document_position(n.label, position_context) + get_document_position_label(n.label, position_context) case ^Using_Stmt: get_document_position(n.list, position_context) case ^Bad_Decl: @@ -5307,21 +5110,21 @@ get_document_position_node :: proc( case ^Multi_Pointer_Type: get_document_position(n.elem, position_context) case ^Struct_Type: - position_context.struct_type = cast(^Struct_Type)node + position_context.struct_type = n get_document_position(n.poly_params, position_context) get_document_position(n.align, position_context) get_document_position(n.fields, position_context) case ^Union_Type: - position_context.union_type = cast(^Union_Type)node + position_context.union_type = n get_document_position(n.poly_params, position_context) get_document_position(n.align, position_context) get_document_position(n.variants, position_context) case ^Enum_Type: - position_context.enum_type = cast(^Enum_Type)node + position_context.enum_type = n get_document_position(n.base_type, position_context) get_document_position(n.fields, position_context) case ^Bit_Set_Type: - position_context.bitset_type = cast(^Bit_Set_Type)node + position_context.bitset_type = n get_document_position(n.elem, position_context) get_document_position(n.underlying, position_context) case ^Map_Type: @@ -5337,7 +5140,7 @@ get_document_position_node :: proc( case ^ast.Or_Return_Expr: get_document_position(n.expr, position_context) case ^ast.Bit_Field_Type: - position_context.bit_field_type = cast(^Bit_Field_Type)node + position_context.bit_field_type = n get_document_position(n.backing_type, position_context) get_document_position(n.fields, position_context) case ^ast.Bit_Field_Field: @@ -5345,8 +5148,8 @@ get_document_position_node :: proc( get_document_position(n.type, position_context) get_document_position(n.bit_size, position_context) case ^ast.Or_Branch_Expr: + get_document_position_label(n.label, position_context) get_document_position(n.expr, position_context) - get_document_position(n.label, position_context) case: } } diff --git a/src/server/build.odin b/src/server/build.odin index 810c161..312cb00 100644 --- a/src/server/build.odin +++ b/src/server/build.odin @@ -189,17 +189,7 @@ setup_index :: proc() { ) indexer.index = make_memory_index(symbol_collection) - dir_exe, ok := filepath.abs( - path.dir(os.args[0], context.temp_allocator), - context.temp_allocator, - ) - - if !ok { - log.error( - "Failed to find ols executable path to build the builtin packages", - ) - return - } + dir_exe := common.get_executable_path() try_build_package(path.join({dir_exe, "builtin"}, context.temp_allocator)) } diff --git a/src/server/caches.odin b/src/server/caches.odin index d286624..a861cc6 100644 --- a/src/server/caches.odin +++ b/src/server/caches.odin @@ -24,9 +24,7 @@ resolve_entire_file_cached :: proc( file_resolve_cache.files[document.uri.uri] = FileResolve { symbols = resolve_entire_file( document, - "", .None, - false, common.scratch_allocator(document.allocator), ), } diff --git a/src/server/collector.odin b/src/server/collector.odin index eecf2ed..bdc69a6 100644 --- a/src/server/collector.odin +++ b/src/server/collector.odin @@ -39,6 +39,7 @@ SymbolPackage :: struct { symbols: map[string]Symbol, objc_structs: map[string]ObjcStruct, //mapping from struct name to function methods: map[Method][dynamic]Symbol, + imports: [dynamic]string, //Used for references to figure whether the package is even able to reference the symbol } get_index_unique_string :: proc { @@ -100,6 +101,7 @@ delete_symbol_collection :: proc(collection: SymbolCollection) { delete(v.methods) delete(v.objc_structs) delete(v.symbols) + delete(v.imports) } delete(collection.packages) @@ -566,6 +568,10 @@ collect_objc :: proc( } } +collect_imports :: proc(collection: ^SymbolCollection, file: ast.File) { + +} + collect_symbols :: proc( collection: ^SymbolCollection, file: ast.File, @@ -577,6 +583,8 @@ collect_symbols :: proc( exprs := common.collect_globals(file, true) + collect_imports(collection, file) + for expr in exprs { symbol: Symbol diff --git a/src/server/completion.odin b/src/server/completion.odin index d3dfa08..42bec3e 100644 --- a/src/server/completion.odin +++ b/src/server/completion.odin @@ -99,7 +99,19 @@ get_completion_list :: proc( } if position_context.selector != nil { - completion_type = .Selector + if position_context.selector_expr != nil { + if selector_call, ok := position_context.selector_expr.derived.(^ast.Selector_Call_Expr); + ok { + if !position_in_node( + selector_call.call, + position_context.position, + ) { + completion_type = .Selector + } + } + } else { + completion_type = .Selector + } } if position_context.tag != nil { @@ -1271,6 +1283,42 @@ get_implicit_completion :: proc( } } } + + if position_context.index != nil { + symbol: Symbol + ok := false + if position_context.previous_index != nil { + symbol, ok = resolve_type_expression( + ast_context, + position_context.previous_index, + ) + if !ok { + return + } + } else { + symbol, ok = resolve_type_expression( + ast_context, + position_context.index.expr, + ) + } + + if array, ok := symbol.value.(SymbolFixedArrayValue); ok { + if enum_value, ok := unwrap_enum(ast_context, array.len); ok { + for name in enum_value.names { + item := CompletionItem { + label = name, + kind = .EnumMember, + detail = name, + } + + append(&items, item) + } + + list.items = items[:] + return + } + } + } } get_identifier_completion :: proc( @@ -1398,6 +1446,10 @@ get_identifier_completion :: proc( k, ) + if local_offset == -1 { + continue + } + reset_ast_context(ast_context) ast_context.current_package = ast_context.document_package diff --git a/src/server/definition.odin b/src/server/definition.odin index 021a556..9d32faa 100644 --- a/src/server/definition.odin +++ b/src/server/definition.odin @@ -90,10 +90,10 @@ get_definition_location :: proc( if position_context.import_stmt != nil { if get_all_package_file_locations( - document, - position_context.import_stmt, - &locations, - ) { + document, + position_context.import_stmt, + &locations, + ) { return locations[:], true } } else if position_context.selector_expr != nil { @@ -130,6 +130,8 @@ get_definition_location :: proc( ); ok { location.range = resolved.range uri = resolved.uri + } else { + return {}, false } } else if position_context.field_value != nil && position_context.comp_lit != nil && diff --git a/src/server/file_resolve.odin b/src/server/file_resolve.odin new file mode 100644 index 0000000..09da787 --- /dev/null +++ b/src/server/file_resolve.odin @@ -0,0 +1,540 @@ +package server + +import "core:fmt" +import "core:log" +import "core:mem" +import "core:odin/ast" +import "core:odin/parser" +import "core:odin/tokenizer" +import "core:path/filepath" +import path "core:path/slashpath" +import "core:reflect" +import "core:slice" +import "core:sort" +import "core:strconv" +import "core:strings" +import "core:unicode/utf8" + +import "src:common" + +ResolveReferenceFlag :: enum { + None, + Identifier, + Base, + Field, +} + +@(private = "file") +reset_position_context :: proc(position_context: ^DocumentPositionContext) { + position_context.comp_lit = nil + position_context.parent_comp_lit = nil + position_context.identifier = nil + position_context.call = nil + position_context.binary = nil + position_context.parent_binary = nil + position_context.previous_index = nil + position_context.index = nil +} + +resolve_entire_file :: proc( + document: ^Document, + flag := ResolveReferenceFlag.None, + allocator := context.allocator, +) -> map[uintptr]SymbolAndNode { + ast_context := make_ast_context( + document.ast, + document.imports, + document.package_name, + document.uri.uri, + document.fullpath, + allocator, + ) + + position_context: DocumentPositionContext + + get_globals(document.ast, &ast_context) + + ast_context.current_package = ast_context.document_package + + symbols := make(map[uintptr]SymbolAndNode, 10000, allocator) + + for decl in document.ast.decls { + if _, is_value := decl.derived.(^ast.Value_Decl); !is_value { + continue + } + + resolve_decl( + &position_context, + &ast_context, + document, + decl, + &symbols, + flag, + allocator, + ) + clear(&ast_context.locals) + } + + return symbols +} + +FileResolveData :: struct { + ast_context: ^AstContext, + symbols: ^map[uintptr]SymbolAndNode, + id_counter: int, + document: ^Document, + position_context: ^DocumentPositionContext, + flag: ResolveReferenceFlag, +} + +@(private = "file") +resolve_decl :: proc( + position_context: ^DocumentPositionContext, + ast_context: ^AstContext, + document: ^Document, + decl: ^ast.Node, + symbols: ^map[uintptr]SymbolAndNode, + flag: ResolveReferenceFlag, + allocator := context.allocator, +) { + data := FileResolveData { + position_context = position_context, + ast_context = ast_context, + symbols = symbols, + document = document, + flag = flag, + } + + resolve_node(decl, &data) +} + + +@(private = "file") +local_scope_deferred :: proc(data: ^FileResolveData, stmt: ^ast.Stmt) { + clear_local_group(data.ast_context, data.ast_context.local_id) + data.ast_context.local_id -= 1 +} + +@(deferred_in = local_scope_deferred) +@(private = "file") +local_scope :: proc(data: ^FileResolveData, stmt: ^ast.Stmt) { + if stmt == nil { + return + } + + data.ast_context.local_id += 1 + + add_local_group(data.ast_context, data.ast_context.local_id) + + data.position_context.position = stmt.end.offset + + get_locals_stmt( + data.ast_context.file, + stmt, + data.ast_context, + data.position_context, + ) +} + +@(private = "file") +resolve_node :: proc(node: ^ast.Node, data: ^FileResolveData) { + using ast + + if node == nil { + return + } + + reset_ast_context(data.ast_context) + + #partial switch n in node.derived { + case ^Bad_Expr: + case ^Ident: + data.position_context.identifier = node + if data.flag != .None { + if symbol, ok := resolve_location_identifier(data.ast_context, n^); + ok { + data.symbols[cast(uintptr)node] = SymbolAndNode { + node = n, + symbol = symbol, + } + } + } else { + if symbol, ok := resolve_type_identifier(data.ast_context, n^); + ok { + data.symbols[cast(uintptr)node] = SymbolAndNode { + node = n, + symbol = symbol, + } + } + } + case ^Selector_Call_Expr: + data.position_context.selector = n.expr + data.position_context.field = n.call + data.position_context.selector_expr = node + + if _, ok := n.call.derived.(^ast.Call_Expr); ok { + data.position_context.call = n.call + } + + resolve_node(n.expr, data) + resolve_node(n.call, data) + case ^Implicit_Selector_Expr: + data.position_context.implicit = true + data.position_context.implicit_selector_expr = n + if data.flag != .None { + data.position_context.position = n.pos.offset + if symbol, ok := resolve_location_implicit_selector( + data.ast_context, + data.position_context, + n, + ); ok { + data.symbols[cast(uintptr)node] = SymbolAndNode { + node = n, + symbol = symbol, + } + } + } + resolve_node(n.field, data) + case ^Selector_Expr: + data.position_context.selector = n.expr + data.position_context.field = n.field + data.position_context.selector_expr = node + + if data.flag != .None { + if symbol, ok := resolve_location_selector(data.ast_context, n); + ok { + if data.flag != .Base { + data.symbols[cast(uintptr)node] = SymbolAndNode { + node = n.field, + symbol = symbol, + } + } else { + data.symbols[cast(uintptr)node] = SymbolAndNode { + node = n, + symbol = symbol, + } + } + + } + } else { + if symbol, ok := resolve_type_expression( + data.ast_context, + &n.node, + ); ok { + data.symbols[cast(uintptr)node] = SymbolAndNode { + node = n, + symbol = symbol, + } + } + } + + resolve_node(n.expr, data) + resolve_node(n.field, data) + case ^Field_Value: + data.position_context.field_value = n + + if data.flag != .None && data.position_context.comp_lit != nil { + data.position_context.position = n.pos.offset + + if symbol, ok := resolve_location_comp_lit_field( + data.ast_context, + data.position_context, + ); ok { + data.symbols[cast(uintptr)node] = SymbolAndNode { + node = n.field, + symbol = symbol, + } + } + + resolve_node(n.value, data) + } else { + resolve_node(n.field, data) + resolve_node(n.value, data) + } + case ^Proc_Lit: + local_scope(data, n.body) + + get_locals_proc_param_and_results( + data.ast_context.file, + n^, + data.ast_context, + data.position_context, + ) + + resolve_node(n.type, data) + + data.position_context.function = cast(^Proc_Lit)node + + append( + &data.position_context.functions, + data.position_context.function, + ) + + resolve_node(n.body, data) + case ^ast.Inline_Range_Stmt: + local_scope(data, n) + resolve_node(n.val0, data) + resolve_node(n.val1, data) + resolve_node(n.expr, data) + resolve_node(n.body, data) + case ^For_Stmt: + local_scope(data, n) + resolve_node(n.label, data) + resolve_node(n.init, data) + resolve_node(n.cond, data) + resolve_node(n.post, data) + resolve_node(n.body, data) + case ^Range_Stmt: + local_scope(data, n) + resolve_node(n.label, data) + resolve_nodes(n.vals, data) + resolve_node(n.expr, data) + resolve_node(n.body, data) + case ^Switch_Stmt: + local_scope(data, n) + data.position_context.switch_stmt = n + resolve_node(n.label, data) + resolve_node(n.init, data) + resolve_node(n.cond, data) + resolve_node(n.body, data) + case ^If_Stmt: + local_scope(data, n) + resolve_node(n.label, data) + resolve_node(n.init, data) + resolve_node(n.cond, data) + resolve_node(n.body, data) + resolve_node(n.else_stmt, data) + case ^When_Stmt: + resolve_node(n.cond, data) + resolve_node(n.body, data) + resolve_node(n.else_stmt, data) + case ^Implicit: + if n.tok.text == "context" { + data.position_context.implicit_context = n + } + case ^Undef: + case ^Basic_Lit: + data.position_context.basic_lit = cast(^Basic_Lit)node + case ^Matrix_Index_Expr: + resolve_node(n.expr, data) + resolve_node(n.row_index, data) + resolve_node(n.column_index, data) + case ^Matrix_Type: + resolve_node(n.row_count, data) + resolve_node(n.column_count, data) + resolve_node(n.elem, data) + case ^Ellipsis: + resolve_node(n.expr, data) + case ^Comp_Lit: + //only set this for the parent comp literal, since we will need to walk through it to infer types. + if data.position_context.parent_comp_lit == nil { + data.position_context.parent_comp_lit = n + } + + data.position_context.comp_lit = n + + resolve_node(n.type, data) + resolve_nodes(n.elems, data) + case ^Tag_Expr: + resolve_node(n.expr, data) + case ^Unary_Expr: + resolve_node(n.expr, data) + case ^Binary_Expr: + if data.position_context.parent_binary == nil { + data.position_context.parent_binary = cast(^Binary_Expr)node + } + data.position_context.binary = n + resolve_node(n.left, data) + resolve_node(n.right, data) + case ^Paren_Expr: + resolve_node(n.expr, data) + case ^Call_Expr: + data.position_context.call = n + resolve_node(n.expr, data) + + for arg in n.args { + data.position_context.position = arg.pos.offset + resolve_node(arg, data) + } + case ^Index_Expr: + resolve_node(n.expr, data) + resolve_node(n.index, data) + case ^Deref_Expr: + resolve_node(n.expr, data) + case ^Slice_Expr: + resolve_node(n.expr, data) + resolve_node(n.low, data) + resolve_node(n.high, data) + case ^Ternary_If_Expr: + resolve_node(n.x, data) + resolve_node(n.cond, data) + resolve_node(n.y, data) + case ^Ternary_When_Expr: + resolve_node(n.x, data) + resolve_node(n.cond, data) + resolve_node(n.y, data) + case ^Type_Assertion: + resolve_node(n.expr, data) + resolve_node(n.type, data) + case ^Type_Cast: + resolve_node(n.type, data) + resolve_node(n.expr, data) + case ^Auto_Cast: + resolve_node(n.expr, data) + case ^Bad_Stmt: + case ^Empty_Stmt: + case ^Expr_Stmt: + resolve_node(n.expr, data) + case ^Tag_Stmt: + r := cast(^Tag_Stmt)node + resolve_node(r.stmt, data) + case ^Block_Stmt: + resolve_node(n.label, data) + resolve_nodes(n.stmts, data) + case ^Return_Stmt: + data.position_context.returns = n + resolve_nodes(n.results, data) + case ^Defer_Stmt: + resolve_node(n.stmt, data) + case ^Case_Clause: + resolve_nodes(n.list, data) + resolve_nodes(n.body, data) + case ^Type_Switch_Stmt: + data.position_context.switch_type_stmt = n + resolve_node(n.label, data) + resolve_node(n.tag, data) + resolve_node(n.expr, data) + resolve_node(n.body, data) + case ^Branch_Stmt: + resolve_node(n.label, data) + case ^Using_Stmt: + resolve_nodes(n.list, data) + case ^Bad_Decl: + case ^Assign_Stmt: + data.position_context.assign = n + reset_position_context(data.position_context) + resolve_nodes(n.lhs, data) + resolve_nodes(n.rhs, data) + case ^Value_Decl: + data.position_context.value_decl = n + reset_position_context(data.position_context) + resolve_nodes(n.names, data) + resolve_node(n.type, data) + resolve_nodes(n.values, data) + case ^Package_Decl: + case ^Import_Decl: + case ^Foreign_Block_Decl: + resolve_node(n.foreign_library, data) + resolve_node(n.body, data) + case ^Foreign_Import_Decl: + resolve_node(n.name, data) + case ^Proc_Group: + resolve_nodes(n.args, data) + case ^Attribute: + resolve_nodes(n.elems, data) + case ^Field: + resolve_nodes(n.names, data) + resolve_node(n.type, data) + resolve_node(n.default_value, data) + case ^Field_List: + resolve_nodes(n.list, data) + case ^Typeid_Type: + resolve_node(n.specialization, data) + case ^Helper_Type: + resolve_node(n.type, data) + case ^Distinct_Type: + resolve_node(n.type, data) + case ^Poly_Type: + resolve_node(n.type, data) + resolve_node(n.specialization, data) + case ^Proc_Type: + resolve_node(n.params, data) + resolve_node(n.results, data) + case ^Pointer_Type: + resolve_node(n.elem, data) + case ^Array_Type: + resolve_node(n.len, data) + resolve_node(n.elem, data) + case ^Dynamic_Array_Type: + resolve_node(n.elem, data) + case ^Multi_Pointer_Type: + resolve_node(n.elem, data) + case ^Struct_Type: + data.position_context.struct_type = n + resolve_node(n.poly_params, data) + resolve_node(n.align, data) + resolve_node(n.fields, data) + + if data.flag != .None { + for field in n.fields.list { + for name in field.names { + data.symbols[cast(uintptr)name] = SymbolAndNode { + node = name, + symbol = Symbol { + range = common.get_token_range( + name, + string(data.document.text), + ), + }, + } + } + } + } + case ^Union_Type: + data.position_context.union_type = n + resolve_node(n.poly_params, data) + resolve_node(n.align, data) + resolve_nodes(n.variants, data) + case ^Enum_Type: + data.position_context.enum_type = n + resolve_node(n.base_type, data) + resolve_nodes(n.fields, data) + + if data.flag != .None { + for field in n.fields { + data.symbols[cast(uintptr)field] = SymbolAndNode { + node = field, + symbol = Symbol { + range = common.get_token_range( + field, + string(data.document.text), + ), + }, + } + } + } + case ^Bit_Set_Type: + data.position_context.bitset_type = n + resolve_node(n.elem, data) + resolve_node(n.underlying, data) + case ^Map_Type: + resolve_node(n.key, data) + resolve_node(n.value, data) + case ^ast.Or_Else_Expr: + resolve_node(n.x, data) + resolve_node(n.y, data) + case ^ast.Or_Return_Expr: + resolve_node(n.expr, data) + case ^ast.Bit_Field_Type: + data.position_context.bit_field_type = n + resolve_node(n.backing_type, data) + resolve_nodes(n.fields, data) + case ^ast.Bit_Field_Field: + resolve_node(n.name, data) + resolve_node(n.type, data) + resolve_node(n.bit_size, data) + case ^ast.Or_Branch_Expr: + resolve_node(n.expr, data) + resolve_node(n.label, data) + case: + } + + +} + +@(private = "file") +resolve_nodes :: proc(array: []$T/^ast.Node, data: ^FileResolveData) { + for elem in array { + resolve_node(elem, data) + } +} diff --git a/src/server/generics.odin b/src/server/generics.odin index 65271b2..9e915c3 100644 --- a/src/server/generics.odin +++ b/src/server/generics.odin @@ -261,6 +261,25 @@ resolve_poly :: proc( return true } } + case ^ast.Pointer_Type: + if call_pointer, ok := call_node.derived.(^ast.Pointer_Type); ok { + if poly_type, ok := p.elem.derived.(^ast.Poly_Type); ok { + if ident, ok := unwrap_ident(poly_type.type); ok { + save_poly_map(ident, call_pointer.elem, poly_map) + } + + if poly_type.specialization != nil { + return resolve_poly( + ast_context, + call_pointer.elem, + call_symbol, + p.elem, + poly_map, + ) + } + return true + } + } case ^ast.Comp_Lit: if comp_lit, ok := call_node.derived.(^ast.Comp_Lit); ok { if poly_type, ok := p.type.derived.(^ast.Poly_Type); ok { @@ -284,7 +303,7 @@ resolve_poly :: proc( return true } case: - log.panicf("Unhandled specialization %v", specialization.derived) + log.error("Unhandled specialization %v", specialization.derived) } return false diff --git a/src/server/imports.odin b/src/server/imports.odin index 7bdf6a9..cd2271d 100644 --- a/src/server/imports.odin +++ b/src/server/imports.odin @@ -1,7 +1,7 @@ package server -import "core:odin/ast" import "core:mem" +import "core:odin/ast" fix_imports :: proc(document: ^Document) { @@ -11,7 +11,7 @@ fix_imports :: proc(document: ^Document) { context.allocator = mem.arena_allocator(&arena) - symbols_and_nodes := resolve_entire_file(document, "", .None, true) + symbols_and_nodes := resolve_entire_file(document, .None) } diff --git a/src/server/references.odin b/src/server/references.odin index 49d95bf..17add83 100644 --- a/src/server/references.odin +++ b/src/server/references.odin @@ -10,6 +10,7 @@ import "core:odin/parser" import "core:os" import "core:path/filepath" import path "core:path/slashpath" +import "core:slice" import "core:strings" import "src:common" @@ -24,6 +25,8 @@ walk_directories :: proc( err: os.Errno, skip_dir: bool, ) { + document := cast(^Document)user_data + if info.is_dir { return 0, false } @@ -33,230 +36,364 @@ walk_directories :: proc( } if strings.contains(info.name, ".odin") { - append(&fullpaths, strings.clone(info.fullpath)) - } - - return 0, false -} - -position_in_struct_names :: proc( - position_context: ^DocumentPositionContext, - type: ^ast.Struct_Type, -) -> bool { - for field in type.fields.list { - for name in field.names { - if position_in_node(name, position_context.position) { - return true - } + slash_path, _ := filepath.to_slash( + info.fullpath, + context.temp_allocator, + ) + if slash_path != document.fullpath { + append( + &fullpaths, + strings.clone(info.fullpath, context.temp_allocator), + ) } } - return false + return 0, false } - -resolve_references :: proc( +prepare_references :: proc( + document: ^Document, ast_context: ^AstContext, position_context: ^DocumentPositionContext, ) -> ( - []common.Location, - bool, + symbol: Symbol, + resolve_flag: ResolveReferenceFlag, + ok: bool, ) { - locations := make([dynamic]common.Location, 0, ast_context.allocator) - fullpaths = make([dynamic]string, 0, ast_context.allocator) - - resolve_flag: ResolveReferenceFlag + ok = false reference := "" - symbol: Symbol - ok: bool pkg := "" - filepath.walk( - filepath.dir(os.args[0], context.allocator), - walk_directories, - nil, - ) - - for workspace in common.config.workspace_folders { - uri, _ := common.parse_uri(workspace.uri, context.temp_allocator) - filepath.walk(uri.path, walk_directories, nil) - } - - reset_ast_context(ast_context) - - if position_context.struct_type != nil && - position_in_struct_names( - position_context, - position_context.struct_type, - ) { - return {}, true + if position_context.label != nil { + return + } else if position_context.struct_type != nil { + found := false + done_struct: for field in position_context.struct_type.fields.list { + for name in field.names { + if position_in_node(name, position_context.position) { + symbol = Symbol { + range = common.get_token_range( + name, + string(document.text), + ), + } + found = true + resolve_flag = .Field + break done_struct + } + } + } + if !found { + return + } } else if position_context.enum_type != nil { - return {}, true + /* + found := false + done_enum: for field in position_context.struct_type.fields.list { + for name in field.names { + if position_in_node(name, position_context.position) { + symbol = Symbol { + range = common.get_token_range( + name, + string(document.text), + ), + } + found = true + resolve_flag = .Field + break done_enum + } + } + } + if !found { + return {}, false + } + */ } else if position_context.bitset_type != nil { - return {}, true + return } else if position_context.union_type != nil { - return {}, true - } else if position_context.selector_expr != nil { - if resolved, ok := resolve_type_expression( - ast_context, - position_context.selector, - ); ok { - if _, is_package := resolved.value.(SymbolPackageValue); - !is_package { - return {}, true + found := false + for variant in position_context.union_type.variants { + if position_in_node(variant, position_context.position) { + if ident, ok := variant.derived.(^ast.Ident); ok { + symbol, ok = resolve_location_identifier( + ast_context, + ident^, + ) + reference = ident.name + resolve_flag = .Identifier + + if !ok { + return + } + + found = true + + break + } else { + return + } } - resolve_flag = .Constant + } + if !found { + return } - symbol, ok = resolve_location_selector( + } else if position_context.field_value != nil && + position_context.comp_lit != nil && + !common.is_expr_basic_lit(position_context.field_value.field) && + position_in_node( + position_context.field_value.field, + position_context.position, + ) { + symbol, ok = resolve_location_comp_lit_field( ast_context, - position_context.selector_expr, + position_context, ) if !ok { - return {}, true + return } - if ident, ok := position_context.identifier.derived.(^ast.Ident); ok { - reference = ident.name - } else { - return {}, true + //Only support structs for now + if _, ok := symbol.value.(SymbolStructValue); !ok { + return } - } else if position_context.implicit { - return {}, true - } else if position_context.identifier != nil { - ident := position_context.identifier.derived.(^ast.Ident) - if resolved, ok := resolve_type_identifier(ast_context, ident^); ok { - if resolved.type == .Variable { - resolve_flag = .Variable - } else { - resolve_flag = .Constant + resolve_flag = .Field + } else if position_context.selector_expr != nil { + resolve_flag = .Field + + base: ^ast.Ident + base, ok = position_context.selector.derived.(^ast.Ident) + + if position_in_node(base, position_context.position) && + position_context.identifier != nil && + ok { + + ident := position_context.identifier.derived.(^ast.Ident) + + symbol, ok = resolve_location_identifier(ast_context, ident^) + + if !ok { + return } + + resolve_flag = .Base } else { - log.errorf( - "Failed to resolve identifier for indexing: %v", - ident.name, + symbol, ok = resolve_location_selector( + ast_context, + position_context.selector_expr, ) - return {}, true + + resolve_flag = .Field } + } else if position_context.implicit { + resolve_flag = .Field + + symbol, ok = resolve_location_implicit_selector( + ast_context, + position_context, + position_context.implicit_selector_expr, + ) + + if !ok { + return + } + } else if position_context.identifier != nil { + ident := position_context.identifier.derived.(^ast.Ident) reference = ident.name symbol, ok = resolve_location_identifier(ast_context, ident^) + resolve_flag = .Identifier + if !ok { - return {}, true + return } + } else { + return } + return symbol, resolve_flag, true +} + +resolve_references :: proc( + document: ^Document, + ast_context: ^AstContext, + position_context: ^DocumentPositionContext, +) -> ( + []common.Location, + bool, +) { + locations := make([dynamic]common.Location, 0, ast_context.allocator) + fullpaths = make([dynamic]string, 0, ast_context.allocator) + + symbol, resolve_flag, ok := prepare_references( + document, + ast_context, + position_context, + ) + if !ok { return {}, true } - resolve_arena: mem.Arena - mem.arena_init(&resolve_arena, make([]byte, mem.Megabyte * 25)) + when !ODIN_TEST { + for workspace in common.config.workspace_folders { + uri, _ := common.parse_uri(workspace.uri, context.temp_allocator) + filepath.walk(uri.path, walk_directories, document) + } + } - context.allocator = mem.arena_allocator(&resolve_arena) + reset_ast_context(ast_context) - for fullpath in fullpaths { - data, ok := os.read_entire_file(fullpath, context.allocator) - if !ok { - log.errorf("failed to read entire file for indexing %v", fullpath) - continue - } + arena: runtime.Arena - p := parser.Parser { - err = log_error_handler, - warn = log_warning_handler, - flags = {.Optional_Semicolons}, - } + _ = runtime.arena_init( + &arena, + mem.Megabyte * 40, + runtime.default_allocator(), + ) - dir := filepath.dir(fullpath) - base := filepath.base(dir) - forward_dir, _ := filepath.to_slash(dir) + defer runtime.arena_destroy(&arena) - pkg := new(ast.Package) - pkg.kind = .Normal - pkg.fullpath = fullpath - pkg.name = base + context.allocator = runtime.arena_allocator(&arena) - if base == "runtime" { - pkg.kind = .Runtime - } + fullpaths := slice.unique(fullpaths[:]) - file := ast.File { - fullpath = fullpath, - src = string(data), - pkg = pkg, - } + if .Local not_in symbol.flags { + for fullpath in fullpaths { + dir := filepath.dir(fullpath) + base := filepath.base(dir) + forward_dir, _ := filepath.to_slash(dir) - ok = parser.parse_file(&p, &file) + data, ok := os.read_entire_file(fullpath, context.allocator) - if !ok { - if !strings.contains(fullpath, "builtin.odin") && - !strings.contains(fullpath, "intrinsics.odin") { - log.errorf("error in parse file for indexing %v", fullpath) + if !ok { + log.errorf( + "failed to read entire file for indexing %v", + fullpath, + ) + continue } - continue - } - uri := common.create_uri(fullpath, context.allocator) + p := parser.Parser { + err = log_error_handler, + warn = log_warning_handler, + flags = {.Optional_Semicolons}, + } - document := Document { - ast = file, - } - document.uri = uri - document.text = transmute([]u8)file.src - document.used_text = len(file.src) + pkg := new(ast.Package) + pkg.kind = .Normal + pkg.fullpath = fullpath + pkg.name = base - document_setup(&document) + if base == "runtime" { + pkg.kind = .Runtime + } - parse_imports(&document, &common.config) + file := ast.File { + fullpath = fullpath, + src = string(data), + pkg = pkg, + } - in_pkg := false + ok = parser.parse_file(&p, &file) - for pkg in document.imports { - if pkg.name == symbol.pkg || forward_dir == symbol.pkg { - in_pkg = true + if !ok { + if !strings.contains(fullpath, "builtin.odin") && + !strings.contains(fullpath, "intrinsics.odin") { + log.errorf("error in parse file for indexing %v", fullpath) + } continue } - } - if in_pkg { - symbols_and_nodes := resolve_entire_file( - &document, - reference, - resolve_flag, - false, - context.allocator, - ) + uri := common.create_uri(fullpath, context.allocator) - for k, v in symbols_and_nodes { - if v.symbol.uri == symbol.uri && - v.symbol.range == symbol.range { - node_uri := common.create_uri( - v.node.pos.file, - ast_context.allocator, - ) + document := Document { + ast = file, + } - location := common.Location { - range = common.get_token_range( - v.node^, - string(document.text), - ), - uri = strings.clone( - node_uri.uri, + document.uri = uri + document.text = transmute([]u8)file.src + document.used_text = len(file.src) + + document_setup(&document) + + parse_imports(&document, &common.config) + + in_pkg := false + + for pkg in document.imports { + if pkg.name == symbol.pkg { + in_pkg = true + continue + } + } + + if in_pkg || symbol.pkg == document.package_name { + symbols_and_nodes := resolve_entire_file( + &document, + resolve_flag, + context.allocator, + ) + + for k, v in symbols_and_nodes { + if v.symbol.uri == symbol.uri && + v.symbol.range == symbol.range { + node_uri := common.create_uri( + v.node.pos.file, ast_context.allocator, - ), + ) + + location := common.Location { + range = common.get_token_range( + v.node^, + string(document.text), + ), + uri = strings.clone( + node_uri.uri, + ast_context.allocator, + ), + } + append(&locations, location) } - append(&locations, location) } } + + free_all(context.allocator) } + } - free_all(context.allocator) + symbols_and_nodes := resolve_entire_file( + document, + resolve_flag, + context.allocator, + ) + + for k, v in symbols_and_nodes { + if v.symbol.uri == symbol.uri && v.symbol.range == symbol.range { + node_uri := common.create_uri( + v.node.pos.file, + ast_context.allocator, + ) + + range := common.get_token_range(v.node^, string(document.text)) + + //We don't have to have the `.` with, otherwise it renames the dot. + if _, ok := v.node.derived.(^ast.Implicit_Selector_Expr); ok { + range.start.character += 1 + } + + location := common.Location { + range = range, + uri = strings.clone(node_uri.uri, ast_context.allocator), + } + + append(&locations, location) + } } return locations[:], true @@ -269,21 +406,13 @@ get_references :: proc( []common.Location, bool, ) { - data := make([]byte, mem.Megabyte * 55, runtime.default_allocator()) - defer delete(data) - - arena: mem.Arena - mem.arena_init(&arena, data) - - context.allocator = mem.arena_allocator(&arena) - ast_context := make_ast_context( document.ast, document.imports, document.package_name, document.uri.uri, document.fullpath, - context.allocator, + context.temp_allocator, ) position_context, ok := get_document_position_context( @@ -305,7 +434,11 @@ get_references :: proc( ) } - locations, ok2 := resolve_references(&ast_context, &position_context) + locations, ok2 := resolve_references( + document, + &ast_context, + &position_context, + ) temp_locations := make([dynamic]common.Location, 0, context.temp_allocator) diff --git a/src/server/rename.odin b/src/server/rename.odin index 252ebaa..6041641 100644 --- a/src/server/rename.odin +++ b/src/server/rename.odin @@ -17,21 +17,13 @@ get_rename :: proc( WorkspaceEdit, bool, ) { - data := make([]byte, mem.Megabyte * 55, runtime.default_allocator()) - defer delete(data) - - arena: mem.Arena - mem.arena_init(&arena, data) - - context.allocator = mem.arena_allocator(&arena) - ast_context := make_ast_context( document.ast, document.imports, document.package_name, document.uri.uri, document.fullpath, - context.allocator, + context.temp_allocator, ) position_context, ok := get_document_position_context( @@ -53,30 +45,21 @@ get_rename :: proc( ) } - locations, ok2 := resolve_references(&ast_context, &position_context) - - document_edits := make( - map[string][dynamic]TextEdit, - 0, - context.temp_allocator, + locations, ok2 := resolve_references( + document, + &ast_context, + &position_context, ) + changes := make(map[string][dynamic]TextEdit, 0, context.temp_allocator) + for location in locations { edits: ^[dynamic]TextEdit - /* - if location.range.start.line <= position.line && - position.line <= location.range.end.line && - location.range.start.character <= position.character && - position.character <= location.range.end.character { - continue - } - */ - - if edits = &document_edits[location.uri]; edits == nil { - document_edits[strings.clone(location.uri, context.temp_allocator)] = + if edits = &changes[location.uri]; edits == nil { + changes[strings.clone(location.uri, context.temp_allocator)] = make([dynamic]TextEdit, context.temp_allocator) - edits = &document_edits[location.uri] + edits = &changes[location.uri] } append(edits, TextEdit{newText = new_text, range = location.range}) @@ -84,19 +67,61 @@ get_rename :: proc( workspace: WorkspaceEdit - document_changes := make([dynamic]TextDocumentEdit, context.temp_allocator) + workspace.changes = make( + map[string][]TextEdit, + len(changes), + context.temp_allocator, + ) + + for k, v in changes { + workspace.changes[k] = v[:] + } + + return workspace, true +} + + +get_prepare_rename :: proc( + document: ^Document, + position: common.Position, +) -> ( + common.Range, + bool, +) { + ast_context := make_ast_context( + document.ast, + document.imports, + document.package_name, + document.uri.uri, + document.fullpath, + context.temp_allocator, + ) + + position_context, ok := get_document_position_context( + document, + position, + .Hover, + ) + + get_globals(document.ast, &ast_context) + + ast_context.current_package = ast_context.document_package - for k, v in document_edits { - append( - &document_changes, - TextDocumentEdit { - edits = v[:], - textDocument = {uri = k, version = document.version}, - }, + if position_context.function != nil { + get_locals( + document.ast, + position_context.function, + &ast_context, + &position_context, ) } - workspace.documentChanges = document_changes[:] + symbol, _, ok2 := prepare_references( + document, + &ast_context, + &position_context, + ) + - return workspace, true + return symbol.range, ok2 } diff --git a/src/server/requests.odin b/src/server/requests.odin index 0fd73ba..b47c4a0 100644 --- a/src/server/requests.odin +++ b/src/server/requests.odin @@ -275,6 +275,7 @@ call_map: map[string]proc( "textDocument/inlayHint" = request_inlay_hint, "textDocument/documentLink" = request_document_links, "textDocument/rename" = request_rename, + "textDocument/prepareRename" = request_prepare_rename, "textDocument/references" = request_references, "window/progress" = request_noop, "workspace/symbol" = request_workspace_symbols, @@ -696,10 +697,10 @@ request_initialize :: proc( config.enable_semantic_tokens = false config.enable_procedure_context = false config.enable_snippets = false - config.enable_references = false + config.enable_references = true config.verbose = false config.file_log = false - config.enable_rename = false + config.enable_rename = true config.odin_command = "" config.checker_args = "" config.enable_inlay_hints = false @@ -801,7 +802,7 @@ request_initialize :: proc( change = 2, save = {includeText = true}, }, - renameProvider = config.enable_rename, + renameProvider = RenameOptions{prepareProvider = true}, workspaceSymbolProvider = true, referencesProvider = config.enable_references, definitionProvider = true, @@ -1271,17 +1272,18 @@ request_semantic_token_full :: proc( end = common.Position{line = 9000000}, //should be enough } - symbols: SemanticTokens + tokens_params: SemanticTokensResponseParams if config.enable_semantic_tokens { resolve_entire_file_cached(document) if file, ok := file_resolve_cache.files[document.uri.uri]; ok { - symbols = get_semantic_tokens(document, range, file.symbols) + tokens := get_semantic_tokens(document, range, file.symbols) + tokens_params = semantic_tokens_to_response_params(tokens) } } - response := make_response_message(params = symbols, id = id) + response := make_response_message(params = tokens_params, id = id) send_response(response, writer) @@ -1312,21 +1314,22 @@ request_semantic_token_range :: proc( return .InternalError } - symbols: SemanticTokens + tokens_params: SemanticTokensResponseParams if config.enable_semantic_tokens { resolve_entire_file_cached(document) if file, ok := file_resolve_cache.files[document.uri.uri]; ok { - symbols = get_semantic_tokens( + tokens := get_semantic_tokens( document, semantic_params.range, file.symbols, ) + tokens_params = semantic_tokens_to_response_params(tokens) } } - response := make_response_message(params = symbols, id = id) + response := make_response_message(params = tokens_params, id = id) send_response(response, writer) @@ -1490,6 +1493,41 @@ request_document_links :: proc( return .None } +request_prepare_rename :: proc( + params: json.Value, + id: RequestId, + config: ^common.Config, + writer: ^Writer, +) -> common.Error { + params_object, ok := params.(json.Object) + + if !ok { + return .ParseError + } + + rename_param: PrepareRenameParams + + if unmarshal(params, rename_param, context.temp_allocator) != nil { + return .ParseError + } + + document := document_get(rename_param.textDocument.uri) + + if document == nil { + return .InternalError + } + + if range, ok := get_prepare_rename(document, rename_param.position); ok { + response := make_response_message(params = range, id = id) + send_response(response, writer) + } else { + response := make_response_message(params = nil, id = id) + send_response(response, writer) + } + + return .None +} + request_rename :: proc( params: json.Value, id: RequestId, diff --git a/src/server/semantic_tokens.odin b/src/server/semantic_tokens.odin index 35df4d0..8ace068 100644 --- a/src/server/semantic_tokens.odin +++ b/src/server/semantic_tokens.odin @@ -15,7 +15,7 @@ import "core:unicode/utf8" import "src:common" -SemanticTokenTypes :: enum u8 { +SemanticTokenTypes :: enum u32 { Namespace, Type, Enum, @@ -101,22 +101,40 @@ SemanticTokensRangeParams :: struct { range: common.Range, } -SemanticTokens :: struct { +SemanticToken :: struct { + // token line number, relative to the previous token + delta_line: u32, + // token start character, relative to the previous token + // (relative to 0 or the previous token’s start if they are on the same line) + delta_char: u32, + len: u32, + type: SemanticTokenTypes, + modifiers: SemanticTokenModifiers, +} +#assert(size_of(SemanticToken) == 5 * size_of(u32)) + +SemanticTokensResponseParams :: struct { data: []u32, } SemanticTokenBuilder :: struct { current_start: int, - tokens: [dynamic]u32, + tokens: [dynamic]SemanticToken, symbols: map[uintptr]SymbolAndNode, src: string, } +semantic_tokens_to_response_params :: proc( + tokens: []SemanticToken, +) -> SemanticTokensResponseParams { + return {data = (cast([^]u32)raw_data(tokens))[:len(tokens) * 5]} +} + get_semantic_tokens :: proc( document: ^Document, range: common.Range, symbols: map[uintptr]SymbolAndNode, -) -> SemanticTokens { +) -> []SemanticToken { ast_context := make_ast_context( document.ast, document.imports, @@ -127,7 +145,12 @@ get_semantic_tokens :: proc( ast_context.current_package = ast_context.document_package builder: SemanticTokenBuilder = { - tokens = make([dynamic]u32, 0, 10000, context.temp_allocator), + tokens = make( + [dynamic]SemanticToken, + 0, + 2000, + context.temp_allocator, + ), symbols = symbols, src = ast_context.file.src, } @@ -139,7 +162,7 @@ get_semantic_tokens :: proc( } } - return {data = builder.tokens[:]} + return builder.tokens[:] } write_semantic_at_pos :: proc( @@ -156,11 +179,13 @@ write_semantic_at_pos :: proc( ) append( &builder.tokens, - cast(u32)position.line, - cast(u32)position.character, - cast(u32)len, - cast(u32)type, - transmute(u32)modifiers, + SemanticToken { + delta_line = cast(u32)position.line, + delta_char = cast(u32)position.character, + len = cast(u32)len, + type = type, + modifiers = modifiers, + }, ) builder.current_start = pos } @@ -562,11 +587,15 @@ visit_ident :: proc( modifiers += {.ReadOnly} } + //log.errorf("%# \n", symbol) + /* variable idents */ #partial switch symbol.type { case .Variable, .Constant, .Function: #partial switch _ in symbol.value { - case SymbolProcedureValue, SymbolProcedureGroupValue, SymbolAggregateValue: + case SymbolProcedureValue, + SymbolProcedureGroupValue, + SymbolAggregateValue: write_semantic_node(builder, ident, .Function, modifiers) case: write_semantic_node(builder, ident, .Variable, modifiers) @@ -598,7 +627,5 @@ visit_ident :: proc( case SymbolGenericValue, SymbolProcedureGroupValue, SymbolAggregateValue: // unused case: - // log.errorf("Unexpected symbol value: %v", symbol.value); - // panic(fmt.tprintf("Unexpected symbol value: %v", symbol.value)); } } diff --git a/src/server/symbol.odin b/src/server/symbol.odin index d46cf99..9e0f154 100644 --- a/src/server/symbol.odin +++ b/src/server/symbol.odin @@ -14,9 +14,8 @@ import "core:strings" import "src:common" SymbolAndNode :: struct { - symbol: Symbol, - node: ^ast.Node, - is_resolved: bool, + symbol: Symbol, + node: ^ast.Node, } SymbolStructValue :: struct { @@ -216,6 +215,7 @@ free_symbol :: proc(symbol: Symbol, allocator: mem.Allocator) { common.free_ast(v.arg_types, allocator) case SymbolStructValue: delete(v.names, allocator) + delete(v.ranges, allocator) common.free_ast(v.types, allocator) case SymbolGenericValue: common.free_ast(v.expr, allocator) @@ -249,6 +249,7 @@ free_symbol :: proc(symbol: Symbol, allocator: mem.Allocator) { case SymbolPackageValue: case SymbolBitFieldValue: delete(v.names, allocator) + delete(v.ranges, allocator) common.free_ast(v.types, allocator) } } diff --git a/src/server/types.odin b/src/server/types.odin index 9cc1e01..5079bf6 100644 --- a/src/server/types.odin +++ b/src/server/types.odin @@ -23,13 +23,14 @@ ResponseParams :: union { CompletionList, SignatureHelp, []DocumentSymbol, - SemanticTokens, + SemanticTokensResponseParams, Hover, []TextEdit, []InlayHint, []DocumentLink, []WorkspaceSymbol, WorkspaceEdit, + common.Range, } ResponseMessage :: struct { @@ -102,12 +103,17 @@ ServerCapabilities :: struct { hoverProvider: bool, documentFormattingProvider: bool, inlayHintProvider: bool, - renameProvider: bool, + renameProvider: RenameOptions, referencesProvider: bool, workspaceSymbolProvider: bool, documentLinkProvider: DocumentLinkOptions, } +RenameOptions :: struct { + prepareProvider: bool, +} + + CompletionOptions :: struct { resolveProvider: bool, triggerCharacters: []string, @@ -468,6 +474,11 @@ RenameParams :: struct { position: common.Position, } +PrepareRenameParams :: struct { + textDocument: TextDocumentIdentifier, + position: common.Position, +} + ReferenceParams :: struct { textDocument: TextDocumentIdentifier, position: common.Position, @@ -484,7 +495,7 @@ TextDocumentEdit :: struct { } WorkspaceEdit :: struct { - documentChanges: []TextDocumentEdit, + changes: map[string][]TextEdit, } WorkspaceSymbolParams :: struct { diff --git a/src/testing/testing.odin b/src/testing/testing.odin index fb8c034..0d9c028 100644 --- a/src/testing/testing.odin +++ b/src/testing/testing.odin @@ -62,7 +62,8 @@ setup :: proc(src: ^Source) { } else if current == '\n' { current_line += 1 current_character = 0 - } else if src.main[current_index:current_index + 3] == "{*}" { + } else if len(src.main) > current_index + 3 && + src.main[current_index:current_index + 3] == "{*}" { dst_slice := transmute([]u8)src.main[current_index:] src_slice := transmute([]u8)src.main[current_index + 3:] copy(dst_slice, src_slice) @@ -382,3 +383,67 @@ expect_definition_locations :: proc( } } } + +expect_reference_locations :: proc( + t: ^testing.T, + src: ^Source, + expect_locations: []common.Location, +) { + setup(src) + defer teardown(src) + + locations, ok := server.get_references(src.document, src.position) + + for expect_location in expect_locations { + match := false + for location in locations { + if location.range == expect_location.range { + match = true + } + } + if !match { + ok = false + log.errorf("Failed to match with location: %v", expect_location) + } + } + + if !ok { + log.error("Received:") + for location in locations { + log.errorf("%v \n", location) + } + } +} + +expect_semantic_tokens :: proc( + t: ^testing.T, + src: ^Source, + expected: []server.SemanticToken, +) { + setup(src) + defer teardown(src) + + + resolve_flag: server.ResolveReferenceFlag + symbols_and_nodes := server.resolve_entire_file( + src.document, + resolve_flag, + context.temp_allocator, + ) + + range := common.Range{end = {line = 9000000}} //should be enough + tokens := server.get_semantic_tokens(src.document, range, symbols_and_nodes) + + testing.expectf(t, len(expected) == len(tokens), "\nExpected %d tokens, but received %d", len(expected), len(tokens)) + + for i in 0..<min(len(expected), len(tokens)) { + e, a := expected[i], tokens[i] + testing.expectf(t, + e == a, + "\n[%d]: Expected \n(%d, %d, %d, %v, %w)\nbut received\n(%d, %d, %d, %v, %w)", + i, + e.delta_line, e.delta_char, e.len, e.type, e.modifiers, + a.delta_line, a.delta_char, a.len, a.type, a.modifiers, + ) + } +} diff --git a/tests/completions_test.odin b/tests/completions_test.odin index d74de1b..60e7070 100644 --- a/tests/completions_test.odin +++ b/tests/completions_test.odin @@ -2665,6 +2665,33 @@ ast_simple_bit_field_completion :: proc(t: ^testing.T) { ) } +@(test) +ast_simple_union_of_enums_completion :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + Sub_Enum_1 :: enum { + ONE, + } + Sub_Enum_2 :: enum { + TWO, + } + + Super_Enum :: union { + Sub_Enum_1, + Sub_Enum_2, + } + + fn :: proc(mode: Super_Enum) {} + + main :: proc() { + fn(.{*}) + } + `, + } + + test.expect_completion_labels(t, &source, ".", {"ONE", "TWO"}) +} + @(test) ast_generics_function_with_struct_same_pkg :: proc(t: ^testing.T) { @@ -2819,3 +2846,35 @@ ast_generics_function_with_comp_lit_struct :: proc(t: ^testing.T) { }, ) } + +@(test) +ast_enumerated_array_index_completion :: proc(t: ^testing.T) { + source := test.Source { + main = `package main + Direction :: enum { + North, + East, + South, + West, + } + + Direction_Vectors :: [Direction][2]int { + .North = {0, -1}, + .East = {+1, 0}, + .South = {0, +1}, + .West = {-1, 0}, + } + + main :: proc() { + Direction_Vectors[.{*}] + } + `, + } + + test.expect_completion_labels( + t, + &source, + ".", + {"North", "East", "South", "West"}, + ) +} diff --git a/tests/definition_test.odin b/tests/definition_test.odin index 98b055c..cd32628 100644 --- a/tests/definition_test.odin +++ b/tests/definition_test.odin @@ -230,3 +230,126 @@ ast_goto_shadowed_value_decls :: proc(t: ^testing.T) { {{range = {{line = 2, character = 4}, {line = 2, character = 7}}}}, ) } + +@(test) +ast_goto_implicit_super_enum_infer_from_assignment :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + Sub_Enum1 :: enum { + ONE, + } + Sub_Enum2 :: enum { + TWO, + } + + Super_Enum :: union { + Sub_Enum1, + Sub_Enum2, + } + + main :: proc() { + my_enum: Super_Enum + my_enum = .ON{*}E + } + `, + packages = {}, + } + + location := common.Location { + range = { + start = {line = 2, character = 3}, + end = {line = 2, character = 6}, + }, + } + + test.expect_definition_locations(t, &source, {location}) +} + +@(test) +ast_goto_implicit_enum_infer_from_assignment :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + My_Enum :: enum { + One, + Two, + Three, + Four, + } + + my_function :: proc() { + my_enum: My_Enum + my_enum = .Fo{*}ur + } + `, + packages = {}, + } + + location := common.Location { + range = { + start = {line = 5, character = 3}, + end = {line = 5, character = 7}, + }, + } + + test.expect_definition_locations(t, &source, {location}) +} + +@(test) +ast_goto_implicit_enum_infer_from_return :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + My_Enum :: enum { + One, + Two, + Three, + Four, + } + + my_function :: proc() -> My_Enum { + return .Fo{*}ur + } + `, + packages = {}, + } + + location := common.Location { + range = { + start = {line = 5, character = 3}, + end = {line = 5, character = 7}, + }, + } + + test.expect_definition_locations(t, &source, {location}) +} + +@(test) +ast_goto_implicit_enum_infer_from_function :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + My_Enum :: enum { + One, + Two, + Three, + Four, + } + + my_fn :: proc(my_enum: My_Enum) { + + } + + my_function :: proc() { + my_fn(.Fo{*}ur) + } + `, + packages = {}, + } + + location := common.Location { + range = { + start = {line = 5, character = 3}, + end = {line = 5, character = 7}, + }, + } + + test.expect_definition_locations(t, &source, {location}) +} diff --git a/tests/objc_test.odin b/tests/objc_test.odin index 4857861..9c69afb 100644 --- a/tests/objc_test.odin +++ b/tests/objc_test.odin @@ -145,7 +145,7 @@ objc_hover_chained_selector :: proc(t: ^testing.T) { ) } -//@(test) TODO: Disabled for now until refractor +@(test) objc_implicit_enum_completion :: proc(t: ^testing.T) { packages := make([dynamic]test.Package, context.temp_allocator) diff --git a/tests/references_test.odin b/tests/references_test.odin index 25993ab..c07e8cf 100644 --- a/tests/references_test.odin +++ b/tests/references_test.odin @@ -2,3 +2,257 @@ package tests import "core:fmt" import "core:testing" + +import test "src:testing" + +@(test) +reference_variables_in_function :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + my_function :: proc() { + a := 2 + b := a + c := 2 + b{*} + } + `, + } + + test.expect_reference_locations( + t, + &source, + { + { + range = { + start = {line = 3, character = 3}, + end = {line = 3, character = 4}, + }, + }, + { + range = { + start = {line = 4, character = 12}, + end = {line = 4, character = 13}, + }, + }, + }, + ) +} + +@(test) +reference_variables_in_function_parameters :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + my_function :: proc(a: int) { + b := a{*} + } + `, + } + + test.expect_reference_locations( + t, + &source, + { + { + range = { + start = {line = 1, character = 22}, + end = {line = 1, character = 23}, + }, + }, + }, + ) +} + +@(test) +reference_selectors_in_function :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + My_Struct :: struct { + a: int, + } + + my_function :: proc() { + my: My_Struct + my.a{*} = 2 + } + `, + } + + test.expect_reference_locations( + t, + &source, + { + { + range = { + start = {line = 2, character = 3}, + end = {line = 2, character = 4}, + }, + }, + { + range = { + start = {line = 7, character = 6}, + end = {line = 7, character = 7}, + }, + }, + }, + ) +} + + +@(test) +reference_field_comp_lit :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + Foo :: struct { + soo_many_cases: int, + } + + My_Struct :: struct { + foo: Foo, + } + + my_function :: proc(my_struct: My_Struct) { + my := My_Struct { + foo = {soo_many_cases{*} = 2}, + } + } + `, + } + + test.expect_reference_locations( + t, + &source, + { + { + range = { + start = {line = 2, character = 3}, + end = {line = 2, character = 17}, + }, + }, + { + range = { + start = {line = 11, character = 11}, + end = {line = 11, character = 25}, + }, + }, + }, + ) +} + +@(test) +reference_field_comp_lit_infer_from_function :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + Foo :: struct { + soo_many_cases: int, + } + + My_Struct :: struct { + foo: Foo, + } + + my_function :: proc(my_struct: My_Struct) { + my_function({foo = {soo_many_cases{*} = 2}}) + } + `, + } + + test.expect_reference_locations( + t, + &source, + { + { + range = { + start = {line = 2, character = 3}, + end = {line = 2, character = 17}, + }, + }, + { + range = { + start = {line = 10, character = 23}, + end = {line = 10, character = 37}, + }, + }, + }, + ) +} + +@(test) +reference_field_comp_lit_infer_from_return :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + Foo :: struct { + soo_many_cases: int, + } + + My_Struct :: struct { + foo: Foo, + } + + my_function :: proc() -> My_Struct { + return {foo = {soo_many_cases{*} = 2}} + } + `, + } + + test.expect_reference_locations( + t, + &source, + { + { + range = { + start = {line = 2, character = 3}, + end = {line = 2, character = 17}, + }, + }, + { + range = { + start = {line = 10, character = 18}, + end = {line = 10, character = 32}, + }, + }, + }, + ) +} + + +@(test) +reference_enum_field_infer_from_assignment :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + Sub_Enum1 :: enum { + ONE, + } + Sub_Enum2 :: enum { + TWO, + } + + Super_Enum :: union { + Sub_Enum1, + Sub_Enum2, + } + + main :: proc() { + my_enum: Super_Enum + my_enum = .ON{*}E + } + `, + } + + test.expect_reference_locations( + t, + &source, + { + { + range = { + start = {line = 2, character = 3}, + end = {line = 2, character = 6}, + }, + }, + { + range = { + start = {line = 15, character = 14}, + end = {line = 15, character = 17}, + }, + }, + }, + ) +} diff --git a/tests/semantic_tokens_test.odin b/tests/semantic_tokens_test.odin new file mode 100644 index 0000000..aff7788 --- /dev/null +++ b/tests/semantic_tokens_test.odin @@ -0,0 +1,36 @@ +package tests + +import "core:fmt" +import "core:testing" + +import "src:server" +import test "src:testing" + +@(test) +semantic_tokens :: proc(t: ^testing.T) { + src := test.Source { + main = +`package test +Proc_Type :: proc(a: string) -> int +my_function :: proc() { + a := 2 + b := a + c := 2 + b +} +`, + } + + test.expect_semantic_tokens(t, &src, { + {1, 0, 9, .Type, {.ReadOnly}}, // [0] Proc_Type + {0, 18, 1, .Parameter, {}}, // [1] a + {0, 3, 6, .Type, {.ReadOnly}}, // [2] string + {0, 11, 3, .Type, {.ReadOnly}}, // [3] int + {1, 0, 11, .Function, {.ReadOnly}}, // [4] my_function + {0, 0, 11, .Type, {.ReadOnly}}, // [5] !!! WRONG !!! + {1, 1, 1, .Variable, {}}, // [6] a + {1, 1, 1, .Variable, {}}, // [7] b + {0, 5, 1, .Variable, {}}, // [8] a + {1, 1, 1, .Variable, {}}, // [9] c + {0, 9, 1, .Variable, {}}, // [10] b + }) +} |