fix(extract): a C# operator declaration is a method with its own node

operator_declaration was no function kind, so `operator+` had no node and every
call and usage in its body was attributed to the file. The resolver then published
`Vec CALLS Vec2.Vec2` from a body that belongs to a named member.

The operator is now a Method named by its token, spelled like C++'s `operator+`,
through the one resolver the def side and the call scope share. Overloads merge
into one node with variants, as other C# methods do.

RED: mkc_c2_csharp_operator_plus reported scope=Vec for the constructor call. The
binary operator site `a + b` still abstains, and conversion operators, property
accessors and indexers still have no node of their own.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-11-01 20:17:29 +01:00
1 parent d6335b5239
commit 2840080f69
3 files changed
+27 -8

No files matched your search

+9 -3
View File
@@ -866,6 +866,11 @@ TSNode cbm_resolve_func_name(TSNode node, CBMLanguage lang) {
for (int tmpl_depth = 0; tmpl_depth < MAX_TEMPLATE_DEPTH; tmpl_depth++) {
const char *kind = ts_node_type(node);
// C#: an overloaded operator has no name field, its token names it.
if (lang == CBM_LANG_CSHARP && strcmp(kind, "operator_declaration") == 0) {
return ts_node_child_by_field_name(node, TS_FIELD("operator"));
}
if (lang == CBM_LANG_HASKELL && strcmp(kind, "signature") == 0) {
TSNode null_node = {0};
return null_node;
@@ -9291,9 +9296,10 @@ static TSNode resolve_method_name(TSNode child, CBMLanguage lang) {
const char *ck = ts_node_type(child);
if ((lang == CBM_LANG_C || lang == CBM_LANG_CPP || lang == CBM_LANG_CUDA ||
lang == CBM_LANG_GLSL) &&
strcmp(ck, "function_definition") == 0) {
if (((lang == CBM_LANG_C || lang == CBM_LANG_CPP || lang == CBM_LANG_CUDA ||
lang == CBM_LANG_GLSL) &&
strcmp(ck, "function_definition") == 0) ||
(lang == CBM_LANG_CSHARP && strcmp(ck, "operator_declaration") == 0)) {
return cbm_resolve_func_name(child, lang);
}
+14 -1
View File
@@ -864,7 +864,8 @@ static const char *func_kinds_ocaml[] = {"value_definition", "let_binding", NULL
static const char *func_kinds_zig[] = {"function_declaration", "test_declaration", NULL};
static const char *func_kinds_bash[] = {"function_definition", NULL};
static const char *func_kinds_erlang[] = {"function_clause", NULL};
static const char *func_kinds_csharp[] = {"method_declaration", "constructor_declaration", NULL};
static const char *func_kinds_csharp[] = {"method_declaration", "constructor_declaration",
"operator_declaration", NULL};
static const char *func_kinds_matlab[] = {"function_definition", NULL};
static const char *func_kinds_lean[] = {"def", "theorem", "instance", "abbrev", NULL};
static const char *func_kinds_form[] = {"procedure_definition", NULL};
@@ -1203,6 +1204,13 @@ char *cbm_func_name_node_text(CBMArena *a, TSNode name_node, const char *source,
*paren = '\0';
}
}
/* C# `operator +` is named by its token alone, an anonymous node; any other C# name
* is a named node. Spelled like C++'s `operator+` and `operator bool`, here so the
* def name and the call-scope QN cannot disagree. */
if (text && lang == CBM_LANG_CSHARP && !ts_node_is_named(name_node)) {
bool word = text[0] >= 'a' && text[0] <= 'z'; /* operator true, operator false */
return cbm_arena_sprintf(a, "operator%s%s", word ? " " : "", text);
}
/* Nix quoted attrpath segment: `"kebab-case" = a: a;` and `services."my.svc" = …`
* are ordinary names that merely need quoting in source. The node text carries
* the delimiters, so without this the def is named `"kebab-case"` — quotes and
@@ -1493,6 +1501,11 @@ static const char *func_node_name(CBMArena *a, TSNode func_node, const char *sou
if (!ts_node_is_null(name_node)) {
return cbm_node_text(a, name_node, source);
}
// C#: an overloaded operator has no name field, its token names it.
if (lang == CBM_LANG_CSHARP && strcmp(ts_node_type(func_node), "operator_declaration") == 0) {
TSNode op = ts_node_child_by_field_name(func_node, TS_FIELD("operator"));
return ts_node_is_null(op) ? NULL : cbm_func_name_node_text(a, op, source, lang);
}
// Arrow functions: check parent variable_declarator
if (strcmp(ts_node_type(func_node), "arrow_function") == 0) {
TSNode parent = ts_node_parent(func_node);
+4 -4
View File
@@ -369,10 +369,10 @@ static const char *cpp_assign_types[] = {"assignment_expression", "update_expres
static const char *cpp_throw_types[] = {"throw_statement", NULL};
// ==================== C# ====================
static const char *cs_func_types[] = {"destructor_declaration", "local_function_statement",
"function_pointer_type", "constructor_declaration",
"anonymous_method_expression", "lambda_expression",
"method_declaration", NULL};
static const char *cs_func_types[] = {
"destructor_declaration", "local_function_statement", "function_pointer_type",
"constructor_declaration", "anonymous_method_expression", "lambda_expression",
"method_declaration", "operator_declaration", NULL};
static const char *cs_class_types[] = {"class_declaration",
"struct_declaration",
"enum_declaration",