Skip to content

Commit a4a8b5c

Browse files
committed
fix(julia): emit base name for parameterized abstract types
handle_abstract_def previously fell back to the type_head node itself when no plain identifier was found, dumping the full raw text "Name{T} <: Super{T,1}" as a definition name for any parameterized generic abstract type. The TS counterpart returns early on no-name; the native port should match. Recurse into wrapper shapes (binary_expression, parameterized identifier, type_parameter_list, type_argument_list) to locate the base identifier, and skip emission when none is found. Adds a regression test asserting the base name "AbstractVector" for "abstract type AbstractVector{T} <: AbstractArray{T,1} end".
1 parent 3393ce7 commit a4a8b5c

1 file changed

Lines changed: 64 additions & 10 deletions

File tree

  • crates/codegraph-core/src/extractors

crates/codegraph-core/src/extractors/julia.rs

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -258,16 +258,21 @@ fn handle_struct_def(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
258258

259259
fn handle_abstract_def(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
260260
// abstract_definition: `abstract type` type_head `end`
261-
// type_head wraps the name identifier (or a `Name <: Super` binary_expr).
262-
let name_node = match find_child(node, "type_head") {
263-
Some(th) => find_child(&th, "identifier")
264-
.or_else(|| {
265-
find_child(&th, "binary_expression")
266-
.and_then(|bin| find_child(&bin, "identifier"))
267-
})
268-
.unwrap_or(th),
269-
None => match find_child(node, "identifier") {
270-
Some(n) => n,
261+
// type_head wraps the name identifier — possibly nested in a
262+
// `Name <: Super` binary_expression or a `Name{T,...}` parametrized form
263+
// (`parameterized_identifier` / `type_parameter_list`).
264+
let name_node = match node
265+
.child_by_field_name("name")
266+
.or_else(|| find_child(node, "identifier"))
267+
{
268+
Some(n) => n,
269+
None => match find_child(node, "type_head") {
270+
Some(th) => match find_abstract_name(&th) {
271+
Some(n) => n,
272+
// Mirror the TS extractor: skip rather than emit a garbled
273+
// definition name (e.g. raw `Name{T} <: Super{T,1}` text).
274+
None => return,
275+
},
271276
None => return,
272277
},
273278
};
@@ -283,6 +288,38 @@ fn handle_abstract_def(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
283288
});
284289
}
285290

291+
/// Locate the base-name identifier within a `type_head` node.
292+
///
293+
/// Handles plain identifiers, `Name <: Super` binary expressions, and
294+
/// parameterized forms like `Name{T}` / `Name{T} <: Super{T,1}` by recursing
295+
/// into common wrapper kinds (binary expressions, parametrized identifiers,
296+
/// type-parameter lists). Returns `None` when no identifier can be located —
297+
/// callers should skip emitting a definition in that case.
298+
fn find_abstract_name<'a>(node: &Node<'a>) -> Option<Node<'a>> {
299+
// Direct identifier child wins.
300+
if let Some(id) = find_child(node, "identifier") {
301+
return Some(id);
302+
}
303+
// Otherwise recurse through wrapper shapes that may contain the
304+
// base-name identifier (parameterized or supertyped forms).
305+
for i in 0..node.child_count() {
306+
let Some(child) = node.child(i) else { continue };
307+
match child.kind() {
308+
"binary_expression"
309+
| "parametrized_type_expression"
310+
| "parameterized_identifier"
311+
| "type_parameter_list"
312+
| "type_argument_list" => {
313+
if let Some(found) = find_abstract_name(&child) {
314+
return Some(found);
315+
}
316+
}
317+
_ => {}
318+
}
319+
}
320+
None
321+
}
322+
286323
fn handle_macro_def(
287324
node: &Node,
288325
source: &[u8],
@@ -573,6 +610,23 @@ mod tests {
573610
assert_eq!(t.kind, "type");
574611
}
575612

613+
#[test]
614+
fn extracts_parameterized_abstract_type_base_name() {
615+
// Parameterized generics with a supertype must record only the base
616+
// identifier — never the raw `Name{T} <: Super{T,1}` text.
617+
let s = parse_jl("abstract type AbstractVector{T} <: AbstractArray{T,1} end\n");
618+
let names: Vec<&str> = s.definitions.iter().map(|d| d.name.as_str()).collect();
619+
assert!(
620+
names.contains(&"AbstractVector"),
621+
"expected base name `AbstractVector`, got {names:?}"
622+
);
623+
// Guard against the previous garbled-name regression.
624+
assert!(
625+
!names.iter().any(|n| n.contains('{') || n.contains('<')),
626+
"definition name leaked raw type-head text: {names:?}"
627+
);
628+
}
629+
576630
#[test]
577631
fn extracts_macro_def() {
578632
let s = parse_jl("macro mymac(x)\n x\nend\n");

0 commit comments

Comments
 (0)