aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBradley Lewis <22850972+BradLewis@users.noreply.github.com>2025-08-17 21:42:44 -0400
committerGitHub <noreply@github.com>2025-08-17 21:42:44 -0400
commit7f8c9bdf7d1be2f4e4f2301de163a8bac8bb025b (patch)
treebd06c4da9dbfe0f56cc2817f5e3287b7d4dd657d
parent6282669ff2438cdeb1b90b05af0f573602ad3144 (diff)
parent03a531cc687b4a97c5834037598a7ba74b3ef116 (diff)
Merge pull request #900 from BradLewis/feat/soa
Support #soa fields
-rw-r--r--src/server/analysis.odin70
-rw-r--r--src/server/ast.odin12
-rw-r--r--src/server/completion.odin109
-rw-r--r--src/server/documentation.odin27
-rw-r--r--src/server/hover.odin43
-rw-r--r--src/server/symbol.odin1
-rw-r--r--tests/completions_test.odin57
-rw-r--r--tests/definition_test.odin21
-rw-r--r--tests/hover_test.odin115
-rw-r--r--tests/references_test.odin44
10 files changed, 467 insertions, 32 deletions
diff --git a/src/server/analysis.odin b/src/server/analysis.odin
index 1524112..e2ce2a6 100644
--- a/src/server/analysis.odin
+++ b/src/server/analysis.odin
@@ -1080,7 +1080,8 @@ internal_resolve_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Ex
out^, ok = make_symbol_map_from_ast(ast_context, v^, ast_context.field_name), true
return ok
case ^Proc_Type:
- out^, ok = make_symbol_procedure_from_ast(ast_context, node, v^, ast_context.field_name.name, {}, true, .None), true
+ out^, ok =
+ make_symbol_procedure_from_ast(ast_context, node, v^, ast_context.field_name.name, {}, true, .None), true
return ok
case ^Bit_Field_Type:
out^, ok = make_symbol_bit_field_from_ast(ast_context, v, ast_context.field_name.name, true), true
@@ -1144,6 +1145,9 @@ internal_resolve_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Ex
case ^Pointer_Type:
ok := internal_resolve_type_expression(ast_context, v.elem, out)
out.pointers += 1
+ if pointer_is_soa(v^) {
+ out.flags += {.SoaPointer}
+ }
return ok
case ^Matrix_Index_Expr:
if ok := internal_resolve_type_expression(ast_context, v.expr, out); ok {
@@ -1300,6 +1304,54 @@ resolve_type_assertion_expr :: proc(ast_context: ^AstContext, v: ^ast.Type_Asser
return symbol, ok
}
+resolve_soa_selector_field :: proc(
+ ast_context: ^AstContext,
+ selector: Symbol,
+ expr: ^ast.Expr,
+ size: ^ast.Expr,
+ name: string,
+) -> (
+ Symbol,
+ bool,
+) {
+ if .Soa not_in selector.flags && .SoaPointer not_in selector.flags {
+ return {}, false
+ }
+
+ if symbol, ok := resolve_type_expression(ast_context, expr); ok {
+ if v, ok := symbol.value.(SymbolStructValue); ok {
+ for n, i in v.names {
+ if n == name {
+ if .SoaPointer in selector.flags {
+ if resolved, ok := resolve_type_expression(ast_context, v.types[i]); ok {
+ symbol.value = resolved.value
+ symbol.pkg = symbol.name
+ } else {
+ return {}, false
+ }
+ } else if size != nil {
+ symbol.value = SymbolFixedArrayValue {
+ expr = v.types[i],
+ len = size,
+ }
+ } else {
+ symbol.value = SymbolMultiPointerValue {
+ expr = v.types[i],
+ }
+ }
+
+ symbol.name = name
+ symbol.type = .Field
+ symbol.range = v.ranges[i]
+ return symbol, true
+ }
+ }
+ }
+ }
+
+ return {}, false
+}
+
resolve_selector_expression :: proc(ast_context: ^AstContext, node: ^ast.Selector_Expr) -> (Symbol, bool) {
selector := Symbol{}
if ok := internal_resolve_type_expression(ast_context, node.expr, &selector); ok {
@@ -1310,6 +1362,9 @@ resolve_selector_expression :: proc(ast_context: ^AstContext, node: ^ast.Selecto
symbol := Symbol{}
#partial switch s in selector.value {
case SymbolFixedArrayValue:
+ if symbol, ok := resolve_soa_selector_field(ast_context, selector, s.expr, s.len, node.field.name); ok {
+ return symbol, ok
+ }
components_count := 0
for c in node.field.name {
if c == 'x' || c == 'y' || c == 'z' || c == 'w' || c == 'r' || c == 'g' || c == 'b' || c == 'a' {
@@ -1382,6 +1437,10 @@ resolve_selector_expression :: proc(ast_context: ^AstContext, node: ^ast.Selecto
// enum members probably require own symbol value
selector.type = .EnumMember
return selector, true
+ case SymbolSliceValue:
+ return resolve_soa_selector_field(ast_context, selector, s.expr, nil, node.field.name)
+ case SymbolDynamicArrayValue:
+ return resolve_soa_selector_field(ast_context, selector, s.expr, nil, node.field.name)
}
}
@@ -1620,7 +1679,8 @@ resolve_local_identifier :: proc(ast_context: ^AstContext, node: ast.Ident, loca
if !ok && !ast_context.overloading {
return_symbol, ok =
- make_symbol_procedure_from_ast(ast_context, local.rhs, v.type^, node.name, {}, false, v.inlining), true
+ make_symbol_procedure_from_ast(ast_context, local.rhs, v.type^, node.name, {}, false, v.inlining),
+ true
}
} else {
return_symbol, ok =
@@ -2596,6 +2656,12 @@ resolve_symbol_selector :: proc(
if s, ok := resolve_type_expression(ast_context, v.return_types[0].type); ok {
return resolve_symbol_selector(ast_context, selector, s)
}
+ case SymbolSliceValue:
+ return resolve_soa_selector_field(ast_context, symbol, v.expr, nil, field)
+ case SymbolDynamicArrayValue:
+ return resolve_soa_selector_field(ast_context, symbol, v.expr, nil, field)
+ case SymbolFixedArrayValue:
+ return resolve_soa_selector_field(ast_context, symbol, v.expr, v.len, field)
}
return symbol, true
diff --git a/src/server/ast.odin b/src/server/ast.odin
index f33ac6b..36f6a88 100644
--- a/src/server/ast.odin
+++ b/src/server/ast.odin
@@ -243,6 +243,15 @@ unwrap_pointer_expr :: proc(expr: ^ast.Expr) -> (^ast.Expr, int, bool) {
return expr, n, true
}
+pointer_is_soa :: proc(pointer: ast.Pointer_Type) -> bool {
+ if pointer.tag != nil {
+ if basic, ok := pointer.tag.derived.(^ast.Basic_Directive); ok && basic.name == "soa" {
+ return true
+ }
+ }
+ return false
+}
+
array_is_soa :: proc(array: ast.Array_Type) -> bool {
if array.tag != nil {
if basic, ok := array.tag.derived.(^ast.Basic_Directive); ok && basic.name == "soa" {
@@ -1216,16 +1225,19 @@ build_string_node :: proc(node: ^ast.Node, builder: ^strings.Builder, remove_poi
build_string(n.results, builder, remove_pointers)
}
case ^Pointer_Type:
+ build_string(n.tag, builder, remove_pointers)
if !remove_pointers {
strings.write_string(builder, "^")
}
build_string(n.elem, builder, remove_pointers)
case ^Array_Type:
+ build_string(n.tag, builder, remove_pointers)
strings.write_string(builder, "[")
build_string(n.len, builder, remove_pointers)
strings.write_string(builder, "]")
build_string(n.elem, builder, remove_pointers)
case ^Dynamic_Array_Type:
+ build_string(n.tag, builder, remove_pointers)
strings.write_string(builder, "[dynamic]")
build_string(n.elem, builder, remove_pointers)
case ^Struct_Type:
diff --git a/src/server/completion.odin b/src/server/completion.odin
index 9bb2f3b..4bd0272 100644
--- a/src/server/completion.odin
+++ b/src/server/completion.odin
@@ -1,6 +1,7 @@
#+feature dynamic-literals
package server
+import "base:runtime"
import "core:fmt"
import "core:log"
import "core:mem"
@@ -14,7 +15,6 @@ import "core:slice"
import "core:sort"
import "core:strconv"
import "core:strings"
-import "base:runtime"
import "src:common"
@@ -214,14 +214,14 @@ convert_completion_results :: proc(
// temporary as we move things to use the symbols directly
if item.documentation == nil {
item.documentation = MarkupContent {
- kind = "markdown",
- value = fmt.tprintf("```odin\n%v\n```", item.detail)
+ kind = "markdown",
+ value = fmt.tprintf("```odin\n%v\n```", item.detail),
}
item.detail = ""
} else if s, ok := item.documentation.(string); ok && s == "" {
item.documentation = MarkupContent {
- kind = "markdown",
- value = fmt.tprintf("```odin\n%v\n```", item.detail)
+ kind = "markdown",
+ value = fmt.tprintf("```odin\n%v\n```", item.detail),
}
item.detail = ""
}
@@ -316,7 +316,7 @@ convert_completion_results :: proc(
}
if completion_type == .Identifier {
- append_non_imported_packages(ast_context, position_context, &items)
+ append_non_imported_packages(ast_context, position_context, &items)
}
return items[:]
@@ -419,13 +419,15 @@ DIRECTIVE_NAME_LIST :: []string {
completion_items_directives: []CompletionResult
@(init)
-_init_completion_items_directives :: proc "contextless" () {
+_init_completion_items_directives :: proc "contextless" () {
context = runtime.default_context()
completion_items_directives = slice.mapper(DIRECTIVE_NAME_LIST, proc(name: string) -> CompletionResult {
- return CompletionResult{
- completion_item = CompletionItem{
- detail = strings.concatenate({"#", name}) or_else name, label = name, kind = .Constant,
- }
+ return CompletionResult {
+ completion_item = CompletionItem {
+ detail = strings.concatenate({"#", name}) or_else name,
+ label = name,
+ kind = .Constant,
+ },
}
})
}
@@ -502,6 +504,54 @@ get_comp_lit_completion :: proc(
return false
}
+add_soa_field_completion :: proc(
+ ast_context: ^AstContext,
+ selector: Symbol,
+ expr: ^ast.Expr,
+ size: ^ast.Expr,
+ results: ^[dynamic]CompletionResult,
+ parent_name: string,
+) {
+ if .Soa not_in selector.flags && .SoaPointer not_in selector.flags {
+ return
+ }
+ if symbol, ok := resolve_type_expression(ast_context, expr); ok {
+ if v, ok := symbol.value.(SymbolStructValue); ok {
+ for name, i in v.names {
+ if name == "_" {
+ continue
+ }
+
+ resolved := Symbol {
+ type = .Field,
+ range = v.ranges[i],
+ pkg = parent_name,
+ }
+ if .SoaPointer in selector.flags {
+ if s, ok := resolve_type_expression(ast_context, v.types[i]); ok {
+ resolved.value = s.value
+ resolved.pkg = symbol.name
+ } else {
+ continue
+ }
+ } else if size != nil {
+ resolved.value = SymbolFixedArrayValue {
+ expr = v.types[i],
+ len = size,
+ }
+ } else {
+ resolved.value = SymbolMultiPointerValue {
+ expr = v.types[i],
+ }
+ }
+ resolved.name = name
+ build_documentation(ast_context, &resolved)
+ append(results, CompletionResult{symbol = resolved})
+ }
+ }
+ }
+}
+
get_selector_completion :: proc(
ast_context: ^AstContext,
position_context: ^DocumentPositionContext,
@@ -652,6 +702,7 @@ get_selector_completion :: proc(
append(results, CompletionResult{completion_item = item})
}
}
+ add_soa_field_completion(ast_context, selector, v.expr, v.len, results, selector.name)
case SymbolUnionValue:
is_incomplete = false
@@ -728,20 +779,19 @@ get_selector_completion :: proc(
for name in enumv.names {
append(
results,
- CompletionResult{
- completion_item = CompletionItem{
+ CompletionResult {
+ completion_item = CompletionItem {
label = fmt.tprintf(".%s", name),
kind = .EnumMember,
detail = fmt.tprintf("%s.%s", selector.name, name),
additionalTextEdits = additionalTextEdits,
},
- }
+ },
)
}
case SymbolStructValue:
is_incomplete = false
-
for name, i in v.names {
if name == "_" {
continue
@@ -781,7 +831,6 @@ get_selector_completion :: proc(
append(results, CompletionResult{completion_item = item})
}
}
-
case SymbolBitFieldValue:
is_incomplete = false
@@ -841,10 +890,11 @@ get_selector_completion :: proc(
case SymbolDynamicArrayValue:
is_incomplete = false
append_magic_array_like_completion(position_context, selector, results)
+ add_soa_field_completion(ast_context, selector, v.expr, nil, results, selector.name)
case SymbolSliceValue:
is_incomplete = false
append_magic_array_like_completion(position_context, selector, results)
-
+ add_soa_field_completion(ast_context, selector, v.expr, nil, results, selector.name)
case SymbolMapValue:
is_incomplete = false
append_magic_map_completion(position_context, selector, results)
@@ -866,7 +916,7 @@ get_implicit_completion :: proc(
ast_context: ^AstContext,
position_context: ^DocumentPositionContext,
results: ^[dynamic]CompletionResult,
-) -> bool{
+) -> bool {
is_incomplete := false
selector: Symbol
@@ -916,10 +966,12 @@ get_implicit_completion :: proc(
}
} else if v, ok := symbol.value.(SymbolStructValue); ok {
if position_context.field_value != nil {
- if symbol, ok := resolve_implicit_selector_comp_literal(ast_context, position_context, symbol); ok {
+ if symbol, ok := resolve_implicit_selector_comp_literal(ast_context, position_context, symbol);
+ ok {
if enum_value, ok := symbol.value.(SymbolEnumValue); ok {
for name in enum_value.names {
- if position_context.comp_lit != nil && field_exists_in_comp_lit(position_context.comp_lit, name) {
+ if position_context.comp_lit != nil &&
+ field_exists_in_comp_lit(position_context.comp_lit, name) {
continue
}
item := CompletionItem {
@@ -1055,7 +1107,7 @@ get_implicit_completion :: proc(
}
}
}
-
+
//infer bitset and enums based on the identifier comp_lit, i.e. a := My_Struct { my_ident = . }
if position_context.comp_lit != nil && position_context.parent_comp_lit != nil {
if symbol, ok := resolve_comp_literal(ast_context, position_context); ok {
@@ -1240,7 +1292,8 @@ get_implicit_completion :: proc(
if enum_value, ok := unwrap_enum(ast_context, arg_type.type); ok {
for name in enum_value.names {
- if position_context.comp_lit != nil && field_exists_in_comp_lit(position_context.comp_lit, name) {
+ if position_context.comp_lit != nil &&
+ field_exists_in_comp_lit(position_context.comp_lit, name) {
continue
}
item := CompletionItem {
@@ -1402,9 +1455,9 @@ get_identifier_completion :: proc(
if symbol, ok := resolve_type_identifier(ast_context, ident^); ok {
if score, ok := common.fuzzy_match(matcher, ident.name); ok == 1 {
- symbol.type_name = symbol.name
- symbol.type_pkg = symbol.pkg
- symbol.name = clean_ident(ident.name)
+ symbol.type_name = symbol.name
+ symbol.type_pkg = symbol.pkg
+ symbol.name = clean_ident(ident.name)
append(results, CompletionResult{score = score * 1.1, symbol = symbol})
}
}
@@ -1912,6 +1965,12 @@ append_magic_array_like_completion :: proc(
symbol: Symbol,
results: ^[dynamic]CompletionResult,
) {
+ // Can't iterate over an soa pointer
+ // eg foos: #soa^#soa[]struct{}
+ if .SoaPointer in symbol.flags {
+ return
+ }
+
range, ok := get_range_from_selection_start_to_dot(position_context)
if !ok {
diff --git a/src/server/documentation.odin b/src/server/documentation.odin
index 2f73ed2..dc0784d 100644
--- a/src/server/documentation.odin
+++ b/src/server/documentation.odin
@@ -372,15 +372,36 @@ write_short_signature :: proc(sb: ^strings.Builder, ast_context: ^AstContext, sy
write_node(sb, ast_context, v.expr, "", short_signature = true)
return
case SymbolDynamicArrayValue:
- fmt.sbprintf(sb, "%s[dynamic]", pointer_prefix)
+ if .SoaPointer in symbol.flags {
+ strings.write_string(sb, "#soa")
+ }
+ strings.write_string(sb, pointer_prefix)
+ if .Soa in symbol.flags {
+ strings.write_string(sb, "#soa")
+ }
+ strings.write_string(sb, "[dynamic]")
write_node(sb, ast_context, v.expr, "", short_signature = true)
return
case SymbolSliceValue:
- fmt.sbprintf(sb, "%s[]", pointer_prefix)
+ if .SoaPointer in symbol.flags {
+ strings.write_string(sb, "#soa")
+ }
+ strings.write_string(sb, pointer_prefix)
+ if .Soa in symbol.flags {
+ strings.write_string(sb, "#soa")
+ }
+ strings.write_string(sb, "[]")
write_node(sb, ast_context, v.expr, "", short_signature = true)
return
case SymbolFixedArrayValue:
- fmt.sbprintf(sb, "%s[", pointer_prefix)
+ if .SoaPointer in symbol.flags {
+ strings.write_string(sb, "#soa")
+ }
+ strings.write_string(sb, pointer_prefix)
+ if .Soa in symbol.flags {
+ strings.write_string(sb, "#soa")
+ }
+ strings.write_string(sb, "[")
build_string_node(v.len, sb, false)
strings.write_string(sb, "]")
write_node(sb, ast_context, v.expr, "", short_signature = true)
diff --git a/src/server/hover.odin b/src/server/hover.odin
index 2c68ad0..03cdedf 100644
--- a/src/server/hover.odin
+++ b/src/server/hover.odin
@@ -153,7 +153,12 @@ get_hover_information :: proc(document: ^Document, position: common.Position) ->
position_context.value_decl.names[0],
); ok {
if value, ok := struct_symbol.value.(SymbolStructValue); ok {
- construct_struct_field_symbol(&symbol, struct_symbol.name, value, field_index+name_index)
+ construct_struct_field_symbol(
+ &symbol,
+ struct_symbol.name,
+ value,
+ field_index + name_index,
+ )
build_documentation(&ast_context, &symbol, true)
hover.contents = write_hover_content(&ast_context, symbol)
return hover, true, true
@@ -188,7 +193,8 @@ get_hover_information :: proc(document: ^Document, position: common.Position) ->
}
}
- if position_context.field_value != nil && position_in_node(position_context.field_value.field, position_context.position) {
+ if position_context.field_value != nil &&
+ position_in_node(position_context.field_value.field, position_context.position) {
if position_context.comp_lit != nil {
if comp_symbol, ok := resolve_comp_literal(&ast_context, &position_context); ok {
if field, ok := position_context.field_value.field.derived.(^ast.Ident); ok {
@@ -347,6 +353,12 @@ get_hover_information :: proc(document: ^Document, position: common.Position) ->
return hover, true, true
}
}
+ case SymbolSliceValue:
+ return get_soa_hover(&ast_context, selector, v.expr, nil, field)
+ case SymbolDynamicArrayValue:
+ return get_soa_hover(&ast_context, selector, v.expr, nil, field)
+ case SymbolFixedArrayValue:
+ return get_soa_hover(&ast_context, selector, v.expr, v.len, field)
}
} else if position_context.implicit_selector_expr != nil {
implicit_selector := position_context.implicit_selector_expr
@@ -425,3 +437,30 @@ get_hover_information :: proc(document: ^Document, position: common.Position) ->
return hover, false, true
}
+
+@(private = "file")
+get_soa_hover :: proc(
+ ast_context: ^AstContext,
+ selector: Symbol,
+ expr: ^ast.Expr,
+ size: ^ast.Expr,
+ field: string,
+) -> (
+ Hover,
+ bool,
+ bool,
+) {
+ if .SoaPointer not_in selector.flags && .Soa not_in selector.flags {
+ return {}, false, true
+ }
+ if symbol, ok := resolve_soa_selector_field(ast_context, selector, expr, size, field); ok {
+ if selector.name != "" {
+ symbol.pkg = selector.name
+ }
+ build_documentation(ast_context, &symbol, false)
+ hover: Hover
+ hover.contents = write_hover_content(ast_context, symbol)
+ return hover, true, true
+ }
+ return {}, false, true
+}
diff --git a/src/server/symbol.odin b/src/server/symbol.odin
index 2e2f26b..d163e45 100644
--- a/src/server/symbol.odin
+++ b/src/server/symbol.odin
@@ -197,6 +197,7 @@ SymbolFlag :: enum {
ObjC,
ObjCIsClassMethod, // should be set true only when ObjC is enabled
Soa,
+ SoaPointer,
Parameter, //If the symbol is a procedure argument
}
diff --git a/tests/completions_test.odin b/tests/completions_test.odin
index 0b2bc5f..9fcc797 100644
--- a/tests/completions_test.odin
+++ b/tests/completions_test.odin
@@ -4415,3 +4415,60 @@ ast_completion_basic_type_other_pkg :: proc(t: ^testing.T) {
test.expect_completion_docs(t, &source, "", {"my_package.foo: int"})
}
+
+@(test)
+ast_completion_soa_slice_fields :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x: int,
+ y: string,
+ }
+
+ main :: proc() {
+ foos: #soa[]Foo
+ foos.{*}
+ }
+ `,
+ }
+
+ test.expect_completion_docs(t, &source, "", {"foos.x: [^]int", "foos.y: [^]string"})
+}
+
+@(test)
+ast_completion_soa_fixed_array_fields :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x: int,
+ y: string,
+ }
+
+ main :: proc() {
+ foos: #soa[3]Foo
+ foos.{*}
+ }
+ `,
+ }
+
+ test.expect_completion_docs(t, &source, "", {"foos.x: [3]int", "foos.y: [3]string"})
+}
+
+@(test)
+ast_completion_soa_pointer :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x: int,
+ y: string,
+ }
+
+ main :: proc() {
+ foos: #soa^#soa[3]Foo
+ foos.{*}
+ }
+ `,
+ }
+
+ test.expect_completion_docs(t, &source, "", {"Foo.x: int", "Foo.y: string"})
+}
diff --git a/tests/definition_test.odin b/tests/definition_test.odin
index e41cae4..261ef83 100644
--- a/tests/definition_test.odin
+++ b/tests/definition_test.odin
@@ -606,3 +606,24 @@ ast_goto_enum_struct_field_without_name :: proc(t: ^testing.T) {
test.expect_definition_locations(t, &source, locations[:])
}
+
+@(test)
+ast_goto_soa_field :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x, y: int,
+ }
+
+ main :: proc() {
+ foos: #soa[]Foo
+ x := foos.x{*}
+ }
+ `,
+ }
+ locations := []common.Location {
+ {range = {start = {line = 2, character = 3}, end = {line = 2, character = 4}}},
+ }
+
+ test.expect_definition_locations(t, &source, locations[:])
+}
diff --git a/tests/hover_test.odin b/tests/hover_test.odin
index 5bf54ce..7301a7e 100644
--- a/tests/hover_test.odin
+++ b/tests/hover_test.odin
@@ -4131,6 +4131,121 @@ ast_hover_struct_tags_field_align :: proc(t: ^testing.T) {
test.expect_hover(t, &source, "test.Foo: struct #max_field_align(4) #min_field_align(2) {}")
}
+@(test)
+ast_hover_soa_slice :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x, y: int,
+ }
+
+ main :: proc() {
+ f{*}oos: #soa[]Foo
+ }
+ `,
+ }
+ test.expect_hover(t, &source, "test.foos: #soa[]Foo")
+}
+
+@(test)
+ast_hover_struct_with_soa_field :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x, y: int,
+ }
+
+ B{*}ar :: struct {
+ foos: #soa[5]Foo,
+ }
+ `,
+ }
+ test.expect_hover(t, &source, "test.Bar: struct {\n\tfoos: #soa[5]Foo,\n}")
+}
+
+@(test)
+ast_hover_soa_slice_field :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x, y: int,
+ }
+
+ main :: proc() {
+ foos: #soa[]Foo
+ foos.x{*}
+ }
+ `,
+ }
+ test.expect_hover(t, &source, "foos.x: [^]int")
+}
+
+@(test)
+ast_hover_identifier_soa_slice_field :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x, y: int,
+ }
+
+ main :: proc() {
+ foos: #soa[]Foo
+ x{*} := foos.x
+ }
+ `,
+ }
+ test.expect_hover(t, &source, "test.x: [^]int")
+}
+
+@(test)
+ast_hover_soa_fixed_array_field :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x, y: int,
+ }
+
+ main :: proc() {
+ foos: #soa[6]Foo
+ foos.x{*}
+ }
+ `,
+ }
+ test.expect_hover(t, &source, "foos.x: [6]int")
+}
+
+@(test)
+ast_hover_soa_pointer :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x, y: int,
+ }
+
+ main :: proc() {
+ f{*}oo: #soa^#soa[6]Foo
+ }
+ `,
+ }
+ test.expect_hover(t, &source, "test.foo: #soa^#soa[6]Foo")
+}
+
+@(test)
+ast_hover_soa_pointer_field :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x, y: int,
+ }
+
+ main :: proc() {
+ foo: #soa^#soa[6]Foo
+ foo.x{*}
+ }
+ `,
+ }
+ test.expect_hover(t, &source, "Foo.x: int")
+}
/*
Waiting for odin fix
diff --git a/tests/references_test.odin b/tests/references_test.odin
index fb409e6..3510283 100644
--- a/tests/references_test.odin
+++ b/tests/references_test.odin
@@ -1148,3 +1148,47 @@ ast_references_poly_type :: proc(t: ^testing.T) {
test.expect_reference_locations(t, &source, locations[:])
}
+
+@(test)
+ast_references_soa_field :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x, y: int,
+ }
+
+ main :: proc() {
+ foos: #soa[]Foo
+ x := foos.x{*}
+ }
+ `,
+ }
+ locations := []common.Location {
+ {range = {start = {line = 2, character = 3}, end = {line = 2, character = 4}}},
+ {range = {start = {line = 7, character = 13}, end = {line = 7, character = 14}}},
+ }
+
+ test.expect_reference_locations(t, &source, locations[:])
+}
+
+@(test)
+ast_references_soa_pointer_field :: proc(t: ^testing.T) {
+ source := test.Source {
+ main = `package test
+ Foo :: struct {
+ x, y: int,
+ }
+
+ main :: proc() {
+ foos: #soa^#soa[]Foo
+ x := foos.x{*}
+ }
+ `,
+ }
+ locations := []common.Location {
+ {range = {start = {line = 2, character = 3}, end = {line = 2, character = 4}}},
+ {range = {start = {line = 7, character = 13}, end = {line = 7, character = 14}}},
+ }
+
+ test.expect_reference_locations(t, &source, locations[:])
+}