@@ -1499,10 +1499,12 @@ allow_empty_list(Ty) ->
14991499 end .
15001500
15011501- type fun_ty () :: fun_ty_simple ()
1502+ | fun_ty_bounded ()
15021503 | fun_ty_intersection ()
15031504 | fun_ty_union ().
15041505
15051506- type fun_ty_simple () :: {fun_ty , [type ()], type ()}.
1507+ - type fun_ty_bounded () :: {fun_ty_bounded , [type ()], type (), #{atom () => type ()}}.
15061508- type fun_ty_intersection () :: {fun_ty_intersection , [fun_ty_simple ()]}.
15071509- type fun_ty_union () :: {fun_ty_union , [fun_ty ()]}.
15081510
@@ -1527,11 +1529,51 @@ expect_fun_type(Env, Type, Arity) ->
15271529- spec expect_fun_type1 (env (), type () | [type ()], arity ()) -> fun_ty () | type_error .
15281530expect_fun_type1 (Env , BTy = {type , _ , bounded_fun , [Ft , _Fc ]}, Arity ) ->
15291531 Sub = bounded_type_subst (Env , BTy ),
1530- Ft = ? assert_type (Ft , type ()),
1531- {fun_ty , ArgsTy , ResTy } = expect_fun_type1 (Env , Ft , Arity ),
1532- ArgsTy1 = subst_ty (Sub , ArgsTy ),
1533- ResTy1 = subst_ty (Sub , ResTy ),
1534- {fun_ty , ArgsTy1 , ResTy1 };
1532+ Ft1 = ? assert_type (Ft , type ()),
1533+ {fun_ty , ArgsTy , ResTy } = expect_fun_type1 (Env , Ft1 , Arity ),
1534+ case Env # env .solve_constraints of
1535+ true ->
1536+ % % When constraint solving is enabled, check if we can treat bounded
1537+ % % type variables polymorphically. This is only useful when type variables
1538+ % % in the spec have concrete (variable-free) bounds, like:
1539+ % % -spec f([A]) -> [A] when A :: {top(), top()}.
1540+ % % For specs where bounds reference other type variables (like lists:map),
1541+ % % we fall through to the standard substitution.
1542+ BoundedVars = maps :filter (fun (_Var , {var , _ , _ }) -> false ;
1543+ (_Var , Bound ) ->
1544+ % % Only keep if bound is variable-free
1545+ maps :size (free_vars (Bound )) == 0
1546+ end , Sub ),
1547+ case maps :size (BoundedVars ) > 0 of
1548+ true ->
1549+ % % Keep bounded type variables, substitute only
1550+ % % unconstrained ones and those with variable-containing bounds
1551+ PartialSub = maps :without (maps :keys (BoundedVars ), Sub ),
1552+ ArgsTy1 = subst_ty (PartialSub , ArgsTy ),
1553+ ResTy1 = subst_ty (PartialSub , ResTy ),
1554+ HasRemainingVars = lists :any (fun contains_type_variables /1 , [ResTy1 | ArgsTy1 ]),
1555+ case HasRemainingVars of
1556+ true ->
1557+ UBounds = maps :map (fun (_Var , _ ) ->
1558+ % % Get the bound from the constraint list
1559+ maps :get (_Var , BoundedVars )
1560+ end , BoundedVars ),
1561+ {fun_ty_bounded , ArgsTy1 , ResTy1 , UBounds };
1562+ false ->
1563+ % % All vars were substituted away, no poly needed
1564+ {fun_ty , ArgsTy1 , ResTy1 }
1565+ end ;
1566+ false ->
1567+ % % No concrete-bounded vars, use standard substitution
1568+ ArgsTy1 = subst_ty (Sub , ArgsTy ),
1569+ ResTy1 = subst_ty (Sub , ResTy ),
1570+ {fun_ty , ArgsTy1 , ResTy1 }
1571+ end ;
1572+ false ->
1573+ ArgsTy1 = subst_ty (Sub , ArgsTy ),
1574+ ResTy1 = subst_ty (Sub , ResTy ),
1575+ {fun_ty , ArgsTy1 , ResTy1 }
1576+ end ;
15351577expect_fun_type1 (_Env , {type , _ , 'fun' , [{type , _ , product , ArgsTy }, ResTy ]}, _Arity ) ->
15361578 {fun_ty , ArgsTy , ? assert_type (ResTy , type ())};
15371579expect_fun_type1 (_Env , {type , _ , 'fun' , []}, Arity ) ->
@@ -1591,6 +1633,14 @@ expect_intersection_type(Env, [FunTy|Tys], Arity) ->
15911633 {fun_ty_union , _ } ->
15921634 % % We can't have a union of functions as a spec clause
15931635 type_error ;
1636+ {fun_ty_bounded , ArgsTy , ResTy , _ } ->
1637+ % % Bounded poly within intersection: fall back to substituted form
1638+ Ty = {fun_ty , replace_type_vars_with_any (ArgsTy ),
1639+ replace_type_vars_with_any (ResTy )},
1640+ case expect_intersection_type (Env , Tys , Arity ) of
1641+ type_error -> type_error ;
1642+ Tyss -> [Ty | Tyss ]
1643+ end ;
15941644 Ty ->
15951645 Ty = ? assert_type (Ty , fun_ty_simple ()),
15961646 case expect_intersection_type (Env , Tys , Arity ) of
@@ -2532,6 +2582,14 @@ type_check_call_ty(Env, {fun_ty, ArgsTy, ResTy} = Ty, Args, {_, P, _} = E) ->
25322582 {LenTy , LenArgs } ->
25332583 throw (argument_length_mismatch (P , arity (LenTy ), arity (LenArgs )))
25342584 end ;
2585+ type_check_call_ty (Env , {fun_ty_bounded , ArgsTy , ResTy , UBounds }, Args , {_ , P , _ } = E ) ->
2586+ case {length (ArgsTy ), length (Args )} of
2587+ {L , L } ->
2588+ FunTy = {fun_ty , ArgsTy , ResTy },
2589+ type_check_poly_call_bounded (Env , FunTy , Args , E , UBounds );
2590+ {LenTy , LenArgs } ->
2591+ throw (argument_length_mismatch (P , arity (LenTy ), arity (LenArgs )))
2592+ end ;
25352593type_check_call_ty (Env , {fun_ty_intersection , ClauseTys }, Args , E ) ->
25362594 type_check_call_ty_intersect (Env , ClauseTys , Args , E );
25372595type_check_call_ty (Env , {fun_ty_union , Tyss }, Args , E ) ->
@@ -2596,6 +2654,36 @@ type_check_poly_call(Env, {fun_ty, ParamTys, ResTy}, Args, {Name, P, FunTy}) ->
25962654 NewEnv = union_var_binds (VarBindsList , Env ),
25972655 {NewResTy , NewEnv }.
25982656
2657+ % % Like type_check_poly_call but seeds the constraint system with upper bounds
2658+ % % from bounded type variable declarations (e.g. `when A :: {top(), top()}`).
2659+ - spec type_check_poly_call_bounded (env (), fun_ty_simple (), [type ()], {_ , anno (), type ()},
2660+ #{atom () => type ()}) -> {type (), env ()}.
2661+ type_check_poly_call_bounded (Env , {fun_ty , ParamTys , ResTy }, Args , {Name , P , FunTy }, UBounds ) ->
2662+ {ArgTys , VarBindsList } = lists :unzip ([ type_check_expr (Env , Arg ) || Arg <- Args ]),
2663+ Css = lists :map (fun ({ParamTy , Arg , ArgTy }) ->
2664+ case subtype_with_constraints (ArgTy , ParamTy , Env ) of
2665+ {true , Cs } -> Cs ;
2666+ false -> throw (type_error (Arg , ArgTy , ParamTy ))
2667+ end
2668+ end , lists :zip3 (ParamTys , Args , ArgTys )),
2669+ % % Seed upper bounds from the `when` clause
2670+ InitialUBoundCs = maps :fold (fun (Var , Bound , Acc ) ->
2671+ constraints :combine (Acc , constraints :upper (Var , Bound ), Env )
2672+ end , constraints :empty (), UBounds ),
2673+ CombinedCs = constraints :combine ([InitialUBoundCs | Css ], Env ),
2674+ CallExpr = {call , P , Name , Args },
2675+ ? verbose (Env , " ~s Bounded constraints: ~p~n " , [gradualizer_fmt :format_location (CallExpr , brief ), CombinedCs ]),
2676+ case constraints :satisfiable (CombinedCs , Env ) of
2677+ true -> ok ;
2678+ {false , Var , LBound , UBound } ->
2679+ throw ({constraint_error , Var , LBound , UBound , CallExpr , FunTy , ArgTys })
2680+ end ,
2681+ Subst = minimal_substitution (CombinedCs , ResTy ),
2682+ ? verbose (Env , " ~s Bounded substitution: ~p~n " , [gradualizer_fmt :format_location (CallExpr , brief ), Subst ]),
2683+ NewResTy = subst_ty (Subst , ? assert_type (ResTy , type ())),
2684+ NewEnv = union_var_binds (VarBindsList , Env ),
2685+ {NewResTy , NewEnv }.
2686+
25992687- spec type_check_call_ty_intersect (env (), _ , _ , _ ) -> {type (), env ()}.
26002688type_check_call_ty_intersect (Env , ClauseTys , Args , E = {Name , P , FunTy }) ->
26012689 check_call_arity (hd (ClauseTys ), Args , E ),
@@ -3137,6 +3225,10 @@ do_type_check_expr_in(Env, Ty, {'fun', _, {clauses, Clauses}} = Fun) ->
31373225 case expect_fun_type (Env , Ty , clause_arity (hd (Clauses ))) of
31383226 {fun_ty , ArgsTys , ResTy } ->
31393227 check_clauses (Env , ArgsTys , ResTy , Clauses , bind_vars );
3228+ {fun_ty_bounded , ArgsTys , ResTy , _UBounds } ->
3229+ ArgsTys1 = replace_type_vars_with_any (ArgsTys ),
3230+ ResTy1 = replace_type_vars_with_any (ResTy ),
3231+ check_clauses (Env , ArgsTys1 , ResTy1 , Clauses , bind_vars );
31403232 % % TODO: Can this case actually happen?
31413233 {fun_ty_intersection , Tyss } ->
31423234 check_clauses_intersect (Env , Tyss , Clauses );
@@ -3186,6 +3278,10 @@ do_type_check_expr_in(Env, Ty, {named_fun, _, FunName, Clauses} = Fun) ->
31863278 case expect_fun_type (Env , Ty , clause_arity (hd (Clauses ))) of
31873279 {fun_ty , ArgsTy , ResTy } ->
31883280 check_clauses (Env1 , ArgsTy , ResTy , Clauses , bind_vars );
3281+ {fun_ty_bounded , ArgsTy , ResTy , _UBounds } ->
3282+ ArgsTy1 = replace_type_vars_with_any (ArgsTy ),
3283+ ResTy1 = replace_type_vars_with_any (ResTy ),
3284+ check_clauses (Env1 , ArgsTy1 , ResTy1 , Clauses , bind_vars );
31893285 % % TODO: Can this case actually happen?
31903286 {fun_ty_intersection , Tyss } ->
31913287 check_clauses_intersect (Env1 , Tyss , Clauses );
@@ -3893,14 +3989,15 @@ type_check_call_intersection(Env, ResTy, OrigExpr, ClauseTys, Args, {Name, P, Fu
38933989 end .
38943990
38953991- spec check_call_arity (_ , _ , _ ) -> ok .
3896- check_call_arity ({fun_ty , ArgsTy , _FunResTy }, Args , {Name , P , _ }) ->
3897- case length (ArgsTy ) =:= length (Args ) of
3898- true -> ok ;
3899- false ->
3900- LenTys = arity (length (ArgsTy )),
3901- LenArgs = arity (length (Args )),
3902- throw (type_error (call_arity , P , Name , LenTys , LenArgs ))
3903- end .
3992+ check_call_arity ({fun_ty , ArgsTy , _FunResTy }, Args , E ) ->
3993+ check_call_arity_len (length (ArgsTy ), Args , E );
3994+ check_call_arity ({fun_ty_bounded , ArgsTy , _FunResTy , _UBounds }, Args , E ) ->
3995+ check_call_arity_len (length (ArgsTy ), Args , E ).
3996+
3997+ - spec check_call_arity_len (non_neg_integer (), list (), {_ , _ , _ }) -> ok .
3998+ check_call_arity_len (LenTys , Args , _ ) when LenTys =:= length (Args ) -> ok ;
3999+ check_call_arity_len (LenTys , Args , {Name , P , _ }) ->
4000+ throw (type_error (call_arity , P , Name , arity (LenTys ), arity (length (Args )))).
39044001
39054002- spec type_check_call (env (), type (), _ , _ , _ , _ ) -> env ().
39064003type_check_call (_Env , _ResTy , _ , {fun_ty , ArgsTy , _FunResTy }, Args , {Name , P , _ })
@@ -3926,6 +4023,18 @@ type_check_call(Env, ResTy, OrigExpr, {fun_ty, ArgsTy, FunResTy} = FunTy, Args,
39264023 false ->
39274024 throw (type_error (OrigExpr , CallResTy , ResTy ))
39284025 end ;
4026+ type_check_call (Env , ResTy , OrigExpr , {fun_ty_bounded , ArgsTy , FunResTy , UBounds }, Args , {Name , P , OrigFunTy }) ->
4027+ % % Bounded polymorphic call: type variables have upper bounds from `when` clauses.
4028+ % % We seed the constraint system with these upper bounds before solving.
4029+ FunTy = {fun_ty , ArgsTy , FunResTy },
4030+ {CallResTy , VarBinds } =
4031+ type_check_poly_call_bounded (Env , FunTy , Args , {Name , P , OrigFunTy }, UBounds ),
4032+ case subtype (CallResTy , ResTy , Env ) of
4033+ true ->
4034+ VarBinds ;
4035+ false ->
4036+ throw (type_error (OrigExpr , CallResTy , ResTy ))
4037+ end ;
39294038type_check_call (Env , ResTy , OrigExpr , {fun_ty_intersection , Tys }, Args , E ) ->
39304039 type_check_call_intersection (Env , ResTy , OrigExpr , Tys , Args , E );
39314040type_check_call (Env , ResTy , OrigExpr , {fun_ty_union , Tys }, Args , E ) ->
@@ -4070,6 +4179,8 @@ make_rigid_type_vars({ann_type, P, [AnnoVar, Type]}) ->
40704179 {ann_type , P , [AnnoVar , make_rigid_type_vars (Type )]};
40714180make_rigid_type_vars ({fun_ty , ArgTys , ResTy }) ->
40724181 {fun_ty , make_rigid_type_vars (ArgTys ), make_rigid_type_vars (ResTy )};
4182+ make_rigid_type_vars ({fun_ty_bounded , ArgTys , ResTy , UBounds }) ->
4183+ {fun_ty_bounded , make_rigid_type_vars (ArgTys ), make_rigid_type_vars (ResTy ), UBounds };
40734184make_rigid_type_vars ({fun_ty_intersection , FunTys }) ->
40744185 {fun_ty_intersection , make_rigid_type_vars (FunTys )};
40754186make_rigid_type_vars ({fun_ty_union , FunTys }) ->
@@ -4109,6 +4220,13 @@ infer_clause(Env, {clause, _, Args, Guards, Block}) ->
41094220 R :: env ().
41104221check_clauses_fun (Env , {fun_ty , ArgsTy , FunResTy }, Clauses ) ->
41114222 check_clauses (Env , ArgsTy , FunResTy , Clauses , bind_vars );
4223+ check_clauses_fun (Env , {fun_ty_bounded , ArgsTy , FunResTy , _UBounds }, Clauses ) ->
4224+ % % When checking the function definition, treat bounded type variables
4225+ % % the same as their bounds (substitute back). The bounded poly behavior
4226+ % % is only relevant at call sites.
4227+ ArgsTy1 = replace_type_vars_with_any (ArgsTy ),
4228+ FunResTy1 = replace_type_vars_with_any (FunResTy ),
4229+ check_clauses (Env , ArgsTy1 , FunResTy1 , Clauses , bind_vars );
41124230check_clauses_fun (Env , {fun_ty_intersection , Tys }, Clauses ) ->
41134231 check_clauses_intersect (Env , Tys , Clauses );
41144232check_clauses_fun (Env , {fun_ty_union , Tys }, Clauses ) ->
0 commit comments