-
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
Type guard fails to refine due to the presence of another method #33051
Comments
In the first case the predicate type is a subtype of source type, so it narrows to the predicate. This is not true in the second case: the additional method make the predicate unassignable to the source because any is not assignable to never. The method has an effect because it means the predicate does not produce a more precise type: the type of behaviour is less precise in the predicate type. |
I was wondering if the problem wasn't related to assignability. I also tried |
As usual I don't have much useful to add to @jack-williams's analysis. |
The example above is a reduced version of: // If T is a refinement of C (T !== C, T is a subtype of C), produce T, otherwise never.
type Refines<T extends C, C> = C extends T ? never : T;
interface Map2<K, HK extends K> {
has<RK extends K>(key: RK): this is Map2<K, Refines<RK, K> | HK>;
get(key: HK): number;
get(key: K): number | undefined;
}
declare let map2: Map2<string, never>;
if(map2.has("foo")) {
let x: number = map2.get("foo"); // fail: map is still Map2<string, never>
} The issue goes away if I use |
TLDR: Not 100% sure on that, but what I think is happening: First thing to note is that, ignoring This is most likely because type predicates are covariant, so As to why Here is an inlined expansion of what I think is going on: // corresponds to Refines<RK, K> | HK
interface FooCO<K, HK extends K> {
foo: (x: any) => HK | (K extends (any) ? never : (any));
}
declare const one: FooCO<string, never>;
declare const two: FooCO<string, 'foo'>;
const a: FooCO<string, never> = two; // error, not contra in HK
const b: FooCO<string, 'foo'> = one; // co in HK
// corresponds to Refines<RK | HK, K>
interface FooBI<K, HK extends K> {
foo: (x: any) => (K extends (HK|any) ? never : (HK|any));
}
declare const one2: FooBI<string, never>;
declare const two2: FooBI<string, 'foo'>;
const a2: FooBI<string, never> = two2; // contra in HK
const b2: FooBI<string, 'foo'> = one2; // co in HK
// corresponds to RK | HK
interface FooBI2<K, HK extends K> {
foo: (x: any) => (HK | any);
}
declare const one3: FooBI2<string, never>;
declare const two3: FooBI2<string, 'foo'>;
const a3: FooBI2<string, never> = two3; // contra in HK
const b3: FooBI2<string, 'foo'> = one3; // co in HK So, getting to the point: I think that when relating If you change the definition of Refines to type Refines<T extends C, C> = [C] extends [T] ? never : T You should get the intended behaviour because the conditional type eagerly resolves to |
And I'll just add this as a piece of relevant work #30461: it's not related to your specific bugs/questions, but it is related to your wider goal --- maybe it's interesting. |
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow. |
1 similar comment
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow. |
TypeScript Version: 3.7.0-dev.20190823
Search Terms: type guard, refine, fail, assignment
Code
Expected behavior:
map2.has
should refine the type just the same asmap1.has
does, regardless of the presence of other methods on the interface.Actual behavior:
map2.has
doesn't refine the type at all so long asbehavior
has the return typeHK
.Playground Link:
playground
The text was updated successfully, but these errors were encountered: