rust.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import type { Node as SyntaxNode } from 'web-tree-sitter';
  2. import { getNodeText, getChildByField } from '../tree-sitter-helpers';
  3. import type { LanguageExtractor } from '../tree-sitter-types';
  4. /**
  5. * A Rust function's declared return type, normalized to the bare type a chained
  6. * `Foo::new().bar()` could be called on (the #645/#608 mechanism). Reads the
  7. * `return_type` field: `-> Self` yields the marker `self` (resolved to the impl's
  8. * own type at resolution time, like PHP's `self`/`static`); a concrete `-> Foo` /
  9. * `-> FooBuilder` its name; a reference (`&Foo`) is unwrapped; generics are reduced
  10. * to the base type (`Vec<Foo>` → `Vec`); primitives / unit / tuple yield undefined.
  11. * Stdlib types that aren't in the graph simply fail the later existence check.
  12. */
  13. function extractRustReturnType(node: SyntaxNode, source: string): string | undefined {
  14. let rt = getChildByField(node, 'return_type');
  15. if (!rt) return undefined;
  16. if (rt.type === 'reference_type') {
  17. rt =
  18. rt.namedChildren.find(
  19. (c: SyntaxNode) =>
  20. c.type === 'type_identifier' ||
  21. c.type === 'scoped_type_identifier' ||
  22. c.type === 'generic_type',
  23. ) ?? rt;
  24. }
  25. if (!rt || rt.type === 'primitive_type' || rt.type === 'unit_type' || rt.type === 'tuple_type') {
  26. return undefined;
  27. }
  28. const text = getNodeText(rt, source).trim().replace(/<[^>]*>/g, '');
  29. const last = text.split('::').pop()?.trim();
  30. if (!last || !/^[A-Za-z_]\w*$/.test(last)) return undefined;
  31. return last === 'Self' ? 'self' : last;
  32. }
  33. /**
  34. * The implementing type's simple name for an `impl` block, read from the
  35. * grammar's `type` field (#1588). Mirrored byte-for-byte by the native
  36. * kernel's `impl_type_name` (codegraph-kernel/src/rustlang.rs) — change both.
  37. *
  38. * `impl<T> Source for BufSource<T>`, `impl<'a> Iterator for Parents<'a>`,
  39. * `impl Trait for &Foo`, `impl Trait for m::Foo` all yield the implementing
  40. * TYPE (`BufSource`, `Parents`, `Foo`, `Foo`). The previous rule took the last
  41. * bare `type_identifier` child of the `impl_item`; once the implementing type
  42. * carries parameters it parses as a `generic_type`, so the only bare
  43. * identifier left was the TRAIT's — every parameterized impl's methods were
  44. * qualified by the trait (`Source::read`), unaddressable by their type and
  45. * colliding with the trait's own declaration.
  46. *
  47. * Shapes that name no single type (tuples, `dyn Trait`, pointers, primitives,
  48. * function types…) yield undefined: no receiver, and the fn is extracted
  49. * exactly as before.
  50. */
  51. export function rustImplTypeName(typeNode: SyntaxNode | null, source: string): string | undefined {
  52. if (!typeNode) return undefined;
  53. switch (typeNode.type) {
  54. case 'type_identifier':
  55. case 'identifier':
  56. return getNodeText(typeNode, source);
  57. // `Foo<T>` — the `type` field is the bare (or scoped) name, never the args.
  58. case 'generic_type':
  59. return rustImplTypeName(getChildByField(typeNode, 'type'), source);
  60. // `m::Foo` — the last segment is the type's name.
  61. case 'scoped_type_identifier':
  62. case 'scoped_identifier':
  63. return rustImplTypeName(getChildByField(typeNode, 'name'), source);
  64. // `&Foo` / `&'a mut Foo` — the referenced type.
  65. case 'reference_type':
  66. return rustImplTypeName(getChildByField(typeNode, 'type'), source);
  67. default:
  68. return undefined;
  69. }
  70. }
  71. export const rustExtractor: LanguageExtractor = {
  72. // `function_signature_item` is a trait method DECLARATION (`fn render(&self);`,
  73. // no body). Extracting it makes a trait's method set first-class, which
  74. // impl-navigation and trait-dispatch synthesis need (a struct's method set is
  75. // matched against the trait's).
  76. functionTypes: ['function_item', 'function_signature_item'],
  77. classTypes: [], // Rust has impl blocks
  78. methodTypes: ['function_item', 'function_signature_item'],
  79. interfaceTypes: ['trait_item'],
  80. structTypes: ['struct_item'],
  81. // Unions share struct member syntax and impl attachment, but retain their
  82. // distinct semantic kind in the graph.
  83. unionTypes: ['union_item'],
  84. enumTypes: ['enum_item'],
  85. enumMemberTypes: ['enum_variant'],
  86. typeAliasTypes: ['type_item'], // Rust type aliases
  87. importTypes: ['use_declaration'],
  88. callTypes: ['call_expression'],
  89. variableTypes: ['let_declaration', 'const_item', 'static_item'],
  90. interfaceKind: 'trait',
  91. nameField: 'name',
  92. bodyField: 'body',
  93. paramsField: 'parameters',
  94. returnField: 'return_type',
  95. getReturnType: extractRustReturnType,
  96. getSignature: (node, source) => {
  97. const params = getChildByField(node, 'parameters');
  98. const returnType = getChildByField(node, 'return_type');
  99. if (!params) return undefined;
  100. let sig = getNodeText(params, source);
  101. if (returnType) {
  102. sig += ' -> ' + getNodeText(returnType, source);
  103. }
  104. return sig;
  105. },
  106. isAsync: (node) => {
  107. for (let i = 0; i < node.childCount; i++) {
  108. const child = node.child(i);
  109. if (child?.type === 'async') return true;
  110. }
  111. return false;
  112. },
  113. getVisibility: (node) => {
  114. for (let i = 0; i < node.childCount; i++) {
  115. const child = node.child(i);
  116. if (child?.type === 'visibility_modifier') {
  117. return child.text.includes('pub') ? 'public' : 'private';
  118. }
  119. }
  120. return 'private'; // Rust defaults to private
  121. },
  122. getReceiverType: (node, source) => {
  123. // Walk up the tree-sitter AST to find a parent impl_item
  124. let parent = node.parent;
  125. while (parent) {
  126. if (parent.type === 'impl_item') {
  127. // The grammar names the implementing type directly (the `type` field)
  128. // for both `impl Type { … }` and `impl Trait for Type { … }` — see
  129. // rustImplTypeName for why the old positional scan was wrong (#1588).
  130. return rustImplTypeName(getChildByField(parent, 'type'), source);
  131. }
  132. parent = parent.parent;
  133. }
  134. return undefined;
  135. },
  136. extractImport: (node, source) => {
  137. const importText = source.substring(node.startIndex, node.endIndex).trim();
  138. // Helper to get the root crate/module from a scoped path
  139. const getRootModule = (scopedNode: SyntaxNode): string => {
  140. const firstChild = scopedNode.namedChild(0);
  141. if (!firstChild) return source.substring(scopedNode.startIndex, scopedNode.endIndex);
  142. if (firstChild.type === 'identifier' ||
  143. firstChild.type === 'crate' ||
  144. firstChild.type === 'super' ||
  145. firstChild.type === 'self') {
  146. return source.substring(firstChild.startIndex, firstChild.endIndex);
  147. } else if (firstChild.type === 'scoped_identifier') {
  148. return getRootModule(firstChild);
  149. }
  150. return source.substring(firstChild.startIndex, firstChild.endIndex);
  151. };
  152. // Find the use argument (scoped_use_list or scoped_identifier)
  153. const useArg = node.namedChildren.find((c: SyntaxNode) =>
  154. c.type === 'scoped_use_list' ||
  155. c.type === 'scoped_identifier' ||
  156. c.type === 'use_list' ||
  157. c.type === 'identifier'
  158. );
  159. if (useArg) {
  160. return { moduleName: getRootModule(useArg), signature: importText };
  161. }
  162. return null;
  163. },
  164. };