-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
3.5.1 regression when using this
with an index signature of a generic union type
#31731
Comments
This looks like an effect of #30769 which is a known breaking change. It used to be that |
@ahejlsberg - is there a good heuristic to understand the new "stricter rules" for |
@justingrant Yes, the reason for the difference is that I did a bit of debugging to see what exactly is going on in the example. When the base type of the When the base type is written as |
Makes sense. Thanks so much @ahejlsberg for the investigation and clear explanation of what's going on. |
@justingrant You're welcome. BTW, as I was looking into this I noticed the very large sets of overloaded export type Runtype<T = unknown> = {
T: T; // Witness
guard(value: unknown): value is T;
}
export type Record<T extends { [key: string]: Runtype }> = { [P in keyof T]: T[P]['T'] }
export declare const Record: <T extends { [key: string]: Runtype }>(def: T) => Runtype<Record<T>>;
export type Union<T extends Runtype[]> = T[number]['T'];
export declare const Union: <T extends Runtype[]>(...types: T) => Runtype<Union<T>>;
type UnionToIntersection<U> = (U extends U ? (x: U) => void : never) extends (x: infer I) => void ? I : never
export type Intersect<T extends Runtype[]> = UnionToIntersection<T[number]['T']>;
export declare const Intersect: <T extends Runtype[]>(...types: T) => Runtype<Intersect<T>>;
export declare const String: Runtype<string>;
export declare const Number: Runtype<number>;
export declare const Literal: <T extends string | number | boolean>(value: T) => Runtype<T>;
const Boolean = Union(Literal(true), Literal(false));
// { s: string, b: boolean } & { n: number } | string
const MyType = Union(Intersect(Record({ s: String, b: Boolean }), Record({ n: Number })), String);
function foo(x: unknown) {
if (MyType.guard(x)) {
if (typeof x === "string") {
return x;
}
else {
if (x.b) {
return "true";
}
if (x.n > 10) {
return "" + x.n;
}
return x.s;
}
}
return "Error";
} |
TS 3.5 broke the runtypes library which uses
this
as as a type parameter to a generic method of a generic interface. After simplifying the break into a 4 lines of TS declarations, it looks like the problem is caused by TS failing to correctly resolve types using the index signature of a union type where both sides of the union share the same property name but different generic property types. In 3.4,(A | B)['value']
(where bothA
andB
are generic types with avalue
property) compiled OK, but in 3.5.1 and 3.6.0-dev.20190602 it triggers a "not assignable to" error whenthis
is used as one of the type parameters.FWIW, a slightly different syntax
A['value'] | B['value']
works OK in both 3.4 and 3.5.I see that @ahejlsberg has been working on assignment using
this
in #31704 and #31454, so perhaps this issue here is a case that slipped through those PRs?TypeScript Version: typescript@3.6.0-dev.20190602
Search Terms: this interface typescript recursion recursive union index signature
Code
Expected behavior:
No compiler errors (like in 3.4.5)
Actual behavior:
Playground Link: https://www.typescriptlang.org/play/#src=%2F%2F%20this%20declaration%20fails%20in%203.5.1%20(and%203.6.0-dev.20190602)%2C%20but%20OK%20in%203.4.5%20%0D%0Ainterface%20R%3CA%20%3D%20unknown%3E%20%7B%0D%0A%20%20value%3A%20A%3B%0D%0A%20%20Or%3CB%20extends%20R%3E(b%3A%20B)%3A%20Union%3Cthis%2C%20B%3E%3B%20%2F%2F%20compiler%20error%20on%20%60this%60%0D%0A%7D%0D%0Ainterface%20Union%3CA%20extends%20R%2C%20B%20extends%20R%3E%20extends%20R%3C(A%20%7C%20B)%5B'value'%5D%3E%20%7B%7D%0D%0A%0D%0A%2F%2F%20this%20declaration%20works%20OK%20in%203.4.5%2C%203.5.1%2C%20and%203.6.0-dev.20190602%0D%0A%2F%2F%20The%20only%20difference%20is%20using%20%60A%5B'value'%5D%20%7C%20B%5B'value'%5D%60%20instead%20of%20%60(A%20%7C%20B)%5B'value'%5D%60%0D%0Ainterface%20R2%3CA%20%3D%20unknown%3E%20%7B%0D%0A%20%20value%3A%20A%3B%0D%0A%20%20Or%3CB%20extends%20R2%3E(b%3A%20B)%3A%20Union2%3Cthis%2C%20B%3E%3B%0D%0A%7D%0D%0Ainterface%20Union2%3CA%20extends%20R2%2C%20B%20extends%20R2%3E%20extends%20R2%3CA%5B'value'%5D%20%7C%20B%5B'value'%5D%3E%20%7B%7D
Related Issues: #31691 #31454 #31704 #31439 #30769
The text was updated successfully, but these errors were encountered: