Skip to content
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

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
127 changes: 62 additions & 65 deletions src/compiler/checker.ts
Expand Up @@ -1793,6 +1793,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (type === emptyGenericType) return undefined;
return type;
},
createTypeReference: (target, typeArguments) => {
if (target !== emptyGenericType && getObjectFlags(target) & ObjectFlags.ClassOrInterface) {
const interfaceType = target as InterfaceType;
if (some(interfaceType.typeParameters)) {
return createTypeReference(interfaceType as GenericType, typeArguments);
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
}
}
},
isSymbolAccessible,
isArrayType,
isTupleType,
Expand Down Expand Up @@ -2115,7 +2123,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

var anyIterationTypes = createIterationTypes(anyType, anyType, anyType);
var anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType);
var defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`.

var asyncIterationTypesResolver: IterationTypesResolver = {
iterableCacheKey: "iterationTypesOfAsyncIterable",
Expand Down Expand Up @@ -6823,7 +6830,39 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
let typeArgumentNodes: readonly TypeNode[] | undefined;
if (typeArguments.length > 0) {
const typeParameterCount = (type.target.typeParameters || emptyArray).length;
let typeParameterCount: number;
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
// 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.
if (
isReferenceToType(type, getGlobalIterableType(/*reportErrors*/ false)) ||
isReferenceToType(type, getGlobalIterableIteratorType(/*reportErrors*/ false)) ||
isReferenceToType(type, getGlobalAsyncIterableType(/*reportErrors*/ false)) ||
isReferenceToType(type, getGlobalAsyncIterableIteratorType(/*reportErrors*/ false))
) {
if (
!type.node || !isTypeReferenceNode(type.node) || !type.node.typeArguments ||
type.node.typeArguments.length < typeParameterCount
) {
while (typeParameterCount > 0) {
const typeArgument = typeArguments[typeParameterCount - 1];
const defaultType = getDefaultFromTypeParameter(type.target.typeParameters[typeParameterCount - 1]);
if (!defaultType || !isTypeIdenticalTo(typeArgument, defaultType)) {
break;
}
typeParameterCount--;
}
}
}
}
else {
typeParameterCount = 0;
}

typeArgumentNodes = mapToTypeNodes(typeArguments.slice(i, typeParameterCount), context);
}
const flags = context.flags;
Expand Down Expand Up @@ -16373,31 +16412,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function getGlobalAsyncIterableType(reportErrors: boolean) {
return (deferredGlobalAsyncIterableType ||= getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
return (deferredGlobalAsyncIterableType ||= getGlobalType("AsyncIterable" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType;
}

function getGlobalAsyncIteratorType(reportErrors: boolean) {
return (deferredGlobalAsyncIteratorType ||= getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType;
}

function getGlobalAsyncIterableIteratorType(reportErrors: boolean) {
return (deferredGlobalAsyncIterableIteratorType ||= getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
return (deferredGlobalAsyncIterableIteratorType ||= getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType;
}

function getGlobalAsyncGeneratorType(reportErrors: boolean) {
return (deferredGlobalAsyncGeneratorType ||= getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType;
}

function getGlobalIterableType(reportErrors: boolean) {
return (deferredGlobalIterableType ||= getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
return (deferredGlobalIterableType ||= getGlobalType("Iterable" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType;
}

function getGlobalIteratorType(reportErrors: boolean) {
return (deferredGlobalIteratorType ||= getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType;
}

function getGlobalIterableIteratorType(reportErrors: boolean) {
return (deferredGlobalIterableIteratorType ||= getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
return (deferredGlobalIterableIteratorType ||= getGlobalType("IterableIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType;
}

function getGlobalGeneratorType(reportErrors: boolean) {
Expand Down Expand Up @@ -16500,7 +16539,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function createIterableType(iteratedType: Type): Type {
return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType]);
return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType, voidType, undefinedType]);
}

function createArrayType(elementType: Type, readonly?: boolean): ObjectType {
Expand Down Expand Up @@ -37188,28 +37227,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
returnType = resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || unknownType;
nextType = resolver.resolveIterationType(nextType, /*errorNode*/ undefined) || unknownType;
if (globalGeneratorType === emptyGenericType) {
// Fall back to the global IterableIterator if returnType is assignable to the expected return iteration
// type of IterableIterator, and the expected next iteration type of IterableIterator is assignable to
// nextType.
const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false);
const iterationTypes = globalType !== emptyGenericType ? getIterationTypesOfGlobalIterableType(globalType, resolver) : undefined;
const iterableIteratorReturnType = iterationTypes ? iterationTypes.returnType : anyType;
const iterableIteratorNextType = iterationTypes ? iterationTypes.nextType : undefinedType;
if (
isTypeAssignableTo(returnType, iterableIteratorReturnType) &&
isTypeAssignableTo(iterableIteratorNextType, nextType)
) {
if (globalType !== emptyGenericType) {
return createTypeFromGenericGlobalType(globalType, [yieldType]);
}

// The global IterableIterator type doesn't exist, so report an error
resolver.getGlobalIterableIteratorType(/*reportErrors*/ true);
return emptyObjectType;
// Fall back to the global IterableIterator type.
const globalIterableIteratorType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false);
if (globalIterableIteratorType !== emptyGenericType) {
return createTypeFromGenericGlobalType(globalIterableIteratorType, [yieldType, returnType, nextType]);
}

// The global Generator type doesn't exist, so report an error
resolver.getGlobalGeneratorType(/*reportErrors*/ true);
resolver.getGlobalIterableIteratorType(/*reportErrors*/ true);
return emptyObjectType;
}

Expand Down Expand Up @@ -43769,12 +43794,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return getCachedIterationTypes(type, resolver.iterableCacheKey);
}

function getIterationTypesOfGlobalIterableType(globalType: Type, resolver: IterationTypesResolver) {
const globalIterationTypes = getIterationTypesOfIterableCached(globalType, resolver) ||
getIterationTypesOfIterableSlow(globalType, resolver, /*errorNode*/ undefined, /*errorOutputContainer*/ undefined, /*noCache*/ false);
return globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes;
}

/**
* Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like
* type from from common heuristics.
Expand All @@ -43788,28 +43807,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* `getIterationTypesOfIterable` instead.
*/
function getIterationTypesOfIterableFast(type: Type, resolver: IterationTypesResolver) {
// As an optimization, if the type is an instantiation of one of the following global types, then
// just grab its related type argument:
// - `Iterable<T>` or `AsyncIterable<T>`
// - `IterableIterator<T>` or `AsyncIterableIterator<T>`
let globalType: Type;
if (
isReferenceToType(type, globalType = resolver.getGlobalIterableType(/*reportErrors*/ false)) ||
isReferenceToType(type, globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false))
) {
const [yieldType] = getTypeArguments(type as GenericType);
// The "return" and "next" types of `Iterable` and `IterableIterator` are defined by the
// iteration types of their `[Symbol.iterator]()` method. The same is true for their async cousins.
// While we define these as `any` and `undefined` in our libs by default, a custom lib *could* use
// different definitions.
const { returnType, nextType } = getIterationTypesOfGlobalIterableType(globalType, resolver);
return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType));
}

// As an optimization, if the type is an instantiation of the following global type, then
// just grab its related type arguments:
// - `Iterable<T, TReturn, TNext>` or `AsyncIterable<T, TReturn, TNext>`
// - `IterableIterator<T, TReturn, TNext>` or `AsyncIterableIterator<T, TReturn, TNext>`
// - `Generator<T, TReturn, TNext>` or `AsyncGenerator<T, TReturn, TNext>`
if (isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) {
if (
isReferenceToType(type, resolver.getGlobalIterableType(/*reportErrors*/ false)) ||
isReferenceToType(type, resolver.getGlobalIterableIteratorType(/*reportErrors*/ false)) ||
isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))
) {
const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType);
return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType));
}
Expand Down Expand Up @@ -43861,7 +43868,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
isForOfStatement(errorNode.parent) &&
errorNode.parent.expression === errorNode &&
getGlobalAsyncIterableType(/*reportErrors*/ false) !== emptyGenericType &&
isTypeAssignableTo(type, getGlobalAsyncIterableType(/*reportErrors*/ false))
isTypeAssignableTo(type, createTypeFromGenericGlobalType(getGlobalAsyncIterableType(/*reportErrors*/ false), [anyType, anyType, anyType]))
);
return errorAndMaybeSuggestAwait(errorNode, suggestAwait, message, typeToString(type));
}
Expand Down Expand Up @@ -43927,22 +43934,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
*/
function getIterationTypesOfIteratorFast(type: Type, resolver: IterationTypesResolver) {
// As an optimization, if the type is an instantiation of one of the following global types,
// then just grab its related type argument:
// - `IterableIterator<T>` or `AsyncIterableIterator<T>`
// then just grab its related type argumenst:
// - `IterableIterator<T, TReturn, TNext>` or `AsyncIterableIterator<T, TReturn, TNext>`
// - `Iterator<T, TReturn, TNext>` or `AsyncIterator<T, TReturn, TNext>`
// - `Generator<T, TReturn, TNext>` or `AsyncGenerator<T, TReturn, TNext>`
const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false);
if (isReferenceToType(type, globalType)) {
const [yieldType] = getTypeArguments(type as GenericType);
// The "return" and "next" types of `IterableIterator` and `AsyncIterableIterator` are defined by the
// iteration types of their `next`, `return`, and `throw` methods. While we define these as `any`
// and `undefined` in our libs by default, a custom lib *could* use different definitions.
const globalIterationTypes = getIterationTypesOfIteratorCached(globalType, resolver) ||
getIterationTypesOfIteratorSlow(globalType, resolver, /*errorNode*/ undefined, /*errorOutputContainer*/ undefined, /*noCache*/ false);
const { returnType, nextType } = globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes;
return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType));
}
if (
isReferenceToType(type, resolver.getGlobalIterableIteratorType(/*reportErrors*/ false)) ||
isReferenceToType(type, resolver.getGlobalIteratorType(/*reportErrors*/ false)) ||
isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))
) {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Expand Up @@ -5200,6 +5200,7 @@ export interface TypeChecker {
/** @internal */ getPromiseType(): Type;
/** @internal */ getPromiseLikeType(): Type;
/** @internal */ getAsyncIterableType(): Type | undefined;
/** @internal */ createTypeReference(target: Type, typeArguments: readonly Type[] | undefined): Type | undefined;

/**
* Returns true if the "source" type is assignable to the "target" type.
Expand Down