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, implicit_context: ^ast.Implicit, 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 { lhs: ^ast.Expr, rhs: ^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: []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, fullpath: string, recursion_counter: int, //Sometimes the ast is so malformed that it causes infinite recursion. } make_ast_context :: proc(file: ast.File, imports: []Package, package_name: string, uri: string, fullpath: 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, fullpath = fullpath, allocator = allocator, } add_local_group(&ast_context, 0) 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 SymbolMultiPointer: b_value := b.value.(SymbolMultiPointer) 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 > 200 { log.error("Recursion passed 200 attempts - giving up", node) assert(false) 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 ^Multi_Pointer_Type: return make_symbol_multi_pointer_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 ^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) case SymbolMultiPointer: symbol, ok = resolve_type_expression(ast_context, v2.expr) } 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 try_build_package(ast_context.current_package) 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, lhs: ^ast.Expr, rhs: ^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 {lhs = lhs, rhs = rhs, 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_lhs_and_rhs :: proc(ast_context: ^AstContext, offset: int, name: string) -> (^ast.Expr, ^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, nil } else { ret := local_stack[i - previous].rhs if ident, ok := ret.derived.(^ast.Ident); ok && ident.name == name { if i - previous - 1 < 0 { return nil, nil } if _, ok := ast_context.parameters[ident.name]; ok { return local_stack[i - previous].lhs, local_stack[i - previous].rhs } } return local_stack[i - previous].lhs, local_stack[i - previous].rhs; } } } } } return nil, nil } get_local :: proc(ast_context: ^AstContext, offset: int, name: string) -> ^ast.Expr { lhs, rhs := get_local_lhs_and_rhs(ast_context, offset, name) return rhs } 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 > 200 { log.error("Recursion passed 200 attempts - giving up", node) assert(false) 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 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 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 } } } 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_symbol.flags |= {.Local} 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 { //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 } 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 { if !resolve_unresolved_symbol(ast_context, &symbol) { return {}, false } } #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) if symbol.type == .Variable { ret.type = symbol.type } return ret, ok } return symbol, true } resolve_unresolved_symbol :: proc(ast_context: ^AstContext, symbol: ^Symbol) -> bool { if symbol.type != .Unresolved { return true } #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 } else { return false } } return true } resolve_location_identifier :: proc(ast_context: ^AstContext, node: ast.Ident) -> (Symbol, bool) { symbol: Symbol if local, _ := get_local_lhs_and_rhs(ast_context, node.pos.offset, node.name); local != nil { symbol.range = common.get_token_range(local, ast_context.file.src) uri := common.create_uri(local.pos.file, ast_context.allocator) symbol.pkg = ast_context.document_package symbol.uri = uri.uri 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) uri := common.create_uri(global.expr.pos.file, ast_context.allocator) symbol.pkg = ast_context.document_package symbol.uri = uri.uri 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) 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_multi_pointer_from_ast :: proc(ast_context: ^AstContext, v: ast.Multi_Pointer_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 = SymbolMultiPointer { 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" } types := make([dynamic]^ast.Expr, ast_context.allocator) for variant in v.variants { if v.poly_params != nil { append(&types, clone_type(variant, ast_context.allocator, nil)) } else { append(&types, variant) } } symbol.value = SymbolUnionValue { types = types[:], } 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 && field.type != nil { if identifier.name == "_" { continue } append(&names, identifier.name) if v.poly_params != nil { append(&types, clone_type(field.type, ast_context.allocator, nil)) } else { append(&types, field.type) } 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 ^Or_Return_Expr: get_generic_assignment(file, v.expr, ast_context, results) 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, name, 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, name, 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, u, 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, lhs, 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 binary, ok := stmt.expr.derived.(^ast.Binary_Expr); ok { if binary.op.kind == .Range_Half { if len(stmt.vals) >= 1 { if ident, ok := stmt.vals[0].derived.(^Ident); ok { store_local(ast_context, ident, make_int_ast(ast_context), ident.pos.offset, ident.name, ast_context.local_id) ast_context.variables[ident.name] = true } } } } 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, ident, 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, ident, 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, ident, 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, ident, 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, ident, 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, ident, 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, ident, 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, ident, 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 { 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, ident, cause.list[0], ident.pos.offset, ident.name, ast_context.local_id) ast_context.variables[ident.name] = true } for b in cause.body { get_locals_stmt(file, b, ast_context, document_position) } } } } } 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, name, 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, name, 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, name, 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) } ResolveReferenceFlag :: enum { None, Identifier, Call, } resolve_entire_file :: proc(document: ^Document, reference := "", flag := ResolveReferenceFlag.None, allocator := context.allocator) -> map[uintptr]SymbolAndNode { ast_context := make_ast_context(document.ast, document.imports, document.package_name, document.uri.uri, document.fullpath, 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, reference, flag, allocator) clear(&ast_context.locals) } return symbols } resolve_entire_decl :: proc(ast_context: ^AstContext, decl: ^ast.Node, symbols: ^map[uintptr]SymbolAndNode, reference := "", flag := ResolveReferenceFlag.None, 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, last_visit: ^ast.Node, resolve_flag: ResolveReferenceFlag, reference: string, } data := Visit_Data { ast_context = ast_context, symbols = symbols, scopes = make([dynamic]Scope, allocator), resolve_flag = flag, reference = reference, } 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 data.last_visit = node; //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.Proc_Lit: if v.body == nil { break } 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_proc_param_and_results(ast_context.file, v^, ast_context, &position_context) get_locals_stmt(ast_context.file, cast(^ast.Stmt)node, ast_context, &position_context) case ^ast.If_Stmt, ^ast.For_Stmt, ^ast.Range_Stmt, ^ast.Inline_Range_Stmt: 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) } if data.resolve_flag == .None { #partial switch v in node.derived { 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, } } } } else { #partial done: switch v in node.derived { case ^ast.Ident: if data.resolve_flag != .Identifier { break done } if v.name != data.reference { break done } //get_document_position() if symbol, ok := resolve_location_identifier(ast_context, v^); ok { data.symbols[cast(uintptr)node] = SymbolAndNode { node = v, symbol = symbol, } } } } 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 SymbolMultiPointer: return strings.concatenate(a = {"[^]", common.node_to_string(v.expr)}, allocator = ast_context.allocator) 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: ^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: ^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: ^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: ^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: if n.tok.text == "context" { position_context.implicit_context = n } 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 ^Multi_Pointer_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 ^ast.Or_Else_Expr: get_document_position(n.x, position_context) get_document_position(n.y, position_context) case ^ast.Or_Return_Expr: get_document_position(n.expr, position_context) case: log.errorf("Unhandled node kind: %T", n) } }