Skip to content

Commit f04e3b3

Browse files
authored
improve error message for trying to do dot access on option/array (#7732)
* improve error message for trying to do dot access for a record field on something that is an option/array * changelog * update text
1 parent 118294a commit f04e3b3

File tree

8 files changed

+111
-24
lines changed

8 files changed

+111
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- Show deprecation warnings for `bs-dependencies` etc. for local dependencies only. https://github.com/rescript-lang/rescript/pull/7724
1919
- Add check for minimum required node version. https://github.com/rescript-lang/rescript/pull/7723
2020
- Use more optional args in stdlib and deprecate some functions. https://github.com/rescript-lang/rescript/pull/7730
21+
- Improve error message for when trying to do dot access on an option/array. https://github.com/rescript-lang/rescript/pull/7732
2122

2223
#### :bug: Bug fix
2324

compiler/ml/typecore.ml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -822,7 +822,8 @@ module NameChoice (Name : sig
822822
val get_descrs : Env.type_descriptions -> t list
823823

824824
val unsafe_do_not_use__add_with_name : t -> string -> t
825-
val unbound_name_error : Env.t -> Longident.t loc -> 'a
825+
val unbound_name_error :
826+
?from_type:type_expr -> Env.t -> Longident.t loc -> 'a
826827
end) =
827828
struct
828829
open Name
@@ -881,7 +882,7 @@ struct
881882
List.find check_type lbls
882883

883884
let disambiguate ?(warn = Location.prerr_warning) ?(check_lk = fun _ _ -> ())
884-
?scope lid env opath lbls =
885+
?(from_type : Types.type_expr option) ?scope lid env opath lbls =
885886
let scope =
886887
match scope with
887888
| None -> lbls
@@ -891,7 +892,7 @@ struct
891892
match opath with
892893
| None -> (
893894
match lbls with
894-
| [] -> unbound_name_error env lid
895+
| [] -> unbound_name_error ?from_type env lid
895896
| (lbl, use) :: rest ->
896897
use ();
897898
let paths = ambiguous_types env lbl rest in
@@ -910,7 +911,7 @@ struct
910911
check_lk tpath lbl;
911912
lbl
912913
with Not_found ->
913-
if lbls = [] then unbound_name_error env lid
914+
if lbls = [] then unbound_name_error ?from_type env lid
914915
else
915916
let tp = (tpath0, expand_path env tpath) in
916917
let tpl =
@@ -3311,7 +3312,7 @@ and type_label_access env srecord lid =
33113312
let labels = Typetexp.find_all_labels env lid.loc lid.txt in
33123313
let label =
33133314
wrap_disambiguate "This expression has" ty_exp
3314-
(Label.disambiguate lid env opath)
3315+
(Label.disambiguate ~from_type:ty_exp lid env opath)
33153316
labels
33163317
in
33173318
(record, label, opath)

compiler/ml/typetexp.ml

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ type error =
4343
| Method_mismatch of string * type_expr * type_expr
4444
| Unbound_value of Longident.t
4545
| Unbound_constructor of Longident.t
46-
| Unbound_label of Longident.t
46+
| Unbound_label of Longident.t * type_expr option
4747
| Unbound_module of Longident.t
4848
| Unbound_modtype of Longident.t
4949
| Ill_typed_functor_application of Longident.t
@@ -129,7 +129,7 @@ let find_all_constructors =
129129
find_component Env.lookup_all_constructors (fun lid ->
130130
Unbound_constructor lid)
131131
let find_all_labels =
132-
find_component Env.lookup_all_labels (fun lid -> Unbound_label lid)
132+
find_component Env.lookup_all_labels (fun lid -> Unbound_label (lid, None))
133133

134134
let find_value env loc lid =
135135
Env.check_value_name (Longident.last lid) loc;
@@ -160,12 +160,14 @@ let find_modtype env loc lid =
160160
Builtin_attributes.check_deprecated loc decl.mtd_attributes (Path.name path);
161161
r
162162

163-
let unbound_constructor_error env lid =
163+
let unbound_constructor_error ?from_type env lid =
164+
ignore from_type;
164165
narrow_unbound_lid_error env lid.loc lid.txt (fun lid ->
165166
Unbound_constructor lid)
166167

167-
let unbound_label_error env lid =
168-
narrow_unbound_lid_error env lid.loc lid.txt (fun lid -> Unbound_label lid)
168+
let unbound_label_error ?from_type env lid =
169+
narrow_unbound_lid_error env lid.loc lid.txt (fun lid ->
170+
Unbound_label (lid, from_type))
169171

170172
(* Support for first-class modules. *)
171173

@@ -909,18 +911,49 @@ let report_error env ppf = function
909911
= Bar@}.@]@]"
910912
Printtyp.longident lid Printtyp.longident lid Printtyp.longident lid;
911913
spellcheck ppf fold_constructors env lid
912-
| Unbound_label lid ->
914+
| Unbound_label (lid, from_type) ->
913915
(* modified *)
914-
Format.fprintf ppf
915-
"@[<v>@{<info>%a@} refers to a record field, but no corresponding record \
916-
type is in scope.@,\
917-
@,\
918-
If it's defined in another module or file, bring it into scope by:@,\
919-
@[- Prefixing the field name with the module name:@ \
920-
@{<info>TheModule.%a@}@]@,\
921-
@[- Or specifying the record type explicitly:@ @{<info>let theValue: \
922-
TheModule.theType = {%a: VALUE}@}@]@]"
923-
Printtyp.longident lid Printtyp.longident lid Printtyp.longident lid;
916+
(match from_type with
917+
| Some {desc = Tconstr (p, _, _)} when Path.same p Predef.path_option ->
918+
(* TODO: Extend for nullable/null? *)
919+
Format.fprintf ppf
920+
"@[<v>You're trying to access the record field @{<info>%a@}, but the \
921+
value you're trying to access it on is an @{<info>option@}.@ You need \
922+
to unwrap the option first before accessing the record field.@,\
923+
@\n\
924+
Possible solutions:@,\
925+
@[- Use @{<info>Option.map@} to transform the option: \
926+
@{<info>xx->Option.map(field => field.%a)@}@]@,\
927+
@[- Or use @{<info>Option.getOr@} with a default: \
928+
@{<info>xx->Option.getOr(defaultRecord).%a@}@]@]"
929+
Printtyp.longident lid Printtyp.longident lid Printtyp.longident lid
930+
| Some {desc = Tconstr (p, _, _)} when Path.same p Predef.path_array ->
931+
Format.fprintf ppf
932+
"@[<v>You're trying to access the record field @{<info>%a@}, but the \
933+
value you're trying to access it on is an @{<info>array@}.@ You need \
934+
to access an individual element of the array if you want to access an \
935+
individual record field.@]"
936+
Printtyp.longident lid
937+
| Some ({desc = Tconstr (_p, _, _)} as t1) ->
938+
Format.fprintf ppf
939+
"@[<v>You're trying to access the record field @{<info>%a@}, but the \
940+
thing you're trying to access it on is not a record. @,\n\
941+
The type of the thing you're trying to access it on is:@,\n\
942+
%a@,\n\
943+
@,\
944+
Only records have fields that can be accessed with dot notation.@]"
945+
Printtyp.longident lid Error_message_utils.type_expr t1
946+
| None | Some _ ->
947+
Format.fprintf ppf
948+
"@[<v>@{<info>%a@} refers to a record field, but no corresponding \
949+
record type is in scope.@,\
950+
@,\
951+
If it's defined in another module or file, bring it into scope by:@,\
952+
@[- Prefixing the field name with the module name:@ \
953+
@{<info>TheModule.%a@}@]@,\
954+
@[- Or specifying the record type explicitly:@ @{<info>let theValue: \
955+
TheModule.theType = {%a: VALUE}@}@]@]"
956+
Printtyp.longident lid Printtyp.longident lid Printtyp.longident lid);
924957
spellcheck ppf fold_labels env lid
925958
| Unbound_modtype lid ->
926959
fprintf ppf "Unbound module type %a" longident lid;

compiler/ml/typetexp.mli

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ type error =
5252
| Method_mismatch of string * type_expr * type_expr
5353
| Unbound_value of Longident.t
5454
| Unbound_constructor of Longident.t
55-
| Unbound_label of Longident.t
55+
| Unbound_label of Longident.t * type_expr option
5656
| Unbound_module of Longident.t
5757
| Unbound_modtype of Longident.t
5858
| Ill_typed_functor_application of Longident.t
@@ -96,5 +96,7 @@ val lookup_module : ?load:bool -> Env.t -> Location.t -> Longident.t -> Path.t
9696
val find_modtype :
9797
Env.t -> Location.t -> Longident.t -> Path.t * modtype_declaration
9898

99-
val unbound_constructor_error : Env.t -> Longident.t Location.loc -> 'a
100-
val unbound_label_error : Env.t -> Longident.t Location.loc -> 'a
99+
val unbound_constructor_error :
100+
?from_type:type_expr -> Env.t -> Longident.t Location.loc -> 'a
101+
val unbound_label_error :
102+
?from_type:type_expr -> Env.t -> Longident.t Location.loc -> 'a
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/access_record_field_on_array.res:12:15
4+
5+
10 │ }
6+
11 │
7+
12 │ let f = X.x.c.d
8+
13 │
9+
10+
You're trying to access the record field d, but the value you're trying to access it on is an array.
11+
You need to access an individual element of the array if you want to access an individual record field.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/access_record_field_on_option.res:12:15
4+
5+
10 │ }
6+
11 │
7+
12 │ let f = X.x.c.d
8+
13 │
9+
10+
You're trying to access the record field d, but the value you're trying to access it on is an option.
11+
You need to unwrap the option first before accessing the record field.
12+
13+
Possible solutions:
14+
- Use Option.map to transform the option: xx->Option.map(field => field.d)
15+
- Or use Option.getOr with a default: xx->Option.getOr(defaultRecord).d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module X = {
2+
type y = {d: int}
3+
type x = {
4+
a: int,
5+
b: int,
6+
c: array<y>,
7+
}
8+
9+
let x = {a: 1, b: 2, c: [{d: 3}]}
10+
}
11+
12+
let f = X.x.c.d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module X = {
2+
type y = {d: int}
3+
type x = {
4+
a: int,
5+
b: int,
6+
c: option<y>,
7+
}
8+
9+
let x = {a: 1, b: 2, c: Some({d: 3})}
10+
}
11+
12+
let f = X.x.c.d

0 commit comments

Comments
 (0)