From 9708f0f4939d6686fe18a9719724f8137ad7df8c Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Fri, 14 Aug 2026 16:32:26 +0200 Subject: [PATCH 1/2] Enforce function arity in inclusion, type equality, and coercion Add the arity guard (already present in unify) to the Tarrow cases of moregen, eqtype, and subtype_rec. Previously a curried implementation (int => int => int) could satisfy an uncurried interface ((int, int) => int) through signature inclusion or :> coercion; calls made through the interface type compile to direct JavaScript calls with the declared arity, so a first-class use of such a value miscompiled (e.g. returning a closure where an int was expected). The value-mismatch report in includemod now prints a dedicated hint when the two sides are functions of different arities, replacing the vestigial empty curry_kind slot. mcomp is deliberately left arity-lenient: it is an incompatibility oracle for pattern and GADT reasoning, where leniency errs toward "possibly compatible". Co-Authored-By: Claude Fable 5 Signed-off-by: Cristiano Calcagno --- CHANGELOG.md | 1 + compiler/ml/ctype.ml | 12 ++--- compiler/ml/includemod.ml | 50 ++++++++++++++++--- tests/ERROR_VARIANTS.md | 8 +-- .../coercion_arity_mismatch.res.expected | 9 ++++ ...dule_sig_value_arity_mismatch.res.expected | 30 +++++++++++ ...ig_value_arity_mismatch_alias.res.expected | 30 +++++++++++ ...g_value_arity_mismatch_nested.res.expected | 30 +++++++++++ ..._value_arity_mismatch_reverse.res.expected | 30 +++++++++++ ..._decl_function_arity_mismatch.res.expected | 28 +++++++++++ .../fixtures/coercion_arity_mismatch.res | 2 + .../module_sig_value_arity_mismatch.res | 5 ++ .../module_sig_value_arity_mismatch_alias.res | 7 +++ ...module_sig_value_arity_mismatch_nested.res | 5 ++ ...odule_sig_value_arity_mismatch_reverse.res | 5 ++ .../type_decl_function_arity_mismatch.res | 5 ++ .../Iface_value_arity_mismatch.expected | 20 ++++++++ .../Iface_value_arity_mismatch/Foo.res | 1 + .../Iface_value_arity_mismatch/Foo.resi | 1 + 19 files changed, 263 insertions(+), 16 deletions(-) create mode 100644 tests/build_tests/super_errors/expected/coercion_arity_mismatch.res.expected create mode 100644 tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch.res.expected create mode 100644 tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_alias.res.expected create mode 100644 tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_nested.res.expected create mode 100644 tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_reverse.res.expected create mode 100644 tests/build_tests/super_errors/expected/type_decl_function_arity_mismatch.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/coercion_arity_mismatch.res create mode 100644 tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch.res create mode 100644 tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch_alias.res create mode 100644 tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch_nested.res create mode 100644 tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch_reverse.res create mode 100644 tests/build_tests/super_errors/fixtures/type_decl_function_arity_mismatch.res create mode 100644 tests/build_tests/super_errors_multi/expected/Iface_value_arity_mismatch.expected create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_value_arity_mismatch/Foo.res create mode 100644 tests/build_tests/super_errors_multi/fixtures/Iface_value_arity_mismatch/Foo.resi diff --git a/CHANGELOG.md b/CHANGELOG.md index 8aad3d4f947..bbc9e88a8b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ #### :bug: Bug fix - Preserve parentheses around multiplication, division, and modulo expressions used as exponents. https://github.com/rescript-lang/rescript/pull/8550 +- Enforce function arity in interface/module inclusion and type coercion. Previously a curried implementation (e.g. `int => int => int`) could satisfy an uncurried interface (`(int, int) => int`) or be coerced to it, which could miscompile calls made through the interface type. Such mismatches are now compile errors with an explanatory hint. https://github.com/rescript-lang/rescript/pull/8559 - Preserve multibyte characters when wrapping long source lines in compiler code frames. https://github.com/rescript-lang/rescript/pull/8520 - Fix reanalyze optional-argument diagnostics for functions passed or returned as first-class values. https://github.com/rescript-lang/rescript/pull/8321 - Prevent the developer playground from loading stale compiler and library assets after PR preview updates. https://github.com/rescript-lang/rescript/pull/8556 diff --git a/compiler/ml/ctype.ml b/compiler/ml/ctype.ml index bc9cff521d7..7de120c53f7 100644 --- a/compiler/ml/ctype.ml +++ b/compiler/ml/ctype.ml @@ -2740,8 +2740,8 @@ let rec moregen inst_nongen type_pairs env t1 t2 = | Tvar _, _ when may_instantiate inst_nongen t1' -> moregen_occur env t1'.level t2; link_type t1' t2 - | Tarrow (arg1, ret1, _), Tarrow (arg2, ret2, _) - when Asttypes.same_arg_label arg1.lbl arg2.lbl -> + | Tarrow (arg1, ret1, a1), Tarrow (arg2, ret2, a2) + when a1 = a2 && Asttypes.same_arg_label arg1.lbl arg2.lbl -> moregen inst_nongen type_pairs env arg1.typ arg2.typ; moregen inst_nongen type_pairs env ret1 ret2 | Ttuple tl1, Ttuple tl2 -> @@ -3010,8 +3010,8 @@ let rec eqtype rename type_pairs subst env t1 t2 = if List.exists (fun (_, t) -> t == t2') !subst then raise (Unify []); subst := (t1', t2') :: !subst) - | Tarrow (arg1, ret1, _), Tarrow (arg2, ret2, _) - when Asttypes.same_arg_label arg1.lbl arg2.lbl -> + | Tarrow (arg1, ret1, a1), Tarrow (arg2, ret2, a2) + when a1 = a2 && Asttypes.same_arg_label arg1.lbl arg2.lbl -> eqtype rename type_pairs subst env arg1.typ arg2.typ; eqtype rename type_pairs subst env ret1 ret2 | Ttuple tl1, Ttuple tl2 -> @@ -3410,8 +3410,8 @@ let rec subtype_rec env trace t1 t2 cstrs = Type_pairs.add subtypes (t1, t2) (); match (t1.desc, t2.desc) with | Tvar _, _ | _, Tvar _ -> (trace, t1, t2, !univar_pairs, None) :: cstrs - | Tarrow (arg1, ret1, _), Tarrow (arg2, ret2, _) - when Asttypes.same_arg_label arg1.lbl arg2.lbl -> + | Tarrow (arg1, ret1, a1), Tarrow (arg2, ret2, a2) + when a1 = a2 && Asttypes.same_arg_label arg1.lbl arg2.lbl -> let cstrs = subtype_rec env ((arg2.typ, arg1.typ) :: trace) diff --git a/compiler/ml/includemod.ml b/compiler/ml/includemod.ml index bd2e430030e..b91e13ef8d9 100644 --- a/compiler/ml/includemod.ml +++ b/compiler/ml/includemod.ml @@ -493,16 +493,54 @@ let show_locs ppf (loc1, loc2) = show_loc "Expected declaration" ppf loc2; show_loc "Actual declaration" ppf loc1 -let include_err ppf = function +let find_arity_mismatch env ty1 ty2 = + let rec loop seen ty1 ty2 = + let ty1 = Btype.repr ty1 in + let ty2 = Btype.repr ty2 in + if List.exists (fun (t1, t2) -> t1 == ty1 && t2 == ty2) seen then None + else + let seen = (ty1, ty2) :: seen in + let ty1 = Btype.repr (Ctype.expand_head env ty1) in + let ty2 = Btype.repr (Ctype.expand_head env ty2) in + match (ty1.desc, ty2.desc) with + | Tarrow (arg1, ret1, arity1), Tarrow (arg2, ret2, arity2) -> ( + match (arity1, arity2) with + | Some n1, Some n2 when n1 <> n2 -> Some (n1, n2) + | _ when arity1 = arity2 && Asttypes.same_arg_label arg1.lbl arg2.lbl + -> ( + match loop seen arg1.typ arg2.typ with + | Some _ as mismatch -> mismatch + | None -> loop seen ret1 ret2) + | _ -> None) + | _ -> None + in + loop [] ty1 ty2 + +let show_arity_mismatch env ppf (d1 : value_description) + (d2 : value_description) = + match find_arity_mismatch env d1.val_type d2.val_type with + | Some (n1, n2) -> + let args n = + if n = 1 then "1 argument" else string_of_int n ^ " arguments" + in + fprintf ppf + "@\n\ + @[The implementation contains a function taking %s where the interface \ + expects one taking %s.@ A function's arity is part of its type: calls \ + are compiled to plain JavaScript calls with exactly that many \ + arguments.@]" + (args n1) (args n2) + | _ -> () + +let include_symptom env ppf = function | Missing_field (id, loc, kind) -> fprintf ppf "The %s `%a' is required but not provided" kind ident id; show_loc "Expected declaration" ppf loc | Value_descriptions (id, d1, d2) -> - let curry_kind_1, curry_kind_2 = ("", "") in fprintf ppf - "@[Values do not match:@ %a%s@;<1 -2>is not included in@ %a%s@]" - (value_description id) d1 curry_kind_1 (value_description id) d2 - curry_kind_2; + "@[Values do not match:@ %a@;<1 -2>is not included in@ %a@]" + (value_description id) d1 (value_description id) d2; + show_arity_mismatch env ppf d1 d2; show_locs ppf (d1.val_loc, d2.val_loc) | Type_declarations (id, d1, d2, errs) -> fprintf ppf "@[@[%s:@;<1 2>%a@ %s@;<1 2>%a@]%a%a@]" @@ -584,7 +622,7 @@ let context ppf cxt = let include_err ppf (cxt, env, err) = Printtyp.wrap_printing_env env (fun () -> - fprintf ppf "@[%a%a@]" context (List.rev cxt) include_err err) + fprintf ppf "@[%a%a@]" context (List.rev cxt) (include_symptom env) err) let buffer = ref Bytes.empty let is_big obj = diff --git a/tests/ERROR_VARIANTS.md b/tests/ERROR_VARIANTS.md index f22efc29f25..057faaf6fa5 100644 --- a/tests/ERROR_VARIANTS.md +++ b/tests/ERROR_VARIANTS.md @@ -208,7 +208,7 @@ Source: [typecore.ml:27](../compiler/ml/typecore.ml). | `Undefined_method` | ✓ | `super_errors_multi/Cross_module_alias_dot_access`, `undefined_method` | | | `Private_type` | ✓ | `private_type_construction.res` | | | `Private_label` | ✓ | `private_label.res` | | -| `Not_subtype` | ✓ | `subtype_*.res`, `dict_show_no_coercion.res`, etc. | | +| `Not_subtype` | ✓ | `subtype_*.res`, `coercion_arity_mismatch.res`, `dict_show_no_coercion.res`, etc. | | | `Too_many_arguments` | ✓ | `too_many_arguments.res`, `moreArguments*.res` | | | `Abstract_wrong_label` | ✓ | `abstract_wrong_label.res` | Multi-arg function literal where an inner argument label doesn't match the expected arrow's label (e.g. `let f: (~a, ~b) => int = (~a, ~c) => …`). | | `Scoping_let_module` | ✓ | `scoping_let_module.res` | | @@ -353,8 +353,8 @@ Wrapper symptoms attached to inclusion failures. Source: [includemod.ml:23](../c | Variant | Status | Fixture | Notes | |---|---|---|---| | `Missing_field` | ✓ | `super_errors_multi/Iface_missing_value` | | -| `Value_descriptions` | ✓ | `super_errors_multi/Iface_value_descriptions`, `super_errors_multi/Smoke_interface_mismatch` | | -| `Type_declarations` | ✓ | `super_errors_multi/Iface_type_decl_record`, `super_errors_multi/Iface_type_decl_variant`, `RecordInclusion.res` | | +| `Value_descriptions` | ✓ | `super_errors_multi/Iface_value_descriptions`, `super_errors_multi/Iface_value_arity_mismatch`, `super_errors_multi/Smoke_interface_mismatch`, `module_sig_value_arity_mismatch*.res` | Arity mismatches print a dedicated hint (implementation vs interface argument counts), including through aliases and nested function types. | +| `Type_declarations` | ✓ | `super_errors_multi/Iface_type_decl_record`, `super_errors_multi/Iface_type_decl_variant`, `RecordInclusion.res`, `type_decl_function_arity_mismatch.res` | | | `Extension_constructors` | ✓ | `super_errors_multi/Iface_extension_constructors` | | | `Module_types` | ✓ | `super_errors_multi/Iface_module_types` | | | `Modtype_infos` | ✓ | `super_errors_multi/Iface_modtype_infos` | | @@ -377,7 +377,7 @@ Source: [includecore.ml:159](../compiler/ml/includecore.ml). | `Privacy` | ✓ | `super_errors_multi/Iface_privacy_mismatch` | | | `Kind` | ✓ | `super_errors_multi/Iface_kind_mismatch` | Record-in-impl vs variant-in-interface. | | `Constraint` | ✓ | `super_errors_multi/Iface_constraint_mismatch` | Implementation adds a `constraint 'a = …`; interface has none. | -| `Manifest` | ✓ | `super_errors_multi/Iface_manifest_mismatch` | Manifest types differ (`int` vs `string`). | +| `Manifest` | ✓ | `super_errors_multi/Iface_manifest_mismatch`, `type_decl_function_arity_mismatch.res` | Manifest types differ, including function types with different arities. | | `Variance` | ✓ | `super_errors_multi/Iface_variance_mismatch` | Interface annotates `+'a`; implementation's inferred variance differs. | | `Field_type` | ✓ | `super_errors_multi/Iface_type_decl_record` | | | `Field_mutable` | ✓ | `super_errors_multi/Iface_field_mutable_mismatch` | | diff --git a/tests/build_tests/super_errors/expected/coercion_arity_mismatch.res.expected b/tests/build_tests/super_errors/expected/coercion_arity_mismatch.res.expected new file mode 100644 index 00000000000..6fd77c58aa4 --- /dev/null +++ b/tests/build_tests/super_errors/expected/coercion_arity_mismatch.res.expected @@ -0,0 +1,9 @@ + + We've found a bug for you! + /.../fixtures/coercion_arity_mismatch.res:2:10-31 + + 1 │ let f = (x: int) => (y: int) => x + y + 2 │ let g = (f :> (int, int) => int) + 3 │ + + Type int => int => int is not a subtype of (int, int) => int \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch.res.expected b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch.res.expected new file mode 100644 index 00000000000..5a5bd8c9060 --- /dev/null +++ b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch.res.expected @@ -0,0 +1,30 @@ + + We've found a bug for you! + /.../fixtures/module_sig_value_arity_mismatch.res:3:5-5:1 + + 1 │ module M: { + 2 │ let f: (int, int) => int + 3 │ } = { + 4 │  let f = (x: int) => (y: int) => x + y + 5 │ } + 6 │ + + Signature mismatch: + Modules do not match: + { + let f: int => int => int +} + is not included in + { + let f: (int, int) => int +} + Values do not match: + let f: int => int => int + is not included in + let f: (int, int) => int + The implementation contains a function taking 1 argument where the interface expects one taking 2 arguments. + A function's arity is part of its type: calls are compiled to plain JavaScript calls with exactly that many arguments. + /.../fixtures/module_sig_value_arity_mismatch.res:2:3-26: + Expected declaration + /.../fixtures/module_sig_value_arity_mismatch.res:4:7: + Actual declaration diff --git a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_alias.res.expected b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_alias.res.expected new file mode 100644 index 00000000000..ccbef6832ee --- /dev/null +++ b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_alias.res.expected @@ -0,0 +1,30 @@ + + We've found a bug for you! + /.../fixtures/module_sig_value_arity_mismatch_alias.res:5:5-7:1 + + 3 │ module M: { + 4 │ let f: (int, int) => int + 5 │ } = { + 6 │  let f: curried = (x: int) => (_y: int) => x + 7 │ } + 8 │ + + Signature mismatch: + Modules do not match: + { + let f: curried +} + is not included in + { + let f: (int, int) => int +} + Values do not match: + let f: curried + is not included in + let f: (int, int) => int + The implementation contains a function taking 1 argument where the interface expects one taking 2 arguments. + A function's arity is part of its type: calls are compiled to plain JavaScript calls with exactly that many arguments. + /.../fixtures/module_sig_value_arity_mismatch_alias.res:4:3-26: + Expected declaration + /.../fixtures/module_sig_value_arity_mismatch_alias.res:6:7: + Actual declaration diff --git a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_nested.res.expected b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_nested.res.expected new file mode 100644 index 00000000000..4931e12ac36 --- /dev/null +++ b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_nested.res.expected @@ -0,0 +1,30 @@ + + We've found a bug for you! + /.../fixtures/module_sig_value_arity_mismatch_nested.res:3:5-5:1 + + 1 │ module M: { + 2 │ let f: int => (int, int) => int + 3 │ } = { + 4 │  let f = (_x: int) => (y: int) => (_z: int) => y + 5 │ } + 6 │ + + Signature mismatch: + Modules do not match: + { + let f: int => int => int => int +} + is not included in + { + let f: int => (int, int) => int +} + Values do not match: + let f: int => int => int => int + is not included in + let f: int => (int, int) => int + The implementation contains a function taking 1 argument where the interface expects one taking 2 arguments. + A function's arity is part of its type: calls are compiled to plain JavaScript calls with exactly that many arguments. + /.../fixtures/module_sig_value_arity_mismatch_nested.res:2:3-33: + Expected declaration + /.../fixtures/module_sig_value_arity_mismatch_nested.res:4:7: + Actual declaration diff --git a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_reverse.res.expected b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_reverse.res.expected new file mode 100644 index 00000000000..f51ffd5b822 --- /dev/null +++ b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_reverse.res.expected @@ -0,0 +1,30 @@ + + We've found a bug for you! + /.../fixtures/module_sig_value_arity_mismatch_reverse.res:3:5-5:1 + + 1 │ module M: { + 2 │ let f: int => int => int + 3 │ } = { + 4 │  let f = (x: int, _y: int) => x + 5 │ } + 6 │ + + Signature mismatch: + Modules do not match: + { + let f: (int, int) => int +} + is not included in + { + let f: int => int => int +} + Values do not match: + let f: (int, int) => int + is not included in + let f: int => int => int + The implementation contains a function taking 2 arguments where the interface expects one taking 1 argument. + A function's arity is part of its type: calls are compiled to plain JavaScript calls with exactly that many arguments. + /.../fixtures/module_sig_value_arity_mismatch_reverse.res:2:3-26: + Expected declaration + /.../fixtures/module_sig_value_arity_mismatch_reverse.res:4:7: + Actual declaration diff --git a/tests/build_tests/super_errors/expected/type_decl_function_arity_mismatch.res.expected b/tests/build_tests/super_errors/expected/type_decl_function_arity_mismatch.res.expected new file mode 100644 index 00000000000..14e91bcf247 --- /dev/null +++ b/tests/build_tests/super_errors/expected/type_decl_function_arity_mismatch.res.expected @@ -0,0 +1,28 @@ + + We've found a bug for you! + /.../fixtures/type_decl_function_arity_mismatch.res:3:5-5:1 + + 1 │ module M: { + 2 │ type t = (int, int) => int + 3 │ } = { + 4 │  type t = int => int => int + 5 │ } + 6 │ + + Signature mismatch: + Modules do not match: + { + type t = int => int => int +} + is not included in + { + type t = (int, int) => int +} + Type declarations do not match: + type t = int => int => int + is not included in + type t = (int, int) => int + /.../fixtures/type_decl_function_arity_mismatch.res:2:3-28: + Expected declaration + /.../fixtures/type_decl_function_arity_mismatch.res:4:3-28: + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/coercion_arity_mismatch.res b/tests/build_tests/super_errors/fixtures/coercion_arity_mismatch.res new file mode 100644 index 00000000000..93d9111360e --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/coercion_arity_mismatch.res @@ -0,0 +1,2 @@ +let f = (x: int) => (y: int) => x + y +let g = (f :> (int, int) => int) diff --git a/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch.res b/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch.res new file mode 100644 index 00000000000..a335585ac0a --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch.res @@ -0,0 +1,5 @@ +module M: { + let f: (int, int) => int +} = { + let f = (x: int) => (y: int) => x + y +} diff --git a/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch_alias.res b/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch_alias.res new file mode 100644 index 00000000000..a1f5b776121 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch_alias.res @@ -0,0 +1,7 @@ +type curried = int => int => int + +module M: { + let f: (int, int) => int +} = { + let f: curried = (x: int) => (_y: int) => x +} diff --git a/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch_nested.res b/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch_nested.res new file mode 100644 index 00000000000..5f54347910b --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch_nested.res @@ -0,0 +1,5 @@ +module M: { + let f: int => (int, int) => int +} = { + let f = (_x: int) => (y: int) => (_z: int) => y +} diff --git a/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch_reverse.res b/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch_reverse.res new file mode 100644 index 00000000000..1a12806b5a8 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/module_sig_value_arity_mismatch_reverse.res @@ -0,0 +1,5 @@ +module M: { + let f: int => int => int +} = { + let f = (x: int, _y: int) => x +} diff --git a/tests/build_tests/super_errors/fixtures/type_decl_function_arity_mismatch.res b/tests/build_tests/super_errors/fixtures/type_decl_function_arity_mismatch.res new file mode 100644 index 00000000000..4a831f446dd --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/type_decl_function_arity_mismatch.res @@ -0,0 +1,5 @@ +module M: { + type t = (int, int) => int +} = { + type t = int => int => int +} diff --git a/tests/build_tests/super_errors_multi/expected/Iface_value_arity_mismatch.expected b/tests/build_tests/super_errors_multi/expected/Iface_value_arity_mismatch.expected new file mode 100644 index 00000000000..15dc9811e61 --- /dev/null +++ b/tests/build_tests/super_errors_multi/expected/Iface_value_arity_mismatch.expected @@ -0,0 +1,20 @@ +===== Foo.res ===== + + We've found a bug for you! + /.../fixtures/Iface_value_arity_mismatch/Foo.res:1:5 + + 1 │ let f = (x: int) => (y: int) => x + y + 2 │ + + The implementation /.../fixtures/Iface_value_arity_mismatch/Foo.res + does not match the interface /.../fixtures/Iface_value_arity_mismatch/foo.cmi: + Values do not match: + let f: int => int => int + is not included in + let f: (int, int) => int + The implementation contains a function taking 1 argument where the interface expects one taking 2 arguments. + A function's arity is part of its type: calls are compiled to plain JavaScript calls with exactly that many arguments. + /.../fixtures/Iface_value_arity_mismatch/Foo.resi:1:1-24: + Expected declaration + /.../fixtures/Iface_value_arity_mismatch/Foo.res:1:5: + Actual declaration diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_value_arity_mismatch/Foo.res b/tests/build_tests/super_errors_multi/fixtures/Iface_value_arity_mismatch/Foo.res new file mode 100644 index 00000000000..84288dfea8d --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_value_arity_mismatch/Foo.res @@ -0,0 +1 @@ +let f = (x: int) => (y: int) => x + y diff --git a/tests/build_tests/super_errors_multi/fixtures/Iface_value_arity_mismatch/Foo.resi b/tests/build_tests/super_errors_multi/fixtures/Iface_value_arity_mismatch/Foo.resi new file mode 100644 index 00000000000..7724dd35de5 --- /dev/null +++ b/tests/build_tests/super_errors_multi/fixtures/Iface_value_arity_mismatch/Foo.resi @@ -0,0 +1 @@ +let f: (int, int) => int From 4dd2af48397f5e2d44fec73fdcf3acd93400384a Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Wed, 19 Aug 2026 09:27:17 +0200 Subject: [PATCH 2/2] Shorten the arity mismatch error message Drop the explanatory second sentence from the signature arity mismatch error; the first sentence already states the problem. Co-Authored-By: Claude Fable 5 Signed-off-by: Cristiano Calcagno --- compiler/ml/includemod.ml | 4 +--- .../expected/module_sig_value_arity_mismatch.res.expected | 3 +-- .../module_sig_value_arity_mismatch_alias.res.expected | 3 +-- .../module_sig_value_arity_mismatch_nested.res.expected | 3 +-- .../module_sig_value_arity_mismatch_reverse.res.expected | 3 +-- .../expected/Iface_value_arity_mismatch.expected | 3 +-- 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/compiler/ml/includemod.ml b/compiler/ml/includemod.ml index b91e13ef8d9..a6e872441bb 100644 --- a/compiler/ml/includemod.ml +++ b/compiler/ml/includemod.ml @@ -526,9 +526,7 @@ let show_arity_mismatch env ppf (d1 : value_description) fprintf ppf "@\n\ @[The implementation contains a function taking %s where the interface \ - expects one taking %s.@ A function's arity is part of its type: calls \ - are compiled to plain JavaScript calls with exactly that many \ - arguments.@]" + expects one taking %s.@]" (args n1) (args n2) | _ -> () diff --git a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch.res.expected b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch.res.expected index 5a5bd8c9060..7e3f3839293 100644 --- a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch.res.expected +++ b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch.res.expected @@ -23,8 +23,7 @@ is not included in let f: (int, int) => int The implementation contains a function taking 1 argument where the interface expects one taking 2 arguments. - A function's arity is part of its type: calls are compiled to plain JavaScript calls with exactly that many arguments. /.../fixtures/module_sig_value_arity_mismatch.res:2:3-26: Expected declaration /.../fixtures/module_sig_value_arity_mismatch.res:4:7: - Actual declaration + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_alias.res.expected b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_alias.res.expected index ccbef6832ee..71b4ef3810f 100644 --- a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_alias.res.expected +++ b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_alias.res.expected @@ -23,8 +23,7 @@ is not included in let f: (int, int) => int The implementation contains a function taking 1 argument where the interface expects one taking 2 arguments. - A function's arity is part of its type: calls are compiled to plain JavaScript calls with exactly that many arguments. /.../fixtures/module_sig_value_arity_mismatch_alias.res:4:3-26: Expected declaration /.../fixtures/module_sig_value_arity_mismatch_alias.res:6:7: - Actual declaration + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_nested.res.expected b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_nested.res.expected index 4931e12ac36..8c704527b90 100644 --- a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_nested.res.expected +++ b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_nested.res.expected @@ -23,8 +23,7 @@ is not included in let f: int => (int, int) => int The implementation contains a function taking 1 argument where the interface expects one taking 2 arguments. - A function's arity is part of its type: calls are compiled to plain JavaScript calls with exactly that many arguments. /.../fixtures/module_sig_value_arity_mismatch_nested.res:2:3-33: Expected declaration /.../fixtures/module_sig_value_arity_mismatch_nested.res:4:7: - Actual declaration + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_reverse.res.expected b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_reverse.res.expected index f51ffd5b822..ac11162f097 100644 --- a/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_reverse.res.expected +++ b/tests/build_tests/super_errors/expected/module_sig_value_arity_mismatch_reverse.res.expected @@ -23,8 +23,7 @@ is not included in let f: int => int => int The implementation contains a function taking 2 arguments where the interface expects one taking 1 argument. - A function's arity is part of its type: calls are compiled to plain JavaScript calls with exactly that many arguments. /.../fixtures/module_sig_value_arity_mismatch_reverse.res:2:3-26: Expected declaration /.../fixtures/module_sig_value_arity_mismatch_reverse.res:4:7: - Actual declaration + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Iface_value_arity_mismatch.expected b/tests/build_tests/super_errors_multi/expected/Iface_value_arity_mismatch.expected index 15dc9811e61..4001195e3ba 100644 --- a/tests/build_tests/super_errors_multi/expected/Iface_value_arity_mismatch.expected +++ b/tests/build_tests/super_errors_multi/expected/Iface_value_arity_mismatch.expected @@ -13,8 +13,7 @@ is not included in let f: (int, int) => int The implementation contains a function taking 1 argument where the interface expects one taking 2 arguments. - A function's arity is part of its type: calls are compiled to plain JavaScript calls with exactly that many arguments. /.../fixtures/Iface_value_arity_mismatch/Foo.resi:1:1-24: Expected declaration /.../fixtures/Iface_value_arity_mismatch/Foo.res:1:5: - Actual declaration + Actual declaration \ No newline at end of file