diff --git a/CHANGELOG.md b/CHANGELOG.md index b76174e1cc..4e6d03896f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ # 12.0.0-beta.4 (Unreleased) +#### :nail_care: Polish + +- Apply heuristic to suggest using JSX fragments where we guess that might be what the user wanted. https://github.com/rescript-lang/rescript/pull/7714 + # 12.0.0-beta.3 #### :boom: Breaking Change @@ -50,7 +54,6 @@ - Remove all leftovers of `pinned-dependencies` handling. https://github.com/rescript-lang/rescript/pull/7686 - Add `rust-version` field to Rewatch's `Cargo.toml`. https://github.com/rescript-lang/rescript/pull/7701 - # 12.0.0-beta.2 #### :boom: Breaking Change diff --git a/compiler/ml/error_message_utils.ml b/compiler/ml/error_message_utils.ml index 46be9e727f..1e969887f0 100644 --- a/compiler/ml/error_message_utils.ml +++ b/compiler/ml/error_message_utils.ml @@ -353,14 +353,29 @@ let print_extra_type_clash_help ~extract_concrete_typedecl ~env loc ppf -> fprintf ppf "@,@,Dicts are written like: @{dict{\"a\": 1, \"b\": 2}@}@," - | _, Some ({Types.desc = Tconstr (_p1, _, _)}, {desc = Tconstr (p2, _, _)}) + | ( _, + Some + (({Types.desc = Tconstr (_p1, _, _)} as ty), {desc = Tconstr (p2, _, _)}) + ) when Path.same Predef.path_unit p2 -> - fprintf ppf - "\n\n\ - \ - Did you mean to assign this to a variable?\n\ - \ - 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" + fprintf ppf "\n\n"; + let is_jsx_element = + match Ctype.expand_head env ty with + | {desc = Tconstr (Pdot (Pident {name = "Jsx"}, "element", _), _, _)} -> + true + | _ -> false + in + if is_jsx_element then + fprintf ppf + " - Did you forget to wrap this + adjacent JSX in a JSX fragment \ + (@{<>@})?\n\ + \ - Did you mean to assign this to a variable?\n\n" + else + fprintf ppf + " - Did you mean to assign this to a variable?\n\ + \ - 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" | _, Some ({desc = Tobject _}, ({Types.desc = Tconstr _} as t1)) when is_record_type ~extract_concrete_typedecl ~env t1 -> fprintf ppf @@ -714,7 +729,7 @@ let type_clash_context_maybe_option ty_expected ty_res = let type_clash_context_in_statement sexp = match sexp.Parsetree.pexp_desc with - | Pexp_apply _ -> Some (Statement FunctionCall) + | Pexp_apply {transformed_jsx = false} -> Some (Statement FunctionCall) | _ -> None let print_contextual_unification_error ppf t1 t2 = diff --git a/tests/build_tests/super_errors/expected/jsx_maybe_missing_fragment.res.expected b/tests/build_tests/super_errors/expected/jsx_maybe_missing_fragment.res.expected new file mode 100644 index 0000000000..a21dfbd5b9 --- /dev/null +++ b/tests/build_tests/super_errors/expected/jsx_maybe_missing_fragment.res.expected @@ -0,0 +1,15 @@ + + We've found a bug for you! + /.../fixtures/jsx_maybe_missing_fragment.res:18:3-8 + + 16 │ + 17 │ let x = { + 18 │ <>  + 19 │ <> + 20 │ } + + This has type: React.element (defined as Jsx.element) + But it's expected to have type: unit + + - Did you forget to wrap this + adjacent JSX in a JSX fragment (<>)? + - Did you mean to assign this to a variable? \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/jsx_maybe_missing_fragment.res b/tests/build_tests/super_errors/fixtures/jsx_maybe_missing_fragment.res new file mode 100644 index 0000000000..b0c101a4f0 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/jsx_maybe_missing_fragment.res @@ -0,0 +1,20 @@ +@@config({ + flags: ["-bs-jsx", "4"], +}) + +module React = { + type element = Jsx.element + type componentLike<'props, 'return> = 'props => 'return + type component<'props> = Jsx.component<'props> + + @module("react/jsx-runtime") + external jsx: (component<'props>, 'props) => element = "jsx" + + type fragmentProps = {children?: element} + @module("react/jsx-runtime") external jsxFragment: component = "Fragment" +} + +let x = { + <> + <> +}