-
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
Add TReturn/TNext to Iterable et al #58243
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
@jakebailey, @weswigham: Am I missing something? The bot says there were interesting changes but it links to a clean pipeline result. |
src/compiler/checker.ts
Outdated
if (type === intrinsicMarkerType && symbol.escapedName === "BuiltinIteratorReturn") { | ||
type = strictBuiltinIteratorReturn ? undefinedType : anyType; | ||
} |
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.
Can this be done in the same way as IntrinsicTypeKind
like IntrinsicTypeKind.NoInfer
, rather than hardcoding the name in a couple of places?
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.
No. IntrinsicTypeKind.NoInfer
(and all of the other IntrinsicTypeKind
entries) indicate a generic type mapping, e.g. type NoInfer<T> = intrinsic
maps T
to something else, which in that instance is a substitution type. Since BuiltinIteratorReturn
is not generic it isn't picked up by getTypeAliasInstantiation
, so never hits those code paths.
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.
If anything, it would exist in its own map of non-generic intrinsic
types, but since it would be the only entry there's no need for a map yet. When I started implementing this, I considered adding an arity
to intrinsicTypeKinds
, but it was far too complex to be worth it.
I believe this is what we decided on in the meeting and seems sound; I think that all that's left here is just to run perf on the final state (I don't think its been done in a bit, just the extended tests). At least, once ADO is back online: https://status.dev.azure.com/ |
@typescript-bot perf test |
Starting jobs; this comment will be updated as builds start and complete.
|
The perf shouldn't have changed since the only real changes since it was last run were swapping around what type to use when the flag is enabled, but I'll try it again when the AzDO outage is over |
@typescript-bot perf test |
@rbuckton Here they are:
tscComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
tsserverComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
startupComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
Developer Information: |
@jakebailey: It looks like the perf run was nominal. |
if (type.target.typeParameters) { | ||
typeParameterCount = Math.min(type.target.typeParameters.length, typeArguments.length); | ||
|
||
// Maybe we should do this for more types, but for now we only elide type arguments that are |
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.
what other kinds of types would benefit from 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.
Potentially any type since this simplifies the emit output to avoid repeating type arguments for type parameters with defaults, but rather than take that on in this PR I focused on this subset to improve back compat.
type.node.typeArguments.length < typeParameterCount | ||
) { | ||
while (typeParameterCount > 0) { | ||
const typeArgument = typeArguments[typeParameterCount - 1]; |
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.
an incidental question, but: what is a case where typeArguments.length > 0
but also !type.node.typeArguments
is true?
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.
type argument inference
// Maybe we should do this for more types, but for now we only elide type arguments that are | ||
// identical to their associated type parameters' defaults for `Iterable`, `IterableIterator`, | ||
// `AsyncIterable`, and `AsyncIterableIterator` to provide backwards-compatible .d.ts emit due | ||
// to each now having three type parameters instead of only one. |
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.
overall this seems like an odd way to support this backward compatibility, but I don't understand the problem space well enough to suggest anything else
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.
Realistically, this is all about what the type "looks" like, right? Or are we really needing to depend on this emit still emitting the "old thing" to continue to work right for downstream users of d.ts files?
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.
Consider an application that is currently built using TS 5.5 that depends on a library that is also being built using TS 5.5. The library chooses to upgrade its version of TypeScript to 5.6, and the new emit produces Iterable<T, any, any>
where it previously produced Iterable<T>
. If the library makes no other code changes and publishes their new build, the application cannot take the update without also updating TypeScript because the expected arity of Iterable
has changed.
The library can't just ship a forward declaration of interface Iterable<T, TReturn = any, TNext = any> {}
, because our checker performs arity checks on built-ins, and the application might not be running with skipLibCheck
, so one way or another they would have errors they cannot silence without possibly giving up the dependency verification that skipLibChecks: false
offers.
@@ -132,6 +132,7 @@ export class SortedMap<K, V> { | |||
this._copyOnWrite = false; | |||
} | |||
} | |||
return 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.
will all generators need to return undefined
now? do you have an estimate of how much code that breaks?
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 reading type baselines, it looks like their return type changes to any
instead of 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.
This is specifically because this is an implementation of the native Map<K, V>
interface, which is now specified to return IterableIterator<T, BuiltinIteratorReturn>
(which is IterableIterator<T, undefined>
under the new flag).
I tested this on the most iterator-y codebase I know (effect) and it only caused one break (positively), so that's good. |
In all of the PRs I've created so far for top libraries, it's usually only been one or two cases where they were (possibly unknowingly) depending on the result 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.
I still have a couple of background questions. I think this PR is worth trying in the beta to see if it's too breaky. But all the first-order breaks look good, and the higher-order ones are, I bet, rare. (like, just fpt-ts :)
We avoided making this change in the past as it was very breaky, but at some point we need to address this discrepancy. Having incorrect types here is also causing problems with properly typing the Iterator Helpers proposal.
This also adds a new
BuiltinIteratorReturn
intrinsic type whose actual type is determined by the state of a newstrictBuiltinIteratorReturn
compiler option:"strictBuiltinIteratorReturn": false
-any
(emulates current behavior forIterableIterator
)"strictBuiltinIteratorReturn": true
-undefined
The
strictBuiltinIteratorReturn
is astrict
option flag, meaning that it is enabled automatically when you set"strict": true
in yourtsconfig.json
.The new
BuiltinIteratorReturn
type is passed as theTReturn
type argument for built-ins usingIterableIterator
orAsyncIterableIterator
to enable stricter checks against the result of callingnext()
on the iterator.Enabling
strictBuiltinIteratorReturn
results in a more accurate and type-safe return type for thenext()
methods of iterators produced by built-ins likeArray
,Set
,Map
, etc.:vs
Since this is a
strict
flag, there is a fair amount of existing code that will likely produce new compilation errors as a result of this change:This now fails as there is no correlation between
set.size
and the iterator produced bykeys()
, so the compiler is unaware that this constraint has been validated. If you are certain iterator will always yield at least one value, you can use a non-null assertion operator to strip off the| undefined
:A follow-on PR to TypeScript-DOM-lib-generator can be found here: microsoft/TypeScript-DOM-lib-generator#1713
DefinitelyTyped breaks will be addressed by DefinitelyTyped/DefinitelyTyped#69632
fp-ts
breaks will be addressed by gcanti/fp-ts#1949webpack
breaks will be addressed by webpack/webpack#18591Fixes #33353
Fixes #52998
Fixes #43750
Closes #56517
Related #58222