Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/ERROR_VARIANTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ Source: [typecore.ml:27](../compiler/ml/typecore.ml).
| `Or_pattern_type_clash` | ✓ | `or_pattern_type_clash.res` | |
| `Multiply_bound_variable` | ✓ | `multiply_bound_variable.res` | |
| `Orpat_vars` | ✓ | `orpat_vars_unbalanced.res` | |
| `Expr_type_clash` | ✓ | many `*.res` | Most-fired expression error. Trace-shape sub-cases covered: `if_return_type_mismatch.res` (IfReturn), `maybe_unwrap_option.res` (MaybeUnwrapOption), `string_concat_non_string.res` (StringConcat), `labeled_fn_argument_type_clash.res` (FunctionArgument with explicit label), `math_operator_*.res` (MathOperator family), `ternary_branch_mismatch.res`, `switch_different_types.res`, `try_catch_same_type.res`, `comparison_operator.res`, `array_item_type_mismatch.res`, `array_literal_passed_to_tuple.res`, `if_condition_mismatch.res`, `while_condition.res`, `for_loop_condition.res`, `assert_condition.res`, `function_call_mismatch.res`, `awaiting_non_promise.res`, multiple `jsx_*` fixtures, `object_literal_for_poly_field.res` (object literal against a polymorphic field annotation). |
| `Expr_type_clash` | ✓ | many `*.res` | Most-fired expression error. Trace-shape sub-cases covered: `if_return_type_mismatch.res` (IfReturn), `maybe_unwrap_option.res` (MaybeUnwrapOption), `string_concat_non_string.res` (StringConcat), `labeled_fn_argument_type_clash.res` (FunctionArgument with explicit label), `math_operator_*.res` (MathOperator family), `ternary_branch_mismatch.res`, `switch_different_types.res`, `try_catch_same_type.res`, `comparison_operator.res`, `array_item_type_mismatch.res`, `array_literal_passed_to_tuple.res`, `if_condition_mismatch.res`, `while_condition.res`, `for_loop_condition.res`, `assert_condition.res`, `function_call_mismatch.res`, `awaiting_non_promise.res`, multiple `jsx_*` fixtures (including `jsx_preserve_external_function.res` for a plain-function external in a JSX component position), `object_literal_for_poly_field.res` (object literal against a polymorphic field annotation). |
| `Apply_non_function` | ✓ | `apply_non_function.res` | |
| `Apply_wrong_label` | ✓ | `apply_wrong_label.res` | |
| `Label_multiply_defined` | ✓ | `label_multiply_defined_literal.res` | |
Expand Down
15 changes: 15 additions & 0 deletions tests/build_tests/jsx_preserve_external/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @ts-check

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { setup } from "#dev/process";

const { execBuildOrThrow, execClean } = setup(import.meta.dirname);

await execClean();
await execBuildOrThrow();

const output = readFileSync(new URL("src/Test.jsx", import.meta.url), "utf8");
assert.match(output, /import \* as SomeLib from "some-lib"/);
assert.match(output, /<SomeLib\.Head>\s*\{<div\s*\/>\}\s*<\/SomeLib\.Head>/);
assert.doesNotMatch(output, /=>/);
14 changes: 14 additions & 0 deletions tests/build_tests/jsx_preserve_external/rescript.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "jsx-preserve-external",
"sources": ["src"],
"package-specs": {
"module": "esmodule",
"in-source": true,
"suffix": ".jsx"
},
"jsx": {
"module": "Preact",
"preserve": true
},
"compiler-flags": ["-check-lam"]
}
23 changes: 23 additions & 0 deletions tests/build_tests/jsx_preserve_external/src/Preact.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
type element = Jsx.element
// Custom JSX bindings must use the abstract component type, as React does.
// A function alias here lets function-style externals through and can produce
// invalid preserved JSX, as in the original bindings from issue #8047.
type component<'props> = Jsx.component<'props>

@module("preact/jsx-runtime")
external jsx: (component<'props>, 'props) => element = "jsx"

@module("preact/jsx-runtime")
external jsxs: (component<'props>, 'props) => element = "jsxs"

type domProps = {children?: element}

module Elements = {
external someElement: element => option<element> = "%identity"

@module("preact/jsx-runtime")
external jsx: (string, domProps) => element = "jsx"

@module("preact/jsx-runtime")
external jsxs: (string, domProps) => element = "jsxs"
}
13 changes: 13 additions & 0 deletions tests/build_tests/jsx_preserve_external/src/Test.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Regression for https://github.com/rescript-lang/rescript/issues/8047.
// External components must remain imported values, without function wrappers.
module Head = {
type props = {children?: Preact.element}

@module("some-lib")
external make: Preact.component<props> = "Head"
}

let test =
<Head>
<div />
</Head>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

We've found a bug for you!
/.../fixtures/jsx_preserve_external_function.res:30:4-7

28 │
29 │ let test =
30 │ <Head>
31 │ <div />
32 │ </Head>

This JSX tag has type: Head.props => Preact.element
But JSX component positions require:
Preact.component<'a> (defined as Jsx.component<'a>)

JSX tags must be React components, not plain functions.

Possible solutions:
- If this function takes labeled props, annotate it with @react.component
- If this function takes a single props record, annotate it with @react.componentWithProps
- If this is already a valid component-like value, wrap it with React.component(...)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@@config({flags: ["-bs-jsx-preserve"]})
@@jsxConfig({version: 4, module_: "Preact"})

// With abstract components, the function-style external from #8047 is rejected.
module Preact = {
type element = Jsx.element
type component<'props> = Jsx.component<'props>

@module("preact/jsx-runtime")
external jsx: (component<'props>, 'props) => element = "jsx"

type domProps = {children?: element}

module Elements = {
external someElement: element => option<element> = "%identity"

@module("preact/jsx-runtime")
external jsx: (string, domProps) => element = "jsx"
}
}

module Head = {
type props = {children?: Preact.element}

@module("some-lib")
external make: props => Preact.element = "Head"
}

let test =
<Head>
<div />
</Head>
Loading