Skip to content
Merged
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
2 changes: 1 addition & 1 deletion codegraph-kernel/src/resolve/declarations_upstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ impl KernelResolver {
candidates: None,
file_path: n.file_path.clone(),
language: n.language.clone(),
failure_reason: None,
failure_reason: None, generated: None,
};
self.ensure_cpp_default_index(&n.file_path, &site)?;
let namespaces = self
Expand Down
4 changes: 2 additions & 2 deletions codegraph-kernel/src/resolve/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ impl KernelResolver {
.map(|line| strip_line_comments(line)).collect::<Vec<_>>().join("\n");
if owner.kind == "type_alias" {
// Alias method sets follow their targets; defined types do not.
let site = ResolveRefIn { from_node_id: owner.id.clone(), reference_name: owner.name.clone(), reference_kind: "references".to_string(), line: owner.start_line, column: owner.start_column, file_path: owner.file_path.clone(), language: "go".to_string(), row_id: None, candidates: None, failure_reason: None };
let site = ResolveRefIn { from_node_id: owner.id.clone(), reference_name: owner.name.clone(), reference_kind: "references".to_string(), line: owner.start_line, column: owner.start_column, file_path: owner.file_path.clone(), language: "go".to_string(), row_id: None, candidates: None, failure_reason: None, generated: None };
if let Some(tree) = self.parsed_tree(&lines, &site) {
let mut declaration = super::iteration::descendant_for_position(
tree.root_node(), lines.text(),
Expand Down Expand Up @@ -542,7 +542,7 @@ impl KernelResolver {
candidates: None,
file_path: cls.file_path.clone(),
language: cls.language.clone(),
failure_reason: None,
failure_reason: None, generated: None,
};
let mut owner: Option<Arc<KNode>> = Some(Arc::new(cls.clone()));
if let Some(holder) = holder {
Expand Down
2 changes: 1 addition & 1 deletion codegraph-kernel/src/resolve/go_asserted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ fn local_at(tree: &tree_sitter::Tree, source: &str, name: &str, line: i64, colum
impl KernelResolver {
pub(super) fn go_local_declaration_at(&mut self, name: String, file_path: String, line: i64, column: i64) -> Result<Option<GoLocalDeclaration>> {
let r = ResolveRefIn { row_id: None, from_node_id: String::new(), reference_name: name.clone(), reference_kind: "references".into(),
line, column, candidates: None, file_path: file_path.clone(), language: "go".into(), failure_reason: None };
line, column, candidates: None, file_path: file_path.clone(), language: "go".into(), failure_reason: None, generated: None };
let Some(file) = self.read_file(&file_path) else { return Ok(None) };
let Some(tree) = self.parsed_tree(&file, &r) else { return Ok(None) };
Ok(local_at(&tree, file.text(), &name, line, column))
Expand Down
2 changes: 1 addition & 1 deletion codegraph-kernel/src/resolve/js_objects_upstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ impl KernelResolver {
let site = ResolveRefIn { row_id: None, from_node_id: n.id.clone(),
reference_name: root.to_string(), reference_kind: "references".to_string(),
line: n.start_line, column: n.start_column, candidates: None,
file_path: n.file_path.clone(), language: n.language.clone(), failure_reason: None };
file_path: n.file_path.clone(), language: n.language.clone(), failure_reason: None, generated: None };
if self.js_root_binding_scope(&site, root).is_some() { return Ok(false); }
let global = if global_host(n) {
let host = root;
Expand Down
2 changes: 1 addition & 1 deletion codegraph-kernel/src/resolve/kotlin_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ impl KernelResolver {
let call = re!(r"(?:^|[^\w$])([a-z_]\w*)\s*(?:<[^<>{}]*>)?\s*(?:\([^(){}]*\))?\s*$").captures(&pending).map(|m| m[1].to_string());
if let Some(call) = call.filter(|name| !matches!(name.as_str(), "if" | "else" | "for" | "while" | "do" | "when" | "try" | "catch" | "finally" | "init" | "get" | "set" | "constructor" | "fun" | "class" | "object" | "interface" | "return" | "by" | "lazy" | "apply" | "run" | "also" | "let" | "with" | "use")) {
if !type_lambdas { stack.push((line, vec![UNTYPED_LAMBDA.to_string()])); pending.clear(); continue; }
let site = ResolveRefIn { row_id: None, from_node_id: String::new(), reference_name: call.clone(), reference_kind: "calls".to_string(), line, column: 0, candidates: None, file_path: file.to_string(), language: "kotlin".to_string(), failure_reason: None };
let site = ResolveRefIn { row_id: None, from_node_id: String::new(), reference_name: call.clone(), reference_kind: "calls".to_string(), line, column: 0, candidates: None, file_path: file.to_string(), language: "kotlin".to_string(), failure_reason: None, generated: None };
let mut owners: HashSet<String> = stack.iter().flat_map(|(_, names): &(i64, Vec<String>)| names.iter().filter(|name| name.as_str() != "*").cloned()).collect();
// An explicit typed receiver also proves dispatch: m.single { ... }, m: Module.
// One shared pattern with the callee captured, not a pattern compiled per call name:
Expand Down
2 changes: 1 addition & 1 deletion codegraph-kernel/src/resolve/kotlin_type_scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl KernelResolver {
candidates: None,
file_path: declaration.file_path.clone(),
language: "kotlin".to_string(),
failure_reason: None,
failure_reason: None, generated: None,
};
let Some(tree) = self.parsed_tree(&source, &site) else {
continue;
Expand Down
47 changes: 40 additions & 7 deletions codegraph-kernel/src/resolve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::native::{Error, Result};
#[cfg(feature = "addon")]
use napi_derive::napi;
use regex::Regex;
use rusqlite::{Connection, OpenFlags};
use rusqlite::{Connection, OpenFlags, OptionalExtension};
use std::cell::{OnceCell, RefCell};
use std::collections::{HashMap, HashSet, VecDeque};
use std::rc::Rc;
Expand Down Expand Up @@ -175,6 +175,11 @@ pub struct ResolveRefIn {
pub file_path: String,
pub language: String,
pub failure_reason: Option<String>,
/// Files marked generated at index time (minified bundles, codegen
/// output) get a bounded name-match: their same-name candidate pools
/// are minification artifacts, and scanning all of them costs ~10× a
/// normal ref. Absent/NULL means false.
pub generated: Option<bool>,
}

impl ResolveRefIn {
Expand Down Expand Up @@ -1086,10 +1091,11 @@ impl KernelResolver {
) -> Result<Vec<ResolveRefIn>> {
const PREREQ_KINDS: &str = "('imports','extends','implements')";
let sql = format!(
"SELECT id, from_node_id, reference_name, reference_kind, line, col, \
candidates, file_path, language, failure_reason FROM unresolved_refs \
WHERE status = 'pending' AND id > ?1 \
AND reference_kind {} {} ORDER BY id LIMIT ?2",
"SELECT r.id, r.from_node_id, r.reference_name, r.reference_kind, r.line, r.col, \
r.candidates, r.file_path, r.language, r.failure_reason, f.generated \
FROM unresolved_refs r LEFT JOIN files f ON f.path = r.file_path \
Comment thread
coderabbitai[bot] marked this conversation as resolved.
WHERE r.status = 'pending' AND r.id > ?1 \
AND r.reference_kind {} {} ORDER BY r.id LIMIT ?2",
if prerequisites { "IN" } else { "NOT IN" },
PREREQ_KINDS
);
Expand All @@ -1110,6 +1116,7 @@ impl KernelResolver {
row.get::<_, Option<String>>(7)?.unwrap_or_default(),
row.get::<_, Option<String>>(8)?.unwrap_or_default(),
row.get::<_, Option<String>>(9)?,
row.get::<_, Option<i64>>(10)?.map(|g| g != 0),
))
})
.map_err(|e| Error::from_reason(e.to_string()))?;
Expand All @@ -1119,13 +1126,14 @@ impl KernelResolver {
}
drop(stmt);
let mut out = Vec::new();
for (row_id, from_node_id, reference_name, reference_kind, line, column, candidates, mut file_path, mut language, failure_reason) in collected
for (row_id, from_node_id, reference_name, reference_kind, line, column, candidates, mut file_path, mut language, failure_reason, mut generated) in collected
{
// Denormalized fallback — mirrors UnresolvedRef construction in
// resolveBatchYielding: `raw.filePath || nodePath`,
// `raw.language || nodeLang` — falsy (empty) only; a stored
// 'unknown' passes through unchanged.
if file_path.is_empty() || language.is_empty() {
let stored_path_empty = file_path.is_empty();
if stored_path_empty || language.is_empty() {
if let Some(node) = self.node_by_id(&from_node_id)? {
if file_path.is_empty() {
file_path = node.file_path.clone();
Expand All @@ -1135,6 +1143,16 @@ impl KernelResolver {
}
}
}
// The files.generated join could not match an empty stored
// path: look the flag up under the path the fallback supplied,
// as generatedPredicateFor does on the TS side.
if stored_path_empty && generated != Some(true) {
generated = self.file_generated(&file_path)?;
}
// Union with the path convention — same rule as
// generatedPredicateFor on the TS side: the DB flag OR a
// generated-looking filename.
let generated = generated == Some(true) || crate::textutil::is_generated_file(&file_path);
out.push(ResolveRefIn {
row_id: Some(row_id),
from_node_id,
Expand All @@ -1146,11 +1164,26 @@ impl KernelResolver {
file_path,
language,
failure_reason,
generated: Some(generated),
});
}
Ok(out)
}

/// `SELECT generated FROM files WHERE path = ?` — the join's fallback
/// when a ref's stored file_path was empty (see read_pending_batch).
fn file_generated(&mut self, path: &str) -> Result<Option<bool>> {
if path.is_empty() {
return Ok(None);
}
let hit = self
.conn()?
.query_row("SELECT generated FROM files WHERE path = ?1", [path], |row| row.get::<_, i64>(0))
.optional()
.map_err(|e| Error::from_reason(e.to_string()))?;
Ok(hit.map(|g| g != 0))
}

/// The `settle` stage — one verdict per ref in input order.
#[cfg_attr(feature = "addon", napi)]
pub fn resolve_chunk(&mut self, refs: Vec<ResolveRefIn>) -> Result<Vec<ResolveOutcome>> {
Expand Down
39 changes: 38 additions & 1 deletion codegraph-kernel/src/resolve/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ use super::name_scope::{can_name_in_type_position, is_bare_rust_name};
/// no repository class uses may claim: a guess, not a proven link.
const PHP_UNSEEN_ANCESTOR_CONFIDENCE: f64 = 0.5;

/// Max same-name candidate pool a generated-file ref will evaluate: real
/// same-name pools sit in the tens; minified bundles push thousands of
/// artifacts into one pool and turn each ref's retain/scope chain
/// quadratic (pretix's fabric.min.js: ~950ms per 250-ref chunk vs a ~90ms
/// floor). Over the cap the name-match declines — unresolved, never a
/// truncated winner.
const GENERATED_NAME_POOL_CAP: usize = 256;

/// A module a bundler loads as data, not code: a stylesheet, an image, a GraphQL document, JSON, a font.
const ASSET_MODULE: &str = r"(?i)\.(?:css|scss|sass|less|styl|stylus|pcss|graphql|gql|png|jpe?g|gif|svg|webp|avif|ico|bmp|tiff?|json5?|ya?ml|toml|md|mdx|txt|html?|xml|csv|woff2?|ttf|otf|eot|mp3|mp4|webm|ogg|wav|flac|pdf|wasm)$";

Expand Down Expand Up @@ -280,7 +288,7 @@ impl KernelResolver {
row_id: None, from_node_id: candidate.id.clone(), reference_name: candidate.name.clone(),
reference_kind: "calls".to_string(), line: candidate.start_line,
column: candidate.start_column, candidates: None, file_path: candidate.file_path.clone(),
language: "rust".to_string(), failure_reason: None,
language: "rust".to_string(), failure_reason: None, generated: None,
};
let is_trait = self.rust_self_context(&site).is_some_and(|cx| cx.trait_name.is_some());
self.rust_trait_memo.insert(candidate.id.clone(), is_trait);
Expand Down Expand Up @@ -686,6 +694,26 @@ impl KernelResolver {
}
}
let named = self.nodes_by_name(&r.reference_name)?;
// Big same-name pools make a bare match pathological: a name like
// `t` gets thousands of candidates whose retain/scope checks
// dominate a whole batch (pretix's fabric.min.js: ~950ms per
// 250-ref chunk vs a ~90ms floor). Two bounds, both declining the
// name-match entirely — `unresolved`, never a truncated winner —
// with the framework merge still applying downstream:
// * the ref's own file is generated-flagged (minified/codegen
// sources degrade by precedent);
// * the pool is dominated by generated nodes — an artifact pool
// cannot hold a real target anyway, so handwritten refs in
// vendored sources (d3.v6.js, fabric.js, pdf.worker.js —
// unminified, unflagged) get the same bound, while real big
// pools (Migration, __init__) proceed untouched.
if named.len() > GENERATED_NAME_POOL_CAP
&& (r.generated == Some(true)
|| named.iter().filter(|n| crate::textutil::is_generated_file(&n.file_path)).count() * 4
>= named.len() * 3)
{
return Ok(None);
}
// Macro constants are not callees (#1838). Where the name is a
// function-like macro, the call can only reach the function another
// build configuration compiles, never a same-named type
Expand Down Expand Up @@ -973,6 +1001,15 @@ impl KernelResolver {
}
}
let candidates = self.nodes_by_lower_name(&r.reference_name)?;
// Same bound as match_by_exact_name_uncapped: the lowercase pool is
// at least as wide, so decline before the retain chain.
if candidates.len() > GENERATED_NAME_POOL_CAP
&& (r.generated == Some(true)
|| candidates.iter().filter(|n| crate::textutil::is_generated_file(&n.file_path)).count() * 4
>= candidates.len() * 3)
{
return Ok(None);
}
let type_ref = self.is_dotnet_type_ref(r);
let rust_bare = is_bare_rust_name(r);
// Names are case-sensitive in every language but a handful: Rust's
Expand Down
2 changes: 1 addition & 1 deletion codegraph-kernel/src/resolve/php_scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ impl KernelResolver {
return false;
}
let Some(lines) = self.read_file(&candidate.file_path) else { return false };
let site = ResolveRefIn { row_id: None, from_node_id: candidate.id.clone(), reference_name: candidate.name.clone(), reference_kind: "references".to_string(), line: candidate.start_line, column: candidate.start_column, candidates: None, file_path: candidate.file_path.clone(), language: candidate.language.clone(), failure_reason: None };
let site = ResolveRefIn { row_id: None, from_node_id: candidate.id.clone(), reference_name: candidate.name.clone(), reference_kind: "references".to_string(), line: candidate.start_line, column: candidate.start_column, candidates: None, file_path: candidate.file_path.clone(), language: candidate.language.clone(), failure_reason: None, generated: None };
if let Some(tree) = self.parsed_tree(&lines, &site) {
let mut node = super::iteration::descendant_for_position(tree.root_node(), lines.text(), ((site.line - 1).max(0) as usize, site.column.max(0) as usize));
while let Some(parent) = node.parent() {
Expand Down
2 changes: 1 addition & 1 deletion codegraph-kernel/src/resolve/resolver_upstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ impl KernelResolver {
let Some(source) = self.read_file(file) else { return !name.starts_with('_') };
let site = ResolveRefIn { row_id: None, from_node_id: String::new(), reference_name: String::new(),
reference_kind: "imports".to_string(), line: 1, column: 0, candidates: None,
file_path: file.to_string(), language: "python".to_string(), failure_reason: None };
file_path: file.to_string(), language: "python".to_string(), failure_reason: None, generated: None };
let Some(tree) = self.parsed_tree(&source, &site) else { return !name.starts_with('_') };
let mut names = HashSet::new();
let mut literal = true;
Expand Down
2 changes: 1 addition & 1 deletion codegraph-kernel/src/resolve/rust_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ impl KernelResolver {
line: 1,
column: 0,
candidates: None,
failure_reason: None,
failure_reason: None, generated: None,
};
let mut tree = self.parsed_tree(&source, &r)?;
// pin-project exposes its struct declaration inside a macro token tree.
Expand Down
3 changes: 3 additions & 0 deletions src/extraction/kernel/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ export interface ResolveRefIn {
filePath: string;
language: string;
failureReason?: string;
/** files.generated at index time — bounds the name-match pool for
* minified/codegen sources (see kernel GENERATED_NAME_POOL_CAP). */
generated?: boolean;
}

export interface KernelCandidateOut {
Expand Down
33 changes: 21 additions & 12 deletions src/resolution/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1896,18 +1896,27 @@ export class ReferenceResolver {

/** One kernel `resolveChunk` over `refs`, in order. Throws on any native failure. */
private resolveChunkWithKernel(kernel: KernelResolverLike, refs: UnresolvedReference[]): ResolveOutcome[] {
const kernelRows: ResolveRefIn[] = refs.map((raw) => ({
rowId: raw.rowId,
fromNodeId: raw.fromNodeId,
referenceName: raw.referenceName,
referenceKind: raw.referenceKind,
line: raw.line,
column: raw.column,
candidates: raw.candidates ? JSON.stringify(raw.candidates) : undefined,
filePath: raw.filePath || this.getFilePathFromNodeId(raw.fromNodeId) || '',
language: raw.language || this.getLanguageFromNodeId(raw.fromNodeId) || '',
failureReason: raw.failureReason,
}));
// generatedPredicateFor unions the indexed flag with the path convention —
// the kernel applies its bounded name-match only to generated sources
// (minified bundles' same-name pools cost ~10x a normal ref to scan).
const filePaths: string[] = refs.map((raw) => raw.filePath || this.getFilePathFromNodeId(raw.fromNodeId) || '');
const isGenerated = this.queries.generatedPredicateFor(filePaths);
const kernelRows: ResolveRefIn[] = refs.map((raw, i) => {
const filePath = filePaths[i] ?? '';
return {
rowId: raw.rowId,
fromNodeId: raw.fromNodeId,
referenceName: raw.referenceName,
referenceKind: raw.referenceKind,
line: raw.line,
column: raw.column,
candidates: raw.candidates ? JSON.stringify(raw.candidates) : undefined,
filePath,
language: raw.language || this.getLanguageFromNodeId(raw.fromNodeId) || '',
failureReason: raw.failureReason,
generated: isGenerated(filePath),
};
});
const outcomes = kernel.resolveChunk(kernelRows);
if (outcomes.length !== refs.length) {
throw new Error(`kernel resolveChunk returned ${outcomes.length} outcomes for ${refs.length} refs`);
Expand Down
Loading