-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
[BUGFIX release] Correct types for Ember Arrays #20256
Conversation
86ce43b
to
fa78d49
Compare
Top-level request (I'll review further later): can you add an explanation to the PR so we can refer to it later to understand why these specific changes are correct? |
5c8555b
to
cfaf5d5
Compare
export type MethodsOf<O> = { | ||
[K in keyof O]: O[K] extends AnyFn ? O[K] : never; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't actually work, unfortunately. You need something like the final keyof
as used in MethodNamesOf
below to make it properly strip out the bits which are actually never
. It turns out the easiest way to get MethodsOf
is to reuse MethodNamesOf
in conjunction with Pick
:
type MethodsOf<O> = Pick<O, MethodNamesOf<O>>;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export type MethodsOf<O> = { | |
[K in keyof O]: O[K] extends AnyFn ? O[K] : never; | |
}; | |
export type MethodsOf<O> = Pick<O, MethodNamesOf<O>>; |
4513a24
to
3a1c614
Compare
@@ -15,7 +15,7 @@ declare module '@ember/array/mutable' { | |||
/** | |||
* __Required.__ You must implement this method to apply this mixin. | |||
*/ | |||
replace(idx: number, amt: number, objects: T[]): this; | |||
replace(idx: number, amt: number, objects: T[]): void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In some cases we do return this
, but the docs state that we do not return.
@@ -535,7 +540,7 @@ interface EmberArray<T> extends Enumerable { | |||
@return {Object} receiver | |||
@public | |||
*/ | |||
setEach<K extends string>(key: K, value: Value<T, K>): this; | |||
setEach<K extends keyof T>(key: K, value: T[K]): this; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In theory we can set compound keys, but I feel like we probably shouldn't.
find<S extends T, Target = void>( | ||
predicate: (this: void, value: T, index: number, obj: T[]) => value is S, | ||
thisArg?: Target | ||
): S | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just moved this.
invoke<M extends MethodNamesOf<T>>( | ||
methodName: M, | ||
...args: MethodParams<T, M> | ||
): NativeArray<MethodReturns<T, M>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The public preview types were better here.
@@ -26,13 +27,11 @@ expectTypeOf(people.objectAt(0)).toEqualTypeOf<Person | undefined>(); | |||
expectTypeOf(people.objectsAt([1, 2, 3])).toEqualTypeOf<Array<Person | undefined>>(); | |||
|
|||
expectTypeOf(people.filterBy('isHappy')).toMatchTypeOf<Person[]>(); | |||
expectTypeOf(people.filterBy('isHappy')).toMatchTypeOf<MutableArray<Person>>(); | |||
expectTypeOf(people.filterBy('isHappy')).toMatchTypeOf<NativeArray<Person>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NativeArray !== MutableArray
@@ -153,7 +153,7 @@ expectTypeOf(Ember.Application.create()).toEqualTypeOf<Ember.Application>(); | |||
expectTypeOf(new Ember.ApplicationInstance()).toEqualTypeOf<Ember.ApplicationInstance>(); | |||
expectTypeOf(Ember.ApplicationInstance.create()).toEqualTypeOf<Ember.ApplicationInstance>(); | |||
// Ember.Array | |||
const a1: Ember.Array<string> = Ember.A([]); | |||
const a1: Ember.NativeArray<string> = Ember.A([]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NativeArray !== EmberArray
* The final definition of NativeArray removes all native methods. This is the list of removed methods | ||
* when run in Chrome 106. | ||
*/ | ||
type IGNORED_MUTABLE_ARRAY_METHODS = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The below should be the same as internal.
@@ -4,6 +4,9 @@ declare module '@ember/array' { | |||
import Enumerable from '@ember/array/-private/enumerable'; | |||
import NativeArray from '@ember/array/-private/native-array'; | |||
|
|||
// We don't currently attempt to fully type-check compound keys | |||
type CompoundPropertyKey<T> = `${keyof T & string}.${string}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bring these more in line with internal types.
@@ -45,12 +45,12 @@ declare module '@ember/array/mutable' { | |||
* Pop object from array or nil if none are left. Works just like `pop()` but | |||
* it is KVO-compliant. | |||
*/ | |||
popObject(): T; | |||
popObject(): T | null | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These types were incorrect before.
a6735ca
to
e3d387e
Compare
@@ -792,6 +790,10 @@ interface EmberArray<T> extends Enumerable { | |||
@return {Object} Found item or `undefined`. | |||
@public | |||
*/ | |||
find<S extends T, Target = void>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: conventionally/idiomatically it's U
in a case like this (since T → U in the alphabet). Not a thing I care about changing, to be sure, just worth noting!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I just copied this from somewhere else :)
/** | ||
* The final definition of NativeArray removes all native methods. This is the list of removed methods | ||
* when run in Chrome 106. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note for posterity/future readers looking at this PR historically—we definitely need this, but we need even more to just get the heck rid of it, since this could in principle change dynamically over time.
|
||
expectTypeOf(arr).toMatchTypeOf<MutableArray<Foo>>(); | ||
|
||
expectTypeOf(arr.replace(1, 1, [foo])).toEqualTypeOf<void>(); | ||
// TODO: Why doesn't this fail? | ||
// @ts-expect-error invalid item |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's nice that all of these fail now the way we want them to. 👍🏼
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we just needed a better test item :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of tweaks needed, but this is a really nice step forward. 💙
catalogEntriesByType(type: string): string[] { | ||
let namespaces = Namespace.NAMESPACES; | ||
let types: string[] = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Documentation is very apt to be incorrect in this space, though; folks who wrote the docs likely were not thinking about the differences (because the differences are subtle!). Let's avoid changing the behavior here—we can always do so in a later PR, but keeping types changes separate from behavior changes will help us avoid snafus.
let containerDebugAdapter = this.containerDebugAdapter; | ||
|
||
let stringTypes = containerDebugAdapter.canCatalogEntriesByType('model') | ||
? containerDebugAdapter.catalogEntriesByType('model') | ||
: this._getObjectsOnNamespaces(); | ||
|
||
// New adapters return strings instead of classes. | ||
let klassTypes = emberA(stringTypes).map((name) => { | ||
let klassTypes = stringTypes.map((name) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the one hand, this is definitely a change I'm excited to be able to make; on the other, my note here is the same as above. Let's do it in a separate PR so it's easy to trace back to if for some reason it ends up being an issue for folks.
let namespaces = Namespace.NAMESPACES; | ||
let types: string[] = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, presumably.
3623985
to
02c9eed
Compare
02c9eed
to
c1c9a47
Compare
1. Using the keyof constraint feature introduced in 4.1, avoid ever introducing types with properties whose type is `never` for the non-method fields. 2. Handle recursively applying this constraint to nested methods for the `EmberMethodsOf` type, which has additional complexity to handle Ember's string-based dispatch. Combined with (1), this actually *simplifies* the types, eliminating one layer of conditional types.
c1c9a47
to
072830a
Compare
This PR does a couple of things to improve array types:
Fixes #20230