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
8 changes: 7 additions & 1 deletion crates/rag-rat-core/src/index/edges/resolve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ pub(crate) fn resolve_symbol<'a>(
.copied()
.filter(|symbol| kind_matches(symbol))
.collect::<Vec<_>>();
let preferred = preferred_matches(request.edge_kind, &matches);
let preferred = preferred_matches(request.edge_kind, request.source_language, &matches);
// In languages with separate type/value namespaces (Rust, C, C++), a `references_type`
// reference must resolve to a type DEFINITION (struct/enum/trait/type/…). If none of the
// same-named candidates is one, do NOT fall back to a non-type symbol (an `impl` block, a
Expand Down Expand Up @@ -736,12 +736,18 @@ pub(crate) fn is_common_member_name(value: &str) -> bool {
}
pub(crate) fn preferred_matches<'a>(
edge_kind: &str,
source_language: Option<&str>,
matches: &[&'a IndexedSymbol],
) -> Vec<&'a IndexedSymbol> {
let preferred_kinds: &[&str] = match edge_kind {
"calls_name" => &["function", "method"],
"constructs" => &["struct", "class", "object"],
"uses_macro" => &["macro"],
// Python base-class inheritance emits an `implements` edge to a CLASS — Python has no
// traits/interfaces — so prefer class-like kinds for Python (#172). Kept language-scoped so
// Kotlin/TS are UNCHANGED: there `implements`/`: I` targets an interface, and a same-named
// class must not be preferred over it.
"implements" if source_language == Some(Language::Python.as_str()) => &["class", "object"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid preferring foreign classes for Python bases

In mixed-language indexes this branch prefers any class/object symbol by kind only; preferred_matches does not check symbol.language, and the name buckets are repo-wide. When a Python base is external or otherwise not indexed as a Python class, but a TS/Kotlin/C++ class or Kotlin object with the same short name exists alongside another same-named non-class candidate, this new preferred set can resolve class Sub(Base) to the foreign symbol instead of leaving it unresolved. Please restrict the Python implements preference to Python symbols (or otherwise suppress cross-language candidates) when applying this branch.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid forcing call-style Python bases to classes

This preference also applies to every Python implements edge even when the superclass expression is a call, because python_edges emits implements for each positional superclass and python_type_head leaves a call expression to last_identifier_text (for example class Sub(Base()):). In that scenario, if the repo has both def Base(...) and a decoy class Base, the new preferred set resolves the inheritance edge to the class, whereas the actual base expression depends on the callable and the old resolver would leave the class/function collision ambiguous. Please avoid applying the class preference to call-shaped bases, or encode the expression shape so these dynamic bases do not bind to an unrelated class.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid preferring foreign classes for Python bases

In mixed-language indexes this branch prefers any class/object symbol by kind only; preferred_matches does not check symbol.language, and the name buckets are repo-wide. When a Python base is external or otherwise not indexed as a Python class, but a TS/Kotlin/C++ class or Kotlin object with the same short name exists alongside another same-named non-class candidate, this new preferred set can resolve class Sub(Base) to the foreign symbol instead of leaving it unresolved. Please restrict the Python implements preference to Python symbols (or otherwise suppress cross-language candidates) when applying this branch.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid forcing call-style Python bases to classes

This preference also applies to every Python implements edge even when the superclass expression is a call, because python_edges emits implements for each positional superclass and python_type_head leaves a call expression to last_identifier_text (for example class Sub(Base()):). In that scenario, if the repo has both def Base(...) and a decoy class Base, the new preferred set resolves the inheritance edge to the class, whereas the actual base expression depends on the callable and the old resolver would leave the class/function collision ambiguous. Please avoid applying the class preference to call-shaped bases, or encode the expression shape so these dynamic bases do not bind to an unrelated class.

Useful? React with 👍 / 👎.

"implements" => &["trait", "interface"],
"references_type" => &["struct", "enum", "trait", "type", "class", "interface", "object"],
_ => &[],
Expand Down
55 changes: 55 additions & 0 deletions crates/rag-rat-core/src/index/edges/resolve/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,3 +572,58 @@ fn oracle_unaffected_by_import_scope_columns() {
scope columns must not pull it in"
);
}

/// #172: a Python `class Sub(Base)` emits an `implements` edge to `Base`, which is a CLASS (Python
/// has no traits/interfaces). The resolver must prefer the base CLASS over a same-named non-class
/// (e.g. a function) — language-scoped, so Kotlin/TS `implements` still prefers an interface.
#[test]
fn python_implements_prefers_a_base_class_over_a_non_class() {
let conn = seeded_conn();
let sub = py_source(&conn, "sub.py");
let base = py_source(&conn, "base.py");
let other = py_source(&conn, "other.py");
// A symbol in sub.py so the resolver knows the reference's source language is Python (a file's
// language is inferred from its symbols, and the language-scoped `implements` preference is
// what this exercises).
py_sym(&conn, sub, "Sub", "sub.py::Sub", "class");
// The real base class, and a DECOY same-named non-class (a module-level function `Base`).
let base_class = py_sym(&conn, base, "Base", "base.py::Base", "class");
let _decoy_fn = py_sym(&conn, other, "Base", "other.py::Base", "function");
conn.execute(
"INSERT INTO edges(source_file_id, to_name, edge_kind, confidence, resolution, \
source_start_byte) VALUES (?1, 'Base', 'implements', 'NameOnly', 'unresolved', 10)",
params![sub],
)
.unwrap();
let edge: i64 = conn.query_row("SELECT MAX(id) FROM edges_data", [], |r| r.get(0)).unwrap();

crate::index::install_scope_view(&conn, NEW, "").unwrap();
resolve_all_edges(&conn).unwrap();

let (to, _confidence, _resolution) = edge_state(&conn, edge);
assert_eq!(
to,
Some(base_class),
"implements must prefer the base CLASS, not the decoy function"
);
}

fn py_source(conn: &Connection, path: &str) -> i64 {
conn.execute(
"INSERT INTO main.files(path, language, kind, sha256, modified_at_ms, indexed_at_ms, \
commit_sha, worktree_id) VALUES (?1, 'python', 'source', ?2, 0, 0, ?3, '')",
params![path, format!("sha-{path}"), NEW],
)
.unwrap();
conn.last_insert_rowid()
}

fn py_sym(conn: &Connection, file_id: i64, name: &str, qualified: &str, kind: &str) -> i64 {
conn.execute(
"INSERT INTO symbols(file_id, language, name, qualified_name, kind, start_byte, end_byte, \
start_line, end_line) VALUES (?1, 'python', ?2, ?3, ?4, 0, 10, 1, 1)",
params![file_id, name, qualified, kind],
)
.unwrap();
conn.last_insert_rowid()
}
Loading