|
| 1 | +let extract_concrete_typedecl : |
| 2 | + (Env.t -> Types.type_expr -> Path.t * Path.t * Types.type_declaration) ref = |
| 3 | + ref (Obj.magic ()) |
| 4 | + |
| 5 | +type 'value value = Known of 'value | Unknown |
| 6 | + |
| 7 | +type object_property = { |
| 8 | + key: string; |
| 9 | + value: runtime_js_value list value; |
| 10 | + optional: bool; |
| 11 | +} |
| 12 | +and runtime_js_value = |
| 13 | + | String of {value: string value} |
| 14 | + | Number of {value: string value} |
| 15 | + | BigInt of {value: string value} |
| 16 | + | Boolean of {value: bool value} |
| 17 | + | NullLiteral |
| 18 | + | UndefinedLiteral |
| 19 | + | Array of {element_type: runtime_js_value value} |
| 20 | + | Object of { |
| 21 | + properties: object_property list; |
| 22 | + can_have_unknown_properties: bool; |
| 23 | + } |
| 24 | + | Dict of {value_type: runtime_js_value list} |
| 25 | + | Promise of {resolved_type: runtime_js_value value} |
| 26 | + | Any |
| 27 | + |
| 28 | +let rec debug_print_runtime_value (value : runtime_js_value) = |
| 29 | + match value with |
| 30 | + | String {value = Known v} -> Printf.sprintf "String(%s)" v |
| 31 | + | String {value = Unknown} -> "String" |
| 32 | + | Number {value = Known v} -> Printf.sprintf "Number(%s)" v |
| 33 | + | Number {value = Unknown} -> "Number" |
| 34 | + | BigInt {value = Known v} -> Printf.sprintf "BigInt(%s)" v |
| 35 | + | BigInt {value = Unknown} -> "BigInt" |
| 36 | + | Boolean {value = Known v} -> Printf.sprintf "Boolean(%b)" v |
| 37 | + | Boolean {value = Unknown} -> "Boolean" |
| 38 | + | NullLiteral -> "Null" |
| 39 | + | UndefinedLiteral -> "Undefined" |
| 40 | + | Array {element_type = Known v} -> |
| 41 | + Printf.sprintf "Array(%s)" (debug_print_runtime_value v) |
| 42 | + | Array {element_type = Unknown} -> "Array" |
| 43 | + | Object {properties} -> |
| 44 | + Printf.sprintf "Object(%s)" |
| 45 | + (properties |
| 46 | + |> List.map (fun {key; value; optional} -> |
| 47 | + Printf.sprintf "{key: %s, value: %s, optional: %b}" key |
| 48 | + (match value with |
| 49 | + | Known v -> |
| 50 | + v |> List.map debug_print_runtime_value |> String.concat ", " |
| 51 | + | Unknown -> "Unknown") |
| 52 | + optional) |
| 53 | + |> String.concat ", ") |
| 54 | + | Promise {resolved_type = Known v} -> |
| 55 | + Printf.sprintf "Promise(%s)" (debug_print_runtime_value v) |
| 56 | + | Any -> "Any" |
| 57 | + | _ -> "__other__" |
| 58 | + |
| 59 | +type runtime_representation = {possible_values: runtime_js_value list} |
| 60 | + |
| 61 | +let tag_type_to_possible_values (tag_type : Ast_untagged_variants.tag_type) : |
| 62 | + runtime_js_value = |
| 63 | + match tag_type with |
| 64 | + | String v -> String {value = Known v} |
| 65 | + | Int v -> Number {value = Known (string_of_int v)} |
| 66 | + | Float v -> Number {value = Known v} |
| 67 | + | BigInt v -> BigInt {value = Known v} |
| 68 | + | Bool v -> Boolean {value = Known v} |
| 69 | + | Null -> NullLiteral |
| 70 | + | Undefined -> UndefinedLiteral |
| 71 | + | Untagged (IntType | FloatType) -> Number {value = Unknown} |
| 72 | + | Untagged StringType -> String {value = Unknown} |
| 73 | + | Untagged BooleanType -> Boolean {value = Unknown} |
| 74 | + | Untagged ObjectType -> |
| 75 | + Object {properties = []; can_have_unknown_properties = true} |
| 76 | + | Untagged UnknownType -> Any |
| 77 | + | _ -> Any |
| 78 | + |
| 79 | +let process_fields fields env type_expr_to_possible_values = |
| 80 | + fields |
| 81 | + |> List.map (fun (label : Types.label_declaration) -> |
| 82 | + { |
| 83 | + optional = false (* TODO: Replicate existing rules*); |
| 84 | + key = label.ld_id.name (* TODO: @as attribute *); |
| 85 | + value = Known (type_expr_to_possible_values label.ld_type env); |
| 86 | + }) |
| 87 | + |
| 88 | +let rec type_expr_to_possible_values (type_expr : Types.type_expr) (env : Env.t) |
| 89 | + : runtime_js_value list = |
| 90 | + match type_expr.desc with |
| 91 | + (* Builtins *) |
| 92 | + | Tconstr (p, _, _) when Path.same p Predef.path_string -> |
| 93 | + [String {value = Unknown}] |
| 94 | + | Tconstr (p, _, _) when Path.same p Predef.path_bool -> |
| 95 | + [Boolean {value = Unknown}] |
| 96 | + | Tconstr (p, _, _) |
| 97 | + when Path.same p Predef.path_float || Path.same p Predef.path_int -> |
| 98 | + [Number {value = Unknown}] |
| 99 | + | Tconstr (p, [inner], _) when Path.same p Predef.path_option -> |
| 100 | + [UndefinedLiteral] @ type_expr_to_possible_values inner env |
| 101 | + | Tconstr (p, [inner], _) when Path.same p Predef.path_dict -> |
| 102 | + [Dict {value_type = type_expr_to_possible_values inner env}] |
| 103 | + (* Types needing lookup*) |
| 104 | + | Tconstr (_, _, _) -> ( |
| 105 | + try |
| 106 | + match !extract_concrete_typedecl env type_expr with |
| 107 | + | _, _, {type_kind = Type_abstract | Type_open} -> [Any] |
| 108 | + | _, _, {type_kind = Type_record (fields, _)} -> |
| 109 | + [ |
| 110 | + Object |
| 111 | + { |
| 112 | + properties = process_fields fields env type_expr_to_possible_values; |
| 113 | + can_have_unknown_properties = false; |
| 114 | + }; |
| 115 | + ] |
| 116 | + | _, _, {type_kind = Type_variant consructors; type_attributes} -> |
| 117 | + let _unboxed = Ast_untagged_variants.process_untagged type_attributes in |
| 118 | + let tag_name = Ast_untagged_variants.process_tag_name type_attributes in |
| 119 | + |
| 120 | + consructors |
| 121 | + |> List.map (fun (c : Types.constructor_declaration) -> |
| 122 | + let tag_type = |
| 123 | + Ast_untagged_variants.process_tag_type c.cd_attributes |
| 124 | + in |
| 125 | + match (c.cd_args, tag_type) with |
| 126 | + | Cstr_tuple [], None -> String {value = Known c.cd_id.name} |
| 127 | + | Cstr_tuple [], Some tag_type -> |
| 128 | + tag_type_to_possible_values tag_type |
| 129 | + | Cstr_tuple payloads, maybe_tag_type -> |
| 130 | + let tag_value = |
| 131 | + match maybe_tag_type with |
| 132 | + | Some tag_type -> tag_type_to_possible_values tag_type |
| 133 | + | None -> String {value = Known c.cd_id.name} |
| 134 | + in |
| 135 | + Object |
| 136 | + { |
| 137 | + properties = |
| 138 | + [ |
| 139 | + { |
| 140 | + optional = false; |
| 141 | + key = |
| 142 | + (match tag_name with |
| 143 | + | None -> "TAG" |
| 144 | + | Some t -> t); |
| 145 | + value = Known [tag_value]; |
| 146 | + }; |
| 147 | + ] |
| 148 | + @ (payloads |
| 149 | + |> List.mapi (fun index (payload : Types.type_expr) -> |
| 150 | + { |
| 151 | + optional = false; |
| 152 | + key = "_" ^ string_of_int index; |
| 153 | + value = |
| 154 | + Known |
| 155 | + (type_expr_to_possible_values payload env); |
| 156 | + })); |
| 157 | + can_have_unknown_properties = false; |
| 158 | + } |
| 159 | + | Cstr_record fields, maybe_tag_type -> |
| 160 | + let tag_value = |
| 161 | + match maybe_tag_type with |
| 162 | + | Some tag_type -> tag_type_to_possible_values tag_type |
| 163 | + | None -> String {value = Known c.cd_id.name} |
| 164 | + in |
| 165 | + Object |
| 166 | + { |
| 167 | + properties = |
| 168 | + [ |
| 169 | + { |
| 170 | + optional = false; |
| 171 | + key = |
| 172 | + (match tag_name with |
| 173 | + | None -> "TAG" |
| 174 | + | Some t -> t); |
| 175 | + value = Known [tag_value]; |
| 176 | + }; |
| 177 | + ] |
| 178 | + @ process_fields fields env type_expr_to_possible_values; |
| 179 | + can_have_unknown_properties = false; |
| 180 | + }) |
| 181 | + with Not_found -> [Any]) |
| 182 | + (* Polyvariants *) |
| 183 | + | Tvariant {row_fields; row_closed} -> |
| 184 | + row_fields |
| 185 | + |> List.map (fun ((label, field) : string * Types.row_field) -> |
| 186 | + match field with |
| 187 | + | Rpresent None -> [String {value = Known label}] |
| 188 | + | Rpresent (Some inner) -> |
| 189 | + [ |
| 190 | + Object |
| 191 | + { |
| 192 | + can_have_unknown_properties = not row_closed; |
| 193 | + properties = |
| 194 | + [ |
| 195 | + { |
| 196 | + key = "NAME"; |
| 197 | + value = Known [String {value = Known label}]; |
| 198 | + optional = false; |
| 199 | + }; |
| 200 | + { |
| 201 | + key = "VAL"; |
| 202 | + optional = false; |
| 203 | + value = Known (type_expr_to_possible_values inner env); |
| 204 | + }; |
| 205 | + ]; |
| 206 | + }; |
| 207 | + ] |
| 208 | + | _ -> []) |
| 209 | + |> List.concat |
| 210 | + | _ -> [] |
| 211 | + |
| 212 | +let runtime_values_match (a : runtime_js_value) (b : runtime_js_value) = |
| 213 | + match (a, b) with |
| 214 | + | String {value = Known a_value}, String {value = Known b_value} -> |
| 215 | + a_value = b_value |
| 216 | + | Number {value = Known a_value}, Number {value = Known b_value} -> |
| 217 | + a_value = b_value |
| 218 | + | BigInt {value = Known a_value}, BigInt {value = Known b_value} -> |
| 219 | + a_value = b_value |
| 220 | + | Boolean {value = Known a_value}, Boolean {value = Known b_value} -> |
| 221 | + a_value = b_value |
| 222 | + | NullLiteral, NullLiteral -> true |
| 223 | + | UndefinedLiteral, UndefinedLiteral -> true |
| 224 | + | _ -> false |
| 225 | + |
| 226 | +let a_can_be_represented_as_b (a : runtime_js_value list) |
| 227 | + (b : runtime_js_value list) = |
| 228 | + a |
| 229 | + |> List.for_all (fun a_value -> |
| 230 | + b |> List.exists (fun b_value -> runtime_values_match a_value b_value)) |
| 231 | + |
| 232 | +let log t1 t2 env = |
| 233 | + Printf.sprintf "Can be coerced: %b\n\nt1 dump: %s\n\nt2 dump: %s\n" |
| 234 | + (a_can_be_represented_as_b |
| 235 | + (type_expr_to_possible_values t1 env) |
| 236 | + (type_expr_to_possible_values t2 env)) |
| 237 | + (type_expr_to_possible_values t1 env |
| 238 | + |> List.map debug_print_runtime_value |
| 239 | + |> String.concat " | ") |
| 240 | + (type_expr_to_possible_values t2 env |
| 241 | + |> List.map debug_print_runtime_value |
| 242 | + |> String.concat " | ") |
0 commit comments