Skip to content

Commit 0b61e51

Browse files
authored
Fix issue with untagged variants and object when null is one case. (#7303)
* Fix issue with untagged variants and object when null is one case. Fixes #7289 The check emitted for case `Object` was simply `type of ... === "object"`, though that is insufficient when the variant has one case corresponding to `null`. Now we check if such a variant case exists, and emit `... != null` in addition. * Add test example.
1 parent 5b7e803 commit 0b61e51

7 files changed

+49
-20
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
- Fix issue in functors with more than one argument (which are curried): emit nested function always. https://github.com/rescript-lang/rescript/pull/7273
3333
- Fix dot completion issue with React primitives. https://github.com/rescript-lang/rescript/pull/7292
3434
- Stdlib namespace for Core modules (fixes name clashes with user modules). https://github.com/rescript-lang/rescript/pull/7285
35+
- Fix runtime type check for Object in untagged variants when one variant case is `null`. https://github.com/rescript-lang/rescript/pull/7303
3536

3637
#### :house: Internal
3738

compiler/core/lam_compile.ml

+16-11
Original file line numberDiff line numberDiff line change
@@ -638,14 +638,14 @@ let compile output_prefix =
638638
Some ((String name, lam) :: string_table)
639639
| _, _ -> None)
640640
table (Some [])
641-
and compile_cases ?(untagged = false) ~cxt ~(switch_exp : E.t)
642-
?(default = NonComplete) ?(get_tag = fun _ -> None) ?(block_cases = [])
643-
cases : initialization =
641+
and compile_cases ?(untagged = false) ?(has_null_case = false) ~cxt
642+
~(switch_exp : E.t) ?(default = NonComplete) ?(get_tag = fun _ -> None)
643+
?(block_cases = []) cases : initialization =
644644
match use_compile_literal_cases cases ~get_tag with
645645
| Some string_cases ->
646646
if untagged then
647647
compile_untagged_cases ~cxt ~switch_exp ~block_cases ~default
648-
string_cases
648+
~has_null_case string_cases
649649
else compile_string_cases ~cxt ~switch_exp ~default string_cases
650650
| None ->
651651
cases
@@ -718,7 +718,7 @@ let compile output_prefix =
718718
else
719719
(* [e] will be used twice *)
720720
let dispatch e =
721-
let is_a_literal_case =
721+
let is_a_literal_case () =
722722
if untagged then
723723
E.is_a_literal_case
724724
~literal_cases:(get_literal_cases sw_names)
@@ -740,13 +740,17 @@ let compile output_prefix =
740740
&& List.length sw_consts = 0
741741
&& eq_default sw_num_default sw_blocks_default
742742
then
743+
let literal_cases = get_literal_cases sw_names in
744+
let has_null_case =
745+
List.mem Ast_untagged_variants.Null literal_cases
746+
in
743747
compile_cases ~untagged ~cxt
744748
~switch_exp:(if untagged then e else E.tag ~name:tag_name e)
745-
~block_cases ~default:sw_blocks_default ~get_tag:get_block_tag
746-
sw_blocks
749+
~block_cases ~has_null_case ~default:sw_blocks_default
750+
~get_tag:get_block_tag sw_blocks
747751
else
748752
[
749-
S.if_ is_a_literal_case
753+
S.if_ (is_a_literal_case ())
750754
(compile_cases ~cxt ~switch_exp:e ~block_cases
751755
~default:sw_num_default ~get_tag:get_const_tag sw_consts)
752756
~else_:
@@ -789,16 +793,17 @@ let compile output_prefix =
789793
~switch:(fun ?default ?declaration e clauses ->
790794
S.string_switch ?default ?declaration e clauses)
791795
~switch_exp ~default
792-
and compile_untagged_cases ~cxt ~switch_exp ~default ~block_cases cases =
796+
and compile_untagged_cases ~cxt ~switch_exp ~default ~block_cases
797+
~has_null_case cases =
793798
let mk_eq (i : Ast_untagged_variants.tag_type option) x j y =
794799
let check =
795800
match (i, j) with
796801
| Some tag_type, _ ->
797802
Ast_untagged_variants.DynamicChecks.add_runtime_type_check ~tag_type
798-
~block_cases (Expr x) (Expr y)
803+
~has_null_case ~block_cases (Expr x) (Expr y)
799804
| _, Some tag_type ->
800805
Ast_untagged_variants.DynamicChecks.add_runtime_type_check ~tag_type
801-
~block_cases (Expr y) (Expr x)
806+
~has_null_case ~block_cases (Expr y) (Expr x)
802807
| _ -> Ast_untagged_variants.DynamicChecks.( == ) (Expr x) (Expr y)
803808
in
804809
E.emit_check check

compiler/ml/ast_untagged_variants.ml

+8-5
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,8 @@ module DynamicChecks = struct
535535
else (* (undefiled + other) || other *)
536536
typeof e != object_
537537

538-
let add_runtime_type_check ~tag_type ~(block_cases : block_type list) x y =
538+
let add_runtime_type_check ~tag_type ~has_null_case
539+
~(block_cases : block_type list) x y =
539540
let instances =
540541
Ext_list.filter_map block_cases (function
541542
| InstanceType i -> Some i
@@ -547,14 +548,16 @@ module DynamicChecks = struct
547548
| FunctionType ) ->
548549
typeof y == x
549550
| Untagged ObjectType ->
551+
let object_case =
552+
if has_null_case then typeof y == x &&& (y != nil) else typeof y == x
553+
in
550554
if instances <> [] then
551555
let not_one_of_the_instances =
552-
Ext_list.fold_right instances
553-
(typeof y == x)
554-
(fun i x -> x &&& not (is_instance i y))
556+
Ext_list.fold_right instances object_case (fun i x ->
557+
x &&& not (is_instance i y))
555558
in
556559
not_one_of_the_instances
557-
else typeof y == x
560+
else object_case
558561
| Untagged (InstanceType i) -> is_instance i y
559562
| Untagged UnknownType ->
560563
(* This should not happen because unknown must be the only non-literal case *)

tests/tests/src/UntaggedVariants.mjs

+12
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,17 @@ let MergeCases = {
589589
can_merge: can_merge
590590
};
591591

592+
function printLength(json) {
593+
if (typeof json !== "object" || json === null || Array.isArray(json)) {
594+
return;
595+
}
596+
console.log("Length: ", Object.values(json).length);
597+
}
598+
599+
let ObjectAndNull = {
600+
printLength: printLength
601+
};
602+
592603
let $$Array;
593604

594605
let i = 42;
@@ -641,5 +652,6 @@ export {
641652
Aliased,
642653
OnlyOne,
643654
MergeCases,
655+
ObjectAndNull,
644656
}
645657
/* l2 Not a pure module */

tests/tests/src/UntaggedVariants.res

+8
Original file line numberDiff line numberDiff line change
@@ -461,3 +461,11 @@ module MergeCases = {
461461
| Boolean(_) => "merge"
462462
}
463463
}
464+
465+
module ObjectAndNull = {
466+
let printLength = (json: JSON.t) =>
467+
switch json {
468+
| Object(o) => Console.log2("Length: ", o->Dict.valuesToArray->Array.length)
469+
| _ => ()
470+
}
471+
}

tests/tests/src/core/Core_JsonTests.mjs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import * as Test from "./Test.mjs";
55
function decodeJsonTest() {
66
let json = {"someProp":{"otherProp": null, "thirdProp": [true, false]}};
77
let decodedCorrectly;
8-
if (typeof json === "object" && !Array.isArray(json)) {
8+
if (typeof json === "object" && json !== null && !Array.isArray(json)) {
99
let match = json["someProp"];
10-
if (typeof match === "object" && !Array.isArray(match)) {
10+
if (typeof match === "object" && match !== null && !Array.isArray(match)) {
1111
let match$1 = match["thirdProp"];
1212
if (Array.isArray(match$1) && match$1.length === 2) {
1313
let match$2 = match$1[0];

tests/tests/src/json_decorders.mjs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import * as Stdlib_Array from "rescript/lib/es6/Stdlib_Array.js";
44

55
function decodeUser(json) {
6-
if (typeof json !== "object" || Array.isArray(json)) {
6+
if (typeof json !== "object" || json === null || Array.isArray(json)) {
77
return;
88
}
99
let id = json.id;
@@ -30,7 +30,7 @@ function decodeUser(json) {
3030
}
3131

3232
function decodeGroup(json) {
33-
if (typeof json !== "object" || Array.isArray(json)) {
33+
if (typeof json !== "object" || json === null || Array.isArray(json)) {
3434
return;
3535
}
3636
let id = json.id;

0 commit comments

Comments
 (0)