Skip to content

Commit 78e75d4

Browse files
steveklabnikclaude
andcommitted
Anon-struct type aliases are visible to inference: ICE, field typing, annotations, method results (RUE-170, RUE-164)
Sema pre-resolves let-bound comptime type aliases before HM inference runs (precompute_comptime_type_locals in comptime_eval.rs, the same evaluation gate as analyze_call's implicit-comptime path) and feeds the alias map + lazily-registered anon-struct method signatures into ConstraintGenerator. Aliases route through the same concrete paths as named structs, fixing all four facets: the E9000 ICE on compound field initializers, the i32-defaulted FieldGet literal, unenforced let-annotations, and the <error> method-result decay. Supporting fixes: anon-struct method registration is atomic (partial inserts let the duplicate-method error be swallowed on re-evaluation), and StructInit's unknown-type fallback visits field initializers (ICE -> ordinary sema error even when alias resolution fails). Residual (pre-existing, not regressed): P::f() assoc-fn calls on alias-named types still resolve only via the named-struct table. 11 CLI cases (comptime_alias_infer.toml): 4 facets, generic/captured-value/chained-alias shapes, duplicate-method regression, named-struct controls. Frontend-only. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 5613eae commit 78e75d4

4 files changed

Lines changed: 479 additions & 10 deletions

File tree

crates/rue-air/src/inference/generate.rs

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,23 @@ pub struct ConstraintGenerator<'a> {
185185
/// `None` only in unit tests; production passes the map via
186186
/// [`Self::with_module_binding_types`].
187187
module_binding_types: Option<&'a HashMap<(FileId, Spur), Type>>,
188+
/// Compile-time type aliases bound by `let` in the current function body
189+
/// (`let P = F();` where `F` returns `type`), pre-resolved by sema before
190+
/// constraint generation. Consulted after `type_subst` when resolving
191+
/// struct-literal type names and `let` annotations, so anonymous-struct
192+
/// aliases route through the same concrete paths as named structs
193+
/// (RUE-170). Like sema's `comptime_type_vars`, the map is flat (not
194+
/// scope-aware). `None` only in unit tests; production passes the map via
195+
/// [`Self::with_comptime_local_types`].
196+
comptime_local_types: Option<&'a HashMap<Spur, Type>>,
197+
/// Method signatures registered after the shared `InferenceContext` was
198+
/// built: anonymous-struct methods are registered lazily during comptime
199+
/// evaluation, so they're absent from `methods`. Consulted when a method
200+
/// key misses `methods`, so a call on an anonymous-struct receiver yields
201+
/// its declared return type instead of `<error>` (RUE-164). `None` only
202+
/// in unit tests; production passes the map via
203+
/// [`Self::with_extra_method_sigs`].
204+
extra_method_sigs: Option<&'a HashMap<(StructId, Spur), MethodSig>>,
188205
/// Type intern pool for creating pointer and array types during constraint generation.
189206
type_pool: &'a TypeInternPool,
190207
}
@@ -235,6 +252,8 @@ impl<'a> ConstraintGenerator<'a> {
235252
type_subst,
236253
const_types: None,
237254
module_binding_types: None,
255+
comptime_local_types: None,
256+
extra_method_sigs: None,
238257
type_pool,
239258
}
240259
}
@@ -256,6 +275,28 @@ impl<'a> ConstraintGenerator<'a> {
256275
self
257276
}
258277

278+
/// Provide pre-resolved comptime type aliases (local name -> concrete
279+
/// type) for struct-literal and `let`-annotation resolution. See the
280+
/// `comptime_local_types` field (RUE-170).
281+
pub fn with_comptime_local_types(
282+
mut self,
283+
comptime_local_types: &'a HashMap<Spur, Type>,
284+
) -> Self {
285+
self.comptime_local_types = Some(comptime_local_types);
286+
self
287+
}
288+
289+
/// Provide late-registered method signatures (anonymous-struct methods)
290+
/// for method-call resolution. See the `extra_method_sigs` field
291+
/// (RUE-164).
292+
pub fn with_extra_method_sigs(
293+
mut self,
294+
extra_method_sigs: &'a HashMap<(StructId, Spur), MethodSig>,
295+
) -> Self {
296+
self.extra_method_sigs = Some(extra_method_sigs);
297+
self
298+
}
299+
259300
/// Get the type variables allocated for integer literals.
260301
pub fn int_literal_vars(&self) -> &[TypeVarId] {
261302
&self.int_literal_vars
@@ -288,6 +329,14 @@ impl<'a> ConstraintGenerator<'a> {
288329
}
289330
}
290331

332+
/// Look up a method signature, falling back to the late-registered
333+
/// (anonymous-struct) signatures when the shared map misses (RUE-164).
334+
fn method_sig(&self, key: &(StructId, Spur)) -> Option<&'a MethodSig> {
335+
self.methods
336+
.get(key)
337+
.or_else(|| self.extra_method_sigs.and_then(|sigs| sigs.get(key)))
338+
}
339+
291340
/// Resolve a field's declared type on a concrete struct type.
292341
fn field_type_of(&self, struct_ty: Type, field: Spur) -> Option<Type> {
293342
let TypeKind::Struct(struct_id) = struct_ty.kind() else {
@@ -548,9 +597,18 @@ impl<'a> ConstraintGenerator<'a> {
548597
let init_info = self.generate(*init, ctx);
549598

550599
let var_ty = if let Some(ty_sym) = type_annotation {
551-
// Explicit type annotation - use it and constrain init to match
552-
let ty_name = self.interner.resolve(ty_sym);
553-
if let Some(annotated_ty) = self.resolve_type_name(ty_name) {
600+
// Explicit type annotation - use it and constrain init to
601+
// match. Comptime type aliases (`let P = F(); let p: P =
602+
// ...`) resolve first, mirroring sema's annotation
603+
// validation order (`comptime_type_vars` before the type
604+
// tables); without this the annotation was unenforced and
605+
// any value typechecked against it (RUE-170).
606+
let annotated = self
607+
.comptime_local_types
608+
.and_then(|aliases| aliases.get(ty_sym).copied())
609+
.map(|ty| self.type_to_infer(ty))
610+
.or_else(|| self.resolve_type_name(self.interner.resolve(ty_sym)));
611+
if let Some(annotated_ty) = annotated {
554612
self.add_constraint(Constraint::equal(
555613
init_info.ty,
556614
annotated_ty.clone(),
@@ -1111,14 +1169,22 @@ impl<'a> ConstraintGenerator<'a> {
11111169
fields_len,
11121170
..
11131171
} => {
1114-
// Check type_subst first (for Self and type parameters in method bodies)
1172+
// Check type_subst first (for Self and type parameters in
1173+
// method bodies), then comptime type aliases (`let P = F();
1174+
// P { ... }`, RUE-170) — mirroring sema, which consults
1175+
// `comptime_type_vars` before the struct table — then named
1176+
// structs.
11151177
let struct_ty = self
11161178
.type_subst
11171179
.and_then(|subst| subst.get(type_name).copied())
1180+
.or_else(|| {
1181+
self.comptime_local_types
1182+
.and_then(|aliases| aliases.get(type_name).copied())
1183+
})
11181184
.or_else(|| self.structs.get(type_name).copied());
11191185

1186+
let fields = self.rir.get_field_inits(*fields_start, *fields_len);
11201187
if let Some(struct_ty) = struct_ty {
1121-
let fields = self.rir.get_field_inits(*fields_start, *fields_len);
11221188
// Constrain each initializer against its field's declared
11231189
// type, so literal initializers are range-checked at the
11241190
// field's width instead of silently wrapping
@@ -1133,6 +1199,14 @@ impl<'a> ConstraintGenerator<'a> {
11331199
}
11341200
InferType::Concrete(struct_ty)
11351201
} else {
1202+
// Unknown type name — sema reports the error. Still visit
1203+
// the initializers so every sub-expression gets a type;
1204+
// skipping them left compound initializers (`-1`, `1+2`)
1205+
// with unresolved variables, which sema then reported as
1206+
// an internal compiler error (RUE-170).
1207+
for (_, value_ref) in fields.iter() {
1208+
self.generate(*value_ref, ctx);
1209+
}
11361210
InferType::Concrete(Type::ERROR)
11371211
}
11381212
}
@@ -1336,9 +1410,11 @@ impl<'a> ConstraintGenerator<'a> {
13361410
}
13371411
InferType::Concrete(ty) => {
13381412
if let Some(struct_id) = ty.as_struct() {
1339-
// Use StructId directly for method lookup
1413+
// Use StructId directly for method lookup (falls
1414+
// back to late-registered anonymous-struct
1415+
// signatures, RUE-164)
13401416
let method_key = (struct_id, *method);
1341-
if let Some(method_sig) = self.methods.get(&method_key) {
1417+
if let Some(method_sig) = self.method_sig(&method_key) {
13421418
// Generate constraints for arguments
13431419
for (arg, param_type) in
13441420
args.iter().zip(method_sig.param_types.iter())

crates/rue-air/src/sema/analysis.rs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1679,6 +1679,44 @@ impl<'a> Sema<'a> {
16791679
type_subst: Option<&HashMap<Spur, Type>>,
16801680
value_subst: Option<&HashMap<Spur, ConstValue>>,
16811681
) -> CompileResult<HashMap<InstRef, Type>> {
1682+
// Pre-resolve `let`-bound comptime type aliases (`let P = F();` where
1683+
// `F` returns `type`) so inference can see the concrete anonymous
1684+
// struct types behind them. Without this, `P { ... }`, `let p: P`,
1685+
// and methods on `P`-typed receivers all fell through to `<error>`
1686+
// or unconstrained variables (RUE-170, RUE-164). This may create the
1687+
// anonymous structs (idempotently — analysis re-evaluates the same
1688+
// initializers later and structural equality dedups them).
1689+
let comptime_local_types =
1690+
self.precompute_comptime_type_locals(body, type_subst, value_subst);
1691+
1692+
// Anonymous-struct methods are registered lazily (during comptime
1693+
// evaluation, including the pre-pass above), after the shared
1694+
// `InferenceContext` was built — so collect the signatures it doesn't
1695+
// know about. Without these, a method call on an anonymous-struct
1696+
// receiver inferred to `<error>` and poisoned sibling constraints
1697+
// (RUE-164).
1698+
let extra_method_sigs: HashMap<(StructId, Spur), crate::inference::MethodSig> = self
1699+
.methods
1700+
.iter()
1701+
.filter(|(key, _)| !infer_ctx.method_sigs.contains_key(*key))
1702+
.map(|(key, info)| {
1703+
(
1704+
*key,
1705+
crate::inference::MethodSig {
1706+
struct_type: info.struct_type,
1707+
has_self: info.has_self,
1708+
param_types: self
1709+
.param_arena
1710+
.types(info.params)
1711+
.iter()
1712+
.map(|t| self.type_to_infer_type(*t))
1713+
.collect(),
1714+
return_type: self.type_to_infer_type(info.return_type),
1715+
},
1716+
)
1717+
})
1718+
.collect();
1719+
16821720
// Create constraint generator using pre-computed inference context
16831721
let mut cgen = ConstraintGenerator::with_type_subst(
16841722
self.rir,
@@ -1691,7 +1729,9 @@ impl<'a> Sema<'a> {
16911729
type_subst,
16921730
)
16931731
.with_const_types(&infer_ctx.const_types)
1694-
.with_module_binding_types(&infer_ctx.module_binding_types);
1732+
.with_module_binding_types(&infer_ctx.module_binding_types)
1733+
.with_comptime_local_types(&comptime_local_types)
1734+
.with_extra_method_sigs(&extra_method_sigs);
16951735

16961736
// Build parameter map for constraint context.
16971737
// Convert Type to InferType so arrays are represented structurally.
@@ -4650,6 +4690,14 @@ impl<'a> Sema<'a> {
46504690
// Track method names in this registration batch to detect duplicates
46514691
let mut seen_methods: std::collections::HashSet<Spur> = std::collections::HashSet::new();
46524692

4693+
// Stage registrations and commit only if the whole batch validates.
4694+
// Inserting one-by-one left earlier methods registered when a later
4695+
// one failed (e.g. a duplicate name), so re-evaluating the same
4696+
// AnonStructType — which happens since the RUE-170 inference pre-pass
4697+
// evaluates type aliases before analysis does — saw the methods as
4698+
// "already registered", skipped this check, and silently succeeded.
4699+
let mut staged: Vec<((StructId, Spur), MethodInfo)> = Vec::new();
4700+
46534701
for method_ref in method_refs {
46544702
let method_inst = self.rir.get(method_ref);
46554703
if let InstData::FnDecl {
@@ -4704,7 +4752,7 @@ impl<'a> Sema<'a> {
47044752
.param_arena
47054753
.alloc_method(param_names.into_iter(), param_types.into_iter());
47064754

4707-
self.methods.insert(
4755+
staged.push((
47084756
key,
47094757
MethodInfo {
47104758
struct_type,
@@ -4714,9 +4762,10 @@ impl<'a> Sema<'a> {
47144762
body: *body,
47154763
span: method_inst.span,
47164764
},
4717-
);
4765+
));
47184766
}
47194767
}
4768+
self.methods.extend(staged);
47204769
Some(())
47214770
}
47224771

0 commit comments

Comments
 (0)