mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Merge lane/core-go: remove work budgets and identity conflicts, true Go static rules
Union with the other lanes. Merged same-kind declarations are one graph identity in core-go's rule; JVM overloads and TypeScript merged value/type stay on their own paths, and the JavaScript span rule still applies only to a source in this file. Evidence writing keeps the perf-scale writer, proven edge-identical. The lane's commit d38554e3d is labelled WIP but its code is final; the label could not be amended.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
commit
8594f8c518
18 files changed
+3441
-523
No files matched your search
@@ -159,6 +159,24 @@ static void add_ts_annotation_refs(CBMExtractCtx *ctx, TSNode node, const char *
|
||||
}
|
||||
}
|
||||
|
||||
/* Go spells wrappers around a name (`[]*v1.Pod`, `map[string]T`, `Box[T]`). Each named type
|
||||
* inside is its own exact occurrence, where the wrapped text would match no site. */
|
||||
static void add_go_type_refs(CBMExtractCtx *ctx, TSNode node, const char *func_qn, unsigned depth) {
|
||||
enum { MAX_GO_TYPE_DEPTH = 32 };
|
||||
if (ts_node_is_null(node) || depth >= MAX_GO_TYPE_DEPTH) {
|
||||
return;
|
||||
}
|
||||
const char *kind = ts_node_type(node);
|
||||
if (strcmp(kind, "type_identifier") == 0 || strcmp(kind, "qualified_type") == 0) {
|
||||
add_type_ref(ctx, cbm_node_text(ctx->arena, node, ctx->source), func_qn, node);
|
||||
return;
|
||||
}
|
||||
uint32_t count = ts_node_named_child_count(node);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
add_go_type_refs(ctx, ts_node_named_child(node, i), func_qn, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract parameter types from a parameters/formal_parameters node.
|
||||
static void extract_param_type_refs(CBMExtractCtx *ctx, TSNode params, const char *func_qn) {
|
||||
uint32_t count = ts_node_child_count(params);
|
||||
@@ -168,6 +186,8 @@ static void extract_param_type_refs(CBMExtractCtx *ctx, TSNode params, const cha
|
||||
if (!ts_node_is_null(type_node)) {
|
||||
if (ts_family(ctx->language)) {
|
||||
add_ts_annotation_refs(ctx, type_node, func_qn, 0);
|
||||
} else if (ctx->language == CBM_LANG_GO) {
|
||||
add_go_type_refs(ctx, type_node, func_qn, 0);
|
||||
continue;
|
||||
}
|
||||
const char *tname = extract_type_text(ctx->arena, type_node, ctx->source);
|
||||
@@ -186,6 +206,10 @@ static void extract_return_type_refs(CBMExtractCtx *ctx, TSNode func_node, const
|
||||
break;
|
||||
}
|
||||
if (!ts_node_is_null(rt)) {
|
||||
if (ctx->language == CBM_LANG_GO) {
|
||||
add_go_type_refs(ctx, rt, func_qn, 0);
|
||||
break;
|
||||
}
|
||||
const char *tname = extract_type_text(ctx->arena, rt, ctx->source);
|
||||
add_type_ref(ctx, tname, func_qn, rt);
|
||||
break;
|
||||
@@ -199,6 +223,10 @@ static void extract_type_field_ref(CBMExtractCtx *ctx, TSNode node, const char *
|
||||
if (!ts_node_is_null(type_node) && ts_family(ctx->language)) {
|
||||
add_ts_annotation_refs(ctx, type_node, func_qn, 0);
|
||||
} else if (!ts_node_is_null(type_node)) {
|
||||
if (ctx->language == CBM_LANG_GO) {
|
||||
add_go_type_refs(ctx, type_node, func_qn, 0);
|
||||
return;
|
||||
}
|
||||
add_type_ref(ctx, extract_type_text(ctx->arena, type_node, ctx->source), func_qn,
|
||||
type_node);
|
||||
}
|
||||
@@ -387,10 +415,10 @@ static void extract_signature_type_refs(CBMExtractCtx *ctx, TSNode node, WalkSta
|
||||
return;
|
||||
}
|
||||
|
||||
const char *func_qn;
|
||||
if (state->enclosing_class_qn) {
|
||||
func_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", state->enclosing_class_qn, func_name);
|
||||
} else {
|
||||
/* The scope a call in this body would get. file + bare name misses a Go package's
|
||||
* directory-based module and a Go receiver, leaving a scope no graph node carries. */
|
||||
const char *func_qn = cbm_declaration_scope_qn(ctx, node, cbm_lang_spec(ctx->language), state);
|
||||
if (!func_qn) {
|
||||
func_qn = cbm_fqn_compute(ctx->arena, ctx->project, ctx->rel_path, func_name);
|
||||
}
|
||||
|
||||
|
||||
@@ -2543,6 +2543,11 @@ static bool push_python_default_owner(CBMExtractCtx *ctx, TSNode node, WalkState
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *cbm_declaration_scope_qn(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
|
||||
WalkState *state) {
|
||||
return compute_func_qn(ctx, node, spec, state);
|
||||
}
|
||||
|
||||
static bool push_pre_node_scope(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
|
||||
WalkState *state, uint32_t depth) {
|
||||
for (int i = 0; i < state->scope_top; i++) {
|
||||
|
||||
@@ -218,6 +218,9 @@ void cbm_finalize_lexical_usages(CBMExtractCtx *ctx, WalkState *state);
|
||||
void handle_throws(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, WalkState *state);
|
||||
void handle_readwrites(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, WalkState *state);
|
||||
void handle_type_refs(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, WalkState *state);
|
||||
/* The call-scope QN of one callable declaration node, NULL when it pushes no scope. */
|
||||
const char *cbm_declaration_scope_qn(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
|
||||
WalkState *state);
|
||||
void handle_env_accesses(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
|
||||
WalkState *state);
|
||||
void handle_type_assigns(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
|
||||
|
||||
+1619
-336
File diff suppressed because it is too large.
Load diff
@@ -12,14 +12,29 @@ typedef struct cbm_go_stabilization cbm_go_stabilization_t;
|
||||
cbm_go_stabilization_t *cbm_go_stabilization_new(CBMArena *, cbm_resolve_proof_pool_t *,
|
||||
cbm_resolve_solver_t *);
|
||||
void cbm_go_stabilization_dispose(cbm_go_stabilization_t *);
|
||||
/* Before any collect: one file's declared package name, keyed by its directory module. An
|
||||
* unaliased import binds this name, which need not be the import path's last segment
|
||||
* (k8s.io/mount-utils declares `package mount`). */
|
||||
bool cbm_go_stabilization_note_package(cbm_go_stabilization_t *, const char *module,
|
||||
const char *source, size_t length);
|
||||
bool cbm_go_stabilization_collect(cbm_go_stabilization_t *, const CBMFileResult *, const char *,
|
||||
size_t, const cbm_resolve_proof_source_t *, const char *,
|
||||
const cbm_resolve_import_snapshot_t *);
|
||||
bool cbm_go_stabilization_register(cbm_go_stabilization_t *);
|
||||
bool cbm_go_stabilization_seal(cbm_go_stabilization_t *);
|
||||
/* true means this exact supported occurrence is controlled by the shared
|
||||
* fixed point, including uncertainty; callers must not fall back on failure.
|
||||
* Published results/proofs are immutable and safe for concurrent workers. */
|
||||
/* true means this exact call occurrence was analysed by the shared fixed point. An unknown
|
||||
* result is missing knowledge: a caller may keep a proof it holds independently, but never
|
||||
* replace an ambiguous result. Published results/proofs are immutable and safe for
|
||||
* concurrent workers. */
|
||||
bool cbm_go_stabilization_call(const cbm_go_stabilization_t *, const char *, const char *, uint32_t,
|
||||
uint32_t, cbm_resolve_result_t *);
|
||||
/* A non-call reference token: a package-scope name, a qualified name or a selected member.
|
||||
* True only when Go's rules select a declaration or find two; the proof attests the
|
||||
* declaration, so the caller adds the site and role it read this token for. */
|
||||
bool cbm_go_stabilization_reference(const cbm_go_stabilization_t *, const char *file,
|
||||
uint32_t start, uint32_t end, cbm_resolve_result_t *);
|
||||
/* The project-level names worth keeping as derived bindings. Member, element and per-site
|
||||
* facts are reached from the occurrences they justify, and would otherwise be kept once per
|
||||
* name for every type in the project. */
|
||||
bool cbm_go_stabilization_summary_key(const char *key);
|
||||
#endif
|
||||
@@ -21,8 +21,9 @@ typedef struct {
|
||||
const char *imports;
|
||||
} edge_row_t;
|
||||
static const edge_row_t edge_rows[] = {
|
||||
{CBM_LANG_GO, SOURCE_EDGES,
|
||||
"Exact project package paths with configured roots; unresolved external packages abstain."},
|
||||
{CBM_LANG_GO, SOURCE_EDGES | EDGE(WRITES) | EDGE(USES_TYPE),
|
||||
"Exact project package paths from the source's module, its go.mod replace directives and "
|
||||
"the enclosing go.work; every other package abstains."},
|
||||
{CBM_LANG_C, C_EDGES,
|
||||
"Literal include paths and configured search roots only; unproved preprocessing abstains."},
|
||||
{CBM_LANG_CPP, C_EDGES,
|
||||
@@ -178,8 +179,8 @@ static void edge_capabilities(cbm_resolve_capabilities_t *out) {
|
||||
"nested builds and transformed manifests abstain.";
|
||||
if (out->language == CBM_LANG_GO)
|
||||
out->edges[CBM_RESOLVE_EDGE_WRITES].limitation =
|
||||
"No admitted Go field/write bridge; assignment extraction remains a resolver quality "
|
||||
"gap.";
|
||||
"Package variables and fields selected through a declared static type. Writes through "
|
||||
"an unknown operand and keys of a literal whose element type is elided abstain.";
|
||||
}
|
||||
|
||||
bool cbm_resolve_capabilities(CBMLanguage language, cbm_resolve_capabilities_t *out) {
|
||||
@@ -234,8 +235,10 @@ bool cbm_resolve_capabilities(CBMLanguage language, cbm_resolve_capabilities_t *
|
||||
out->proof_subsets = CBM_RESOLVE_CAP_SCOPE | CBM_RESOLVE_CAP_IMPORT_EXPORT |
|
||||
CBM_RESOLVE_CAP_DECLARED_TYPE | CBM_RESOLVE_CAP_MEMBER |
|
||||
CBM_RESOLVE_CAP_TYPE_FLOW | CBM_RESOLVE_CAP_CALLABLE;
|
||||
out->limitation = "Exact declarations and typed receivers; unknown owners, effects and "
|
||||
"competing targets abstain.";
|
||||
out->limitation = "Lexical scope, declared static types and Go's selector rule for "
|
||||
"embedding. Exported names behind an embedded type outside the project, "
|
||||
"a map's key type, a type parameter's constraint methods and competing "
|
||||
"targets abstain.";
|
||||
break;
|
||||
case CBM_LANG_C:
|
||||
case CBM_LANG_CPP:
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
#include <string.h>
|
||||
|
||||
#define PROOF_MAX_DEPTH 256u
|
||||
#define PROOF_DEFAULT_NODES 65536u
|
||||
#define PROOF_DEFAULT_PREMISES 65536u
|
||||
#define PROOF_DEFAULT_WORK UINT64_C(4194304)
|
||||
|
||||
struct cbm_resolve_proof_entry {
|
||||
cbm_resolve_proof_t node;
|
||||
@@ -55,13 +52,13 @@ void cbm_resolve_proof_pool_init(cbm_resolve_proof_pool_t *pool, CBMArena *arena
|
||||
return;
|
||||
*pool = (cbm_resolve_proof_pool_t){
|
||||
.arena = arena,
|
||||
.node_limit = node_limit ? node_limit : PROOF_DEFAULT_NODES,
|
||||
.premise_limit = premise_limit ? premise_limit : PROOF_DEFAULT_PREMISES,
|
||||
.work_limit = work_limit ? work_limit : PROOF_DEFAULT_WORK,
|
||||
.node_limit = node_limit ? node_limit : SIZE_MAX,
|
||||
.premise_limit = premise_limit ? premise_limit : SIZE_MAX,
|
||||
.work_limit = work_limit ? work_limit : UINT64_MAX,
|
||||
.failure = arena ? CBM_RESOLVE_ANALYSIS_COMPLETE : CBM_RESOLVE_ANALYSIS_FAILED};
|
||||
}
|
||||
void cbm_resolve_proof_pool_init_project(cbm_resolve_proof_pool_t *pool, CBMArena *arena) {
|
||||
cbm_resolve_proof_pool_init(pool, arena, SIZE_MAX, SIZE_MAX, UINT64_MAX);
|
||||
cbm_resolve_proof_pool_init(pool, arena, 0, 0, 0);
|
||||
}
|
||||
const cbm_resolve_proof_view_t *cbm_resolve_proof_view(const cbm_resolve_proof_t *proof) {
|
||||
return proof ? &proof->value : NULL;
|
||||
@@ -520,7 +517,7 @@ bool cbm_resolve_proof_visit(const cbm_resolve_proof_t *root, size_t node_limit,
|
||||
if (!root || !visit)
|
||||
return false;
|
||||
proof_walk_t walk = {
|
||||
.visit = visit, .context = context, .limit = node_limit ? node_limit : PROOF_DEFAULT_NODES};
|
||||
.visit = visit, .context = context, .limit = node_limit ? node_limit : SIZE_MAX};
|
||||
bool ok = walk_node(&walk, root, 1) != NULL;
|
||||
cbm_free(CBM_MEM_CLASS_EXTRACT, walk.slots);
|
||||
return ok;
|
||||
@@ -539,7 +536,7 @@ bool cbm_resolve_proof_visit_roots(const cbm_resolve_proof_t *const *roots, size
|
||||
memcpy(sorted, roots, root_count * sizeof(*sorted));
|
||||
qsort(sorted, root_count, sizeof(*sorted), compare_premises);
|
||||
proof_walk_t walk = {
|
||||
.visit = visit, .context = context, .limit = node_limit ? node_limit : PROOF_DEFAULT_NODES};
|
||||
.visit = visit, .context = context, .limit = node_limit ? node_limit : SIZE_MAX};
|
||||
bool ok = true;
|
||||
for (size_t i = 0; i < root_count; i++) {
|
||||
if (!walk_node(&walk, sorted[i], 1)) {
|
||||
|
||||
@@ -87,7 +87,10 @@ typedef struct cbm_resolve_proof_pool {
|
||||
cbm_resolve_analysis_t failure;
|
||||
} cbm_resolve_proof_pool_t;
|
||||
|
||||
/* Zero limits select finite defaults. A pool is confined to its owning
|
||||
/* A zero limit means no cap: a pool holds one node per distinct fact and each
|
||||
* operation is linear in its own text, so its total follows the input. A fixed
|
||||
* default stopped a large enough file partway through, at a point that also
|
||||
* depended on the length of the checkout path. A pool is confined to its owning
|
||||
* analysis thread; published nodes are immutable. Its arena must outlive
|
||||
* nodes, but pool storage may end after construction. Never allocate into a
|
||||
* shared registry's arena. */
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
enum { SOLVER_DEFAULT_NODES = 1000000, SOLVER_DEFAULT_EDGES = 4000000 };
|
||||
#define SOLVER_DEFAULT_STEPS UINT64_C(16000000)
|
||||
|
||||
typedef enum {
|
||||
SOLVER_LEAF,
|
||||
SOLVER_COPY,
|
||||
@@ -24,7 +21,7 @@ typedef struct {
|
||||
size_t remaining;
|
||||
size_t reverse_head;
|
||||
const char *selector, *prefix, *suffix;
|
||||
bool selected, select_type;
|
||||
bool selected, select_type, element;
|
||||
CBMTypeKind wrapper;
|
||||
uint32_t version;
|
||||
} solver_node_t;
|
||||
@@ -59,9 +56,9 @@ cbm_resolve_solver_t *cbm_resolve_solver_new(CBMArena *arena, cbm_resolve_proof_
|
||||
solver->arena = arena;
|
||||
solver->pool = pool;
|
||||
solver->limits =
|
||||
(cbm_resolve_solver_limits_t){.nodes = limits.nodes ? limits.nodes : SOLVER_DEFAULT_NODES,
|
||||
.edges = limits.edges ? limits.edges : SOLVER_DEFAULT_EDGES,
|
||||
.steps = limits.steps ? limits.steps : SOLVER_DEFAULT_STEPS};
|
||||
(cbm_resolve_solver_limits_t){.nodes = limits.nodes ? limits.nodes : SIZE_MAX,
|
||||
.edges = limits.edges ? limits.edges : SIZE_MAX,
|
||||
.steps = limits.steps ? limits.steps : UINT64_MAX};
|
||||
return solver;
|
||||
}
|
||||
|
||||
@@ -274,6 +271,15 @@ bool cbm_resolve_solver_type(cbm_resolve_solver_t *solver, const char *key, cons
|
||||
return solver->failure != CBM_SOLVER_FAILED;
|
||||
}
|
||||
|
||||
bool cbm_resolve_solver_element(cbm_resolve_solver_t *solver, const char *key,
|
||||
const char *dependency, const char *prefix,
|
||||
const cbm_resolve_proof_t *audit) {
|
||||
bool ok = cbm_resolve_solver_select_type(solver, key, dependency, prefix, "", audit);
|
||||
if (ok && solver->failure == CBM_SOLVER_PENDING)
|
||||
solver->nodes[solver->count - 1]->element = true;
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int compare_nodes(const void *left, const void *right) {
|
||||
return strcmp((*(solver_node_t *const *)left)->result.key,
|
||||
(*(solver_node_t *const *)right)->result.key);
|
||||
@@ -367,14 +373,15 @@ static cbm_resolve_result_t outcome(const cbm_resolve_solver_result_t *result) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Declarations of one name under one target are one graph identity, whichever a build keeps,
|
||||
* so they agree on what they select. Their results and fields are separate facts, and join
|
||||
* only if those agree in turn. */
|
||||
static bool same_value(cbm_resolve_solver_value_t left, cbm_resolve_solver_value_t right) {
|
||||
return left.target && right.target && !strcmp(left.target, right.target) &&
|
||||
left.kind == right.kind && left.module == right.module &&
|
||||
left.fact_kind == right.fact_kind &&
|
||||
!strcmp(left.type_identity ? left.type_identity : "",
|
||||
right.type_identity ? right.type_identity : "") &&
|
||||
!strcmp(left.declaration_identity ? left.declaration_identity : "",
|
||||
right.declaration_identity ? right.declaration_identity : "");
|
||||
right.type_identity ? right.type_identity : "");
|
||||
}
|
||||
|
||||
static const cbm_resolve_proof_t *missing_proof(cbm_resolve_solver_t *solver,
|
||||
@@ -441,7 +448,23 @@ static bool settle(cbm_resolve_solver_t *solver, solver_node_t *node, bool cycle
|
||||
result->status = CBM_SOLVER_AMBIGUOUS;
|
||||
}
|
||||
}
|
||||
if (node->operation == SOLVER_TYPE && result->status == CBM_SOLVER_KNOWN && have_value) {
|
||||
if (node->operation == SOLVER_TYPE && node->element && have_value &&
|
||||
result->status == CBM_SOLVER_KNOWN) {
|
||||
const CBMType *type = result->value.type;
|
||||
const CBMType *inner = !type ? NULL
|
||||
: type->kind == CBM_TYPE_SLICE ? type->data.slice.elem
|
||||
: type->kind == CBM_TYPE_POINTER ? type->data.pointer.elem
|
||||
: NULL;
|
||||
const char *identity =
|
||||
inner ? cbm_resolve_solver_type_identity(solver->arena, inner) : NULL;
|
||||
if (identity) {
|
||||
result->value.type = inner;
|
||||
result->value.type_identity = identity;
|
||||
result->value.fact_kind = CBM_SOLVER_FACT_TYPE;
|
||||
} else {
|
||||
result->status = CBM_SOLVER_UNSUPPORTED;
|
||||
}
|
||||
} else if (node->operation == SOLVER_TYPE && result->status == CBM_SOLVER_KNOWN && have_value) {
|
||||
const CBMType *type = result->value.type;
|
||||
if (!type || (node->wrapper != CBM_TYPE_NAMED && node->wrapper != CBM_TYPE_POINTER &&
|
||||
node->wrapper != CBM_TYPE_SLICE)) {
|
||||
@@ -564,7 +587,11 @@ bool cbm_resolve_solver_solve(cbm_resolve_solver_t *solver) {
|
||||
const char *identity = selector ? (node->select_type ? selector->value.type_identity
|
||||
: selector->value.target)
|
||||
: NULL;
|
||||
if (selector && selector->status == CBM_SOLVER_KNOWN && identity && *identity) {
|
||||
/* A wrapper's element is its own structure: there is nothing to look up. */
|
||||
if (node->element && selector && selector->status == CBM_SOLVER_KNOWN &&
|
||||
selector->value.type && selector->value.type->kind != CBM_TYPE_NAMED)
|
||||
node->operation = SOLVER_TYPE;
|
||||
else if (selector && selector->status == CBM_SOLVER_KNOWN && identity && *identity) {
|
||||
const char *selected = cbm_arena_sprintf(solver->arena, "%s%s%s", node->prefix,
|
||||
identity, node->suffix);
|
||||
const char **keys = cbm_arena_alloc(solver->arena, 2 * sizeof(*keys));
|
||||
|
||||
@@ -61,11 +61,13 @@ typedef struct cbm_resolve_solver cbm_resolve_solver_t;
|
||||
*
|
||||
* Registration then solve is single-threaded. Published results/proofs are
|
||||
* immutable. Keys, values and dependencies are copied into arena; audit roots
|
||||
* are cloned into pool. Both must outlive the solver. Zero limits use finite
|
||||
* defaults. Duplicate keys fail the whole solve closed, never last-wins.
|
||||
* Node/edge/step exhaustion makes EVERY result LIMITED, independent of input
|
||||
* order. Steps count node settlement and dependency indexing/inspection/
|
||||
* notification; stable-key sorting and indexed lookup do not spend semantic
|
||||
* are cloned into pool. Both must outlive the solver. A zero limit means no
|
||||
* cap: one solver holds a whole project's facts, each settled once, so its size
|
||||
* follows the input and a cap would only refuse a large enough repository. Only
|
||||
* the memory core can refuse it. Duplicate keys fail the whole solve closed,
|
||||
* never last-wins. An explicit node/edge/step limit makes EVERY result LIMITED
|
||||
* when reached, independent of input order. Steps count node settlement and dependency
|
||||
* indexing/inspection/ notification; stable-key sorting and indexed lookup do not spend semantic
|
||||
* budget. There are no corpus-wide repeated convergence passes. */
|
||||
cbm_resolve_solver_t *cbm_resolve_solver_new(CBMArena *arena, cbm_resolve_proof_pool_t *pool,
|
||||
cbm_resolve_solver_limits_t limits);
|
||||
@@ -98,6 +100,12 @@ const char *cbm_resolve_solver_type_identity(CBMArena *, const CBMType *);
|
||||
bool cbm_resolve_solver_type(cbm_resolve_solver_t *solver, const char *key, const char *dependency,
|
||||
CBMTypeKind wrapper, uint32_t version,
|
||||
const cbm_resolve_proof_t *audit);
|
||||
/* The inverse transfer: what a SLICE wrapper yields when ranged or indexed, or what a
|
||||
* POINTER points to. A NAMED type's element is not structural, so this subscribes to prefix +
|
||||
* its type identity like a SELECT, and an unregistered name stays UNKNOWN. */
|
||||
bool cbm_resolve_solver_element(cbm_resolve_solver_t *solver, const char *key,
|
||||
const char *dependency, const char *prefix,
|
||||
const cbm_resolve_proof_t *audit);
|
||||
bool cbm_resolve_solver_solve(cbm_resolve_solver_t *solver);
|
||||
|
||||
/* No named fact is implicitly known. Before solve all lookups are PENDING.
|
||||
|
||||
+183
-15
@@ -4628,9 +4628,10 @@ static cbm_resolve_record_t import_record(const CBMFileResult *result, const cha
|
||||
}
|
||||
|
||||
/* Module discovery is inventory, not dependency authority. This bounded Go
|
||||
* rule proves only an import within the source's nearest module, with no
|
||||
* nested go.mod boundary at the destination. Workspace/replacement mappings
|
||||
* remain unknown until their source directives have an exact model. */
|
||||
* rule proves an import within the source's nearest module, or one that module's
|
||||
* own go.mod `replace` (or the enclosing go.work `use`) maps to a local directory,
|
||||
* with no nested go.mod boundary at the destination. A required module with no such
|
||||
* directive comes from the module cache even if a same-named directory exists here. */
|
||||
static bool go_module_declaration(char *source, char *module, size_t capacity) {
|
||||
if (strstr(source, "/*"))
|
||||
return false; /* Block-comment stripping is outside this finite reader. */
|
||||
@@ -4702,29 +4703,196 @@ static bool go_module_owner(const cbm_pipeline_ctx_t *ctx, const char *path, boo
|
||||
owner[0] = '\0';
|
||||
}
|
||||
}
|
||||
/* The next significant token of one directive line, or NULL at its end or a comment. */
|
||||
static const char *go_directive_token(const char *cursor, char *token, size_t capacity) {
|
||||
while (*cursor == ' ' || *cursor == '\t' || *cursor == '\r')
|
||||
cursor++;
|
||||
if (!*cursor || !strncmp(cursor, "//", 2))
|
||||
return NULL;
|
||||
size_t size = 0;
|
||||
while (cursor[size] && !isspace((unsigned char)cursor[size]))
|
||||
size++;
|
||||
if (!size || size >= capacity || memchr(cursor, '"', size) || memchr(cursor, '`', size))
|
||||
return NULL;
|
||||
memcpy(token, cursor, size);
|
||||
token[size] = '\0';
|
||||
return cursor + size;
|
||||
}
|
||||
|
||||
/* `old [version] => ./local` naming a directory. A module-cache target keeps a version and
|
||||
* no leading dot, so it never matches; an absolute path lies outside the repository. */
|
||||
static bool go_replace_line(const char *line, const char *import, char *best, size_t *best_size,
|
||||
char *directory, size_t capacity) {
|
||||
char old[PKGMAP_PATH_BUF], token[PKGMAP_PATH_BUF];
|
||||
const char *cursor = go_directive_token(line, old, sizeof(old));
|
||||
if (!cursor)
|
||||
return false;
|
||||
cursor = go_directive_token(cursor, token, sizeof(token));
|
||||
if (cursor && strcmp(token, "=>"))
|
||||
cursor = go_directive_token(cursor, token, sizeof(token));
|
||||
if (!cursor || strcmp(token, "=>"))
|
||||
return false;
|
||||
cursor = go_directive_token(cursor, token, sizeof(token));
|
||||
if (!cursor || (strncmp(token, "./", 2) && strncmp(token, "../", 3) && strcmp(token, ".")))
|
||||
return false;
|
||||
size_t size = strlen(old);
|
||||
if (strncmp(import, old, size) || (import[size] && import[size] != '/') || size <= *best_size ||
|
||||
strlen(token) >= capacity)
|
||||
return false;
|
||||
*best_size = size;
|
||||
snprintf(best, PKGMAP_PATH_BUF, "%s", old);
|
||||
snprintf(directory, capacity, "%s", token);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* The module path and local directory of the longest replace matching this import. */
|
||||
static bool go_module_replacement(const cbm_pipeline_ctx_t *ctx, const char *owner,
|
||||
const char *import, char *old, char *directory, size_t capacity) {
|
||||
char relative[PKGMAP_PATH_BUF], absolute[PKGMAP_PATH_BUF];
|
||||
if (!cbm_repo_rel_join(owner, "go.mod", relative, sizeof(relative)) ||
|
||||
!import_absolute_path(ctx->repo_path, relative, absolute, sizeof(absolute)))
|
||||
return false;
|
||||
int length = 0;
|
||||
char *source = pkgmap_read_file(absolute, &length);
|
||||
if (!source || memchr(source, '\0', (size_t)length) || strstr(source, "/*")) {
|
||||
cbm_free(CBM_MEM_CLASS_EXTRACT, source);
|
||||
return false;
|
||||
}
|
||||
bool found = false, block = false;
|
||||
size_t best = 0;
|
||||
for (char *line = source; line;) {
|
||||
char *next = strchr(line, '\n');
|
||||
if (next)
|
||||
*next++ = '\0';
|
||||
while (*line == ' ' || *line == '\t' || *line == '\r')
|
||||
line++;
|
||||
if (block) {
|
||||
if (*line == ')')
|
||||
block = false;
|
||||
else
|
||||
found |= go_replace_line(line, import, old, &best, directory, capacity);
|
||||
} else if (!strncmp(line, "replace", 7) && isspace((unsigned char)line[7])) {
|
||||
const char *body = line + 7;
|
||||
while (isspace((unsigned char)*body))
|
||||
body++;
|
||||
if (*body == '(')
|
||||
block = true;
|
||||
else
|
||||
found |= go_replace_line(body, import, old, &best, directory, capacity);
|
||||
}
|
||||
line = next;
|
||||
}
|
||||
cbm_free(CBM_MEM_CLASS_EXTRACT, source);
|
||||
return found;
|
||||
}
|
||||
|
||||
/* The directory holding the nearest go.work above the source module. */
|
||||
static bool go_workspace_lists(const cbm_pipeline_ctx_t *ctx, const char *source_owner,
|
||||
const char *target_owner) {
|
||||
char owner[PKGMAP_PATH_BUF];
|
||||
if (snprintf(owner, sizeof(owner), "%s", source_owner) >= (int)sizeof(owner))
|
||||
return false;
|
||||
for (;;) {
|
||||
char relative[PKGMAP_PATH_BUF], absolute[PKGMAP_PATH_BUF];
|
||||
if (!cbm_repo_rel_join(owner, "go.work", relative, sizeof(relative)) ||
|
||||
!import_absolute_path(ctx->repo_path, relative, absolute, sizeof(absolute)))
|
||||
return false;
|
||||
struct stat info;
|
||||
if (stat(absolute, &info) == 0) {
|
||||
if (!S_ISREG(info.st_mode))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
if ((errno != ENOENT && errno != ENOTDIR) || !*owner)
|
||||
return false;
|
||||
char *slash = strrchr(owner, '/');
|
||||
if (slash)
|
||||
*slash = '\0';
|
||||
else
|
||||
owner[0] = '\0';
|
||||
}
|
||||
char relative[PKGMAP_PATH_BUF], absolute[PKGMAP_PATH_BUF];
|
||||
if (!cbm_repo_rel_join(owner, "go.work", relative, sizeof(relative)) ||
|
||||
!import_absolute_path(ctx->repo_path, relative, absolute, sizeof(absolute)))
|
||||
return false;
|
||||
int length = 0;
|
||||
char *source = pkgmap_read_file(absolute, &length);
|
||||
if (!source || memchr(source, '\0', (size_t)length) || strstr(source, "/*")) {
|
||||
cbm_free(CBM_MEM_CLASS_EXTRACT, source);
|
||||
return false;
|
||||
}
|
||||
bool has_source = false, has_target = false, block = false;
|
||||
for (char *line = source; line;) {
|
||||
char *next = strchr(line, '\n');
|
||||
if (next)
|
||||
*next++ = '\0';
|
||||
while (*line == ' ' || *line == '\t' || *line == '\r')
|
||||
line++;
|
||||
const char *entry = NULL;
|
||||
if (block) {
|
||||
if (*line == ')')
|
||||
block = false;
|
||||
else
|
||||
entry = line;
|
||||
} else if (!strncmp(line, "use", 3) && isspace((unsigned char)line[3])) {
|
||||
const char *body = line + 3;
|
||||
while (isspace((unsigned char)*body))
|
||||
body++;
|
||||
if (*body == '(')
|
||||
block = true;
|
||||
else
|
||||
entry = body;
|
||||
}
|
||||
char token[PKGMAP_PATH_BUF], used[PKGMAP_PATH_BUF];
|
||||
if (entry && go_directive_token(entry, token, sizeof(token)) &&
|
||||
cbm_repo_rel_join(owner, token, used, sizeof(used))) {
|
||||
has_source |= !strcmp(used, source_owner);
|
||||
has_target |= !strcmp(used, target_owner);
|
||||
}
|
||||
line = next;
|
||||
}
|
||||
cbm_free(CBM_MEM_CLASS_EXTRACT, source);
|
||||
return has_source && has_target;
|
||||
}
|
||||
|
||||
static bool go_import_inside_module(const char *import, const char *module, const char *owner,
|
||||
const char *target_path) {
|
||||
char expected[PKGMAP_PATH_BUF];
|
||||
size_t size = strlen(module);
|
||||
if (strncmp(import, module, size) || (import[size] && import[size] != '/'))
|
||||
return false;
|
||||
const char *suffix = import + size;
|
||||
if (*suffix)
|
||||
suffix++;
|
||||
return cbm_repo_rel_join(owner, suffix, expected, sizeof(expected)) &&
|
||||
!strcmp(expected, target_path);
|
||||
}
|
||||
|
||||
bool cbm_pipeline_go_import_owned(const cbm_pipeline_ctx_t *ctx, const char *source_rel,
|
||||
const CBMImport *import, const cbm_gbuf_node_t *target) {
|
||||
if (!ctx || !source_rel || !import || !import->module_path || !target || !target->file_path ||
|
||||
!target->label || strcmp(target->label, "Folder"))
|
||||
return false;
|
||||
char source_owner[PKGMAP_PATH_BUF], source_module[PKGMAP_PATH_BUF],
|
||||
target_owner[PKGMAP_PATH_BUF], target_module[PKGMAP_PATH_BUF], expected[PKGMAP_PATH_BUF];
|
||||
target_owner[PKGMAP_PATH_BUF], target_module[PKGMAP_PATH_BUF];
|
||||
if (!go_module_owner(ctx, source_rel, false, source_owner, source_module,
|
||||
sizeof(source_owner)) ||
|
||||
!go_module_owner(ctx, target->file_path, true, target_owner, target_module,
|
||||
sizeof(target_owner)) ||
|
||||
strcmp(source_owner, target_owner) || strcmp(source_module, target_module))
|
||||
sizeof(target_owner)))
|
||||
return false;
|
||||
size_t size = strlen(source_module);
|
||||
if (strncmp(import->module_path, source_module, size) ||
|
||||
(import->module_path[size] && import->module_path[size] != '/'))
|
||||
if (!go_import_inside_module(import->module_path, target_module, target_owner,
|
||||
target->file_path))
|
||||
return false;
|
||||
const char *suffix = import->module_path + size;
|
||||
if (*suffix)
|
||||
suffix++;
|
||||
if (!cbm_repo_rel_join(source_owner, suffix, expected, sizeof(expected)))
|
||||
return false;
|
||||
return !strcmp(expected, target->file_path);
|
||||
if (!strcmp(source_owner, target_owner))
|
||||
return !strcmp(source_module, target_module);
|
||||
/* The source module's own directive decides first: `go` resolves that way when the
|
||||
* module is built on its own, and a replacement names exactly one directory. */
|
||||
char old[PKGMAP_PATH_BUF], directory[PKGMAP_PATH_BUF], mapped[PKGMAP_PATH_BUF];
|
||||
if (go_module_replacement(ctx, source_owner, import->module_path, old, directory,
|
||||
sizeof(directory)))
|
||||
return cbm_repo_rel_join(source_owner, directory, mapped, sizeof(mapped)) &&
|
||||
!strcmp(mapped, target_owner) && !strcmp(old, target_module);
|
||||
return go_workspace_lists(ctx, source_owner, target_owner);
|
||||
}
|
||||
|
||||
/* Coordinator normalization before shared semantic resolution. The ordinary
|
||||
|
||||
+250
-54
@@ -68,12 +68,19 @@ typedef struct {
|
||||
bool invalid;
|
||||
} conditional_owner_t;
|
||||
|
||||
typedef struct target_file {
|
||||
const char *file;
|
||||
struct target_file *next;
|
||||
} target_file_t;
|
||||
|
||||
typedef struct {
|
||||
const char *file;
|
||||
const char *label;
|
||||
uint32_t start_line, end_line, second_start_line, second_end_line;
|
||||
unsigned declarations;
|
||||
conditional_owner_t *conditional;
|
||||
/* Other files declaring this QN (Go build-tag twins, C declaration headers). */
|
||||
target_file_t *other_files;
|
||||
bool ambiguous;
|
||||
/* Java/Kotlin/C# overloads of one owner share a qualified name, and the
|
||||
* graph keeps them as one node. That node faithfully names all of them. */
|
||||
@@ -86,6 +93,8 @@ typedef struct {
|
||||
bool signature_only;
|
||||
/* A same-QN declaration lives in another file, so no span can settle it. */
|
||||
bool cross_file;
|
||||
/* A different kind sharing the QN (C: struct and function) is no variant. */
|
||||
bool mixed_kinds, rebound;
|
||||
} target_proof_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -295,6 +304,37 @@ static bool ecma_merge_target(target_proof_t *prior, const CBMDefinition *defini
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool target_declared_in(const target_proof_t *target, const char *file) {
|
||||
if (!strcmp(target->file, file))
|
||||
return true;
|
||||
for (const target_file_t *other = target->other_files; other; other = other->next)
|
||||
if (!strcmp(other->file, file))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Overloads, trait impls and build-tag twins all stay live, so one node is exact for any of them.
|
||||
* Elsewhere a later declaration of a name rebinds it, as a second Python def does, and the bodies
|
||||
* are not equivalent: selecting that node would claim edges from a body nothing may reach. */
|
||||
static bool declarations_coexist(CBMLanguage language) {
|
||||
switch (language) {
|
||||
case CBM_LANG_GO:
|
||||
case CBM_LANG_RUST:
|
||||
case CBM_LANG_JAVA:
|
||||
case CBM_LANG_KOTLIN:
|
||||
case CBM_LANG_SCALA:
|
||||
case CBM_LANG_CSHARP:
|
||||
case CBM_LANG_CPP:
|
||||
case CBM_LANG_C:
|
||||
case CBM_LANG_SWIFT:
|
||||
case CBM_LANG_TYPESCRIPT:
|
||||
case CBM_LANG_TSX:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool add_target(cbm_shared_resolver_t *resolver, const CBMDefinition *definition,
|
||||
const char *file, CBMLanguage language) {
|
||||
if (!definition->qualified_name || !definition->label || !file)
|
||||
@@ -314,6 +354,16 @@ static bool add_target(cbm_shared_resolver_t *resolver, const CBMDefinition *def
|
||||
prior->overload_set = true;
|
||||
else
|
||||
prior->ambiguous |= !same_owner || !same_span || language == CBM_LANG_PYTHON;
|
||||
prior->mixed_kinds |= strcmp(prior->label, definition->label) != 0;
|
||||
prior->rebound |= !declarations_coexist(language);
|
||||
if (!target_declared_in(prior, file)) {
|
||||
target_file_t *other = cbm_arena_calloc(&resolver->arena, sizeof(*other));
|
||||
const char *copy = cbm_arena_strdup(&resolver->arena, file);
|
||||
if (!other || !copy)
|
||||
return false;
|
||||
*other = (target_file_t){.file = copy, .next = prior->other_files};
|
||||
prior->other_files = other;
|
||||
}
|
||||
if (prior->declarations == 1 && same_owner) {
|
||||
prior->second_start_line = definition->start_line;
|
||||
prior->second_end_line = definition->end_line;
|
||||
@@ -332,6 +382,7 @@ static bool add_target(cbm_shared_resolver_t *resolver, const CBMDefinition *def
|
||||
entry->end_line = definition->end_line;
|
||||
entry->declarations = 1;
|
||||
entry->signature_only = definition->is_abstract;
|
||||
entry->rebound = !declarations_coexist(language);
|
||||
if (!entry->file || !entry->label)
|
||||
return false;
|
||||
cbm_ht_set(resolver->targets, key, entry);
|
||||
@@ -450,8 +501,18 @@ static bool target_materialized(const file_resolver_t *resolver, const cbm_gbuf_
|
||||
!node->label)
|
||||
return false;
|
||||
const target_proof_t *proof = cbm_ht_get(resolver->shared->targets, node->qualified_name);
|
||||
if (!proof || proof->ambiguous || strcmp(proof->file, node->file_path) ||
|
||||
node->start_line < 0 || node->end_line < 0)
|
||||
if (!proof)
|
||||
return false;
|
||||
/* Several declarations of one kind sharing a QN are one identity in this graph: one
|
||||
* node whose `variants` keep every span and whose edges are the union of every body
|
||||
* (graph_buffer.c, "Definition variants"). Rust `impl From<A> for E` beside
|
||||
* `impl From<B> for E`, overloads and build-tag twins all land here, and any member
|
||||
* reaches the same node, so selecting that node is exact whichever member a site
|
||||
* means. The node must still be one the census recorded. */
|
||||
if (proof->ambiguous)
|
||||
return !proof->mixed_kinds && !proof->rebound && !strcmp(proof->label, node->label) &&
|
||||
target_declared_in(proof, node->file_path);
|
||||
if (strcmp(proof->file, node->file_path) || node->start_line < 0 || node->end_line < 0)
|
||||
return false;
|
||||
if (proof->merged_label && !strcmp(proof->merged_label, node->label) &&
|
||||
proof->merged_start_line == (uint32_t)node->start_line &&
|
||||
@@ -504,6 +565,27 @@ static bool target_declares_type(const file_resolver_t *resolver, const cbm_gbuf
|
||||
(cbm_label_is_type_like(proof->merged_label) || cbm_label_is_type_like(proof->label));
|
||||
}
|
||||
|
||||
/* The occurrence's enclosing declaration must be the source node, and that declaration
|
||||
* must live in this file. For a variant set the winning span may be another member's, even
|
||||
* in another file (Go build tags), so ownership is the census' list of declaring files. */
|
||||
static bool source_identity_holds(const file_resolver_t *resolver, const cbm_gbuf_node_t *source,
|
||||
bool conditional) {
|
||||
const char *file = resolver->file->rel_path;
|
||||
if (!source->file_path)
|
||||
return false;
|
||||
const target_proof_t *proof =
|
||||
resolver->shared && resolver->shared->complete && source->qualified_name
|
||||
? cbm_ht_get(resolver->shared->targets, source->qualified_name)
|
||||
: NULL;
|
||||
if (proof && proof->ambiguous && !proof->mixed_kinds && !proof->rebound &&
|
||||
source != resolver->file_node)
|
||||
return target_materialized(resolver, source) && target_declared_in(proof, file);
|
||||
if (strcmp(source->file_path, file))
|
||||
return false;
|
||||
return source == resolver->file_node || (source->label && !strcmp(source->label, "Module")) ||
|
||||
target_materialized(resolver, source) || conditional;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const target_proof_t *target;
|
||||
unsigned seen;
|
||||
@@ -1808,6 +1890,34 @@ static bool prepare_python_queries(cbm_shared_resolver_t *resolver, const cbm_pi
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Imports bind by declared package name, which every importer needs before any file's
|
||||
* imports are collected. An unreadable file simply declares nothing here. */
|
||||
static bool note_go_packages(cbm_shared_resolver_t *resolver, const cbm_pipeline_ctx_t *ctx,
|
||||
const cbm_file_info_t *files, int file_count) {
|
||||
for (int f = 0; f < file_count; f++) {
|
||||
if (files[f].language != CBM_LANG_GO)
|
||||
continue;
|
||||
size_t length = 0;
|
||||
char *source = load_source(files[f].path, &length);
|
||||
if (!source)
|
||||
continue;
|
||||
CBMArena arena;
|
||||
cbm_arena_init(&arena);
|
||||
char *path = cbm_arena_strdup(&arena, files[f].rel_path);
|
||||
if (path)
|
||||
cbm_normalize_path_sep(path);
|
||||
const char *module =
|
||||
path ? cbm_fqn_module_source_lang(&arena, ctx->project_name, path, CBM_LANG_GO) : NULL;
|
||||
bool ok =
|
||||
module && cbm_go_stabilization_note_package(resolver->go_types, module, source, length);
|
||||
cbm_arena_destroy(&arena);
|
||||
cbm_free(CBM_MEM_CLASS_EXTRACT, source);
|
||||
if (!ok)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool collect_go_types(cbm_shared_resolver_t *resolver, const cbm_pipeline_ctx_t *ctx,
|
||||
const CBMFileResult *result, const cbm_file_info_t *file) {
|
||||
if (file->language != CBM_LANG_GO)
|
||||
@@ -1902,6 +2012,10 @@ cbm_shared_resolver_t *cbm_shared_resolver_build(const cbm_pipeline_ctx_t *ctx,
|
||||
cbm_shared_resolver_free(resolver);
|
||||
return NULL;
|
||||
}
|
||||
if (!note_go_packages(resolver, ctx, files, file_count)) {
|
||||
cbm_shared_resolver_free(resolver);
|
||||
return NULL;
|
||||
}
|
||||
for (int f = 0; f < file_count; f++) {
|
||||
/* Embedded ECMAScript surfaces need their own complete use audit.
|
||||
* Their absence from this adapter cannot certify non-mutation. */
|
||||
@@ -1967,7 +2081,7 @@ cbm_shared_resolver_t *cbm_shared_resolver_build(const cbm_pipeline_ctx_t *ctx,
|
||||
}
|
||||
for (size_t i = 0; i < cbm_resolve_solver_count(resolver->solver); i++) {
|
||||
const cbm_resolve_solver_result_t *binding = cbm_resolve_solver_at(resolver->solver, i);
|
||||
if (strncmp(binding->key, "go/", 3))
|
||||
if (!cbm_go_stabilization_summary_key(binding->key))
|
||||
continue;
|
||||
const cbm_resolve_proof_view_t *proof = cbm_resolve_proof_view(binding->proof);
|
||||
char *payload = solver_summary_payload(binding);
|
||||
@@ -2872,9 +2986,11 @@ static const cbm_gbuf_node_t *source_node(const file_resolver_t *resolver, const
|
||||
if (!scope || !scope[0])
|
||||
return resolver->file_node;
|
||||
const cbm_gbuf_node_t *node = cbm_gbuf_find_by_qn(resolver->file->graph, scope);
|
||||
/* A class-level occurrence in a directory-module language carries the
|
||||
* package's QN. That node is every file of the package; this is one (#787). */
|
||||
return cbm_pipeline_node_is_dir_container(node) ? resolver->file_node : node;
|
||||
/* A package-level reference in a directory-module language carries the DIRECTORY
|
||||
* module QN, which is the Folder shared by every file of the package. An anonymous
|
||||
* callable's scope names no node at all. Either way the occurrence is in this file, the
|
||||
* attribution every ordinary resolver uses (#787). */
|
||||
return !node || cbm_pipeline_node_is_dir_container(node) ? resolver->file_node : node;
|
||||
}
|
||||
|
||||
static bool callable_node(const cbm_gbuf_node_t *node) {
|
||||
@@ -2928,6 +3044,44 @@ static const cbm_resolve_proof_t *local_occurrence_proof(const file_resolver_t *
|
||||
premises, 2, true);
|
||||
}
|
||||
|
||||
/* The extraction carrier read this token in this role. A Go reference's proof attests the
|
||||
* declaration, shared by every site that selects it. A package-level occurrence's proof
|
||||
* names the module scope, which in a directory-module language is the shared Folder, while
|
||||
* the edge starts at this file's node. Either way this ties that proof to the exact site. */
|
||||
static bool carrier_needs_site_proof(const file_resolver_t *resolver, const cbm_gbuf_node_t *source,
|
||||
const char *scope, const cbm_resolve_result_t *result) {
|
||||
if (!result->proof || !cbm_resolve_admit(result))
|
||||
return false;
|
||||
if (source == resolver->file_node && scope && scope[0] && strcmp(scope, source->qualified_name))
|
||||
return true;
|
||||
return resolver->file->language == CBM_LANG_GO &&
|
||||
cbm_resolve_proof_view(result->proof)->key.start_byte == 0;
|
||||
}
|
||||
|
||||
static const cbm_resolve_proof_t *carrier_occurrence_proof(const file_resolver_t *resolver,
|
||||
const char *source_qn, const char *name,
|
||||
const char *relation, uint32_t start,
|
||||
uint32_t end, CBMSourceOrigin origin,
|
||||
const cbm_resolve_result_t *result) {
|
||||
cbm_resolve_lookup_context_t *context = cbm_resolve_binding_lookup_context(resolver->bindings);
|
||||
if (!context || !source_qn || !name)
|
||||
return NULL;
|
||||
cbm_resolve_proof_source_t source =
|
||||
cbm_result_proof_source(resolver->file->result, origin, source_qn);
|
||||
source.file = resolver->file->rel_path;
|
||||
cbm_resolve_proof_key_t key = {.lookup_domain = source_qn,
|
||||
.lookup_key = name,
|
||||
.binding_identity = result->target,
|
||||
.start_byte = start,
|
||||
.end_byte = end,
|
||||
.origin = (uint32_t)origin,
|
||||
.detail = relation};
|
||||
cbm_resolve_result_t site = *result;
|
||||
site.proof = NULL;
|
||||
site.reason = "extraction_carrier_occurrence";
|
||||
return cbm_resolve_proof_leaf(context->pool, CBM_PROOF_SOURCE_FACT, &source, &key, &site);
|
||||
}
|
||||
|
||||
/* Projection retains the complete source spelling and argument metadata.
|
||||
* Fixed-size JSON buffers can silently merge long imported/reference names. */
|
||||
static char *record_properties(const char *name, const char *relation, uint32_t start, uint32_t end,
|
||||
@@ -2982,14 +3136,13 @@ static bool record_result(const file_resolver_t *resolver, const char *scope, co
|
||||
}
|
||||
const owner_variant_t *conditional =
|
||||
conditional_source_owner(resolver, source, start, end, origin);
|
||||
if (source &&
|
||||
(!source->file_path || strcmp(source->file_path, resolver->file->rel_path) ||
|
||||
(source != resolver->file_node && (!source->label || strcmp(source->label, "Module")) &&
|
||||
!target_materialized(resolver, source) && !conditional &&
|
||||
!ecma_source_span_settles(resolver, source, start, resolver->source,
|
||||
resolver->source_length)))) {
|
||||
if (source && !source_identity_holds(resolver, source, conditional != NULL) &&
|
||||
!(source->file_path && !strcmp(source->file_path, resolver->file->rel_path) &&
|
||||
ecma_source_span_settles(resolver, source, start, resolver->source,
|
||||
resolver->source_length))) {
|
||||
result.facts &= ~CBM_RESOLVE_FACT_EXACT_TARGET;
|
||||
result.reason = "source_declaration_identity_conflict";
|
||||
if (result.binding == CBM_RESOLVE_BINDING_UNIQUE)
|
||||
result.reason = "source_declaration_identity_conflict";
|
||||
}
|
||||
if (target && !target_materialized(resolver, target) &&
|
||||
!ecma_target_span_settles(resolver, target, name, start, end, origin)) {
|
||||
@@ -3021,6 +3174,9 @@ static bool record_result(const file_resolver_t *resolver, const char *scope, co
|
||||
const cbm_resolve_proof_t *binding =
|
||||
local_occurrence_proof(resolver, source ? source->qualified_name : scope, name, relation,
|
||||
start, end, origin, &result);
|
||||
if (!binding && source && carrier_needs_site_proof(resolver, source, scope, &result))
|
||||
binding = carrier_occurrence_proof(resolver, source->qualified_name, name, relation, start,
|
||||
end, origin, &result);
|
||||
if (conditional && result.proof && cbm_resolve_admit(&result)) {
|
||||
/* Extend local authority rather than cloning shared export ancestors
|
||||
* into every worker. An export summary cannot attest a local site. */
|
||||
@@ -3069,29 +3225,45 @@ static bool record_result(const file_resolver_t *resolver, const char *scope, co
|
||||
|
||||
static bool resolve_calls(file_resolver_t *resolver, CBMArena *arena) {
|
||||
const CBMFileResult *result = resolver->file->result;
|
||||
bool go = resolver->file->language == CBM_LANG_GO;
|
||||
for (int c = 0; c < result->calls.count; c++) {
|
||||
const CBMCall *call = &result->calls.items[c];
|
||||
const semantic_site_t *site =
|
||||
find_semantic(resolver, call->enclosing_func_qn, call->site_start_byte,
|
||||
call->site_end_byte, call->source_origin, CBM_RESOLVED_INVOCATION);
|
||||
/* Exact simple JSX tags can obtain an independent lexical proof. An
|
||||
* existing semantic result, including ambiguity, remains authoritative. */
|
||||
bool lexical_jsx = call->requires_lsp_resolution &&
|
||||
cbm_resolve_binding_jsx_occurrence(
|
||||
resolver->bindings, call->callee_name, call->site_start_byte,
|
||||
call->site_end_byte, call->source_origin);
|
||||
cbm_resolve_result_t outcome =
|
||||
site ? semantic_result(resolver, site, arena)
|
||||
: call->requires_lsp_resolution && !lexical_jsx
|
||||
? unresolved("semantic_resolution_required")
|
||||
: generic_result(resolver, call->callee_name, call->enclosing_func_qn,
|
||||
call->site_start_byte, call->site_end_byte, call->source_origin,
|
||||
true);
|
||||
if (resolver->file->language == CBM_LANG_GO && resolver->source_matches &&
|
||||
call->source_origin == CBM_SOURCE_ORIGIN_RAW)
|
||||
(void)cbm_go_stabilization_call(resolver->shared->go_types, resolver->file->rel_path,
|
||||
call->enclosing_func_qn, call->site_start_byte,
|
||||
call->site_end_byte, &outcome);
|
||||
/* The shared fixed point decides a Go site it can answer, including ambiguity. A site
|
||||
* it leaves unknown keeps whatever the hybrid engine proved on its own: an unknown
|
||||
* operand here is missing knowledge, not a contradiction of that proof. Go's lookup
|
||||
* goes through neither the registry nor a lexical adapter, so nothing else can answer. */
|
||||
cbm_resolve_result_t stabilized = unresolved("go_callee_unresolved");
|
||||
bool go_site =
|
||||
go && resolver->source_matches && call->source_origin == CBM_SOURCE_ORIGIN_RAW &&
|
||||
cbm_go_stabilization_call(resolver->shared->go_types, resolver->file->rel_path,
|
||||
call->enclosing_func_qn, call->site_start_byte,
|
||||
call->site_end_byte, &stabilized);
|
||||
cbm_resolve_result_t outcome;
|
||||
if (go_site && stabilized.binding != CBM_RESOLVE_BINDING_UNRESOLVED) {
|
||||
outcome = stabilized;
|
||||
} else {
|
||||
const semantic_site_t *site =
|
||||
find_semantic(resolver, call->enclosing_func_qn, call->site_start_byte,
|
||||
call->site_end_byte, call->source_origin, CBM_RESOLVED_INVOCATION);
|
||||
if (go) {
|
||||
outcome = site ? semantic_result(resolver, site, arena) : stabilized;
|
||||
if (!cbm_resolve_admit(&outcome))
|
||||
outcome = stabilized;
|
||||
} else {
|
||||
/* Exact simple JSX tags can obtain an independent lexical proof. An
|
||||
* existing semantic result, including ambiguity, remains authoritative. */
|
||||
bool lexical_jsx = call->requires_lsp_resolution &&
|
||||
cbm_resolve_binding_jsx_occurrence(
|
||||
resolver->bindings, call->callee_name, call->site_start_byte,
|
||||
call->site_end_byte, call->source_origin);
|
||||
outcome = site ? semantic_result(resolver, site, arena)
|
||||
: call->requires_lsp_resolution && !lexical_jsx
|
||||
? unresolved("semantic_resolution_required")
|
||||
: generic_result(resolver, call->callee_name, call->enclosing_func_qn,
|
||||
call->site_start_byte, call->site_end_byte,
|
||||
call->source_origin, true);
|
||||
}
|
||||
}
|
||||
if (!record_result(resolver, call->enclosing_func_qn, call->callee_name, "CALLS",
|
||||
call->site_start_byte, call->site_end_byte, call->source_origin, outcome,
|
||||
call))
|
||||
@@ -3100,6 +3272,20 @@ static bool resolve_calls(file_resolver_t *resolver, CBMArena *arena) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Go's lexical and selector rules decide a reference token the fixed point analysed. Go
|
||||
* has no other authority, so an unanalysed token skips the registry's lookup and its proof. */
|
||||
static bool go_reference(const file_resolver_t *resolver, uint32_t start, uint32_t end,
|
||||
CBMSourceOrigin origin, bool member, cbm_resolve_result_t *out) {
|
||||
if (resolver->file->language != CBM_LANG_GO)
|
||||
return false;
|
||||
if (resolver->source_matches && origin == CBM_SOURCE_ORIGIN_RAW &&
|
||||
cbm_go_stabilization_reference(resolver->shared->go_types, resolver->file->rel_path, start,
|
||||
end, out))
|
||||
return true;
|
||||
*out = unresolved(member ? "receiver_type_required" : "go_reference_unresolved");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool resolve_usages(file_resolver_t *resolver, CBMArena *arena) {
|
||||
const CBMFileResult *result = resolver->file->result;
|
||||
for (int u = 0; u < result->usages.count; u++) {
|
||||
@@ -3136,13 +3322,16 @@ static bool resolve_usages(file_resolver_t *resolver, CBMArena *arena) {
|
||||
site = find_semantic(resolver, usage->enclosing_func_qn, usage->site_start_byte,
|
||||
usage->site_end_byte, usage->source_origin,
|
||||
CBM_RESOLVED_FIELD_REFERENCE);
|
||||
cbm_resolve_result_t outcome =
|
||||
site ? semantic_result(resolver, site, arena)
|
||||
: usage->is_member_access
|
||||
? unresolved("receiver_type_required")
|
||||
: generic_result(resolver, usage->ref_name, usage->enclosing_func_qn,
|
||||
usage->site_start_byte, usage->site_end_byte, usage->source_origin,
|
||||
false);
|
||||
cbm_resolve_result_t outcome;
|
||||
if (site)
|
||||
outcome = semantic_result(resolver, site, arena);
|
||||
else if (!go_reference(resolver, usage->site_start_byte, usage->site_end_byte,
|
||||
usage->source_origin, usage->is_member_access, &outcome))
|
||||
outcome = usage->is_member_access
|
||||
? unresolved("receiver_type_required")
|
||||
: generic_result(resolver, usage->ref_name, usage->enclosing_func_qn,
|
||||
usage->site_start_byte, usage->site_end_byte,
|
||||
usage->source_origin, false);
|
||||
/* Source binding can establish a direct callable argument without a
|
||||
* hybrid row. Composite expressions remain USAGE: the extraction
|
||||
* carrier deliberately leaves may_be_call_reference false for them. */
|
||||
@@ -3164,12 +3353,16 @@ static bool resolve_writes(file_resolver_t *resolver, CBMArena *arena) {
|
||||
const semantic_site_t *site =
|
||||
find_semantic(resolver, rw->enclosing_func_qn, rw->site_start_byte, rw->site_end_byte,
|
||||
rw->source_origin, CBM_RESOLVED_FIELD_REFERENCE);
|
||||
cbm_resolve_result_t outcome =
|
||||
site ? semantic_result(resolver, site, arena)
|
||||
: rw->is_member_access
|
||||
? unresolved("receiver_type_required")
|
||||
: generic_result(resolver, rw->var_name, rw->enclosing_func_qn, rw->site_start_byte,
|
||||
rw->site_end_byte, rw->source_origin, false);
|
||||
cbm_resolve_result_t outcome;
|
||||
if (site)
|
||||
outcome = semantic_result(resolver, site, arena);
|
||||
else if (!go_reference(resolver, rw->site_start_byte, rw->site_end_byte, rw->source_origin,
|
||||
rw->is_member_access, &outcome))
|
||||
outcome = rw->is_member_access
|
||||
? unresolved("receiver_type_required")
|
||||
: generic_result(resolver, rw->var_name, rw->enclosing_func_qn,
|
||||
rw->site_start_byte, rw->site_end_byte,
|
||||
rw->source_origin, false);
|
||||
if (!record_result(resolver, rw->enclosing_func_qn, rw->var_name,
|
||||
rw->is_write ? "WRITES" : "READS", rw->site_start_byte,
|
||||
rw->site_end_byte, rw->source_origin, outcome, NULL))
|
||||
@@ -3187,13 +3380,16 @@ static bool resolve_types(file_resolver_t *resolver) {
|
||||
type->site_start_byte, type->site_end_byte,
|
||||
type->source_origin, CBM_RESOLVED_TYPE_REFERENCE)
|
||||
: NULL;
|
||||
cbm_resolve_result_t outcome =
|
||||
site ? semantic_result(resolver, site, resolver->arena)
|
||||
: !type->site_is_exact
|
||||
? unresolved("type_occurrence_identity_missing")
|
||||
: generic_result(resolver, type->type_name, type->enclosing_func_qn,
|
||||
type->site_start_byte, type->site_end_byte, type->source_origin,
|
||||
false);
|
||||
cbm_resolve_result_t outcome;
|
||||
if (site)
|
||||
outcome = semantic_result(resolver, site, resolver->arena);
|
||||
else if (!type->site_is_exact)
|
||||
outcome = unresolved("type_occurrence_identity_missing");
|
||||
else if (!go_reference(resolver, type->site_start_byte, type->site_end_byte,
|
||||
type->source_origin, false, &outcome))
|
||||
outcome = generic_result(resolver, type->type_name, type->enclosing_func_qn,
|
||||
type->site_start_byte, type->site_end_byte,
|
||||
type->source_origin, false);
|
||||
if (!record_result(resolver, type->enclosing_func_qn, type->type_name, "USES_TYPE",
|
||||
type->site_start_byte, type->site_end_byte, type->source_origin, outcome,
|
||||
NULL))
|
||||
|
||||
+22
-67
@@ -13506,7 +13506,7 @@ static void write_go_init_lsp_fixture(const char *tmp, int pad_files) {
|
||||
|
||||
/* Each init's LSP-resolved edges start at that init's own node. */
|
||||
static int assert_go_init_lsp_edges(cbm_store_t *s, const char *project) {
|
||||
const char *lsp = "\"strategy\":\"lsp_";
|
||||
const char *lsp = "\"strategy\":\"shared_resolver\"";
|
||||
ASSERT_GTE(go_calls_between(s, project, ".reg.init#a.go:1", ".reg.Codec.register", lsp), 1);
|
||||
ASSERT_GTE(go_calls_between(s, project, ".reg.init#b.go:1", ".reg.Store.register", lsp), 1);
|
||||
ASSERT_GTE(go_calls_between(s, project, ".reg.init#b.go:2", ".reg.Store.warm", lsp), 1);
|
||||
@@ -14216,13 +14216,7 @@ static bool go_multi_return_quality(cbm_store_t *store, const char *project, con
|
||||
.label = "Method",
|
||||
.declaration_line = 11,
|
||||
.declaration_text = "func (e *Encoder) EncodeTuple() int { return e.n }"},
|
||||
.mode = RQ_KNOWN_GAP,
|
||||
.gap_id = "GO-RETURN-COMPONENT-RECEIVER-FLOW",
|
||||
.why = "Tuple/named/slice return components and subsequent field/index receivers lack a "
|
||||
"joined exact type-flow proof.",
|
||||
.tried = "The original factories and receiver expressions remain; direct factory "
|
||||
"invocation is required independently. Interface calls target their static "
|
||||
"interface declaration, never a guessed implementation."},
|
||||
.mode = RQ_REQUIRED},
|
||||
{.case_id = "go.return-flow.UseNamed.factory",
|
||||
.site = {.file = "app/app.go", .caller = "UseNamed", .needle = "enc.NewNamed()"},
|
||||
.relation = "CALLS",
|
||||
@@ -14240,13 +14234,7 @@ static bool go_multi_return_quality(cbm_store_t *store, const char *project, con
|
||||
.label = "Method",
|
||||
.declaration_line = 13,
|
||||
.declaration_text = "func (e *Encoder) EncodeNamed() int { return e.n }"},
|
||||
.mode = RQ_KNOWN_GAP,
|
||||
.gap_id = "GO-RETURN-COMPONENT-RECEIVER-FLOW",
|
||||
.why = "Tuple/named/slice return components and subsequent field/index receivers lack a "
|
||||
"joined exact type-flow proof.",
|
||||
.tried = "The original factories and receiver expressions remain; direct factory "
|
||||
"invocation is required independently. Interface calls target their static "
|
||||
"interface declaration, never a guessed implementation."},
|
||||
.mode = RQ_REQUIRED},
|
||||
{.case_id = "go.return-flow.UseField.factory",
|
||||
.site = {.file = "app/app.go", .caller = "UseField", .needle = "newHead()"},
|
||||
.relation = "CALLS",
|
||||
@@ -14267,13 +14255,7 @@ static bool go_multi_return_quality(cbm_store_t *store, const char *project, con
|
||||
.label = "Method",
|
||||
.declaration_line = 5,
|
||||
.declaration_text = "func (i *isolation) LowWatermarkField() int { return 0 }"},
|
||||
.mode = RQ_KNOWN_GAP,
|
||||
.gap_id = "GO-RETURN-COMPONENT-RECEIVER-FLOW",
|
||||
.why = "Tuple/named/slice return components and subsequent field/index receivers lack a "
|
||||
"joined exact type-flow proof.",
|
||||
.tried = "The original factories and receiver expressions remain; direct factory "
|
||||
"invocation is required independently. Interface calls target their static "
|
||||
"interface declaration, never a guessed implementation."},
|
||||
.mode = RQ_REQUIRED},
|
||||
{.case_id = "go.return-flow.UseSlice.factory",
|
||||
.site = {.file = "app/app.go", .caller = "UseSlice", .needle = "diag.Run()"},
|
||||
.relation = "CALLS",
|
||||
@@ -14292,13 +14274,7 @@ static bool go_multi_return_quality(cbm_store_t *store, const char *project, con
|
||||
.declaration_line = 7,
|
||||
.declaration_text =
|
||||
"func (d Diagnostics) HasErrorsSlice() bool { return len(d) > 0 }"},
|
||||
.mode = RQ_KNOWN_GAP,
|
||||
.gap_id = "GO-RETURN-COMPONENT-RECEIVER-FLOW",
|
||||
.why = "Tuple/named/slice return components and subsequent field/index receivers lack a "
|
||||
"joined exact type-flow proof.",
|
||||
.tried = "The original factories and receiver expressions remain; direct factory "
|
||||
"invocation is required independently. Interface calls target their static "
|
||||
"interface declaration, never a guessed implementation."},
|
||||
.mode = RQ_REQUIRED},
|
||||
{.case_id = "go.return-flow.UseGrouped.factory",
|
||||
.site = {.file = "app/app.go", .caller = "UseGrouped", .needle = "enc.NewPair()"},
|
||||
.relation = "CALLS",
|
||||
@@ -14317,13 +14293,7 @@ static bool go_multi_return_quality(cbm_store_t *store, const char *project, con
|
||||
.label = "Method",
|
||||
.declaration_line = 15,
|
||||
.declaration_text = "func (e *Encoder) EncodeGrouped() int { return e.n }"},
|
||||
.mode = RQ_KNOWN_GAP,
|
||||
.gap_id = "GO-RETURN-COMPONENT-RECEIVER-FLOW",
|
||||
.why = "Tuple/named/slice return components and subsequent field/index receivers lack a "
|
||||
"joined exact type-flow proof.",
|
||||
.tried = "The original factories and receiver expressions remain; direct factory "
|
||||
"invocation is required independently. Interface calls target their static "
|
||||
"interface declaration, never a guessed implementation."},
|
||||
.mode = RQ_REQUIRED},
|
||||
{.case_id = "go.return-flow.UseIndex.factory",
|
||||
.site = {.file = "app/app.go", .caller = "UseIndex", .needle = "samples.Generate()"},
|
||||
.relation = "CALLS",
|
||||
@@ -14341,13 +14311,7 @@ static bool go_multi_return_quality(cbm_store_t *store, const char *project, con
|
||||
.label = "Method",
|
||||
.declaration_line = 3,
|
||||
.declaration_text = "type Sample interface{ TS() int64 }"},
|
||||
.mode = RQ_KNOWN_GAP,
|
||||
.gap_id = "GO-RETURN-COMPONENT-RECEIVER-FLOW",
|
||||
.why = "Tuple/named/slice return components and subsequent field/index receivers lack a "
|
||||
"joined exact type-flow proof.",
|
||||
.tried = "The original factories and receiver expressions remain; direct factory "
|
||||
"invocation is required independently. Interface calls target their static "
|
||||
"interface declaration, never a guessed implementation."},
|
||||
.mode = RQ_REQUIRED},
|
||||
{.case_id = "go.return-flow.UseTupleSlice.factory",
|
||||
.site = {.file = "app/app.go", .caller = "UseTupleSlice", .needle = "samples.List()"},
|
||||
.relation = "CALLS",
|
||||
@@ -14366,13 +14330,7 @@ static bool go_multi_return_quality(cbm_store_t *store, const char *project, con
|
||||
.label = "Method",
|
||||
.declaration_line = 3,
|
||||
.declaration_text = "type Sample interface{ TS() int64 }"},
|
||||
.mode = RQ_KNOWN_GAP,
|
||||
.gap_id = "GO-RETURN-COMPONENT-RECEIVER-FLOW",
|
||||
.why = "Tuple/named/slice return components and subsequent field/index receivers lack a "
|
||||
"joined exact type-flow proof.",
|
||||
.tried = "The original factories and receiver expressions remain; direct factory "
|
||||
"invocation is required independently. Interface calls target their static "
|
||||
"interface declaration, never a guessed implementation."},
|
||||
.mode = RQ_REQUIRED},
|
||||
};
|
||||
return pipeline_quality_files(store, project, "go-return-flow", root, files, 5, expected,
|
||||
sizeof(expected) / sizeof(expected[0]), NULL, 0);
|
||||
@@ -14455,11 +14413,7 @@ static bool go_global_write_quality(cbm_store_t *store, const char *project, con
|
||||
.label = "Variable",
|
||||
.declaration_line = 3,
|
||||
.declaration_text = "var Counter int"},
|
||||
.mode = RQ_KNOWN_GAP,
|
||||
.gap_id = "GO-PACKAGE-VARIABLE-WRITE",
|
||||
.why = "Go package-variable writes have no admitted source binding and write-role bridge.",
|
||||
.tried = "Both files declare the same configured package and no local shadows Counter; the "
|
||||
"legacy short-name writer has no sealed occurrence evidence."};
|
||||
.mode = RQ_REQUIRED};
|
||||
return pipeline_quality_files(store, project, "go-package-write", root, files, 2, &expected, 1,
|
||||
NULL, 0);
|
||||
}
|
||||
@@ -15351,11 +15305,7 @@ static bool go_selector_roles_quality(cbm_store_t *store, const char *project, c
|
||||
.label = "Field",
|
||||
.declaration_line = 4,
|
||||
.declaration_text = "\terr error"},
|
||||
.mode = RQ_KNOWN_GAP,
|
||||
.gap_id = "GO-FIELD-WRITE-PROOF",
|
||||
.why = "Go field writes have no admitted owner/declaration/write-role proof bridge.",
|
||||
.tried = "The explicit Tracker receiver and unique declared field establish the fixture "
|
||||
"target; generic reference extraction does not carry that owner proof."},
|
||||
.mode = RQ_REQUIRED},
|
||||
{.case_id = "go.typed-field-value-use",
|
||||
.site = {.file = "state/reset.go", .caller = "Reset"},
|
||||
.relation = "USAGE",
|
||||
@@ -15364,11 +15314,7 @@ static bool go_selector_roles_quality(cbm_store_t *store, const char *project, c
|
||||
.label = "Field",
|
||||
.declaration_line = 5,
|
||||
.declaration_text = "\tn int"},
|
||||
.mode = RQ_KNOWN_GAP,
|
||||
.gap_id = "GO-FIELD-VALUE-PROOF",
|
||||
.why = "Go field value uses have no admitted owner/declaration proof bridge.",
|
||||
.tried = "The return selector uses Tracker.n; a name-only value reference cannot prove "
|
||||
"the owner."},
|
||||
.mode = RQ_REQUIRED},
|
||||
{.case_id = "go.typed-field-read",
|
||||
.site = {.file = "state/reset.go", .caller = "Reset"},
|
||||
.relation = "READS",
|
||||
@@ -15381,7 +15327,16 @@ static bool go_selector_roles_quality(cbm_store_t *store, const char *project, c
|
||||
.gap_id = "GO-FIELD-READ-PROOF",
|
||||
.why = "Go field reads have no admitted owner/declaration/read-role proof bridge.",
|
||||
.tried = "The return expression reads Tracker.n; neither spelling nor the write guard "
|
||||
"supplies exact read evidence."}};
|
||||
"supplies exact read evidence."},
|
||||
{.case_id = "go.receiver-type-reference",
|
||||
.site = {.file = "state/reset.go", .caller = "Reset", .needle = "Tracker"},
|
||||
.relation = "USAGE",
|
||||
.target = {.file = "state/state.go",
|
||||
.suffix = "Tracker",
|
||||
.label = "Struct",
|
||||
.declaration_line = 3,
|
||||
.declaration_text = "type Tracker struct {"},
|
||||
.mode = RQ_REQUIRED}};
|
||||
/* Anchor the n leaf of the literal return selector without relying on a
|
||||
* substring count through function keywords or identifiers. */
|
||||
char *source = pipeline_quality_source(root, "state/reset.go");
|
||||
@@ -15398,7 +15353,7 @@ static bool go_selector_roles_quality(cbm_store_t *store, const char *project, c
|
||||
expected[i].site.end_byte = at + 1;
|
||||
}
|
||||
free(source);
|
||||
return pipeline_quality_files(store, project, "go-selector-roles", root, files, 2, expected, 3,
|
||||
return pipeline_quality_files(store, project, "go-selector-roles", root, files, 2, expected, 4,
|
||||
NULL, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,10 @@ TEST(resolver_capabilities_report_supported_subsets_and_quality_gaps) {
|
||||
ASSERT(cbm_resolve_edge_capability(CBM_LANG_GO, "CALL_REFERENCE", &edge));
|
||||
ASSERT_EQ(edge.availability, CBM_RESOLVE_EDGE_SUPPORTED_SUBSET);
|
||||
ASSERT(cbm_resolve_edge_capability(CBM_LANG_GO, "WRITES", &edge));
|
||||
ASSERT_EQ(edge.availability, CBM_RESOLVE_EDGE_SUPPORTED_SUBSET);
|
||||
ASSERT(cbm_resolve_edge_capability(CBM_LANG_GO, "USES_TYPE", &edge));
|
||||
ASSERT_EQ(edge.availability, CBM_RESOLVE_EDGE_SUPPORTED_SUBSET);
|
||||
ASSERT(cbm_resolve_edge_capability(CBM_LANG_GO, "READS", &edge));
|
||||
ASSERT_EQ(edge.availability, CBM_RESOLVE_EDGE_UNSUPPORTED);
|
||||
ASSERT(cbm_resolve_edge_capability(CBM_LANG_DART, "IMPORTS", &edge));
|
||||
ASSERT_EQ(edge.availability, CBM_RESOLVE_EDGE_SUPPORTED_SUBSET);
|
||||
|
||||
@@ -713,7 +713,191 @@ TEST(resolver_pipeline_infra_adapter_pool_has_no_proof_work_budget) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
static int python_file_emitted_calls(size_t name_bytes, int functions) {
|
||||
enum { LINE_BYTES = 64 };
|
||||
char *project = malloc(name_bytes + 1);
|
||||
char *source = malloc((size_t)functions * LINE_BYTES + 64);
|
||||
int emitted = -1;
|
||||
if (!project || !source) {
|
||||
free(project);
|
||||
free(source);
|
||||
return emitted;
|
||||
}
|
||||
memset(project, 'p', name_bytes);
|
||||
project[name_bytes] = '\0';
|
||||
size_t used = (size_t)snprintf(source, 64, "def base():\n return 1\n");
|
||||
for (int i = 0; i < functions; i++)
|
||||
used += (size_t)snprintf(source + used, LINE_BYTES, "def f%d():\n return base()\n", i);
|
||||
CBMFileResult *result =
|
||||
cbm_extract_file(source, (int)used, CBM_LANG_PYTHON, project, "module.py", 0, NULL, NULL);
|
||||
cbm_gbuf_t *graph = cbm_gbuf_new(project, "/tmp");
|
||||
cbm_registry_t *registry = cbm_registry_new();
|
||||
if (result && graph && registry) {
|
||||
result->source = source;
|
||||
result->source_len = (int)used;
|
||||
const char *target = NULL;
|
||||
for (int d = 0; d < result->defs.count; d++) {
|
||||
const CBMDefinition *def = &result->defs.items[d];
|
||||
if (!def->qualified_name || !def->name || !def->label)
|
||||
continue;
|
||||
cbm_gbuf_upsert_node(graph, def->label, def->name, def->qualified_name, "module.py",
|
||||
def->start_line, def->end_line, "{}");
|
||||
cbm_registry_add(registry, def->name, def->qualified_name, def->label);
|
||||
if (!strcmp(def->name, "base"))
|
||||
target = def->qualified_name;
|
||||
}
|
||||
cbm_file_info_t file = {
|
||||
.path = "/tmp/module.py", .rel_path = "module.py", .language = CBM_LANG_PYTHON};
|
||||
cbm_pipeline_ctx_t ctx = {
|
||||
.project_name = project, .repo_path = "/tmp", .gbuf = graph, .registry = registry};
|
||||
cbm_shared_resolver_t *shared = cbm_shared_resolver_build(&ctx, &file, 1, &result);
|
||||
cbm_resolver_file_t input = {.ctx = &ctx,
|
||||
.registry = registry,
|
||||
.graph = graph,
|
||||
.output = graph,
|
||||
.result = result,
|
||||
.rel_path = "module.py",
|
||||
.module_qn = result->module_qn,
|
||||
.language = CBM_LANG_PYTHON};
|
||||
if (shared && target && cbm_shared_resolver_file(shared, &input))
|
||||
emitted = emitted_target(graph, "CALLS", target);
|
||||
cbm_shared_resolver_free(shared);
|
||||
}
|
||||
cbm_registry_free(registry);
|
||||
cbm_gbuf_free(graph);
|
||||
if (result)
|
||||
cbm_free_result(result);
|
||||
free(source);
|
||||
free(project);
|
||||
return emitted;
|
||||
}
|
||||
|
||||
/* A file's own lookup proofs have the same shape as a project's: one per occurrence, each
|
||||
* linear in its text. A fixed budget stopped resolving partway through any large enough
|
||||
* file, leaving every later occurrence as registry_lookup_proof_unavailable, and how soon
|
||||
* depended on the length of the checkout path because every proof hashes the project name.
|
||||
* The short name is the control: only the hashed text differs between these two files. */
|
||||
TEST(resolver_pipeline_file_lookup_proofs_have_no_work_budget) {
|
||||
enum { FUNCTIONS = 400 };
|
||||
ASSERT_EQ(python_file_emitted_calls(4, FUNCTIONS), FUNCTIONS);
|
||||
ASSERT_EQ(python_file_emitted_calls(4000, FUNCTIONS), FUNCTIONS);
|
||||
PASS();
|
||||
}
|
||||
|
||||
static int reason_rows(const cbm_gbuf_t *graph, const char *reason) {
|
||||
size_t count = 0;
|
||||
const cbm_resolve_record_t *records = cbm_gbuf_resolutions(graph, &count);
|
||||
int matched = 0;
|
||||
for (size_t r = 0; r < count; r++)
|
||||
matched += records[r].result.reason && !strcmp(records[r].result.reason, reason);
|
||||
return matched;
|
||||
}
|
||||
|
||||
/* Emitted CALLS to target_name, or with reason set, the rows carrying that reason. */
|
||||
static int rust_source_count(const char *source, const char *target_name, const char *reason) {
|
||||
CBMFileResult *result = cbm_extract_file(source, (int)strlen(source), CBM_LANG_RUST, "proj",
|
||||
"lib.rs", 0, NULL, NULL);
|
||||
cbm_gbuf_t *graph = cbm_gbuf_new("proj", "/tmp");
|
||||
cbm_registry_t *registry = cbm_registry_new();
|
||||
int emitted = -1;
|
||||
if (result && graph && registry && add_result(graph, registry, result, "lib.rs", source)) {
|
||||
char target[128];
|
||||
snprintf(target, sizeof(target), "%s.%s", result->module_qn, target_name);
|
||||
cbm_file_info_t file = {
|
||||
.path = "/tmp/lib.rs", .rel_path = "lib.rs", .language = CBM_LANG_RUST};
|
||||
cbm_pipeline_ctx_t ctx = {
|
||||
.project_name = "proj", .repo_path = "/tmp", .gbuf = graph, .registry = registry};
|
||||
cbm_shared_resolver_t *shared = cbm_shared_resolver_build(&ctx, &file, 1, &result);
|
||||
cbm_resolver_file_t input = {.ctx = &ctx,
|
||||
.registry = registry,
|
||||
.graph = graph,
|
||||
.output = graph,
|
||||
.result = result,
|
||||
.rel_path = "lib.rs",
|
||||
.module_qn = result->module_qn,
|
||||
.language = CBM_LANG_RUST};
|
||||
if (shared && cbm_shared_resolver_file(shared, &input))
|
||||
emitted = reason ? reason_rows(graph, reason) : emitted_target(graph, "CALLS", target);
|
||||
cbm_shared_resolver_free(shared);
|
||||
}
|
||||
cbm_registry_free(registry);
|
||||
cbm_gbuf_free(graph);
|
||||
if (result)
|
||||
cbm_free_result(result);
|
||||
return emitted;
|
||||
}
|
||||
static int rust_source_emits(const char *source, const char *target_name) {
|
||||
return rust_source_count(source, target_name, NULL);
|
||||
}
|
||||
|
||||
/* Two `impl From<_> for Error` both declare Error.from: one QN, so one graph node, whose edges
|
||||
* are the union of every variant's body. meilisearch alone has hundreds of these, and every
|
||||
* call inside them was void as source_declaration_identity_conflict. The Rust adapter decides
|
||||
* these sites itself, so this pins the identity. Emission through it is pinned for Java in
|
||||
* resolver_stabilization_overloads_share_an_identity_and_rebinding_does_not. */
|
||||
TEST(resolver_pipeline_same_kind_declarations_sharing_a_qn_are_one_source) {
|
||||
const char *source = "struct Error;\nstruct A;\nstruct B;\n"
|
||||
"fn helper() -> i32 { 1 }\n"
|
||||
"impl From<A> for Error {\n"
|
||||
" fn from(a: A) -> Error { helper(); Error }\n"
|
||||
"}\n"
|
||||
"impl From<B> for Error {\n"
|
||||
" fn from(b: B) -> Error { helper(); Error }\n"
|
||||
"}\n";
|
||||
ASSERT_EQ(rust_source_count(source, "helper", "source_declaration_identity_conflict"), 0);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* A struct and a function sharing a QN are different things: the node can stand for only one
|
||||
* of them, so the function's body cannot speak for it. */
|
||||
TEST(resolver_pipeline_different_kinds_sharing_a_qn_stay_a_conflict) {
|
||||
const char *source = "struct Twin { x: i32 }\n"
|
||||
"fn Twin() -> i32 { helper() }\n"
|
||||
"fn helper() -> i32 { 1 }\n";
|
||||
ASSERT_EQ(rust_source_emits(source, "helper"), 0);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* USES_TYPE scopes were file + bare function name, which no node carries for a method's class
|
||||
* or a Go package, so every signature type in those reported source_not_materialized. */
|
||||
TEST(resolver_pipeline_signature_type_refs_name_the_declaring_scope) {
|
||||
const char *go = "package lib\n\ntype Thing struct{}\n\n"
|
||||
"func Make(t Thing) Thing { return t }\n\n"
|
||||
"func (t Thing) Same(o Thing) Thing { return o }\n";
|
||||
CBMFileResult *result = cbm_extract_file(go, (int)strlen(go), CBM_LANG_GO, "proj", "lib/lib.go",
|
||||
0, NULL, NULL);
|
||||
ASSERT_NOT_NULL(result);
|
||||
int make = 0, same = 0, wrong = 0;
|
||||
for (int i = 0; i < result->type_refs.count; i++) {
|
||||
const char *scope = result->type_refs.items[i].enclosing_func_qn;
|
||||
make += scope && !strcmp(scope, "proj.lib.Make");
|
||||
same += scope && !strcmp(scope, "proj.lib.Thing.Same");
|
||||
wrong += scope && strstr(scope, "lib.lib") != NULL;
|
||||
}
|
||||
cbm_free_result(result);
|
||||
ASSERT_GT(make, 0);
|
||||
ASSERT_GT(same, 0);
|
||||
ASSERT_EQ(wrong, 0);
|
||||
const char *ts = "interface Thing { x: number }\n"
|
||||
"class Service {\n run(t: Thing): Thing { return t; }\n}\n";
|
||||
result = cbm_extract_file(ts, (int)strlen(ts), CBM_LANG_TYPESCRIPT, "proj", "app.ts", 0, NULL,
|
||||
NULL);
|
||||
ASSERT_NOT_NULL(result);
|
||||
int method = 0;
|
||||
for (int i = 0; i < result->type_refs.count; i++) {
|
||||
const char *scope = result->type_refs.items[i].enclosing_func_qn;
|
||||
method += scope && !strcmp(scope, "proj.app.Service.run");
|
||||
}
|
||||
cbm_free_result(result);
|
||||
ASSERT_GT(method, 0);
|
||||
PASS();
|
||||
}
|
||||
|
||||
SUITE(resolver_pipeline) {
|
||||
RUN_TEST(resolver_pipeline_same_kind_declarations_sharing_a_qn_are_one_source);
|
||||
RUN_TEST(resolver_pipeline_different_kinds_sharing_a_qn_stay_a_conflict);
|
||||
RUN_TEST(resolver_pipeline_signature_type_refs_name_the_declaring_scope);
|
||||
RUN_TEST(resolver_pipeline_file_lookup_proofs_have_no_work_budget);
|
||||
RUN_TEST(resolver_pipeline_shared_build_has_no_proof_work_budget);
|
||||
RUN_TEST(resolver_pipeline_graph_pool_has_no_proof_work_budget);
|
||||
RUN_TEST(resolver_pipeline_infra_adapter_pool_has_no_proof_work_budget);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "resolve/policy.h"
|
||||
#include "resolve/symbols.h"
|
||||
#include "lsp/proof_bridge.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static cbm_resolve_result_t proof_outcome(const char *target) {
|
||||
@@ -165,6 +166,26 @@ TEST(resolver_proof_incomplete_predecessors_revoke_authority) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Per-file pools once defaulted to 65536 nodes and 4M hashed bytes. Reaching either left
|
||||
* the rest of that file's occurrences LIMITED, so a graph edge depended on where it sat in
|
||||
* the file and on how long the checkout path was. Explicit limits still apply. */
|
||||
TEST(resolver_proof_zero_limits_do_not_cap_a_file_sized_population) {
|
||||
CBMArena arena;
|
||||
cbm_arena_init(&arena);
|
||||
cbm_resolve_proof_pool_t pool;
|
||||
cbm_resolve_proof_pool_init(&pool, &arena, 0, 0, 0);
|
||||
for (int i = 0; i < 65537; i++) {
|
||||
char name[32];
|
||||
snprintf(name, sizeof(name), "project.fact_%d", i);
|
||||
ASSERT_NOT_NULL(proof_leaf(&pool, name, CBM_RESOLVE_ANALYSIS_COMPLETE));
|
||||
}
|
||||
pool.work = UINT64_C(4194304);
|
||||
ASSERT_NOT_NULL(proof_leaf(&pool, "project.after_former_budget", CBM_RESOLVE_ANALYSIS_COMPLETE));
|
||||
ASSERT_EQ(pool.failure, CBM_RESOLVE_ANALYSIS_COMPLETE);
|
||||
cbm_arena_destroy(&arena);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(resolver_proof_limits_and_unknown_source_are_explicit) {
|
||||
CBMArena arena;
|
||||
cbm_arena_init(&arena);
|
||||
@@ -541,6 +562,7 @@ SUITE(resolver_proof) {
|
||||
RUN_TEST(resolver_proof_closed_join_keeps_all_distinct_supports);
|
||||
RUN_TEST(resolver_proof_clone_owns_dag_and_canonical_traversal);
|
||||
RUN_TEST(resolver_proof_incomplete_predecessors_revoke_authority);
|
||||
RUN_TEST(resolver_proof_zero_limits_do_not_cap_a_file_sized_population);
|
||||
RUN_TEST(resolver_proof_limits_and_unknown_source_are_explicit);
|
||||
RUN_TEST(resolver_proof_same_pool_chain_does_not_revisit_ancestors);
|
||||
RUN_TEST(resolver_proof_named_copy_keeps_rule_and_dependency_without_admission_flags);
|
||||
|
||||
@@ -272,7 +272,37 @@ TEST(resolver_solver_limits_abstain_globally_independent_of_registration_order)
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* One solver holds every fact of a project, and exhausting it voids every occurrence in
|
||||
* the generation. kubernetes' staging tree alone registers 1315391 facts and settles them
|
||||
* in 4.4M steps, so the former 1M node cap turned all 6.4M occurrences of the repository
|
||||
* into source_declaration_identity_conflict and published no CALLS at all. */
|
||||
TEST(resolver_solver_zero_limits_do_not_cap_a_project_sized_population) {
|
||||
enum { FACTS = 1000001 };
|
||||
solver_fixture_t fixture;
|
||||
ASSERT(solver_fixture_init(&fixture, (cbm_resolve_solver_limits_t){0}));
|
||||
const cbm_resolve_proof_t *audit = solver_audit(&fixture, "project_audit");
|
||||
ASSERT_NOT_NULL(audit);
|
||||
for (int i = 0; i < FACTS; i++) {
|
||||
char key[32];
|
||||
snprintf(key, sizeof(key), "fact_%07d", i);
|
||||
ASSERT(cbm_resolve_solver_leaf(fixture.solver, key, CBM_SOLVER_KNOWN,
|
||||
(cbm_resolve_solver_value_t){.target = "project.target",
|
||||
.kind = 1},
|
||||
audit));
|
||||
}
|
||||
ASSERT(cbm_resolve_solver_solve(fixture.solver));
|
||||
ASSERT(cbm_resolve_solver_complete(fixture.solver));
|
||||
ASSERT_EQ(cbm_resolve_solver_count(fixture.solver), FACTS);
|
||||
cbm_resolve_solver_result_t last;
|
||||
cbm_resolve_solver_get(fixture.solver, "fact_1000000", &last);
|
||||
ASSERT_EQ(last.status, CBM_SOLVER_KNOWN);
|
||||
ASSERT_STR_EQ(last.value.target, "project.target");
|
||||
cbm_arena_destroy(&fixture.arena);
|
||||
PASS();
|
||||
}
|
||||
|
||||
SUITE(resolver_solver) {
|
||||
RUN_TEST(resolver_solver_zero_limits_do_not_cap_a_project_sized_population);
|
||||
RUN_TEST(resolver_solver_pending_is_not_unknown_and_copy_settles);
|
||||
RUN_TEST(resolver_solver_join_preserves_unknown_and_unsupported_competitors);
|
||||
RUN_TEST(resolver_solver_join_compares_target_kind_and_module_identity);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user