@@ -354,8 +354,18 @@ compat_ty({type, _, union, Tys1}, {type, _, union, Tys2} = Ty2, Seen, Env) ->
354354 end ,
355355 {Seen2 , constraints :combine (C1 , C2 , Env )}
356356 end , {Seen , constraints :empty ()}, Tys1 );
357- compat_ty (Ty1 , {type , _ , union , Tys2 }, Seen , Env ) ->
358- any_type (Ty1 , Tys2 , Seen , Env );
357+ compat_ty (Ty1 , {type , _ , union , Tys2 } = Ty2 , Seen , Env ) ->
358+ try
359+ any_type (Ty1 , Tys2 , Seen , Env )
360+ catch
361+ nomatch ->
362+ case distribute_inner_unions (Ty1 ) of
363+ false ->
364+ throw (nomatch );
365+ {true , DistributedTy1 } ->
366+ compat (DistributedTy1 , Ty2 , Seen , Env )
367+ end
368+ end ;
359369compat_ty ({type , _ , union , Tys1 }, Ty2 , Seen , Env ) ->
360370 all_type (Tys1 , Ty2 , Seen , Env );
361371
@@ -590,6 +600,81 @@ all_type([Ty1|Tys], Ty, AIn, Css, Env) ->
590600 {AOut , Cs } = compat (Ty1 , Ty , AIn , Env ),
591601 all_type (Tys , Ty , AOut , [Cs |Css ], Env ).
592602
603+ % % Distribute unions inside compound types (tuples, maps) to create a union of
604+ % % compound types. For example:
605+ % % {t, a | b} -> {t, a} | {t, b}
606+ % % #{a => b | c} -> #{a => b} | #{a => c}
607+ % % Returns `false' if Ty contains no inner unions to distribute, or
608+ % % `{true, DistributedUnion}' after distribution.
609+ % % A size limit prevents exponential blowup for types with many union elements.
610+ - spec distribute_inner_unions (type ()) -> false | {true , type ()}.
611+ distribute_inner_unions ({type , Ann , tuple , Args }) when is_list (Args ) ->
612+ case has_inner_union (Args ) of
613+ false -> false ;
614+ true ->
615+ Distributed = distribute_tuple_args (Ann , Args ),
616+ case length (Distributed ) of
617+ N when N > 100 -> false ;
618+ _ -> {true , type (union , Distributed )}
619+ end
620+ end ;
621+ distribute_inner_unions ({type , Ann , map , Assocs }) when is_list (Assocs ) ->
622+ case has_inner_union_in_assocs (Assocs ) of
623+ false -> false ;
624+ true ->
625+ Distributed = distribute_map_assocs (Ann , Assocs ),
626+ case length (Distributed ) of
627+ N when N > 100 -> false ;
628+ _ -> {true , type (union , Distributed )}
629+ end
630+ end ;
631+ distribute_inner_unions (_ ) ->
632+ false .
633+
634+ - spec has_inner_union ([type ()]) -> boolean ().
635+ has_inner_union ([]) -> false ;
636+ has_inner_union ([{type , _ , union , _ } | _ ]) -> true ;
637+ has_inner_union ([_ | Rest ]) -> has_inner_union (Rest ).
638+
639+ - spec has_inner_union_in_assocs ([type ()]) -> boolean ().
640+ has_inner_union_in_assocs ([]) -> false ;
641+ has_inner_union_in_assocs ([{type , _ , _ , [_K , {type , _ , union , _ }]} | _ ]) -> true ;
642+ has_inner_union_in_assocs ([_ | Rest ]) -> has_inner_union_in_assocs (Rest ).
643+
644+ % % Distribute unions in tuple element positions.
645+ % % {A, B|C, D} -> [{A, B, D}, {A, C, D}]
646+ - spec distribute_tuple_args (erl_anno :anno (), [type ()]) -> [type ()].
647+ distribute_tuple_args (Ann , Args ) ->
648+ [{type , Ann , tuple , Combo } || Combo <- cartesian_product (Args )].
649+
650+ % % Distribute unions in map association value positions.
651+ % % #{K1 => V1|V2, K2 => V3} -> [#{K1 => V1, K2 => V3}, #{K1 => V2, K2 => V3}]
652+ - spec distribute_map_assocs (erl_anno :anno (), [type ()]) -> [type ()].
653+ distribute_map_assocs (Ann , Assocs ) ->
654+ ExpandedAssocLists = cartesian_product_assocs (Assocs ),
655+ [{type , Ann , map , AssocList } || AssocList <- ExpandedAssocLists ].
656+
657+ % % Cartesian product of type lists, expanding union elements.
658+ % % [a, b|c] -> [[a, b], [a, c]]
659+ - spec cartesian_product ([type ()]) -> [[type ()]].
660+ cartesian_product ([]) -> [[]];
661+ cartesian_product ([{type , _ , union , Tys } | Rest ]) ->
662+ RestProduct = cartesian_product (Rest ),
663+ [[Ty | R ] || Ty <- Tys , R <- RestProduct ];
664+ cartesian_product ([Arg | Rest ]) ->
665+ RestProduct = cartesian_product (Rest ),
666+ [[Arg | R ] || R <- RestProduct ].
667+
668+ % % Cartesian product of map associations, expanding unions in value positions.
669+ - spec cartesian_product_assocs ([type ()]) -> [[type ()]].
670+ cartesian_product_assocs ([]) -> [[]];
671+ cartesian_product_assocs ([{type , Ann , AssocType , [K , {type , _ , union , Tys }]} | Rest ]) ->
672+ RestProduct = cartesian_product_assocs (Rest ),
673+ [[{type , Ann , AssocType , [K , Ty ]} | R ] || Ty <- Tys , R <- RestProduct ];
674+ cartesian_product_assocs ([Assoc | Rest ]) ->
675+ RestProduct = cartesian_product_assocs (Rest ),
676+ [[Assoc | R ] || R <- RestProduct ].
677+
593678% % Looks up the fields of a record by name and, if present, by the module where
594679% % it belongs if a filename is included in the Anno.
595680- spec get_maybe_remote_record_fields (RecName :: atom (),
@@ -2892,8 +2977,13 @@ do_type_check_expr_in(Env, ResTy, {tuple, _, TS} = Tup) ->
28922977 {elem_tys , Tyss } ->
28932978 case type_check_tuple_union_in (Env , Tyss , TS ) of
28942979 none ->
2895- {Ty , _VB } = type_check_expr (Env , Tup ),
2896- throw (type_error (Tup , Ty , ResTy ));
2980+ {Ty , VB } = type_check_expr (Env , Tup ),
2981+ case subtype (Ty , ResTy , Env ) of
2982+ true ->
2983+ VB ;
2984+ false ->
2985+ throw (type_error (Tup , Ty , ResTy ))
2986+ end ;
28972987 VBs ->
28982988 union_var_binds (VBs , Env )
28992989 end ;
0 commit comments