From 4cdf8c60a184831b6c7b10fd34b35b3ef6fa40b1 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Fri, 30 Aug 2024 22:46:56 +0200 Subject: [PATCH] wip sketching out an IR for the runtime representation of types --- jscomp/core/matching_polyfill.ml | 1 + jscomp/ml/ctype.ml | 1 + jscomp/ml/runtime_representation.ml | 242 ++++++++++++++++++++++++++++ tst.res | 12 ++ 4 files changed, 256 insertions(+) create mode 100644 jscomp/ml/runtime_representation.ml create mode 100644 tst.res diff --git a/jscomp/core/matching_polyfill.ml b/jscomp/core/matching_polyfill.ml index 8955813581e..a7a8a64d955 100644 --- a/jscomp/core/matching_polyfill.ml +++ b/jscomp/core/matching_polyfill.ml @@ -23,6 +23,7 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) let () = Ast_untagged_variants.extract_concrete_typedecl := Ctype.extract_concrete_typedecl +let () = Runtime_representation.extract_concrete_typedecl := Ctype.extract_concrete_typedecl let () = Ast_untagged_variants.expand_head := Ctype.expand_head let names_from_construct_pattern (pat : Typedtree.pattern) = diff --git a/jscomp/ml/ctype.ml b/jscomp/ml/ctype.ml index 950a1a598f8..fe7fc8499ca 100644 --- a/jscomp/ml/ctype.ml +++ b/jscomp/ml/ctype.ml @@ -3560,6 +3560,7 @@ let rec subtype_rec env trace t1 t2 cstrs = cstrs with Not_found -> TypePairs.add subtypes (t1, t2) (); + Runtime_representation.log t1 t2 env |> print_endline; match (t1.desc, t2.desc) with (Tvar _, _) | (_, Tvar _) -> (trace, t1, t2, !univar_pairs)::cstrs diff --git a/jscomp/ml/runtime_representation.ml b/jscomp/ml/runtime_representation.ml new file mode 100644 index 00000000000..5f6ed35f100 --- /dev/null +++ b/jscomp/ml/runtime_representation.ml @@ -0,0 +1,242 @@ +let extract_concrete_typedecl : + (Env.t -> Types.type_expr -> Path.t * Path.t * Types.type_declaration) ref = + ref (Obj.magic ()) + +type 'value value = Known of 'value | Unknown + +type object_property = { + key: string; + value: runtime_js_value list value; + optional: bool; +} +and runtime_js_value = + | String of {value: string value} + | Number of {value: string value} + | BigInt of {value: string value} + | Boolean of {value: bool value} + | NullLiteral + | UndefinedLiteral + | Array of {element_type: runtime_js_value value} + | Object of { + properties: object_property list; + can_have_unknown_properties: bool; + } + | Dict of {value_type: runtime_js_value list} + | Promise of {resolved_type: runtime_js_value value} + | Any + +let rec debug_print_runtime_value (value : runtime_js_value) = + match value with + | String {value = Known v} -> Printf.sprintf "String(%s)" v + | String {value = Unknown} -> "String" + | Number {value = Known v} -> Printf.sprintf "Number(%s)" v + | Number {value = Unknown} -> "Number" + | BigInt {value = Known v} -> Printf.sprintf "BigInt(%s)" v + | BigInt {value = Unknown} -> "BigInt" + | Boolean {value = Known v} -> Printf.sprintf "Boolean(%b)" v + | Boolean {value = Unknown} -> "Boolean" + | NullLiteral -> "Null" + | UndefinedLiteral -> "Undefined" + | Array {element_type = Known v} -> + Printf.sprintf "Array(%s)" (debug_print_runtime_value v) + | Array {element_type = Unknown} -> "Array" + | Object {properties} -> + Printf.sprintf "Object(%s)" + (properties + |> List.map (fun {key; value; optional} -> + Printf.sprintf "{key: %s, value: %s, optional: %b}" key + (match value with + | Known v -> + v |> List.map debug_print_runtime_value |> String.concat ", " + | Unknown -> "Unknown") + optional) + |> String.concat ", ") + | Promise {resolved_type = Known v} -> + Printf.sprintf "Promise(%s)" (debug_print_runtime_value v) + | Any -> "Any" + | _ -> "__other__" + +type runtime_representation = {possible_values: runtime_js_value list} + +let tag_type_to_possible_values (tag_type : Ast_untagged_variants.tag_type) : + runtime_js_value = + match tag_type with + | String v -> String {value = Known v} + | Int v -> Number {value = Known (string_of_int v)} + | Float v -> Number {value = Known v} + | BigInt v -> BigInt {value = Known v} + | Bool v -> Boolean {value = Known v} + | Null -> NullLiteral + | Undefined -> UndefinedLiteral + | Untagged (IntType | FloatType) -> Number {value = Unknown} + | Untagged StringType -> String {value = Unknown} + | Untagged BooleanType -> Boolean {value = Unknown} + | Untagged ObjectType -> + Object {properties = []; can_have_unknown_properties = true} + | Untagged UnknownType -> Any + | _ -> Any + +let process_fields fields env type_expr_to_possible_values = + fields + |> List.map (fun (label : Types.label_declaration) -> + { + optional = false (* TODO: Replicate existing rules*); + key = label.ld_id.name (* TODO: @as attribute *); + value = Known (type_expr_to_possible_values label.ld_type env); + }) + +let rec type_expr_to_possible_values (type_expr : Types.type_expr) (env : Env.t) + : runtime_js_value list = + match type_expr.desc with + (* Builtins *) + | Tconstr (p, _, _) when Path.same p Predef.path_string -> + [String {value = Unknown}] + | Tconstr (p, _, _) when Path.same p Predef.path_bool -> + [Boolean {value = Unknown}] + | Tconstr (p, _, _) + when Path.same p Predef.path_float || Path.same p Predef.path_int -> + [Number {value = Unknown}] + | Tconstr (p, [inner], _) when Path.same p Predef.path_option -> + [UndefinedLiteral] @ type_expr_to_possible_values inner env + | Tconstr (p, [inner], _) when Path.same p Predef.path_dict -> + [Dict {value_type = type_expr_to_possible_values inner env}] + (* Types needing lookup*) + | Tconstr (_, _, _) -> ( + try + match !extract_concrete_typedecl env type_expr with + | _, _, {type_kind = Type_abstract | Type_open} -> [Any] + | _, _, {type_kind = Type_record (fields, _)} -> + [ + Object + { + properties = process_fields fields env type_expr_to_possible_values; + can_have_unknown_properties = false; + }; + ] + | _, _, {type_kind = Type_variant consructors; type_attributes} -> + let _unboxed = Ast_untagged_variants.process_untagged type_attributes in + let tag_name = Ast_untagged_variants.process_tag_name type_attributes in + + consructors + |> List.map (fun (c : Types.constructor_declaration) -> + let tag_type = + Ast_untagged_variants.process_tag_type c.cd_attributes + in + match (c.cd_args, tag_type) with + | Cstr_tuple [], None -> String {value = Known c.cd_id.name} + | Cstr_tuple [], Some tag_type -> + tag_type_to_possible_values tag_type + | Cstr_tuple payloads, maybe_tag_type -> + let tag_value = + match maybe_tag_type with + | Some tag_type -> tag_type_to_possible_values tag_type + | None -> String {value = Known c.cd_id.name} + in + Object + { + properties = + [ + { + optional = false; + key = + (match tag_name with + | None -> "TAG" + | Some t -> t); + value = Known [tag_value]; + }; + ] + @ (payloads + |> List.mapi (fun index (payload : Types.type_expr) -> + { + optional = false; + key = "_" ^ string_of_int index; + value = + Known + (type_expr_to_possible_values payload env); + })); + can_have_unknown_properties = false; + } + | Cstr_record fields, maybe_tag_type -> + let tag_value = + match maybe_tag_type with + | Some tag_type -> tag_type_to_possible_values tag_type + | None -> String {value = Known c.cd_id.name} + in + Object + { + properties = + [ + { + optional = false; + key = + (match tag_name with + | None -> "TAG" + | Some t -> t); + value = Known [tag_value]; + }; + ] + @ process_fields fields env type_expr_to_possible_values; + can_have_unknown_properties = false; + }) + with Not_found -> [Any]) + (* Polyvariants *) + | Tvariant {row_fields; row_closed} -> + row_fields + |> List.map (fun ((label, field) : string * Types.row_field) -> + match field with + | Rpresent None -> [String {value = Known label}] + | Rpresent (Some inner) -> + [ + Object + { + can_have_unknown_properties = not row_closed; + properties = + [ + { + key = "NAME"; + value = Known [String {value = Known label}]; + optional = false; + }; + { + key = "VAL"; + optional = false; + value = Known (type_expr_to_possible_values inner env); + }; + ]; + }; + ] + | _ -> []) + |> List.concat + | _ -> [] + +let runtime_values_match (a : runtime_js_value) (b : runtime_js_value) = + match (a, b) with + | String {value = Known a_value}, String {value = Known b_value} -> + a_value = b_value + | Number {value = Known a_value}, Number {value = Known b_value} -> + a_value = b_value + | BigInt {value = Known a_value}, BigInt {value = Known b_value} -> + a_value = b_value + | Boolean {value = Known a_value}, Boolean {value = Known b_value} -> + a_value = b_value + | NullLiteral, NullLiteral -> true + | UndefinedLiteral, UndefinedLiteral -> true + | _ -> false + +let a_can_be_represented_as_b (a : runtime_js_value list) + (b : runtime_js_value list) = + a + |> List.for_all (fun a_value -> + b |> List.exists (fun b_value -> runtime_values_match a_value b_value)) + +let log t1 t2 env = + Printf.sprintf "Can be coerced: %b\n\nt1 dump: %s\n\nt2 dump: %s\n" + (a_can_be_represented_as_b + (type_expr_to_possible_values t1 env) + (type_expr_to_possible_values t2 env)) + (type_expr_to_possible_values t1 env + |> List.map debug_print_runtime_value + |> String.concat " | ") + (type_expr_to_possible_values t2 env + |> List.map debug_print_runtime_value + |> String.concat " | ") diff --git a/tst.res b/tst.res new file mode 100644 index 00000000000..79c3639c60a --- /dev/null +++ b/tst.res @@ -0,0 +1,12 @@ +type x = [#One | #Two] + +@tag("kind") +type y = | @as("one") One({hello: [#hello]}) | @as(null) Two + +let x: x = #One + +let xx = #One({"hello": "hi"}) + +let y: y = One({hello: #hello}) + +let z = (x :> y)