-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
This narrowing typeguard effect bleeds into subsequent statments on a type with bivariant type-parameter #30461
Comments
I think this is a design limitation and ultimately an issue with bivariance. I think what is going on is that the type of Under bivariance As a small way to see this: type A = GuardedMap<"bar" | "foo">
type B = GuardedMap<"foo">
type C = A | B;
declare const a: A;
declare const b: B;
// w has type GuardedMap<"foo"> under strict checks
// but, GuardedMap<"bar" | "foo"> without.
const w = Math.random() > 0.5 ? a : b; I'm not sure there is a clean way to fix this without being overly disruptive, but someone who knows the narrowing code better may have something else to say. Perhaps there is some targeted fix specifically for |
@jack-williams Are you sure about the difference between strict on/off and the type of From my debugging the subsequent statement is indeed impacted by both branches of the Digging a bit deeper the I understand some of the mechanics of the result, but the result is surprising. Is the fact that the flow type in subsequent statements is the union of both branch outcomes useful in this scenario? I don't think so. Not sure I can carve out the situations when this is useful from the ones it is not though. Might be a corner of corner cases and not worth the hassle of dealing with it in any better way, but I though I might document it here in case someone else comes across it and see if anyone thinks it worth fixing :) |
Sorry, I was assuming that
It's probably not useful in this scenario, but across the board it's generally useful and because it's compositional it's easier to implement. Where you see the benefit is when you have multiple branches, some of which return. In that case, gathering the flow types in a union is simple and effective. In your example, the problem only arises due to unsoundness in the type system.
It sort of feels like that, but maybe there is an easy fix. I think the problem is that any fix effectively amounts to a heuristic. I agree that documenting it, at the least, is worth the time. |
Extremely good analysis @jack-williams Here's a simplified repro with fewer things going on type Animal = { a: "" };
type Cat = Animal & { c: "" };
type Dog = Animal & { d: "" };
type Box<T> = {
get(x: T): void;
}
declare function isCatBox(x: any): x is Box<Cat>;
function fn(arg: Box<Cat | Dog>) {
arg.get({ a: "", d: "" }); // OK
if (isCatBox(arg)) {
}
arg.get({ a: "", d: "" }); // Error
} As for documentation, I have no idea how you would write this down in a way that anyone could find it unless they already knew what the doc said. |
@weswigham @ahejlsberg this is quite the interesting example FYI |
I'm curious to see what RWC would break if the union from branch joins was reduced using strict function types regardless of the current compiler flag. Though any change such as this would not affect the original example because method typing is beyond the reach of the strict flag. |
I think the fix here might be to make the |
Prototyping to see what happens |
Other places do depend on subtype reduction on methods: // Now an error
(''.match(/ /) || []).map(s => s.toLowerCase()); |
If we fixed our union call signature generic handling to be a bit better, that wouldn't need to reduce, since |
TypeScript Version: 3.4.0-dev.201xxxxx (although 3.3 is also impacted)
Search Terms: this type guards
If we create a type-guard that narrows
this
on a type that has a type-parameter that is present only in a bivariant position, the effect of the type guard persists outside of the guarded block.Code
Expected behavior:
Type guard only impacts the guarded block.
Actual behavior:
The effect of the type guard bleads into all subsequent statements. (marked with
OK!?!?!
andOK?!
)Note: With
strictFunctionTypes
on, declaringget
asget: (k: KnownKeys) => number;
makes the code work as expected.Playground Link: link
Related Issues: Similar to #14817
Found this while playing with a solution for #9619
The text was updated successfully, but these errors were encountered: