Skip to content

Commit c2005fd

Browse files
zthcristianoctsnobip
authored
Pattern matching for dicts (#7059)
* Draft dict pattern matching. Works for this: ``` type myDict = {name?:string, anyOtherField?: int} let tst = (d: myDict) => switch d { | {name:n, something:i} => String.length(n) + i | {name:n} => String.length(n) | {something:i} => i | _ => 0 } ``` * Avoid mis-firing when a field is legit missing. * Make lbl_all mutable. With lbl_all mutable, it can be extended when new fields are used in pattern matching. This handles examples with multiple fields: ``` type myDict = {name?:string, anyOtherField?: int} let tst = (d: myDict) => switch d { | {a:i, b:j} => i + j | _ => 0 } ``` * Add test for the various aspects of first class dicts. * update tests * make builtin dict type be a record with anyOtherField catch all * make typechecker account for res.dictPattern attribute to infer record pattern as dict pattern match when the type is not already known * format * add some tests, and disallow direct record field access on dicts * make code path handling the magic record field for dicts just work on the predefined dict * remove now irrelevant test since we reduced scope to just focus on dicts in the first iteration, not record-with-some-and-some-unknown-properties * remove lingering file * format * make sure coercion is disallowed for dicts * add internal test making sure dict labels dont stack * add more fields to test * comment + rename file * share a few definitions * no need to check tvar * remove comment * add more comments * syntax support * cleanup * add broken dict pattern parsing test * fix pattern matching of dict * comments and changelog * a few more comment tests * undo changelog formatting * fixes * simplify * add live attribute suppressing dead code analysis for dicts since they can't be statically analysed for unused fields --------- Co-authored-by: Cristiano Calcagno <[email protected]> Co-authored-by: Paul Tsnobiladzé <[email protected]>
1 parent 70700fe commit c2005fd

34 files changed

+619
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- Use FORCE_COLOR environmental variable to force colorized output https://github.com/rescript-lang/rescript-compiler/pull/7033
2222
- Allow spreads of variants in patterns (`| ...someVariant as v => `) when the variant spread is a subtype of the variant matched on. https://github.com/rescript-lang/rescript-compiler/pull/6721
2323
- Fix the issue where dynamic imports are not working for function-defined externals. https://github.com/rescript-lang/rescript-compiler/pull/7060
24+
- Allow pattern matching on dicts. `switch someDict { | dict{"one": 1} => Js.log("one is one") }` https://github.com/rescript-lang/rescript-compiler/pull/7059
2425

2526
#### :bug: Bug fix
2627

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/dict_coercion.res:7:10-30
4+
5+
5 │ type fakeDict<'t> = {dictValuesType?: 't}
6+
6 │
7+
7 │ let d = (dict :> fakeDict<int>)
8+
8 │
9+
10+
Type Js.Dict.t<int> = dict<int> is not a subtype of fakeDict<int>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/dict_magic_field_on_non_dict.res:5:6-23
4+
5+
3 │ let foo = (fakeDict: fakeDict<'a>) => {
6+
4 │ switch fakeDict {
7+
5 │ | {someUndefinedField: 1} => Js.log("one")
8+
6 │ | _ => Js.log("not one")
9+
7 │ }
10+
11+
The field someUndefinedField does not belong to type fakeDict
12+
13+
This record pattern is expected to have type fakeDict<'a>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/dict_pattern_inference.res:3:27-33
4+
5+
1 │ let foo = dict =>
6+
2 │ switch dict {
7+
3 │ | dict{"one": 1, "two": "hello"} => Js.log("one")
8+
4 │ | _ => Js.log("not one")
9+
5 │ }
10+
11+
This pattern matches values of type string
12+
but a pattern was expected which matches values of type int
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/dict_pattern_inference_constrained.res:4:27-30
4+
5+
2 ┆ switch dict {
6+
3 ┆ | dict{"one": 1} =>
7+
4 ┆ let _: dict<string> = dict
8+
5 ┆ Js.log("one")
9+
6 ┆ | _ => Js.log("not one")
10+
11+
This has type: dict<int>
12+
But it's expected to have type: dict<string>
13+
14+
The incompatible parts:
15+
int vs string
16+
17+
You can convert int to string with Belt.Int.toString.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/dict_pattern_regular_record.res:5:5-22
4+
5+
3 │ let constrainedAsDict = (dict: x) =>
6+
4 │ switch dict {
7+
5 │ | dict{"one": "one"} => Js.log("one")
8+
6 │ | _ => Js.log("not one")
9+
7 │ }
10+
11+
This pattern matches values of type dict<string>
12+
but a pattern was expected which matches values of type x
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/dict_record_style_field_access.res:5:20-23
4+
5+
3 │ }
6+
4 │
7+
5 │ let x = stringDict.name
8+
9+
Direct field access on a dict is not supported. Use Dict.get instead.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let dict = Js.Dict.empty()
2+
dict->Js.Dict.set("someKey1", 1)
3+
dict->Js.Dict.set("someKey2", 2)
4+
5+
type fakeDict<'t> = {dictValuesType?: 't}
6+
7+
let d = (dict :> fakeDict<int>)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
type fakeDict<'t> = {dictValuesType?: 't}
2+
3+
let foo = (fakeDict: fakeDict<'a>) => {
4+
switch fakeDict {
5+
| {someUndefinedField: 1} => Js.log("one")
6+
| _ => Js.log("not one")
7+
}
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
let foo = dict =>
2+
switch dict {
3+
| dict{"one": 1, "two": "hello"} => Js.log("one")
4+
| _ => Js.log("not one")
5+
}

0 commit comments

Comments
 (0)