Repository navigation
Show signature mismatches as the actual and expected declaration, each under its location - #9140
Conversation
…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>
Changelog💅 Polish
Semantic Diff4 commits · Full page Show signature mismatches as the actual and expected declaration, each under its location5de3a96 · cknitt + Claude · Oct 30, 2026, 11:40 UTC
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}
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
}
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: intDiagnostic 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
}
}
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 declarationDiagnostic modified (functor parameter) module M: {
module F: (X: {let a: int}) => {}
} = {
module F = (X: {let a: string}) => {}
}
-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: intDiagnostic modified (missing values) module M: {
module N: {
let a: int
let b: string
}
} = {
module N = {}
}
-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: stringDiagnostic modified (runtime representation reason)
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)
-Foo.res:
- Expected declaration
+Expected declaration (Foo.resi:4:3-13):
+ type t += Boom(int)Review
Label the two sides when runtime representations differ7641737 · cknitt + Claude · Oct 30, 2026, 12:46 UTC
Diagnostic modified (runtime representation reason)
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 (
Their runtime representations differ:
- The expected declaration is @notUndefined,
- but the actual declaration can hold undefined.
+ Actual declaration: can hold undefined
+ Expected declaration: @notUndefinedMapping modified (what a declaration states, Ast_untagged_variants.describe_declared_values)
@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 typeofReview
Keep the module type comparison around a permutation of its fields3a0228c · cknitt + Claude · Oct 30, 2026, 13:18 UTC
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
}
}
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 way42eed6a · cknitt + Claude · Oct 30, 2026, 13:34 UTC · new
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
}
}
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 Report❌ Patch coverage is
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
🚀 New features to boost your workflow:
|
rescript
@rescript/belt
@rescript/darwin-arm64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
|
Developer playground preview: https://rescript-lang.github.io/rescript/dev-playground/?version=pr-9140 |
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 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".
f29ecf7 to
400784b
Compare
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>
400784b to
e819c19
Compare
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>
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>
|
@codex review |
There was a problem hiding this comment.
💡 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".
b354825 to
3a0228c
Compare
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>
|
@codex review |
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
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:
After:
What changes:
Includemod.report_errorleaves 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.Actual declaration: typeof "object"instead ofthe first declaration states values whose typeof is "object".Bugs fixed along the way:
Subst.extension_constructordropped it when writing the.cmi, unlike every other declaration, so the expected side of an extension mismatch printed asFoo.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.New tests:
tests/build_tests/super_errors/fixtures/inclusion_layout_*.rescover 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
@tagand runtime annotations) will be rebased on top of this.🤖 Generated with Claude Code
https://claude.ai/code/session_01Kbf3gLpf7RN1MiRcubPgRn