aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaniel Gavin <danielgavin5@hotmail.com>2021-04-27 18:22:41 +0200
committerDaniel Gavin <danielgavin5@hotmail.com>2021-04-27 18:22:41 +0200
commit07e8e34144d2c8929c8eb40779488464d77308bc (patch)
tree25a64714b5f32241b6120071a2f3748aadc326cc /src
parent6f6f95c000edb7305371f96d466731122ac54178 (diff)
work on completion tests + new symbols
Diffstat (limited to 'src')
-rw-r--r--src/index/symbol.odin76
-rw-r--r--src/server/analysis.odin280
-rw-r--r--src/server/semantic_tokens.odin4
-rw-r--r--src/testing/testing.odin38
4 files changed, 255 insertions, 143 deletions
diff --git a/src/index/symbol.odin b/src/index/symbol.odin
index 0f9069a..0b932a0 100644
--- a/src/index/symbol.odin
+++ b/src/index/symbol.odin
@@ -10,12 +10,6 @@ import "core:slice"
import "shared:common"
-/*
- Note(Daniel, how concerned should we be about keeping the memory usage low for the symbol. You could hash some of strings.
- Right now I have the unique string map in order to have strings reference the same string match.)
-
-*/
-
SymbolStructValue :: struct {
names: []string,
types: []^ast.Expr,
@@ -35,6 +29,11 @@ SymbolProcedureGroupValue :: struct {
group: ^ast.Expr,
}
+//runtime temp symbol value
+SymbolAggregateValue :: struct {
+ symbols: []Symbol,
+}
+
SymbolEnumValue :: struct {
names: []string,
}
@@ -44,10 +43,36 @@ SymbolUnionValue :: struct {
types: []^ast.Expr,
}
+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),
*/
@@ -64,18 +89,27 @@ SymbolValue :: union {
SymbolUnionValue,
SymbolEnumValue,
SymbolBitSetValue,
+ SymbolAggregateValue,
+ SymbolDynamicArrayValue,
+ SymbolFixedArrayValue,
+ SymbolMapValue,
+ SymbolSliceValue,
+ SymbolBasicValue,
+ SymbolUntypedValue,
}
Symbol :: struct {
- range: common.Range,
- uri: string,
- pkg: string,
- name: string,
- doc: string,
- signature: string,
- returns: string,
- type: SymbolType,
- value: SymbolValue,
+ range: common.Range,
+ uri: string,
+ pkg: string,
+ name: string,
+ doc: string,
+ signature: string,
+ returns: string,
+ type: SymbolType,
+ value: SymbolValue,
+ pointers: int,
+ is_distinct: bool,
}
SymbolType :: enum {
@@ -93,8 +127,8 @@ SymbolType :: enum {
free_symbol :: proc(symbol: Symbol, allocator: mem.Allocator) {
if symbol.signature != "" && symbol.signature != "struct" &&
- symbol.signature != "union" && symbol.signature != "enum" &&
- symbol.signature != "bitset" {
+ symbol.signature != "union" && symbol.signature != "enum" &&
+ symbol.signature != "bitset" {
delete(symbol.signature, allocator);
}
@@ -124,6 +158,14 @@ free_symbol :: proc(symbol: Symbol, allocator: mem.Allocator) {
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);
+ case SymbolSliceValue:
+ common.free_ast(v.expr, allocator);
+ case SymbolBasicValue:
+ common.free_ast(v.ident, allocator);
}
}
diff --git a/src/server/analysis.odin b/src/server/analysis.odin
index f769ec2..6c615e6 100644
--- a/src/server/analysis.odin
+++ b/src/server/analysis.odin
@@ -23,10 +23,6 @@ import "shared:index"
TODO(try to flatten some of the nested branches if possible)
*/
-bool_lit := "bool";
-int_lit := "int";
-string_lit := "string";
-
DocumentPositionContextHint :: enum {
Completion,
SignatureHelp,
@@ -374,9 +370,11 @@ resolve_generic_function_symbol :: proc(ast_context: ^AstContext, params: []^ast
if arg_eval, ok := resolve_type_expression(ast_context, call_expr.args[i]); ok {
+ /*
if value, ok := arg_eval.value.(index.SymbolGenericValue); ok {
resolve_poly_spec_node(ast_context, value.expr, poly.specialization, &poly_map);
}
+ */
}
}
@@ -464,6 +462,8 @@ resolve_function_overload :: proc(ast_context: ^AstContext, group: ast.Proc_Grou
call_expr := ast_context.call;
+ candidates := make([dynamic] index.Symbol, context.temp_allocator);
+
for arg_expr in group.args {
next_fn: if f, ok := resolve_type_expression(ast_context, arg_expr); ok {
@@ -480,52 +480,56 @@ resolve_function_overload :: proc(ast_context: ^AstContext, group: ast.Proc_Grou
#partial switch v in eval_call_expr.value {
case index.SymbolProcedureValue:
+ /*
case index.SymbolGenericValue:
if !common.node_equal(v.expr, procedure.arg_types[i].type) {
break next_fn;
}
+ */
case index.SymbolStructValue:
}
} else {
- //log.debug("Failed to resolve call expr");
return index.Symbol {}, false;
}
}
- //log.debugf("return overload %v", f);
+ append(&candidates, f);
+
return f, true;
}
}
}
+ if len(candidates) > 1 {
+ return index.Symbol {
+ value = index.SymbolAggregateValue {
+ symbols = candidates[:],
+ },
+ }, true;
+ } else if len(candidates) == 1 {
+ return candidates[0], true;
+ }
+
return index.Symbol {}, false;
}
resolve_basic_lit :: proc(ast_context: ^AstContext, basic_lit: ast.Basic_Lit) -> (index.Symbol, bool) {
- /*
- This is temporary, since basic lit is untyped, but either way it's going to be an ident representing a keyword.
-
- Could perhaps name them "$integer", "$float", etc.
- */
-
- ident := index.new_type(ast.Ident, basic_lit.pos, basic_lit.end, context.temp_allocator);
-
symbol := index.Symbol {
type = .Keyword,
};
+ value: index.SymbolUntypedValue;
+
if v, ok := strconv.parse_bool(basic_lit.tok.text); ok {
- ident.name = bool_lit;
+ value.type = .Bool;
} else if v, ok := strconv.parse_int(basic_lit.tok.text); ok {
- ident.name = int_lit;
+ value.type = .Integer;
} else {
- ident.name = string_lit;
+ value.type = .String;
}
- symbol.value = index.SymbolGenericValue {
- expr = ident,
- };
+ symbol.value = value;
return symbol, true;
}
@@ -551,12 +555,16 @@ resolve_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Expr) -> (i
return resolve_type_expression(ast_context, v.expr);
case Unary_Expr:
if v.op.kind == .And {
- return resolve_type_expression(ast_context, make_pointer_ast(v.expr));
+ 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:
- return resolve_type_expression(ast_context, v.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:
@@ -580,32 +588,21 @@ resolve_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Expr) -> (i
}
}
case Pointer_Type:
-
- /*
- Add flag to not pull out a type from a pointer for function overloading.
- */
-
- if v2, ok := v.elem.derived.(ast.Pointer_Type); !ok {
- return resolve_type_expression(ast_context, v.elem);
- } else if v2, ok := v.elem.derived.(ast.Type_Assertion); !ok {
- return resolve_type_expression(ast_context, v.elem);
- } else {
- return make_symbol_generic_from_ast(ast_context, node), true;
- }
-
+ 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 generic, ok := indexed.value.(index.SymbolGenericValue); ok {
-
- switch c in generic.expr.derived {
- case Array_Type:
- return resolve_type_expression(ast_context, c.elem);
- case Dynamic_Array_Type:
- return resolve_type_expression(ast_context, c.elem);
- case Map_Type:
- return resolve_type_expression(ast_context, c.value);
- }
+ #partial switch v2 in indexed.value {
+ case index.SymbolDynamicArrayValue:
+ return resolve_type_expression(ast_context, v2.expr);
+ case index.SymbolSliceValue:
+ return resolve_type_expression(ast_context, v2.expr);
+ case index.SymbolFixedArrayValue:
+ return resolve_type_expression(ast_context, v2.expr);
+ case index.SymbolMapValue:
+ return resolve_type_expression(ast_context, v2.value);
}
return index.Symbol {}, false;
@@ -663,8 +660,6 @@ resolve_type_expression :: proc(ast_context: ^AstContext, node: ^ast.Expr) -> (i
if v == nil {
return {}, false;
}
-
- return make_symbol_generic_from_ast(ast_context, node), true;
}
return index.Symbol {}, false;
@@ -769,9 +764,9 @@ resolve_type_identifier :: proc(ast_context: ^AstContext, node: ast.Ident) -> (i
case Proc_Group:
return resolve_function_overload(ast_context, v);
case Array_Type:
- return make_symbol_generic_from_ast(ast_context, local), true;
+ return make_symbol_array_from_ast(ast_context, v), true;
case Dynamic_Array_Type:
- return make_symbol_generic_from_ast(ast_context, local), true;
+ return make_symbol_dynamic_array_from_ast(ast_context, v), true;
case Call_Expr:
return resolve_type_expression(ast_context, local);
case:
@@ -806,9 +801,9 @@ resolve_type_identifier :: proc(ast_context: ^AstContext, node: ast.Ident) -> (i
case Proc_Group:
return resolve_function_overload(ast_context, v);
case Array_Type:
- return make_symbol_generic_from_ast(ast_context, global), true;
+ return make_symbol_array_from_ast(ast_context, v), true;
case Dynamic_Array_Type:
- return make_symbol_generic_from_ast(ast_context, global), true;
+ return make_symbol_dynamic_array_from_ast(ast_context, v), true;
case Call_Expr:
return resolve_type_expression(ast_context, global);
case:
@@ -827,13 +822,12 @@ resolve_type_identifier :: proc(ast_context: ^AstContext, node: ast.Ident) -> (i
type = .Keyword,
signature = node.name,
pkg = ast_context.current_package,
- value = index.SymbolGenericValue {
- expr = ident,
+ value = index.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, "/") {
@@ -845,7 +839,6 @@ resolve_type_identifier :: proc(ast_context: ^AstContext, node: ast.Ident) -> (i
return symbol, true;
} else {
-
//part of the ast so we check the imports of the document
for imp in ast_context.imports {
@@ -988,7 +981,6 @@ resolve_symbol_return :: proc(ast_context: ^AstContext, symbol: index.Symbol, ok
return symbol, true;
}
case index.SymbolStructValue:
-
//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;
@@ -997,9 +989,6 @@ resolve_symbol_return :: proc(ast_context: ^AstContext, symbol: index.Symbol, ok
} else {
return symbol, true;
}
-
- case index.SymbolGenericValue:
- return resolve_type_expression(ast_context, v.expr);
}
return symbol, true;
@@ -1009,15 +998,13 @@ fix_symbol_unresolved_type :: proc(symbol: ^index.Symbol) {
using index;
- switch v in symbol.value {
+ #partial switch v in symbol.value {
case SymbolStructValue:
symbol.type = .Struct;
case SymbolPackageValue:
symbol.type = .Package;
case SymbolProcedureValue, SymbolProcedureGroupValue:
symbol.type = .Function;
- case SymbolGenericValue:
- symbol.type = .Variable;
case SymbolUnionValue:
symbol.type = .Enum;
case SymbolEnumValue:
@@ -1098,13 +1085,13 @@ make_pointer_ast :: proc(elem: ^ast.Expr) -> ^ast.Pointer_Type {
make_bool_ast :: proc() -> ^ast.Ident {
ident := index.new_type(ast.Ident, {}, {}, context.temp_allocator);
- ident.name = bool_lit;
+ ident.name = "bool";
return ident;
}
make_int_ast :: proc() -> ^ast.Ident {
ident := index.new_type(ast.Ident, {}, {}, context.temp_allocator);
- ident.name = int_lit;
+ ident.name = "int";
return ident;
}
@@ -1148,10 +1135,9 @@ make_symbol_procedure_from_ast :: proc(ast_context: ^AstContext, n: ^ast.Node, v
range = common.get_token_range(n^, ast_context.file.src),
type = .Function,
pkg = get_package_from_node(n^),
+ name = name,
};
- symbol.name = name;
-
return_types := make([dynamic]^ast.Field, context.temp_allocator);
arg_types := make([dynamic]^ast.Field, context.temp_allocator);
@@ -1181,17 +1167,64 @@ make_symbol_procedure_from_ast :: proc(ast_context: ^AstContext, n: ^ast.Node, v
return symbol;
}
-make_symbol_generic_from_ast :: proc(ast_context: ^AstContext, expr: ^ast.Expr) -> index.Symbol {
+make_symbol_slice_from_ast :: proc(ast_context: ^AstContext, n: ^ast.Node, v: ast.Slice_Expr) -> index.Symbol {
+
+ symbol := index.Symbol {
+ range = common.get_token_range(n^, ast_context.file.src),
+ pkg = get_package_from_node(n^),
+ };
+
+ symbol.value = index.SymbolSliceValue {
+ expr = v.expr,
+ };
+
+ return symbol;
+}
+
+make_symbol_array_from_ast :: proc(ast_context: ^AstContext, v: ast.Array_Type) -> index.Symbol {
+
+ symbol := index.Symbol {
+ range = common.get_token_range(v.node, ast_context.file.src),
+ pkg = get_package_from_node(v.node),
+ };
+
+ if v.len == nil {
+ symbol.value = index.SymbolFixedArrayValue {
+ expr = v.elem,
+ len = v.len,
+ };
+ } else {
+ symbol.value = index.SymbolDynamicArrayValue {
+ expr = v.elem,
+ };
+ }
+
+ return symbol;
+}
+
+make_symbol_dynamic_array_from_ast :: proc(ast_context: ^AstContext, v: ast.Dynamic_Array_Type) -> index.Symbol {
symbol := index.Symbol {
- range = common.get_token_range(expr, ast_context.file.src),
- type = .Variable,
- signature = index.node_to_string(expr),
- pkg = get_package_from_node(expr^),
+ range = common.get_token_range(v.node, ast_context.file.src),
+ pkg = get_package_from_node(v.node),
};
- symbol.value = index.SymbolGenericValue {
- expr = expr,
+ symbol.value = index.SymbolDynamicArrayValue {
+ expr = v.elem,
+ };
+
+ return symbol;
+}
+
+make_symbol_basic_type_from_ast :: proc(ast_context: ^AstContext, n: ^ast.Node, v: ^ast.Ident) -> index.Symbol {
+
+ symbol := index.Symbol {
+ range = common.get_token_range(n^, ast_context.file.src),
+ pkg = get_package_from_node(n^),
+ };
+
+ symbol.value = index.SymbolBasicValue {
+ ident = v,
};
return symbol;
@@ -1603,59 +1636,66 @@ get_locals_for_range_stmt :: proc(file: ast.File, stmt: ast.Range_Stmt, ast_cont
}
if symbol, ok := resolve_type_expression(ast_context, stmt.expr); ok {
-
- if generic, ok := symbol.value.(index.SymbolGenericValue); ok {
-
- switch v in generic.expr.derived {
- case Map_Type:
- 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.variables[ident.name] = true;
- ast_context.in_package[ident.name] = symbol.pkg;
- }
+ #partial switch v in symbol.value {
+ case index.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.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.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.variables[ident.name] = true;
+ ast_context.in_package[ident.name] = symbol.pkg;
}
- case Dynamic_Array_Type:
- if len(stmt.vals) >= 1 {
- if ident, ok := stmt.vals[0].derived.(Ident); ok {
- store_local(ast_context, v.elem, ident.pos.offset, ident.name);
- ast_context.variables[ident.name] = true;
- ast_context.in_package[ident.name] = symbol.pkg;
- }
+ }
+ case index.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.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(), ident.pos.offset, ident.name);
- 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(), ident.pos.offset, ident.name);
+ ast_context.variables[ident.name] = true;
+ ast_context.in_package[ident.name] = symbol.pkg;
}
- case Array_Type:
- if len(stmt.vals) >= 1 {
-
- if ident, ok := stmt.vals[0].derived.(Ident); ok {
- store_local(ast_context, v.elem, ident.pos.offset, ident.name);
- ast_context.variables[ident.name] = true;
- ast_context.in_package[ident.name] = symbol.pkg;
- }
+ }
+ case index.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.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(), ident.pos.offset, ident.name);
- 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(), ident.pos.offset, ident.name);
+ ast_context.variables[ident.name] = true;
+ ast_context.in_package[ident.name] = symbol.pkg;
+ }
+ }
+ case index.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.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(), ident.pos.offset, ident.name);
+ ast_context.variables[ident.name] = true;
+ ast_context.in_package[ident.name] = symbol.pkg;
}
}
}
diff --git a/src/server/semantic_tokens.odin b/src/server/semantic_tokens.odin
index 4feced5..c9fde74 100644
--- a/src/server/semantic_tokens.odin
+++ b/src/server/semantic_tokens.odin
@@ -381,9 +381,9 @@ write_semantic_token_basic_lit :: proc(basic_lit: ast.Basic_Lit, builder: ^Seman
ident := generic.expr.derived.(ast.Ident);
- if ident.name == string_lit {
+ if ident.name == "string" {
write_semantic_node(builder, generic.expr, ast_context.file.src, .String, .None);
- } else if ident.name == int_lit {
+ } else if ident.name == "int" {
write_semantic_node(builder, generic.expr, ast_context.file.src, .Number, .None);
} else {
}
diff --git a/src/testing/testing.odin b/src/testing/testing.odin
index aaba07b..9562bc4 100644
--- a/src/testing/testing.odin
+++ b/src/testing/testing.odin
@@ -74,7 +74,7 @@ expect_signature_labels :: proc(t: ^testing.T, src: ^Source, expect_labels: []st
help, ok := server.get_signature_information(src.document, src.position);
if !ok {
- testing.errorf(t, "Failed get_signature_information");
+ testing.error(t, "Failed get_signature_information");
}
if len(expect_labels) == 0 && len(help.signatures) > 0 {
@@ -83,9 +83,9 @@ expect_signature_labels :: proc(t: ^testing.T, src: ^Source, expect_labels: []st
flags := make([]int, len(expect_labels));
- for expect_signature, i in expect_labels {
+ for expect_label, i in expect_labels {
for signature, j in help.signatures {
- if expect_signature == signature.label {
+ if expect_label == signature.label {
flags[i] += 1;
}
}
@@ -99,9 +99,39 @@ expect_signature_labels :: proc(t: ^testing.T, src: ^Source, expect_labels: []st
}
-expect_completion :: proc(t: ^testing.T, src: ^Source, dot: bool, expect_completions: []string) {
+expect_completion_details :: proc(t: ^testing.T, src: ^Source, trigger_character: string, expect_details: []string) {
setup(src);
+ completion_context := server.CompletionContext {
+ triggerCharacter = trigger_character,
+ };
+
+ completion_list, ok := server.get_completion_list(src.document, src.position, completion_context);
+
+ if !ok {
+ testing.error(t, "Failed get_completion_list");
+ }
+
+ if len(expect_details) == 0 && len(completion_list.items) > 0 {
+ testing.errorf(t, "Expected empty completion label, but received %v", completion_list.items);
+ }
+
+ flags := make([]int, len(expect_details));
+
+ for expect_detail, i in expect_details {
+ for completion, j in completion_list.items {
+ if expect_detail == completion.detail {
+ flags[i] += 1;
+ }
+ }
+ }
+
+ for flag, i in flags {
+ if flag != 1 {
+ testing.errorf(t, "Expected completion label %v, but received %v", expect_details[i], completion_list.items);
+ }
+ }
+
}
expect_hover :: proc(t: ^testing.T, src: ^Source, expect_hover_info: string) {