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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
//! Method-dispatch resolution: maps a `(receiver_ty, method_name)` to a
//! `DispatchKind` and provides the impl-/trait-lookup helpers used by
//! both `resolve_dispatch_kind` and `resolve_method_return_type`.
use crate::error::CompilerError;
use crate::ir::lower::IrLowerer;
use crate::ir::{DispatchKind, ImplId, ResolvedType, TraitId};
impl IrLowerer<'_> {
/// Resolve the dispatch kind for a method call.
///
/// * Concrete struct/enum receivers resolve to `Static` dispatch pointing
/// at the impl block that provides the method body. When the call site
/// is inside the impl that is still being lowered, the `ImplId` refers
/// to the slot that impl will occupy in `module.impls` once finalized.
/// * Type-parameter receivers (`T: Trait`) and trait-object receivers
/// resolve to `Virtual` dispatch through the relevant trait.
/// * Other receiver shapes (primitives, arrays, tuples, etc.) are a
/// compiler bug at this layer — semantic analysis should have rejected
/// them. We record an `InternalError` and return a sentinel
/// `Virtual` dispatch pointing at `TraitId(u32::MAX)` so downstream
/// code never silently emits against a bogus trait id.
pub(super) fn resolve_dispatch_kind(
&mut self,
receiver_ty: &ResolvedType,
method_name: &str,
) -> DispatchKind {
// Unwrap a Generic wrapper to its base so `Box<T>.method()` and
// `Option<T>.method()` dispatch the same way a concrete Struct/Enum
// receiver would.
let concrete = match receiver_ty {
ResolvedType::Generic { base, .. } => match base {
crate::ir::GenericBase::Struct(id) => Some(ResolvedType::Struct(*id)),
crate::ir::GenericBase::Enum(id) => Some(ResolvedType::Enum(*id)),
// A trait base wouldn't appear here for a method
// call receiver post item E2. Stay None and let the
// resolver fall through to the existing error path.
crate::ir::GenericBase::Trait(_) => None,
},
ResolvedType::Primitive(_)
| ResolvedType::Struct(_)
| ResolvedType::Trait(_)
| ResolvedType::Enum(_)
| ResolvedType::Tuple(_)
| ResolvedType::TypeParam(_)
| ResolvedType::External { .. }
| ResolvedType::Closure { .. }
| ResolvedType::Error => None,
};
let effective_ty = concrete.as_ref().unwrap_or(receiver_ty);
if let ResolvedType::Struct(struct_id) = effective_ty {
if let Some(impl_id) = self.find_impl_for_struct(*struct_id, method_name) {
return DispatchKind::Static { impl_id };
}
return DispatchKind::Static {
impl_id: self.next_impl_id_or_record(),
};
}
if let ResolvedType::Enum(enum_id) = effective_ty {
if let Some(impl_id) = self.find_impl_for_enum(*enum_id, method_name) {
return DispatchKind::Static { impl_id };
}
return DispatchKind::Static {
impl_id: self.next_impl_id_or_record(),
};
}
if let ResolvedType::Primitive(prim) = effective_ty {
// SB-3: primitive receivers dispatch via the prelude's
// `extern impl <Primitive>` blocks. Scan module.impls for
// an `ImplTarget::Primitive(prim)` carrying `method_name`.
for (idx, imp) in self.module.impls.iter().enumerate() {
if matches!(imp.target, crate::ir::ImplTarget::Primitive(p) if p == *prim)
&& imp.functions.iter().any(|f| f.name == method_name)
{
return DispatchKind::Static {
impl_id: crate::ir::ImplId(u32::try_from(idx).unwrap_or(0)),
};
}
}
// No matching primitive impl found. Fall through to the
// generic next-impl-id sentinel so ResolveReferencesPass
// still has a placeholder to walk.
return DispatchKind::Static {
impl_id: self.next_impl_id_or_record(),
};
}
if let ResolvedType::TypeParam(param_name) = receiver_ty {
if let Some(trait_id) = self.find_trait_for_method(param_name, method_name) {
return DispatchKind::Virtual {
trait_id,
method_name: method_name.to_string(),
};
}
}
if let ResolvedType::Trait(trait_id) = receiver_ty {
// Trait-typed receiver: emit virtual dispatch through the
// trait's vtable. Semantic accepts trait values at let /
// param / return positions; the backend resolves the
// call_indirect via the per-trait vtable for the concrete
// type stored in the binding.
return DispatchKind::Virtual {
trait_id: *trait_id,
method_name: method_name.to_string(),
};
}
self.errors.push(CompilerError::InternalError {
detail: format!(
"IR lowering: cannot resolve dispatch for method `{method_name}` on receiver {receiver_ty:?}"
),
span: self.current_span,
});
DispatchKind::Virtual {
trait_id: TraitId(u32::MAX),
method_name: method_name.to_string(),
}
}
/// Return the `ImplId` that will be assigned to the next impl block added.
/// On u32 overflow, records a `TooManyDefinitions` error and returns a
/// sentinel ID so compilation fails loudly rather than producing wrong dispatch.
fn next_impl_id_or_record(&mut self) -> ImplId {
self.module.next_impl_id().unwrap_or_else(|| {
self.errors.push(CompilerError::TooManyDefinitions {
kind: "impl",
span: self.current_span,
});
ImplId(u32::MAX)
})
}
/// Record `TooManyDefinitions` for an impl index that does not fit in `u32`
/// and return a sentinel `ImplId`. Callers should have already established
/// an `add_impl`-enforced invariant; this path exists purely to keep the
/// compiler type-safe without an unchecked cast.
fn impl_id_from_idx(&mut self, idx: usize) -> ImplId {
if let Ok(v) = u32::try_from(idx) {
ImplId(v)
} else {
self.errors.push(CompilerError::TooManyDefinitions {
kind: "impl",
span: self.current_span,
});
ImplId(u32::MAX)
}
}
fn trait_id_from_idx(&mut self, idx: usize) -> TraitId {
if let Ok(v) = u32::try_from(idx) {
TraitId(v)
} else {
self.errors.push(CompilerError::TooManyDefinitions {
kind: "trait",
span: self.current_span,
});
TraitId(u32::MAX)
}
}
fn find_impl_for_struct(
&mut self,
id: crate::ir::StructId,
method_name: &str,
) -> Option<ImplId> {
let found_idx = self.module.impls.iter().enumerate().find_map(|(idx, b)| {
if b.struct_id() == Some(id) && b.functions.iter().any(|f| f.name == method_name) {
Some(idx)
} else {
None
}
})?;
Some(self.impl_id_from_idx(found_idx))
}
fn find_impl_for_enum(&mut self, id: crate::ir::EnumId, method_name: &str) -> Option<ImplId> {
let found_idx = self.module.impls.iter().enumerate().find_map(|(idx, b)| {
if b.enum_id() == Some(id) && b.functions.iter().any(|f| f.name == method_name) {
Some(idx)
} else {
None
}
})?;
Some(self.impl_id_from_idx(found_idx))
}
/// Look up the trait that declares `method_name` among the constraints
/// attached to generic parameter `param_name`. Walks the innermost
/// generic scope outwards, finds the param by name, then scans its
/// trait constraints. Falls back to a module-wide search only when the
/// param is not in any active scope (e.g. a lowering invariant was
/// violated upstream) — this matches the pre-#12 behaviour so we don't
/// regress on cases where the scope hasn't been populated.
/// Walk the active generic scopes for `param_name` and return the
/// first constraint trait that declares a field named `field_name`.
/// Used by `resolve_field_type` when the receiver is a generic-param
/// value and we need to resolve a field access through one of its
/// trait bounds (e.g. `<T: Tagged>` and `t.name`).
pub(super) fn find_trait_for_field(
&self,
param_name: &str,
field_name: &str,
) -> Option<TraitId> {
for frame in self.generic_scopes.iter().rev() {
if let Some(param) = frame.iter().find(|p| p.name == param_name) {
for constraint in ¶m.constraints {
let idx = constraint.trait_id.0 as usize;
if let Some(trait_def) = self.module.traits.get(idx) {
if trait_def.fields.iter().any(|f| f.name == field_name) {
return Some(constraint.trait_id);
}
}
}
return None;
}
}
None
}
pub(super) fn find_trait_for_method(
&mut self,
param_name: &str,
method_name: &str,
) -> Option<TraitId> {
for frame in self.generic_scopes.iter().rev() {
if let Some(param) = frame.iter().find(|p| p.name == param_name) {
for constraint in ¶m.constraints {
let idx = constraint.trait_id.0 as usize;
if let Some(trait_def) = self.module.traits.get(idx) {
if trait_def.methods.iter().any(|m| m.name == method_name) {
return Some(constraint.trait_id);
}
}
}
// Param is in scope but none of its constraints declare the
// method — the semantic analyser should already have flagged
// this; return None rather than picking an unrelated trait.
return None;
}
}
// Fallback: no matching scope frame — behave as before.
let found_idx = self
.module
.traits
.iter()
.enumerate()
.find_map(|(idx, trait_def)| {
trait_def
.methods
.iter()
.any(|m| m.name == method_name)
.then_some(idx)
})?;
Some(self.trait_id_from_idx(found_idx))
}
}