Skip to content

Commit bd2abd1

Browse files
committed
add some tests, and disallow direct record field access on dicts
1 parent 8f555d1 commit bd2abd1

File tree

11 files changed

+99
-11
lines changed

11 files changed

+99
-11
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
Warning number 9
3+
/.../fixtures/dict_pattern_inference_constrained.res:3:22-29
4+
5+
1 │ let foo = dict =>
6+
2 │ switch dict {
7+
3 │ | @res.dictPattern {one: 1} =>
8+
4 │ let _: dict<string> = dict
9+
5 │ Js.log("one")
10+
11+
the following labels are not bound in this record pattern: anyOtherField
12+
Either bind these labels explicitly or add ', _' to the pattern.
13+
14+
15+
We've found a bug for you!
16+
/.../fixtures/dict_pattern_inference_constrained.res:4:27-30
17+
18+
2 ┆ switch dict {
19+
3 ┆ | @res.dictPattern {one: 1} =>
20+
4 ┆ let _: dict<string> = dict
21+
5 ┆ Js.log("one")
22+
6 ┆ | _ => Js.log("not one")
23+
24+
This has type: dict<int>
25+
But it's expected to have type: dict<string>
26+
27+
The incompatible parts:
28+
int vs string
29+
30+
You can convert int to string with Belt.Int.toString.
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 foo = dict =>
2+
switch dict {
3+
| @res.dictPattern {one: 1} =>
4+
let _: dict<string> = dict
5+
Js.log("one")
6+
| _ => Js.log("not one")
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
let stringDict = dict{
2+
"name": "hello",
3+
}
4+
5+
let x = stringDict.name

jscomp/ml/dicts.ml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,10 @@ let has_dict_pattern_attribute attrs =
22
attrs
33
|> List.find_opt (fun (({txt}, _) : Parsetree.attribute) ->
44
txt = "res.dictPattern")
5-
|> Option.is_some
5+
|> Option.is_some
6+
7+
let has_dict_attribute attrs =
8+
attrs
9+
|> List.find_opt (fun (({txt}, _) : Parsetree.attribute) ->
10+
txt = "res.dict")
11+
|> Option.is_some

jscomp/ml/predef.ml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,17 +221,18 @@ let common_initial_env add_type add_extension empty_env =
221221
and decl_dict =
222222
let tvar = newgenvar() in
223223
{decl_abstr with
224+
type_attributes = [(Location.mknoloc "res.dict", Parsetree.PStr [])];
224225
type_params = [tvar];
225226
type_arity = 1;
226227
type_variance = [Variance.full];
227-
type_kind =
228-
Type_record ([
229-
{ld_id = ident_anyOtherField;
228+
type_kind = Type_record ([{
229+
ld_id = ident_anyOtherField;
230230
ld_attributes = [(Location.mknoloc "res.optional", Parsetree.PStr [])];
231231
ld_loc = Location.none;
232232
ld_mutable = Immutable; (* TODO(dict-pattern-matching) Should probably be mutable? *)
233233
ld_type = newgenty (Tconstr (path_option, [tvar], ref Mnil));
234-
}], Record_optional_labels [Ident.name ident_anyOtherField]);
234+
}],
235+
Record_optional_labels [Ident.name ident_anyOtherField]);
235236
}
236237
and decl_uncurried =
237238
let tvar1, tvar2 = newgenvar(), newgenvar() in

jscomp/ml/typecore.ml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type error =
7575
| Uncurried_arity_mismatch of type_expr * int * int
7676
| Field_not_optional of string * type_expr
7777
| Type_params_not_supported of Longident.t
78+
| Field_access_on_dict_type
7879
exception Error of Location.t * Env.t * error
7980
exception Error_forward of Location.error
8081

@@ -3013,8 +3014,12 @@ and type_label_access env srecord lid =
30133014
let ty_exp = record.exp_type in
30143015
let opath =
30153016
try
3016-
let (p0, p, _, _) = extract_concrete_record env ty_exp in
3017-
Some(p0, p)
3017+
match extract_concrete_typedecl env ty_exp with
3018+
| (p0, _, {type_attributes})
3019+
when Path.same p0 Predef.path_dict && Dicts.has_dict_attribute type_attributes ->
3020+
raise(Error(lid.loc, env, Field_access_on_dict_type))
3021+
| (p0, p, {type_kind=Type_record _}) -> Some(p0, p)
3022+
| _ -> None
30183023
with Not_found -> None
30193024
in
30203025
let labels = Typetexp.find_all_labels env lid.loc lid.txt in
@@ -4131,6 +4136,8 @@ let report_error env ppf = function
41314136
type_expr typ
41324137
| Type_params_not_supported lid ->
41334138
fprintf ppf "The type %a@ has type parameters, but type parameters is not supported here." longident lid
4139+
| Field_access_on_dict_type ->
4140+
fprintf ppf "Direct field access on a dict is not supported. Use Dict.get instead."
41344141
41354142
41364143
let super_report_error_no_wrap_printing_env = report_error

jscomp/ml/typecore.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ type error =
108108
| Uncurried_arity_mismatch of type_expr * int * int
109109
| Field_not_optional of string * type_expr
110110
| Type_params_not_supported of Longident.t
111+
| Field_access_on_dict_type
111112
exception Error of Location.t * Env.t * error
112113
exception Error_forward of Location.error
113114

jscomp/test/DictInference.res

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ let dict = Js.Dict.empty()
22
dict->Js.Dict.set("someKey1", 1)
33
dict->Js.Dict.set("someKey2", 2)
44
let asArray = dict->Js.Dict.values
5+
6+
let _: dict<int> = dict

jscomp/test/DictTests.js

Lines changed: 12 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)