From 1db203e9be49227dc9eed6e7241c628d9736889a Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Tue, 27 Oct 2026 12:41:26 +0000 Subject: [PATCH] Word signature mismatch errors in ReScript terms Why: Inclusion errors still used OCaml wording: variant constructors were called fields, functor positions were written as `module F(X) : ...`, names were quoted as `x', and extension constructors were printed with two spaces after `+=`. Diagnostic modified (variant constructor mismatch): Example: module M: { type rec t<_> = Int(int): t | Str(string): t } = { type rec t<_> = Int(int): t | Str(string): t } Before (diagnostic): The types for field Str are not equal. After (diagnostic): The types for constructor Str are not equal. Diagnostic modified (functor position): Example: module type S = { type t } module M: { module F: (X: S) => S with type t = X.t } = { module F = (X: S) => { type t = int } } Before (diagnostic): At position module F(X) : After (diagnostic): At position module F: (X) => Diagnostic modified (missing value): Before (diagnostic): The value `b' is required but not provided After (diagnostic): The value `b` is required but not provided Diagnostic modified (extension constructor): Before (diagnostic): type t += Boom(string) After (diagnostic): type t += Boom(string) Review: Do record fields keep their wording? Yes. Only the comparison of variant constructors uses the new constructor errors; inline record fields of a constructor are still compared as fields. Fixes: #9066 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Kbf3gLMawVgtmiKy3TMBc3 Signed-off-by: Christoph Knittel --- changes/inclusion-error-wording.polish.md | 1 + compiler/ml/includecore.ml | 30 ++++++++--- compiler/ml/includecore.mli | 4 ++ compiler/ml/includemod.ml | 15 ++---- compiler/syntax/src/res_outcome_printer.ml | 2 +- .../functor_apply_arg_mismatch.res.expected | 2 +- .../functor_signature_mismatch.res.expected | 53 +++++++++++++++++++ ...onstructor_signature_mismatch.res.expected | 22 ++++++++ ...tension_constructors_mismatch.res.expected | 4 +- .../include_missing_field.res.expected | 2 +- .../fixtures/functor_signature_mismatch.res | 11 ++++ .../gadt_constructor_signature_mismatch.res | 5 ++ .../Iface_extension_constructors.expected | 4 +- .../Iface_field_arity_mismatch.expected | 2 +- .../expected/Iface_missing_value.expected | 2 +- .../expected/Iface_type_decl_variant.expected | 2 +- .../Iface_variant_extra_constructor.expected | 2 +- 17 files changed, 134 insertions(+), 29 deletions(-) create mode 100644 changes/inclusion-error-wording.polish.md create mode 100644 tests/build_tests/super_errors/expected/functor_signature_mismatch.res.expected create mode 100644 tests/build_tests/super_errors/expected/gadt_constructor_signature_mismatch.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/functor_signature_mismatch.res create mode 100644 tests/build_tests/super_errors/fixtures/gadt_constructor_signature_mismatch.res diff --git a/changes/inclusion-error-wording.polish.md b/changes/inclusion-error-wording.polish.md new file mode 100644 index 00000000000..510a73ecae8 --- /dev/null +++ b/changes/inclusion-error-wording.polish.md @@ -0,0 +1 @@ +- Signature mismatch errors name variant constructors as constructors, write functor positions and quotes in ReScript syntax, and print `type t += A` with a single space. diff --git a/compiler/ml/includecore.ml b/compiler/ml/includecore.ml index 5b86154e005..99414e90784 100644 --- a/compiler/ml/includecore.ml +++ b/compiler/ml/includecore.ml @@ -179,6 +179,10 @@ type type_mismatch = | Field_arity of Ident.t | Field_names of int * string * string | Field_missing of bool * Ident.t + | Constructor_type of Ident.t + | Constructor_arity of Ident.t + | Constructor_names of int * string * string + | Constructor_missing of bool * Ident.t | Record_representation of record_representation * record_representation | Unboxed_representation of bool (* true means second one is unboxed *) | Immediate @@ -208,6 +212,16 @@ let report_type_mismatch0 first second decl ppf err = pr "The field %s is only present in %s %s" (Ident.name s) (if b then second else first) decl + | Constructor_type s -> + pr "The types for constructor %s are not equal" (Ident.name s) + | Constructor_arity s -> + pr "The arities for constructor %s differ" (Ident.name s) + | Constructor_names (n, name1, name2) -> + pr "Constructors number %i have different names, %s and %s" n name1 name2 + | Constructor_missing (b, s) -> + pr "The constructor %s is only present in %s %s" (Ident.name s) + (if b then second else first) + decl | Record_representation (_rep1, _rep2) -> pr "Their internal representations differ" | Unboxed_representation b -> @@ -237,26 +251,26 @@ let report_type_mismatch first second decl ppf = let rec compare_constructor_arguments env cstr params1 params2 arg1 arg2 = match (arg1, arg2) with | Types.Cstr_tuple arg1, Types.Cstr_tuple arg2 -> - if List.length arg1 <> List.length arg2 then [Field_arity cstr] + if List.length arg1 <> List.length arg2 then [Constructor_arity cstr] else if (* Ctype.equal must be called on all arguments at once, cf. PR#7378 *) Ctype.equal env true (params1 @ arg1) (params2 @ arg2) then [] - else [Field_type cstr] + else [Constructor_type cstr] | Types.Cstr_record l1, Types.Cstr_record l2 -> compare_records env params1 params2 0 l1 l2 - | _ -> [Field_type cstr] + | _ -> [Constructor_type cstr] and compare_variants ~loc env params1 params2 ~layout1 ~layout2 n (cstrs1 : Types.constructor_declaration list) (cstrs2 : Types.constructor_declaration list) = match (cstrs1, cstrs2) with | [], [] -> [] - | [], c :: _ -> [Field_missing (true, c.Types.cd_id)] - | c :: _, [] -> [Field_missing (false, c.Types.cd_id)] + | [], c :: _ -> [Constructor_missing (true, c.Types.cd_id)] + | c :: _, [] -> [Constructor_missing (false, c.Types.cd_id)] | cd1 :: rem1, cd2 :: rem2 -> if Ident.name cd1.cd_id <> Ident.name cd2.cd_id then - [Field_names (n, cd1.cd_id.name, cd2.cd_id.name)] + [Constructor_names (n, cd1.cd_id.name, cd2.cd_id.name)] else ( Builtin_attributes.check_deprecated_inclusion ~def:cd1.cd_loc ~use:cd2.cd_loc loc cd1.cd_attributes cd2.cd_attributes @@ -267,8 +281,8 @@ and compare_variants ~loc env params1 params2 ~layout1 ~layout2 n if Ctype.equal env true [r1] [r2] then compare_constructor_arguments env cd1.cd_id [r1] [r2] cd1.cd_args cd2.cd_args - else [Field_type cd1.cd_id] - | Some _, None | None, Some _ -> [Field_type cd1.cd_id] + else [Constructor_type cd1.cd_id] + | Some _, None | None, Some _ -> [Constructor_type cd1.cd_id] | _ -> compare_constructor_arguments env cd1.cd_id params1 params2 cd1.cd_args cd2.cd_args diff --git a/compiler/ml/includecore.mli b/compiler/ml/includecore.mli index eec47e1f2f9..96679120ecc 100644 --- a/compiler/ml/includecore.mli +++ b/compiler/ml/includecore.mli @@ -33,6 +33,10 @@ type type_mismatch = | Field_arity of Ident.t | Field_names of int * string * string | Field_missing of bool * Ident.t + | Constructor_type of Ident.t + | Constructor_arity of Ident.t + | Constructor_names of int * string * string + | Constructor_missing of bool * Ident.t | Record_representation of record_representation * record_representation | Unboxed_representation of bool | Immediate diff --git a/compiler/ml/includemod.ml b/compiler/ml/includemod.ml index 890c6c97ded..e3f7fcc5682 100644 --- a/compiler/ml/includemod.ml +++ b/compiler/ml/includemod.ml @@ -543,7 +543,7 @@ let show_arity_mismatch env ppf (d1 : value_description) let include_symptom env ppf = function | Missing_field (id, loc, kind) -> - fprintf ppf "The %s `%a' is required but not provided" kind ident id; + fprintf ppf "The %s `%a` is required but not provided" kind ident id; show_loc "Expected declaration" ppf loc | Value_descriptions (id, d1, d2) -> fprintf ppf @@ -588,21 +588,16 @@ let rec context ppf = function | Module id :: rem -> fprintf ppf "@[<2>module %a%a@]" ident id args rem | Modtype id :: rem -> fprintf ppf "@[<2>module type %a =@ %a@]" ident id context_mty rem - | Body x :: rem -> - fprintf ppf "functor (%s) ->@ %a" (argname x) context_mty rem - | Arg x :: rem -> - fprintf ppf "functor (%a : %a) -> ..." ident x context_mty rem + | Body x :: rem -> fprintf ppf "(%s) =>@ %a" (argname x) context_mty rem + | Arg x :: rem -> fprintf ppf "(%a:@ %a) => ..." ident x context_mty rem | [] -> fprintf ppf "" and context_mty ppf = function | (Module _ | Modtype _) :: _ as rem -> - fprintf ppf "@[<2>sig@ %a@;<1 -2>end@]" context rem + fprintf ppf "@[<2>{@ %a@;<1 -2>}@]" context rem | cxt -> context ppf cxt -and args ppf = function - | Body x :: rem -> fprintf ppf "(%s)%a" (argname x) args rem - | Arg x :: rem -> fprintf ppf "(%a :@ %a) : ..." ident x context_mty rem - | cxt -> fprintf ppf " :@ %a" context_mty cxt +and args ppf cxt = fprintf ppf ":@ %a" context_mty cxt and argname x = let s = Ident.name x in diff --git a/compiler/syntax/src/res_outcome_printer.ml b/compiler/syntax/src/res_outcome_printer.ml index 67ecf86a340..cabef35d205 100644 --- a/compiler/syntax/src/res_outcome_printer.ml +++ b/compiler/syntax/src/res_outcome_printer.ml @@ -889,7 +889,7 @@ and print_out_extension_constructor_doc Doc.text "type "; Printer.print_ident_like ~allow_uident:false out_ext.oext_type_name; type_params; - Doc.text " += "; + Doc.text " +="; Doc.line; (if out_ext.oext_private = Asttypes.Private then Doc.text "private " else Doc.nil); diff --git a/tests/build_tests/super_errors/expected/functor_apply_arg_mismatch.res.expected b/tests/build_tests/super_errors/expected/functor_apply_arg_mismatch.res.expected index 763249f6187..d027d93b243 100644 --- a/tests/build_tests/super_errors/expected/functor_apply_arg_mismatch.res.expected +++ b/tests/build_tests/super_errors/expected/functor_apply_arg_mismatch.res.expected @@ -15,6 +15,6 @@ } is not included in HasFoo - The value `foo' is required but not provided + The value `foo` is required but not provided /.../fixtures/functor_apply_arg_mismatch.res:2:3-14: Expected declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/functor_signature_mismatch.res.expected b/tests/build_tests/super_errors/expected/functor_signature_mismatch.res.expected new file mode 100644 index 00000000000..1be12f852e9 --- /dev/null +++ b/tests/build_tests/super_errors/expected/functor_signature_mismatch.res.expected @@ -0,0 +1,53 @@ + + We've found a bug for you! + /.../fixtures/functor_signature_mismatch.res:7:5-11:1 + + 5 │ module M: { + 6 │ module F: (X: S) => (S with type t = X.t) + 7 │ } = { + 8 │  module F = (X: S) => { + 9 │  type t = int + 10 │  } + 11 │ } + 12 │ + + Signature mismatch: + Modules do not match: + { + module F: (X: S) => { + type t = int + } + } + is not included in + { + module F: (X: S) => { + type t = X.t + } + } + In module F: + Modules do not match: + (X: S) => { + type t = int + } + is not included in + (X: S) => { + type t = X.t + } + At position module F: (X) => + Modules do not match: + { + type t = int + } + is not included in + { + type t = X.t + } + At position module F: (X) => + Type declarations do not match: + type t = int + is not included in + type t = X.t + /.../fixtures/functor_signature_mismatch.res:6:36: + Expected declaration + /.../fixtures/functor_signature_mismatch.res:9:5-16: + Actual declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/gadt_constructor_signature_mismatch.res.expected b/tests/build_tests/super_errors/expected/gadt_constructor_signature_mismatch.res.expected new file mode 100644 index 00000000000..e9edddbcf49 --- /dev/null +++ b/tests/build_tests/super_errors/expected/gadt_constructor_signature_mismatch.res.expected @@ -0,0 +1,22 @@ + + We've found a bug for you! + /.../fixtures/gadt_constructor_signature_mismatch.res:3:5-5:1 + + 1 │ module M: { + 2 │ type rec t<_> = Int(int): t | Str(string): t + 3 │ } = { + 4 │  type rec t<_> = Int(int): t | Str(string): t + 5 │ } + 6 │ + + Signature mismatch: + ... + Type declarations do not match: + type t<_> = Int(int): t | Str(string): t + is not included in + type t<_> = Int(int): t | Str(string): t + /.../fixtures/gadt_constructor_signature_mismatch.res:2:3-59: + Expected declaration + /.../fixtures/gadt_constructor_signature_mismatch.res:4:3-56: + Actual declaration + The types for constructor Str are not equal. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/include_extension_constructors_mismatch.res.expected b/tests/build_tests/super_errors/expected/include_extension_constructors_mismatch.res.expected index 8e522abbc2b..2866c6a589b 100644 --- a/tests/build_tests/super_errors/expected/include_extension_constructors_mismatch.res.expected +++ b/tests/build_tests/super_errors/expected/include_extension_constructors_mismatch.res.expected @@ -22,9 +22,9 @@ type t += E(int) } Extension declarations do not match: - type t += E(string) + type t += E(string) is not included in - type t += E(int) + type t += E(int) /.../fixtures/include_extension_constructors_mismatch.res:3:13-18: Expected declaration /.../fixtures/include_extension_constructors_mismatch.res:6:13-21: diff --git a/tests/build_tests/super_errors/expected/include_missing_field.res.expected b/tests/build_tests/super_errors/expected/include_missing_field.res.expected index cc755057906..82f498a3bfb 100644 --- a/tests/build_tests/super_errors/expected/include_missing_field.res.expected +++ b/tests/build_tests/super_errors/expected/include_missing_field.res.expected @@ -19,6 +19,6 @@ let x: int let y: string } - The value `y' is required but not provided + The value `y` is required but not provided /.../fixtures/include_missing_field.res:3:3-15: Expected declaration \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/functor_signature_mismatch.res b/tests/build_tests/super_errors/fixtures/functor_signature_mismatch.res new file mode 100644 index 00000000000..9dcfe156df1 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/functor_signature_mismatch.res @@ -0,0 +1,11 @@ +module type S = { + type t +} + +module M: { + module F: (X: S) => (S with type t = X.t) +} = { + module F = (X: S) => { + type t = int + } +} diff --git a/tests/build_tests/super_errors/fixtures/gadt_constructor_signature_mismatch.res b/tests/build_tests/super_errors/fixtures/gadt_constructor_signature_mismatch.res new file mode 100644 index 00000000000..67a898dc9ee --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/gadt_constructor_signature_mismatch.res @@ -0,0 +1,5 @@ +module M: { + type rec t<_> = Int(int): t | Str(string): t +} = { + type rec t<_> = Int(int): t | Str(string): t +} 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 index ac624fa56ff..1b116eed3fd 100644 --- a/tests/build_tests/super_errors_multi/expected/Iface_extension_constructors.expected +++ b/tests/build_tests/super_errors_multi/expected/Iface_extension_constructors.expected @@ -6,9 +6,9 @@ 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) + type t += Boom(string) is not included in - type t += Boom(int) + type t += Boom(int) /.../fixtures/Iface_extension_constructors/Foo.res: Expected declaration /.../fixtures/Iface_extension_constructors/Foo.res:4:3-16: diff --git a/tests/build_tests/super_errors_multi/expected/Iface_field_arity_mismatch.expected b/tests/build_tests/super_errors_multi/expected/Iface_field_arity_mismatch.expected index 5f1609f1f95..92021a38533 100644 --- a/tests/build_tests/super_errors_multi/expected/Iface_field_arity_mismatch.expected +++ b/tests/build_tests/super_errors_multi/expected/Iface_field_arity_mismatch.expected @@ -16,4 +16,4 @@ Expected declaration /.../fixtures/Iface_field_arity_mismatch/Foo.res:1:1-19: Actual declaration - The arities for field A differ. \ No newline at end of file + The arities for constructor A differ. \ 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 index b69da19d07c..afd9c825542 100644 --- a/tests/build_tests/super_errors_multi/expected/Iface_missing_value.expected +++ b/tests/build_tests/super_errors_multi/expected/Iface_missing_value.expected @@ -9,6 +9,6 @@ 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 + 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_type_decl_variant.expected b/tests/build_tests/super_errors_multi/expected/Iface_type_decl_variant.expected index e3d5b75c8d4..0805c41df8c 100644 --- 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 @@ -18,4 +18,4 @@ 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 + The types for constructor B are not equal. \ No newline at end of file diff --git a/tests/build_tests/super_errors_multi/expected/Iface_variant_extra_constructor.expected b/tests/build_tests/super_errors_multi/expected/Iface_variant_extra_constructor.expected index 24f4bd78925..f8d5e277dee 100644 --- a/tests/build_tests/super_errors_multi/expected/Iface_variant_extra_constructor.expected +++ b/tests/build_tests/super_errors_multi/expected/Iface_variant_extra_constructor.expected @@ -16,4 +16,4 @@ Expected declaration /.../fixtures/Iface_variant_extra_constructor/Foo.res:1:1-18: Actual declaration - The field C is only present in the first declaration. \ No newline at end of file + The constructor C is only present in the first declaration. \ No newline at end of file