-
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
lib Fix Part 2/6 – Array-related methods #50450
base: main
Are you sure you want to change the base?
lib Fix Part 2/6 – Array-related methods #50450
Conversation
Actually this is not as complicated as you think (there are actually no breaking changes). The large amount of changes is really only due to |
valueOf(): Uint8ClampedArray; | ||
|
||
[index: number]: number; | ||
} | ||
|
||
interface Uint8ClampedArrayConstructor { | ||
readonly prototype: Uint8ClampedArray; | ||
new(length: number): Uint8ClampedArray; | ||
new(length?: number): Uint8ClampedArray; |
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'm confused about these TypedArray types.
They are introduced in ES6 (ES2015), why are they in es5.d.ts? (Can we stop maintaining these old spec versions and merge them all)
This constructor without parameter is now declared in es2017.typedarrays.d.ts
, but in es6 spec chapter 22.2.1.1 %TypedArray% ( ) they are already allowed.
@typescript-bot run dt |
Heya @sandersn, I've started to run the diff-based user code test suite on this PR at 4bcd472. You can monitor the build here. Update: The results are in! |
Heya @sandersn, I've started to run the diff-based top-repos suite on this PR at 4bcd472. You can monitor the build here. Update: The results are in! |
@sandersn Here are the results of running the user test suite comparing Everything looks good! |
@sandersn Here are the results of running the top-repos suite comparing Something interesting changed - please have a look. Details
|
|
||
/** | ||
* Returns a new array from a set of elements. | ||
* @param items A set of elements to include in the new array object. | ||
*/ | ||
of<T>(...items: T[]): T[]; | ||
of<T extends any[]>(...items: T): T; |
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.
possible break: better typing of tuples, meaning more precise downstream errors.
* @param end If not specified, length of the this object is used as its default value. | ||
*/ | ||
copyWithin(target: number, start: number, end?: number): this; | ||
copyWithin(target: number, start: number, end?: number): T[]; |
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.
possible break: subtypes of Array will now just be Array, missing any of the subtype methods.
Is the only reason for this change consistency?
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 has to be long.
Returning this
in {Array, %TypedArray%}.prototype.{fill, copyWithin, sort}
seems to be correct, but TypeScript's type system is purely type-of-value-based, not instance-based. And because a subtype such as a tuple, or the generic parameters of a type, can contain the information of the values, returning this
, which means an object with a same type, is technically incorrect for these methods which mutate the original object in a disruptive way. For example:
declare const foo: [42, 1, 2, 3];
const bar = foo.fill(42); // !!?
interface ArraySubtype extends Array<string> {
0: "foo";
}
const bar: ArraySubtype = ["foo", "bar", "baz"];
const bar1 = bar.sort(); // !!!
interface Int8ArraySubtype extends Int8Array {
0: 42;
}
const baz = new Int8Array([42, 1, 2, 3]) as Int8ArraySubtype;
const baz1 = baz.sort(); // ?!!
In fact, I'd done the some thing on RegExp
(it's a deprecated method though):
Line 988 in facef94
compile(pattern: string, flags?: string): RegExp; |
I understand these are all rare cases, but since they return the same instance, people who find the original type desirable could just use the original thing. It just makes sense, and just because these are all rare cases it has to be corrected.
In contrast, the following is better typed this
. That's why the this
keyword in type level is misleading:
class Foo<T> {
constructor(public foo: T) {}
clone(): this {
return Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this));
}
}
class Bar<T> extends Foo<T> {
bar = true;
}
new Bar(42).clone(); // still a Bar<number>
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.
A use case of the above:
interface Array<T> {
slice(): this;
}
(But then for ReadonlyArray
, should the same method overload be typed this & T[]
?)
(idea from #36554 (comment))
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.
Is there a bug for this? Like the flatMap change in es2019, this isn't worth fixing unless it's breaking somebody's actual code, because any fix will break other code. Backward compatibility like this is the reason, for example, that we haven't been able to make overriding methods inherit parameter types.
* order, until it finds one where predicate returns true. If such an element is found, find | ||
* immediately returns that element value. Otherwise, find returns undefined. | ||
* @param thisArg If provided, it will be used as the this value for each invocation of | ||
* predicate. If it is not provided, undefined is used instead. |
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.
from the repl, this === global
when not provided. I'd either leave off the last sentence, or reference the rules for this
.
* @param predicate find calls predicate once for each element of the array, in ascending | ||
* order, until it finds one where predicate returns true. If such an element is found, find | ||
* immediately returns that element value. Otherwise, find returns undefined. | ||
* @param thisArg If provided, it will be used as the this value for each invocation of |
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.
* @param thisArg If provided, it will be used as the this value for each invocation of | |
* @param thisArg If provided, thisArg will be used as the this value for each invocation of | |
* Returns the value of the first element in the array where predicate is true, and undefined | ||
* otherwise. | ||
* @param predicate find calls predicate once for each element of the array, in ascending | ||
* order, until it finds one where predicate returns true. If such an element is found, find |
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.
* order, until it finds one where predicate returns true. If such an element is found, find | |
* order, until predicate returns true for some element. If such an element is found, find |
I would personally drop the second sentence and change the last one to start "If no element is found,". The second sentence is redundant with the first, though, and redundancy can be useful for understanding, so it's fine if you want to keep it.
@@ -182,7 +182,7 @@ interface BigInt64Array { | |||
* @param thisArg An object to which the this keyword can refer in the predicate function. | |||
* If thisArg is omitted, undefined is used as the this value. | |||
*/ | |||
filter(predicate: (value: bigint, index: number, array: BigInt64Array) => any, thisArg?: any): BigInt64Array; | |||
filter(predicate: (value: bigint, index: number, array: BigInt64Array) => unknown, thisArg?: any): BigInt64Array; |
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 probably won't break anything. But might!
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 guess I would do it the other way round, i.e. changing all unknown
to any
then.
find<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S | undefined; | ||
|
||
/** | ||
* Returns the value of the first element in the array where predicate is true, and 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.
looking at the change to unknown
in es2020.bigint reminds me that technically 'true' here is technically 'truthy'. I don't know if it's worth calling out in the text, though.
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.
Yup, but this is another issue: whether there should be a syntax to allow returning anything in type predicates.
@@ -271,7 +271,7 @@ interface BigInt64Array { | |||
* the accumulation. The first call to the callbackfn function provides this value as an argument | |||
* instead of an array value. | |||
*/ | |||
reduce(callbackfn: (previousValue: bigint, currentValue: bigint, currentIndex: number, array: BigInt64Array) => bigint): bigint; | |||
reduce(callbackfn: (previousValue: bigint, currentValue: bigint, currentIndex: number, array: BigInt64Array) => bigint, initialValue?: bigint): bigint; |
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 change makes sense, but I don't understand why this overload exists in the first place.
/** | ||
* Returns the primitive value of the array. | ||
*/ | ||
valueOf(): T[]; |
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.
possible (but unlikely) break since valueOf now has a more specific type
@@ -1245,8 +1249,7 @@ interface ReadonlyArray<T> { | |||
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array. | |||
* @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value. | |||
*/ | |||
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T): T; | |||
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T, initialValue: T): T; | |||
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T, initialValue?: T): T; |
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 can't imagine how this will be a breaking change, but removing/adding overloads nearly always is somehow
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.
BTW is #7014 not resolved also due to compatibility? It has been an issue for years.
I marked what I think would be breaking changes. @andrewbranch Can you look at this for wording and possible breaks as well? @RyanCavanaugh you might have opinions too. |
To what extent are breaking changes acceptable? I agree that reverting some of these changes reduces the risk of causing compatibility problems, but to address the issues, I still think some of them are unavoidable. The recent changes to Anyway, I would like to wait until all reviewers reach a consensus on what must be reverted and what are acceptable before I commit changes to it. |
I notice that #47351 is an issue with |
Definitely a separate PR. Smaller changes to the standard lib are better. |
I would say changes should target an improvement that feels like a "missing feature", or a fix for a specific bugs. Small improvements, like improved consistency or fixes for problems that nobody has noticed, are not worth it. However, @RyanCavanaugh I'm sure has additional opinions and nuance. Edit: Specifically, for this PR, fixing the three bugs in the description to add new methods is worthwhile, but needs to be done in a way that minimises changes elsewhere. |
General Information
PR separated out from #49855, because there might be some members expecting smaller PRs. As mentioned in the comments from the big PR, whether to review a single, large PR or 6 smaller PRs is up to the TypeScript Team to decide. I couldn't have found a better way for this; hopefully this will not bring any trouble to the Team.
This PR partially fixes #49773.
For details and the track list about the changes, please see #49773.
Part 2/6, Array-related methods
This is still a large PR, but I found no way to further split it because all the changes are related.
Fix #44191
Fix #45198
Fix #45199