Skip to content

Commit 3f31581

Browse files
committed
add action for automatically unwrapping record field access through option
1 parent 37d0d90 commit 3f31581

File tree

4 files changed

+82
-12
lines changed

4 files changed

+82
-12
lines changed

compiler/ml/cmt_utils.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type action_type =
5555
| PartiallyApplyFunction
5656
| InsertMissingArguments of {missing_args: Asttypes.Noloc.arg_label list}
5757
| ChangeRecordFieldOptional of {optional: bool}
58+
| UnwrapOptionMapRecordField of {field_name: Longident.t}
5859

5960
(* TODO:
6061
- Unused var in patterns (and aliases )*)
@@ -112,6 +113,9 @@ let action_to_string = function
112113
| ChangeRecordFieldOptional {optional} ->
113114
Printf.sprintf "ChangeRecordFieldOptional(%s)"
114115
(if optional then "true" else "false")
116+
| UnwrapOptionMapRecordField {field_name} ->
117+
Printf.sprintf "UnwrapOptionMapRecordField(%s)"
118+
(Longident.flatten field_name |> String.concat ".")
115119

116120
let _add_possible_action : (cmt_action -> unit) ref = ref (fun _ -> ())
117121
let add_possible_action action = !_add_possible_action action

compiler/ml/typetexp.ml

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ type error =
4343
| Method_mismatch of string * type_expr * type_expr
4444
| Unbound_value of Longident.t * Location.t
4545
| Unbound_constructor of Longident.t
46-
| Unbound_label of Longident.t * type_expr option
46+
| Unbound_label of {
47+
loc: Location.t;
48+
field_name: Longident.t;
49+
from_type: type_expr option;
50+
}
4751
| Unbound_module of Longident.t
4852
| Unbound_modtype of Longident.t
4953
| Ill_typed_functor_application of Longident.t
@@ -129,8 +133,10 @@ let find_constructor =
129133
let find_all_constructors =
130134
find_component Env.lookup_all_constructors (fun lid ->
131135
Unbound_constructor lid)
132-
let find_all_labels =
133-
find_component Env.lookup_all_labels (fun lid -> Unbound_label (lid, None))
136+
let find_all_labels env loc =
137+
find_component Env.lookup_all_labels
138+
(fun lid -> Unbound_label {loc; field_name = lid; from_type = None})
139+
env loc
134140

135141
let find_value ?deprecated_context env loc lid =
136142
Env.check_value_name (Longident.last lid) loc;
@@ -170,8 +176,9 @@ let unbound_constructor_error ?from_type env lid =
170176
Unbound_constructor lid)
171177

172178
let unbound_label_error ?from_type env lid =
179+
let lid_with_loc = lid in
173180
narrow_unbound_lid_error env lid.loc lid.txt (fun lid ->
174-
Unbound_label (lid, from_type))
181+
Unbound_label {loc = lid_with_loc.loc; field_name = lid; from_type})
175182

176183
(* Support for first-class modules. *)
177184

@@ -938,10 +945,17 @@ let report_error env ppf = function
938945
= Bar@}.@]@]"
939946
Printtyp.longident lid Printtyp.longident lid Printtyp.longident lid;
940947
spellcheck ppf fold_constructors env lid
941-
| Unbound_label (lid, from_type) ->
948+
| Unbound_label {loc; field_name; from_type} ->
942949
(* modified *)
943950
(match from_type with
944951
| Some {desc = Tconstr (p, _, _)} when Path.same p Predef.path_option ->
952+
Cmt_utils.add_possible_action
953+
{
954+
loc;
955+
action = UnwrapOptionMapRecordField {field_name};
956+
description =
957+
"Unwrap the option first before accessing the record field";
958+
};
945959
(* TODO: Extend for nullable/null? *)
946960
Format.fprintf ppf
947961
"@[<v>You're trying to access the record field @{<info>%a@}, but the \
@@ -953,14 +967,15 @@ let report_error env ppf = function
953967
@{<info>xx->Option.map(field => field.%a)@}@]@,\
954968
@[- Or use @{<info>Option.getOr@} with a default: \
955969
@{<info>xx->Option.getOr(defaultRecord).%a@}@]@]"
956-
Printtyp.longident lid Printtyp.longident lid Printtyp.longident lid
970+
Printtyp.longident field_name Printtyp.longident field_name
971+
Printtyp.longident field_name
957972
| Some {desc = Tconstr (p, _, _)} when Path.same p Predef.path_array ->
958973
Format.fprintf ppf
959974
"@[<v>You're trying to access the record field @{<info>%a@}, but the \
960975
value you're trying to access it on is an @{<info>array@}.@ You need \
961976
to access an individual element of the array if you want to access an \
962977
individual record field.@]"
963-
Printtyp.longident lid
978+
Printtyp.longident field_name
964979
| Some ({desc = Tconstr (_p, _, _)} as t1) ->
965980
Format.fprintf ppf
966981
"@[<v>You're trying to access the record field @{<info>%a@}, but the \
@@ -969,7 +984,7 @@ let report_error env ppf = function
969984
%a@,\n\
970985
@,\
971986
Only records have fields that can be accessed with dot notation.@]"
972-
Printtyp.longident lid Error_message_utils.type_expr t1
987+
Printtyp.longident field_name Error_message_utils.type_expr t1
973988
| None | Some _ ->
974989
Format.fprintf ppf
975990
"@[<v>@{<info>%a@} refers to a record field, but no corresponding \
@@ -980,8 +995,9 @@ let report_error env ppf = function
980995
@{<info>TheModule.%a@}@]@,\
981996
@[- Or specifying the record type explicitly:@ @{<info>let theValue: \
982997
TheModule.theType = {%a: VALUE}@}@]@]"
983-
Printtyp.longident lid Printtyp.longident lid Printtyp.longident lid);
984-
spellcheck ppf fold_labels env lid
998+
Printtyp.longident field_name Printtyp.longident field_name
999+
Printtyp.longident field_name);
1000+
spellcheck ppf fold_labels env field_name
9851001
| Unbound_modtype lid ->
9861002
fprintf ppf "Unbound module type %a" longident lid;
9871003
spellcheck ppf fold_modtypes env lid

compiler/ml/typetexp.mli

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@ type error =
5252
| Method_mismatch of string * type_expr * type_expr
5353
| Unbound_value of Longident.t * Location.t
5454
| Unbound_constructor of Longident.t
55-
| Unbound_label of Longident.t * type_expr option
55+
| Unbound_label of {
56+
loc: Location.t;
57+
field_name: Longident.t;
58+
from_type: type_expr option;
59+
}
5660
| Unbound_module of Longident.t
5761
| Unbound_modtype of Longident.t
5862
| Ill_typed_functor_application of Longident.t

tools/src/tools.ml

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,24 @@ module ExtractCodeblocks = struct
12941294
end
12951295

12961296
module Migrate = Migrate
1297+
module TemplateUtils = struct
1298+
let get_expr source =
1299+
let {Res_driver.parsetree; invalid} =
1300+
Res_driver.parse_implementation_from_source ~for_printer:true
1301+
~display_filename:"<generated>" ~source
1302+
in
1303+
if invalid then Error "Could not parse expression"
1304+
else
1305+
match parsetree with
1306+
| [{pstr_desc = Pstr_eval (e, _)}] -> Ok e
1307+
| _ -> Error "Expected a record expression"
1308+
1309+
let get_expr_exn source =
1310+
match get_expr source with
1311+
| Ok e -> e
1312+
| Error e -> failwith e
1313+
end
1314+
12971315
module Actions = struct
12981316
let change_record_field_optional (record_el : _ Parsetree.record_element)
12991317
target_loc actions =
@@ -1636,6 +1654,32 @@ module Actions = struct
16361654
else
16371655
(* Other cases when the loc is on something else in the expr *)
16381656
match (expr.pexp_desc, action.action) with
1657+
| ( Pexp_field (e, {loc}),
1658+
UnwrapOptionMapRecordField {field_name} )
1659+
when action.loc = loc ->
1660+
Some
1661+
{
1662+
expr with
1663+
pexp_desc =
1664+
Pexp_apply
1665+
{
1666+
funct =
1667+
Ast_helper.Exp.ident
1668+
(Location.mknoloc (Longident.Lident "->"));
1669+
partial = false;
1670+
transformed_jsx = false;
1671+
args =
1672+
[
1673+
(Nolabel, e);
1674+
( Nolabel,
1675+
TemplateUtils.get_expr_exn
1676+
(Printf.sprintf
1677+
"Option.map(v => v.%s)"
1678+
(Longident.flatten field_name
1679+
|> String.concat ".")) );
1680+
];
1681+
};
1682+
}
16391683
| ( Pexp_apply ({funct; args} as apply),
16401684
InsertMissingArguments {missing_args} )
16411685
when funct.pexp_loc = action.loc ->
@@ -1834,7 +1878,9 @@ module Actions = struct
18341878
| InsertMissingArguments _ ->
18351879
List.mem "InsertMissingArguments" filter
18361880
| ChangeRecordFieldOptional _ ->
1837-
List.mem "ChangeRecordFieldOptional" filter)
1881+
List.mem "ChangeRecordFieldOptional" filter
1882+
| UnwrapOptionMapRecordField _ ->
1883+
List.mem "UnwrapOptionMapRecordField" filter)
18381884
in
18391885
match applyActionsToFile path possible_actions with
18401886
| Ok applied ->

0 commit comments

Comments
 (0)