Skip to content

Commit f8c108f

Browse files
authored
Error for braced single identifier to record (#7806)
* dedicated error for braced ident where record is expected * tests * changelog * cleanup * message order + trim * skip potentially expensive check
1 parent bfec8a2 commit f8c108f

11 files changed

+98
-10
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
- Make parser less strict around leading attributes. https://github.com/rescript-lang/rescript/pull/7787
3434
- Dedicated error message for ternary type mismatch. https://github.com/rescript-lang/rescript/pull/7804
35+
- Dedicated error message for passing a braced ident to something expected to be a record. https://github.com/rescript-lang/rescript/pull/7806
3536

3637
#### :house: Internal
3738

compiler/ml/error_message_utils.ml

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ end = struct
6363
match parse_expr_at_loc loc with
6464
| Some (exp, comments) -> (
6565
match mapper exp with
66-
| Some exp -> Some (!reprint_source (wrap_in_structure exp) comments)
66+
| Some exp ->
67+
Some (!reprint_source (wrap_in_structure exp) comments |> String.trim)
6768
| None -> None)
6869
| None -> None
6970
end
@@ -105,6 +106,7 @@ type type_clash_context =
105106
is_constant: string option;
106107
}
107108
| FunctionArgument of {optional: bool; name: string option}
109+
| BracedIdent
108110
| Statement of type_clash_statement
109111
| ForLoopCondition
110112
| Await
@@ -128,6 +130,7 @@ let context_to_string = function
128130
| Some IfReturn -> "IfReturn"
129131
| Some TernaryReturn -> "TernaryReturn"
130132
| Some Await -> "Await"
133+
| Some BracedIdent -> "BracedIdent"
131134
| None -> "None"
132135
133136
let fprintf = Format.fprintf
@@ -198,7 +201,7 @@ let error_expected_type_text ppf type_clash_context =
198201
fprintf ppf
199202
"But you're using @{<info>await@} on this expression, so it is expected \
200203
to be of type:"
201-
| Some MaybeUnwrapOption | None ->
204+
| Some MaybeUnwrapOption | Some BracedIdent | None ->
202205
fprintf ppf "But it's expected to have type:"
203206
204207
let is_record_type ~(extract_concrete_typedecl : extract_concrete_typedecl) ~env
@@ -546,6 +549,35 @@ let print_extra_type_clash_help ~extract_concrete_typedecl ~env loc ppf
546549
with @{<info>?@} to show you want to pass the option, like: \
547550
@{<info>?%s@}"
548551
(Parser.extract_text_at_loc loc)
552+
| Some BracedIdent, Some (_, ({desc = Tconstr (_, _, _)} as t))
553+
when is_record_type ~extract_concrete_typedecl ~env t ->
554+
fprintf ppf
555+
"@,\
556+
@,\
557+
You might have meant to pass this as a record, but wrote it as a block.@,\
558+
Braces with a single identifier counts as a block, not a record with a \
559+
single (punned) field.@,\
560+
@,\
561+
Possible solutions: @,\
562+
- Write out the full record with field and value, like: @{<info>%s@}@,\
563+
- Return the expected record from the block"
564+
(match
565+
Parser.reprint_expr_at_loc
566+
~mapper:(fun e ->
567+
match e.pexp_desc with
568+
| Pexp_ident {txt} ->
569+
Some
570+
{
571+
e with
572+
pexp_desc =
573+
Pexp_record
574+
([{lid = Location.mknoloc txt; opt = false; x = e}], None);
575+
}
576+
| _ -> None)
577+
loc
578+
with
579+
| None -> ""
580+
| Some s -> s)
549581
| _, Some ({Types.desc = Tconstr (p1, _, _)}, {desc = Tvariant row_desc})
550582
when Path.same Predef.path_string p1 -> (
551583
(* Check if we have a string constant that could be a polymorphic variant constructor *)

compiler/ml/typecore.ml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2256,6 +2256,17 @@ let rec type_exp ~context ?recarg env sexp =
22562256
*)
22572257
22582258
and type_expect ~context ?in_function ?recarg env sexp ty_expected =
2259+
(* Special errors for braced identifiers passed to records *)
2260+
let context =
2261+
match sexp.pexp_desc with
2262+
| Pexp_ident _ ->
2263+
if
2264+
sexp.pexp_attributes
2265+
|> List.exists (fun (attr, _) -> attr.txt = "res.braces")
2266+
then Some Error_message_utils.BracedIdent
2267+
else context
2268+
| _ -> context
2269+
in
22592270
let previous_saved_types = Cmt_format.get_saved_types () in
22602271
let exp =
22612272
Builtin_attributes.warning_scope sexp.pexp_attributes (fun () ->

tests/build_tests/super_errors/expected/array_literal_passed_to_tuple.res.expected

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,4 @@
1010
This has type: array<'a>
1111
But it's expected to have type: (string, string)
1212

13-
- Fix this by passing a tuple instead of an array, like: ("hello", "world")
14-

13+
- Fix this by passing a tuple instead of an array, like: ("hello", "world")

tests/build_tests/super_errors/expected/object_literal_passed_when_record_expected.res.expected

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,4 @@
1313
You're passing a ReScript object where a record is expected. Objects are written with quoted keys, and records with unquoted keys.
1414

1515
Possible solutions:
16-
- Rewrite the object to a record, like: {one: true}
17-

16+
- Rewrite the object to a record, like: {one: true}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/pass_braced_identifier_to_record.res:4:15-25
4+
5+
2 │ let householdId = "12"
6+
3 │
7+
4 │ let ff: xx = {householdId}
8+
5 │
9+
10+
This has type: string
11+
But it's expected to have type: xx
12+
13+
You might have meant to pass this as a record, but wrote it as a block.
14+
Braces with a single identifier counts as a block, not a record with a single (punned) field.
15+
16+
Possible solutions:
17+
- Write out the full record with field and value, like: {householdId: householdId}
18+
- Return the expected record from the block
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/pass_braced_identifier_to_record_fn_argument.res:8:15-25
4+
5+
6 │
6+
7 │ let householdId = "12"
7+
8 │ let ff = doX({householdId})
8+
9 │
9+
10+
This has type: string
11+
But it's expected to have type: xx
12+
13+
You might have meant to pass this as a record, but wrote it as a block.
14+
Braces with a single identifier counts as a block, not a record with a single (punned) field.
15+
16+
Possible solutions:
17+
- Write out the full record with field and value, like: {householdId: householdId}
18+
- Return the expected record from the block

tests/build_tests/super_errors/expected/string_constant_to_polyvariant.res.expected

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,4 @@
1111
But this function argument is expecting: [#ONE | #TWO]
1212

1313
Possible solutions:
14-
- The constant passed matches one of the expected polymorphic variant constructors. Did you mean to pass this as a polymorphic variant? If so, rewrite "ONE" to #ONE
15-

14+
- The constant passed matches one of the expected polymorphic variant constructors. Did you mean to pass this as a polymorphic variant? If so, rewrite "ONE" to #ONE

tests/build_tests/super_errors/expected/string_constant_to_variant.res.expected

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,4 @@
1111
But this function argument is expecting: status
1212

1313
Possible solutions:
14-
- The constant passed matches the runtime representation of one of the expected variant constructors. Did you mean to pass this as a variant constructor? If so, rewrite "Active" to Active
15-

14+
- The constant passed matches the runtime representation of one of the expected variant constructors. Did you mean to pass this as a variant constructor? If so, rewrite "Active" to Active
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
type xx = {householdId: string}
2+
let householdId = "12"
3+
4+
let ff: xx = {householdId}

0 commit comments

Comments
 (0)