use super::resolution::{RustResolutionContext, RustTraitResolver};
use crate::FileId;
use crate::Visibility;
use crate::parsing::behavior_state::{BehaviorState, StatefulBehavior};
use crate::parsing::{InheritanceResolver, LanguageBehavior, ResolutionScope};
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use tree_sitter::{Language, Node};
fn find_parameter_type(node: Node, code: &str, var_name: &str) -> Option<String> {
if node.kind() == "parameter" {
if let Some(pattern) = node.child_by_field_name("pattern") {
if &code[pattern.byte_range()] == var_name {
let type_node = node.child_by_field_name("type")?;
return reduce_type_to_name(type_node, code);
}
}
}
for child in node.children(&mut node.walk()) {
if let Some(found) = find_parameter_type(child, code, var_name) {
return Some(found);
}
}
None
}
fn reduce_type_to_name(node: Node, code: &str) -> Option<String> {
match node.kind() {
"type_identifier" | "primitive_type" => Some(code[node.byte_range()].to_string()),
"reference_type" | "generic_type" => {
reduce_type_to_name(node.child_by_field_name("type")?, code)
}
"scoped_type_identifier" => reduce_type_to_name(node.child_by_field_name("name")?, code),
_ => None,
}
}
#[derive(Clone)]
pub struct RustBehavior {
language: Language,
state: BehaviorState,
trait_resolver: Arc<RwLock<RustTraitResolver>>,
}
impl RustBehavior {
pub fn new() -> Self {
Self {
language: tree_sitter_rust::LANGUAGE.into(),
state: BehaviorState::new(),
trait_resolver: Arc::new(RwLock::new(RustTraitResolver::new())),
}
}
}
impl StatefulBehavior for RustBehavior {
fn state(&self) -> &BehaviorState {
&self.state
}
}
impl Default for RustBehavior {
fn default() -> Self {
Self::new()
}
}
impl LanguageBehavior for RustBehavior {
fn language_id(&self) -> crate::parsing::registry::LanguageId {
crate::parsing::registry::LanguageId::new("rust")
}
fn format_module_path(&self, base_path: &str, symbol_name: &str) -> String {
format!("{base_path}::{symbol_name}")
}
fn parse_visibility(&self, signature: &str) -> Visibility {
if signature.contains("pub(crate)") {
Visibility::Crate
} else if signature.contains("pub(super)") {
Visibility::Module
} else if signature.contains("pub ") || signature.starts_with("pub ") {
Visibility::Public
} else {
Visibility::Private
}
}
fn module_separator(&self) -> &'static str {
"::"
}
fn supports_traits(&self) -> bool {
true
}
fn supports_inherent_methods(&self) -> bool {
true
}
fn get_language(&self) -> Language {
self.language.clone()
}
fn format_path_as_module(&self, components: &[&str]) -> Option<String> {
if components.is_empty() {
return Some("crate".to_string());
}
let components: Vec<&str> = if components.last() == Some(&"mod") {
components[..components.len() - 1].to_vec()
} else {
components.to_vec()
};
if components.is_empty()
|| (components.len() == 1 && (components[0] == "main" || components[0] == "lib"))
{
return Some("crate".to_string());
}
Some(format!("crate::{}", components.join("::")))
}
fn create_resolution_context(&self, file_id: FileId) -> Box<dyn ResolutionScope> {
Box::new(RustResolutionContext::new(file_id))
}
fn create_inheritance_resolver(&self) -> Box<dyn InheritanceResolver> {
let resolver = self.trait_resolver.read().unwrap();
Box::new(resolver.clone())
}
fn is_receiver_compatible(
&self,
candidate: &crate::Symbol,
receiver: &str,
caller: Option<&crate::Symbol>,
) -> bool {
if receiver == "Self" {
let Some(caller) = caller else {
return false;
};
let Some(crate::symbol::ScopeContext::ClassMember {
class_name: Some(caller_class),
}) = caller.scope_context.as_ref()
else {
return false;
};
let resolved: &str = caller_class;
if let Some(crate::symbol::ScopeContext::ClassMember {
class_name: Some(class),
}) = candidate.scope_context.as_ref()
{
if &**class == resolved {
return true;
}
}
let suffix = format!("::{resolved}");
return candidate
.module_path
.as_deref()
.is_some_and(|path| path.ends_with(&suffix));
}
if let Some(crate::symbol::ScopeContext::ClassMember {
class_name: Some(class),
}) = candidate.scope_context.as_ref()
{
if &**class == receiver {
return true;
}
}
let suffix = format!("::{receiver}");
candidate
.module_path
.as_deref()
.is_some_and(|path| path.ends_with(&suffix))
}
fn extract_parameter_type(&self, signature: &str, var_name: &str) -> Option<String> {
let mut parser = tree_sitter::Parser::new();
parser.set_language(&self.language).ok()?;
let tree = parser.parse(signature, None)?;
find_parameter_type(tree.root_node(), signature, var_name)
}
fn is_resolvable_symbol(&self, symbol: &crate::Symbol) -> bool {
use crate::SymbolKind;
use crate::symbol::ScopeContext;
if let Some(ref scope_context) = symbol.scope_context {
match scope_context {
ScopeContext::Module | ScopeContext::Global | ScopeContext::Package => true,
ScopeContext::Local { .. } | ScopeContext::Parameter => false,
ScopeContext::ClassMember { .. } => {
matches!(symbol.kind, SymbolKind::Method)
|| matches!(symbol.visibility, crate::Visibility::Public)
}
}
} else {
matches!(
symbol.kind,
SymbolKind::Function
| SymbolKind::Method
| SymbolKind::Struct
| SymbolKind::Trait
| SymbolKind::Interface
| SymbolKind::Class
| SymbolKind::TypeAlias
| SymbolKind::Enum
| SymbolKind::Constant
)
}
}
fn add_trait_impl(&self, type_name: String, trait_name: String, file_id: FileId) {
let mut resolver = self.trait_resolver.write().unwrap();
resolver.add_trait_impl(type_name, trait_name, file_id);
}
fn add_inherent_methods(&self, type_name: String, methods: Vec<String>) {
let mut resolver = self.trait_resolver.write().unwrap();
resolver.add_inherent_methods(type_name, methods);
}
fn add_trait_methods(&self, trait_name: String, methods: Vec<String>) {
let mut resolver = self.trait_resolver.write().unwrap();
resolver.add_trait_methods(trait_name, methods);
}
fn resolve_method_trait(&self, _type_name: &str, _method: &str) -> Option<&str> {
None
}
fn format_method_call(&self, receiver: &str, method: &str) -> String {
format!("{receiver}.{method}")
}
fn inheritance_relation_name(&self) -> &'static str {
"implements"
}
fn map_relationship(&self, language_specific: &str) -> crate::relationship::RelationKind {
use crate::relationship::RelationKind;
match language_specific {
"implements" => RelationKind::Implements,
"uses" => RelationKind::Uses,
"calls" => RelationKind::Calls,
"defines" => RelationKind::Defines,
"references" => RelationKind::References,
_ => RelationKind::References,
}
}
fn register_file(&self, path: PathBuf, file_id: FileId, module_path: String) {
self.register_file_with_state(path, file_id, module_path);
}
fn add_import(&self, import: crate::parsing::Import) {
self.add_import_with_state(import);
}
fn get_imports_for_file(&self, file_id: FileId) -> Vec<crate::parsing::Import> {
self.get_imports_from_state(file_id)
}
fn is_symbol_visible_from_file(&self, symbol: &crate::Symbol, from_file: FileId) -> bool {
if symbol.file_id == from_file {
return true;
}
match symbol.visibility {
Visibility::Public => true,
Visibility::Crate => {
true
}
Visibility::Module => {
false
}
Visibility::Private => false,
}
}
fn get_module_path_for_file(&self, file_id: FileId) -> Option<String> {
self.state.get_module_path(file_id)
}
fn import_matches_symbol(
&self,
import_path: &str,
symbol_module_path: &str,
importing_module: Option<&str>,
) -> bool {
if import_path == symbol_module_path {
return true;
}
if let Some(without_crate) = import_path.strip_prefix("crate::") {
if without_crate == symbol_module_path {
return true;
}
}
if symbol_module_path.starts_with("crate::") && !import_path.starts_with("crate::") {
let symbol_without_crate = &symbol_module_path[7..];
if import_path == symbol_without_crate {
return true;
}
}
if let Some((import_prefix, import_name)) = import_path.rsplit_once("::") {
if symbol_module_path.ends_with(&format!("::{import_name}")) {
if symbol_module_path.starts_with(&format!("{import_prefix}::")) {
tracing::debug!(
"[rust] re-export heuristic matched (direct): import='{import_path}', symbol='{symbol_module_path}'"
);
return true;
}
if let Some(without_crate) = import_prefix.strip_prefix("crate::") {
if symbol_module_path.starts_with(&format!("{without_crate}::")) {
tracing::debug!(
"[rust] re-export heuristic matched (import had crate::): import='{import_path}', symbol='{symbol_module_path}'"
);
return true;
}
}
if symbol_module_path.starts_with("crate::")
&& !import_prefix.starts_with("crate::")
{
let symbol_without_crate = &symbol_module_path[7..];
if symbol_without_crate.starts_with(&format!("{import_prefix}::")) {
tracing::debug!(
"[rust] re-export heuristic matched (symbol had crate::): import='{import_path}', symbol='{symbol_module_path}'"
);
return true;
}
}
}
}
if import_path.starts_with("super::") {
if let Some(importing_mod) = importing_module {
let relative_path = import_path.strip_prefix("super::").unwrap();
if let Some(parent) = importing_mod.rsplit_once("::") {
let candidate = format!("{}::{}", parent.0, relative_path);
if candidate == symbol_module_path {
return true;
}
if symbol_module_path.ends_with(&format!("::{relative_path}"))
&& (symbol_module_path.starts_with(&format!("{}::", parent.0))
|| symbol_module_path == parent.0)
{
tracing::debug!(
"[rust] re-export heuristic matched (super): import='{import_path}', symbol='{symbol_module_path}'"
);
return true;
}
}
}
}
if let Some(importing_mod) = importing_module {
if !import_path.starts_with("crate::")
&& !import_path.starts_with("std::")
&& !import_path.starts_with("core::")
&& !import_path.starts_with("alloc::")
&& !import_path.starts_with("super::")
{
let candidate = format!("{importing_mod}::{import_path}");
if candidate == symbol_module_path {
return true;
}
if let Some((base, name)) = candidate.rsplit_once("::") {
if symbol_module_path.ends_with(&format!("::{name}"))
&& (symbol_module_path.starts_with(&format!("{base}::"))
|| symbol_module_path == base)
{
tracing::debug!(
"[rust] re-export heuristic matched (relative): import='{import_path}', symbol='{symbol_module_path}'"
);
return true;
}
}
if let Some(parent) = importing_mod.rsplit_once("::") {
let sibling = format!("{}::{}", parent.0, import_path);
if sibling == symbol_module_path {
return true;
}
if let Some((base, name)) = sibling.rsplit_once("::") {
if symbol_module_path.ends_with(&format!("::{name}"))
&& (symbol_module_path.starts_with(&format!("{base}::"))
|| symbol_module_path == base)
{
tracing::debug!(
"[rust] re-export heuristic matched (sibling): import='{import_path}', symbol='{symbol_module_path}'"
);
return true;
}
}
}
}
}
false
}
fn disambiguate_symbol(
&self,
_name: &str,
candidates: &[(crate::SymbolId, crate::SymbolKind)],
rel_kind: crate::relationship::RelationKind,
role: crate::parsing::RelationRole,
) -> Option<crate::SymbolId> {
use crate::SymbolKind::*;
use crate::parsing::RelationRole::*;
use crate::relationship::RelationKind::*;
let preferred_kinds: &[crate::SymbolKind] = match (rel_kind, role) {
(Implements, From) => &[Struct, Enum],
(Implements, To) => &[Trait],
(Calls, From) | (Calls, To) => &[Function, Method],
(Extends, From) | (Extends, To) => &[Trait],
_ => return candidates.first().map(|(id, _)| *id),
};
candidates
.iter()
.find(|(_, kind)| preferred_kinds.contains(kind))
.or_else(|| candidates.first())
.map(|(id, _)| *id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_module_path() {
let behavior = RustBehavior::new();
assert_eq!(
behavior.format_module_path("crate::module", "function"),
"crate::module::function"
);
}
#[test]
fn test_import_matches_symbol_reexport_cases() {
let behavior = RustBehavior::new();
assert!(behavior.import_matches_symbol(
"crate::parsing::LanguageBehavior",
"crate::parsing::LanguageBehavior",
Some("crate::parsing::rust")
));
assert!(behavior.import_matches_symbol("crate::foo::Bar", "foo::Bar", Some("crate::foo")));
assert!(behavior.import_matches_symbol("foo::Bar", "crate::foo::Bar", Some("crate::foo")));
assert!(behavior.import_matches_symbol(
"crate::parsing::LanguageBehavior",
"crate::parsing::language_behavior::LanguageBehavior",
Some("crate::parsing::rust")
));
assert!(behavior.import_matches_symbol(
"super::TypeScriptBehavior",
"crate::parsing::typescript::behavior::TypeScriptBehavior",
Some("crate::parsing::typescript::parser")
));
assert!(behavior.import_matches_symbol(
"LanguageBehavior",
"crate::parsing::language_behavior::LanguageBehavior",
Some("crate::parsing")
));
assert!(behavior.import_matches_symbol(
"LanguageBehavior",
"crate::parsing::language_behavior::LanguageBehavior",
Some("crate::parsing::rust")
));
}
#[test]
fn test_import_matches_symbol_negative_cases() {
let behavior = RustBehavior::new();
assert!(!behavior.import_matches_symbol(
"crate::parsing::Foo",
"crate::parsing::language_behavior::Bar",
Some("crate::parsing::rust")
));
assert!(!behavior.import_matches_symbol(
"crate::utils::Helper",
"crate::parsing::utils::Helper",
Some("crate::utils")
));
assert!(!behavior.import_matches_symbol(
"super::Foo",
"crate::x::Foo",
Some("crate::a::b::c")
));
assert!(!behavior.import_matches_symbol(
"helpers::func",
"crate::other::helpers::func",
Some("crate::module")
));
assert!(!behavior.import_matches_symbol(
"crate::foo::Bar",
"crate::bar::Bar",
Some("crate::foo")
));
assert!(!behavior.import_matches_symbol(
"LanguageBehavior",
"crate::other::language_behavior::LanguageBehavior",
Some("crate::parsing::rust")
));
}
#[test]
fn test_parse_visibility() {
let behavior = RustBehavior::new();
assert_eq!(
behavior.parse_visibility("pub fn foo()"),
Visibility::Public
);
assert_eq!(behavior.parse_visibility("fn foo()"), Visibility::Private);
assert_eq!(
behavior.parse_visibility("pub(crate) fn foo()"),
Visibility::Crate
);
assert_eq!(
behavior.parse_visibility("pub(super) fn foo()"),
Visibility::Module
);
}
#[test]
fn test_module_separator() {
let behavior = RustBehavior::new();
assert_eq!(behavior.module_separator(), "::");
}
#[test]
fn test_supports_features() {
let behavior = RustBehavior::new();
assert!(behavior.supports_traits());
assert!(behavior.supports_inherent_methods());
}
#[test]
fn test_abi_version() {
let behavior = RustBehavior::new();
assert_eq!(behavior.get_abi_version(), 15);
}
#[test]
fn test_validate_node_kinds() {
let behavior = RustBehavior::new();
assert!(behavior.validate_node_kind("function_item"));
assert!(behavior.validate_node_kind("struct_item"));
assert!(behavior.validate_node_kind("impl_item"));
assert!(behavior.validate_node_kind("trait_item"));
assert!(!behavior.validate_node_kind("made_up_node"));
}
#[test]
fn test_format_path_as_module() {
let behavior = RustBehavior::new();
assert_eq!(
behavior.format_path_as_module(&[]),
Some("crate".to_string())
);
assert_eq!(
behavior.format_path_as_module(&["main"]),
Some("crate".to_string())
);
assert_eq!(
behavior.format_path_as_module(&["lib"]),
Some("crate".to_string())
);
assert_eq!(
behavior.format_path_as_module(&["foo", "bar"]),
Some("crate::foo::bar".to_string())
);
assert_eq!(
behavior.format_path_as_module(&["foo", "mod"]),
Some("crate::foo".to_string())
);
assert_eq!(
behavior.format_path_as_module(&["a", "b", "c"]),
Some("crate::a::b::c".to_string())
);
assert_eq!(
behavior.format_path_as_module(&["tests", "integration"]),
Some("crate::tests::integration".to_string())
);
}
}