@@ -303,6 +303,10 @@ export class TreeSitterExtractor {
303303 else if ( this . extractor . callTypes . includes ( nodeType ) ) {
304304 this . extractCall ( node ) ;
305305 }
306+ // Rust: `impl Trait for Type { ... }` — creates implements edge from Type to Trait
307+ else if ( nodeType === 'impl_item' ) {
308+ this . extractRustImplItem ( node ) ;
309+ }
306310
307311 // Visit children (unless the extract method already visited them)
308312 if ( ! skipChildren ) {
@@ -406,6 +410,13 @@ export class TreeSitterExtractor {
406410 private extractFunction ( node : SyntaxNode ) : void {
407411 if ( ! this . extractor ) return ;
408412
413+ // If the language provides getReceiverType and this function has a receiver
414+ // (e.g., Rust function_item inside an impl block), extract as method instead
415+ if ( this . extractor . getReceiverType ?.( node , this . source ) ) {
416+ this . extractMethod ( node ) ;
417+ return ;
418+ }
419+
409420 let name = extractName ( node , this . source , this . extractor ) ;
410421 // For arrow functions and function expressions assigned to variables,
411422 // resolve the name from the parent variable_declarator.
@@ -498,10 +509,15 @@ export class TreeSitterExtractor {
498509 private extractMethod ( node : SyntaxNode ) : void {
499510 if ( ! this . extractor ) return ;
500511
512+ // For languages with receiver types (Go, Rust), include receiver in qualified name
513+ // so FTS can match "scrapeLoop.run" → qualified_name "...::scrapeLoop::run"
514+ const receiverType = this . extractor . getReceiverType ?.( node , this . source ) ;
515+
501516 // For most languages, only extract as method if inside a class-like node
502517 // Languages with methodsAreTopLevel (e.g. Go) always treat them as methods
503- if ( ! this . isInsideClassLikeNode ( ) && ! this . extractor . methodsAreTopLevel ) {
504- // Not inside a class-like node and not Go, treat as function
518+ // Languages with getReceiverType (e.g. Rust) extract as method when receiver is found
519+ if ( ! this . isInsideClassLikeNode ( ) && ! this . extractor . methodsAreTopLevel && ! receiverType ) {
520+ // Not inside a class-like node and no receiver type, treat as function
505521 this . extractFunction ( node ) ;
506522 return ;
507523 }
@@ -512,10 +528,6 @@ export class TreeSitterExtractor {
512528 const visibility = this . extractor . getVisibility ?.( node ) ;
513529 const isAsync = this . extractor . isAsync ?.( node ) ;
514530 const isStatic = this . extractor . isStatic ?.( node ) ;
515-
516- // For languages with receiver types (Go), include receiver in qualified name
517- // so FTS can match "scrapeLoop.run" → qualified_name "...::scrapeLoop::run"
518- const receiverType = this . extractor . getReceiverType ?.( node , this . source ) ;
519531 const extraProps : Partial < Node > = {
520532 docstring,
521533 signature,
@@ -530,6 +542,24 @@ export class TreeSitterExtractor {
530542 const methodNode = this . createNode ( 'method' , name , node , extraProps ) ;
531543 if ( ! methodNode ) return ;
532544
545+ // For methods with a receiver type but no class-like parent on the stack
546+ // (e.g., Rust impl blocks), add a contains edge from the owning struct/trait
547+ if ( receiverType && ! this . isInsideClassLikeNode ( ) ) {
548+ const ownerNode = this . nodes . find (
549+ ( n ) =>
550+ n . name === receiverType &&
551+ n . filePath === this . filePath &&
552+ ( n . kind === 'struct' || n . kind === 'class' || n . kind === 'enum' || n . kind === 'trait' )
553+ ) ;
554+ if ( ownerNode ) {
555+ this . edges . push ( {
556+ source : ownerNode . id ,
557+ target : methodNode . id ,
558+ kind : 'contains' ,
559+ } ) ;
560+ }
561+ }
562+
533563 // Extract type annotations (parameter types and return type)
534564 this . extractTypeAnnotations ( node , methodNode . id ) ;
535565
@@ -1311,6 +1341,40 @@ export class TreeSitterExtractor {
13111341 }
13121342 }
13131343
1344+ // Rust trait supertraits: `trait SubTrait: SuperTrait + Display { ... }`
1345+ // trait_bounds contains type_identifier, generic_type, or higher_ranked_trait_bound children
1346+ if ( child . type === 'trait_bounds' ) {
1347+ for ( const bound of child . namedChildren ) {
1348+ let typeName : string | undefined ;
1349+ let posNode : SyntaxNode | undefined ;
1350+
1351+ if ( bound . type === 'type_identifier' ) {
1352+ typeName = getNodeText ( bound , this . source ) ;
1353+ posNode = bound ;
1354+ } else if ( bound . type === 'generic_type' ) {
1355+ // e.g. `Deserialize<'de>`
1356+ const inner = bound . namedChildren . find ( ( c : SyntaxNode ) => c . type === 'type_identifier' ) ;
1357+ if ( inner ) { typeName = getNodeText ( inner , this . source ) ; posNode = inner ; }
1358+ } else if ( bound . type === 'higher_ranked_trait_bound' ) {
1359+ // e.g. `for<'de> Deserialize<'de>`
1360+ const generic = bound . namedChildren . find ( ( c : SyntaxNode ) => c . type === 'generic_type' ) ;
1361+ const typeId = generic ?. namedChildren . find ( ( c : SyntaxNode ) => c . type === 'type_identifier' )
1362+ ?? bound . namedChildren . find ( ( c : SyntaxNode ) => c . type === 'type_identifier' ) ;
1363+ if ( typeId ) { typeName = getNodeText ( typeId , this . source ) ; posNode = typeId ; }
1364+ }
1365+
1366+ if ( typeName && posNode ) {
1367+ this . unresolvedReferences . push ( {
1368+ fromNodeId : classId ,
1369+ referenceName : typeName ,
1370+ referenceKind : 'extends' ,
1371+ line : posNode . startPosition . row + 1 ,
1372+ column : posNode . startPosition . column ,
1373+ } ) ;
1374+ }
1375+ }
1376+ }
1377+
13141378 // Swift: inheritance_specifier > user_type > type_identifier
13151379 // Used for class inheritance, protocol conformance, and protocol inheritance
13161380 if ( child . type === 'inheritance_specifier' ) {
@@ -1336,6 +1400,69 @@ export class TreeSitterExtractor {
13361400 }
13371401 }
13381402
1403+ /**
1404+ * Rust `impl Trait for Type` — creates an implements edge from Type to Trait.
1405+ * For plain `impl Type { ... }` (no trait), no inheritance edge is needed.
1406+ */
1407+ private extractRustImplItem ( node : SyntaxNode ) : void {
1408+ // Check if this is `impl Trait for Type` by looking for a `for` keyword
1409+ const hasFor = node . children . some (
1410+ ( c : SyntaxNode ) => c . type === 'for' && ! c . isNamed
1411+ ) ;
1412+ if ( ! hasFor ) return ;
1413+
1414+ // In `impl Trait for Type`, the type_identifiers are:
1415+ // first = Trait name, last = implementing Type name
1416+ // Also handle generic types like `impl<T> Trait for MyStruct<T>`
1417+ const typeIdents = node . namedChildren . filter (
1418+ ( c : SyntaxNode ) => c . type === 'type_identifier' || c . type === 'generic_type' || c . type === 'scoped_type_identifier'
1419+ ) ;
1420+ if ( typeIdents . length < 2 ) return ;
1421+
1422+ const traitNode = typeIdents [ 0 ] ! ;
1423+ const typeNode = typeIdents [ typeIdents . length - 1 ] ! ;
1424+
1425+ // Get the trait name (handle scoped paths like std::fmt::Display)
1426+ const traitName = traitNode . type === 'scoped_type_identifier'
1427+ ? this . source . substring ( traitNode . startIndex , traitNode . endIndex )
1428+ : getNodeText ( traitNode , this . source ) ;
1429+
1430+ // Get the implementing type name (extract inner type_identifier for generics)
1431+ let typeName : string ;
1432+ if ( typeNode . type === 'generic_type' ) {
1433+ const inner = typeNode . namedChildren . find (
1434+ ( c : SyntaxNode ) => c . type === 'type_identifier'
1435+ ) ;
1436+ typeName = inner ? getNodeText ( inner , this . source ) : getNodeText ( typeNode , this . source ) ;
1437+ } else {
1438+ typeName = getNodeText ( typeNode , this . source ) ;
1439+ }
1440+
1441+ // Find the struct/type node for the implementing type
1442+ const typeNodeId = this . findNodeByName ( typeName ) ;
1443+ if ( typeNodeId ) {
1444+ this . unresolvedReferences . push ( {
1445+ fromNodeId : typeNodeId ,
1446+ referenceName : traitName ,
1447+ referenceKind : 'implements' ,
1448+ line : traitNode . startPosition . row + 1 ,
1449+ column : traitNode . startPosition . column ,
1450+ } ) ;
1451+ }
1452+ }
1453+
1454+ /**
1455+ * Find a previously-extracted node by name (used for back-references like impl blocks)
1456+ */
1457+ private findNodeByName ( name : string ) : string | undefined {
1458+ for ( const node of this . nodes ) {
1459+ if ( node . name === name && ( node . kind === 'struct' || node . kind === 'enum' || node . kind === 'class' ) ) {
1460+ return node . id ;
1461+ }
1462+ }
1463+ return undefined ;
1464+ }
1465+
13391466 /**
13401467 * Languages that support type annotations (TypeScript, etc.)
13411468 */
0 commit comments