diff options
| author | Brad Lewis <22850972+BradLewis@users.noreply.github.com> | 2025-06-15 08:56:52 -0400 |
|---|---|---|
| committer | Brad Lewis <22850972+BradLewis@users.noreply.github.com> | 2025-06-24 20:35:20 -0400 |
| commit | e4804807bb7c7a26b2bff10919d719a1a430870f (patch) | |
| tree | 116985fc1454378d4a5f01fc7859043a69336ab5 /src/server | |
| parent | be08855c0a96ffff4f577fa5ac703853422ca153 (diff) | |
Introduce a builder for `SymbolStructValue` and add docs and comments to
struct hover
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/analysis.odin | 266 | ||||
| -rw-r--r-- | src/server/ast.odin | 48 | ||||
| -rw-r--r-- | src/server/collector.odin | 31 | ||||
| -rw-r--r-- | src/server/generics.odin | 22 | ||||
| -rw-r--r-- | src/server/hover.odin | 17 | ||||
| -rw-r--r-- | src/server/symbol.odin | 278 |
6 files changed, 471 insertions, 191 deletions
diff --git a/src/server/analysis.odin b/src/server/analysis.odin index e779547..4f5b6d6 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -900,7 +900,7 @@ internal_resolve_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Ex case ^Enum_Type: return make_symbol_enum_from_ast(ast_context, v^, ast_context.field_name, true), true case ^Struct_Type: - return make_symbol_struct_from_ast(ast_context, v^, ast_context.field_name, {}, true), true + return make_symbol_struct_from_ast(ast_context, v, ast_context.field_name, {}, true), true case ^Bit_Set_Type: return make_symbol_bitset_from_ast(ast_context, v^, ast_context.field_name, true), true case ^Array_Type: @@ -1373,7 +1373,7 @@ internal_resolve_type_identifier :: proc(ast_context: ^AstContext, node: ast.Ide return_symbol, ok = make_symbol_enum_from_ast(ast_context, v^, node), true return_symbol.name = node.name case ^Struct_Type: - return_symbol, ok = make_symbol_struct_from_ast(ast_context, v^, node, {}), true + return_symbol, ok = make_symbol_struct_from_ast(ast_context, v, node, {}), true return_symbol.name = node.name case ^Bit_Set_Type: return_symbol, ok = make_symbol_bitset_from_ast(ast_context, v^, node), true @@ -1483,7 +1483,7 @@ internal_resolve_type_identifier :: proc(ast_context: ^AstContext, node: ast.Ide } case ^Struct_Type: - return_symbol, ok = make_symbol_struct_from_ast(ast_context, v^, node, global.attributes), true + return_symbol, ok = make_symbol_struct_from_ast(ast_context, v, node, global.attributes), true return_symbol.name = node.name case ^Bit_Set_Type: return_symbol, ok = make_symbol_bitset_from_ast(ast_context, v^, node), true @@ -1614,79 +1614,33 @@ internal_resolve_type_identifier :: proc(ast_context: ^AstContext, node: ast.Ide return Symbol{}, false } -expand_struct_usings :: proc(ast_context: ^AstContext, symbol: Symbol, value: SymbolStructValue) -> SymbolStructValue { - names := slice.to_dynamic(value.names, ast_context.allocator) - types := slice.to_dynamic(value.types, ast_context.allocator) - ranges := slice.to_dynamic(value.ranges, ast_context.allocator) - - for k, v in value.usings { - set_ast_package_set_scoped(ast_context, symbol.pkg) - - field_expr: ^ast.Expr - - field_expr = value.types[k] - - if field_expr == nil { - continue - } - - if s, ok := resolve_type_expression(ast_context, field_expr); ok { - if struct_value, ok := s.value.(SymbolStructValue); ok { - for name in struct_value.names { - append(&names, name) - } - - for type in struct_value.types { - append(&types, type) - } - - for range in struct_value.ranges { - append(&ranges, range) - } - } - } - - //We have to resolve the expressions two times, so clear it to prevent it from being picked up as recursion. - delete_key(&ast_context.recursion_map, value.types[k]) +struct_type_from_identifier :: proc(ast_context: ^AstContext, node: ast.Ident) -> (^ast.Struct_Type, bool) { + if check_node_recursion(ast_context, node.derived.(^ast.Ident)) { + return {}, false } - if .ObjC in symbol.flags { - pkg := indexer.index.collection.packages[symbol.pkg] - - if obj_struct, ok := pkg.objc_structs[symbol.name]; ok { - _objc_function: for function, i in obj_struct.functions { - base := new_type(ast.Ident, {}, {}, context.temp_allocator) - base.name = obj_struct.pkg - - field := new_type(ast.Ident, {}, {}, context.temp_allocator) - field.name = function.physical_name - - selector := new_type(ast.Selector_Expr, {}, {}, context.temp_allocator) - - selector.field = field - selector.expr = base + //Try to prevent stack overflows and prevent indexing out of bounds. + if ast_context.deferred_count >= DeferredDepth { + return {}, false + } - //Check if the base functions need to be overridden. Potentially look at some faster approach than a linear loop. - for name, j in names { - if name == function.logical_name { - names[j] = function.logical_name - types[j] = selector - ranges[j] = obj_struct.ranges[i] - continue _objc_function - } - } + set_ast_package_scoped(ast_context) - append(&names, function.logical_name) - append(&types, selector) - append(&ranges, obj_struct.ranges[i]) - } + if local, ok := get_local(ast_context^, node); ok && (ast_context.use_locals || local.local_global) { + v, ok := local.rhs.derived.(^ast.Struct_Type) + return v, ok + } - } + if global, ok := ast_context.globals[node.name]; + ast_context.current_package == ast_context.document_package && ok { + v, ok := global.expr.derived.(^ast.Struct_Type) + return v, ok } - return {names = names[:], types = types[:], ranges = ranges[:]} + return nil, false } + resolve_slice_expression :: proc(ast_context: ^AstContext, slice_expr: ^ast.Slice_Expr) -> (symbol: Symbol, ok: bool) { symbol = resolve_type_expression(ast_context, slice_expr.expr) or_return @@ -1995,26 +1949,15 @@ resolve_symbol_return :: proc(ast_context: ^AstContext, symbol: Symbol, ok := tr } return symbol, ok case SymbolStructValue: + b := symbol_struct_value_builder_make(symbol, v, ast_context.allocator) if v.poly != nil { - types := make([dynamic]^ast.Expr, ast_context.allocator) - - for type in v.types { - append(&types, clone_expr(type, context.temp_allocator, nil)) - } - - v.types = types[:] - - resolve_poly_struct(ast_context, v.poly, &symbol) + resolve_poly_struct(ast_context, &b, v.poly) } //expand the types and names from the using - can't be done while indexing without complicating everything(this also saves memory) - if len(v.usings) > 0 || .ObjC in symbol.flags { - expanded := symbol - expanded.value = expand_struct_usings(ast_context, symbol, v) - return expanded, true - } else { - return symbol, true - } + expand_usings(ast_context, &b) + expand_objc(ast_context, &b) + return to_symbol(b), ok case SymbolGenericValue: ret, ok := resolve_type_expression(ast_context, v.expr) if symbol.type == .Variable { @@ -2466,8 +2409,11 @@ get_using_packages :: proc(ast_context: ^AstContext) -> []string { } get_symbol_pkg_name :: proc(ast_context: ^AstContext, symbol: Symbol) -> string { - name := path.base(symbol.pkg, false, context.temp_allocator) + return get_pkg_name(ast_context, symbol.pkg) +} +get_pkg_name :: proc(ast_context: ^AstContext, pkg: string) -> string { + name := path.base(pkg, false, context.temp_allocator) for imp in ast_context.imports { if imp.base_original == name { return imp.base @@ -2761,11 +2707,12 @@ make_symbol_bitset_from_ast :: proc( make_symbol_struct_from_ast :: proc( ast_context: ^AstContext, - v: ast.Struct_Type, + v: ^ast.Struct_Type, ident: ast.Ident, attributes: []^ast.Attribute, inlined := false, ) -> Symbol { + node := v.node symbol := Symbol { range = common.get_token_range(v, ast_context.file.src), type = .Struct, @@ -2778,54 +2725,9 @@ make_symbol_struct_from_ast :: proc( symbol.name = "struct" } - names := make([dynamic]string, ast_context.allocator) - types := make([dynamic]^ast.Expr, ast_context.allocator) - usings := make(map[int]bool, 0, ast_context.allocator) - ranges := make([dynamic]common.Range, 0, ast_context.allocator) - - for field in v.fields.list { - for n in field.names { - if identifier, ok := n.derived.(^ast.Ident); ok && field.type != nil { - if .Using in field.flags { - usings[len(types)] = true - } - - append(&names, identifier.name) - if v.poly_params != nil { - append(&types, clone_type(field.type, ast_context.allocator, nil)) - } else { - append(&types, field.type) - } - - append(&ranges, common.get_token_range(n, ast_context.file.src)) - } - } - } - - symbol.value = SymbolStructValue { - names = names[:], - types = types[:], - ranges = ranges[:], - usings = usings, - poly = v.poly_params, - } - - if _, ok := get_attribute_objc_class_name(attributes); ok { - symbol.flags |= {.ObjC} - if get_attribute_objc_is_class_method(attributes) { - symbol.flags |= {.ObjCIsClassMethod} - } - } - - if v.poly_params != nil { - resolve_poly_struct(ast_context, v.poly_params, &symbol) - } - - //TODO change the expand to not double copy the array, but just pass the dynamic arrays - if len(usings) > 0 || .ObjC in symbol.flags { - symbol.value = expand_struct_usings(ast_context, symbol, symbol.value.(SymbolStructValue)) - } - + b := symbol_struct_value_builder_make(symbol, ast_context.allocator) + write_struct_type(ast_context, &b, v^, ident, attributes, -1, inlined) + symbol = to_symbol(b) return symbol } @@ -3956,6 +3858,16 @@ append_variable_full_name :: proc( return } +make_comment_map :: proc(groups: []^ast.Comment_Group, allocator: mem.Allocator) -> map[int]^ast.Comment_Group { + comment_map := make(map[int]^ast.Comment_Group, allocator = allocator) + + for cg in groups { + comment_map[cg.pos.line] = cg + } + + return comment_map +} + get_signature :: proc( ast_context: ^AstContext, symbol: Symbol, @@ -3971,7 +3883,7 @@ get_signature :: proc( } is_variable := symbol.type == .Variable - + is_field := symbol.type == .Field pointer_prefix := repeat("^", symbol.pointers, context.temp_allocator) @@ -4024,36 +3936,30 @@ get_signature :: proc( builder := strings.builder_make(ast_context.allocator) if is_variable { append_variable_full_name(&builder, ast_context, symbol, pointer_prefix) + } else if is_field { + pkg_name := get_pkg_name(ast_context, symbol.type_pkg) + if pkg_name == "" { + fmt.sbprintf(&builder, "%s%s", pointer_prefix, symbol.type_name) + } else { + fmt.sbprintf(&builder, "%s%s.%s", pointer_prefix, pkg_name, symbol.type_name) + } + if symbol.comment != "" { + fmt.sbprintf(&builder, " %s", symbol.comment) + } + return strings.to_string(builder) } else if symbol.type_name != "" { if symbol.type_pkg == "" { fmt.sbprintf(&builder, "%s%s :: ", pointer_prefix, symbol.type_name) } else { - // TODO: make this function just take a string - symbol := symbol - symbol.pkg = symbol.type_pkg - pkg_name := get_symbol_pkg_name(ast_context, symbol) + pkg_name := get_pkg_name(ast_context, symbol.type_pkg) fmt.sbprintf(&builder, "%s%s.%s :: ", pointer_prefix, pkg_name, symbol.type_name) } } - longestNameLen := 0 - for name in v.names { - if len(name) > longestNameLen { - longestNameLen = len(name) - } - } if len(v.names) == 0 { strings.write_string(&builder, "struct {}") return strings.to_string(builder) } - strings.write_string(&builder, "struct {\n") - for i in 0 ..< len(v.names) { - strings.write_string(&builder, "\t") - strings.write_string(&builder, v.names[i]) - fmt.sbprintf(&builder, ":%*s", longestNameLen - len(v.names[i]) + 1, "") - build_string_node(v.types[i], &builder, false) - strings.write_string(&builder, ",\n") - } - strings.write_string(&builder, "}") + write_struct_hover(ast_context, &builder, v) return strings.to_string(builder) case SymbolUnionValue: if short_signature { @@ -4134,6 +4040,60 @@ get_signature :: proc( return "" } +write_struct_hover :: proc(ast_context: ^AstContext, sb: ^strings.Builder, v: SymbolStructValue) { + using_prefix := "using " + longestNameLen := 0 + for name, i in v.names { + l := len(name) + if _, ok := v.usings[i]; ok { + l += len(using_prefix) + } + if l > longestNameLen { + longestNameLen = len(name) + } + } + + using_index := -1 + + strings.write_string(sb, "struct {\n") + for i in 0 ..< len(v.names) { + if i < len(v.from_usings) { + if index := v.from_usings[i]; index != using_index { + fmt.sbprintf(sb, "\n\t// from `using %s: ", v.names[index]) + build_string_node(v.types[index], sb, false) + strings.write_string(sb, "`\n") + using_index = index + } + } + if i < len(v.docs) && v.docs[i] != nil { + for c in v.docs[i].list { + fmt.sbprintf(sb, "\t%s\n", c.text) + } + } + + strings.write_string(sb, "\t") + + name_len := len(v.names[i]) + if _, ok := v.usings[i]; ok { + strings.write_string(sb, using_prefix) + name_len += len(using_prefix) + } + strings.write_string(sb, v.names[i]) + fmt.sbprintf(sb, ":%*s", longestNameLen - name_len + 1, "") + build_string_node(v.types[i], sb, false) + strings.write_string(sb, ",") + + if i < len(v.comments) && v.comments[i] != nil { + for c in v.comments[i].list { + fmt.sbprintf(sb, " %s\n", c.text) + } + } else { + strings.write_string(sb, "\n") + } + } + strings.write_string(sb, "}") +} + position_in_proc_decl :: proc(position_context: ^DocumentPositionContext) -> bool { if position_context.value_decl == nil { return false diff --git a/src/server/ast.odin b/src/server/ast.odin index 78e037c..73c84cc 100644 --- a/src/server/ast.odin +++ b/src/server/ast.odin @@ -1174,3 +1174,51 @@ repeat :: proc(value: string, count: int, allocator := context.allocator) -> str } return strings.repeat(value, count, allocator) } + +construct_struct_field_docs :: proc(file: ast.File, v: ^ast.Struct_Type) { + 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. + // see https://github.com/odin-lang/Odin/issues/5353 + if field.comment == nil { + // We check if the comment is at the start of the next field + if i != len(v.fields.list) - 1 { + next_field := v.fields.list[i + 1] + if next_field.docs != nil && len(next_field.docs.list) > 0 { + list := next_field.docs.list + if list[0].pos.line == field.pos.line { + field.comment = ast.new(ast.Comment_Group, list[0].pos, parser.end_pos(list[0])) + field.comment.list = list[:1] + if len(list) > 1 { + next_field.docs = ast.new( + ast.Comment_Group, + list[1].pos, + parser.end_pos(list[len(list) - 2]), + ) + next_field.docs.list = list[1:] + } else { + next_field.docs = nil + } + } + } + } else { + // We need to check the file to see if it contains a line comment as there is no next field + // TODO: linear scan might be a bit slow for files with lots of comments? + for c in file.comments { + if c.pos.line == field.pos.line { + for item, j in c.list { + field.comment = ast.new(ast.Comment_Group, item.pos, parser.end_pos(item)) + if j == len(c.list) - 1 { + field.comment.list = c.list[j:] + } else { + field.comment.list = c.list[j:j + 1] + } + break + } + } + } + } + } + } + +} diff --git a/src/server/collector.odin b/src/server/collector.odin index fb2588a..1bbe569 100644 --- a/src/server/collector.odin +++ b/src/server/collector.odin @@ -118,40 +118,37 @@ collect_procedure_fields :: proc( collect_struct_fields :: proc( collection: ^SymbolCollection, - struct_type: ast.Struct_Type, + struct_type: ^ast.Struct_Type, package_map: map[string]string, file: ast.File, ) -> SymbolStructValue { - names := make([dynamic]string, 0, collection.allocator) - types := make([dynamic]^ast.Expr, 0, collection.allocator) - usings := make(map[int]bool, 0, collection.allocator) - ranges := make([dynamic]common.Range, 0, collection.allocator) + b := symbol_struct_value_builder_make(collection.allocator) + construct_struct_field_docs(file, struct_type) for field in struct_type.fields.list { for n in field.names { if ident, ok := n.derived.(^ast.Ident); ok { - append(&names, get_index_unique_string(collection, ident.name)) + append(&b.names, get_index_unique_string(collection, ident.name)) cloned := clone_type(field.type, collection.allocator, &collection.unique_strings) replace_package_alias(cloned, package_map, collection) - append(&types, cloned) + append(&b.types, cloned) if .Using in field.flags { - usings[len(names) - 1] = true + append(&b.unexpanded_usings, len(b.names) - 1) } - append(&ranges, common.get_token_range(n, file.src)) + append(&b.ranges, common.get_token_range(n, file.src)) + + append(&b.docs, field.docs) + append(&b.comments, field.comment) + append(&b.from_usings, -1) } } } - value := SymbolStructValue { - names = names[:], - types = types[:], - ranges = ranges[:], - usings = usings, - poly = cast(^ast.Field_List)clone_type(struct_type.poly_params, collection.allocator, &collection.unique_strings), - } + value := to_symbol_struct_value(b) + value.poly = cast(^ast.Field_List)clone_type(struct_type.poly_params, collection.allocator, &collection.unique_strings) return value } @@ -522,7 +519,7 @@ collect_symbols :: proc(collection: ^SymbolCollection, file: ast.File, uri: stri case ^ast.Struct_Type: token = v^ token_type = .Struct - symbol.value = collect_struct_fields(collection, v^, package_map, file) + symbol.value = collect_struct_fields(collection, v, package_map, file) symbol.signature = "struct" if _, is_objc := get_attribute_objc_class_name(expr.attributes); is_objc { diff --git a/src/server/generics.odin b/src/server/generics.odin index db7ad88..d31dfcd 100644 --- a/src/server/generics.odin +++ b/src/server/generics.odin @@ -643,21 +643,15 @@ is_procedure_generic :: proc(proc_type: ^ast.Proc_Type) -> bool { } -resolve_poly_struct :: proc(ast_context: ^AstContext, poly_params: ^ast.Field_List, symbol: ^Symbol) { +// TODO: update to use builder +resolve_poly_struct :: proc(ast_context: ^AstContext, b: ^SymbolStructValueBuilder, poly_params: ^ast.Field_List) { if ast_context.call == nil { return } - symbol_value := &symbol.value.(SymbolStructValue) - - if symbol_value == nil { - return - } - i := 0 poly_map := make(map[string]^ast.Expr, 0, context.temp_allocator) - args := make([dynamic]^ast.Expr, 0, context.temp_allocator) for param in poly_params.list { for name in param.names { @@ -679,7 +673,7 @@ resolve_poly_struct :: proc(ast_context: ^AstContext, poly_params: ^ast.Field_Li } } - append(&args, ast_context.call.args[i]) + append(&b.args, ast_context.call.args[i]) i += 1 } @@ -687,7 +681,7 @@ resolve_poly_struct :: proc(ast_context: ^AstContext, poly_params: ^ast.Field_Li Visit_Data :: struct { poly_map: map[string]^ast.Expr, - symbol_value: ^SymbolStructValue, + symbol_value_builder: ^SymbolStructValueBuilder, parent: ^ast.Node, i: int, poly_index: int, @@ -712,7 +706,7 @@ resolve_poly_struct :: proc(ast_context: ^AstContext, poly_params: ^ast.Field_Li v.elem = expr } } else { - data.symbol_value.types[data.i] = expr + data.symbol_value_builder.types[data.i] = expr data.poly_index += 1 } } @@ -726,10 +720,10 @@ resolve_poly_struct :: proc(ast_context: ^AstContext, poly_params: ^ast.Field_Li return visitor } - for type, i in symbol_value.types { + for type, i in b.types { data := Visit_Data { poly_map = poly_map, - symbol_value = symbol_value, + symbol_value_builder = b, i = i, } @@ -740,8 +734,6 @@ resolve_poly_struct :: proc(ast_context: ^AstContext, poly_params: ^ast.Field_Li ast.walk(&visitor, type) } - - symbol_value.args = args[:] } diff --git a/src/server/hover.odin b/src/server/hover.odin index afeab99..dd4af97 100644 --- a/src/server/hover.odin +++ b/src/server/hover.odin @@ -125,10 +125,27 @@ get_hover_information :: proc(document: ^Document, position: common.Position) -> &ast_context, position_context.value_decl.names[0], ); ok { + symbol.type = .Field + symbol.range = common.get_token_range(field.node, ast_context.file.src) symbol.type_name = symbol.name symbol.type_pkg = symbol.pkg symbol.pkg = struct_symbol.name symbol.name = identifier.name + + // Get the inline comment from the field definition if it exists + if value, ok := struct_symbol.value.(SymbolStructValue); ok { + index := -1 + for n, i in value.names { + if n == symbol.name { + index = i + break + } + } + if index != -1 && value.comments[index] != nil && len(value.comments[index].list) > 0 { + symbol.comment = value.comments[index].list[0].text + } + } + symbol.signature = get_signature(&ast_context, symbol) hover.contents = write_hover_content(&ast_context, symbol) return hover, true, true diff --git a/src/server/symbol.odin b/src/server/symbol.odin index 49ada35..7bd6cd2 100644 --- a/src/server/symbol.odin +++ b/src/server/symbol.odin @@ -5,6 +5,7 @@ import "core:hash" 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" @@ -18,13 +19,22 @@ SymbolAndNode :: struct { node: ^ast.Node, } +UsingInfo :: struct { + from_index: int, + is_using: bool, +} + SymbolStructValue :: struct { - names: []string, - ranges: []common.Range, - types: []^ast.Expr, - usings: map[int]bool, - poly: ^ast.Field_List, - args: []^ast.Expr, //The arguments in the call expression for poly + names: []string, + ranges: []common.Range, + types: []^ast.Expr, + usings: map[int]struct{}, + from_usings: []int, + unexpanded_usings: []int, + poly: ^ast.Field_List, + args: []^ast.Expr, //The arguments in the call expression for poly + docs: []^ast.Comment_Group, + comments: []^ast.Comment_Group, } SymbolBitFieldValue :: struct { @@ -160,6 +170,7 @@ Symbol :: struct { pkg: string, //absolute directory path where the symbol resides name: string, //name of the symbol doc: string, + comment: string, signature: string, //type signature type: SymbolType, type_pkg: string, @@ -185,6 +196,261 @@ SymbolType :: enum { Unresolved = 1, //Use text if not being able to resolve it. } +SymbolStructValueBuilder :: struct { + symbol: Symbol, + names: [dynamic]string, + types: [dynamic]^ast.Expr, + args: [dynamic]^ast.Expr, //The arguments in the call expression for poly + ranges: [dynamic]common.Range, + docs: [dynamic]^ast.Comment_Group, + comments: [dynamic]^ast.Comment_Group, + usings: map[int]struct{}, + from_usings: [dynamic]int, + unexpanded_usings: [dynamic]int, + poly: ^ast.Field_List, +} + +symbol_struct_value_builder_make_none :: proc(allocator := context.allocator) -> SymbolStructValueBuilder { + return SymbolStructValueBuilder { + names = make([dynamic]string, allocator), + types = make([dynamic]^ast.Expr, allocator), + args = make([dynamic]^ast.Expr, allocator), + ranges = make([dynamic]common.Range, allocator), + docs = make([dynamic]^ast.Comment_Group, allocator), + comments = make([dynamic]^ast.Comment_Group, allocator), + usings = make(map[int]struct{}, allocator), + from_usings = make([dynamic]int, allocator), + unexpanded_usings = make([dynamic]int, allocator), + } +} + +symbol_struct_value_builder_make_symbol :: proc( + symbol: Symbol, + allocator := context.allocator, +) -> SymbolStructValueBuilder { + return SymbolStructValueBuilder { + symbol = symbol, + names = make([dynamic]string, allocator), + types = make([dynamic]^ast.Expr, allocator), + args = make([dynamic]^ast.Expr, allocator), + ranges = make([dynamic]common.Range, allocator), + docs = make([dynamic]^ast.Comment_Group, allocator), + comments = make([dynamic]^ast.Comment_Group, allocator), + usings = make(map[int]struct{}, allocator), + from_usings = make([dynamic]int, allocator), + unexpanded_usings = make([dynamic]int, allocator), + } +} + +symbol_struct_value_builder_make_symbol_symbol_struct_value :: proc( + symbol: Symbol, + v: SymbolStructValue, + allocator := context.allocator, +) -> SymbolStructValueBuilder { + return SymbolStructValueBuilder { + symbol = symbol, + names = slice.to_dynamic(v.names, allocator), + types = slice.to_dynamic(v.types, allocator), + args = slice.to_dynamic(v.args, allocator), + ranges = slice.to_dynamic(v.ranges, allocator), + docs = slice.to_dynamic(v.docs, allocator), + comments = slice.to_dynamic(v.comments, allocator), + from_usings = slice.to_dynamic(v.from_usings, allocator), + unexpanded_usings = slice.to_dynamic(v.unexpanded_usings, allocator), + } +} + +symbol_struct_value_builder_make :: proc { + symbol_struct_value_builder_make_none, + symbol_struct_value_builder_make_symbol, + symbol_struct_value_builder_make_symbol_symbol_struct_value, +} + +to_symbol :: proc(b: SymbolStructValueBuilder) -> Symbol { + symbol := b.symbol + symbol.value = to_symbol_struct_value(b) + return symbol +} + +to_symbol_struct_value :: proc(b: SymbolStructValueBuilder) -> SymbolStructValue { + return SymbolStructValue { + names = b.names[:], + types = b.types[:], + ranges = b.ranges[:], + args = b.args[:], + docs = b.docs[:], + comments = b.comments[:], + usings = b.usings, + from_usings = b.from_usings[:], + unexpanded_usings = b.unexpanded_usings[:], + poly = b.poly, + } +} + +write_struct_type :: proc( + ast_context: ^AstContext, + b: ^SymbolStructValueBuilder, + v: ast.Struct_Type, + ident: ast.Ident, + attributes: []^ast.Attribute, + base_using_index: int, + inlined := false, +) { + b.poly = v.poly_params + v := v + construct_struct_field_docs(ast_context.file, &v) + for field, i in v.fields.list { + for n in field.names { + if identifier, ok := n.derived.(^ast.Ident); ok && field.type != nil { + if .Using in field.flags { + append(&b.unexpanded_usings, len(b.types)) + } + + append(&b.names, identifier.name) + if v.poly_params != nil { + append(&b.types, clone_type(field.type, ast_context.allocator, nil)) + } else { + append(&b.types, field.type) + } + + append(&b.ranges, common.get_token_range(n, ast_context.file.src)) + append(&b.docs, field.docs) + append(&b.comments, field.comment) + append(&b.from_usings, base_using_index) + } + } + } + + if _, ok := get_attribute_objc_class_name(attributes); ok { + b.symbol.flags |= {.ObjC} + if get_attribute_objc_is_class_method(attributes) { + b.symbol.flags |= {.ObjCIsClassMethod} + } + } + + if v.poly_params != nil { + resolve_poly_struct(ast_context, b, v.poly_params) + } + + expand_usings(ast_context, b) + expand_objc(ast_context, b) +} + +write_symbol_struct_value :: proc( + ast_context: ^AstContext, b: ^SymbolStructValueBuilder, v: SymbolStructValue, base_using_index: int +) { + base_index := len(b.names) + for name in v.names { + append(&b.names, name) + } + for type in v.types { + append(&b.types, type) + } + for arg in v.args { + append(&b.args, arg) + } + for range in v.ranges { + append(&b.ranges, range) + } + for doc in v.docs { + append(&b.docs, doc) + } + for comment in v.comments { + append(&b.comments, comment) + } + for u in v.from_usings { + if u == -1 { + append(&b.from_usings, base_using_index) + } else { + append(&b.from_usings, u + base_index) + } + } + for u in v.unexpanded_usings { + append(&b.unexpanded_usings, u+base_index) + } + for k in v.usings { + b.usings[k+base_index] = struct{}{} + } + expand_usings(ast_context, b) +} + +expand_usings :: proc(ast_context: ^AstContext, b: ^SymbolStructValueBuilder) { + base := len(b.names) - 1 + for len(b.unexpanded_usings) > 0 { + u := pop_front(&b.unexpanded_usings) + + field_expr := b.types[u] + pkg := get_package_from_node(field_expr.expr_base) + set_ast_package_set_scoped(ast_context, pkg) + + + if field_expr == nil { + continue + } + + b.usings[u] = struct{}{} + + if ident, ok := field_expr.derived.(^ast.Ident); ok { + if v, ok := struct_type_from_identifier(ast_context, ident^); ok { + write_struct_type(ast_context, b, v^, ident^, {}, u, true) + } else { + clear(&ast_context.recursion_map) + if symbol, ok := resolve_type_identifier(ast_context, ident^); ok { + if v, ok := symbol.value.(SymbolStructValue); ok { + write_symbol_struct_value(ast_context, b, v, u) + } + } + } + } else if selector, ok := field_expr.derived.(^ast.Selector_Expr); ok { + if s, ok := resolve_selector_expression(ast_context, selector); ok { + if v, ok := s.value.(SymbolStructValue); ok { + write_symbol_struct_value(ast_context, b, v, u) + } + } + } + delete_key(&ast_context.recursion_map, b.types[u]) + } +} + +expand_objc :: proc(ast_context: ^AstContext, b: ^SymbolStructValueBuilder) { + symbol := b.symbol + if .ObjC in symbol.flags { + pkg := indexer.index.collection.packages[symbol.pkg] + + if obj_struct, ok := pkg.objc_structs[symbol.name]; ok { + _objc_function: for function, i in obj_struct.functions { + base := new_type(ast.Ident, {}, {}, context.temp_allocator) + base.name = obj_struct.pkg + + field := new_type(ast.Ident, {}, {}, context.temp_allocator) + field.name = function.physical_name + + selector := new_type(ast.Selector_Expr, {}, {}, context.temp_allocator) + + selector.field = field + selector.expr = base + + //Check if the base functions need to be overridden. Potentially look at some faster approach than a linear loop. + for name, j in b.names { + if name == function.logical_name { + b.names[j] = function.logical_name + b.types[j] = selector + b.ranges[j] = obj_struct.ranges[i] + continue _objc_function + } + } + + append(&b.names, function.logical_name) + append(&b.types, selector) + append(&b.ranges, obj_struct.ranges[i]) + append(&b.docs, nil) + append(&b.comments, nil) + append(&b.from_usings, -1) + } + } + } +} + new_clone_symbol :: proc(data: Symbol, allocator := context.allocator) -> ^Symbol { new_symbol := new(Symbol, allocator) new_symbol^ = data |