From f720e810bf5539652c4f743b5945d47aeb11b413 Mon Sep 17 00:00:00 2001 From: simontreanor Date: Mon, 28 Sep 2026 02:54:13 +0100 Subject: [PATCH] lowering: native data class fields name their user types A field declared as a non-generic user sum type or record now names it in native output, as a string since the union alias comes after the classes (Add._0: "Expr"), instead of Any. mypy accepts it with the same flags, and mypyc can keep the field typed rather than boxing every read. --- src/lowering/mod.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/lowering/mod.rs b/src/lowering/mod.rs index 6d62de3..408367d 100644 --- a/src/lowering/mod.rs +++ b/src/lowering/mod.rs @@ -876,6 +876,59 @@ impl Lowerer { }, ); } + // Data class fields: one whose declared type is a user sum type or record + // (non-generic) names it, as a string, since the alias comes after the + // classes. `Any` otherwise, which the checker's own fields already say. + let field_spell = |te: &crate::parser::ast::TypeExpr| -> Option { + match te { + crate::parser::ast::TypeExpr::Con(name, _, args) if args.is_empty() => { + spell.get(name).map(|s| format!("\"{s}\"")) + } + _ => None, + } + }; + let mut field_types: HashMap>> = HashMap::new(); + for item in &module.items { + if let Item::Type(decl) = item + && decl.params.is_empty() + { + match &decl.kind { + TypeDeclKind::Sum(variants) => { + for v in variants { + field_types.insert( + py_ctor_name(&v.name), + v.fields.iter().map(field_spell).collect(), + ); + } + } + TypeDeclKind::Record(fields) => { + field_types.insert( + py_record_class(&decl.name), + fields.iter().map(|f| field_spell(&f.ty)).collect(), + ); + } + _ => {} + } + } + } + for stmt in &mut py.body { + if let PyStmt::ClassDef { + name, + field_types: types, + .. + } = stmt + && let Some(spelled) = field_types.get(name.as_str()) + && spelled.len() == types.len() + { + for (ty, spelled) in types.iter_mut().zip(spelled) { + if let Some(spelled) = spelled + && (ty == "Any" || ty == "object") + { + *ty = spelled.clone(); + } + } + } + } // The top-level function bindings and their types. let mut fn_types: HashMap = HashMap::new(); for item in &module.items {