From e050527f7509b9f66fe61ca3913417061326ca31 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sun, 13 Oct 2024 19:12:53 +0200 Subject: [PATCH] Improve error message when an object is passed where a record is expected (#7101) * improve error message when an object is passed where a record is expected * changelog --- CHANGELOG.md | 1 + compiler/ml/error_message_utils.ml | 19 ++++++++++++++++++- compiler/ml/typecore.ml | 3 ++- ...t_passed_when_record_expected.res.expected | 15 +++++++++++++++ .../object_passed_when_record_expected.res | 4 ++++ 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 tests/build_tests/super_errors/expected/object_passed_when_record_expected.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/object_passed_when_record_expected.res diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d9af08b53..a1fb07c761 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ - Improve error messages around JSX components. https://github.com/rescript-lang/rescript-compiler/pull/7038 - Improve output of record copying. https://github.com/rescript-lang/rescript-compiler/pull/7043 - Provide additional context in error message when `unit` is expected. https://github.com/rescript-lang/rescript-compiler/pull/7045 +- Improve error message when passing an object where a record is expected. https://github.com/rescript-lang/rescript-compiler/pull/7101 #### :house: Internal diff --git a/compiler/ml/error_message_utils.ml b/compiler/ml/error_message_utils.ml index 27768a77ec..4df379693f 100644 --- a/compiler/ml/error_message_utils.ml +++ b/compiler/ml/error_message_utils.ml @@ -57,7 +57,15 @@ let error_expected_type_text ppf type_clash_context = fprintf ppf "But this function is expecting you to return:" | _ -> fprintf ppf "But it's expected to have type:" -let print_extra_type_clash_help ppf trace type_clash_context = +let is_record_type ~extract_concrete_typedecl ~env ty = + try + match extract_concrete_typedecl env ty with + | _, _, {Types.type_kind = Type_record _; _} -> true + | _ -> false + with _ -> false + +let print_extra_type_clash_help ~extract_concrete_typedecl ~env ppf trace + type_clash_context = match (type_clash_context, trace) with | Some (MathOperator {for_float; operator; is_constant}), _ -> ( let operator_for_other_type = @@ -176,6 +184,15 @@ let print_extra_type_clash_help ppf trace type_clash_context = \ - If you don't care about the result of this expression, you can \ assign it to @{_@} via @{let _ = ...@} or pipe it to \ @{ignore@} via @{expression->ignore@}\n\n" + | _, [({desc = Tobject _}, _); (({Types.desc = Tconstr _} as t1), _)] + when is_record_type ~extract_concrete_typedecl ~env t1 -> + fprintf ppf + "\n\n\ + \ You're passing a @{ReScript object@} where a @{record@} \ + is expected. \n\n\ + \ - Did you mean to pass a record instead of an object? Objects are \ + written with quoted keys, and records with unquoted keys. Remove the \ + quotes from the object keys to pass it as a record instead of object. \n\n" | _ -> () let type_clash_context_from_function sexp sfunct = diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 3499b5f7e5..f4334ba9bb 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -814,7 +814,8 @@ let print_expr_type_clash ?type_clash_context env trace ppf = | ppf -> error_type_text ppf type_clash_context) (function | ppf -> error_expected_type_text ppf type_clash_context); - print_extra_type_clash_help ppf trace type_clash_context; + print_extra_type_clash_help ~extract_concrete_typedecl ~env ppf trace + type_clash_context; show_extra_help ppf env trace let report_arity_mismatch ~arity_a ~arity_b ppf = diff --git a/tests/build_tests/super_errors/expected/object_passed_when_record_expected.res.expected b/tests/build_tests/super_errors/expected/object_passed_when_record_expected.res.expected new file mode 100644 index 0000000000..be09067202 --- /dev/null +++ b/tests/build_tests/super_errors/expected/object_passed_when_record_expected.res.expected @@ -0,0 +1,15 @@ + + We've found a bug for you! + /.../fixtures/object_passed_when_record_expected.res:4:14-26 + + 2 │ type xx = array + 3 │ + 4 │ let x: xx = [{"one": true}] + 5 │ + + This has type: {"one": bool} + But it's expected to have type: x + + You're passing a ReScript object where a record is expected. + + - Did you mean to pass a record instead of an object? Objects are written with quoted keys, and records with unquoted keys. Remove the quotes from the object keys to pass it as a record instead of object. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/object_passed_when_record_expected.res b/tests/build_tests/super_errors/fixtures/object_passed_when_record_expected.res new file mode 100644 index 0000000000..1451c3f3d8 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/object_passed_when_record_expected.res @@ -0,0 +1,4 @@ +type x = {one: bool} +type xx = array + +let x: xx = [{"one": true}]