aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad Lewis <22850972+BradLewis@users.noreply.github.com>2025-08-23 12:22:36 -0400
committerBrad Lewis <22850972+BradLewis@users.noreply.github.com>2025-08-24 08:55:23 -0400
commit118db4f7667c91b24fcc21353df7c3cd7a5830b3 (patch)
tree0f098b492a06712f79782d493797604eec4e844b
parent39b2e758cacb40ba1a062b8af87af9edf8958375 (diff)
Add special case for handling `append` for dynamic arrays
-rw-r--r--src/server/completion.odin57
-rw-r--r--src/testing/testing.odin38
-rw-r--r--tests/completions_test.odin243
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"})
}