1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! Path canonicalisation for call targets.
use super::super::bindings::{
canonical_from_type, extract_let_binding, normalize_alias_expansion, CanonScope,
};
use super::super::local_symbols::{scope_for_local, FileScope};
use super::super::type_infer::resolve::{resolve_type, ResolveContext};
use super::super::type_infer::self_subst::substitute_bare_self;
use super::super::type_infer::{
extract_bindings, extract_for_bindings, infer_type, BindingLookup, CanonicalType, InferContext,
WorkspaceTypeIndex,
};
use super::{
bare, collect_pattern_idents, extract_pat_ident_name, method_unknown, parse_macro_tokens,
CanonicalCallCollector, CollectorBindings, FnContext,
};
use crate::adapters::analyzers::architecture::forbidden_rule::{
file_to_module_segments, resolve_to_crate_absolute_in,
};
use crate::adapters::shared::use_tree::AliasTarget;
use std::collections::{HashMap, HashSet};
use syn::spanned::Spanned;
impl<'a> CanonicalCallCollector<'a> {
/// Resolve `Q::method(...)` where `Q` is a generic type param with
/// trait bound(s) to the trait-method anchor canonicals. Returns
/// `None` when the first segment isn't a known generic param, or
/// when the path is an explicit absolute path (`::Q::method(...)`
/// is the caller's disambiguation away from in-scope generics —
/// gated centrally via `matched_generic_param`), or when the
/// param has bounds we couldn't canonicalise. Multiple bounds =>
/// multiple anchors (over-approximation). Operation.
pub(super) fn canonicalise_generic_param_path(
&self,
segments: &[String],
leading_colon_set: bool,
) -> Option<Vec<String>> {
if segments.len() < 2 {
return None;
}
let info = super::super::signature_params::matched_generic_param(
segments,
leading_colon_set,
&self.generic_params,
)?;
let method_tail = &segments[1..];
let canonicals: Vec<String> = info
.bounds
.iter()
.map(|bound| {
let mut full = bound.clone();
full.extend_from_slice(method_tail);
full.join("::")
})
.collect();
Some(canonicals)
}
/// Turn a path-segment list into the canonical String used for all
/// call-target comparisons in the call-parity check.
/// `leading_colon_set` reflects `syn::Path.leading_colon` —
/// `::Foo::bar()` is Rust 2018+ extern-root syntax that explicitly
/// disambiguates AWAY from workspace symbols, so it short-circuits
/// straight to `<bare>:` without consulting the alias / local /
/// crate-root resolvers. Mirrors the same gate
/// `canonicalise_workspace_path` enforces for `Option`-returning
/// type canonicalisation. Integration: each branch delegates to a
/// dedicated helper.
pub(super) fn canonicalise_path(&self, segments: &[String], leading_colon_set: bool) -> String {
if segments.is_empty() {
return String::new();
}
// Extern-root path (`::Foo::bar`) — workspace canonicalisation
// does not apply. Without this gate, a same-named workspace
// symbol would produce a false `crate::...::Foo::bar` edge.
if leading_colon_set {
return bare(&segments.join("::"));
}
if segments[0] == "Self" {
return self.canonicalise_self_path(segments);
}
if matches!(segments[0].as_str(), "crate" | "self" | "super") {
return self.canonicalise_keyword_path(segments);
}
if let Some(canonical) = self.canonicalise_alias_path(segments) {
return canonical;
}
if let Some(canonical) = self.canonicalise_local_symbol_path(segments) {
return canonical;
}
// Rust 2018+ absolute call: `app::foo()` without `use` is the
// crate-root `app` module, equivalent to `crate::app::foo()`.
// If `app` is a known workspace root module, prepend `crate::`
// so the canonical matches graph nodes.
if self.file.crate_root_modules.contains(&segments[0]) {
let mut full = vec!["crate".to_string()];
full.extend_from_slice(segments);
return full.join("::");
}
// Unknown path (external crate, stdlib, or not imported) → bare.
bare(&segments.join("::"))
}
/// `Self::method` — substitute the enclosing impl's canonical
/// self-type for `Self`. Falls back to `<bare>:` when we're not
/// inside an impl. Operation.
pub(super) fn canonicalise_self_path(&self, segments: &[String]) -> String {
if let Some(self_canonical) = &self.self_type_canonical {
let mut full = self_canonical.clone();
full.extend_from_slice(&segments[1..]);
return full.join("::");
}
bare(&segments.join("::"))
}
pub(super) fn canonicalise_keyword_path(&self, segments: &[String]) -> String {
if let Some(resolved) =
resolve_to_crate_absolute_in(self.file.path, self.mod_stack, segments)
{
let mut full = vec!["crate".to_string()];
full.extend(resolved);
return full.join("::");
}
bare(&segments.join("::"))
}
/// First segment hits a `use` alias visible at the current
/// `mod_stack`. The alias path is then re-normalised (it may
/// itself reference `self::`/`super::super::` or a Rust-2018 crate-root
/// module). Returns `None` when no alias matches.
pub(super) fn canonicalise_alias_path(&self, segments: &[String]) -> Option<String> {
let alias = self.lookup_alias_at_scope(&segments[0])?;
let mut full = alias.segments.to_vec();
full.extend_from_slice(&segments[1..]);
let scope = CanonScope {
file: self.file,
mod_stack: self.mod_stack,
reexports: self.reexports,
};
let normalized = normalize_alias_expansion(full, alias.absolute_root, &scope)?;
Some(normalized.join("::"))
}
/// Look up `name` in the alias map for exactly the current
/// `mod_stack`. Falls back to the flat top-level `alias_map` for
/// legacy callers that don't populate `aliases_per_scope`.
pub(super) fn lookup_alias_at_scope(&self, name: &str) -> Option<&AliasTarget> {
if let Some(map) = self.file.aliases_per_scope.get(self.mod_stack) {
return map.get(name);
}
self.file.alias_map.get(name)
}
/// Same-file fallback: first segment is declared in this file at
/// exactly the current `mod_stack`. Returns `None` when the name
/// isn't in `local_symbols` or its declaration is in a different
/// scope, letting the caller fall through to crate-root resolution.
pub(super) fn canonicalise_local_symbol_path(&self, segments: &[String]) -> Option<String> {
if !self.file.local_symbols.contains(&segments[0]) {
return None;
}
let mod_path = scope_for_local(self.file.local_decl_scopes, &segments[0], self.mod_stack)?;
let mut full = vec!["crate".to_string()];
full.extend(file_to_module_segments(self.file.path));
full.extend(mod_path.iter().cloned());
full.extend_from_slice(segments);
Some(full.join("::"))
}
}