From ff6ba0db06508ec17edf5c4c8727f95a1b3886d9 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Wed, 20 May 2026 09:38:31 +0200 Subject: [PATCH 01/16] super_errors_multi: add multi-file fixture harness with 4 smoke fixtures --- .../build_tests/super_errors_multi/.gitignore | 6 + .../Smoke_cross_module_type_clash.expected | 12 ++ .../Smoke_interface_mismatch.expected | 15 ++ .../expected/Smoke_missing_field.expected | 9 + .../Smoke_unbound_module_reference.expected | 12 ++ .../Smoke_cross_module_type_clash/Lib.res | 1 + .../Smoke_cross_module_type_clash/User.res | 1 + .../fixtures/Smoke_interface_mismatch/Foo.res | 1 + .../Smoke_interface_mismatch/Foo.resi | 1 + .../fixtures/Smoke_missing_field/Defs.res | 3 + .../fixtures/Smoke_missing_field/Use.res | 1 + .../Consumer.res | 1 + tests/build_tests/super_errors_multi/input.js | 181 ++++++++++++++++++ 13 files changed, 244 insertions(+) create mode 100644 tests/build_tests/super_errors_multi/.gitignore create mode 100644 tests/build_tests/super_errors_multi/expected/Smoke_cross_module_type_clash.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Smoke_interface_mismatch.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Smoke_missing_field.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Smoke_unbound_module_reference.expected create mode 100644 tests/build_tests/super_errors_multi/fixtures/Smoke_cross_module_type_clash/Lib.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Smoke_cross_module_type_clash/User.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Smoke_interface_mismatch/Foo.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Smoke_interface_mismatch/Foo.resi create mode 100644 tests/build_tests/super_errors_multi/fixtures/Smoke_missing_field/Defs.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Smoke_missing_field/Use.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Smoke_unbound_module_reference/Consumer.res create mode 100644 tests/build_tests/super_errors_multi/input.js diff --git a/tests/build_tests/super_errors_multi/.gitignore b/tests/build_tests/super_errors_multi/.gitignore new file mode 100644 index 00000000000..d9d632f901c --- /dev/null +++ b/tests/build_tests/super_errors_multi/.gitignore @@ -0,0 +1,6 @@ +fixtures/**/*.cmi +fixtures/**/*.cmj +fixtures/**/*.cmt +fixtures/**/*.cmti +fixtures/**/*.mjs +fixtures/**/*.js diff --git a/tests/build_tests/super_errors_multi/expected/Smoke_cross_module_type_clash.expected b/tests/build_tests/super_errors_multi/expected/Smoke_cross_module_type_clash.expected new file mode 100644 index 00000000000..73cdc4f6dab --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Smoke_cross_module_type_clash.expected @@ -0,0 +1,12 @@ +===== User.res ===== + + We've found a bug for you! + /.../fixtures/Smoke_cross_module_type_clash/User.res:1:23-29 + + 1 │ let v = Lib.increment("hello") + 2 │ + + This has type: string + But this function argument is expecting: int + + You can convert string to int with Int.fromString. \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Smoke_interface_mismatch.expected b/tests/build_tests/super_errors_multi/expected/Smoke_interface_mismatch.expected new file mode 100644 index 00000000000..96c0f47f00d --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Smoke_interface_mismatch.expected @@ -0,0 +1,15 @@ +===== Foo.res ===== + + We've found a bug for you! + /.../fixtures/Smoke_interface_mismatch/Foo.res:1:5 + + 1 │ let v = "not an int" + 2 │ + + The implementation /.../fixtures/Smoke_interface_mismatch/Foo.res + does not match the interface /.../fixtures/Smoke_interface_mismatch/foo.cmi: + Values do not match: let v: string is not included in let v: int + /.../fixtures/Smoke_interface_mismatch/Foo.resi:1:1-10: + Expected declaration + /.../fixtures/Smoke_interface_mismatch/Foo.res:1:5: + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Smoke_missing_field.expected b/tests/build_tests/super_errors_multi/expected/Smoke_missing_field.expected new file mode 100644 index 00000000000..0012f34f130 --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Smoke_missing_field.expected @@ -0,0 +1,9 @@ +===== Use.res ===== + + We've found a bug for you! + /.../fixtures/Smoke_missing_field/Use.res:1:23-28 + + 1 │ let bad: Defs.point = {x: 1} + 2 │ + + Some required record fields are missing: y. \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Smoke_unbound_module_reference.expected b/tests/build_tests/super_errors_multi/expected/Smoke_unbound_module_reference.expected new file mode 100644 index 00000000000..b22c4670827 --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Smoke_unbound_module_reference.expected @@ -0,0 +1,12 @@ +===== Consumer.res ===== + + We've found a bug for you! + /.../fixtures/Smoke_unbound_module_reference/Consumer.res:1:9-31 + + 1 │ let x = NonexistentModule.value + 2 │ + + The module or file NonexistentModule can't be found. + - If it's a third-party dependency: + - Did you add it to the "dependencies" or "dev-dependencies" in rescript.json? + - Did you include the file's directory to the "sources" in rescript.json? \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/fixtures/Smoke_cross_module_type_clash/Lib.res b/tests/build_tests/super_errors_multi/fixtures/Smoke_cross_module_type_clash/Lib.res new file mode 100644 index 00000000000..9e769359ed8 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Smoke_cross_module_type_clash/Lib.res @@ -0,0 +1 @@ +let increment = (x: int) => x + 1 diff --git a/tests/build_tests/super_errors_multi/fixtures/Smoke_cross_module_type_clash/User.res b/tests/build_tests/super_errors_multi/fixtures/Smoke_cross_module_type_clash/User.res new file mode 100644 index 00000000000..953f935a093 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Smoke_cross_module_type_clash/User.res @@ -0,0 +1 @@ +let v = Lib.increment("hello") diff --git a/tests/build_tests/super_errors_multi/fixtures/Smoke_interface_mismatch/Foo.res b/tests/build_tests/super_errors_multi/fixtures/Smoke_interface_mismatch/Foo.res new file mode 100644 index 00000000000..618952e8492 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Smoke_interface_mismatch/Foo.res @@ -0,0 +1 @@ +let v = "not an int" diff --git a/tests/build_tests/super_errors_multi/fixtures/Smoke_interface_mismatch/Foo.resi b/tests/build_tests/super_errors_multi/fixtures/Smoke_interface_mismatch/Foo.resi new file mode 100644 index 00000000000..19b83dc216f --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Smoke_interface_mismatch/Foo.resi @@ -0,0 +1 @@ +let v: int diff --git a/tests/build_tests/super_errors_multi/fixtures/Smoke_missing_field/Defs.res b/tests/build_tests/super_errors_multi/fixtures/Smoke_missing_field/Defs.res new file mode 100644 index 00000000000..56a48ca98f4 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Smoke_missing_field/Defs.res @@ -0,0 +1,3 @@ +type point = {x: int, y: int} + +let make = (x, y) => {x, y} diff --git a/tests/build_tests/super_errors_multi/fixtures/Smoke_missing_field/Use.res b/tests/build_tests/super_errors_multi/fixtures/Smoke_missing_field/Use.res new file mode 100644 index 00000000000..ea5921fb374 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Smoke_missing_field/Use.res @@ -0,0 +1 @@ +let bad: Defs.point = {x: 1} diff --git a/tests/build_tests/super_errors_multi/fixtures/Smoke_unbound_module_reference/Consumer.res b/tests/build_tests/super_errors_multi/fixtures/Smoke_unbound_module_reference/Consumer.res new file mode 100644 index 00000000000..1a74a469eb5 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Smoke_unbound_module_reference/Consumer.res @@ -0,0 +1 @@ +let x = NonexistentModule.value diff --git a/tests/build_tests/super_errors_multi/input.js b/tests/build_tests/super_errors_multi/input.js new file mode 100644 index 00000000000..ab29a789349 --- /dev/null +++ b/tests/build_tests/super_errors_multi/input.js @@ -0,0 +1,181 @@ +// @ts-check + +import { readdirSync, statSync } from "node:fs"; +import * as fs from "node:fs/promises"; +import * as os from "node:os"; +import * as path from "node:path"; +import { setup } from "#dev/process"; +import { normalizeNewlines } from "#dev/utils"; + +const { bsc } = setup(import.meta.dirname); + +const fixturesDir = path.join(import.meta.dirname, "fixtures"); +const expectedDir = path.join(import.meta.dirname, "expected"); + +const fixtures = readdirSync(fixturesDir) + .filter(name => statSync(path.join(fixturesDir, name)).isDirectory()) + .sort(); + +const bscFlags = ["-w", "+A", "-bs-jsx", "4", "-color", "always"]; + +const updateTests = process.argv[2] === "update"; + +/** + * @param {string} fixtureName + * @param {string} output + * @return {string} + */ +function postProcessErrorOutput(fixtureName, output) { + let result = output; + result = result.trimEnd(); + result = result.replace( + new RegExp( + `(?:[A-Z]:)?[\\\\/][^ ]+?tests[\\\\/]build_tests[\\\\/]super_errors_multi[\\\\/]fixtures[\\\\/]${fixtureName}[\\\\/]([^:\\s]+)`, + "g", + ), + (_match, file) => + `/.../fixtures/${fixtureName}/${file.replace(/\\/g, "/")}`, + ); + return normalizeNewlines(result); +} + +/** + * Per-fixture artifact extensions to clean between runs so each invocation + * compiles against a known-empty .cmi/.cmj/.cmt set. + */ +const artifactExts = [".cmi", ".cmj", ".cmt", ".cmti", ".mjs", ".js"]; + +/** + * @param {string} dir + */ +async function cleanArtifacts(dir) { + const entries = await fs.readdir(dir); + await Promise.all( + entries.map(async name => { + if (artifactExts.includes(path.extname(name))) { + await fs.unlink(path.join(dir, name)); + } + }), + ); +} + +/** + * @param {string} fixtureName + * @returns {Promise<{ fixtureName: string, failure: string | null }>} + */ +async function runFixture(fixtureName) { + const fixtureDir = path.join(fixturesDir, fixtureName); + + // Compile sources in alphabetical order, with one tweak: for any module + // that has both `.resi` and `.res`, the interface goes first so the + // implementation type-checks against the produced `.cmi`. Fixtures that + // need a specific dependency order across modules can prefix filenames + // with numeric labels (e.g. `01_Foo.res`, `02_Bar.res`). + const sources = (await fs.readdir(fixtureDir)) + .filter(name => name.endsWith(".res") || name.endsWith(".resi")) + .sort((a, b) => { + const aStem = a.replace(/\.resi?$/, ""); + const bStem = b.replace(/\.resi?$/, ""); + if (aStem === bStem) { + return a.endsWith(".resi") ? -1 : 1; + } + return aStem < bStem ? -1 : aStem > bStem ? 1 : 0; + }); + + await cleanArtifacts(fixtureDir); + + // Modules that have both an interface and an implementation need + // `-bs-read-cmi` on the implementation so the `.cmi` produced from the + // interface is enforced rather than overwritten. + const hasInterface = new Set(); + for (const source of sources) { + if (source.endsWith(".resi")) hasInterface.add(source.slice(0, -5)); + } + + const chunks = []; + for (const source of sources) { + const stem = source.replace(/\.resi?$/, ""); + const extraFlags = + source.endsWith(".res") && hasInterface.has(stem) ? ["-bs-read-cmi"] : []; + const { stderr } = await bsc( + [ + ...bscFlags, + ...extraFlags, + "-I", + fixtureDir, + path.join(fixtureDir, source), + ], + { cwd: fixtureDir }, + ); + const stderrStr = stderr.toString(); + if (stderrStr.length > 0) { + // Tag each chunk so a reader can tell which file produced which + // diagnostic — important when the same error gets re-emitted in the + // downstream consumer. + chunks.push(`===== ${source} =====\n${stderrStr}`); + } + } + + const actualErrorOutput = postProcessErrorOutput( + fixtureName, + chunks.join("\n"), + ); + const expectedFilePath = path.join(expectedDir, `${fixtureName}.expected`); + + if (updateTests) { + await fs.writeFile(expectedFilePath, actualErrorOutput); + return { fixtureName, failure: null }; + } + + let expectedErrorOutput; + try { + expectedErrorOutput = postProcessErrorOutput( + fixtureName, + await fs.readFile(expectedFilePath, "utf-8"), + ); + } catch { + return { + fixtureName, + failure: `Missing expected snapshot for ${fixtureName} (run with 'update' to create)`, + }; + } + + if (expectedErrorOutput === actualErrorOutput) { + return { fixtureName, failure: null }; + } + return { + fixtureName, + failure: [ + `The old and new error output for fixture ${fixtureName} aren't the same`, + "\n=== Old:", + expectedErrorOutput, + "\n=== New:", + actualErrorOutput, + ].join("\n"), + }; +} + +const concurrency = Math.max(1, os.availableParallelism()); +let cursor = 0; +const results = new Array(fixtures.length); + +await Promise.all( + Array.from({ length: Math.min(concurrency, fixtures.length) }, async () => { + while (true) { + const i = cursor++; + if (i >= fixtures.length) return; + results[i] = await runFixture(fixtures[i]); + } + }), +); + +let atLeastOneTaskFailed = false; +for (const { failure } of results) { + if (failure !== null) { + console.error(failure); + atLeastOneTaskFailed = true; + } +} +if (atLeastOneTaskFailed) { + process.exit(1); +} From 35e019debfcd014fa3f8f3277579f44f6957c211 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Wed, 20 May 2026 09:42:08 +0200 Subject: [PATCH 02/16] super_errors_multi: cover Includemod variants through compunit (.resi/.res signature mismatch) --- .../Iface_extension_constructors.expected | 15 ++++++++++ .../expected/Iface_missing_value.expected | 14 +++++++++ .../expected/Iface_modtype_infos.expected | 30 +++++++++++++++++++ .../expected/Iface_module_types.expected | 22 ++++++++++++++ .../expected/Iface_type_decl_record.expected | 19 ++++++++++++ .../expected/Iface_type_decl_variant.expected | 21 +++++++++++++ .../Iface_value_descriptions.expected | 18 +++++++++++ .../Iface_extension_constructors/Foo.res | 4 +++ .../Iface_extension_constructors/Foo.resi | 4 +++ .../fixtures/Iface_missing_value/Foo.res | 1 + .../fixtures/Iface_missing_value/Foo.resi | 2 ++ .../fixtures/Iface_modtype_infos/Foo.res | 3 ++ .../fixtures/Iface_modtype_infos/Foo.resi | 3 ++ .../fixtures/Iface_module_types/Foo.res | 3 ++ .../fixtures/Iface_module_types/Foo.resi | 3 ++ .../fixtures/Iface_type_decl_record/Foo.res | 1 + .../fixtures/Iface_type_decl_record/Foo.resi | 1 + .../fixtures/Iface_type_decl_variant/Foo.res | 3 ++ .../fixtures/Iface_type_decl_variant/Foo.resi | 3 ++ .../fixtures/Iface_value_descriptions/Foo.res | 1 + .../Iface_value_descriptions/Foo.resi | 1 + 21 files changed, 172 insertions(+) create mode 100644 tests/build_tests/super_errors_multi/expected/Iface_extension_constructors.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Iface_missing_value.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Iface_modtype_infos.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Iface_module_types.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Iface_type_decl_record.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Iface_type_decl_variant.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Iface_value_descriptions.expected create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_extension_constructors/Foo.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_extension_constructors/Foo.resi create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_missing_value/Foo.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_missing_value/Foo.resi create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_modtype_infos/Foo.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_modtype_infos/Foo.resi create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_module_types/Foo.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_module_types/Foo.resi create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_record/Foo.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_record/Foo.resi create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_variant/Foo.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_variant/Foo.resi create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_value_descriptions/Foo.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_value_descriptions/Foo.resi diff --git a/tests/build_tests/super_errors_multi/expected/Iface_extension_constructors.expected b/tests/build_tests/super_errors_multi/expected/Iface_extension_constructors.expected new file mode 100644 index 00000000000..ac624fa56ff --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Iface_extension_constructors.expected @@ -0,0 +1,15 @@ +===== Foo.res ===== + + We've found a bug for you! + /.../fixtures/Iface_extension_constructors/Foo.res + + The implementation /.../fixtures/Iface_extension_constructors/Foo.res + does not match the interface /.../fixtures/Iface_extension_constructors/foo.cmi: + Extension declarations do not match: + type t += Boom(string) + is not included in + type t += Boom(int) + /.../fixtures/Iface_extension_constructors/Foo.res: + Expected declaration + /.../fixtures/Iface_extension_constructors/Foo.res:4:3-16: + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Iface_missing_value.expected b/tests/build_tests/super_errors_multi/expected/Iface_missing_value.expected new file mode 100644 index 00000000000..b69da19d07c --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Iface_missing_value.expected @@ -0,0 +1,14 @@ +===== Foo.res ===== + + We've found a bug for you! + /.../fixtures/Iface_missing_value/Foo.resi:2:1-13 + + 1 │ let a: int + 2 │ let b: string + 3 │ + + The implementation /.../fixtures/Iface_missing_value/Foo.res + does not match the interface /.../fixtures/Iface_missing_value/foo.cmi: + The value `b' is required but not provided + /.../fixtures/Iface_missing_value/Foo.resi:2:1-13: + Expected declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Iface_modtype_infos.expected b/tests/build_tests/super_errors_multi/expected/Iface_modtype_infos.expected new file mode 100644 index 00000000000..2cf2a858fbf --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Iface_modtype_infos.expected @@ -0,0 +1,30 @@ +===== Foo.res ===== + + We've found a bug for you! + /.../fixtures/Iface_modtype_infos/Foo.res + + The implementation /.../fixtures/Iface_modtype_infos/Foo.res + does not match the interface /.../fixtures/Iface_modtype_infos/foo.cmi: + Module type declarations do not match: + module type T = { + let v: string +} + does not match + module type T = { + let v: int +} + At position module type T = + Modules do not match: + { + let v: string +} + is not included in + { + let v: int +} + At position module type T = + Values do not match: let v: string is not included in let v: int + /.../fixtures/Iface_modtype_infos/Foo.resi:2:3-12: + Expected declaration + /.../fixtures/Iface_modtype_infos/Foo.res:2:3-15: + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Iface_module_types.expected b/tests/build_tests/super_errors_multi/expected/Iface_module_types.expected new file mode 100644 index 00000000000..6276a98fcd1 --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Iface_module_types.expected @@ -0,0 +1,22 @@ +===== Foo.res ===== + + We've found a bug for you! + /.../fixtures/Iface_module_types/Foo.res + + The implementation /.../fixtures/Iface_module_types/Foo.res + does not match the interface /.../fixtures/Iface_module_types/foo.cmi: + In module Inner: + Modules do not match: + { + let value: string +} + is not included in + { + let value: int +} + In module Inner: + Values do not match: let value: string is not included in let value: int + /.../fixtures/Iface_module_types/Foo.resi:2:3-16: + Expected declaration + /.../fixtures/Iface_module_types/Foo.res:2:7-11: + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Iface_type_decl_record.expected b/tests/build_tests/super_errors_multi/expected/Iface_type_decl_record.expected new file mode 100644 index 00000000000..16ea04ada1a --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Iface_type_decl_record.expected @@ -0,0 +1,19 @@ +===== Foo.res ===== + + We've found a bug for you! + /.../fixtures/Iface_type_decl_record/Foo.res:1:1-25 + + 1 │ type t = {x: int, z: int} + 2 │ + + The implementation /.../fixtures/Iface_type_decl_record/Foo.res + does not match the interface /.../fixtures/Iface_type_decl_record/foo.cmi: + Type declarations do not match: + type t = {x: int, z: int} + is not included in + type t = {x: int, y: int} + /.../fixtures/Iface_type_decl_record/Foo.resi:1:1-25: + Expected declaration + /.../fixtures/Iface_type_decl_record/Foo.res:1:1-25: + Actual declaration + Fields number 2 have different names, z and y. \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Iface_type_decl_variant.expected b/tests/build_tests/super_errors_multi/expected/Iface_type_decl_variant.expected new file mode 100644 index 00000000000..e3d5b75c8d4 --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Iface_type_decl_variant.expected @@ -0,0 +1,21 @@ +===== Foo.res ===== + + We've found a bug for you! + /.../fixtures/Iface_type_decl_variant/Foo.res:1:1-3:13 + + 1 │ type t = + 2 │  | A + 3 │  | B(string) + 4 │ + + The implementation /.../fixtures/Iface_type_decl_variant/Foo.res + does not match the interface /.../fixtures/Iface_type_decl_variant/foo.cmi: + Type declarations do not match: + type t = A | B(string) + is not included in + type t = A | B(int) + /.../fixtures/Iface_type_decl_variant/Foo.resi:1:1-3:10: + Expected declaration + /.../fixtures/Iface_type_decl_variant/Foo.res:1:1-3:13: + Actual declaration + The types for field B are not equal. \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Iface_value_descriptions.expected b/tests/build_tests/super_errors_multi/expected/Iface_value_descriptions.expected new file mode 100644 index 00000000000..6f9df31b053 --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Iface_value_descriptions.expected @@ -0,0 +1,18 @@ +===== Foo.res ===== + + We've found a bug for you! + /.../fixtures/Iface_value_descriptions/Foo.res:1:5-11 + + 1 │ let compute = (s: string) => String.length(s) + 2 │ + + The implementation /.../fixtures/Iface_value_descriptions/Foo.res + does not match the interface /.../fixtures/Iface_value_descriptions/foo.cmi: + Values do not match: + let compute: string => int + is not included in + let compute: int => int + /.../fixtures/Iface_value_descriptions/Foo.resi:1:1-23: + Expected declaration + /.../fixtures/Iface_value_descriptions/Foo.res:1:5-11: + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_extension_constructors/Foo.res b/tests/build_tests/super_errors_multi/fixtures/Iface_extension_constructors/Foo.res new file mode 100644 index 00000000000..3caf4e6f3d4 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_extension_constructors/Foo.res @@ -0,0 +1,4 @@ +type t = .. + +type t += + | Boom(string) diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_extension_constructors/Foo.resi b/tests/build_tests/super_errors_multi/fixtures/Iface_extension_constructors/Foo.resi new file mode 100644 index 00000000000..47a74f838dd --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_extension_constructors/Foo.resi @@ -0,0 +1,4 @@ +type t = .. + +type t += + | Boom(int) diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_missing_value/Foo.res b/tests/build_tests/super_errors_multi/fixtures/Iface_missing_value/Foo.res new file mode 100644 index 00000000000..67bbe3ba8e7 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_missing_value/Foo.res @@ -0,0 +1 @@ +let a = 1 diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_missing_value/Foo.resi b/tests/build_tests/super_errors_multi/fixtures/Iface_missing_value/Foo.resi new file mode 100644 index 00000000000..0fb0b0c7e85 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_missing_value/Foo.resi @@ -0,0 +1,2 @@ +let a: int +let b: string diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_modtype_infos/Foo.res b/tests/build_tests/super_errors_multi/fixtures/Iface_modtype_infos/Foo.res new file mode 100644 index 00000000000..708a6eb23ec --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_modtype_infos/Foo.res @@ -0,0 +1,3 @@ +module type T = { + let v: string +} diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_modtype_infos/Foo.resi b/tests/build_tests/super_errors_multi/fixtures/Iface_modtype_infos/Foo.resi new file mode 100644 index 00000000000..4d77f569616 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_modtype_infos/Foo.resi @@ -0,0 +1,3 @@ +module type T = { + let v: int +} diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_module_types/Foo.res b/tests/build_tests/super_errors_multi/fixtures/Iface_module_types/Foo.res new file mode 100644 index 00000000000..086f64fce1b --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_module_types/Foo.res @@ -0,0 +1,3 @@ +module Inner = { + let value = "not an int" +} diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_module_types/Foo.resi b/tests/build_tests/super_errors_multi/fixtures/Iface_module_types/Foo.resi new file mode 100644 index 00000000000..c1c80261e51 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_module_types/Foo.resi @@ -0,0 +1,3 @@ +module Inner: { + let value: int +} diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_record/Foo.res b/tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_record/Foo.res new file mode 100644 index 00000000000..3bb325d27a1 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_record/Foo.res @@ -0,0 +1 @@ +type t = {x: int, z: int} diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_record/Foo.resi b/tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_record/Foo.resi new file mode 100644 index 00000000000..b5eab822459 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_record/Foo.resi @@ -0,0 +1 @@ +type t = {x: int, y: int} diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_variant/Foo.res b/tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_variant/Foo.res new file mode 100644 index 00000000000..a968c4a7333 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_variant/Foo.res @@ -0,0 +1,3 @@ +type t = + | A + | B(string) diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_variant/Foo.resi b/tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_variant/Foo.resi new file mode 100644 index 00000000000..94e648c4224 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_type_decl_variant/Foo.resi @@ -0,0 +1,3 @@ +type t = + | A + | B(int) diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_value_descriptions/Foo.res b/tests/build_tests/super_errors_multi/fixtures/Iface_value_descriptions/Foo.res new file mode 100644 index 00000000000..b6c3061ea61 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_value_descriptions/Foo.res @@ -0,0 +1 @@ +let compute = (s: string) => String.length(s) diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_value_descriptions/Foo.resi b/tests/build_tests/super_errors_multi/fixtures/Iface_value_descriptions/Foo.resi new file mode 100644 index 00000000000..aec7b21274f --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_value_descriptions/Foo.resi @@ -0,0 +1 @@ +let compute: int => int From ba0a78ab65d36fa2e395dd216612aa70b65b48f2 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Wed, 20 May 2026 09:45:54 +0200 Subject: [PATCH 03/16] super_errors_multi: cover cross-module type / record / constructor disambiguation errors --- .../Cross_abstract_type_construction.expected | 10 ++++++++++ .../expected/Cross_chain_of_aliases.expected | 12 ++++++++++++ .../expected/Cross_dependent_clash.expected | 9 +++++++++ .../Cross_module_record_disambiguation.expected | 11 +++++++++++ .../Cross_qualified_constructor_mismatch.expected | 10 ++++++++++ .../Cross_abstract_type_construction/Abs.res | 3 +++ .../Cross_abstract_type_construction/Abs.resi | 3 +++ .../Cross_abstract_type_construction/User.res | 1 + .../fixtures/Cross_chain_of_aliases/A.res | 4 ++++ .../fixtures/Cross_chain_of_aliases/B.res | 1 + .../fixtures/Cross_chain_of_aliases/C.res | 1 + .../fixtures/Cross_dependent_clash/Sender.res | 3 +++ .../fixtures/Cross_dependent_clash/User.res | 1 + .../Cross_module_record_disambiguation/Box.res | 1 + .../Cross_module_record_disambiguation/Point.res | 1 + .../Cross_module_record_disambiguation/User.res | 1 + .../Cross_qualified_constructor_mismatch/Color.res | 3 +++ .../Cross_qualified_constructor_mismatch/Shape.res | 3 +++ .../Cross_qualified_constructor_mismatch/User.res | 1 + 19 files changed, 79 insertions(+) create mode 100644 tests/build_tests/super_errors_multi/expected/Cross_abstract_type_construction.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Cross_chain_of_aliases.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Cross_dependent_clash.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Cross_module_record_disambiguation.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Cross_qualified_constructor_mismatch.expected create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_abstract_type_construction/Abs.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_abstract_type_construction/Abs.resi create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_abstract_type_construction/User.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_chain_of_aliases/A.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_chain_of_aliases/B.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_chain_of_aliases/C.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_dependent_clash/Sender.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_dependent_clash/User.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_module_record_disambiguation/Box.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_module_record_disambiguation/Point.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_module_record_disambiguation/User.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_qualified_constructor_mismatch/Color.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_qualified_constructor_mismatch/Shape.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_qualified_constructor_mismatch/User.res diff --git a/tests/build_tests/super_errors_multi/expected/Cross_abstract_type_construction.expected b/tests/build_tests/super_errors_multi/expected/Cross_abstract_type_construction.expected new file mode 100644 index 00000000000..c3c8d10770b --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Cross_abstract_type_construction.expected @@ -0,0 +1,10 @@ +===== User.res ===== + + We've found a bug for you! + /.../fixtures/Cross_abstract_type_construction/User.res:1:18 + + 1 │ let bad: Abs.t = 1 + 2 │ + + This has type: int + But it's expected to have type: Abs.t \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Cross_chain_of_aliases.expected b/tests/build_tests/super_errors_multi/expected/Cross_chain_of_aliases.expected new file mode 100644 index 00000000000..2b762fd2a92 --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Cross_chain_of_aliases.expected @@ -0,0 +1,12 @@ +===== C.res ===== + + We've found a bug for you! + /.../fixtures/Cross_chain_of_aliases/C.res:1:23-29 + + 1 │ let v: B.Reexport.t = "wrong" + 2 │ + + This has type: string + But it's expected to have type: B.Reexport.t (defined as int) + + You can convert string to int with Int.fromString. \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Cross_dependent_clash.expected b/tests/build_tests/super_errors_multi/expected/Cross_dependent_clash.expected new file mode 100644 index 00000000000..479acd93378 --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Cross_dependent_clash.expected @@ -0,0 +1,9 @@ +===== User.res ===== + + We've found a bug for you! + /.../fixtures/Cross_dependent_clash/User.res:1:21-34 + + 1 │ let _ = Sender.send({to_: "alice"}) + 2 │ + + Some required record fields are missing: body. \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Cross_module_record_disambiguation.expected b/tests/build_tests/super_errors_multi/expected/Cross_module_record_disambiguation.expected new file mode 100644 index 00000000000..33e0bdfb778 --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Cross_module_record_disambiguation.expected @@ -0,0 +1,11 @@ +===== User.res ===== + + We've found a bug for you! + /.../fixtures/Cross_module_record_disambiguation/User.res:1:19-23 + + 1 │ let r: Point.t = {width: 1, height: 2} + 2 │ + + The field width does not belong to type Point.t + + This record expression is expected to have type Point.t \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Cross_qualified_constructor_mismatch.expected b/tests/build_tests/super_errors_multi/expected/Cross_qualified_constructor_mismatch.expected new file mode 100644 index 00000000000..f89b459b4eb --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Cross_qualified_constructor_mismatch.expected @@ -0,0 +1,10 @@ +===== User.res ===== + + We've found a bug for you! + /.../fixtures/Cross_qualified_constructor_mismatch/User.res:1:18-26 + + 1 │ let v: Shape.t = Color.Red + 2 │ + + The constructor Color.Red belongs to the variant type Color.t + but a constructor was expected belonging to the variant type Shape.t \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_abstract_type_construction/Abs.res b/tests/build_tests/super_errors_multi/fixtures/Cross_abstract_type_construction/Abs.res new file mode 100644 index 00000000000..144d9bb482c --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_abstract_type_construction/Abs.res @@ -0,0 +1,3 @@ +type t = int + +let make = v => v diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_abstract_type_construction/Abs.resi b/tests/build_tests/super_errors_multi/fixtures/Cross_abstract_type_construction/Abs.resi new file mode 100644 index 00000000000..5c236fb1c87 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_abstract_type_construction/Abs.resi @@ -0,0 +1,3 @@ +type t + +let make: int => t diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_abstract_type_construction/User.res b/tests/build_tests/super_errors_multi/fixtures/Cross_abstract_type_construction/User.res new file mode 100644 index 00000000000..4d423a447b3 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_abstract_type_construction/User.res @@ -0,0 +1 @@ +let bad: Abs.t = 1 diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_chain_of_aliases/A.res b/tests/build_tests/super_errors_multi/fixtures/Cross_chain_of_aliases/A.res new file mode 100644 index 00000000000..23d13e567bc --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_chain_of_aliases/A.res @@ -0,0 +1,4 @@ +module Inner = { + type t = int + let v = 0 +} diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_chain_of_aliases/B.res b/tests/build_tests/super_errors_multi/fixtures/Cross_chain_of_aliases/B.res new file mode 100644 index 00000000000..d2ede9cbaa0 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_chain_of_aliases/B.res @@ -0,0 +1 @@ +module Reexport = A.Inner diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_chain_of_aliases/C.res b/tests/build_tests/super_errors_multi/fixtures/Cross_chain_of_aliases/C.res new file mode 100644 index 00000000000..a2ceb98aea6 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_chain_of_aliases/C.res @@ -0,0 +1 @@ +let v: B.Reexport.t = "wrong" diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_dependent_clash/Sender.res b/tests/build_tests/super_errors_multi/fixtures/Cross_dependent_clash/Sender.res new file mode 100644 index 00000000000..26c6349dfed --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_dependent_clash/Sender.res @@ -0,0 +1,3 @@ +type message = {to_: string, body: string} + +let send: message => unit = _msg => () diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_dependent_clash/User.res b/tests/build_tests/super_errors_multi/fixtures/Cross_dependent_clash/User.res new file mode 100644 index 00000000000..0dd83b5b4b4 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_dependent_clash/User.res @@ -0,0 +1 @@ +let _ = Sender.send({to_: "alice"}) diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_module_record_disambiguation/Box.res b/tests/build_tests/super_errors_multi/fixtures/Cross_module_record_disambiguation/Box.res new file mode 100644 index 00000000000..6915e332a3d --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_module_record_disambiguation/Box.res @@ -0,0 +1 @@ +type t = {width: int, height: int} diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_module_record_disambiguation/Point.res b/tests/build_tests/super_errors_multi/fixtures/Cross_module_record_disambiguation/Point.res new file mode 100644 index 00000000000..b5eab822459 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_module_record_disambiguation/Point.res @@ -0,0 +1 @@ +type t = {x: int, y: int} diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_module_record_disambiguation/User.res b/tests/build_tests/super_errors_multi/fixtures/Cross_module_record_disambiguation/User.res new file mode 100644 index 00000000000..dbde9b1c866 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_module_record_disambiguation/User.res @@ -0,0 +1 @@ +let r: Point.t = {width: 1, height: 2} diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_qualified_constructor_mismatch/Color.res b/tests/build_tests/super_errors_multi/fixtures/Cross_qualified_constructor_mismatch/Color.res new file mode 100644 index 00000000000..efc0eae0b5f --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_qualified_constructor_mismatch/Color.res @@ -0,0 +1,3 @@ +type t = + | Red + | Green diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_qualified_constructor_mismatch/Shape.res b/tests/build_tests/super_errors_multi/fixtures/Cross_qualified_constructor_mismatch/Shape.res new file mode 100644 index 00000000000..36d3a19172c --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_qualified_constructor_mismatch/Shape.res @@ -0,0 +1,3 @@ +type t = + | Circle + | Square diff --git a/tests/build_tests/super_errors_multi/fixtures/Cross_qualified_constructor_mismatch/User.res b/tests/build_tests/super_errors_multi/fixtures/Cross_qualified_constructor_mismatch/User.res new file mode 100644 index 00000000000..a809c8d4cae --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Cross_qualified_constructor_mismatch/User.res @@ -0,0 +1 @@ +let v: Shape.t = Color.Red From cf5519f3ea06c3bf46ddef172bd21ca2903bb2c0 Mon Sep 17 00:00:00 2001 From: Jono Prest Date: Wed, 20 May 2026 09:48:44 +0200 Subject: [PATCH 04/16] super_errors_multi: cover cross-module external, private constructor, unused open --- .../Cross_external_argument_clash.expected | 12 ++++++++ .../Cross_jsx_component_wrong_prop.expected | 29 ++++++++++++++++++ ...Cross_private_constructor_pattern.expected | 30 +++++++++++++++++++ .../expected/Cross_unused_open.expected | 10 +++++++ .../Cross_external_argument_clash/Ffi.res | 1 + .../Cross_external_argument_clash/User.res | 1 + .../Result.res | 5 ++++ .../Result.resi | 6 ++++ .../User.res | 1 + .../fixtures/Cross_unused_open/Helpers.res | 1 + .../fixtures/Cross_unused_open/User.res | 3 ++ 11 files changed, 99 insertions(+) create mode 100644 tests/build_tests/super_errors_multi/expected/Cross_external_argument_clash.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Cross_jsx_component_wrong_prop.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Cross_private_constructor_pattern.expected create mode 100644 tests/build_tests/super_errors_multi/expected/Cross_unused_open.expected create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_external_argument_clash/Ffi.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_external_argument_clash/User.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_private_constructor_pattern/Result.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_private_constructor_pattern/Result.resi create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_private_constructor_pattern/User.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_unused_open/Helpers.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Cross_unused_open/User.res diff --git a/tests/build_tests/super_errors_multi/expected/Cross_external_argument_clash.expected b/tests/build_tests/super_errors_multi/expected/Cross_external_argument_clash.expected new file mode 100644 index 00000000000..7c8ec25834d --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Cross_external_argument_clash.expected @@ -0,0 +1,12 @@ +===== User.res ===== + + We've found a bug for you! + /.../fixtures/Cross_external_argument_clash/User.res:1:22-23 + + 1 │ let x = Ffi.parseInt(42) + 2 │ + + This has type: int + But this function argument is expecting: string + + You can convert int to string with Int.toString. \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Cross_jsx_component_wrong_prop.expected b/tests/build_tests/super_errors_multi/expected/Cross_jsx_component_wrong_prop.expected new file mode 100644 index 00000000000..273f3d80e89 --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Cross_jsx_component_wrong_prop.expected @@ -0,0 +1,29 @@ +===== App.res ===== + + We've found a bug for you! + /.../fixtures/Cross_jsx_component_wrong_prop/App.res:2:18-36 + + 1 │ @react.component + 2 │ let make = () =>