-
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
"ABScene<X extends A & B> extends Scene<X>" should not be assignable to Scene<A> #40624
Comments
But there is no such constraint. Nothing in the type Type arguments themselves are not considered for structural comparison:
My guess would be that this is a design limitation and you should design your class differently / immutable, but maybe the TypeScript team can shed more lights. |
Well the constraint does exist for the But yeah, I also guess that this isn't a bug and just an inconvenient side-effect of structural comparison, which is a damn shame, because this pattern provides a very convenient way to build a powerful composable class hierarchy. So my two questions are:
|
This is a soundness hole, but it’s really much simpler than your example: playground. The hole is that properties (and index signatures, as in the array example) are compared covariantly, when really that’s only sound if they’re I do want to emphasize that @MartinJohns is correct about the fact that type parameters only affect assignability by merit of where they’re structurally used, but that this is not “an inconvenient side-effect of structural comparison”—the strict variance you’re looking for is totally compatible with structural typing. In fact, we really like how variance arises from structure, and it does work in a strict way when it comes to function types [playground]: declare class Box<Read, Write> {
get: () => Read;
set: (x: Write) => void;
}
declare let b1: Box<string, number>;
declare let b2: Box<unknown, 2>;
// This is allowed
b2 = b1;
b2.get(); // because here, we know this is a string, and unknown includes strings, so it's safe
b2.set(2); // and here, we know we could actually pass any number, but limiting us to 2 is safe
// so b1's contract is fulfilled by b2
// This is not allowed
declare let b3: Box<"hello", number | Date>;
b3 = b1;
// ~~~~
b3.get(); // because this says we'd get 'hello', when b1.get() can return any string
b3.set(new Date()); // and this says we can pass in a Date, even though b1.set can only take a number Because the variance of Practically, I would echo @MartinJohns’s advice that you could embrace immutability (you could add // Bad:
const a: Scene<A> = new ABScene<A & B>({a: 'a', b: 'b'});
// Much better:
const a = new ABScene<A & B>({a: 'a', b: 'b'}); |
Thank you so much for your elaborate explanation! |
TypeScript Version: 4.1.0-dev.20200917
Search Terms:
generics, generic constraints, extends, intersection, intersect, assignable, type safety, compatible
Code
Expected behavior:
There should be an error that type 'A' does not satisfy the constraint 'A & B'.
Actual behavior:
It compiles with no error.
I guess the compiler forgets the generic constraint of the subclass.
Playground Link:
Playground Link
Related Issues:
#31006
The text was updated successfully, but these errors were encountered: