@@ -422,6 +422,14 @@ let const_constructor (tag : Variant_runtime.tag) =
422422 | Some (Variant_runtime. Int v ) -> Const_int (Int32. of_int v)
423423 | _ -> Const_constructor tag
424424
425+ (* An untagged constructor has no runtime existence: [Color("primary")] is the
426+ string "primary", exactly as [Primary] is. Erasing the wrapper lets folding
427+ inspect the payload rather than a constructor the runtime cannot see. *)
428+ let const_block (tag_info : tag_info ) (args : structured_constant list ) =
429+ match (tag_info, args) with
430+ | Blk_constructor {runtime = {untagged = true } } , [payload] -> payload
431+ | _ -> Const_block (tag_info, args)
432+
425433(* A constructor with an optional shape carries no payload when constant. *)
426434let const_shape_none = Const_js_undefined {is_unit = false }
427435
@@ -773,6 +781,87 @@ and eq_option l1 l2 =
773781
774782and eq_approx_list ls ls1 = Ext_list. for_all2_no_exn ls ls1 eq_approx
775783
784+ (* Classify constants by the JavaScript value emitted by [Lam_compile_const]
785+ and [Js_dump], not by their source constructor. Unknown representations
786+ must retain runtime dispatch. *)
787+ type value_kind =
788+ | Is_literal of Variant_runtime .literal_tag
789+ | Is_object
790+ | Is_array
791+ | Unknown_value
792+
793+ let rec runtime_value_kind (c : structured_constant ) =
794+ match c with
795+ | Const_string s -> Is_literal (String s)
796+ | Const_int i -> Is_literal (Int (Int32. to_int i))
797+ | Const_float f -> Is_literal (Float f)
798+ | Const_js_true -> Is_literal (Bool true )
799+ | Const_js_false -> Is_literal (Bool false )
800+ | Const_js_null -> Is_literal Null
801+ | Const_js_undefined _ -> Is_literal Undefined
802+ | Const_polyvar name -> Is_literal (String name)
803+ | Const_constructor {name = "[]" ; literal = None } -> Is_literal (Int 0 )
804+ | Const_constructor {name; literal = None } -> Is_literal (String name)
805+ | Const_constructor {literal = Some (BigInt _ )} -> Unknown_value
806+ | Const_constructor {literal = Some literal } -> Is_literal literal
807+ | Const_block (Blk_constructor {runtime = {untagged = true } } , args ) -> (
808+ (* Also handle wrappers in constants read from existing compiler data. *)
809+ match args with
810+ | [payload] -> runtime_value_kind payload
811+ | _ -> Unknown_value )
812+ | Const_block (Blk_tuple, _ ) -> Is_array
813+ | Const_block (Blk_record {fields} , _ ) ->
814+ if
815+ Array. length fields <> 0
816+ && Ext_array. for_alli fields (fun i (name , _ ) -> string_of_int i = name)
817+ then Is_array
818+ else Is_object
819+ | Const_block
820+ ( ( Blk_constructor _ | Blk_record_inlined _ | Blk_poly_var
821+ | Blk_record_ext _ | Blk_module _ | Blk_module_export _ | Blk_extension
822+ ),
823+ _ ) ->
824+ Is_object
825+ | Const_char _ | Const_bigint _ | Const_some _ | Const_module_alias
826+ | Const_assertfalse ->
827+ Unknown_value
828+
829+ (* Runtime equality, rather than equality of tags: [@as(1)] and a payload
830+ [1.0] are the same JavaScript number. Bigint spellings are not compared;
831+ both bigint payloads and bigint constructors are classified as unknown.
832+ Integer tags are emitted through [Int32.of_int], just like [Const_int]. *)
833+ let literal_denotes_same (a : Variant_runtime.literal_tag )
834+ (b : Variant_runtime.literal_tag ) =
835+ match (a, b) with
836+ | String x , String y -> x = y
837+ | Int x , Int y -> Int32. of_int x = Int32. of_int y
838+ | Float x , Float y -> float_of_string x = float_of_string y
839+ | Int x , Float y | Float y , Int x ->
840+ Int32. to_float (Int32. of_int x) = float_of_string y
841+ | Bool x , Bool y -> x = y
842+ | Null , Null | Undefined , Undefined -> true
843+ | (String _ | Int _ | Float _ | Bool _ | BigInt _ | Null | Undefined ), _ ->
844+ false
845+
846+ (* Mirror [Dynamic_checks]: literals take precedence; the object case excludes
847+ arrays when an array case exists. Int and float share one runtime type. *)
848+ let value_has_block_type ~block_types (kind : value_kind )
849+ (block_type : Variant_runtime.block_type ) =
850+ match (block_type, kind) with
851+ | (IntType | FloatType ), Is_literal (Int _ | Float _ ) -> true
852+ | StringType , Is_literal (String _ ) -> true
853+ | BooleanType , Is_literal (Bool _ ) -> true
854+ | ObjectType , Is_object -> true
855+ | InstanceType Array , Is_array -> true
856+ | ObjectType , Is_array ->
857+ not (List. mem (Variant_runtime. InstanceType Array ) block_types)
858+ | UnknownType , (Is_literal _ | Is_object | Is_array ) -> true
859+ | UnknownType , Unknown_value -> false
860+ | ( ( IntType | FloatType | StringType | BooleanType | ObjectType | BigintType
861+ | FunctionType | InstanceType _ ),
862+ _ ) ->
863+ false
864+
776865let switch lam (lam_switch : lambda_switch ) : t =
777866 let action_or_switch = function
778867 | Some action -> action
@@ -781,8 +870,49 @@ let switch lam (lam_switch : lambda_switch) : t =
781870 | Some action -> action
782871 | None -> Lswitch (lam, lam_switch))
783872 in
784- match lam with
785- | Lconst (Const_constructor cstr_name ) ->
873+ (* An untagged variant is dispatched on the value, so a constant scrutinee is
874+ decided here rather than by the constructor it was written with - which
875+ has no runtime existence and may be shared with a literal constructor.
876+ [`Undecided] means this layer cannot name the constant's runtime shape, so
877+ the switch has to stay; it is not the same as "no case matches". *)
878+ let untagged_action (facts : Variant_runtime.matching_facts ) cst =
879+ let find_in cases matches =
880+ `Case
881+ (Ext_list. find_opt cases (fun (key , action ) ->
882+ match key with
883+ | Switch_constructor case when matches case -> Some action
884+ | Switch_int _ | Switch_constructor _ -> None ))
885+ in
886+ let literal_of_tag (tag : Variant_runtime.tag ) =
887+ match tag.literal with
888+ | Some literal -> literal
889+ | None -> Variant_runtime. String tag.name
890+ in
891+ let matches_block = value_has_block_type ~block_types: facts.block_types in
892+ let kind = runtime_value_kind cst in
893+ match kind with
894+ | Unknown_value -> `Undecided
895+ | Is_literal literal
896+ when Ext_list. exists facts.literal_tags (literal_denotes_same literal) ->
897+ (* The literal side wins, exactly as it does at runtime. *)
898+ find_in lam_switch.sw_consts (function
899+ | Constant tag -> literal_denotes_same literal (literal_of_tag tag)
900+ | Block _ -> false )
901+ | Is_literal _ | Is_object | Is_array ->
902+ (* Not a declared literal, so the payload's runtime shape decides. *)
903+ if Ext_list. exists facts.block_types (matches_block kind) then
904+ find_in lam_switch.sw_blocks (function
905+ | Block {block_type = Some block_type } ->
906+ matches_block kind block_type
907+ | Constant _ | Block {block_type = None } -> false )
908+ else `Undecided
909+ in
910+ match (lam, lam_switch.sw_dispatch) with
911+ | Lconst cst , Switch_variant ({block_types = _ :: _ } as facts ) -> (
912+ match untagged_action facts cst with
913+ | `Case action -> action_or_switch action
914+ | `Undecided -> Lswitch (lam, lam_switch))
915+ | Lconst (Const_constructor cstr_name ), _ ->
786916 let action =
787917 Ext_list. find_opt lam_switch.sw_consts (fun (key , action ) ->
788918 match key with
@@ -791,7 +921,7 @@ let switch lam (lam_switch : lambda_switch) : t =
791921 | Switch_int _ | Switch_constructor _ -> None )
792922 in
793923 action_or_switch action
794- | Lconst (Const_int i ) ->
924+ | Lconst (Const_int i ), _ ->
795925 (* Because of inlining and dead code, we might be looking at a value of unexpected type
796926 e.g. an integer, so the const case might not be found *)
797927 let i = Int32. to_int i in
@@ -806,15 +936,7 @@ let switch lam (lam_switch : lambda_switch) : t =
806936 | Switch_int _ | Switch_constructor _ -> None )
807937 in
808938 action_or_switch action
809- | Lconst
810- (Const_block
811- ( ( Blk_constructor {runtime = {untagged = true }}
812- | Blk_record_inlined {runtime = {untagged = true }} ),
813- _ )) ->
814- (* An untagged payload can have the same runtime value as a literal
815- constructor. Its source constructor does not determine the match. *)
816- Lswitch (lam, lam_switch)
817- | Lconst (Const_block (tag_info , _ )) ->
939+ | Lconst (Const_block (tag_info , _ )), _ ->
818940 let runtime =
819941 match tag_info with
820942 | Blk_constructor {runtime} | Blk_record_inlined {runtime} -> Some runtime
0 commit comments