@@ -3249,6 +3249,20 @@ fn check_module_impl(module: &Module, registry: &ModuleRegistry, collect_span_ty
32493249 }
32503250 }
32513251
3252+ // Build map of type alias constraints: alias name → constraints from its definition.
3253+ // These are stripped during convert_type_expr and lost from the Type representation,
3254+ // but needed for codegen when a value's signature uses the alias (e.g. `three :: Expr Number`
3255+ // where `type Expr a = forall e. E e => e a`).
3256+ let mut type_alias_constraints: HashMap<Symbol, Vec<(QualifiedIdent, Vec<Type>)>> = HashMap::new();
3257+ for decl in &module.decls {
3258+ if let Decl::TypeAlias { name, ty, .. } = decl {
3259+ let constraints = extract_type_signature_constraints(ty, &type_ops);
3260+ if !constraints.is_empty() {
3261+ type_alias_constraints.insert(name.value, constraints);
3262+ }
3263+ }
3264+ }
3265+
32523266 // Pass 1: Collect type signatures and data constructors
32533267 for decl in &module.decls {
32543268 let decl_name = match decl.name() {
@@ -3324,7 +3338,28 @@ fn check_module_impl(module: &Module, registry: &ModuleRegistry, collect_span_ty
33243338 .insert(qi(name.value), sig_constraints);
33253339 }
33263340 // Extract return-type inner-forall constraints
3327- let rt_constraints = extract_return_type_constraints(ty, &type_ops);
3341+ let mut rt_constraints = extract_return_type_constraints(ty, &type_ops);
3342+ // Fallback: if CST-level extraction found nothing, check if the
3343+ // return type is a type alias that hides a forall+constraints.
3344+ // E.g. `foo :: forall a. a -> Foo a` where `type Foo a = forall f. Monad f => f a`
3345+ if rt_constraints.is_empty() {
3346+ let sig_ty = &signatures[&name.value].1;
3347+ // Only check return-type alias expansion when the sig has
3348+ // function arrows. For 0-arity values like `three :: Expr Number`,
3349+ // the constraint is top-level and goes into signature_constraints.
3350+ let has_arrows = matches!(find_return_type_depth(sig_ty), d if d > 0);
3351+ if has_arrows {
3352+ let ret_type = find_return_type(sig_ty);
3353+ let expanded_ret = expand_type_aliases_limited(ret_type, &ctx.state.type_aliases, 0);
3354+ if matches!(&expanded_ret, Type::Forall(..)) {
3355+ if let Some(alias_name) = extract_type_head_name(ret_type) {
3356+ if let Some(alias_cs) = type_alias_constraints.get(&alias_name) {
3357+ rt_constraints = alias_cs.clone();
3358+ }
3359+ }
3360+ }
3361+ }
3362+ }
33283363 if !rt_constraints.is_empty() {
33293364 let depth = count_return_type_arrow_depth(ty);
33303365 ctx.return_type_constraints
@@ -6062,10 +6097,15 @@ fn check_module_impl(module: &Module, registry: &ModuleRegistry, collect_span_ty
60626097 // expands to `forall e. E e => e Number`, so the body is checked against
60636098 // the inner type with rigid Var(e), producing deferrable constraints.
60646099 let expanded_sig_storage;
6100+ let mut sig_alias_expanded_to_forall = false;
6101+ let mut sig_alias_name: Option<Symbol> = None;
60656102 let sig = if let Some(sig_ty) = sig {
60666103 if !matches!(sig_ty, Type::Forall(..)) {
60676104 let expanded = expand_type_aliases_limited(sig_ty, &ctx.state.type_aliases, 0);
60686105 if matches!(&expanded, Type::Forall(..)) {
6106+ sig_alias_expanded_to_forall = true;
6107+ // Extract alias name from head of original sig type
6108+ sig_alias_name = extract_type_head_name(sig_ty);
60696109 expanded_sig_storage = expanded;
60706110 Some(&expanded_sig_storage)
60716111 } else {
@@ -6550,15 +6590,22 @@ fn check_module_impl(module: &Module, registry: &ModuleRegistry, collect_span_ty
65506590 // `type Expr a = forall e. E e => e a`). Only alias-hidden
65516591 // constraints need body extraction; for regular explicit
65526592 // signatures, body constraints should NOT propagate.
6553- let mut has_alias_hidden_forall = false ;
6593+ let has_alias_hidden_forall = sig_alias_expanded_to_forall ;
65546594 let scheme = if let Some(sig_ty) = sig {
6555- // If sig_ty is NOT already a Forall, it might hide one inside
6556- // a type alias. Expand aliases to expose the hidden Forall
6557- // so the scheme has proper forall_vars.
6558- if !matches!(sig_ty, Type::Forall(..)) {
6595+ if sig_alias_expanded_to_forall {
6596+ // The sig was alias-expanded earlier to reveal a hidden Forall.
6597+ // Extract forall vars properly for the scheme.
6598+ if let Type::Forall(vars, body) = sig_ty {
6599+ Scheme {
6600+ forall_vars: vars.iter().map(|&(v, _)| v).collect(),
6601+ ty: (**body).clone(),
6602+ }
6603+ } else {
6604+ Scheme::mono(ctx.state.zonk(sig_ty.clone()))
6605+ }
6606+ } else if !matches!(sig_ty, Type::Forall(..)) {
65596607 let expanded = expand_type_aliases_limited(sig_ty, &ctx.state.type_aliases, 0);
65606608 if let Type::Forall(vars, body) = expanded {
6561- has_alias_hidden_forall = true;
65626609 Scheme {
65636610 forall_vars: vars.iter().map(|&(v, _)| v).collect(),
65646611 ty: *body,
@@ -6664,6 +6711,17 @@ fn check_module_impl(module: &Module, registry: &ModuleRegistry, collect_span_ty
66646711 // constraints (e.g., Union from calling `make` inside
66656712 // `makeStateless`) should not propagate to callers — the
66666713 // explicit signature defines the public contract.
6714+ // For alias-expanded sigs, look up constraints from the
6715+ // type alias definition (stored in type_alias_constraints).
6716+ // These constraints are stripped during convert_type_expr
6717+ // so they're not in the Type representation.
6718+ if has_alias_hidden_forall && !ctx.signature_constraints.contains_key(&qualified) {
6719+ if let Some(alias_name) = sig_alias_name {
6720+ if let Some(alias_constraints) = type_alias_constraints.get(&alias_name) {
6721+ ctx.signature_constraints.insert(qualified.clone(), alias_constraints.clone());
6722+ }
6723+ }
6724+ }
66676725 let skip_body_constraint_extraction =
66686726 sig.is_some() && !has_alias_hidden_forall;
66696727 if !skip_body_constraint_extraction && !ctx.signature_constraints.contains_key(&qualified) {
@@ -15908,6 +15966,36 @@ fn types_match_up_to_vars(pattern: &Type, target: &Type, subst: &mut HashMap<Sym
1590815966 }
1590915967}
1591015968
15969+ /// Find the return type of a function type, stripping outer Forall.
15970+ /// E.g. `Forall(a, Fun(Var(a), App(Con("Foo"), Var(a))))` → `App(Con("Foo"), Var(a))`
15971+ fn find_return_type(ty: &Type) -> &Type {
15972+ match ty {
15973+ Type::Forall(_, body) => find_return_type(body),
15974+ Type::Fun(_, ret) => find_return_type(ret),
15975+ other => other,
15976+ }
15977+ }
15978+
15979+ /// Count function arrow depth, stripping Forall.
15980+ fn find_return_type_depth(ty: &Type) -> usize {
15981+ match ty {
15982+ Type::Forall(_, body) => find_return_type_depth(body),
15983+ Type::Fun(_, ret) => 1 + find_return_type_depth(ret),
15984+ _ => 0,
15985+ }
15986+ }
15987+
15988+ /// Extract the head type constructor name from a Type.
15989+ /// E.g. `App(Con("Expr"), Con("Number"))` → Some("Expr")
15990+ /// E.g. `Con("Foo")` → Some("Foo")
15991+ fn extract_type_head_name(ty: &Type) -> Option<Symbol> {
15992+ match ty {
15993+ Type::Con(qi) => Some(qi.name),
15994+ Type::App(head, _) => extract_type_head_name(head),
15995+ _ => None,
15996+ }
15997+ }
15998+
1591115999/// Walks through Forall → Constrained patterns, converting constraint args to internal Types.
1591216000/// Skips Partial and Warn (which are handled separately).
1591316001pub(crate) fn extract_type_signature_constraints(
0 commit comments