Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

1198 lines
39 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 "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();
}
/* ── 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);
}