Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions src/pipeline/pass_calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,8 @@ static const cbm_gbuf_node_t *calls_find_source(cbm_pipeline_ctx_t *ctx, const c

/* Resolve one call and emit the appropriate edge. Returns 1 if resolved, 0 if not. */
static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call,
const CBMResolvedCallArray *lsp_calls, const char *rel,
const CBMResolvedCallArray *lsp_calls,
const CBMImportArray *file_imports, const char *rel,
const char *module_qn, const char **imp_keys, const char **imp_vals,
int imp_count, CBMLanguage lang) {
const cbm_gbuf_node_t *source_node = calls_find_source(ctx, rel, call->enclosing_func_qn);
Expand Down Expand Up @@ -633,10 +634,22 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call,
* flag is set only for Python — this gate MUST match pass_parallel.c's
* exactly, for the same divergence reason noted above. */
bool suppress_weak_local_binding = lang == CBM_LANG_PYTHON;
/* #1355: `import { eq } from "drizzle-orm"` binds `eq` to a package that is
* not in the indexed tree, so a project-wide same-name guess must not turn
* `eq(...)` into a CALLS edge to an unrelated project `eq`. Joined to
* drop_plain_call for the reason spelled out above: dropping the call here
* would also skip route/HTTP/CONFIG classification, and a route
* registration reached through a static import (`import static
* spark.Spark.get` + `get("/x", handler)`) is exactly a bare call bound by
* a package specifier. Suppressing only the plain-CALLS fall-through keeps
* every Route node and service edge main-identical. */
bool drop_plain_call =
cbm_suppress_weak_member_match(suppress_weak_member, call->is_method, res.strategy) ||
cbm_suppress_weak_local_binding_call(suppress_weak_local_binding,
call->callee_is_locally_bound, res.strategy);
call->callee_is_locally_bound, res.strategy) ||
cbm_suppress_external_import_shadow(call->callee_name, res.strategy, file_imports, imp_keys,
imp_count, cbm_pipeline_get_pkgmap(),
cbm_pipeline_get_nsmap());

/* Service-pattern HTTP/ASYNC calls to an EXTERNAL client library (e.g.
* `requests.get("/api/orders/{id}")`) resolve to a QN containing the library
Expand Down Expand Up @@ -827,8 +840,8 @@ int cbm_pipeline_pass_calls(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *file
continue;
}
total_calls++;
if (resolve_single_call(ctx, call, &result->resolved_calls, rel, module_qn, imp_keys,
imp_vals, imp_count, files[i].language)) {
if (resolve_single_call(ctx, call, &result->resolved_calls, &result->imports, rel,
module_qn, imp_keys, imp_vals, imp_count, files[i].language)) {
resolved++;
} else {
unresolved++;
Expand Down
4 changes: 3 additions & 1 deletion src/pipeline/pass_definitions.c
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,9 @@ int cbm_pipeline_pass_definitions(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t
create_channel_edges_for_file(ctx, result, files[i].rel_path);
cbm_pipeline_create_env_configures_for_file(ctx, result, files[i].rel_path);
}
cbm_pipeline_namespace_map_free(namespace_map);
/* Publish instead of free — see the twin in pass_parallel.c (#1355). */
cbm_pipeline_namespace_map_free(cbm_pipeline_get_nsmap());
cbm_pipeline_set_nsmap(namespace_map);
if (owns_local_cache) {
for (int i = 0; i < file_count; i++) {
if (local_cache[i]) {
Expand Down
16 changes: 14 additions & 2 deletions src/pipeline/pass_parallel.c
Original file line number Diff line number Diff line change
Expand Up @@ -1753,7 +1753,11 @@ int cbm_build_registry_from_cache(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t
}
}

cbm_pipeline_namespace_map_free(namespace_map);
/* Publish instead of free: the call pass needs the same declared-package
* set to tell an in-tree package path from a third-party one (#1355).
* The pipeline owns it from here and frees it in its cleanup. */
cbm_pipeline_namespace_map_free(cbm_pipeline_get_nsmap());
cbm_pipeline_set_nsmap(namespace_map);

cbm_log_info("parallel.registry.done", "entries", itoa_log(reg_entries), "defines",
itoa_log(defines_edges), "imports", itoa_log(imports_edges));
Expand Down Expand Up @@ -2935,10 +2939,18 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
/* Bare-call local-binding suppression — see the note in pass_calls.c.
* This gate MUST stay identical to the one there. */
bool suppress_weak_local_binding = lang == CBM_LANG_PYTHON;
/* #1355: same guard as pass_calls.c, and joined to drop_plain_call for
* the same reason — a `continue` here would also skip route/HTTP/CONFIG
* classification, and a static-import route registration is a bare call
* bound by a package specifier. Suppress only the plain-CALLS
* fall-through so every Route node stays main-identical. */
bool drop_plain_call =
cbm_suppress_weak_member_match(suppress_weak_member, call->is_method, res.strategy) ||
cbm_suppress_weak_local_binding_call(suppress_weak_local_binding,
call->callee_is_locally_bound, res.strategy);
call->callee_is_locally_bound, res.strategy) ||
cbm_suppress_external_import_shadow(call->callee_name, res.strategy, &result->imports,
imp_keys, imp_count, cbm_pipeline_get_pkgmap(),
cbm_pipeline_get_nsmap());

/* Service-pattern HTTP/ASYNC client call (`requests.get(url)`): the
* service signal lives in the callee_name. The registry can mis-resolve
Expand Down
14 changes: 14 additions & 0 deletions src/pipeline/pipeline.c
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,18 @@ void cbm_pipeline_set_pkgmap(CBMHashTable *map) {
g_pkgmap = map;
}

/* ── Global declared-package map (one active pipeline at a time) ── */

static CBMHashTable *g_nsmap = NULL;

CBMHashTable *cbm_pipeline_get_nsmap(void) {
return g_nsmap;
}

void cbm_pipeline_set_nsmap(CBMHashTable *map) {
g_nsmap = map;
}

bool cbm_pipeline_had_format_migration(const cbm_pipeline_t *p) {
return p && p->format_migration;
}
Expand Down Expand Up @@ -2835,6 +2847,8 @@ static int cbm_pipeline_run_staged(cbm_pipeline_t *p) {
cleanup:
cbm_pkgmap_free(cbm_pipeline_get_pkgmap());
cbm_pipeline_set_pkgmap(NULL);
cbm_pipeline_namespace_map_free(cbm_pipeline_get_nsmap());
cbm_pipeline_set_nsmap(NULL);
cbm_discover_free(files, file_count);
cbm_pipeline_free_semantic_manifest(baseline_manifest, baseline_count);
cbm_gbuf_free(p->gbuf);
Expand Down
24 changes: 22 additions & 2 deletions src/pipeline/pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
#include <stdint.h>
#include <stdatomic.h>

#include "discover/discover.h" /* cbm_ignored_file_t (#963) */
#include "foundation/constants.h" /* CBM_SZ_512 */
#include "discover/discover.h" /* cbm_ignored_file_t (#963) */
#include "foundation/constants.h" /* CBM_SZ_512 */
#include "foundation/hash_table.h" /* CBMHashTable (#1355 pkgmap lookup) */

/* Forward declarations */
typedef struct cbm_store cbm_store_t;
Expand Down Expand Up @@ -296,6 +297,25 @@ bool cbm_suppress_weak_member_match(bool enabled, bool is_method, const char *st
* Pure; unit-tested in test_registry.c. */
bool cbm_suppress_weak_local_binding_call(bool enabled, bool callee_is_locally_bound,
const char *strategy);
/* #1355: drop a project-wide same-name guess (suffix_match / unique_name /
* field_type_hint / fuzzy) for a BARE call whose name the calling file binds to
* a NON-RELATIVE (package) import that resolved to nothing in the graph —
* `import { eq } from "drizzle-orm"` must not make `eq(...)` a CALLS edge to an
* unrelated project `eq`. A name the import map does bind, a relative
* specifier, a member/qualified callee, and every import-/receiver-aware
* strategy are all kept. `indexed_packages` is the pipeline package map: a
* specifier naming a package the tree itself declares (a workspace sibling)
* counts as in-tree and is kept too; NULL disables that check.
* `declared_packages` is the pipeline namespace map and does the same job for
* package-path specifiers, which have no in-tree/external shape of their own:
* `import org.example.util.assertThing` is kept when some indexed file
* declares `package org.example.util`; NULL disables that check.
* Pure; unit-tested in test_registry.c. */
bool cbm_suppress_external_import_shadow(const char *callee_name, const char *strategy,
const CBMImportArray *file_imports,
const char **import_map_keys, int import_map_count,
const CBMHashTable *indexed_packages,
const CBMHashTable *declared_packages);

/* #725: drop a suffix_match CALLS edge when the caller language and the
* target file's language disagree. unique_name (candidates == 1) is #1572
Expand Down
6 changes: 6 additions & 0 deletions src/pipeline/pipeline_incremental.c
Original file line number Diff line number Diff line change
Expand Up @@ -1620,6 +1620,8 @@ static int closure_probe_surfaces(cbm_pipeline_t *p, const char *project,
* leaked one map per probed run (LSan, macOS CI). */
cbm_pkgmap_free(cbm_pipeline_get_pkgmap());
cbm_pipeline_set_pkgmap(NULL);
cbm_pipeline_namespace_map_free(cbm_pipeline_get_nsmap());
cbm_pipeline_set_nsmap(NULL);
cbm_path_alias_collection_free(aliases);
cbm_gbuf_free(probe_gbuf);
return rc;
Expand Down Expand Up @@ -2191,6 +2193,8 @@ static int run_closure_delta(cbm_pipeline_t *p, const char *db_path, const char
}
cbm_pkgmap_free(cbm_pipeline_get_pkgmap());
cbm_pipeline_set_pkgmap(NULL);
cbm_pipeline_namespace_map_free(cbm_pipeline_get_nsmap());
cbm_pipeline_set_nsmap(NULL);
if (phase_rc != 0) {
cbm_log_error("delta.err", "phase", "extract_resolve", "rc", itoa_buf(phase_rc));
goto out;
Expand Down Expand Up @@ -2770,6 +2774,8 @@ int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_fil
* full pipeline's ownership boundary on both success and failure. */
cbm_pkgmap_free(cbm_pipeline_get_pkgmap());
cbm_pipeline_set_pkgmap(NULL);
cbm_pipeline_namespace_map_free(cbm_pipeline_get_nsmap());
cbm_pipeline_set_nsmap(NULL);

if (phase_rc != 0) {
cbm_log_error("incremental.err", "phase", "extract_resolve", "rc", itoa_buf(phase_rc));
Expand Down
10 changes: 10 additions & 0 deletions src/pipeline/pipeline_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,16 @@ static inline int cbm_pipeline_relpath_is_excluded(const char *rel_path, char *c
CBMHashTable *cbm_pipeline_get_pkgmap(void);
void cbm_pipeline_set_pkgmap(CBMHashTable *map);

/* Get the current pipeline's declared-package map (NULL if none): the
* namespace/package → File-QN map built by cbm_pipeline_namespace_map_build
* from every indexed file's own `package`/`namespace`/`using` declaration.
* The import passes build it to resolve namespace imports; it is published
* here so the call passes can ask whether a package-path specifier names a
* package the tree itself declares (#1355). Set before resolve starts, read
* only afterwards — same single-writer contract as the pkgmap above. */
CBMHashTable *cbm_pipeline_get_nsmap(void);
void cbm_pipeline_set_nsmap(CBMHashTable *map);

/* Unified module resolver: relative → pkgmap → fqn_module fallback.
* Handles bare specifiers via pkgmap lookup with prefix matching.
* Caller must free() the returned string. */
Expand Down
Loading
Loading