diff options
| author | Daniel Gavin <danielgavin5@hotmail.com> | 2022-04-09 22:19:08 +0200 |
|---|---|---|
| committer | Daniel Gavin <danielgavin5@hotmail.com> | 2022-04-09 22:19:08 +0200 |
| commit | 144d2b3e36d3dca77b8531dc0136ba084b530d54 (patch) | |
| tree | a06cfd4d76baa4cd64f77379e7ee9920e93371ae /src/server | |
| parent | 4d0d079b4b79ce5730d8c2ee8694652a3f73049f (diff) | |
Merge packages
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/analysis.odin | 3397 | ||||
| -rw-r--r-- | src/server/build.odin | 215 | ||||
| -rw-r--r-- | src/server/caches.odin | 8 | ||||
| -rw-r--r-- | src/server/clone.odin | 253 | ||||
| -rw-r--r-- | src/server/collector.odin | 602 | ||||
| -rw-r--r-- | src/server/completion.odin | 89 | ||||
| -rw-r--r-- | src/server/definition.odin | 15 | ||||
| -rw-r--r-- | src/server/document_links.odin | 4 | ||||
| -rw-r--r-- | src/server/document_symbols.odin | 5 | ||||
| -rw-r--r-- | src/server/hover.odin | 16 | ||||
| -rw-r--r-- | src/server/indexer.odin | 109 | ||||
| -rw-r--r-- | src/server/inlay_hints.odin | 8 | ||||
| -rw-r--r-- | src/server/lens.odin | 5 | ||||
| -rw-r--r-- | src/server/memory_index.odin | 65 | ||||
| -rw-r--r-- | src/server/references.odin | 41 | ||||
| -rw-r--r-- | src/server/rename.odin | 3 | ||||
| -rw-r--r-- | src/server/requests.odin | 27 | ||||
| -rw-r--r-- | src/server/semantic_tokens.odin | 61 | ||||
| -rw-r--r-- | src/server/signature.odin | 24 | ||||
| -rw-r--r-- | src/server/symbol.odin | 197 |
20 files changed, 4986 insertions, 158 deletions
diff --git a/src/server/analysis.odin b/src/server/analysis.odin new file mode 100644 index 0000000..3ef9733 --- /dev/null +++ b/src/server/analysis.odin @@ -0,0 +1,3397 @@ +package server + +import "core:odin/parser" +import "core:odin/ast" +import "core:odin/tokenizer" +import "core:fmt" +import "core:log" +import "core:strings" +import path "core:path/slashpath" +import "core:mem" +import "core:strconv" +import "core:path/filepath" +import "core:sort" +import "core:slice" +import "core:unicode/utf8" +import "core:reflect" + +import "shared:common" + +DocumentPositionContextHint :: enum { + Completion, + SignatureHelp, + Definition, + Hover, +} + +DocumentPositionContext :: struct { + file: ast.File, + position: common.AbsolutePosition, + line: int, + function: ^ast.Proc_Lit, //used to help with type resolving in function scope + selector: ^ast.Expr, //used for completion + identifier: ^ast.Node, + tag: ^ast.Node, + field: ^ast.Expr, //used for completion + call: ^ast.Expr, //used for signature help + returns: ^ast.Return_Stmt, //used for completion + comp_lit: ^ast.Comp_Lit, //used for completion + parent_comp_lit: ^ast.Comp_Lit, //used for completion + field_value: ^ast.Field_Value, + implicit: bool, //used for completion + arrow: bool, + binary: ^ast.Binary_Expr, //used for completion + parent_binary: ^ast.Binary_Expr, //used for completion + assign: ^ast.Assign_Stmt, //used for completion + switch_stmt: ^ast.Switch_Stmt, //used for completion + switch_type_stmt: ^ast.Type_Switch_Stmt, //used for completion + case_clause: ^ast.Case_Clause, //used for completion + value_decl: ^ast.Value_Decl, //used for completion + abort_completion: bool, + hint: DocumentPositionContextHint, + global_lhs_stmt: bool, + import_stmt: ^ast.Import_Decl, + call_commas: []int, +} + +DocumentLocal :: struct { + expr: ^ast.Expr, + offset: int, + id: int, //Id that can used to connect the local to something, i.e. for stmt begin offset +} + +AstContext :: struct { + locals: map[int]map[string][dynamic]DocumentLocal, //locals all the way to the document position + globals: map[string]common.GlobalExpr, + variables: map[string]bool, + parameters: map[string]bool, + in_package: map[string]string, //sometimes you have to extract types from arrays/maps and you lose package information + usings: [dynamic]string, + file: ast.File, + allocator: mem.Allocator, + imports: []common.Package, //imports for the current document + current_package: string, + document_package: string, + use_globals: bool, + use_locals: bool, + local_id: int, + call: ^ast.Call_Expr, //used to determene the types for generics and the correct function for overloaded functions + position: common.AbsolutePosition, + value_decl: ^ast.Value_Decl, + field_name: string, + uri: string, + recursion_counter: int, //Sometimes the ast is so malformed that it causes infinite recursion. +} + +make_ast_context :: proc(file: ast.File, imports: []common.Package, package_name: string, uri: string, allocator := context.temp_allocator) -> AstContext { + ast_context := AstContext { + locals = make(map[int]map[string][dynamic]DocumentLocal, 0, allocator), + globals = make(map[string]common.GlobalExpr, 0, allocator), + variables = make(map[string]bool, 0, allocator), + usings = make([dynamic]string, allocator), + parameters = make(map[string]bool, 0, allocator), + in_package = make(map[string]string, 0, allocator), + file = file, + imports = imports, + use_locals = true, + use_globals = true, + document_package = package_name, + current_package = package_name, + uri = uri, + allocator = allocator, + } + + add_local_group(&ast_context, 0) + + when ODIN_OS == .Windows { + ast_context.uri = strings.to_lower(ast_context.uri, allocator) + } + + return ast_context +} + +tokenizer_error_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) { +} + +/* + Walk through the type expression while both the call expression and specialization type are the same +*/ + +resolve_poly_spec :: proc { + resolve_poly_spec_node, + resolve_poly_spec_array, + resolve_poly_spec_dynamic_array, +} + +resolve_poly_spec_array :: proc(ast_context: ^AstContext, call_array: $A/[]^$T, spec_array: $D/[]^$K, poly_map: ^map[string]^ast.Expr) { + if len(call_array) != len(spec_array) { + return + } + + for elem, i in call_array { + resolve_poly_spec(ast_context, elem, spec_array[i], poly_map) + } +} + +resolve_poly_spec_dynamic_array :: proc(ast_context: ^AstContext, call_array: $A/[dynamic]^$T, spec_array: $D/[dynamic]^$K, poly_map: ^map[string]^ast.Expr) { + if len(call_array) != len(spec_array) { + return + } + + for elem, i in call_array { + resolve_poly_spec(ast_context, elem, spec_array[i], poly_map) + } +} + +get_poly_node_to_expr :: proc(node: ^ast.Node) -> ^ast.Expr { + using ast + + #partial switch v in node.derived { + case ^Ident: + return cast(^Expr)node + case: + log.warnf("Unhandled poly to node kind %v", v) + } + + return nil +} + +resolve_poly_spec_node :: proc(ast_context: ^AstContext, call_node: ^ast.Node, spec_node: ^ast.Node, poly_map: ^map[string]^ast.Expr) { + using ast + + if call_node == nil || spec_node == nil { + return + } + + #partial switch m in spec_node.derived { + case ^Bad_Expr: + case ^Ident: + case ^Implicit: + case ^Undef: + case ^Basic_Lit: + case ^Poly_Type: + if expr := get_poly_node_to_expr(call_node); expr != nil { + poly_map[m.type.name] = expr + } + case ^Ellipsis: + if n, ok := call_node.derived.(^Ellipsis); ok { + resolve_poly_spec(ast_context, n.expr, m.expr, poly_map) + } + case ^Tag_Expr: + if n, ok := call_node.derived.(^Tag_Expr); ok { + resolve_poly_spec(ast_context, n.expr, m.expr, poly_map) + } + case ^Unary_Expr: + if n, ok := call_node.derived.(^Unary_Expr); ok { + resolve_poly_spec(ast_context, n.expr, m.expr, poly_map) + } + case ^Binary_Expr: + if n, ok := call_node.derived.(^Binary_Expr); ok { + resolve_poly_spec(ast_context, n.left, m.left, poly_map) + resolve_poly_spec(ast_context, n.right, m.right, poly_map) + } + case ^Paren_Expr: + if n, ok := call_node.derived.(^Paren_Expr); ok { + resolve_poly_spec(ast_context, n.expr, m.expr, poly_map) + } + case ^Selector_Expr: + if n, ok := call_node.derived.(^Selector_Expr); ok { + resolve_poly_spec(ast_context, n.expr, m.expr, poly_map) + resolve_poly_spec(ast_context, n.field, m.field, poly_map) + } + case ^Slice_Expr: + if n, ok := call_node.derived.(^Slice_Expr); ok { + resolve_poly_spec(ast_context, n.expr, m.expr, poly_map) + resolve_poly_spec(ast_context, n.low, m.low, poly_map) + resolve_poly_spec(ast_context, n.high, m.high, poly_map) + } + case ^Distinct_Type: + if n, ok := call_node.derived.(^Distinct_Type); ok { + resolve_poly_spec(ast_context, n.type, m.type, poly_map) + } + case ^Proc_Type: + if n, ok := call_node.derived.(^Proc_Type); ok { + resolve_poly_spec(ast_context, n.params, m.params, poly_map) + resolve_poly_spec(ast_context, n.results, m.results, poly_map) + } + case ^Pointer_Type: + if n, ok := call_node.derived.(^Pointer_Type); ok { + resolve_poly_spec(ast_context, n.elem, m.elem, poly_map) + } + case ^Array_Type: + if n, ok := call_node.derived.(^Array_Type); ok { + resolve_poly_spec(ast_context, n.len, m.len, poly_map) + resolve_poly_spec(ast_context, n.elem, m.elem, poly_map) + } + case ^Dynamic_Array_Type: + if n, ok := call_node.derived.(^Dynamic_Array_Type); ok { + resolve_poly_spec(ast_context, n.elem, m.elem, poly_map) + } + case ^Struct_Type: + if n, ok := call_node.derived.(^Struct_Type); ok { + resolve_poly_spec(ast_context, n.poly_params, m.poly_params, poly_map) + resolve_poly_spec(ast_context, n.align, m.align, poly_map) + resolve_poly_spec(ast_context, n.fields, m.fields, poly_map) + } + case ^Field: + if n, ok := call_node.derived.(^Field); ok { + resolve_poly_spec(ast_context, n.names, m.names, poly_map) + resolve_poly_spec(ast_context, n.type, m.type, poly_map) + resolve_poly_spec(ast_context, n.default_value, m.default_value, poly_map) + } + case ^Field_List: + if n, ok := call_node.derived.(^Field_List); ok { + resolve_poly_spec(ast_context, n.list, m.list, poly_map) + } + case ^Field_Value: + if n, ok := call_node.derived.(^Field_Value); ok { + resolve_poly_spec(ast_context, n.field, m.field, poly_map) + resolve_poly_spec(ast_context, n.value, m.value, poly_map) + } + case ^Union_Type: + if n, ok := call_node.derived.(^Union_Type); ok { + resolve_poly_spec(ast_context, n.poly_params, m.poly_params, poly_map) + resolve_poly_spec(ast_context, n.align, m.align, poly_map) + resolve_poly_spec(ast_context, n.variants, m.variants, poly_map) + } + case ^Enum_Type: + if n, ok := call_node.derived.(^Enum_Type); ok { + resolve_poly_spec(ast_context, n.base_type, m.base_type, poly_map) + resolve_poly_spec(ast_context, n.fields, m.fields, poly_map) + } + case ^Bit_Set_Type: + if n, ok := call_node.derived.(^Bit_Set_Type); ok { + resolve_poly_spec(ast_context, n.elem, m.elem, poly_map) + resolve_poly_spec(ast_context, n.underlying, m.underlying, poly_map) + } + case ^Map_Type: + if n, ok := call_node.derived.(^Map_Type); ok { + resolve_poly_spec(ast_context, n.key, m.key, poly_map) + resolve_poly_spec(ast_context, n.value, m.value, poly_map) + } + case ^Call_Expr: + if n, ok := call_node.derived.(^Call_Expr); ok { + resolve_poly_spec(ast_context, n.expr, m.expr, poly_map) + resolve_poly_spec(ast_context, n.args, m.args, poly_map) + } + case ^Typeid_Type: + if n, ok := call_node.derived.(^Typeid_Type); ok { + resolve_poly_spec(ast_context, n.specialization, m.specialization, poly_map) + } + case: + log.error("Unhandled poly node kind: %T", m) + } +} + +resolve_type_comp_literal :: proc(ast_context: ^AstContext, position_context: ^DocumentPositionContext, current_symbol: Symbol, current_comp_lit: ^ast.Comp_Lit) -> (Symbol, ^ast.Comp_Lit, bool) { + if position_context.comp_lit == current_comp_lit { + return current_symbol, current_comp_lit, true + } else if current_comp_lit == nil { + return {}, nil, false + } + + element_index := 0 + + prev_package := ast_context.current_package + ast_context.current_package = current_symbol.pkg + + defer ast_context.current_package = prev_package + + for elem, i in current_comp_lit.elems { + if position_in_node(elem, position_context.position) { + element_index = i + } + } + + for elem in current_comp_lit.elems { + if !position_in_node(elem, position_context.position) { + continue + } + + if field_value, ok := elem.derived.(^ast.Field_Value); ok { //named + if comp_lit, ok := field_value.value.derived.(^ast.Comp_Lit); ok { + if s, ok := current_symbol.value.(SymbolStructValue); ok { + for name, i in s.names { + if name == field_value.field.derived.(^ast.Ident).name { + if symbol, ok := resolve_type_expression(ast_context, s.types[i]); ok { + //Stop at bitset, because we don't want to enter a comp_lit of a bitset + if _, ok := symbol.value.(SymbolBitSetValue); ok { + return current_symbol, current_comp_lit, true + } + return resolve_type_comp_literal(ast_context, position_context, symbol, cast(^ast.Comp_Lit)field_value.value) + } + } + } + } + } + } else { //indexed + if s, ok := current_symbol.value.(SymbolStructValue); ok { + + if len(s.types) <= element_index { + return {}, {}, false + } + + if symbol, ok := resolve_type_expression(ast_context, s.types[element_index]); ok { + //Stop at bitset, because we don't want to enter a comp_lit of a bitset + if _, ok := symbol.value.(SymbolBitSetValue); ok { + return current_symbol, current_comp_lit, true + } + return resolve_type_comp_literal(ast_context, position_context, symbol, cast(^ast.Comp_Lit)field_value.value) + } + } + } + } + + return current_symbol, current_comp_lit, true +} + +resolve_generic_function :: proc { + resolve_generic_function_ast, + resolve_generic_function_symbol, +} + +resolve_generic_function_symbol :: proc(ast_context: ^AstContext, params: []^ast.Field, results: []^ast.Field) -> (Symbol, bool) { + if params == nil { + return {}, false + } + + if results == nil { + return {}, false + } + + if ast_context.call == nil { + return {}, false + } + + call_expr := ast_context.call + poly_map := make(map[string]^ast.Expr, 0, context.temp_allocator) + i := 0 + count_required_params := 0 + + for param in params { + if param.default_value == nil { + count_required_params += 1 + } + + for name in param.names { + if len(call_expr.args) <= i { + break + } + + if poly, ok := name.derived.(^ast.Poly_Type); ok { + poly_map[poly.type.name] = call_expr.args[i] + } + + if param.type == nil { + continue + } + + if type_id, ok := param.type.derived.(^ast.Typeid_Type); ok { + if type_id.specialization != nil && !common.node_equal(call_expr.args[i], type_id.specialization) { + return {}, false + } + } + + resolve_poly_spec_node(ast_context, call_expr.args[i], param.type, &poly_map) + + i += 1 + } + } + + if count_required_params > len(call_expr.args) || count_required_params == 0 || len(call_expr.args) == 0 { + return {}, false + } + + function_name := "" + function_range: common.Range + + if ident, ok := call_expr.expr.derived.(^ast.Ident); ok { + function_name = ident.name + function_range = common.get_token_range(ident, ast_context.file.src) + } else if selector, ok := call_expr.expr.derived.(^ast.Selector_Expr); ok { + function_name = selector.field.name + function_range = common.get_token_range(selector, ast_context.file.src) + } else { + return {}, false + } + + symbol := Symbol { + range = function_range, + type = .Function, + name = function_name, + } + + return_types := make([dynamic]^ast.Field, ast_context.allocator) + argument_types := make([dynamic]^ast.Field, ast_context.allocator) + + for result in results { + if result.type == nil { + continue + } + + ident, ok := common.unwrap_pointer(result.type) + + if ok { + if m, ok := poly_map[ident.name]; ok { + field := cast(^ast.Field)clone_node(result, ast_context.allocator, nil) + field.type = m + append(&return_types, field) + } else { + append(&return_types, result) + } + } else { + append(&return_types, result) + } + } + + for param in params { + if len(param.names) == 0 { + continue + } + + //check the name for poly + if poly_type, ok := param.names[0].derived.(^ast.Poly_Type); ok && param.type != nil { + if m, ok := poly_map[poly_type.type.name]; ok { + field := cast(^ast.Field)clone_node(param, ast_context.allocator, nil) + field.type = m + append(&argument_types, field) + } + } else { + append(&argument_types, param) + } + } + + symbol.value = SymbolProcedureValue { + return_types = return_types[:], + arg_types = argument_types[:], + } + + return symbol, true +} + +resolve_generic_function_ast :: proc(ast_context: ^AstContext, proc_lit: ast.Proc_Lit) -> (Symbol, bool) { + + using ast + + if proc_lit.type.params == nil { + return Symbol {}, false + } + + if proc_lit.type.results == nil { + return Symbol {}, false + } + + if ast_context.call == nil { + return Symbol {}, false + } + + return resolve_generic_function_symbol(ast_context, proc_lit.type.params.list, proc_lit.type.results.list) +} + +is_symbol_same_typed :: proc(ast_context: ^AstContext, a, b: Symbol, flags: ast.Field_Flags = {}) -> bool { + //relying on the fact that a is the call argument to avoid checking both sides for untyped. + if untyped, ok := a.value.(SymbolUntypedValue); ok { + if basic, ok := b.value.(SymbolBasicValue); ok { + switch untyped.type { + case .Integer: + switch basic.ident.name { + case "int", "uint", "u32", "i32", "u8", "i8", "u64", "u16", "i16": return true + case: return false + } + case .Bool: + switch basic.ident.name { + case "bool", "b32", "b64": return true + case: return false + } + case .String: + switch basic.ident.name { + case "string", "cstring": return true + case: return false + } + case .Float: + switch basic.ident.name { + case "f32", "f64": return true + case: return false + } + } + } + } + + a_id := reflect.union_variant_typeid(a.value) + b_id := reflect.union_variant_typeid(b.value) + + if a_id != b_id { + return false + } + + if a.pointers != b.pointers { + return false + } + + if .Distinct in a.flags != .Distinct in b.flags { + return false + } + + if .Distinct in a.flags == .Distinct in b.flags && + .Distinct in a.flags && + a.name == b.name && + a.pkg == b.pkg { + return true + } + + #partial switch b_value in b.value { + case SymbolBasicValue: + if .Auto_Cast in flags { + return true + } else if .Any_Int in flags { + //Temporary - make a function that finds the base type of basic values + //This code only works with non distinct ints + switch a.name { + case "int", "uint", "u32", "i32", "u8", "i8", "u64", "u16", "i16": return true + } + } + } + + #partial switch a_value in a.value { + case SymbolBasicValue: + return a.name == b.name && a.pkg == b.pkg + case SymbolStructValue, SymbolEnumValue, SymbolUnionValue, SymbolBitSetValue: + return a.name == b.name && a.pkg == b.pkg + case SymbolSliceValue: + b_value := b.value.(SymbolSliceValue) + + a_symbol: Symbol + b_symbol: Symbol + ok: bool + + a_symbol, ok = resolve_type_expression(ast_context, a_value.expr) + + if !ok { + return false + } + + b_symbol, ok = resolve_type_expression(ast_context, b_value.expr) + + if !ok { + return false + } + + return is_symbol_same_typed(ast_context, a_symbol, b_symbol) + case SymbolFixedArrayValue: + b_value := b.value.(SymbolFixedArrayValue) + + a_symbol: Symbol + b_symbol: Symbol + ok: bool + + a_symbol, ok = resolve_type_expression(ast_context, a_value.expr) + + if !ok { + return false + } + + b_symbol, ok = resolve_type_expression(ast_context, b_value.expr) + + if !ok { + return false + } + + return is_symbol_same_typed(ast_context, a_symbol, b_symbol) + case SymbolDynamicArrayValue: + b_value := b.value.(SymbolDynamicArrayValue) + + a_symbol: Symbol + b_symbol: Symbol + ok: bool + + a_symbol, ok = resolve_type_expression(ast_context, a_value.expr) + + if !ok { + return false + } + + b_symbol, ok = resolve_type_expression(ast_context, b_value.expr) + + if !ok { + return false + } + + return is_symbol_same_typed(ast_context, a_symbol, b_symbol) + case SymbolMapValue: + b_value := b.value.(SymbolMapValue) + + a_key_symbol: Symbol + b_key_symbol: Symbol + a_value_symbol: Symbol + b_value_symbol: Symbol + ok: bool + + a_key_symbol, ok = resolve_type_expression(ast_context, a_value.key) + + if !ok { + return false + } + + b_key_symbol, ok = resolve_type_expression(ast_context, b_value.key) + + if !ok { + return false + } + + a_value_symbol, ok = resolve_type_expression(ast_context, a_value.value) + + if !ok { + return false + } + + b_value_symbol, ok = resolve_type_expression(ast_context, b_value.value) + + if !ok { + return false + } + + return is_symbol_same_typed(ast_context, a_key_symbol, b_key_symbol) && is_symbol_same_typed(ast_context, a_value_symbol, b_value_symbol) + } + + return false +} + +get_field_list_name_index :: proc(name: string, field_list: []^ast.Field) -> (int, bool) { + for field, i in field_list { + for field_name in field.names { + if ident, ok := field_name.derived.(^ast.Ident); ok { + if name == ident.name { + return i, true + } + } + } + } + + return 0, false +} + +/* + Figure out which function the call expression is using out of the list from proc group +*/ +resolve_function_overload :: proc(ast_context: ^AstContext, group: ast.Proc_Group) -> (Symbol, bool) { + using ast + + call_expr := ast_context.call + + candidates := make([dynamic]Symbol, context.temp_allocator) + + for arg_expr in group.args { + + next_fn: if f, ok := resolve_type_expression(ast_context, arg_expr); ok { + + if call_expr == nil || len(call_expr.args) == 0 { + append(&candidates, f) + break next_fn + } + + if procedure, ok := f.value.(SymbolProcedureValue); ok { + + count_required_params := 0 + + for arg in procedure.arg_types { + if arg.default_value == nil { + count_required_params += 1 + } + } + + if len(procedure.arg_types) < len(call_expr.args) { + continue + } + + for arg, i in call_expr.args { + + ast_context.use_locals = true + + call_symbol: Symbol + arg_symbol: Symbol + ok: bool + i := i + + if _, ok = arg.derived.(^ast.Bad_Expr); ok { + continue + } + + //named parameter + if field, is_field := arg.derived.(^ast.Field_Value); is_field { + call_symbol, ok = resolve_type_expression(ast_context, field.value) + if !ok { + break next_fn + } + + if ident, is_ident := field.field.derived.(^ast.Ident); is_ident { + i, ok = get_field_list_name_index(field.field.derived.(^ast.Ident).name, procedure.arg_types) + } else { + break next_fn + } + } else { + call_symbol, ok = resolve_type_expression(ast_context, arg) + } + + if !ok { + break next_fn + } + + if p, ok := call_symbol.value.(SymbolProcedureValue); ok { + if len(p.return_types) != 1 { + break next_fn + } + if s, ok := resolve_type_expression(ast_context, p.return_types[0].type); ok { + call_symbol = s + } + } + + if procedure.arg_types[i].type != nil { + arg_symbol, ok = resolve_type_expression(ast_context, procedure.arg_types[i].type) + } else { + arg_symbol, ok = resolve_type_expression(ast_context, procedure.arg_types[i].default_value) + } + + if !ok { + break next_fn + } + + if !is_symbol_same_typed(ast_context, call_symbol, arg_symbol, procedure.arg_types[i].flags) { + break next_fn + } + } + + append(&candidates, f) + } + } + } + + if len(candidates) > 1 { + return Symbol { + type = candidates[0].type, + name = candidates[0].name, + pkg = candidates[0].pkg, + value = SymbolAggregateValue { + symbols = candidates[:], + }, + }, true + } else if len(candidates) == 1 { + return candidates[0], true + } + + return Symbol {}, false +} + +resolve_basic_lit :: proc(ast_context: ^AstContext, basic_lit: ast.Basic_Lit) -> (Symbol, bool) { + symbol := Symbol { + type = .Constant, + } + + value: SymbolUntypedValue + + if v, ok := strconv.parse_int(basic_lit.tok.text); ok { + value.type = .Integer + } else if v, ok := strconv.parse_bool(basic_lit.tok.text); ok { + value.type = .Bool + } else if v, ok := strconv.parse_f64(basic_lit.tok.text); ok { + value.type = .Float + } else { + value.type = .String + } + + symbol.pkg = ast_context.current_package + symbol.value = value + + return symbol, true +} + +resolve_basic_directive :: proc(ast_context: ^AstContext, directive: ast.Basic_Directive, a := #caller_location) -> (Symbol, bool) { + switch directive.name { + case "caller_location": + ident := new_type(ast.Ident, directive.pos, directive.end, ast_context.allocator) + ident.name = "Source_Code_Location" + ast_context.current_package = ast_context.document_package + return resolve_type_identifier(ast_context, ident^) + } + + return {}, false +} + + +resolve_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Expr) -> (Symbol, bool) { + if node == nil { + return {}, false + } + + saved_package := ast_context.current_package + + defer { + ast_context.current_package = saved_package + } + + if ast_context.recursion_counter > 15 { + log.error("Recursion passed 15 attempts - giving up") + return {}, false + } + + ast_context.recursion_counter += 1 + + defer { + ast_context.recursion_counter -= 1 + } + + using ast + + #partial switch v in node.derived { + case ^Union_Type: + return make_symbol_union_from_ast(ast_context, v^, ast_context.field_name, true), true + 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 + case ^Bit_Set_Type: + return make_symbol_bitset_from_ast(ast_context, v^, ast_context.field_name, true), true + case ^Array_Type: + return make_symbol_array_from_ast(ast_context, v^, ast_context.field_name), true + case ^Dynamic_Array_Type: + return make_symbol_dynamic_array_from_ast(ast_context, v^, ast_context.field_name), true + case ^Map_Type: + return make_symbol_map_from_ast(ast_context, v^, ast_context.field_name), true + case ^Proc_Type: + return make_symbol_procedure_from_ast(ast_context, node, v^, ast_context.field_name), true + case ^Basic_Directive: + return resolve_basic_directive(ast_context, v^) + case ^Binary_Expr: + return resolve_first_symbol_from_binary_expression(ast_context, v) + case ^Ident: + return resolve_type_identifier(ast_context, v^) + case ^Basic_Lit: + return resolve_basic_lit(ast_context, v^) + case ^Type_Cast: + return resolve_type_expression(ast_context, v.type) + case ^Auto_Cast: + return resolve_type_expression(ast_context, v.expr) + case ^Comp_Lit: + return resolve_type_expression(ast_context, v.type) + case ^Unary_Expr: + if v.op.kind == .And { + symbol, ok := resolve_type_expression(ast_context, v.expr) + symbol.pointers += 1 + return symbol, ok + } else { + return resolve_type_expression(ast_context, v.expr) + } + case ^Deref_Expr: + symbol, ok := resolve_type_expression(ast_context, v.expr) + symbol.pointers -= 1 + return symbol, ok + case ^Paren_Expr: + return resolve_type_expression(ast_context, v.expr) + case ^Slice_Expr: + return resolve_type_expression(ast_context, v.expr) + case ^Tag_Expr: + return resolve_type_expression(ast_context, v.expr) + case ^Helper_Type: + return resolve_type_expression(ast_context, v.type) + case ^Ellipsis: + return resolve_type_expression(ast_context, v.expr) + case ^Implicit: + ident := new_type(Ident, v.node.pos, v.node.end, context.temp_allocator) + ident.name = v.tok.text + return resolve_type_identifier(ast_context, ident^) + case ^Type_Assertion: + if unary, ok := v.type.derived.(^ast.Unary_Expr); ok { + if unary.op.kind == .Question { + if symbol, ok := resolve_type_expression(ast_context, v.expr); ok { + if union_value, ok := symbol.value.(SymbolUnionValue); ok { + if len(union_value.types) != 1 { + return {}, false + } + return resolve_type_expression(ast_context, union_value.types[0]) + } + } + } + } else { + return resolve_type_expression(ast_context, v.type) + } + case ^Proc_Lit: + if v.type.results != nil { + if len(v.type.results.list) == 1 { + return resolve_type_expression(ast_context, v.type.results.list[0].type) + } + } + case ^Pointer_Type: + symbol, ok := resolve_type_expression(ast_context, v.elem) + symbol.pointers += 1 + return symbol, ok + case ^Multi_Pointer_Type: + symbol, ok := resolve_type_expression(ast_context, v.elem) + symbol.pointers += 1 + return symbol, ok + case ^Index_Expr: + indexed, ok := resolve_type_expression(ast_context, v.expr) + + if !ok { + return {}, false + } + + symbol: Symbol + + #partial switch v2 in indexed.value { + case SymbolDynamicArrayValue: + symbol, ok = resolve_type_expression(ast_context, v2.expr) + case SymbolSliceValue: + symbol, ok = resolve_type_expression(ast_context, v2.expr) + case SymbolFixedArrayValue: + symbol, ok = resolve_type_expression(ast_context, v2.expr) + case SymbolMapValue: + symbol, ok = resolve_type_expression(ast_context, v2.value) + } + + symbol.type = indexed.type + + return symbol, ok + case ^Call_Expr: + ast_context.call = cast(^Call_Expr)node + return resolve_type_expression(ast_context, v.expr) + case ^Implicit_Selector_Expr: + return Symbol {}, false + case ^Selector_Call_Expr: + return resolve_type_expression(ast_context, v.expr) + case ^Selector_Expr: + if selector, ok := resolve_type_expression(ast_context, v.expr); ok { + ast_context.use_locals = false + + #partial switch s in selector.value { + case SymbolFixedArrayValue: + components_count := 0 + for c in v.field.name { + if c == 'x' || c == 'y' || c == 'z' || c == 'w' || + c == 'r' || c == 'g' || c == 'b' || c == 'a' { + components_count += 1 + } + } + + if components_count == 0 { + return {}, false + } + + if components_count == 1 { + if selector.pkg != "" { + ast_context.current_package = selector.pkg + } else { + ast_context.current_package = ast_context.document_package + } + symbol, ok := resolve_type_expression(ast_context, s.expr) + symbol.type = .Variable + return symbol, ok + } else { + value := SymbolFixedArrayValue { + expr = s.expr, + len = make_int_basic_value(ast_context, components_count), + } + selector.value = value + selector.type = .Variable + return selector, true + } + case SymbolProcedureValue: + if len(s.return_types) == 1 { + selector_expr := new_type(ast.Selector_Expr, s.return_types[0].node.pos, s.return_types[0].node.end, context.temp_allocator) + selector_expr.expr = s.return_types[0].type + selector_expr.field = v.field + return resolve_type_expression(ast_context, selector_expr) + } + case SymbolStructValue: + if selector.pkg != "" { + ast_context.current_package = selector.pkg + } else { + ast_context.current_package = ast_context.document_package + } + + for name, i in s.names { + if v.field != nil && name == v.field.name { + ast_context.field_name = v.field.name + symbol, ok := resolve_type_expression(ast_context, s.types[i]) + symbol.type = .Variable + return symbol, ok + } + } + case SymbolPackageValue: + ast_context.current_package = selector.pkg + + if v.field != nil { + return resolve_symbol_return(ast_context, lookup(v.field.name, selector.pkg)) + } else { + return Symbol {}, false + } + } + } else { + return Symbol {}, false + } + case: + log.warnf("default node kind, resolve_type_expression: %T", v) + if v == nil { + return {}, false + } + } + + return Symbol {}, false +} + +store_local :: proc(ast_context: ^AstContext, expr: ^ast.Expr, offset: int, name: string, id := 0) { + local_stack := &ast_context.locals[id][name] + + if local_stack == nil { + locals := &ast_context.locals[id] + locals[name] = make([dynamic]DocumentLocal, ast_context.allocator) + local_stack = &locals[name] + } + + append(local_stack, DocumentLocal {expr = expr, offset = offset, id = id}) +} + +add_local_group :: proc(ast_context: ^AstContext, id: int) { + ast_context.locals[id] = make(map[string][dynamic]DocumentLocal, 100, ast_context.allocator) +} + +clear_local_group :: proc(ast_context: ^AstContext, id: int) { + ast_context.locals[id] = {} +} + +get_local :: proc(ast_context: ^AstContext, offset: int, name: string) -> ^ast.Expr { + previous := 0 + + //is the local we are getting being declared? + if ast_context.value_decl != nil { + for value_decl_name in ast_context.value_decl.names { + if ident, ok := value_decl_name.derived.(^ast.Ident); ok { + if ident.name == name { + previous = 1 + break + } + } + } + } + + for _, locals in &ast_context.locals { + if local_stack, ok := locals[name]; ok { + for i := len(local_stack) - 1; i >= 0; i -= 1 { + if local_stack[i].offset <= offset { + if i - previous < 0 { + return nil + } else { + return local_stack[i - previous].expr + } + } + } + } + } + + return nil +} + +get_local_offset :: proc(ast_context: ^AstContext, offset: int, name: string) -> int { + for _, locals in &ast_context.locals { + if local_stack, ok := locals[name]; ok { + for i := len(local_stack) - 1; i >= 0; i -= 1 { + if local_stack[i].offset <= offset { + if i < 0 { + return -1 + } else { + return local_stack[i].offset + } + } + } + } + } + + return -1 +} + +resolve_type_identifier :: proc(ast_context: ^AstContext, node: ast.Ident) -> (Symbol, bool) { + using ast + + if ast_context.recursion_counter > 15 { + log.error("Recursion passed 15 attempts - giving up") + return {}, false + } + + saved_package := ast_context.current_package + + defer { + ast_context.current_package = saved_package + } + + ast_context.recursion_counter += 1 + + defer { + ast_context.recursion_counter -= 1 + } + + if pkg, ok := ast_context.in_package[node.name]; ok { + ast_context.current_package = pkg + } + + if _, ok := ast_context.parameters[node.name]; ok { + for imp in ast_context.imports { + if strings.compare(imp.base, node.name) == 0 { + symbol := Symbol { + type = .Package, + pkg = imp.name, + value = SymbolPackageValue {}, + } + + return symbol, true + } + } + } + + //note(Daniel, if global and local ends up being 100% same just make a function that takes the map) + if local := get_local(ast_context, node.pos.offset, node.name); local != nil && ast_context.use_locals { + is_distinct := false + + if dist, ok := local.derived.(^ast.Distinct_Type); ok { + if dist.type != nil { + local = dist.type + is_distinct = true + } + } + + return_symbol: Symbol + ok: bool + + #partial switch v in local.derived { + case ^Ident: + return_symbol, ok = resolve_type_identifier(ast_context, v^) + case ^Union_Type: + return_symbol, ok = make_symbol_union_from_ast(ast_context, v^, node.name), true + return_symbol.name = node.name + case ^Enum_Type: + return_symbol, ok = make_symbol_enum_from_ast(ast_context, v^, node.name), true + return_symbol.name = node.name + case ^Struct_Type: + return_symbol, ok = make_symbol_struct_from_ast(ast_context, v^, node.name), true + return_symbol.name = node.name + case ^Bit_Set_Type: + return_symbol, ok = make_symbol_bitset_from_ast(ast_context, v^, node.name), true + return_symbol.name = node.name + case ^Proc_Lit: + if !v.type.generic { + return_symbol, ok = make_symbol_procedure_from_ast(ast_context, local, v.type^, node.name), true + } else { + if return_symbol, ok = resolve_generic_function(ast_context, v^); !ok { + return_symbol, ok = make_symbol_procedure_from_ast(ast_context, local, v.type^, node.name), true + } + } + case ^Proc_Group: + return_symbol, ok = resolve_function_overload(ast_context, v^) + case ^Array_Type: + return_symbol, ok = make_symbol_array_from_ast(ast_context, v^, node.name), true + case ^Dynamic_Array_Type: + return_symbol, ok = make_symbol_dynamic_array_from_ast(ast_context, v^, node.name), true + case ^Map_Type: + return_symbol, ok = make_symbol_map_from_ast(ast_context, v^, node.name), true + case ^Basic_Lit: + return_symbol, ok = resolve_basic_lit(ast_context, v^) + return_symbol.name = node.name + return_symbol.type = ast_context.variables[node.name] ? .Variable : .Constant + case: + return_symbol, ok = resolve_type_expression(ast_context, local) + } + + if is_distinct { + return_symbol.name = node.name + return_symbol.flags |= {.Distinct} + } + + if is_variable, ok := ast_context.variables[node.name]; ok && is_variable { + //return_symbol.name = node.name + return_symbol.type = .Variable + } + + return return_symbol, ok + + } else if global, ok := ast_context.globals[node.name]; ast_context.use_globals && ok { + is_distinct := false + + if dist, ok := global.expr.derived.(^ast.Distinct_Type); ok { + if dist.type != nil { + global.expr = dist.type + is_distinct = true + } + } + + return_symbol: Symbol + ok: bool + + #partial switch v in global.expr.derived { + case ^Ident: + return_symbol, ok = resolve_type_identifier(ast_context, v^) + case ^Struct_Type: + return_symbol, ok = make_symbol_struct_from_ast(ast_context, v^, node.name), true + return_symbol.name = node.name + case ^Bit_Set_Type: + return_symbol, ok = make_symbol_bitset_from_ast(ast_context, v^, node.name), true + return_symbol.name = node.name + case ^Union_Type: + return_symbol, ok = make_symbol_union_from_ast(ast_context, v^, node.name), true + return_symbol.name = node.name + case ^Enum_Type: + return_symbol, ok = make_symbol_enum_from_ast(ast_context, v^, node.name), true + return_symbol.name = node.name + case ^Proc_Lit: + if !v.type.generic { + return_symbol, ok = make_symbol_procedure_from_ast(ast_context, global.expr, v.type^, node.name), true + } else { + if return_symbol, ok = resolve_generic_function(ast_context, v^); !ok { + return_symbol, ok = make_symbol_procedure_from_ast(ast_context, global.expr, v.type^, node.name), true + } + } + case ^Proc_Group: + return_symbol, ok = resolve_function_overload(ast_context, v^) + case ^Array_Type: + return_symbol, ok = make_symbol_array_from_ast(ast_context, v^, node.name), true + case ^Dynamic_Array_Type: + return_symbol, ok = make_symbol_dynamic_array_from_ast(ast_context, v^, node.name), true + case ^Map_Type: + return_symbol, ok = make_symbol_map_from_ast(ast_context, v^, node.name), true + case ^Basic_Lit: + return_symbol, ok = resolve_basic_lit(ast_context, v^) + return_symbol.name = node.name + return_symbol.type = global.mutable ? .Variable : .Constant + case: + return_symbol, ok = resolve_type_expression(ast_context, global.expr) + } + + if is_distinct { + return_symbol.name = node.name + return_symbol.flags |= {.Distinct} + } + + if is_variable, ok := ast_context.variables[node.name]; ok && is_variable { + return_symbol.type = .Variable + } + + return_symbol.doc = common.get_doc(global.docs, ast_context.allocator) + + return return_symbol, ok + } else if node.name == "context" { + for built in indexer.builtin_packages { + if symbol, ok := lookup("Context", built); ok { + symbol.type = .Variable + return symbol, ok + } + } + } else if v, ok := common.keyword_map[node.name]; ok { + //keywords + ident := new_type(Ident, node.pos, node.end, ast_context.allocator) + ident.name = node.name + + symbol: Symbol + + switch ident.name { + case "true", "false": + symbol = Symbol { + type = .Keyword, + signature = node.name, + pkg = ast_context.current_package, + value = SymbolUntypedValue { + type = .Bool, + }, + } + case: + symbol = Symbol { + type = .Keyword, + signature = node.name, + name = ident.name, + pkg = ast_context.current_package, + value = SymbolBasicValue { + ident = ident, + }, + } + } + + return symbol, true + } else { + //right now we replace the package ident with the absolute directory name, so it should have '/' which is not a valid ident character + if strings.contains(node.name, "/") { + symbol := Symbol { + type = .Package, + pkg = node.name, + value = SymbolPackageValue {}, + } + + return symbol, true + } else { + //part of the ast so we check the imports of the document + for imp in ast_context.imports { + if strings.compare(imp.base, node.name) == 0 { + symbol := Symbol { + type = .Package, + pkg = imp.name, + value = SymbolPackageValue {}, + } + + return symbol, true + } + } + } + + //last option is to check the index + if symbol, ok := lookup(node.name, ast_context.current_package); ok { + return resolve_symbol_return(ast_context, symbol) + } + + //If we are resolving a symbol that is in the document package, then we'll check the builtin packages. + if ast_context.current_package == ast_context.document_package { + if symbol, ok := lookup(node.name, "$builtin"); ok { + return resolve_symbol_return(ast_context, symbol) + } + for built in indexer.builtin_packages { + if symbol, ok := lookup(node.name, built); ok { + return resolve_symbol_return(ast_context, symbol) + } + } + } + + for u in ast_context.usings { + //TODO(Daniel, make into a map, not really required for performance but looks nicer) + for imp in ast_context.imports { + if strings.compare(imp.base, u) == 0 { + if symbol, ok := lookup(node.name, imp.name); ok { + return resolve_symbol_return(ast_context, symbol) + } + } + } + } + } + + return Symbol {}, false +} + +resolve_ident_is_package :: proc(ast_context: ^AstContext, node: ast.Ident) -> bool { + if strings.contains(node.name, "/") { + return true + } else { + for imp in ast_context.imports { + + if imp.base == node.name { + return true + } + } + } + + return 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) + + for k, v in value.usings { + ast_context.current_package = symbol.pkg + + field_expr: ^ast.Expr + + for name, i in value.names { + + if name == k && v { + field_expr = value.types[i] + } + } + + 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) + } + } + } + } + + return { + names = names[:], + types = types[:], + } +} + +resolve_symbol_return :: proc(ast_context: ^AstContext, symbol: Symbol, ok := true) -> (Symbol, bool) { + if !ok { + return symbol, ok + } + + symbol := symbol + + if symbol.type == .Unresolved { + resolve_unresolved_symbol(ast_context, &symbol) + } + + #partial switch v in &symbol.value { + case SymbolProcedureGroupValue: + if symbol, ok := resolve_function_overload(ast_context, v.group.derived.(^ast.Proc_Group)^); ok { + return symbol, true + } else { + return symbol, false + } + case SymbolProcedureValue: + if v.generic { + if resolved_symbol, ok := resolve_generic_function(ast_context, v.arg_types, v.return_types); ok { + return resolved_symbol, ok + } else { + return symbol, true + } + } else { + return symbol, true + } + case SymbolUnionValue: + if v.poly != nil { + //Todo(daniel): Maybe change the function to return a new symbol instead of referencing it. + //resolving the poly union means changing the type, so we do a copy of it. + types := make([dynamic]^ast.Expr, ast_context.allocator) + append_elems(&types, ..v.types) + v.types = types[:] + resolve_poly_union(ast_context, v.poly, &symbol) + } + return symbol, ok + case SymbolStructValue: + if v.poly != nil { + //Todo(daniel): Maybe change the function to return a new symbol instead of referencing it. + //resolving the struct union means changing the type, so we do a copy of it. + types := make([dynamic]^ast.Expr, ast_context.allocator) + append_elems(&types, ..v.types) + v.types = types[:] + resolve_poly_struct(ast_context, v.poly, &symbol) + } + + //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 { + expanded := symbol + expanded.value = expand_struct_usings(ast_context, symbol, v) + return expanded, true + } else { + return symbol, true + } + case SymbolGenericValue: + ret, ok := resolve_type_expression(ast_context, v.expr) + return ret, ok + } + + return symbol, true +} + +resolve_unresolved_symbol :: proc(ast_context: ^AstContext, symbol: ^Symbol) { + if symbol.type != .Unresolved { + return + } + + #partial switch v in symbol.value { + case SymbolStructValue: + symbol.type = .Struct + case SymbolPackageValue: + symbol.type = .Package + case SymbolProcedureValue, SymbolProcedureGroupValue: + symbol.type = .Function + case SymbolUnionValue: + symbol.type = .Enum + case SymbolEnumValue: + symbol.type = .Enum + case SymbolBitSetValue: + symbol.type = .Enum + case SymbolGenericValue: + ast_context.current_package = symbol.pkg + if ret, ok := resolve_type_expression(ast_context, v.expr); ok { + symbol.type = ret.type + symbol.signature = ret.signature + } + } +} + +resolve_location_identifier :: proc(ast_context: ^AstContext, node: ast.Ident) -> (Symbol, bool) { + symbol: Symbol + + if local := get_local(ast_context, node.pos.offset, node.name); local != nil { + symbol.range = common.get_token_range(get_local(ast_context, node.pos.offset, node.name), ast_context.file.src) + return symbol, true + } else if global, ok := ast_context.globals[node.name]; ok { + symbol.range = common.get_token_range(global.expr, ast_context.file.src) + return symbol, true + } + + if symbol, ok := lookup(node.name, ast_context.document_package); ok { + return symbol, ok + } + + usings := get_using_packages(ast_context) + + for pkg in usings { + if symbol, ok := lookup(node.name, pkg); ok { + return symbol, ok + } + } + + return {}, false +} + +resolve_first_symbol_from_binary_expression :: proc(ast_context: ^AstContext, binary: ^ast.Binary_Expr) -> (Symbol, bool) { + //Fairly simple function to find the earliest identifier symbol in binary expression. + + if binary.left != nil { + + if ident, ok := binary.left.derived.(^ast.Ident); ok { + if s, ok := resolve_type_identifier(ast_context, ident^); ok { + return s, ok + } + } else if _, ok := binary.left.derived.(^ast.Binary_Expr); ok { + if s, ok := resolve_first_symbol_from_binary_expression(ast_context, cast(^ast.Binary_Expr)binary.left); ok { + return s, ok + } + } + } + + if binary.right != nil { + if ident, ok := binary.right.derived.(^ast.Ident); ok { + if s, ok := resolve_type_identifier(ast_context, ident^); ok { + return s, ok + } + } else if _, ok := binary.right.derived.(^ast.Binary_Expr); ok { + if s, ok := resolve_first_symbol_from_binary_expression(ast_context, cast(^ast.Binary_Expr)binary.right); ok { + return s, ok + } + } + } + + return {}, false +} + +find_position_in_call_param :: proc(ast_context: ^AstContext, call: ast.Call_Expr) -> (int, bool) { + if call.args == nil { + return 0, false + } + + for arg, i in call.args { + if position_in_node(arg, ast_context.position) { + return i, true + } + } + + return len(call.args) - 1, true +} + +make_pointer_ast :: proc(ast_context: ^AstContext, elem: ^ast.Expr) -> ^ast.Pointer_Type { + pointer := new_type(ast.Pointer_Type, elem.pos, elem.end, ast_context.allocator) + pointer.elem = elem + return pointer +} + +make_bool_ast :: proc(ast_context: ^AstContext) -> ^ast.Ident { + ident := new_type(ast.Ident, {}, {}, ast_context.allocator) + ident.name = "bool" + return ident +} + +make_int_ast :: proc(ast_context: ^AstContext) -> ^ast.Ident { + ident := new_type(ast.Ident, {}, {}, ast_context.allocator) + ident.name = "int" + return ident +} + +make_int_basic_value :: proc(ast_context: ^AstContext, n: int) -> ^ast.Basic_Lit { + basic := new_type(ast.Basic_Lit, {}, {}, ast_context.allocator) + basic.tok.text = fmt.tprintf("%v", n) + return basic +} + +get_package_from_node :: proc(node: ast.Node) -> string { + slashed, _ := filepath.to_slash(node.pos.file, context.temp_allocator) + + when ODIN_OS == .Windows { + ret := strings.to_lower(path.dir(slashed, context.temp_allocator), context.temp_allocator) + } else { + ret := path.dir(slashed, context.temp_allocator) + } + + return ret +} + +get_using_packages :: proc(ast_context: ^AstContext) -> []string { + usings := make([]string, len(ast_context.usings), context.temp_allocator) + + if len(ast_context.usings) == 0 { + return usings + } + + //probably map instead + for u, i in ast_context.usings { + + for imp in ast_context.imports { + + if strings.compare(imp.base, u) == 0 { + usings[i] = imp.name + } + } + } + + return usings +} + +make_symbol_procedure_from_ast :: proc(ast_context: ^AstContext, n: ^ast.Node, v: ast.Proc_Type, name: string) -> Symbol { + symbol := Symbol { + range = common.get_token_range(n^, ast_context.file.src), + type = .Function, + pkg = get_package_from_node(n^), + name = name, + } + + return_types := make([dynamic]^ast.Field, ast_context.allocator) + arg_types := make([dynamic]^ast.Field, ast_context.allocator) + + if v.results != nil { + for ret in v.results.list { + append(&return_types, ret) + } + } + + if v.params != nil { + for param in v.params.list { + append(&arg_types, param) + } + } + + if expr, ok := ast_context.globals[name]; ok { + if expr.deprecated { + symbol.flags |= {.Distinct} + } + } + + symbol.value = SymbolProcedureValue { + return_types = return_types[:], + arg_types = arg_types[:], + } + + return symbol +} + +make_symbol_array_from_ast :: proc(ast_context: ^AstContext, v: ast.Array_Type, name: string) -> Symbol { + symbol := Symbol { + range = common.get_token_range(v.node, ast_context.file.src), + type = .Variable, + pkg = get_package_from_node(v.node), + name = name, + } + + if v.len != nil { + symbol.value = SymbolFixedArrayValue { + expr = v.elem, + len = v.len, + } + } else { + symbol.value = SymbolSliceValue { + expr = v.elem, + } + } + + return symbol +} + +make_symbol_dynamic_array_from_ast :: proc(ast_context: ^AstContext, v: ast.Dynamic_Array_Type, name: string) -> Symbol { + symbol := Symbol { + range = common.get_token_range(v.node, ast_context.file.src), + type = .Variable, + pkg = get_package_from_node(v.node), + name = name, + } + + symbol.value = SymbolDynamicArrayValue { + expr = v.elem, + } + + return symbol +} + +make_symbol_map_from_ast :: proc(ast_context: ^AstContext, v: ast.Map_Type, name: string) -> Symbol { + symbol := Symbol { + range = common.get_token_range(v.node, ast_context.file.src), + type = .Variable, + pkg = get_package_from_node(v.node), + name = name, + } + + symbol.value = SymbolMapValue { + key = v.key, + value = v.value, + } + + return symbol +} + +make_symbol_basic_type_from_ast :: proc(ast_context: ^AstContext, n: ^ast.Node, v: ^ast.Ident) -> Symbol { + symbol := Symbol { + range = common.get_token_range(n^, ast_context.file.src), + type = .Variable, + pkg = get_package_from_node(n^), + } + + symbol.value = SymbolBasicValue { + ident = v, + } + + return symbol +} + +make_symbol_union_from_ast :: proc(ast_context: ^AstContext, v: ast.Union_Type, ident: string, inlined := false) -> Symbol { + symbol := Symbol { + range = common.get_token_range(v, ast_context.file.src), + type = .Union, + pkg = get_package_from_node(v.node), + name = ident, + } + + if inlined { + symbol.flags |= {.Anonymous} + symbol.name = "union" + } + + symbol.value = SymbolUnionValue { + types = v.variants, + } + + if v.poly_params != nil { + resolve_poly_union(ast_context, v.poly_params, &symbol) + } + + return symbol +} + +make_symbol_enum_from_ast :: proc(ast_context: ^AstContext, v: ast.Enum_Type, ident: string, inlined := false) -> Symbol { + symbol := Symbol { + range = common.get_token_range(v, ast_context.file.src), + type = .Enum, + name = ident, + pkg = get_package_from_node(v.node), + } + + if inlined { + symbol.flags |= {.Anonymous} + symbol.name = "enum" + } + + + names := make([dynamic]string, ast_context.allocator) + + for n in v.fields { + if ident, ok := n.derived.(^ast.Ident); ok { + append(&names, ident.name) + } else if field, ok := n.derived.(^ast.Field_Value); ok { + if ident, ok := field.field.derived.(^ast.Ident); ok { + append(&names, ident.name) + } else if binary, ok := field.field.derived.(^ast.Binary_Expr); ok { + append(&names, binary.left.derived.(^ast.Ident).name) + } + } + } + + symbol.value = SymbolEnumValue { + names = names[:], + } + + return symbol +} + +make_symbol_bitset_from_ast :: proc(ast_context: ^AstContext, v: ast.Bit_Set_Type, ident: string, inlined := false) -> Symbol { + symbol := Symbol { + range = common.get_token_range(v, ast_context.file.src), + type = .Enum, + name = ident, + pkg = get_package_from_node(v.node), + } + + if inlined { + symbol.flags |= {.Anonymous} + symbol.name = "bitset" + } + + symbol.value = SymbolBitSetValue { + expr = v.elem, + } + + return symbol +} + +make_symbol_struct_from_ast :: proc(ast_context: ^AstContext, v: ast.Struct_Type, ident: string, inlined := false) -> Symbol { + symbol := Symbol { + range = common.get_token_range(v, ast_context.file.src), + type = .Struct, + pkg = get_package_from_node(v.node), + name = ident, + } + + if inlined { + symbol.flags |= {.Anonymous} + symbol.name = "struct" + } + + names := make([dynamic]string, ast_context.allocator) + types := make([dynamic]^ast.Expr, ast_context.allocator) + usings := make(map[string]bool, 0, ast_context.allocator) + + for field in v.fields.list { + for n in field.names { + if identifier, ok := n.derived.(^ast.Ident); ok { + append(&names, identifier.name) + append(&types, clone_type(field.type, ast_context.allocator, nil)) + + if .Using in field.flags { + usings[identifier.name] = true + } + } + } + } + + symbol.value = SymbolStructValue { + names = names[:], + types = types[:], + usings = usings, + } + + 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 { + symbol.value = expand_struct_usings(ast_context, symbol, symbol.value.(SymbolStructValue)) + } + + return symbol +} + +resolve_poly_union :: proc(ast_context: ^AstContext, poly_params: ^ast.Field_List, symbol: ^Symbol) { + if ast_context.call == nil { + return + } + + symbol_value := &symbol.value.(SymbolUnionValue) + + if symbol_value == nil { + return + } + + i := 0 + + poly_map := make(map[string]^ast.Expr, 0, context.temp_allocator) + + for param in poly_params.list { + for name in param.names { + if len(ast_context.call.args) <= i { + break + } + + if param.type == nil { + continue + } + + if poly, ok := param.type.derived.(^ast.Typeid_Type); ok { + if ident, ok := name.derived.(^ast.Ident); ok { + poly_map[ident.name] = ast_context.call.args[i] + } else if poly, ok := name.derived.(^ast.Poly_Type); ok { + if poly.type != nil { + poly_map[poly.type.name] = ast_context.call.args[i] + } + } + } + + i += 1 + } + } + + for type, i in symbol_value.types { + if ident, ok := type.derived.(^ast.Ident); ok { + if expr, ok := poly_map[ident.name]; ok { + symbol_value.types[i] = expr + } + } else if call_expr, ok := type.derived.(^ast.Call_Expr); ok { + if call_expr.args == nil { + continue + } + + for arg, i in call_expr.args { + if ident, ok := arg.derived.(^ast.Ident); ok { + if expr, ok := poly_map[ident.name]; ok { + symbol_value.types[i] = expr + } + } + } + } + } +} + +resolve_poly_struct :: proc(ast_context: ^AstContext, poly_params: ^ast.Field_List, symbol: ^Symbol) { + 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) + + for param in poly_params.list { + for name in param.names { + if len(ast_context.call.args) <= i { + break + } + + if param.type == nil { + continue + } + + if poly, ok := param.type.derived.(^ast.Typeid_Type); ok { + if ident, ok := name.derived.(^ast.Ident); ok { + poly_map[ident.name] = ast_context.call.args[i] + } else if poly, ok := name.derived.(^ast.Poly_Type); ok { + if poly.type != nil { + poly_map[poly.type.name] = ast_context.call.args[i] + } + } + } + + i += 1 + } + } + + for type, i in symbol_value.types { + if ident, ok := type.derived.(^ast.Ident); ok { + if expr, ok := poly_map[ident.name]; ok { + symbol_value.types[i] = expr + } + } else if call_expr, ok := type.derived.(^ast.Call_Expr); ok { + if call_expr.args == nil { + continue + } + + for arg, i in call_expr.args { + if ident, ok := arg.derived.(^ast.Ident); ok { + if expr, ok := poly_map[ident.name]; ok { + symbol_value.types[i] = expr + } + } + } + } + } +} + +get_globals :: proc(file: ast.File, ast_context: ^AstContext) { + ast_context.variables["context"] = true + + exprs := common.collect_globals(file) + + for expr in exprs { + ast_context.variables[expr.name] = expr.mutable + ast_context.globals[expr.name] = expr + } +} + +get_generic_assignment :: proc(file: ast.File, value: ^ast.Expr, ast_context: ^AstContext, results: ^[dynamic]^ast.Expr) { + using ast + + ast_context.use_locals = true + ast_context.use_globals = true + + #partial switch v in value.derived { + case ^Call_Expr: + ast_context.call = cast(^ast.Call_Expr)value + + if symbol, ok := resolve_type_expression(ast_context, v.expr); ok { + if procedure, ok := symbol.value.(SymbolProcedureValue); ok { + for ret in procedure.return_types { + append(results, ret.type) + } + } + } + case ^Comp_Lit: + if v.type != nil { + append(results, v.type) + } + case ^Array_Type: + if v.elem != nil { + append(results, v.elem) + } + case ^Dynamic_Array_Type: + if v.elem != nil { + append(results, v.elem) + } + case ^Selector_Expr: + if v.expr != nil { + append(results, value) + } + case ^Type_Assertion: + if v.type != nil { + //This is the unique .? that can only be used with maybe + if unary, ok := v.type.derived.(^ast.Unary_Expr); ok && unary.op.kind == .Question { + append(results, cast(^ast.Expr)&v.node) + } else { + append(results, v.type) + } + + b := make_bool_ast(ast_context) + b.pos.file = v.type.pos.file + append(results, b) + } + case: + //log.debugf("default node get_generic_assignment %v", v); + append(results, value) + } +} + +get_locals_value_decl :: proc(file: ast.File, value_decl: ast.Value_Decl, ast_context: ^AstContext) { + using ast + + if len(value_decl.names) <= 0 { + return + } + + if value_decl.type != nil { + for name, i in value_decl.names { + str := common.get_ast_node_string(value_decl.names[i], file.src) + ast_context.variables[str] = value_decl.is_mutable + store_local(ast_context, value_decl.type, value_decl.end.offset, str, ast_context.local_id) + } + return + } + + results := make([dynamic]^Expr, context.temp_allocator) + + for value in value_decl.values { + get_generic_assignment(file, value, ast_context, &results) + } + + if len(results) == 0 { + return + } + + for name, i in value_decl.names { + result_i := min(len(results)-1, i) + str := common.get_ast_node_string(name, file.src) + ast_context.in_package[str] = get_package_from_node(results[result_i]^) + store_local(ast_context, results[result_i], value_decl.end.offset, str, ast_context.local_id) + ast_context.variables[str] = value_decl.is_mutable + } +} + +get_locals_stmt :: proc(file: ast.File, stmt: ^ast.Stmt, ast_context: ^AstContext, document_position: ^DocumentPositionContext, save_assign := false) { + ast_context.use_locals = true + ast_context.use_globals = true + ast_context.current_package = ast_context.document_package + + using ast + + if stmt == nil { + return + } + + if stmt.pos.offset > document_position.position { + return + } + + #partial switch v in stmt.derived { + case ^Value_Decl: + get_locals_value_decl(file, v^, ast_context) + case ^Type_Switch_Stmt: + get_locals_type_switch_stmt(file, v^, ast_context, document_position) + case ^Switch_Stmt: + get_locals_switch_stmt(file, v^, ast_context, document_position) + case ^For_Stmt: + get_locals_for_stmt(file, v^, ast_context, document_position) + case ^Inline_Range_Stmt: + get_locals_stmt(file, v.body, ast_context, document_position) + case ^Range_Stmt: + get_locals_for_range_stmt(file, v^, ast_context, document_position) + case ^If_Stmt: + get_locals_if_stmt(file, v^, ast_context, document_position) + case ^Block_Stmt: + get_locals_block_stmt(file, v^, ast_context, document_position) + case ^Proc_Lit: + get_locals_stmt(file, v.body, ast_context, document_position) + case ^Assign_Stmt: + if save_assign { + get_locals_assign_stmt(file, v^, ast_context) + } + case ^Using_Stmt: + get_locals_using_stmt(v^, ast_context) + case ^When_Stmt: + get_locals_stmt(file, v.else_stmt, ast_context, document_position) + get_locals_stmt(file, v.body, ast_context, document_position) + case ^Case_Clause: + for stmt in v.body { + get_locals_stmt(file, stmt, ast_context, document_position) + } + case: + //log.debugf("default node local stmt %v", v); + } +} + +get_locals_block_stmt :: proc(file: ast.File, block: ast.Block_Stmt, ast_context: ^AstContext, document_position: ^DocumentPositionContext) { + if !(block.pos.offset <= document_position.position && document_position.position <= block.end.offset) { + return + } + + for stmt in block.stmts { + get_locals_stmt(file, stmt, ast_context, document_position) + } +} + +get_locals_using_stmt :: proc(stmt: ast.Using_Stmt, ast_context: ^AstContext) { + for u in stmt.list { + if symbol, ok := resolve_type_expression(ast_context, u); ok { + #partial switch v in symbol.value { + case SymbolPackageValue: + if ident, ok := u.derived.(^ast.Ident); ok { + append(&ast_context.usings, ident.name) + } + case SymbolStructValue: + for name, i in v.names { + selector := new_type(ast.Selector_Expr, v.types[i].pos, v.types[i].end, context.temp_allocator) + selector.expr = u + selector.field = new_type(ast.Ident, v.types[i].pos, v.types[i].end, context.temp_allocator) + selector.field.name = name + store_local(ast_context, selector, 0, name, ast_context.local_id) + ast_context.variables[name] = true + } + } + } + } +} + +get_locals_assign_stmt :: proc(file: ast.File, stmt: ast.Assign_Stmt, ast_context: ^AstContext) { + using ast + + if stmt.lhs == nil || stmt.rhs == nil { + return + } + + results := make([dynamic]^Expr, context.temp_allocator) + + for rhs in stmt.rhs { + get_generic_assignment(file, rhs, ast_context, &results) + } + + if len(stmt.lhs) != len(results) { + return + } + + for lhs, i in stmt.lhs { + if ident, ok := lhs.derived.(^ast.Ident); ok { + store_local(ast_context, results[i], ident.pos.offset, ident.name, ast_context.local_id) + ast_context.variables[ident.name] = true + } + } +} + +get_locals_if_stmt :: proc(file: ast.File, stmt: ast.If_Stmt, ast_context: ^AstContext, document_position: ^DocumentPositionContext) { + if !(stmt.pos.offset <= document_position.position && document_position.position <= stmt.end.offset) { + return + } + + get_locals_stmt(file, stmt.init, ast_context, document_position, true) + get_locals_stmt(file, stmt.body, ast_context, document_position) + get_locals_stmt(file, stmt.else_stmt, ast_context, document_position) +} + +get_locals_for_range_stmt :: proc(file: ast.File, stmt: ast.Range_Stmt, ast_context: ^AstContext, document_position: ^DocumentPositionContext) { + using ast + + if !(stmt.body.pos.offset <= document_position.position && document_position.position <= stmt.body.end.offset) { + return + } + + results := make([dynamic]^Expr, context.temp_allocator) + + if stmt.expr == nil { + return + } + + if symbol, ok := resolve_type_expression(ast_context, stmt.expr); ok { + #partial switch v in symbol.value { + case SymbolMapValue: + if len(stmt.vals) >= 1 { + if ident, ok := stmt.vals[0].derived.(^Ident); ok { + store_local(ast_context, v.key, ident.pos.offset, ident.name, ast_context.local_id) + ast_context.variables[ident.name] = true + ast_context.in_package[ident.name] = symbol.pkg + } + } + if len(stmt.vals) >= 2 { + if ident, ok := stmt.vals[1].derived.(^Ident); ok { + store_local(ast_context, v.value, ident.pos.offset, ident.name, ast_context.local_id) + ast_context.variables[ident.name] = true + ast_context.in_package[ident.name] = symbol.pkg + } + } + case SymbolDynamicArrayValue: + if len(stmt.vals) >= 1 { + if ident, ok := stmt.vals[0].derived.(^Ident); ok { + store_local(ast_context, v.expr, ident.pos.offset, ident.name, ast_context.local_id) + ast_context.variables[ident.name] = true + ast_context.in_package[ident.name] = symbol.pkg + } + } + if len(stmt.vals) >= 2 { + if ident, ok := stmt.vals[1].derived.(^Ident); ok { + store_local(ast_context, make_int_ast(ast_context), ident.pos.offset, ident.name, ast_context.local_id) + ast_context.variables[ident.name] = true + ast_context.in_package[ident.name] = symbol.pkg + } + } + case SymbolFixedArrayValue: + if len(stmt.vals) >= 1 { + if ident, ok := stmt.vals[0].derived.(^Ident); ok { + store_local(ast_context, v.expr, ident.pos.offset, ident.name, ast_context.local_id) + ast_context.variables[ident.name] = true + ast_context.in_package[ident.name] = symbol.pkg + } + } + + if len(stmt.vals) >= 2 { + if ident, ok := stmt.vals[1].derived.(^Ident); ok { + store_local(ast_context, make_int_ast(ast_context), ident.pos.offset, ident.name, ast_context.local_id) + ast_context.variables[ident.name] = true + ast_context.in_package[ident.name] = symbol.pkg + } + } + case SymbolSliceValue: + if len(stmt.vals) >= 1 { + if ident, ok := stmt.vals[0].derived.(^Ident); ok { + store_local(ast_context, v.expr, ident.pos.offset, ident.name, ast_context.local_id) + ast_context.variables[ident.name] = true + ast_context.in_package[ident.name] = symbol.pkg + } + } + if len(stmt.vals) >= 2 { + if ident, ok := stmt.vals[1].derived.(^Ident); ok { + store_local(ast_context, make_int_ast(ast_context), ident.pos.offset, ident.name, ast_context.local_id) + ast_context.variables[ident.name] = true + ast_context.in_package[ident.name] = symbol.pkg + } + } + } + } + + get_locals_stmt(file, stmt.body, ast_context, document_position) +} + +get_locals_for_stmt :: proc(file: ast.File, stmt: ast.For_Stmt, ast_context: ^AstContext, document_position: ^DocumentPositionContext) { + if !(stmt.pos.offset <= document_position.position && document_position.position <= stmt.end.offset) { + return + } + + get_locals_stmt(file, stmt.init, ast_context, document_position, true) + get_locals_stmt(file, stmt.body, ast_context, document_position) +} + +get_locals_switch_stmt :: proc(file: ast.File, stmt: ast.Switch_Stmt, ast_context: ^AstContext, document_position: ^DocumentPositionContext) { + if !(stmt.pos.offset <= document_position.position && document_position.position <= stmt.end.offset) { + return + } + + get_locals_stmt(file, stmt.body, ast_context, document_position) +} + +get_locals_type_switch_stmt :: proc(file: ast.File, stmt: ast.Type_Switch_Stmt, ast_context: ^AstContext, document_position: ^DocumentPositionContext) { + using ast + + if !(stmt.pos.offset <= document_position.position && document_position.position <= stmt.end.offset) { + return + } + + if stmt.body == nil { + return + } + + if block, ok := stmt.body.derived.(^Block_Stmt); ok { + for block_stmt in block.stmts { + if cause, ok := block_stmt.derived.(^Case_Clause); ok && cause.pos.offset <= document_position.position && document_position.position <= cause.end.offset { + for b in cause.body { + get_locals_stmt(file, b, ast_context, document_position) + } + + tag := stmt.tag.derived.(^Assign_Stmt) + + if len(tag.lhs) == 1 && len(cause.list) == 1 { + ident := tag.lhs[0].derived.(^Ident) + store_local(ast_context, cause.list[0], ident.pos.offset, ident.name, ast_context.local_id) + ast_context.variables[ident.name] = true + } + } + } + } +} + +get_locals_proc_param_and_results :: proc(file: ast.File, function: ast.Proc_Lit, ast_context: ^AstContext, document_position: ^DocumentPositionContext) { + proc_lit, ok := function.derived.(^ast.Proc_Lit) + + if !ok || proc_lit.body == nil { + return + } + + if proc_lit.type != nil && proc_lit.type.params != nil { + for arg in proc_lit.type.params.list { + for name in arg.names { + if arg.type != nil { + str := common.get_ast_node_string(name, file.src) + store_local(ast_context, arg.type, name.pos.offset, str, ast_context.local_id) + ast_context.variables[str] = true + ast_context.parameters[str] = true + + if .Using in arg.flags { + using_stmt: ast.Using_Stmt + using_stmt.list = make([]^ast.Expr, 1, context.temp_allocator) + using_stmt.list[0] = arg.type + get_locals_using_stmt(using_stmt, ast_context) + } + } else { + str := common.get_ast_node_string(name, file.src) + store_local(ast_context, arg.default_value, name.pos.offset, str, ast_context.local_id) + ast_context.variables[str] = true + ast_context.parameters[str] = true + } + } + } + } + + if proc_lit.type != nil && proc_lit.type.results != nil { + for result in proc_lit.type.results.list { + for name in result.names { + if result.type != nil { + str := common.get_ast_node_string(name, file.src) + store_local(ast_context, result.type, name.pos.offset, str, ast_context.local_id) + ast_context.variables[str] = true + ast_context.parameters[str] = true + } + } + } + } +} + +get_locals :: proc(file: ast.File, function: ^ast.Node, ast_context: ^AstContext, document_position: ^DocumentPositionContext) { + proc_lit, ok := function.derived.(^ast.Proc_Lit) + + if !ok || proc_lit.body == nil { + return + } + + get_locals_proc_param_and_results(file, proc_lit^, ast_context, document_position) + + block: ^ast.Block_Stmt + block, ok = proc_lit.body.derived.(^ast.Block_Stmt) + + if !ok { + log.error("Proc_List body not block") + return + } + + for stmt in block.stmts { + get_locals_stmt(file, stmt, ast_context, document_position) + } +} + +clear_locals :: proc(ast_context: ^AstContext) { + clear(&ast_context.locals) + clear(&ast_context.parameters) + clear(&ast_context.variables) + clear(&ast_context.usings) +} + +resolve_entire_file :: proc(document: ^common.Document, allocator := context.allocator) -> map[uintptr]SymbolAndNode { + ast_context := make_ast_context(document.ast, document.imports, document.package_name, document.uri.uri, allocator) + + get_globals(document.ast, &ast_context) + + ast_context.current_package = ast_context.document_package + + symbols := make(map[uintptr]SymbolAndNode, 10000, allocator) + + for decl in document.ast.decls { + resolve_entire_decl(&ast_context, decl, &symbols, allocator) + clear_local_group(&ast_context, 0) + add_local_group(&ast_context, 0) + } + + return symbols +} + +resolve_entire_decl :: proc(ast_context: ^AstContext, decl: ^ast.Node, symbols: ^map[uintptr]SymbolAndNode, allocator := context.allocator) { + Scope :: struct { + offset: int, + id: int, + } + + Visit_Data :: struct { + ast_context: ^AstContext, + symbols: ^map[uintptr]SymbolAndNode, + scopes: [dynamic]Scope, + id_counter: int, + } + + data := Visit_Data { + ast_context = ast_context, + symbols = symbols, + scopes = make([dynamic]Scope, allocator), + } + + visit :: proc(visitor: ^ast.Visitor, node: ^ast.Node) -> ^ast.Visitor { + if node == nil || visitor == nil { + return nil + } + data := cast(^Visit_Data)visitor.data + ast_context := data.ast_context + ast_context.use_locals = true + ast_context.use_globals = true + + //It's somewhat silly to check the scope everytime, but the alternative is to implement my own walker function. + if len(data.scopes) > 0 { + current_scope := data.scopes[len(data.scopes)-1] + + if current_scope.offset < node.end.offset { + clear_local_group(ast_context, current_scope.id) + + pop(&data.scopes) + + if len(data.scopes) > 0 { + current_scope = data.scopes[len(data.scopes)-1] + ast_context.local_id = current_scope.id + } else { + ast_context.local_id = 0 + } + } + } + + #partial switch v in node.derived { + case ^ast.If_Stmt, ^ast.For_Stmt, ^ast.Range_Stmt, ^ast.Inline_Range_Stmt, ^ast.Proc_Lit: + scope: Scope + scope.id = data.id_counter + scope.offset = node.end.offset + data.id_counter += 1 + ast_context.local_id = scope.id + + append(&data.scopes, scope) + add_local_group(ast_context, scope.id) + + position_context: DocumentPositionContext + position_context.position = node.end.offset + get_locals_stmt(ast_context.file, cast(^ast.Stmt)node, ast_context, &position_context) + case ^ast.Ident: + if symbol, ok := resolve_type_identifier(ast_context, v^); ok { + data.symbols[cast(uintptr)node] = SymbolAndNode { + node = v, + symbol = symbol, + } + } + case ^ast.Selector_Expr: + if symbol, ok := resolve_type_expression(ast_context, &v.node); ok { + data.symbols[cast(uintptr)node] = SymbolAndNode { + node = v, + symbol = symbol, + } + } + case ^ast.Call_Expr: + if symbol, ok := resolve_type_expression(ast_context, &v.node); ok { + data.symbols[cast(uintptr)node] = SymbolAndNode { + node = v, + symbol = symbol, + } + } + } + + #partial switch v in node.derived { + case ^ast.Proc_Lit: + if v.body == nil { + break + } + + type_position_context: DocumentPositionContext + type_position_context.position = v.end.offset + get_locals_proc_param_and_results(ast_context.file, v^, ast_context, &type_position_context) + } + + return visitor + } + + visitor := ast.Visitor { + data = &data, + visit = visit, + } + + ast.walk(&visitor, decl) +} + +concatenate_symbol_information :: proc { + concatenate_raw_symbol_information, + concatenate_raw_string_information, +} + +concatenate_raw_symbol_information :: proc(ast_context: ^AstContext, symbol: Symbol, is_completion: bool) -> string { + return concatenate_raw_string_information(ast_context, symbol.pkg, symbol.name, symbol.signature, symbol.type, is_completion) +} + +concatenate_raw_string_information :: proc(ast_context: ^AstContext, pkg: string, name: string, signature: string, type: SymbolType, is_completion: bool) -> string { + pkg := path.base(pkg, false, context.temp_allocator) + + if type == .Package { + return fmt.tprintf("%v: package", name) + } else if type == .Keyword && is_completion { + return name + } else { + if signature != "" { + return fmt.tprintf("%v.%v: %v", pkg, name, signature) + } else { + return fmt.tprintf("%v.%v", pkg, name) + } + } +} + +unwrap_enum :: proc(ast_context: ^AstContext, node: ^ast.Expr) -> (SymbolEnumValue, bool) { + if node == nil { + return {}, false + } + + if enum_symbol, ok := resolve_type_expression(ast_context, node); ok { + + if enum_value, ok := enum_symbol.value.(SymbolEnumValue); ok { + return enum_value, true + } + } + + return {}, false +} + +unwrap_union :: proc(ast_context: ^AstContext, node: ^ast.Expr) -> (SymbolUnionValue, bool) { + if union_symbol, ok := resolve_type_expression(ast_context, node); ok { + if union_value, ok := union_symbol.value.(SymbolUnionValue); ok { + return union_value, true + } + } + + return {}, false +} + +unwrap_bitset :: proc(ast_context: ^AstContext, bitset_symbol: Symbol) -> (SymbolEnumValue, bool) { + if bitset_value, ok := bitset_symbol.value.(SymbolBitSetValue); ok { + if enum_symbol, ok := resolve_type_expression(ast_context, bitset_value.expr); ok { + if enum_value, ok := enum_symbol.value.(SymbolEnumValue); ok { + return enum_value, true + } + } + } + + return {}, false +} + +get_signature :: proc(ast_context: ^AstContext, ident: ast.Ident, symbol: Symbol, was_variable := false) -> string { + if symbol.type == .Function { + return symbol.signature + } + + if .Distinct in symbol.flags { + return symbol.name + } + + is_variable := symbol.type == .Variable + + #partial switch v in symbol.value { + case SymbolBasicValue: + return common.node_to_string(v.ident) + case SymbolBitSetValue: + return common.node_to_string(v.expr) + case SymbolEnumValue: + if is_variable { + return symbol.name + } + else { + return "enum" + } + case SymbolMapValue: + return strings.concatenate(a = {"map[", common.node_to_string(v.key), "]", common.node_to_string(v.value)}, allocator = ast_context.allocator) + case SymbolProcedureValue: + return "proc" + case SymbolStructValue: + if is_variable { + return symbol.name + } + else { + return "struct" + } + case SymbolUnionValue: + if is_variable { + return symbol.name + } + else { + return "union" + } + case SymbolDynamicArrayValue: + return strings.concatenate(a = {"[dynamic]", common.node_to_string(v.expr)}, allocator = ast_context.allocator) + case SymbolSliceValue: + return strings.concatenate(a = {"[]", common.node_to_string(v.expr)}, allocator = ast_context.allocator) + case SymbolFixedArrayValue: + return strings.concatenate(a = {"[", common.node_to_string(v.len), "]", common.node_to_string(v.expr)}, allocator = ast_context.allocator) + case SymbolPackageValue: + return "package" + case SymbolUntypedValue: + switch v.type { + case .Float: return "float" + case .String: return "string" + case .Bool: return "bool" + case .Integer: return "int" + } + } + + return "" +} + +position_in_proc_decl :: proc(position_context: ^DocumentPositionContext) -> bool { + if position_context.value_decl == nil { + return false + } + + if len(position_context.value_decl.values) != 1 { + return false + } + + if _, ok := position_context.value_decl.values[0].derived.(^ast.Proc_Type); ok { + return true + } + + if proc_lit, ok := position_context.value_decl.values[0].derived.(^ast.Proc_Lit); ok { + if proc_lit.type != nil && position_in_node(proc_lit.type, position_context.position) { + return true + } + } + + return false +} + + +is_lhs_comp_lit :: proc(position_context: ^DocumentPositionContext) -> bool { + if position_context.position <= position_context.comp_lit.open.offset { + return false + } + + if len(position_context.comp_lit.elems) == 0 { + return true + } + + for elem in position_context.comp_lit.elems { + if position_in_node(elem, position_context.position) { + if ident, ok := elem.derived.(^ast.Ident); ok { + return true + } else if field, ok := elem.derived.(^ast.Field_Value); ok { + + if position_in_node(field.value, position_context.position) { + return false + } + } + } + } + + return true +} + +field_exists_in_comp_lit :: proc(comp_lit: ^ast.Comp_Lit, name: string) -> bool { + for elem in comp_lit.elems { + if field, ok := elem.derived.(^ast.Field_Value); ok { + if field.field != nil { + if ident, ok := field.field.derived.(^ast.Ident); ok { + if ident.name == name { + return true + } + } + } + } + } + + return false +} + +/* + Parser gives ranges of expression, but not actually where the commas are placed. +*/ +get_call_commas :: proc(position_context: ^DocumentPositionContext, document: ^common.Document) { + if position_context.call == nil { + return + } + + commas := make([dynamic]int, 0, 10, context.temp_allocator) + + paren_count := 0 + bracket_count := 0 + brace_count := 0 + + if call, ok := position_context.call.derived.(^ast.Call_Expr); ok { + if document.text[call.open.offset] == '(' { + paren_count -= 1 + } + for i := call.open.offset; i < call.close.offset; i += 1 { + switch document.text[i] { + case '[': paren_count += 1 + case ']': paren_count -= 1 + case '{': brace_count += 1 + case '}': brace_count -= 1 + case '(': paren_count += 1 + case ')': paren_count -= 1 + case ',': + if paren_count == 0 && brace_count == 0 && bracket_count == 0 { + append(&commas, i) + } + } + } + } + + position_context.call_commas = commas[:] +} + +type_to_string :: proc(ast_context: ^AstContext, expr: ^ast.Expr) -> string { + if symbol, ok := resolve_type_expression(ast_context, expr); ok { + if .Anonymous in symbol.flags { + return symbol.name + } + } + + return common.node_to_string(expr) +} + +/* + Figure out what exactly is at the given position and whether it is in a function, struct, etc. +*/ +get_document_position_context :: proc(document: ^common.Document, position: common.Position, hint: DocumentPositionContextHint) -> (DocumentPositionContext, bool) { + position_context: DocumentPositionContext + + position_context.hint = hint + position_context.file = document.ast + position_context.line = position.line + + absolute_position, ok := common.get_absolute_position(position, document.text) + + if !ok { + log.error("failed to get absolute position") + return position_context, false + } + + position_context.position = absolute_position + + exists_in_decl := false + + for decl in document.ast.decls { + if position_in_node(decl, position_context.position) { + get_document_position(decl, &position_context) + exists_in_decl = true + #partial switch v in decl.derived { + case ^ast.Expr_Stmt: + position_context.global_lhs_stmt = true + } + break + } + } + + for import_stmt in document.ast.imports { + if position_in_node(import_stmt, position_context.position) { + position_context.import_stmt = import_stmt + break + } + } + + if !exists_in_decl && position_context.import_stmt == nil { + position_context.abort_completion = true + } + + if !position_in_node(position_context.comp_lit, position_context.position) { + position_context.comp_lit = nil + } + + if !position_in_node(position_context.parent_comp_lit, position_context.position) { + position_context.parent_comp_lit = nil + } + + if !position_in_node(position_context.assign, position_context.position) { + position_context.assign = nil + } + + if !position_in_node(position_context.binary, position_context.position) { + position_context.binary = nil + } + + if !position_in_node(position_context.parent_binary, position_context.position) { + position_context.parent_binary = nil + } + + if hint == .Completion && position_context.selector == nil && position_context.field == nil { + fallback_position_context_completion(document, position, &position_context) + } + + if (hint == .SignatureHelp || hint == .Completion) && position_context.call == nil { + fallback_position_context_signature(document, position, &position_context) + } + + if hint == .SignatureHelp { + get_call_commas(&position_context, document) + } + + return position_context, true +} + +//terrible fallback code +fallback_position_context_completion :: proc(document: ^common.Document, position: common.Position, position_context: ^DocumentPositionContext) { + paren_count: int + bracket_count: int + end: int + start: int + empty_dot: bool + empty_arrow: bool + last_dot: bool + last_arrow: bool + dots_seen: int + partial_arrow: bool + + i := position_context.position - 1 + + end = i + + for i > 0 { + c := position_context.file.src[i] + + if c == '(' && paren_count == 0 { + start = i + 1 + break + } else if c == '[' && bracket_count == 0 { + start = i + 1 + break + } else if c == ']' && !last_dot { + start = i + 1 + break + } else if c == ')' && !last_dot { + start = i + 1 + break + } else if c == ')' { + paren_count -= 1 + } else if c == '(' { + paren_count += 1 + } else if c == '[' { + bracket_count += 1 + } else if c == ']' { + bracket_count -= 1 + } else if c == '.' { + dots_seen += 1 + last_dot = true + i -= 1 + continue + } else if position_context.file.src[max(0, i - 1)] == '-' && c == '>' { + last_arrow = true + i -= 2 + continue + } + + //ignore everything in the bracket + if bracket_count != 0 || paren_count != 0 { + i -= 1 + continue + } + + //yeah.. + if c == ' ' || c == '{' || c == ',' || + c == '}' || c == '^' || c == ':' || + c == '\n' || c == '\r' || c == '=' || + c == '<' || c == '-' || c == '!' || + c == '+' || c == '&'|| c == '|' { + start = i + 1 + break + } else if c == '>' { + partial_arrow = true + } + + last_dot = false + last_arrow = false + + i -= 1 + } + + if i >= 0 && position_context.file.src[end] == '.' { + empty_dot = true + end -= 1 + } else if i >= 0 && position_context.file.src[max(0, end - 1)] == '-' && position_context.file.src[end] == '>' { + empty_arrow = true + end -= 2 + position_context.arrow = true + } + + begin_offset := max(0, start) + end_offset := max(start, end + 1) + line_offset := begin_offset + + if line_offset < len(position_context.file.src) { + for line_offset > 0 { + c := position_context.file.src[line_offset] + if c == '\n' || c == '\r' { + line_offset += 1 + break + } + line_offset -= 1 + } + } + + str := position_context.file.src[0:end_offset] + + if empty_dot && end_offset - begin_offset == 0 { + position_context.implicit = true + return + } + + s := string(position_context.file.src[begin_offset:end_offset]) + + if !partial_arrow { + + only_whitespaces := true + + for r in s { + if !strings.is_space(r) { + only_whitespaces = false + } + } + + if only_whitespaces { + return + } + } + + p := parser.Parser { + err = common.parser_warning_handler, //empty + warn = common.parser_warning_handler, //empty + flags = {.Optional_Semicolons}, + file = &position_context.file, + } + + tokenizer.init(&p.tok, str, position_context.file.fullpath, common.parser_warning_handler) + + p.tok.ch = ' ' + p.tok.line_count = position.line + 1 + p.tok.line_offset = line_offset + p.tok.offset = begin_offset + p.tok.read_offset = begin_offset + + tokenizer.advance_rune(&p.tok) + + if p.tok.ch == utf8.RUNE_BOM { + tokenizer.advance_rune(&p.tok) + } + + parser.advance_token(&p) + + context.allocator = context.temp_allocator + + e := parser.parse_expr(&p, true) + + if empty_dot || empty_arrow { + position_context.selector = e + } else if s, ok := e.derived.(^ast.Selector_Expr); ok { + position_context.selector = s.expr + position_context.field = s.field + } else if s, ok := e.derived.(^ast.Implicit_Selector_Expr); ok { + position_context.implicit = true + } else if s, ok := e.derived.(^ast.Tag_Expr); ok { + position_context.tag = s.expr + } else if bad_expr, ok := e.derived.(^ast.Bad_Expr); ok { + //this is most likely because of use of 'in', 'context', etc. + //try to go back one dot. + + src_with_dot := string(position_context.file.src[0:min(len(position_context.file.src), end_offset + 1)]) + last_dot := strings.last_index(src_with_dot, ".") + + if last_dot == -1 { + return + } + + tokenizer.init(&p.tok, position_context.file.src[0:last_dot], position_context.file.fullpath, common.parser_warning_handler) + + p.tok.ch = ' ' + p.tok.line_count = position.line + 1 + p.tok.line_offset = line_offset + p.tok.offset = begin_offset + p.tok.read_offset = begin_offset + + tokenizer.advance_rune(&p.tok) + + if p.tok.ch == utf8.RUNE_BOM { + tokenizer.advance_rune(&p.tok) + } + + parser.advance_token(&p) + + e := parser.parse_expr(&p, true) + + if e == nil { + position_context.abort_completion = true + return + } else if e, ok := e.derived.(^ast.Bad_Expr); ok { + position_context.abort_completion = true + return + } + + position_context.selector = e + + ident := new_type(ast.Ident, e.pos, e.end, context.temp_allocator) + ident.name = string(position_context.file.src[last_dot + 1:end_offset]) + + if ident.name != "" { + position_context.field = ident + } + } else { + position_context.identifier = e + } +} + +fallback_position_context_signature :: proc(document: ^common.Document, position: common.Position, position_context: ^DocumentPositionContext) { + end: int + start: int + i := position_context.position - 1 + end = i + + for i > 0 { + + c := position_context.file.src[i] + + if c == ' ' || c == '\n' || c == '\r' { + start = i + 1 + break + } + + i -= 1 + } + + if end < 0 { + return + } + + if position_context.file.src[end] != '(' { + return + } + + end -= 1 + + begin_offset := max(0, start) + end_offset := max(start, end + 1) + + if end_offset - begin_offset <= 1 { + return + } + + str := position_context.file.src[0:end_offset] + + p := parser.Parser { + err = common.parser_warning_handler, //empty + warn = common.parser_warning_handler, //empty + file = &position_context.file, + } + + tokenizer.init(&p.tok, str, position_context.file.fullpath, common.parser_warning_handler) + + p.tok.ch = ' ' + p.tok.line_count = position.line + p.tok.offset = begin_offset + p.tok.read_offset = begin_offset + + tokenizer.advance_rune(&p.tok) + + if p.tok.ch == utf8.RUNE_BOM { + tokenizer.advance_rune(&p.tok) + } + + parser.advance_token(&p) + + context.allocator = context.temp_allocator + + position_context.call = parser.parse_expr(&p, true) + + if _, ok := position_context.call.derived.(^ast.Proc_Type); ok { + position_context.call = nil + } + + //log.error(string(position_context.file.src[begin_offset:end_offset])); +} + +/* + All these fallback functions are not perfect and should be fixed. A lot of weird use of the odin tokenizer and parser. +*/ + +get_document_position ::proc { + get_document_position_array, + get_document_position_dynamic_array, + get_document_position_node, +} + +get_document_position_array :: proc(array: $A/[]^$T, position_context: ^DocumentPositionContext) { + for elem, i in array { + get_document_position(elem, position_context) + } +} + +get_document_position_dynamic_array :: proc(array: $A/[dynamic]^$T, position_context: ^DocumentPositionContext) { + for elem, i in array { + get_document_position(elem, position_context) + } +} + +position_in_node :: proc(node: ^ast.Node, position: common.AbsolutePosition) -> bool { + return node != nil && node.pos.offset <= position && position <= node.end.offset +} + +get_document_position_node :: proc(node: ^ast.Node, position_context: ^DocumentPositionContext) { + using ast + + if node == nil { + return + } + + if !position_in_node(node, position_context.position) { + return + } + + #partial switch n in node.derived { + case ^Bad_Expr: + case ^Ident: + position_context.identifier = node + case ^Implicit: + case ^Undef: + case ^Basic_Lit: + case ^Ellipsis: + get_document_position(n.expr, position_context) + case ^Proc_Lit: + get_document_position(n.type, position_context) + + if position_in_node(n.body, position_context.position) { + position_context.function = cast(^Proc_Lit)node + get_document_position(n.body, position_context) + } + case ^Comp_Lit: + //only set this for the parent comp literal, since we will need to walk through it to infer types. + if position_context.parent_comp_lit == nil { + position_context.parent_comp_lit = cast(^Comp_Lit)node + } + + position_context.comp_lit = cast(^Comp_Lit)node + + get_document_position(n.type, position_context) + get_document_position(n.elems, position_context) + case ^Tag_Expr: + get_document_position(n.expr, position_context) + case ^Unary_Expr: + get_document_position(n.expr, position_context) + case ^Binary_Expr: + if position_context.parent_binary == nil { + position_context.parent_binary = cast(^Binary_Expr)node + } + position_context.binary = cast(^Binary_Expr)node + get_document_position(n.left, position_context) + get_document_position(n.right, position_context) + case ^Paren_Expr: + get_document_position(n.expr, position_context) + case ^Call_Expr: + if position_context.hint == .SignatureHelp || position_context.hint == .Completion { + position_context.call = cast(^Expr)node + } + get_document_position(n.expr, position_context) + get_document_position(n.args, position_context) + case ^Selector_Expr: + if position_context.hint == .Completion { + if n.field != nil && n.field.pos.line - 1 == position_context.line { + //The parser is not fault tolerant enough, relying on the fallback as the main completion parsing for now + //position_context.selector = n.expr; + //position_context.field = n.field; + } + } else if (position_context.hint == .Definition || position_context.hint == .Hover) && n.field != nil { + position_context.selector = n.expr + position_context.field = n.field + get_document_position(n.expr, position_context) + get_document_position(n.field, position_context) + } else { + get_document_position(n.expr, position_context) + get_document_position(n.field, position_context) + } + case ^Index_Expr: + get_document_position(n.expr, position_context) + get_document_position(n.index, position_context) + case ^Deref_Expr: + get_document_position(n.expr, position_context) + case ^Slice_Expr: + get_document_position(n.expr, position_context) + get_document_position(n.low, position_context) + get_document_position(n.high, position_context) + case ^Field_Value: + position_context.field_value = cast(^Field_Value)node + get_document_position(n.field, position_context) + get_document_position(n.value, position_context) + case ^Ternary_If_Expr: + get_document_position(n.x, position_context) + get_document_position(n.cond, position_context) + get_document_position(n.y, position_context) + case ^Ternary_When_Expr: + get_document_position(n.x, position_context) + get_document_position(n.cond, position_context) + get_document_position(n.y, position_context) + case ^Type_Assertion: + get_document_position(n.expr, position_context) + get_document_position(n.type, position_context) + case ^Type_Cast: + get_document_position(n.type, position_context) + get_document_position(n.expr, position_context) + case ^Auto_Cast: + get_document_position(n.expr, position_context) + case ^Bad_Stmt: + case ^Empty_Stmt: + case ^Expr_Stmt: + get_document_position(n.expr, position_context) + case ^Tag_Stmt: + r := cast(^Tag_Stmt)node + get_document_position(r.stmt, position_context) + case ^Assign_Stmt: + position_context.assign = cast(^Assign_Stmt)node + get_document_position(n.lhs, position_context) + get_document_position(n.rhs, position_context) + case ^Block_Stmt: + get_document_position(n.label, position_context) + get_document_position(n.stmts, position_context) + case ^If_Stmt: + get_document_position(n.label, position_context) + get_document_position(n.init, position_context) + get_document_position(n.cond, position_context) + get_document_position(n.body, position_context) + get_document_position(n.else_stmt, position_context) + case ^When_Stmt: + get_document_position(n.cond, position_context) + get_document_position(n.body, position_context) + get_document_position(n.else_stmt, position_context) + case ^Return_Stmt: + position_context.returns = cast(^Return_Stmt)node + get_document_position(n.results, position_context) + case ^Defer_Stmt: + get_document_position(n.stmt, position_context) + case ^For_Stmt: + get_document_position(n.label, position_context) + get_document_position(n.init, position_context) + get_document_position(n.cond, position_context) + get_document_position(n.post, position_context) + get_document_position(n.body, position_context) + case ^Range_Stmt: + get_document_position(n.label, position_context) + get_document_position(n.vals, position_context) + get_document_position(n.expr, position_context) + get_document_position(n.body, position_context) + case ^Case_Clause: + for elem in n.list { + if position_in_node(elem, position_context.position) { + position_context.case_clause = cast(^Case_Clause)node + break + } + } + + get_document_position(n.list, position_context) + get_document_position(n.body, position_context) + case ^Switch_Stmt: + position_context.switch_stmt = cast(^Switch_Stmt)node + get_document_position(n.label, position_context) + get_document_position(n.init, position_context) + get_document_position(n.cond, position_context) + get_document_position(n.body, position_context) + case ^Type_Switch_Stmt: + position_context.switch_type_stmt = cast(^Type_Switch_Stmt)node + get_document_position(n.label, position_context) + get_document_position(n.tag, position_context) + get_document_position(n.expr, position_context) + get_document_position(n.body, position_context) + case ^Branch_Stmt: + get_document_position(n.label, position_context) + case ^Using_Stmt: + get_document_position(n.list, position_context) + case ^Bad_Decl: + case ^Value_Decl: + position_context.value_decl = cast(^Value_Decl)node + get_document_position(n.attributes, position_context) + + for name in n.names { + if position_in_node(name, position_context.position) && n.end.line - 1 == position_context.line { + position_context.abort_completion = true + break + } + } + get_document_position(n.names, position_context) + get_document_position(n.type, position_context) + get_document_position(n.values, position_context) + case ^Package_Decl: + case ^Import_Decl: + case ^Foreign_Block_Decl: + get_document_position(n.attributes, position_context) + get_document_position(n.foreign_library, position_context) + get_document_position(n.body, position_context) + case ^Foreign_Import_Decl: + get_document_position(n.name, position_context) + case ^Proc_Group: + get_document_position(n.args, position_context) + case ^Attribute: + get_document_position(n.elems, position_context) + case ^Field: + get_document_position(n.names, position_context) + get_document_position(n.type, position_context) + get_document_position(n.default_value, position_context) + case ^Field_List: + get_document_position(n.list, position_context) + case ^Typeid_Type: + get_document_position(n.specialization, position_context) + case ^Helper_Type: + get_document_position(n.type, position_context) + case ^Distinct_Type: + get_document_position(n.type, position_context) + case ^Poly_Type: + get_document_position(n.type, position_context) + get_document_position(n.specialization, position_context) + case ^Proc_Type: + get_document_position(n.params, position_context) + get_document_position(n.results, position_context) + case ^Pointer_Type: + get_document_position(n.elem, position_context) + case ^Array_Type: + get_document_position(n.len, position_context) + get_document_position(n.elem, position_context) + case ^Dynamic_Array_Type: + get_document_position(n.elem, position_context) + case ^Struct_Type: + get_document_position(n.poly_params, position_context) + get_document_position(n.align, position_context) + get_document_position(n.fields, position_context) + case ^Union_Type: + get_document_position(n.poly_params, position_context) + get_document_position(n.align, position_context) + get_document_position(n.variants, position_context) + case ^Enum_Type: + get_document_position(n.base_type, position_context) + get_document_position(n.fields, position_context) + case ^Bit_Set_Type: + get_document_position(n.elem, position_context) + get_document_position(n.underlying, position_context) + case ^Map_Type: + get_document_position(n.key, position_context) + get_document_position(n.value, position_context) + case ^Implicit_Selector_Expr: + position_context.implicit = true + get_document_position(n.field, position_context) + case: + log.errorf("Unhandled node kind: %T", n) + } +} diff --git a/src/server/build.odin b/src/server/build.odin new file mode 100644 index 0000000..c9edf93 --- /dev/null +++ b/src/server/build.odin @@ -0,0 +1,215 @@ +package server + +import "core:path/filepath" +import path "core:path/slashpath" +import "core:os" +import "core:fmt" +import "core:odin/parser" +import "core:odin/ast" +import "core:log" +import "core:odin/tokenizer" +import "core:strings" +import "core:mem" +import "core:runtime" + +import "shared:common" + + +symbol_collection: SymbolCollection + +files: [dynamic]string + +platform_os: map[string]bool = { + "windows" = true, + "linux" = true, + "essence" = true, + "js" = true, + "freebsd" = true, + "darwin" = true, + "wasm32" = true, +} + + +os_enum_to_string: map[runtime.Odin_OS_Type]string = { + .Windows = "windows", + .Darwin = "darwin", + .Linux = "linux", + .Essence = "essence", + .FreeBSD = "freebsd", + .WASI = "wasi", + .JS = "js", + .Freestanding = "freestanding", +} + + +walk_static_index_build :: proc(info: os.File_Info, in_err: os.Errno) -> (err: os.Errno, skip_dir: bool) { + if info.is_dir { + return 0, false + } + + if filepath.ext(info.name) != ".odin" { + return 0, false + } + + last_underscore_index := strings.last_index(info.name, "_") + last_dot_index := strings.last_index(info.name, ".") + + if last_underscore_index + 1 < last_dot_index { + name_between := info.name[last_underscore_index + 1:last_dot_index] + + if _, ok := platform_os[name_between]; ok { + if name_between != os_enum_to_string[ODIN_OS] { + return 0, false + } + } + } + + forward, _ := filepath.to_slash(info.fullpath, context.temp_allocator) + + append(&files, strings.clone(forward, context.allocator)) + + return 0, false +} + +build_static_index :: proc(allocator := context.allocator, config: ^common.Config) { + symbol_collection = make_symbol_collection(allocator, config) + + files = make([dynamic]string, context.allocator) + + for k, v in config.collections { + filepath.walk(v, walk_static_index_build) + } + + when ODIN_OS == .Windows { + slashed, _ := filepath.to_slash(os.get_current_directory(context.temp_allocator), context.temp_allocator) + } else { + slashed, _ := filepath.to_slash(os.get_current_directory(), context.temp_allocator) + } + + when ODIN_OS == .Windows { + builtin_package := path.join(elems = {slashed, "builtin"}, allocator = context.temp_allocator) + append(&indexer.builtin_packages, strings.to_lower(builtin_package)) + } else { + builtin_package := path.join(elems = {slashed, "builtin"}, allocator = context.allocator) + append(&indexer.builtin_packages, builtin_package) + } + + filepath.walk(builtin_package, walk_static_index_build) + + temp_arena: mem.Arena + + mem.init_arena(&temp_arena, make([]byte, mem.megabytes(100))) + + context.allocator = mem.arena_allocator(&temp_arena) + + for fullpath in files { + data, ok := os.read_entire_file(fullpath, context.allocator) + + if !ok { + log.errorf("failed to read entire file for indexing %v", fullpath) + continue + } + + p := parser.Parser { + err = log_error_handler, + warn = log_warning_handler, + flags = {.Optional_Semicolons}, + } + + dir := filepath.base(filepath.dir(fullpath, context.allocator)) + + pkg := new(ast.Package) + pkg.kind = .Normal + pkg.fullpath = fullpath + pkg.name = dir + + if dir == "runtime" { + pkg.kind = .Runtime + } + + file := ast.File { + fullpath = fullpath, + src = string(data), + pkg = pkg, + } + + ok = parser.parse_file(&p, &file) + + if !ok { + log.info(pkg) + log.errorf("error in parse file for indexing %v", fullpath) + } + + uri := common.create_uri(fullpath, context.allocator) + + collect_symbols(&symbol_collection, file, uri.uri) + + free_all(context.allocator) + } + + //afterwards, I have to go through the files again to find all the references. Better to reload individually the files again to ensure we don't use too much memory. + + for fullpath in files { + data, ok := os.read_entire_file(fullpath, context.allocator) + + if !ok { + log.errorf("failed to read entire file for indexing %v", fullpath) + continue + } + + p := parser.Parser { + err = log_error_handler, + warn = log_warning_handler, + flags = {.Optional_Semicolons}, + } + + dir := filepath.base(filepath.dir(fullpath, context.allocator)) + + pkg := new(ast.Package) + pkg.kind = .Normal + pkg.fullpath = fullpath + pkg.name = dir + + if dir == "runtime" { + pkg.kind = .Runtime + } + + file := ast.File { + fullpath = fullpath, + src = string(data), + pkg = pkg, + } + + ok = parser.parse_file(&p, &file) + + if !ok { + log.info(pkg) + log.errorf("error in parse file for indexing %v", fullpath) + } + + uri := common.create_uri(fullpath, context.allocator) + + //collect_references(&symbol_collection, file, uri.uri) + + free_all(context.allocator) + + delete(fullpath, allocator) + } + + delete(files) + delete(temp_arena.data) + + indexer.static_index = make_memory_index(symbol_collection) +} + +free_static_index :: proc() { + delete_symbol_collection(symbol_collection) +} + +log_error_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) { + log.warnf("%v %v %v", pos, msg, args) +} + +log_warning_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) { + log.warnf("%v %v %v", pos, msg, args) +} diff --git a/src/server/caches.odin b/src/server/caches.odin index cc993c3..42dbc27 100644 --- a/src/server/caches.odin +++ b/src/server/caches.odin @@ -1,19 +1,17 @@ package server -import "shared:index" -import "shared:analysis" import "shared:common" //Used in semantic tokens and inlay hints to handle the entire file being resolved. FileResolveCache :: struct { - files: map[string]map[uintptr]index.SymbolAndNode, + files: map[string]map[uintptr]SymbolAndNode, } file_resolve_cache: FileResolveCache -resolve_entire_file :: proc(document: ^common.Document) -> map[uintptr]index.SymbolAndNode{ +resolve_entire_file_cached :: proc(document: ^common.Document) -> map[uintptr]SymbolAndNode{ if document.uri.uri not_in file_resolve_cache.files { - file_resolve_cache.files[document.uri.uri] = analysis.resolve_entire_file( + file_resolve_cache.files[document.uri.uri] = resolve_entire_file( document, common.scratch_allocator(document.allocator), ) diff --git a/src/server/clone.odin b/src/server/clone.odin new file mode 100644 index 0000000..9a4af46 --- /dev/null +++ b/src/server/clone.odin @@ -0,0 +1,253 @@ +package server + +import "core:mem" +import "core:fmt" +import "core:odin/tokenizer" +import "core:odin/ast" +import "core:strings" +import "core:log" +import "core:intrinsics" +import "core:reflect" +_ :: intrinsics + +new_type :: proc($T: typeid, pos, end: tokenizer.Pos, allocator: mem.Allocator) -> ^T { + n, _ := mem.new(T, allocator) + n.pos = pos + n.end = end + n.derived = n + base: ^ast.Node = n // dummy check + _ = base // "Use" type to make -vet happy + when intrinsics.type_has_field(T, "derived_expr") { + n.derived_expr = n + } + when intrinsics.type_has_field(T, "derived_stmt") { + n.derived_stmt = n + } + return n +} + +clone_type :: proc { + clone_node, + clone_expr, + clone_array, + clone_dynamic_array, +} + +clone_array :: proc(array: $A/[]^$T, allocator: mem.Allocator, unique_strings: ^map[string]string) -> A { + if len(array) == 0 { + return nil + } + res := make(A, len(array), allocator) + for elem, i in array { + res[i] = cast(^T)clone_type(elem, allocator, unique_strings) + } + return res +} + +clone_dynamic_array :: proc(array: $A/[dynamic]^$T, allocator: mem.Allocator, unique_strings: ^map[string]string) -> A { + if len(array) == 0 { + return nil + } + res := make(A, len(array), allocator) + for elem, i in array { + res[i] = auto_cast clone_type(elem, allocator, unique_strings) + } + return res +} + +clone_expr :: proc(node: ^ast.Expr, allocator: mem.Allocator, unique_strings: ^map[string]string) -> ^ast.Expr { + return cast(^ast.Expr)clone_node(node, allocator, unique_strings) +} + +clone_node :: proc(node: ^ast.Node, allocator: mem.Allocator, unique_strings: ^map[string]string) -> ^ast.Node { + using ast + if node == nil { + return nil + } + + size := size_of(Node) + align := align_of(Node) + ti := reflect.union_variant_type_info(node.derived) + if ti != nil { + elem := ti.variant.(reflect.Type_Info_Pointer).elem + size = elem.size + align = elem.align + } + + #partial switch in node.derived { + case ^Package, ^File: + panic("Cannot clone this node type") + } + + res := cast(^Node)mem.alloc(size, align, allocator) + src: rawptr = node + if node.derived != nil { + src = (^rawptr)(&node.derived)^ + } + mem.copy(res, src, size) + res_ptr_any: any + res_ptr_any.data = &res + res_ptr_any.id = ti.id + + if unique_strings != nil && node.pos.file != "" { + res.pos.file = get_index_unique_string(unique_strings, allocator, node.pos.file) + } else { + res.pos.file = node.pos.file + } + + if unique_strings != nil && node.end.file != "" { + res.end.file = get_index_unique_string(unique_strings, allocator, node.end.file) + } else { + res.end.file = node.end.file + } + + reflect.set_union_value(res.derived, res_ptr_any) + + res_ptr := reflect.deref(res_ptr_any) + + if de := reflect.struct_field_value_by_name(res_ptr, "derived_expr", true); de != nil { + reflect.set_union_value(de, res_ptr_any) + } + if ds := reflect.struct_field_value_by_name(res_ptr, "derived_stmt", true); ds != nil { + reflect.set_union_value(ds, res_ptr_any) + } + + if res.derived != nil do #partial switch r in res.derived { + case ^Ident: + n := node.derived.(^Ident) + + if unique_strings == nil { + r.name = strings.clone(n.name, allocator) + } else { + r.name = get_index_unique_string(unique_strings, allocator, n.name) + } + case ^Implicit: + n := node.derived.(^Implicit) + if unique_strings == nil { + r.tok.text = strings.clone(n.tok.text, allocator) + } else { + r.tok.text = get_index_unique_string(unique_strings, allocator, n.tok.text) + } + case ^Undef: + case ^Basic_Lit: + n := node.derived.(^Basic_Lit) + if unique_strings == nil { + r.tok.text = strings.clone(n.tok.text, allocator) + } else { + r.tok.text = get_index_unique_string(unique_strings, allocator, n.tok.text) + } + case ^Basic_Directive: + n := node.derived.(^Basic_Directive) + if unique_strings == nil { + r.name = strings.clone(n.name, allocator) + } else { + r.name = get_index_unique_string(unique_strings, allocator, n.name) + } + case ^Ellipsis: + r.expr = clone_type(r.expr, allocator, unique_strings) + case ^Tag_Expr: + r.expr = clone_type(r.expr, allocator, unique_strings) + case ^Unary_Expr: + r.expr = clone_type(r.expr, allocator, unique_strings) + case ^Binary_Expr: + r.left = clone_type(r.left, allocator, unique_strings) + r.right = clone_type(r.right, allocator, unique_strings) + case ^Paren_Expr: + r.expr = clone_type(r.expr, allocator, unique_strings) + case ^Selector_Expr: + r.expr = clone_type(r.expr, allocator, unique_strings) + r.field = auto_cast clone_type(r.field, allocator, unique_strings) + case ^Implicit_Selector_Expr: + r.field = auto_cast clone_type(r.field, allocator, unique_strings) + case ^Slice_Expr: + r.expr = clone_type(r.expr, allocator, unique_strings) + r.low = clone_type(r.low, allocator, unique_strings) + r.high = clone_type(r.high, allocator, unique_strings) + case ^Attribute: + r.elems = clone_type(r.elems, allocator, unique_strings) + case ^Distinct_Type: + r.type = clone_type(r.type, allocator, unique_strings) + case ^Proc_Type: + r.params = auto_cast clone_type(r.params, allocator, unique_strings) + r.results = auto_cast clone_type(r.results, allocator, unique_strings) + case ^Pointer_Type: + r.elem = clone_type(r.elem, allocator, unique_strings) + case ^Array_Type: + r.len = clone_type(r.len, allocator, unique_strings) + r.elem = clone_type(r.elem, allocator, unique_strings) + r.tag = clone_type(r.tag, allocator, unique_strings) + case ^Dynamic_Array_Type: + r.elem = clone_type(r.elem, allocator, unique_strings) + r.tag = clone_type(r.tag, allocator, unique_strings) + case ^Struct_Type: + r.poly_params = auto_cast clone_type(r.poly_params, allocator, unique_strings) + r.align = clone_type(r.align, allocator, unique_strings) + r.fields = auto_cast clone_type(r.fields, allocator, unique_strings) + r.where_clauses = clone_type(r.where_clauses, allocator, unique_strings) + case ^Field: + r.names = clone_type(r.names, allocator, unique_strings) + r.type = clone_type(r.type, allocator, unique_strings) + r.default_value = clone_type(r.default_value, allocator, unique_strings) + case ^Field_List: + r.list = clone_type(r.list, allocator, unique_strings) + case ^Field_Value: + r.field = clone_type(r.field, allocator, unique_strings) + r.value = clone_type(r.value, allocator, unique_strings) + case ^Union_Type: + r.poly_params = auto_cast clone_type(r.poly_params, allocator, unique_strings) + r.align = clone_type(r.align, allocator, unique_strings) + r.variants = clone_type(r.variants, allocator, unique_strings) + r.where_clauses = clone_type(r.where_clauses, allocator, unique_strings) + case ^Enum_Type: + r.base_type = clone_type(r.base_type, allocator, unique_strings) + r.fields = clone_type(r.fields, allocator, unique_strings) + case ^Bit_Set_Type: + r.elem = clone_type(r.elem, allocator, unique_strings) + r.underlying = clone_type(r.underlying, allocator, unique_strings) + case ^Map_Type: + r.key = clone_type(r.key, allocator, unique_strings) + r.value = clone_type(r.value, allocator, unique_strings) + case ^Call_Expr: + r.expr = clone_type(r.expr, allocator, unique_strings) + r.args = clone_type(r.args, allocator, unique_strings) + case ^Typeid_Type: + r.specialization = clone_type(r.specialization, allocator, unique_strings) + case ^Ternary_When_Expr: + r.x = clone_type(r.x, allocator, unique_strings) + r.cond = clone_type(r.cond, allocator, unique_strings) + r.y = clone_type(r.y, allocator, unique_strings) + case ^Poly_Type: + r.type = auto_cast clone_type(r.type, allocator, unique_strings) + r.specialization = clone_type(r.specialization, allocator, unique_strings) + case ^Proc_Group: + r.args = clone_type(r.args, allocator, unique_strings) + case ^Comp_Lit: + r.type = clone_type(r.type, allocator, unique_strings) + r.elems = clone_type(r.elems, allocator, unique_strings) + case ^Proc_Lit: + r.type = cast(^Proc_Type)clone_type(cast(^Node)r.type, allocator, unique_strings) + r.body = nil + r.where_clauses = nil + case ^Helper_Type: + r.type = clone_type(r.type, allocator, unique_strings) + case ^Type_Cast: + r.type = clone_type(r.type, allocator, unique_strings) + r.expr = clone_type(r.expr, allocator, unique_strings) + case ^Deref_Expr: + r.expr = clone_type(r.expr, allocator, unique_strings) + case ^Index_Expr: + r.expr = clone_type(r.expr, allocator, unique_strings) + r.index = clone_type(r.index, allocator, unique_strings) + case ^Multi_Pointer_Type: + r.elem = clone_type(r.elem, allocator, unique_strings) + case ^Matrix_Type: + r.elem = clone_type(r.elem, allocator, unique_strings) + case ^Type_Assertion: + r.expr = clone_type(r.expr, allocator, unique_strings) + r.type = clone_type(r.type, allocator, unique_strings) + case: + //fmt.logf("Unhandled node kind: %T", r) + } + + return res +} diff --git a/src/server/collector.odin b/src/server/collector.odin new file mode 100644 index 0000000..e049c66 --- /dev/null +++ b/src/server/collector.odin @@ -0,0 +1,602 @@ +package server + +import "core:odin/ast" +import "core:hash" +import "core:strings" +import "core:mem" +import "core:fmt" +import "core:path/filepath" +import path "core:path/slashpath" +import "core:log" +import "core:strconv" + +import "shared:common" + +SymbolCollection :: struct { + allocator: mem.Allocator, + config: ^common.Config, + packages: map[string]map[string]Symbol, + references: map[string]map[string]Reference, + unique_strings: map[string]string, //store all our strings as unique strings and reference them to save memory. +} + +get_index_unique_string :: proc { + get_index_unique_string_collection, + get_index_unique_string_collection_raw, +} + +get_index_unique_string_collection :: proc(collection: ^SymbolCollection, s: string) -> string { + return get_index_unique_string_collection_raw(&collection.unique_strings, collection.allocator, s) +} + +get_index_unique_string_collection_raw :: proc(unique_strings: ^map[string]string, allocator: mem.Allocator, s: string) -> string { + if _, ok := unique_strings[s]; !ok { + str := strings.clone(s, allocator) + unique_strings[str] = str + } + + return unique_strings[s] +} + +make_symbol_collection :: proc(allocator := context.allocator, config: ^common.Config) -> SymbolCollection { + return SymbolCollection { + allocator = allocator, + config = config, + packages = make(map[string]map[string]Symbol, 16, allocator), + unique_strings = make(map[string]string, 16, allocator), + } +} + +delete_symbol_collection :: proc(collection: SymbolCollection) { + for k, v in collection.packages { + for k2, v2 in v { + free_symbol(v2, collection.allocator) + } + } + + for k, v in collection.unique_strings { + delete(v, collection.allocator) + } + + for k, v in collection.packages { + delete(v) + } + + delete(collection.packages) + delete(collection.unique_strings) +} + +collect_procedure_fields :: proc(collection: ^SymbolCollection, proc_type: ^ast.Proc_Type, arg_list: ^ast.Field_List, return_list: ^ast.Field_List, package_map: map[string]string) -> SymbolProcedureValue { + returns := make([dynamic]^ast.Field, 0, collection.allocator) + args := make([dynamic]^ast.Field, 0, collection.allocator) + + if return_list != nil { + for ret in return_list.list { + cloned := cast(^ast.Field)clone_type(ret, collection.allocator, &collection.unique_strings) + replace_package_alias(cloned, package_map, collection) + append(&returns, cloned) + } + } + + if arg_list != nil { + for arg in arg_list.list { + cloned := cast(^ast.Field)clone_type(arg, collection.allocator, &collection.unique_strings) + replace_package_alias(cloned, package_map, collection) + append(&args, cloned) + } + } + + value := SymbolProcedureValue { + return_types = returns[:], + arg_types = args[:], + generic = proc_type.generic, + } + return value +} + +collect_struct_fields :: proc(collection: ^SymbolCollection, struct_type: ast.Struct_Type, package_map: map[string]string) -> SymbolStructValue { + names := make([dynamic]string, 0, collection.allocator) + types := make([dynamic]^ast.Expr, 0, collection.allocator) + usings := make(map[string]bool, 0, collection.allocator) + + for field in struct_type.fields.list { + for n in field.names { + ident := n.derived.(^ast.Ident) + append(&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) + + if .Using in field.flags { + usings[names[len(names) - 1]] = true + } + } + } + + value := SymbolStructValue { + names = names[:], + types = types[:], + usings = usings, + poly = cast(^ast.Field_List)clone_type(struct_type.poly_params, collection.allocator, &collection.unique_strings), + } + + return value +} + +collect_enum_fields :: proc(collection: ^SymbolCollection, fields: []^ast.Expr, package_map: map[string]string) -> SymbolEnumValue { + names := make([dynamic]string, 0, collection.allocator) + + //ERROR no hover on n in the for, but elsewhere is fine + for n in fields { + if ident, ok := n.derived.(^ast.Ident); ok { + append(&names, get_index_unique_string(collection, ident.name)) + } else if field, ok := n.derived.(^ast.Field_Value); ok { + if ident, ok := field.field.derived.(^ast.Ident); ok { + append(&names, get_index_unique_string(collection, ident.name)) + } else if binary, ok := field.field.derived.(^ast.Binary_Expr); ok { + append(&names, get_index_unique_string(collection, binary.left.derived.(^ast.Ident).name)) + } + } + } + + value := SymbolEnumValue { + names = names[:], + } + + return value +} + +collect_union_fields :: proc(collection: ^SymbolCollection, union_type: ast.Union_Type, package_map: map[string]string) -> SymbolUnionValue { + types := make([dynamic]^ast.Expr, 0, collection.allocator) + + for variant in union_type.variants { + cloned := clone_type(variant, collection.allocator, &collection.unique_strings) + replace_package_alias(cloned, package_map, collection) + append(&types, cloned) + } + + value := SymbolUnionValue { + types = types[:], + poly = cast(^ast.Field_List)clone_type(union_type.poly_params, collection.allocator, &collection.unique_strings), + } + + return value +} + +collect_bitset_field :: proc(collection: ^SymbolCollection, bitset_type: ast.Bit_Set_Type, package_map: map[string]string) -> SymbolBitSetValue { + cloned := clone_type(bitset_type.elem, collection.allocator, &collection.unique_strings) + replace_package_alias(cloned, package_map, collection) + + return SymbolBitSetValue { + expr = cloned, + } +} + +collect_slice :: proc(collection: ^SymbolCollection, array: ast.Array_Type, package_map: map[string]string) -> SymbolFixedArrayValue { + elem := clone_type(array.elem, collection.allocator, &collection.unique_strings) + len := clone_type(array.len, collection.allocator, &collection.unique_strings) + + replace_package_alias(elem, package_map, collection) + replace_package_alias(len, package_map, collection) + + return SymbolFixedArrayValue { + expr = elem, + len = len, + } +} + +collect_array :: proc(collection: ^SymbolCollection, array: ast.Array_Type, package_map: map[string]string) -> SymbolSliceValue { + elem := clone_type(array.elem, collection.allocator, &collection.unique_strings) + + replace_package_alias(elem, package_map, collection) + + return SymbolSliceValue { + expr = elem, + } +} + +collect_map :: proc(collection: ^SymbolCollection, m: ast.Map_Type, package_map: map[string]string) -> SymbolMapValue { + key := clone_type(m.key, collection.allocator, &collection.unique_strings) + value := clone_type(m.value, collection.allocator, &collection.unique_strings) + + replace_package_alias(key, package_map, collection) + replace_package_alias(value, package_map, collection) + + return SymbolMapValue { + key = key, + value = value, + } +} + +collect_dynamic_array :: proc(collection: ^SymbolCollection, array: ast.Dynamic_Array_Type, package_map: map[string]string) -> SymbolDynamicArrayValue { + elem := clone_type(array.elem, collection.allocator, &collection.unique_strings) + + replace_package_alias(elem, package_map, collection) + + return SymbolDynamicArrayValue { + expr = elem, + } +} + +collect_generic :: proc(collection: ^SymbolCollection, expr: ^ast.Expr, package_map: map[string]string, uri: string) -> SymbolGenericValue { + //Bit hacky right now, but it's hopefully a temporary solution. + //In the c package code it uses a documentation package(builtin). + if selector, ok := expr.derived.(^ast.Selector_Expr); ok { + if ident, ok := selector.expr.derived.(^ast.Ident); ok { + if ident.name == "builtin" && strings.contains(uri, "Odin/core/c/c.odin") { + cloned := clone_type(selector.field, collection.allocator, &collection.unique_strings) + replace_package_alias(cloned, package_map, collection) + value := SymbolGenericValue { + expr = cloned, + } + return value + } + } + } + + cloned := clone_type(expr, collection.allocator, &collection.unique_strings) + replace_package_alias(cloned, package_map, collection) + + value := SymbolGenericValue { + expr = cloned, + } + + return value +} + +collect_symbols :: proc(collection: ^SymbolCollection, file: ast.File, uri: string) -> common.Error { + forward, _ := filepath.to_slash(file.fullpath, context.temp_allocator) + + when ODIN_OS == .Windows { + directory := strings.to_lower(path.dir(forward, context.temp_allocator), context.temp_allocator) + } else { + directory := path.dir(forward, context.temp_allocator) + } + + package_map := get_package_mapping(file, collection.config, directory) + + exprs := common.collect_globals(file, true) + + for expr in exprs { + symbol: Symbol + + token: ast.Node + token_type: SymbolType + + name := expr.name + + col_expr := expr.expr + + if helper, ok := col_expr.derived.(^ast.Helper_Type); ok { + if helper.type != nil { + col_expr = helper.type + } + } + + if dist, ok := col_expr.derived.(^ast.Distinct_Type); ok { + if dist.type != nil { + col_expr = dist.type + } + } + + #partial switch v in col_expr.derived { + case ^ast.Proc_Lit: + token = v^ + token_type = .Function + + if v.type != nil { + symbol.value = collect_procedure_fields(collection, v.type, v.type.params, v.type.results, package_map) + } + case ^ast.Proc_Type: + token = v^ + token_type = .Function + symbol.value = collect_procedure_fields(collection, cast(^ast.Proc_Type)col_expr, v.params, v.results, package_map) + case ^ast.Proc_Group: + token = v^ + token_type = .Function + symbol.value = SymbolProcedureGroupValue { + group = clone_type(col_expr, collection.allocator, &collection.unique_strings), + } + case ^ast.Struct_Type: + token = v^ + token_type = .Struct + symbol.value = collect_struct_fields(collection, v^, package_map) + symbol.signature = "struct" + case ^ast.Enum_Type: + token = v^ + token_type = .Enum + symbol.value = collect_enum_fields(collection, v.fields, package_map) + symbol.signature = "enum" + case ^ast.Union_Type: + token = v^ + token_type = .Union + symbol.value = collect_union_fields(collection, v^, package_map) + symbol.signature = "union" + case ^ast.Bit_Set_Type: + token = v^ + token_type = .Enum + symbol.value = collect_bitset_field(collection, v^, package_map) + symbol.signature = "bitset" + case ^ast.Map_Type: + token = v^ + token_type = .Variable + symbol.value = collect_map(collection, v^, package_map) + case ^ast.Array_Type: + token = v^ + token_type = .Variable + if v.len == nil { + symbol.value = collect_slice(collection, v^, package_map) + } else { + symbol.value = collect_array(collection, v^, package_map) + } + case ^ast.Dynamic_Array_Type: + token = v^ + token_type = .Variable + symbol.value = collect_dynamic_array(collection, v^, package_map) + case ^ast.Basic_Lit: + token = v^ + symbol.value = collect_generic(collection, col_expr, package_map, uri) + if expr.mutable { + token_type = .Variable + } else { + token_type = .Constant + } + case ^ast.Ident: + token = v^ + symbol.value = collect_generic(collection, col_expr, package_map, uri) + if expr.mutable { + token_type = .Variable + } else { + token_type = .Unresolved + } + case: // default + symbol.value = collect_generic(collection, col_expr, package_map, uri) + if expr.mutable { + token_type = .Variable + } else { + token_type = .Unresolved + } + token = expr.expr + } + + symbol.range = common.get_token_range(token, file.src) + symbol.name = get_index_unique_string(collection, name) + symbol.pkg = get_index_unique_string(collection, directory) + symbol.type = token_type + symbol.doc = common.get_doc(expr.docs, collection.allocator) + + if expr.builtin { + symbol.pkg = "$builtin" + } else { + symbol.pkg = get_index_unique_string(collection, directory) + } + + if expr.deprecated { + symbol.flags |= {.Deprecated} + } + + if expr.file_private { + symbol.flags |= {.PrivateFile} + } + + if expr.package_private { + symbol.flags |= {.PrivatePackage} + } + + when ODIN_OS == .Windows { + symbol.uri = get_index_unique_string(collection, strings.to_lower(uri, context.temp_allocator)) + } else { + symbol.uri = get_index_unique_string(collection, uri) + } + + pkg: ^map[string]Symbol + ok: bool + + if pkg, ok = &collection.packages[symbol.pkg]; !ok { + collection.packages[symbol.pkg] = make(map[string]Symbol, 100, collection.allocator) + pkg = &collection.packages[symbol.pkg] + } + + if v, ok := pkg[symbol.name]; !ok || v.name == "" { + pkg[symbol.name] = symbol + } else { + free_symbol(symbol, collection.allocator) + } + } + + return .None +} + +/* + Gets the map from import alias to absolute package directory +*/ +get_package_mapping :: proc(file: ast.File, config: ^common.Config, directory: string) -> map[string]string { + package_map := make(map[string]string, 0, context.temp_allocator) + + for imp, index in file.imports { + //collection specified + if i := strings.index(imp.fullpath, ":"); i != -1 { + collection := imp.fullpath[1:i] + p := imp.fullpath[i + 1:len(imp.fullpath) - 1] + + dir, ok := config.collections[collection] + + if !ok { + continue + } + + name: string + + when ODIN_OS == .Windows { + full := path.join(elems = {strings.to_lower(dir, context.temp_allocator), p}, allocator = context.temp_allocator) + } else { + full := path.join(elems = {dir, p}, allocator = context.temp_allocator) + } + + if imp.name.text != "" { + name = imp.name.text + } else { + name = path.base(full, false, context.temp_allocator) + } + + when ODIN_OS == .Windows { + package_map[name] = strings.to_lower(full, context.temp_allocator) + } else { + package_map[name] = full + } + } else { + name: string + + full := path.join(elems = {directory, imp.fullpath[1:len(imp.fullpath) - 1]}, allocator = context.temp_allocator) + full = path.clean(full, context.temp_allocator) + + if imp.name.text != "" { + name = imp.name.text + } else { + name = path.base(full, false, context.temp_allocator) + } + + when ODIN_OS == .Windows { + package_map[name] = strings.to_lower(full, context.temp_allocator) + } else { + package_map[name] = full + } + } + } + + return package_map +} + +/* + We can't have the alias names for packages with selector expression since that is specific to every files import, instead just replace it with the absolute + package name(absolute directory path) +*/ + +replace_package_alias :: proc { + replace_package_alias_node, + replace_package_alias_expr, + replace_package_alias_array, + replace_package_alias_dynamic_array, +} + +replace_package_alias_array :: proc(array: $A/[]^$T, package_map: map[string]string, collection: ^SymbolCollection) { + for elem, i in array { + replace_package_alias(elem, package_map, collection) + } +} + +replace_package_alias_dynamic_array :: proc(array: $A/[dynamic]^$T, package_map: map[string]string, collection: ^SymbolCollection) { + for elem, i in array { + replace_package_alias(elem, package_map, collection) + } +} + +replace_package_alias_expr :: proc(node: ^ast.Expr, package_map: map[string]string, collection: ^SymbolCollection) { + replace_package_alias_node(node, package_map, collection) +} + +replace_package_alias_node :: proc(node: ^ast.Node, package_map: map[string]string, collection: ^SymbolCollection) { + using ast + + if node == nil { + return + } + + #partial switch n in node.derived { + case ^Bad_Expr: + case ^Ident: + case ^Implicit: + case ^Undef: + case ^Basic_Lit: + case ^Basic_Directive: + case ^Ellipsis: + replace_package_alias(n.expr, package_map, collection) + case ^Tag_Expr: + replace_package_alias(n.expr, package_map, collection) + case ^Unary_Expr: + replace_package_alias(n.expr, package_map, collection) + case ^Binary_Expr: + replace_package_alias(n.left, package_map, collection) + replace_package_alias(n.right, package_map, collection) + case ^Paren_Expr: + replace_package_alias(n.expr, package_map, collection) + case ^Selector_Expr: + if _, ok := n.expr.derived.(^Ident); ok { + ident := n.expr.derived.(^Ident) + + if package_name, ok := package_map[ident.name]; ok { + ident.name = get_index_unique_string(collection, package_name) + } + } else { + replace_package_alias(n.expr, package_map, collection) + replace_package_alias(n.field, package_map, collection) + } + case ^Implicit_Selector_Expr: + replace_package_alias(n.field, package_map, collection) + case ^Slice_Expr: + replace_package_alias(n.expr, package_map, collection) + replace_package_alias(n.low, package_map, collection) + replace_package_alias(n.high, package_map, collection) + case ^Attribute: + replace_package_alias(n.elems, package_map, collection) + case ^Distinct_Type: + replace_package_alias(n.type, package_map, collection) + case ^Proc_Type: + replace_package_alias(n.params, package_map, collection) + replace_package_alias(n.results, package_map, collection) + case ^Pointer_Type: + replace_package_alias(n.elem, package_map, collection) + case ^Array_Type: + replace_package_alias(n.len, package_map, collection) + replace_package_alias(n.elem, package_map, collection) + case ^Dynamic_Array_Type: + replace_package_alias(n.elem, package_map, collection) + case ^Struct_Type: + replace_package_alias(n.poly_params, package_map, collection) + replace_package_alias(n.align, package_map, collection) + replace_package_alias(n.fields, package_map, collection) + case ^Field: + replace_package_alias(n.names, package_map, collection) + replace_package_alias(n.type, package_map, collection) + replace_package_alias(n.default_value, package_map, collection) + case ^Field_List: + replace_package_alias(n.list, package_map, collection) + case ^Field_Value: + replace_package_alias(n.field, package_map, collection) + replace_package_alias(n.value, package_map, collection) + case ^Union_Type: + replace_package_alias(n.poly_params, package_map, collection) + replace_package_alias(n.align, package_map, collection) + replace_package_alias(n.variants, package_map, collection) + case ^Enum_Type: + replace_package_alias(n.base_type, package_map, collection) + replace_package_alias(n.fields, package_map, collection) + case ^Bit_Set_Type: + replace_package_alias(n.elem, package_map, collection) + replace_package_alias(n.underlying, package_map, collection) + case ^Map_Type: + replace_package_alias(n.key, package_map, collection) + replace_package_alias(n.value, package_map, collection) + case ^Call_Expr: + replace_package_alias(n.expr, package_map, collection) + replace_package_alias(n.args, package_map, collection) + case ^Typeid_Type: + replace_package_alias(n.specialization, package_map, collection) + case ^Poly_Type: + replace_package_alias(n.type, package_map, collection) + replace_package_alias(n.specialization, package_map, collection) + case ^Proc_Group: + replace_package_alias(n.args, package_map, collection) + case ^Comp_Lit: + replace_package_alias(n.type, package_map, collection) + replace_package_alias(n.elems, package_map, collection) + case ^Helper_Type: + replace_package_alias(n.type, package_map, collection) + case ^Proc_Lit: + case ^Multi_Pointer_Type: + replace_package_alias(n.elem, package_map, collection) + case: + log.warnf("Replace Unhandled node kind: %T", n) + } +} diff --git a/src/server/completion.odin b/src/server/completion.odin index 02ed78f..40018b1 100644 --- a/src/server/completion.odin +++ b/src/server/completion.odin @@ -16,8 +16,6 @@ import "core:os" import "shared:common" -import "shared:index" -import "shared:analysis" /* TODOS: Making the signature details is really annoying and not that nice - try to see if this can be refractored. @@ -35,7 +33,6 @@ Completion_Type :: enum { } get_completion_list :: proc(document: ^common.Document, position: common.Position, completion_context: CompletionContext) -> (CompletionList, bool) { - using analysis list: CompletionList @@ -90,8 +87,7 @@ get_completion_list :: proc(document: ^common.Document, position: common.Positio ast_context.use_locals = true if symbol, ok := resolve_type_expression(&ast_context, assign.rhs[0]); ok { - - if union_value, ok := symbol.value.(index.SymbolUnionValue); ok { + if union_value, ok := symbol.value.(SymbolUnionValue); ok { completion_type = .Switch_Type } } @@ -118,11 +114,11 @@ get_completion_list :: proc(document: ^common.Document, position: common.Positio return list, true } -get_attribute_completion :: proc(ast_context: ^analysis.AstContext, position_context: ^analysis.DocumentPositionContext, list: ^CompletionList) { +get_attribute_completion :: proc(ast_context: ^AstContext, position_context: ^DocumentPositionContext, list: ^CompletionList) { } -get_directive_completion :: proc(ast_context: ^analysis.AstContext, position_context: ^analysis.DocumentPositionContext, list: ^CompletionList) { +get_directive_completion :: proc(ast_context: ^AstContext, position_context: ^DocumentPositionContext, list: ^CompletionList) { list.isIncomplete = false @@ -167,8 +163,7 @@ get_directive_completion :: proc(ast_context: ^analysis.AstContext, position_con list.items = items[:] } -get_comp_lit_completion :: proc(ast_context: ^analysis.AstContext, position_context: ^analysis.DocumentPositionContext, list: ^CompletionList) { - using analysis +get_comp_lit_completion :: proc(ast_context: ^AstContext, position_context: ^DocumentPositionContext, list: ^CompletionList) { items := make([dynamic]CompletionItem, context.temp_allocator) @@ -180,7 +175,7 @@ get_comp_lit_completion :: proc(ast_context: ^analysis.AstContext, position_cont if comp_symbol, _, ok := resolve_type_comp_literal(ast_context, position_context, symbol, position_context.parent_comp_lit); ok { ast_context.current_package = comp_symbol.pkg; #partial switch v in comp_symbol.value { - case index.SymbolStructValue: + case SymbolStructValue: for name, i in v.names { ast_context.current_package = comp_symbol.pkg @@ -206,14 +201,13 @@ get_comp_lit_completion :: proc(ast_context: ^analysis.AstContext, position_cont list.items = items[:] } -get_selector_completion :: proc(ast_context: ^analysis.AstContext, position_context: ^analysis.DocumentPositionContext, list: ^CompletionList) { - using analysis +get_selector_completion :: proc(ast_context: ^AstContext, position_context: ^DocumentPositionContext, list: ^CompletionList) { items := make([dynamic]CompletionItem, context.temp_allocator) ast_context.current_package = ast_context.document_package - selector: index.Symbol + selector: Symbol ok: bool ast_context.use_locals = true @@ -244,7 +238,7 @@ get_selector_completion :: proc(ast_context: ^analysis.AstContext, position_cont } } - if s, ok := selector.value.(index.SymbolProcedureValue); ok { + if s, ok := selector.value.(SymbolProcedureValue); ok { if len(s.return_types) == 1 { if selector, ok = resolve_type_expression(ast_context, s.return_types[0].type); !ok { return @@ -253,7 +247,7 @@ get_selector_completion :: proc(ast_context: ^analysis.AstContext, position_cont } #partial switch v in selector.value { - case index.SymbolFixedArrayValue: + case SymbolFixedArrayValue: list.isIncomplete = true containsColor := 1 @@ -348,7 +342,7 @@ get_selector_completion :: proc(ast_context: ^analysis.AstContext, position_cont append(&items, item) } } - case index.SymbolUnionValue: + case SymbolUnionValue: list.isIncomplete = false append_magic_union_completion(position_context, selector, &items) @@ -373,7 +367,7 @@ get_selector_completion :: proc(ast_context: ^analysis.AstContext, position_cont } } - case index.SymbolEnumValue: + case SymbolEnumValue: list.isIncomplete = false for name in v.names { @@ -386,7 +380,7 @@ get_selector_completion :: proc(ast_context: ^analysis.AstContext, position_cont append(&items, item) } - case index.SymbolStructValue: + case SymbolStructValue: list.isIncomplete = false for name, i in v.names { @@ -428,10 +422,10 @@ get_selector_completion :: proc(ast_context: ^analysis.AstContext, position_cont } } - case index.SymbolPackageValue: + case SymbolPackageValue: list.isIncomplete = true - if searched, ok := index.fuzzy_search(field, {selector.pkg}); ok { + if searched, ok := fuzzy_search(field, {selector.pkg}); ok { for search in searched { symbol := search.symbol @@ -458,10 +452,10 @@ get_selector_completion :: proc(ast_context: ^analysis.AstContext, position_cont log.errorf("Failed to fuzzy search, field: %v, package: %v", field, selector.pkg) return } - case index.SymbolDynamicArrayValue: + case SymbolDynamicArrayValue: list.isIncomplete = false append_magic_dynamic_array_completion(position_context, selector, &items) - case index.SymbolMapValue: + case SymbolMapValue: list.isIncomplete = false append_magic_map_completion(position_context, selector, &items) } @@ -469,14 +463,13 @@ get_selector_completion :: proc(ast_context: ^analysis.AstContext, position_cont list.items = items[:] } -get_implicit_completion :: proc(ast_context: ^analysis.AstContext, position_context: ^analysis.DocumentPositionContext, list: ^CompletionList) { - using analysis +get_implicit_completion :: proc(ast_context: ^AstContext, position_context: ^DocumentPositionContext, list: ^CompletionList) { items := make([dynamic]CompletionItem, context.temp_allocator) list.isIncomplete = false - selector: index.Symbol + selector: Symbol ast_context.use_locals = true ast_context.use_globals = true @@ -586,7 +579,7 @@ get_implicit_completion :: proc(ast_context: ^analysis.AstContext, position_cont if symbol, ok := resolve_type_expression(ast_context, position_context.parent_comp_lit.type); ok { if comp_symbol, comp_lit, ok := resolve_type_comp_literal(ast_context, position_context, symbol, position_context.parent_comp_lit); ok { - if s, ok := comp_symbol.value.(index.SymbolStructValue); ok { + if s, ok := comp_symbol.value.(SymbolStructValue); ok { ast_context.current_package = comp_symbol.pkg; //We can either have the final @@ -686,7 +679,7 @@ get_implicit_completion :: proc(ast_context: ^analysis.AstContext, position_cont } else { //procedures are the only types that can return more than one value if symbol, ok := resolve_type_expression(ast_context, elem); ok { - if procedure, ok := symbol.value.(index.SymbolProcedureValue); ok { + if procedure, ok := symbol.value.(SymbolProcedureValue); ok { if procedure.return_types == nil { return } @@ -761,7 +754,7 @@ get_implicit_completion :: proc(ast_context: ^analysis.AstContext, position_cont if call, ok := position_context.call.derived.(^ast.Call_Expr); ok { parameter_index, parameter_ok := find_position_in_call_param(ast_context, call^) if symbol, ok := resolve_type_expression(ast_context, call.expr); ok && parameter_ok { - if proc_value, ok := symbol.value.(index.SymbolProcedureValue); ok { + if proc_value, ok := symbol.value.(SymbolProcedureValue); ok { if len(proc_value.arg_types) <= parameter_index { return } @@ -786,8 +779,7 @@ get_implicit_completion :: proc(ast_context: ^analysis.AstContext, position_cont } } -get_identifier_completion :: proc(ast_context: ^analysis.AstContext, position_context: ^analysis.DocumentPositionContext, list: ^CompletionList) { - using analysis +get_identifier_completion :: proc(ast_context: ^AstContext, position_context: ^DocumentPositionContext, list: ^CompletionList) { items := make([dynamic]CompletionItem, context.temp_allocator) @@ -797,11 +789,11 @@ get_identifier_completion :: proc(ast_context: ^analysis.AstContext, position_co score: f32, snippet: Snippet_Info, name: string, - type: index.SymbolType, + type: SymbolType, doc: string, pkg: string, signature: string, - flags: index.SymbolFlags, + flags: SymbolFlags, } combined_sort_interface :: proc(s: ^[dynamic]CombinedResult) -> sort.Interface { @@ -824,11 +816,11 @@ get_identifier_completion :: proc(ast_context: ^analysis.AstContext, position_co combined := make([dynamic]CombinedResult) - lookup := "" + lookup_name := "" if position_context.identifier != nil { if ident, ok := position_context.identifier.derived.(^ast.Ident); ok { - lookup = ident.name + lookup_name = ident.name } } @@ -843,7 +835,7 @@ get_identifier_completion :: proc(ast_context: ^analysis.AstContext, position_co append(&pkgs, ast_context.document_package) append(&pkgs, "$builtin") - if results, ok := index.fuzzy_search(lookup, pkgs[:]); ok { + if results, ok := fuzzy_search(lookup_name, pkgs[:]); ok { for r in results { r := r resolve_unresolved_symbol(ast_context, &r.symbol) @@ -862,7 +854,7 @@ get_identifier_completion :: proc(ast_context: ^analysis.AstContext, position_co } } - matcher := common.make_fuzzy_matcher(lookup) + matcher := common.make_fuzzy_matcher(lookup_name) global: for k, v in ast_context.globals { if position_context.global_lhs_stmt { @@ -880,7 +872,7 @@ get_identifier_completion :: proc(ast_context: ^analysis.AstContext, position_co ast_context.use_globals = true ast_context.current_package = ast_context.document_package - ident := index.new_type(ast.Ident, v.expr.pos, v.expr.end, context.temp_allocator) + ident := new_type(ast.Ident, v.expr.pos, v.expr.end, context.temp_allocator) ident.name = k if symbol, ok := resolve_type_identifier(ast_context, ident^); ok { @@ -914,7 +906,7 @@ get_identifier_completion :: proc(ast_context: ^analysis.AstContext, position_co ast_context.use_globals = true ast_context.current_package = ast_context.document_package - ident := index.new_type(ast.Ident, {offset = local_offset}, {offset = local_offset}, context.temp_allocator) + ident := new_type(ast.Ident, {offset = local_offset}, {offset = local_offset}, context.temp_allocator) ident.name = k if symbol, ok := resolve_type_identifier(ast_context, ident^); ok { @@ -942,7 +934,7 @@ get_identifier_completion :: proc(ast_context: ^analysis.AstContext, position_co break } - symbol := index.Symbol { + symbol := Symbol { name = pkg.base, type = .Package, } @@ -961,7 +953,7 @@ get_identifier_completion :: proc(ast_context: ^analysis.AstContext, position_co } for keyword, _ in common.keyword_map { - symbol := index.Symbol { + symbol := Symbol { name = keyword, type = .Keyword, } @@ -980,7 +972,7 @@ get_identifier_completion :: proc(ast_context: ^analysis.AstContext, position_co } for keyword, _ in language_keywords { - symbol := index.Symbol { + symbol := Symbol { name = keyword, type = .Keyword, } @@ -1064,7 +1056,7 @@ get_identifier_completion :: proc(ast_context: ^analysis.AstContext, position_co list.items = items[:] } -get_package_completion :: proc(ast_context: ^analysis.AstContext, position_context: ^analysis.DocumentPositionContext, list: ^CompletionList) { +get_package_completion :: proc(ast_context: ^AstContext, position_context: ^DocumentPositionContext, list: ^CompletionList) { items := make([dynamic]CompletionItem, context.temp_allocator) @@ -1146,8 +1138,7 @@ search_for_packages :: proc(fullpath: string) -> [] string { return packages[:] } -get_type_switch_completion :: proc(ast_context: ^analysis.AstContext, position_context: ^analysis.DocumentPositionContext, list: ^CompletionList) { - using analysis +get_type_switch_completion :: proc(ast_context: ^AstContext, position_context: ^DocumentPositionContext, list: ^CompletionList) { items := make([dynamic]CompletionItem, context.temp_allocator) list.isIncomplete = false @@ -1197,7 +1188,7 @@ get_type_switch_completion :: proc(ast_context: ^analysis.AstContext, position_c list.items = items[:] } -get_core_insert_package_if_non_existent :: proc(ast_context: ^analysis.AstContext, pkg: string) -> (TextEdit, bool) { +get_core_insert_package_if_non_existent :: proc(ast_context: ^AstContext, pkg: string) -> (TextEdit, bool) { builder := strings.make_builder(context.temp_allocator) for imp in ast_context.imports { @@ -1223,7 +1214,7 @@ get_core_insert_package_if_non_existent :: proc(ast_context: ^analysis.AstContex }, true } -get_range_from_selection_start_to_dot :: proc(position_context: ^analysis.DocumentPositionContext) -> (common.Range, bool) { +get_range_from_selection_start_to_dot :: proc(position_context: ^DocumentPositionContext) -> (common.Range, bool) { if position_context.selector != nil { range := common.get_token_range(position_context.selector, position_context.file.src) range.end.character += 1 @@ -1233,7 +1224,7 @@ get_range_from_selection_start_to_dot :: proc(position_context: ^analysis.Docume return {}, false } -append_magic_map_completion :: proc(position_context: ^analysis.DocumentPositionContext, symbol: index.Symbol, items: ^[dynamic]CompletionItem) { +append_magic_map_completion :: proc(position_context: ^DocumentPositionContext, symbol: Symbol, items: ^[dynamic]CompletionItem) { range, ok := get_range_from_selection_start_to_dot(position_context) if !ok { @@ -1277,7 +1268,7 @@ append_magic_map_completion :: proc(position_context: ^analysis.DocumentPosition } -append_magic_dynamic_array_completion :: proc(position_context: ^analysis.DocumentPositionContext, symbol: index.Symbol, items: ^[dynamic]CompletionItem) { +append_magic_dynamic_array_completion :: proc(position_context: ^DocumentPositionContext, symbol: Symbol, items: ^[dynamic]CompletionItem) { range, ok := get_range_from_selection_start_to_dot(position_context) if !ok { @@ -1341,7 +1332,7 @@ append_magic_dynamic_array_completion :: proc(position_context: ^analysis.Docume } -append_magic_union_completion :: proc(position_context: ^analysis.DocumentPositionContext, symbol: index.Symbol, items: ^[dynamic]CompletionItem) { +append_magic_union_completion :: proc(position_context: ^DocumentPositionContext, symbol: Symbol, items: ^[dynamic]CompletionItem) { range, ok := get_range_from_selection_start_to_dot(position_context) if !ok { diff --git a/src/server/definition.odin b/src/server/definition.odin index cfa2c49..4447bb2 100644 --- a/src/server/definition.odin +++ b/src/server/definition.odin @@ -14,14 +14,9 @@ import "core:sort" import "core:slice" import "core:os" - import "shared:common" -import "shared:index" -import "shared:analysis" get_definition_location :: proc(document: ^common.Document, position: common.Position) -> ([]common.Location, bool) { - using analysis - locations := make([dynamic]common.Location, context.temp_allocator) location: common.Location @@ -69,7 +64,7 @@ get_definition_location :: proc(document: ^common.Document, position: common.Pos //otherwise it's the field the client wants to go to. - selector: index.Symbol + selector: Symbol ast_context.use_locals = true ast_context.use_globals = true @@ -94,16 +89,16 @@ get_definition_location :: proc(document: ^common.Document, position: common.Pos uri = selector.uri #partial switch v in selector.value { - case index.SymbolEnumValue: + case SymbolEnumValue: location.range = selector.range - case index.SymbolStructValue: + case SymbolStructValue: for name, i in v.names { if strings.compare(name, field) == 0 { location.range = common.get_token_range(v.types[i]^, document.ast.src) } } - case index.SymbolPackageValue: - if symbol, ok := index.lookup(field, selector.pkg); ok { + case SymbolPackageValue: + if symbol, ok := lookup(field, selector.pkg); ok { location.range = symbol.range uri = symbol.uri } else { diff --git a/src/server/document_links.odin b/src/server/document_links.odin index a1514b0..08330ab 100644 --- a/src/server/document_links.odin +++ b/src/server/document_links.odin @@ -16,12 +16,8 @@ import "core:os" import "shared:common" -import "shared:index" -import "shared:analysis" get_document_links :: proc(document: ^common.Document) -> ([]DocumentLink, bool) { - using analysis - links := make([dynamic]DocumentLink, 0, context.temp_allocator) for imp in document.ast.imports { diff --git a/src/server/document_symbols.odin b/src/server/document_symbols.odin index 667b94a..b442bfe 100644 --- a/src/server/document_symbols.odin +++ b/src/server/document_symbols.odin @@ -16,13 +16,8 @@ import "core:os" import "shared:common" -import "shared:index" -import "shared:analysis" - get_document_symbols :: proc(document: ^common.Document) -> []DocumentSymbol { - using analysis - ast_context := make_ast_context(document.ast, document.imports, document.package_name, document.uri.uri) get_globals(document.ast, &ast_context) diff --git a/src/server/hover.odin b/src/server/hover.odin index 4286ee8..1fdecd5 100644 --- a/src/server/hover.odin +++ b/src/server/hover.odin @@ -14,17 +14,13 @@ import "core:sort" import "core:slice" import "shared:common" -import "shared:index" -import "shared:analysis" - -write_hover_content :: proc(ast_context: ^analysis.AstContext, symbol: index.Symbol) -> MarkupContent { - using analysis +write_hover_content :: proc(ast_context: ^AstContext, symbol: Symbol) -> MarkupContent { content: MarkupContent symbol := symbol - if untyped, ok := symbol.value.(index.SymbolUntypedValue); ok { + if untyped, ok := symbol.value.(SymbolUntypedValue); ok { switch untyped.type { case .String: symbol.signature = "string" case .Bool: symbol.signature = "bool" @@ -49,8 +45,6 @@ write_hover_content :: proc(ast_context: ^analysis.AstContext, symbol: index.Sym get_hover_information :: proc(document: ^common.Document, position: common.Position) -> (Hover, bool) { - using analysis - hover := Hover { contents = { kind = "plaintext", @@ -104,7 +98,7 @@ get_hover_information :: proc(document: ^common.Document, position: common.Posit } } - selector: index.Symbol + selector: Symbol selector, ok = resolve_type_expression(&ast_context, position_context.selector) if !ok { @@ -123,7 +117,7 @@ get_hover_information :: proc(document: ^common.Document, position: common.Posit hover.range = common.get_token_range(position_context.identifier^, document.ast.src) #partial switch v in selector.value { - case index.SymbolStructValue: + case SymbolStructValue: for name, i in v.names { if strings.compare(name, field) == 0 { if symbol, ok := resolve_type_expression(&ast_context, v.types[i]); ok { @@ -135,7 +129,7 @@ get_hover_information :: proc(document: ^common.Document, position: common.Posit } } } - case index.SymbolPackageValue: + case SymbolPackageValue: if position_context.field != nil { if ident, ok := position_context.field.derived.(^ast.Ident); ok { ast_context.current_package = selector.pkg diff --git a/src/server/indexer.odin b/src/server/indexer.odin new file mode 100644 index 0000000..03b3e75 --- /dev/null +++ b/src/server/indexer.odin @@ -0,0 +1,109 @@ +package server + +import "core:odin/ast" +import "core:fmt" +import "core:strings" +import "core:log" +import "core:sort" + +/* + Concept ideas: + + static indexing: + + is responsible for implementing the indexing of symbols for static files. + + This is to solve the scaling problem of large projects with many files and symbols, as most of these files will be static. + + Possible scopes for static files: + global scope (we don't have hiarachy of namespaces and therefore only need to look at the global scope) + + Scopes not part of the indexer: + function scope, file scope, package scope(these are only relevant for dynamic active files in your project, that use the ast instead of indexing) + + Potential features: + Allow for saving the indexer, instead of recreating it everytime the lsp starts(but you would have to account for stale data). + + + dynamic indexing: + + When the user modifies files we need some smaller index to handle everything the user is using right now. This will allow + us to rebuild parts of the index without too much of a performance hit. + + This index is first searched and if nothing is found look in the static index. +*/ + + + +Indexer :: struct { + builtin_packages: [dynamic]string, + static_index: MemoryIndex, + dynamic_index: MemoryIndex, + dynamic_uri_owned: map[string]bool, +} + +indexer: Indexer + +FuzzyResult :: struct { + symbol: Symbol, + score: f32, +} + +lookup :: proc(name: string, pkg: string, loc := #caller_location) -> (Symbol, bool) { + if symbol, ok := memory_index_lookup(&indexer.dynamic_index, name, pkg); ok { + log.infof("lookup dynamic name: %v pkg: %v, symbol %v location %v", name, pkg, symbol, loc) + return symbol, true + } + + if symbol, ok := memory_index_lookup(&indexer.static_index, name, pkg); ok && symbol.uri not_in indexer.dynamic_uri_owned { + log.infof("lookup name: %v pkg: %v, symbol %v location %v", name, pkg, symbol, loc) + return symbol, true + } + + log.infof("lookup failed name: %v pkg: %v location %v", name, pkg, loc) + return {}, false +} + +fuzzy_search :: proc(name: string, pkgs: []string) -> ([]FuzzyResult, bool) { + dynamic_results, dynamic_ok := memory_index_fuzzy_search(&indexer.dynamic_index, name, pkgs) + static_results, static_ok := memory_index_fuzzy_search(&indexer.static_index, name, pkgs) + result := make([dynamic]FuzzyResult, context.temp_allocator) + + if !dynamic_ok || !static_ok { + return {}, false + } + + for r in dynamic_results { + append(&result, r) + } + + for r in static_results { + if r.symbol.uri in indexer.dynamic_uri_owned { + continue + } + + append(&result, r) + } + + sort.sort(fuzzy_sort_interface(&result)) + + return result[:], true +} + +fuzzy_sort_interface :: proc(s: ^[dynamic]FuzzyResult) -> sort.Interface { + return sort.Interface { + collection = rawptr(s), + len = proc(it: sort.Interface) -> int { + s := (^[dynamic]FuzzyResult)(it.collection) + return len(s^) + }, + less = proc(it: sort.Interface, i, j: int) -> bool { + s := (^[dynamic]FuzzyResult)(it.collection) + return s[i].score > s[j].score + }, + swap = proc(it: sort.Interface, i, j: int) { + s := (^[dynamic]FuzzyResult)(it.collection) + s[i], s[j] = s[j], s[i] + }, + } +} diff --git a/src/server/inlay_hints.odin b/src/server/inlay_hints.odin index a5be9c2..b8f4024 100644 --- a/src/server/inlay_hints.odin +++ b/src/server/inlay_hints.odin @@ -4,13 +4,9 @@ import "core:odin/ast" import "core:fmt" import "shared:common" -import "shared:analysis" -import "shared:index" //document -get_inlay_hints :: proc(document: ^common.Document, symbols: map[uintptr]index.SymbolAndNode) -> ([]InlayHint, bool) { - using analysis - +get_inlay_hints :: proc(document: ^common.Document, symbols: map[uintptr]SymbolAndNode) -> ([]InlayHint, bool) { hints := make([dynamic]InlayHint, context.temp_allocator) ast_context := make_ast_context(document.ast, document.imports, document.package_name, document.uri.uri) @@ -58,7 +54,7 @@ get_inlay_hints :: proc(document: ^common.Document, symbols: map[uintptr]index.S } if symbol_and_node, ok := symbols[cast(uintptr)node_call]; ok { - if symbol_call, ok := symbol_and_node.symbol.value.(index.SymbolProcedureValue); ok { + if symbol_call, ok := symbol_and_node.symbol.value.(SymbolProcedureValue); ok { for arg in symbol_call.arg_types { for name in arg.names { if symbol_arg_count >= len(call.args) { diff --git a/src/server/lens.odin b/src/server/lens.odin index 93827f5..abde537 100644 --- a/src/server/lens.odin +++ b/src/server/lens.odin @@ -2,7 +2,7 @@ package server import "core:odin/ast" -import "shared:analysis" + import "shared:common" @@ -21,9 +21,6 @@ CodeLens :: struct { } get_code_lenses :: proc(document: ^common.Document, position: common.Position) -> ([]CodeLens, bool) { - - using analysis - ast_context := make_ast_context(document.ast, document.imports, document.package_name, document.uri.uri) get_globals(document.ast, &ast_context) diff --git a/src/server/memory_index.odin b/src/server/memory_index.odin new file mode 100644 index 0000000..03504c1 --- /dev/null +++ b/src/server/memory_index.odin @@ -0,0 +1,65 @@ +package server + +import "core:hash" +import "core:strings" +import "core:fmt" +import "core:log" +import "core:sort" + +import "shared:common" + +/* + This is a in memory index designed for the dynamic indexing of symbols and files. + Designed for few files and should be fast at rebuilding. + + Right now the implementation is extremely naive. +*/ +MemoryIndex :: struct { + collection: SymbolCollection, +} + +make_memory_index :: proc(collection: SymbolCollection) -> MemoryIndex { + return MemoryIndex { + collection = collection, + } +} + +memory_index_lookup :: proc(index: ^MemoryIndex, name: string, pkg: string) -> (Symbol, bool) { + if pkg, ok := &index.collection.packages[pkg]; ok { + return pkg[name] + } + + return {}, false +} + +memory_index_fuzzy_search :: proc(index: ^MemoryIndex, name: string, pkgs: []string) -> ([]FuzzyResult, bool) { + symbols := make([dynamic]FuzzyResult, 0, context.temp_allocator) + + fuzzy_matcher := common.make_fuzzy_matcher(name) + + top := 20 + + for pkg in pkgs { + if pkg, ok := index.collection.packages[pkg]; ok { + for _, symbol in pkg { + if score, ok := common.fuzzy_match(fuzzy_matcher, symbol.name); ok == 1 { + result := FuzzyResult { + symbol = symbol, + score = score, + } + + append(&symbols, result) + } + } + } + } + + sort.sort(fuzzy_sort_interface(&symbols)) + + if name == "" { + return symbols[:], true + } else { + return symbols[:min(top, len(symbols))], true + } +} + diff --git a/src/server/references.odin b/src/server/references.odin new file mode 100644 index 0000000..86645b1 --- /dev/null +++ b/src/server/references.odin @@ -0,0 +1,41 @@ +package server + + +import "shared:common" + +import "core:strings" +import "core:odin/ast" +import path "core:path/slashpath" + + +Reference :: struct { + identifiers: [dynamic]^ast.Ident, + selectors: [dynamic]^ast.Selector_Expr, +} + +collect_references :: proc(collection: ^SymbolCollection, file: ast.File, uri: string) -> common.Error { + document := common.Document { + ast = file, + } + + uri, ok := common.parse_uri(uri, context.temp_allocator) + + if !ok { + return .ParseError + } + + when ODIN_OS == .Windows { + document.package_name = strings.to_lower(path.dir(document.uri.path, context.temp_allocator)) + } else { + document.package_name = path.dir(document.uri.path) + } + + document.uri = uri + + + + return {} + +} + + diff --git a/src/server/rename.odin b/src/server/rename.odin index b7d39ef..7c4629e 100644 --- a/src/server/rename.odin +++ b/src/server/rename.odin @@ -1,14 +1,11 @@ package server import "shared:common" -import "shared:analysis" import "core:log" import "core:odin/ast" get_rename :: proc(document: ^common.Document, new_text: string, position: common.Position) -> (WorkspaceEdit, bool) { - using analysis - workspace: WorkspaceEdit document_changes := make([dynamic]TextDocumentEdit, context.temp_allocator) diff --git a/src/server/requests.odin b/src/server/requests.odin index 8ffaa97..fdc5d31 100644 --- a/src/server/requests.odin +++ b/src/server/requests.odin @@ -18,7 +18,6 @@ import "core:odin/ast" import "core:odin/parser" import "shared:common" -import "shared:index" Header :: struct { content_length: int, @@ -537,10 +536,10 @@ request_initialize :: proc (params: json.Value, id: RequestId, config: ^common.C Temp index here, but should be some background thread that starts the indexing */ - index.indexer.dynamic_index = index.make_memory_index(index.make_symbol_collection(context.allocator, config)) - index.indexer.dynamic_uri_owned = make(map[string]bool, 200, context.allocator) + indexer.dynamic_index = make_memory_index(make_symbol_collection(context.allocator, config)) + indexer.dynamic_uri_owned = make(map[string]bool, 200, context.allocator) - index.build_static_index(context.allocator, config) + build_static_index(context.allocator, config) /* Add runtime package @@ -548,9 +547,9 @@ request_initialize :: proc (params: json.Value, id: RequestId, config: ^common.C if core, ok := config.collections["core"]; ok { when ODIN_OS == .Windows { - append(&index.indexer.builtin_packages, path.join(strings.to_lower(core, context.temp_allocator), "runtime")) + append(&indexer.builtin_packages, path.join(strings.to_lower(core, context.temp_allocator), "runtime")) } else { - append(&index.indexer.builtin_packages, path.join(core, "runtime")) + append(&indexer.builtin_packages, path.join(core, "runtime")) } } @@ -799,8 +798,8 @@ notification_did_save :: proc (params: json.Value, id: RequestId, config: ^commo fullpath := uri.path p := parser.Parser { - err = index.log_error_handler, - warn = index.log_warning_handler, + err = log_error_handler, + warn = log_warning_handler, flags = {.Optional_Semicolons}, } @@ -827,20 +826,20 @@ notification_did_save :: proc (params: json.Value, id: RequestId, config: ^commo log.errorf("error in parse file for indexing %v", fullpath) } - for k, v in &index.indexer.dynamic_index.collection.packages { + for k, v in &indexer.dynamic_index.collection.packages { for k2, v2 in &v { if v2.uri == uri.uri { - index.free_symbol(v2, context.allocator) + free_symbol(v2, context.allocator) v[k2] = {} } } } - if ret := index.collect_symbols(&index.indexer.dynamic_index.collection, file, uri.uri); ret != .None { + if ret := collect_symbols(&indexer.dynamic_index.collection, file, uri.uri); ret != .None { log.errorf("failed to collect symbols on save %v", ret) } - index.indexer.dynamic_uri_owned[uri.uri] = true + indexer.dynamic_uri_owned[uri.uri] = true check(uri, writer, config) @@ -878,7 +877,7 @@ request_semantic_token_full :: proc (params: json.Value, id: RequestId, config: symbols: SemanticTokens if config.enable_semantic_tokens { - resolve_entire_file(document) + resolve_entire_file_cached(document) if cache_symbols, ok := file_resolve_cache.files[document.uri.uri]; ok { symbols = get_semantic_tokens(document, range, cache_symbols) @@ -1008,7 +1007,7 @@ request_inlay_hint :: proc (params: json.Value, id: RequestId, config: ^common.C hints: []InlayHint - resolve_entire_file(document) + resolve_entire_file_cached(document) if cache_symbols, ok := file_resolve_cache.files[document.uri.uri]; ok { hints, ok = get_inlay_hints(document, cache_symbols) diff --git a/src/server/semantic_tokens.odin b/src/server/semantic_tokens.odin index 5ae011d..acbe1a5 100644 --- a/src/server/semantic_tokens.odin +++ b/src/server/semantic_tokens.odin @@ -6,8 +6,7 @@ import "core:log" import "core:fmt" import "shared:common" -import "shared:index" -import "shared:analysis" + /* Right now I might be setting the wrong types, since there is no documentation as to what should be what, and looking at other LSP there is no consistancy. @@ -78,7 +77,7 @@ SemanticTokens :: struct { SemanticTokenBuilder :: struct { current_start: int, tokens: [dynamic]u32, - symbols: map[uintptr]index.SymbolAndNode, + symbols: map[uintptr]SymbolAndNode, selector: bool, } @@ -94,9 +93,7 @@ get_tokens :: proc(builder: SemanticTokenBuilder) -> SemanticTokens { } } -get_semantic_tokens :: proc(document: ^common.Document, range: common.Range, symbols: map[uintptr]index.SymbolAndNode) -> SemanticTokens { - using analysis - +get_semantic_tokens :: proc(document: ^common.Document, range: common.Range, symbols: map[uintptr]SymbolAndNode) -> SemanticTokens { builder := make_token_builder() if document.ast.pkg_decl != nil { @@ -144,23 +141,23 @@ visit :: proc { visit_stmt, } -visit_array :: proc(array: $A/[]^$T, builder: ^SemanticTokenBuilder, ast_context: ^analysis.AstContext) { +visit_array :: proc(array: $A/[]^$T, builder: ^SemanticTokenBuilder, ast_context: ^AstContext) { for elem, i in array { visit(elem, builder, ast_context) } } -visit_dynamic_array :: proc(array: $A/[dynamic]^$T, builder: ^SemanticTokenBuilder, ast_context: ^analysis.AstContext) { +visit_dynamic_array :: proc(array: $A/[dynamic]^$T, builder: ^SemanticTokenBuilder, ast_context: ^AstContext) { for elem, i in array { visit(elem, builder, ast_context) } } -visit_stmt :: proc(node: ^ast.Stmt, builder: ^SemanticTokenBuilder, ast_context: ^analysis.AstContext) { +visit_stmt :: proc(node: ^ast.Stmt, builder: ^SemanticTokenBuilder, ast_context: ^AstContext) { visit_node(node, builder, ast_context) } -visit_node :: proc(node: ^ast.Node, builder: ^SemanticTokenBuilder, ast_context: ^analysis.AstContext) { +visit_node :: proc(node: ^ast.Node, builder: ^SemanticTokenBuilder, ast_context: ^AstContext) { using ast if node == nil { @@ -179,21 +176,21 @@ visit_node :: proc(node: ^ast.Node, builder: ^SemanticTokenBuilder, ast_context: } #partial switch v in symbol_and_node.symbol.value { - case index.SymbolPackageValue: + case SymbolPackageValue: write_semantic_node(builder, node, ast_context.file.src, .Namespace, .None) - case index.SymbolStructValue: + case SymbolStructValue: write_semantic_node(builder, node, ast_context.file.src, .Struct, .None) - case index.SymbolEnumValue: + case SymbolEnumValue: write_semantic_node(builder, node, ast_context.file.src, .Enum, .None) - case index.SymbolUnionValue: + case SymbolUnionValue: write_semantic_node(builder, node, ast_context.file.src, .Enum, .None) - case index.SymbolProcedureValue: + case SymbolProcedureValue: write_semantic_node(builder, node, ast_context.file.src, .Function, .None) - case index.SymbolProcedureGroupValue: + case SymbolProcedureGroupValue: write_semantic_node(builder, node, ast_context.file.src, .Function, .None) - case index.SymbolUntypedValue: + case SymbolUntypedValue: write_semantic_node(builder, node, ast_context.file.src, .Type, .None) - case index.SymbolBasicValue: + case SymbolBasicValue: write_semantic_node(builder, node, ast_context.file.src, .Type, .None) case: //log.errorf("Unexpected symbol value: %v", symbol.value); @@ -349,11 +346,9 @@ visit_node :: proc(node: ^ast.Node, builder: ^SemanticTokenBuilder, ast_context: } } -visit_basic_lit :: proc(basic_lit: ast.Basic_Lit, builder: ^SemanticTokenBuilder, ast_context: ^analysis.AstContext) { - using analysis - +visit_basic_lit :: proc(basic_lit: ast.Basic_Lit, builder: ^SemanticTokenBuilder, ast_context: ^AstContext) { if symbol, ok := resolve_basic_lit(ast_context, basic_lit); ok { - if untyped, ok := symbol.value.(index.SymbolUntypedValue); ok { + if untyped, ok := symbol.value.(SymbolUntypedValue); ok { switch untyped.type { case .Bool: write_semantic_token(builder, basic_lit.tok, ast_context.file.src, .Keyword, .None) @@ -366,7 +361,7 @@ visit_basic_lit :: proc(basic_lit: ast.Basic_Lit, builder: ^SemanticTokenBuilder } } -visit_value_decl :: proc(value_decl: ast.Value_Decl, builder: ^SemanticTokenBuilder, ast_context: ^analysis.AstContext) { +visit_value_decl :: proc(value_decl: ast.Value_Decl, builder: ^SemanticTokenBuilder, ast_context: ^AstContext) { using ast if value_decl.type != nil { @@ -433,7 +428,7 @@ visit_token_op :: proc(builder: ^SemanticTokenBuilder, token: tokenizer.Token, s } } -visit_proc_type :: proc(node: ^ast.Proc_Type, builder: ^SemanticTokenBuilder, ast_context: ^analysis.AstContext) { +visit_proc_type :: proc(node: ^ast.Proc_Type, builder: ^SemanticTokenBuilder, ast_context: ^AstContext) { using ast if node == nil { @@ -460,7 +455,7 @@ visit_proc_type :: proc(node: ^ast.Proc_Type, builder: ^SemanticTokenBuilder, as } } -visit_enum_fields :: proc(node: ast.Enum_Type, builder: ^SemanticTokenBuilder, ast_context: ^analysis.AstContext) { +visit_enum_fields :: proc(node: ast.Enum_Type, builder: ^SemanticTokenBuilder, ast_context: ^AstContext) { using ast if node.fields == nil { @@ -480,7 +475,7 @@ visit_enum_fields :: proc(node: ast.Enum_Type, builder: ^SemanticTokenBuilder, a } } -visit_struct_fields :: proc(node: ast.Struct_Type, builder: ^SemanticTokenBuilder, ast_context: ^analysis.AstContext) { +visit_struct_fields :: proc(node: ast.Struct_Type, builder: ^SemanticTokenBuilder, ast_context: ^AstContext) { using ast if node.fields == nil { @@ -498,7 +493,7 @@ visit_struct_fields :: proc(node: ast.Struct_Type, builder: ^SemanticTokenBuilde } } -visit_selector :: proc(selector: ^ast.Selector_Expr, builder: ^SemanticTokenBuilder, ast_context: ^analysis.AstContext) { +visit_selector :: proc(selector: ^ast.Selector_Expr, builder: ^SemanticTokenBuilder, ast_context: ^AstContext) { if _, ok := selector.expr.derived.(^ast.Selector_Expr); ok { visit_selector(cast(^ast.Selector_Expr)selector.expr, builder, ast_context) } else { @@ -511,17 +506,17 @@ visit_selector :: proc(selector: ^ast.Selector_Expr, builder: ^SemanticTokenBuil write_semantic_node(builder, selector.field, ast_context.file.src, .Method, .None) } #partial switch v in symbol_and_node.symbol.value { - case index.SymbolPackageValue: + case SymbolPackageValue: write_semantic_node(builder, selector.field, ast_context.file.src, .Namespace, .None) - case index.SymbolStructValue: + case SymbolStructValue: write_semantic_node(builder, selector.field, ast_context.file.src, .Struct, .None) - case index.SymbolEnumValue: + case SymbolEnumValue: write_semantic_node(builder, selector.field, ast_context.file.src, .Enum, .None) - case index.SymbolUnionValue: + case SymbolUnionValue: write_semantic_node(builder, selector.field, ast_context.file.src, .Enum, .None) - case index.SymbolProcedureValue: + case SymbolProcedureValue: write_semantic_node(builder, selector.field, ast_context.file.src, .Function, .None) - case index.SymbolProcedureGroupValue: + case SymbolProcedureGroupValue: write_semantic_node(builder, selector.field, ast_context.file.src, .Function, .None) } } diff --git a/src/server/signature.odin b/src/server/signature.odin index e8b3ae1..e866953 100644 --- a/src/server/signature.odin +++ b/src/server/signature.odin @@ -14,8 +14,6 @@ import "core:sort" import "core:slice" import "shared:common" -import "shared:index" -import "shared:analysis" SignatureInformationCapabilities :: struct { parameterInformation: ParameterInformationCapabilities, @@ -52,8 +50,8 @@ ParameterInformation :: struct { /* Lazily build the signature and returns from ast.Nodes */ -build_procedure_symbol_signature :: proc(symbol: ^index.Symbol) { - if value, ok := symbol.value.(index.SymbolProcedureValue); ok { +build_procedure_symbol_signature :: proc(symbol: ^Symbol) { + if value, ok := symbol.value.(SymbolProcedureValue); ok { builder := strings.make_builder(context.temp_allocator) strings.write_string(&builder, "proc") @@ -85,13 +83,13 @@ build_procedure_symbol_signature :: proc(symbol: ^index.Symbol) { } } symbol.signature = strings.to_string(builder) - } else if value, ok := symbol.value.(index.SymbolAggregateValue); ok { + } else if value, ok := symbol.value.(SymbolAggregateValue); ok { symbol.signature = "proc" } } -seperate_proc_field_arguments :: proc(procedure: ^index.Symbol) { - if value, ok := &procedure.value.(index.SymbolProcedureValue); ok { +seperate_proc_field_arguments :: proc(procedure: ^Symbol) { + if value, ok := &procedure.value.(SymbolProcedureValue); ok { types := make([dynamic]^ast.Field, context.temp_allocator) for arg, i in value.arg_types { @@ -101,7 +99,7 @@ seperate_proc_field_arguments :: proc(procedure: ^index.Symbol) { } for name in arg.names { - field : ^ast.Field = index.new_type(ast.Field, {}, {}, context.temp_allocator) + field : ^ast.Field = new_type(ast.Field, {}, {}, context.temp_allocator) field.names = make([]^ast.Expr, 1, context.temp_allocator) field.names[0] = name field.type = arg.type @@ -114,8 +112,6 @@ seperate_proc_field_arguments :: proc(procedure: ^index.Symbol) { } get_signature_information :: proc(document: ^common.Document, position: common.Position) -> (SignatureHelp, bool) { - using analysis - signature_help: SignatureHelp ast_context := make_ast_context(document.ast, document.imports, document.package_name, document.uri.uri) @@ -145,7 +141,7 @@ get_signature_information :: proc(document: ^common.Document, position: common.P } } - call: index.Symbol + call: Symbol call, ok = resolve_type_expression(&ast_context, position_context.call) if !ok { @@ -156,7 +152,7 @@ get_signature_information :: proc(document: ^common.Document, position: common.P signature_information := make([dynamic]SignatureInformation, context.temp_allocator) - if value, ok := call.value.(index.SymbolProcedureValue); ok { + if value, ok := call.value.(SymbolProcedureValue); ok { parameters := make([]ParameterInformation, len(value.arg_types), context.temp_allocator) for arg, i in value.arg_types { @@ -177,13 +173,13 @@ get_signature_information :: proc(document: ^common.Document, position: common.P parameters = parameters, } append(&signature_information, info) - } else if value, ok := call.value.(index.SymbolAggregateValue); ok { + } else if value, ok := call.value.(SymbolAggregateValue); ok { //function overloaded procedures for symbol in value.symbols { symbol := symbol - if value, ok := symbol.value.(index.SymbolProcedureValue); ok { + if value, ok := symbol.value.(SymbolProcedureValue); ok { parameters := make([]ParameterInformation, len(value.arg_types), context.temp_allocator) diff --git a/src/server/symbol.odin b/src/server/symbol.odin new file mode 100644 index 0000000..b6a3a3c --- /dev/null +++ b/src/server/symbol.odin @@ -0,0 +1,197 @@ +package server + +import "core:odin/ast" +import "core:hash" +import "core:strings" +import "core:mem" +import "core:path/filepath" +import path "core:path/slashpath" +import "core:slice" + +import "shared:common" + +SymbolAndNode :: struct { + symbol: Symbol, + node: ^ast.Node, +} + +SymbolStructValue :: struct { + names: []string, + types: []^ast.Expr, + usings: map[string]bool, + poly: ^ast.Field_List, +} + +SymbolPackageValue :: struct {} + +SymbolProcedureValue :: struct { + return_types: []^ast.Field, + arg_types: []^ast.Field, + generic: bool, +} + +SymbolProcedureGroupValue :: struct { + group: ^ast.Expr, +} + +//runtime temp symbol value +SymbolAggregateValue :: struct { + symbols: []Symbol, +} + +SymbolEnumValue :: struct { + names: []string, +} + +SymbolUnionValue :: struct { + types: []^ast.Expr, + poly: ^ast.Field_List, +} + +SymbolDynamicArrayValue :: struct { + expr: ^ast.Expr, +} + +SymbolFixedArrayValue :: struct { + len: ^ast.Expr, + expr: ^ast.Expr, +} + +SymbolSliceValue :: struct { + expr: ^ast.Expr, +} + +SymbolBasicValue :: struct { + ident: ^ast.Ident, +} + +SymbolBitSetValue :: struct { + expr: ^ast.Expr, +} + +SymbolUntypedValue :: struct { + type: enum {Integer, Float, String, Bool}, +} + +SymbolMapValue :: struct { + key: ^ast.Expr, + value: ^ast.Expr, +} + +/* + Generic symbol that is used by the indexer for any variable type(constants, defined global variables, etc), +*/ +SymbolGenericValue :: struct { + expr: ^ast.Expr, +} + +SymbolValue :: union { + SymbolStructValue, + SymbolPackageValue, + SymbolProcedureValue, + SymbolGenericValue, + SymbolProcedureGroupValue, + SymbolUnionValue, + SymbolEnumValue, + SymbolBitSetValue, + SymbolAggregateValue, + SymbolDynamicArrayValue, + SymbolFixedArrayValue, + SymbolMapValue, + SymbolSliceValue, + SymbolBasicValue, + SymbolUntypedValue, +} + +SymbolFlag :: enum { + Distinct, + Deprecated, + PrivateFile, + PrivatePackage, + Anonymous, //Usually applied to structs that are defined inline inside another struct + Variable, //Symbols that are variable, this means their value decl was mutable +} + +SymbolFlags :: bit_set[SymbolFlag] + +Symbol :: struct { + range: common.Range, //the range of the symbol in the file + uri: string, //uri of the file the symbol resides + pkg: string, //absolute directory path where the symbol resides + name: string, //name of the symbol + doc: string, + signature: string, //type signature + type: SymbolType, + value: SymbolValue, + pointers: int, //how many `^` are applied to the symbol + flags: SymbolFlags, +} + +SymbolType :: enum { + Function = 3, + Field = 5, + Variable = 6, + Package = 9, + Enum = 13, + Keyword = 14, + EnumMember = 20, + Constant = 21, + Struct = 22, + Union = 7, + Unresolved = 9999, +} + +new_clone_symbol :: proc(data: Symbol, allocator := context.allocator) -> (^Symbol) { + new_symbol := new(Symbol, allocator) + new_symbol^ = data + new_symbol.value = data.value + return new_symbol +} + +free_symbol :: proc(symbol: Symbol, allocator: mem.Allocator) { + if symbol.signature != "" && symbol.signature != "struct" && + symbol.signature != "union" && symbol.signature != "enum" && + symbol.signature != "bitset" { + delete(symbol.signature, allocator) + } + + if symbol.doc != "" { + delete(symbol.doc, allocator) + } + + switch v in symbol.value { + case SymbolProcedureValue: + common.free_ast(v.return_types, allocator) + common.free_ast(v.arg_types, allocator) + case SymbolStructValue: + delete(v.names, allocator) + common.free_ast(v.types, allocator) + case SymbolGenericValue: + common.free_ast(v.expr, allocator) + case SymbolProcedureGroupValue: + common.free_ast(v.group, allocator) + case SymbolEnumValue: + delete(v.names, allocator) + case SymbolUnionValue: + common.free_ast(v.types, allocator) + case SymbolBitSetValue: + common.free_ast(v.expr, allocator) + case SymbolDynamicArrayValue: + common.free_ast(v.expr, allocator) + case SymbolFixedArrayValue: + common.free_ast(v.expr, allocator) + common.free_ast(v.len, allocator) + case SymbolSliceValue: + common.free_ast(v.expr, allocator) + case SymbolBasicValue: + common.free_ast(v.ident, allocator) + case SymbolAggregateValue: + for symbol in v.symbols { + free_symbol(symbol, allocator) + } + case SymbolMapValue: + common.free_ast(v.key, allocator) + common.free_ast(v.value, allocator) + case SymbolUntypedValue, SymbolPackageValue: + } +}
\ No newline at end of file |