180 lines
7.1 KiB
C
180 lines
7.1 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` */
|
|
} 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;
|
|
|
|
/* 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;
|
|
|
|
/* 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);
|
|
|
|
/* 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 */
|