The type of a && b should be typeof a | typeof b, but appears to
actually be merely typeof a. This enables trivially breaking the type
system—any value can be coerced to the type of any truthy value:
// @flow
function f<A, B>(a: A, b: B): A {
return a && b;
}
f(17, "broken").toFixed(); // runtime error, not caught statically
Some observations:
(a && b: A) for a && b is valid;f, then the proper(17 && "broken": number)) causes the properThis can be trivially extended to coerce a value to any inhabited
type:
// @flow
function f<A, B>(a: A, b: B): A {
return a && b;
}
function g<A, B>(a: A, b: B): A {
return a || b;
}
function coerce<A, B>(a: A, b: B): A {
return a ? f(a, b) : g(a, b);
}
coerce(17, "broken").toFixed(); // runtime error, not caught statically
With a bit of thought, we can see that this yields coerce without a
single any-cast or any other unsafe features:
// @flow
// `coerce` always returns its argument unchanged; it never throws.
function coerce<T, U>(t: T): U {
function f<A, B>(a: A, b: B): A {
return a && b;
}
class V {}
const result: U | V = f(new V(), t);
if (result instanceof V) {
throw new Error("impossible; we will never get here");
}
return result;
}
// or, using initial/terminal types instead of generics...
function coerceToEmpty(t: mixed): empty {
return coerce(t);
}
const wat1: number = coerce("hello");
const wat2: boolean = coerceToEmpty(123);
const wat3: empty = coerce(null);
This works on all Flow versions currently available on flow.org/try
(namely, v0.37.0 to v0.78.0).
Woohoo! Thanks, @mvitousek .
@wchargin Thanks for the great bug report and sorry it took a long time for us to get to it!
Most helpful comment
@wchargin Thanks for the great bug report and sorry it took a long time for us to get to it!