88//! bindings need single-use tracking, and resolves polymorphic return types
99//! by manually instantiating callee schemes with known argument types.
1010
11+ use crate::adt::AdtRegistry;
1112use crate::infer::ty::{Kind, Scheme, Ty, TypeVarId};
1213use std::collections::HashMap;
1314use strata_ast::ast::{Block, Expr, Pat, Stmt};
@@ -104,10 +105,12 @@ pub struct MoveChecker<'a> {
104105 /// The environment with generalized function schemes (for resolving
105106 /// polymorphic call return types).
106107 env: &'a HashMap<String, Scheme>,
108+ /// ADT registry for resolving generic field types in pattern destructuring.
109+ adt_registry: &'a AdtRegistry,
107110}
108111
109112impl<'a> MoveChecker<'a> {
110- fn new(env: &'a HashMap<String, Scheme>) -> Self {
113+ fn new(env: &'a HashMap<String, Scheme>, adt_registry: &'a AdtRegistry ) -> Self {
111114 MoveChecker {
112115 name_to_id: HashMap::new(),
113116 tracked: HashMap::new(),
@@ -116,6 +119,7 @@ impl<'a> MoveChecker<'a> {
116119 errors: Vec::new(),
117120 binding_types: HashMap::new(),
118121 env,
122+ adt_registry,
119123 }
120124 }
121125
@@ -236,6 +240,106 @@ impl<'a> MoveChecker<'a> {
236240 }
237241 }
238242
243+ // -----------------------------------------------------------------------
244+ // ADT field type resolution for pattern destructuring
245+ // -----------------------------------------------------------------------
246+
247+ /// Resolve variant field types by matching the scrutinee type against the
248+ /// ADT definition and substituting generic type parameters.
249+ ///
250+ /// For example, if `Box<T>` has variant `Val(T)` and the scrutinee is
251+ /// `Box<FsCap>`, this returns `[FsCap]` for the variant fields.
252+ ///
253+ /// Returns None if the ADT or variant can't be resolved (fallback to unit).
254+ fn resolve_variant_field_types(
255+ &self,
256+ path: &strata_ast::ast::Path,
257+ scrutinee_ty: &Ty,
258+ ) -> Option<Vec<Ty>> {
259+ // Path for a variant is e.g. ["Box", "Val"] — first segment is ADT name
260+ if path.segments.len() < 2 {
261+ return None;
262+ }
263+ let adt_name = &path.segments[0].text;
264+ let variant_name = &path.segments[1].text;
265+
266+ let adt_def = self.adt_registry.get(adt_name)?;
267+ let variant = adt_def.find_variant(variant_name)?;
268+
269+ let raw_field_types = match &variant.fields {
270+ crate::adt::VariantFields::Unit => return Some(vec![]),
271+ crate::adt::VariantFields::Tuple(tys) => tys,
272+ };
273+
274+ // Build substitution: type_params[i] → scrutinee_args[i]
275+ let scrutinee_args = match scrutinee_ty {
276+ Ty::Adt { args, .. } => args,
277+ _ => return None,
278+ };
279+
280+ if adt_def.type_params.is_empty() || scrutinee_args.is_empty() {
281+ // Non-generic ADT — raw field types are already concrete
282+ return Some(raw_field_types.clone());
283+ }
284+
285+ // Map type param TypeVarIds to the concrete types from the scrutinee.
286+ // ADT type params are registered with TypeVarId(0), TypeVarId(1), etc.
287+ let mut mapping: HashMap<TypeVarId, Ty> = HashMap::new();
288+ for (i, arg) in scrutinee_args.iter().enumerate() {
289+ mapping.insert(TypeVarId(i as u32), arg.clone());
290+ }
291+
292+ Some(
293+ raw_field_types
294+ .iter()
295+ .map(|t| apply_type_mapping(t, &mapping))
296+ .collect(),
297+ )
298+ }
299+
300+ /// Resolve struct field types by matching the scrutinee type against the
301+ /// ADT definition and substituting generic type parameters.
302+ ///
303+ /// Returns a map from field name to resolved type, or None if unresolvable.
304+ fn resolve_struct_field_types(
305+ &self,
306+ path: &strata_ast::ast::Path,
307+ scrutinee_ty: &Ty,
308+ ) -> Option<HashMap<String, Ty>> {
309+ let adt_name = &path.segments.first()?.text;
310+
311+ let adt_def = self.adt_registry.get(adt_name)?;
312+ let fields = adt_def.fields()?;
313+
314+ // Build substitution from scrutinee args
315+ let scrutinee_args = match scrutinee_ty {
316+ Ty::Adt { args, .. } => args,
317+ _ => return None,
318+ };
319+
320+ if adt_def.type_params.is_empty() || scrutinee_args.is_empty() {
321+ // Non-generic struct — raw field types are already concrete
322+ return Some(
323+ fields
324+ .iter()
325+ .map(|f| (f.name.clone(), f.ty.clone()))
326+ .collect(),
327+ );
328+ }
329+
330+ let mut mapping: HashMap<TypeVarId, Ty> = HashMap::new();
331+ for (i, arg) in scrutinee_args.iter().enumerate() {
332+ mapping.insert(TypeVarId(i as u32), arg.clone());
333+ }
334+
335+ Some(
336+ fields
337+ .iter()
338+ .map(|f| (f.name.clone(), apply_type_mapping(&f.ty, &mapping)))
339+ .collect(),
340+ )
341+ }
342+
239343 // -----------------------------------------------------------------------
240344 // Type resolution for function call return types
241345 // -----------------------------------------------------------------------
@@ -553,15 +657,34 @@ impl<'a> MoveChecker<'a> {
553657 }
554658 }
555659 }
556- Pat::Variant { fields, .. } => {
557- // Caps in ADT fields are banned, so variant fields are unrestricted
558- for p in fields {
559- self.introduce_pattern_bindings(p, &Ty::unit());
660+ Pat::Variant { path, fields, .. } => {
661+ // DEFENSE-IN-DEPTH: When the caps-in-ADTs ban is lifted (post-linear
662+ // types), this substitution ensures generic variant fields are properly
663+ // resolved so capability bindings are tracked as affine.
664+ // Currently the ban prevents caps from reaching ADT fields, so all
665+ // field types resolve to unrestricted. But when the ban is lifted,
666+ // e.g. Box<FsCap> matched as Box::Val(inner), inner must be FsCap.
667+ let field_types = self.resolve_variant_field_types(path, ty);
668+ let unit = Ty::unit();
669+ for (i, p) in fields.iter().enumerate() {
670+ let field_ty = field_types
671+ .as_ref()
672+ .and_then(|tys| tys.get(i))
673+ .unwrap_or(&unit);
674+ self.introduce_pattern_bindings(p, field_ty);
560675 }
561676 }
562- Pat::Struct { fields, .. } => {
677+ Pat::Struct { path, fields, .. } => {
678+ // DEFENSE-IN-DEPTH: Same as variant — resolve struct field types
679+ // through the generic substitution when possible.
680+ let field_types = self.resolve_struct_field_types(path, ty);
681+ let unit = Ty::unit();
563682 for f in fields {
564- self.introduce_pattern_bindings(&f.pat, &Ty::unit());
683+ let field_ty = field_types
684+ .as_ref()
685+ .and_then(|m| m.get(&f.name.text))
686+ .unwrap_or(&unit);
687+ self.introduce_pattern_bindings(&f.pat, field_ty);
565688 }
566689 }
567690 }
@@ -657,8 +780,9 @@ pub fn check_function_body(
657780 params: &[(String, Ty, Span)],
658781 body: &Block,
659782 env: &HashMap<String, Scheme>,
783+ adt_registry: &AdtRegistry,
660784) -> Result<(), MoveError> {
661- let mut checker = MoveChecker::new(env);
785+ let mut checker = MoveChecker::new(env, adt_registry );
662786
663787 // Introduce function parameters as alive bindings
664788 for (name, ty, span) in params {
0 commit comments