Fix optional default-arg typing and improve generated match errors#8386
Fix optional default-arg typing and improve generated match errors#8386
Conversation
Signed-off-by: Christoph Knittel <ck@cca.io>
rescript
@rescript/darwin-arm64
@rescript/darwin-x64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
6102195 to
979541f
Compare
|
Not sure why this should type check? let f = (~test: option<int>=42) => {
let _: int = test
}This type checks: let f = (~test: int = 42) => {
let _: int = test
}That's how things have been expected to be until now, as far as I can remember. |
|
@cristianoc Hmmmm. Maybe the root problem is indeed something different. Past behavior was this: module type Test = {
@react.component
let make: (~test: int=?) => React.element
}
// Compiles with warning:
// React: optional argument annotations must have explicit `option`. Did you mean `option<int>=?`?
module A: Test = {
@react.component
let make = (~test: int=?) => {
ignore(test)
React.null
}
}
// Compiles without warning
module B: Test = {
@react.component
let make = (~test: option<int>=?) => {
ignore(test)
React.null
}
}
// Compiles without warning
module C: Test = {
@react.component
let make = (~test: int=42) => {
ignore(test)
React.null
}
}
// Compiles without warning
module D: Test = {
@react.component
let make = (~test: option<int>=42) => {
ignore(test)
React.null
}
}The warnings seems to imply that the version with Maybe that warning could be removed and the version without option be made the canonical form? |
|
Same example, but where you can see in the body which type it actually gets: module type Test = {
@react.component
let make: (~test: int=?) => React.element
}
// Compiles with warning:
// React: optional argument annotations must have explicit `option`. Did you mean `option<int>=?`?
module A: Test = {
@react.component
let make = (~test: int=?) => {
// test becomes option<int>
test->Option.mapOr(React.null, React.int)
}
}
// Compiles without warning
module B: Test = {
@react.component
let make = (~test: option<int>=?) => {
// test becomes option<int>
test->Option.mapOr(React.null, React.int)
}
}
// Compiles without warning
module C: Test = {
@react.component
let make = (~test: int=42) => {
// test becomes int
React.int(test)
}
}
// Compiles without warning
module D: Test = {
@react.component
let make = (~test: option<int>=42) => {
// test becomes int
React.int(test)
}
} |
|
I now think that we should remove the warning for A and all special handling, and that A and C should be the allowed forms, everything else should give a type error (B would have Will try that out. What do you think @cristianoc? |
I think it should reflect what happens in the non-component case when defining just a function. |
|
@cristianoc Actually, checking again for "plain" functions without @react.component, we have the exact same behavior there when implementing an interface: module type Test = {
let f: (~test: int=?) => int
}
module A: Test = {
// compiles
let f = (~test: int=42) => test + 1
}
module B: Test = {
// compiles
let f = (~test: option<int>=?) => test->Option.getOr(42) + 1
}
module C: Test = {
// Error:
// This pattern matches values of type int
// but a pattern was expected which matches values of type option<'a>
//
// The value you're pattern matching on here is wrapped in an option, but you're trying to match on the actual value.
// Wrap the highlighted pattern in Some() to make it work.
let f = (~test: int=?) => test->Option.getOr(42) + 1
} |
|
So basically it is The only remaining question is why for PPX |
Summary
This PR fixes issue #8385 and also addresses a related older bug in optional-argument default lowering.
It does two things:
@react.component/ module-signature repro from Regression in optional args typecheck #8385Background
Optional arguments with defaults are lowered in two steps: first the raw optional value is bound, then the resolved value is rebound after handling
Some/None.That caused two separate problems:
switch/matchthat does not exist in user codeoption<_>, the compiler reused the original annotation even for the resolved binding, where the value is no longeroption<_>A reduced repro of the older issue is:
The original issue #8385 exposed the same underlying shape through the JSX transform with a
@react.componentconstrained by a module signature.Changes
1. Improve error message for generated optional-default match
The compiler now marks the synthetic match generated for optional-default lowering and reports errors in a function context instead of as a user-written
switch.This avoids misleading messages like:
for code that contains no explicit switch.
2. Fix typing of resolved default-value bindings
When lowering an optional arg with a default, the compiler now adapts the original pattern before rebinding the resolved value.
For example, the synthesized rebinding for:
now treats the resolved binding as
int, notoption<int>.This fixes both:
@react.componentregression from Regression in optional args typecheck #8385Tests
Added / updated coverage for:
super_errorstests/testsResult
The following now typechecks correctly:
and the original
@react.componentrepro from #8385 also builds again.