diff options
| author | Brad Lewis <22850972+BradLewis@users.noreply.github.com> | 2025-08-23 12:22:36 -0400 |
|---|---|---|
| committer | Brad Lewis <22850972+BradLewis@users.noreply.github.com> | 2025-08-24 08:55:23 -0400 |
| commit | 118db4f7667c91b24fcc21353df7c3cd7a5830b3 (patch) | |
| tree | 0f098b492a06712f79782d493797604eec4e844b | |
| parent | 39b2e758cacb40ba1a062b8af87af9edf8958375 (diff) | |
Add special case for handling `append` for dynamic arrays
| -rw-r--r-- | src/server/completion.odin | 57 | ||||
| -rw-r--r-- | src/testing/testing.odin | 38 | ||||
| -rw-r--r-- | tests/completions_test.odin | 243 |
3 files changed, 166 insertions, 172 deletions
diff --git a/src/server/completion.odin b/src/server/completion.odin index 61e528b..51a9911 100644 --- a/src/server/completion.odin +++ b/src/server/completion.odin @@ -11,6 +11,7 @@ import "core:odin/tokenizer" import "core:os" import "core:path/filepath" import path "core:path/slashpath" +import "core:reflect" import "core:slice" import "core:sort" import "core:strconv" @@ -186,9 +187,16 @@ get_completion_list :: proc( get_target_symbol :: proc(ast_context: ^AstContext, position_context: ^DocumentPositionContext) -> Maybe(Symbol) { if position_context.call != nil { if call, ok := position_context.call.derived.(^ast.Call_Expr); ok { - if call_symbol, ok := resolve_type_expression(ast_context, call.expr); ok { - if value, ok := call_symbol.value.(SymbolProcedureValue); ok { - if param_index, ok := find_position_in_call_param(position_context, call^); ok { + if param_index, ok := find_position_in_call_param(position_context, call^); ok { + // Manually check this so we handle the pointer case of the first argument + if ident, ok := call.expr.derived.(^ast.Ident); ok && param_index == 0 { + switch ident.name { + case "append", "non_zero_append": + return Symbol{value = SymbolDynamicArrayValue{}, pointers = 1} + } + } + if call_symbol, ok := resolve_type_expression(ast_context, call.expr); ok { + if value, ok := call_symbol.value.(SymbolProcedureValue); ok { if arg_type, arg_type_ok := get_proc_arg_type_from_index(value, param_index); ok { if position_context.field_value != nil { // we are using a named param so we want to ensure we use that type and not the @@ -350,7 +358,6 @@ convert_completion_results :: proc( } item.kind = symbol_type_to_completion_kind(result.symbol.type) - if result.symbol.type == .Function && common.config.enable_snippets && common.config.enable_procedure_snippet { item.insertText = fmt.tprintf("%v($0)", item.label) item.insertTextFormat = .Snippet @@ -379,19 +386,43 @@ handle_pointers :: proc( item: ^CompletionItem, completion_type: Completion_Type, ) { - if v, ok := arg_symbol.value.(SymbolBasicValue); ok { - if v.ident.name == "any" { - return + should_skip :: proc(arg_symbol, result_symbol: Symbol) -> bool { + if v, ok := arg_symbol.value.(SymbolBasicValue); ok { + if v.ident.name == "any" { + return true + } } - } - if _, ok := result_symbol.value.(SymbolUntypedValue); ok && arg_symbol.type == .Keyword { - if _, ok := are_symbol_untyped_basic_same_typed(arg_symbol, result_symbol); !ok { - if _, ok := are_symbol_untyped_basic_same_typed(result_symbol, arg_symbol); !ok { - return + if _, ok := result_symbol.value.(SymbolUntypedValue); ok && arg_symbol.type == .Keyword { + if _, ok := are_symbol_untyped_basic_same_typed(arg_symbol, result_symbol); !ok { + if _, ok := are_symbol_untyped_basic_same_typed(result_symbol, arg_symbol); !ok { + return true + } + return false } + return false } - } else if result_symbol.uri != arg_symbol.uri || result_symbol.range != arg_symbol.range { + + a_id := reflect.union_variant_typeid(arg_symbol.value) + b_id := reflect.union_variant_typeid(result_symbol.value) + + if a_id != b_id { + return true + } + + #partial switch v in arg_symbol.value { + case SymbolMapValue, SymbolDynamicArrayValue, SymbolSliceValue, SymbolMultiPointerValue, SymbolFixedArrayValue: + return false + } + + if result_symbol.uri != arg_symbol.uri || result_symbol.range != arg_symbol.range { + return true + } + + return false + } + + if should_skip(arg_symbol, result_symbol) { return } diff --git a/src/testing/testing.odin b/src/testing/testing.odin index e03e8b5..ca3fa1e 100644 --- a/src/testing/testing.odin +++ b/src/testing/testing.odin @@ -261,6 +261,44 @@ expect_completion_docs :: proc( } } +expect_completion_insert_text :: proc(t: ^testing.T, src: ^Source, trigger_character: string, expect_inserts: []string) { + setup(src) + defer teardown(src) + + completion_context := server.CompletionContext { + triggerCharacter = trigger_character, + } + + completion_list, ok := server.get_completion_list(src.document, src.position, completion_context) + + if !ok { + log.error("Failed get_completion_list") + } + + if len(expect_inserts) == 0 && len(completion_list.items) > 0 { + log.errorf("Expected empty completion inserts, but received %v", completion_list.items) + } + + flags := make([]int, len(expect_inserts), context.temp_allocator) + + for expect_insert, i in expect_inserts { + for completion, j in completion_list.items { + if insert_text, ok := completion.insertText.(string); ok { + if expect_insert == insert_text { + flags[i] += 1 + continue + } + } + } + } + + for flag, i in flags { + if flag != 1 { + log.errorf("Expected completion insert %v, but received %v", expect_inserts[i], completion_list.items) + } + } +} + expect_hover :: proc(t: ^testing.T, src: ^Source, expect_hover_string: string) { setup(src) defer teardown(src) diff --git a/tests/completions_test.odin b/tests/completions_test.odin index 8308eda..b496774 100644 --- a/tests/completions_test.odin +++ b/tests/completions_test.odin @@ -52,12 +52,7 @@ ast_index_array_completion :: proc(t: ^testing.T) { packages = {}, } - test.expect_completion_docs( - t, - &source, - ".", - {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}, - ) + test.expect_completion_docs(t, &source, ".", {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}) } @(test) @@ -79,12 +74,7 @@ ast_index_dynamic_array_completion :: proc(t: ^testing.T) { packages = {}, } - test.expect_completion_docs( - t, - &source, - ".", - {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}, - ) + test.expect_completion_docs(t, &source, ".", {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}) } @(test) @@ -106,12 +96,7 @@ ast_struct_pointer_completion :: proc(t: ^testing.T) { packages = {}, } - test.expect_completion_docs( - t, - &source, - ".", - {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}, - ) + test.expect_completion_docs(t, &source, ".", {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}) } @(test) @@ -134,12 +119,7 @@ ast_struct_take_address_completion :: proc(t: ^testing.T) { packages = {}, } - test.expect_completion_docs( - t, - &source, - ".", - {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}, - ) + test.expect_completion_docs(t, &source, ".", {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}) } @(test) @@ -162,12 +142,7 @@ ast_struct_deref_completion :: proc(t: ^testing.T) { packages = {}, } - test.expect_completion_docs( - t, - &source, - ".", - {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}, - ) + test.expect_completion_docs(t, &source, ".", {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}) } @(test) @@ -193,12 +168,7 @@ ast_range_map :: proc(t: ^testing.T) { packages = {}, } - test.expect_completion_docs( - t, - &source, - ".", - {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}, - ) + test.expect_completion_docs(t, &source, ".", {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}) } @(test) @@ -224,12 +194,7 @@ ast_range_array :: proc(t: ^testing.T) { packages = {}, } - test.expect_completion_docs( - t, - &source, - ".", - {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}, - ) + test.expect_completion_docs(t, &source, ".", {"My_Struct.one: int", "My_Struct.two: int", "My_Struct.three: int"}) } @(test) @@ -3212,7 +3177,7 @@ ast_completion_on_struct_using_field_selector_directly :: proc(t: ^testing.T) { }, ) source := test.Source { - main = `package main + main = `package main import "my_package" @@ -3311,18 +3276,12 @@ ast_completion_multi_pointer_nested :: proc(t: ^testing.T) { ast_completion_struct_documentation :: 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 My_Struct :: struct { } - `, - }, - ) + `}) source := test.Source { - main = `package main + main = `package main import "my_package" @@ -3440,7 +3399,7 @@ ast_completion_poly_struct_another_package :: proc(t: ^testing.T) { }, ) source := test.Source { - main = `package test + main = `package test import "my_package" @@ -3478,7 +3437,7 @@ ast_completion_poly_struct_another_package_field :: proc(t: ^testing.T) { }, ) source := test.Source { - main = `package test + main = `package test import "my_package" @@ -3514,18 +3473,15 @@ ast_completion_poly_proc_mixed_packages :: proc(t: ^testing.T) { } `, }, - test.Package { - pkg = "bar_package", - source = `package bar_package + test.Package{pkg = "bar_package", source = `package bar_package Bar :: struct { bar: int, } - `, - }, + `}, ) source := test.Source { - main = `package test + main = `package test import "foo_package" import "bar_package" @@ -3879,15 +3835,9 @@ ast_completion_proc_enum_param :: proc(t: ^testing.T) { ast_completion_using_aliased_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() {} - `, - }, - ) + `}) source := test.Source { main = `package test @@ -3910,25 +3860,13 @@ ast_completion_using_aliased_package :: proc(t: ^testing.T) { ast_completion_using_aliased_package_multiple :: proc(t: ^testing.T) { packages := make([dynamic]test.Package, context.temp_allocator) - append( - &packages, - test.Package { - pkg = "foo_pkg", - source = `package foo_pkg + append(&packages, test.Package{pkg = "foo_pkg", source = `package foo_pkg foo :: proc() {} - `, - }, - ) + `}) - append( - &packages, - test.Package { - pkg = "bar_pkg", - source = `package bar_pkg + append(&packages, test.Package{pkg = "bar_pkg", source = `package bar_pkg bar :: proc() {} - `, - }, - ) + `}) source := test.Source { main = `package test @@ -4118,7 +4056,7 @@ ast_completion_enum_array_in_proc_param :: proc(t: ^testing.T) { @(test) ast_completion_union_with_poly :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test Foo :: union($T: typeid) { T, @@ -4130,12 +4068,7 @@ ast_completion_union_with_poly :: proc(t: ^testing.T) { } `, } - test.expect_completion_docs( - t, - &source, - "", - {"test.foo: test.Foo(int)"}, - ) + test.expect_completion_docs(t, &source, "", {"test.foo: test.Foo(int)"}) } @(test) @@ -4144,15 +4077,12 @@ ast_completion_union_with_poly_from_package :: proc(t: ^testing.T) { append( &packages, - test.Package { - pkg = "my_package", - source = `package my_package + test.Package{pkg = "my_package", source = `package my_package Foo :: union($T: typeid) { T, } - `, - }, + `}, ) source := test.Source { main = `package test @@ -4165,18 +4095,13 @@ ast_completion_union_with_poly_from_package :: proc(t: ^testing.T) { `, packages = packages[:], } - test.expect_completion_docs( - t, - &source, - "", - {"test.foo: my_package.Foo(int)"}, - ) + test.expect_completion_docs(t, &source, "", {"test.foo: my_package.Foo(int)"}) } @(test) ast_completion_chained_proc_call_params :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test Foo :: struct { data: int, } @@ -4194,13 +4119,13 @@ ast_completion_chained_proc_call_params :: proc(t: ^testing.T) { } `, } - test.expect_completion_docs( t, &source, "", {"Foo.data: int"}) + test.expect_completion_docs(t, &source, "", {"Foo.data: int"}) } @(test) -ast_completion_multiple_chained_call_expr :: proc(t: ^testing.T) { +ast_completion_multiple_chained_call_expr :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test Foo :: struct { someData: int, } @@ -4220,13 +4145,13 @@ ast_completion_multiple_chained_call_expr :: proc(t: ^testing.T) { foo :: proc() -> Bar {} `, } - test.expect_completion_docs( t, &source, "", {"Bazz.bazz: string"}) + test.expect_completion_docs(t, &source, "", {"Bazz.bazz: string"}) } @(test) -ast_completion_nested_struct_with_enum_fields_unnamed :: proc(t: ^testing.T) { +ast_completion_nested_struct_with_enum_fields_unnamed :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test Foo1 :: enum { A, B, } @@ -4256,13 +4181,13 @@ ast_completion_nested_struct_with_enum_fields_unnamed :: proc(t: ^testing.T) { } `, } - test.expect_completion_docs( t, &source, "", {"C", "D"}, {"A", "B"}) + test.expect_completion_docs(t, &source, "", {"C", "D"}, {"A", "B"}) } @(test) ast_completion_poly_type :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test foo :: proc(array: $A/[]$T) { for elem, i in array { el{*} @@ -4270,13 +4195,13 @@ ast_completion_poly_type :: proc(t: ^testing.T) { } `, } - test.expect_completion_docs( t, &source, "", {"test.elem: $T"}) + test.expect_completion_docs(t, &source, "", {"test.elem: $T"}) } @(test) ast_completion_proc_field_names :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test foo :: proc(i: int, bar := "") {} main :: proc() { @@ -4284,13 +4209,13 @@ ast_completion_proc_field_names :: proc(t: ^testing.T) { } `, } - test.expect_completion_docs( t, &source, "", {"test.bar: string"}) + test.expect_completion_docs(t, &source, "", {"test.bar: string"}) } @(test) ast_completion_enum_variadiac_args :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test Foo :: enum { A, B, @@ -4304,13 +4229,13 @@ ast_completion_enum_variadiac_args :: proc(t: ^testing.T) { } `, } - test.expect_completion_docs( t, &source, "", {"A", "B", "C"}) + test.expect_completion_docs(t, &source, "", {"A", "B", "C"}) } @(test) ast_completion_proc_variadiac_arg :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test Foo :: enum { A, B, @@ -4320,13 +4245,13 @@ ast_completion_proc_variadiac_arg :: proc(t: ^testing.T) { foo :: proc(foos: ..{*}) {} `, } - test.expect_completion_docs( t, &source, "", {"test.Foo: enum {..}"}) + test.expect_completion_docs(t, &source, "", {"test.Foo: enum {..}"}) } @(test) ast_completion_within_struct_decl :: proc(t: ^testing.T) { source := test.Source { - main = `package test + main = `package test Foo :: enum { A, B, @@ -4340,7 +4265,7 @@ ast_completion_within_struct_decl :: proc(t: ^testing.T) { } `, } - test.expect_completion_docs( t, &source, "", {"test.Foo: enum {..}"}, {"test.foo: proc(f: Foo)"}) + test.expect_completion_docs(t, &source, "", {"test.Foo: enum {..}"}, {"test.foo: proc(f: Foo)"}) } @(test) @@ -4393,17 +4318,11 @@ ast_completion_enum_map_value_global :: proc(t: ^testing.T) { ast_completion_basic_type_other_pkg :: 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: int - `, - }, - ) + `}) source := test.Source { - main = `package test + main = `package test import "my_package" foo :: proc() { @@ -4541,43 +4460,49 @@ ast_completion_bit_set_on_struct :: proc(t: ^testing.T) { } @(test) -ast_completions_should_not_have_private_overloads :: proc(t: ^testing.T) { - packages := make([dynamic]test.Package, context.temp_allocator) +ast_completions_handle_pointers_basic_types :: proc(t: ^testing.T) { + source := test.Source { + main = `package test - append( - &packages, - test.Package { - pkg = "my_package", - source = `package my_package + bar :: proc(i: ^int) {} - @(private) - foo_str :: proc(s: string) {} - @(private = "file") - foo_int :: proc(i: int) {} - foo :: proc { - foo_str, - foo_int, - } + main :: proc() { + foo: int + bar(f{*}) + } `, - }, - ) + } + test.expect_completion_insert_text(t, &source, "", {"&foo"}) +} + +@(test) +ast_completions_handle_pointers_struct :: proc(t: ^testing.T) { source := test.Source { - main = `package test - import "my_package" + main = `package test + + Foo :: struct{} + + bar :: proc(foo: ^Foo) {} main :: proc() { - foo := my_package.f{*} + foo: Foo + bar(f{*}) } `, - packages = packages[:], } - test.expect_completion_docs( - t, - &source, - "", - {"my_package.foo: proc (..)"}, { - "@(private)\nmy_package.foo_str: proc(s: string)", - "@(private=\"file\")\nmy_package.foo_int: proc(i: int)", - }, - ) + test.expect_completion_insert_text(t, &source, "", {"&foo"}) +} + +@(test) +ast_completions_handle_pointers_append :: proc(t: ^testing.T) { + source := test.Source { + main = `package test + + main :: proc() { + foos: [dynamic]int + append(fo{*}) + } + `, + } + test.expect_completion_insert_text(t, &source, "", {"&foos"}) } |