From 2819ec6573d7d4db26ecb624926ddfd2b4557b47 Mon Sep 17 00:00:00 2001 From: The Bearodactyl Date: Sun, 14 Dec 2025 13:35:22 -0600 Subject: [PATCH 1/6] improve `should_resolve_all_proc_overload_possibilities` function --- src/server/analysis.odin | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/server/analysis.odin b/src/server/analysis.odin index 2c7f4f40..7ce69276 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -684,7 +684,6 @@ get_top_candiate :: proc(candidates: []Candidate) -> (Candidate, bool) { should_resolve_all_proc_overload_possibilities :: proc(ast_context: ^AstContext, call_expr: ^ast.Call_Expr) -> bool { - // TODO: We need a better way to handle this if ast_context.resolve_specific_overload { return false } @@ -693,7 +692,12 @@ should_resolve_all_proc_overload_possibilities :: proc(ast_context: ^AstContext, return false } - return ast_context.position_hint == .Completion || ast_context.position_hint == .SignatureHelp || call_expr == nil + #partial switch ast_context.position_hint { + case .Completion, .SignatureHelp: + return true + case: + return call_expr == nil + } } /* @@ -1385,10 +1389,14 @@ resolve_call_directive :: proc(ast_context: ^AstContext, call: ^ast.Call_Expr) - if len(call.args) == 1 { ident := new_type(ast.Ident, call.pos, call.end, ast_context.allocator) ident.name = "u8" - value := SymbolSliceValue{ - expr = ident + value := SymbolSliceValue { + expr = ident, + } + symbol := Symbol { + name = "#load", + pkg = ast_context.current_package, + value = value, } - symbol := Symbol{name = "#load", pkg = ast_context.current_package, value = value} return symbol, true } else if len(call.args) == 2 { return resolve_type_expression(ast_context, call.args[1]) @@ -1407,10 +1415,14 @@ resolve_call_directive :: proc(ast_context: ^AstContext, call: ^ast.Call_Expr) - selector := new_type(ast.Selector_Expr, call.pos, call.end, ast_context.allocator) selector.expr = pkg selector.field = field - value := SymbolSliceValue{ - expr = selector + value := SymbolSliceValue { + expr = selector, + } + symbol := Symbol { + name = "#load_directory", + pkg = ast_context.current_package, + value = value, } - symbol := Symbol{name = "#load_directory", pkg = ast_context.current_package, value = value} return symbol, true } From 509f86dc726f6aa4f1f89da15e71f0db5cee25a0 Mon Sep 17 00:00:00 2001 From: The Bearodactyl Date: Sun, 14 Dec 2025 13:43:41 -0600 Subject: [PATCH 2/6] optimize comment retrieval --- src/server/ast.odin | 84 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/src/server/ast.odin b/src/server/ast.odin index d1846f71..e3b336c3 100644 --- a/src/server/ast.odin +++ b/src/server/ast.odin @@ -1,6 +1,7 @@ #+feature dynamic-literals package server +import "base:runtime" import "core:fmt" import "core:mem" import "core:odin/ast" @@ -388,12 +389,14 @@ collect_value_decl :: proc( stmt: ^ast.Node, foreign_attrs: []^ast.Attribute, ) { + index := build_comment_index(file) + defer destroy_comment_index(&index) value_decl, is_value_decl := stmt.derived.(^ast.Value_Decl) if !is_value_decl { return } - comment, _ := get_file_comment(file, value_decl.pos.line) + comment, _ := get_file_comment(file, value_decl.pos.line, index = &index) attributes := merge_attributes(value_decl.attributes[:], foreign_attrs) @@ -1373,6 +1376,9 @@ repeat :: proc(value: string, count: int, allocator := context.allocator) -> str // Corrects docs and comments on a Struct_Type. Creates new nodes and adds them to the provided struct // using the provided allocator, so `v` should have the same lifetime as the allocator. construct_struct_field_docs :: proc(file: ast.File, v: ^ast.Struct_Type, allocator := context.temp_allocator) { + index := build_comment_index(file) + defer destroy_comment_index(&index) + for field, i in v.fields.list { // There is currently a bug in the odin parser where it adds line comments for a field to the // docs of the following field, we address this problem here. @@ -1411,7 +1417,7 @@ construct_struct_field_docs :: proc(file: ast.File, v: ^ast.Struct_Type, allocat } } else if field.comment == nil { // We need to check the file to see if it contains a line comment as it might be skipped - field.comment, _ = get_file_comment(file, field.pos.line, allocator = allocator) + field.comment, _ = get_file_comment(file, field.pos.line, allocator = allocator, index = &index) } } } @@ -1419,6 +1425,8 @@ construct_struct_field_docs :: proc(file: ast.File, v: ^ast.Struct_Type, allocat // Corrects docs and comments on a Bit_Field_Type. Creates new nodes and adds them to the provided bit_field // using the provided allocator, so `v` should have the same lifetime as the allocator. construct_bit_field_field_docs :: proc(file: ast.File, v: ^ast.Bit_Field_Type, allocator := context.temp_allocator) { + index := build_comment_index(file) + defer destroy_comment_index(&index) for field, i in v.fields { // There is currently a bug in the odin parser where it adds line comments for a field to the // docs of the following field, we address this problem here. @@ -1448,9 +1456,41 @@ construct_bit_field_field_docs :: proc(file: ast.File, v: ^ast.Bit_Field_Type, a } } else if field.comments == nil { // We need to check the file to see if it contains a line comment as there is no next field - field.comments, _ = get_file_comment(file, field.pos.line, allocator = allocator) + field.comments, _ = get_file_comment(file, field.pos.line, allocator = allocator, index = &index) + } + } +} + +Comment_Index :: struct { + line_to_idx: map[int][dynamic]int, + allocator: runtime.Allocator, +} + +build_comment_index :: proc(file: ast.File, allocator := context.allocator) -> Comment_Index { + index := Comment_Index { + line_to_idx = make(map[int][dynamic]int, allocator), + allocator = allocator, + } + + for c, i in file.comments { + line := c.pos.line + + if line not_in index.line_to_idx { + index.line_to_idx[line] = make([dynamic]int, 0, 1, allocator) } + + append(&index.line_to_idx[line], i) } + + return index +} + +destroy_comment_index :: proc(index: ^Comment_Index) { + for _, &indices in index.line_to_idx { + delete(indices) + } + + delete(index.line_to_idx) } // Retrives the comment group from the specified line of the file @@ -1459,26 +1499,58 @@ get_file_comment :: proc( file: ast.File, line: int, start_index := 0, + index: ^Comment_Index = nil, allocator := context.temp_allocator, ) -> ( ^ast.Comment_Group, int, ) { - // TODO: linear scan might be a bit slow for files with lots of comments? + if index != nil { + indices, ok := index.line_to_idx[line] + + if !ok { + return nil, -1 + } + + for idx in indices { + if idx >= start_index { + c := file.comments[idx] + + for item, j in c.list { + comment := new_type(ast.Comment_Group, item.pos, parser.end_pos(item), allocator) + + if j == len(c.list) - 1 { + comment.list = c.list[j:] + } else { + comment.list = c.list[j:j + 1] + } + + return comment, idx + } + } + } + + return nil, -1 + } + for i := start_index; i < len(file.comments); i += 1 { c := file.comments[i] + if c.pos.line == line { for item, j in c.list { comment := new_type(ast.Comment_Group, item.pos, parser.end_pos(item), allocator) + if j == len(c.list) - 1 { comment.list = c.list[j:] } else { comment.list = c.list[j:j + 1] } + return comment, i } } } + return nil, -1 } @@ -1524,6 +1596,9 @@ get_field_docs_and_comments :: proc( [dynamic]^ast.Comment_Group, [dynamic]^ast.Comment_Group, ) { + index := build_comment_index(file) + defer destroy_comment_index(&index) + docs := make([dynamic]^ast.Comment_Group, allocator) comments := make([dynamic]^ast.Comment_Group, allocator) prev_line := -1 @@ -1554,6 +1629,7 @@ get_field_docs_and_comments :: proc( n.pos.line, start_index = last_comment + 1, allocator = allocator, + index = &index, ) } From b29cbf2e2ec4d34fd5dab1d12f37651b3d3dba17 Mon Sep 17 00:00:00 2001 From: The Bearodactyl Date: Sun, 14 Dec 2025 13:46:06 -0600 Subject: [PATCH 3/6] context-specific directive completions --- src/server/completion.odin | 144 ++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 2 deletions(-) diff --git a/src/server/completion.odin b/src/server/completion.odin index 6663be9f..53274b2c 100644 --- a/src/server/completion.odin +++ b/src/server/completion.odin @@ -587,12 +587,152 @@ get_directive_completion :: proc( results: ^[dynamic]CompletionResult, ) -> bool { is_incomplete := false + context_directives := make([dynamic]CompletionResult, 0, context.temp_allocator) + + if position_context.value_decl != nil { + for value in position_context.value_decl.values { + if _, ok := value.derived.(^ast.Struct_Type); ok { + append_struct_directives(&context_directives) + break + } else if _, ok := value.derived.(^ast.Bit_Field_Type); ok { + append_bitfield_directives(&context_directives) + break + } else if _, ok := value.derived.(^ast.Union_Type); ok { + append_union_directives(&context_directives) + break + } + } + } + + if position_context.function != nil { + append_procedure_directives(&context_directives) + } + + if position_context.switch_stmt != nil || position_context.switch_type_stmt != nil { + append_switch_directives(&context_directives) + } + + if len(context_directives) > 0 { + append(results, ..context_directives[:]) + } else { + append(results, ..completion_items_directives[:]) + } - // Right now just return all the possible completions, but later on I should give the context specific ones - append(results, ..completion_items_directives[:]) return is_incomplete } +@(private = "file") +append_struct_directives :: proc(results: ^[dynamic]CompletionResult) { + struct_directives := []string{"align", "packed", "raw_union"} + + for directive in struct_directives { + if doc, ok := directive_docs[directive]; ok { + documentation := MarkupContent { + kind = "markdown", + value = doc, + } + append( + results, + CompletionResult { + completion_item = CompletionItem { + label = directive, + kind = .Constant, + documentation = documentation, + }, + }, + ) + } + } +} + +@(private = "file") +append_bitfield_directives :: proc(results: ^[dynamic]CompletionResult) { + if doc, ok := directive_docs["align"]; ok { + documentation := MarkupContent { + kind = "markdown", + value = doc, + } + append( + results, + CompletionResult { + completion_item = CompletionItem{label = "align", kind = .Constant, documentation = documentation}, + }, + ) + } +} + +@(private = "file") +append_union_directives :: proc(results: ^[dynamic]CompletionResult) { + union_directives := []string{"no_nil", "shared_nil", "align"} + + for directive in union_directives { + if doc, ok := directive_docs[directive]; ok { + documentation := MarkupContent { + kind = "markdown", + value = doc, + } + append( + results, + CompletionResult { + completion_item = CompletionItem { + label = directive, + kind = .Constant, + documentation = documentation, + }, + }, + ) + } + } +} + +@(private = "file") +append_procedure_directives :: proc(results: ^[dynamic]CompletionResult) { + proc_directives := []string{"optional_ok", "require_results", "deprecated", "cold", "test"} + + for directive in proc_directives { + if doc, ok := directive_docs[directive]; ok { + documentation := MarkupContent { + kind = "markdown", + value = doc, + } + append( + results, + CompletionResult { + completion_item = CompletionItem { + label = directive, + kind = .Constant, + documentation = documentation, + }, + }, + ) + } + } +} + +@(private = "file") +append_switch_directives :: proc(results: ^[dynamic]CompletionResult) { + switch_directives := []string{"partial"} + + for directive in switch_directives { + if doc, ok := directive_docs[directive]; ok { + documentation := MarkupContent { + kind = "markdown", + value = doc, + } + append( + results, + CompletionResult { + completion_item = CompletionItem { + label = directive, + kind = .Constant, + documentation = documentation, + }, + }, + ) + } + } +} + get_comp_lit_completion :: proc( ast_context: ^AstContext, position_context: ^DocumentPositionContext, From f7fe37994dd892c6d2babdac2065cd5d4b0eb979 Mon Sep 17 00:00:00 2001 From: The Bearodactyl Date: Sun, 14 Dec 2025 13:49:20 -0600 Subject: [PATCH 4/6] add nested field traversal when fuzzy searching --- src/server/memory_index.odin | 130 +++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 50 deletions(-) diff --git a/src/server/memory_index.odin b/src/server/memory_index.odin index 487d7a20..a4e45785 100644 --- a/src/server/memory_index.odin +++ b/src/server/memory_index.odin @@ -51,6 +51,82 @@ score_name :: proc(matchers: []^common.FuzzyMatcher, name: string) -> (f32, bool return score, true } +traverse_nested_fields :: proc( + index: ^MemoryIndex, + symbol: Symbol, + prefix: string, + matchers: []^common.FuzzyMatcher, + symbols: ^[dynamic]FuzzyResult, +) { + #partial switch v in symbol.value { + case SymbolStructValue: + for name, i in v.names { + full_name := fmt.tprintf("%s.%s", prefix, name) + + if score, ok := score_name(matchers, full_name); ok { + s := symbol + construct_struct_field_symbol(&s, prefix, v, i) + s.name = full_name + result := FuzzyResult { + symbol = s, + score = score, + } + + append(symbols, result) + } + + field_symbol := symbol + construct_struct_field_symbol(&field_symbol, prefix, v, i) + traverse_nested_fields(index, field_symbol, full_name, matchers, symbols) + } + + case SymbolBitFieldValue: + for name, i in v.names { + full_name := fmt.tprintf("%s.%s", prefix, name) + + if score, ok := score_name(matchers, full_name); ok { + s := symbol + construct_bit_field_field_symbol(&s, prefix, v, i) + s.name = full_name + result := FuzzyResult { + symbol = s, + score = score, + } + + append(symbols, result) + } + + field_symbol := symbol + construct_bit_field_field_symbol(&field_symbol, prefix, v, i) + traverse_nested_fields(index, field_symbol, full_name, matchers, symbols) + } + + case SymbolGenericValue: + for name, i in v.field_names { + full_name := fmt.tprintf("%s.%s", prefix, name) + + if score, ok := score_name(matchers, full_name); ok { + s := symbol + s.name = full_name + s.type = .Field + s.range = v.ranges[i] + result := FuzzyResult { + symbol = s, + score = score, + } + + append(symbols, result) + } + + field_symbol := symbol + field_symbol.name = full_name + field_symbol.type = .Field + field_symbol.range = v.ranges[i] + traverse_nested_fields(index, field_symbol, full_name, matchers, symbols) + } + } +} + memory_index_fuzzy_search :: proc( index: ^MemoryIndex, name: string, @@ -63,9 +139,9 @@ memory_index_fuzzy_search :: proc( bool, ) { symbols := make([dynamic]FuzzyResult, 0, context.temp_allocator) - fields := strings.fields(name, context.temp_allocator) matchers := make([dynamic]^common.FuzzyMatcher, 0, len(fields), context.temp_allocator) + for field in fields { append(&matchers, common.make_fuzzy_matcher(field)) } @@ -79,57 +155,11 @@ memory_index_fuzzy_search :: proc( if should_skip_private_symbol(symbol, current_pkg, current_file) { continue } + if resolve_fields { - // TODO: this only does the top level fields, we may want to travers all the way down in the future - #partial switch v in symbol.value { - case SymbolStructValue: - for name, i in v.names { - full_name := fmt.tprintf("%s.%s", symbol.name, name) - if score, ok := score_name(matchers[:], full_name); ok { - s := symbol - construct_struct_field_symbol(&s, symbol.name, v, i) - s.name = full_name - result := FuzzyResult { - symbol = s, - score = score, - } - - append(&symbols, result) - } - } - case SymbolBitFieldValue: - for name, i in v.names { - full_name := fmt.tprintf("%s.%s", symbol.name, name) - if score, ok := score_name(matchers[:], full_name); ok { - s := symbol - construct_bit_field_field_symbol(&s, symbol.name, v, i) - s.name = full_name - result := FuzzyResult { - symbol = s, - score = score, - } - - append(&symbols, result) - } - } - case SymbolGenericValue: - for name, i in v.field_names { - full_name := fmt.tprintf("%s.%s", symbol.name, name) - if score, ok := score_name(matchers[:], full_name); ok { - s := symbol - s.name = full_name - s.type = .Field - s.range = v.ranges[i] - result := FuzzyResult { - symbol = s, - score = score, - } - - append(&symbols, result) - } - } - } + traverse_nested_fields(index, symbol, symbol.name, matchers[:], &symbols) } + if score, ok := score_name(matchers[:], symbol.name); ok { result := FuzzyResult { symbol = symbol, From 872b943a492ecf0de034c053aea0b1d6f90377e4 Mon Sep 17 00:00:00 2001 From: The Bearodactyl Date: Sun, 14 Dec 2025 13:50:13 -0600 Subject: [PATCH 5/6] remove position hint dependency from poly type resolution --- src/server/generics.odin | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/server/generics.odin b/src/server/generics.odin index db858cc8..7e5e04db 100644 --- a/src/server/generics.odin +++ b/src/server/generics.odin @@ -53,11 +53,7 @@ resolve_poly :: proc( if ident, ok := unwrap_ident(type); ok { call_node_id := reflect.union_variant_typeid(call_node.derived) specialization_id := reflect.union_variant_typeid(specialization.derived) - if ast_context.position_hint == .TypeDefinition && call_node_id == specialization_id { - // TODO: Fix this so it doesn't need to be aware that we're in a type definition - // if the specialization type matches the type of the parameter passed to the proc - // we store that rather than the specialization so we can follow it correctly - // for things like `textDocument/typeDefinition` + if call_node_id == specialization_id { save_poly_map( ident, make_ident_ast(ast_context, call_node.pos, call_node.end, call_symbol.name), @@ -133,7 +129,6 @@ resolve_poly :: proc( return false } - //It's not enough for them to both arrays, they also have to share soa attributes if p.tag != nil && call_array.tag != nil { a, ok1 := p.tag.derived.(^ast.Basic_Directive) b, ok2 := call_array.tag.derived.(^ast.Basic_Directive) @@ -162,7 +157,6 @@ resolve_poly :: proc( return false } - //It's not enough for them to both arrays, they also have to share soa attributes if p.tag != nil && call_array.tag != nil { a, ok1 := p.tag.derived.(^ast.Basic_Directive) b, ok2 := call_array.tag.derived.(^ast.Basic_Directive) @@ -796,6 +790,7 @@ resolve_poly_struct :: proc(ast_context: ^AstContext, b: ^SymbolStructValueBuild } } else if data.parent_proc == nil { data.symbol_value_builder.types[data.i] = expr + data.poly_index += 1 } } From 90f0b8536d711d3c3a20935d47411e7e82666e68 Mon Sep 17 00:00:00 2001 From: The Bearodactyl Date: Sun, 14 Dec 2025 20:21:46 -0600 Subject: [PATCH 6/6] fix failing test --- src/server/analysis.odin | 19 +++++--- src/server/ast.odin | 13 ++---- src/server/generics.odin | 25 ++++++++++- tests/hover_test.odin | 97 +++++++++++++++++++++++++--------------- 4 files changed, 101 insertions(+), 53 deletions(-) diff --git a/src/server/analysis.odin b/src/server/analysis.odin index 7ce69276..2bb95519 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -684,6 +684,7 @@ get_top_candiate :: proc(candidates: []Candidate) -> (Candidate, bool) { should_resolve_all_proc_overload_possibilities :: proc(ast_context: ^AstContext, call_expr: ^ast.Call_Expr) -> bool { + // TODO: We need a better way to handle this if ast_context.resolve_specific_overload { return false } @@ -692,12 +693,7 @@ should_resolve_all_proc_overload_possibilities :: proc(ast_context: ^AstContext, return false } - #partial switch ast_context.position_hint { - case .Completion, .SignatureHelp: - return true - case: - return call_expr == nil - } + return ast_context.position_hint == .Completion || ast_context.position_hint == .SignatureHelp || call_expr == nil } /* @@ -964,8 +960,13 @@ resolve_function_overload :: proc(ast_context: ^AstContext, group: ^ast.Proc_Gro resolve_call_arg_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Expr) -> (Symbol, bool) { old_current_package := ast_context.current_package ast_context.current_package = ast_context.document_package + + old_use_locals := ast_context.use_locals + ast_context.use_locals = true + defer { ast_context.current_package = old_current_package + ast_context.use_locals = old_use_locals } return resolve_type_expression(ast_context, node) @@ -1128,6 +1129,12 @@ resolve_location_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Ex } resolve_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Expr) -> (Symbol, bool) { + if node != nil { + if _, ok := node.derived.(^ast.Bad_Expr); ok { + return {}, false + } + } + clear(&ast_context.recursion_map) symbol := Symbol{} ok := internal_resolve_type_expression(ast_context, node, &symbol) diff --git a/src/server/ast.odin b/src/server/ast.odin index e3b336c3..138ce578 100644 --- a/src/server/ast.odin +++ b/src/server/ast.odin @@ -389,14 +389,12 @@ collect_value_decl :: proc( stmt: ^ast.Node, foreign_attrs: []^ast.Attribute, ) { - index := build_comment_index(file) - defer destroy_comment_index(&index) value_decl, is_value_decl := stmt.derived.(^ast.Value_Decl) if !is_value_decl { return } - comment, _ := get_file_comment(file, value_decl.pos.line, index = &index) + comment, _ := get_file_comment(file, value_decl.pos.line) attributes := merge_attributes(value_decl.attributes[:], foreign_attrs) @@ -1376,9 +1374,6 @@ repeat :: proc(value: string, count: int, allocator := context.allocator) -> str // Corrects docs and comments on a Struct_Type. Creates new nodes and adds them to the provided struct // using the provided allocator, so `v` should have the same lifetime as the allocator. construct_struct_field_docs :: proc(file: ast.File, v: ^ast.Struct_Type, allocator := context.temp_allocator) { - index := build_comment_index(file) - defer destroy_comment_index(&index) - for field, i in v.fields.list { // There is currently a bug in the odin parser where it adds line comments for a field to the // docs of the following field, we address this problem here. @@ -1417,7 +1412,7 @@ construct_struct_field_docs :: proc(file: ast.File, v: ^ast.Struct_Type, allocat } } else if field.comment == nil { // We need to check the file to see if it contains a line comment as it might be skipped - field.comment, _ = get_file_comment(file, field.pos.line, allocator = allocator, index = &index) + field.comment, _ = get_file_comment(file, field.pos.line, allocator = allocator) } } } @@ -1425,8 +1420,6 @@ construct_struct_field_docs :: proc(file: ast.File, v: ^ast.Struct_Type, allocat // Corrects docs and comments on a Bit_Field_Type. Creates new nodes and adds them to the provided bit_field // using the provided allocator, so `v` should have the same lifetime as the allocator. construct_bit_field_field_docs :: proc(file: ast.File, v: ^ast.Bit_Field_Type, allocator := context.temp_allocator) { - index := build_comment_index(file) - defer destroy_comment_index(&index) for field, i in v.fields { // There is currently a bug in the odin parser where it adds line comments for a field to the // docs of the following field, we address this problem here. @@ -1456,7 +1449,7 @@ construct_bit_field_field_docs :: proc(file: ast.File, v: ^ast.Bit_Field_Type, a } } else if field.comments == nil { // We need to check the file to see if it contains a line comment as there is no next field - field.comments, _ = get_file_comment(file, field.pos.line, allocator = allocator, index = &index) + field.comments, _ = get_file_comment(file, field.pos.line, allocator = allocator) } } } diff --git a/src/server/generics.odin b/src/server/generics.odin index 7e5e04db..cc737123 100644 --- a/src/server/generics.odin +++ b/src/server/generics.odin @@ -53,7 +53,7 @@ resolve_poly :: proc( if ident, ok := unwrap_ident(type); ok { call_node_id := reflect.union_variant_typeid(call_node.derived) specialization_id := reflect.union_variant_typeid(specialization.derived) - if call_node_id == specialization_id { + if ast_context.position_hint == .TypeDefinition && call_node_id == specialization_id { save_poly_map( ident, make_ident_ast(ast_context, call_node.pos, call_node.end, call_symbol.name), @@ -252,6 +252,28 @@ resolve_poly :: proc( } 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 poly_type.specialization != nil { + if poly_dynamic, ok := poly_type.specialization.derived.(^ast.Dynamic_Array_Type); ok { + if call_dynamic, ok := call_pointer.elem.derived.(^ast.Dynamic_Array_Type); ok { + if poly_type.type != nil { + if ident, ok := unwrap_ident(poly_type.type); ok { + save_poly_map(ident, call_pointer.elem, poly_map) + } + } + + if elem_poly, ok := poly_dynamic.elem.derived.(^ast.Poly_Type); ok { + if elem_ident, ok := unwrap_ident(elem_poly.type); ok { + save_poly_map(elem_ident, call_dynamic.elem, poly_map) + } + } + + return true + } + } + } + } + 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) @@ -260,6 +282,7 @@ resolve_poly :: proc( if poly_type.specialization != nil { return resolve_poly(ast_context, call_pointer.elem, call_symbol, p.elem, poly_map) } + return true } } diff --git a/tests/hover_test.odin b/tests/hover_test.odin index 6f8cecff..2689b618 100644 --- a/tests/hover_test.odin +++ b/tests/hover_test.odin @@ -4431,7 +4431,11 @@ ast_hover_parapoly_elem_overloaded_proc_multiple_options :: proc(t: ^testing.T) } `, } - test.expect_hover(t, &source, "test.foo :: proc {\n\tfoo_int :: proc(i: int),\n\tfoo_string :: proc(s: string),\n}") + test.expect_hover( + t, + &source, + "test.foo :: proc {\n\tfoo_int :: proc(i: int),\n\tfoo_string :: proc(s: string),\n}", + ) } @(test) @@ -4560,7 +4564,11 @@ ast_hover_parapoly_union_with_where_clause :: proc(t: ^testing.T) { } `, } - test.expect_hover(t, &source, "test.Foo :: union($T: typeid) #no_nil where type_is_integer(T) {\n\tT,\n\tstring,\n}") + test.expect_hover( + t, + &source, + "test.Foo :: union($T: typeid) #no_nil where type_is_integer(T) {\n\tT,\n\tstring,\n}", + ) } @(test) @@ -4851,7 +4859,7 @@ ast_hover_proc_param_tags :: proc(t: ^testing.T) { @(test) ast_hover_simd_array :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test f{*}oo := #simd[2]f32{} `, } @@ -4861,7 +4869,7 @@ ast_hover_simd_array :: proc(t: ^testing.T) { @(test) ast_hover_simd_array_pointer :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test f{*}oo := &#simd[4]f32{} `, } @@ -4955,7 +4963,11 @@ ast_hover_const_complex_comp_lit :: proc(t: ^testing.T) { } `, } - test.expect_hover(t, &source, "test.COLOURS :: Colours {\n\tblue = frgba{0.1, 0.1, 0.1, 0.1},\n\tgreen = frgba{0.1, 0.1, 0.1, 0.1},\n\tfoo = {\n\t\ta = 32,\n\t\tb = \"testing\",\n\t},\n\tbar = 1 + 2,\n}") + test.expect_hover( + t, + &source, + "test.COLOURS :: Colours {\n\tblue = frgba{0.1, 0.1, 0.1, 0.1},\n\tgreen = frgba{0.1, 0.1, 0.1, 0.1},\n\tfoo = {\n\t\ta = 32,\n\t\tb = \"testing\",\n\t},\n\tbar = 1 + 2,\n}", + ) } @(test) @@ -5008,7 +5020,7 @@ ast_hover_proc_overload_basic_type_alias :: proc(t: ^testing.T) { `}) source := test.Source { - main = `package test + main = `package test import "my_package" foo_int :: proc(i: int) {} @@ -5052,23 +5064,11 @@ ast_hover_proc_overload_nil_pointer :: proc(t: ^testing.T) { ast_hover_package_proc_naming_conflicting_with_another_package :: proc(t: ^testing.T) { packages := make([dynamic]test.Package, context.temp_allocator) - append( - &packages, - test.Package { - pkg = "my_package", - source = `package my_package + append(&packages, test.Package{pkg = "my_package", source = `package my_package foo :: proc() {} - `, - }, - ) - append( - &packages, - test.Package { - pkg = "foo", - source = `package foo - `, - }, - ) + `}) + append(&packages, test.Package{pkg = "foo", source = `package foo + `}) source := test.Source { main = `package test @@ -5155,14 +5155,17 @@ ast_hover_generic_proc_with_inlining :: proc(t: ^testing.T) { ast_hover_using_import_statement_name_conflict :: proc(t: ^testing.T) { packages := make([dynamic]test.Package, context.temp_allocator) - append(&packages, test.Package{pkg = "my_package", source = `package my_package + append( + &packages, + test.Package{pkg = "my_package", source = `package my_package Bar :: struct { b: string, } - `}) + `}, + ) source := test.Source { - main = `package test + main = `package test import "my_package" Bar :: struct { @@ -5352,12 +5355,18 @@ ast_hover_quaternion_literal :: proc(t: ^testing.T) { ast_hover_parapoly_other_package :: proc(t: ^testing.T) { packages := make([dynamic]test.Package, context.temp_allocator) - append(&packages, test.Package{pkg = "my_package", source = `package my_package + append( + &packages, + test.Package { + pkg = "my_package", + source = `package my_package // Docs! bar :: proc(_: $T) {} // Comment! - `}) + `, + }, + ) source := test.Source { - main = `package test + main = `package test import "my_package" main :: proc() { @@ -5809,14 +5818,20 @@ ast_hover_nested_proc_docs_spaces :: proc(t: ^testing.T) { ast_hover_propagate_docs_alias_in_package :: proc(t: ^testing.T) { packages := make([dynamic]test.Package, context.temp_allocator) - append(&packages, test.Package{pkg = "my_package", source = `package my_package + append( + &packages, + test.Package { + pkg = "my_package", + source = `package my_package // Docs! foo :: proc() {} // Comment! bar :: foo - `}) + `, + }, + ) source := test.Source { - main = `package test + main = `package test import "my_package" main :: proc() { @@ -5832,15 +5847,21 @@ ast_hover_propagate_docs_alias_in_package :: proc(t: ^testing.T) { ast_hover_propagate_docs_alias_in_package_override :: proc(t: ^testing.T) { packages := make([dynamic]test.Package, context.temp_allocator) - append(&packages, test.Package{pkg = "my_package", source = `package my_package + append( + &packages, + test.Package { + pkg = "my_package", + source = `package my_package // Docs! foo :: proc() {} // Comment! // Overridden bar :: foo - `}) + `, + }, + ) source := test.Source { - main = `package test + main = `package test import "my_package" main :: proc() { @@ -5884,7 +5905,7 @@ ast_hover_const_aliases_from_other_pkg :: proc(t: ^testing.T) { Foo :: 3 + 4 `}) source := test.Source { - main = `package test + main = `package test import "my_package" B{*}ar :: my_package.Foo @@ -5958,7 +5979,11 @@ ast_hover_directives_config_info :: proc(t: ^testing.T) { bar :: #c{*}onfig(TEST, false) `, } - test.expect_hover(t, &source, "#config(, default)\n\nChecks if an identifier is defined through the command line, or gives a default value instead.\n\nValues can be set with the `-define:NAME=VALUE` command line flag.") + test.expect_hover( + t, + &source, + "#config(, default)\n\nChecks if an identifier is defined through the command line, or gives a default value instead.\n\nValues can be set with the `-define:NAME=VALUE` command line flag.", + ) } /*