Skip to content

Show signature mismatches as the actual and expected declaration, each under its location - #9140

Merged
cknitt merged 4 commits into
masterfrom
signature-mismatch-layout
Oct 30, 2026
Merged

cknitt merged 4 commits into
masterfrom
signature-mismatch-layout

Conversation

@cknitt

@cknitt cknitt commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Signature mismatch errors printed every enclosing module in full, then the two declarations, then their locations as file:line: lines with a label below them, and the reasons without a break. This PR lays them out like the type checker's errors: the actual and the expected declaration as two blocks, each under its label and location, separated by blank lines, and only the items that differ, under the module path to them.

Before:

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
Foo.resi:2:3-16:
  Expected declaration
Foo.res:2:7-11:
  Actual declaration

After:

In module Inner:

Actual declaration (Foo.res:2:7-11):
  let value: string

Expected declaration (Foo.resi:2:3-16):
  let value: int

What changes:

  • Includemod.report_error leaves out a module or module type comparison when an item inside it follows, so the "..." size check is gone. The path reads "In module P:", "In module type P.T:", "In the result of functor P:" or "In the parameter X of functor P:", with "At position …" for anything else.
  • Values, type declarations, extension constructors and module type declarations print as the two blocks. A missing item prints the expected declaration under its location. A module-level mismatch prints "Actual module type:" / "Expected module type:".
  • The reasons say "the actual" and "the expected" declaration instead of "the first" and "the second". The runtime-representation reasons indent their two lines under the lead-in and label the two sides like the declaration blocks: Actual declaration: typeof "object" instead of the first declaration states values whose typeof is "object".

Bugs fixed along the way:

  1. Extension constructors and exceptions declared in an interface lost their location. Subst.extension_constructor dropped it when writing the .cmi, unlike every other declaration, so the expected side of an extension mismatch printed as Foo.res: with no position (and pointed at the implementation). It is kept now. As a side effect, reanalyze also records references to exceptions declared in an interface: two new lines in the deadcode debug trace, with the findings unchanged.
  2. Missing items were listed in reverse order. They now follow the order of the expected signature.
  3. Where inclusion is checked the other way round, "actual" and "expected" were swapped. That is the case in a functor's parameter and in the second half of the equality check of two module types. "Actual declaration" pointed at the signature, and an item only the implementation's module type has was reported as "required but not provided" on the expected side. The implementation's side is now the actual declaration, in the labels, the reasons and the arity sentence, and such an item reads "only present in the actual declaration".
  4. A module where a functor is expected printed as a one-line comparison of empty-looking module types. It now shows the two module types as blocks.
  5. A module type declaration in a signature had no location in the parser, unlike one in a structure, so it could not be pointed at. It now spans from the name to the end of the declaration.

New tests: tests/build_tests/super_errors/fixtures/inclusion_layout_*.res cover several missing values in one module, a module where a functor is expected, a functor parameter, a module type in a nested module, and an item only the actual module type has.

#9133 (matching fields and constructors by name) and #9136 (printing @tag and runtime annotations) will be rebased on top of this.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Kbf3gLpf7RN1MiRcubPgRn

…h under its location

Why:
    A signature mismatch printed every enclosing module in full, then the two
    declarations, then their locations as "file:line:" lines with a label on
    the next line that read like headings with nothing under them, and the
    reasons without a break. Which text was where had to be matched up by
    hand, and declarations that read the same looked like a declaration not
    including itself. The type checker's errors separate their parts with
    blank lines and indent the content under a lead-in.

Includemod.report_error now leaves out a module or module type comparison
when an item inside it follows, so that only the items that differ are
printed, each after a line naming where it is: "In module P:", "In module
type P.T:", "In the result of functor P:", "In the parameter X of functor
P:", or "At position ..." otherwise, once for items at the same position.
Missing items are listed in the order of the expected signature, and in a
functor's parameter, where inclusion is reversed, the implementation's side
is still the actual declaration.

Diagnostic modified (type declaration in an interface):
Example:
    // Foo.res
    type t = {a: int, b: int}
    // Foo.resi
    type t = {x: int, y: int}
Before (diagnostic):
    The implementation Foo.res
    does not match the interface foo.cmi:
    Type declarations do not match:
      type t = {a: int, b: int}
    is not included in
      type t = {x: int, y: int}
    Foo.resi:1:1-25:
      Expected declaration
    Foo.res:1:1-25:
      Actual declaration
    Fields number 1 have different names, a and x.
After (diagnostic):
    The implementation Foo.res
    does not match the interface foo.cmi:

    Actual declaration (Foo.res:1:1-25):
      type t = {a: int, b: int}

    Expected declaration (Foo.resi:1:1-25):
      type t = {x: int, y: int}

    Fields number 1 have different names, a and x.

Diagnostic modified (value in a nested module):
Example:
    // Foo.res
    module Inner = {
      let value = "not an int"
    }
    // Foo.resi
    module Inner: {
      let value: int
    }
Before (diagnostic):
    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
    Foo.resi:2:3-16:
      Expected declaration
    Foo.res:2:7-11:
      Actual declaration
After (diagnostic):
    In module Inner:

    Actual declaration (Foo.res:2:7-11):
      let value: string

    Expected declaration (Foo.resi:2:3-16):
      let value: int

Diagnostic modified (functor result):
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):
    Signature mismatch:
    Modules do not match:
      …
    In module F:
    Modules do not match:
      …
    At position module F: (X) => <here>
    Modules do not match:
      …
    At position module F: (X) => <here>
    Type declarations do not match:
      type t = int
    is not included in
      type t = X.t
    functor_signature_mismatch.res:6:36:
      Expected declaration
    functor_signature_mismatch.res:9:5-16:
      Actual declaration
After (diagnostic):
    Signature mismatch:

    In the result of functor F:

    Actual declaration (functor_signature_mismatch.res:9:5-16):
      type t = int

    Expected declaration (functor_signature_mismatch.res:6:36):
      type t = X.t

Diagnostic modified (functor parameter):
Example:
    module M: {
      module F: (X: {let a: int}) => {}
    } = {
      module F = (X: {let a: string}) => {}
    }
Before (diagnostic):
    Signature mismatch:
    Modules do not match:
    … the module, then the functor, each pair printed in full
    At position module F: (X: <here>) => ...
    Values do not match: let a: int is not included in let a: string
    inclusion_layout_functor_parameter.res:12:7-19:
      Expected declaration
    inclusion_layout_functor_parameter.res:6:7-16:
      Actual declaration
After (diagnostic):
    In the parameter X of functor F:

    Actual declaration (inclusion_layout_functor_parameter.res:12:7-19):
      let a: string

    Expected declaration (inclusion_layout_functor_parameter.res:6:7-16):
      let a: int

Diagnostic modified (missing values):
Example:
    module M: {
      module N: {
        let a: int
        let b: string
      }
    } = {
      module N = {}
    }
Before (diagnostic):
    Signature mismatch:
    Modules do not match:
    … the module, then module N, each pair printed in full
    In module N:
    The value `b` is required but not provided
    inclusion_layout_missing_values.res:5:5-17:
      Expected declaration
    In module N:
    The value `a` is required but not provided
    inclusion_layout_missing_values.res:4:5-14:
      Expected declaration
After (diagnostic):
    In module N:

    The value `a` is required but not provided.

    Expected declaration (inclusion_layout_missing_values.res:4:5-14):
      let a: int

    The value `b` is required but not provided.

    Expected declaration (inclusion_layout_missing_values.res:5:5-17):
      let b: string

Diagnostic modified (runtime representation reason):
Before (diagnostic):
    Their runtime representations differ:
    the first declaration states values whose typeof is "object",
    the second declaration states values whose typeof is "string".
After (diagnostic):
    Their runtime representations differ:
      The actual declaration states values whose typeof is "object".
      The expected declaration states values whose typeof is "string".

Subst.extension_constructor dropped the location of an extension
constructor when writing a .cmi, unlike every other declaration. It keeps it
now, so the expected side of an extension mismatch has a position, and
reanalyze also records a reference to an exception declared in an interface.

Diagnostic modified (extension constructor declared in an interface):
Example:
    // Foo.res
    type t += Boom(string)
    // Foo.resi
    type t += Boom(int)
Before (diagnostic):
    Foo.res:
      Expected declaration
After (diagnostic):
    Expected declaration (Foo.resi:4:3-13):
      type t += Boom(int)

Review: Is anything dropped that was useful? The enclosing modules printed in full repeated the items below them; the path line names where the items are. A big part was printed as "..." for the same reason, so the size check is gone.
Review: Why "actual" and "expected" and not "implementation" and "interface"? The same errors are reported for module type constraints and functor arguments within one file, where there is no interface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gLpf7RN1MiRcubPgRn
Signed-off-by: Christoph Knittel <ck@cca.io>
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Changelog

💅 Polish

  • Show a signature mismatch as the actual and the expected declaration, each under its location, with only the items that differ and the module path to them.
  • Keep the location of an extension constructor or exception declared in an interface, so that errors and reanalyze point to it.

Semantic Diff

4 commits · Full page

Show signature mismatches as the actual and expected declaration, each under its location

5de3a96 · cknitt + Claude · Oct 30, 2026, 11:40 UTC

Why

A signature mismatch printed every enclosing module in full, then the two declarations, then their locations as "file:line:" lines with a label on the next line that read like headings with nothing under them, and the reasons without a break. Which text was where had to be matched up by hand, and declarations that read the same looked like a declaration not including itself. The type checker's errors separate their parts with blank lines and indent the content under a lead-in.

Includemod.report_error now leaves out a module or module type comparison when an item inside it follows, so that only the items that differ are printed, each after a line naming where it is: "In module P:", "In module type P.T:", "In the result of functor P:", "In the parameter X of functor P:", or "At position ..." otherwise, once for items at the same position. Missing items are listed in the order of the expected signature, and in a functor's parameter, where inclusion is reversed, the implementation's side is still the actual declaration.

Diagnostic modified (type declaration in an interface)

// Foo.res
type t = {a: int, b: int}
// Foo.resi
type t = {x: int, y: int}

diagnostic

 The implementation Foo.res
 does not match the interface foo.cmi:
-Type declarations do not match:
+
+Actual declaration (Foo.res:1:1-25):
   type t = {a: int, b: int}
-is not included in
+
+Expected declaration (Foo.resi:1:1-25):
   type t = {x: int, y: int}
-Foo.resi:1:1-25:
-  Expected declaration
-Foo.res:1:1-25:
-  Actual declaration
+
 Fields number 1 have different names, a and x.

Diagnostic modified (value in a nested module)

// Foo.res
module Inner = {
  let value = "not an int"
}
// Foo.resi
module Inner: {
  let value: int
}

diagnostic

 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
-Foo.resi:2:3-16:
-  Expected declaration
-Foo.res:2:7-11:
-  Actual declaration
+
+Actual declaration (Foo.res:2:7-11):
+  let value: string
+
+Expected declaration (Foo.resi:2:3-16):
+  let value: int

Diagnostic modified (functor result)

module type S = {
  type t
}
module M: {
  module F: (X: S) => S with type t = X.t
} = {
  module F = (X: S) => {
    type t = int
  }
}

diagnostic

 Signature mismatch:
-Modules do not match:
-  …
-In module F:
-Modules do not match:
-  …
-At position module F: (X) => <here>
-Modules do not match:
-  …
-At position module F: (X) => <here>
-Type declarations do not match:
+
+In the result of functor F:
+
+Actual declaration (functor_signature_mismatch.res:9:5-16):
   type t = int
-is not included in
+
+Expected declaration (functor_signature_mismatch.res:6:36):
   type t = X.t
-functor_signature_mismatch.res:6:36:
-  Expected declaration
-functor_signature_mismatch.res:9:5-16:
-  Actual declaration

Diagnostic modified (functor parameter)

module M: {
  module F: (X: {let a: int}) => {}
} = {
  module F = (X: {let a: string}) => {}
}

diagnostic

-Signature mismatch:
-Modules do not match:
-… the module, then the functor, each pair printed in full
-At position module F: (X: <here>) => ...
-Values do not match: let a: int is not included in let a: string
-inclusion_layout_functor_parameter.res:12:7-19:
-  Expected declaration
-inclusion_layout_functor_parameter.res:6:7-16:
-  Actual declaration
+In the parameter X of functor F:
+
+Actual declaration (inclusion_layout_functor_parameter.res:12:7-19):
+  let a: string
+
+Expected declaration (inclusion_layout_functor_parameter.res:6:7-16):
+  let a: int

Diagnostic modified (missing values)

module M: {
  module N: {
    let a: int
    let b: string
  }
} = {
  module N = {}
}

diagnostic

-Signature mismatch:
-Modules do not match:
-… the module, then module N, each pair printed in full
 In module N:
-The value `b` is required but not provided
-inclusion_layout_missing_values.res:5:5-17:
-  Expected declaration
-In module N:
-The value `a` is required but not provided
-inclusion_layout_missing_values.res:4:5-14:
-  Expected declaration
+
+The value `a` is required but not provided.
+
+Expected declaration (inclusion_layout_missing_values.res:4:5-14):
+  let a: int
+
+The value `b` is required but not provided.
+
+Expected declaration (inclusion_layout_missing_values.res:5:5-17):
+  let b: string

Diagnostic modified (runtime representation reason)

diagnostic

 Their runtime representations differ:
-the first declaration states values whose typeof is "object",
-the second declaration states values whose typeof is "string".
+  The actual declaration states values whose typeof is "object".
+  The expected declaration states values whose typeof is "string".

Subst.extension_constructor dropped the location of an extension constructor when writing a .cmi, unlike every other declaration. It keeps it now, so the expected side of an extension mismatch has a position, and reanalyze also records a reference to an exception declared in an interface.

Diagnostic modified (extension constructor declared in an interface)

// Foo.res
type t += Boom(string)
// Foo.resi
type t += Boom(int)

diagnostic

-Foo.res:
-  Expected declaration
+Expected declaration (Foo.resi:4:3-13):
+  type t += Boom(int)

Review

  • Is anything dropped that was useful? The enclosing modules printed in full repeated the items below them; the path line names where the items are. A big part was printed as "..." for the same reason, so the size check is gone.
  • Why "actual" and "expected" and not "implementation" and "interface"? The same errors are reported for module type constraints and functor arguments within one file, where there is no interface.
Label the two sides when runtime representations differ

7641737 · cknitt + Claude · Oct 30, 2026, 12:46 UTC

Why

"The actual declaration states values whose typeof is "object"" reads clumsily: a declaration doesn't state values. The two lines now use the labels of the declaration blocks above them, followed by what each side states.

Diagnostic modified (runtime representation reason)

diagnostic

 Their runtime representations differ:
-  The actual declaration states values whose typeof is "object".
-  The expected declaration states values whose typeof is "string".
+  Actual declaration: typeof "object"
+  Expected declaration: typeof "string"

Diagnostic modified (@notUndefined reason)

diagnostic

 Their runtime representations differ:
-  The expected declaration is @notUndefined,
-  but the actual declaration can hold undefined.
+  Actual declaration: can hold undefined
+  Expected declaration: @notUndefined

Mapping modified (what a declaration states, Ast_untagged_variants.describe_declared_values)

representation

 @instanceOf("Dog")               ↦ instances of Dog
 @instanceOf("Dog") of a subclass ↦ instances of Dog, a subclass of Animal
 arrays                           ↦ arrays
-@typeOf("string")                ↦ values whose typeof is "string"
-nothing                          ↦ no runtime class or typeof
+@typeOf("string")                ↦ typeof "string"
+nothing                          ↦ no stated class or typeof

Review

  • Where do the labels come from? From the side names the caller passes, without their article: "the actual" and "declaration" make "Actual declaration"; the type definition errors of Typedecl get "Original definition" and "This definition".
  • Why no period? The lines are labels with a value, like the declaration labels, not sentences.
Keep the module type comparison around a permutation of its fields

3a0228c · cknitt + Claude · Oct 30, 2026, 13:18 UTC

Why

Codex review of #9140: when two module types have the same fields in another order, the innermost symptom is the permutation, which shows no declarations, and leaving out the module type comparison around it left only "Illegal permutation of structure fields". A comparison is now left out only when a later symptom shows the declarations that differ.

The parser gave a module type declaration in a signature no location, unlike one in a structure, so the expected side printed without a position. It now spans from the name to the end, as in a structure.

Diagnostic modified (fields of a module type in another order)

module M: {
  module type T = {
    let a: int
    let b: int
  }
} = {
  module type T = {
    let b: int
    let a: int
  }
}

diagnostic

 Signature mismatch:
 
+Actual declaration (include_modtype_permutation.res:7:15-10:3):
+  module type T = {
+    let b: int
+    let a: int
+  }
+
+Expected declaration (include_modtype_permutation.res:2:15-5:3):
+  module type T = {
+    let a: int
+    let b: int
+  }
+
 In module type T:
 
 Illegal permutation of structure fields
✨ Report the actual side of a module type found by comparing the other way

42eed6a · cknitt + Claude · Oct 30, 2026, 13:34 UTC · new

Why

Codex review of #9140: two module types must be equal, and check_modtype_equiv also includes the expected one in the actual one. An item only the actual module type has is found by that reverse check, but the error called it required and its declaration the expected one. This was mislabelled before too, and the comparison of the two module types printed above it was the only hint, which the new layout leaves out.

The reverse check now adds a Reverse position to the path. Like a functor parameter it swaps the sides, and it is not printed.

Diagnostic modified (an item only the actual module type has)

module M: {
  module type T = {
    let a: int
  }
} = {
  module type T = {
    let a: int
    let b: int
  }
}

diagnostic

 In module type T:
 
-The value `b` is required but not provided.
+The value `b` is only present in the actual declaration.
 
-Expected declaration (inclusion_layout_module_type_extra.res:10:5-14):
+Actual declaration (inclusion_layout_module_type_extra.res:10:5-14):
   let b: int

@codecov

codecov Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.69565% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.69%. Comparing base (25e3dfb) to head (42eed6a).

Files with missing lines Patch % Lines
compiler/ml/includemod.ml 83.56% 12 Missing ⚠️
compiler/ml/includecore.ml 78.57% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9140      +/-   ##
==========================================
- Coverage   81.72%   81.69%   -0.03%     
==========================================
  Files         460      460              
  Lines       64817    64855      +38     
==========================================
+ Hits        52969    52984      +15     
- Misses      11848    11871      +23     
Files with missing lines Coverage Δ
compiler/ml/ast_untagged_variants.ml 83.14% <100.00%> (-0.10%) ⬇️
compiler/ml/subst.ml 85.88% <100.00%> (ø)
compiler/ml/typemod.ml 76.98% <ø> (ø)
compiler/syntax/src/res_core.ml 92.02% <100.00%> (+<0.01%) ⬆️
compiler/ml/includecore.ml 78.78% <78.57%> (-0.04%) ⬇️
compiler/ml/includemod.ml 75.29% <83.56%> (-2.63%) ⬇️

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pkg-pr-new

pkg-pr-new Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript@9140

@rescript/belt

npm i https://pkg.pr.new/@rescript/belt@9140

@rescript/darwin-arm64

npm i https://pkg.pr.new/@rescript/darwin-arm64@9140

@rescript/linux-arm64

npm i https://pkg.pr.new/@rescript/linux-arm64@9140

@rescript/linux-x64

npm i https://pkg.pr.new/@rescript/linux-x64@9140

@rescript/runtime

npm i https://pkg.pr.new/@rescript/runtime@9140

@rescript/win32-x64

npm i https://pkg.pr.new/@rescript/win32-x64@9140

commit: 42eed6a

@github-actions

Copy link
Copy Markdown

@cknitt

cknitt commented Oct 30, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T13:36:45.917968Z 42eed6a Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f29ecf7903

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread compiler/ml/includemod.ml Resolved
@cknitt
cknitt force-pushed the signature-mismatch-layout branch from f29ecf7 to 400784b Compare October 30, 2026 12:57
Why:
    "The actual declaration states values whose typeof is "object"" reads
    clumsily: a declaration doesn't state values. The two lines now use the
    labels of the declaration blocks above them, followed by what each side
    states.

Diagnostic modified (runtime representation reason):
Before (diagnostic):
    Their runtime representations differ:
      The actual declaration states values whose typeof is "object".
      The expected declaration states values whose typeof is "string".
After (diagnostic):
    Their runtime representations differ:
      Actual declaration: typeof "object"
      Expected declaration: typeof "string"

Diagnostic modified (@notundefined reason):
Before (diagnostic):
    Their runtime representations differ:
      The expected declaration is @notundefined,
      but the actual declaration can hold undefined.
After (diagnostic):
    Their runtime representations differ:
      Actual declaration: can hold undefined
      Expected declaration: @notundefined

Mapping modified (what a declaration states, Ast_untagged_variants.describe_declared_values):
Before (representation):
    @instanceof("Dog") ↦ instances of Dog
    @instanceof("Dog") of a subclass ↦ instances of Dog, a subclass of Animal
    arrays ↦ arrays
    @typeof("string") ↦ values whose typeof is "string"
    nothing ↦ no runtime class or typeof
After (representation):
    @instanceof("Dog") ↦ instances of Dog
    @instanceof("Dog") of a subclass ↦ instances of Dog, a subclass of Animal
    arrays ↦ arrays
    @typeof("string") ↦ typeof "string"
    nothing ↦ no stated class or typeof

Review: Where do the labels come from? From the side names the caller passes, without their article: "the actual" and "declaration" make "Actual declaration"; the type definition errors of Typedecl get "Original definition" and "This definition".
Review: Why no period? The lines are labels with a value, like the declaration labels, not sentences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gLpf7RN1MiRcubPgRn
Signed-off-by: Christoph Knittel <ck@cca.io>
@cknitt
cknitt force-pushed the signature-mismatch-layout branch from 400784b to e819c19 Compare October 30, 2026 13:01
Why:
    Codex review of #9140: when two module types have the same fields in
    another order, the innermost symptom is the permutation, which shows no
    declarations, and leaving out the module type comparison around it left
    only "Illegal permutation of structure fields". A comparison is now left
    out only when a later symptom shows the declarations that differ.

    The parser gave a module type declaration in a signature no location,
    unlike one in a structure, so the expected side printed without a
    position. It now spans from the name to the end, as in a structure.

Diagnostic modified (fields of a module type in another order):
Example:
    module M: {
      module type T = {
        let a: int
        let b: int
      }
    } = {
      module type T = {
        let b: int
        let a: int
      }
    }
Before (diagnostic):
    Signature mismatch:

    In module type T:

    Illegal permutation of structure fields
After (diagnostic):
    Signature mismatch:

    Actual declaration (include_modtype_permutation.res:7:15-10:3):
      module type T = {
        let b: int
        let a: int
      }

    Expected declaration (include_modtype_permutation.res:2:15-5:3):
      module type T = {
        let a: int
        let b: int
      }

    In module type T:

    Illegal permutation of structure fields

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gLpf7RN1MiRcubPgRn
Signed-off-by: Christoph Knittel <ck@cca.io>
cknitt added a commit that referenced this pull request Oct 30, 2026
Why:
    Codex review of #9140: when two module types have the same fields in
    another order, the innermost symptom is the permutation, which shows no
    declarations, and leaving out the module type comparison around it left
    only "Illegal permutation of structure fields". A comparison is now left
    out only when a later symptom shows the declarations that differ.

    The parser gave a module type declaration in a signature no location,
    unlike one in a structure, so the expected side printed without a
    position. It now spans from the name to the end, as in a structure.

Diagnostic modified (fields of a module type in another order):
Example:
    module M: {
      module type T = {
        let a: int
        let b: int
      }
    } = {
      module type T = {
        let b: int
        let a: int
      }
    }
Before (diagnostic):
    Signature mismatch:

    In module type T:

    Illegal permutation of structure fields
After (diagnostic):
    Signature mismatch:

    Actual declaration (include_modtype_permutation.res:7:15-10:3):
      module type T = {
        let b: int
        let a: int
      }

    Expected declaration (include_modtype_permutation.res:2:15-5:3):
      module type T = {
        let a: int
        let b: int
      }

    In module type T:

    Illegal permutation of structure fields

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gLpf7RN1MiRcubPgRn
Signed-off-by: Christoph Knittel <ck@cca.io>
@cknitt

cknitt commented Oct 30, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b3548255f7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread compiler/ml/includemod.ml Resolved
@cknitt
cknitt force-pushed the signature-mismatch-layout branch from b354825 to 3a0228c Compare October 30, 2026 13:25
Why:
    Codex review of #9140: two module types must be equal, and
    check_modtype_equiv also includes the expected one in the actual one. An
    item only the actual module type has is found by that reverse check, but
    the error called it required and its declaration the expected one. This
    was mislabelled before too, and the comparison of the two module types
    printed above it was the only hint, which the new layout leaves out.

    The reverse check now adds a Reverse position to the path. Like a
    functor parameter it swaps the sides, and it is not printed.

Diagnostic modified (an item only the actual module type has):
Example:
    module M: {
      module type T = {
        let a: int
      }
    } = {
      module type T = {
        let a: int
        let b: int
      }
    }
Before (diagnostic):
    In module type T:

    The value `b` is required but not provided.

    Expected declaration (inclusion_layout_module_type_extra.res:10:5-14):
      let b: int
After (diagnostic):
    In module type T:

    The value `b` is only present in the actual declaration.

    Actual declaration (inclusion_layout_module_type_extra.res:10:5-14):
      let b: int

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gLpf7RN1MiRcubPgRn
Signed-off-by: Christoph Knittel <ck@cca.io>
@cknitt

cknitt commented Oct 30, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 42eed6ab00

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@cknitt
cknitt enabled auto-merge (squash) October 30, 2026 13:37
@cknitt
cknitt requested a review from cristianoc October 30, 2026 13:37
@cknitt
cknitt merged commit acdf563 into master Oct 30, 2026
26 checks passed
@cknitt
cknitt deleted the signature-mismatch-layout branch October 30, 2026 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants