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>
199 lines
8.2 KiB
C
199 lines
8.2 KiB
C
#ifndef CBM_LSP_CS_LSP_H
|
|
#define CBM_LSP_CS_LSP_H
|
|
|
|
#include "type_rep.h"
|
|
#include "scope.h"
|
|
#include "type_registry.h"
|
|
#include "../cbm.h"
|
|
#include "go_lsp.h" /* CBMLSPDef, CBMResolvedCall, CBMResolvedCallArray are reused */
|
|
|
|
/*
|
|
* cs_lsp — C# Light Semantic Pass.
|
|
*
|
|
* Reverse-engineered from Roslyn's Binder pipeline (src/Compilers/CSharp/
|
|
* Portable/Binder/Binder_*.cs). Mirrors the structure of go_lsp / c_lsp /
|
|
* php_lsp / py_lsp so the shared pipeline (lsp_resolve.h) treats every
|
|
* language identically.
|
|
*
|
|
* Coverage targets (≥90% parity vs Roslyn for typical user code):
|
|
* - using / using static / using alias / global using
|
|
* - file-scoped + block namespaces; nested namespaces
|
|
* - classes, structs, records, interfaces, enums
|
|
* - inheritance + interface implementation; partial classes
|
|
* - methods (instance, static, generic, async, extension `this`)
|
|
* - properties (auto, expression-bodied, full); indexers
|
|
* - constructors + primary constructors (records / C# 12 classes)
|
|
* - object creation `new T(...)` / target-typed `new(...)`
|
|
* - var + explicit local types; foreach element inference
|
|
* - tuples (literal + parameter)
|
|
* - lambdas + delegate calls
|
|
* - await: Task<T> → T, ValueTask<T> → T
|
|
* - this / base / `base.Method()` calls
|
|
* - cast `(T)x`, `x as T`, pattern `x is T y`
|
|
* - null-conditional `?.`, null-coalescing `??`
|
|
* - generic instantiation + type-parameter substitution
|
|
* - extension method dispatch (`obj.Foo()` -> static Foo(this T self, ...))
|
|
*
|
|
* Out-of-scope (intentional):
|
|
* - flow analysis-driven nullable narrowing
|
|
* - LINQ query syntax (handled as method-syntax via `Select`/`Where` lookup)
|
|
* - dynamic / reflection
|
|
* - source generators / Roslyn analyzers
|
|
*/
|
|
|
|
/* CSAlias / CSUsing — per-file using state.
|
|
*
|
|
* `using Foo.Bar;` -> namespace import (kind = NAMESPACE)
|
|
* `using static Foo.Bar;` -> static-member import (kind = STATIC)
|
|
* `using F = Foo.Bar;` -> alias (kind = ALIAS, local_name = "F")
|
|
* `global using ...;` -> kept identical, marked is_global (rare in
|
|
* a single file but handled for ASP.NET-style
|
|
* Program.cs files).
|
|
*/
|
|
typedef enum {
|
|
CBM_CS_USING_NAMESPACE = 0,
|
|
CBM_CS_USING_STATIC,
|
|
CBM_CS_USING_ALIAS,
|
|
} CBMCSUsingKind;
|
|
|
|
typedef struct {
|
|
CBMCSUsingKind kind;
|
|
const char *local_name; /* alias name; "" for non-alias */
|
|
const char *target_qn; /* dotted target QN */
|
|
bool is_global; /* `global using` */
|
|
/* Declared inside a namespace body rather than at compilation-unit level.
|
|
* C# consults a namespace declaration's usings right after that
|
|
* namespace's own members, but compilation-unit usings only after the
|
|
* global namespace's members, so the two rank on opposite sides of it. */
|
|
bool in_namespace;
|
|
} CBMCSUsing;
|
|
|
|
/* CSLSPContext — per-file type-evaluation state. */
|
|
typedef struct {
|
|
CBMArena *arena;
|
|
const char *source;
|
|
int source_len;
|
|
const CBMTypeRegistry *registry;
|
|
CBMScope *current_scope;
|
|
|
|
/* Namespace stack (innermost first). C# allows nested + file-scoped. */
|
|
const char **namespace_stack;
|
|
int namespace_count;
|
|
int namespace_cap;
|
|
|
|
/* Active using directives in the current file. C# resolves bare names
|
|
* by walking the namespace stack outward, then searching using
|
|
* directives. */
|
|
CBMCSUsing *usings;
|
|
int using_count;
|
|
int using_cap;
|
|
|
|
/* Enclosing class / struct / record / interface — the "type" body
|
|
* we're currently inside. NULL outside type body. */
|
|
const char *enclosing_class_qn;
|
|
const char *enclosing_base_qn; /* base class QN; NULL if none */
|
|
const char **enclosing_iface_qns; /* NULL-terminated; NULL if none */
|
|
|
|
/* Enclosing function/method/lambda. */
|
|
const char *enclosing_func_qn;
|
|
|
|
/* Module QN for this file (matches what the unified extractor records). */
|
|
const char *module_qn;
|
|
|
|
/* Output: resolved calls accumulate here. */
|
|
CBMResolvedCallArray *resolved_calls;
|
|
/* Existing parser call carriers for this file. Exact callable aliases
|
|
* retarget their occurrence and require the semantic result, preventing a
|
|
* same-named definition from winning through textual fallback. */
|
|
CBMCallArray *call_carriers;
|
|
|
|
/* Active type-parameter substitution map (for generic methods/types).
|
|
* Parallel arrays. NULL-terminated. */
|
|
const char **type_param_names;
|
|
const CBMType **type_param_args;
|
|
int type_param_count;
|
|
|
|
/* Recursion guard for cs_eval_expr_type. */
|
|
int eval_depth;
|
|
|
|
/* Recursion guard for the nested-type qualifier walk of
|
|
* cs_resolve_type_name: resolving `A.B` resolves the qualifier `A` as a
|
|
* type first, and a `using A = A.B;` alias cycles back into it. */
|
|
int qualifier_depth;
|
|
|
|
/* Debug mode (CBM_LSP_DEBUG env). */
|
|
bool debug;
|
|
} CSLSPContext;
|
|
|
|
/* Initialize a CSLSPContext for processing one file. */
|
|
void cs_lsp_init(CSLSPContext *ctx, CBMArena *arena, const char *source, int source_len,
|
|
const CBMTypeRegistry *registry, const char *module_qn,
|
|
CBMResolvedCallArray *out);
|
|
|
|
/* Append a using directive. local_name may be NULL/empty for non-alias kinds. */
|
|
void cs_lsp_add_using(CSLSPContext *ctx, CBMCSUsingKind kind, const char *local_name,
|
|
const char *target_qn, bool is_global);
|
|
|
|
/* Process a file's AST. */
|
|
void cs_lsp_process_file(CSLSPContext *ctx, TSNode root);
|
|
|
|
/* Evaluate the type of an expression. Never returns NULL — falls back to
|
|
* cbm_type_unknown(). */
|
|
const CBMType *cs_eval_expr_type(CSLSPContext *ctx, TSNode node);
|
|
|
|
/* Convert a C# type-AST node to a CBMType. */
|
|
const CBMType *cs_parse_type_node(CSLSPContext *ctx, TSNode node);
|
|
|
|
/* Resolve a bare or dotted type name against namespace stack + using map.
|
|
* Returns dotted QN or NULL. */
|
|
const char *cs_resolve_type_name(CSLSPContext *ctx, const char *name);
|
|
|
|
/* Look up a method on a type, walking base + interface chains. */
|
|
const CBMRegisteredFunc *cs_lookup_method(CSLSPContext *ctx, const char *type_qn,
|
|
const char *method_name);
|
|
|
|
/* Apply the local AST-declared return-type refinement to an already populated
|
|
* registry. Kept as a production-used seam so post-registration signature
|
|
* preservation can be regression-tested directly. */
|
|
void cbm_cs_refine_ast_return_types(CSLSPContext *ctx, CBMTypeRegistry *reg, TSNode root);
|
|
|
|
/* Single-file entry: build registry from file defs + stdlib, run resolution. */
|
|
void cbm_run_cs_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
|
|
TSNode root);
|
|
|
|
/* Cross-file entry. Like Go/C: caller supplies pre-resolved defs from siblings. */
|
|
void cbm_run_cs_lsp_cross(CBMArena *arena, const char *source, int source_len,
|
|
const char *module_qn, CBMLSPDef *defs, int def_count,
|
|
const char **using_targets, int using_count,
|
|
TSTree *cached_tree, CBMResolvedCallArray *out);
|
|
|
|
/* Tier 2: build a project-wide C# registry ONCE from all defs (filters
|
|
* by lang), shared READ-ONLY across resolve workers. Def-driven. */
|
|
CBMTypeRegistry *cbm_cs_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, int def_count);
|
|
|
|
/* Cross-file resolve using a pre-built shared registry (Tier 2). */
|
|
void cbm_run_cs_lsp_cross_with_registry(CBMArena *arena, const char *source, int source_len,
|
|
const char *module_qn, CBMTypeRegistry *reg,
|
|
const char **using_targets, int using_count,
|
|
TSTree *cached_tree, CBMResolvedCallArray *out);
|
|
|
|
/* Batch cross-file entry for one CGo call from the parallel pipeline. */
|
|
typedef struct {
|
|
const char *source;
|
|
int source_len;
|
|
const char *module_qn;
|
|
TSTree *cached_tree;
|
|
CBMLSPDef *defs;
|
|
int def_count;
|
|
const char **using_targets;
|
|
int using_count;
|
|
} CBMBatchCSLSPFile;
|
|
|
|
void cbm_batch_cs_lsp_cross(CBMArena *arena, CBMBatchCSLSPFile *files, int file_count,
|
|
CBMResolvedCallArray *out);
|
|
|
|
/* Register .NET BCL stdlib types and functions. Generated. */
|
|
void cbm_csharp_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
|
|
|
|
#endif /* CBM_LSP_CS_LSP_H */
|