Skip to content

Commit 64b9dc7

Browse files
authored
fix: cross-module reference tracking in unused lints (#651)
1 parent cd78bfb commit 64b9dc7

2 files changed

Lines changed: 681 additions & 32 deletions

File tree

crates/semantics/src/passes/lints/ref_graph/extract.rs

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use syntax::ast::{
44
Annotation, Expression, ImportAlias, Pattern, SelectArm, SelectArmPattern, StructSpread,
55
};
66
use syntax::program::File;
7-
use syntax::program::{DefinitionBody, Module};
7+
use syntax::program::{DefinitionBody, DotAccessKind, Module};
88
use syntax::types::{Symbol, Type, unqualified_name};
99

1010
use super::reference_graph::{EnumVariantId, ModuleItemId, ReferenceGraph, StructFieldId};
@@ -38,6 +38,10 @@ impl AliasMap {
3838
}
3939
self.aliases.get(name).cloned()
4040
}
41+
42+
fn is_import_alias(&self, name: &str) -> bool {
43+
self.aliases.contains_key(name)
44+
}
4145
}
4246

4347
pub fn extract_references(
@@ -83,18 +87,20 @@ fn walk_expression(
8387
}
8488

8589
Expression::DotAccess {
86-
expression, member, ..
90+
expression,
91+
member,
92+
dot_access_kind,
93+
..
8794
} => {
8895
walk_expression(module, expression, graph, alias_map, ctx);
8996
if let Some(ty_name) = type_name(&expression.get_type()) {
9097
graph.mark_struct_field_used(StructFieldId::new(&ty_name, member));
9198
}
92-
// Also track method references: when calling Type.method() or instance.method(),
93-
// add a reference to the method if it exists in the module.
94-
// The add_reference is a no-op if the target doesn't exist.
95-
if let Some(from) = ctx {
96-
let method_id = ModuleItemId::new(member);
97-
graph.add_reference(from, method_id);
99+
if let Some(from) = ctx
100+
&& is_method_access(dot_access_kind)
101+
&& credits_local_method(&expression.get_type(), module)
102+
{
103+
graph.add_reference(from, ModuleItemId::new(member));
98104
}
99105
}
100106

@@ -513,19 +519,7 @@ fn walk_pattern(
513519
ty,
514520
..
515521
} => {
516-
let variant_name = unqualified_name(identifier);
517-
518-
let enum_name = type_name(ty).or_else(|| {
519-
let mut segments = identifier.split('.');
520-
let first = segments.next().unwrap_or("");
521-
segments.next().is_some().then(|| first.to_string())
522-
});
523-
524-
if let Some(ref enum_name) = enum_name {
525-
add_ref(graph, ctx, alias_map, module, enum_name);
526-
graph.mark_enum_variant_used(EnumVariantId::new(enum_name, variant_name));
527-
}
528-
522+
mark_constructor_pattern(module, identifier, ty, graph, alias_map, ctx);
529523
for f in fields {
530524
walk_pattern(module, f, graph, alias_map, ctx);
531525
}
@@ -536,17 +530,7 @@ fn walk_pattern(
536530
ty,
537531
..
538532
} => {
539-
add_ref(graph, ctx, alias_map, module, identifier);
540-
// Mark enum variant as used for struct variant patterns (e.g., Enum.Variant { ... })
541-
let variant_name = unqualified_name(identifier);
542-
let enum_name = type_name(ty).or_else(|| {
543-
let mut segments = identifier.split('.');
544-
let first = segments.next().unwrap_or("");
545-
segments.next().is_some().then(|| first.to_string())
546-
});
547-
if let Some(ref enum_name) = enum_name {
548-
graph.mark_enum_variant_used(EnumVariantId::new(enum_name, variant_name));
549-
}
533+
mark_constructor_pattern(module, identifier, ty, graph, alias_map, ctx);
550534
for f in fields {
551535
walk_pattern(module, &f.value, graph, alias_map, ctx);
552536
graph.mark_struct_field_used(StructFieldId::new(identifier, &f.name));
@@ -693,6 +677,58 @@ fn add_ref(
693677
}
694678
}
695679

680+
fn mark_constructor_pattern(
681+
module: &Module,
682+
identifier: &str,
683+
ty: &Type,
684+
graph: &mut ReferenceGraph,
685+
alias_map: &AliasMap,
686+
ctx: Option<&ModuleItemId>,
687+
) {
688+
if let Some((alias, _)) = identifier.split_once('.')
689+
&& alias_map.is_import_alias(alias)
690+
{
691+
add_ref(graph, ctx, alias_map, module, alias);
692+
return;
693+
}
694+
695+
let enum_name = type_name(ty).or_else(|| {
696+
identifier
697+
.split_once('.')
698+
.map(|(first, _)| first.to_string())
699+
});
700+
if let Some(enum_name) = enum_name {
701+
add_ref(graph, ctx, alias_map, module, &enum_name);
702+
graph.mark_enum_variant_used(EnumVariantId::new(&enum_name, unqualified_name(identifier)));
703+
}
704+
}
705+
706+
fn is_method_access(kind: &Option<DotAccessKind>) -> bool {
707+
matches!(
708+
kind,
709+
Some(
710+
DotAccessKind::InstanceMethod { .. }
711+
| DotAccessKind::InstanceMethodValue { .. }
712+
| DotAccessKind::StaticMethod { .. }
713+
)
714+
)
715+
}
716+
717+
fn credits_local_method(receiver_ty: &Type, module: &Module) -> bool {
718+
let mut current = receiver_ty.strip_refs();
719+
while let Some(next) = current.get_underlying().cloned() {
720+
current = next;
721+
}
722+
current = match current {
723+
Type::Function(f) => (*f.return_type).clone(),
724+
other => other,
725+
};
726+
match current {
727+
Type::Nominal { id, .. } => id.as_str().starts_with(&format!("{}.", module.id)),
728+
_ => false,
729+
}
730+
}
731+
696732
fn type_name(ty: &Type) -> Option<String> {
697733
let mut current = ty.strip_refs();
698734
while let Some(next) = current.get_underlying().cloned() {

0 commit comments

Comments
 (0)