-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Explicitly Omit unspreadable properties from rest type in the generic case #47078
Conversation
@typescript-bot pack this |
Heya @jakebailey, I've started to run the parallelized community code test suite on this PR at f2bb9b7. You can monitor the build here. |
Heya @jakebailey, I've started to run the tarball bundle task on this PR at f2bb9b7. You can monitor the build here. |
Hey @jakebailey, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
The user suite test run you requested has finished and failed. I've opened a PR with the baseline diff from master. |
This is good; we're "allowed" to have types which don't represent the full list of properties present at runtime (this is the same logic by which |
Good, less work for me! 😄 |
Updated the PR to also test |
This is less about |
Yes, this is related to property spreading of a value with unspreadable properties; |
@jakebailey But this still breaks on runtime if you uses interface that are implemented with a class, right? interface Thing {
someProperty: number;
someMethod(): void;
}
function foo<T extends Thing>(x: T) {
let { someProperty, ...rest } = x;
// Not an error, but will break on runtime with: foo(new MyThing());
rest.someMethod();
}
class MyThing implements Thing {
someProperty = 42;
someMethod() {
// ...
}
}
// fine
foo({ someProperty: 1, someMethod: () => { } });
// breaks on runtime
foo(new MyThing()); |
Sure, but that code doesn't error before this PR too. I'd file a new issue for that (though there's probably an issue somewhere about how these sorts of own property semantics aren't fully modeled). |
Hello everyone! Thank you for your changes @jakebailey, I really appreciate it! I have one question out of curiosity: if I have a constructor where I bind methods, they should be allowed for the rest type, right? At the moment I see error that Playground – https://tsplay.dev/mMMebm Exampleclass Thing {
someProperty = 42;
constructor() {
this.someMethod = this.someMethod.bind(this);
}
someMethod() {
// ...
}
}
function foo<T extends Thing>(x: T) {
let { someProperty, ...rest } = x;
// Used to work, is now an error!
// Property 'someMethod' does not exist on type 'Omit<T, "someProperty" | "someMethod">'.
rest.someMethod();
} Should I create a separate issue for it? @RyanCavanaugh |
I would file a new issue (as this is the wrong place to discuss it), but I'm not sure that we do anything special for a method bound like that. Check out the This is no different than how we've always behaved in the non-generic case: Playground Link This PR made things consistent between the two. |
Fixes #46967
In the non-generic case, we'd produce a concrete type that consisted only of the spreadable properties, excluding already destructured and unspreadable properties. This is good.
However, in the generic case (including
this
, as it's an implicit type parameter), we instead produced anOmit
type, but only omitted the already destructured keys. This left a type where all of the unspreadable properties (e.g. properties with getters, methods) where left accessible.Make the generic case match the behavior of the non-generic case by also adding the unspreadable items to the omitted set of keys.
This might get pretty hairy for types with lots of getters/setters/methods, though, but the alternative (making a concrete type instead if it looks like
Omit
can't handle it) appears to break a few tests in even more undesirable ways. Hopefully destructuringthis
with a rest is not all that common (the user tests imply it is not).Additionally, I don't know if the behavior on non-public properties is correct; it seems weird that destructuring
this
within the class would only keep the public properties, even though at runtime they are there. They're dropped in theT extends SomeClass
case because that's a use where those symbols are not intended to be visible, but internally, maybe it's not right to drop them.Before: Playground Link
After: Playground Link