mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
C# looks a simple type name up namespace by namespace, innermost outward.
At each namespace N it checks the members of N, then the usings of the
namespace declaration enclosing the reference. The global namespace is
the last N, and its declaration is the compilation unit. So a type
declared without a namespace hides a type that only a compilation-unit
`using` imports, while a `using` inside a namespace body still wins over
the global namespace. Step 8b used to rank every using before the global
namespace.
- CBMCSUsing records whether a directive sits inside a namespace body
(in_namespace). cs_collect_imports tracks it per node, including the
compilation-unit siblings after a file-scoped namespace; the cross-file
entry pre-loads the pipeline import list without placement and the AST
walk upgrades it.
- cs_visible_namespaces ranks a simple name as: enclosing namespaces
innermost outward, namespace-body usings, the global namespace "", and
last the compilation-unit usings.
Ranking the global namespace that high also requires that only a type
proven global claims it. The vendored grammar does not parse a
`ref partial struct` with members, and error recovery dissolves the
enclosing namespace, so every type after it in that namespace surfaced
with no namespace. Mapped to "" those types outranked the real, imported
declaration: on dotnet/runtime 5,313 CALLS moved from implementation
files into reference assemblies (System.Runtime.cs, which declares
DefaultInterpolatedStringHandler as a `ref partial struct`).
cs_type_decl_namespace now returns "" only when the compilation unit
parsed without errors and NULL (unknown, invisible to step 8b, as on
main) otherwise; cs_top_level_namespace passes that through.
Tests:
- parallel_csharp_nested_type_and_global_namespace_binding: the
compilation-unit `using` probe now expects the global type (spec), and
a new namespace-body `using` probe expects the imported one. RED with
the previous order: cu_using=0/0.
- parallel_csharp_parse_damaged_namespace_is_not_global (sequential and
parallel). RED without the clean-parse rule: real=0 no_damaged=0 on
both paths.
- cs_lsp::cslsp_decl_namespace_global_needs_clean_parse: extractor
contract ("" for a clean global type, the namespace when found, never
"" after a parse error).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
1368 lines
47 KiB
C
1368 lines
47 KiB
C
/*
|
|
* test_cs_lsp.c — Tests for C# Light Semantic Pass.
|
|
*
|
|
* Coverage focus matches the docs/PLAN_PHP_LSP_INTEGRATION.md template
|
|
* adapted for C# semantics:
|
|
* - Compilation unit / file-scoped namespace / nested namespaces
|
|
* - using directives (regular, static, alias, global)
|
|
* - class / struct / record / interface / enum
|
|
* - inheritance + interface implementation; partial classes
|
|
* - method dispatch on typed receivers (instance, static)
|
|
* - generic type instantiation + element-type extraction (List<T>, etc.)
|
|
* - extension methods (Linq Where/Select/Count/...)
|
|
* - properties + indexers
|
|
* - constructors + primary constructors (records / C# 12 classes)
|
|
* - object creation `new T(...)`
|
|
* - var inference + foreach element typing
|
|
* - tuples + cast / as / is patterns
|
|
* - lambdas + delegate calls
|
|
* - await: Task<T> → T
|
|
* - this / base
|
|
*
|
|
* Each test exercises a specific construct against the C# tree-sitter
|
|
* grammar and asserts that the LSP emits the right resolved-call entry.
|
|
*/
|
|
|
|
#include "test_framework.h"
|
|
#include "cbm.h"
|
|
#include "../src/pipeline/lsp_resolve.h"
|
|
#include "lsp/cs_lsp.h"
|
|
#include <string.h>
|
|
|
|
/* ── Helpers ───────────────────────────────────────────────────── */
|
|
|
|
static CBMFileResult *extract_cs(const char *source) {
|
|
return cbm_extract_file(source, (int)strlen(source), CBM_LANG_CSHARP, "test", "main.cs", 0,
|
|
NULL, NULL);
|
|
}
|
|
|
|
static int find_resolved(const CBMFileResult *r, const char *callerSub, const char *calleeSub) {
|
|
for (int i = 0; i < r->resolved_calls.count; i++) {
|
|
const CBMResolvedCall *rc = &r->resolved_calls.items[i];
|
|
if (rc->caller_qn && strstr(rc->caller_qn, callerSub) && rc->callee_qn &&
|
|
strstr(rc->callee_qn, calleeSub))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int require_resolved(const CBMFileResult *r, const char *callerSub, const char *calleeSub) {
|
|
int idx = find_resolved(r, callerSub, calleeSub);
|
|
if (idx < 0) {
|
|
printf(" MISSING resolved call: caller~%s -> callee~%s (have %d)\n", callerSub, calleeSub,
|
|
r->resolved_calls.count);
|
|
for (int i = 0; i < r->resolved_calls.count; i++) {
|
|
const CBMResolvedCall *rc = &r->resolved_calls.items[i];
|
|
printf(" %s -> %s [%s %.2f]\n", rc->caller_qn ? rc->caller_qn : "(null)",
|
|
rc->callee_qn ? rc->callee_qn : "(null)",
|
|
rc->strategy ? rc->strategy : "(null)", rc->confidence);
|
|
}
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
static int count_resolved_with_strategy(const CBMFileResult *r, const char *strategy) {
|
|
int n = 0;
|
|
for (int i = 0; i < r->resolved_calls.count; i++) {
|
|
const CBMResolvedCall *rc = &r->resolved_calls.items[i];
|
|
if (rc->strategy && strcmp(rc->strategy, strategy) == 0) n++;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/* ── 1. Local class, instance method via local variable ───────── */
|
|
|
|
TEST(cslsp_local_method_via_new) {
|
|
const char *src =
|
|
"class Greeter {\n"
|
|
" public string Hello() { return \"hi\"; }\n"
|
|
"}\n"
|
|
"class Caller {\n"
|
|
" public void Go() {\n"
|
|
" var g = new Greeter();\n"
|
|
" g.Hello();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "Caller.Go", "Greeter.Hello") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 2. Method via typed parameter ────────────────────────────── */
|
|
|
|
TEST(cslsp_method_via_typed_param) {
|
|
const char *src =
|
|
"class P { public string Value() { return \"x\"; } }\n"
|
|
"class C {\n"
|
|
" public void Run(P p) { p.Value(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "P.Value") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 3. Static method on class ────────────────────────────────── */
|
|
|
|
TEST(cslsp_static_method) {
|
|
const char *src =
|
|
"class Util { public static string Fmt(string s) { return s; } }\n"
|
|
"class C {\n"
|
|
" public void Run() { Util.Fmt(\"hi\"); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "Util.Fmt") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 4. base.Method() and inherited method dispatch ───────────── */
|
|
|
|
TEST(cslsp_base_dispatch) {
|
|
const char *src =
|
|
"class Base { public virtual string Tag() { return \"b\"; } }\n"
|
|
"class Child : Base {\n"
|
|
" public string Alt() { return \"c\"; }\n"
|
|
" public void Go() {\n"
|
|
" Alt();\n"
|
|
" Tag();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "Child.Go", "Child.Alt") >= 0);
|
|
/* Tag() is inherited from Base; resolution should attribute to Base.Tag */
|
|
ASSERT(require_resolved(r, "Child.Go", "Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 5. Constructor call ──────────────────────────────────────── */
|
|
|
|
TEST(cslsp_ctor) {
|
|
const char *src =
|
|
"class Point {\n"
|
|
" public Point(int x, int y) { }\n"
|
|
"}\n"
|
|
"class C {\n"
|
|
" public void Make() { var p = new Point(1, 2); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Make", "Point") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 6. Method chaining via return type ──────────────────────── */
|
|
|
|
TEST(cslsp_method_chain) {
|
|
const char *src =
|
|
"class B {\n"
|
|
" public string Tag() { return \"b\"; }\n"
|
|
"}\n"
|
|
"class A {\n"
|
|
" public B GetB() { return new B(); }\n"
|
|
"}\n"
|
|
"class C {\n"
|
|
" public void Go() {\n"
|
|
" var a = new A();\n"
|
|
" a.GetB().Tag();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Go", "A.GetB") >= 0);
|
|
ASSERT(require_resolved(r, "C.Go", "B.Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 7. File-scoped namespace ─────────────────────────────────── */
|
|
|
|
TEST(cslsp_file_scoped_namespace) {
|
|
const char *src =
|
|
"namespace MyApp;\n"
|
|
"class P { public string V() { return \"x\"; } }\n"
|
|
"class C {\n"
|
|
" public void Run(P p) { p.V(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "P.V") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 8. Block namespace ───────────────────────────────────────── */
|
|
|
|
TEST(cslsp_block_namespace) {
|
|
const char *src =
|
|
"namespace Outer.Inner {\n"
|
|
" class P { public string V() { return \"x\"; } }\n"
|
|
" class C {\n"
|
|
" public void Run(P p) { p.V(); }\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "P.V") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 9. using directive ───────────────────────────────────────── */
|
|
|
|
TEST(cslsp_using_directive) {
|
|
/* Verifies that a `using System;` lets `Console.WriteLine` resolve. */
|
|
const char *src =
|
|
"using System;\n"
|
|
"class C {\n"
|
|
" public void Greet() { Console.WriteLine(\"hi\"); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Greet", "Console.WriteLine") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 10. using static ─────────────────────────────────────────── */
|
|
|
|
TEST(cslsp_using_static) {
|
|
const char *src =
|
|
"using static System.Math;\n"
|
|
"class C {\n"
|
|
" public double X() { return Sqrt(2.0); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.X", "Math.Sqrt") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 11. using alias ──────────────────────────────────────────── */
|
|
|
|
TEST(cslsp_using_alias) {
|
|
const char *src =
|
|
"using IL = System.Collections.Generic.List<int>;\n"
|
|
"class C {\n"
|
|
" public void Use(IL list) { list.Add(1); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Use", "List.Add") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 12. Generic List<T> + var foreach element ────────────────── */
|
|
|
|
TEST(cslsp_generic_list_foreach) {
|
|
const char *src =
|
|
"using System.Collections.Generic;\n"
|
|
"class Item { public string Tag() { return \"x\"; } }\n"
|
|
"class C {\n"
|
|
" public void Each(List<Item> items) {\n"
|
|
" foreach (var it in items) { it.Tag(); }\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Each", "Item.Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 13. List.Add resolves to System.Collections.Generic.List ── */
|
|
|
|
TEST(cslsp_list_add_resolved) {
|
|
const char *src =
|
|
"using System.Collections.Generic;\n"
|
|
"class C {\n"
|
|
" public void Build() {\n"
|
|
" var xs = new List<int>();\n"
|
|
" xs.Add(1);\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Build", "List.Add") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 14. Linq extension method dispatch ───────────────────────── */
|
|
|
|
TEST(cslsp_linq_extension) {
|
|
const char *src =
|
|
"using System.Collections.Generic;\n"
|
|
"using System.Linq;\n"
|
|
"class C {\n"
|
|
" public int CountOdd(List<int> xs) {\n"
|
|
" return xs.Count();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
/* Dispatch should hit Enumerable.Count, an extension method. */
|
|
ASSERT(require_resolved(r, "C.CountOdd", "Count") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 15. Property access typing ────────────────────────────────── */
|
|
|
|
TEST(cslsp_property_chain) {
|
|
const char *src =
|
|
"class Inner { public string Run() { return \"x\"; } }\n"
|
|
"class Outer {\n"
|
|
" public Inner I { get; set; }\n"
|
|
"}\n"
|
|
"class C {\n"
|
|
" public void Go(Outer o) { o.I.Run(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Go", "Inner.Run") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 16. Auto-property field collection ────────────────────────── */
|
|
|
|
TEST(cslsp_auto_property_field) {
|
|
/* Property `Name : string` is read via member access, then ToLower()
|
|
* is invoked on it — a real method call we should resolve. */
|
|
const char *src =
|
|
"class C {\n"
|
|
" public string Name { get; set; }\n"
|
|
" public string Lower() { return Name.ToLower(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Lower", "ToLower") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 17. Async / Task<T> unwrap ────────────────────────────────── */
|
|
|
|
TEST(cslsp_async_await) {
|
|
const char *src =
|
|
"using System.Threading.Tasks;\n"
|
|
"class Loader {\n"
|
|
" public Task<string> LoadAsync() { return Task.FromResult(\"x\"); }\n"
|
|
"}\n"
|
|
"class C {\n"
|
|
" public async Task Run(Loader l) {\n"
|
|
" var s = await l.LoadAsync();\n"
|
|
" s.Trim();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "Loader.LoadAsync") >= 0);
|
|
/* `s` is Task<string> awaited → string; s.Trim() resolves on String. */
|
|
ASSERT(require_resolved(r, "C.Run", "Trim") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 18. Records: primary constructor parameters ──────────────── */
|
|
|
|
TEST(cslsp_record_primary_ctor) {
|
|
const char *src =
|
|
"record Person(string Name, int Age) {\n"
|
|
" public string Greet() { return Name; }\n"
|
|
"}\n"
|
|
"class C {\n"
|
|
" public string Hello(Person p) { return p.Greet(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Hello", "Person.Greet") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 19. Pattern: x is T y → bind y as T ──────────────────────── */
|
|
|
|
TEST(cslsp_pattern_is_type) {
|
|
/* This test verifies that patterns don't break resolution — full
|
|
* pattern narrowing is best-effort. */
|
|
const char *src =
|
|
"class A { public string Tag() { return \"a\"; } }\n"
|
|
"class C {\n"
|
|
" public void Go(object o) {\n"
|
|
" if (o is A a) { a.Tag(); }\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
/* We don't require resolution here because `o is A a` declarator
|
|
* binding isn't fully implemented. The test verifies no crash. */
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 20. Cast: (T)x ─────────────────────────────────────────────── */
|
|
|
|
TEST(cslsp_cast) {
|
|
const char *src =
|
|
"class A { public string Tag() { return \"a\"; } }\n"
|
|
"class C {\n"
|
|
" public void Go(object o) {\n"
|
|
" var a = (A)o;\n"
|
|
" a.Tag();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Go", "A.Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 21. as expression ────────────────────────────────────────── */
|
|
|
|
TEST(cslsp_as_expression) {
|
|
const char *src =
|
|
"class A { public string Tag() { return \"a\"; } }\n"
|
|
"class C {\n"
|
|
" public void Go(object o) {\n"
|
|
" var a = o as A;\n"
|
|
" a.Tag();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Go", "A.Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 22. Generic method type-parameter dispatch ──────────────── */
|
|
|
|
TEST(cslsp_generic_method) {
|
|
const char *src =
|
|
"class Box<T> { public T Get() { return default; } }\n"
|
|
"class C {\n"
|
|
" public string Use(Box<string> b) {\n"
|
|
" return b.Get();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Use", "Box.Get") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 23. Inherited from base via field type ───────────────────── */
|
|
|
|
TEST(cslsp_inherited_method_via_field) {
|
|
const char *src =
|
|
"class Base { public string Common() { return \"c\"; } }\n"
|
|
"class Derived : Base { }\n"
|
|
"class C {\n"
|
|
" Derived d;\n"
|
|
" public void Go() { d.Common(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Go", "Common") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 24. nameof (returns String) ──────────────────────────────── */
|
|
|
|
TEST(cslsp_nameof) {
|
|
const char *src =
|
|
"class C {\n"
|
|
" public string Run() {\n"
|
|
" var s = nameof(System);\n"
|
|
" return s.ToLower();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "ToLower") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 25. typeof ────────────────────────────────────────────────── */
|
|
|
|
TEST(cslsp_typeof) {
|
|
const char *src =
|
|
"class C {\n"
|
|
" public string Run() {\n"
|
|
" var t = typeof(string);\n"
|
|
" return t.ToString();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
/* typeof returns System.Type; ToString resolves on Object/Type. */
|
|
ASSERT(require_resolved(r, "C.Run", "ToString") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 26. String literal calls ─────────────────────────────────── */
|
|
|
|
TEST(cslsp_string_literal_method) {
|
|
const char *src =
|
|
"class C {\n"
|
|
" public string Run() {\n"
|
|
" return \"hello\".ToUpper();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "ToUpper") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 27. Null-conditional `?.` ─────────────────────────────────── */
|
|
|
|
TEST(cslsp_null_conditional) {
|
|
const char *src =
|
|
"class A { public string Tag() { return \"a\"; } }\n"
|
|
"class C {\n"
|
|
" public void Go(A a) { a?.Tag(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
/* tree-sitter-c-sharp may emit ?. as conditional_access_expression;
|
|
* resolution should still yield A.Tag. We don't strictly require it. */
|
|
(void)r;
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 28. Tuple expression typing ──────────────────────────────── */
|
|
|
|
TEST(cslsp_tuple_expression) {
|
|
const char *src =
|
|
"class C {\n"
|
|
" public (int, string) Pair() { return (1, \"x\"); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 29. Top-level statements ─────────────────────────────────── */
|
|
|
|
TEST(cslsp_top_level_statements) {
|
|
const char *src =
|
|
"using System;\n"
|
|
"Console.WriteLine(\"hi\");\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
/* Emission requires an enclosing func QN; we synth from module_qn. */
|
|
(void)r;
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 30. Interface dispatch ───────────────────────────────────── */
|
|
|
|
TEST(cslsp_interface_dispatch) {
|
|
const char *src =
|
|
"interface IFoo { string Tag(); }\n"
|
|
"class Impl : IFoo { public string Tag() { return \"x\"; } }\n"
|
|
"class C {\n"
|
|
" public void Use(IFoo f) { f.Tag(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Use", "Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 31. Enum: bare values ────────────────────────────────────── */
|
|
|
|
TEST(cslsp_enum_does_not_crash) {
|
|
const char *src =
|
|
"enum Color { Red, Green, Blue }\n"
|
|
"class C {\n"
|
|
" public void Use() { var c = Color.Red; }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
(void)r;
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 32. Object initializer: `new T { X = ... }` ──────────────── */
|
|
|
|
TEST(cslsp_object_initializer) {
|
|
const char *src =
|
|
"class P { public string Name { get; set; } public string V() { return Name; } }\n"
|
|
"class C {\n"
|
|
" public string Make() {\n"
|
|
" var p = new P { Name = \"a\" };\n"
|
|
" return p.V();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Make", "P.V") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 33. var with explicit constructor ────────────────────────── */
|
|
|
|
TEST(cslsp_var_assigned_object) {
|
|
const char *src =
|
|
"class A { public string Tag() { return \"a\"; } }\n"
|
|
"class C {\n"
|
|
" public void Run() {\n"
|
|
" var a = new A();\n"
|
|
" a.Tag();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "A.Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 34. Multiple return type via async generic Task<T> ───────── */
|
|
|
|
TEST(cslsp_task_generic_unwrap) {
|
|
const char *src =
|
|
"using System.Threading.Tasks;\n"
|
|
"class S { public string Tag() { return \"x\"; } }\n"
|
|
"class A {\n"
|
|
" public Task<S> GetAsync() { return Task.FromResult(new S()); }\n"
|
|
"}\n"
|
|
"class C {\n"
|
|
" public async Task Run(A a) {\n"
|
|
" var s = await a.GetAsync();\n"
|
|
" s.Tag();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "A.GetAsync") >= 0);
|
|
ASSERT(require_resolved(r, "C.Run", "S.Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 35. Field collected via field_declaration ────────────────── */
|
|
|
|
TEST(cslsp_field_typed) {
|
|
const char *src =
|
|
"class A { public string Tag() { return \"a\"; } }\n"
|
|
"class C {\n"
|
|
" private A a;\n"
|
|
" public void Use() { a.Tag(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Use", "Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 36. this. accessor ───────────────────────────────────────── */
|
|
|
|
TEST(cslsp_this_accessor) {
|
|
const char *src =
|
|
"class C {\n"
|
|
" public string V() { return \"v\"; }\n"
|
|
" public string Run() { return this.V(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "C.V") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 37. Conditional expression (a ? b : c) typing ─────────────── */
|
|
|
|
TEST(cslsp_conditional_expression) {
|
|
const char *src =
|
|
"class A { public string Tag() { return \"a\"; } }\n"
|
|
"class C {\n"
|
|
" public void Run(bool b, A x, A y) {\n"
|
|
" var a = b ? x : y;\n"
|
|
" a.Tag();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "A.Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 38. Element access: arr[i] ───────────────────────────────── */
|
|
|
|
TEST(cslsp_array_element_access) {
|
|
const char *src =
|
|
"using System.Collections.Generic;\n"
|
|
"class A { public string Tag() { return \"a\"; } }\n"
|
|
"class C {\n"
|
|
" public void Use(List<A> xs) { xs[0].Tag(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Use", "A.Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 39. Predefined types alias (int → System.Int32) ──────────── */
|
|
|
|
TEST(cslsp_predefined_int_parse) {
|
|
const char *src =
|
|
"class C {\n"
|
|
" public int Run(string s) { return int.Parse(s); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "Parse") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 40. Multiple usings + cross-namespace dispatch ──────────── */
|
|
|
|
TEST(cslsp_multi_usings) {
|
|
const char *src =
|
|
"using System;\n"
|
|
"using System.Collections.Generic;\n"
|
|
"using System.Linq;\n"
|
|
"class C {\n"
|
|
" public int Run() {\n"
|
|
" var xs = new List<int>();\n"
|
|
" return xs.Count();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "Count") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 41. Indexer call ─────────────────────────────────────────── */
|
|
|
|
TEST(cslsp_indexer) {
|
|
const char *src =
|
|
"class C {\n"
|
|
" public string this[int i] { get { return \"\"; } }\n"
|
|
" public string Get(int i) { return this[i]; }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 42. Lambda inside method body ────────────────────────────── */
|
|
|
|
TEST(cslsp_lambda_body) {
|
|
const char *src =
|
|
"using System.Collections.Generic;\n"
|
|
"using System.Linq;\n"
|
|
"class A { public int Score() { return 42; } }\n"
|
|
"class C {\n"
|
|
" public int Sum(List<A> xs) {\n"
|
|
" return xs.Sum(x => x.Score());\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
/* Sum is the extension; we don't require the lambda body to resolve. */
|
|
ASSERT(require_resolved(r, "C.Sum", "Sum") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 43. switch expression returns last arm type ──────────────── */
|
|
|
|
TEST(cslsp_switch_expression) {
|
|
const char *src =
|
|
"class A { public string Tag() { return \"a\"; } }\n"
|
|
"class C {\n"
|
|
" public void Run(int x) {\n"
|
|
" var v = x switch {\n"
|
|
" 1 => new A(),\n"
|
|
" _ => new A(),\n"
|
|
" };\n"
|
|
" v.Tag();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 44. struct method ────────────────────────────────────────── */
|
|
|
|
TEST(cslsp_struct_method) {
|
|
const char *src =
|
|
"struct Point {\n"
|
|
" public int Mag() { return 0; }\n"
|
|
"}\n"
|
|
"class C {\n"
|
|
" public int Use(Point p) { return p.Mag(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Use", "Point.Mag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 45. Generic Dictionary<K, V> ─────────────────────────────── */
|
|
|
|
TEST(cslsp_dictionary) {
|
|
const char *src =
|
|
"using System.Collections.Generic;\n"
|
|
"class C {\n"
|
|
" public void Use() {\n"
|
|
" var d = new Dictionary<string, int>();\n"
|
|
" d.Add(\"k\", 1);\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Use", "Dictionary.Add") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 46. namespace nesting and same-namespace lookup ──────────── */
|
|
|
|
TEST(cslsp_same_ns_lookup) {
|
|
const char *src =
|
|
"namespace MyApp {\n"
|
|
" class Util { public static string Trim(string s) { return s; } }\n"
|
|
" class Cli { public string Go() { return Util.Trim(\"x\"); } }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "Cli.Go", "Util.Trim") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 47. Inheritance chain method dispatch ────────────────────── */
|
|
|
|
TEST(cslsp_inheritance_chain) {
|
|
const char *src =
|
|
"class A { public string Common() { return \"a\"; } }\n"
|
|
"class B : A { }\n"
|
|
"class C : B { }\n"
|
|
"class Caller {\n"
|
|
" public string Use(C c) { return c.Common(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "Caller.Use", "Common") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 48. expression-bodied method ─────────────────────────────── */
|
|
|
|
TEST(cslsp_expression_bodied) {
|
|
const char *src =
|
|
"class A { public string Tag() => \"a\"; }\n"
|
|
"class C {\n"
|
|
" public string Run(A a) => a.Tag();\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "A.Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 49. Negative: textual call to non-existent method ─────────── */
|
|
|
|
TEST(cslsp_unresolved_returns_no_resolved) {
|
|
const char *src =
|
|
"class C {\n"
|
|
" public void Run() { Mystery(\"x\"); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
/* No specific resolved call expected — Mystery isn't registered. */
|
|
int idx = find_resolved(r, "C.Run", "Mystery");
|
|
/* If it's resolved with a low-confidence fallback, that's OK; if not
|
|
* resolved at all, also OK. We just confirm no crash. */
|
|
(void)idx;
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 50. Quality: count resolved with high-confidence strategies ── */
|
|
|
|
TEST(cslsp_quality_indicator) {
|
|
/* This synthetic file exercises 5 distinct lookups; verify that at
|
|
* least 4/5 produce high-confidence (>= 0.9) entries — our 90%-parity
|
|
* goal. */
|
|
const char *src =
|
|
"using System;\n"
|
|
"using System.Collections.Generic;\n"
|
|
"class S { public string V() { return \"x\"; } }\n"
|
|
"class C {\n"
|
|
" public void Run(S s, List<S> xs) {\n"
|
|
" s.V();\n"
|
|
" xs.Add(s);\n"
|
|
" Console.WriteLine(\"hi\");\n"
|
|
" var p = new S();\n"
|
|
" p.V();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
int high = 0;
|
|
int total_named = 0;
|
|
for (int i = 0; i < r->resolved_calls.count; i++) {
|
|
const CBMResolvedCall *rc = &r->resolved_calls.items[i];
|
|
if (!rc->callee_qn) continue;
|
|
if (rc->confidence >= 0.9f) high++;
|
|
total_named++;
|
|
}
|
|
/* Expect resolutions for: s.V, xs.Add, Console.WriteLine, S.ctor, p.V → 5 */
|
|
ASSERT(total_named >= 4);
|
|
ASSERT(high >= 4); /* >= 80% high confidence */
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 51. Return-type fallback when method registered with return ── */
|
|
|
|
TEST(cslsp_return_type_fallback) {
|
|
/* Confirms cs_parse_return_type_text + registry refinement chain. */
|
|
const char *src =
|
|
"class B { public string Tag() { return \"b\"; } }\n"
|
|
"class A { public B GetB() { return new B(); } }\n"
|
|
"class C {\n"
|
|
" public void Run(A a) { a.GetB().Tag(); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "A.GetB") >= 0);
|
|
ASSERT(require_resolved(r, "C.Run", "B.Tag") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 52. Multiple namespaces in same file ─────────────────────── */
|
|
|
|
TEST(cslsp_multi_namespace_same_file) {
|
|
const char *src =
|
|
"namespace A {\n"
|
|
" public class P { public string V() { return \"p\"; } }\n"
|
|
"}\n"
|
|
"namespace B {\n"
|
|
" using A;\n"
|
|
" public class C {\n"
|
|
" public void Run(P p) { p.V(); }\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "P.V") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 53. Local function ────────────────────────────────────────── */
|
|
|
|
TEST(cslsp_local_function) {
|
|
const char *src =
|
|
"class C {\n"
|
|
" public string Run() {\n"
|
|
" string Helper(string s) { return s; }\n"
|
|
" return Helper(\"x\");\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 54. params keyword (variadic) — coverage smoke ──────────── */
|
|
|
|
TEST(cslsp_params_smoke) {
|
|
const char *src =
|
|
"class C {\n"
|
|
" public void Sum(params int[] xs) { }\n"
|
|
" public void Run() { Sum(1, 2, 3); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "C.Sum") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 55. ref/out parameters ──────────────────────────────────── */
|
|
|
|
TEST(cslsp_ref_out_param) {
|
|
const char *src =
|
|
"class C {\n"
|
|
" public bool TryGet(string key, out string value) { value = \"\"; return true; }\n"
|
|
" public void Run() { string v; TryGet(\"k\", out v); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "C.TryGet") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 56. global using (smoke) ─────────────────────────────────── */
|
|
|
|
TEST(cslsp_global_using_smoke) {
|
|
const char *src =
|
|
"global using System;\n"
|
|
"class C {\n"
|
|
" public void Run() { Console.WriteLine(\"hi\"); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "Console.WriteLine") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 57. Strategy distribution sanity ─────────────────────────── */
|
|
|
|
TEST(cslsp_strategy_distribution) {
|
|
const char *src =
|
|
"using System;\n"
|
|
"class S { public string Tag() { return \"s\"; } }\n"
|
|
"class C {\n"
|
|
" public void Run(S s) {\n"
|
|
" s.Tag();\n"
|
|
" Console.WriteLine(\"x\");\n"
|
|
" var s2 = new S();\n"
|
|
" s2.Tag();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
int typed = count_resolved_with_strategy(r, "cs_method_typed");
|
|
int statics = count_resolved_with_strategy(r, "cs_static_typed");
|
|
/* At least one of each. */
|
|
ASSERT(typed >= 1);
|
|
ASSERT(statics >= 1);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 58. Console.WriteLine via using static ───────────────────── */
|
|
|
|
TEST(cslsp_using_static_console) {
|
|
const char *src =
|
|
"using static System.Console;\n"
|
|
"class C {\n"
|
|
" public void Run() { WriteLine(\"hi\"); }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "Console.WriteLine") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 59. StringBuilder fluent chain ──────────────────────────── */
|
|
|
|
TEST(cslsp_stringbuilder_chain) {
|
|
const char *src =
|
|
"using System.Text;\n"
|
|
"class C {\n"
|
|
" public string Build() {\n"
|
|
" var sb = new StringBuilder();\n"
|
|
" return sb.Append(\"a\").Append(\"b\").ToString();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Build", "StringBuilder.Append") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* ── 60. HttpClient async chain ──────────────────────────────── */
|
|
|
|
TEST(cslsp_httpclient_chain) {
|
|
const char *src =
|
|
"using System.Net.Http;\n"
|
|
"using System.Threading.Tasks;\n"
|
|
"class C {\n"
|
|
" public async Task<string> Run(HttpClient c) {\n"
|
|
" var s = await c.GetStringAsync(\"u\");\n"
|
|
" return s.Trim();\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "C.Run", "HttpClient.GetStringAsync") >= 0);
|
|
ASSERT(require_resolved(r, "C.Run", "Trim") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* Two typed receivers can invoke the same method leaf in one caller. Each
|
|
* parser carrier and type-aware semantic result must retain the full,
|
|
* occurrence-specific call span so the bridge cannot join both calls to the
|
|
* first Render target (or let a legacy zero-span record hijack either one). */
|
|
TEST(cslsp_ordinary_same_leaf_calls_join_by_exact_site) {
|
|
static const char source[] = "class Alpha { public void Render() {} }\n"
|
|
"class Beta { public void Render() {} }\n"
|
|
"class OccurrenceProbe {\n"
|
|
" public void Run(Alpha alpha, Beta beta) {\n"
|
|
" alpha.Render();\n"
|
|
" beta.Render();\n"
|
|
" }\n"
|
|
"}\n";
|
|
static const char alpha_text[] = "alpha.Render()";
|
|
static const char beta_text[] = "beta.Render()";
|
|
|
|
const char *alpha_site = strstr(source, alpha_text);
|
|
const char *beta_site = strstr(source, beta_text);
|
|
ASSERT_NOT_NULL(alpha_site);
|
|
ASSERT_NOT_NULL(beta_site);
|
|
const uint32_t alpha_start = (uint32_t)(alpha_site - source);
|
|
const uint32_t alpha_end = alpha_start + (uint32_t)strlen(alpha_text);
|
|
const uint32_t beta_start = (uint32_t)(beta_site - source);
|
|
const uint32_t beta_end = beta_start + (uint32_t)strlen(beta_text);
|
|
|
|
CBMFileResult *r = extract_cs(source);
|
|
ASSERT_NOT_NULL(r);
|
|
|
|
const CBMCall *alpha_call = NULL;
|
|
const CBMCall *beta_call = NULL;
|
|
int render_carriers = 0;
|
|
int zero_span_carriers = 0;
|
|
for (int i = 0; i < r->calls.count; i++) {
|
|
const CBMCall *call = &r->calls.items[i];
|
|
if (!call->enclosing_func_qn || !strstr(call->enclosing_func_qn, "OccurrenceProbe.Run") ||
|
|
!call->callee_name || !strstr(call->callee_name, "Render")) {
|
|
continue;
|
|
}
|
|
render_carriers++;
|
|
if (call->site_end_byte <= call->site_start_byte)
|
|
zero_span_carriers++;
|
|
if (call->site_start_byte == alpha_start && call->site_end_byte == alpha_end)
|
|
alpha_call = call;
|
|
if (call->site_start_byte == beta_start && call->site_end_byte == beta_end)
|
|
beta_call = call;
|
|
}
|
|
|
|
ASSERT_EQ(render_carriers, 2);
|
|
ASSERT_EQ(zero_span_carriers, 0);
|
|
ASSERT_NOT_NULL(alpha_call);
|
|
ASSERT_NOT_NULL(beta_call);
|
|
ASSERT_TRUE(alpha_call != beta_call);
|
|
|
|
const CBMResolvedCall *alpha_semantic = NULL;
|
|
const CBMResolvedCall *beta_semantic = NULL;
|
|
int render_semantics = 0;
|
|
int zero_span_hijackers = 0;
|
|
for (int i = 0; i < r->resolved_calls.count; i++) {
|
|
const CBMResolvedCall *rc = &r->resolved_calls.items[i];
|
|
if (rc->kind != CBM_RESOLVED_INVOCATION || rc->confidence <= 0.0f || !rc->caller_qn ||
|
|
!strstr(rc->caller_qn, "OccurrenceProbe.Run") || !rc->callee_qn ||
|
|
!strstr(rc->callee_qn, ".Render")) {
|
|
continue;
|
|
}
|
|
render_semantics++;
|
|
if (rc->site_end_byte <= rc->site_start_byte)
|
|
zero_span_hijackers++;
|
|
if (strstr(rc->callee_qn, "Alpha.Render") && rc->site_start_byte == alpha_start &&
|
|
rc->site_end_byte == alpha_end) {
|
|
alpha_semantic = rc;
|
|
}
|
|
if (strstr(rc->callee_qn, "Beta.Render") && rc->site_start_byte == beta_start &&
|
|
rc->site_end_byte == beta_end) {
|
|
beta_semantic = rc;
|
|
}
|
|
}
|
|
|
|
ASSERT_EQ(render_semantics, 2);
|
|
ASSERT_EQ(zero_span_hijackers, 0);
|
|
ASSERT_NOT_NULL(alpha_semantic);
|
|
ASSERT_NOT_NULL(beta_semantic);
|
|
ASSERT_TRUE(alpha_semantic != beta_semantic);
|
|
|
|
const CBMResolvedCall *alpha_join =
|
|
cbm_pipeline_find_lsp_resolution(&r->resolved_calls, alpha_call, false);
|
|
const CBMResolvedCall *beta_join =
|
|
cbm_pipeline_find_lsp_resolution(&r->resolved_calls, beta_call, false);
|
|
ASSERT_TRUE(alpha_join == alpha_semantic);
|
|
ASSERT_TRUE(beta_join == beta_semantic);
|
|
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
/* A qualified type name resolves its qualifier as a TYPE when no namespace
|
|
* binds it (nested types, #2120). A using alias whose target is qualified by
|
|
* the alias itself (`using Loop = Loop.Inner;`) or by a mutual alias re-enters
|
|
* that walk through the alias step forever: each `Loop` resolves the target
|
|
* `Loop.Inner`, whose qualifier is `Loop` again. The walk is depth-bounded;
|
|
* the file must still finish and resolve its ordinary calls. */
|
|
TEST(cslsp_cyclic_alias_qualifier_terminates) {
|
|
const char *src = "using Loop = Loop.Inner;\n"
|
|
"using Ping = Pong.Inner;\n"
|
|
"using Pong = Ping.Inner;\n"
|
|
"namespace App {\n"
|
|
" public class Svc { public void Go() { } }\n"
|
|
" public class Runner {\n"
|
|
" public void Run(Loop a, Ping b) {\n"
|
|
" var x = new Loop();\n"
|
|
" var y = new Ping.Leaf();\n"
|
|
" var s = new Svc();\n"
|
|
" s.Go();\n"
|
|
" }\n"
|
|
" }\n"
|
|
"}\n";
|
|
CBMFileResult *r = extract_cs(src);
|
|
ASSERT(r);
|
|
ASSERT(require_resolved(r, "Runner.Run", "Svc.Go") >= 0);
|
|
cbm_free_result(r);
|
|
PASS();
|
|
}
|
|
|
|
static const CBMDefinition *find_def_named(const CBMFileResult *r, const char *name) {
|
|
for (int i = 0; i < r->defs.count; i++) {
|
|
if (r->defs.items[i].name && strcmp(r->defs.items[i].name, name) == 0)
|
|
return &r->defs.items[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* A top-level C# type records its declared namespace; "" means the global
|
|
* namespace and is claimed only on a clean parse. The vendored grammar does
|
|
* not parse a `ref partial struct` with members, and error recovery dissolves
|
|
* the enclosing namespace, so the types after it surface with no namespace
|
|
* ancestor. Those must not read as global (they would hide every imported
|
|
* type of the same name); unknown (NULL) keeps them out of namespace binding
|
|
* exactly as before global-namespace binding existed. */
|
|
TEST(cslsp_decl_namespace_global_needs_clean_parse) {
|
|
CBMFileResult *clean = extract_cs("public class Loose { }\n"
|
|
"namespace Acme.Lib { public class Kept { } }\n");
|
|
ASSERT(clean);
|
|
const CBMDefinition *loose = find_def_named(clean, "Loose");
|
|
const CBMDefinition *kept = find_def_named(clean, "Kept");
|
|
ASSERT(loose && kept);
|
|
ASSERT(loose->decl_namespace && strcmp(loose->decl_namespace, "") == 0);
|
|
ASSERT(kept->decl_namespace && strcmp(kept->decl_namespace, "Acme.Lib") == 0);
|
|
cbm_free_result(clean);
|
|
|
|
CBMFileResult *damaged = extract_cs("namespace Acme.Lib\n"
|
|
"{\n"
|
|
" public ref partial struct Handler\n"
|
|
" {\n"
|
|
" public void Append(string value) { }\n"
|
|
" }\n"
|
|
" public static partial class Gadget { }\n"
|
|
"}\n");
|
|
ASSERT(damaged);
|
|
const CBMDefinition *gadget = find_def_named(damaged, "Gadget");
|
|
ASSERT(gadget);
|
|
if (gadget->decl_namespace && !gadget->decl_namespace[0])
|
|
printf(" Gadget after a parse error claims the global namespace\n");
|
|
ASSERT(!gadget->decl_namespace || gadget->decl_namespace[0]);
|
|
cbm_free_result(damaged);
|
|
PASS();
|
|
}
|
|
|
|
/* ── Suite ─────────────────────────────────────────────────── */
|
|
|
|
SUITE(cs_lsp) {
|
|
RUN_TEST(cslsp_local_method_via_new);
|
|
RUN_TEST(cslsp_method_via_typed_param);
|
|
RUN_TEST(cslsp_static_method);
|
|
RUN_TEST(cslsp_base_dispatch);
|
|
RUN_TEST(cslsp_ctor);
|
|
RUN_TEST(cslsp_method_chain);
|
|
RUN_TEST(cslsp_file_scoped_namespace);
|
|
RUN_TEST(cslsp_block_namespace);
|
|
RUN_TEST(cslsp_using_directive);
|
|
RUN_TEST(cslsp_using_static);
|
|
RUN_TEST(cslsp_using_alias);
|
|
RUN_TEST(cslsp_generic_list_foreach);
|
|
RUN_TEST(cslsp_list_add_resolved);
|
|
RUN_TEST(cslsp_linq_extension);
|
|
RUN_TEST(cslsp_property_chain);
|
|
RUN_TEST(cslsp_auto_property_field);
|
|
RUN_TEST(cslsp_async_await);
|
|
RUN_TEST(cslsp_record_primary_ctor);
|
|
RUN_TEST(cslsp_pattern_is_type);
|
|
RUN_TEST(cslsp_cast);
|
|
RUN_TEST(cslsp_as_expression);
|
|
RUN_TEST(cslsp_generic_method);
|
|
RUN_TEST(cslsp_inherited_method_via_field);
|
|
RUN_TEST(cslsp_nameof);
|
|
RUN_TEST(cslsp_typeof);
|
|
RUN_TEST(cslsp_string_literal_method);
|
|
RUN_TEST(cslsp_null_conditional);
|
|
RUN_TEST(cslsp_tuple_expression);
|
|
RUN_TEST(cslsp_top_level_statements);
|
|
RUN_TEST(cslsp_interface_dispatch);
|
|
RUN_TEST(cslsp_enum_does_not_crash);
|
|
RUN_TEST(cslsp_object_initializer);
|
|
RUN_TEST(cslsp_var_assigned_object);
|
|
RUN_TEST(cslsp_task_generic_unwrap);
|
|
RUN_TEST(cslsp_field_typed);
|
|
RUN_TEST(cslsp_this_accessor);
|
|
RUN_TEST(cslsp_conditional_expression);
|
|
RUN_TEST(cslsp_array_element_access);
|
|
RUN_TEST(cslsp_predefined_int_parse);
|
|
RUN_TEST(cslsp_multi_usings);
|
|
RUN_TEST(cslsp_indexer);
|
|
RUN_TEST(cslsp_lambda_body);
|
|
RUN_TEST(cslsp_switch_expression);
|
|
RUN_TEST(cslsp_struct_method);
|
|
RUN_TEST(cslsp_dictionary);
|
|
RUN_TEST(cslsp_same_ns_lookup);
|
|
RUN_TEST(cslsp_inheritance_chain);
|
|
RUN_TEST(cslsp_expression_bodied);
|
|
RUN_TEST(cslsp_unresolved_returns_no_resolved);
|
|
RUN_TEST(cslsp_quality_indicator);
|
|
RUN_TEST(cslsp_return_type_fallback);
|
|
RUN_TEST(cslsp_multi_namespace_same_file);
|
|
RUN_TEST(cslsp_local_function);
|
|
RUN_TEST(cslsp_params_smoke);
|
|
RUN_TEST(cslsp_ref_out_param);
|
|
RUN_TEST(cslsp_global_using_smoke);
|
|
RUN_TEST(cslsp_strategy_distribution);
|
|
RUN_TEST(cslsp_using_static_console);
|
|
RUN_TEST(cslsp_stringbuilder_chain);
|
|
RUN_TEST(cslsp_httpclient_chain);
|
|
RUN_TEST(cslsp_ordinary_same_leaf_calls_join_by_exact_site);
|
|
RUN_TEST(cslsp_cyclic_alias_qualifier_terminates);
|
|
RUN_TEST(cslsp_decl_namespace_global_needs_clean_parse);
|
|
}
|