From 852cb8859f7e1f10b7adfb6df1354d8dcc29015f Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Sun, 1 Nov 2026 20:24:05 +0100 Subject: [PATCH] test(resolver): attest class mentions and cover C# operator scopes A matrix row on a class target brings that class's other roles into the harness audit. `make(): Widget` and `anon(): User` name the class they also construct, so the signature's USAGE, and USES_TYPE in Scala and Swift, become required rows at that exact token. mkc_c2_csharp_operator_overloads pins the operator scope for overloads stored as variants, for a spaced `operator -` and for the word form `operator true`. Signed-off-by: Martin Vogel --- tests/test_matrix_known_classes.c | 126 ++++++++++++++++++++++++----- tests/test_matrix_new_constructs.c | 19 +++++ 2 files changed, 126 insertions(+), 19 deletions(-) diff --git a/tests/test_matrix_known_classes.c b/tests/test_matrix_known_classes.c index d3c2d805e..5b818ae54 100644 --- a/tests/test_matrix_known_classes.c +++ b/tests/test_matrix_known_classes.c @@ -183,12 +183,6 @@ static const MKC_Golden mkc_goldens[] = { " public float Dot(Vec2 o) { return X*o.X + Y*o.Y; }", "Method", 8, 0, false}, {"mkc_c2_csharp_operator_plus", "operator+", "new Vec2(a.X + b.X, a.Y + b.Y)", "Vec2", " public Vec2(float x, float y) { X = x; Y = y; }", "Method", 4, 0, true}, - {"mkc_c2_csharp_operator_overloads", "operator+", "new Vec2(a.X + b.X)", "Vec2", - " public Vec2(float x) { X = x; }", "Method", 4, 0, true}, - {"mkc_c2_csharp_operator_overloads", "operator+", "new Vec2(a.X + b)", "Vec2", - " public Vec2(float x) { X = x; }", "Method", 4, 0, true}, - {"mkc_c2_csharp_operator_overloads", "operator-", "new Vec2(a.X - b.X)", "Vec2", - " public Vec2(float x) { X = x; }", "Method", 4, 0, true}, {"mkc_c2_scala_symbolic_method", "run", "a + b", "+", " def +(other: Vec2): Vec2 = Vec2(x + other.x, y + other.y)", "Method", 2, 0, false}, {"mkc_c2_scala_symbolic_method", "run", "(a + b).dot(b)", "dot", @@ -271,6 +265,23 @@ static const MKC_Golden mkc_goldens[] = { {"mkc_c8_kotlin_constructor_samefile", "run", "make(name).label()", "label", " fun label(): String = name", "Method", 2, 0, true}, }; +/* The harness audits a known target under every role. A class named in a signature is + * referenced there, in these languages as USAGE and as USES_TYPE, apart from the call. */ +typedef struct { + const char *test, *caller, *needle, *relation, *target, *declaration; + int line; + unsigned occurrence; +} MKC_TypeMention; +static const MKC_TypeMention mkc_type_mentions[] = { + {"mkc_c1_scala_constructor_new", "make", "Widget", "USAGE", "Widget", + "class Widget(val name: String) {", 1, 1}, + {"mkc_c1_scala_constructor_new", "make", "Widget", "USES_TYPE", "Widget", + "class Widget(val name: String) {", 1, 1}, + {"mkc_c1_swift_initializer", "make", "Widget", "USAGE", "Widget", "class Widget {", 1, 1}, + {"mkc_c1_swift_initializer", "make", "Widget", "USES_TYPE", "Widget", "class Widget {", 1, 1}, + {"mkc_c8_kotlin_constructor_samefile", "make", "Widget", "USAGE", "Widget", + "class Widget(val name: String) {", 1, 1}, +}; typedef struct { const char *test, *caller, *needle; unsigned occurrence; @@ -346,6 +357,28 @@ static bool mkc_quality(const MKC_File *original, int count, const char *test) { required += g->required; n++; } + for (size_t i = 0; i < sizeof(mkc_type_mentions) / sizeof(mkc_type_mentions[0]); i++) { + const MKC_TypeMention *g = mkc_type_mentions + i; + if (strcmp(test, g->test)) + continue; + if (n == sizeof(expected) / sizeof(expected[0])) + return false; + snprintf(ids[n], sizeof(ids[n]), "%s-%zu", test, n); + expected[n] = (RQ_Expected){.case_id = ids[n], + .site = {.file = original[0].name, + .caller = g->caller, + .needle = g->needle, + .occurrence_index = g->occurrence}, + .relation = g->relation, + .target = {.file = original[0].name, + .suffix = g->target, + .label = "Class", + .declaration_line = g->line, + .declaration_text = g->declaration}, + .mode = RQ_REQUIRED}; + required++; + n++; + } for (size_t i = 0; i < sizeof(mkc_forbidden) / sizeof(mkc_forbidden[0]); i++) { const MKC_Forbidden *g = mkc_forbidden + i; if (strcmp(test, g->test)) @@ -661,20 +694,75 @@ TEST(mkc_c2_csharp_operator_plus) { PASS(); } -/* A C# operator's body has its own node, also when the operator is overloaded. A call in it - * belongs to that operator, never to the file. */ +/* A C# operator's body has its own node, also when the operator is overloaded or spelled + * with a space or a word. A call in it belongs to that operator, never to the file. */ TEST(mkc_c2_csharp_operator_overloads) { - static const MKC_File f[] = { - {"Vec.cs", "namespace App {\n" - " struct Vec2 {\n" - " public float X;\n" - " public Vec2(float x) { X = x; }\n" - " public static Vec2 operator+(Vec2 a, Vec2 b) { return new Vec2(a.X + b.X); }\n" - " public static Vec2 operator+(Vec2 a, float b) { return new Vec2(a.X + b); }\n" - " public static Vec2 operator -(Vec2 a, Vec2 b) { return new Vec2(a.X - b.X); }\n" - " }\n" - "}\n"}}; - ASSERT_TRUE(mkc_quality(f, 1, __func__)); + static const char source[] = + "namespace App {\n" + " struct Vec2 {\n" + " public float X;\n" + " public Vec2(float x) { X = x; }\n" + " public static Vec2 operator+(Vec2 a, Vec2 b) { return new Vec2(a.X + b.X); }\n" + " public static Vec2 operator+(Vec2 a, float b) { return new Vec2(a.X + b); }\n" + " public static Vec2 operator -(Vec2 a, Vec2 b) { return new Vec2(a.X - b.X); }\n" + " public static bool operator true(Vec2 a) { return Check(a); }\n" + " static bool Check(Vec2 a) { return true; }\n" + " }\n" + "}\n"; + const MKC_File files[] = {{"Vec.cs", source}}; + const RQ_Source sources[] = {{.path = "Vec.cs", .text = source}}; + const RQ_Target ctor = {.file = "Vec.cs", + .suffix = "Vec2.Vec2", + .label = "Method", + .declaration_line = 4, + .declaration_text = " public Vec2(float x) { X = x; }"}; + const RQ_Target check = {.file = "Vec.cs", + .suffix = "Vec2.Check", + .label = "Method", + .declaration_line = 9, + .declaration_text = + " static bool Check(Vec2 a) { return true; }"}; + const RQ_Expected expected[] = { + {.case_id = "first-plus", + .site = {.file = "Vec.cs", + .caller = "Vec2.operator+", + .needle = "new Vec2(a.X + b.X)", + .caller_variant_start_line = 5, + .caller_variant_end_line = 5, + .caller_variant_declaration = " public static Vec2 operator+(Vec2 a, Vec2 " + "b) { return new Vec2(a.X + b.X); }"}, + .relation = "CALLS", + .target = ctor, + .mode = RQ_REQUIRED}, + {.case_id = "second-plus", + .site = {.file = "Vec.cs", + .caller = "Vec2.operator+", + .needle = "new Vec2(a.X + b)", + .caller_variant_start_line = 6, + .caller_variant_end_line = 6, + .caller_variant_declaration = " public static Vec2 operator+(Vec2 a, float " + "b) { return new Vec2(a.X + b); }"}, + .relation = "CALLS", + .target = ctor, + .mode = RQ_REQUIRED}, + {.case_id = "minus", + .site = {.file = "Vec.cs", .caller = "Vec2.operator-", .needle = "new Vec2(a.X - b.X)"}, + .relation = "CALLS", + .target = ctor, + .mode = RQ_REQUIRED}, + {.case_id = "word", + .site = {.file = "Vec.cs", .caller = "Vec2.operator true", .needle = "Check(a)"}, + .relation = "CALLS", + .target = check, + .mode = RQ_REQUIRED}}; + MKC_Proj project; + cbm_store_t *store = mkc_index(&project, files, 1); + RQ_Report report = {0}; + bool ok = store && rq_check_project_declarations(store, project.project, __func__, sources, 1, + expected, 4, NULL, 0, &report, stderr); + mkc_cleanup(&project, store); + ASSERT_TRUE(ok); + ASSERT_EQ(report.found, 4); PASS(); } diff --git a/tests/test_matrix_new_constructs.c b/tests/test_matrix_new_constructs.c index 33c22b011..30b213dc5 100644 --- a/tests/test_matrix_new_constructs.c +++ b/tests/test_matrix_new_constructs.c @@ -414,6 +414,25 @@ static bool mn_quality(const char *test, const char *filename, const char *conte } if (n == 1) return false; + /* The harness audits a known target under every role: `anon(): User` names the class + * it also constructs, and that signature is a USAGE of it. */ + if (!strcmp(test, "mn_record_kotlin_data_class")) { + if (n == sizeof(expected) / sizeof(expected[0])) + return false; + snprintf(ids[n], sizeof(ids[n]), "%s-%zu", test, n); + expected[n] = (RQ_Expected){ + .case_id = ids[n], + .site = {.file = filename, .caller = "anon", .needle = "User", .occurrence_index = 1}, + .relation = "USAGE", + .target = {.file = filename, + .suffix = "User", + .label = "Class", + .declaration_line = 1, + .declaration_text = "data class User(val name: String, val age: Int) {"}, + .mode = RQ_REQUIRED}; + required++; + n++; + } MN_LangProj lp; cbm_store_t *store = mn_index_files(&lp, files, 2); RQ_Report report = {0};