Files
codebase-memory-mcp/internal/cbm/lsp/php_lsp.h
Martin Vogel 9adada0185 fix(php): index trait methods by receiver while flattening (#1265)
flatten_trait_into_class looked up each trait's methods by scanning every
function of the registry, and every flattened method appends more of them.
A file with many trait-using classes paid O(classes x functions), and the
scan grew with its own output.

The class-field pass now keeps a receiver -> function-id index, synced
incrementally as functions are registered, and flattening reads only the
trait's own entries registered before the current class. If the index
cannot be built, the previous full scan runs unchanged. Only entries
registered before flattening started are visited, so the output is
identical.

Tests: the work-counter tests for #1265 (100 -> 200 trait-using classes,
ratio gate 2.5) and #765 (trait collision shapes stay bounded) use the
existing test-only work ceiling.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-19 04:39:09 +02:00

172 lines
7.2 KiB
C

#ifndef CBM_LSP_PHP_LSP_H
#define CBM_LSP_PHP_LSP_H
#include "type_rep.h"
#include "scope.h"
#include "type_registry.h"
#include "../cbm.h"
#include "go_lsp.h" /* CBMLSPDef reused across languages */
/* Per-file typed import evidence. NULL targets means unknown; otherwise
* targets has one slot per represented import, NULL only for a binding whose
* absence was established. Any unsupported unresolved binding makes the whole
* file unknown. The array is caller-owned; strings and import records borrowed. */
typedef struct {
const CBMImportArray *imports;
const char **targets;
} CBMPHPImportBindings;
/* PHPLSPContext — per-file state for PHP type-aware call resolution.
* Mirrors GoLSPContext / CLSPContext structure. */
typedef struct {
CBMArena *arena;
const char *source;
int source_len;
const CBMTypeRegistry *registry;
CBMScope *current_scope;
/* Namespace state. PHP files declare a single namespace
* (or use the global namespace if none); empty string means global. */
const char *current_namespace_qn;
/* `use` clause map.
* use_kinds[i] selects whether the local maps a class, function, or const. */
const char **use_local_names;
const char **use_target_qns;
enum { CBM_PHP_USE_CLASS = 0, CBM_PHP_USE_FUNCTION, CBM_PHP_USE_CONST } *use_kinds;
int use_count;
int use_cap;
/* Cross-file mode: dotted targets of the file's class `use` declarations
* that the pipeline's import map left unresolved (vendor / unmapped
* namespaces, #1186). A type named through one is outside the project: it
* never matches a project type by short name. */
const char **external_use_qns;
int external_use_count;
/* Current function/method/class context. */
const char *enclosing_func_qn;
const char *enclosing_class_qn; /* NULL outside class body */
const char *enclosing_parent_qn; /* parent class QN (for parent::), or NULL */
const char *module_qn;
/* Output: resolved calls accumulate here. */
CBMResolvedCallArray *resolved_calls;
/* @phpstan-type alias map (per-file, populated from class docblocks).
* Used by resolve_phpdoc_type before generic name resolution so user
* type aliases like `@phpstan-type UserId int|string` and references
* to `UserId` in @var/@param/@return all resolve to the aliased type.
*/
const char **phpstan_alias_names; /* arena-allocated, NULL-terminated */
const CBMType **phpstan_alias_types;
int phpstan_alias_count;
int phpstan_alias_cap;
/* Recursion guard for php_eval_expr_type. */
int eval_depth;
/* AST-walk recursion depth for php_resolve_calls_in_node (guards stack
* overflow on deeply-nested/cyclic files; see cbm_lsp_max_walk_depth).
* Zero via memset. */
int walk_depth;
/* Receiver -> registered-method index used while traits are flattened
* (php_lsp.c, php_trait_index_t). Owned by the class-field pass, NULL
* outside it. Zero via memset. */
struct php_trait_index *trait_index;
/* Debug mode (CBM_LSP_DEBUG env). */
bool debug;
} PHPLSPContext;
/* Initialize a PHPLSPContext for processing one file. */
void php_lsp_init(PHPLSPContext *ctx, CBMArena *arena, const char *source, int source_len,
const CBMTypeRegistry *registry, const char *module_qn,
CBMResolvedCallArray *out);
/* Add a `use` mapping. */
void php_lsp_add_use(PHPLSPContext *ctx, const char *local_name, const char *target_qn,
int use_kind);
/* Process a file's AST: walk top-level decls, then function/method bodies. */
void php_lsp_process_file(PHPLSPContext *ctx, TSNode root);
/* Refine an already-populated registry from local class ASTs: declared and
* PHPDoc returns, trait flattening, and typed fields. */
void cbm_php_refine_lsp_registry(PHPLSPContext *ctx, CBMTypeRegistry *reg, TSNode root);
/* Evaluate a PHP expression's type. May return NULL / CBM_TYPE_UNKNOWN. */
const CBMType *php_eval_expr_type(PHPLSPContext *ctx, TSNode node);
/* Parse a PHP type-AST node (named_type, primitive_type, union_type, ...) to CBMType. */
const CBMType *php_parse_type_node(PHPLSPContext *ctx, TSNode node);
/* Resolve a class name (bare or qualified) using current namespace + use map. */
const char *php_resolve_class_name(PHPLSPContext *ctx, const char *name);
/* Look up a method on a class, walking parent chain (registry-based). */
const CBMRegisteredFunc *php_lookup_method(PHPLSPContext *ctx, const char *class_qn,
const char *method_name);
/* Entry point: build registry from file defs + stdlib + composer (if present),
* then run resolution. Called from cbm_extract_file(). */
void cbm_run_php_lsp(CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
TSNode root);
/* Register PHP stdlib + curated framework types into a registry. */
void cbm_php_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
/* --- Cross-file LSP resolution ---
*
* Mirrors cbm_run_py_lsp_cross / cbm_run_ts_lsp_cross. Caller supplies the
* combined CBMLSPDef[] (file-local + cross-file) and a resolved import map
* (use → target QN). Imports are added as CLASS-kind uses; file-internal
* `use` declarations from the AST are layered on top by process_file.
*
* Reuses go_lsp.h's CBMLSPDef so cross-language registration is uniform. */
void cbm_php_register_lsp_defs(CBMArena *arena, CBMArena *idx_arena, CBMTypeRegistry *reg,
const CBMLSPDef *defs, int def_count);
void cbm_run_php_lsp_cross(
CBMArena *arena,
const char *source, int source_len,
const char *module_qn,
CBMLSPDef *defs, int def_count,
const char **import_names, const char **import_qns, int import_count,
TSTree *cached_tree, /* NULL = parse internally */
CBMResolvedCallArray *out);
/* Complete typed bindings enable external receiver evidence. */
void cbm_run_php_lsp_cross_with_bindings(CBMArena *arena, const char *source, int source_len,
const char *module_qn, CBMLSPDef *defs, int def_count,
const char **import_names, const char **import_qns,
int import_count,
TSTree *cached_tree, /* NULL = parse internally */
CBMResolvedCallArray *out,
const CBMPHPImportBindings *bindings);
/* --- Batch cross-file LSP --- */
/* Per-file input for batch PHP LSP processing. */
typedef struct {
const char *source;
int source_len;
const char *module_qn;
TSTree *cached_tree; /* NULL = parse internally */
CBMLSPDef *defs; /* combined file-local + cross-file defs */
int def_count;
const char **import_names; /* parallel arrays, import_count long */
const char **import_qns;
int import_count;
} CBMBatchPHPLSPFile;
/* Process multiple PHP files' cross-file LSP in one call. out must point to
* file_count pre-zeroed CBMResolvedCallArray structs. */
void cbm_batch_php_lsp_cross(
CBMArena *arena,
CBMBatchPHPLSPFile *files, int file_count,
CBMResolvedCallArray *out);
#endif /* CBM_LSP_PHP_LSP_H */