-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Fix contextual typing on yield and return expressions in generator function #49736
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
Conversation
|
@typescript-bot perf test this |
|
Heya @gabritto, I've started to run the perf test suite on this PR at e955108. You can monitor the build here. Update: The results are in! |
|
@gabritto Here they are:
CompilerComparison Report - main..49736
System
Hosts
Scenarios
System
Hosts
Scenarios
Developer Information: |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
andrewbranch
left a 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.
Makes sense to me. Definitely get @rbuckton to double check though.
src/compiler/checker.ts
Outdated
| if (!iterationTypes) { | ||
| if (contextualReturnType.flags & TypeFlags.Union) { | ||
| contextualReturnType = filterType(contextualReturnType, type => { | ||
| const generator = createGeneratorReturnType(anyType, anyType, anyType, (functionFlags & FunctionFlags.Async) !== 0); |
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.
Maybe this should be hoisted out to a createTypeChecker singleton anyGenerator? (Maybe lazily initialized though)
|
@typescript-bot perf test this |
|
Heya @gabritto, I've started to run the perf test suite on this PR at e955108. You can monitor the build here. Update: The results are in! |
rbuckton
left a 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.
I think the approach is sound, but you're doing extra work by comparing the Generator type to each constituent.
src/compiler/checker.ts
Outdated
| if (contextualReturnType.flags & TypeFlags.Union) { | ||
| contextualReturnType = filterType(contextualReturnType, type => { | ||
| const generator = createGeneratorReturnType(anyType, anyType, anyType, (functionFlags & FunctionFlags.Async) !== 0); | ||
| return checkTypeAssignableTo(generator, type, /*errorNode*/ 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.
getIterationTypeOfGeneratorFunctionReturnType will do this work for you (returning undefined if its not generator-like), would this work instead?
const isAsyncGenerator = (functionFlags & functionFlags.Async) !== 0;
const iterationReturnType = mapType(contextualReturnType,
type => getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsyncGenerator));
if (iterationReturnType) {
return undefined;
}mapType will remove undefined results, and will return undefined if all results were undefined, so it can also act as a filter.
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 problem is that getIterationTypeOfGeneratorFunctionReturnType returns, e.g., any for type number, which is why I went for removing the useless union components in the first place. Should I somehow change the behavior of getIterationTypeOfGeneratorFunctionReturnType instead? That seemed more tricky.
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 returns any if the type passed is a number type? It's only supposed to return any if the type passed in is any. For anything else that isn't iterable it's supposed to return undefined.
Most of the relevant functions used by getIterationTypeOfX have something like the following at the top of the function:
if (isTypeAny(type)) {
return anyIterationTypes;
}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.
Yes, it returns any. What happens is: we call getIterationTypeOfGeneratorFunctionReturnType with type number. This calls getIterationTypesOfGeneratorFunctionReturnType, which then returns getIterationTypesOfIterable(number, use, /*errorNode*/ undefined) || getIterationTypesOfIterator(number, resolver, /*errorNode*/ undefined).
getIterationTypesOfIterable(number, use, /*errorNode*/ undefined) returns undefined, so we return getIterationTypesOfIterator(number, resolver, /*errorNode*/ undefined).
getIterationTypesOfIterator(number, resolver, /*errorNode*/ undefined) eventually just tries to get the types of properties next, return, throw, which are all any for type number. So it returns something like IteratorType { next = any, throw = any, return = any }, from which we conclude that the generator return type of type number is any.
So yeah, in short, getIterationTypeOfGeneratorFunctionReturnType returns any for a non-iterable/iterator type like number, it doesn't return undefined.
I don't understand all the subtleties involved, so that's why I went with filtering the union by checking generator type assignability, but maybe I could be filtering using some of the other getIterationTypesX functions?
I think filtering by calling getIterationTypesOfIterable and seeing if it's undefined could maybe work at first, but the fact that when it returns undefined we also call getIterationTypesOfIterator seemed to suggest to me that getIterationTypesOfIterable didn't cover all of the cases of iterator/iterable types.
Let me know what you think.
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 seems getIterationTypesOfIteratorSlow may be doing the wrong thing here. It should be returning noIterationTypes for number:
function getIterationTypesOfIteratorSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) {
const iterationTypes = combineIterationTypes([
getIterationTypesOfMethod(type, resolver, "next", errorNode),
getIterationTypesOfMethod(type, resolver, "return", errorNode),
getIterationTypesOfMethod(type, resolver, "throw", errorNode),
]);
return setCachedIterationTypes(type, resolver.iteratorCacheKey, iterationTypes);
}getIterationTypesOfMethod(number, resolver, "next", errorNode)- This is probably the culprit, as it should returnundefinedfor typenumber, but instead returnsanyIterationTypesdue to this logic:// Both async and non-async iterators *must* have a `next` method. const methodSignatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : emptyArray; if (methodSignatures.length === 0) { if (errorNode) { const diagnostic = methodName === "next" ? resolver.mustHaveANextMethodDiagnostic : resolver.mustBeAMethodDiagnostic; error(errorNode, diagnostic, methodName); } return methodName === "next" ? anyIterationTypes : undefined; // ^- should probably just be `return undefined;` }
getIterationTypesOfMethod(number, resolver, "return", errorNode)- This looks like it returnsundefinedfor typenumber, which is correct.getIterationTypesOfMethod(number, resolver, "throw", errorNode)- This looks like it returnsundefinedfor typenumber, which is correct.
The results of those three methods are passed to combineIterationTypes, which would then return noIterationTypes once the iteration types for the "next" method are corrected.
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 need to look into this a bit further, as changing this results in some other additional diagnostics that we may not want.
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 return value for getIterationTypesOfIteratorSlow should be fixed by #50146.
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 ended up not using mapType in the way you suggested, because in the previous version of the code, when we call getIterationTypeOfGeneratorFunctionReturnType on a union, although it maps the workers to each component type in the union, the way it combines the resulting iterationTypes is specific, so I don't think we'd get the same result if we just called mapType, which just unions the resulting mapped types.
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 it should have produced the same type for IterationTypeKind.Return, since the logic in getIterationTypesOfIterable related to union types just appends the resulting types and unions them for yield and return, with a bit more in the way of caching and error reporting. The only case that would differ would be for IterationTypeKind.Next, since the next types are intersected rather than unioned.
src/compiler/checker.ts
Outdated
| if (!node.asteriskToken && contextualReturnType.flags & TypeFlags.Union) { | ||
| contextualReturnType = filterType(contextualReturnType, type => { | ||
| const generator = createGeneratorReturnType(anyType, anyType, anyType, isAsyncGenerator); | ||
| return checkTypeAssignableTo(generator, type, /*errorNode*/ 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.
The same idea applies here as above.
|
@gabritto Here they are:
CompilerComparison Report - main..49736
System
Hosts
Scenarios
TSServerComparison Report - main..49736
System
Hosts
Scenarios
Developer Information: |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@typescript-bot perf test this |
|
Heya @gabritto, I've started to run the perf test suite on this PR at e955108. You can monitor the build here. Update: The results are in! |
|
@gabritto Here they are:
CompilerComparison Report - main..49736
System
Hosts
Scenarios
TSServerComparison Report - main..49736
System
Hosts
Scenarios
Developer Information: |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@typescript-bot perf test this |
|
Heya @gabritto, I've started to run the perf test suite on this PR at f6132ca. You can monitor the build here. Update: The results are in! |
|
@gabritto Here they are:
CompilerComparison Report - main..49736
System
Hosts
Scenarios
TSServerComparison Report - main..49736
System
Hosts
Scenarios
Developer Information: |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Fixes #45596.
Also complemented the fix for #35995.