From 0d393e324acb8353d386b4723756e914feefbd27 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 15 May 2023 18:15:24 -0700 Subject: [PATCH 01/90] WIP: custom instantiation --- src/compiler/checker.ts | 385 +++++++++++++++++++++++++++++++++++++++- src/compiler/types.ts | 2 + 2 files changed, 384 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 98cd87923761e..193a17e6bc5bf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -359,6 +359,7 @@ import { getThisParameter, getTrailingSemicolonDeferringWriter, getTypeParameterFromJsDoc, + getTypeParameterOwner, getTypesPackageName, getUseDefineForClassFields, group, @@ -948,6 +949,7 @@ import { shouldResolveJsRequire, Signature, SignatureDeclaration, + SignatureDeclarationBase, SignatureFlags, SignatureKind, singleElementArray, @@ -18013,6 +18015,167 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return links.resolvedType; } + function getNarrowConditionalType(root: ConditionalRoot, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + let result; + let extraTypes: Type[] | undefined; + let tailCount = 0; + // We loop here for an immediately nested conditional type in the false position, effectively treating + // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for + // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of + // another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive + // cases we increment the tail recursion counter and stop after 1000 iterations. + while (true) { + if (tailCount === 1000) { + error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); + return errorType; + } + // >> TODO: (some) calls to `instantiateType` should be calls to instantiate narrow type worker etc + // const checkType = root.isDistributive + // ? instantiateNarrowType(getActualTypeVariable(root.checkType), narrowMapper, mapper) + // : instantiateNarrowType(getActualTypeVariable(root.checkType), narrowMapper, mapper); + const checkType = instantiateNarrowType(getActualTypeVariable(root.checkType), narrowMapper, mapper); + const extendsType = instantiateType(root.extendsType, mapper); + if (checkType === errorType || extendsType === errorType) { + return errorType; + } + if (checkType === wildcardType || extendsType === wildcardType) { + return wildcardType; + } + // When the check and extends types are simple tuple types of the same arity, we defer resolution of the + // conditional type when any tuple elements are generic. This is such that non-distributable conditional + // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. + const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) && + length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements); + // const checkTypeDeferred = isDeferredType(checkType, checkTuples); // >> TODO: what do we do about this? + const checkTypeDeferred = false; + let combinedMapper: TypeMapper | undefined; + if (root.inferTypeParameters) { + // When we're looking at making an inference for an infer type, when we get its constraint, it'll automagically be + // instantiated with the context, so it doesn't need the mapper for the inference contex - however the constraint + // may refer to another _root_, _uncloned_ `infer` type parameter [1], or to something mapped by `mapper` [2]. + // [1] Eg, if we have `Foo` and `Foo` - `B` is constrained to `T`, which, in turn, has been instantiated + // as `number` + // Conversely, if we have `Foo`, `B` is still constrained to `T` and `T` is instantiated as `A` + // [2] Eg, if we have `Foo` and `Foo` where `Q` is mapped by `mapper` into `number` - `B` is constrained to `T` + // which is in turn instantiated as `Q`, which is in turn instantiated as `number`. + // So we need to: + // * Clone the type parameters so their constraints can be instantiated in the context of `mapper` (otherwise theyd only get inference context information) + // * Set the clones to both map the conditional's enclosing `mapper` and the original params + // * instantiate the extends type with the clones + // * incorporate all of the component mappers into the combined mapper for the true and false members + // This means we have three mappers that need applying: + // * The original `mapper` used to create this conditional + // * The mapper that maps the old root type parameter to the clone (`freshMapper`) + // * The mapper that maps the clone to its inference result (`context.mapper`) + const freshParams = sameMap(root.inferTypeParameters, maybeCloneTypeParameter); + const freshMapper = freshParams !== root.inferTypeParameters ? createTypeMapper(root.inferTypeParameters, freshParams) : undefined; + const context = createInferenceContext(freshParams, /*signature*/ undefined, InferenceFlags.None); + if (freshMapper) { + const freshCombinedMapper = combineTypeMappers(mapper, freshMapper); + for (const p of freshParams) { + if (root.inferTypeParameters.indexOf(p) === -1) { + p.mapper = freshCombinedMapper; + } + } + } + if (!checkTypeDeferred) { + // We don't want inferences from constraints as they may cause us to eagerly resolve the + // conditional type instead of deferring resolution. Also, we always want strict function + // types rules (i.e. proper contravariance) for inferences. + inferTypes(context.inferences, checkType, instantiateType(extendsType, freshMapper), InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + } + const innerMapper = combineTypeMappers(freshMapper, context.mapper); + // It's possible for 'infer T' type paramteters to be given uninstantiated constraints when the + // those type parameters are used in type references (see getInferredTypeParameterConstraint). For + // that reason we need context.mapper to be first in the combined mapper. See #42636 for examples. + combinedMapper = mapper ? combineTypeMappers(innerMapper, mapper) : innerMapper; + } + // Instantiate the extends type including inferences for 'infer T' type parameters + const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; + // We attempt to resolve the conditional type only when the check and extends types are non-generic + if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { + // Return falseType for a definitely false extends check. We check an instantiations of the two + // types with type parameters mapped to the wildcard type, the most permissive instantiations + // possible (the wildcard type is assignable to and from all types). If those are not related, + // then no instantiations will be and we can just return the false branch type. + if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { + // Return union of trueType and falseType for 'any' since it matches anything + if (checkType.flags & TypeFlags.Any) { + (extraTypes || (extraTypes = [])).push( + instantiateNarrowType(getTypeFromTypeNode(root.node.trueType), narrowMapper, combinedMapper || mapper)); + } + // If falseType is an immediately nested conditional type that isn't distributive or has an + // identical checkType, switch to that type and loop. + const falseType = getTypeFromTypeNode(root.node.falseType); + if (falseType.flags & TypeFlags.Conditional) { + const newRoot = (falseType as ConditionalType).root; + if (newRoot.node.parent === root.node && (!newRoot.isDistributive || newRoot.checkType === root.checkType)) { + root = newRoot; + continue; + } + if (canTailRecurse(falseType, mapper)) { + continue; + } + } + result = instantiateNarrowType(falseType, narrowMapper, mapper); + break; + } + // Return trueType for a definitely true extends check. We check instantiations of the two + // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter + // that has no constraint. This ensures that, for example, the type + // type Foo = T extends { x: string } ? string : number + // doesn't immediately resolve to 'string' instead of being deferred. + if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { + const trueType = getTypeFromTypeNode(root.node.trueType); + const trueMapper = combinedMapper || mapper; + if (canTailRecurse(trueType, trueMapper)) { + continue; + } + result = instantiateNarrowType(trueType, narrowMapper, trueMapper); + break; + } + } + // Return a deferred type for a check that is neither definitely true nor definitely false + result = createType(TypeFlags.Conditional) as ConditionalType; + result.root = root; + result.checkType = instantiateType(root.checkType, mapper); // >> TODO: what `instantiate` should this one be? + result.extendsType = instantiateType(root.extendsType, mapper); // >> TODO: what `instantiate` should this one be? + result.mapper = mapper; + result.combinedMapper = combinedMapper; + result.aliasSymbol = aliasSymbol || root.aliasSymbol; + result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 + break; + } + // return extraTypes ? getUnionType(append(extraTypes, result)) : result; // >> TODO: should be intersection + return extraTypes ? getIntersectionType(append(extraTypes, result)) : result; + // We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and + // (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check + // type. Note that recursion is possible only through aliased conditional types, so we only increment the tail + // recursion counter for those. + function canTailRecurse(newType: Type, newMapper: TypeMapper | undefined) { // >> TODO: do we need to update this? + if (newType.flags & TypeFlags.Conditional && newMapper) { + const newRoot = (newType as ConditionalType).root; + if (newRoot.outerTypeParameters) { + const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper); + const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)); + const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); + const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined; + if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { + root = newRoot; + mapper = newRootMapper; + aliasSymbol = undefined; + aliasTypeArguments = undefined; + if (newRoot.aliasSymbol) { + tailCount++; + } + return true; + } + } + } + return false; + } + } + function getTypeFromInferTypeNode(node: InferTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { @@ -19119,6 +19282,153 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } + function instantiateNarrowType(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type; + function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type | undefined; + function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type | undefined { + return type ? instantiateNarrowTypeWithAlias(type, narrowMapper, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : type; + } + + function instantiateNarrowTypeWithAlias(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { + if (!couldContainTypeVariables(type)) { + return type; + } + if (instantiationDepth === 100 || instantiationCount >= 5000000) { + // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement + // or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types + // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. + tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); + error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); + return errorType; + } + totalInstantiationCount++; + instantiationCount++; + instantiationDepth++; + const result = instantiateNarrowTypeWorker(type, narrowMapper, mapper, aliasSymbol, aliasTypeArguments); + instantiationDepth--; + return result; + } + + function instantiateNarrowTypeWorker( + type: Type, + narrowMapper: TypeMapper, + mapper: TypeMapper | undefined, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { + const flags = type.flags; + if (flags & TypeFlags.TypeParameter) { + return getMappedType(type, combineTypeMappers(mapper, narrowMapper)); + } + // if (flags & TypeFlags.Object) { + // const objectFlags = (type as ObjectType).objectFlags; + // if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + // if (objectFlags & ObjectFlags.Reference && !(type as TypeReference).node) { + // const resolvedTypeArguments = (type as TypeReference).resolvedTypeArguments; + // const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); + // return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type as TypeReference).target, newTypeArguments) : type; + // } + // if (objectFlags & ObjectFlags.ReverseMapped) { + // return instantiateReverseMappedType(type as ReverseMappedType, mapper); + // } + // return getObjectTypeInstantiation(type as TypeReference | AnonymousType | MappedType, mapper, aliasSymbol, aliasTypeArguments); + // } + // return type; + // } + // if (flags & TypeFlags.UnionOrIntersection) { + // const origin = type.flags & TypeFlags.Union ? (type as UnionType).origin : undefined; + // const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (origin as UnionOrIntersectionType).types : (type as UnionOrIntersectionType).types; + // const newTypes = instantiateTypes(types, mapper); + // if (newTypes === types && aliasSymbol === type.aliasSymbol) { + // return type; + // } + // const newAliasSymbol = aliasSymbol || type.aliasSymbol; + // const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + // return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ? + // getIntersectionType(newTypes, newAliasSymbol, newAliasTypeArguments) : + // getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments); + // } + // if (flags & TypeFlags.Index) { + // return getIndexType(instantiateType((type as IndexType).type, mapper)); + // } + // if (flags & TypeFlags.TemplateLiteral) { + // return getTemplateLiteralType((type as TemplateLiteralType).texts, instantiateTypes((type as TemplateLiteralType).types, mapper)); + // } + // if (flags & TypeFlags.StringMapping) { + // return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper)); + // } + if (flags & TypeFlags.IndexedAccess) { + // >> TODO: what's that extra alias stuff here? + const newAliasSymbol = aliasSymbol || type.aliasSymbol; + const newAliasTypeArguments = aliasSymbol || !mapper ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + const objectType = instantiateType((type as IndexedAccessType).objectType, mapper); + let indexType = instantiateType((type as IndexedAccessType).indexType, mapper); + if (indexType.flags & TypeFlags.TypeParameter) { + indexType = getMappedType(indexType, narrowMapper); + } + return getIndexedAccessType( + objectType, + indexType, + (type as IndexedAccessType).accessFlags | AccessFlags.Writing, // Get the writing type + /*accessNode*/ undefined, + newAliasSymbol, + newAliasTypeArguments); + } + if (flags & TypeFlags.Conditional) { + return getNarrowConditionalTypeInstantiation( + type as ConditionalType, + narrowMapper, + mapper ? combineTypeMappers((type as ConditionalType).mapper, mapper) : (type as ConditionalType).mapper, + aliasSymbol, + aliasTypeArguments); + } + // if (flags & TypeFlags.Substitution) { + // const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); + // const newConstraint = instantiateType((type as SubstitutionType).constraint, mapper); + // // A substitution type originates in the true branch of a conditional type and can be resolved + // // to just the base type in the same cases as the conditional type resolves to its true branch + // // (because the base type is then known to satisfy the constraint). + // if (newBaseType.flags & TypeFlags.TypeVariable && isGenericType(newConstraint)) { + // return getSubstitutionType(newBaseType, newConstraint); + // } + // if (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { + // return newBaseType; + // } + // return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); + // } + return type; + } + + function getNarrowConditionalTypeInstantiation(type: ConditionalType, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + const root = type.root; + if (root.outerTypeParameters) { + // We are instantiating a conditional type that has one or more type parameters in scope. Apply the + // mapper to the type parameters to produce the effective list of type arguments, and compute the + // instantiation cache key from the type IDs of the type arguments. + const typeArguments = mapper ? map(root.outerTypeParameters, t => getMappedType(t, mapper)) : root.outerTypeParameters; + // >> No caching + // const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); + // let result = root.instantiations!.get(id); + // if (!result) { + let result; + const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); + const checkType = root.checkType; + const distributionType = root.isDistributive ? getMappedType(checkType, combineTypeMappers(newMapper, narrowMapper)) : undefined; + // Distributive conditional types are distributed over union types. For example, when the + // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the + // result is (A extends U ? X : Y) | (B extends U ? X : Y). + if (distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { + result = mapTypeWithAlias(getReducedType(distributionType), t => getNarrowConditionalType(root, narrowMapper, prependTypeMapping(checkType, t, newMapper)), aliasSymbol, aliasTypeArguments); + if (result.flags & TypeFlags.Union) { + result = getIntersectionType((result as UnionType).types, aliasSymbol, aliasTypeArguments); + } + } + else { + result =getNarrowConditionalType(root, narrowMapper, newMapper, aliasSymbol, aliasTypeArguments); + } + // root.instantiations!.set(id, result); + // } + return result; + } + return type; + } + function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) { const innerMappedType = instantiateType(type.mappedType, mapper); if (!(getObjectFlags(innerMappedType) & ObjectFlags.Mapped)) { @@ -27749,7 +28059,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'. const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) && someType(type, isGenericTypeWithUnionConstraint) && - (isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode)); + ((reference.flags & NodeFlags.Synthesized) || isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode)); // >> TODO: find other way to signal this return substituteConstraints ? mapType(type, getBaseConstraintOrType) : type; } @@ -42362,11 +42672,35 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const unwrappedExprType = functionFlags & FunctionFlags.Async ? checkAwaitedType(exprType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) : exprType; - if (unwrappedReturnType) { + const outerTypeParameters = getOuterTypeParameters(container, /*includeThisTypes*/ false); + const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); + const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); + let actualReturnType = unwrappedReturnType; + if (queryTypeParameters) { + const narrowMapper = createTypeMapper(queryTypeParameters, queryTypeParameters.map(tp => { + const originalName = tp.exprName; + const fakeName = factory.cloneNode(originalName); // Fake a narrowable node. + setParent(fakeName, node.parent); + setNodeFlags(fakeName, fakeName.flags | NodeFlags.Synthesized); + fakeName.flowNode = node.flowNode; + return checkExpression(fakeName); + })); + actualReturnType = instantiateNarrowTypeWorker( + unwrappedReturnType, + narrowMapper, + /* mapper*/ undefined, + /*aliasSymbol*/ undefined, + /*aliasTypeArguments*/ undefined, + ); + // /*writing*/ true); + } + // if (unwrappedReturnType) { + if (actualReturnType) { // If the function has a return type, but promisedType is // undefined, an error will be reported in checkAsyncFunctionReturnType // so we don't need to report one here. - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); + // checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, actualReturnType, node, node.expression); } } } @@ -42376,6 +42710,51 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + function isQueryTypeParameter(typeParameter: TypeParameter): typeParameter is TypeParameter & { exprName: EntityName } { + if (typeParameter.exprName) { + return true; // >> TODO: also have a way to mark a type parameter as not a query one + } + if (isThisTypeParameter(typeParameter)) { + return false; + } + if (!typeParameter.symbol) { + return false; + // >> TODO: deal with synthetic tps? + } + // getTypeParameterOwner + typeParameter.symbol + const declaration = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; + const owner = getTypeParameterOwner(declaration)!; + if (!isFunctionLike(owner)) { + return false; // Owner is class or interface + } + const references: Node[] = []; + forEachChild(owner, doSomething); + if (references.length === 1) { + const reference = references[0]; + let exprName; + if (isParameter(reference.parent) && (exprName = getNameOfDeclaration(reference.parent))) { + typeParameter.exprName = exprName as Identifier; + return true; + } + } + return false; + + function doSomething(node: Node) { + if (isNodeDescendantOf(node, (owner as SignatureDeclarationBase).type)) { + return; + } + if (isTypeReferenceNode(node)) { + const type = getTypeFromTypeNode(node); + if (type.flags & TypeFlags.TypeParameter && isTypeIdenticalTo(type, typeParameter)) { + references.push(node); + } + return; + } + forEachChild(node, doSomething); + } + } + function checkWithStatement(node: WithStatement) { // Grammar checking for withStatement if (!checkGrammarStatementInAmbientContext(node)) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c97bbaffa2834..f086ac00c9d8f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6587,6 +6587,8 @@ export interface TypeParameter extends InstantiableType { isThisType?: boolean; /** @internal */ resolvedDefaultType?: Type; + /** @internal */ + exprName?: EntityName; } /** @internal */ From a1d9f89f5054034899d8ffd467898407223a3201 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 25 May 2023 17:43:52 -0700 Subject: [PATCH 02/90] fix more things --- src/compiler/checker.ts | 99 ++++++++++++------- src/compiler/types.ts | 1 + .../reference/controlFlowGenericTypes.types | 4 +- .../returnTypeParameterWithModules.js | 4 +- .../returnTypeParameterWithModules.symbols | 40 ++++---- .../returnTypeParameterWithModules.types | 4 +- .../returnTypeParameterWithModules.ts | 4 +- 7 files changed, 95 insertions(+), 61 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 193a17e6bc5bf..e812a2bdd4575 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19307,11 +19307,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { instantiationDepth--; return result; } - function instantiateNarrowTypeWorker( + type: Type, narrowMapper: TypeMapper, - mapper: TypeMapper | undefined, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { + mapper: TypeMapper | undefined, + aliasSymbol: Symbol | undefined, + aliasTypeArguments: readonly Type[] | undefined): Type { const flags = type.flags; if (flags & TypeFlags.TypeParameter) { return getMappedType(type, combineTypeMappers(mapper, narrowMapper)); @@ -29159,9 +29161,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getContextualTypeForReturnExpression(node: Expression, contextFlags: ContextFlags | undefined): Type | undefined { const func = getContainingFunction(node); if (func) { + const functionFlags = getFunctionFlags(func); + const links = getNodeLinks(node); + if (links.contextualReturnType) { + if (functionFlags & FunctionFlags.Async) { + return getUnionType([links.contextualReturnType, createPromiseLikeType(links.contextualReturnType)]); + } + return links.contextualReturnType; + } let contextualReturnType = getContextualReturnType(func, contextFlags); if (contextualReturnType) { - const functionFlags = getFunctionFlags(func); if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function const isAsyncGenerator = (functionFlags & FunctionFlags.Async) !== 0; if (contextualReturnType.flags & TypeFlags.Union) { @@ -29177,8 +29186,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function // Get the awaited type without the `Awaited` alias - const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeNoAlias); - return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); + // const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeNoAlias); + const contextualAwaitedType = getAwaitedTypeNoAlias(contextualReturnType); // >> TODO: test this change separately + return contextualAwaitedType && + getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } return contextualReturnType; // Regular function or Generator function @@ -42656,44 +42667,65 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const returnType = getReturnTypeOfSignature(signature); const functionFlags = getFunctionFlags(container); if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { - const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; + // const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; if (container.kind === SyntaxKind.SetAccessor) { if (node.expression) { error(node, Diagnostics.Setters_cannot_return_a_value); } } else if (container.kind === SyntaxKind.Constructor) { + const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; if (node.expression && !checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression)) { error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); } } else if (getReturnTypeFromAnnotation(container)) { const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; + let actualReturnType = unwrappedReturnType; + /* Begin weird stuff */ + const links = node.expression && getNodeLinks(node.expression); + if (links && !links.contextualReturnType) { + const outerTypeParameters = getOuterTypeParameters(container, /*includeThisTypes*/ false); + const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); + const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); + if (queryTypeParameters) { + const narrowMapper = createTypeMapper(queryTypeParameters, queryTypeParameters.map(tp => { + const originalName = tp.exprName; + const fakeName = factory.cloneNode(originalName); // Fake a narrowable node. + setParent(fakeName, node.parent); + setNodeFlags(fakeName, fakeName.flags | NodeFlags.Synthesized); + fakeName.flowNode = node.flowNode; + // >> TODO: this call to checkExpression might report errors, + // >> and so might throw when trying to get span for fakeName. + // >> TODO: also, it shouldn't throw errors. + const exprType = checkExpression(fakeName); + // >> TODO: is there a better way of detecting that narrowing will be useless? + // >> https://github.com/microsoft/TypeScript/issues/51525 might help + if (getConstraintOfTypeParameter(tp)) { + const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); + if (narrowableConstraintType === exprType) { + return tp; // Don't narrow if narrowing didn't do anything but obtain constraints + // >> TODO: exclude such type parameters from the mapper + } + } + return exprType; + })); + // >> TODO: don't instantiate at the top level? + actualReturnType = instantiateNarrowTypeWorker( + unwrappedReturnType, + narrowMapper, + /*mapper*/ undefined, + /*aliasSymbol*/ undefined, + /*aliasTypeArguments*/ undefined, + ); + } + links.contextualReturnType = actualReturnType; // >> maybe don't store this if we don't need to? + } + /* End weird stuff */ + const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; const unwrappedExprType = functionFlags & FunctionFlags.Async ? checkAwaitedType(exprType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) : exprType; - const outerTypeParameters = getOuterTypeParameters(container, /*includeThisTypes*/ false); - const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); - let actualReturnType = unwrappedReturnType; - if (queryTypeParameters) { - const narrowMapper = createTypeMapper(queryTypeParameters, queryTypeParameters.map(tp => { - const originalName = tp.exprName; - const fakeName = factory.cloneNode(originalName); // Fake a narrowable node. - setParent(fakeName, node.parent); - setNodeFlags(fakeName, fakeName.flags | NodeFlags.Synthesized); - fakeName.flowNode = node.flowNode; - return checkExpression(fakeName); - })); - actualReturnType = instantiateNarrowTypeWorker( - unwrappedReturnType, - narrowMapper, - /* mapper*/ undefined, - /*aliasSymbol*/ undefined, - /*aliasTypeArguments*/ undefined, - ); - // /*writing*/ true); - } // if (unwrappedReturnType) { if (actualReturnType) { // If the function has a return type, but promisedType is @@ -42721,19 +42753,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; // >> TODO: deal with synthetic tps? } - // getTypeParameterOwner - typeParameter.symbol const declaration = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; const owner = getTypeParameterOwner(declaration)!; - if (!isFunctionLike(owner)) { - return false; // Owner is class or interface + if (!isFunctionLikeDeclaration(owner)) { + return false; // Owner is class or interface, or a signature without an implementation } const references: Node[] = []; forEachChild(owner, doSomething); if (references.length === 1) { const reference = references[0]; let exprName; - if (isParameter(reference.parent) && (exprName = getNameOfDeclaration(reference.parent))) { + if (isParameter(reference.parent) && reference.parent.parent === owner && (exprName = getNameOfDeclaration(reference.parent))) { typeParameter.exprName = exprName as Identifier; return true; } @@ -42741,6 +42771,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; function doSomething(node: Node) { + if (isFunctionLikeDeclaration(node.parent) && node === node.parent.body) { + return; + } if (isNodeDescendantOf(node, (owner as SignatureDeclarationBase).type)) { return; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f086ac00c9d8f..27f5074f4ebc2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6058,6 +6058,7 @@ export interface NodeLinks { spreadIndices?: { first: number | undefined, last: number | undefined }; // Indices of first and last spread elements in array literal parameterInitializerContainsUndefined?: boolean; // True if this is a parameter declaration whose type annotation contains "undefined". fakeScopeForSignatureDeclaration?: boolean; // True if this is a fake scope injected into an enclosing declaration chain. + contextualReturnType?: Type; // If the node is a return statement's expression, then this is the contextual return type. } /** @internal */ diff --git a/tests/baselines/reference/controlFlowGenericTypes.types b/tests/baselines/reference/controlFlowGenericTypes.types index 7684cd439112d..110477e19ca44 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.types +++ b/tests/baselines/reference/controlFlowGenericTypes.types @@ -184,11 +184,11 @@ export function bounceAndTakeIfA(value: AB): AB { >value : "A" return value; ->value : AB +>value : "A" } else { return value; ->value : AB +>value : "B" } } diff --git a/tests/baselines/reference/returnTypeParameterWithModules.js b/tests/baselines/reference/returnTypeParameterWithModules.js index 4790fe1341b94..37ebde04eb22c 100644 --- a/tests/baselines/reference/returnTypeParameterWithModules.js +++ b/tests/baselines/reference/returnTypeParameterWithModules.js @@ -8,8 +8,8 @@ module M2 { import A = M1 export function compose() { A.reduce(arguments, compose2); - }; - export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { + }; + export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { return function (x) { return g(f(x)); } }; }; diff --git a/tests/baselines/reference/returnTypeParameterWithModules.symbols b/tests/baselines/reference/returnTypeParameterWithModules.symbols index f147c3f79a23e..2ddf9cd4a6e1d 100644 --- a/tests/baselines/reference/returnTypeParameterWithModules.symbols +++ b/tests/baselines/reference/returnTypeParameterWithModules.symbols @@ -42,30 +42,30 @@ module M2 { >A : Symbol(A, Decl(returnTypeParameterWithModules.ts, 5, 11)) >reduce : Symbol(A.reduce, Decl(returnTypeParameterWithModules.ts, 0, 11)) >arguments : Symbol(arguments) ->compose2 : Symbol(compose2, Decl(returnTypeParameterWithModules.ts, 9, 6)) +>compose2 : Symbol(compose2, Decl(returnTypeParameterWithModules.ts, 9, 4)) - }; - export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { ->compose2 : Symbol(compose2, Decl(returnTypeParameterWithModules.ts, 9, 6)) ->B : Symbol(B, Decl(returnTypeParameterWithModules.ts, 10, 29)) ->C : Symbol(C, Decl(returnTypeParameterWithModules.ts, 10, 31)) ->D : Symbol(D, Decl(returnTypeParameterWithModules.ts, 10, 34)) ->g : Symbol(g, Decl(returnTypeParameterWithModules.ts, 10, 38)) ->x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 10, 42)) ->B : Symbol(B, Decl(returnTypeParameterWithModules.ts, 10, 29)) ->C : Symbol(C, Decl(returnTypeParameterWithModules.ts, 10, 31)) ->f : Symbol(f, Decl(returnTypeParameterWithModules.ts, 10, 53)) ->x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 10, 58)) ->D : Symbol(D, Decl(returnTypeParameterWithModules.ts, 10, 34)) ->B : Symbol(B, Decl(returnTypeParameterWithModules.ts, 10, 29)) ->x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 10, 72)) ->D : Symbol(D, Decl(returnTypeParameterWithModules.ts, 10, 34)) ->C : Symbol(C, Decl(returnTypeParameterWithModules.ts, 10, 31)) + }; + export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { +>compose2 : Symbol(compose2, Decl(returnTypeParameterWithModules.ts, 9, 4)) +>B : Symbol(B, Decl(returnTypeParameterWithModules.ts, 10, 27)) +>C : Symbol(C, Decl(returnTypeParameterWithModules.ts, 10, 29)) +>D : Symbol(D, Decl(returnTypeParameterWithModules.ts, 10, 32)) +>g : Symbol(g, Decl(returnTypeParameterWithModules.ts, 10, 36)) +>x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 10, 40)) +>B : Symbol(B, Decl(returnTypeParameterWithModules.ts, 10, 27)) +>C : Symbol(C, Decl(returnTypeParameterWithModules.ts, 10, 29)) +>f : Symbol(f, Decl(returnTypeParameterWithModules.ts, 10, 51)) +>x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 10, 56)) +>D : Symbol(D, Decl(returnTypeParameterWithModules.ts, 10, 32)) +>B : Symbol(B, Decl(returnTypeParameterWithModules.ts, 10, 27)) +>x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 10, 70)) +>D : Symbol(D, Decl(returnTypeParameterWithModules.ts, 10, 32)) +>C : Symbol(C, Decl(returnTypeParameterWithModules.ts, 10, 29)) return function (x) { return g(f(x)); } >x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 11, 21)) ->g : Symbol(g, Decl(returnTypeParameterWithModules.ts, 10, 38)) ->f : Symbol(f, Decl(returnTypeParameterWithModules.ts, 10, 53)) +>g : Symbol(g, Decl(returnTypeParameterWithModules.ts, 10, 36)) +>f : Symbol(f, Decl(returnTypeParameterWithModules.ts, 10, 51)) >x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 11, 21)) }; diff --git a/tests/baselines/reference/returnTypeParameterWithModules.types b/tests/baselines/reference/returnTypeParameterWithModules.types index d3601048bce91..97214c54c2773 100644 --- a/tests/baselines/reference/returnTypeParameterWithModules.types +++ b/tests/baselines/reference/returnTypeParameterWithModules.types @@ -46,8 +46,8 @@ module M2 { >arguments : IArguments >compose2 : (g: (x: B) => C, f: (x: D) => B) => (x: D) => C - }; - export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { + }; + export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { >compose2 : (g: (x: B) => C, f: (x: D) => B) => (x: D) => C >g : (x: B) => C >x : B diff --git a/tests/cases/compiler/returnTypeParameterWithModules.ts b/tests/cases/compiler/returnTypeParameterWithModules.ts index 113d45c6df8f2..2ef8cd2eaa5de 100644 --- a/tests/cases/compiler/returnTypeParameterWithModules.ts +++ b/tests/cases/compiler/returnTypeParameterWithModules.ts @@ -7,8 +7,8 @@ module M2 { import A = M1 export function compose() { A.reduce(arguments, compose2); - }; - export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { + }; + export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { return function (x) { return g(f(x)); } }; }; \ No newline at end of file From b9495a988e79515a8f411b210ef25f6532d1d4ee Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 31 May 2023 09:36:29 -0700 Subject: [PATCH 03/90] latest fixes --- src/compiler/checker.ts | 26 ++- src/compiler/utilities.ts | 2 +- .../reference/dependentReturnType1.symbols | 192 ++++++++++++++++++ .../reference/dependentReturnType1.types | 171 ++++++++++++++++ tests/cases/compiler/dependentReturnType1.ts | 145 +++++++++++++ 5 files changed, 525 insertions(+), 11 deletions(-) create mode 100644 tests/baselines/reference/dependentReturnType1.symbols create mode 100644 tests/baselines/reference/dependentReturnType1.types create mode 100644 tests/cases/compiler/dependentReturnType1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e812a2bdd4575..15bebd1e5d965 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18044,8 +18044,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // When the check and extends types are simple tuple types of the same arity, we defer resolution of the // conditional type when any tuple elements are generic. This is such that non-distributable conditional // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. - const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) && - length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements); + // const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) && + // length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements); // const checkTypeDeferred = isDeferredType(checkType, checkTuples); // >> TODO: what do we do about this? const checkTypeDeferred = false; let combinedMapper: TypeMapper | undefined; @@ -18093,7 +18093,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Instantiate the extends type including inferences for 'infer T' type parameters const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; // We attempt to resolve the conditional type only when the check and extends types are non-generic - if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { + // if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { + if (true) { // Return falseType for a definitely false extends check. We check an instantiations of the two // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, @@ -19422,7 +19423,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else { - result =getNarrowConditionalType(root, narrowMapper, newMapper, aliasSymbol, aliasTypeArguments); + result = getNarrowConditionalType(root, narrowMapper, newMapper, aliasSymbol, aliasTypeArguments); } // root.instantiations!.set(id, result); // } @@ -42754,12 +42755,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // >> TODO: deal with synthetic tps? } const declaration = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; - const owner = getTypeParameterOwner(declaration)!; - if (!isFunctionLikeDeclaration(owner)) { + const owner = getTypeParameterOwner(declaration); + if (!owner || !isFunctionLikeDeclaration(owner)) { return false; // Owner is class or interface, or a signature without an implementation } - const references: Node[] = []; - forEachChild(owner, doSomething); + const references: Node[] = []; // All nodes of kind `T` that resolve to the type parameter + owner.parameters.forEach(parameter => forEachChild(parameter, collectReferences)); if (references.length === 1) { const reference = references[0]; let exprName; @@ -42770,7 +42771,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return false; - function doSomething(node: Node) { + function collectReferences(node: Node) { if (isFunctionLikeDeclaration(node.parent) && node === node.parent.body) { return; } @@ -42784,8 +42785,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return; } - forEachChild(node, doSomething); + forEachChild(node, collectReferences); } + + // function collectPath(node: TypeNode) { + // // Assumptions: start from a type parameter/reference node, go up to parents, + + // } } function checkWithStatement(node: WithStatement) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index fde771ab80db5..19a6ddf46c343 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2269,7 +2269,7 @@ export function getErrorSpanForNode(sourceFile: SourceFile, node: Node): TextSpa Debug.assert(pos <= errorNode.end, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809"); } - return createTextSpanFromBounds(pos, errorNode.end); + return createTextSpanFromBounds(pos, errorNode.end); // >> TODO: this might fail if the node is synthetic: don't error then } /** @internal */ diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols new file mode 100644 index 0000000000000..93cc73ae2ef84 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -0,0 +1,192 @@ +=== tests/cases/compiler/dependentReturnType1.ts === +// function f1(x: T): T { +// return x; +// } + +// function f2(x: T): T { +// if (x) { +// return x; +// } +// return x; +// } + +// function f3(x: T, y: T): T { +// return y; +// } + +// interface A { +// 1: number; +// 2: string; +// } + +// function f4(x: T): A[T] { +// if (x === 1) { +// // return "one"; +// return 0; +// } +// else { +// return false; +// } +// } + +// interface One { +// a: "a"; +// b: "b"; +// c: "c"; +// d: "d"; +// } + +// interface Two { +// a: "a"; +// b: "b"; +// e: "e"; +// f: "f"; +// } + +// interface Three { +// a: "a"; +// c: "c"; +// e: "e"; +// g: "g"; +// } + +// interface Four { +// a: "a"; +// d: "d"; +// f: "f"; +// g: "g"; +// } + +// function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { +// if (x === 1 || x === 2) { +// // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; +// return { a: "a" }; +// } +// // Excess property becomes a problem with the change, +// // because we now check assignability to a narrower type... +// return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; +// } + +// interface Animal { +// name: string; +// } + +// interface Dog extends Animal { +// bark: () => string; +// } + +// declare function isDog(x: Animal): x is Dog; +// declare function doggy(x: Dog): number; +// function f12(x: T): T extends Dog ? number : string { +// if (isDog(x)) { // `x` has type `T & Dog` here +// return doggy(x); // Should work +// } +// return ""; // Should not work because we can't express "not a Dog" in the type system +// } + +// function f(entry: EntryId): Entry[EntryId] { +// const entries = {} as Entry; +// return entries[entry]; +// } + +// interface F { +// a: "a", +// b: "b", +// } + +// declare function takesA(x: "a"): number; +// function f(x: T) { +// if (x === "a") { +// takesA(x); // we narrow x +// x; // we don't narrow x in `getNarrowableTypeForReference`, therefore we don't narrow it at all +// } +// } + +// declare function takeA(val: 'A'): void; +// export function bounceAndTakeIfA(value: AB): AB { +// if (value === 'A') { +// takeA(value); +// takeAB(value); +// return value; +// } +// function takeAB(val: AB): void {} +// } + +// export function bbb(value: AB): "a" { +// if (value === "a") { +// return value; +// } +// } + +// declare function takeA(val: 'A'): void; + +// export function bounceAndTakeIfA(value: AB): AB { +// if (value === 'A') { +// takeA(value); +// return value; +// } +// } + +function conditionalProducingIf( +>conditionalProducingIf : Symbol(conditionalProducingIf, Decl(dependentReturnType1.ts, 0, 0)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 128, 39)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 128, 48)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 128, 57)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 128, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 128, 39)) + + arg: Arg, +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 128, 98)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 128, 67)) + + cond: (arg: LeftIn | RightIn) => arg is LeftIn, +>cond : Symbol(cond, Decl(dependentReturnType1.ts, 129, 13)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 130, 11)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 128, 39)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 130, 11)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) + + produceLeftOut: (arg: LeftIn) => LeftOut, +>produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 130, 51)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 131, 21)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 128, 48)) + + produceRightOut: (arg: RightIn) => RightOut): +>produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 131, 45)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 132, 22)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 128, 39)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 128, 57)) + + Arg extends LeftIn ? LeftOut : RightOut +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 128, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 128, 48)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 128, 57)) +{ + type OK = Arg extends LeftIn ? LeftOut : RightOut; +>OK : Symbol(OK, Decl(dependentReturnType1.ts, 134, 1)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 128, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 128, 48)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 128, 57)) + + if (cond(arg)) { +>cond : Symbol(cond, Decl(dependentReturnType1.ts, 129, 13)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 128, 98)) + + return produceLeftOut(arg); +>produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 130, 51)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 128, 98)) + + } else { + return produceRightOut(arg as RightIn) as OK; +>produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 131, 45)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 128, 98)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 128, 39)) +>OK : Symbol(OK, Decl(dependentReturnType1.ts, 134, 1)) + } +} diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types new file mode 100644 index 0000000000000..2fe23777cba22 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType1.types @@ -0,0 +1,171 @@ +=== tests/cases/compiler/dependentReturnType1.ts === +// function f1(x: T): T { +// return x; +// } + +// function f2(x: T): T { +// if (x) { +// return x; +// } +// return x; +// } + +// function f3(x: T, y: T): T { +// return y; +// } + +// interface A { +// 1: number; +// 2: string; +// } + +// function f4(x: T): A[T] { +// if (x === 1) { +// // return "one"; +// return 0; +// } +// else { +// return false; +// } +// } + +// interface One { +// a: "a"; +// b: "b"; +// c: "c"; +// d: "d"; +// } + +// interface Two { +// a: "a"; +// b: "b"; +// e: "e"; +// f: "f"; +// } + +// interface Three { +// a: "a"; +// c: "c"; +// e: "e"; +// g: "g"; +// } + +// interface Four { +// a: "a"; +// d: "d"; +// f: "f"; +// g: "g"; +// } + +// function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { +// if (x === 1 || x === 2) { +// // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; +// return { a: "a" }; +// } +// // Excess property becomes a problem with the change, +// // because we now check assignability to a narrower type... +// return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; +// } + +// interface Animal { +// name: string; +// } + +// interface Dog extends Animal { +// bark: () => string; +// } + +// declare function isDog(x: Animal): x is Dog; +// declare function doggy(x: Dog): number; +// function f12(x: T): T extends Dog ? number : string { +// if (isDog(x)) { // `x` has type `T & Dog` here +// return doggy(x); // Should work +// } +// return ""; // Should not work because we can't express "not a Dog" in the type system +// } + +// function f(entry: EntryId): Entry[EntryId] { +// const entries = {} as Entry; +// return entries[entry]; +// } + +// interface F { +// a: "a", +// b: "b", +// } + +// declare function takesA(x: "a"): number; +// function f(x: T) { +// if (x === "a") { +// takesA(x); // we narrow x +// x; // we don't narrow x in `getNarrowableTypeForReference`, therefore we don't narrow it at all +// } +// } + +// declare function takeA(val: 'A'): void; +// export function bounceAndTakeIfA(value: AB): AB { +// if (value === 'A') { +// takeA(value); +// takeAB(value); +// return value; +// } +// function takeAB(val: AB): void {} +// } + +// export function bbb(value: AB): "a" { +// if (value === "a") { +// return value; +// } +// } + +// declare function takeA(val: 'A'): void; + +// export function bounceAndTakeIfA(value: AB): AB { +// if (value === 'A') { +// takeA(value); +// return value; +// } +// } + +function conditionalProducingIf( +>conditionalProducingIf : (arg: Arg, cond: (arg: LeftIn | RightIn) => arg is LeftIn, produceLeftOut: (arg: LeftIn) => LeftOut, produceRightOut: (arg: RightIn) => RightOut) => Arg extends LeftIn ? LeftOut : RightOut + + arg: Arg, +>arg : Arg + + cond: (arg: LeftIn | RightIn) => arg is LeftIn, +>cond : (arg: LeftIn | RightIn) => arg is LeftIn +>arg : LeftIn | RightIn + + produceLeftOut: (arg: LeftIn) => LeftOut, +>produceLeftOut : (arg: LeftIn) => LeftOut +>arg : LeftIn + + produceRightOut: (arg: RightIn) => RightOut): +>produceRightOut : (arg: RightIn) => RightOut +>arg : RightIn + + Arg extends LeftIn ? LeftOut : RightOut +{ + type OK = Arg extends LeftIn ? LeftOut : RightOut; +>OK : Arg extends LeftIn ? LeftOut : RightOut + + if (cond(arg)) { +>cond(arg) : boolean +>cond : (arg: LeftIn | RightIn) => arg is LeftIn +>arg : Arg + + return produceLeftOut(arg); +>produceLeftOut(arg) : LeftOut +>produceLeftOut : (arg: LeftIn) => LeftOut +>arg : Arg & LeftIn + + } else { + return produceRightOut(arg as RightIn) as OK; +>produceRightOut(arg as RightIn) as OK : Arg extends LeftIn ? LeftOut : RightOut +>produceRightOut(arg as RightIn) : RightOut +>produceRightOut : (arg: RightIn) => RightOut +>arg as RightIn : RightIn +>arg : Arg + } +} diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts new file mode 100644 index 0000000000000..8a662d0318784 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -0,0 +1,145 @@ +// @strict: true +// @noEmit: true + +// function f1(x: T): T { +// return x; +// } + +// function f2(x: T): T { +// if (x) { +// return x; +// } +// return x; +// } + +// function f3(x: T, y: T): T { +// return y; +// } + +// interface A { +// 1: number; +// 2: string; +// } + +// function f4(x: T): A[T] { +// if (x === 1) { +// // return "one"; +// return 0; +// } +// else { +// return false; +// } +// } + +// interface One { +// a: "a"; +// b: "b"; +// c: "c"; +// d: "d"; +// } + +// interface Two { +// a: "a"; +// b: "b"; +// e: "e"; +// f: "f"; +// } + +// interface Three { +// a: "a"; +// c: "c"; +// e: "e"; +// g: "g"; +// } + +// interface Four { +// a: "a"; +// d: "d"; +// f: "f"; +// g: "g"; +// } + +// function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { +// if (x === 1 || x === 2) { +// // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; +// return { a: "a" }; +// } +// // Excess property becomes a problem with the change, +// // because we now check assignability to a narrower type... +// return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; +// } + +// interface Animal { +// name: string; +// } + +// interface Dog extends Animal { +// bark: () => string; +// } + +// declare function isDog(x: Animal): x is Dog; +// declare function doggy(x: Dog): number; +// function f12(x: T): T extends Dog ? number : string { +// if (isDog(x)) { // `x` has type `T & Dog` here +// return doggy(x); // Should work +// } +// return ""; // Should not work because we can't express "not a Dog" in the type system +// } + +// function f(entry: EntryId): Entry[EntryId] { +// const entries = {} as Entry; +// return entries[entry]; +// } + +// interface F { +// a: "a", +// b: "b", +// } + +// declare function takesA(x: "a"): number; +// function f(x: T) { +// if (x === "a") { +// takesA(x); // we narrow x +// x; // we don't narrow x in `getNarrowableTypeForReference`, therefore we don't narrow it at all +// } +// } + +// declare function takeA(val: 'A'): void; +// export function bounceAndTakeIfA(value: AB): AB { +// if (value === 'A') { +// takeA(value); +// takeAB(value); +// return value; +// } +// function takeAB(val: AB): void {} +// } + +// export function bbb(value: AB): "a" { +// if (value === "a") { +// return value; +// } +// } + +// declare function takeA(val: 'A'): void; + +// export function bounceAndTakeIfA(value: AB): AB { +// if (value === 'A') { +// takeA(value); +// return value; +// } +// } + +function conditionalProducingIf( + arg: Arg, + cond: (arg: LeftIn | RightIn) => arg is LeftIn, + produceLeftOut: (arg: LeftIn) => LeftOut, + produceRightOut: (arg: RightIn) => RightOut): + Arg extends LeftIn ? LeftOut : RightOut +{ + type OK = Arg extends LeftIn ? LeftOut : RightOut; + if (cond(arg)) { + return produceLeftOut(arg); + } else { + return produceRightOut(arg as RightIn) as OK; + } +} \ No newline at end of file From af57b3469c8e064595209ebb4cef120bbf8fda9c Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 31 May 2023 12:00:06 -0700 Subject: [PATCH 04/90] fix bare type parameter instantiation --- src/compiler/checker.ts | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a8d5d95ab2517..22ee471e8360b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19273,13 +19273,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } - function instantiateNarrowType(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type; - function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type | undefined; - function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type | undefined { - return type ? instantiateNarrowTypeWithAlias(type, narrowMapper, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : type; + function instantiateNarrowType(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, noTopLevel?: boolean): Type; + function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, noTopLevel?: boolean): Type | undefined; + function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, noTopLevel = false): Type | undefined { + return type ? instantiateNarrowTypeWithAlias(type, narrowMapper, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, noTopLevel) : type; } - function instantiateNarrowTypeWithAlias(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { + function instantiateNarrowTypeWithAlias( + type: Type, + narrowMapper: TypeMapper, + mapper: TypeMapper | undefined, + aliasSymbol: Symbol | undefined, + aliasTypeArguments: readonly Type[] | undefined, + noTopLevel: boolean): Type { if (!couldContainTypeVariables(type)) { return type; } @@ -19294,19 +19300,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { totalInstantiationCount++; instantiationCount++; instantiationDepth++; - const result = instantiateNarrowTypeWorker(type, narrowMapper, mapper, aliasSymbol, aliasTypeArguments); + const result = instantiateNarrowTypeWorker(type, narrowMapper, mapper, aliasSymbol, aliasTypeArguments, noTopLevel); instantiationDepth--; return result; } function instantiateNarrowTypeWorker( - type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, aliasSymbol: Symbol | undefined, - aliasTypeArguments: readonly Type[] | undefined): Type { + aliasTypeArguments: readonly Type[] | undefined, + noTopLevel: boolean): Type { const flags = type.flags; if (flags & TypeFlags.TypeParameter) { + if (noTopLevel) { + return type; + } return getMappedType(type, combineTypeMappers(mapper, narrowMapper)); } // if (flags & TypeFlags.Object) { @@ -42726,13 +42735,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return exprType; })); - // >> TODO: don't instantiate at the top level? - actualReturnType = instantiateNarrowTypeWorker( + // >> TODO: don't instantiate at the top level + actualReturnType = instantiateNarrowType( unwrappedReturnType, narrowMapper, /*mapper*/ undefined, - /*aliasSymbol*/ undefined, - /*aliasTypeArguments*/ undefined, + /*noTopLevel*/ true, ); } links.contextualReturnType = actualReturnType; // >> maybe don't store this if we don't need to? From 01152a0df832520205c9b3498ed315ee89a9efef Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 23 Jun 2023 10:39:21 -0700 Subject: [PATCH 05/90] change binder. BROKEN --- src/compiler/binder.ts | 25 +++ src/compiler/checker.ts | 212 +++++++++++++----- ...alTypeAssignabilityWhenDeferred.errors.txt | 12 +- .../reference/controlFlowGenericTypes.types | 4 +- .../reference/dependentReturnType1.symbols | 115 +++++----- .../reference/dependentReturnType1.types | 95 ++++---- tests/cases/compiler/dependentReturnType1.ts | 83 +++++-- 7 files changed, 365 insertions(+), 181 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index f54a3e0666f00..21354ad626a16 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -65,6 +65,7 @@ import { Expression, ExpressionStatement, findAncestor, + FlowContainer, FlowFlags, FlowLabel, FlowNode, @@ -137,6 +138,7 @@ import { IsBlockScopedContainer, isCallExpression, isClassStaticBlockDeclaration, + isConditionalExpression, isConditionalTypeNode, IsContainer, isDeclaration, @@ -206,6 +208,7 @@ import { isPrototypeAccess, isPushOrUnshiftIdentifier, isRequireCall, + isReturnStatement, isShorthandPropertyAssignment, isSignedNumericLiteral, isSourceFile, @@ -1946,6 +1949,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { } function bindConditionalExpressionFlow(node: ConditionalExpression) { + const isInReturnStatement = isConditionalExpressionInReturnStatement(node); const trueLabel = createBranchLabel(); const falseLabel = createBranchLabel(); const postExpressionLabel = createBranchLabel(); @@ -1953,14 +1957,35 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { currentFlow = finishFlowLabel(trueLabel); bind(node.questionToken); bind(node.whenTrue); + if (isInReturnStatement) { + (node.whenTrue as Node as FlowContainer).flowNode = currentFlow; + } addAntecedent(postExpressionLabel, currentFlow); currentFlow = finishFlowLabel(falseLabel); bind(node.colonToken); bind(node.whenFalse); + if (isInReturnStatement) { + (node.whenFalse as Node as FlowContainer).flowNode = currentFlow; + } addAntecedent(postExpressionLabel, currentFlow); currentFlow = finishFlowLabel(postExpressionLabel); } + function isConditionalExpressionInReturnStatement(node: ConditionalExpression) { + const returnStmt = findAncestor(node.parent, isReturnStatement); + if (!returnStmt) { + return false; + } + let n: Node = node; + while (n !== returnStmt) { + if (!isConditionalExpression(n)) { + return false; + } + n = skipParentheses(n.parent); + } + return true; + } + function bindInitializedVariableFlow(node: VariableDeclaration | ArrayBindingElement) { const name = !isOmittedExpression(node) ? node.name : undefined; if (isBindingPattern(name)) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 22ee471e8360b..6384d105d7a97 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -376,6 +376,7 @@ import { hasEffectiveReadonlyModifier, HasExpressionInitializer, hasExtension, + HasFlowNode, HasIllegalDecorators, HasIllegalModifiers, HasInitializer, @@ -482,6 +483,7 @@ import { isCompoundAssignment, isComputedNonLiteralName, isComputedPropertyName, + isConditionalExpression, isConstructorDeclaration, isConstructorTypeNode, isConstructSignatureDeclaration, @@ -18129,6 +18131,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { result = instantiateNarrowType(trueType, narrowMapper, trueMapper); break; } + // >> TODO: let's see + if (isTypeStrictSubtypeOf(checkType, inferredExtendsType)) { + const trueType = getTypeFromTypeNode(root.node.trueType); + const trueMapper = combinedMapper || mapper; + if (canTailRecurse(trueType, trueMapper)) { + continue; + } + result = instantiateNarrowType(trueType, narrowMapper, trueMapper); + break; + } } // Return a deferred type for a check that is neither definitely true nor definitely false result = createType(TypeFlags.Conditional) as ConditionalType; @@ -19416,10 +19428,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the // result is (A extends U ? X : Y) | (B extends U ? X : Y). if (distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { - result = mapTypeWithAlias(getReducedType(distributionType), t => getNarrowConditionalType(root, narrowMapper, prependTypeMapping(checkType, t, newMapper)), aliasSymbol, aliasTypeArguments); - if (result.flags & TypeFlags.Union) { - result = getIntersectionType((result as UnionType).types, aliasSymbol, aliasTypeArguments); - } + result = mapTypeWithAlias( + getReducedType(distributionType), + t => getNarrowConditionalType( + root, + narrowMapper, + prependTypeMapping(checkType, t, newMapper)), + aliasSymbol, + aliasTypeArguments, + /*useIntersection*/ true); } else { result = getNarrowConditionalType(root, narrowMapper, newMapper, aliasSymbol, aliasTypeArguments); @@ -26378,9 +26395,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Apply a mapping function to a type and return the resulting type. If the source type // is a union type, the mapping function is applied to each constituent type and a union // of the resulting types is returned. - function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined { + function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean, useIntersection?: boolean): Type; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean, useIntersection?: boolean): Type | undefined; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean, useIntersection = false): Type | undefined { if (type.flags & TypeFlags.Never) { return type; } @@ -26403,13 +26420,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } - return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type; + return changed ? mappedTypes && (useIntersection ? getIntersectionType(mappedTypes) : getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal)) : type; } - function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { + function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, useIntersection = false) { return type.flags & TypeFlags.Union && aliasSymbol ? - getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments) : - mapType(type, mapper); + (useIntersection ? getIntersectionType(map((type as UnionType).types, mapper), aliasSymbol, aliasTypeArguments) : getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments)) : + mapType(type, mapper, useIntersection); } function extractTypesOfKind(type: Type, kind: TypeFlags) { @@ -29815,6 +29832,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (index >= 0) { return contextualTypes[index]; } + const links = getNodeLinks(node); + if (node.kind !== SyntaxKind.ReturnStatement && links.contextualReturnType) { + return links.contextualReturnType; + } const { parent } = node; switch (parent.kind) { case SyntaxKind.VariableDeclaration: @@ -42707,56 +42728,127 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if (getReturnTypeFromAnnotation(container)) { const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; let actualReturnType = unwrappedReturnType; - /* Begin weird stuff */ - const links = node.expression && getNodeLinks(node.expression); - if (links && !links.contextualReturnType) { - const outerTypeParameters = getOuterTypeParameters(container, /*includeThisTypes*/ false); - const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); - if (queryTypeParameters) { - const narrowMapper = createTypeMapper(queryTypeParameters, queryTypeParameters.map(tp => { - const originalName = tp.exprName; - const fakeName = factory.cloneNode(originalName); // Fake a narrowable node. - setParent(fakeName, node.parent); - setNodeFlags(fakeName, fakeName.flags | NodeFlags.Synthesized); - fakeName.flowNode = node.flowNode; - // >> TODO: this call to checkExpression might report errors, - // >> and so might throw when trying to get span for fakeName. - // >> TODO: also, it shouldn't throw errors. - const exprType = checkExpression(fakeName); - // >> TODO: is there a better way of detecting that narrowing will be useless? - // >> https://github.com/microsoft/TypeScript/issues/51525 might help - if (getConstraintOfTypeParameter(tp)) { - const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); - if (narrowableConstraintType === exprType) { - return tp; // Don't narrow if narrowing didn't do anything but obtain constraints - // >> TODO: exclude such type parameters from the mapper + // /* Begin weird stuff */ + // const links = node.expression && getNodeLinks(node.expression); + // if (links && !links.contextualReturnType) { + // const outerTypeParameters = getOuterTypeParameters(container, /*includeThisTypes*/ false); + // const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); + // const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); + // if (queryTypeParameters) { + // const narrowMapper = createTypeMapper(queryTypeParameters, queryTypeParameters.map(tp => { + // const originalName = tp.exprName; + // const fakeName = factory.cloneNode(originalName); // Fake a narrowable node. + // setParent(fakeName, node.parent); + // setNodeFlags(fakeName, fakeName.flags | NodeFlags.Synthesized); + // fakeName.flowNode = node.flowNode; + // // >> TODO: this call to checkExpression might report errors, + // // >> and so might throw when trying to get span for fakeName. + // // >> TODO: also, it shouldn't throw errors. + // const exprType = checkExpression(fakeName); + // // >> TODO: is there a better way of detecting that narrowing will be useless? + // // >> https://github.com/microsoft/TypeScript/issues/51525 might help + // if (getConstraintOfTypeParameter(tp)) { + // const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); + // if (narrowableConstraintType === exprType) { + // return tp; // Don't narrow if narrowing didn't do anything but obtain constraints + // // >> TODO: exclude such type parameters from the mapper + // } + // } + // return exprType; + // })); + // // >> TODO: don't instantiate at the top level + // actualReturnType = instantiateNarrowType( + // unwrappedReturnType, + // narrowMapper, + // /*mapper*/ undefined, + // /*noTopLevel*/ true, + // ); + // } + // links.contextualReturnType = actualReturnType; // >> maybe don't store this if we don't need to? + // } + // /* End weird stuff */ + // const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; + // const unwrappedExprType = functionFlags & FunctionFlags.Async + // ? checkAwaitedType(exprType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) + // : exprType; + // // if (unwrappedReturnType) { + // if (actualReturnType) { + // // If the function has a return type, but promisedType is + // // undefined, an error will be reported in checkAsyncFunctionReturnType + // // so we don't need to report one here. + // // checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); + // checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, actualReturnType, node, node.expression); + // } + + checkReturnStatementExpression(node.expression); + + function checkReturnStatementExpression(expr: Expression | undefined): void { + // >> TODO: implement this + // >> TODO: skip parentheses + if (expr && isConditionalExpression(expr)) { + return checkReturnConditionalExpression(expr); + } + // >> ELSE: base case: just do the checks + /* Begin weird stuff */ + const links = expr && getNodeLinks(expr); + if (links && !links.contextualReturnType) { + const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); + const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); + const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); + if (queryTypeParameters) { + const narrowMapper = createTypeMapper(queryTypeParameters, queryTypeParameters.map(tp => { + const originalName = tp.exprName; + const fakeName = factory.cloneNode(originalName); // Fake a narrowable node. + const fakePosition = isConditionalExpression(expr.parent) ? expr : expr.parent; + setParent(fakeName, fakePosition.parent); + setNodeFlags(fakeName, fakeName.flags | NodeFlags.Synthesized); + fakeName.flowNode = (fakePosition as HasFlowNode).flowNode; + // >> TODO: this call to checkExpression might report errors, + // >> and so might throw when trying to get span for fakeName. + // >> TODO: also, it shouldn't throw errors. + const exprType = checkExpression(fakeName); + // >> TODO: is there a better way of detecting that narrowing will be useless? + // >> https://github.com/microsoft/TypeScript/issues/51525 might help + if (getConstraintOfTypeParameter(tp)) { + const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); + if (narrowableConstraintType === exprType) { + return tp; // Don't narrow if narrowing didn't do anything but obtain constraints + // >> TODO: exclude such type parameters from the mapper + } } - } - return exprType; - })); - // >> TODO: don't instantiate at the top level - actualReturnType = instantiateNarrowType( - unwrappedReturnType, - narrowMapper, - /*mapper*/ undefined, - /*noTopLevel*/ true, - ); - } - links.contextualReturnType = actualReturnType; // >> maybe don't store this if we don't need to? - } - /* End weird stuff */ - const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; - const unwrappedExprType = functionFlags & FunctionFlags.Async - ? checkAwaitedType(exprType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) - : exprType; - // if (unwrappedReturnType) { - if (actualReturnType) { - // If the function has a return type, but promisedType is - // undefined, an error will be reported in checkAsyncFunctionReturnType - // so we don't need to report one here. - // checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, actualReturnType, node, node.expression); + return exprType; + })); + // >> TODO: don't instantiate at the top level + actualReturnType = instantiateNarrowType( + unwrappedReturnType, + narrowMapper, + /*mapper*/ undefined, + /*noTopLevel*/ true, + ); + } + links.contextualReturnType = actualReturnType; // >> maybe don't store this if we don't need to? + } + /* End weird stuff */ + const exprType = expr ? checkExpressionCached(expr) : undefinedType; + const unwrappedExprType = functionFlags & FunctionFlags.Async + ? checkAwaitedType( + exprType, + /*withAlias*/ false, + node, // >> TODO: should this be node or expr? + Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) + : exprType; + if (actualReturnType) { + // If the function has a return type, but promisedType is + // undefined, an error will be reported in checkAsyncFunctionReturnType + // so we don't need to report one here. + // checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, actualReturnType, node, expr); // >> TODO: should this be node or expr? + } + } + + function checkReturnConditionalExpression(expr: ConditionalExpression): void { + checkReturnStatementExpression(expr.whenTrue); + checkReturnStatementExpression(expr.whenFalse); } } } diff --git a/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt b/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt index de6b4cb5e7288..19c2ac9f53126 100644 --- a/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt +++ b/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt @@ -6,8 +6,10 @@ tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(63,9): error TS tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(95,23): error TS2345: Argument of type 'string' is not assignable to parameter of type 'Unwrap'. tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(106,3): error TS2322: Type 'Q' is not assignable to type 'InferBecauseWhyNot'. Type '(arg: any) => any' is not assignable to type 'InferBecauseWhyNot'. -tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(116,3): error TS2322: Type 'Q' is not assignable to type 'InferBecauseWhyNotDistributive'. - Type '(arg: any) => any' is not assignable to type 'InferBecauseWhyNotDistributive'. +tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(116,3): error TS2322: Type 'Q' is not assignable to type 'P1 | T'. + Type '(arg: any) => any' is not assignable to type 'P1 | T'. + Type '(arg: any) => any' is not assignable to type 'T'. + 'T' could be instantiated with an arbitrary type which could be unrelated to '(arg: any) => any'. ==== tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts (8 errors) ==== @@ -143,7 +145,9 @@ tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(116,3): error T ): InferBecauseWhyNotDistributive { return x; // should fail ~~~~~~ -!!! error TS2322: Type 'Q' is not assignable to type 'InferBecauseWhyNotDistributive'. -!!! error TS2322: Type '(arg: any) => any' is not assignable to type 'InferBecauseWhyNotDistributive'. +!!! error TS2322: Type 'Q' is not assignable to type 'P1 | T'. +!!! error TS2322: Type '(arg: any) => any' is not assignable to type 'P1 | T'. +!!! error TS2322: Type '(arg: any) => any' is not assignable to type 'T'. +!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to '(arg: any) => any'. } \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowGenericTypes.types b/tests/baselines/reference/controlFlowGenericTypes.types index 110477e19ca44..7684cd439112d 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.types +++ b/tests/baselines/reference/controlFlowGenericTypes.types @@ -184,11 +184,11 @@ export function bounceAndTakeIfA(value: AB): AB { >value : "A" return value; ->value : "A" +>value : AB } else { return value; ->value : "B" +>value : AB } } diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 93cc73ae2ef84..2250cc0a0aac1 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -127,66 +127,59 @@ // } // } -function conditionalProducingIf( ->conditionalProducingIf : Symbol(conditionalProducingIf, Decl(dependentReturnType1.ts, 0, 0)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 128, 39)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 128, 48)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 128, 57)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 128, 67)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 128, 39)) - - arg: Arg, ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 128, 98)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 128, 67)) - - cond: (arg: LeftIn | RightIn) => arg is LeftIn, ->cond : Symbol(cond, Decl(dependentReturnType1.ts, 129, 13)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 130, 11)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 128, 39)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 130, 11)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) - - produceLeftOut: (arg: LeftIn) => LeftOut, ->produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 130, 51)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 131, 21)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 128, 48)) - - produceRightOut: (arg: RightIn) => RightOut): ->produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 131, 45)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 132, 22)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 128, 39)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 128, 57)) - - Arg extends LeftIn ? LeftOut : RightOut ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 128, 67)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 128, 48)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 128, 57)) -{ - type OK = Arg extends LeftIn ? LeftOut : RightOut; ->OK : Symbol(OK, Decl(dependentReturnType1.ts, 134, 1)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 128, 67)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 128, 32)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 128, 48)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 128, 57)) - - if (cond(arg)) { ->cond : Symbol(cond, Decl(dependentReturnType1.ts, 129, 13)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 128, 98)) - - return produceLeftOut(arg); ->produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 130, 51)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 128, 98)) - - } else { - return produceRightOut(arg as RightIn) as OK; ->produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 131, 45)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 128, 98)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 128, 39)) ->OK : Symbol(OK, Decl(dependentReturnType1.ts, 134, 1)) +// function conditionalProducingIf( +// arg: Arg, +// cond: (arg: LeftIn | RightIn) => arg is LeftIn, +// produceLeftOut: (arg: LeftIn) => LeftOut, +// produceRightOut: (arg: RightIn) => RightOut): +// Arg extends LeftIn ? LeftOut : RightOut +// { +// type OK = Arg extends LeftIn ? LeftOut : RightOut; +// if (cond(arg)) { +// return produceLeftOut(arg); +// } else { +// return produceRightOut(arg as RightIn) as OK; +// } +// } + +class Unnamed { +>Unnamed : Symbol(Unnamed, Decl(dependentReturnType1.ts, 0, 0)) + + root!: { name: string }; +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) + + name(name?: T): T extends string ? this : string { +>name : Symbol(Unnamed.name, Decl(dependentReturnType1.ts, 144, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 145, 9)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 145, 27)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 145, 9)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 145, 9)) + + if (typeof name === 'undefined') { +>name : Symbol(name, Decl(dependentReturnType1.ts, 145, 27)) + + return this.root.name; +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 0, 0)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) + + // return this; // Error; as it should + } + this.root.name = name; +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 0, 0)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 145, 27)) + + name; // type `T < string` here +>name : Symbol(name, Decl(dependentReturnType1.ts, 145, 27)) + + return this; // What's going on here?? +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 0, 0)) } } diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 2fe23777cba22..2c23906ea3f80 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -127,45 +127,60 @@ // } // } -function conditionalProducingIf( ->conditionalProducingIf : (arg: Arg, cond: (arg: LeftIn | RightIn) => arg is LeftIn, produceLeftOut: (arg: LeftIn) => LeftOut, produceRightOut: (arg: RightIn) => RightOut) => Arg extends LeftIn ? LeftOut : RightOut - - arg: Arg, ->arg : Arg - - cond: (arg: LeftIn | RightIn) => arg is LeftIn, ->cond : (arg: LeftIn | RightIn) => arg is LeftIn ->arg : LeftIn | RightIn - - produceLeftOut: (arg: LeftIn) => LeftOut, ->produceLeftOut : (arg: LeftIn) => LeftOut ->arg : LeftIn - - produceRightOut: (arg: RightIn) => RightOut): ->produceRightOut : (arg: RightIn) => RightOut ->arg : RightIn - - Arg extends LeftIn ? LeftOut : RightOut -{ - type OK = Arg extends LeftIn ? LeftOut : RightOut; ->OK : Arg extends LeftIn ? LeftOut : RightOut - - if (cond(arg)) { ->cond(arg) : boolean ->cond : (arg: LeftIn | RightIn) => arg is LeftIn ->arg : Arg - - return produceLeftOut(arg); ->produceLeftOut(arg) : LeftOut ->produceLeftOut : (arg: LeftIn) => LeftOut ->arg : Arg & LeftIn - - } else { - return produceRightOut(arg as RightIn) as OK; ->produceRightOut(arg as RightIn) as OK : Arg extends LeftIn ? LeftOut : RightOut ->produceRightOut(arg as RightIn) : RightOut ->produceRightOut : (arg: RightIn) => RightOut ->arg as RightIn : RightIn ->arg : Arg +// function conditionalProducingIf( +// arg: Arg, +// cond: (arg: LeftIn | RightIn) => arg is LeftIn, +// produceLeftOut: (arg: LeftIn) => LeftOut, +// produceRightOut: (arg: RightIn) => RightOut): +// Arg extends LeftIn ? LeftOut : RightOut +// { +// type OK = Arg extends LeftIn ? LeftOut : RightOut; +// if (cond(arg)) { +// return produceLeftOut(arg); +// } else { +// return produceRightOut(arg as RightIn) as OK; +// } +// } + +class Unnamed { +>Unnamed : Unnamed + + root!: { name: string }; +>root : { name: string; } +>name : string + + name(name?: T): T extends string ? this : string { +>name : (name?: T) => T extends string ? this : string +>name : T | undefined + + if (typeof name === 'undefined') { +>typeof name === 'undefined' : boolean +>typeof name : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>name : T | undefined +>'undefined' : "undefined" + + return this.root.name; +>this.root.name : string +>this.root : { name: string; } +>this : this +>root : { name: string; } +>name : string + + // return this; // Error; as it should + } + this.root.name = name; +>this.root.name = name : T +>this.root.name : string +>this.root : { name: string; } +>this : this +>root : { name: string; } +>name : string +>name : T + + name; // type `T < string` here +>name : T + + return this; // What's going on here?? +>this : this } } diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index 8a662d0318784..cc0f4e53f049a 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -129,17 +129,72 @@ // } // } -function conditionalProducingIf( - arg: Arg, - cond: (arg: LeftIn | RightIn) => arg is LeftIn, - produceLeftOut: (arg: LeftIn) => LeftOut, - produceRightOut: (arg: RightIn) => RightOut): - Arg extends LeftIn ? LeftOut : RightOut -{ - type OK = Arg extends LeftIn ? LeftOut : RightOut; - if (cond(arg)) { - return produceLeftOut(arg); - } else { - return produceRightOut(arg as RightIn) as OK; - } -} \ No newline at end of file +// function conditionalProducingIf( +// arg: Arg, +// cond: (arg: LeftIn | RightIn) => arg is LeftIn, +// produceLeftOut: (arg: LeftIn) => LeftOut, +// produceRightOut: (arg: RightIn) => RightOut): +// Arg extends LeftIn ? LeftOut : RightOut +// { +// type OK = Arg extends LeftIn ? LeftOut : RightOut; +// if (cond(arg)) { +// return produceLeftOut(arg); +// } else { +// return produceRightOut(arg as RightIn) as OK; +// } +// } + +// class Unnamed { +// root!: { name: string }; +// name(name?: T): T extends string ? this : string { +// if (typeof name === 'undefined') { +// return this.root.name; +// // return this; // Error; as it should +// } +// this.root.name = name; +// name; // type `T < string` here +// return this; // What's going on here?? +// } +// } + +// interface A { +// data: number, +// } + +// interface B { +// other: string, +// } + +// declare function abf(): A | B; +// declare const eitherab: A | B; + +// function wrong(arg?: T): T extends true ? void : ReturnType { +// if (arg) { +// return undefined; +// } +// return eitherab; +// } + + +// interface A { +// 1: number; +// 2: string; +// 3: string; +// } + +// function trivial(x: T): A[T] { +// if (x !== 1) { +// return x === 2 ? "" : `${x}`; +// } +// else { +// return 0; +// } +// } + +function conditional(x: T): T extends true ? 1 : 2 { + return x ? 1 : 2; +} + +function contextualConditional(x: T): T extends "a" ? "a" : number { + return x === "a" ? x : parseInt(x); +} From eac319203a0da13ff58b0d7e0858e7f9ec4129f0 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 17 Jul 2023 14:06:11 -0700 Subject: [PATCH 06/90] fix binder change --- src/compiler/binder.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 21354ad626a16..78bcf93d998eb 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -194,6 +194,7 @@ import { isOmittedExpression, isOptionalChain, isOptionalChainRoot, + isOuterExpression, isOutermostOptionalChain, isParameterDeclaration, isParameterPropertyDeclaration, @@ -1981,7 +1982,10 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { if (!isConditionalExpression(n)) { return false; } - n = skipParentheses(n.parent); + n = n.parent; + while (isOuterExpression(n)) { + n = n.parent; + } } return true; } From 199ecd028f0a0427d3bf1795b382db8661ef39b8 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 4 Aug 2023 17:40:36 -0700 Subject: [PATCH 07/90] clean up + tests --- src/compiler/binder.ts | 2 +- src/compiler/checker.ts | 341 ++++----- src/compiler/types.ts | 2 +- .../reference/dependentReturnType1.errors.txt | 247 +++++++ .../reference/dependentReturnType1.symbols | 685 +++++++++++++----- .../reference/dependentReturnType1.types | 606 ++++++++++++---- tests/cases/compiler/dependentReturnType1.ts | 383 +++++----- tests/cases/compiler/dependentReturnType2.ts | 11 + 8 files changed, 1566 insertions(+), 711 deletions(-) create mode 100644 tests/baselines/reference/dependentReturnType1.errors.txt create mode 100644 tests/cases/compiler/dependentReturnType2.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 78bcf93d998eb..afbc29b8ffd5d 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1983,7 +1983,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { return false; } n = n.parent; - while (isOuterExpression(n)) { + while (isOuterExpression(n)) { // >> TODO: I think we should only skip parentheses n = n.parent; } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6384d105d7a97..d30fcb07bb9a5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -950,7 +950,6 @@ import { shouldResolveJsRequire, Signature, SignatureDeclaration, - SignatureDeclarationBase, SignatureFlags, SignatureKind, singleElementArray, @@ -17830,7 +17829,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let combinedMapper: TypeMapper | undefined; if (root.inferTypeParameters) { // When we're looking at making an inference for an infer type, when we get its constraint, it'll automagically be - // instantiated with the context, so it doesn't need the mapper for the inference contex - however the constraint + // instantiated with the context, so it doesn't need the mapper for the inference context - however the constraint // may refer to another _root_, _uncloned_ `infer` type parameter [1], or to something mapped by `mapper` [2]. // [1] Eg, if we have `Foo` and `Foo` - `B` is constrained to `T`, which, in turn, has been instantiated // as `number` @@ -17838,7 +17837,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // [2] Eg, if we have `Foo` and `Foo` where `Q` is mapped by `mapper` into `number` - `B` is constrained to `T` // which is in turn instantiated as `Q`, which is in turn instantiated as `number`. // So we need to: - // * Clone the type parameters so their constraints can be instantiated in the context of `mapper` (otherwise theyd only get inference context information) + // * Clone the type parameters so their constraints can be instantiated in the context of `mapper` (otherwise they'd only get inference context information) // * Set the clones to both map the conditional's enclosing `mapper` and the original params // * instantiate the extends type with the clones // * incorporate all of the component mappers into the combined mapper for the true and false members @@ -17873,7 +17872,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; // We attempt to resolve the conditional type only when the check and extends types are non-generic if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { - // Return falseType for a definitely false extends check. We check an instantiations of the two + // Return falseType for a definitely false extends check. We check an instantiation of the two // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, // then no instantiations will be and we can just return the false branch type. @@ -18025,12 +18024,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); return errorType; } - // >> TODO: (some) calls to `instantiateType` should be calls to instantiate narrow type worker etc - // const checkType = root.isDistributive - // ? instantiateNarrowType(getActualTypeVariable(root.checkType), narrowMapper, mapper) - // : instantiateNarrowType(getActualTypeVariable(root.checkType), narrowMapper, mapper); - const checkType = instantiateNarrowType(getActualTypeVariable(root.checkType), narrowMapper, mapper); - const extendsType = instantiateType(root.extendsType, mapper); + // >> TODO: we should only do this instantiation with `narrowMapper` if checktype is distributive + // >> TODO: what about if root is substitution type? should we still instantiate it?? + const checkType = root.isDistributive ? + instantiateType(getActualTypeVariable(root.checkType), combineTypeMappers(mapper, narrowMapper)) : + // >> TODO: in what order can we combine mappers? mapper first guarantess the mapping from check type to a + // type of a union comes first in the distributive case, + // but then if narrow mapper refers to a variable in mapper, it won't be properly instantiated. + // but can that even happen? + instantiateType(getActualTypeVariable(root.checkType), mapper); + const extendsType = instantiateType(root.extendsType, mapper); // >> TODO: Type parameter narrowing should not affect the extends type. Except it does here, because the mapper will have info from `narrowType` in case this is distributive, from the `getNarrowConditionalTypeInstantiation` caller if (checkType === errorType || extendsType === errorType) { return errorType; } @@ -18091,7 +18094,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // We attempt to resolve the conditional type only when the check and extends types are non-generic // if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { if (true) { - // Return falseType for a definitely false extends check. We check an instantiations of the two + // Return falseType for a definitely false extends check. We check an instantiation of the two // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, // then no instantiations will be and we can just return the false branch type. @@ -18110,7 +18113,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { root = newRoot; continue; } - if (canTailRecurse(falseType, mapper)) { + if (canTailRecurse(falseType, narrowMapper, mapper)) { // >> TODO: should this be `mapper` or `narrowMapper`? continue; } } @@ -18125,17 +18128,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { const trueType = getTypeFromTypeNode(root.node.trueType); const trueMapper = combinedMapper || mapper; - if (canTailRecurse(trueType, trueMapper)) { + if (canTailRecurse(trueType, narrowMapper, trueMapper)) { continue; } result = instantiateNarrowType(trueType, narrowMapper, trueMapper); break; } - // >> TODO: let's see + // >> TODO: document why/when we need this if (isTypeStrictSubtypeOf(checkType, inferredExtendsType)) { const trueType = getTypeFromTypeNode(root.node.trueType); const trueMapper = combinedMapper || mapper; - if (canTailRecurse(trueType, trueMapper)) { + if (canTailRecurse(trueType, narrowMapper, trueMapper)) { continue; } result = instantiateNarrowType(trueType, narrowMapper, trueMapper); @@ -18145,28 +18148,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Return a deferred type for a check that is neither definitely true nor definitely false result = createType(TypeFlags.Conditional) as ConditionalType; result.root = root; - result.checkType = instantiateType(root.checkType, mapper); // >> TODO: what `instantiate` should this one be? - result.extendsType = instantiateType(root.extendsType, mapper); // >> TODO: what `instantiate` should this one be? + result.checkType = instantiateType(root.checkType, mapper); + result.extendsType = instantiateType(root.extendsType, mapper); result.mapper = mapper; result.combinedMapper = combinedMapper; result.aliasSymbol = aliasSymbol || root.aliasSymbol; result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 + // >> TODO: narrowly instantiate true and false branch? No, it doesn't seem that useful break; } - // return extraTypes ? getUnionType(append(extraTypes, result)) : result; // >> TODO: should be intersection return extraTypes ? getIntersectionType(append(extraTypes, result)) : result; // We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and // (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check // type. Note that recursion is possible only through aliased conditional types, so we only increment the tail // recursion counter for those. - function canTailRecurse(newType: Type, newMapper: TypeMapper | undefined) { // >> TODO: do we need to update this? - if (newType.flags & TypeFlags.Conditional && newMapper) { + function canTailRecurse(newType: Type, narrowMapper: TypeMapper, newMapper: TypeMapper | undefined) { + if (newType.flags & TypeFlags.Conditional && newMapper) { // >> TODO: do we need to use `narrowMapper` here? const newRoot = (newType as ConditionalType).root; if (newRoot.outerTypeParameters) { const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper); const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)); const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); - const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined; + const newCheckType = newRoot.isDistributive ? + // getMappedType(newRoot.checkType, newRootMapper) : + instantiateType(getActualTypeVariable(root.checkType), combineTypeMappers(mapper, narrowMapper)) : // >> TODO: I think now we need to actually instantiate it fully to see if the new check type is union or not, because now we have two mappers + undefined; if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { root = newRoot; mapper = newRootMapper; @@ -19285,6 +19291,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } + /** + * This is similar to `instantiateType`, but with behavior specific to narrowing a return type based on control flow for type parameters. + */ function instantiateNarrowType(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, noTopLevel?: boolean): Type; function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, noTopLevel?: boolean): Type | undefined; function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, noTopLevel = false): Type | undefined { @@ -19297,7 +19306,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { mapper: TypeMapper | undefined, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, - noTopLevel: boolean): Type { + _noTopLevel: boolean): Type { if (!couldContainTypeVariables(type)) { return type; } @@ -19305,84 +19314,63 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement // or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. - tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); + tracing?.instant(tracing.Phase.CheckTypes, "instantiateNarrowType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); return errorType; } totalInstantiationCount++; instantiationCount++; instantiationDepth++; - const result = instantiateNarrowTypeWorker(type, narrowMapper, mapper, aliasSymbol, aliasTypeArguments, noTopLevel); + const result = instantiateNarrowTypeWorker(type, narrowMapper, mapper, aliasSymbol, aliasTypeArguments /*, noTopLevel*/); instantiationDepth--; return result; } + + /** + * + * @param narrowMapper special mapper that has mappings originating from type parameter narrowing, and should only be considered in some places + * @param mapper the usual mapper that should be used for all instantiations + */ function instantiateNarrowTypeWorker( type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, - noTopLevel: boolean): Type { + // noTopLevel: boolean): Type { + ): Type { const flags = type.flags; - if (flags & TypeFlags.TypeParameter) { - if (noTopLevel) { - return type; - } - return getMappedType(type, combineTypeMappers(mapper, narrowMapper)); - } - // if (flags & TypeFlags.Object) { - // const objectFlags = (type as ObjectType).objectFlags; - // if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - // if (objectFlags & ObjectFlags.Reference && !(type as TypeReference).node) { - // const resolvedTypeArguments = (type as TypeReference).resolvedTypeArguments; - // const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); - // return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type as TypeReference).target, newTypeArguments) : type; - // } - // if (objectFlags & ObjectFlags.ReverseMapped) { - // return instantiateReverseMappedType(type as ReverseMappedType, mapper); - // } - // return getObjectTypeInstantiation(type as TypeReference | AnonymousType | MappedType, mapper, aliasSymbol, aliasTypeArguments); - // } - // return type; - // } - // if (flags & TypeFlags.UnionOrIntersection) { - // const origin = type.flags & TypeFlags.Union ? (type as UnionType).origin : undefined; - // const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (origin as UnionOrIntersectionType).types : (type as UnionOrIntersectionType).types; - // const newTypes = instantiateTypes(types, mapper); - // if (newTypes === types && aliasSymbol === type.aliasSymbol) { + // if (flags & TypeFlags.TypeParameter) { + // // We don't want to narrowly instantiate a return type that is just a type parameter. + // if (noTopLevel) { // return type; // } - // const newAliasSymbol = aliasSymbol || type.aliasSymbol; - // const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - // return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ? - // getIntersectionType(newTypes, newAliasSymbol, newAliasTypeArguments) : - // getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments); - // } - // if (flags & TypeFlags.Index) { - // return getIndexType(instantiateType((type as IndexType).type, mapper)); - // } - // if (flags & TypeFlags.TemplateLiteral) { - // return getTemplateLiteralType((type as TemplateLiteralType).texts, instantiateTypes((type as TemplateLiteralType).types, mapper)); - // } - // if (flags & TypeFlags.StringMapping) { - // return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper)); + // return getMappedType(type, combineTypeMappers(mapper, narrowMapper)); // } if (flags & TypeFlags.IndexedAccess) { - // >> TODO: what's that extra alias stuff here? + // >> TODO: what's that extra alias stuff here doing? const newAliasSymbol = aliasSymbol || type.aliasSymbol; const newAliasTypeArguments = aliasSymbol || !mapper ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - const objectType = instantiateType((type as IndexedAccessType).objectType, mapper); - let indexType = instantiateType((type as IndexedAccessType).indexType, mapper); + const objectType = instantiateNarrowType((type as IndexedAccessType).objectType, narrowMapper, mapper); + let indexType = instantiateNarrowType((type as IndexedAccessType).indexType, narrowMapper, mapper); + let accessFlags = (type as IndexedAccessType).accessFlags; if (indexType.flags & TypeFlags.TypeParameter) { indexType = getMappedType(indexType, narrowMapper); + accessFlags |= AccessFlags.Writing; // Get the writing type + } + // >> NOTE: this possibly recurses forever; how do we break this recursion? is the below enough? + if (indexType === (type as IndexedAccessType).indexType && objectType === (type as IndexedAccessType).objectType) { + return type; // No type reduction or narrowing happened; so don't do anything else to avoid infinite recursion } - return getIndexedAccessType( + const result = getIndexedAccessType( objectType, indexType, - (type as IndexedAccessType).accessFlags | AccessFlags.Writing, // Get the writing type + accessFlags, /*accessNode*/ undefined, newAliasSymbol, newAliasTypeArguments); + // >> NOTE: We need to detect if result is different from just putting the already resolved types together + return instantiateNarrowType(result, narrowMapper, mapper); } if (flags & TypeFlags.Conditional) { return getNarrowConditionalTypeInstantiation( @@ -19392,7 +19380,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { aliasSymbol, aliasTypeArguments); } - // if (flags & TypeFlags.Substitution) { + // if (flags & TypeFlags.Substitution) { // >> TODO: why/when do we even need this?? If we're not doing anything special with narrow mapper, then we can just rely on the common case below and remove this // const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); // const newConstraint = instantiateType((type as SubstitutionType).constraint, mapper); // // A substitution type originates in the true branch of a conditional type and can be resolved @@ -19406,24 +19394,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // } // return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); // } - return type; + + return instantiateType(type, mapper); } - function getNarrowConditionalTypeInstantiation(type: ConditionalType, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + function getNarrowConditionalTypeInstantiation( + type: ConditionalType, + narrowMapper: TypeMapper, + mapper: TypeMapper | undefined, + aliasSymbol?: Symbol, + aliasTypeArguments?: readonly Type[]): Type { const root = type.root; if (root.outerTypeParameters) { // We are instantiating a conditional type that has one or more type parameters in scope. Apply the // mapper to the type parameters to produce the effective list of type arguments, and compute the // instantiation cache key from the type IDs of the type arguments. const typeArguments = mapper ? map(root.outerTypeParameters, t => getMappedType(t, mapper)) : root.outerTypeParameters; - // >> No caching + // >> No caching yet // const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); // let result = root.instantiations!.get(id); // if (!result) { let result; const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); const checkType = root.checkType; - const distributionType = root.isDistributive ? getMappedType(checkType, combineTypeMappers(newMapper, narrowMapper)) : undefined; + // >> TODO: what should be the mapper order here?? + const distributionType = root.isDistributive ? getMappedType(checkType, combineTypeMappers(narrowMapper, newMapper)) : undefined; // Distributive conditional types are distributed over union types. For example, when the // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the // result is (A extends U ? X : Y) | (B extends U ? X : Y). @@ -19436,7 +19431,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { prependTypeMapping(checkType, t, newMapper)), aliasSymbol, aliasTypeArguments, - /*useIntersection*/ true); + /*useIntersection*/ true); // We use the intersection instead of unioning over the conditional types, + // because we want the write type so that it is safe } else { result = getNarrowConditionalType(root, narrowMapper, newMapper, aliasSymbol, aliasTypeArguments); @@ -19445,7 +19441,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // } return result; } - return type; + return type; // >> TODO: narrowly instantiate true and false branch } function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) { @@ -26409,7 +26405,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let mappedTypes: Type[] | undefined; let changed = false; for (const t of types) { - const mapped = t.flags & TypeFlags.Union ? mapType(t, mapper, noReductions) : mapper(t); + const mapped = t.flags & TypeFlags.Union ? mapType(t, mapper, noReductions, useIntersection) : mapper(t); changed ||= t !== mapped; if (mapped) { if (!mappedTypes) { @@ -26425,8 +26421,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, useIntersection = false) { return type.flags & TypeFlags.Union && aliasSymbol ? - (useIntersection ? getIntersectionType(map((type as UnionType).types, mapper), aliasSymbol, aliasTypeArguments) : getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments)) : - mapType(type, mapper, useIntersection); + (useIntersection ? + getIntersectionType(map((type as UnionType).types, mapper), aliasSymbol, aliasTypeArguments) : + getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments)) : + mapType(type, mapper, /*noReductions*/ undefined, useIntersection); } function extractTypesOfKind(type: Type, kind: TypeFlags) { @@ -29833,7 +29831,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return contextualTypes[index]; } const links = getNodeLinks(node); - if (node.kind !== SyntaxKind.ReturnStatement && links.contextualReturnType) { + if (links.contextualReturnType) { + if (node.flags & NodeFlags.AwaitContext) { + return getUnionType([links.contextualReturnType, createPromiseLikeType(links.contextualReturnType)]); + } return links.contextualReturnType; } const { parent } = node; @@ -42727,106 +42728,52 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } else if (getReturnTypeFromAnnotation(container)) { const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; - let actualReturnType = unwrappedReturnType; - // /* Begin weird stuff */ - // const links = node.expression && getNodeLinks(node.expression); - // if (links && !links.contextualReturnType) { - // const outerTypeParameters = getOuterTypeParameters(container, /*includeThisTypes*/ false); - // const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - // const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); - // if (queryTypeParameters) { - // const narrowMapper = createTypeMapper(queryTypeParameters, queryTypeParameters.map(tp => { - // const originalName = tp.exprName; - // const fakeName = factory.cloneNode(originalName); // Fake a narrowable node. - // setParent(fakeName, node.parent); - // setNodeFlags(fakeName, fakeName.flags | NodeFlags.Synthesized); - // fakeName.flowNode = node.flowNode; - // // >> TODO: this call to checkExpression might report errors, - // // >> and so might throw when trying to get span for fakeName. - // // >> TODO: also, it shouldn't throw errors. - // const exprType = checkExpression(fakeName); - // // >> TODO: is there a better way of detecting that narrowing will be useless? - // // >> https://github.com/microsoft/TypeScript/issues/51525 might help - // if (getConstraintOfTypeParameter(tp)) { - // const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); - // if (narrowableConstraintType === exprType) { - // return tp; // Don't narrow if narrowing didn't do anything but obtain constraints - // // >> TODO: exclude such type parameters from the mapper - // } - // } - // return exprType; - // })); - // // >> TODO: don't instantiate at the top level - // actualReturnType = instantiateNarrowType( - // unwrappedReturnType, - // narrowMapper, - // /*mapper*/ undefined, - // /*noTopLevel*/ true, - // ); - // } - // links.contextualReturnType = actualReturnType; // >> maybe don't store this if we don't need to? - // } - // /* End weird stuff */ - // const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; - // const unwrappedExprType = functionFlags & FunctionFlags.Async - // ? checkAwaitedType(exprType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) - // : exprType; - // // if (unwrappedReturnType) { - // if (actualReturnType) { - // // If the function has a return type, but promisedType is - // // undefined, an error will be reported in checkAsyncFunctionReturnType - // // so we don't need to report one here. - // // checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); - // checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, actualReturnType, node, node.expression); - // } - checkReturnStatementExpression(node.expression); function checkReturnStatementExpression(expr: Expression | undefined): void { - // >> TODO: implement this - // >> TODO: skip parentheses - if (expr && isConditionalExpression(expr)) { - return checkReturnConditionalExpression(expr); - } - // >> ELSE: base case: just do the checks - /* Begin weird stuff */ - const links = expr && getNodeLinks(expr); - if (links && !links.contextualReturnType) { - const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); - const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); - if (queryTypeParameters) { - const narrowMapper = createTypeMapper(queryTypeParameters, queryTypeParameters.map(tp => { - const originalName = tp.exprName; - const fakeName = factory.cloneNode(originalName); // Fake a narrowable node. - const fakePosition = isConditionalExpression(expr.parent) ? expr : expr.parent; - setParent(fakeName, fakePosition.parent); - setNodeFlags(fakeName, fakeName.flags | NodeFlags.Synthesized); - fakeName.flowNode = (fakePosition as HasFlowNode).flowNode; - // >> TODO: this call to checkExpression might report errors, - // >> and so might throw when trying to get span for fakeName. - // >> TODO: also, it shouldn't throw errors. - const exprType = checkExpression(fakeName); - // >> TODO: is there a better way of detecting that narrowing will be useless? - // >> https://github.com/microsoft/TypeScript/issues/51525 might help - if (getConstraintOfTypeParameter(tp)) { - const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); - if (narrowableConstraintType === exprType) { - return tp; // Don't narrow if narrowing didn't do anything but obtain constraints - // >> TODO: exclude such type parameters from the mapper + let actualReturnType = unwrappedReturnType; + if (expr) { + expr = skipParentheses(expr); + if (isConditionalExpression(expr)) { + return checkReturnConditionalExpression(expr); + } + /* Begin weird stuff */ + const links = getNodeLinks(expr); + if (!links.contextualReturnType) { + const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); + const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); + const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); + if (queryTypeParameters) { + const narrowParent = isConditionalExpression(expr.parent) ? expr : expr.parent; + const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, tp => { + const narrowReference = factory.cloneNode(tp.exprName); // Construct a reference that can be narrowed. + setParent(narrowReference, narrowParent.parent); + setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); + narrowReference.flowNode = (narrowParent as HasFlowNode).flowNode; + // >> TODO: this call to checkExpression might report errors, + // >> and so might throw when trying to get span for fakeName. + // >> TODO: also, it shouldn't throw errors. Maybe we can reuse `CheckMode.TypeOnly`? + const exprType = checkExpression(narrowReference); + // >> TODO: is there a better way of detecting that narrowing will be useless? + // >> https://github.com/microsoft/TypeScript/issues/51525 might help + if (getConstraintOfTypeParameter(tp)) { + const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); + if (narrowableConstraintType === exprType) { + return undefined; // Don't narrow if narrowing didn't do anything but obtain constraints + } } - } - return exprType; - })); - // >> TODO: don't instantiate at the top level - actualReturnType = instantiateNarrowType( - unwrappedReturnType, - narrowMapper, - /*mapper*/ undefined, - /*noTopLevel*/ true, - ); + return [tp, exprType]; + }); + const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); + actualReturnType = instantiateNarrowType( + unwrappedReturnType, + narrowMapper, + /*mapper*/ undefined, + /*noTopLevel*/ true, + ); + } + links.contextualReturnType = actualReturnType; } - links.contextualReturnType = actualReturnType; // >> maybe don't store this if we don't need to? } /* End weird stuff */ const exprType = expr ? checkExpressionCached(expr) : undefinedType; @@ -42841,8 +42788,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If the function has a return type, but promisedType is // undefined, an error will be reported in checkAsyncFunctionReturnType // so we don't need to report one here. - // checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, actualReturnType, node, expr); // >> TODO: should this be node or expr? + // >> TODO: should this be node or expr? + const errorNode = node.expression && isConditionalExpression(skipParentheses(node.expression)) ? expr : node; + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, actualReturnType, errorNode, expr); } } @@ -42859,9 +42807,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function isQueryTypeParameter(typeParameter: TypeParameter): typeParameter is TypeParameter & { exprName: EntityName } { - if (typeParameter.exprName) { - return true; // >> TODO: also have a way to mark a type parameter as not a query one - } if (isThisTypeParameter(typeParameter)) { return false; } @@ -42869,44 +42814,46 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; // >> TODO: deal with synthetic tps? } + if (typeParameter.exprName) { + return true; + } + if (typeParameter.exprName === null) { + return false; + } + + typeParameter.exprName = null; + // It should have a type parameter declaration because it is not a `this` type parameter, and it has a symbol const declaration = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; const owner = getTypeParameterOwner(declaration); if (!owner || !isFunctionLikeDeclaration(owner)) { return false; // Owner is class or interface, or a signature without an implementation } - const references: Node[] = []; // All nodes of kind `T` that resolve to the type parameter - owner.parameters.forEach(parameter => forEachChild(parameter, collectReferences)); + const references: Node[] = []; // All parameter type nodes that look like `T` that resolve to the type parameter. + owner.parameters.forEach(parameter => parameter.type && collectReferences(parameter.type)); if (references.length === 1) { const reference = references[0]; let exprName; - if (isParameter(reference.parent) && reference.parent.parent === owner && (exprName = getNameOfDeclaration(reference.parent))) { - typeParameter.exprName = exprName as Identifier; + if (isParameter(reference.parent) && + reference.parent.parent === owner && + (exprName = getNameOfDeclaration(reference.parent)) && + isIdentifier(exprName)) { + typeParameter.exprName = exprName; return true; } } return false; function collectReferences(node: Node) { - if (isFunctionLikeDeclaration(node.parent) && node === node.parent.body) { - return; - } - if (isNodeDescendantOf(node, (owner as SignatureDeclarationBase).type)) { - return; - } if (isTypeReferenceNode(node)) { const type = getTypeFromTypeNode(node); - if (type.flags & TypeFlags.TypeParameter && isTypeIdenticalTo(type, typeParameter)) { + if (type.flags & TypeFlags.TypeParameter && getActualTypeVariable(type) === typeParameter) { references.push(node); } return; } + // >> TODO: should we skip some kinds of type nodes? e.g. typeof _? forEachChild(node, collectReferences); } - - // function collectPath(node: TypeNode) { - // // Assumptions: start from a type parameter/reference node, go up to parents, - - // } } function checkWithStatement(node: WithStatement) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index aa69a856f7019..8c38a08622f19 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6594,7 +6594,7 @@ export interface TypeParameter extends InstantiableType { /** @internal */ resolvedDefaultType?: Type; /** @internal */ - exprName?: EntityName; + exprName?: EntityName | null; } /** @internal */ diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt new file mode 100644 index 0000000000000..d0db7ac0bdc00 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -0,0 +1,247 @@ +tests/cases/compiler/dependentReturnType1.ts(11,9): error TS2322: Type 'number' is not assignable to type 'string'. +tests/cases/compiler/dependentReturnType1.ts(26,9): error TS2322: Type 'string' is not assignable to type 'never'. +tests/cases/compiler/dependentReturnType1.ts(35,9): error TS2322: Type 'string' is not assignable to type 'never'. +tests/cases/compiler/dependentReturnType1.ts(70,9): error TS2322: Type '{ a: "a"; }' is not assignable to type 'One & Two'. + Type '{ a: "a"; }' is missing the following properties from type 'One': b, c, d +tests/cases/compiler/dependentReturnType1.ts(74,22): error TS2322: Type '{ a: "a"; b: string; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'Three & Four'. + Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. +tests/cases/compiler/dependentReturnType1.ts(89,9): error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. +tests/cases/compiler/dependentReturnType1.ts(108,5): error TS2322: Type 'string' is not assignable to type 'T extends Dog ? number : string'. +tests/cases/compiler/dependentReturnType1.ts(148,9): error TS2322: Type 'this' is not assignable to type 'string & (T extends string ? this : string)'. + Type 'Unnamed' is not assignable to type 'string & (T extends string ? this : string)'. + Type 'Unnamed' is not assignable to type 'string'. + Type 'this' is not assignable to type 'string'. + Type 'Unnamed' is not assignable to type 'string'. +tests/cases/compiler/dependentReturnType1.ts(177,24): error TS2322: Type 'string' is not assignable to type 'number'. +tests/cases/compiler/dependentReturnType1.ts(177,28): error TS2322: Type 'number' is not assignable to type 'string'. + + +==== tests/cases/compiler/dependentReturnType1.ts (10 errors) ==== + interface A { + 1: number; + 2: string; + } + + function f1(x: T): A[T] { + if (x === 1) { + return 0; + } + else { + return 1; + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + } + } + + interface C { + 1: number; + 2: string; + 3: boolean; + } + + function f2(x: T): C[T] { + if (x === 1) { + return 0; + } + else { + return ""; // Error, returned expression needs to have type string & boolean (= never) + ~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'never'. + } + } + + function f3(x: T): T extends 1 ? number : T extends 2 ? string : T extends 3 ? boolean : never { + if (x === 1) { + return 0; + } + else { + return ""; // Error, returned expression needs to have type string & boolean (= never) + ~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'never'. + } + } + + interface One { + a: "a"; + b: "b"; + c: "c"; + d: "d"; + } + + interface Two { + a: "a"; + b: "b"; + e: "e"; + f: "f"; + } + + interface Three { + a: "a"; + c: "c"; + e: "e"; + g: "g"; + } + + interface Four { + a: "a"; + d: "d"; + f: "f"; + g: "g"; + } + + function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { + if (x === 1 || x === 2) { + // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; + return { a: "a" }; + ~~~~~~ +!!! error TS2322: Type '{ a: "a"; }' is not assignable to type 'One & Two'. +!!! error TS2322: Type '{ a: "a"; }' is missing the following properties from type 'One': b, c, d + } + // Excess property becomes a problem with the change, + // because we now check assignability to a narrower type... + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; + ~ +!!! error TS2322: Type '{ a: "a"; b: string; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'Three & Four'. +!!! error TS2322: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. + } + + // Asymmetry + function conditionalProducingIf( + arg: Arg, + cond: (arg: LeftIn | RightIn) => arg is LeftIn, + produceLeftOut: (arg: LeftIn) => LeftOut, + produceRightOut: (arg: RightIn) => RightOut): + Arg extends LeftIn ? LeftOut : RightOut + { + type OK = Arg extends LeftIn ? LeftOut : RightOut; + if (cond(arg)) { + return produceLeftOut(arg); + } else { + return produceRightOut(arg as RightIn); // Doesn't work because we don't narrow `arg` to `Arg & RightIn` here + ~~~~~~ +!!! error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. + return produceRightOut(arg as RightIn) as OK; + } + } + + interface Animal { + name: string; + } + + interface Dog extends Animal { + bark: () => string; + } + + declare function isDog(x: Animal): x is Dog; + declare function doggy(x: Dog): number; + function f12(x: T): T extends Dog ? number : string { + if (isDog(x)) { // `x` has type `T & Dog` here + return doggy(x); // Should work + } + return ""; // Should not work because we can't express "not a Dog" in the type system + ~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'T extends Dog ? number : string'. + } + + // Cannot narrow `keyof` too eagerly or something like the below breaks + function f(entry: EntryId): Entry[EntryId] { + const entries = {} as Entry; + return entries[entry]; + } + + // Works the same as before + declare function takeA(val: 'A'): void; + export function bounceAndTakeIfA(value: AB): AB { + if (value === 'A') { + takeA(value); + takeAB(value); + return value; + } + + return value; + function takeAB(val: AB): void {} + } + + // Works the same as before + export function bbb(value: AB): "a" { + if (value === "a") { + return value; + } + return "a"; + } + + class Unnamed { + root!: { name: string }; + name(name?: T): T extends string ? this : string { + if (typeof name === 'undefined') { + return this.root.name; + } + return this; + } + + nameWithError(name?: T): T extends string ? this : string { + return this; // Investigate error message + ~~~~~~ +!!! error TS2322: Type 'this' is not assignable to type 'string & (T extends string ? this : string)'. +!!! error TS2322: Type 'Unnamed' is not assignable to type 'string & (T extends string ? this : string)'. +!!! error TS2322: Type 'Unnamed' is not assignable to type 'string'. +!!! error TS2322: Type 'this' is not assignable to type 'string'. +!!! error TS2322: Type 'Unnamed' is not assignable to type 'string'. + } + } + + interface A { + 1: number; + 2: string; + 3: string; + } + + function trivialConditional(x: T): A[T] { + if (x !== 1) { + return x === 2 ? "" : `${x}`; + } + else { + return 0; + } + } + + // Conditional expressions + function conditional(x: T): T extends true ? 1 : 2 { + return x ? 1 : 2; + } + + function contextualConditional(x: T): T extends "a" ? "a" : number { + return x === "a" ? x : parseInt(x); + } + + function conditionalWithError(x: T): T extends "a" ? number : string { + return x === "a" ? x : parseInt(x); + ~ +!!! error TS2322: Type 'string' is not assignable to type 'number'. + ~~~~~~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + } + + // Multiple reductions + interface BB { + "a": number; + [y: number]: string; + } + + interface AA { + "c": BB[T]; + "d": boolean, + } + + function reduction(x: T, y: U): AA[U] { + if (y === "c" && x === "a") { + // AA[U='c'] -> BB[T] + // BB[T='a'] -> number + return 0; + } + + return undefined as never; + } + + // TODO: test substitution type + // TODO: test non-tail recursive and tail recursive conditionals \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 2250cc0a0aac1..02467cf3b9401 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -1,185 +1,532 @@ === tests/cases/compiler/dependentReturnType1.ts === -// function f1(x: T): T { -// return x; -// } - -// function f2(x: T): T { -// if (x) { -// return x; -// } -// return x; -// } - -// function f3(x: T, y: T): T { -// return y; -// } - -// interface A { -// 1: number; -// 2: string; -// } - -// function f4(x: T): A[T] { -// if (x === 1) { -// // return "one"; -// return 0; -// } -// else { -// return false; -// } -// } - -// interface One { -// a: "a"; -// b: "b"; -// c: "c"; -// d: "d"; -// } - -// interface Two { -// a: "a"; -// b: "b"; -// e: "e"; -// f: "f"; -// } - -// interface Three { -// a: "a"; -// c: "c"; -// e: "e"; -// g: "g"; -// } - -// interface Four { -// a: "a"; -// d: "d"; -// f: "f"; -// g: "g"; -// } - -// function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { -// if (x === 1 || x === 2) { -// // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; -// return { a: "a" }; -// } -// // Excess property becomes a problem with the change, -// // because we now check assignability to a narrower type... -// return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; -// } - -// interface Animal { -// name: string; -// } - -// interface Dog extends Animal { -// bark: () => string; -// } - -// declare function isDog(x: Animal): x is Dog; -// declare function doggy(x: Dog): number; -// function f12(x: T): T extends Dog ? number : string { -// if (isDog(x)) { // `x` has type `T & Dog` here -// return doggy(x); // Should work -// } -// return ""; // Should not work because we can't express "not a Dog" in the type system -// } - -// function f(entry: EntryId): Entry[EntryId] { -// const entries = {} as Entry; -// return entries[entry]; -// } - -// interface F { -// a: "a", -// b: "b", -// } - -// declare function takesA(x: "a"): number; -// function f(x: T) { -// if (x === "a") { -// takesA(x); // we narrow x -// x; // we don't narrow x in `getNarrowableTypeForReference`, therefore we don't narrow it at all -// } -// } - -// declare function takeA(val: 'A'): void; -// export function bounceAndTakeIfA(value: AB): AB { -// if (value === 'A') { -// takeA(value); -// takeAB(value); -// return value; -// } -// function takeAB(val: AB): void {} -// } - -// export function bbb(value: AB): "a" { -// if (value === "a") { -// return value; -// } -// } - -// declare function takeA(val: 'A'): void; - -// export function bounceAndTakeIfA(value: AB): AB { -// if (value === 'A') { -// takeA(value); -// return value; -// } -// } - -// function conditionalProducingIf( -// arg: Arg, -// cond: (arg: LeftIn | RightIn) => arg is LeftIn, -// produceLeftOut: (arg: LeftIn) => LeftOut, -// produceRightOut: (arg: RightIn) => RightOut): -// Arg extends LeftIn ? LeftOut : RightOut -// { -// type OK = Arg extends LeftIn ? LeftOut : RightOut; -// if (cond(arg)) { -// return produceLeftOut(arg); -// } else { -// return produceRightOut(arg as RightIn) as OK; -// } -// } +interface A { +>A : Symbol(A, Decl(dependentReturnType1.ts, 0, 0), Decl(dependentReturnType1.ts, 149, 1)) + + 1: number; +>1 : Symbol(A[1], Decl(dependentReturnType1.ts, 0, 13), Decl(dependentReturnType1.ts, 151, 13)) + + 2: string; +>2 : Symbol(A[2], Decl(dependentReturnType1.ts, 1, 14), Decl(dependentReturnType1.ts, 152, 14)) +} + +function f1(x: T): A[T] { +>f1 : Symbol(f1, Decl(dependentReturnType1.ts, 3, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 5, 12)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 5, 29)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 5, 12)) +>A : Symbol(A, Decl(dependentReturnType1.ts, 0, 0), Decl(dependentReturnType1.ts, 149, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 5, 12)) + + if (x === 1) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 5, 29)) + + return 0; + } + else { + return 1; + } +} + +interface C { +>C : Symbol(C, Decl(dependentReturnType1.ts, 12, 1)) + + 1: number; +>1 : Symbol(C[1], Decl(dependentReturnType1.ts, 14, 13)) + + 2: string; +>2 : Symbol(C[2], Decl(dependentReturnType1.ts, 15, 14)) + + 3: boolean; +>3 : Symbol(C[3], Decl(dependentReturnType1.ts, 16, 14)) +} + +function f2(x: T): C[T] { +>f2 : Symbol(f2, Decl(dependentReturnType1.ts, 18, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 20, 12)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 20, 33)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 20, 12)) +>C : Symbol(C, Decl(dependentReturnType1.ts, 12, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 20, 12)) + + if (x === 1) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 20, 33)) + + return 0; + } + else { + return ""; // Error, returned expression needs to have type string & boolean (= never) + } +} + +function f3(x: T): T extends 1 ? number : T extends 2 ? string : T extends 3 ? boolean : never { +>f3 : Symbol(f3, Decl(dependentReturnType1.ts, 27, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 29, 12)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 29, 33)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 29, 12)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 29, 12)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 29, 12)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 29, 12)) + + if (x === 1) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 29, 33)) + + return 0; + } + else { + return ""; // Error, returned expression needs to have type string & boolean (= never) + } +} + +interface One { +>One : Symbol(One, Decl(dependentReturnType1.ts, 36, 1)) + + a: "a"; +>a : Symbol(One.a, Decl(dependentReturnType1.ts, 38, 15)) + + b: "b"; +>b : Symbol(One.b, Decl(dependentReturnType1.ts, 39, 11)) + + c: "c"; +>c : Symbol(One.c, Decl(dependentReturnType1.ts, 40, 11)) + + d: "d"; +>d : Symbol(One.d, Decl(dependentReturnType1.ts, 41, 11)) +} + +interface Two { +>Two : Symbol(Two, Decl(dependentReturnType1.ts, 43, 1)) + + a: "a"; +>a : Symbol(Two.a, Decl(dependentReturnType1.ts, 45, 15)) + + b: "b"; +>b : Symbol(Two.b, Decl(dependentReturnType1.ts, 46, 11)) + + e: "e"; +>e : Symbol(Two.e, Decl(dependentReturnType1.ts, 47, 11)) + + f: "f"; +>f : Symbol(Two.f, Decl(dependentReturnType1.ts, 48, 11)) +} + +interface Three { +>Three : Symbol(Three, Decl(dependentReturnType1.ts, 50, 1)) + + a: "a"; +>a : Symbol(Three.a, Decl(dependentReturnType1.ts, 52, 17)) + + c: "c"; +>c : Symbol(Three.c, Decl(dependentReturnType1.ts, 53, 11)) + + e: "e"; +>e : Symbol(Three.e, Decl(dependentReturnType1.ts, 54, 11)) + + g: "g"; +>g : Symbol(Three.g, Decl(dependentReturnType1.ts, 55, 11)) +} + +interface Four { +>Four : Symbol(Four, Decl(dependentReturnType1.ts, 57, 1)) + + a: "a"; +>a : Symbol(Four.a, Decl(dependentReturnType1.ts, 59, 16)) + + d: "d"; +>d : Symbol(Four.d, Decl(dependentReturnType1.ts, 60, 11)) + + f: "f"; +>f : Symbol(Four.f, Decl(dependentReturnType1.ts, 61, 11)) + + g: "g"; +>g : Symbol(Four.g, Decl(dependentReturnType1.ts, 62, 11)) +} + +function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { +>f10 : Symbol(f10, Decl(dependentReturnType1.ts, 64, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 66, 13)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 66, 38)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 66, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 66, 13)) +>One : Symbol(One, Decl(dependentReturnType1.ts, 36, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 66, 13)) +>Two : Symbol(Two, Decl(dependentReturnType1.ts, 43, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 66, 13)) +>Three : Symbol(Three, Decl(dependentReturnType1.ts, 50, 1)) +>Four : Symbol(Four, Decl(dependentReturnType1.ts, 57, 1)) + + if (x === 1 || x === 2) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 66, 38)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 66, 38)) + + // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; + return { a: "a" }; +>a : Symbol(a, Decl(dependentReturnType1.ts, 69, 16)) + } + // Excess property becomes a problem with the change, + // because we now check assignability to a narrower type... + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; +>a : Symbol(a, Decl(dependentReturnType1.ts, 73, 12)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 73, 20)) +>c : Symbol(c, Decl(dependentReturnType1.ts, 73, 28)) +>d : Symbol(d, Decl(dependentReturnType1.ts, 73, 36)) +>e : Symbol(e, Decl(dependentReturnType1.ts, 73, 44)) +>f : Symbol(f, Decl(dependentReturnType1.ts, 73, 52)) +>g : Symbol(g, Decl(dependentReturnType1.ts, 73, 60)) +} + +// Asymmetry +function conditionalProducingIf( +>conditionalProducingIf : Symbol(conditionalProducingIf, Decl(dependentReturnType1.ts, 74, 1)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 77, 48)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 77, 57)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 77, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) + + arg: Arg, +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 77, 67)) + + cond: (arg: LeftIn | RightIn) => arg is LeftIn, +>cond : Symbol(cond, Decl(dependentReturnType1.ts, 78, 13)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 79, 11)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 79, 11)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) + + produceLeftOut: (arg: LeftIn) => LeftOut, +>produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 79, 51)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 80, 21)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 77, 48)) + + produceRightOut: (arg: RightIn) => RightOut): +>produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 80, 45)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 81, 22)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 77, 57)) + + Arg extends LeftIn ? LeftOut : RightOut +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 77, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 77, 48)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 77, 57)) +{ + type OK = Arg extends LeftIn ? LeftOut : RightOut; +>OK : Symbol(OK, Decl(dependentReturnType1.ts, 83, 1)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 77, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 77, 48)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 77, 57)) + + if (cond(arg)) { +>cond : Symbol(cond, Decl(dependentReturnType1.ts, 78, 13)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) + + return produceLeftOut(arg); +>produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 79, 51)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) + + } else { + return produceRightOut(arg as RightIn); // Doesn't work because we don't narrow `arg` to `Arg & RightIn` here +>produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 80, 45)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) + + return produceRightOut(arg as RightIn) as OK; +>produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 80, 45)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) +>OK : Symbol(OK, Decl(dependentReturnType1.ts, 83, 1)) + } +} + +interface Animal { +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 91, 1)) + + name: string; +>name : Symbol(Animal.name, Decl(dependentReturnType1.ts, 93, 18)) +} + +interface Dog extends Animal { +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 95, 1)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 91, 1)) + + bark: () => string; +>bark : Symbol(Dog.bark, Decl(dependentReturnType1.ts, 97, 30)) +} + +declare function isDog(x: Animal): x is Dog; +>isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 99, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 101, 23)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 91, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 101, 23)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 95, 1)) + +declare function doggy(x: Dog): number; +>doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 101, 44)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 102, 23)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 95, 1)) + +function f12(x: T): T extends Dog ? number : string { +>f12 : Symbol(f12, Decl(dependentReturnType1.ts, 102, 39)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 103, 13)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 91, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 103, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 103, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 103, 13)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 95, 1)) + + if (isDog(x)) { // `x` has type `T & Dog` here +>isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 99, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 103, 31)) + + return doggy(x); // Should work +>doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 101, 44)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 103, 31)) + } + return ""; // Should not work because we can't express "not a Dog" in the type system +} + +// Cannot narrow `keyof` too eagerly or something like the below breaks +function f(entry: EntryId): Entry[EntryId] { +>f : Symbol(f, Decl(dependentReturnType1.ts, 108, 1)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 111, 11)) +>index : Symbol(index, Decl(dependentReturnType1.ts, 111, 28)) +>EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 111, 63)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 111, 11)) +>entry : Symbol(entry, Decl(dependentReturnType1.ts, 111, 93)) +>EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 111, 63)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 111, 11)) +>EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 111, 63)) + + const entries = {} as Entry; +>entries : Symbol(entries, Decl(dependentReturnType1.ts, 112, 9)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 111, 11)) + + return entries[entry]; +>entries : Symbol(entries, Decl(dependentReturnType1.ts, 112, 9)) +>entry : Symbol(entry, Decl(dependentReturnType1.ts, 111, 93)) +} + +// Works the same as before +declare function takeA(val: 'A'): void; +>takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 114, 1)) +>val : Symbol(val, Decl(dependentReturnType1.ts, 117, 23)) + +export function bounceAndTakeIfA(value: AB): AB { +>bounceAndTakeIfA : Symbol(bounceAndTakeIfA, Decl(dependentReturnType1.ts, 117, 39)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 118, 33)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 118, 55)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 118, 33)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 118, 33)) + + if (value === 'A') { +>value : Symbol(value, Decl(dependentReturnType1.ts, 118, 55)) + + takeA(value); +>takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 114, 1)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 118, 55)) + + takeAB(value); +>takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 125, 17)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 118, 55)) + + return value; +>value : Symbol(value, Decl(dependentReturnType1.ts, 118, 55)) + } + + return value; +>value : Symbol(value, Decl(dependentReturnType1.ts, 118, 55)) + + function takeAB(val: AB): void {} +>takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 125, 17)) +>val : Symbol(val, Decl(dependentReturnType1.ts, 126, 20)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 118, 33)) +} + +// Works the same as before +export function bbb(value: AB): "a" { +>bbb : Symbol(bbb, Decl(dependentReturnType1.ts, 127, 1)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 130, 20)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 130, 42)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 130, 20)) + + if (value === "a") { +>value : Symbol(value, Decl(dependentReturnType1.ts, 130, 42)) + + return value; +>value : Symbol(value, Decl(dependentReturnType1.ts, 130, 42)) + } + return "a"; +} class Unnamed { ->Unnamed : Symbol(Unnamed, Decl(dependentReturnType1.ts, 0, 0)) +>Unnamed : Symbol(Unnamed, Decl(dependentReturnType1.ts, 135, 1)) root!: { name: string }; ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 137, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 138, 12)) name(name?: T): T extends string ? this : string { ->name : Symbol(Unnamed.name, Decl(dependentReturnType1.ts, 144, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 145, 9)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 145, 27)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 145, 9)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 145, 9)) +>name : Symbol(Unnamed.name, Decl(dependentReturnType1.ts, 138, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 139, 9)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 139, 27)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 139, 9)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 139, 9)) if (typeof name === 'undefined') { ->name : Symbol(name, Decl(dependentReturnType1.ts, 145, 27)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 139, 27)) return this.root.name; ->this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) ->this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 0, 0)) ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) - - // return this; // Error; as it should +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 138, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 137, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 135, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 137, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 138, 12)) } - this.root.name = name; ->this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) ->this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 0, 0)) ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 145, 27)) + return this; +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 135, 1)) + } + + nameWithError(name?: T): T extends string ? this : string { +>nameWithError : Symbol(Unnamed.nameWithError, Decl(dependentReturnType1.ts, 144, 5)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 146, 18)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 146, 36)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 146, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 146, 18)) - name; // type `T < string` here ->name : Symbol(name, Decl(dependentReturnType1.ts, 145, 27)) + return this; // Investigate error message +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 135, 1)) + } +} + +interface A { +>A : Symbol(A, Decl(dependentReturnType1.ts, 0, 0), Decl(dependentReturnType1.ts, 149, 1)) + + 1: number; +>1 : Symbol(A[1], Decl(dependentReturnType1.ts, 0, 13), Decl(dependentReturnType1.ts, 151, 13)) + + 2: string; +>2 : Symbol(A[2], Decl(dependentReturnType1.ts, 1, 14), Decl(dependentReturnType1.ts, 152, 14)) + + 3: string; +>3 : Symbol(A[3], Decl(dependentReturnType1.ts, 153, 14)) +} + +function trivialConditional(x: T): A[T] { +>trivialConditional : Symbol(trivialConditional, Decl(dependentReturnType1.ts, 155, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 157, 28)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 157, 49)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 157, 28)) +>A : Symbol(A, Decl(dependentReturnType1.ts, 0, 0), Decl(dependentReturnType1.ts, 149, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 157, 28)) + + if (x !== 1) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 157, 49)) + + return x === 2 ? "" : `${x}`; +>x : Symbol(x, Decl(dependentReturnType1.ts, 157, 49)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 157, 49)) + } + else { + return 0; + } +} + +// Conditional expressions +function conditional(x: T): T extends true ? 1 : 2 { +>conditional : Symbol(conditional, Decl(dependentReturnType1.ts, 164, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 167, 21)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 167, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 167, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 167, 21)) + + return x ? 1 : 2; +>x : Symbol(x, Decl(dependentReturnType1.ts, 167, 40)) +} + +function contextualConditional(x: T): T extends "a" ? "a" : number { +>contextualConditional : Symbol(contextualConditional, Decl(dependentReturnType1.ts, 169, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 171, 31)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 171, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 171, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 171, 31)) - return this; // What's going on here?? ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 0, 0)) + return x === "a" ? x : parseInt(x); +>x : Symbol(x, Decl(dependentReturnType1.ts, 171, 52)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 171, 52)) +>parseInt : Symbol(parseInt, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 171, 52)) +} + +function conditionalWithError(x: T): T extends "a" ? number : string { +>conditionalWithError : Symbol(conditionalWithError, Decl(dependentReturnType1.ts, 173, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 175, 30)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 175, 51)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 175, 30)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 175, 30)) + + return x === "a" ? x : parseInt(x); +>x : Symbol(x, Decl(dependentReturnType1.ts, 175, 51)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 175, 51)) +>parseInt : Symbol(parseInt, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 175, 51)) +} + +// Multiple reductions +interface BB { +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 177, 1)) + + "a": number; +>"a" : Symbol(BB["a"], Decl(dependentReturnType1.ts, 180, 14)) + + [y: number]: string; +>y : Symbol(y, Decl(dependentReturnType1.ts, 182, 5)) +} + +interface AA { +>AA : Symbol(AA, Decl(dependentReturnType1.ts, 183, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 185, 13)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 177, 1)) + + "c": BB[T]; +>"c" : Symbol(AA["c"], Decl(dependentReturnType1.ts, 185, 34)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 177, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 185, 13)) + + "d": boolean, +>"d" : Symbol(AA["d"], Decl(dependentReturnType1.ts, 186, 15)) +} + +function reduction(x: T, y: U): AA[U] { +>reduction : Symbol(reduction, Decl(dependentReturnType1.ts, 188, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 190, 19)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 177, 1)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 190, 38)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 190, 60)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 190, 19)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 190, 65)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 190, 38)) +>AA : Symbol(AA, Decl(dependentReturnType1.ts, 183, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 190, 19)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 190, 38)) + + if (y === "c" && x === "a") { +>y : Symbol(y, Decl(dependentReturnType1.ts, 190, 65)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 190, 60)) + + // AA[U='c'] -> BB[T] + // BB[T='a'] -> number + return 0; } + + return undefined as never; +>undefined : Symbol(undefined) } + +// TODO: test substitution type +// TODO: test non-tail recursive and tail recursive conditionals diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 2c23906ea3f80..408bf3e015a68 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -1,146 +1,327 @@ === tests/cases/compiler/dependentReturnType1.ts === -// function f1(x: T): T { -// return x; -// } - -// function f2(x: T): T { -// if (x) { -// return x; -// } -// return x; -// } - -// function f3(x: T, y: T): T { -// return y; -// } - -// interface A { -// 1: number; -// 2: string; -// } - -// function f4(x: T): A[T] { -// if (x === 1) { -// // return "one"; -// return 0; -// } -// else { -// return false; -// } -// } - -// interface One { -// a: "a"; -// b: "b"; -// c: "c"; -// d: "d"; -// } - -// interface Two { -// a: "a"; -// b: "b"; -// e: "e"; -// f: "f"; -// } - -// interface Three { -// a: "a"; -// c: "c"; -// e: "e"; -// g: "g"; -// } - -// interface Four { -// a: "a"; -// d: "d"; -// f: "f"; -// g: "g"; -// } - -// function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { -// if (x === 1 || x === 2) { -// // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; -// return { a: "a" }; -// } -// // Excess property becomes a problem with the change, -// // because we now check assignability to a narrower type... -// return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; -// } - -// interface Animal { -// name: string; -// } - -// interface Dog extends Animal { -// bark: () => string; -// } - -// declare function isDog(x: Animal): x is Dog; -// declare function doggy(x: Dog): number; -// function f12(x: T): T extends Dog ? number : string { -// if (isDog(x)) { // `x` has type `T & Dog` here -// return doggy(x); // Should work -// } -// return ""; // Should not work because we can't express "not a Dog" in the type system -// } - -// function f(entry: EntryId): Entry[EntryId] { -// const entries = {} as Entry; -// return entries[entry]; -// } - -// interface F { -// a: "a", -// b: "b", -// } - -// declare function takesA(x: "a"): number; -// function f(x: T) { -// if (x === "a") { -// takesA(x); // we narrow x -// x; // we don't narrow x in `getNarrowableTypeForReference`, therefore we don't narrow it at all -// } -// } - -// declare function takeA(val: 'A'): void; -// export function bounceAndTakeIfA(value: AB): AB { -// if (value === 'A') { -// takeA(value); -// takeAB(value); -// return value; -// } -// function takeAB(val: AB): void {} -// } - -// export function bbb(value: AB): "a" { -// if (value === "a") { -// return value; -// } -// } - -// declare function takeA(val: 'A'): void; - -// export function bounceAndTakeIfA(value: AB): AB { -// if (value === 'A') { -// takeA(value); -// return value; -// } -// } - -// function conditionalProducingIf( -// arg: Arg, -// cond: (arg: LeftIn | RightIn) => arg is LeftIn, -// produceLeftOut: (arg: LeftIn) => LeftOut, -// produceRightOut: (arg: RightIn) => RightOut): -// Arg extends LeftIn ? LeftOut : RightOut -// { -// type OK = Arg extends LeftIn ? LeftOut : RightOut; -// if (cond(arg)) { -// return produceLeftOut(arg); -// } else { -// return produceRightOut(arg as RightIn) as OK; -// } -// } +interface A { + 1: number; +>1 : number + + 2: string; +>2 : string +} + +function f1(x: T): A[T] { +>f1 : (x: T) => A[T] +>x : T + + if (x === 1) { +>x === 1 : boolean +>x : T +>1 : 1 + + return 0; +>0 : 0 + } + else { + return 1; +>1 : 1 + } +} + +interface C { + 1: number; +>1 : number + + 2: string; +>2 : string + + 3: boolean; +>3 : boolean +} + +function f2(x: T): C[T] { +>f2 : (x: T) => C[T] +>x : T + + if (x === 1) { +>x === 1 : boolean +>x : T +>1 : 1 + + return 0; +>0 : 0 + } + else { + return ""; // Error, returned expression needs to have type string & boolean (= never) +>"" : "" + } +} + +function f3(x: T): T extends 1 ? number : T extends 2 ? string : T extends 3 ? boolean : never { +>f3 : (x: T) => T extends 1 ? number : T extends 2 ? string : T extends 3 ? boolean : never +>x : T + + if (x === 1) { +>x === 1 : boolean +>x : T +>1 : 1 + + return 0; +>0 : 0 + } + else { + return ""; // Error, returned expression needs to have type string & boolean (= never) +>"" : "" + } +} + +interface One { + a: "a"; +>a : "a" + + b: "b"; +>b : "b" + + c: "c"; +>c : "c" + + d: "d"; +>d : "d" +} + +interface Two { + a: "a"; +>a : "a" + + b: "b"; +>b : "b" + + e: "e"; +>e : "e" + + f: "f"; +>f : "f" +} + +interface Three { + a: "a"; +>a : "a" + + c: "c"; +>c : "c" + + e: "e"; +>e : "e" + + g: "g"; +>g : "g" +} + +interface Four { + a: "a"; +>a : "a" + + d: "d"; +>d : "d" + + f: "f"; +>f : "f" + + g: "g"; +>g : "g" +} + +function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { +>f10 : (x: T) => T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four +>x : T + + if (x === 1 || x === 2) { +>x === 1 || x === 2 : boolean +>x === 1 : boolean +>x : T +>1 : 1 +>x === 2 : boolean +>x : T +>2 : 2 + + // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; + return { a: "a" }; +>{ a: "a" } : { a: "a"; } +>a : "a" +>"a" : "a" + } + // Excess property becomes a problem with the change, + // because we now check assignability to a narrower type... + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; +>{ a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" } : { a: "a"; b: string; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; } +>a : "a" +>"a" : "a" +>b : string +>"b" : "b" +>c : "c" +>"c" : "c" +>d : "d" +>"d" : "d" +>e : "e" +>"e" : "e" +>f : "f" +>"f" : "f" +>g : "g" +>"g" : "g" +} + +// Asymmetry +function conditionalProducingIf( +>conditionalProducingIf : (arg: Arg, cond: (arg: LeftIn | RightIn) => arg is LeftIn, produceLeftOut: (arg: LeftIn) => LeftOut, produceRightOut: (arg: RightIn) => RightOut) => Arg extends LeftIn ? LeftOut : RightOut + + arg: Arg, +>arg : Arg + + cond: (arg: LeftIn | RightIn) => arg is LeftIn, +>cond : (arg: LeftIn | RightIn) => arg is LeftIn +>arg : LeftIn | RightIn + + produceLeftOut: (arg: LeftIn) => LeftOut, +>produceLeftOut : (arg: LeftIn) => LeftOut +>arg : LeftIn + + produceRightOut: (arg: RightIn) => RightOut): +>produceRightOut : (arg: RightIn) => RightOut +>arg : RightIn + + Arg extends LeftIn ? LeftOut : RightOut +{ + type OK = Arg extends LeftIn ? LeftOut : RightOut; +>OK : Arg extends LeftIn ? LeftOut : RightOut + + if (cond(arg)) { +>cond(arg) : boolean +>cond : (arg: LeftIn | RightIn) => arg is LeftIn +>arg : Arg + + return produceLeftOut(arg); +>produceLeftOut(arg) : LeftOut +>produceLeftOut : (arg: LeftIn) => LeftOut +>arg : Arg & LeftIn + + } else { + return produceRightOut(arg as RightIn); // Doesn't work because we don't narrow `arg` to `Arg & RightIn` here +>produceRightOut(arg as RightIn) : RightOut +>produceRightOut : (arg: RightIn) => RightOut +>arg as RightIn : RightIn +>arg : Arg + + return produceRightOut(arg as RightIn) as OK; +>produceRightOut(arg as RightIn) as OK : Arg extends LeftIn ? LeftOut : RightOut +>produceRightOut(arg as RightIn) : RightOut +>produceRightOut : (arg: RightIn) => RightOut +>arg as RightIn : RightIn +>arg : Arg + } +} + +interface Animal { + name: string; +>name : string +} + +interface Dog extends Animal { + bark: () => string; +>bark : () => string +} + +declare function isDog(x: Animal): x is Dog; +>isDog : (x: Animal) => x is Dog +>x : Animal + +declare function doggy(x: Dog): number; +>doggy : (x: Dog) => number +>x : Dog + +function f12(x: T): T extends Dog ? number : string { +>f12 : (x: T) => T extends Dog ? number : string +>x : T + + if (isDog(x)) { // `x` has type `T & Dog` here +>isDog(x) : boolean +>isDog : (x: Animal) => x is Dog +>x : T + + return doggy(x); // Should work +>doggy(x) : number +>doggy : (x: Dog) => number +>x : T & Dog + } + return ""; // Should not work because we can't express "not a Dog" in the type system +>"" : "" +} + +// Cannot narrow `keyof` too eagerly or something like the below breaks +function f(entry: EntryId): Entry[EntryId] { +>f : (entry: EntryId) => Entry[EntryId] +>index : string +>entry : EntryId + + const entries = {} as Entry; +>entries : Entry +>{} as Entry : Entry +>{} : {} + + return entries[entry]; +>entries[entry] : Entry[EntryId] +>entries : Entry +>entry : EntryId +} + +// Works the same as before +declare function takeA(val: 'A'): void; +>takeA : (val: 'A') => void +>val : "A" + +export function bounceAndTakeIfA(value: AB): AB { +>bounceAndTakeIfA : (value: AB) => AB +>value : AB + + if (value === 'A') { +>value === 'A' : boolean +>value : AB +>'A' : "A" + + takeA(value); +>takeA(value) : void +>takeA : (val: "A") => void +>value : "A" + + takeAB(value); +>takeAB(value) : void +>takeAB : (val: AB) => void +>value : AB + + return value; +>value : AB + } + + return value; +>value : AB + + function takeAB(val: AB): void {} +>takeAB : (val: AB) => void +>val : AB +} + +// Works the same as before +export function bbb(value: AB): "a" { +>bbb : (value: AB) => "a" +>value : AB + + if (value === "a") { +>value === "a" : boolean +>value : AB +>"a" : "a" + + return value; +>value : "a" + } + return "a"; +>"a" : "a" +} class Unnamed { >Unnamed : Unnamed @@ -165,22 +346,139 @@ class Unnamed { >this : this >root : { name: string; } >name : string - - // return this; // Error; as it should } - this.root.name = name; ->this.root.name = name : T ->this.root.name : string ->this.root : { name: string; } + return this; >this : this ->root : { name: string; } ->name : string ->name : T + } - name; // type `T < string` here ->name : T + nameWithError(name?: T): T extends string ? this : string { +>nameWithError : (name?: T) => T extends string ? this : string +>name : T | undefined - return this; // What's going on here?? + return this; // Investigate error message >this : this } } + +interface A { + 1: number; +>1 : number + + 2: string; +>2 : string + + 3: string; +>3 : string +} + +function trivialConditional(x: T): A[T] { +>trivialConditional : (x: T) => A[T] +>x : T + + if (x !== 1) { +>x !== 1 : boolean +>x : T +>1 : 1 + + return x === 2 ? "" : `${x}`; +>x === 2 ? "" : `${x}` : string +>x === 2 : boolean +>x : T +>2 : 2 +>"" : "" +>`${x}` : string +>x : T + } + else { + return 0; +>0 : 0 + } +} + +// Conditional expressions +function conditional(x: T): T extends true ? 1 : 2 { +>conditional : (x: T) => T extends true ? 1 : 2 +>x : T +>true : true + + return x ? 1 : 2; +>x ? 1 : 2 : 1 | 2 +>x : T +>1 : 1 +>2 : 2 +} + +function contextualConditional(x: T): T extends "a" ? "a" : number { +>contextualConditional : (x: T) => T extends "a" ? "a" : number +>x : T + + return x === "a" ? x : parseInt(x); +>x === "a" ? x : parseInt(x) : number | "a" +>x === "a" : boolean +>x : T +>"a" : "a" +>x : "a" +>parseInt(x) : number +>parseInt : (string: string, radix?: number | undefined) => number +>x : "b" +} + +function conditionalWithError(x: T): T extends "a" ? number : string { +>conditionalWithError : (x: T) => T extends "a" ? number : string +>x : T + + return x === "a" ? x : parseInt(x); +>x === "a" ? x : parseInt(x) : number | "a" +>x === "a" : boolean +>x : T +>"a" : "a" +>x : "a" +>parseInt(x) : number +>parseInt : (string: string, radix?: number | undefined) => number +>x : "b" +} + +// Multiple reductions +interface BB { + "a": number; +>"a" : number + + [y: number]: string; +>y : number +} + +interface AA { + "c": BB[T]; +>"c" : BB[T] + + "d": boolean, +>"d" : boolean +} + +function reduction(x: T, y: U): AA[U] { +>reduction : (x: T, y: U) => AA[U] +>x : T +>y : U + + if (y === "c" && x === "a") { +>y === "c" && x === "a" : boolean +>y === "c" : boolean +>y : U +>"c" : "c" +>x === "a" : boolean +>x : T +>"a" : "a" + + // AA[U='c'] -> BB[T] + // BB[T='a'] -> number + return 0; +>0 : 0 + } + + return undefined as never; +>undefined as never : never +>undefined : undefined +} + +// TODO: test substitution type +// TODO: test non-tail recursive and tail recursive conditionals diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index cc0f4e53f049a..eeeeab2a17db3 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -1,196 +1,173 @@ // @strict: true // @noEmit: true -// function f1(x: T): T { -// return x; -// } - -// function f2(x: T): T { -// if (x) { -// return x; -// } -// return x; -// } - -// function f3(x: T, y: T): T { -// return y; -// } - -// interface A { -// 1: number; -// 2: string; -// } - -// function f4(x: T): A[T] { -// if (x === 1) { -// // return "one"; -// return 0; -// } -// else { -// return false; -// } -// } - -// interface One { -// a: "a"; -// b: "b"; -// c: "c"; -// d: "d"; -// } - -// interface Two { -// a: "a"; -// b: "b"; -// e: "e"; -// f: "f"; -// } - -// interface Three { -// a: "a"; -// c: "c"; -// e: "e"; -// g: "g"; -// } - -// interface Four { -// a: "a"; -// d: "d"; -// f: "f"; -// g: "g"; -// } - -// function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { -// if (x === 1 || x === 2) { -// // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; -// return { a: "a" }; -// } -// // Excess property becomes a problem with the change, -// // because we now check assignability to a narrower type... -// return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; -// } - -// interface Animal { -// name: string; -// } - -// interface Dog extends Animal { -// bark: () => string; -// } - -// declare function isDog(x: Animal): x is Dog; -// declare function doggy(x: Dog): number; -// function f12(x: T): T extends Dog ? number : string { -// if (isDog(x)) { // `x` has type `T & Dog` here -// return doggy(x); // Should work -// } -// return ""; // Should not work because we can't express "not a Dog" in the type system -// } - -// function f(entry: EntryId): Entry[EntryId] { -// const entries = {} as Entry; -// return entries[entry]; -// } - -// interface F { -// a: "a", -// b: "b", -// } - -// declare function takesA(x: "a"): number; -// function f(x: T) { -// if (x === "a") { -// takesA(x); // we narrow x -// x; // we don't narrow x in `getNarrowableTypeForReference`, therefore we don't narrow it at all -// } -// } - -// declare function takeA(val: 'A'): void; -// export function bounceAndTakeIfA(value: AB): AB { -// if (value === 'A') { -// takeA(value); -// takeAB(value); -// return value; -// } -// function takeAB(val: AB): void {} -// } - -// export function bbb(value: AB): "a" { -// if (value === "a") { -// return value; -// } -// } - -// declare function takeA(val: 'A'): void; - -// export function bounceAndTakeIfA(value: AB): AB { -// if (value === 'A') { -// takeA(value); -// return value; -// } -// } - -// function conditionalProducingIf( -// arg: Arg, -// cond: (arg: LeftIn | RightIn) => arg is LeftIn, -// produceLeftOut: (arg: LeftIn) => LeftOut, -// produceRightOut: (arg: RightIn) => RightOut): -// Arg extends LeftIn ? LeftOut : RightOut -// { -// type OK = Arg extends LeftIn ? LeftOut : RightOut; -// if (cond(arg)) { -// return produceLeftOut(arg); -// } else { -// return produceRightOut(arg as RightIn) as OK; -// } -// } - -// class Unnamed { -// root!: { name: string }; -// name(name?: T): T extends string ? this : string { -// if (typeof name === 'undefined') { -// return this.root.name; -// // return this; // Error; as it should -// } -// this.root.name = name; -// name; // type `T < string` here -// return this; // What's going on here?? -// } -// } - -// interface A { -// data: number, -// } - -// interface B { -// other: string, -// } - -// declare function abf(): A | B; -// declare const eitherab: A | B; - -// function wrong(arg?: T): T extends true ? void : ReturnType { -// if (arg) { -// return undefined; -// } -// return eitherab; -// } - - -// interface A { -// 1: number; -// 2: string; -// 3: string; -// } - -// function trivial(x: T): A[T] { -// if (x !== 1) { -// return x === 2 ? "" : `${x}`; -// } -// else { -// return 0; -// } -// } +interface A { + 1: number; + 2: string; +} + +function f1(x: T): A[T] { + if (x === 1) { + return 0; + } + else { + return 1; + } +} + +interface C { + 1: number; + 2: string; + 3: boolean; +} + +function f2(x: T): C[T] { + if (x === 1) { + return 0; + } + else { + return ""; // Error, returned expression needs to have type string & boolean (= never) + } +} + +function f3(x: T): T extends 1 ? number : T extends 2 ? string : T extends 3 ? boolean : never { + if (x === 1) { + return 0; + } + else { + return ""; // Error, returned expression needs to have type string & boolean (= never) + } +} + +interface One { + a: "a"; + b: "b"; + c: "c"; + d: "d"; +} + +interface Two { + a: "a"; + b: "b"; + e: "e"; + f: "f"; +} + +interface Three { + a: "a"; + c: "c"; + e: "e"; + g: "g"; +} + +interface Four { + a: "a"; + d: "d"; + f: "f"; + g: "g"; +} + +function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { + if (x === 1 || x === 2) { + // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; + return { a: "a" }; + } + // Excess property becomes a problem with the change, + // because we now check assignability to a narrower type... + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; +} + +// Asymmetry +function conditionalProducingIf( + arg: Arg, + cond: (arg: LeftIn | RightIn) => arg is LeftIn, + produceLeftOut: (arg: LeftIn) => LeftOut, + produceRightOut: (arg: RightIn) => RightOut): + Arg extends LeftIn ? LeftOut : RightOut +{ + type OK = Arg extends LeftIn ? LeftOut : RightOut; + if (cond(arg)) { + return produceLeftOut(arg); + } else { + return produceRightOut(arg as RightIn); // Doesn't work because we don't narrow `arg` to `Arg & RightIn` here + return produceRightOut(arg as RightIn) as OK; + } +} + +interface Animal { + name: string; +} + +interface Dog extends Animal { + bark: () => string; +} +declare function isDog(x: Animal): x is Dog; +declare function doggy(x: Dog): number; +function f12(x: T): T extends Dog ? number : string { + if (isDog(x)) { // `x` has type `T & Dog` here + return doggy(x); // Should work + } + return ""; // Should not work because we can't express "not a Dog" in the type system +} + +// Cannot narrow `keyof` too eagerly or something like the below breaks +function f(entry: EntryId): Entry[EntryId] { + const entries = {} as Entry; + return entries[entry]; +} + +// Works the same as before +declare function takeA(val: 'A'): void; +export function bounceAndTakeIfA(value: AB): AB { + if (value === 'A') { + takeA(value); + takeAB(value); + return value; + } + + return value; + function takeAB(val: AB): void {} +} + +// Works the same as before +export function bbb(value: AB): "a" { + if (value === "a") { + return value; + } + return "a"; +} + +class Unnamed { + root!: { name: string }; + name(name?: T): T extends string ? this : string { + if (typeof name === 'undefined') { + return this.root.name; + } + return this; + } + + nameWithError(name?: T): T extends string ? this : string { + return this; // Investigate error message + } +} + +interface A { + 1: number; + 2: string; + 3: string; +} + +function trivialConditional(x: T): A[T] { + if (x !== 1) { + return x === 2 ? "" : `${x}`; + } + else { + return 0; + } +} + +// Conditional expressions function conditional(x: T): T extends true ? 1 : 2 { return x ? 1 : 2; } @@ -198,3 +175,31 @@ function conditional(x: T): T extends true ? 1 : 2 { function contextualConditional(x: T): T extends "a" ? "a" : number { return x === "a" ? x : parseInt(x); } + +function conditionalWithError(x: T): T extends "a" ? number : string { + return x === "a" ? x : parseInt(x); +} + +// Multiple reductions +interface BB { + "a": number; + [y: number]: string; +} + +interface AA { + "c": BB[T]; + "d": boolean, +} + +function reduction(x: T, y: U): AA[U] { + if (y === "c" && x === "a") { + // AA[U='c'] -> BB[T] + // BB[T='a'] -> number + return 0; + } + + return undefined as never; +} + +// TODO: test substitution type +// TODO: test non-tail recursive and tail recursive conditionals \ No newline at end of file diff --git a/tests/cases/compiler/dependentReturnType2.ts b/tests/cases/compiler/dependentReturnType2.ts new file mode 100644 index 0000000000000..b9790da88b629 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType2.ts @@ -0,0 +1,11 @@ +// @strict: true +// @noEmit: true + +function f3(x: T): T extends 1 ? number : T extends 2 ? string : T extends 3 ? boolean : never { + if (x === 1) { + return 0; + } + else { + return ""; // Error, returned expression needs to have type string & boolean (= never) + } +} \ No newline at end of file From d0e85d0c5afc393b93cb50761492565985fa820b Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 1 Sep 2023 16:47:28 -0700 Subject: [PATCH 08/90] WIP --- src/compiler/checker.ts | 26 +++++++++++-------- .../reference/dependentReturnType1.errors.txt | 13 ++++++++-- .../reference/dependentReturnType1.symbols | 17 +++++++++++- .../reference/dependentReturnType1.types | 16 +++++++++++- tests/cases/compiler/dependentReturnType1.ts | 9 ++++++- tests/cases/compiler/dependentReturnType2.ts | 17 +++++++----- 6 files changed, 75 insertions(+), 23 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d30fcb07bb9a5..5d5c1fcd9d9b8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18024,16 +18024,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); return errorType; } - // >> TODO: we should only do this instantiation with `narrowMapper` if checktype is distributive - // >> TODO: what about if root is substitution type? should we still instantiate it?? - const checkType = root.isDistributive ? - instantiateType(getActualTypeVariable(root.checkType), combineTypeMappers(mapper, narrowMapper)) : + // >> We instantiate checkType with the narrow mapper information + const checkTypeVariable = getActualTypeVariable(root.checkType); + const checkType = checkTypeVariable.flags & TypeFlags.TypeParameter ? + instantiateType(checkTypeVariable, combineTypeMappers2(narrowMapper, mapper)) : // >> TODO: in what order can we combine mappers? mapper first guarantess the mapping from check type to a // type of a union comes first in the distributive case, // but then if narrow mapper refers to a variable in mapper, it won't be properly instantiated. // but can that even happen? - instantiateType(getActualTypeVariable(root.checkType), mapper); - const extendsType = instantiateType(root.extendsType, mapper); // >> TODO: Type parameter narrowing should not affect the extends type. Except it does here, because the mapper will have info from `narrowType` in case this is distributive, from the `getNarrowConditionalTypeInstantiation` caller + instantiateType(checkTypeVariable, mapper); + const extendsType = instantiateType(root.extendsType, mapper); // >> Type parameter narrowing should not affect the extends type. if (checkType === errorType || extendsType === errorType) { return errorType; } @@ -18113,7 +18113,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { root = newRoot; continue; } - if (canTailRecurse(falseType, narrowMapper, mapper)) { // >> TODO: should this be `mapper` or `narrowMapper`? + if (canTailRecurse(falseType, narrowMapper, mapper)) { continue; } } @@ -18154,7 +18154,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { result.combinedMapper = combinedMapper; result.aliasSymbol = aliasSymbol || root.aliasSymbol; result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 - // >> TODO: narrowly instantiate true and false branch? No, it doesn't seem that useful + // >> Narrowly instantiate true and false branch? No, it doesn't seem that useful break; } return extraTypes ? getIntersectionType(append(extraTypes, result)) : result; @@ -18171,7 +18171,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); const newCheckType = newRoot.isDistributive ? // getMappedType(newRoot.checkType, newRootMapper) : - instantiateType(getActualTypeVariable(root.checkType), combineTypeMappers(mapper, narrowMapper)) : // >> TODO: I think now we need to actually instantiate it fully to see if the new check type is union or not, because now we have two mappers + instantiateType(getActualTypeVariable(root.checkType), combineTypeMappers2(narrowMapper, mapper)) : // >> I think now we need to actually instantiate it fully to see if the new check type is union or not, because now we have two mappers undefined; if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { root = newRoot; @@ -18848,6 +18848,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper2; } + function combineTypeMappers2(mapper1: TypeMapper, mapper2: TypeMapper | undefined): TypeMapper { + return mapper2 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper1; + } + function mergeTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Merged, mapper1, mapper2) : mapper2; } @@ -19427,8 +19431,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getReducedType(distributionType), t => getNarrowConditionalType( root, - narrowMapper, - prependTypeMapping(checkType, t, newMapper)), + prependTypeMapping(checkType, t, narrowMapper), + newMapper), aliasSymbol, aliasTypeArguments, /*useIntersection*/ true); // We use the intersection instead of unioning over the conditional types, diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index d0db7ac0bdc00..6cd667775aa10 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -14,9 +14,10 @@ tests/cases/compiler/dependentReturnType1.ts(148,9): error TS2322: Type 'this' i Type 'Unnamed' is not assignable to type 'string'. tests/cases/compiler/dependentReturnType1.ts(177,24): error TS2322: Type 'string' is not assignable to type 'number'. tests/cases/compiler/dependentReturnType1.ts(177,28): error TS2322: Type 'number' is not assignable to type 'string'. +tests/cases/compiler/dependentReturnType1.ts(202,47): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. -==== tests/cases/compiler/dependentReturnType1.ts (10 errors) ==== +==== tests/cases/compiler/dependentReturnType1.ts (11 errors) ==== interface A { 1: number; 2: string; @@ -243,5 +244,13 @@ tests/cases/compiler/dependentReturnType1.ts(177,28): error TS2322: Type 'number return undefined as never; } - // TODO: test substitution type + // Conditional with substitution types should also be narrowed + function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'. + if (x === 1) { + return ""; + } + } + // TODO: test non-tail recursive and tail recursive conditionals \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 02467cf3b9401..26e4e60d6ac09 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -528,5 +528,20 @@ function reduction(x: T, y: U): AA[U >undefined : Symbol(undefined) } -// TODO: test substitution type +// Conditional with substitution types should also be narrowed +function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { +>subsCond : Symbol(subsCond, Decl(dependentReturnType1.ts, 198, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 201, 18)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 201, 39)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 201, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 201, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 201, 18)) + + if (x === 1) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 201, 39)) + + return ""; + } +} + // TODO: test non-tail recursive and tail recursive conditionals diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 408bf3e015a68..fbc1e3137de21 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -480,5 +480,19 @@ function reduction(x: T, y: U): AA[U >undefined : undefined } -// TODO: test substitution type +// Conditional with substitution types should also be narrowed +function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { +>subsCond : (x: T) => T extends 1 | 2 ? (T extends 1 ? string : boolean) : number +>x : T + + if (x === 1) { +>x === 1 : boolean +>x : T +>1 : 1 + + return ""; +>"" : "" + } +} + // TODO: test non-tail recursive and tail recursive conditionals diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index eeeeab2a17db3..f10ffac1e1767 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -102,6 +102,7 @@ interface Dog extends Animal { bark: () => string; } +// TODO: this is unsafe declare function isDog(x: Animal): x is Dog; declare function doggy(x: Dog): number; function f12(x: T): T extends Dog ? number : string { @@ -201,5 +202,11 @@ function reduction(x: T, y: U): AA[U return undefined as never; } -// TODO: test substitution type +// Conditional with substitution types should also be narrowed +function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { + if (x === 1) { + return ""; + } +} + // TODO: test non-tail recursive and tail recursive conditionals \ No newline at end of file diff --git a/tests/cases/compiler/dependentReturnType2.ts b/tests/cases/compiler/dependentReturnType2.ts index b9790da88b629..33c5e2f837440 100644 --- a/tests/cases/compiler/dependentReturnType2.ts +++ b/tests/cases/compiler/dependentReturnType2.ts @@ -1,11 +1,14 @@ // @strict: true // @noEmit: true -function f3(x: T): T extends 1 ? number : T extends 2 ? string : T extends 3 ? boolean : never { - if (x === 1) { - return 0; - } - else { - return ""; // Error, returned expression needs to have type string & boolean (= never) +declare function q(x: object): x is { b: number }; + +function foo(x: T): T extends { a: string } ? number : (string | number) { + if (q(x)) { + x.b; + return ""; } -} \ No newline at end of file +} + +let y = { a: "", b: 1 } +const r = foo<{ a: string }>(y); // number \ No newline at end of file From d9ac1027a371842567bc1f3e8bcfa594ea763915 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 5 Sep 2023 14:54:16 -0700 Subject: [PATCH 09/90] defer conditional unless narrowing --- src/compiler/checker.ts | 38 +++++++------------ ...alTypeAssignabilityWhenDeferred.errors.txt | 6 +-- 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3b4334e9a7842..36dba4ed982df 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18405,16 +18405,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); return errorType; } - // >> We instantiate checkType with the narrow mapper information + // We instantiate a distributive checkType with the narrow mapper information const checkTypeVariable = getActualTypeVariable(root.checkType); - const checkType = checkTypeVariable.flags & TypeFlags.TypeParameter ? + const checkType = root.isDistributive ? instantiateType(checkTypeVariable, combineTypeMappers2(narrowMapper, mapper)) : - // >> TODO: in what order can we combine mappers? mapper first guarantess the mapping from check type to a - // type of a union comes first in the distributive case, - // but then if narrow mapper refers to a variable in mapper, it won't be properly instantiated. - // but can that even happen? instantiateType(checkTypeVariable, mapper); - const extendsType = instantiateType(root.extendsType, mapper); // >> Type parameter narrowing should not affect the extends type. + const extendsType = instantiateType(root.extendsType, mapper); if (checkType === errorType || extendsType === errorType) { return errorType; } @@ -18424,10 +18420,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // When the check and extends types are simple tuple types of the same arity, we defer resolution of the // conditional type when any tuple elements are generic. This is such that non-distributable conditional // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. - // const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) && - // length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements); - // const checkTypeDeferred = isDeferredType(checkType, checkTuples); // >> TODO: what do we do about this? - const checkTypeDeferred = false; + const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) && + length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements); + const forceEagerNarrowing = root.isDistributive && getMappedType(getActualTypeVariable(root.checkType), narrowMapper) !== root.checkType; + const checkTypeDeferred = isDeferredType(checkType, checkTuples) && !forceEagerNarrowing; let combinedMapper: TypeMapper | undefined; if (root.inferTypeParameters) { // When we're looking at making an inference for an infer type, when we get its constraint, it'll automagically be @@ -18474,7 +18470,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; // We attempt to resolve the conditional type only when the check and extends types are non-generic // if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { - if (true) { + if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { // Return falseType for a definitely false extends check. We check an instantiation of the two // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, @@ -18535,7 +18531,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { result.combinedMapper = combinedMapper; result.aliasSymbol = aliasSymbol || root.aliasSymbol; result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 - // >> Narrowly instantiate true and false branch? No, it doesn't seem that useful break; } return extraTypes ? getIntersectionType(append(extraTypes, result)) : result; @@ -18544,15 +18539,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // type. Note that recursion is possible only through aliased conditional types, so we only increment the tail // recursion counter for those. function canTailRecurse(newType: Type, narrowMapper: TypeMapper, newMapper: TypeMapper | undefined) { - if (newType.flags & TypeFlags.Conditional && newMapper) { // >> TODO: do we need to use `narrowMapper` here? + if (newType.flags & TypeFlags.Conditional && (newMapper || (newType as ConditionalType).root.isDistributive)) { const newRoot = (newType as ConditionalType).root; if (newRoot.outerTypeParameters) { - const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper); - const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)); + const typeParamMapper = newMapper ? combineTypeMappers((newType as ConditionalType).mapper, newMapper) : (newType as ConditionalType).mapper; + const typeArguments = typeParamMapper ? map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)) : newRoot.outerTypeParameters; const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); const newCheckType = newRoot.isDistributive ? - // getMappedType(newRoot.checkType, newRootMapper) : - instantiateType(getActualTypeVariable(root.checkType), combineTypeMappers2(narrowMapper, mapper)) : // >> I think now we need to actually instantiate it fully to see if the new check type is union or not, because now we have two mappers + instantiateType(getActualTypeVariable(root.checkType), combineTypeMappers2(narrowMapper, mapper)) : undefined; if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { root = newRoot; @@ -19811,13 +19805,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // instantiation cache key from the type IDs of the type arguments. const typeArguments = mapper ? map(root.outerTypeParameters, t => getMappedType(t, mapper)) : root.outerTypeParameters; // >> No caching yet - // const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); - // let result = root.instantiations!.get(id); - // if (!result) { let result; const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); const checkType = root.checkType; - // >> TODO: what should be the mapper order here?? const distributionType = root.isDistributive ? getMappedType(checkType, combineTypeMappers(narrowMapper, newMapper)) : undefined; // Distributive conditional types are distributed over union types. For example, when the // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the @@ -19837,11 +19827,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else { result = getNarrowConditionalType(root, narrowMapper, newMapper, aliasSymbol, aliasTypeArguments); } - // root.instantiations!.set(id, result); - // } return result; } - return type; // >> TODO: narrowly instantiate true and false branch + return type; } function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) { diff --git a/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt b/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt index 333bbcddc3c46..c81b39e8f03a4 100644 --- a/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt +++ b/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt @@ -143,9 +143,7 @@ conditionalTypeAssignabilityWhenDeferred.ts(116,3): error TS2322: Type 'Q' is no ): InferBecauseWhyNotDistributive { return x; // should fail ~~~~~~ -!!! error TS2322: Type 'Q' is not assignable to type 'P1 | T'. -!!! error TS2322: Type '(arg: any) => any' is not assignable to type 'P1 | T'. -!!! error TS2322: Type '(arg: any) => any' is not assignable to type 'T'. -!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to '(arg: any) => any'. +!!! error TS2322: Type 'Q' is not assignable to type 'InferBecauseWhyNotDistributive'. +!!! error TS2322: Type '(arg: any) => any' is not assignable to type 'InferBecauseWhyNotDistributive'. } \ No newline at end of file From b4910f45f8d9b1a44ae714e5ef0be865594ec292 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 5 Sep 2023 17:01:15 -0700 Subject: [PATCH 10/90] don't skip jsdoc type assertions --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 36dba4ed982df..23ca2145e65fc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -43473,7 +43473,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkReturnStatementExpression(expr: Expression | undefined): void { let actualReturnType = unwrappedReturnType; if (expr) { - expr = skipParentheses(expr); + expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); if (isConditionalExpression(expr)) { return checkReturnConditionalExpression(expr); } @@ -43499,7 +43499,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (getConstraintOfTypeParameter(tp)) { const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); if (narrowableConstraintType === exprType) { - return undefined; // Don't narrow if narrowing didn't do anything but obtain constraints + return undefined; // Don't narrow if narrowing didn't do anything but default to constraints } } return [tp, exprType]; From 6258bc0ae2fcb900ab93a116afb934f4b5ca726f Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 4 Oct 2023 11:42:56 -0700 Subject: [PATCH 11/90] more fixes --- src/compiler/checker.ts | 112 ++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 62 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 23ca2145e65fc..e0fc1db89ad82 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18408,7 +18408,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // We instantiate a distributive checkType with the narrow mapper information const checkTypeVariable = getActualTypeVariable(root.checkType); const checkType = root.isDistributive ? - instantiateType(checkTypeVariable, combineTypeMappers2(narrowMapper, mapper)) : + instantiateType(checkTypeVariable, combineTypeMappers(mapper, narrowMapper)) : instantiateType(checkTypeVariable, mapper); const extendsType = instantiateType(root.extendsType, mapper); if (checkType === errorType || extendsType === errorType) { @@ -18470,7 +18470,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; // We attempt to resolve the conditional type only when the check and extends types are non-generic // if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { - if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { + if (!checkTypeDeferred) { // Return falseType for a definitely false extends check. We check an instantiation of the two // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, @@ -18546,7 +18546,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const typeArguments = typeParamMapper ? map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)) : newRoot.outerTypeParameters; const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); const newCheckType = newRoot.isDistributive ? - instantiateType(getActualTypeVariable(root.checkType), combineTypeMappers2(narrowMapper, mapper)) : + instantiateType(getActualTypeVariable(root.checkType), combineTypeMappers(mapper, narrowMapper)) : undefined; if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { root = newRoot; @@ -19224,10 +19224,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper2; } - function combineTypeMappers2(mapper1: TypeMapper, mapper2: TypeMapper | undefined): TypeMapper { - return mapper2 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper1; - } - function mergeTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Merged, mapper1, mapper2) : mapper2; } @@ -19733,6 +19729,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { aliasTypeArguments: readonly Type[] | undefined, // noTopLevel: boolean): Type { ): Type { + type = instantiateType(type, mapper); + mapper = undefined; const flags = type.flags; // if (flags & TypeFlags.TypeParameter) { // // We don't want to narrowly instantiate a return type that is just a type parameter. @@ -19774,22 +19772,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { aliasSymbol, aliasTypeArguments); } - // if (flags & TypeFlags.Substitution) { // >> TODO: why/when do we even need this?? If we're not doing anything special with narrow mapper, then we can just rely on the common case below and remove this - // const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); - // const newConstraint = instantiateType((type as SubstitutionType).constraint, mapper); - // // A substitution type originates in the true branch of a conditional type and can be resolved - // // to just the base type in the same cases as the conditional type resolves to its true branch - // // (because the base type is then known to satisfy the constraint). - // if (newBaseType.flags & TypeFlags.TypeVariable && isGenericType(newConstraint)) { - // return getSubstitutionType(newBaseType, newConstraint); - // } - // if (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { - // return newBaseType; - // } - // return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); - // } - return instantiateType(type, mapper); + return type; + // return instantiateType(type, mapper); } function getNarrowConditionalTypeInstantiation( @@ -19808,7 +19793,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let result; const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); const checkType = root.checkType; - const distributionType = root.isDistributive ? getMappedType(checkType, combineTypeMappers(narrowMapper, newMapper)) : undefined; + const distributionType = root.isDistributive ? getMappedType(checkType, combineTypeMappers(newMapper, narrowMapper)) : undefined; // Distributive conditional types are distributed over union types. For example, when the // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the // result is (A extends U ? X : Y) | (B extends U ? X : Y). @@ -29770,8 +29755,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function // Get the awaited type without the `Awaited` alias - // const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeNoAlias); - const contextualAwaitedType = getAwaitedTypeNoAlias(contextualReturnType); // >> TODO: test this change separately + const contextualAwaitedType = getAwaitedTypeNoAlias(contextualReturnType); return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } @@ -43454,7 +43438,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const returnType = getReturnTypeOfSignature(signature); const functionFlags = getFunctionFlags(container); if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { - // const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; if (container.kind === SyntaxKind.SetAccessor) { if (node.expression) { error(node, Diagnostics.Setters_cannot_return_a_value); @@ -43477,41 +43460,48 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isConditionalExpression(expr)) { return checkReturnConditionalExpression(expr); } - /* Begin weird stuff */ + } + + /* Begin weird stuff */ + const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); + const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); + const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); + if (queryTypeParameters) { + const narrowParent = expr + ? isConditionalExpression(expr.parent) + ? expr + : expr.parent + : node; + const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, tp => { + const narrowReference = factory.cloneNode(tp.exprName); // Construct a reference that can be narrowed. + setParent(narrowReference, narrowParent.parent); + setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); + narrowReference.flowNode = (narrowParent as HasFlowNode).flowNode; + // >> TODO: this call to checkExpression might report errors, + // >> and so might throw when trying to get span for fakeName. + // >> TODO: also, it shouldn't throw errors. Maybe we can reuse `CheckMode.TypeOnly`? + const exprType = checkExpression(narrowReference); + // >> TODO: is there a better way of detecting that narrowing will be useless? + if (getConstraintOfTypeParameter(tp)) { + const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); + if (narrowableConstraintType === exprType) { + return undefined; // Don't narrow if narrowing didn't do anything but default to constraints + } + } + return [tp, exprType]; + }); + const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); + actualReturnType = instantiateNarrowType( + unwrappedReturnType, + narrowMapper, + /*mapper*/ undefined, + /*noTopLevel*/ true, + ); + } + + if (expr) { const links = getNodeLinks(expr); if (!links.contextualReturnType) { - const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); - const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); - if (queryTypeParameters) { - const narrowParent = isConditionalExpression(expr.parent) ? expr : expr.parent; - const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, tp => { - const narrowReference = factory.cloneNode(tp.exprName); // Construct a reference that can be narrowed. - setParent(narrowReference, narrowParent.parent); - setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); - narrowReference.flowNode = (narrowParent as HasFlowNode).flowNode; - // >> TODO: this call to checkExpression might report errors, - // >> and so might throw when trying to get span for fakeName. - // >> TODO: also, it shouldn't throw errors. Maybe we can reuse `CheckMode.TypeOnly`? - const exprType = checkExpression(narrowReference); - // >> TODO: is there a better way of detecting that narrowing will be useless? - // >> https://github.com/microsoft/TypeScript/issues/51525 might help - if (getConstraintOfTypeParameter(tp)) { - const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); - if (narrowableConstraintType === exprType) { - return undefined; // Don't narrow if narrowing didn't do anything but default to constraints - } - } - return [tp, exprType]; - }); - const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); - actualReturnType = instantiateNarrowType( - unwrappedReturnType, - narrowMapper, - /*mapper*/ undefined, - /*noTopLevel*/ true, - ); - } links.contextualReturnType = actualReturnType; } } @@ -43521,14 +43511,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ? checkAwaitedType( exprType, /*withAlias*/ false, - node, // >> TODO: should this be node or expr? + node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) : exprType; if (actualReturnType) { // If the function has a return type, but promisedType is // undefined, an error will be reported in checkAsyncFunctionReturnType // so we don't need to report one here. - // >> TODO: should this be node or expr? const errorNode = node.expression && isConditionalExpression(skipParentheses(node.expression)) ? expr : node; checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, actualReturnType, errorNode, expr); } @@ -43591,7 +43580,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return; } - // >> TODO: should we skip some kinds of type nodes? e.g. typeof _? forEachChild(node, collectReferences); } } From de3428d887b21a2836e791ed60383e89e78886a7 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 7 Nov 2023 11:49:11 -0800 Subject: [PATCH 12/90] WIP: account for pre-existing mapping --- src/compiler/checker.ts | 40 +- .../reference/dependentReturnType1.errors.txt | 132 +++++-- .../reference/dependentReturnType1.symbols | 362 ++++++++++++------ .../reference/dependentReturnType1.types | 181 +++++++-- tests/cases/compiler/dependentReturnType1.ts | 81 ++-- 5 files changed, 570 insertions(+), 226 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e0fc1db89ad82..77479ca8a2e93 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18407,8 +18407,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } // We instantiate a distributive checkType with the narrow mapper information const checkTypeVariable = getActualTypeVariable(root.checkType); - const checkType = root.isDistributive ? - instantiateType(checkTypeVariable, combineTypeMappers(mapper, narrowMapper)) : + const narrowableCheckTypeVariable = getNarrowableCheckTypeVariable(root, mapper); + const checkType = narrowableCheckTypeVariable ? + // instantiateType(checkTypeVariable, combineTypeMappers(mapper, narrowMapper)) : + getMappedType(narrowableCheckTypeVariable, narrowMapper) : instantiateType(checkTypeVariable, mapper); const extendsType = instantiateType(root.extendsType, mapper); if (checkType === errorType || extendsType === errorType) { @@ -18422,7 +18424,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) && length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements); - const forceEagerNarrowing = root.isDistributive && getMappedType(getActualTypeVariable(root.checkType), narrowMapper) !== root.checkType; + const forceEagerNarrowing = narrowableCheckTypeVariable && getMappedType(narrowableCheckTypeVariable, narrowMapper) !== root.checkType; // >> TODO: can probably optimize this const checkTypeDeferred = isDeferredType(checkType, checkTuples) && !forceEagerNarrowing; let combinedMapper: TypeMapper | undefined; if (root.inferTypeParameters) { @@ -18545,8 +18547,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const typeParamMapper = newMapper ? combineTypeMappers((newType as ConditionalType).mapper, newMapper) : (newType as ConditionalType).mapper; const typeArguments = typeParamMapper ? map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)) : newRoot.outerTypeParameters; const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); - const newCheckType = newRoot.isDistributive ? - instantiateType(getActualTypeVariable(root.checkType), combineTypeMappers(mapper, narrowMapper)) : + const newCheckTypeVariable = getNarrowableCheckTypeVariable(root, newRootMapper); + // const newCheckType = newRoot.isDistributive ? + // instantiateType(getActualTypeVariable(root.checkType), combineTypeMappers(newRootMapper, narrowMapper)) : + // undefined; + const newCheckType = newCheckTypeVariable ? + instantiateType(newCheckTypeVariable, narrowMapper) : undefined; if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { root = newRoot; @@ -19729,8 +19735,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { aliasTypeArguments: readonly Type[] | undefined, // noTopLevel: boolean): Type { ): Type { - type = instantiateType(type, mapper); - mapper = undefined; + // type = instantiateType(type, mapper); + // mapper = undefined; const flags = type.flags; // if (flags & TypeFlags.TypeParameter) { // // We don't want to narrowly instantiate a return type that is just a type parameter. @@ -19750,7 +19756,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { indexType = getMappedType(indexType, narrowMapper); accessFlags |= AccessFlags.Writing; // Get the writing type } - // >> NOTE: this possibly recurses forever; how do we break this recursion? is the below enough? + // >> NOTE: this possibly recurs forever; how do we break this recursion? is the below enough? if (indexType === (type as IndexedAccessType).indexType && objectType === (type as IndexedAccessType).objectType) { return type; // No type reduction or narrowing happened; so don't do anything else to avoid infinite recursion } @@ -19773,8 +19779,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { aliasTypeArguments); } - return type; - // return instantiateType(type, mapper); + // return type; + return instantiateType(type, mapper); + } + + function getNarrowableCheckTypeVariable(root: ConditionalRoot, mapper: TypeMapper | undefined): TypeParameter | undefined { + if (!root.isDistributive) { + return; + } + const checkType = mapper ? getMappedType(root.checkType, mapper) : root.checkType; + const variable = getActualTypeVariable(checkType); + if (variable.flags & TypeFlags.TypeParameter) { + return variable as TypeParameter; + } } function getNarrowConditionalTypeInstantiation( @@ -19793,7 +19810,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let result; const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); const checkType = root.checkType; - const distributionType = root.isDistributive ? getMappedType(checkType, combineTypeMappers(newMapper, narrowMapper)) : undefined; + const checkTypeVariable = getNarrowableCheckTypeVariable(root, newMapper); + const distributionType = checkTypeVariable ? getMappedType(checkTypeVariable, narrowMapper) : undefined; // Distributive conditional types are distributed over union types. For example, when the // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the // result is (A extends U ? X : Y) | (B extends U ? X : Y). diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index 6cd667775aa10..182b5dc0ff40a 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -1,23 +1,28 @@ -tests/cases/compiler/dependentReturnType1.ts(11,9): error TS2322: Type 'number' is not assignable to type 'string'. -tests/cases/compiler/dependentReturnType1.ts(26,9): error TS2322: Type 'string' is not assignable to type 'never'. -tests/cases/compiler/dependentReturnType1.ts(35,9): error TS2322: Type 'string' is not assignable to type 'never'. -tests/cases/compiler/dependentReturnType1.ts(70,9): error TS2322: Type '{ a: "a"; }' is not assignable to type 'One & Two'. - Type '{ a: "a"; }' is missing the following properties from type 'One': b, c, d -tests/cases/compiler/dependentReturnType1.ts(74,22): error TS2322: Type '{ a: "a"; b: string; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'Three & Four'. - Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. -tests/cases/compiler/dependentReturnType1.ts(89,9): error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. -tests/cases/compiler/dependentReturnType1.ts(108,5): error TS2322: Type 'string' is not assignable to type 'T extends Dog ? number : string'. -tests/cases/compiler/dependentReturnType1.ts(148,9): error TS2322: Type 'this' is not assignable to type 'string & (T extends string ? this : string)'. +dependentReturnType1.ts(11,9): error TS2322: Type 'number' is not assignable to type 'string'. +dependentReturnType1.ts(26,9): error TS2322: Type 'string' is not assignable to type 'never'. +dependentReturnType1.ts(35,9): error TS2322: Type 'string' is not assignable to type 'never'. +dependentReturnType1.ts(70,9): error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. +dependentReturnType1.ts(74,22): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. +dependentReturnType1.ts(89,9): error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. +dependentReturnType1.ts(108,5): error TS2322: Type 'string' is not assignable to type 'T extends Dog ? number : string'. +dependentReturnType1.ts(145,9): error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. + Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. +dependentReturnType1.ts(149,9): error TS2322: Type 'this' is not assignable to type 'string & (T extends string ? this : string)'. Type 'Unnamed' is not assignable to type 'string & (T extends string ? this : string)'. Type 'Unnamed' is not assignable to type 'string'. Type 'this' is not assignable to type 'string'. Type 'Unnamed' is not assignable to type 'string'. -tests/cases/compiler/dependentReturnType1.ts(177,24): error TS2322: Type 'string' is not assignable to type 'number'. -tests/cases/compiler/dependentReturnType1.ts(177,28): error TS2322: Type 'number' is not assignable to type 'string'. -tests/cases/compiler/dependentReturnType1.ts(202,47): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. +dependentReturnType1.ts(178,24): error TS2322: Type 'string' is not assignable to type 'number'. +dependentReturnType1.ts(178,28): error TS2322: Type 'number' is not assignable to type 'string'. +dependentReturnType1.ts(203,47): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. +dependentReturnType1.ts(205,9): error TS2322: Type '""' is not assignable to type 'T extends 1 ? string : boolean'. +dependentReturnType1.ts(211,62): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. +dependentReturnType1.ts(228,9): error TS2322: Type '1' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. +dependentReturnType1.ts(231,9): error TS2322: Type '2' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. +dependentReturnType1.ts(233,5): error TS2322: Type '0' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. -==== tests/cases/compiler/dependentReturnType1.ts (11 errors) ==== +==== dependentReturnType1.ts (17 errors) ==== interface A { 1: number; 2: string; @@ -25,10 +30,10 @@ tests/cases/compiler/dependentReturnType1.ts(202,47): error TS2366: Function lac function f1(x: T): A[T] { if (x === 1) { - return 0; + return 0; // Ok } else { - return 1; + return 1; // Error ~~~~~~ !!! error TS2322: Type 'number' is not assignable to type 'string'. } @@ -42,7 +47,7 @@ tests/cases/compiler/dependentReturnType1.ts(202,47): error TS2366: Function lac function f2(x: T): C[T] { if (x === 1) { - return 0; + return 0; // Ok } else { return ""; // Error, returned expression needs to have type string & boolean (= never) @@ -53,7 +58,7 @@ tests/cases/compiler/dependentReturnType1.ts(202,47): error TS2366: Function lac function f3(x: T): T extends 1 ? number : T extends 2 ? string : T extends 3 ? boolean : never { if (x === 1) { - return 0; + return 0; // Ok } else { return ""; // Error, returned expression needs to have type string & boolean (= never) @@ -92,18 +97,16 @@ tests/cases/compiler/dependentReturnType1.ts(202,47): error TS2366: Function lac function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { if (x === 1 || x === 2) { - // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; - return { a: "a" }; + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok + return { a: "a" }; // Error ~~~~~~ -!!! error TS2322: Type '{ a: "a"; }' is not assignable to type 'One & Two'. -!!! error TS2322: Type '{ a: "a"; }' is missing the following properties from type 'One': b, c, d +!!! error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. } // Excess property becomes a problem with the change, // because we now check assignability to a narrower type... - return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error ~ -!!! error TS2322: Type '{ a: "a"; b: string; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'Three & Four'. -!!! error TS2322: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. +!!! error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. } // Asymmetry @@ -116,12 +119,11 @@ tests/cases/compiler/dependentReturnType1.ts(202,47): error TS2366: Function lac { type OK = Arg extends LeftIn ? LeftOut : RightOut; if (cond(arg)) { - return produceLeftOut(arg); + return produceLeftOut(arg); // Ok } else { - return produceRightOut(arg as RightIn); // Doesn't work because we don't narrow `arg` to `Arg & RightIn` here + return produceRightOut(arg as RightIn); // Error: Doesn't work because we don't narrow `arg` to `Arg & RightIn` here ~~~~~~ !!! error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. - return produceRightOut(arg as RightIn) as OK; } } @@ -133,13 +135,14 @@ tests/cases/compiler/dependentReturnType1.ts(202,47): error TS2366: Function lac bark: () => string; } + // This is unsafe declare function isDog(x: Animal): x is Dog; declare function doggy(x: Dog): number; function f12(x: T): T extends Dog ? number : string { if (isDog(x)) { // `x` has type `T & Dog` here - return doggy(x); // Should work + return doggy(x); // Ok } - return ""; // Should not work because we can't express "not a Dog" in the type system + return ""; // Error: Should not work because we can't express "not a Dog" in the type system ~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'T extends Dog ? number : string'. } @@ -173,15 +176,19 @@ tests/cases/compiler/dependentReturnType1.ts(202,47): error TS2366: Function lac class Unnamed { root!: { name: string }; + // Error because parameter is optional name(name?: T): T extends string ? this : string { if (typeof name === 'undefined') { return this.root.name; } return this; + ~~~~~~ +!!! error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. +!!! error TS2322: Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. } - + // Error because parameter is optional? nameWithError(name?: T): T extends string ? this : string { - return this; // Investigate error message + return this; // Error: Investigate error message ~~~~~~ !!! error TS2322: Type 'this' is not assignable to type 'string & (T extends string ? this : string)'. !!! error TS2322: Type 'Unnamed' is not assignable to type 'string & (T extends string ? this : string)'. @@ -191,13 +198,13 @@ tests/cases/compiler/dependentReturnType1.ts(202,47): error TS2366: Function lac } } - interface A { + interface Aa { 1: number; 2: string; 3: string; } - function trivialConditional(x: T): A[T] { + function trivialConditional(x: T): Aa[T] { if (x !== 1) { return x === 2 ? "" : `${x}`; } @@ -208,15 +215,15 @@ tests/cases/compiler/dependentReturnType1.ts(202,47): error TS2366: Function lac // Conditional expressions function conditional(x: T): T extends true ? 1 : 2 { - return x ? 1 : 2; + return x ? 1 : 2; // Ok } function contextualConditional(x: T): T extends "a" ? "a" : number { - return x === "a" ? x : parseInt(x); + return x === "a" ? x : parseInt(x); // Ok } function conditionalWithError(x: T): T extends "a" ? number : string { - return x === "a" ? x : parseInt(x); + return x === "a" ? x : parseInt(x); // Error ~ !!! error TS2322: Type 'string' is not assignable to type 'number'. ~~~~~~~~~~~ @@ -238,19 +245,64 @@ tests/cases/compiler/dependentReturnType1.ts(202,47): error TS2366: Function lac if (y === "c" && x === "a") { // AA[U='c'] -> BB[T] // BB[T='a'] -> number - return 0; + return 0; // Ok } return undefined as never; } - // Conditional with substitution types should also be narrowed + // Substitution types are not narrowed? function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'. if (x === 1) { return ""; + ~~~~~~ +!!! error TS2322: Type '""' is not assignable to type 'T extends 1 ? string : boolean'. + } + } + + // Unsafe: supertype problem + declare function q(x: object): x is { b: number }; + function foo(x: T): T extends { a: string } ? number : (string | number) { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'. + if (q(x)) { + x.b; + return ""; } } - // TODO: test non-tail recursive and tail recursive conditionals \ No newline at end of file + let y = { a: "", b: 1 } + const r = foo<{ a: string }>(y); // number + + type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; + + function foo2(x: U, y: V): + HelperCond<{ x: U, y: V }, + { x: string, y: true }, 1, + { x: number, y: false }, 2> { + if (typeof x === "string" && y === true) { + return 1; + ~~~~~~ +!!! error TS2322: Type '1' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. + } + if (typeof x === "number" && y === false) { + return 2; + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. + } + return 0; + ~~~~~~ +!!! error TS2322: Type '0' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. + } + + // >> TODO: test non-tail recursive and tail recursive conditionals + + // >> TODO: fix this + function voidRet(x: T): T extends {} ? void : number { + if (x) { + return; + } + return 1; + } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 26e4e60d6ac09..977d7e24fd424 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -1,12 +1,14 @@ -=== tests/cases/compiler/dependentReturnType1.ts === +//// [tests/cases/compiler/dependentReturnType1.ts] //// + +=== dependentReturnType1.ts === interface A { ->A : Symbol(A, Decl(dependentReturnType1.ts, 0, 0), Decl(dependentReturnType1.ts, 149, 1)) +>A : Symbol(A, Decl(dependentReturnType1.ts, 0, 0)) 1: number; ->1 : Symbol(A[1], Decl(dependentReturnType1.ts, 0, 13), Decl(dependentReturnType1.ts, 151, 13)) +>1 : Symbol(A[1], Decl(dependentReturnType1.ts, 0, 13)) 2: string; ->2 : Symbol(A[2], Decl(dependentReturnType1.ts, 1, 14), Decl(dependentReturnType1.ts, 152, 14)) +>2 : Symbol(A[2], Decl(dependentReturnType1.ts, 1, 14)) } function f1(x: T): A[T] { @@ -14,16 +16,16 @@ function f1(x: T): A[T] { >T : Symbol(T, Decl(dependentReturnType1.ts, 5, 12)) >x : Symbol(x, Decl(dependentReturnType1.ts, 5, 29)) >T : Symbol(T, Decl(dependentReturnType1.ts, 5, 12)) ->A : Symbol(A, Decl(dependentReturnType1.ts, 0, 0), Decl(dependentReturnType1.ts, 149, 1)) +>A : Symbol(A, Decl(dependentReturnType1.ts, 0, 0)) >T : Symbol(T, Decl(dependentReturnType1.ts, 5, 12)) if (x === 1) { >x : Symbol(x, Decl(dependentReturnType1.ts, 5, 29)) - return 0; + return 0; // Ok } else { - return 1; + return 1; // Error } } @@ -51,7 +53,7 @@ function f2(x: T): C[T] { if (x === 1) { >x : Symbol(x, Decl(dependentReturnType1.ts, 20, 33)) - return 0; + return 0; // Ok } else { return ""; // Error, returned expression needs to have type string & boolean (= never) @@ -70,7 +72,7 @@ function f3(x: T): T extends 1 ? number : T extends 2 ? str if (x === 1) { >x : Symbol(x, Decl(dependentReturnType1.ts, 29, 33)) - return 0; + return 0; // Ok } else { return ""; // Error, returned expression needs to have type string & boolean (= never) @@ -158,13 +160,20 @@ function f10(x: T): T extends 1 ? One : T extends 2 ? T >x : Symbol(x, Decl(dependentReturnType1.ts, 66, 38)) >x : Symbol(x, Decl(dependentReturnType1.ts, 66, 38)) - // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; - return { a: "a" }; + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok +>a : Symbol(a, Decl(dependentReturnType1.ts, 68, 16)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 68, 24)) +>c : Symbol(c, Decl(dependentReturnType1.ts, 68, 32)) +>d : Symbol(d, Decl(dependentReturnType1.ts, 68, 40)) +>e : Symbol(e, Decl(dependentReturnType1.ts, 68, 48)) +>f : Symbol(f, Decl(dependentReturnType1.ts, 68, 56)) + + return { a: "a" }; // Error >a : Symbol(a, Decl(dependentReturnType1.ts, 69, 16)) } // Excess property becomes a problem with the change, // because we now check assignability to a narrower type... - return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error >a : Symbol(a, Decl(dependentReturnType1.ts, 73, 12)) >b : Symbol(b, Decl(dependentReturnType1.ts, 73, 20)) >c : Symbol(c, Decl(dependentReturnType1.ts, 73, 28)) @@ -226,69 +235,64 @@ function conditionalProducingIfcond : Symbol(cond, Decl(dependentReturnType1.ts, 78, 13)) >arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) - return produceLeftOut(arg); + return produceLeftOut(arg); // Ok >produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 79, 51)) >arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) } else { - return produceRightOut(arg as RightIn); // Doesn't work because we don't narrow `arg` to `Arg & RightIn` here ->produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 80, 45)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) - - return produceRightOut(arg as RightIn) as OK; + return produceRightOut(arg as RightIn); // Error: Doesn't work because we don't narrow `arg` to `Arg & RightIn` here >produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 80, 45)) >arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) >RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) ->OK : Symbol(OK, Decl(dependentReturnType1.ts, 83, 1)) } } interface Animal { ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 91, 1)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 90, 1)) name: string; ->name : Symbol(Animal.name, Decl(dependentReturnType1.ts, 93, 18)) +>name : Symbol(Animal.name, Decl(dependentReturnType1.ts, 92, 18)) } interface Dog extends Animal { ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 95, 1)) ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 91, 1)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 94, 1)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 90, 1)) bark: () => string; ->bark : Symbol(Dog.bark, Decl(dependentReturnType1.ts, 97, 30)) +>bark : Symbol(Dog.bark, Decl(dependentReturnType1.ts, 96, 30)) } +// This is unsafe declare function isDog(x: Animal): x is Dog; ->isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 99, 1)) +>isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 98, 1)) >x : Symbol(x, Decl(dependentReturnType1.ts, 101, 23)) ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 91, 1)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 90, 1)) >x : Symbol(x, Decl(dependentReturnType1.ts, 101, 23)) ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 95, 1)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 94, 1)) declare function doggy(x: Dog): number; >doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 101, 44)) >x : Symbol(x, Decl(dependentReturnType1.ts, 102, 23)) ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 95, 1)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 94, 1)) function f12(x: T): T extends Dog ? number : string { >f12 : Symbol(f12, Decl(dependentReturnType1.ts, 102, 39)) >T : Symbol(T, Decl(dependentReturnType1.ts, 103, 13)) ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 91, 1)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 90, 1)) >x : Symbol(x, Decl(dependentReturnType1.ts, 103, 31)) >T : Symbol(T, Decl(dependentReturnType1.ts, 103, 13)) >T : Symbol(T, Decl(dependentReturnType1.ts, 103, 13)) ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 95, 1)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 94, 1)) if (isDog(x)) { // `x` has type `T & Dog` here ->isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 99, 1)) +>isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 98, 1)) >x : Symbol(x, Decl(dependentReturnType1.ts, 103, 31)) - return doggy(x); // Should work + return doggy(x); // Ok >doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 101, 44)) >x : Symbol(x, Decl(dependentReturnType1.ts, 103, 31)) } - return ""; // Should not work because we can't express "not a Dog" in the type system + return ""; // Error: Should not work because we can't express "not a Dog" in the type system } // Cannot narrow `keyof` too eagerly or something like the below breaks @@ -371,15 +375,16 @@ class Unnamed { >root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 137, 15)) >name : Symbol(name, Decl(dependentReturnType1.ts, 138, 12)) + // Error because parameter is optional name(name?: T): T extends string ? this : string { >name : Symbol(Unnamed.name, Decl(dependentReturnType1.ts, 138, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 139, 9)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 139, 27)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 139, 9)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 139, 9)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 140, 9)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 140, 27)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 140, 9)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 140, 9)) if (typeof name === 'undefined') { ->name : Symbol(name, Decl(dependentReturnType1.ts, 139, 27)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 140, 27)) return this.root.name; >this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 138, 12)) @@ -391,46 +396,46 @@ class Unnamed { return this; >this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 135, 1)) } - + // Error because parameter is optional? nameWithError(name?: T): T extends string ? this : string { ->nameWithError : Symbol(Unnamed.nameWithError, Decl(dependentReturnType1.ts, 144, 5)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 146, 18)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 146, 36)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 146, 18)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 146, 18)) +>nameWithError : Symbol(Unnamed.nameWithError, Decl(dependentReturnType1.ts, 145, 5)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 147, 18)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 147, 36)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 147, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 147, 18)) - return this; // Investigate error message + return this; // Error: Investigate error message >this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 135, 1)) } } -interface A { ->A : Symbol(A, Decl(dependentReturnType1.ts, 0, 0), Decl(dependentReturnType1.ts, 149, 1)) +interface Aa { +>Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 150, 1)) 1: number; ->1 : Symbol(A[1], Decl(dependentReturnType1.ts, 0, 13), Decl(dependentReturnType1.ts, 151, 13)) +>1 : Symbol(Aa[1], Decl(dependentReturnType1.ts, 152, 14)) 2: string; ->2 : Symbol(A[2], Decl(dependentReturnType1.ts, 1, 14), Decl(dependentReturnType1.ts, 152, 14)) +>2 : Symbol(Aa[2], Decl(dependentReturnType1.ts, 153, 14)) 3: string; ->3 : Symbol(A[3], Decl(dependentReturnType1.ts, 153, 14)) +>3 : Symbol(Aa[3], Decl(dependentReturnType1.ts, 154, 14)) } -function trivialConditional(x: T): A[T] { ->trivialConditional : Symbol(trivialConditional, Decl(dependentReturnType1.ts, 155, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 157, 28)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 157, 49)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 157, 28)) ->A : Symbol(A, Decl(dependentReturnType1.ts, 0, 0), Decl(dependentReturnType1.ts, 149, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 157, 28)) +function trivialConditional(x: T): Aa[T] { +>trivialConditional : Symbol(trivialConditional, Decl(dependentReturnType1.ts, 156, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 158, 28)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 158, 49)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 158, 28)) +>Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 150, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 158, 28)) if (x !== 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 157, 49)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 158, 49)) return x === 2 ? "" : `${x}`; ->x : Symbol(x, Decl(dependentReturnType1.ts, 157, 49)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 157, 49)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 158, 49)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 158, 49)) } else { return 0; @@ -439,109 +444,222 @@ function trivialConditional(x: T): A[T] { // Conditional expressions function conditional(x: T): T extends true ? 1 : 2 { ->conditional : Symbol(conditional, Decl(dependentReturnType1.ts, 164, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 167, 21)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 167, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 167, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 167, 21)) - - return x ? 1 : 2; ->x : Symbol(x, Decl(dependentReturnType1.ts, 167, 40)) +>conditional : Symbol(conditional, Decl(dependentReturnType1.ts, 165, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 168, 21)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 168, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 168, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 168, 21)) + + return x ? 1 : 2; // Ok +>x : Symbol(x, Decl(dependentReturnType1.ts, 168, 40)) } function contextualConditional(x: T): T extends "a" ? "a" : number { ->contextualConditional : Symbol(contextualConditional, Decl(dependentReturnType1.ts, 169, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 171, 31)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 171, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 171, 31)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 171, 31)) - - return x === "a" ? x : parseInt(x); ->x : Symbol(x, Decl(dependentReturnType1.ts, 171, 52)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 171, 52)) +>contextualConditional : Symbol(contextualConditional, Decl(dependentReturnType1.ts, 170, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 172, 31)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 172, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 172, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 172, 31)) + + return x === "a" ? x : parseInt(x); // Ok +>x : Symbol(x, Decl(dependentReturnType1.ts, 172, 52)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 172, 52)) >parseInt : Symbol(parseInt, Decl(lib.es5.d.ts, --, --)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 171, 52)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 172, 52)) } function conditionalWithError(x: T): T extends "a" ? number : string { ->conditionalWithError : Symbol(conditionalWithError, Decl(dependentReturnType1.ts, 173, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 175, 30)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 175, 51)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 175, 30)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 175, 30)) - - return x === "a" ? x : parseInt(x); ->x : Symbol(x, Decl(dependentReturnType1.ts, 175, 51)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 175, 51)) +>conditionalWithError : Symbol(conditionalWithError, Decl(dependentReturnType1.ts, 174, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 176, 30)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 176, 51)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 176, 30)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 176, 30)) + + return x === "a" ? x : parseInt(x); // Error +>x : Symbol(x, Decl(dependentReturnType1.ts, 176, 51)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 176, 51)) >parseInt : Symbol(parseInt, Decl(lib.es5.d.ts, --, --)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 175, 51)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 176, 51)) } // Multiple reductions interface BB { ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 177, 1)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 178, 1)) "a": number; ->"a" : Symbol(BB["a"], Decl(dependentReturnType1.ts, 180, 14)) +>"a" : Symbol(BB["a"], Decl(dependentReturnType1.ts, 181, 14)) [y: number]: string; ->y : Symbol(y, Decl(dependentReturnType1.ts, 182, 5)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 183, 5)) } interface AA { ->AA : Symbol(AA, Decl(dependentReturnType1.ts, 183, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 185, 13)) ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 177, 1)) +>AA : Symbol(AA, Decl(dependentReturnType1.ts, 184, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 186, 13)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 178, 1)) "c": BB[T]; ->"c" : Symbol(AA["c"], Decl(dependentReturnType1.ts, 185, 34)) ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 177, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 185, 13)) +>"c" : Symbol(AA["c"], Decl(dependentReturnType1.ts, 186, 34)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 178, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 186, 13)) "d": boolean, ->"d" : Symbol(AA["d"], Decl(dependentReturnType1.ts, 186, 15)) +>"d" : Symbol(AA["d"], Decl(dependentReturnType1.ts, 187, 15)) } function reduction(x: T, y: U): AA[U] { ->reduction : Symbol(reduction, Decl(dependentReturnType1.ts, 188, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 190, 19)) ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 177, 1)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 190, 38)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 190, 60)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 190, 19)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 190, 65)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 190, 38)) ->AA : Symbol(AA, Decl(dependentReturnType1.ts, 183, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 190, 19)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 190, 38)) +>reduction : Symbol(reduction, Decl(dependentReturnType1.ts, 189, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 191, 19)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 178, 1)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 191, 38)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 191, 60)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 191, 19)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 191, 65)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 191, 38)) +>AA : Symbol(AA, Decl(dependentReturnType1.ts, 184, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 191, 19)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 191, 38)) if (y === "c" && x === "a") { ->y : Symbol(y, Decl(dependentReturnType1.ts, 190, 65)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 190, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 191, 65)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 191, 60)) // AA[U='c'] -> BB[T] // BB[T='a'] -> number - return 0; + return 0; // Ok } return undefined as never; >undefined : Symbol(undefined) } -// Conditional with substitution types should also be narrowed +// Substitution types are not narrowed? function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { ->subsCond : Symbol(subsCond, Decl(dependentReturnType1.ts, 198, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 201, 18)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 201, 39)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 201, 18)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 201, 18)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 201, 18)) +>subsCond : Symbol(subsCond, Decl(dependentReturnType1.ts, 199, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 202, 18)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 202, 39)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 202, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 202, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 202, 18)) if (x === 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 201, 39)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 202, 39)) return ""; } } -// TODO: test non-tail recursive and tail recursive conditionals +// Unsafe: supertype problem +declare function q(x: object): x is { b: number }; +>q : Symbol(q, Decl(dependentReturnType1.ts, 206, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 209, 19)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 209, 19)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 209, 37)) + +function foo(x: T): T extends { a: string } ? number : (string | number) { +>foo : Symbol(foo, Decl(dependentReturnType1.ts, 209, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 210, 13)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 210, 24)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 210, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 210, 54)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 210, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 210, 13)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 210, 72)) + + if (q(x)) { +>q : Symbol(q, Decl(dependentReturnType1.ts, 206, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 210, 54)) + + x.b; +>x.b : Symbol(b, Decl(dependentReturnType1.ts, 210, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 210, 54)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 210, 40)) + + return ""; + } +} + +let y = { a: "", b: 1 } +>y : Symbol(y, Decl(dependentReturnType1.ts, 217, 3)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 217, 9)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 217, 16)) + +const r = foo<{ a: string }>(y); // number +>r : Symbol(r, Decl(dependentReturnType1.ts, 218, 5)) +>foo : Symbol(foo, Decl(dependentReturnType1.ts, 209, 50)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 218, 15)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 217, 3)) + +type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 218, 32)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 220, 16)) +>A : Symbol(A, Decl(dependentReturnType1.ts, 220, 18)) +>R1 : Symbol(R1, Decl(dependentReturnType1.ts, 220, 21)) +>B : Symbol(B, Decl(dependentReturnType1.ts, 220, 25)) +>R2 : Symbol(R2, Decl(dependentReturnType1.ts, 220, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 220, 16)) +>A : Symbol(A, Decl(dependentReturnType1.ts, 220, 18)) +>R1 : Symbol(R1, Decl(dependentReturnType1.ts, 220, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 220, 16)) +>B : Symbol(B, Decl(dependentReturnType1.ts, 220, 25)) +>R2 : Symbol(R2, Decl(dependentReturnType1.ts, 220, 28)) +>R1 : Symbol(R1, Decl(dependentReturnType1.ts, 220, 21)) +>R2 : Symbol(R2, Decl(dependentReturnType1.ts, 220, 28)) + +function foo2(x: U, y: V): +>foo2 : Symbol(foo2, Decl(dependentReturnType1.ts, 220, 81)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 222, 14)) +>V : Symbol(V, Decl(dependentReturnType1.ts, 222, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 222, 60)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 222, 14)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 222, 65)) +>V : Symbol(V, Decl(dependentReturnType1.ts, 222, 40)) + + HelperCond<{ x: U, y: V }, +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 218, 32)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 223, 16)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 222, 14)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 223, 22)) +>V : Symbol(V, Decl(dependentReturnType1.ts, 222, 40)) + + { x: string, y: true }, 1, +>x : Symbol(x, Decl(dependentReturnType1.ts, 224, 9)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 224, 20)) + + { x: number, y: false }, 2> { +>x : Symbol(x, Decl(dependentReturnType1.ts, 225, 9)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 225, 20)) + + if (typeof x === "string" && y === true) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 222, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 222, 65)) + + return 1; + } + if (typeof x === "number" && y === false) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 222, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 222, 65)) + + return 2; + } + return 0; +} + +// >> TODO: test non-tail recursive and tail recursive conditionals + +// >> TODO: fix this +function voidRet(x: T): T extends {} ? void : number { +>voidRet : Symbol(voidRet, Decl(dependentReturnType1.ts, 233, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 238, 17)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 238, 28)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 238, 54)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 238, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 238, 17)) + + if (x) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 238, 54)) + + return; + } + return 1; +} diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index fbc1e3137de21..1c4759aeeb2b2 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -1,4 +1,6 @@ -=== tests/cases/compiler/dependentReturnType1.ts === +//// [tests/cases/compiler/dependentReturnType1.ts] //// + +=== dependentReturnType1.ts === interface A { 1: number; >1 : number @@ -16,11 +18,11 @@ function f1(x: T): A[T] { >x : T >1 : 1 - return 0; + return 0; // Ok >0 : 0 } else { - return 1; + return 1; // Error >1 : 1 } } @@ -45,7 +47,7 @@ function f2(x: T): C[T] { >x : T >1 : 1 - return 0; + return 0; // Ok >0 : 0 } else { @@ -63,7 +65,7 @@ function f3(x: T): T extends 1 ? number : T extends 2 ? str >x : T >1 : 1 - return 0; + return 0; // Ok >0 : 0 } else { @@ -141,15 +143,29 @@ function f10(x: T): T extends 1 ? One : T extends 2 ? T >x : T >2 : 2 - // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; - return { a: "a" }; + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok +>{ a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" } : { a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; } +>a : "a" +>"a" : "a" +>b : "b" +>"b" : "b" +>c : "c" +>"c" : "c" +>d : "d" +>"d" : "d" +>e : "e" +>"e" : "e" +>f : "f" +>"f" : "f" + + return { a: "a" }; // Error >{ a: "a" } : { a: "a"; } >a : "a" >"a" : "a" } // Excess property becomes a problem with the change, // because we now check assignability to a narrower type... - return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error >{ a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" } : { a: "a"; b: string; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; } >a : "a" >"a" : "a" @@ -196,20 +212,13 @@ function conditionalProducingIfcond : (arg: LeftIn | RightIn) => arg is LeftIn >arg : Arg - return produceLeftOut(arg); + return produceLeftOut(arg); // Ok >produceLeftOut(arg) : LeftOut >produceLeftOut : (arg: LeftIn) => LeftOut >arg : Arg & LeftIn } else { - return produceRightOut(arg as RightIn); // Doesn't work because we don't narrow `arg` to `Arg & RightIn` here ->produceRightOut(arg as RightIn) : RightOut ->produceRightOut : (arg: RightIn) => RightOut ->arg as RightIn : RightIn ->arg : Arg - - return produceRightOut(arg as RightIn) as OK; ->produceRightOut(arg as RightIn) as OK : Arg extends LeftIn ? LeftOut : RightOut + return produceRightOut(arg as RightIn); // Error: Doesn't work because we don't narrow `arg` to `Arg & RightIn` here >produceRightOut(arg as RightIn) : RightOut >produceRightOut : (arg: RightIn) => RightOut >arg as RightIn : RightIn @@ -227,6 +236,7 @@ interface Dog extends Animal { >bark : () => string } +// This is unsafe declare function isDog(x: Animal): x is Dog; >isDog : (x: Animal) => x is Dog >x : Animal @@ -244,12 +254,12 @@ function f12(x: T): T extends Dog ? number : string { >isDog : (x: Animal) => x is Dog >x : T - return doggy(x); // Should work + return doggy(x); // Ok >doggy(x) : number >doggy : (x: Dog) => number >x : T & Dog } - return ""; // Should not work because we can't express "not a Dog" in the type system + return ""; // Error: Should not work because we can't express "not a Dog" in the type system >"" : "" } @@ -330,6 +340,7 @@ class Unnamed { >root : { name: string; } >name : string + // Error because parameter is optional name(name?: T): T extends string ? this : string { >name : (name?: T) => T extends string ? this : string >name : T | undefined @@ -350,17 +361,17 @@ class Unnamed { return this; >this : this } - + // Error because parameter is optional? nameWithError(name?: T): T extends string ? this : string { >nameWithError : (name?: T) => T extends string ? this : string >name : T | undefined - return this; // Investigate error message + return this; // Error: Investigate error message >this : this } } -interface A { +interface Aa { 1: number; >1 : number @@ -371,8 +382,8 @@ interface A { >3 : string } -function trivialConditional(x: T): A[T] { ->trivialConditional : (x: T) => A[T] +function trivialConditional(x: T): Aa[T] { +>trivialConditional : (x: T) => Aa[T] >x : T if (x !== 1) { @@ -401,7 +412,7 @@ function conditional(x: T): T extends true ? 1 : 2 { >x : T >true : true - return x ? 1 : 2; + return x ? 1 : 2; // Ok >x ? 1 : 2 : 1 | 2 >x : T >1 : 1 @@ -412,7 +423,7 @@ function contextualConditional(x: T): T extends "a" ? "a" : >contextualConditional : (x: T) => T extends "a" ? "a" : number >x : T - return x === "a" ? x : parseInt(x); + return x === "a" ? x : parseInt(x); // Ok >x === "a" ? x : parseInt(x) : number | "a" >x === "a" : boolean >x : T @@ -427,7 +438,7 @@ function conditionalWithError(x: T): T extends "a" ? number >conditionalWithError : (x: T) => T extends "a" ? number : string >x : T - return x === "a" ? x : parseInt(x); + return x === "a" ? x : parseInt(x); // Error >x === "a" ? x : parseInt(x) : number | "a" >x === "a" : boolean >x : T @@ -471,7 +482,7 @@ function reduction(x: T, y: U): AA[U // AA[U='c'] -> BB[T] // BB[T='a'] -> number - return 0; + return 0; // Ok >0 : 0 } @@ -480,7 +491,7 @@ function reduction(x: T, y: U): AA[U >undefined : undefined } -// Conditional with substitution types should also be narrowed +// Substitution types are not narrowed? function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { >subsCond : (x: T) => T extends 1 | 2 ? (T extends 1 ? string : boolean) : number >x : T @@ -495,4 +506,114 @@ function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? s } } -// TODO: test non-tail recursive and tail recursive conditionals +// Unsafe: supertype problem +declare function q(x: object): x is { b: number }; +>q : (x: object) => x is { b: number; } +>x : object +>b : number + +function foo(x: T): T extends { a: string } ? number : (string | number) { +>foo : (x: T) => T extends { a: string;} ? number : (string | number) +>a : string +>b : number +>x : T +>a : string + + if (q(x)) { +>q(x) : boolean +>q : (x: object) => x is { b: number; } +>x : { a: string; } | { b: number; } + + x.b; +>x.b : number +>x : { b: number; } +>b : number + + return ""; +>"" : "" + } +} + +let y = { a: "", b: 1 } +>y : { a: string; b: number; } +>{ a: "", b: 1 } : { a: string; b: number; } +>a : string +>"" : "" +>b : number +>1 : 1 + +const r = foo<{ a: string }>(y); // number +>r : number +>foo<{ a: string }>(y) : number +>foo : (x: T) => T extends { a: string; } ? number : string | number +>a : string +>y : { a: string; b: number; } + +type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; +>HelperCond : HelperCond + +function foo2(x: U, y: V): +>foo2 : (x: U, y: V) => HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2> +>x : U +>y : V + + HelperCond<{ x: U, y: V }, +>x : U +>y : V + + { x: string, y: true }, 1, +>x : string +>y : true +>true : true + + { x: number, y: false }, 2> { +>x : number +>y : false +>false : false + + if (typeof x === "string" && y === true) { +>typeof x === "string" && y === true : boolean +>typeof x === "string" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : U +>"string" : "string" +>y === true : boolean +>y : V +>true : true + + return 1; +>1 : 1 + } + if (typeof x === "number" && y === false) { +>typeof x === "number" && y === false : boolean +>typeof x === "number" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : U +>"number" : "number" +>y === false : boolean +>y : V +>false : false + + return 2; +>2 : 2 + } + return 0; +>0 : 0 +} + +// >> TODO: test non-tail recursive and tail recursive conditionals + +// >> TODO: fix this +function voidRet(x: T): T extends {} ? void : number { +>voidRet : (x: T) => T extends {} ? void : number +>a : string +>x : T + + if (x) { +>x : T + + return; + } + return 1; +>1 : 1 +} diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index f10ffac1e1767..bc0699af05dc0 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -8,10 +8,10 @@ interface A { function f1(x: T): A[T] { if (x === 1) { - return 0; + return 0; // Ok } else { - return 1; + return 1; // Error } } @@ -23,7 +23,7 @@ interface C { function f2(x: T): C[T] { if (x === 1) { - return 0; + return 0; // Ok } else { return ""; // Error, returned expression needs to have type string & boolean (= never) @@ -32,7 +32,7 @@ function f2(x: T): C[T] { function f3(x: T): T extends 1 ? number : T extends 2 ? string : T extends 3 ? boolean : never { if (x === 1) { - return 0; + return 0; // Ok } else { return ""; // Error, returned expression needs to have type string & boolean (= never) @@ -69,12 +69,12 @@ interface Four { function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { if (x === 1 || x === 2) { - // return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; - return { a: "a" }; + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok + return { a: "a" }; // Error } // Excess property becomes a problem with the change, // because we now check assignability to a narrower type... - return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error } // Asymmetry @@ -87,10 +87,9 @@ function conditionalProducingIf string; } -// TODO: this is unsafe +// This is unsafe declare function isDog(x: Animal): x is Dog; declare function doggy(x: Dog): number; function f12(x: T): T extends Dog ? number : string { if (isDog(x)) { // `x` has type `T & Dog` here - return doggy(x); // Should work + return doggy(x); // Ok } - return ""; // Should not work because we can't express "not a Dog" in the type system + return ""; // Error: Should not work because we can't express "not a Dog" in the type system } // Cannot narrow `keyof` too eagerly or something like the below breaks @@ -141,25 +140,26 @@ export function bbb(value: AB): "a" { class Unnamed { root!: { name: string }; + // Error because parameter is optional name(name?: T): T extends string ? this : string { if (typeof name === 'undefined') { return this.root.name; } return this; } - + // Error because parameter is optional? nameWithError(name?: T): T extends string ? this : string { - return this; // Investigate error message + return this; // Error: Investigate error message } } -interface A { +interface Aa { 1: number; 2: string; 3: string; } -function trivialConditional(x: T): A[T] { +function trivialConditional(x: T): Aa[T] { if (x !== 1) { return x === 2 ? "" : `${x}`; } @@ -170,15 +170,15 @@ function trivialConditional(x: T): A[T] { // Conditional expressions function conditional(x: T): T extends true ? 1 : 2 { - return x ? 1 : 2; + return x ? 1 : 2; // Ok } function contextualConditional(x: T): T extends "a" ? "a" : number { - return x === "a" ? x : parseInt(x); + return x === "a" ? x : parseInt(x); // Ok } function conditionalWithError(x: T): T extends "a" ? number : string { - return x === "a" ? x : parseInt(x); + return x === "a" ? x : parseInt(x); // Error } // Multiple reductions @@ -196,17 +196,52 @@ function reduction(x: T, y: U): AA[U if (y === "c" && x === "a") { // AA[U='c'] -> BB[T] // BB[T='a'] -> number - return 0; + return 0; // Ok } return undefined as never; } -// Conditional with substitution types should also be narrowed +// Substitution types are not narrowed? function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { if (x === 1) { return ""; } } -// TODO: test non-tail recursive and tail recursive conditionals \ No newline at end of file +// Unsafe: supertype problem +declare function q(x: object): x is { b: number }; +function foo(x: T): T extends { a: string } ? number : (string | number) { + if (q(x)) { + x.b; + return ""; + } +} + +let y = { a: "", b: 1 } +const r = foo<{ a: string }>(y); // number + +type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; + +function foo2(x: U, y: V): + HelperCond<{ x: U, y: V }, + { x: string, y: true }, 1, + { x: number, y: false }, 2> { + if (typeof x === "string" && y === true) { + return 1; + } + if (typeof x === "number" && y === false) { + return 2; + } + return 0; +} + +// >> TODO: test non-tail recursive and tail recursive conditionals + +// >> TODO: fix this +function voidRet(x: T): T extends {} ? void : number { + if (x) { + return; + } + return 1; +} \ No newline at end of file From 04980d3e7bfc2457d305cdb5df3a0e3dd2a8a1bc Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 27 Nov 2023 19:48:16 -0800 Subject: [PATCH 13/90] only narrow when we take a true branch of a conditional --- src/compiler/checker.ts | 62 +- .../reference/dependentReturnType1.errors.txt | 155 +++- .../reference/dependentReturnType1.symbols | 738 +++++++++++------- .../reference/dependentReturnType1.types | 261 ++++++- tests/cases/compiler/dependentReturnType1.ts | 82 +- 5 files changed, 958 insertions(+), 340 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 77479ca8a2e93..bf4e7af9e358c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18395,6 +18395,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let result; let extraTypes: Type[] | undefined; let tailCount = 0; + + let lastRoot = root; + let lastCombinedMapper: TypeMapper | undefined = undefined; + let lastAliasSymbol = aliasSymbol; + let lastAliasTypeArguments = aliasTypeArguments; + + let saveNext = false; // We loop here for an immediately nested conditional type in the false position, effectively treating // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of @@ -18405,11 +18412,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); return errorType; } - // We instantiate a distributive checkType with the narrow mapper information + // We instantiate a distributive checkType with the narrow mapper const checkTypeVariable = getActualTypeVariable(root.checkType); const narrowableCheckTypeVariable = getNarrowableCheckTypeVariable(root, mapper); const checkType = narrowableCheckTypeVariable ? - // instantiateType(checkTypeVariable, combineTypeMappers(mapper, narrowMapper)) : getMappedType(narrowableCheckTypeVariable, narrowMapper) : instantiateType(checkTypeVariable, mapper); const extendsType = instantiateType(root.extendsType, mapper); @@ -18424,7 +18430,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) && length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements); - const forceEagerNarrowing = narrowableCheckTypeVariable && getMappedType(narrowableCheckTypeVariable, narrowMapper) !== root.checkType; // >> TODO: can probably optimize this + const forceEagerNarrowing = narrowableCheckTypeVariable && getMappedType(narrowableCheckTypeVariable, narrowMapper) !== narrowableCheckTypeVariable; // >> TODO: can probably optimize this const checkTypeDeferred = isDeferredType(checkType, checkTuples) && !forceEagerNarrowing; let combinedMapper: TypeMapper | undefined; if (root.inferTypeParameters) { @@ -18470,6 +18476,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } // Instantiate the extends type including inferences for 'infer T' type parameters const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; + + if (saveNext) { + saveConditional(combinedMapper); + } // We attempt to resolve the conditional type only when the check and extends types are non-generic // if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { if (!checkTypeDeferred) { @@ -18480,6 +18490,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { // Return union of trueType and falseType for 'any' since it matches anything if (checkType.flags & TypeFlags.Any) { + // >> TODO: took true here? I don't think this can ever happen -- we wouldn't narrow the check type to `any` because we don't narrow anything to `any` (extraTypes || (extraTypes = [])).push( instantiateNarrowType(getTypeFromTypeNode(root.node.trueType), narrowMapper, combinedMapper || mapper)); } @@ -18488,6 +18499,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const falseType = getTypeFromTypeNode(root.node.falseType); if (falseType.flags & TypeFlags.Conditional) { const newRoot = (falseType as ConditionalType).root; + // >> TODO: can we make this recurse even when the original check types are different, + // as long as the check types instantiated with `mapper` are the same? + // What could be wrong is `aliasSymbol` and `aliasTypeArguments`? + // >> TODO: reset those as we do inside `canTailRecurse` if (newRoot.node.parent === root.node && (!newRoot.isDistributive || newRoot.checkType === root.checkType)) { root = newRoot; continue; @@ -18496,46 +18511,59 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { continue; } } - result = instantiateNarrowType(falseType, narrowMapper, mapper); - break; + // >> TODO: basically disallow this, and go to fall-through case + // result = instantiateNarrowType(falseType, narrowMapper, mapper); + // break; } // Return trueType for a definitely true extends check. We check instantiations of the two // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter // that has no constraint. This ensures that, for example, the type // type Foo = T extends { x: string } ? string : number // doesn't immediately resolve to 'string' instead of being deferred. - if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { + else if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { const trueType = getTypeFromTypeNode(root.node.trueType); const trueMapper = combinedMapper || mapper; if (canTailRecurse(trueType, narrowMapper, trueMapper)) { + saveNext = true; continue; } result = instantiateNarrowType(trueType, narrowMapper, trueMapper); break; } // >> TODO: document why/when we need this - if (isTypeStrictSubtypeOf(checkType, inferredExtendsType)) { + // Same as above + else if (isTypeStrictSubtypeOf(checkType, inferredExtendsType)) { const trueType = getTypeFromTypeNode(root.node.trueType); const trueMapper = combinedMapper || mapper; if (canTailRecurse(trueType, narrowMapper, trueMapper)) { + saveNext = true; continue; } result = instantiateNarrowType(trueType, narrowMapper, trueMapper); break; } } + // >> TODO: use saved conditional // Return a deferred type for a check that is neither definitely true nor definitely false result = createType(TypeFlags.Conditional) as ConditionalType; - result.root = root; - result.checkType = instantiateType(root.checkType, mapper); - result.extendsType = instantiateType(root.extendsType, mapper); + // result.root = root; + // result.checkType = instantiateType(root.checkType, mapper); + // result.extendsType = instantiateType(root.extendsType, mapper); + // result.mapper = mapper; + // result.combinedMapper = combinedMapper; + // result.aliasSymbol = aliasSymbol || root.aliasSymbol; + // result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 + result.root = lastRoot; + result.checkType = instantiateType(lastRoot.checkType, mapper); + result.extendsType = instantiateType(lastRoot.extendsType, mapper); result.mapper = mapper; - result.combinedMapper = combinedMapper; - result.aliasSymbol = aliasSymbol || root.aliasSymbol; - result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 + result.combinedMapper = lastCombinedMapper; + result.aliasSymbol = lastAliasSymbol || lastRoot.aliasSymbol; + result.aliasTypeArguments = lastAliasSymbol ? lastAliasTypeArguments : instantiateTypes(lastRoot.aliasTypeArguments, mapper!); // TODO: GH#18217 break; } return extraTypes ? getIntersectionType(append(extraTypes, result)) : result; + // We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and // (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check // type. Note that recursion is possible only through aliased conditional types, so we only increment the tail @@ -18568,6 +18596,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return false; } + + function saveConditional(combinedMapper: TypeMapper | undefined): void { + lastRoot = root; + lastCombinedMapper = combinedMapper; + lastAliasSymbol = aliasSymbol; + lastAliasTypeArguments = aliasTypeArguments; + saveNext = false; + } } function getTypeFromInferTypeNode(node: InferTypeNode): Type { diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index 182b5dc0ff40a..2ce7eb7c8dd89 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -2,27 +2,37 @@ dependentReturnType1.ts(11,9): error TS2322: Type 'number' is not assignable to dependentReturnType1.ts(26,9): error TS2322: Type 'string' is not assignable to type 'never'. dependentReturnType1.ts(35,9): error TS2322: Type 'string' is not assignable to type 'never'. dependentReturnType1.ts(70,9): error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. -dependentReturnType1.ts(74,22): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. -dependentReturnType1.ts(89,9): error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. -dependentReturnType1.ts(108,5): error TS2322: Type 'string' is not assignable to type 'T extends Dog ? number : string'. -dependentReturnType1.ts(145,9): error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. +dependentReturnType1.ts(74,5): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'Three & (T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four)'. + Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. +dependentReturnType1.ts(80,9): error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four'. +dependentReturnType1.ts(84,22): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. +dependentReturnType1.ts(99,9): error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. +dependentReturnType1.ts(118,5): error TS2322: Type 'string' is not assignable to type 'T extends Dog ? number : string'. +dependentReturnType1.ts(153,13): error TS2322: Type 'string' is not assignable to type 'T extends string ? this : string'. +dependentReturnType1.ts(155,9): error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. -dependentReturnType1.ts(149,9): error TS2322: Type 'this' is not assignable to type 'string & (T extends string ? this : string)'. - Type 'Unnamed' is not assignable to type 'string & (T extends string ? this : string)'. - Type 'Unnamed' is not assignable to type 'string'. - Type 'this' is not assignable to type 'string'. - Type 'Unnamed' is not assignable to type 'string'. -dependentReturnType1.ts(178,24): error TS2322: Type 'string' is not assignable to type 'number'. -dependentReturnType1.ts(178,28): error TS2322: Type 'number' is not assignable to type 'string'. -dependentReturnType1.ts(203,47): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. -dependentReturnType1.ts(205,9): error TS2322: Type '""' is not assignable to type 'T extends 1 ? string : boolean'. -dependentReturnType1.ts(211,62): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. -dependentReturnType1.ts(228,9): error TS2322: Type '1' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. -dependentReturnType1.ts(231,9): error TS2322: Type '2' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. -dependentReturnType1.ts(233,5): error TS2322: Type '0' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. +dependentReturnType1.ts(159,9): error TS2322: Type 'this' is not assignable to type '(T extends string ? this : string) & (T extends string ? this : string)'. + Type 'Unnamed' is not assignable to type '(T extends string ? this : string) & (T extends string ? this : string)'. + Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. + Type 'this' is not assignable to type 'T extends string ? this : string'. + Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. +dependentReturnType1.ts(174,13): error TS2322: Type 'this' is not assignable to type 'string'. + Type 'Unnamed' is not assignable to type 'string'. +dependentReturnType1.ts(177,9): error TS2322: Type 'T & {}' is not assignable to type 'this'. + 'this' could be instantiated with an arbitrary type which could be unrelated to 'T & {}'. +dependentReturnType1.ts(207,24): error TS2322: Type 'string' is not assignable to type 'number'. +dependentReturnType1.ts(207,28): error TS2322: Type 'number' is not assignable to type 'string'. +dependentReturnType1.ts(232,47): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. +dependentReturnType1.ts(234,9): error TS2322: Type '""' is not assignable to type 'T extends 1 ? string : boolean'. +dependentReturnType1.ts(240,62): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. +dependentReturnType1.ts(243,9): error TS2322: Type 'string' is not assignable to type 'T extends { a: string; } ? number : string | number'. +dependentReturnType1.ts(268,9): error TS2322: Type '1' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. +dependentReturnType1.ts(271,9): error TS2322: Type '2' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. +dependentReturnType1.ts(273,5): error TS2322: Type '0' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. +dependentReturnType1.ts(291,9): error TS2322: Type 'string' is not assignable to type 'string[]'. -==== dependentReturnType1.ts (17 errors) ==== +==== dependentReturnType1.ts (24 errors) ==== interface A { 1: number; 2: string; @@ -95,12 +105,27 @@ dependentReturnType1.ts(233,5): error TS2322: Type '0' is not assignable to type g: "g"; } - function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { + function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { // Badly written conditional if (x === 1 || x === 2) { return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok return { a: "a" }; // Error ~~~~~~ !!! error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. + } + // Excess property becomes a problem with the change, + // because we now check assignability to a narrower type... + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error + ~~~~~~ +!!! error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'Three & (T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four)'. +!!! error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. + } + + function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four { // Well written conditional + if (x === 1 || x === 2) { + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok + return { a: "a" }; // Error + ~~~~~~ +!!! error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four'. } // Excess property becomes a problem with the change, // because we now check assignability to a narrower type... @@ -180,6 +205,8 @@ dependentReturnType1.ts(233,5): error TS2322: Type '0' is not assignable to type name(name?: T): T extends string ? this : string { if (typeof name === 'undefined') { return this.root.name; + ~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'T extends string ? this : string'. } return this; ~~~~~~ @@ -190,11 +217,35 @@ dependentReturnType1.ts(233,5): error TS2322: Type '0' is not assignable to type nameWithError(name?: T): T extends string ? this : string { return this; // Error: Investigate error message ~~~~~~ -!!! error TS2322: Type 'this' is not assignable to type 'string & (T extends string ? this : string)'. -!!! error TS2322: Type 'Unnamed' is not assignable to type 'string & (T extends string ? this : string)'. -!!! error TS2322: Type 'Unnamed' is not assignable to type 'string'. -!!! error TS2322: Type 'this' is not assignable to type 'string'. -!!! error TS2322: Type 'Unnamed' is not assignable to type 'string'. +!!! error TS2322: Type 'this' is not assignable to type '(T extends string ? this : string) & (T extends string ? this : string)'. +!!! error TS2322: Type 'Unnamed' is not assignable to type '(T extends string ? this : string) & (T extends string ? this : string)'. +!!! error TS2322: Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. +!!! error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. +!!! error TS2322: Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. + } + + // Good conditional + name2(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { + if (typeof name === 'undefined') { + return this.root.name; // Ok + } + this.root.name = name; + return this; // Ok + } + + // Good conditional, wrong return expressions + name3(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { + if (typeof name === 'undefined') { + return this; // Error + ~~~~~~ +!!! error TS2322: Type 'this' is not assignable to type 'string'. +!!! error TS2322: Type 'Unnamed' is not assignable to type 'string'. + } + this.root.name = name; + return name; // Error + ~~~~~~ +!!! error TS2322: Type 'T & {}' is not assignable to type 'this'. +!!! error TS2322: 'this' could be instantiated with an arbitrary type which could be unrelated to 'T & {}'. } } @@ -214,15 +265,16 @@ dependentReturnType1.ts(233,5): error TS2322: Type '0' is not assignable to type } // Conditional expressions - function conditional(x: T): T extends true ? 1 : 2 { + function conditional(x: T): + T extends true ? 1 : T extends false ? 2 : 1 | 2 { return x ? 1 : 2; // Ok } - function contextualConditional(x: T): T extends "a" ? "a" : number { + function contextualConditional(x: T): T extends "a" ? "a" : T extends "b" ? number : "a" | number { return x === "a" ? x : parseInt(x); // Ok } - function conditionalWithError(x: T): T extends "a" ? number : string { + function conditionalWithError(x: T): T extends "a" ? number : T extends "b" ? string : number | string { return x === "a" ? x : parseInt(x); // Error ~ !!! error TS2322: Type 'string' is not assignable to type 'number'. @@ -270,39 +322,72 @@ dependentReturnType1.ts(233,5): error TS2322: Type '0' is not assignable to type if (q(x)) { x.b; return ""; + ~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'T extends { a: string; } ? number : string | number'. } } let y = { a: "", b: 1 } const r = foo<{ a: string }>(y); // number + function lessBadFoo(x: T): T extends { b: number } ? string : T extends { a: string } ? number : (string | number) { + if (q(x)) { + x.b; + return ""; + } + return 2; + } + + const r2 = lessBadFoo<{ a: string }>(y); // number, bad + type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; + // We don't narrow the return type because the conditionals are not distributive function foo2(x: U, y: V): HelperCond<{ x: U, y: V }, { x: string, y: true }, 1, { x: number, y: false }, 2> { if (typeof x === "string" && y === true) { - return 1; + return 1; // Error ~~~~~~ !!! error TS2322: Type '1' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. } if (typeof x === "number" && y === false) { - return 2; + return 2; // Error ~~~~~~ !!! error TS2322: Type '2' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. } - return 0; + return 0; // Error ~~~~~~ !!! error TS2322: Type '0' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. } - // >> TODO: test non-tail recursive and tail recursive conditionals + // From https://github.com/microsoft/TypeScript/issues/24929#issue-332087943 + declare function isString(s: unknown): s is string; + // capitalize a string or each element of an array of strings + function capitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { + if (isString(input)) { + return input[0].toUpperCase() + input.slice(1); // Ok + } else { + return input.map(elt => capitalize(elt)); // Ok + } + } + + function badCapitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { + if (isString(input)) { + return input[0].toUpperCase() + input.slice(1); // Ok + } else { + return input[0].toUpperCase() + input.slice(1); // Bad + ~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'string[]'. + } + } + + // >> TODO: test non-tail recursive conditionals - // >> TODO: fix this - function voidRet(x: T): T extends {} ? void : number { + function voidRet(x: T): T extends {} ? void : T extends undefined ? number : void | number { if (x) { - return; + return; // Ok } - return 1; + return 1; // Ok } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 977d7e24fd424..7c23780ab65f0 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -143,7 +143,7 @@ interface Four { >g : Symbol(Four.g, Decl(dependentReturnType1.ts, 62, 11)) } -function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { +function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { // Badly written conditional >f10 : Symbol(f10, Decl(dependentReturnType1.ts, 64, 1)) >T : Symbol(T, Decl(dependentReturnType1.ts, 66, 13)) >x : Symbol(x, Decl(dependentReturnType1.ts, 66, 38)) @@ -183,259 +183,362 @@ function f10(x: T): T extends 1 ? One : T extends 2 ? T >g : Symbol(g, Decl(dependentReturnType1.ts, 73, 60)) } +function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four { // Well written conditional +>f101 : Symbol(f101, Decl(dependentReturnType1.ts, 74, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 76, 14)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 76, 39)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 76, 14)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 76, 14)) +>One : Symbol(One, Decl(dependentReturnType1.ts, 36, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 76, 14)) +>Two : Symbol(Two, Decl(dependentReturnType1.ts, 43, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 76, 14)) +>Three : Symbol(Three, Decl(dependentReturnType1.ts, 50, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 76, 14)) +>Four : Symbol(Four, Decl(dependentReturnType1.ts, 57, 1)) +>One : Symbol(One, Decl(dependentReturnType1.ts, 36, 1)) +>Two : Symbol(Two, Decl(dependentReturnType1.ts, 43, 1)) +>Three : Symbol(Three, Decl(dependentReturnType1.ts, 50, 1)) +>Four : Symbol(Four, Decl(dependentReturnType1.ts, 57, 1)) + + if (x === 1 || x === 2) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 76, 39)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 76, 39)) + + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok +>a : Symbol(a, Decl(dependentReturnType1.ts, 78, 16)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 78, 24)) +>c : Symbol(c, Decl(dependentReturnType1.ts, 78, 32)) +>d : Symbol(d, Decl(dependentReturnType1.ts, 78, 40)) +>e : Symbol(e, Decl(dependentReturnType1.ts, 78, 48)) +>f : Symbol(f, Decl(dependentReturnType1.ts, 78, 56)) + + return { a: "a" }; // Error +>a : Symbol(a, Decl(dependentReturnType1.ts, 79, 16)) + } + // Excess property becomes a problem with the change, + // because we now check assignability to a narrower type... + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error +>a : Symbol(a, Decl(dependentReturnType1.ts, 83, 12)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 83, 20)) +>c : Symbol(c, Decl(dependentReturnType1.ts, 83, 28)) +>d : Symbol(d, Decl(dependentReturnType1.ts, 83, 36)) +>e : Symbol(e, Decl(dependentReturnType1.ts, 83, 44)) +>f : Symbol(f, Decl(dependentReturnType1.ts, 83, 52)) +>g : Symbol(g, Decl(dependentReturnType1.ts, 83, 60)) +} + // Asymmetry function conditionalProducingIf( ->conditionalProducingIf : Symbol(conditionalProducingIf, Decl(dependentReturnType1.ts, 74, 1)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 77, 48)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 77, 57)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 77, 67)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) +>conditionalProducingIf : Symbol(conditionalProducingIf, Decl(dependentReturnType1.ts, 84, 1)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 87, 48)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 87, 57)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) arg: Arg, ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 77, 67)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 67)) cond: (arg: LeftIn | RightIn) => arg is LeftIn, ->cond : Symbol(cond, Decl(dependentReturnType1.ts, 78, 13)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 79, 11)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 79, 11)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) +>cond : Symbol(cond, Decl(dependentReturnType1.ts, 88, 13)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 89, 11)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 89, 11)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) produceLeftOut: (arg: LeftIn) => LeftOut, ->produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 79, 51)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 80, 21)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 77, 48)) +>produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 89, 51)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 90, 21)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 87, 48)) produceRightOut: (arg: RightIn) => RightOut): ->produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 80, 45)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 81, 22)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 77, 57)) +>produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 90, 45)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 91, 22)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 87, 57)) Arg extends LeftIn ? LeftOut : RightOut ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 77, 67)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 77, 48)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 77, 57)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 87, 48)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 87, 57)) { type OK = Arg extends LeftIn ? LeftOut : RightOut; ->OK : Symbol(OK, Decl(dependentReturnType1.ts, 83, 1)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 77, 67)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 77, 32)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 77, 48)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 77, 57)) +>OK : Symbol(OK, Decl(dependentReturnType1.ts, 93, 1)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 87, 48)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 87, 57)) if (cond(arg)) { ->cond : Symbol(cond, Decl(dependentReturnType1.ts, 78, 13)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) +>cond : Symbol(cond, Decl(dependentReturnType1.ts, 88, 13)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) return produceLeftOut(arg); // Ok ->produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 79, 51)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) +>produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 89, 51)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) } else { return produceRightOut(arg as RightIn); // Error: Doesn't work because we don't narrow `arg` to `Arg & RightIn` here ->produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 80, 45)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 77, 98)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 77, 39)) +>produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 90, 45)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) } } interface Animal { ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 90, 1)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 100, 1)) name: string; ->name : Symbol(Animal.name, Decl(dependentReturnType1.ts, 92, 18)) +>name : Symbol(Animal.name, Decl(dependentReturnType1.ts, 102, 18)) } interface Dog extends Animal { ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 94, 1)) ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 90, 1)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 104, 1)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 100, 1)) bark: () => string; ->bark : Symbol(Dog.bark, Decl(dependentReturnType1.ts, 96, 30)) +>bark : Symbol(Dog.bark, Decl(dependentReturnType1.ts, 106, 30)) } // This is unsafe declare function isDog(x: Animal): x is Dog; ->isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 98, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 101, 23)) ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 90, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 101, 23)) ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 94, 1)) +>isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 108, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 111, 23)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 100, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 111, 23)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 104, 1)) declare function doggy(x: Dog): number; ->doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 101, 44)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 102, 23)) ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 94, 1)) +>doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 111, 44)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 112, 23)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 104, 1)) function f12(x: T): T extends Dog ? number : string { ->f12 : Symbol(f12, Decl(dependentReturnType1.ts, 102, 39)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 103, 13)) ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 90, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 103, 31)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 103, 13)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 103, 13)) ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 94, 1)) +>f12 : Symbol(f12, Decl(dependentReturnType1.ts, 112, 39)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 113, 13)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 100, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 113, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 113, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 113, 13)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 104, 1)) if (isDog(x)) { // `x` has type `T & Dog` here ->isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 98, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 103, 31)) +>isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 108, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 113, 31)) return doggy(x); // Ok ->doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 101, 44)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 103, 31)) +>doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 111, 44)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 113, 31)) } return ""; // Error: Should not work because we can't express "not a Dog" in the type system } // Cannot narrow `keyof` too eagerly or something like the below breaks function f(entry: EntryId): Entry[EntryId] { ->f : Symbol(f, Decl(dependentReturnType1.ts, 108, 1)) ->Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 111, 11)) ->index : Symbol(index, Decl(dependentReturnType1.ts, 111, 28)) ->EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 111, 63)) ->Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 111, 11)) ->entry : Symbol(entry, Decl(dependentReturnType1.ts, 111, 93)) ->EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 111, 63)) ->Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 111, 11)) ->EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 111, 63)) +>f : Symbol(f, Decl(dependentReturnType1.ts, 118, 1)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 121, 11)) +>index : Symbol(index, Decl(dependentReturnType1.ts, 121, 28)) +>EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 121, 63)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 121, 11)) +>entry : Symbol(entry, Decl(dependentReturnType1.ts, 121, 93)) +>EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 121, 63)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 121, 11)) +>EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 121, 63)) const entries = {} as Entry; ->entries : Symbol(entries, Decl(dependentReturnType1.ts, 112, 9)) ->Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 111, 11)) +>entries : Symbol(entries, Decl(dependentReturnType1.ts, 122, 9)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 121, 11)) return entries[entry]; ->entries : Symbol(entries, Decl(dependentReturnType1.ts, 112, 9)) ->entry : Symbol(entry, Decl(dependentReturnType1.ts, 111, 93)) +>entries : Symbol(entries, Decl(dependentReturnType1.ts, 122, 9)) +>entry : Symbol(entry, Decl(dependentReturnType1.ts, 121, 93)) } // Works the same as before declare function takeA(val: 'A'): void; ->takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 114, 1)) ->val : Symbol(val, Decl(dependentReturnType1.ts, 117, 23)) +>takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 124, 1)) +>val : Symbol(val, Decl(dependentReturnType1.ts, 127, 23)) export function bounceAndTakeIfA(value: AB): AB { ->bounceAndTakeIfA : Symbol(bounceAndTakeIfA, Decl(dependentReturnType1.ts, 117, 39)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 118, 33)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 118, 55)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 118, 33)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 118, 33)) +>bounceAndTakeIfA : Symbol(bounceAndTakeIfA, Decl(dependentReturnType1.ts, 127, 39)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 128, 33)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 128, 55)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 128, 33)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 128, 33)) if (value === 'A') { ->value : Symbol(value, Decl(dependentReturnType1.ts, 118, 55)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 128, 55)) takeA(value); ->takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 114, 1)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 118, 55)) +>takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 124, 1)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 128, 55)) takeAB(value); ->takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 125, 17)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 118, 55)) +>takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 135, 17)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 128, 55)) return value; ->value : Symbol(value, Decl(dependentReturnType1.ts, 118, 55)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 128, 55)) } return value; ->value : Symbol(value, Decl(dependentReturnType1.ts, 118, 55)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 128, 55)) function takeAB(val: AB): void {} ->takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 125, 17)) ->val : Symbol(val, Decl(dependentReturnType1.ts, 126, 20)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 118, 33)) +>takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 135, 17)) +>val : Symbol(val, Decl(dependentReturnType1.ts, 136, 20)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 128, 33)) } // Works the same as before export function bbb(value: AB): "a" { ->bbb : Symbol(bbb, Decl(dependentReturnType1.ts, 127, 1)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 130, 20)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 130, 42)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 130, 20)) +>bbb : Symbol(bbb, Decl(dependentReturnType1.ts, 137, 1)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 140, 20)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 140, 42)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 140, 20)) if (value === "a") { ->value : Symbol(value, Decl(dependentReturnType1.ts, 130, 42)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 140, 42)) return value; ->value : Symbol(value, Decl(dependentReturnType1.ts, 130, 42)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 140, 42)) } return "a"; } class Unnamed { ->Unnamed : Symbol(Unnamed, Decl(dependentReturnType1.ts, 135, 1)) +>Unnamed : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) root!: { name: string }; ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 137, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 138, 12)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) // Error because parameter is optional name(name?: T): T extends string ? this : string { ->name : Symbol(Unnamed.name, Decl(dependentReturnType1.ts, 138, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 140, 9)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 140, 27)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 140, 9)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 140, 9)) +>name : Symbol(Unnamed.name, Decl(dependentReturnType1.ts, 148, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 150, 9)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 150, 27)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 150, 9)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 150, 9)) if (typeof name === 'undefined') { ->name : Symbol(name, Decl(dependentReturnType1.ts, 140, 27)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 150, 27)) return this.root.name; ->this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 138, 12)) ->this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 137, 15)) ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 135, 1)) ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 137, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 138, 12)) +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) } return this; ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 135, 1)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) } // Error because parameter is optional? nameWithError(name?: T): T extends string ? this : string { ->nameWithError : Symbol(Unnamed.nameWithError, Decl(dependentReturnType1.ts, 145, 5)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 147, 18)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 147, 36)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 147, 18)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 147, 18)) +>nameWithError : Symbol(Unnamed.nameWithError, Decl(dependentReturnType1.ts, 155, 5)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 157, 18)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 157, 36)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 157, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 157, 18)) return this; // Error: Investigate error message ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 135, 1)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) + } + + // Good conditional + name2(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { +>name2 : Symbol(Unnamed.name2, Decl(dependentReturnType1.ts, 159, 5)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 162, 10)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 162, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 162, 10)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 162, 10)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 162, 10)) + + if (typeof name === 'undefined') { +>name : Symbol(name, Decl(dependentReturnType1.ts, 162, 40)) + + return this.root.name; // Ok +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) + } + this.root.name = name; +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 162, 40)) + + return this; // Ok +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) + } + + // Good conditional, wrong return expressions + name3(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { +>name3 : Symbol(Unnamed.name3, Decl(dependentReturnType1.ts, 168, 5)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 171, 10)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 171, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 171, 10)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 171, 10)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 171, 10)) + + if (typeof name === 'undefined') { +>name : Symbol(name, Decl(dependentReturnType1.ts, 171, 40)) + + return this; // Error +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) + } + this.root.name = name; +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 171, 40)) + + return name; // Error +>name : Symbol(name, Decl(dependentReturnType1.ts, 171, 40)) } } interface Aa { ->Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 150, 1)) +>Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 178, 1)) 1: number; ->1 : Symbol(Aa[1], Decl(dependentReturnType1.ts, 152, 14)) +>1 : Symbol(Aa[1], Decl(dependentReturnType1.ts, 180, 14)) 2: string; ->2 : Symbol(Aa[2], Decl(dependentReturnType1.ts, 153, 14)) +>2 : Symbol(Aa[2], Decl(dependentReturnType1.ts, 181, 14)) 3: string; ->3 : Symbol(Aa[3], Decl(dependentReturnType1.ts, 154, 14)) +>3 : Symbol(Aa[3], Decl(dependentReturnType1.ts, 182, 14)) } function trivialConditional(x: T): Aa[T] { ->trivialConditional : Symbol(trivialConditional, Decl(dependentReturnType1.ts, 156, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 158, 28)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 158, 49)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 158, 28)) ->Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 150, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 158, 28)) +>trivialConditional : Symbol(trivialConditional, Decl(dependentReturnType1.ts, 184, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 186, 28)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 186, 49)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 186, 28)) +>Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 178, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 186, 28)) if (x !== 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 158, 49)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 186, 49)) return x === 2 ? "" : `${x}`; ->x : Symbol(x, Decl(dependentReturnType1.ts, 158, 49)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 158, 49)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 186, 49)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 186, 49)) } else { return 0; @@ -443,86 +546,91 @@ function trivialConditional(x: T): Aa[T] { } // Conditional expressions -function conditional(x: T): T extends true ? 1 : 2 { ->conditional : Symbol(conditional, Decl(dependentReturnType1.ts, 165, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 168, 21)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 168, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 168, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 168, 21)) +function conditional(x: T): +>conditional : Symbol(conditional, Decl(dependentReturnType1.ts, 193, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 196, 21)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 196, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 196, 21)) + + T extends true ? 1 : T extends false ? 2 : 1 | 2 { +>T : Symbol(T, Decl(dependentReturnType1.ts, 196, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 196, 21)) return x ? 1 : 2; // Ok ->x : Symbol(x, Decl(dependentReturnType1.ts, 168, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 196, 40)) } -function contextualConditional(x: T): T extends "a" ? "a" : number { ->contextualConditional : Symbol(contextualConditional, Decl(dependentReturnType1.ts, 170, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 172, 31)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 172, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 172, 31)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 172, 31)) +function contextualConditional(x: T): T extends "a" ? "a" : T extends "b" ? number : "a" | number { +>contextualConditional : Symbol(contextualConditional, Decl(dependentReturnType1.ts, 199, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 201, 31)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 201, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 201, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 201, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 201, 31)) return x === "a" ? x : parseInt(x); // Ok ->x : Symbol(x, Decl(dependentReturnType1.ts, 172, 52)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 172, 52)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 201, 52)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 201, 52)) >parseInt : Symbol(parseInt, Decl(lib.es5.d.ts, --, --)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 172, 52)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 201, 52)) } -function conditionalWithError(x: T): T extends "a" ? number : string { ->conditionalWithError : Symbol(conditionalWithError, Decl(dependentReturnType1.ts, 174, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 176, 30)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 176, 51)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 176, 30)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 176, 30)) +function conditionalWithError(x: T): T extends "a" ? number : T extends "b" ? string : number | string { +>conditionalWithError : Symbol(conditionalWithError, Decl(dependentReturnType1.ts, 203, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 205, 30)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 205, 51)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 205, 30)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 205, 30)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 205, 30)) return x === "a" ? x : parseInt(x); // Error ->x : Symbol(x, Decl(dependentReturnType1.ts, 176, 51)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 176, 51)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 205, 51)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 205, 51)) >parseInt : Symbol(parseInt, Decl(lib.es5.d.ts, --, --)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 176, 51)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 205, 51)) } // Multiple reductions interface BB { ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 178, 1)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 207, 1)) "a": number; ->"a" : Symbol(BB["a"], Decl(dependentReturnType1.ts, 181, 14)) +>"a" : Symbol(BB["a"], Decl(dependentReturnType1.ts, 210, 14)) [y: number]: string; ->y : Symbol(y, Decl(dependentReturnType1.ts, 183, 5)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 212, 5)) } interface AA { ->AA : Symbol(AA, Decl(dependentReturnType1.ts, 184, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 186, 13)) ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 178, 1)) +>AA : Symbol(AA, Decl(dependentReturnType1.ts, 213, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 215, 13)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 207, 1)) "c": BB[T]; ->"c" : Symbol(AA["c"], Decl(dependentReturnType1.ts, 186, 34)) ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 178, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 186, 13)) +>"c" : Symbol(AA["c"], Decl(dependentReturnType1.ts, 215, 34)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 207, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 215, 13)) "d": boolean, ->"d" : Symbol(AA["d"], Decl(dependentReturnType1.ts, 187, 15)) +>"d" : Symbol(AA["d"], Decl(dependentReturnType1.ts, 216, 15)) } function reduction(x: T, y: U): AA[U] { ->reduction : Symbol(reduction, Decl(dependentReturnType1.ts, 189, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 191, 19)) ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 178, 1)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 191, 38)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 191, 60)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 191, 19)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 191, 65)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 191, 38)) ->AA : Symbol(AA, Decl(dependentReturnType1.ts, 184, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 191, 19)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 191, 38)) +>reduction : Symbol(reduction, Decl(dependentReturnType1.ts, 218, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 220, 19)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 207, 1)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 220, 38)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 220, 60)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 220, 19)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 220, 65)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 220, 38)) +>AA : Symbol(AA, Decl(dependentReturnType1.ts, 213, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 220, 19)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 220, 38)) if (y === "c" && x === "a") { ->y : Symbol(y, Decl(dependentReturnType1.ts, 191, 65)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 191, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 220, 65)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 220, 60)) // AA[U='c'] -> BB[T] // BB[T='a'] -> number @@ -535,15 +643,15 @@ function reduction(x: T, y: U): AA[U // Substitution types are not narrowed? function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { ->subsCond : Symbol(subsCond, Decl(dependentReturnType1.ts, 199, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 202, 18)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 202, 39)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 202, 18)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 202, 18)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 202, 18)) +>subsCond : Symbol(subsCond, Decl(dependentReturnType1.ts, 228, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 231, 18)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 231, 39)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 231, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 231, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 231, 18)) if (x === 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 202, 39)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 231, 39)) return ""; } @@ -551,115 +659,217 @@ function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? s // Unsafe: supertype problem declare function q(x: object): x is { b: number }; ->q : Symbol(q, Decl(dependentReturnType1.ts, 206, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 209, 19)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 209, 19)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 209, 37)) +>q : Symbol(q, Decl(dependentReturnType1.ts, 235, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 238, 19)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 238, 19)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 238, 37)) function foo(x: T): T extends { a: string } ? number : (string | number) { ->foo : Symbol(foo, Decl(dependentReturnType1.ts, 209, 50)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 210, 13)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 210, 24)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 210, 40)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 210, 54)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 210, 13)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 210, 13)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 210, 72)) +>foo : Symbol(foo, Decl(dependentReturnType1.ts, 238, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 239, 13)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 239, 24)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 239, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 239, 54)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 239, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 239, 13)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 239, 72)) if (q(x)) { ->q : Symbol(q, Decl(dependentReturnType1.ts, 206, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 210, 54)) +>q : Symbol(q, Decl(dependentReturnType1.ts, 235, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 239, 54)) x.b; ->x.b : Symbol(b, Decl(dependentReturnType1.ts, 210, 40)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 210, 54)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 210, 40)) +>x.b : Symbol(b, Decl(dependentReturnType1.ts, 239, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 239, 54)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 239, 40)) return ""; } } let y = { a: "", b: 1 } ->y : Symbol(y, Decl(dependentReturnType1.ts, 217, 3)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 217, 9)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 217, 16)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 246, 3)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 246, 9)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 246, 16)) const r = foo<{ a: string }>(y); // number ->r : Symbol(r, Decl(dependentReturnType1.ts, 218, 5)) ->foo : Symbol(foo, Decl(dependentReturnType1.ts, 209, 50)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 218, 15)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 217, 3)) +>r : Symbol(r, Decl(dependentReturnType1.ts, 247, 5)) +>foo : Symbol(foo, Decl(dependentReturnType1.ts, 238, 50)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 247, 15)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 246, 3)) + +function lessBadFoo(x: T): T extends { b: number } ? string : T extends { a: string } ? number : (string | number) { +>lessBadFoo : Symbol(lessBadFoo, Decl(dependentReturnType1.ts, 247, 32)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 249, 20)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 249, 31)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 249, 47)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 249, 61)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 249, 20)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 249, 20)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 249, 79)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 249, 20)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 249, 114)) -type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; ->HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 218, 32)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 220, 16)) ->A : Symbol(A, Decl(dependentReturnType1.ts, 220, 18)) ->R1 : Symbol(R1, Decl(dependentReturnType1.ts, 220, 21)) ->B : Symbol(B, Decl(dependentReturnType1.ts, 220, 25)) ->R2 : Symbol(R2, Decl(dependentReturnType1.ts, 220, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 220, 16)) ->A : Symbol(A, Decl(dependentReturnType1.ts, 220, 18)) ->R1 : Symbol(R1, Decl(dependentReturnType1.ts, 220, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 220, 16)) ->B : Symbol(B, Decl(dependentReturnType1.ts, 220, 25)) ->R2 : Symbol(R2, Decl(dependentReturnType1.ts, 220, 28)) ->R1 : Symbol(R1, Decl(dependentReturnType1.ts, 220, 21)) ->R2 : Symbol(R2, Decl(dependentReturnType1.ts, 220, 28)) + if (q(x)) { +>q : Symbol(q, Decl(dependentReturnType1.ts, 235, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 249, 61)) + + x.b; +>x.b : Symbol(b, Decl(dependentReturnType1.ts, 249, 47)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 249, 61)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 249, 47)) + + return ""; + } + return 2; +} +const r2 = lessBadFoo<{ a: string }>(y); // number, bad +>r2 : Symbol(r2, Decl(dependentReturnType1.ts, 257, 5)) +>lessBadFoo : Symbol(lessBadFoo, Decl(dependentReturnType1.ts, 247, 32)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 257, 23)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 246, 3)) + +type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 257, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 259, 16)) +>A : Symbol(A, Decl(dependentReturnType1.ts, 259, 18)) +>R1 : Symbol(R1, Decl(dependentReturnType1.ts, 259, 21)) +>B : Symbol(B, Decl(dependentReturnType1.ts, 259, 25)) +>R2 : Symbol(R2, Decl(dependentReturnType1.ts, 259, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 259, 16)) +>A : Symbol(A, Decl(dependentReturnType1.ts, 259, 18)) +>R1 : Symbol(R1, Decl(dependentReturnType1.ts, 259, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 259, 16)) +>B : Symbol(B, Decl(dependentReturnType1.ts, 259, 25)) +>R2 : Symbol(R2, Decl(dependentReturnType1.ts, 259, 28)) +>R1 : Symbol(R1, Decl(dependentReturnType1.ts, 259, 21)) +>R2 : Symbol(R2, Decl(dependentReturnType1.ts, 259, 28)) + +// We don't narrow the return type because the conditionals are not distributive function foo2(x: U, y: V): ->foo2 : Symbol(foo2, Decl(dependentReturnType1.ts, 220, 81)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 222, 14)) ->V : Symbol(V, Decl(dependentReturnType1.ts, 222, 40)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 222, 60)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 222, 14)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 222, 65)) ->V : Symbol(V, Decl(dependentReturnType1.ts, 222, 40)) +>foo2 : Symbol(foo2, Decl(dependentReturnType1.ts, 259, 81)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 262, 14)) +>V : Symbol(V, Decl(dependentReturnType1.ts, 262, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 262, 60)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 262, 14)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 262, 65)) +>V : Symbol(V, Decl(dependentReturnType1.ts, 262, 40)) HelperCond<{ x: U, y: V }, ->HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 218, 32)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 223, 16)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 222, 14)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 223, 22)) ->V : Symbol(V, Decl(dependentReturnType1.ts, 222, 40)) +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 257, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 263, 16)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 262, 14)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 263, 22)) +>V : Symbol(V, Decl(dependentReturnType1.ts, 262, 40)) { x: string, y: true }, 1, ->x : Symbol(x, Decl(dependentReturnType1.ts, 224, 9)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 224, 20)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 264, 9)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 264, 20)) { x: number, y: false }, 2> { ->x : Symbol(x, Decl(dependentReturnType1.ts, 225, 9)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 225, 20)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 265, 9)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 265, 20)) if (typeof x === "string" && y === true) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 222, 60)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 222, 65)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 262, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 262, 65)) - return 1; + return 1; // Error } if (typeof x === "number" && y === false) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 222, 60)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 222, 65)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 262, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 262, 65)) + + return 2; // Error + } + return 0; // Error +} + +// From https://github.com/microsoft/TypeScript/issues/24929#issue-332087943 +declare function isString(s: unknown): s is string; +>isString : Symbol(isString, Decl(dependentReturnType1.ts, 273, 1)) +>s : Symbol(s, Decl(dependentReturnType1.ts, 276, 26)) +>s : Symbol(s, Decl(dependentReturnType1.ts, 276, 26)) + +// capitalize a string or each element of an array of strings +function capitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { +>capitalize : Symbol(capitalize, Decl(dependentReturnType1.ts, 276, 51)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 278, 20)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 278, 49)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 278, 20)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 278, 20)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 278, 20)) + + if (isString(input)) { +>isString : Symbol(isString, Decl(dependentReturnType1.ts, 273, 1)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 278, 49)) + + return input[0].toUpperCase() + input.slice(1); // Ok +>input[0].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 278, 49)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>input.slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 278, 49)) +>slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) - return 2; + } else { + return input.map(elt => capitalize(elt)); // Ok +>input.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 278, 49)) +>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) +>elt : Symbol(elt, Decl(dependentReturnType1.ts, 282, 25)) +>capitalize : Symbol(capitalize, Decl(dependentReturnType1.ts, 276, 51)) +>elt : Symbol(elt, Decl(dependentReturnType1.ts, 282, 25)) + } +} + +function badCapitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { +>badCapitalize : Symbol(badCapitalize, Decl(dependentReturnType1.ts, 284, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 286, 23)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 286, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 286, 23)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 286, 23)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 286, 23)) + + if (isString(input)) { +>isString : Symbol(isString, Decl(dependentReturnType1.ts, 273, 1)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 286, 52)) + + return input[0].toUpperCase() + input.slice(1); // Ok +>input[0].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 286, 52)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>input.slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 286, 52)) +>slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) + + } else { + return input[0].toUpperCase() + input.slice(1); // Bad +>input[0].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 286, 52)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>input.slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 286, 52)) +>slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --)) } - return 0; } -// >> TODO: test non-tail recursive and tail recursive conditionals +// >> TODO: test non-tail recursive conditionals -// >> TODO: fix this -function voidRet(x: T): T extends {} ? void : number { ->voidRet : Symbol(voidRet, Decl(dependentReturnType1.ts, 233, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 238, 17)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 238, 28)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 238, 54)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 238, 17)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 238, 17)) +function voidRet(x: T): T extends {} ? void : T extends undefined ? number : void | number { +>voidRet : Symbol(voidRet, Decl(dependentReturnType1.ts, 292, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 296, 17)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 296, 28)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 296, 54)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 296, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 296, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 296, 17)) if (x) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 238, 54)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 296, 54)) - return; + return; // Ok } - return 1; + return 1; // Ok } diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 1c4759aeeb2b2..16717cd877351 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -130,7 +130,7 @@ interface Four { >g : "g" } -function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { +function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { // Badly written conditional >f10 : (x: T) => T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four >x : T @@ -161,6 +161,59 @@ function f10(x: T): T extends 1 ? One : T extends 2 ? T return { a: "a" }; // Error >{ a: "a" } : { a: "a"; } >a : "a" +>"a" : "a" + } + // Excess property becomes a problem with the change, + // because we now check assignability to a narrower type... + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error +>{ a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" } : { a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; } +>a : "a" +>"a" : "a" +>b : "b" +>"b" : "b" +>c : "c" +>"c" : "c" +>d : "d" +>"d" : "d" +>e : "e" +>"e" : "e" +>f : "f" +>"f" : "f" +>g : "g" +>"g" : "g" +} + +function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four { // Well written conditional +>f101 : (x: T) => T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four +>x : T + + if (x === 1 || x === 2) { +>x === 1 || x === 2 : boolean +>x === 1 : boolean +>x : T +>1 : 1 +>x === 2 : boolean +>x : T +>2 : 2 + + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok +>{ a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" } : { a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; } +>a : "a" +>"a" : "a" +>b : "b" +>"b" : "b" +>c : "c" +>"c" : "c" +>d : "d" +>"d" : "d" +>e : "e" +>"e" : "e" +>f : "f" +>"f" : "f" + + return { a: "a" }; // Error +>{ a: "a" } : { a: "a"; } +>a : "a" >"a" : "a" } // Excess property becomes a problem with the change, @@ -369,6 +422,64 @@ class Unnamed { return this; // Error: Investigate error message >this : this } + + // Good conditional + name2(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { +>name2 : (name?: T) => T extends string ? this : T extends undefined ? string : this | undefined +>name : T | undefined + + if (typeof name === 'undefined') { +>typeof name === 'undefined' : boolean +>typeof name : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>name : T | undefined +>'undefined' : "undefined" + + return this.root.name; // Ok +>this.root.name : string +>this.root : { name: string; } +>this : this +>root : { name: string; } +>name : string + } + this.root.name = name; +>this.root.name = name : string +>this.root.name : string +>this.root : { name: string; } +>this : this +>root : { name: string; } +>name : string +>name : string + + return this; // Ok +>this : this + } + + // Good conditional, wrong return expressions + name3(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { +>name3 : (name?: T) => T extends string ? this : T extends undefined ? string : this | undefined +>name : T | undefined + + if (typeof name === 'undefined') { +>typeof name === 'undefined' : boolean +>typeof name : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>name : T | undefined +>'undefined' : "undefined" + + return this; // Error +>this : this + } + this.root.name = name; +>this.root.name = name : string +>this.root.name : string +>this.root : { name: string; } +>this : this +>root : { name: string; } +>name : string +>name : string + + return name; // Error +>name : T & {} + } } interface Aa { @@ -407,10 +518,13 @@ function trivialConditional(x: T): Aa[T] { } // Conditional expressions -function conditional(x: T): T extends true ? 1 : 2 { ->conditional : (x: T) => T extends true ? 1 : 2 +function conditional(x: T): +>conditional : (x: T) => T extends true ? 1 : T extends false ? 2 : 1 | 2 >x : T + + T extends true ? 1 : T extends false ? 2 : 1 | 2 { >true : true +>false : false return x ? 1 : 2; // Ok >x ? 1 : 2 : 1 | 2 @@ -419,8 +533,8 @@ function conditional(x: T): T extends true ? 1 : 2 { >2 : 2 } -function contextualConditional(x: T): T extends "a" ? "a" : number { ->contextualConditional : (x: T) => T extends "a" ? "a" : number +function contextualConditional(x: T): T extends "a" ? "a" : T extends "b" ? number : "a" | number { +>contextualConditional : (x: T) => T extends "a" ? "a" : T extends "b" ? number : "a" | number >x : T return x === "a" ? x : parseInt(x); // Ok @@ -434,8 +548,8 @@ function contextualConditional(x: T): T extends "a" ? "a" : >x : "b" } -function conditionalWithError(x: T): T extends "a" ? number : string { ->conditionalWithError : (x: T) => T extends "a" ? number : string +function conditionalWithError(x: T): T extends "a" ? number : T extends "b" ? string : number | string { +>conditionalWithError : (x: T) => T extends "a" ? number : T extends "b" ? string : number | string >x : T return x === "a" ? x : parseInt(x); // Error @@ -549,9 +663,42 @@ const r = foo<{ a: string }>(y); // number >a : string >y : { a: string; b: number; } +function lessBadFoo(x: T): T extends { b: number } ? string : T extends { a: string } ? number : (string | number) { +>lessBadFoo : (x: T) => T extends { b: number;} ? string : T extends { a: string;} ? number : (string | number) +>a : string +>b : number +>x : T +>b : number +>a : string + + if (q(x)) { +>q(x) : boolean +>q : (x: object) => x is { b: number; } +>x : { a: string; } | { b: number; } + + x.b; +>x.b : number +>x : { b: number; } +>b : number + + return ""; +>"" : "" + } + return 2; +>2 : 2 +} + +const r2 = lessBadFoo<{ a: string }>(y); // number, bad +>r2 : number +>lessBadFoo<{ a: string }>(y) : number +>lessBadFoo : (x: T) => T extends { b: number; } ? string : T extends { a: string; } ? number : string | number +>a : string +>y : { a: string; b: number; } + type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; >HelperCond : HelperCond +// We don't narrow the return type because the conditionals are not distributive function foo2(x: U, y: V): >foo2 : (x: U, y: V) => HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2> >x : U @@ -581,7 +728,7 @@ function foo2(x: U, y: V): >y : V >true : true - return 1; + return 1; // Error >1 : 1 } if (typeof x === "number" && y === false) { @@ -594,26 +741,108 @@ function foo2(x: U, y: V): >y : V >false : false - return 2; + return 2; // Error >2 : 2 } - return 0; + return 0; // Error >0 : 0 } -// >> TODO: test non-tail recursive and tail recursive conditionals +// From https://github.com/microsoft/TypeScript/issues/24929#issue-332087943 +declare function isString(s: unknown): s is string; +>isString : (s: unknown) => s is string +>s : unknown + +// capitalize a string or each element of an array of strings +function capitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { +>capitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : string[] | string +>input : T + + if (isString(input)) { +>isString(input) : boolean +>isString : (s: unknown) => s is string +>input : string | string[] + + return input[0].toUpperCase() + input.slice(1); // Ok +>input[0].toUpperCase() + input.slice(1) : string +>input[0].toUpperCase() : string +>input[0].toUpperCase : () => string +>input[0] : string +>input : string +>0 : 0 +>toUpperCase : () => string +>input.slice(1) : string +>input.slice : (start?: number | undefined, end?: number | undefined) => string +>input : string +>slice : (start?: number | undefined, end?: number | undefined) => string +>1 : 1 + + } else { + return input.map(elt => capitalize(elt)); // Ok +>input.map(elt => capitalize(elt)) : string[] +>input.map : (callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[] +>input : string[] +>map : (callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[] +>elt => capitalize(elt) : (elt: string) => string +>elt : string +>capitalize(elt) : string +>capitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : string | string[] +>elt : string + } +} + +function badCapitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { +>badCapitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : string[] | string +>input : T + + if (isString(input)) { +>isString(input) : boolean +>isString : (s: unknown) => s is string +>input : string | string[] + + return input[0].toUpperCase() + input.slice(1); // Ok +>input[0].toUpperCase() + input.slice(1) : string +>input[0].toUpperCase() : string +>input[0].toUpperCase : () => string +>input[0] : string +>input : string +>0 : 0 +>toUpperCase : () => string +>input.slice(1) : string +>input.slice : (start?: number | undefined, end?: number | undefined) => string +>input : string +>slice : (start?: number | undefined, end?: number | undefined) => string +>1 : 1 + + } else { + return input[0].toUpperCase() + input.slice(1); // Bad +>input[0].toUpperCase() + input.slice(1) : string +>input[0].toUpperCase() : string +>input[0].toUpperCase : () => string +>input[0] : string +>input : string[] +>0 : 0 +>toUpperCase : () => string +>input.slice(1) : string[] +>input.slice : (start?: number | undefined, end?: number | undefined) => string[] +>input : string[] +>slice : (start?: number | undefined, end?: number | undefined) => string[] +>1 : 1 + } +} + +// >> TODO: test non-tail recursive conditionals -// >> TODO: fix this -function voidRet(x: T): T extends {} ? void : number { ->voidRet : (x: T) => T extends {} ? void : number +function voidRet(x: T): T extends {} ? void : T extends undefined ? number : void | number { +>voidRet : (x: T) => T extends {} ? void : T extends undefined ? number : void | number >a : string >x : T if (x) { >x : T - return; + return; // Ok } - return 1; + return 1; // Ok >1 : 1 } diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index bc0699af05dc0..4bea67716a567 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -67,7 +67,17 @@ interface Four { g: "g"; } -function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { +function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { // Badly written conditional + if (x === 1 || x === 2) { + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok + return { a: "a" }; // Error + } + // Excess property becomes a problem with the change, + // because we now check assignability to a narrower type... + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error +} + +function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four { // Well written conditional if (x === 1 || x === 2) { return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok return { a: "a" }; // Error @@ -151,6 +161,24 @@ class Unnamed { nameWithError(name?: T): T extends string ? this : string { return this; // Error: Investigate error message } + + // Good conditional + name2(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { + if (typeof name === 'undefined') { + return this.root.name; // Ok + } + this.root.name = name; + return this; // Ok + } + + // Good conditional, wrong return expressions + name3(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { + if (typeof name === 'undefined') { + return this; // Error + } + this.root.name = name; + return name; // Error + } } interface Aa { @@ -169,15 +197,16 @@ function trivialConditional(x: T): Aa[T] { } // Conditional expressions -function conditional(x: T): T extends true ? 1 : 2 { +function conditional(x: T): + T extends true ? 1 : T extends false ? 2 : 1 | 2 { return x ? 1 : 2; // Ok } -function contextualConditional(x: T): T extends "a" ? "a" : number { +function contextualConditional(x: T): T extends "a" ? "a" : T extends "b" ? number : "a" | number { return x === "a" ? x : parseInt(x); // Ok } -function conditionalWithError(x: T): T extends "a" ? number : string { +function conditionalWithError(x: T): T extends "a" ? number : T extends "b" ? string : number | string { return x === "a" ? x : parseInt(x); // Error } @@ -221,27 +250,56 @@ function foo(x: T): T extends { a: stri let y = { a: "", b: 1 } const r = foo<{ a: string }>(y); // number +function lessBadFoo(x: T): T extends { b: number } ? string : T extends { a: string } ? number : (string | number) { + if (q(x)) { + x.b; + return ""; + } + return 2; +} + +const r2 = lessBadFoo<{ a: string }>(y); // number, bad + type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; +// We don't narrow the return type because the conditionals are not distributive function foo2(x: U, y: V): HelperCond<{ x: U, y: V }, { x: string, y: true }, 1, { x: number, y: false }, 2> { if (typeof x === "string" && y === true) { - return 1; + return 1; // Error } if (typeof x === "number" && y === false) { - return 2; + return 2; // Error + } + return 0; // Error +} + +// From https://github.com/microsoft/TypeScript/issues/24929#issue-332087943 +declare function isString(s: unknown): s is string; +// capitalize a string or each element of an array of strings +function capitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { + if (isString(input)) { + return input[0].toUpperCase() + input.slice(1); // Ok + } else { + return input.map(elt => capitalize(elt)); // Ok + } +} + +function badCapitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { + if (isString(input)) { + return input[0].toUpperCase() + input.slice(1); // Ok + } else { + return input[0].toUpperCase() + input.slice(1); // Bad } - return 0; } -// >> TODO: test non-tail recursive and tail recursive conditionals +// >> TODO: test non-tail recursive conditionals -// >> TODO: fix this -function voidRet(x: T): T extends {} ? void : number { +function voidRet(x: T): T extends {} ? void : T extends undefined ? number : void | number { if (x) { - return; + return; // Ok } - return 1; + return 1; // Ok } \ No newline at end of file From e60a625288302f2aceca6d53effe68089a4064b6 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 1 Dec 2023 17:04:16 -0800 Subject: [PATCH 14/90] small fix + new tests --- src/compiler/checker.ts | 124 +--- .../reference/dependentReturnType1.errors.txt | 30 +- .../reference/dependentReturnType1.symbols | 59 ++ .../reference/dependentReturnType1.types | 59 ++ .../reference/dependentReturnType3.errors.txt | 224 ++++++ .../reference/dependentReturnType3.symbols | 684 ++++++++++++++++++ .../reference/dependentReturnType3.types | 651 +++++++++++++++++ tests/cases/compiler/dependentReturnType1.ts | 25 + tests/cases/compiler/dependentReturnType3.ts | 215 ++++++ 9 files changed, 1960 insertions(+), 111 deletions(-) create mode 100644 tests/baselines/reference/dependentReturnType3.errors.txt create mode 100644 tests/baselines/reference/dependentReturnType3.symbols create mode 100644 tests/baselines/reference/dependentReturnType3.types create mode 100644 tests/cases/compiler/dependentReturnType3.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bf4e7af9e358c..de2b30d7a1632 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18393,25 +18393,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getNarrowConditionalType(root: ConditionalRoot, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { let result; - let extraTypes: Type[] | undefined; - let tailCount = 0; - - let lastRoot = root; - let lastCombinedMapper: TypeMapper | undefined = undefined; - let lastAliasSymbol = aliasSymbol; - let lastAliasTypeArguments = aliasTypeArguments; + let originalRoot = root; - let saveNext = false; // We loop here for an immediately nested conditional type in the false position, effectively treating // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of // another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive // cases we increment the tail recursion counter and stop after 1000 iterations. while (true) { - if (tailCount === 1000) { - error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); - return errorType; - } // We instantiate a distributive checkType with the narrow mapper const checkTypeVariable = getActualTypeVariable(root.checkType); const narrowableCheckTypeVariable = getNarrowableCheckTypeVariable(root, mapper); @@ -18476,10 +18465,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } // Instantiate the extends type including inferences for 'infer T' type parameters const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; - - if (saveNext) { - saveConditional(combinedMapper); - } // We attempt to resolve the conditional type only when the check and extends types are non-generic // if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { if (!checkTypeDeferred) { @@ -18488,32 +18473,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // possible (the wildcard type is assignable to and from all types). If those are not related, // then no instantiations will be and we can just return the false branch type. if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { - // Return union of trueType and falseType for 'any' since it matches anything - if (checkType.flags & TypeFlags.Any) { - // >> TODO: took true here? I don't think this can ever happen -- we wouldn't narrow the check type to `any` because we don't narrow anything to `any` - (extraTypes || (extraTypes = [])).push( - instantiateNarrowType(getTypeFromTypeNode(root.node.trueType), narrowMapper, combinedMapper || mapper)); - } - // If falseType is an immediately nested conditional type that isn't distributive or has an + // >> TODO: I don't think this can ever happen -- we wouldn't narrow the check type to `any` because we don't narrow anything to `any` + Debug.assert(!(checkType.flags & TypeFlags.Any)); + // If falseType is an immediately nested conditional type that has an // identical checkType, switch to that type and loop. const falseType = getTypeFromTypeNode(root.node.falseType); if (falseType.flags & TypeFlags.Conditional) { const newRoot = (falseType as ConditionalType).root; - // >> TODO: can we make this recurse even when the original check types are different, - // as long as the check types instantiated with `mapper` are the same? - // What could be wrong is `aliasSymbol` and `aliasTypeArguments`? - // >> TODO: reset those as we do inside `canTailRecurse` - if (newRoot.node.parent === root.node && (!newRoot.isDistributive || newRoot.checkType === root.checkType)) { + if (newRoot.node.parent === root.node && getNarrowableCheckTypeVariable(newRoot, mapper) === narrowableCheckTypeVariable) { root = newRoot; continue; } - if (canTailRecurse(falseType, narrowMapper, mapper)) { - continue; - } } - // >> TODO: basically disallow this, and go to fall-through case - // result = instantiateNarrowType(falseType, narrowMapper, mapper); - // break; } // Return trueType for a definitely true extends check. We check instantiations of the two // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter @@ -18523,10 +18494,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { const trueType = getTypeFromTypeNode(root.node.trueType); const trueMapper = combinedMapper || mapper; - if (canTailRecurse(trueType, narrowMapper, trueMapper)) { - saveNext = true; - continue; - } result = instantiateNarrowType(trueType, narrowMapper, trueMapper); break; } @@ -18535,75 +18502,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if (isTypeStrictSubtypeOf(checkType, inferredExtendsType)) { const trueType = getTypeFromTypeNode(root.node.trueType); const trueMapper = combinedMapper || mapper; - if (canTailRecurse(trueType, narrowMapper, trueMapper)) { - saveNext = true; - continue; - } result = instantiateNarrowType(trueType, narrowMapper, trueMapper); break; } } - // >> TODO: use saved conditional // Return a deferred type for a check that is neither definitely true nor definitely false result = createType(TypeFlags.Conditional) as ConditionalType; - // result.root = root; - // result.checkType = instantiateType(root.checkType, mapper); - // result.extendsType = instantiateType(root.extendsType, mapper); - // result.mapper = mapper; - // result.combinedMapper = combinedMapper; - // result.aliasSymbol = aliasSymbol || root.aliasSymbol; - // result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 - result.root = lastRoot; - result.checkType = instantiateType(lastRoot.checkType, mapper); - result.extendsType = instantiateType(lastRoot.extendsType, mapper); + result.root = originalRoot; + result.checkType = instantiateType(originalRoot.checkType, mapper); + result.extendsType = instantiateType(originalRoot.extendsType, mapper); result.mapper = mapper; - result.combinedMapper = lastCombinedMapper; - result.aliasSymbol = lastAliasSymbol || lastRoot.aliasSymbol; - result.aliasTypeArguments = lastAliasSymbol ? lastAliasTypeArguments : instantiateTypes(lastRoot.aliasTypeArguments, mapper!); // TODO: GH#18217 + result.combinedMapper = undefined; + result.aliasSymbol = aliasSymbol || originalRoot.aliasSymbol; + result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(originalRoot.aliasTypeArguments, mapper!); // TODO: GH#18217 break; } - return extraTypes ? getIntersectionType(append(extraTypes, result)) : result; - - // We tail-recurse for generic conditional types that (a) have not already been evaluated and cached, and - // (b) are non distributive, have a check type that is unaffected by instantiation, or have a non-union check - // type. Note that recursion is possible only through aliased conditional types, so we only increment the tail - // recursion counter for those. - function canTailRecurse(newType: Type, narrowMapper: TypeMapper, newMapper: TypeMapper | undefined) { - if (newType.flags & TypeFlags.Conditional && (newMapper || (newType as ConditionalType).root.isDistributive)) { - const newRoot = (newType as ConditionalType).root; - if (newRoot.outerTypeParameters) { - const typeParamMapper = newMapper ? combineTypeMappers((newType as ConditionalType).mapper, newMapper) : (newType as ConditionalType).mapper; - const typeArguments = typeParamMapper ? map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)) : newRoot.outerTypeParameters; - const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); - const newCheckTypeVariable = getNarrowableCheckTypeVariable(root, newRootMapper); - // const newCheckType = newRoot.isDistributive ? - // instantiateType(getActualTypeVariable(root.checkType), combineTypeMappers(newRootMapper, narrowMapper)) : - // undefined; - const newCheckType = newCheckTypeVariable ? - instantiateType(newCheckTypeVariable, narrowMapper) : - undefined; - if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { - root = newRoot; - mapper = newRootMapper; - aliasSymbol = undefined; - aliasTypeArguments = undefined; - if (newRoot.aliasSymbol) { - tailCount++; - } - return true; - } - } - } - return false; - } - - function saveConditional(combinedMapper: TypeMapper | undefined): void { - lastRoot = root; - lastCombinedMapper = combinedMapper; - lastAliasSymbol = aliasSymbol; - lastAliasTypeArguments = aliasTypeArguments; - saveNext = false; - } + return result; } function getTypeFromInferTypeNode(node: InferTypeNode): Type { @@ -19769,18 +19683,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { mapper: TypeMapper | undefined, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, - // noTopLevel: boolean): Type { ): Type { - // type = instantiateType(type, mapper); - // mapper = undefined; + type = instantiateType(type, mapper); const flags = type.flags; - // if (flags & TypeFlags.TypeParameter) { - // // We don't want to narrowly instantiate a return type that is just a type parameter. - // if (noTopLevel) { - // return type; - // } - // return getMappedType(type, combineTypeMappers(mapper, narrowMapper)); - // } if (flags & TypeFlags.IndexedAccess) { // >> TODO: what's that extra alias stuff here doing? const newAliasSymbol = aliasSymbol || type.aliasSymbol; @@ -19815,8 +19720,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { aliasTypeArguments); } - // return type; - return instantiateType(type, mapper); + return type; } function getNarrowableCheckTypeVariable(root: ConditionalRoot, mapper: TypeMapper | undefined): TypeParameter | undefined { diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index 2ce7eb7c8dd89..5c7f020828e55 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -30,9 +30,10 @@ dependentReturnType1.ts(268,9): error TS2322: Type '1' is not assignable to type dependentReturnType1.ts(271,9): error TS2322: Type '2' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. dependentReturnType1.ts(273,5): error TS2322: Type '0' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. dependentReturnType1.ts(291,9): error TS2322: Type 'string' is not assignable to type 'string[]'. +dependentReturnType1.ts(307,9): error TS2322: Type '1' is not assignable to type 'T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4'. -==== dependentReturnType1.ts (24 errors) ==== +==== dependentReturnType1.ts (25 errors) ==== interface A { 1: number; 2: string; @@ -390,4 +391,31 @@ dependentReturnType1.ts(291,9): error TS2322: Type 'string' is not assignable to return; // Ok } return 1; // Ok + } + + function woo(x: T, y: U): + T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 { + if (typeof x === "number" && typeof y === "string") { + return 1; // Error + ~~~~~~ +!!! error TS2322: Type '1' is not assignable to type 'T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4'. + } + return undefined as any; + } + + function ttt(x: T, y: U): + T extends string + ? number extends string + ? 6 + : U extends string + ? 1 + : 2 + : U extends number + ? 3 + : 4 { + if (typeof x === "string" && typeof y === "string") { + return 1; // Ok + } + + return undefined as any; } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 7c23780ab65f0..106c2047082a2 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -873,3 +873,62 @@ function voidRet(x: T): T extends {} ? void } return 1; // Ok } + +function woo(x: T, y: U): +>woo : Symbol(woo, Decl(dependentReturnType1.ts, 301, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 303, 13)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 303, 39)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 303, 67)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 303, 13)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 303, 72)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 303, 39)) + +T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 { +>T : Symbol(T, Decl(dependentReturnType1.ts, 303, 13)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 303, 39)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 303, 39)) + + if (typeof x === "number" && typeof y === "string") { +>x : Symbol(x, Decl(dependentReturnType1.ts, 303, 67)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 303, 72)) + + return 1; // Error + } + return undefined as any; +>undefined : Symbol(undefined) +} + +function ttt(x: T, y: U): +>ttt : Symbol(ttt, Decl(dependentReturnType1.ts, 309, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 311, 13)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 311, 39)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 311, 67)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 311, 13)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 311, 72)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 311, 39)) + +T extends string +>T : Symbol(T, Decl(dependentReturnType1.ts, 311, 13)) + +? number extends string + ? 6 + : U extends string +>U : Symbol(U, Decl(dependentReturnType1.ts, 311, 39)) + + ? 1 + : 2 +: U extends number +>U : Symbol(U, Decl(dependentReturnType1.ts, 311, 39)) + + ? 3 + : 4 { + if (typeof x === "string" && typeof y === "string") { +>x : Symbol(x, Decl(dependentReturnType1.ts, 311, 67)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 311, 72)) + + return 1; // Ok + } + + return undefined as any; +>undefined : Symbol(undefined) +} diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 16717cd877351..081b049066967 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -846,3 +846,62 @@ function voidRet(x: T): T extends {} ? void return 1; // Ok >1 : 1 } + +function woo(x: T, y: U): +>woo : (x: T, y: U) => T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 +>x : T +>y : U + +T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 { + if (typeof x === "number" && typeof y === "string") { +>typeof x === "number" && typeof y === "string" : boolean +>typeof x === "number" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T +>"number" : "number" +>typeof y === "string" : boolean +>typeof y : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>y : U +>"string" : "string" + + return 1; // Error +>1 : 1 + } + return undefined as any; +>undefined as any : any +>undefined : undefined +} + +function ttt(x: T, y: U): +>ttt : (x: T, y: U) => T extends string ? number extends string ? 6 : U extends string ? 1 : 2 : U extends number ? 3 : 4 +>x : T +>y : U + +T extends string +? number extends string + ? 6 + : U extends string + ? 1 + : 2 +: U extends number + ? 3 + : 4 { + if (typeof x === "string" && typeof y === "string") { +>typeof x === "string" && typeof y === "string" : boolean +>typeof x === "string" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T +>"string" : "string" +>typeof y === "string" : boolean +>typeof y : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>y : U +>"string" : "string" + + return 1; // Ok +>1 : 1 + } + + return undefined as any; +>undefined as any : any +>undefined : undefined +} diff --git a/tests/baselines/reference/dependentReturnType3.errors.txt b/tests/baselines/reference/dependentReturnType3.errors.txt new file mode 100644 index 0000000000000..2a2c949170da9 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType3.errors.txt @@ -0,0 +1,224 @@ +dependentReturnType3.ts(110,13): error TS2322: Type 'undefined' is not assignable to type 'HelperCond[]>'. +dependentReturnType3.ts(126,16): error TS2536: Type 'I' cannot be used to index type '{ [s: string]: any; }'. +dependentReturnType3.ts(137,9): error TS2322: Type 'Record' is not assignable to type 'HelperCond>'. + + +==== dependentReturnType3.ts (3 errors) ==== + // Adapted from ts-error-deltas repos + + type HelperCond = + T extends A + ? R1 + : T extends B + ? R2 + : (R1 | R2); + + + // File: Rocket.Chat/apps/meteor/app/katex/client/index.ts + interface IMessage { + html?: string; + tokens?: {}[]; + } + + class NewKatex { + render(s: string): string { + return ""; + } + + renderMessage(message: T): + T extends string + ? string + : T extends IMessage + ? IMessage + : (string | IMessage) { + if (typeof message === 'string') { + return this.render(message); // Ok + } + + if (!message.html?.trim()) { + return message; // Ok + } + + if (!message.tokens) { + message.tokens = []; + } + + message.html = this.render(message.html); + return message; // Ok + } + } + + export function createKatexMessageRendering( + options: { + dollarSyntax: boolean; + parenthesisSyntax: boolean; + }, + _isMessage: T, + ): T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : (((message: string) => string) | ((message: IMessage) => IMessage)) { + const instance = new NewKatex(); + if (_isMessage) { + return (message: IMessage): IMessage => instance.renderMessage(message); // Ok + } + return (message: string): string => instance.renderMessage(message); // Ok + } + + // File: Rocket.Chat/apps/meteor/app/settings/lib/settings.ts + type SettingComposedValue = { key: string; value: T }; + type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void; + + type SettingValue = object; + declare const Meteor: { settings: { [s: string]: any } }; + declare const _: { isRegExp(x: unknown): x is RegExp; }; + declare function takesRegExp(x: RegExp): void; + declare function takesString(x: string): void; + + class NewSettingsBase { + public newGet( + _id: I, + callback?: C, + ): HelperCond[]>> { + if (callback !== undefined) { + if (!Meteor.settings) { + return; // Ok + } + if (_id === '*') { + return Object.keys(Meteor.settings).forEach((key) => { // Ok + const value = Meteor.settings[key]; + callback(key, value); + }); + } + if (_.isRegExp(_id) && Meteor.settings) { + return Object.keys(Meteor.settings).forEach((key) => { // Ok + if (!_id.test(key)) { + return; + } + const value = Meteor.settings[key]; + callback(key, value); + }); + } + + if (typeof _id === 'string') { + const value = Meteor.settings[_id]; + if (value != null) { + callback(_id, Meteor.settings[_id]); + } + return; // Ok + } + + return; // Ok, needed for exhaustiveness check + } + + if (!Meteor.settings) { // Wrong: we don't know that _id is string here, cannot return undefined + return undefined; // Error + ~~~~~~ +!!! error TS2322: Type 'undefined' is not assignable to type 'HelperCond[]>'. + } + + if (_.isRegExp(_id)) { + return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { + const value = Meteor.settings[key]; + if (_id.test(key)) { + items.push({ + key, + value, + }); + } + return items; + }, []); // Ok + } + + return Meteor.settings?.[_id]; // Error + ~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2536: Type 'I' cannot be used to index type '{ [s: string]: any; }'. + // The indexing currently doesn't work because it doesn't use the narrowed type of `_id`. + } + } + + // File: Rocket.Chat/apps/meteor/app/ui-utils/client/lib/messageBox.ts + type MessageBoxAction = object; + + function getWithBug(group: T): + HelperCond> { + if (!group) { + return {} as Record; // Error, could fall into this branch when group is empty string + ~~~~~~ +!!! error TS2322: Type 'Record' is not assignable to type 'HelperCond>'. + } + + return [] as MessageBoxAction[]; // Ok + } + + function getWithoutBug(group: T): + HelperCond> { + if (group === undefined) { + return {} as Record; // Ok + } + + return [] as MessageBoxAction[]; // Ok + } + + // File: Rocket.Chat/apps/meteor/ee/server/lib/engagementDashboard/date.ts + declare function mapDateForAPI(x: string): Date; + export function transformDatesForAPI( + start: string, + end?: T + ): HelperCond { + return end !== undefined ? // Ok + { + start: mapDateForAPI(start), + end: mapDateForAPI(end), + } : + { + start: mapDateForAPI(start), + end: undefined + }; + } + + // File: Rocket.Chat/packages/agenda/src/Agenda.ts + type RepeatOptions = object; + type Job = object; + type IJob = { data: object }; + class NewAgenda { + public async _createIntervalJob(interval: string | number, name: string, data: IJob['data'], options: RepeatOptions): Promise { return undefined as any; } + private _createIntervalJobs( + interval: string | number, + names: string[], + data: IJob['data'], + options: RepeatOptions, + ): Promise | undefined { return undefined as any; } + + public async newEvery( + interval: string | number, + name: T, + data: IJob['data'], + options: RepeatOptions): Promise> { + if (typeof name === 'string') { + return this._createIntervalJob(interval, name, data, options); // Ok + } + + if (Array.isArray(name)) { + return this._createIntervalJobs(interval, name, data, options); // Ok + // Possible bug in original: createIntervalJobs can return undefined, but the original overload did not acount for that. + } + + throw new Error('Unexpected error: Invalid job name(s)'); + } + } + + // File: angular/packages/common/src/pipes/case_conversion_pipes.ts + + function transform1(value: T): HelperCond { + if (value == null) return null; // Ok + if (typeof value !== 'string') { + throw new Error(); + } + return value.toLowerCase(); // Ok + } + + + + \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType3.symbols b/tests/baselines/reference/dependentReturnType3.symbols new file mode 100644 index 0000000000000..8358483e3d8c5 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType3.symbols @@ -0,0 +1,684 @@ +//// [tests/cases/compiler/dependentReturnType3.ts] //// + +=== dependentReturnType3.ts === +// Adapted from ts-error-deltas repos + +type HelperCond = +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 2, 16)) +>A : Symbol(A, Decl(dependentReturnType3.ts, 2, 18)) +>R1 : Symbol(R1, Decl(dependentReturnType3.ts, 2, 21)) +>B : Symbol(B, Decl(dependentReturnType3.ts, 2, 25)) +>R2 : Symbol(R2, Decl(dependentReturnType3.ts, 2, 28)) + + T extends A +>T : Symbol(T, Decl(dependentReturnType3.ts, 2, 16)) +>A : Symbol(A, Decl(dependentReturnType3.ts, 2, 18)) + + ? R1 +>R1 : Symbol(R1, Decl(dependentReturnType3.ts, 2, 21)) + + : T extends B +>T : Symbol(T, Decl(dependentReturnType3.ts, 2, 16)) +>B : Symbol(B, Decl(dependentReturnType3.ts, 2, 25)) + + ? R2 +>R2 : Symbol(R2, Decl(dependentReturnType3.ts, 2, 28)) + + : (R1 | R2); +>R1 : Symbol(R1, Decl(dependentReturnType3.ts, 2, 21)) +>R2 : Symbol(R2, Decl(dependentReturnType3.ts, 2, 28)) + + +// File: Rocket.Chat/apps/meteor/app/katex/client/index.ts +interface IMessage { +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) + + html?: string; +>html : Symbol(IMessage.html, Decl(dependentReturnType3.ts, 11, 20)) + + tokens?: {}[]; +>tokens : Symbol(IMessage.tokens, Decl(dependentReturnType3.ts, 12, 18)) +} + +class NewKatex { +>NewKatex : Symbol(NewKatex, Decl(dependentReturnType3.ts, 14, 1)) + + render(s: string): string { +>render : Symbol(NewKatex.render, Decl(dependentReturnType3.ts, 16, 16)) +>s : Symbol(s, Decl(dependentReturnType3.ts, 17, 11)) + + return ""; + } + + renderMessage(message: T): +>renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 21, 18)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 21, 47)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 21, 18)) + + T extends string +>T : Symbol(T, Decl(dependentReturnType3.ts, 21, 18)) + + ? string + : T extends IMessage +>T : Symbol(T, Decl(dependentReturnType3.ts, 21, 18)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) + + ? IMessage +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) + + : (string | IMessage) { +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) + + if (typeof message === 'string') { +>message : Symbol(message, Decl(dependentReturnType3.ts, 21, 47)) + + return this.render(message); // Ok +>this.render : Symbol(NewKatex.render, Decl(dependentReturnType3.ts, 16, 16)) +>this : Symbol(NewKatex, Decl(dependentReturnType3.ts, 14, 1)) +>render : Symbol(NewKatex.render, Decl(dependentReturnType3.ts, 16, 16)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 21, 47)) + } + + if (!message.html?.trim()) { +>message.html?.trim : Symbol(String.trim, Decl(lib.es5.d.ts, --, --)) +>message.html : Symbol(IMessage.html, Decl(dependentReturnType3.ts, 11, 20)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 21, 47)) +>html : Symbol(IMessage.html, Decl(dependentReturnType3.ts, 11, 20)) +>trim : Symbol(String.trim, Decl(lib.es5.d.ts, --, --)) + + return message; // Ok +>message : Symbol(message, Decl(dependentReturnType3.ts, 21, 47)) + } + + if (!message.tokens) { +>message.tokens : Symbol(IMessage.tokens, Decl(dependentReturnType3.ts, 12, 18)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 21, 47)) +>tokens : Symbol(IMessage.tokens, Decl(dependentReturnType3.ts, 12, 18)) + + message.tokens = []; +>message.tokens : Symbol(IMessage.tokens, Decl(dependentReturnType3.ts, 12, 18)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 21, 47)) +>tokens : Symbol(IMessage.tokens, Decl(dependentReturnType3.ts, 12, 18)) + } + + message.html = this.render(message.html); +>message.html : Symbol(IMessage.html, Decl(dependentReturnType3.ts, 11, 20)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 21, 47)) +>html : Symbol(IMessage.html, Decl(dependentReturnType3.ts, 11, 20)) +>this.render : Symbol(NewKatex.render, Decl(dependentReturnType3.ts, 16, 16)) +>this : Symbol(NewKatex, Decl(dependentReturnType3.ts, 14, 1)) +>render : Symbol(NewKatex.render, Decl(dependentReturnType3.ts, 16, 16)) +>message.html : Symbol(IMessage.html, Decl(dependentReturnType3.ts, 11, 20)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 21, 47)) +>html : Symbol(IMessage.html, Decl(dependentReturnType3.ts, 11, 20)) + + return message; // Ok +>message : Symbol(message, Decl(dependentReturnType3.ts, 21, 47)) + } +} + +export function createKatexMessageRendering( +>createKatexMessageRendering : Symbol(createKatexMessageRendering, Decl(dependentReturnType3.ts, 42, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 44, 44)) + + options: { +>options : Symbol(options, Decl(dependentReturnType3.ts, 44, 68)) + + dollarSyntax: boolean; +>dollarSyntax : Symbol(dollarSyntax, Decl(dependentReturnType3.ts, 45, 14)) + + parenthesisSyntax: boolean; +>parenthesisSyntax : Symbol(parenthesisSyntax, Decl(dependentReturnType3.ts, 46, 30)) + + }, + _isMessage: T, +>_isMessage : Symbol(_isMessage, Decl(dependentReturnType3.ts, 48, 6)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 44, 44)) + +): T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : (((message: string) => string) | ((message: IMessage) => IMessage)) { +>T : Symbol(T, Decl(dependentReturnType3.ts, 44, 44)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 50, 21)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 44, 44)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 50, 73)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 50, 105)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 50, 137)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) + + const instance = new NewKatex(); +>instance : Symbol(instance, Decl(dependentReturnType3.ts, 51, 9)) +>NewKatex : Symbol(NewKatex, Decl(dependentReturnType3.ts, 14, 1)) + + if (_isMessage) { +>_isMessage : Symbol(_isMessage, Decl(dependentReturnType3.ts, 48, 6)) + + return (message: IMessage): IMessage => instance.renderMessage(message); // Ok +>message : Symbol(message, Decl(dependentReturnType3.ts, 53, 16)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) +>instance.renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) +>instance : Symbol(instance, Decl(dependentReturnType3.ts, 51, 9)) +>renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 53, 16)) + } + return (message: string): string => instance.renderMessage(message); // Ok +>message : Symbol(message, Decl(dependentReturnType3.ts, 55, 12)) +>instance.renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) +>instance : Symbol(instance, Decl(dependentReturnType3.ts, 51, 9)) +>renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 55, 12)) +} + +// File: Rocket.Chat/apps/meteor/app/settings/lib/settings.ts +type SettingComposedValue = { key: string; value: T }; +>SettingComposedValue : Symbol(SettingComposedValue, Decl(dependentReturnType3.ts, 56, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 59, 26)) +>SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 60, 89)) +>SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 60, 89)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 59, 68)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 59, 81)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 59, 26)) + +type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void; +>SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType3.ts, 59, 93)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 60, 24)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 60, 36)) +>SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 60, 89)) +>initialLoad : Symbol(initialLoad, Decl(dependentReturnType3.ts, 60, 57)) + +type SettingValue = object; +>SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 60, 89)) + +declare const Meteor: { settings: { [s: string]: any } }; +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>s : Symbol(s, Decl(dependentReturnType3.ts, 63, 37)) + +declare const _: { isRegExp(x: unknown): x is RegExp; }; +>_ : Symbol(_, Decl(dependentReturnType3.ts, 64, 13)) +>isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 64, 18)) +>x : Symbol(x, Decl(dependentReturnType3.ts, 64, 28)) +>x : Symbol(x, Decl(dependentReturnType3.ts, 64, 28)) +>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + +declare function takesRegExp(x: RegExp): void; +>takesRegExp : Symbol(takesRegExp, Decl(dependentReturnType3.ts, 64, 56)) +>x : Symbol(x, Decl(dependentReturnType3.ts, 65, 29)) +>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + +declare function takesString(x: string): void; +>takesString : Symbol(takesString, Decl(dependentReturnType3.ts, 65, 46)) +>x : Symbol(x, Decl(dependentReturnType3.ts, 66, 29)) + +class NewSettingsBase { +>NewSettingsBase : Symbol(NewSettingsBase, Decl(dependentReturnType3.ts, 66, 46)) + + public newGet( +>newGet : Symbol(NewSettingsBase.newGet, Decl(dependentReturnType3.ts, 68, 23)) +>C : Symbol(C, Decl(dependentReturnType3.ts, 69, 18)) +>SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType3.ts, 59, 93)) +>I : Symbol(I, Decl(dependentReturnType3.ts, 69, 56)) +>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 69, 83)) +>SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 60, 89)) +>SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 60, 89)) + + _id: I, +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>I : Symbol(I, Decl(dependentReturnType3.ts, 69, 56)) + + callback?: C, +>callback : Symbol(callback, Decl(dependentReturnType3.ts, 70, 15)) +>C : Symbol(C, Decl(dependentReturnType3.ts, 69, 18)) + + ): HelperCondHelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) +>C : Symbol(C, Decl(dependentReturnType3.ts, 69, 18)) + + SettingCallback, void, +>SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType3.ts, 59, 93)) + + undefined, HelperCondHelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) +>I : Symbol(I, Decl(dependentReturnType3.ts, 69, 56)) + + string, T | undefined, +>T : Symbol(T, Decl(dependentReturnType3.ts, 69, 83)) + + RegExp, SettingComposedValue[]>> { +>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>SettingComposedValue : Symbol(SettingComposedValue, Decl(dependentReturnType3.ts, 56, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 69, 83)) + + if (callback !== undefined) { +>callback : Symbol(callback, Decl(dependentReturnType3.ts, 70, 15)) +>undefined : Symbol(undefined) + + if (!Meteor.settings) { +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) + + return; // Ok + } + if (_id === '*') { +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) + + return Object.keys(Meteor.settings).forEach((key) => { // Ok +>Object.keys(Meteor.settings).forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 82, 61)) + + const value = Meteor.settings[key]; +>value : Symbol(value, Decl(dependentReturnType3.ts, 83, 25)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 82, 61)) + + callback(key, value); +>callback : Symbol(callback, Decl(dependentReturnType3.ts, 70, 15)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 82, 61)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 83, 25)) + + }); + } + if (_.isRegExp(_id) && Meteor.settings) { +>_.isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 64, 18)) +>_ : Symbol(_, Decl(dependentReturnType3.ts, 64, 13)) +>isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 64, 18)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) + + return Object.keys(Meteor.settings).forEach((key) => { // Ok +>Object.keys(Meteor.settings).forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 88, 61)) + + if (!_id.test(key)) { +>_id.test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 88, 61)) + + return; + } + const value = Meteor.settings[key]; +>value : Symbol(value, Decl(dependentReturnType3.ts, 92, 25)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 88, 61)) + + callback(key, value); +>callback : Symbol(callback, Decl(dependentReturnType3.ts, 70, 15)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 88, 61)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 92, 25)) + + }); + } + + if (typeof _id === 'string') { +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) + + const value = Meteor.settings[_id]; +>value : Symbol(value, Decl(dependentReturnType3.ts, 98, 21)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) + + if (value != null) { +>value : Symbol(value, Decl(dependentReturnType3.ts, 98, 21)) + + callback(_id, Meteor.settings[_id]); +>callback : Symbol(callback, Decl(dependentReturnType3.ts, 70, 15)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) + } + return; // Ok + } + + return; // Ok, needed for exhaustiveness check + } + + if (!Meteor.settings) { // Wrong: we don't know that _id is string here, cannot return undefined +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) + + return undefined; // Error +>undefined : Symbol(undefined) + } + + if (_.isRegExp(_id)) { +>_.isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 64, 18)) +>_ : Symbol(_, Decl(dependentReturnType3.ts, 64, 13)) +>isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 64, 18)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) + + return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { +>Object.keys(Meteor.settings).reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>items : Symbol(items, Decl(dependentReturnType3.ts, 113, 56)) +>SettingComposedValue : Symbol(SettingComposedValue, Decl(dependentReturnType3.ts, 56, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 69, 83)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 113, 89)) + + const value = Meteor.settings[key]; +>value : Symbol(value, Decl(dependentReturnType3.ts, 114, 9)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 113, 89)) + + if (_id.test(key)) { +>_id.test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 113, 89)) + + items.push({ +>items.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>items : Symbol(items, Decl(dependentReturnType3.ts, 113, 56)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) + + key, +>key : Symbol(key, Decl(dependentReturnType3.ts, 116, 17)) + + value, +>value : Symbol(value, Decl(dependentReturnType3.ts, 117, 10)) + + }); + } + return items; +>items : Symbol(items, Decl(dependentReturnType3.ts, 113, 56)) + + }, []); // Ok + } + + return Meteor.settings?.[_id]; // Error +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) + + // The indexing currently doesn't work because it doesn't use the narrowed type of `_id`. + } +} + +// File: Rocket.Chat/apps/meteor/app/ui-utils/client/lib/messageBox.ts +type MessageBoxAction = object; +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) + +function getWithBug(group: T): +>getWithBug : Symbol(getWithBug, Decl(dependentReturnType3.ts, 131, 31)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 133, 20)) +>group : Symbol(group, Decl(dependentReturnType3.ts, 133, 50)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 133, 20)) + +HelperCond> { +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 133, 20)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) + + if (!group) { +>group : Symbol(group, Decl(dependentReturnType3.ts, 133, 50)) + + return {} as Record; // Error, could fall into this branch when group is empty string +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) + } + + return [] as MessageBoxAction[]; // Ok +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +} + +function getWithoutBug(group: T): +>getWithoutBug : Symbol(getWithoutBug, Decl(dependentReturnType3.ts, 140, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 142, 23)) +>group : Symbol(group, Decl(dependentReturnType3.ts, 142, 53)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 142, 23)) + +HelperCond> { +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 142, 23)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) + + if (group === undefined) { +>group : Symbol(group, Decl(dependentReturnType3.ts, 142, 53)) +>undefined : Symbol(undefined) + + return {} as Record; // Ok +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) + } + + return [] as MessageBoxAction[]; // Ok +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +} + +// File: Rocket.Chat/apps/meteor/ee/server/lib/engagementDashboard/date.ts +declare function mapDateForAPI(x: string): Date; +>mapDateForAPI : Symbol(mapDateForAPI, Decl(dependentReturnType3.ts, 149, 1)) +>x : Symbol(x, Decl(dependentReturnType3.ts, 152, 31)) +>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + +export function transformDatesForAPI( +>transformDatesForAPI : Symbol(transformDatesForAPI, Decl(dependentReturnType3.ts, 152, 48)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 153, 37)) + + start: string, +>start : Symbol(start, Decl(dependentReturnType3.ts, 153, 67)) + + end?: T +>end : Symbol(end, Decl(dependentReturnType3.ts, 154, 18)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 153, 37)) + +): HelperCond { +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 153, 37)) +>start : Symbol(start, Decl(dependentReturnType3.ts, 156, 26)) +>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>end : Symbol(end, Decl(dependentReturnType3.ts, 156, 39)) +>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>start : Symbol(start, Decl(dependentReturnType3.ts, 156, 65)) +>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>end : Symbol(end, Decl(dependentReturnType3.ts, 156, 78)) + + return end !== undefined ? // Ok +>end : Symbol(end, Decl(dependentReturnType3.ts, 154, 18)) +>undefined : Symbol(undefined) + { + start: mapDateForAPI(start), +>start : Symbol(start, Decl(dependentReturnType3.ts, 158, 9)) +>mapDateForAPI : Symbol(mapDateForAPI, Decl(dependentReturnType3.ts, 149, 1)) +>start : Symbol(start, Decl(dependentReturnType3.ts, 153, 67)) + + end: mapDateForAPI(end), +>end : Symbol(end, Decl(dependentReturnType3.ts, 159, 40)) +>mapDateForAPI : Symbol(mapDateForAPI, Decl(dependentReturnType3.ts, 149, 1)) +>end : Symbol(end, Decl(dependentReturnType3.ts, 154, 18)) + + } : + { + start: mapDateForAPI(start), +>start : Symbol(start, Decl(dependentReturnType3.ts, 162, 9)) +>mapDateForAPI : Symbol(mapDateForAPI, Decl(dependentReturnType3.ts, 149, 1)) +>start : Symbol(start, Decl(dependentReturnType3.ts, 153, 67)) + + end: undefined +>end : Symbol(end, Decl(dependentReturnType3.ts, 163, 40)) +>undefined : Symbol(undefined) + + }; +} + +// File: Rocket.Chat/packages/agenda/src/Agenda.ts +type RepeatOptions = object; +>RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 166, 1)) + +type Job = object; +>Job : Symbol(Job, Decl(dependentReturnType3.ts, 169, 28)) + +type IJob = { data: object }; +>IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 170, 18)) +>data : Symbol(data, Decl(dependentReturnType3.ts, 171, 13)) + +class NewAgenda { +>NewAgenda : Symbol(NewAgenda, Decl(dependentReturnType3.ts, 171, 29)) + + public async _createIntervalJob(interval: string | number, name: string, data: IJob['data'], options: RepeatOptions): Promise { return undefined as any; } +>_createIntervalJob : Symbol(NewAgenda._createIntervalJob, Decl(dependentReturnType3.ts, 172, 17)) +>interval : Symbol(interval, Decl(dependentReturnType3.ts, 173, 36)) +>name : Symbol(name, Decl(dependentReturnType3.ts, 173, 62)) +>data : Symbol(data, Decl(dependentReturnType3.ts, 173, 76)) +>IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 170, 18)) +>options : Symbol(options, Decl(dependentReturnType3.ts, 173, 96)) +>RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 166, 1)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>Job : Symbol(Job, Decl(dependentReturnType3.ts, 169, 28)) +>undefined : Symbol(undefined) + + private _createIntervalJobs( +>_createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(dependentReturnType3.ts, 173, 163)) + + interval: string | number, +>interval : Symbol(interval, Decl(dependentReturnType3.ts, 174, 32)) + + names: string[], +>names : Symbol(names, Decl(dependentReturnType3.ts, 175, 34)) + + data: IJob['data'], +>data : Symbol(data, Decl(dependentReturnType3.ts, 176, 24)) +>IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 170, 18)) + + options: RepeatOptions, +>options : Symbol(options, Decl(dependentReturnType3.ts, 177, 27)) +>RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 166, 1)) + + ): Promise | undefined { return undefined as any; } +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>Job : Symbol(Job, Decl(dependentReturnType3.ts, 169, 28)) +>undefined : Symbol(undefined) + + public async newEvery( +>newEvery : Symbol(NewAgenda.newEvery, Decl(dependentReturnType3.ts, 179, 62)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 181, 26)) + + interval: string | number, +>interval : Symbol(interval, Decl(dependentReturnType3.ts, 181, 55)) + + name: T, +>name : Symbol(name, Decl(dependentReturnType3.ts, 182, 34)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 181, 26)) + + data: IJob['data'], +>data : Symbol(data, Decl(dependentReturnType3.ts, 183, 16)) +>IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 170, 18)) + + options: RepeatOptions): Promise> { +>options : Symbol(options, Decl(dependentReturnType3.ts, 184, 27)) +>RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 166, 1)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 181, 26)) +>Job : Symbol(Job, Decl(dependentReturnType3.ts, 169, 28)) +>Job : Symbol(Job, Decl(dependentReturnType3.ts, 169, 28)) + + if (typeof name === 'string') { +>name : Symbol(name, Decl(dependentReturnType3.ts, 182, 34)) + + return this._createIntervalJob(interval, name, data, options); // Ok +>this._createIntervalJob : Symbol(NewAgenda._createIntervalJob, Decl(dependentReturnType3.ts, 172, 17)) +>this : Symbol(NewAgenda, Decl(dependentReturnType3.ts, 171, 29)) +>_createIntervalJob : Symbol(NewAgenda._createIntervalJob, Decl(dependentReturnType3.ts, 172, 17)) +>interval : Symbol(interval, Decl(dependentReturnType3.ts, 181, 55)) +>name : Symbol(name, Decl(dependentReturnType3.ts, 182, 34)) +>data : Symbol(data, Decl(dependentReturnType3.ts, 183, 16)) +>options : Symbol(options, Decl(dependentReturnType3.ts, 184, 27)) + } + + if (Array.isArray(name)) { +>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>name : Symbol(name, Decl(dependentReturnType3.ts, 182, 34)) + + return this._createIntervalJobs(interval, name, data, options); // Ok +>this._createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(dependentReturnType3.ts, 173, 163)) +>this : Symbol(NewAgenda, Decl(dependentReturnType3.ts, 171, 29)) +>_createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(dependentReturnType3.ts, 173, 163)) +>interval : Symbol(interval, Decl(dependentReturnType3.ts, 181, 55)) +>name : Symbol(name, Decl(dependentReturnType3.ts, 182, 34)) +>data : Symbol(data, Decl(dependentReturnType3.ts, 183, 16)) +>options : Symbol(options, Decl(dependentReturnType3.ts, 184, 27)) + + // Possible bug in original: createIntervalJobs can return undefined, but the original overload did not acount for that. + } + + throw new Error('Unexpected error: Invalid job name(s)'); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +// File: angular/packages/common/src/pipes/case_conversion_pipes.ts + +function transform1(value: T): HelperCond { +>transform1 : Symbol(transform1, Decl(dependentReturnType3.ts, 197, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 201, 20)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 201, 57)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 201, 20)) +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 201, 20)) + + if (value == null) return null; // Ok +>value : Symbol(value, Decl(dependentReturnType3.ts, 201, 57)) + + if (typeof value !== 'string') { +>value : Symbol(value, Decl(dependentReturnType3.ts, 201, 57)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + return value.toLowerCase(); // Ok +>value.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 201, 57)) +>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +} + + + + diff --git a/tests/baselines/reference/dependentReturnType3.types b/tests/baselines/reference/dependentReturnType3.types new file mode 100644 index 0000000000000..42a1cf0512f5e --- /dev/null +++ b/tests/baselines/reference/dependentReturnType3.types @@ -0,0 +1,651 @@ +//// [tests/cases/compiler/dependentReturnType3.ts] //// + +=== dependentReturnType3.ts === +// Adapted from ts-error-deltas repos + +type HelperCond = +>HelperCond : HelperCond + + T extends A + ? R1 + : T extends B + ? R2 + : (R1 | R2); + + +// File: Rocket.Chat/apps/meteor/app/katex/client/index.ts +interface IMessage { + html?: string; +>html : string | undefined + + tokens?: {}[]; +>tokens : {}[] | undefined +} + +class NewKatex { +>NewKatex : NewKatex + + render(s: string): string { +>render : (s: string) => string +>s : string + + return ""; +>"" : "" + } + + renderMessage(message: T): +>renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : (string | IMessage) +>message : T + + T extends string + ? string + : T extends IMessage + ? IMessage + : (string | IMessage) { + if (typeof message === 'string') { +>typeof message === 'string' : boolean +>typeof message : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>message : T +>'string' : "string" + + return this.render(message); // Ok +>this.render(message) : string +>this.render : (s: string) => string +>this : this +>render : (s: string) => string +>message : string + } + + if (!message.html?.trim()) { +>!message.html?.trim() : boolean +>message.html?.trim() : string | undefined +>message.html?.trim : (() => string) | undefined +>message.html : string | undefined +>message : IMessage +>html : string | undefined +>trim : (() => string) | undefined + + return message; // Ok +>message : IMessage + } + + if (!message.tokens) { +>!message.tokens : boolean +>message.tokens : {}[] | undefined +>message : IMessage +>tokens : {}[] | undefined + + message.tokens = []; +>message.tokens = [] : never[] +>message.tokens : {}[] | undefined +>message : IMessage +>tokens : {}[] | undefined +>[] : never[] + } + + message.html = this.render(message.html); +>message.html = this.render(message.html) : string +>message.html : string | undefined +>message : IMessage +>html : string | undefined +>this.render(message.html) : string +>this.render : (s: string) => string +>this : this +>render : (s: string) => string +>message.html : string +>message : IMessage +>html : string + + return message; // Ok +>message : IMessage + } +} + +export function createKatexMessageRendering( +>createKatexMessageRendering : (options: { dollarSyntax: boolean; parenthesisSyntax: boolean;}, _isMessage: T) => T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : ((message: string) => string) | ((message: IMessage) => IMessage) +>true : true +>false : false + + options: { +>options : { dollarSyntax: boolean; parenthesisSyntax: boolean; } + + dollarSyntax: boolean; +>dollarSyntax : boolean + + parenthesisSyntax: boolean; +>parenthesisSyntax : boolean + + }, + _isMessage: T, +>_isMessage : T + +): T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : (((message: string) => string) | ((message: IMessage) => IMessage)) { +>true : true +>message : IMessage +>false : false +>message : string +>message : string +>message : IMessage + + const instance = new NewKatex(); +>instance : NewKatex +>new NewKatex() : NewKatex +>NewKatex : typeof NewKatex + + if (_isMessage) { +>_isMessage : T + + return (message: IMessage): IMessage => instance.renderMessage(message); // Ok +>(message: IMessage): IMessage => instance.renderMessage(message) : (message: IMessage) => IMessage +>message : IMessage +>instance.renderMessage(message) : IMessage +>instance.renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : string | IMessage +>instance : NewKatex +>renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : string | IMessage +>message : IMessage + } + return (message: string): string => instance.renderMessage(message); // Ok +>(message: string): string => instance.renderMessage(message) : (message: string) => string +>message : string +>instance.renderMessage(message) : string +>instance.renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : string | IMessage +>instance : NewKatex +>renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : string | IMessage +>message : string +} + +// File: Rocket.Chat/apps/meteor/app/settings/lib/settings.ts +type SettingComposedValue = { key: string; value: T }; +>SettingComposedValue : SettingComposedValue +>key : string +>value : T + +type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void; +>SettingCallback : (key: string, value: SettingValue, initialLoad?: boolean) => void +>key : string +>value : object +>initialLoad : boolean | undefined + +type SettingValue = object; +>SettingValue : object + +declare const Meteor: { settings: { [s: string]: any } }; +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } +>s : string + +declare const _: { isRegExp(x: unknown): x is RegExp; }; +>_ : { isRegExp(x: unknown): x is RegExp; } +>isRegExp : (x: unknown) => x is RegExp +>x : unknown + +declare function takesRegExp(x: RegExp): void; +>takesRegExp : (x: RegExp) => void +>x : RegExp + +declare function takesString(x: string): void; +>takesString : (x: string) => void +>x : string + +class NewSettingsBase { +>NewSettingsBase : NewSettingsBase + + public newGet( +>newGet : (_id: I, callback?: C) => HelperCond[]>> + + _id: I, +>_id : I + + callback?: C, +>callback : C | undefined + + ): HelperCond[]>> { + if (callback !== undefined) { +>callback !== undefined : boolean +>callback : C | undefined +>undefined : undefined + + if (!Meteor.settings) { +>!Meteor.settings : false +>Meteor.settings : { [s: string]: any; } +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } + + return; // Ok + } + if (_id === '*') { +>_id === '*' : boolean +>_id : I +>'*' : "*" + + return Object.keys(Meteor.settings).forEach((key) => { // Ok +>Object.keys(Meteor.settings).forEach((key) => { // Ok const value = Meteor.settings[key]; callback(key, value); }) : void +>Object.keys(Meteor.settings).forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void +>Object.keys(Meteor.settings) : string[] +>Object.keys : { (o: object): string[]; (o: {}): string[]; } +>Object : ObjectConstructor +>keys : { (o: object): string[]; (o: {}): string[]; } +>Meteor.settings : { [s: string]: any; } +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } +>forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void +>(key) => { // Ok const value = Meteor.settings[key]; callback(key, value); } : (key: string) => void +>key : string + + const value = Meteor.settings[key]; +>value : any +>Meteor.settings[key] : any +>Meteor.settings : { [s: string]: any; } +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } +>key : string + + callback(key, value); +>callback(key, value) : void +>callback : SettingCallback +>key : string +>value : any + + }); + } + if (_.isRegExp(_id) && Meteor.settings) { +>_.isRegExp(_id) && Meteor.settings : false | { [s: string]: any; } +>_.isRegExp(_id) : boolean +>_.isRegExp : (x: unknown) => x is RegExp +>_ : { isRegExp(x: unknown): x is RegExp; } +>isRegExp : (x: unknown) => x is RegExp +>_id : string | RegExp +>Meteor.settings : { [s: string]: any; } +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } + + return Object.keys(Meteor.settings).forEach((key) => { // Ok +>Object.keys(Meteor.settings).forEach((key) => { // Ok if (!_id.test(key)) { return; } const value = Meteor.settings[key]; callback(key, value); }) : void +>Object.keys(Meteor.settings).forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void +>Object.keys(Meteor.settings) : string[] +>Object.keys : { (o: object): string[]; (o: {}): string[]; } +>Object : ObjectConstructor +>keys : { (o: object): string[]; (o: {}): string[]; } +>Meteor.settings : { [s: string]: any; } +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } +>forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void +>(key) => { // Ok if (!_id.test(key)) { return; } const value = Meteor.settings[key]; callback(key, value); } : (key: string) => void +>key : string + + if (!_id.test(key)) { +>!_id.test(key) : boolean +>_id.test(key) : boolean +>_id.test : (string: string) => boolean +>_id : RegExp +>test : (string: string) => boolean +>key : string + + return; + } + const value = Meteor.settings[key]; +>value : any +>Meteor.settings[key] : any +>Meteor.settings : { [s: string]: any; } +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } +>key : string + + callback(key, value); +>callback(key, value) : void +>callback : SettingCallback +>key : string +>value : any + + }); + } + + if (typeof _id === 'string') { +>typeof _id === 'string' : boolean +>typeof _id : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>_id : I +>'string' : "string" + + const value = Meteor.settings[_id]; +>value : any +>Meteor.settings[_id] : any +>Meteor.settings : { [s: string]: any; } +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } +>_id : I & string + + if (value != null) { +>value != null : boolean +>value : any + + callback(_id, Meteor.settings[_id]); +>callback(_id, Meteor.settings[_id]) : void +>callback : SettingCallback +>_id : string +>Meteor.settings[_id] : any +>Meteor.settings : { [s: string]: any; } +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } +>_id : I & string + } + return; // Ok + } + + return; // Ok, needed for exhaustiveness check + } + + if (!Meteor.settings) { // Wrong: we don't know that _id is string here, cannot return undefined +>!Meteor.settings : false +>Meteor.settings : { [s: string]: any; } +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } + + return undefined; // Error +>undefined : undefined + } + + if (_.isRegExp(_id)) { +>_.isRegExp(_id) : boolean +>_.isRegExp : (x: unknown) => x is RegExp +>_ : { isRegExp(x: unknown): x is RegExp; } +>isRegExp : (x: unknown) => x is RegExp +>_id : string | RegExp + + return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { +>Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { const value = Meteor.settings[key]; if (_id.test(key)) { items.push({ key, value, }); } return items; }, []) : SettingComposedValue[] +>Object.keys(Meteor.settings).reduce : { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; (callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; } +>Object.keys(Meteor.settings) : string[] +>Object.keys : { (o: object): string[]; (o: {}): string[]; } +>Object : ObjectConstructor +>keys : { (o: object): string[]; (o: {}): string[]; } +>Meteor.settings : { [s: string]: any; } +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } +>reduce : { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; (callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; } +>(items: SettingComposedValue[], key) => { const value = Meteor.settings[key]; if (_id.test(key)) { items.push({ key, value, }); } return items; } : (items: SettingComposedValue[], key: string) => SettingComposedValue[] +>items : SettingComposedValue[] +>key : string + + const value = Meteor.settings[key]; +>value : any +>Meteor.settings[key] : any +>Meteor.settings : { [s: string]: any; } +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } +>key : string + + if (_id.test(key)) { +>_id.test(key) : boolean +>_id.test : (string: string) => boolean +>_id : RegExp +>test : (string: string) => boolean +>key : string + + items.push({ +>items.push({ key, value, }) : number +>items.push : (...items: SettingComposedValue[]) => number +>items : SettingComposedValue[] +>push : (...items: SettingComposedValue[]) => number +>{ key, value, } : { key: string; value: any; } + + key, +>key : string + + value, +>value : any + + }); + } + return items; +>items : SettingComposedValue[] + + }, []); // Ok +>[] : never[] + } + + return Meteor.settings?.[_id]; // Error +>Meteor.settings?.[_id] : any +>Meteor.settings : { [s: string]: any; } +>Meteor : { settings: { [s: string]: any; }; } +>settings : { [s: string]: any; } +>_id : I + + // The indexing currently doesn't work because it doesn't use the narrowed type of `_id`. + } +} + +// File: Rocket.Chat/apps/meteor/app/ui-utils/client/lib/messageBox.ts +type MessageBoxAction = object; +>MessageBoxAction : object + +function getWithBug(group: T): +>getWithBug : (group: T) => HelperCond> +>group : T + +HelperCond> { + if (!group) { +>!group : boolean +>group : T + + return {} as Record; // Error, could fall into this branch when group is empty string +>{} as Record : Record +>{} : {} + } + + return [] as MessageBoxAction[]; // Ok +>[] as MessageBoxAction[] : object[] +>[] : never[] +} + +function getWithoutBug(group: T): +>getWithoutBug : (group: T) => HelperCond> +>group : T + +HelperCond> { + if (group === undefined) { +>group === undefined : boolean +>group : T +>undefined : undefined + + return {} as Record; // Ok +>{} as Record : Record +>{} : {} + } + + return [] as MessageBoxAction[]; // Ok +>[] as MessageBoxAction[] : object[] +>[] : never[] +} + +// File: Rocket.Chat/apps/meteor/ee/server/lib/engagementDashboard/date.ts +declare function mapDateForAPI(x: string): Date; +>mapDateForAPI : (x: string) => Date +>x : string + +export function transformDatesForAPI( +>transformDatesForAPI : (start: string, end?: T) => HelperCond + + start: string, +>start : string + + end?: T +>end : T | undefined + +): HelperCond { +>start : Date +>end : Date +>start : Date +>end : undefined + + return end !== undefined ? // Ok +>end !== undefined ? // Ok { start: mapDateForAPI(start), end: mapDateForAPI(end), } : { start: mapDateForAPI(start), end: undefined } : { start: Date; end: Date; } | { start: Date; end: undefined; } +>end !== undefined : boolean +>end : T | undefined +>undefined : undefined + { +>{ start: mapDateForAPI(start), end: mapDateForAPI(end), } : { start: Date; end: Date; } + + start: mapDateForAPI(start), +>start : Date +>mapDateForAPI(start) : Date +>mapDateForAPI : (x: string) => Date +>start : string + + end: mapDateForAPI(end), +>end : Date +>mapDateForAPI(end) : Date +>mapDateForAPI : (x: string) => Date +>end : string + + } : + { +>{ start: mapDateForAPI(start), end: undefined } : { start: Date; end: undefined; } + + start: mapDateForAPI(start), +>start : Date +>mapDateForAPI(start) : Date +>mapDateForAPI : (x: string) => Date +>start : string + + end: undefined +>end : undefined +>undefined : undefined + + }; +} + +// File: Rocket.Chat/packages/agenda/src/Agenda.ts +type RepeatOptions = object; +>RepeatOptions : object + +type Job = object; +>Job : object + +type IJob = { data: object }; +>IJob : { data: object; } +>data : object + +class NewAgenda { +>NewAgenda : NewAgenda + + public async _createIntervalJob(interval: string | number, name: string, data: IJob['data'], options: RepeatOptions): Promise { return undefined as any; } +>_createIntervalJob : (interval: string | number, name: string, data: IJob['data'], options: RepeatOptions) => Promise +>interval : string | number +>name : string +>data : object +>options : object +>undefined as any : any +>undefined : undefined + + private _createIntervalJobs( +>_createIntervalJobs : (interval: string | number, names: string[], data: IJob['data'], options: RepeatOptions) => Promise | undefined + + interval: string | number, +>interval : string | number + + names: string[], +>names : string[] + + data: IJob['data'], +>data : object + + options: RepeatOptions, +>options : object + + ): Promise | undefined { return undefined as any; } +>undefined as any : any +>undefined : undefined + + public async newEvery( +>newEvery : (interval: string | number, name: T, data: IJob['data'], options: RepeatOptions) => Promise> + + interval: string | number, +>interval : string | number + + name: T, +>name : T + + data: IJob['data'], +>data : object + + options: RepeatOptions): Promise> { +>options : object + + if (typeof name === 'string') { +>typeof name === 'string' : boolean +>typeof name : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>name : T +>'string' : "string" + + return this._createIntervalJob(interval, name, data, options); // Ok +>this._createIntervalJob(interval, name, data, options) : Promise +>this._createIntervalJob : (interval: string | number, name: string, data: object, options: object) => Promise +>this : this +>_createIntervalJob : (interval: string | number, name: string, data: object, options: object) => Promise +>interval : string | number +>name : string +>data : object +>options : object + } + + if (Array.isArray(name)) { +>Array.isArray(name) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>name : string[] + + return this._createIntervalJobs(interval, name, data, options); // Ok +>this._createIntervalJobs(interval, name, data, options) : Promise | undefined +>this._createIntervalJobs : (interval: string | number, names: string[], data: object, options: object) => Promise | undefined +>this : this +>_createIntervalJobs : (interval: string | number, names: string[], data: object, options: object) => Promise | undefined +>interval : string | number +>name : string[] +>data : object +>options : object + + // Possible bug in original: createIntervalJobs can return undefined, but the original overload did not acount for that. + } + + throw new Error('Unexpected error: Invalid job name(s)'); +>new Error('Unexpected error: Invalid job name(s)') : Error +>Error : ErrorConstructor +>'Unexpected error: Invalid job name(s)' : "Unexpected error: Invalid job name(s)" + } +} + +// File: angular/packages/common/src/pipes/case_conversion_pipes.ts + +function transform1(value: T): HelperCond { +>transform1 : (value: T) => HelperCond +>value : T + + if (value == null) return null; // Ok +>value == null : boolean +>value : T + + if (typeof value !== 'string') { +>typeof value !== 'string' : boolean +>typeof value : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>value : NonNullable +>'string' : "string" + + throw new Error(); +>new Error() : Error +>Error : ErrorConstructor + } + return value.toLowerCase(); // Ok +>value.toLowerCase() : string +>value.toLowerCase : () => string +>value : string +>toLowerCase : () => string +} + + + + diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index 4bea67716a567..8431f352484a9 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -302,4 +302,29 @@ function voidRet(x: T): T extends {} ? void return; // Ok } return 1; // Ok +} + +function woo(x: T, y: U): +T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 { + if (typeof x === "number" && typeof y === "string") { + return 1; // Error + } + return undefined as any; +} + +function ttt(x: T, y: U): +T extends string +? number extends string + ? 6 + : U extends string + ? 1 + : 2 +: U extends number + ? 3 + : 4 { + if (typeof x === "string" && typeof y === "string") { + return 1; // Ok + } + + return undefined as any; } \ No newline at end of file diff --git a/tests/cases/compiler/dependentReturnType3.ts b/tests/cases/compiler/dependentReturnType3.ts new file mode 100644 index 0000000000000..2c0e021323e0f --- /dev/null +++ b/tests/cases/compiler/dependentReturnType3.ts @@ -0,0 +1,215 @@ +// @strict: true +// @noEmit: true +// @target: ES6 + +// Adapted from ts-error-deltas repos + +type HelperCond = + T extends A + ? R1 + : T extends B + ? R2 + : (R1 | R2); + + +// File: Rocket.Chat/apps/meteor/app/katex/client/index.ts +interface IMessage { + html?: string; + tokens?: {}[]; +} + +class NewKatex { + render(s: string): string { + return ""; + } + + renderMessage(message: T): + T extends string + ? string + : T extends IMessage + ? IMessage + : (string | IMessage) { + if (typeof message === 'string') { + return this.render(message); // Ok + } + + if (!message.html?.trim()) { + return message; // Ok + } + + if (!message.tokens) { + message.tokens = []; + } + + message.html = this.render(message.html); + return message; // Ok + } +} + +export function createKatexMessageRendering( + options: { + dollarSyntax: boolean; + parenthesisSyntax: boolean; + }, + _isMessage: T, +): T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : (((message: string) => string) | ((message: IMessage) => IMessage)) { + const instance = new NewKatex(); + if (_isMessage) { + return (message: IMessage): IMessage => instance.renderMessage(message); // Ok + } + return (message: string): string => instance.renderMessage(message); // Ok +} + +// File: Rocket.Chat/apps/meteor/app/settings/lib/settings.ts +type SettingComposedValue = { key: string; value: T }; +type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void; + +type SettingValue = object; +declare const Meteor: { settings: { [s: string]: any } }; +declare const _: { isRegExp(x: unknown): x is RegExp; }; +declare function takesRegExp(x: RegExp): void; +declare function takesString(x: string): void; + +class NewSettingsBase { + public newGet( + _id: I, + callback?: C, + ): HelperCond[]>> { + if (callback !== undefined) { + if (!Meteor.settings) { + return; // Ok + } + if (_id === '*') { + return Object.keys(Meteor.settings).forEach((key) => { // Ok + const value = Meteor.settings[key]; + callback(key, value); + }); + } + if (_.isRegExp(_id) && Meteor.settings) { + return Object.keys(Meteor.settings).forEach((key) => { // Ok + if (!_id.test(key)) { + return; + } + const value = Meteor.settings[key]; + callback(key, value); + }); + } + + if (typeof _id === 'string') { + const value = Meteor.settings[_id]; + if (value != null) { + callback(_id, Meteor.settings[_id]); + } + return; // Ok + } + + return; // Ok, needed for exhaustiveness check + } + + if (!Meteor.settings) { // Wrong: we don't know that _id is string here, cannot return undefined + return undefined; // Error + } + + if (_.isRegExp(_id)) { + return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { + const value = Meteor.settings[key]; + if (_id.test(key)) { + items.push({ + key, + value, + }); + } + return items; + }, []); // Ok + } + + return Meteor.settings?.[_id]; // Error + // The indexing currently doesn't work because it doesn't use the narrowed type of `_id`. + } +} + +// File: Rocket.Chat/apps/meteor/app/ui-utils/client/lib/messageBox.ts +type MessageBoxAction = object; + +function getWithBug(group: T): +HelperCond> { + if (!group) { + return {} as Record; // Error, could fall into this branch when group is empty string + } + + return [] as MessageBoxAction[]; // Ok +} + +function getWithoutBug(group: T): +HelperCond> { + if (group === undefined) { + return {} as Record; // Ok + } + + return [] as MessageBoxAction[]; // Ok +} + +// File: Rocket.Chat/apps/meteor/ee/server/lib/engagementDashboard/date.ts +declare function mapDateForAPI(x: string): Date; +export function transformDatesForAPI( + start: string, + end?: T +): HelperCond { + return end !== undefined ? // Ok + { + start: mapDateForAPI(start), + end: mapDateForAPI(end), + } : + { + start: mapDateForAPI(start), + end: undefined + }; +} + +// File: Rocket.Chat/packages/agenda/src/Agenda.ts +type RepeatOptions = object; +type Job = object; +type IJob = { data: object }; +class NewAgenda { + public async _createIntervalJob(interval: string | number, name: string, data: IJob['data'], options: RepeatOptions): Promise { return undefined as any; } + private _createIntervalJobs( + interval: string | number, + names: string[], + data: IJob['data'], + options: RepeatOptions, + ): Promise | undefined { return undefined as any; } + + public async newEvery( + interval: string | number, + name: T, + data: IJob['data'], + options: RepeatOptions): Promise> { + if (typeof name === 'string') { + return this._createIntervalJob(interval, name, data, options); // Ok + } + + if (Array.isArray(name)) { + return this._createIntervalJobs(interval, name, data, options); // Ok + // Possible bug in original: createIntervalJobs can return undefined, but the original overload did not acount for that. + } + + throw new Error('Unexpected error: Invalid job name(s)'); + } +} + +// File: angular/packages/common/src/pipes/case_conversion_pipes.ts + +function transform1(value: T): HelperCond { + if (value == null) return null; // Ok + if (typeof value !== 'string') { + throw new Error(); + } + return value.toLowerCase(); // Ok +} + + + From ee2d1bad15f115ac7c0f213ace2bcfb8118909d7 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 7 Dec 2023 13:19:51 -0800 Subject: [PATCH 15/90] fix optional parameter handling and shadowing --- src/compiler/checker.ts | 28 +++++- .../reference/dependentReturnType1.errors.txt | 62 ++++++++++--- .../reference/dependentReturnType1.symbols | 72 ++++++++++++++++ .../reference/dependentReturnType1.types | 86 +++++++++++++++++++ tests/cases/compiler/dependentReturnType1.ts | 31 +++++++ 5 files changed, 265 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index de2b30d7a1632..31e0d32b31f14 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -43432,13 +43432,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { : node; const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, tp => { const narrowReference = factory.cloneNode(tp.exprName); // Construct a reference that can be narrowed. + // Set the symbol of the synthetic reference. + // This allows us to get the type of the reference at a location where the reference is possibly shadowed. + getNodeLinks(narrowReference).resolvedSymbol = getResolvedSymbol(tp.exprName); setParent(narrowReference, narrowParent.parent); setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); narrowReference.flowNode = (narrowParent as HasFlowNode).flowNode; // >> TODO: this call to checkExpression might report errors, // >> and so might throw when trying to get span for fakeName. // >> TODO: also, it shouldn't throw errors. Maybe we can reuse `CheckMode.TypeOnly`? - const exprType = checkExpression(narrowReference); + // const exprType = checkExpression(narrowReference); + // We don't want to emit errors when getting the type. + const exprType = getTypeOfExpression(narrowReference); // >> TODO: that doesn't work for qualified names // >> TODO: is there a better way of detecting that narrowing will be useless? if (getConstraintOfTypeParameter(tp)) { const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); @@ -43493,7 +43498,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function isQueryTypeParameter(typeParameter: TypeParameter): typeParameter is TypeParameter & { exprName: EntityName } { + function isQueryTypeParameter(typeParameter: TypeParameter): typeParameter is TypeParameter & { exprName: Identifier } { if (isThisTypeParameter(typeParameter)) { return false; } @@ -43509,7 +43514,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } typeParameter.exprName = null; - // It should have a type parameter declaration because it is not a `this` type parameter, and it has a symbol + // Type parameter should have a type parameter declaration because it is not a `this` type parameter, and it has a symbol. const declaration = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; const owner = getTypeParameterOwner(declaration); if (!owner || !isFunctionLikeDeclaration(owner)) { @@ -43524,6 +43529,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { reference.parent.parent === owner && (exprName = getNameOfDeclaration(reference.parent)) && isIdentifier(exprName)) { + // If the parameter is optional, and its type is a type parameter whose constraint doesn't allow for `undefined`, then we can't narrow, because the type parameter doesn't reflect the full type of the parameter. + // For instance, if we have: + // `function f(x?: T): ReturnType { + // if (typeof x === "undefined") { + // ... + // } + // }`, + // The type of `x` is really `T | undefined`, + // and we can't narrow type parameter `T` with type `undefined` inside the `if` statement, because + // `undefined` does not satisfy `T`'s constraint, `string`. + // >> TODO: if we allow the type parameter to be the type of a *property*, we need to update this code because the property could be optional and it could also contain the missing type instead of simply the undefined type. + let constraint; + if (isOptionalParameter(reference.parent) && + (constraint = getConstraintOfTypeParameter(typeParameter)) && + !containsUndefinedType(constraint)) { + return false; + } typeParameter.exprName = exprName; return true; } diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index 5c7f020828e55..0fa9f7a4b14ee 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -11,11 +11,8 @@ dependentReturnType1.ts(118,5): error TS2322: Type 'string' is not assignable to dependentReturnType1.ts(153,13): error TS2322: Type 'string' is not assignable to type 'T extends string ? this : string'. dependentReturnType1.ts(155,9): error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. -dependentReturnType1.ts(159,9): error TS2322: Type 'this' is not assignable to type '(T extends string ? this : string) & (T extends string ? this : string)'. - Type 'Unnamed' is not assignable to type '(T extends string ? this : string) & (T extends string ? this : string)'. - Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. - Type 'this' is not assignable to type 'T extends string ? this : string'. - Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. +dependentReturnType1.ts(159,9): error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. + Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. dependentReturnType1.ts(174,13): error TS2322: Type 'this' is not assignable to type 'string'. Type 'Unnamed' is not assignable to type 'string'. dependentReturnType1.ts(177,9): error TS2322: Type 'T & {}' is not assignable to type 'this'. @@ -31,9 +28,14 @@ dependentReturnType1.ts(271,9): error TS2322: Type '2' is not assignable to type dependentReturnType1.ts(273,5): error TS2322: Type '0' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. dependentReturnType1.ts(291,9): error TS2322: Type 'string' is not assignable to type 'string[]'. dependentReturnType1.ts(307,9): error TS2322: Type '1' is not assignable to type 'T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4'. +dependentReturnType1.ts(333,9): error TS2322: Type '2' is not assignable to type 'T extends string ? 1 : T extends undefined ? 2 : 1 | 2'. +dependentReturnType1.ts(335,5): error TS2322: Type '1' is not assignable to type 'T extends string ? 1 : T extends undefined ? 2 : 1 | 2'. +dependentReturnType1.ts(343,13): error TS2322: Type 'number' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : 1 | 2'. +dependentReturnType1.ts(345,9): error TS2322: Type 'string' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : 1 | 2'. +dependentReturnType1.ts(353,13): error TS2322: Type 'number' is not assignable to type 'string'. -==== dependentReturnType1.ts (25 errors) ==== +==== dependentReturnType1.ts (30 errors) ==== interface A { 1: number; 2: string; @@ -218,11 +220,8 @@ dependentReturnType1.ts(307,9): error TS2322: Type '1' is not assignable to type nameWithError(name?: T): T extends string ? this : string { return this; // Error: Investigate error message ~~~~~~ -!!! error TS2322: Type 'this' is not assignable to type '(T extends string ? this : string) & (T extends string ? this : string)'. -!!! error TS2322: Type 'Unnamed' is not assignable to type '(T extends string ? this : string) & (T extends string ? this : string)'. -!!! error TS2322: Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. -!!! error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. -!!! error TS2322: Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. +!!! error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. +!!! error TS2322: Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. } // Good conditional @@ -418,4 +417,45 @@ dependentReturnType1.ts(307,9): error TS2322: Type '1' is not assignable to type } return undefined as any; + } + + // We won't narrow `T` because it refers to the type of an optional parameter but doesn't allow for narrowing with `undefined` + function opt(x?: T): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { + if (typeof x === "undefined") { + x; + return 2; + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type 'T extends string ? 1 : T extends undefined ? 2 : 1 | 2'. + } + return 1; + ~~~~~~ +!!! error TS2322: Type '1' is not assignable to type 'T extends string ? 1 : T extends undefined ? 2 : 1 | 2'. + } + + // Shadowing of the narrowed reference + function g(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { + if (true) { + let x: number = Math.random() ? 1 : 2; + if (x === 1) { + return 1; // Error + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : 1 | 2'. + } + return ""; // Error + ~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : 1 | 2'. + } + } + + function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { + if (x === 2) { + let x: number = Math.random() ? 1 : 2; + if (x === 1) { + return 1; // Error + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + } + return ""; // Ok + } + return 0; // Ok } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 106c2047082a2..4f50d2ca3fd0f 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -932,3 +932,75 @@ T extends string return undefined as any; >undefined : Symbol(undefined) } + +// We won't narrow `T` because it refers to the type of an optional parameter but doesn't allow for narrowing with `undefined` +function opt(x?: T): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { +>opt : Symbol(opt, Decl(dependentReturnType1.ts, 326, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 329, 13)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 329, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 329, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 329, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 329, 13)) + + if (typeof x === "undefined") { +>x : Symbol(x, Decl(dependentReturnType1.ts, 329, 31)) + + x; +>x : Symbol(x, Decl(dependentReturnType1.ts, 329, 31)) + + return 2; + } + return 1; +} + +// Shadowing of the narrowed reference +function g(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { +>g : Symbol(g, Decl(dependentReturnType1.ts, 335, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 338, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 338, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 338, 11)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 338, 11)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 338, 11)) + + if (true) { + let x: number = Math.random() ? 1 : 2; +>x : Symbol(x, Decl(dependentReturnType1.ts, 340, 11)) +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + if (x === 1) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 340, 11)) + + return 1; // Error + } + return ""; // Error + } +} + +function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { +>h : Symbol(h, Decl(dependentReturnType1.ts, 346, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 348, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 348, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 348, 11)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 348, 11)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 348, 11)) + + if (x === 2) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 348, 28)) + + let x: number = Math.random() ? 1 : 2; +>x : Symbol(x, Decl(dependentReturnType1.ts, 350, 11)) +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + if (x === 1) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 350, 11)) + + return 1; // Error + } + return ""; // Ok + } + return 0; // Ok +} diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 081b049066967..dcdeee9c81b1b 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -905,3 +905,89 @@ T extends string >undefined as any : any >undefined : undefined } + +// We won't narrow `T` because it refers to the type of an optional parameter but doesn't allow for narrowing with `undefined` +function opt(x?: T): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { +>opt : (x?: T) => T extends string ? 1 : T extends undefined ? 2 : 1 | 2 +>x : T | undefined + + if (typeof x === "undefined") { +>typeof x === "undefined" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T | undefined +>"undefined" : "undefined" + + x; +>x : undefined + + return 2; +>2 : 2 + } + return 1; +>1 : 1 +} + +// Shadowing of the narrowed reference +function g(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { +>g : (x: T) => T extends 1 ? number : T extends 2 ? string : 1 | 2 +>x : T + + if (true) { +>true : true + + let x: number = Math.random() ? 1 : 2; +>x : number +>Math.random() ? 1 : 2 : 1 | 2 +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>1 : 1 +>2 : 2 + + if (x === 1) { +>x === 1 : boolean +>x : number +>1 : 1 + + return 1; // Error +>1 : 1 + } + return ""; // Error +>"" : "" + } +} + +function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { +>h : (x: T) => T extends 1 ? number : T extends 2 ? string : 1 | 2 +>x : T + + if (x === 2) { +>x === 2 : boolean +>x : T +>2 : 2 + + let x: number = Math.random() ? 1 : 2; +>x : number +>Math.random() ? 1 : 2 : 1 | 2 +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>1 : 1 +>2 : 2 + + if (x === 1) { +>x === 1 : boolean +>x : number +>1 : 1 + + return 1; // Error +>1 : 1 + } + return ""; // Ok +>"" : "" + } + return 0; // Ok +>0 : 0 +} diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index 8431f352484a9..8fe2240f42065 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -327,4 +327,35 @@ T extends string } return undefined as any; +} + +// We won't narrow `T` because it refers to the type of an optional parameter but doesn't allow for narrowing with `undefined` +function opt(x?: T): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { + if (typeof x === "undefined") { + x; + return 2; + } + return 1; +} + +// Shadowing of the narrowed reference +function g(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { + if (true) { + let x: number = Math.random() ? 1 : 2; + if (x === 1) { + return 1; // Error + } + return ""; // Error + } +} + +function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { + if (x === 2) { + let x: number = Math.random() ? 1 : 2; + if (x === 1) { + return 1; // Error + } + return ""; // Ok + } + return 0; // Ok } \ No newline at end of file From b8ce4903dac236e2f426bc5f0d4555c71592e46c Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 21 Dec 2023 13:58:20 -0800 Subject: [PATCH 16/90] fix skip parentheses --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 31e0d32b31f14..4deee1677fb9c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -43414,7 +43414,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkReturnStatementExpression(expr: Expression | undefined): void { let actualReturnType = unwrappedReturnType; if (expr) { - expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + expr = skipParentheses(expr); if (isConditionalExpression(expr)) { return checkReturnConditionalExpression(expr); } From 24397676014f3e4b5b6ffbe112a57e33c9f293d4 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 21 Dec 2023 17:37:21 -0800 Subject: [PATCH 17/90] fix updates to binder, cleanup in checker --- src/compiler/binder.ts | 32 +++++++++++++++----------------- src/compiler/checker.ts | 31 ++++++++++++++++++------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index c1db297e63468..b2f0d9f384bfb 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -195,7 +195,6 @@ import { isOmittedExpression, isOptionalChain, isOptionalChainRoot, - isOuterExpression, isOutermostOptionalChain, isParameterDeclaration, isParameterPropertyDeclaration, @@ -316,6 +315,7 @@ import { unreachableCodeIsError, unusedLabelIsError, VariableDeclaration, + walkUpParenthesizedExpressions, WhileStatement, WithStatement, } from "./_namespaces/ts"; @@ -1974,38 +1974,36 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { const postExpressionLabel = createBranchLabel(); bindCondition(node.condition, trueLabel, falseLabel); currentFlow = finishFlowLabel(trueLabel); - bind(node.questionToken); - bind(node.whenTrue); if (isInReturnStatement) { - (node.whenTrue as Node as FlowContainer).flowNode = currentFlow; + const expr = skipParentheses(node.whenTrue); + (expr as Node as FlowContainer).flowNode = currentFlow; } + bind(node.questionToken); + bind(node.whenTrue); addAntecedent(postExpressionLabel, currentFlow); currentFlow = finishFlowLabel(falseLabel); - bind(node.colonToken); - bind(node.whenFalse); if (isInReturnStatement) { - (node.whenFalse as Node as FlowContainer).flowNode = currentFlow; + const expr = skipParentheses(node.whenFalse); + (expr as Node as FlowContainer).flowNode = currentFlow; } + bind(node.colonToken); + bind(node.whenFalse); addAntecedent(postExpressionLabel, currentFlow); currentFlow = finishFlowLabel(postExpressionLabel); } function isConditionalExpressionInReturnStatement(node: ConditionalExpression) { - const returnStmt = findAncestor(node.parent, isReturnStatement); - if (!returnStmt) { - return false; - } let n: Node = node; - while (n !== returnStmt) { + while (n) { + if (isReturnStatement(n)) { + return true; + } if (!isConditionalExpression(n)) { return false; } - n = n.parent; - while (isOuterExpression(n)) { // >> TODO: I think we should only skip parentheses - n = n.parent; - } + n = walkUpParenthesizedExpressions(n.parent); } - return true; + return false; } function bindInitializedVariableFlow(node: VariableDeclaration | ArrayBindingElement) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4deee1677fb9c..73cdc6d341d1c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -43425,25 +43425,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); if (queryTypeParameters) { - const narrowParent = expr - ? isConditionalExpression(expr.parent) - ? expr - : expr.parent - : node; + // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: + // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: + // `function foo(...) { + // return cond ? |expr| : ... + // }` + // - When the return expression is undefined, or it is defined and it is not one of the branches of a conditional expression, then the position is the return statement itself: + // `function foo(...) { + // |return expr;| + // }` + // or + // `function foo(...) { + // |return;| + // }` + const narrowPosition = expr && isConditionalExpression(walkUpParenthesizedExpressions(expr.parent)) ? + expr : node; const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, tp => { const narrowReference = factory.cloneNode(tp.exprName); // Construct a reference that can be narrowed. // Set the symbol of the synthetic reference. // This allows us to get the type of the reference at a location where the reference is possibly shadowed. getNodeLinks(narrowReference).resolvedSymbol = getResolvedSymbol(tp.exprName); - setParent(narrowReference, narrowParent.parent); + setParent(narrowReference, narrowPosition.parent); setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); - narrowReference.flowNode = (narrowParent as HasFlowNode).flowNode; - // >> TODO: this call to checkExpression might report errors, - // >> and so might throw when trying to get span for fakeName. - // >> TODO: also, it shouldn't throw errors. Maybe we can reuse `CheckMode.TypeOnly`? - // const exprType = checkExpression(narrowReference); - // We don't want to emit errors when getting the type. - const exprType = getTypeOfExpression(narrowReference); // >> TODO: that doesn't work for qualified names + narrowReference.flowNode = (narrowPosition as HasFlowNode).flowNode; + const exprType = getTypeOfExpression(narrowReference); // >> TODO: is there a better way of detecting that narrowing will be useless? if (getConstraintOfTypeParameter(tp)) { const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); From 5e8ca3ebdbf0cde7bdaf03e79fbc7d627fe0c09a Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 22 Dec 2023 11:56:57 -0800 Subject: [PATCH 18/90] HasFlowNode update & fixes --- src/compiler/binder.ts | 18 +---------------- src/compiler/checker.ts | 11 +++++------ src/compiler/types.ts | 3 ++- src/compiler/utilities.ts | 41 +++++++++++++++++++++++++++++++++------ src/services/services.ts | 1 + 5 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index c638984e3b95e..b45b539cd11f2 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -140,7 +140,7 @@ import { isBooleanLiteral, isCallExpression, isClassStaticBlockDeclaration, - isConditionalExpression, + isConditionalExpressionInReturnStatement, isConditionalTypeNode, IsContainer, isDeclaration, @@ -211,7 +211,6 @@ import { isPrototypeAccess, isPushOrUnshiftIdentifier, isRequireCall, - isReturnStatement, isShorthandPropertyAssignment, isSignedNumericLiteral, isSourceFile, @@ -317,7 +316,6 @@ import { unreachableCodeIsError, unusedLabelIsError, VariableDeclaration, - walkUpParenthesizedExpressions, WhileStatement, WithStatement, } from "./_namespaces/ts"; @@ -2003,20 +2001,6 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { currentFlow = finishFlowLabel(postExpressionLabel); } - function isConditionalExpressionInReturnStatement(node: ConditionalExpression) { - let n: Node = node; - while (n) { - if (isReturnStatement(n)) { - return true; - } - if (!isConditionalExpression(n)) { - return false; - } - n = walkUpParenthesizedExpressions(n.parent); - } - return false; - } - function bindInitializedVariableFlow(node: VariableDeclaration | ArrayBindingElement) { const name = !isOmittedExpression(node) ? node.name : undefined; if (isBindingPattern(name)) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8bd770ce5690c..2b8e36d2f5c29 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -382,7 +382,6 @@ import { hasEffectiveReadonlyModifier, HasExpressionInitializer, hasExtension, - HasFlowNode, HasIllegalDecorators, HasIllegalModifiers, HasInitializer, @@ -18742,7 +18741,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getNarrowConditionalType(root: ConditionalRoot, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { let result; - let originalRoot = root; + const originalRoot = root; // We loop here for an immediately nested conditional type in the false position, effectively treating // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for @@ -44125,7 +44124,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); - if (queryTypeParameters) { + if (queryTypeParameters && queryTypeParameters.length) { // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: // `function foo(...) { @@ -44148,7 +44147,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getNodeLinks(narrowReference).resolvedSymbol = getResolvedSymbol(tp.exprName); setParent(narrowReference, narrowPosition.parent); setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); - narrowReference.flowNode = (narrowPosition as HasFlowNode).flowNode; + narrowReference.flowNode = narrowPosition.flowNode!; const exprType = getTypeOfExpression(narrowReference); // >> TODO: is there a better way of detecting that narrowing will be useless? if (getConstraintOfTypeParameter(tp)) { @@ -44215,11 +44214,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (typeParameter.exprName) { return true; } - if (typeParameter.exprName === null) { + if (typeParameter.exprName === null) { // eslint-disable-line no-null/no-null return false; } - typeParameter.exprName = null; + typeParameter.exprName = null; // eslint-disable-line no-null/no-null // Type parameter should have a type parameter declaration because it is not a `this` type parameter, and it has a symbol. const declaration = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; const owner = getTypeParameterOwner(declaration); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ab6229d1b6796..02903fb568894 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -967,6 +967,7 @@ export interface FlowContainer extends Node { /** @internal */ export type HasFlowNode = + | Expression | Identifier | ThisExpression | SuperExpression @@ -2364,7 +2365,7 @@ export interface TemplateLiteralTypeSpan extends TypeNode { // checker actually thinks you have something of the right type. Note: the brands are // never actually given values. At runtime they have zero cost. -export interface Expression extends Node { +export interface Expression extends FlowContainer, Node { _expressionBrand: any; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 1e81b93088ca7..7aed6e26510fc 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -253,6 +253,7 @@ import { isClassStaticBlockDeclaration, isCommaListExpression, isComputedPropertyName, + isConditionalExpression, isConstructorDeclaration, isDeclaration, isDecorator, @@ -261,6 +262,7 @@ import { isEnumMember, isExportAssignment, isExportDeclaration, + isExpression, isExpressionStatement, isExpressionWithTypeArguments, isExternalModule, @@ -331,6 +333,7 @@ import { isPropertyName, isPropertySignature, isQualifiedName, + isReturnStatement, isRootedDiskPath, isSetAccessorDeclaration, isShiftOperatorOrHigher, @@ -4157,22 +4160,48 @@ function getNestedModuleDeclaration(node: Node): Node | undefined { : undefined; } +/** @internal */ +export function isConditionalExpressionInReturnStatement(node: Expression) { + let n: Node = node; + while (n) { + if (isReturnStatement(n)) { + return true; + } + if (!isConditionalExpression(n)) { + return false; + } + n = walkUpParenthesizedExpressions(n.parent); + } + return false; +} + /** @internal */ export function canHaveFlowNode(node: Node): node is HasFlowNode { if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement) { return true; } + // >> TODO: how precise do we want to be here? + if (isExpression(node)) { + return true; + } + // let parent; + // if (isExpression(node) + // && isConditionalExpression(parent = walkUpParenthesizedExpressions(node.parent)) + // && isConditionalExpressionInReturnStatement(parent)) { + // return true; + // } + switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.ThisKeyword: - case SyntaxKind.SuperKeyword: + // case SyntaxKind.Identifier: + // case SyntaxKind.ThisKeyword: + // case SyntaxKind.SuperKeyword: + // case SyntaxKind.ElementAccessExpression: + // case SyntaxKind.PropertyAccessExpression: + // case SyntaxKind.FunctionExpression: case SyntaxKind.QualifiedName: case SyntaxKind.MetaProperty: - case SyntaxKind.ElementAccessExpression: - case SyntaxKind.PropertyAccessExpression: case SyntaxKind.BindingElement: - case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: diff --git a/src/services/services.ts b/src/services/services.ts index 812eafd2c940e..e5325e7aeda02 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -764,6 +764,7 @@ IdentifierObject.prototype.kind = SyntaxKind.Identifier; class PrivateIdentifierObject extends TokenOrIdentifierObject implements PrivateIdentifier { public override kind: SyntaxKind.PrivateIdentifier = SyntaxKind.PrivateIdentifier; public escapedText!: __String; + declare _flowContainerBrand: any; declare _primaryExpressionBrand: any; declare _memberExpressionBrand: any; declare _leftHandSideExpressionBrand: any; From 534cc38847246051ff301bd1401b65abc023e71b Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 22 Dec 2023 16:05:36 -0800 Subject: [PATCH 19/90] cleanup; always check conditional expression's condition inside return statement --- src/compiler/checker.ts | 56 ++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2b8e36d2f5c29..89b9767b41c35 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19989,10 +19989,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { /** * This is similar to `instantiateType`, but with behavior specific to narrowing a return type based on control flow for type parameters. */ - function instantiateNarrowType(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, noTopLevel?: boolean): Type; - function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, noTopLevel?: boolean): Type | undefined; - function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, noTopLevel = false): Type | undefined { - return type ? instantiateNarrowTypeWithAlias(type, narrowMapper, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, noTopLevel) : type; + function instantiateNarrowType(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type; + function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type | undefined; + function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type | undefined { + return type ? instantiateNarrowTypeWithAlias(type, narrowMapper, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : type; } function instantiateNarrowTypeWithAlias( @@ -20000,8 +20000,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { narrowMapper: TypeMapper, mapper: TypeMapper | undefined, aliasSymbol: Symbol | undefined, - aliasTypeArguments: readonly Type[] | undefined, - _noTopLevel: boolean): Type { + aliasTypeArguments: readonly Type[] | undefined): Type { if (!couldContainTypeVariables(type)) { return type; } @@ -20016,7 +20015,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { totalInstantiationCount++; instantiationCount++; instantiationDepth++; - const result = instantiateNarrowTypeWorker(type, narrowMapper, mapper, aliasSymbol, aliasTypeArguments /*, noTopLevel*/); + const result = instantiateNarrowTypeWorker(type, narrowMapper, mapper, aliasSymbol, aliasTypeArguments); instantiationDepth--; return result; } @@ -20031,8 +20030,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { narrowMapper: TypeMapper, mapper: TypeMapper | undefined, aliasSymbol: Symbol | undefined, - aliasTypeArguments: readonly Type[] | undefined, - ): Type { + aliasTypeArguments: readonly Type[] | undefined): Type { type = instantiateType(type, mapper); const flags = type.flags; if (flags & TypeFlags.IndexedAccess) { @@ -44114,7 +44112,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkReturnStatementExpression(expr: Expression | undefined): void { let actualReturnType = unwrappedReturnType; if (expr) { - expr = skipParentheses(expr); + expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); if (isConditionalExpression(expr)) { return checkReturnConditionalExpression(expr); } @@ -44124,22 +44122,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); - if (queryTypeParameters && queryTypeParameters.length) { - // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: - // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: - // `function foo(...) { - // return cond ? |expr| : ... - // }` - // - When the return expression is undefined, or it is defined and it is not one of the branches of a conditional expression, then the position is the return statement itself: - // `function foo(...) { - // |return expr;| - // }` - // or - // `function foo(...) { - // |return;| - // }` - const narrowPosition = expr && isConditionalExpression(walkUpParenthesizedExpressions(expr.parent)) ? - expr : node; + // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: + // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: + // `function foo(...) { + // return cond ? |expr| : ... + // }` + // - When the return expression is undefined, or it is defined and it is not one of the branches of a conditional expression, then the position is the return statement itself: + // `function foo(...) { + // |return expr;| + // }` + // or + // `function foo(...) { + // |return;| + // }` + const narrowPosition = expr && isConditionalExpression(walkUpParenthesizedExpressions(expr.parent)) ? + expr : node; + if (queryTypeParameters && queryTypeParameters.length && narrowPosition.flowNode) { const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, tp => { const narrowReference = factory.cloneNode(tp.exprName); // Construct a reference that can be narrowed. // Set the symbol of the synthetic reference. @@ -44147,7 +44145,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getNodeLinks(narrowReference).resolvedSymbol = getResolvedSymbol(tp.exprName); setParent(narrowReference, narrowPosition.parent); setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); - narrowReference.flowNode = narrowPosition.flowNode!; + narrowReference.flowNode = narrowPosition.flowNode; const exprType = getTypeOfExpression(narrowReference); // >> TODO: is there a better way of detecting that narrowing will be useless? if (getConstraintOfTypeParameter(tp)) { @@ -44162,8 +44160,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { actualReturnType = instantiateNarrowType( unwrappedReturnType, narrowMapper, - /*mapper*/ undefined, - /*noTopLevel*/ true, + /*mapper*/ undefined ); } @@ -44192,6 +44189,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function checkReturnConditionalExpression(expr: ConditionalExpression): void { + checkExpression(expr.condition); checkReturnStatementExpression(expr.whenTrue); checkReturnStatementExpression(expr.whenFalse); } From 1dc9ab55c873cf6a4f00990526df9843ec7456d6 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 4 Jan 2024 12:35:56 -0800 Subject: [PATCH 20/90] update test after merging --- tests/baselines/reference/dependentReturnType3.types | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/dependentReturnType3.types b/tests/baselines/reference/dependentReturnType3.types index 42a1cf0512f5e..be51c9981d11b 100644 --- a/tests/baselines/reference/dependentReturnType3.types +++ b/tests/baselines/reference/dependentReturnType3.types @@ -139,18 +139,18 @@ export function createKatexMessageRendering( >(message: IMessage): IMessage => instance.renderMessage(message) : (message: IMessage) => IMessage >message : IMessage >instance.renderMessage(message) : IMessage ->instance.renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : string | IMessage +>instance.renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : string | IMessage >instance : NewKatex ->renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : string | IMessage +>renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : string | IMessage >message : IMessage } return (message: string): string => instance.renderMessage(message); // Ok >(message: string): string => instance.renderMessage(message) : (message: string) => string >message : string >instance.renderMessage(message) : string ->instance.renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : string | IMessage +>instance.renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : string | IMessage >instance : NewKatex ->renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : string | IMessage +>renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : string | IMessage >message : string } From a94bc2316c91750d032229c00326c4abe334c500 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 4 Jan 2024 12:37:23 -0800 Subject: [PATCH 21/90] maybe make binder change faster --- src/compiler/binder.ts | 11 ++++++++--- src/compiler/utilities.ts | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b45b539cd11f2..fef7969e26a16 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -540,6 +540,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { var preSwitchCaseFlow: FlowNode | undefined; var activeLabelList: ActiveLabel | undefined; var hasExplicitReturn: boolean; + var inReturnStatement: boolean; // state used for emit helpers var emitFlags: NodeFlags; @@ -616,6 +617,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { currentExceptionTarget = undefined; activeLabelList = undefined; hasExplicitReturn = false; + inReturnStatement = false; inAssignmentPattern = false; emitFlags = NodeFlags.None; } @@ -1546,7 +1548,10 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { } function bindReturnOrThrow(node: ReturnStatement | ThrowStatement): void { + const oldInReturnStatement = inReturnStatement; + inReturnStatement = true; bind(node.expression); + inReturnStatement = oldInReturnStatement; if (node.kind === SyntaxKind.ReturnStatement) { hasExplicitReturn = true; if (currentReturnTarget) { @@ -1977,13 +1982,13 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { } function bindConditionalExpressionFlow(node: ConditionalExpression) { - const isInReturnStatement = isConditionalExpressionInReturnStatement(node); + const isConditionalInReturn = inReturnStatement && isConditionalExpressionInReturnStatement(node); const trueLabel = createBranchLabel(); const falseLabel = createBranchLabel(); const postExpressionLabel = createBranchLabel(); bindCondition(node.condition, trueLabel, falseLabel); currentFlow = finishFlowLabel(trueLabel); - if (isInReturnStatement) { + if (isConditionalInReturn) { const expr = skipParentheses(node.whenTrue); (expr as Node as FlowContainer).flowNode = currentFlow; } @@ -1991,7 +1996,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { bind(node.whenTrue); addAntecedent(postExpressionLabel, currentFlow); currentFlow = finishFlowLabel(falseLabel); - if (isInReturnStatement) { + if (isConditionalInReturn) { const expr = skipParentheses(node.whenFalse); (expr as Node as FlowContainer).flowNode = currentFlow; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7aed6e26510fc..8fa6b86ba97eb 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2295,7 +2295,7 @@ export function getErrorSpanForNode(sourceFile: SourceFile, node: Node): TextSpa Debug.assert(pos <= errorNode.end, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809"); } - return createTextSpanFromBounds(pos, errorNode.end); // >> TODO: this might fail if the node is synthetic: don't error then + return createTextSpanFromBounds(pos, errorNode.end); } /** @internal */ From 046b53c3a67ce63ff3bc1e5b007e16eff1877a3b Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 4 Jan 2024 14:09:14 -0800 Subject: [PATCH 22/90] don't narrow if conditional has infer type params --- src/compiler/checker.ts | 4 +-- .../reference/dependentReturnType1.errors.txt | 19 ++++++++++++-- .../reference/dependentReturnType1.symbols | 24 +++++++++++++++++ .../reference/dependentReturnType1.types | 26 +++++++++++++++++++ tests/cases/compiler/dependentReturnType1.ts | 11 +++++++- 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 89b9767b41c35..6fa7fa422d01e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18854,7 +18854,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { break; } } - // Return a deferred type for a check that is neither definitely true nor definitely false + // Return a deferred type for a check that is not definitely true result = createType(TypeFlags.Conditional) as ConditionalType; result.root = originalRoot; result.checkType = instantiateType(originalRoot.checkType, mapper); @@ -20071,7 +20071,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getNarrowableCheckTypeVariable(root: ConditionalRoot, mapper: TypeMapper | undefined): TypeParameter | undefined { - if (!root.isDistributive) { + if (!root.isDistributive || root.inferTypeParameters) { return; } const checkType = mapper ? getMappedType(root.checkType, mapper) : root.checkType; diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index 0fa9f7a4b14ee..e972fdfc11adc 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -33,9 +33,11 @@ dependentReturnType1.ts(335,5): error TS2322: Type '1' is not assignable to type dependentReturnType1.ts(343,13): error TS2322: Type 'number' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : 1 | 2'. dependentReturnType1.ts(345,9): error TS2322: Type 'string' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : 1 | 2'. dependentReturnType1.ts(353,13): error TS2322: Type 'number' is not assignable to type 'string'. +dependentReturnType1.ts(362,9): error TS2322: Type 'true' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. +dependentReturnType1.ts(364,5): error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. -==== dependentReturnType1.ts (30 errors) ==== +==== dependentReturnType1.ts (32 errors) ==== interface A { 1: number; 2: string; @@ -458,4 +460,17 @@ dependentReturnType1.ts(353,13): error TS2322: Type 'number' is not assignable t return ""; // Ok } return 0; // Ok - } \ No newline at end of file + } + + function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : string | boolean { + if (typeof x === "number") { + return true; + ~~~~~~ +!!! error TS2322: Type 'true' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. + } + return ""; + ~~~~~~ +!!! error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. + } + + const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 4f50d2ca3fd0f..6c27f4a2ba9b7 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -1004,3 +1004,27 @@ function h(x: T): T extends 1 ? number : T extends 2 ? string : } return 0; // Ok } + +function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : string | boolean { +>withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 357, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 359, 48)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 359, 71)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 359, 71)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) + + if (typeof x === "number") { +>x : Symbol(x, Decl(dependentReturnType1.ts, 359, 48)) + + return true; + } + return ""; +} + +const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. +>withInferResult : Symbol(withInferResult, Decl(dependentReturnType1.ts, 366, 5)) +>withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 357, 1)) +>const : Symbol(const) + diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index dcdeee9c81b1b..14ea82b97caed 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -991,3 +991,29 @@ function h(x: T): T extends 1 ? number : T extends 2 ? string : return 0; // Ok >0 : 0 } + +function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : string | boolean { +>withInfer : (x: T) => T extends [infer R] ? R : T extends number ? boolean : string | boolean +>x : T + + if (typeof x === "number") { +>typeof x === "number" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T +>"number" : "number" + + return true; +>true : true + } + return ""; +>"" : "" +} + +const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. +>withInferResult : "a" +>withInfer(["a"] as const) : "a" +>withInfer : (x: T) => T extends [infer R] ? R : T extends number ? boolean : string | boolean +>["a"] as const : ["a"] +>["a"] : ["a"] +>"a" : "a" + diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index 8fe2240f42065..f41485f26399b 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -358,4 +358,13 @@ function h(x: T): T extends 1 ? number : T extends 2 ? string : return ""; // Ok } return 0; // Ok -} \ No newline at end of file +} + +function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : string | boolean { + if (typeof x === "number") { + return true; + } + return ""; +} + +const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. \ No newline at end of file From 5a83feb3e66e62a6adacc80c555ee14f3709f56d Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 4 Jan 2024 14:12:26 -0800 Subject: [PATCH 23/90] don't walk up parents when binding conditional expression --- src/compiler/binder.ts | 6 ++---- src/compiler/utilities.ts | 17 ----------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index fef7969e26a16..d5e87a552acb4 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -140,7 +140,6 @@ import { isBooleanLiteral, isCallExpression, isClassStaticBlockDeclaration, - isConditionalExpressionInReturnStatement, isConditionalTypeNode, IsContainer, isDeclaration, @@ -1982,13 +1981,12 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { } function bindConditionalExpressionFlow(node: ConditionalExpression) { - const isConditionalInReturn = inReturnStatement && isConditionalExpressionInReturnStatement(node); const trueLabel = createBranchLabel(); const falseLabel = createBranchLabel(); const postExpressionLabel = createBranchLabel(); bindCondition(node.condition, trueLabel, falseLabel); currentFlow = finishFlowLabel(trueLabel); - if (isConditionalInReturn) { + if (inReturnStatement) { const expr = skipParentheses(node.whenTrue); (expr as Node as FlowContainer).flowNode = currentFlow; } @@ -1996,7 +1994,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { bind(node.whenTrue); addAntecedent(postExpressionLabel, currentFlow); currentFlow = finishFlowLabel(falseLabel); - if (isConditionalInReturn) { + if (inReturnStatement) { const expr = skipParentheses(node.whenFalse); (expr as Node as FlowContainer).flowNode = currentFlow; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8fa6b86ba97eb..a34316f59b438 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -253,7 +253,6 @@ import { isClassStaticBlockDeclaration, isCommaListExpression, isComputedPropertyName, - isConditionalExpression, isConstructorDeclaration, isDeclaration, isDecorator, @@ -333,7 +332,6 @@ import { isPropertyName, isPropertySignature, isQualifiedName, - isReturnStatement, isRootedDiskPath, isSetAccessorDeclaration, isShiftOperatorOrHigher, @@ -4160,21 +4158,6 @@ function getNestedModuleDeclaration(node: Node): Node | undefined { : undefined; } -/** @internal */ -export function isConditionalExpressionInReturnStatement(node: Expression) { - let n: Node = node; - while (n) { - if (isReturnStatement(n)) { - return true; - } - if (!isConditionalExpression(n)) { - return false; - } - n = walkUpParenthesizedExpressions(n.parent); - } - return false; -} - /** @internal */ export function canHaveFlowNode(node: Node): node is HasFlowNode { if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement) { From 1ac4a588ec22c9ab53ce558e806a66688b90546e Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 4 Jan 2024 14:50:56 -0800 Subject: [PATCH 24/90] add promise and generator tests --- .../reference/dependentReturnType1.errors.txt | 36 +++++++++- .../reference/dependentReturnType1.symbols | 59 ++++++++++++++++- .../reference/dependentReturnType1.types | 65 +++++++++++++++++++ tests/cases/compiler/dependentReturnType1.ts | 29 ++++++++- 4 files changed, 184 insertions(+), 5 deletions(-) diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index e972fdfc11adc..7e72c2641f1f4 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -35,9 +35,11 @@ dependentReturnType1.ts(345,9): error TS2322: Type 'string' is not assignable to dependentReturnType1.ts(353,13): error TS2322: Type 'number' is not assignable to type 'string'. dependentReturnType1.ts(362,9): error TS2322: Type 'true' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. dependentReturnType1.ts(364,5): error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. +dependentReturnType1.ts(389,15): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. +dependentReturnType1.ts(391,11): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. -==== dependentReturnType1.ts (32 errors) ==== +==== dependentReturnType1.ts (34 errors) ==== interface A { 1: number; 2: string; @@ -473,4 +475,34 @@ dependentReturnType1.ts(364,5): error TS2322: Type '""' is not assignable to typ !!! error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. } - const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. \ No newline at end of file + const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. + + // Ok + async function abool(x: T): Promise { + if (x) { + return 1; + } + return 2; + } + + // Ok + function* bbool(x: T): Generator { + yield 3; + if (x) { + return 1; + } + return 2; + } + + // We don't do the same type of narrowing for `yield` statements + function* cbool(x: T): Generator { + if (x) { + yield 1; + ~ +!!! error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. + } + yield 2; + ~ +!!! error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. + return 0; + } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 6c27f4a2ba9b7..48a00d5976e3a 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -966,7 +966,7 @@ function g(x: T): T extends 1 ? number : T extends 2 ? string : let x: number = Math.random() ? 1 : 2; >x : Symbol(x, Decl(dependentReturnType1.ts, 340, 11)) >Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) ->Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) >random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) if (x === 1) { @@ -992,7 +992,7 @@ function h(x: T): T extends 1 ? number : T extends 2 ? string : let x: number = Math.random() ? 1 : 2; >x : Symbol(x, Decl(dependentReturnType1.ts, 350, 11)) >Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) ->Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) >random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) if (x === 1) { @@ -1028,3 +1028,58 @@ const withInferResult = withInfer(["a"] as const); // The type says it returns ` >withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 357, 1)) >const : Symbol(const) +// Ok +async function abool(x: T): Promise { +>abool : Symbol(abool, Decl(dependentReturnType1.ts, 366, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 369, 45)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) + + if (x) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 369, 45)) + + return 1; + } + return 2; +} + +// Ok +function* bbool(x: T): Generator { +>bbool : Symbol(bbool, Decl(dependentReturnType1.ts, 374, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 377, 16)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 377, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 377, 16)) +>Generator : Symbol(Generator, Decl(lib.es2015.generator.d.ts, --, --)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 377, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 377, 16)) + + yield 3; + if (x) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 377, 40)) + + return 1; + } + return 2; +} + +// We don't do the same type of narrowing for `yield` statements +function* cbool(x: T): Generator { +>cbool : Symbol(cbool, Decl(dependentReturnType1.ts, 383, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 386, 16)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 386, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 386, 16)) +>Generator : Symbol(Generator, Decl(lib.es2015.generator.d.ts, --, --)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 386, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 386, 16)) + + if (x) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 386, 40)) + + yield 1; + } + yield 2; + return 0; +} diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 14ea82b97caed..e48097122f86d 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -1017,3 +1017,68 @@ const withInferResult = withInfer(["a"] as const); // The type says it returns ` >["a"] : ["a"] >"a" : "a" +// Ok +async function abool(x: T): Promise { +>abool : (x: T) => Promise +>true : true +>false : false +>x : T +>true : true +>false : false + + if (x) { +>x : T + + return 1; +>1 : 1 + } + return 2; +>2 : 2 +} + +// Ok +function* bbool(x: T): Generator { +>bbool : (x: T) => Generator +>true : true +>false : false +>x : T +>true : true +>false : false + + yield 3; +>yield 3 : unknown +>3 : 3 + + if (x) { +>x : T + + return 1; +>1 : 1 + } + return 2; +>2 : 2 +} + +// We don't do the same type of narrowing for `yield` statements +function* cbool(x: T): Generator { +>cbool : (x: T) => Generator +>true : true +>false : false +>x : T +>true : true +>false : false + + if (x) { +>x : T + + yield 1; +>yield 1 : unknown +>1 : 1 + } + yield 2; +>yield 2 : unknown +>2 : 2 + + return 0; +>0 : 0 +} diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index f41485f26399b..aac0e7817c706 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -1,5 +1,6 @@ // @strict: true // @noEmit: true +// @target: ES2022 interface A { 1: number; @@ -367,4 +368,30 @@ function withInfer(x: T): T extends [infer R] ? R : return ""; } -const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. \ No newline at end of file +const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. + +// Ok +async function abool(x: T): Promise { + if (x) { + return 1; + } + return 2; +} + +// Ok +function* bbool(x: T): Generator { + yield 3; + if (x) { + return 1; + } + return 2; +} + +// We don't do the same type of narrowing for `yield` statements +function* cbool(x: T): Generator { + if (x) { + yield 1; + } + yield 2; + return 0; +} \ No newline at end of file From db219a3f54e5dd88c906637aeac956fbec6cf3bf Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 4 Jan 2024 15:57:10 -0800 Subject: [PATCH 25/90] get rid of cast --- src/compiler/binder.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d5e87a552acb4..1d8614f749e2a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -65,7 +65,6 @@ import { Expression, ExpressionStatement, findAncestor, - FlowContainer, FlowFlags, FlowLabel, FlowNode, @@ -1988,7 +1987,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { currentFlow = finishFlowLabel(trueLabel); if (inReturnStatement) { const expr = skipParentheses(node.whenTrue); - (expr as Node as FlowContainer).flowNode = currentFlow; + expr.flowNode = currentFlow; } bind(node.questionToken); bind(node.whenTrue); @@ -1996,7 +1995,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { currentFlow = finishFlowLabel(falseLabel); if (inReturnStatement) { const expr = skipParentheses(node.whenFalse); - (expr as Node as FlowContainer).flowNode = currentFlow; + expr.flowNode = currentFlow; } bind(node.colonToken); bind(node.whenFalse); From 56792a1c03510bd3c9587f4229181c97b6568014 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 4 Jan 2024 17:11:33 -0800 Subject: [PATCH 26/90] add indexed access tests --- .../reference/dependentReturnType1.errors.txt | 54 ++++++++- .../reference/dependentReturnType1.symbols | 105 ++++++++++++++++++ .../reference/dependentReturnType1.types | 102 +++++++++++++++++ tests/cases/compiler/dependentReturnType1.ts | 49 ++++++++ 4 files changed, 309 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index 7e72c2641f1f4..c11595de8d80b 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -37,9 +37,10 @@ dependentReturnType1.ts(362,9): error TS2322: Type 'true' is not assignable to t dependentReturnType1.ts(364,5): error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. dependentReturnType1.ts(389,15): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. dependentReturnType1.ts(391,11): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. +dependentReturnType1.ts(433,13): error TS2322: Type '2' is not assignable to type '1'. -==== dependentReturnType1.ts (34 errors) ==== +==== dependentReturnType1.ts (35 errors) ==== interface A { 1: number; 2: string; @@ -505,4 +506,55 @@ dependentReturnType1.ts(391,11): error TS2322: Type '2' is not assignable to typ ~ !!! error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. return 0; + } + + // Indexed access tests + interface F { + "t": number, + "f": boolean, + } + + // Ok + function depLikeFun(str: T): F[T] { + if (str === "t") { + return 1; + } else { + return true; + } + } + + depLikeFun("t"); // has type number + depLikeFun("f"); // has type boolean + + type IndirectF = F[T]; + + // Ok + function depLikeFun2(str: T): IndirectF { + if (str === "t") { + return 1; + } else { + return true; + } + } + + + interface CComp { + foo: 1; + [s: string]: 1 | 2; + } + + function indexedCComp(x: T): CComp[T] { + if (x === "foo") { + if (Math.random()) { + return 2; // Error + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type '1'. + } + return 1; // Ok + } + return 2; // Ok + } + + function indexedCComp2(x: T): CComp[T] { + return 2; // Bad, unsafe } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 48a00d5976e3a..5d3d0099a2209 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -1083,3 +1083,108 @@ function* cbool(x: T): GeneratorF : Symbol(F, Decl(dependentReturnType1.ts, 392, 1)) + + "t": number, +>"t" : Symbol(F["t"], Decl(dependentReturnType1.ts, 395, 13)) + + "f": boolean, +>"f" : Symbol(F["f"], Decl(dependentReturnType1.ts, 396, 16)) +} + +// Ok +function depLikeFun(str: T): F[T] { +>depLikeFun : Symbol(depLikeFun, Decl(dependentReturnType1.ts, 398, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 401, 20)) +>str : Symbol(str, Decl(dependentReturnType1.ts, 401, 41)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 401, 20)) +>F : Symbol(F, Decl(dependentReturnType1.ts, 392, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 401, 20)) + + if (str === "t") { +>str : Symbol(str, Decl(dependentReturnType1.ts, 401, 41)) + + return 1; + } else { + return true; + } +} + +depLikeFun("t"); // has type number +>depLikeFun : Symbol(depLikeFun, Decl(dependentReturnType1.ts, 398, 1)) + +depLikeFun("f"); // has type boolean +>depLikeFun : Symbol(depLikeFun, Decl(dependentReturnType1.ts, 398, 1)) + +type IndirectF = F[T]; +>IndirectF : Symbol(IndirectF, Decl(dependentReturnType1.ts, 410, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 412, 15)) +>F : Symbol(F, Decl(dependentReturnType1.ts, 392, 1)) +>F : Symbol(F, Decl(dependentReturnType1.ts, 392, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 412, 15)) + +// Ok +function depLikeFun2(str: T): IndirectF { +>depLikeFun2 : Symbol(depLikeFun2, Decl(dependentReturnType1.ts, 412, 41)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 415, 21)) +>str : Symbol(str, Decl(dependentReturnType1.ts, 415, 42)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 415, 21)) +>IndirectF : Symbol(IndirectF, Decl(dependentReturnType1.ts, 410, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 415, 21)) + + if (str === "t") { +>str : Symbol(str, Decl(dependentReturnType1.ts, 415, 42)) + + return 1; + } else { + return true; + } +} + + +interface CComp { +>CComp : Symbol(CComp, Decl(dependentReturnType1.ts, 421, 1)) + + foo: 1; +>foo : Symbol(CComp.foo, Decl(dependentReturnType1.ts, 424, 17)) + + [s: string]: 1 | 2; +>s : Symbol(s, Decl(dependentReturnType1.ts, 426, 5)) +} + +function indexedCComp(x: T): CComp[T] { +>indexedCComp : Symbol(indexedCComp, Decl(dependentReturnType1.ts, 427, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 429, 22)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 429, 49)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 429, 22)) +>CComp : Symbol(CComp, Decl(dependentReturnType1.ts, 421, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 429, 22)) + + if (x === "foo") { +>x : Symbol(x, Decl(dependentReturnType1.ts, 429, 49)) + + if (Math.random()) { +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + return 2; // Error + } + return 1; // Ok + } + return 2; // Ok +} + +function indexedCComp2(x: T): CComp[T] { +>indexedCComp2 : Symbol(indexedCComp2, Decl(dependentReturnType1.ts, 437, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 439, 23)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 439, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 439, 23)) +>CComp : Symbol(CComp, Decl(dependentReturnType1.ts, 421, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 439, 23)) + + return 2; // Bad, unsafe +} diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index e48097122f86d..47979db039838 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -1082,3 +1082,105 @@ function* cbool(x: T): Generator0 : 0 } + +// Indexed access tests +interface F { + "t": number, +>"t" : number + + "f": boolean, +>"f" : boolean +} + +// Ok +function depLikeFun(str: T): F[T] { +>depLikeFun : (str: T) => F[T] +>str : T + + if (str === "t") { +>str === "t" : boolean +>str : T +>"t" : "t" + + return 1; +>1 : 1 + + } else { + return true; +>true : true + } +} + +depLikeFun("t"); // has type number +>depLikeFun("t") : number +>depLikeFun : (str: T) => F[T] +>"t" : "t" + +depLikeFun("f"); // has type boolean +>depLikeFun("f") : boolean +>depLikeFun : (str: T) => F[T] +>"f" : "f" + +type IndirectF = F[T]; +>IndirectF : IndirectF + +// Ok +function depLikeFun2(str: T): IndirectF { +>depLikeFun2 : (str: T) => IndirectF +>str : T + + if (str === "t") { +>str === "t" : boolean +>str : T +>"t" : "t" + + return 1; +>1 : 1 + + } else { + return true; +>true : true + } +} + + +interface CComp { + foo: 1; +>foo : 1 + + [s: string]: 1 | 2; +>s : string +} + +function indexedCComp(x: T): CComp[T] { +>indexedCComp : (x: T) => CComp[T] +>x : T + + if (x === "foo") { +>x === "foo" : boolean +>x : T +>"foo" : "foo" + + if (Math.random()) { +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number + + return 2; // Error +>2 : 2 + } + return 1; // Ok +>1 : 1 + } + return 2; // Ok +>2 : 2 +} + +function indexedCComp2(x: T): CComp[T] { +>indexedCComp2 : (x: T) => CComp[T] +>x : T + + return 2; // Bad, unsafe +>2 : 2 +} diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index aac0e7817c706..d7f9ee2aa852e 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -394,4 +394,53 @@ function* cbool(x: T): Generator(str: T): F[T] { + if (str === "t") { + return 1; + } else { + return true; + } +} + +depLikeFun("t"); // has type number +depLikeFun("f"); // has type boolean + +type IndirectF = F[T]; + +// Ok +function depLikeFun2(str: T): IndirectF { + if (str === "t") { + return 1; + } else { + return true; + } +} + + +interface CComp { + foo: 1; + [s: string]: 1 | 2; +} + +function indexedCComp(x: T): CComp[T] { + if (x === "foo") { + if (Math.random()) { + return 2; // Error + } + return 1; // Ok + } + return 2; // Ok +} + +function indexedCComp2(x: T): CComp[T] { + return 2; // Bad, unsafe } \ No newline at end of file From b292bd8dea0f602c558f0dcb05701ae36ac1d9ed Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 14 Dec 2023 13:56:39 -0800 Subject: [PATCH 27/90] WIP --- src/compiler/checker.ts | 114 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6fa7fa422d01e..a43323e7bf6d2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -202,6 +202,7 @@ import { FlowAssignment, FlowCall, FlowCondition, + FlowContainer, FlowFlags, FlowLabel, FlowNode, @@ -44267,6 +44268,119 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + function collectQueryTypeParameterReferences(typeParameter: TypeParameter, endFlowNode: FlowNode) { + // >> TODO: this should not be node, should be identifier or prop access + const references: Node[] = []; + visitFlowNode(endFlowNode, collect); + function collect(flow: FlowNode) { + const flags = flow.flags; + // Based on `getTypeAtFlowX` functions + // >> Assignment: narrow lvalue? + // >> Call: narrow arguments if call is to a predicate + let node; + if (flags & FlowFlags.Assignment) { + node = (flow as FlowAssignment).node; + } + if (flags & FlowFlags.Call) { + node = (flow as FlowCall).node; + } + // Expression: go into binary expressions, etc + if (flags & FlowFlags.Condition) { + node = (flow as FlowCondition).node; + } + // same as expression/condition + if (flags & FlowFlags.SwitchClause) { + // switch condition + node = (flow as FlowSwitchClause).switchStatement.expression; + } + // from `getTypeAtFlowArrayMutation` + if (flags & FlowFlags.ArrayMutation) { + const callNode = (flow as FlowArrayMutation).node; + node = callNode.kind === SyntaxKind.CallExpression ? + (callNode.expression as PropertyAccessExpression).expression : + (callNode.left as ElementAccessExpression).expression; + } + getReferences(node); + } + + // >> TODO: maybe refine the type `Node` here based on the above types + // >> TODO: get the type of the references here as well + // >> TODO: what does the control flow graph look like for e.g. an `if` with a condition that is + // an || or && ??? + // Based on `isMatchingReference` + function getReferences(node: Node): void { + switch (node.kind) { + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.NonNullExpression: + return getReferences((node as NonNullExpression | ParenthesizedExpression).expression); + case SyntaxKind.BinaryExpression: + if (isAssignmentExpression(node)) { + getReferences(node.left); + } + if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.CommaToken) { + getReferences(node.right); + } + return; + case SyntaxKind.MetaProperty: + references.push(node); + // >> TODO: I think you can never actually have `import.meta` or + // `new.target` involved in narrowing, so this might be pointless + return; + case SyntaxKind.ThisKeyword: + references.push(node); + return; + case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: + references.push(node); + return; + case SyntaxKind.BindingElement: + case SyntaxKind.VariableDeclaration: + // >> TODO: get name of variable declaration/binding element and use that as ref? + // Can we get away with skipping this? + return; + case SyntaxKind.SuperKeyword: + // >> TODO: is this ever relevant? + references.push(node); + return; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + const type = getTypeOfSymbol(symbol); + references.push(node); + return; + case SyntaxKind.QualifiedName: + // >> TODO: can this happen? we don't ever match on target being this + return; + } + } + } + + type HasFlowAntecedent = + | FlowAssignment + | FlowCondition + | FlowSwitchClause + | FlowArrayMutation + | FlowCall + | FlowReduceLabel; + + function hasFlowAntecedent(flow: FlowNode): flow is HasFlowAntecedent { + return !!(flow.flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.SwitchClause | FlowFlags.ArrayMutation | FlowFlags.Call | FlowFlags.ReduceLabel)); + } + + function visitFlowNode(flow: FlowNode, visit: (n: FlowNode) => void): void { + visit(flow); + const flags = flow.flags; + if (flags & FlowFlags.Label) { + return (flow as FlowLabel).antecedents?.forEach(f => visitFlowNode(f, visit)); + } + if (flags & FlowFlags.ReduceLabel) { + (flow as FlowReduceLabel).antecedents.forEach(f => visitFlowNode(f, visit)); + // >> TODO: also visit flow.target? + } + if (hasFlowAntecedent(flow)) { + return visitFlowNode((flow as HasFlowAntecedent).antecedent, visit); + } + } + function checkWithStatement(node: WithStatement) { // Grammar checking for withStatement if (!checkGrammarStatementInAmbientContext(node)) { From 18464f3401c9d675ad7212e354f463b690348c9c Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 20 Dec 2023 18:58:36 -0800 Subject: [PATCH 28/90] WIP: detect expression to use for narrowing type parameter based on control flow graph references --- src/compiler/checker.ts | 288 +++++++++++++++++++++++++--------------- 1 file changed, 178 insertions(+), 110 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a43323e7bf6d2..7c6fe84ef3be6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -202,7 +202,6 @@ import { FlowAssignment, FlowCall, FlowCondition, - FlowContainer, FlowFlags, FlowLabel, FlowNode, @@ -368,7 +367,6 @@ import { getThisParameter, getTrailingSemicolonDeferringWriter, getTypeParameterFromJsDoc, - getTypeParameterOwner, getUseDefineForClassFields, group, hasAbstractModifier, @@ -414,6 +412,7 @@ import { ImportClause, ImportDeclaration, ImportEqualsDeclaration, + ImportMetaProperty, ImportOrExportSpecifier, ImportsNotUsedAsValues, ImportSpecifier, @@ -986,6 +985,7 @@ import { StructuredType, SubstitutionType, SuperCall, + SuperExpression, SwitchStatement, Symbol, SymbolAccessibility, @@ -1500,6 +1500,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var lastGetCombinedModifierFlagsNode: Declaration | undefined; var lastGetCombinedModifierFlagsResult = ModifierFlags.None; + type NodeId = number; + // >> TODO: this could just be `Expression` + type QueryReference = + | Identifier + | PropertyAccessExpression + | ElementAccessExpression + | ImportMetaProperty + | SuperExpression + | ThisExpression; + type QueryReferences = [Symbol, QueryReference, TypeParameter][]; + var queryTypeParameterReferencesCache: Map = new Map(); // for public members that accept a Node or one of its subtypes, we must guard against // synthetic nodes created during transformations by calling `getParseTreeNode`. // for most of these, we perform the guard only on `checker` to avoid any possible @@ -44122,7 +44133,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { /* Begin weird stuff */ const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - const queryTypeParameters = typeParameters?.filter(isQueryTypeParameter); + const queryTypeParameters = typeParameters + && filterQueryTypeParameters((container as SignatureDeclaration), typeParameters); // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: // `function foo(...) { @@ -44138,14 +44150,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // }` const narrowPosition = expr && isConditionalExpression(walkUpParenthesizedExpressions(expr.parent)) ? expr : node; - if (queryTypeParameters && queryTypeParameters.length && narrowPosition.flowNode) { - const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, tp => { - const narrowReference = factory.cloneNode(tp.exprName); // Construct a reference that can be narrowed. + if (queryTypeParameters && narrowPosition.flowNode) { + const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, ([tp, symbol, reference]) => { + const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. // Set the symbol of the synthetic reference. // This allows us to get the type of the reference at a location where the reference is possibly shadowed. - getNodeLinks(narrowReference).resolvedSymbol = getResolvedSymbol(tp.exprName); + getNodeLinks(narrowReference).resolvedSymbol = symbol; setParent(narrowReference, narrowPosition.parent); setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); + // >> TODO: get rid of this cast? narrowReference.flowNode = narrowPosition.flowNode; const exprType = getTypeOfExpression(narrowReference); // >> TODO: is there a better way of detecting that narrowing will be useless? @@ -44202,156 +44215,211 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function isQueryTypeParameter(typeParameter: TypeParameter): typeParameter is TypeParameter & { exprName: Identifier } { - if (isThisTypeParameter(typeParameter)) { - return false; - } - if (!typeParameter.symbol) { - return false; - // >> TODO: deal with synthetic tps? - } - if (typeParameter.exprName) { - return true; - } - if (typeParameter.exprName === null) { // eslint-disable-line no-null/no-null - return false; - } - - typeParameter.exprName = null; // eslint-disable-line no-null/no-null - // Type parameter should have a type parameter declaration because it is not a `this` type parameter, and it has a symbol. - const declaration = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; - const owner = getTypeParameterOwner(declaration); - if (!owner || !isFunctionLikeDeclaration(owner)) { - return false; // Owner is class or interface, or a signature without an implementation - } - const references: Node[] = []; // All parameter type nodes that look like `T` that resolve to the type parameter. - owner.parameters.forEach(parameter => parameter.type && collectReferences(parameter.type)); - if (references.length === 1) { - const reference = references[0]; - let exprName; - if (isParameter(reference.parent) && - reference.parent.parent === owner && - (exprName = getNameOfDeclaration(reference.parent)) && - isIdentifier(exprName)) { - // If the parameter is optional, and its type is a type parameter whose constraint doesn't allow for `undefined`, then we can't narrow, because the type parameter doesn't reflect the full type of the parameter. - // For instance, if we have: - // `function f(x?: T): ReturnType { - // if (typeof x === "undefined") { - // ... - // } - // }`, - // The type of `x` is really `T | undefined`, - // and we can't narrow type parameter `T` with type `undefined` inside the `if` statement, because - // `undefined` does not satisfy `T`'s constraint, `string`. - // >> TODO: if we allow the type parameter to be the type of a *property*, we need to update this code because the property could be optional and it could also contain the missing type instead of simply the undefined type. - let constraint; - if (isOptionalParameter(reference.parent) && - (constraint = getConstraintOfTypeParameter(typeParameter)) && - !containsUndefinedType(constraint)) { - return false; - } - typeParameter.exprName = exprName; - return true; + function filterQueryTypeParameters(container: SignatureDeclaration, typeParameters: TypeParameter[]): [TypeParameter, Symbol, QueryReference][] | undefined { + const queryTypeParameterReferences = collectQueryTypeParameterReferences(container); + const paramToReferences: Map> = new Map(); + for (const [symbol, reference, typeParam] of queryTypeParameterReferences) { + const id = getTypeId(typeParam); + if (!paramToReferences.has(id)) { + paramToReferences.set(id, new Map()); } + const symbolMap = paramToReferences.get(id)!; + symbolMap.set(getSymbolId(symbol), [symbol, reference]); } - return false; - - function collectReferences(node: Node) { - if (isTypeReferenceNode(node)) { - const type = getTypeFromTypeNode(node); - if (type.flags & TypeFlags.TypeParameter && getActualTypeVariable(type) === typeParameter) { - references.push(node); - } - return; + const queryParameters: [TypeParameter, Symbol, QueryReference][] = []; + for (const typeParam of typeParameters) { + const id = getTypeId(typeParam); + const symbolMap = paramToReferences.get(id); + if (!symbolMap) continue; + if (symbolMap.size === 1) { + const [symbol, reference] = firstIterator(symbolMap.values()); + queryParameters.push([typeParam, symbol, reference]); } - forEachChild(node, collectReferences); } + return queryParameters.length ? queryParameters : undefined; } - function collectQueryTypeParameterReferences(typeParameter: TypeParameter, endFlowNode: FlowNode) { - // >> TODO: this should not be node, should be identifier or prop access - const references: Node[] = []; - visitFlowNode(endFlowNode, collect); + function collectQueryTypeParameterReferences(container: SignatureDeclaration): QueryReferences { + const references: QueryReferences = []; + const nodeId = getNodeId(container); + if (!queryTypeParameterReferencesCache.has(nodeId)) { + const flowNodes = collectFlowNodes(container); + // >> TODO: this will cause us to possibly visit the same flow nodes more than once. + // >> Dedupe work. + flowNodes.forEach(flowNode => visitFlowNode(flowNode, collect)); + queryTypeParameterReferencesCache.set(nodeId, references); + } + return queryTypeParameterReferencesCache.get(nodeId)!; + function collect(flow: FlowNode) { const flags = flow.flags; - // Based on `getTypeAtFlowX` functions - // >> Assignment: narrow lvalue? - // >> Call: narrow arguments if call is to a predicate + // Based on `getTypeAtFlowX` functions. let node; if (flags & FlowFlags.Assignment) { node = (flow as FlowAssignment).node; } - if (flags & FlowFlags.Call) { + else if (flags & FlowFlags.Call) { node = (flow as FlowCall).node; } - // Expression: go into binary expressions, etc - if (flags & FlowFlags.Condition) { + else if (flags & FlowFlags.Condition) { node = (flow as FlowCondition).node; } - // same as expression/condition - if (flags & FlowFlags.SwitchClause) { - // switch condition + else if (flags & FlowFlags.SwitchClause) { node = (flow as FlowSwitchClause).switchStatement.expression; } - // from `getTypeAtFlowArrayMutation` - if (flags & FlowFlags.ArrayMutation) { + else if (flags & FlowFlags.ArrayMutation) { const callNode = (flow as FlowArrayMutation).node; node = callNode.kind === SyntaxKind.CallExpression ? (callNode.expression as PropertyAccessExpression).expression : (callNode.left as ElementAccessExpression).expression; } - getReferences(node); + if (node) getReferences(node); } - - // >> TODO: maybe refine the type `Node` here based on the above types - // >> TODO: get the type of the references here as well - // >> TODO: what does the control flow graph look like for e.g. an `if` with a condition that is - // an || or && ??? - // Based on `isMatchingReference` + + // >> TODO: Based on `isMatchingReference`? + // >> TODO: needs to be based on `narrowType` as well. + // If `node` came from a flow condition's condition, then it is going to be analyzed by + // `narrowType`. function getReferences(node: Node): void { switch (node.kind) { case SyntaxKind.ParenthesizedExpression: case SyntaxKind.NonNullExpression: return getReferences((node as NonNullExpression | ParenthesizedExpression).expression); case SyntaxKind.BinaryExpression: - if (isAssignmentExpression(node)) { - getReferences(node.left); - } - if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.CommaToken) { - getReferences(node.right); - } + getReferences((node as BinaryExpression).left); + getReferences((node as BinaryExpression).right); + return; + case SyntaxKind.CallExpression: + let callAccess; + // `node` is `expr.hasOwnExpression(prop)` + if (isPropertyAccessExpression(callAccess = (node as CallExpression).expression) && isIdentifier(callAccess.name) && callAccess.name.escapedText === "hasOwnProperty" && (node as CallExpression).arguments.length === 1 && isStringLiteralLike((node as CallExpression).arguments[0])) { + const propName = (node as CallExpression).arguments[0] as StringLiteralLike; + const synthPropertyAccess = + canUsePropertyAccess(propName.text, languageVersion) ? + factory.createPropertyAccessExpression(callAccess.expression, propName.text) : + factory.createElementAccessExpression(callAccess.expression, propName); + setParent(synthPropertyAccess, node.parent); + addReference(synthPropertyAccess); + } + getReferences((node as CallExpression).expression); // >> TODO: do we need this? + // This is relevant for type predicate-based narrowing. + (node as CallExpression).arguments.forEach(getReferences); + return; + case SyntaxKind.PrefixUnaryExpression: + case SyntaxKind.PostfixUnaryExpression: // >> TODO: do we need this? + getReferences((node as PrefixUnaryExpression | PostfixUnaryExpression).operand); + return; + case SyntaxKind.TypeOfExpression: + getReferences((node as TypeOfExpression).expression); + return; + case SyntaxKind.VoidExpression: + case SyntaxKind.AwaitExpression: + case SyntaxKind.YieldExpression: + // We won't narrow `e` in `await e`, `void e` or `yield e`. + return; + case SyntaxKind.BindingElement: + case SyntaxKind.VariableDeclaration: + // >> TODO: get name of variable declaration/binding element and use that as ref? + // Can we get away with skipping this? return; case SyntaxKind.MetaProperty: - references.push(node); + if ((node as MetaProperty).keywordToken === SyntaxKind.ImportKeyword) { + addReference((node as ImportMetaProperty)); + } // >> TODO: I think you can never actually have `import.meta` or // `new.target` involved in narrowing, so this might be pointless return; case SyntaxKind.ThisKeyword: - references.push(node); + addReference((node as ThisExpression)); return; case SyntaxKind.Identifier: - case SyntaxKind.PrivateIdentifier: - references.push(node); + addReference((node as Identifier)); return; - case SyntaxKind.BindingElement: - case SyntaxKind.VariableDeclaration: - // >> TODO: get name of variable declaration/binding element and use that as ref? - // Can we get away with skipping this? + case SyntaxKind.PrivateIdentifier: + // Don't add private identifiers as a reference. + // They're only a narrowable reference when used in a property access expression, + // as in `this.#privateId`. return; case SyntaxKind.SuperKeyword: - // >> TODO: is this ever relevant? - references.push(node); + addReference((node as SuperExpression)); return; case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: - const type = getTypeOfSymbol(symbol); - references.push(node); - return; - case SyntaxKind.QualifiedName: - // >> TODO: can this happen? we don't ever match on target being this + addReference((node as PropertyAccessExpression | ElementAccessExpression)); return; } } + + // >> TODO: can reference be a synthetic node? + function addReference(reference: QueryReference): void { + // >> TODO: pick only one of those? + const symbol = getSymbolAtLocation(reference, /*ignoreErrors*/ true) || getSymbolForExpression(reference); + const type = symbol && getTypeOfSymbol(symbol); + if (!type || isErrorType(type)) return; + // Check if we have an optional parameter, an optional property, or an optional tuple element. + // In this case, its type can be `T | undefined`, + // and if `T` allows for the undefined type, then we can still narrow `T`. + if (((symbol.valueDeclaration && isOptionalDeclaration(symbol.valueDeclaration)) + || isOptionalTupleElementSymbol(symbol)) + && type.flags & TypeFlags.Union + && (type as UnionType).types[0] === undefinedOrMissingType + && (type as UnionType).types[1].flags & TypeFlags.TypeParameter) { + const typeParam = (type as UnionType).types[1] as TypeParameter; + const constraint = getConstraintOfTypeParameter(typeParam); + if (!constraint || containsUndefinedType(constraint)) { + references.push([symbol, reference, typeParam]); + } + } + else if (type.flags & TypeFlags.TypeParameter) { + references.push([symbol, reference, type as TypeParameter]); + } + } + } + + function isOptionalTupleElementSymbol(symbol: Symbol): boolean { + if (isTransientSymbol(symbol) + && symbol.links.target + && isTransientSymbol(symbol.links.target) + && symbol.links.target.links.tupleLabelDeclaration) { + return !!symbol.links.target.links.tupleLabelDeclaration?.questionToken + } + return false; + } + + function collectFlowNodes(container: SignatureDeclaration): FlowNode[] { + const flowNodes = []; + visit(container); + if (isFunctionLikeDeclaration(container) && container.endFlowNode) { + flowNodes.push(container.endFlowNode); + } + return flowNodes; + + function visit(node: Node) { + if (node.kind === SyntaxKind.ReturnStatement) { + const nodeContainer = getContainingFunctionOrClassStaticBlock((node as ReturnStatement)); + if (nodeContainer === container) { + visitConditionalReturnExpression((node as ReturnStatement).expression); + if ((node as ReturnStatement).flowNode) { + flowNodes.push((node as ReturnStatement).flowNode); + } + } + return; + } + + forEachChild(node, visit); + } + + function visitConditionalReturnExpression(node: Expression | undefined): void { + if (!node) return; + node = skipParentheses(node, /*excludeJSDocTypeAssertions*/ false); + if (isConditionalExpression(node)) { + visitConditionalReturnExpression(node.whenTrue); + visitConditionalReturnExpression(node.whenFalse); + } + else if ((node as HasFlowNode).flowNode) { + flowNodes.push((node as HasFlowNode).flowNode); + } + } } type HasFlowAntecedent = From 2d30f3827e7bc4f0a586e50aa71eb036dc8041f3 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 21 Dec 2023 13:57:08 -0800 Subject: [PATCH 29/90] use conditional expression in return stmt's flow nodes to collect references --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7c6fe84ef3be6..db62ad22fac3c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44124,7 +44124,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkReturnStatementExpression(expr: Expression | undefined): void { let actualReturnType = unwrappedReturnType; if (expr) { - expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + expr = skipParentheses(expr); if (isConditionalExpression(expr)) { return checkReturnConditionalExpression(expr); } @@ -44411,7 +44411,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function visitConditionalReturnExpression(node: Expression | undefined): void { if (!node) return; - node = skipParentheses(node, /*excludeJSDocTypeAssertions*/ false); + node = skipParentheses(node); if (isConditionalExpression(node)) { visitConditionalReturnExpression(node.whenTrue); visitConditionalReturnExpression(node.whenFalse); From 764c4b67997936fa9ad6147ba502763a8e6c7762 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 22 Dec 2023 16:34:51 -0800 Subject: [PATCH 30/90] fix cast --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index db62ad22fac3c..523ecdb45daf3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44416,8 +44416,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { visitConditionalReturnExpression(node.whenTrue); visitConditionalReturnExpression(node.whenFalse); } - else if ((node as HasFlowNode).flowNode) { - flowNodes.push((node as HasFlowNode).flowNode); + else if (node.flowNode) { + flowNodes.push(node.flowNode); } } } From c9abd248035f7040e50ad436596502272a72c49a Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 3 Jan 2024 12:09:59 -0800 Subject: [PATCH 31/90] update test --- src/compiler/checker.ts | 6 +- .../reference/dependentReturnType1.errors.txt | 42 +++++- .../reference/dependentReturnType1.symbols | 133 ++++++++++++++++++ .../reference/dependentReturnType1.types | 102 ++++++++++++++ tests/cases/compiler/dependentReturnType1.ts | 37 +++++ 5 files changed, 316 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 523ecdb45daf3..200bad5d4cdf6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44243,15 +44243,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const references: QueryReferences = []; const nodeId = getNodeId(container); if (!queryTypeParameterReferencesCache.has(nodeId)) { - const flowNodes = collectFlowNodes(container); + const flowNodes = collectFlowNodes(container); // >> TODO: collectFlowNodes may have duplicates // >> TODO: this will cause us to possibly visit the same flow nodes more than once. // >> Dedupe work. - flowNodes.forEach(flowNode => visitFlowNode(flowNode, collect)); + flowNodes.forEach(flowNode => visitFlowNode(flowNode, collectNode)); queryTypeParameterReferencesCache.set(nodeId, references); } return queryTypeParameterReferencesCache.get(nodeId)!; - function collect(flow: FlowNode) { + function collectNode(flow: FlowNode) { const flags = flow.flags; // Based on `getTypeAtFlowX` functions. let node; diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index c11595de8d80b..a32aca66f1e02 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -38,9 +38,10 @@ dependentReturnType1.ts(364,5): error TS2322: Type '""' is not assignable to typ dependentReturnType1.ts(389,15): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. dependentReturnType1.ts(391,11): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. dependentReturnType1.ts(433,13): error TS2322: Type '2' is not assignable to type '1'. +dependentReturnType1.ts(463,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. -==== dependentReturnType1.ts (35 errors) ==== +==== dependentReturnType1.ts (36 errors) ==== interface A { 1: number; 2: string; @@ -557,4 +558,43 @@ dependentReturnType1.ts(433,13): error TS2322: Type '2' is not assignable to typ function indexedCComp2(x: T): CComp[T] { return 2; // Bad, unsafe + } + + // From #33912 + abstract class Operation { + abstract perform(t: T): R; + } + + type ConditionalReturnType | undefined> = + EOp extends Operation ? R : EOp extends undefined ? T | R : T | R; + + class ConditionalOperation | undefined> extends Operation> { + constructor( + private predicate: (value: T) => boolean, + private thenOp: Operation, + private elseOp?: EOp + ) { + super(); + } + + perform(t: T): ConditionalReturnType { + if (this.predicate(t)) { + return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it + ~~~~~~ +!!! error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. + } else if (typeof this.elseOp !== 'undefined') { + return this.elseOp.perform(t); // Ok + } else { + return t; // Ok + } + } + } + + // Optional tuple element + function tupl(x: [string, some?: T]): + T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 { + if (x[1]) { + return 1; + } + return 2; } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 5d3d0099a2209..6030d0102aa7a 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -1188,3 +1188,136 @@ function indexedCComp2(x: T): CComp[T] { return 2; // Bad, unsafe } + +// From #33912 +abstract class Operation { +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 444, 25)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 444, 27)) + + abstract perform(t: T): R; +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 444, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 445, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 444, 25)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 444, 27)) +} + +type ConditionalReturnType | undefined> = +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 446, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 448, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 448, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 448, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 448, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 448, 29)) + + EOp extends Operation ? R : EOp extends undefined ? T | R : T | R; +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 448, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 448, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 448, 29)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 448, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 448, 32)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 448, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 448, 29)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 448, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 448, 29)) + +class ConditionalOperation | undefined> extends Operation> { +>ConditionalOperation : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 449, 76)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 451, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 451, 29)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 446, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 451, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 32)) + + constructor( + private predicate: (value: T) => boolean, +>predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 452, 16)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 453, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) + + private thenOp: Operation, +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 453, 49)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 451, 29)) + + private elseOp?: EOp +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 454, 40)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 32)) + + ) { + super(); +>super : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) + } + + perform(t: T): ConditionalReturnType { +>perform : Symbol(ConditionalOperation.perform, Decl(dependentReturnType1.ts, 458, 5)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 460, 12)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 446, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 451, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 32)) + + if (this.predicate(t)) { +>this.predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 452, 16)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 449, 76)) +>predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 452, 16)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 460, 12)) + + return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it +>this.thenOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 444, 32)) +>this.thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 453, 49)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 449, 76)) +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 453, 49)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 444, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 460, 12)) + + } else if (typeof this.elseOp !== 'undefined') { +>this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 454, 40)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 449, 76)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 454, 40)) + + return this.elseOp.perform(t); // Ok +>this.elseOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 444, 32)) +>this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 454, 40)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 449, 76)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 454, 40)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 444, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 460, 12)) + + } else { + return t; // Ok +>t : Symbol(t, Decl(dependentReturnType1.ts, 460, 12)) + } + } +} + +// Optional tuple element +function tupl(x: [string, some?: T]): +>tupl : Symbol(tupl, Decl(dependentReturnType1.ts, 469, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 472, 14)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 472, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 472, 14)) + + T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 { +>T : Symbol(T, Decl(dependentReturnType1.ts, 472, 14)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 472, 14)) + + if (x[1]) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 472, 50)) +>1 : Symbol(1) + + return 1; + } + return 2; +} diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 47979db039838..497732e2a3244 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -1184,3 +1184,105 @@ function indexedCComp2(x: T): CComp[T] { return 2; // Bad, unsafe >2 : 2 } + +// From #33912 +abstract class Operation { +>Operation : Operation + + abstract perform(t: T): R; +>perform : (t: T) => R +>t : T +} + +type ConditionalReturnType | undefined> = +>ConditionalReturnType : ConditionalReturnType + + EOp extends Operation ? R : EOp extends undefined ? T | R : T | R; + +class ConditionalOperation | undefined> extends Operation> { +>ConditionalOperation : ConditionalOperation +>Operation : Operation> + + constructor( + private predicate: (value: T) => boolean, +>predicate : (value: T) => boolean +>value : T + + private thenOp: Operation, +>thenOp : Operation + + private elseOp?: EOp +>elseOp : EOp | undefined + + ) { + super(); +>super() : void +>super : typeof Operation + } + + perform(t: T): ConditionalReturnType { +>perform : (t: T) => ConditionalReturnType +>t : T + + if (this.predicate(t)) { +>this.predicate(t) : boolean +>this.predicate : (value: T) => boolean +>this : this +>predicate : (value: T) => boolean +>t : T + + return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it +>this.thenOp.perform(t) : R +>this.thenOp.perform : (t: T) => R +>this.thenOp : Operation +>this : this +>thenOp : Operation +>perform : (t: T) => R +>t : T + + } else if (typeof this.elseOp !== 'undefined') { +>typeof this.elseOp !== 'undefined' : boolean +>typeof this.elseOp : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>this.elseOp : EOp | undefined +>this : this +>elseOp : EOp | undefined +>'undefined' : "undefined" + + return this.elseOp.perform(t); // Ok +>this.elseOp.perform(t) : R +>this.elseOp.perform : (t: T) => R +>this.elseOp : Operation +>this : this +>elseOp : Operation +>perform : (t: T) => R +>t : T + + } else { + return t; // Ok +>t : T + } + } +} + +// Optional tuple element +function tupl(x: [string, some?: T]): +>tupl : (x: [string, some?: T]) => T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 +>true : true +>false : false +>x : [string, some?: T | undefined] + + T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 { +>true : true +>false : false + + if (x[1]) { +>x[1] : T | undefined +>x : [string, some?: T | undefined] +>1 : 1 + + return 1; +>1 : 1 + } + return 2; +>2 : 2 +} diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index d7f9ee2aa852e..56258054ef194 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -443,4 +443,41 @@ function indexedCComp(x: T): CComp[T] { function indexedCComp2(x: T): CComp[T] { return 2; // Bad, unsafe +} + +// From #33912 +abstract class Operation { + abstract perform(t: T): R; +} + +type ConditionalReturnType | undefined> = + EOp extends Operation ? R : EOp extends undefined ? T | R : T | R; + +class ConditionalOperation | undefined> extends Operation> { + constructor( + private predicate: (value: T) => boolean, + private thenOp: Operation, + private elseOp?: EOp + ) { + super(); + } + + perform(t: T): ConditionalReturnType { + if (this.predicate(t)) { + return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it + } else if (typeof this.elseOp !== 'undefined') { + return this.elseOp.perform(t); // Ok + } else { + return t; // Ok + } + } +} + +// Optional tuple element +function tupl(x: [string, some?: T]): + T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 { + if (x[1]) { + return 1; + } + return 2; } \ No newline at end of file From 2ed4774b6df8a991087e844addad970b76962cfa Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 3 Jan 2024 13:09:23 -0800 Subject: [PATCH 32/90] detect useless narrowing when narrowed type is simply type parameter --- src/compiler/checker.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 200bad5d4cdf6..2aeb77357b269 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44158,15 +44158,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getNodeLinks(narrowReference).resolvedSymbol = symbol; setParent(narrowReference, narrowPosition.parent); setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); - // >> TODO: get rid of this cast? narrowReference.flowNode = narrowPosition.flowNode; const exprType = getTypeOfExpression(narrowReference); + // Try to detect if the type of the reference was not narrowed. // >> TODO: is there a better way of detecting that narrowing will be useless? - if (getConstraintOfTypeParameter(tp)) { - const narrowableConstraintType = mapType(tp.constraint!, getBaseConstraintOrType); - if (narrowableConstraintType === exprType) { - return undefined; // Don't narrow if narrowing didn't do anything but default to constraints - } + if (exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { + return undefined; // Don't narrow if narrowing didn't do anything but default to the type parameter or its constraint. } return [tp, exprType]; }); From 0d567afbf64dbc62cb4ba2a0b11bd2d16f3c1160 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 3 Jan 2024 13:29:43 -0800 Subject: [PATCH 33/90] fix visit of flow nodes --- src/compiler/checker.ts | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2aeb77357b269..abb739d3f48b1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44241,9 +44241,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const nodeId = getNodeId(container); if (!queryTypeParameterReferencesCache.has(nodeId)) { const flowNodes = collectFlowNodes(container); // >> TODO: collectFlowNodes may have duplicates - // >> TODO: this will cause us to possibly visit the same flow nodes more than once. - // >> Dedupe work. - flowNodes.forEach(flowNode => visitFlowNode(flowNode, collectNode)); + visitFlowNodes(flowNodes, collectNode); queryTypeParameterReferencesCache.set(nodeId, references); } return queryTypeParameterReferencesCache.get(nodeId)!; @@ -44431,18 +44429,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return !!(flow.flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.SwitchClause | FlowFlags.ArrayMutation | FlowFlags.Call | FlowFlags.ReduceLabel)); } - function visitFlowNode(flow: FlowNode, visit: (n: FlowNode) => void): void { - visit(flow); - const flags = flow.flags; - if (flags & FlowFlags.Label) { - return (flow as FlowLabel).antecedents?.forEach(f => visitFlowNode(f, visit)); - } - if (flags & FlowFlags.ReduceLabel) { - (flow as FlowReduceLabel).antecedents.forEach(f => visitFlowNode(f, visit)); - // >> TODO: also visit flow.target? - } - if (hasFlowAntecedent(flow)) { - return visitFlowNode((flow as HasFlowAntecedent).antecedent, visit); + function visitFlowNodes(flowNodes: FlowNode[], visit: (n: FlowNode) => void): void { + const visited = new Set(); + flowNodes.forEach(visitFlowNode); + function visitFlowNode(flow: FlowNode): void { + const id = getFlowNodeId(flow); + if (visited.has(id)) return; + visited.add(id); + + visit(flow); + const flags = flow.flags; + if (flags & FlowFlags.Label) { + return (flow as FlowLabel).antecedents?.forEach(visitFlowNode); + } + if (flags & FlowFlags.ReduceLabel) { + (flow as FlowReduceLabel).antecedents.forEach(visitFlowNode); + // >> TODO: also visit flow.target? + } + if (hasFlowAntecedent(flow)) { + return visitFlowNode((flow as HasFlowAntecedent).antecedent); + } } } From a33d99de719292d6abb9a490a137fe874288ebaa Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 3 Jan 2024 17:08:03 -0800 Subject: [PATCH 34/90] optimization --- src/compiler/checker.ts | 108 +++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index abb739d3f48b1..5b74f597eea36 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20134,6 +20134,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } + function isNarrowableReturnType(type: Type) { + // >> TODO: also check if generic? + return type.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional); + } + function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) { const innerMappedType = instantiateType(type.mappedType, mapper); if (!(getObjectFlags(innerMappedType) & ObjectFlags.Mapped)) { @@ -44130,58 +44135,60 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - /* Begin weird stuff */ - const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); - const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - const queryTypeParameters = typeParameters - && filterQueryTypeParameters((container as SignatureDeclaration), typeParameters); - // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: - // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: - // `function foo(...) { - // return cond ? |expr| : ... - // }` - // - When the return expression is undefined, or it is defined and it is not one of the branches of a conditional expression, then the position is the return statement itself: - // `function foo(...) { - // |return expr;| - // }` - // or - // `function foo(...) { - // |return;| - // }` - const narrowPosition = expr && isConditionalExpression(walkUpParenthesizedExpressions(expr.parent)) ? - expr : node; - if (queryTypeParameters && narrowPosition.flowNode) { - const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, ([tp, symbol, reference]) => { - const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. - // Set the symbol of the synthetic reference. - // This allows us to get the type of the reference at a location where the reference is possibly shadowed. - getNodeLinks(narrowReference).resolvedSymbol = symbol; - setParent(narrowReference, narrowPosition.parent); - setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); - narrowReference.flowNode = narrowPosition.flowNode; - const exprType = getTypeOfExpression(narrowReference); - // Try to detect if the type of the reference was not narrowed. - // >> TODO: is there a better way of detecting that narrowing will be useless? - if (exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { - return undefined; // Don't narrow if narrowing didn't do anything but default to the type parameter or its constraint. - } - return [tp, exprType]; - }); - const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); - actualReturnType = instantiateNarrowType( - unwrappedReturnType, - narrowMapper, - /*mapper*/ undefined - ); - } + if (isNarrowableReturnType(unwrappedReturnType)) { + /* Begin weird stuff */ + const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); + const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); + const queryTypeParameters = typeParameters + && filterQueryTypeParameters((container as SignatureDeclaration), typeParameters); + // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: + // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: + // `function foo(...) { + // return cond ? |expr| : ... + // }` + // - When the return expression is undefined, or it is defined and it is not one of the branches of a conditional expression, then the position is the return statement itself: + // `function foo(...) { + // |return expr;| + // }` + // or + // `function foo(...) { + // |return;| + // }` + const narrowPosition = expr && isConditionalExpression(walkUpParenthesizedExpressions(expr.parent)) ? + expr : node; + if (queryTypeParameters && narrowPosition.flowNode) { + const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, ([tp, symbol, reference]) => { + const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. + // Set the symbol of the synthetic reference. + // This allows us to get the type of the reference at a location where the reference is possibly shadowed. + getNodeLinks(narrowReference).resolvedSymbol = symbol; + setParent(narrowReference, narrowPosition.parent); + setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); + narrowReference.flowNode = narrowPosition.flowNode; + const exprType = getTypeOfExpression(narrowReference); + // Try to detect if the type of the reference was not narrowed. + // >> TODO: is there a better way of detecting that narrowing will be useless? + if (exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { + return undefined; // Don't narrow if narrowing didn't do anything but default to the type parameter or its constraint. + } + return [tp, exprType]; + }); + const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); + actualReturnType = instantiateNarrowType( + unwrappedReturnType, + narrowMapper, + /*mapper*/ undefined + ); + } - if (expr) { - const links = getNodeLinks(expr); - if (!links.contextualReturnType) { - links.contextualReturnType = actualReturnType; + if (expr) { + const links = getNodeLinks(expr); + if (!links.contextualReturnType) { + links.contextualReturnType = actualReturnType; + } } + /* End weird stuff */ } - /* End weird stuff */ const exprType = expr ? checkExpressionCached(expr) : undefinedType; const unwrappedExprType = functionFlags & FunctionFlags.Async ? checkAwaitedType( @@ -44347,8 +44354,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // >> TODO: can reference be a synthetic node? function addReference(reference: QueryReference): void { - // >> TODO: pick only one of those? - const symbol = getSymbolAtLocation(reference, /*ignoreErrors*/ true) || getSymbolForExpression(reference); + const symbol = getSymbolForExpression(reference); const type = symbol && getTypeOfSymbol(symbol); if (!type || isErrorType(type)) return; // Check if we have an optional parameter, an optional property, or an optional tuple element. From 0b07a73d1828b616bafeb305307244d7a2db2220 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 4 Jan 2024 12:03:03 -0800 Subject: [PATCH 35/90] check if narrowable using couldContainTypeVariables --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5b74f597eea36..b64d1e10f1876 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20136,7 +20136,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function isNarrowableReturnType(type: Type) { // >> TODO: also check if generic? - return type.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional); + return type.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional) && couldContainTypeVariables(type); } function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) { From 45772c7eaae5a9fad8ba025c8c8f52c9aa92f33b Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 5 Jan 2024 17:22:37 -0800 Subject: [PATCH 36/90] fix problem with caching of flow types --- src/compiler/checker.ts | 3 +++ .../returnTypeNarrowingAfterCachingTypes.ts | 12 ++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b64d1e10f1876..c289a473e2ba2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44159,6 +44159,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (queryTypeParameters && narrowPosition.flowNode) { const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, ([tp, symbol, reference]) => { const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. + // Don't reuse the original reference's node id, + // because that could cause us to get a type that was cached for the original reference. + narrowReference.id = undefined; // Set the symbol of the synthetic reference. // This allows us to get the type of the reference at a location where the reference is possibly shadowed. getNodeLinks(narrowReference).resolvedSymbol = symbol; diff --git a/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts b/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts new file mode 100644 index 0000000000000..fd8c7da01abd4 --- /dev/null +++ b/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts @@ -0,0 +1,12 @@ +/// + +// @strict: true +//// function h(obj: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { +//// if (obj.x) { +//// return 1; +//// } +//// return 2; +//// } + +verify.encodedSemanticClassificationsLength("2020", 27); +verify.noErrors(); \ No newline at end of file From dba73d6b7910bae31cd874595bd7fcea24e87e15 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 11 Jan 2024 12:41:26 -0800 Subject: [PATCH 37/90] cleanup --- src/compiler/checker.ts | 80 +++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c289a473e2ba2..4bdad6aed870d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1509,8 +1509,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | ImportMetaProperty | SuperExpression | ThisExpression; - type QueryReferences = [Symbol, QueryReference, TypeParameter][]; - var queryTypeParameterReferencesCache: Map = new Map(); + type QueryReferences = Map>; + var queryTypeParameterReferencesCache = new Map(); // for public members that accept a Node or one of its subtypes, we must guard against // synthetic nodes created during transformations by calling `getParseTreeNode`. // for most of these, we perform the guard only on `checker` to avoid any possible @@ -44224,19 +44224,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function filterQueryTypeParameters(container: SignatureDeclaration, typeParameters: TypeParameter[]): [TypeParameter, Symbol, QueryReference][] | undefined { const queryTypeParameterReferences = collectQueryTypeParameterReferences(container); - const paramToReferences: Map> = new Map(); - for (const [symbol, reference, typeParam] of queryTypeParameterReferences) { - const id = getTypeId(typeParam); - if (!paramToReferences.has(id)) { - paramToReferences.set(id, new Map()); - } - const symbolMap = paramToReferences.get(id)!; - symbolMap.set(getSymbolId(symbol), [symbol, reference]); - } const queryParameters: [TypeParameter, Symbol, QueryReference][] = []; for (const typeParam of typeParameters) { const id = getTypeId(typeParam); - const symbolMap = paramToReferences.get(id); + const symbolMap = queryTypeParameterReferences.get(id); if (!symbolMap) continue; if (symbolMap.size === 1) { const [symbol, reference] = firstIterator(symbolMap.values()); @@ -44247,16 +44238,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function collectQueryTypeParameterReferences(container: SignatureDeclaration): QueryReferences { - const references: QueryReferences = []; + const references: QueryReferences = new Map(); const nodeId = getNodeId(container); if (!queryTypeParameterReferencesCache.has(nodeId)) { - const flowNodes = collectFlowNodes(container); // >> TODO: collectFlowNodes may have duplicates - visitFlowNodes(flowNodes, collectNode); queryTypeParameterReferencesCache.set(nodeId, references); + const flowNodes = collectReturnStatementFlowNodes(container); // >> TODO: collectFlowNodes may have duplicates + visitFlowNodes(flowNodes, getNodeFromFlowNode); } return queryTypeParameterReferencesCache.get(nodeId)!; - function collectNode(flow: FlowNode) { + // Get the node from the flow node that could have references used for narrowing. + function getNodeFromFlowNode(flow: FlowNode) { const flags = flow.flags; // Based on `getTypeAtFlowX` functions. let node; @@ -44278,21 +44270,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { (callNode.expression as PropertyAccessExpression).expression : (callNode.left as ElementAccessExpression).expression; } - if (node) getReferences(node); + if (node) getReferencesFromNode(node); } // >> TODO: Based on `isMatchingReference`? // >> TODO: needs to be based on `narrowType` as well. // If `node` came from a flow condition's condition, then it is going to be analyzed by // `narrowType`. - function getReferences(node: Node): void { + function getReferencesFromNode(node: Node): void { switch (node.kind) { case SyntaxKind.ParenthesizedExpression: case SyntaxKind.NonNullExpression: - return getReferences((node as NonNullExpression | ParenthesizedExpression).expression); + return getReferencesFromNode((node as NonNullExpression | ParenthesizedExpression).expression); case SyntaxKind.BinaryExpression: - getReferences((node as BinaryExpression).left); - getReferences((node as BinaryExpression).right); + getReferencesFromNode((node as BinaryExpression).left); + getReferencesFromNode((node as BinaryExpression).right); return; case SyntaxKind.CallExpression: let callAccess; @@ -44306,16 +44298,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { setParent(synthPropertyAccess, node.parent); addReference(synthPropertyAccess); } - getReferences((node as CallExpression).expression); // >> TODO: do we need this? + getReferencesFromNode((node as CallExpression).expression); // >> TODO: do we need this? // This is relevant for type predicate-based narrowing. - (node as CallExpression).arguments.forEach(getReferences); + (node as CallExpression).arguments.forEach(getReferencesFromNode); return; case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.PostfixUnaryExpression: // >> TODO: do we need this? - getReferences((node as PrefixUnaryExpression | PostfixUnaryExpression).operand); + getReferencesFromNode((node as PrefixUnaryExpression | PostfixUnaryExpression).operand); return; case SyntaxKind.TypeOfExpression: - getReferences((node as TypeOfExpression).expression); + getReferencesFromNode((node as TypeOfExpression).expression); return; case SyntaxKind.VoidExpression: case SyntaxKind.AwaitExpression: @@ -44355,7 +44347,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - // >> TODO: can reference be a synthetic node? function addReference(reference: QueryReference): void { const symbol = getSymbolForExpression(reference); const type = symbol && getTypeOfSymbol(symbol); @@ -44364,33 +44355,32 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // In this case, its type can be `T | undefined`, // and if `T` allows for the undefined type, then we can still narrow `T`. if (((symbol.valueDeclaration && isOptionalDeclaration(symbol.valueDeclaration)) - || isOptionalTupleElementSymbol(symbol)) + || isOptionalTupleElementSymbol(symbol)) && type.flags & TypeFlags.Union && (type as UnionType).types[0] === undefinedOrMissingType && (type as UnionType).types[1].flags & TypeFlags.TypeParameter) { const typeParam = (type as UnionType).types[1] as TypeParameter; const constraint = getConstraintOfTypeParameter(typeParam); if (!constraint || containsUndefinedType(constraint)) { - references.push([symbol, reference, typeParam]); + add(typeParam, symbol, reference); } } else if (type.flags & TypeFlags.TypeParameter) { - references.push([symbol, reference, type as TypeParameter]); + add(type, symbol, reference); } - } - } - function isOptionalTupleElementSymbol(symbol: Symbol): boolean { - if (isTransientSymbol(symbol) - && symbol.links.target - && isTransientSymbol(symbol.links.target) - && symbol.links.target.links.tupleLabelDeclaration) { - return !!symbol.links.target.links.tupleLabelDeclaration?.questionToken + function add(type: Type, symbol: Symbol, reference: QueryReference) { + const typeId = getTypeId(type); + const symbolId = getSymbolId(symbol); + if (!references.has(typeId)) { + references.set(typeId, new Map()); + } + references.get(typeId)!.set(symbolId, [symbol, reference]); + } } - return false; } - function collectFlowNodes(container: SignatureDeclaration): FlowNode[] { + function collectReturnStatementFlowNodes(container: SignatureDeclaration): FlowNode[] { const flowNodes = []; visit(container); if (isFunctionLikeDeclaration(container) && container.endFlowNode) { @@ -44461,6 +44451,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + function isOptionalTupleElementSymbol(symbol: Symbol): boolean { + if ( + isTransientSymbol(symbol) + && symbol.links.target + && isTransientSymbol(symbol.links.target) + && symbol.links.target.links.tupleLabelDeclaration + ) { + return !!symbol.links.target.links.tupleLabelDeclaration?.questionToken; + } + return false; + } + function checkWithStatement(node: WithStatement) { // Grammar checking for withStatement if (!checkGrammarStatementInAmbientContext(node)) { From 16c7d040b6c819c551792baeb9018e3a17e244b4 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 11 Jan 2024 13:03:57 -0800 Subject: [PATCH 38/90] use objects as keys for maps related to flow node references --- src/compiler/checker.ts | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4bdad6aed870d..141bc0e6387fe 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1500,8 +1500,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var lastGetCombinedModifierFlagsNode: Declaration | undefined; var lastGetCombinedModifierFlagsResult = ModifierFlags.None; - type NodeId = number; - // >> TODO: this could just be `Expression` type QueryReference = | Identifier | PropertyAccessExpression @@ -1509,8 +1507,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | ImportMetaProperty | SuperExpression | ThisExpression; - type QueryReferences = Map>; - var queryTypeParameterReferencesCache = new Map(); + type QueryReferences = Map>; + var queryTypeParameterReferencesCache = new Map(); // for public members that accept a Node or one of its subtypes, we must guard against // synthetic nodes created during transformations by calling `getParseTreeNode`. // for most of these, we perform the guard only on `checker` to avoid any possible @@ -44226,11 +44224,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const queryTypeParameterReferences = collectQueryTypeParameterReferences(container); const queryParameters: [TypeParameter, Symbol, QueryReference][] = []; for (const typeParam of typeParameters) { - const id = getTypeId(typeParam); - const symbolMap = queryTypeParameterReferences.get(id); + const symbolMap = queryTypeParameterReferences.get(typeParam); if (!symbolMap) continue; if (symbolMap.size === 1) { - const [symbol, reference] = firstIterator(symbolMap.values()); + const [symbol, reference] = firstIterator(symbolMap.entries()); queryParameters.push([typeParam, symbol, reference]); } } @@ -44239,13 +44236,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function collectQueryTypeParameterReferences(container: SignatureDeclaration): QueryReferences { const references: QueryReferences = new Map(); - const nodeId = getNodeId(container); - if (!queryTypeParameterReferencesCache.has(nodeId)) { - queryTypeParameterReferencesCache.set(nodeId, references); - const flowNodes = collectReturnStatementFlowNodes(container); // >> TODO: collectFlowNodes may have duplicates + if (!queryTypeParameterReferencesCache.has(container)) { + queryTypeParameterReferencesCache.set(container, references); + const flowNodes = collectReturnStatementFlowNodes(container); visitFlowNodes(flowNodes, getNodeFromFlowNode); } - return queryTypeParameterReferencesCache.get(nodeId)!; + return queryTypeParameterReferencesCache.get(container)!; // Get the node from the flow node that could have references used for narrowing. function getNodeFromFlowNode(flow: FlowNode) { @@ -44366,16 +44362,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else if (type.flags & TypeFlags.TypeParameter) { - add(type, symbol, reference); + add(type as TypeParameter, symbol, reference); } - function add(type: Type, symbol: Symbol, reference: QueryReference) { - const typeId = getTypeId(type); - const symbolId = getSymbolId(symbol); - if (!references.has(typeId)) { - references.set(typeId, new Map()); + function add(type: TypeParameter, symbol: Symbol, reference: QueryReference) { + if (!references.has(type)) { + references.set(type, new Map()); } - references.get(typeId)!.set(symbolId, [symbol, reference]); + references.get(type)!.set(symbol, reference); } } } From 58f0d3a4768c45db8ec7a0b5ca74db96cd27a140 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 11 Jan 2024 15:45:16 -0800 Subject: [PATCH 39/90] don't narrow to any or error type --- src/compiler/checker.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 141bc0e6387fe..a39e0f4391a77 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18831,7 +18831,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // possible (the wildcard type is assignable to and from all types). If those are not related, // then no instantiations will be and we can just return the false branch type. if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { - // >> TODO: I don't think this can ever happen -- we wouldn't narrow the check type to `any` because we don't narrow anything to `any` Debug.assert(!(checkType.flags & TypeFlags.Any)); // If falseType is an immediately nested conditional type that has an // identical checkType, switch to that type and loop. @@ -44167,10 +44166,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); narrowReference.flowNode = narrowPosition.flowNode; const exprType = getTypeOfExpression(narrowReference); - // Try to detect if the type of the reference was not narrowed. - // >> TODO: is there a better way of detecting that narrowing will be useless? - if (exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { - return undefined; // Don't narrow if narrowing didn't do anything but default to the type parameter or its constraint. + // Don't narrow the return type if narrowing didn't produce a narrower type for the expression. + if (isTypeAny(exprType) || isErrorType(exprType) || exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { + return undefined; } return [tp, exprType]; }); From fea73f1f5fcbbf39184ae4c53bce10804edfda21 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 11 Jan 2024 15:52:34 -0800 Subject: [PATCH 40/90] hasOwnProperty test --- .../reference/dependentReturnType4.symbols | 43 +++++++++++++++++++ .../reference/dependentReturnType4.types | 43 +++++++++++++++++++ tests/cases/compiler/dependentReturnType4.ts | 16 +++++++ 3 files changed, 102 insertions(+) create mode 100644 tests/baselines/reference/dependentReturnType4.symbols create mode 100644 tests/baselines/reference/dependentReturnType4.types create mode 100644 tests/cases/compiler/dependentReturnType4.ts diff --git a/tests/baselines/reference/dependentReturnType4.symbols b/tests/baselines/reference/dependentReturnType4.symbols new file mode 100644 index 0000000000000..27a89d0d1c76a --- /dev/null +++ b/tests/baselines/reference/dependentReturnType4.symbols @@ -0,0 +1,43 @@ +//// [tests/cases/compiler/dependentReturnType4.ts] //// + +=== dependentReturnType4.ts === +// Test narrowing through `hasOwnProperty` calls +declare const rand: { a?: never }; +>rand : Symbol(rand, Decl(dependentReturnType4.ts, 1, 13)) +>a : Symbol(a, Decl(dependentReturnType4.ts, 1, 21)) + +type MissingType = typeof rand.a; +>MissingType : Symbol(MissingType, Decl(dependentReturnType4.ts, 1, 34)) +>rand.a : Symbol(a, Decl(dependentReturnType4.ts, 1, 21)) +>rand : Symbol(rand, Decl(dependentReturnType4.ts, 1, 13)) +>a : Symbol(a, Decl(dependentReturnType4.ts, 1, 21)) + +declare function takesString(x: string): void; +>takesString : Symbol(takesString, Decl(dependentReturnType4.ts, 2, 33)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 3, 29)) + +function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { +>hasOwnP : Symbol(hasOwnP, Decl(dependentReturnType4.ts, 3, 46)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) +>MissingType : Symbol(MissingType, Decl(dependentReturnType4.ts, 1, 34)) +>obj : Symbol(obj, Decl(dependentReturnType4.ts, 4, 49)) +>a : Symbol(a, Decl(dependentReturnType4.ts, 4, 55)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) + + if (obj.hasOwnProperty("a")) { +>obj.hasOwnProperty : Symbol(Object.hasOwnProperty, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(dependentReturnType4.ts, 4, 49)) +>hasOwnProperty : Symbol(Object.hasOwnProperty, Decl(lib.es5.d.ts, --, --)) + + takesString(obj.a); +>takesString : Symbol(takesString, Decl(dependentReturnType4.ts, 2, 33)) +>obj.a : Symbol(a, Decl(dependentReturnType4.ts, 4, 55)) +>obj : Symbol(obj, Decl(dependentReturnType4.ts, 4, 49)) +>a : Symbol(a, Decl(dependentReturnType4.ts, 4, 55)) + + return 1; + } + return 2; +} diff --git a/tests/baselines/reference/dependentReturnType4.types b/tests/baselines/reference/dependentReturnType4.types new file mode 100644 index 0000000000000..56fa754ec04e4 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType4.types @@ -0,0 +1,43 @@ +//// [tests/cases/compiler/dependentReturnType4.ts] //// + +=== dependentReturnType4.ts === +// Test narrowing through `hasOwnProperty` calls +declare const rand: { a?: never }; +>rand : { a?: never; } +>a : undefined + +type MissingType = typeof rand.a; +>MissingType : undefined +>rand.a : undefined +>rand : { a?: never; } +>a : undefined + +declare function takesString(x: string): void; +>takesString : (x: string) => void +>x : string + +function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { +>hasOwnP : (obj: { a?: T;}) => T extends string ? 1 : T extends undefined ? 2 : 1 | 2 +>obj : { a?: T; } +>a : T | undefined + + if (obj.hasOwnProperty("a")) { +>obj.hasOwnProperty("a") : boolean +>obj.hasOwnProperty : (v: PropertyKey) => boolean +>obj : { a?: T; } +>hasOwnProperty : (v: PropertyKey) => boolean +>"a" : "a" + + takesString(obj.a); +>takesString(obj.a) : void +>takesString : (x: string) => void +>obj.a : string +>obj : { a?: T; } +>a : string + + return 1; +>1 : 1 + } + return 2; +>2 : 2 +} diff --git a/tests/cases/compiler/dependentReturnType4.ts b/tests/cases/compiler/dependentReturnType4.ts new file mode 100644 index 0000000000000..ef4d29bdbbb14 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType4.ts @@ -0,0 +1,16 @@ +// @strict: true +// @noEmit: true +// @target: ES2022 +// @exactOptionalPropertyTypes: true + +// Test narrowing through `hasOwnProperty` calls +declare const rand: { a?: never }; +type MissingType = typeof rand.a; +declare function takesString(x: string): void; +function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { + if (obj.hasOwnProperty("a")) { + takesString(obj.a); + return 1; + } + return 2; +} \ No newline at end of file From 51a3516513a1a083d8d730fc36229b4a465cabd4 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 11 Jan 2024 15:59:41 -0800 Subject: [PATCH 41/90] get rid of closure --- src/compiler/checker.ts | 188 +++++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 90 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a39e0f4391a77..935bb7386cfbd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44106,7 +44106,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const signature = getSignatureFromDeclaration(container); const returnType = getReturnTypeOfSignature(signature); - const functionFlags = getFunctionFlags(container); if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { if (container.kind === SyntaxKind.SetAccessor) { if (node.expression) { @@ -44120,104 +44119,113 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else if (getReturnTypeFromAnnotation(container)) { - const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; - checkReturnStatementExpression(node.expression); - - function checkReturnStatementExpression(expr: Expression | undefined): void { - let actualReturnType = unwrappedReturnType; - if (expr) { - expr = skipParentheses(expr); - if (isConditionalExpression(expr)) { - return checkReturnConditionalExpression(expr); - } - } - - if (isNarrowableReturnType(unwrappedReturnType)) { - /* Begin weird stuff */ - const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); - const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - const queryTypeParameters = typeParameters - && filterQueryTypeParameters((container as SignatureDeclaration), typeParameters); - // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: - // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: - // `function foo(...) { - // return cond ? |expr| : ... - // }` - // - When the return expression is undefined, or it is defined and it is not one of the branches of a conditional expression, then the position is the return statement itself: - // `function foo(...) { - // |return expr;| - // }` - // or - // `function foo(...) { - // |return;| - // }` - const narrowPosition = expr && isConditionalExpression(walkUpParenthesizedExpressions(expr.parent)) ? - expr : node; - if (queryTypeParameters && narrowPosition.flowNode) { - const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, ([tp, symbol, reference]) => { - const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. - // Don't reuse the original reference's node id, - // because that could cause us to get a type that was cached for the original reference. - narrowReference.id = undefined; - // Set the symbol of the synthetic reference. - // This allows us to get the type of the reference at a location where the reference is possibly shadowed. - getNodeLinks(narrowReference).resolvedSymbol = symbol; - setParent(narrowReference, narrowPosition.parent); - setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); - narrowReference.flowNode = narrowPosition.flowNode; - const exprType = getTypeOfExpression(narrowReference); - // Don't narrow the return type if narrowing didn't produce a narrower type for the expression. - if (isTypeAny(exprType) || isErrorType(exprType) || exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { - return undefined; - } - return [tp, exprType]; - }); - const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); - actualReturnType = instantiateNarrowType( - unwrappedReturnType, - narrowMapper, - /*mapper*/ undefined - ); - } + checkReturnStatementExpression(container, returnType, node, node.expression); + } + } + else if (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeUndefinedVoidOrAny(container, returnType)) { + // The function has a return type, but the return statement doesn't have an expression. + error(node, Diagnostics.Not_all_code_paths_return_a_value); + } + } - if (expr) { - const links = getNodeLinks(expr); - if (!links.contextualReturnType) { - links.contextualReturnType = actualReturnType; - } - } - /* End weird stuff */ - } - const exprType = expr ? checkExpressionCached(expr) : undefinedType; - const unwrappedExprType = functionFlags & FunctionFlags.Async - ? checkAwaitedType( - exprType, - /*withAlias*/ false, - node, - Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) - : exprType; - if (actualReturnType) { - // If the function has a return type, but promisedType is - // undefined, an error will be reported in checkAsyncFunctionReturnType - // so we don't need to report one here. - const errorNode = node.expression && isConditionalExpression(skipParentheses(node.expression)) ? expr : node; - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, actualReturnType, errorNode, expr); + function checkReturnStatementExpression( + container: SignatureDeclaration, + returnType: Type, + node: ReturnStatement, + expr: Expression | undefined): void { + const functionFlags = getFunctionFlags(container); + const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; + let actualReturnType = unwrappedReturnType; + if (expr) { + expr = skipParentheses(expr); + if (isConditionalExpression(expr)) { + return checkReturnConditionalExpression(container, returnType, node, expr); + } + } + + if (isNarrowableReturnType(unwrappedReturnType)) { + /* Begin weird stuff */ + const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); + const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); + const queryTypeParameters = typeParameters + && filterQueryTypeParameters((container as SignatureDeclaration), typeParameters); + // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: + // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: + // `function foo(...) { + // return cond ? |expr| : ... + // }` + // - When the return expression is undefined, or it is defined and it is not one of the branches of a conditional expression, then the position is the return statement itself: + // `function foo(...) { + // |return expr;| + // }` + // or + // `function foo(...) { + // |return;| + // }` + const narrowPosition = expr && isConditionalExpression(walkUpParenthesizedExpressions(expr.parent)) ? + expr : node; + if (queryTypeParameters && narrowPosition.flowNode) { + const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, ([tp, symbol, reference]) => { + const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. + // Don't reuse the original reference's node id, + // because that could cause us to get a type that was cached for the original reference. + narrowReference.id = undefined; + // Set the symbol of the synthetic reference. + // This allows us to get the type of the reference at a location where the reference is possibly shadowed. + getNodeLinks(narrowReference).resolvedSymbol = symbol; + setParent(narrowReference, narrowPosition.parent); + setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); + narrowReference.flowNode = narrowPosition.flowNode; + const exprType = getTypeOfExpression(narrowReference); + // Don't narrow the return type if narrowing didn't produce a narrower type for the expression. + if (isTypeAny(exprType) || isErrorType(exprType) || exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { + return undefined; } - } + return [tp, exprType]; + }); + const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); + actualReturnType = instantiateNarrowType( + unwrappedReturnType, + narrowMapper, + /*mapper*/ undefined + ); + } - function checkReturnConditionalExpression(expr: ConditionalExpression): void { - checkExpression(expr.condition); - checkReturnStatementExpression(expr.whenTrue); - checkReturnStatementExpression(expr.whenFalse); + if (expr) { + const links = getNodeLinks(expr); + if (!links.contextualReturnType) { + links.contextualReturnType = actualReturnType; } } + /* End weird stuff */ } - else if (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeUndefinedVoidOrAny(container, returnType)) { - // The function has a return type, but the return statement doesn't have an expression. - error(node, Diagnostics.Not_all_code_paths_return_a_value); + const exprType = expr ? checkExpressionCached(expr) : undefinedType; + const unwrappedExprType = functionFlags & FunctionFlags.Async + ? checkAwaitedType( + exprType, + /*withAlias*/ false, + node, + Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) + : exprType; + if (actualReturnType) { + // If the function has a return type, but promisedType is + // undefined, an error will be reported in checkAsyncFunctionReturnType + // so we don't need to report one here. + const errorNode = node.expression && isConditionalExpression(skipParentheses(node.expression)) ? expr : node; + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, actualReturnType, errorNode, expr); } } + function checkReturnConditionalExpression( + container: SignatureDeclaration, + returnType: Type, + node: ReturnStatement, + expr: ConditionalExpression): void { + checkExpression(expr.condition); + checkReturnStatementExpression(container, returnType, node, expr.whenTrue); + checkReturnStatementExpression(container, returnType, node, expr.whenFalse); + } + function filterQueryTypeParameters(container: SignatureDeclaration, typeParameters: TypeParameter[]): [TypeParameter, Symbol, QueryReference][] | undefined { const queryTypeParameterReferences = collectQueryTypeParameterReferences(container); const queryParameters: [TypeParameter, Symbol, QueryReference][] = []; From 5a9022fae11fa0ec04132bac18bd084baab3b1ae Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 11 Jan 2024 16:48:06 -0800 Subject: [PATCH 42/90] attach flow nodes directly to conditional expression --- src/compiler/binder.ts | 6 +- src/compiler/checker.ts | 29 +++---- src/compiler/types.ts | 7 +- src/compiler/utilities.ts | 24 ++---- src/services/services.ts | 1 - .../reference/dependentReturnType1.errors.txt | 16 ++++ .../reference/dependentReturnType1.symbols | 59 ++++++++++++++ .../reference/dependentReturnType1.types | 76 +++++++++++++++++++ tests/cases/compiler/dependentReturnType1.ts | 16 ++++ 9 files changed, 196 insertions(+), 38 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 1d8614f749e2a..4e51cab4cded3 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1986,16 +1986,14 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { bindCondition(node.condition, trueLabel, falseLabel); currentFlow = finishFlowLabel(trueLabel); if (inReturnStatement) { - const expr = skipParentheses(node.whenTrue); - expr.flowNode = currentFlow; + node.flowNodeWhenTrue = currentFlow; } bind(node.questionToken); bind(node.whenTrue); addAntecedent(postExpressionLabel, currentFlow); currentFlow = finishFlowLabel(falseLabel); if (inReturnStatement) { - const expr = skipParentheses(node.whenFalse); - expr.flowNode = currentFlow; + node.flowNodeWhenFalse = currentFlow; } bind(node.colonToken); bind(node.whenFalse); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 935bb7386cfbd..a7db278337ad5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44137,9 +44137,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; let actualReturnType = unwrappedReturnType; if (expr) { - expr = skipParentheses(expr); - if (isConditionalExpression(expr)) { - return checkReturnConditionalExpression(container, returnType, node, expr); + const unwrappedExpr = skipParentheses(expr); + if (isConditionalExpression(unwrappedExpr)) { + return checkReturnConditionalExpression(container, returnType, node, unwrappedExpr); } } @@ -44162,9 +44162,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // `function foo(...) { // |return;| // }` - const narrowPosition = expr && isConditionalExpression(walkUpParenthesizedExpressions(expr.parent)) ? - expr : node; - if (queryTypeParameters && narrowPosition.flowNode) { + let narrowPosition = node; + let narrowFlowNode = node.flowNode; + if (expr && isConditionalExpression(expr.parent)) { + narrowFlowNode = expr.parent.whenTrue === expr ? expr.parent.flowNodeWhenTrue : expr.parent.flowNodeWhenFalse; + } + if (queryTypeParameters && narrowFlowNode) { const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, ([tp, symbol, reference]) => { const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. // Don't reuse the original reference's node id, @@ -44175,7 +44178,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getNodeLinks(narrowReference).resolvedSymbol = symbol; setParent(narrowReference, narrowPosition.parent); setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); - narrowReference.flowNode = narrowPosition.flowNode; + narrowReference.flowNode = narrowFlowNode; const exprType = getTypeOfExpression(narrowReference); // Don't narrow the return type if narrowing didn't produce a narrower type for the expression. if (isTypeAny(exprType) || isErrorType(exprType) || exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { @@ -44381,7 +44384,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function collectReturnStatementFlowNodes(container: SignatureDeclaration): FlowNode[] { - const flowNodes = []; + const flowNodes: FlowNode[] = []; visit(container); if (isFunctionLikeDeclaration(container) && container.endFlowNode) { flowNodes.push(container.endFlowNode); @@ -44393,8 +44396,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const nodeContainer = getContainingFunctionOrClassStaticBlock((node as ReturnStatement)); if (nodeContainer === container) { visitConditionalReturnExpression((node as ReturnStatement).expression); - if ((node as ReturnStatement).flowNode) { - flowNodes.push((node as ReturnStatement).flowNode); + const flowNode = (node as ReturnStatement).flowNode; + if (flowNode) { + flowNodes.push(flowNode); } } return; @@ -44407,12 +44411,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!node) return; node = skipParentheses(node); if (isConditionalExpression(node)) { + node.flowNodeWhenTrue && flowNodes.push(node.flowNodeWhenTrue); + node.flowNodeWhenFalse && flowNodes.push(node.flowNodeWhenFalse); visitConditionalReturnExpression(node.whenTrue); visitConditionalReturnExpression(node.whenFalse); } - else if (node.flowNode) { - flowNodes.push(node.flowNode); - } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 02903fb568894..8f5d619286570 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -967,7 +967,6 @@ export interface FlowContainer extends Node { /** @internal */ export type HasFlowNode = - | Expression | Identifier | ThisExpression | SuperExpression @@ -2365,7 +2364,7 @@ export interface TemplateLiteralTypeSpan extends TypeNode { // checker actually thinks you have something of the right type. Note: the brands are // never actually given values. At runtime they have zero cost. -export interface Expression extends FlowContainer, Node { +export interface Expression extends Node { _expressionBrand: any; } @@ -2711,6 +2710,10 @@ export interface ConditionalExpression extends Expression { readonly whenTrue: Expression; readonly colonToken: ColonToken; readonly whenFalse: Expression; + /** @internal*/ + flowNodeWhenTrue?: FlowNode; + /** @internal */ + flowNodeWhenFalse?: FlowNode; } export type FunctionBody = Block; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a34316f59b438..1c1f9ce8b633b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -261,7 +261,6 @@ import { isEnumMember, isExportAssignment, isExportDeclaration, - isExpression, isExpressionStatement, isExpressionWithTypeArguments, isExternalModule, @@ -4164,24 +4163,13 @@ export function canHaveFlowNode(node: Node): node is HasFlowNode { return true; } - // >> TODO: how precise do we want to be here? - if (isExpression(node)) { - return true; - } - // let parent; - // if (isExpression(node) - // && isConditionalExpression(parent = walkUpParenthesizedExpressions(node.parent)) - // && isConditionalExpressionInReturnStatement(parent)) { - // return true; - // } - switch (node.kind) { - // case SyntaxKind.Identifier: - // case SyntaxKind.ThisKeyword: - // case SyntaxKind.SuperKeyword: - // case SyntaxKind.ElementAccessExpression: - // case SyntaxKind.PropertyAccessExpression: - // case SyntaxKind.FunctionExpression: + case SyntaxKind.Identifier: + case SyntaxKind.ThisKeyword: + case SyntaxKind.SuperKeyword: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.FunctionExpression: case SyntaxKind.QualifiedName: case SyntaxKind.MetaProperty: case SyntaxKind.BindingElement: diff --git a/src/services/services.ts b/src/services/services.ts index e5325e7aeda02..812eafd2c940e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -764,7 +764,6 @@ IdentifierObject.prototype.kind = SyntaxKind.Identifier; class PrivateIdentifierObject extends TokenOrIdentifierObject implements PrivateIdentifier { public override kind: SyntaxKind.PrivateIdentifier = SyntaxKind.PrivateIdentifier; public escapedText!: __String; - declare _flowContainerBrand: any; declare _primaryExpressionBrand: any; declare _memberExpressionBrand: any; declare _leftHandSideExpressionBrand: any; diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index a32aca66f1e02..a4a7bd15eeb94 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -597,4 +597,20 @@ dependentReturnType1.ts(463,13): error TS2322: Type 'R' is not assignable to typ return 1; } return 2; + } + + // Return conditional expressions with parentheses + function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { + return (opts.x ? (1) : 2); + } + + function returnStuff2(opts: { x: T }): + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 { + return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); + } + + // If the return type is written wrong, it still type checks + function returnStuff3(opts: { x: T }): + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" { + return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 6030d0102aa7a..fc526ef75b383 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -1321,3 +1321,62 @@ function tupl(x: [string, some?: T]): } return 2; } + +// Return conditional expressions with parentheses +function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { +>returnStuff1 : Symbol(returnStuff1, Decl(dependentReturnType1.ts, 478, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 481, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 481, 41)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 481, 48)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 481, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 481, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 481, 22)) + + return (opts.x ? (1) : 2); +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 481, 48)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 481, 41)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 481, 48)) +} + +function returnStuff2(opts: { x: T }): +>returnStuff2 : Symbol(returnStuff2, Decl(dependentReturnType1.ts, 483, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 485, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 485, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 485, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 485, 22)) + + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 { +>T : Symbol(T, Decl(dependentReturnType1.ts, 485, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 485, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 485, 22)) + + return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 485, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 485, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 485, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 485, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 485, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 485, 52)) +} + +// If the return type is written wrong, it still type checks +function returnStuff3(opts: { x: T }): +>returnStuff3 : Symbol(returnStuff3, Decl(dependentReturnType1.ts, 488, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 491, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 491, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 491, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 491, 22)) + + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" { +>T : Symbol(T, Decl(dependentReturnType1.ts, 491, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 491, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 491, 22)) + + return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 491, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 491, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 491, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 491, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 491, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 491, 52)) +} diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 497732e2a3244..f151cdfd51965 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -1286,3 +1286,79 @@ function tupl(x: [string, some?: T]): return 2; >2 : 2 } + +// Return conditional expressions with parentheses +function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { +>returnStuff1 : (opts: { x: T;}) => T extends true ? 1 : T extends false ? 2 : 1 | 2 +>opts : { x: T; } +>x : T +>true : true +>false : false + + return (opts.x ? (1) : 2); +>(opts.x ? (1) : 2) : 1 | 2 +>opts.x ? (1) : 2 : 1 | 2 +>opts.x : T +>opts : { x: T; } +>x : T +>(1) : 1 +>1 : 1 +>2 : 2 +} + +function returnStuff2(opts: { x: T }): +>returnStuff2 : (opts: { x: T;}) => T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 +>opts : { x: T; } +>x : T + + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 { + return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); +>(typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")) : 0 | "one" | "two" +>typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two") : 0 | "one" | "two" +>typeof opts.x === "string" : boolean +>typeof opts.x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>opts.x : T +>opts : { x: T; } +>x : T +>"string" : "string" +>0 : 0 +>(opts.x === 1 ? ("one") : "two") : "one" | "two" +>opts.x === 1 ? ("one") : "two" : "one" | "two" +>opts.x === 1 : boolean +>opts.x : T +>opts : { x: T; } +>x : T +>1 : 1 +>("one") : "one" +>"one" : "one" +>"two" : "two" +} + +// If the return type is written wrong, it still type checks +function returnStuff3(opts: { x: T }): +>returnStuff3 : (opts: { x: T;}) => T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" +>opts : { x: T; } +>x : T + + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" { + return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); +>(typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")) : 0 | "one" | "two" +>typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two") : 0 | "one" | "two" +>typeof opts.x === "string" : boolean +>typeof opts.x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>opts.x : T +>opts : { x: T; } +>x : T +>"string" : "string" +>0 : 0 +>(opts.x === 1 ? ("one") : "two") : "one" | "two" +>opts.x === 1 ? ("one") : "two" : "one" | "two" +>opts.x === 1 : boolean +>opts.x : T +>opts : { x: T; } +>x : T +>1 : 1 +>("one") : "one" +>"one" : "one" +>"two" : "two" +} diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index 56258054ef194..f099cf706177c 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -480,4 +480,20 @@ function tupl(x: [string, some?: T]): return 1; } return 2; +} + +// Return conditional expressions with parentheses +function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { + return (opts.x ? (1) : 2); +} + +function returnStuff2(opts: { x: T }): + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 { + return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); +} + +// If the return type is written wrong, it still type checks +function returnStuff3(opts: { x: T }): + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" { + return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); } \ No newline at end of file From 76f18999b87eacb17d66247d9e2600df798eb83a Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 11 Jan 2024 17:10:03 -0800 Subject: [PATCH 43/90] update baselines --- .../reference/dependentReturnType2.errors.txt | 99 +----- .../reference/dependentReturnType2.symbols | 327 ++---------------- .../reference/dependentReturnType2.types | 315 ++--------------- 3 files changed, 88 insertions(+), 653 deletions(-) diff --git a/tests/baselines/reference/dependentReturnType2.errors.txt b/tests/baselines/reference/dependentReturnType2.errors.txt index e161beca88226..e517ea1c68860 100644 --- a/tests/baselines/reference/dependentReturnType2.errors.txt +++ b/tests/baselines/reference/dependentReturnType2.errors.txt @@ -1,91 +1,20 @@ -dependentReturnType2.ts(61,13): error TS2322: Type 'undefined' is not assignable to type 'HelperCond[]>'. -dependentReturnType2.ts(78,16): error TS2536: Type 'I' cannot be used to index type '{ [s: string]: any; }'. +dependentReturnType2.ts(3,62): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. +dependentReturnType2.ts(6,9): error TS2322: Type 'string' is not assignable to type 'T extends { a: string; } ? number : string | number'. ==== dependentReturnType2.ts (2 errors) ==== - // ---5 - type SettingComposedValue = { key: string; value: T }; - type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void; - - type SettingValue = object; - declare const Meteor: { settings: { [s: string]: any } }; - declare const _: { isRegExp(x: unknown): x is RegExp; }; - - type HelperCond = - T extends A - ? R1 - : T extends B - ? R2 - : (R1 | R2); - - declare function takesRegExp(x: RegExp): void; - declare function takesString(x: string): void; - - class NewSettingsBase { - public newGet( - _id: I, - callback?: C, - ): HelperCond[]>> { - if (callback !== undefined) { - // this.onload(_id, callback); - if (!Meteor.settings) { - return; - } - if (_id === '*') { - return Object.keys(Meteor.settings).forEach((key) => { - const value = Meteor.settings[key]; - callback(key, value); - }); - } - if (_.isRegExp(_id) && Meteor.settings) { - return Object.keys(Meteor.settings).forEach((key) => { - if (!_id.test(key)) { - return; - } - const value = Meteor.settings[key]; - callback(key, value); - }); - } - - if (typeof _id === 'string') { - const value = Meteor.settings[_id]; - if (value != null) { - callback(_id, Meteor.settings[_id]); - } - return; - } - - return; // Needed to add this for exhaustiveness - } - - if (!Meteor.settings) { // Wrong: we don't know that _id is string here, cannot return undefined - return undefined; - ~~~~~~ -!!! error TS2322: Type 'undefined' is not assignable to type 'HelperCond[]>'. - } - - if (_.isRegExp(_id)) { - takesRegExp(_id); - return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { - const value = Meteor.settings[key]; - if (_id.test(key)) { - items.push({ - key, - value, - }); - } - return items; - }, []); - } - - return Meteor.settings?.[_id]; // The indexing doesn't work - ~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2536: Type 'I' cannot be used to index type '{ [s: string]: any; }'. + declare function q(x: object): x is { b: number }; + + function foo(x: T): T extends { a: string } ? number : (string | number) { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'. + if (q(x)) { + x.b; + return ""; + ~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'T extends { a: string; } ? number : string | number'. } } - declare function takesRegExp(x: RegExp): void; \ No newline at end of file + let y = { a: "", b: 1 } + const r = foo<{ a: string }>(y); // number \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType2.symbols b/tests/baselines/reference/dependentReturnType2.symbols index 2474b3baf27ce..9091f7b637b61 100644 --- a/tests/baselines/reference/dependentReturnType2.symbols +++ b/tests/baselines/reference/dependentReturnType2.symbols @@ -1,298 +1,43 @@ //// [tests/cases/compiler/dependentReturnType2.ts] //// === dependentReturnType2.ts === -// ---5 -type SettingComposedValue = { key: string; value: T }; ->SettingComposedValue : Symbol(SettingComposedValue, Decl(dependentReturnType2.ts, 0, 0)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 1, 26)) ->SettingValue : Symbol(SettingValue, Decl(dependentReturnType2.ts, 2, 89)) ->SettingValue : Symbol(SettingValue, Decl(dependentReturnType2.ts, 2, 89)) ->key : Symbol(key, Decl(dependentReturnType2.ts, 1, 68)) ->value : Symbol(value, Decl(dependentReturnType2.ts, 1, 81)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 1, 26)) - -type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void; ->SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType2.ts, 1, 93)) ->key : Symbol(key, Decl(dependentReturnType2.ts, 2, 24)) ->value : Symbol(value, Decl(dependentReturnType2.ts, 2, 36)) ->SettingValue : Symbol(SettingValue, Decl(dependentReturnType2.ts, 2, 89)) ->initialLoad : Symbol(initialLoad, Decl(dependentReturnType2.ts, 2, 57)) - -type SettingValue = object; ->SettingValue : Symbol(SettingValue, Decl(dependentReturnType2.ts, 2, 89)) - -declare const Meteor: { settings: { [s: string]: any } }; ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->s : Symbol(s, Decl(dependentReturnType2.ts, 5, 37)) - -declare const _: { isRegExp(x: unknown): x is RegExp; }; ->_ : Symbol(_, Decl(dependentReturnType2.ts, 6, 13)) ->isRegExp : Symbol(isRegExp, Decl(dependentReturnType2.ts, 6, 18)) ->x : Symbol(x, Decl(dependentReturnType2.ts, 6, 28)) ->x : Symbol(x, Decl(dependentReturnType2.ts, 6, 28)) ->RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) - -type HelperCond = ->HelperCond : Symbol(HelperCond, Decl(dependentReturnType2.ts, 6, 56)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 8, 16)) ->A : Symbol(A, Decl(dependentReturnType2.ts, 8, 18)) ->R1 : Symbol(R1, Decl(dependentReturnType2.ts, 8, 21)) ->B : Symbol(B, Decl(dependentReturnType2.ts, 8, 25)) ->R2 : Symbol(R2, Decl(dependentReturnType2.ts, 8, 28)) - - T extends A ->T : Symbol(T, Decl(dependentReturnType2.ts, 8, 16)) ->A : Symbol(A, Decl(dependentReturnType2.ts, 8, 18)) - - ? R1 ->R1 : Symbol(R1, Decl(dependentReturnType2.ts, 8, 21)) - - : T extends B ->T : Symbol(T, Decl(dependentReturnType2.ts, 8, 16)) ->B : Symbol(B, Decl(dependentReturnType2.ts, 8, 25)) - - ? R2 ->R2 : Symbol(R2, Decl(dependentReturnType2.ts, 8, 28)) - - : (R1 | R2); ->R1 : Symbol(R1, Decl(dependentReturnType2.ts, 8, 21)) ->R2 : Symbol(R2, Decl(dependentReturnType2.ts, 8, 28)) - -declare function takesRegExp(x: RegExp): void; ->takesRegExp : Symbol(takesRegExp, Decl(dependentReturnType2.ts, 13, 24), Decl(dependentReturnType2.ts, 79, 1)) ->x : Symbol(x, Decl(dependentReturnType2.ts, 15, 29)) ->RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) - -declare function takesString(x: string): void; ->takesString : Symbol(takesString, Decl(dependentReturnType2.ts, 15, 46)) ->x : Symbol(x, Decl(dependentReturnType2.ts, 16, 29)) - -class NewSettingsBase { ->NewSettingsBase : Symbol(NewSettingsBase, Decl(dependentReturnType2.ts, 16, 46)) - - public newGet( ->newGet : Symbol(NewSettingsBase.newGet, Decl(dependentReturnType2.ts, 18, 23)) ->C : Symbol(C, Decl(dependentReturnType2.ts, 19, 18)) ->SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType2.ts, 1, 93)) ->I : Symbol(I, Decl(dependentReturnType2.ts, 19, 56)) ->RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 19, 83)) ->SettingValue : Symbol(SettingValue, Decl(dependentReturnType2.ts, 2, 89)) ->SettingValue : Symbol(SettingValue, Decl(dependentReturnType2.ts, 2, 89)) - - _id: I, ->_id : Symbol(_id, Decl(dependentReturnType2.ts, 19, 123)) ->I : Symbol(I, Decl(dependentReturnType2.ts, 19, 56)) - - callback?: C, ->callback : Symbol(callback, Decl(dependentReturnType2.ts, 20, 15)) ->C : Symbol(C, Decl(dependentReturnType2.ts, 19, 18)) - - ): HelperCondHelperCond : Symbol(HelperCond, Decl(dependentReturnType2.ts, 6, 56)) ->C : Symbol(C, Decl(dependentReturnType2.ts, 19, 18)) - - SettingCallback, void, ->SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType2.ts, 1, 93)) - - undefined, HelperCondHelperCond : Symbol(HelperCond, Decl(dependentReturnType2.ts, 6, 56)) ->I : Symbol(I, Decl(dependentReturnType2.ts, 19, 56)) - - string, T | undefined, ->T : Symbol(T, Decl(dependentReturnType2.ts, 19, 83)) - - RegExp, SettingComposedValue[]>> { ->RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) ->SettingComposedValue : Symbol(SettingComposedValue, Decl(dependentReturnType2.ts, 0, 0)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 19, 83)) - - if (callback !== undefined) { ->callback : Symbol(callback, Decl(dependentReturnType2.ts, 20, 15)) ->undefined : Symbol(undefined) - - // this.onload(_id, callback); - if (!Meteor.settings) { ->Meteor.settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) - - return; - } - if (_id === '*') { ->_id : Symbol(_id, Decl(dependentReturnType2.ts, 19, 123)) - - return Object.keys(Meteor.settings).forEach((key) => { ->Object.keys(Meteor.settings).forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) ->Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --)) ->Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) ->keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) ->key : Symbol(key, Decl(dependentReturnType2.ts, 33, 61)) - - const value = Meteor.settings[key]; ->value : Symbol(value, Decl(dependentReturnType2.ts, 34, 25)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->key : Symbol(key, Decl(dependentReturnType2.ts, 33, 61)) - - callback(key, value); ->callback : Symbol(callback, Decl(dependentReturnType2.ts, 20, 15)) ->key : Symbol(key, Decl(dependentReturnType2.ts, 33, 61)) ->value : Symbol(value, Decl(dependentReturnType2.ts, 34, 25)) - - }); - } - if (_.isRegExp(_id) && Meteor.settings) { ->_.isRegExp : Symbol(isRegExp, Decl(dependentReturnType2.ts, 6, 18)) ->_ : Symbol(_, Decl(dependentReturnType2.ts, 6, 13)) ->isRegExp : Symbol(isRegExp, Decl(dependentReturnType2.ts, 6, 18)) ->_id : Symbol(_id, Decl(dependentReturnType2.ts, 19, 123)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) - - return Object.keys(Meteor.settings).forEach((key) => { ->Object.keys(Meteor.settings).forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) ->Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --)) ->Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) ->keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) ->key : Symbol(key, Decl(dependentReturnType2.ts, 39, 61)) - - if (!_id.test(key)) { ->_id.test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) ->_id : Symbol(_id, Decl(dependentReturnType2.ts, 19, 123)) ->test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) ->key : Symbol(key, Decl(dependentReturnType2.ts, 39, 61)) - - return; - } - const value = Meteor.settings[key]; ->value : Symbol(value, Decl(dependentReturnType2.ts, 43, 25)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->key : Symbol(key, Decl(dependentReturnType2.ts, 39, 61)) - - callback(key, value); ->callback : Symbol(callback, Decl(dependentReturnType2.ts, 20, 15)) ->key : Symbol(key, Decl(dependentReturnType2.ts, 39, 61)) ->value : Symbol(value, Decl(dependentReturnType2.ts, 43, 25)) - - }); - } - - if (typeof _id === 'string') { ->_id : Symbol(_id, Decl(dependentReturnType2.ts, 19, 123)) - - const value = Meteor.settings[_id]; ->value : Symbol(value, Decl(dependentReturnType2.ts, 49, 21)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->_id : Symbol(_id, Decl(dependentReturnType2.ts, 19, 123)) - - if (value != null) { ->value : Symbol(value, Decl(dependentReturnType2.ts, 49, 21)) - - callback(_id, Meteor.settings[_id]); ->callback : Symbol(callback, Decl(dependentReturnType2.ts, 20, 15)) ->_id : Symbol(_id, Decl(dependentReturnType2.ts, 19, 123)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->_id : Symbol(_id, Decl(dependentReturnType2.ts, 19, 123)) - } - return; - } - - return; // Needed to add this for exhaustiveness - } - - if (!Meteor.settings) { // Wrong: we don't know that _id is string here, cannot return undefined ->Meteor.settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) - - return undefined; ->undefined : Symbol(undefined) - } - - if (_.isRegExp(_id)) { ->_.isRegExp : Symbol(isRegExp, Decl(dependentReturnType2.ts, 6, 18)) ->_ : Symbol(_, Decl(dependentReturnType2.ts, 6, 13)) ->isRegExp : Symbol(isRegExp, Decl(dependentReturnType2.ts, 6, 18)) ->_id : Symbol(_id, Decl(dependentReturnType2.ts, 19, 123)) - - takesRegExp(_id); ->takesRegExp : Symbol(takesRegExp, Decl(dependentReturnType2.ts, 13, 24), Decl(dependentReturnType2.ts, 79, 1)) ->_id : Symbol(_id, Decl(dependentReturnType2.ts, 19, 123)) - - return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { ->Object.keys(Meteor.settings).reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) ->Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --)) ->Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) ->keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) ->items : Symbol(items, Decl(dependentReturnType2.ts, 65, 56)) ->SettingComposedValue : Symbol(SettingComposedValue, Decl(dependentReturnType2.ts, 0, 0)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 19, 83)) ->key : Symbol(key, Decl(dependentReturnType2.ts, 65, 89)) - - const value = Meteor.settings[key]; ->value : Symbol(value, Decl(dependentReturnType2.ts, 66, 21)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->key : Symbol(key, Decl(dependentReturnType2.ts, 65, 89)) - - if (_id.test(key)) { ->_id.test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) ->_id : Symbol(_id, Decl(dependentReturnType2.ts, 19, 123)) ->test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) ->key : Symbol(key, Decl(dependentReturnType2.ts, 65, 89)) - - items.push({ ->items.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) ->items : Symbol(items, Decl(dependentReturnType2.ts, 65, 56)) ->push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) - - key, ->key : Symbol(key, Decl(dependentReturnType2.ts, 68, 32)) - - value, ->value : Symbol(value, Decl(dependentReturnType2.ts, 69, 28)) - - }); - } - return items; ->items : Symbol(items, Decl(dependentReturnType2.ts, 65, 56)) - - }, []); - } - - return Meteor.settings?.[_id]; // The indexing doesn't work ->Meteor.settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType2.ts, 5, 13)) ->settings : Symbol(settings, Decl(dependentReturnType2.ts, 5, 23)) ->_id : Symbol(_id, Decl(dependentReturnType2.ts, 19, 123)) +declare function q(x: object): x is { b: number }; +>q : Symbol(q, Decl(dependentReturnType2.ts, 0, 0)) +>x : Symbol(x, Decl(dependentReturnType2.ts, 0, 19)) +>x : Symbol(x, Decl(dependentReturnType2.ts, 0, 19)) +>b : Symbol(b, Decl(dependentReturnType2.ts, 0, 37)) + +function foo(x: T): T extends { a: string } ? number : (string | number) { +>foo : Symbol(foo, Decl(dependentReturnType2.ts, 0, 50)) +>T : Symbol(T, Decl(dependentReturnType2.ts, 2, 13)) +>a : Symbol(a, Decl(dependentReturnType2.ts, 2, 24)) +>b : Symbol(b, Decl(dependentReturnType2.ts, 2, 40)) +>x : Symbol(x, Decl(dependentReturnType2.ts, 2, 54)) +>T : Symbol(T, Decl(dependentReturnType2.ts, 2, 13)) +>T : Symbol(T, Decl(dependentReturnType2.ts, 2, 13)) +>a : Symbol(a, Decl(dependentReturnType2.ts, 2, 72)) + + if (q(x)) { +>q : Symbol(q, Decl(dependentReturnType2.ts, 0, 0)) +>x : Symbol(x, Decl(dependentReturnType2.ts, 2, 54)) + + x.b; +>x.b : Symbol(b, Decl(dependentReturnType2.ts, 2, 40)) +>x : Symbol(x, Decl(dependentReturnType2.ts, 2, 54)) +>b : Symbol(b, Decl(dependentReturnType2.ts, 2, 40)) + + return ""; } } -declare function takesRegExp(x: RegExp): void; ->takesRegExp : Symbol(takesRegExp, Decl(dependentReturnType2.ts, 13, 24), Decl(dependentReturnType2.ts, 79, 1)) ->x : Symbol(x, Decl(dependentReturnType2.ts, 81, 29)) ->RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +let y = { a: "", b: 1 } +>y : Symbol(y, Decl(dependentReturnType2.ts, 9, 3)) +>a : Symbol(a, Decl(dependentReturnType2.ts, 9, 9)) +>b : Symbol(b, Decl(dependentReturnType2.ts, 9, 16)) + +const r = foo<{ a: string }>(y); // number +>r : Symbol(r, Decl(dependentReturnType2.ts, 10, 5)) +>foo : Symbol(foo, Decl(dependentReturnType2.ts, 0, 50)) +>a : Symbol(a, Decl(dependentReturnType2.ts, 10, 15)) +>y : Symbol(y, Decl(dependentReturnType2.ts, 9, 3)) diff --git a/tests/baselines/reference/dependentReturnType2.types b/tests/baselines/reference/dependentReturnType2.types index 5f9f92cb82a0a..4f53c49fc9e54 100644 --- a/tests/baselines/reference/dependentReturnType2.types +++ b/tests/baselines/reference/dependentReturnType2.types @@ -1,284 +1,45 @@ //// [tests/cases/compiler/dependentReturnType2.ts] //// === dependentReturnType2.ts === -// ---5 -type SettingComposedValue = { key: string; value: T }; ->SettingComposedValue : SettingComposedValue ->key : string ->value : T - -type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void; ->SettingCallback : (key: string, value: SettingValue, initialLoad?: boolean) => void ->key : string ->value : object ->initialLoad : boolean | undefined - -type SettingValue = object; ->SettingValue : object - -declare const Meteor: { settings: { [s: string]: any } }; ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } ->s : string - -declare const _: { isRegExp(x: unknown): x is RegExp; }; ->_ : { isRegExp(x: unknown): x is RegExp; } ->isRegExp : (x: unknown) => x is RegExp ->x : unknown - -type HelperCond = ->HelperCond : HelperCond - - T extends A - ? R1 - : T extends B - ? R2 - : (R1 | R2); - -declare function takesRegExp(x: RegExp): void; ->takesRegExp : { (x: RegExp): void; (x: RegExp): void; } ->x : RegExp - -declare function takesString(x: string): void; ->takesString : (x: string) => void ->x : string - -class NewSettingsBase { ->NewSettingsBase : NewSettingsBase - - public newGet( ->newGet : (_id: I, callback?: C) => HelperCond[]>> - - _id: I, ->_id : I - - callback?: C, ->callback : C | undefined - - ): HelperCond[]>> { - if (callback !== undefined) { ->callback !== undefined : boolean ->callback : C | undefined ->undefined : undefined - - // this.onload(_id, callback); - if (!Meteor.settings) { ->!Meteor.settings : false ->Meteor.settings : { [s: string]: any; } ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } - - return; - } - if (_id === '*') { ->_id === '*' : boolean ->_id : I ->'*' : "*" - - return Object.keys(Meteor.settings).forEach((key) => { ->Object.keys(Meteor.settings).forEach((key) => { const value = Meteor.settings[key]; callback(key, value); }) : void ->Object.keys(Meteor.settings).forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void ->Object.keys(Meteor.settings) : string[] ->Object.keys : (o: object) => string[] ->Object : ObjectConstructor ->keys : (o: object) => string[] ->Meteor.settings : { [s: string]: any; } ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } ->forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void ->(key) => { const value = Meteor.settings[key]; callback(key, value); } : (key: string) => void ->key : string - - const value = Meteor.settings[key]; ->value : any ->Meteor.settings[key] : any ->Meteor.settings : { [s: string]: any; } ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } ->key : string - - callback(key, value); ->callback(key, value) : void ->callback : SettingCallback ->key : string ->value : any - - }); - } - if (_.isRegExp(_id) && Meteor.settings) { ->_.isRegExp(_id) && Meteor.settings : false | { [s: string]: any; } ->_.isRegExp(_id) : boolean ->_.isRegExp : (x: unknown) => x is RegExp ->_ : { isRegExp(x: unknown): x is RegExp; } ->isRegExp : (x: unknown) => x is RegExp ->_id : string | RegExp ->Meteor.settings : { [s: string]: any; } ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } - - return Object.keys(Meteor.settings).forEach((key) => { ->Object.keys(Meteor.settings).forEach((key) => { if (!_id.test(key)) { return; } const value = Meteor.settings[key]; callback(key, value); }) : void ->Object.keys(Meteor.settings).forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void ->Object.keys(Meteor.settings) : string[] ->Object.keys : (o: object) => string[] ->Object : ObjectConstructor ->keys : (o: object) => string[] ->Meteor.settings : { [s: string]: any; } ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } ->forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void ->(key) => { if (!_id.test(key)) { return; } const value = Meteor.settings[key]; callback(key, value); } : (key: string) => void ->key : string - - if (!_id.test(key)) { ->!_id.test(key) : boolean ->_id.test(key) : boolean ->_id.test : (string: string) => boolean ->_id : RegExp ->test : (string: string) => boolean ->key : string - - return; - } - const value = Meteor.settings[key]; ->value : any ->Meteor.settings[key] : any ->Meteor.settings : { [s: string]: any; } ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } ->key : string - - callback(key, value); ->callback(key, value) : void ->callback : SettingCallback ->key : string ->value : any - - }); - } - - if (typeof _id === 'string') { ->typeof _id === 'string' : boolean ->typeof _id : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" ->_id : I ->'string' : "string" - - const value = Meteor.settings[_id]; ->value : any ->Meteor.settings[_id] : any ->Meteor.settings : { [s: string]: any; } ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } ->_id : I & string - - if (value != null) { ->value != null : boolean ->value : any - - callback(_id, Meteor.settings[_id]); ->callback(_id, Meteor.settings[_id]) : void ->callback : SettingCallback ->_id : string ->Meteor.settings[_id] : any ->Meteor.settings : { [s: string]: any; } ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } ->_id : I & string - } - return; - } - - return; // Needed to add this for exhaustiveness - } - - if (!Meteor.settings) { // Wrong: we don't know that _id is string here, cannot return undefined ->!Meteor.settings : false ->Meteor.settings : { [s: string]: any; } ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } - - return undefined; ->undefined : undefined - } - - if (_.isRegExp(_id)) { ->_.isRegExp(_id) : boolean ->_.isRegExp : (x: unknown) => x is RegExp ->_ : { isRegExp(x: unknown): x is RegExp; } ->isRegExp : (x: unknown) => x is RegExp ->_id : string | RegExp - - takesRegExp(_id); ->takesRegExp(_id) : void ->takesRegExp : { (x: RegExp): void; (x: RegExp): void; } ->_id : RegExp - - return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { ->Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { const value = Meteor.settings[key]; if (_id.test(key)) { items.push({ key, value, }); } return items; }, []) : SettingComposedValue[] ->Object.keys(Meteor.settings).reduce : { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; (callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; } ->Object.keys(Meteor.settings) : string[] ->Object.keys : (o: object) => string[] ->Object : ObjectConstructor ->keys : (o: object) => string[] ->Meteor.settings : { [s: string]: any; } ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } ->reduce : { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; (callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; } ->(items: SettingComposedValue[], key) => { const value = Meteor.settings[key]; if (_id.test(key)) { items.push({ key, value, }); } return items; } : (items: SettingComposedValue[], key: string) => SettingComposedValue[] ->items : SettingComposedValue[] ->key : string - - const value = Meteor.settings[key]; ->value : any ->Meteor.settings[key] : any ->Meteor.settings : { [s: string]: any; } ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } ->key : string - - if (_id.test(key)) { ->_id.test(key) : boolean ->_id.test : (string: string) => boolean ->_id : RegExp ->test : (string: string) => boolean ->key : string - - items.push({ ->items.push({ key, value, }) : number ->items.push : (...items: SettingComposedValue[]) => number ->items : SettingComposedValue[] ->push : (...items: SettingComposedValue[]) => number ->{ key, value, } : { key: string; value: any; } - - key, ->key : string - - value, ->value : any - - }); - } - return items; ->items : SettingComposedValue[] - - }, []); ->[] : never[] - } - - return Meteor.settings?.[_id]; // The indexing doesn't work ->Meteor.settings?.[_id] : any ->Meteor.settings : { [s: string]: any; } ->Meteor : { settings: { [s: string]: any; }; } ->settings : { [s: string]: any; } ->_id : I +declare function q(x: object): x is { b: number }; +>q : (x: object) => x is { b: number; } +>x : object +>b : number + +function foo(x: T): T extends { a: string } ? number : (string | number) { +>foo : (x: T) => T extends { a: string;} ? number : (string | number) +>a : string +>b : number +>x : T +>a : string + + if (q(x)) { +>q(x) : boolean +>q : (x: object) => x is { b: number; } +>x : { a: string; } | { b: number; } + + x.b; +>x.b : number +>x : { b: number; } +>b : number + + return ""; +>"" : "" } } -declare function takesRegExp(x: RegExp): void; ->takesRegExp : { (x: RegExp): void; (x: RegExp): void; } ->x : RegExp +let y = { a: "", b: 1 } +>y : { a: string; b: number; } +>{ a: "", b: 1 } : { a: string; b: number; } +>a : string +>"" : "" +>b : number +>1 : 1 + +const r = foo<{ a: string }>(y); // number +>r : number +>foo<{ a: string }>(y) : number +>foo : (x: T) => T extends { a: string; } ? number : string | number +>a : string +>y : { a: string; b: number; } From 2c3624dabcb0f09af705408ad560346284cb5e49 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 11 Jan 2024 17:32:40 -0800 Subject: [PATCH 44/90] get rid of extra subtype check in conditional type resolution --- src/compiler/checker.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a7db278337ad5..da2e4895a7012 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18854,14 +18854,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { result = instantiateNarrowType(trueType, narrowMapper, trueMapper); break; } - // >> TODO: document why/when we need this - // Same as above - else if (isTypeStrictSubtypeOf(checkType, inferredExtendsType)) { - const trueType = getTypeFromTypeNode(root.node.trueType); - const trueMapper = combinedMapper || mapper; - result = instantiateNarrowType(trueType, narrowMapper, trueMapper); - break; - } } // Return a deferred type for a check that is not definitely true result = createType(TypeFlags.Conditional) as ConditionalType; From bcaf50414fd6e5cbe8fd273b9f882a578ebbd10e Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 16 Jan 2024 14:57:30 -0800 Subject: [PATCH 45/90] WIP: stop narrowing if one of the distribution types can't be narrowed --- src/compiler/checker.ts | 68 ++++++++++--------- .../reference/dependentReturnType1.errors.txt | 6 +- .../reference/dependentReturnType2.errors.txt | 23 +++---- .../reference/dependentReturnType2.symbols | 51 ++++---------- .../reference/dependentReturnType2.types | 49 ++++--------- tests/cases/compiler/dependentReturnType2.ts | 15 ++-- 6 files changed, 78 insertions(+), 134 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index da2e4895a7012..b67d0187fe7a4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18749,9 +18749,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return links.resolvedType; } - function getNarrowConditionalType(root: ConditionalRoot, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + function getNarrowConditionalType(type: ConditionalType, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type { + let root: ConditionalRoot = type.root; let result; - const originalRoot = root; // We loop here for an immediately nested conditional type in the false position, effectively treating // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for @@ -18855,15 +18855,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { break; } } - // Return a deferred type for a check that is not definitely true - result = createType(TypeFlags.Conditional) as ConditionalType; - result.root = originalRoot; - result.checkType = instantiateType(originalRoot.checkType, mapper); - result.extendsType = instantiateType(originalRoot.extendsType, mapper); - result.mapper = mapper; - result.combinedMapper = undefined; - result.aliasSymbol = aliasSymbol || originalRoot.aliasSymbol; - result.aliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(originalRoot.aliasTypeArguments, mapper!); // TODO: GH#18217 + // Return the original type for a check that is not definitely true and could not be narrowed + result = type; break; } return result; @@ -20086,8 +20079,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { type: ConditionalType, narrowMapper: TypeMapper, mapper: TypeMapper | undefined, - aliasSymbol?: Symbol, - aliasTypeArguments?: readonly Type[]): Type { + _aliasSymbol?: Symbol, + _aliasTypeArguments?: readonly Type[]): Type { const root = type.root; if (root.outerTypeParameters) { // We are instantiating a conditional type that has one or more type parameters in scope. Apply the @@ -20095,36 +20088,49 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // instantiation cache key from the type IDs of the type arguments. const typeArguments = mapper ? map(root.outerTypeParameters, t => getMappedType(t, mapper)) : root.outerTypeParameters; // >> No caching yet - let result; const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); - const checkType = root.checkType; const checkTypeVariable = getNarrowableCheckTypeVariable(root, newMapper); const distributionType = checkTypeVariable ? getMappedType(checkTypeVariable, narrowMapper) : undefined; // Distributive conditional types are distributed over union types. For example, when the // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the // result is (A extends U ? X : Y) | (B extends U ? X : Y). - if (distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { - result = mapTypeWithAlias( - getReducedType(distributionType), - t => getNarrowConditionalType( - root, - prependTypeMapping(checkType, t, narrowMapper), - newMapper), - aliasSymbol, - aliasTypeArguments, - /*useIntersection*/ true); // We use the intersection instead of unioning over the conditional types, - // because we want the write type so that it is safe + if (distributionType && checkTypeVariable !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { + return getNarrowDistributedConditionalType(type, distributionType, checkTypeVariable!, narrowMapper, newMapper); } else { - result = getNarrowConditionalType(root, narrowMapper, newMapper, aliasSymbol, aliasTypeArguments); + return getNarrowConditionalType(type, narrowMapper, newMapper); } - return result; } return type; } + // `distributionType` should be a union type (or never). + function getNarrowDistributedConditionalType( + type: ConditionalType, + distributionType: Type, + checkTypeVariable: TypeParameter, + narrowMapper: TypeMapper, + mapper: TypeMapper): Type { + distributionType = getReducedType(distributionType); + if (distributionType.flags & TypeFlags.Never) { + return distributionType; + } + if (distributionType.flags & TypeFlags.Union) { + const mappedTypes: Type[] = []; + for (const t of (distributionType as UnionType).types) { + const result = getNarrowConditionalType(type, prependTypeMapping(checkTypeVariable, t, narrowMapper), mapper); + // If one of the component types could not be narrowed, then don't narrow the whole type + if (result === type) { + return type; + } + mappedTypes.push(result); + } + return getIntersectionType(mappedTypes); + } + return getNarrowConditionalType(type, narrowMapper, mapper); + } + function isNarrowableReturnType(type: Type) { - // >> TODO: also check if generic? return type.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional) && couldContainTypeVariables(type); } @@ -27308,8 +27314,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Apply a mapping function to a type and return the resulting type. If the source type // is a union type, the mapping function is applied to each constituent type and a union // of the resulting types is returned. - function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean, useIntersection?: boolean): Type; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean, useIntersection?: boolean): Type | undefined; + function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean, useIntersection?: boolean, skipIfAnyUnchanged?: boolean): Type; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean, useIntersection?: boolean, skipIfAnyUnchanged?: boolean): Type | undefined; function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean, useIntersection = false): Type | undefined { if (type.flags & TypeFlags.Never) { return type; diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index a4a7bd15eeb94..75f465018af2a 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -2,8 +2,7 @@ dependentReturnType1.ts(11,9): error TS2322: Type 'number' is not assignable to dependentReturnType1.ts(26,9): error TS2322: Type 'string' is not assignable to type 'never'. dependentReturnType1.ts(35,9): error TS2322: Type 'string' is not assignable to type 'never'. dependentReturnType1.ts(70,9): error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. -dependentReturnType1.ts(74,5): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'Three & (T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four)'. - Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. +dependentReturnType1.ts(74,5): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. dependentReturnType1.ts(80,9): error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four'. dependentReturnType1.ts(84,22): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. dependentReturnType1.ts(99,9): error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. @@ -125,8 +124,7 @@ dependentReturnType1.ts(463,13): error TS2322: Type 'R' is not assignable to typ // because we now check assignability to a narrower type... return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error ~~~~~~ -!!! error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'Three & (T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four)'. -!!! error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. +!!! error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. } function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four { // Well written conditional diff --git a/tests/baselines/reference/dependentReturnType2.errors.txt b/tests/baselines/reference/dependentReturnType2.errors.txt index e517ea1c68860..06126c47839fb 100644 --- a/tests/baselines/reference/dependentReturnType2.errors.txt +++ b/tests/baselines/reference/dependentReturnType2.errors.txt @@ -1,20 +1,15 @@ -dependentReturnType2.ts(3,62): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. -dependentReturnType2.ts(6,9): error TS2322: Type 'string' is not assignable to type 'T extends { a: string; } ? number : string | number'. +dependentReturnType2.ts(2,65): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. +dependentReturnType2.ts(4,9): error TS2322: Type '3' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 3'. ==== dependentReturnType2.ts (2 errors) ==== - declare function q(x: object): x is { b: number }; - - function foo(x: T): T extends { a: string } ? number : (string | number) { - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // If during narrowing, one of the conditional types in the distribution doesn't narrow, then the whole type will not be narrowed + function whoKnows(x: T): T extends true ? 1 : T extends false ? 2 : 3 { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'. - if (q(x)) { - x.b; - return ""; + if (typeof x !== "string") { + return 3; ~~~~~~ -!!! error TS2322: Type 'string' is not assignable to type 'T extends { a: string; } ? number : string | number'. +!!! error TS2322: Type '3' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 3'. } - } - - let y = { a: "", b: 1 } - const r = foo<{ a: string }>(y); // number \ No newline at end of file + } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType2.symbols b/tests/baselines/reference/dependentReturnType2.symbols index 9091f7b637b61..26633ee247a7a 100644 --- a/tests/baselines/reference/dependentReturnType2.symbols +++ b/tests/baselines/reference/dependentReturnType2.symbols @@ -1,43 +1,18 @@ //// [tests/cases/compiler/dependentReturnType2.ts] //// === dependentReturnType2.ts === -declare function q(x: object): x is { b: number }; ->q : Symbol(q, Decl(dependentReturnType2.ts, 0, 0)) ->x : Symbol(x, Decl(dependentReturnType2.ts, 0, 19)) ->x : Symbol(x, Decl(dependentReturnType2.ts, 0, 19)) ->b : Symbol(b, Decl(dependentReturnType2.ts, 0, 37)) - -function foo(x: T): T extends { a: string } ? number : (string | number) { ->foo : Symbol(foo, Decl(dependentReturnType2.ts, 0, 50)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 2, 13)) ->a : Symbol(a, Decl(dependentReturnType2.ts, 2, 24)) ->b : Symbol(b, Decl(dependentReturnType2.ts, 2, 40)) ->x : Symbol(x, Decl(dependentReturnType2.ts, 2, 54)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 2, 13)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 2, 13)) ->a : Symbol(a, Decl(dependentReturnType2.ts, 2, 72)) - - if (q(x)) { ->q : Symbol(q, Decl(dependentReturnType2.ts, 0, 0)) ->x : Symbol(x, Decl(dependentReturnType2.ts, 2, 54)) - - x.b; ->x.b : Symbol(b, Decl(dependentReturnType2.ts, 2, 40)) ->x : Symbol(x, Decl(dependentReturnType2.ts, 2, 54)) ->b : Symbol(b, Decl(dependentReturnType2.ts, 2, 40)) - - return ""; +// If during narrowing, one of the conditional types in the distribution doesn't narrow, then the whole type will not be narrowed +function whoKnows(x: T): T extends true ? 1 : T extends false ? 2 : 3 { +>whoKnows : Symbol(whoKnows, Decl(dependentReturnType2.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType2.ts, 1, 18)) +>x : Symbol(x, Decl(dependentReturnType2.ts, 1, 57)) +>T : Symbol(T, Decl(dependentReturnType2.ts, 1, 18)) +>T : Symbol(T, Decl(dependentReturnType2.ts, 1, 18)) +>T : Symbol(T, Decl(dependentReturnType2.ts, 1, 18)) + + if (typeof x !== "string") { +>x : Symbol(x, Decl(dependentReturnType2.ts, 1, 57)) + + return 3; } } - -let y = { a: "", b: 1 } ->y : Symbol(y, Decl(dependentReturnType2.ts, 9, 3)) ->a : Symbol(a, Decl(dependentReturnType2.ts, 9, 9)) ->b : Symbol(b, Decl(dependentReturnType2.ts, 9, 16)) - -const r = foo<{ a: string }>(y); // number ->r : Symbol(r, Decl(dependentReturnType2.ts, 10, 5)) ->foo : Symbol(foo, Decl(dependentReturnType2.ts, 0, 50)) ->a : Symbol(a, Decl(dependentReturnType2.ts, 10, 15)) ->y : Symbol(y, Decl(dependentReturnType2.ts, 9, 3)) - diff --git a/tests/baselines/reference/dependentReturnType2.types b/tests/baselines/reference/dependentReturnType2.types index 4f53c49fc9e54..bbb21bf78d73a 100644 --- a/tests/baselines/reference/dependentReturnType2.types +++ b/tests/baselines/reference/dependentReturnType2.types @@ -1,45 +1,20 @@ //// [tests/cases/compiler/dependentReturnType2.ts] //// === dependentReturnType2.ts === -declare function q(x: object): x is { b: number }; ->q : (x: object) => x is { b: number; } ->x : object ->b : number - -function foo(x: T): T extends { a: string } ? number : (string | number) { ->foo : (x: T) => T extends { a: string;} ? number : (string | number) ->a : string ->b : number +// If during narrowing, one of the conditional types in the distribution doesn't narrow, then the whole type will not be narrowed +function whoKnows(x: T): T extends true ? 1 : T extends false ? 2 : 3 { +>whoKnows : (x: T) => T extends true ? 1 : T extends false ? 2 : 3 >x : T ->a : string - - if (q(x)) { ->q(x) : boolean ->q : (x: object) => x is { b: number; } ->x : { a: string; } | { b: number; } +>true : true +>false : false - x.b; ->x.b : number ->x : { b: number; } ->b : number + if (typeof x !== "string") { +>typeof x !== "string" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T +>"string" : "string" - return ""; ->"" : "" + return 3; +>3 : 3 } } - -let y = { a: "", b: 1 } ->y : { a: string; b: number; } ->{ a: "", b: 1 } : { a: string; b: number; } ->a : string ->"" : "" ->b : number ->1 : 1 - -const r = foo<{ a: string }>(y); // number ->r : number ->foo<{ a: string }>(y) : number ->foo : (x: T) => T extends { a: string; } ? number : string | number ->a : string ->y : { a: string; b: number; } - diff --git a/tests/cases/compiler/dependentReturnType2.ts b/tests/cases/compiler/dependentReturnType2.ts index 33c5e2f837440..c8bc71238bc63 100644 --- a/tests/cases/compiler/dependentReturnType2.ts +++ b/tests/cases/compiler/dependentReturnType2.ts @@ -1,14 +1,9 @@ // @strict: true // @noEmit: true -declare function q(x: object): x is { b: number }; - -function foo(x: T): T extends { a: string } ? number : (string | number) { - if (q(x)) { - x.b; - return ""; +// If during narrowing, one of the conditional types in the distribution doesn't narrow, then the whole type will not be narrowed +function whoKnows(x: T): T extends true ? 1 : T extends false ? 2 : 3 { + if (typeof x !== "string") { + return 3; } -} - -let y = { a: "", b: 1 } -const r = foo<{ a: string }>(y); // number \ No newline at end of file +} \ No newline at end of file From 85cdfadf3335159f105c47c96a4b8a90f4dd6867 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 16 Jan 2024 16:16:54 -0800 Subject: [PATCH 46/90] get rid of aliasSymbol tracking for now; it's never used --- src/compiler/checker.ts | 37 +++++++------------------------------ 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b67d0187fe7a4..e45cf68d54685 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19981,20 +19981,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } /** - * This is similar to `instantiateType`, but with behavior specific to narrowing a return type based on control flow for type parameters. + * This is similar to `instantiateType`, but with behavior specific to narrowing a return type based on control flow narrowing of expressions that have type parameter types. */ - function instantiateNarrowType(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type; - function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type | undefined; - function instantiateNarrowType(type: Type | undefined, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type | undefined { - return type ? instantiateNarrowTypeWithAlias(type, narrowMapper, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : type; - } - - function instantiateNarrowTypeWithAlias( - type: Type, - narrowMapper: TypeMapper, - mapper: TypeMapper | undefined, - aliasSymbol: Symbol | undefined, - aliasTypeArguments: readonly Type[] | undefined): Type { + function instantiateNarrowType(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type { if (!couldContainTypeVariables(type)) { return type; } @@ -20009,7 +19998,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { totalInstantiationCount++; instantiationCount++; instantiationDepth++; - const result = instantiateNarrowTypeWorker(type, narrowMapper, mapper, aliasSymbol, aliasTypeArguments); + const result = instantiateNarrowTypeWorker(type, narrowMapper, mapper); instantiationDepth--; return result; } @@ -20022,15 +20011,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function instantiateNarrowTypeWorker( type: Type, narrowMapper: TypeMapper, - mapper: TypeMapper | undefined, - aliasSymbol: Symbol | undefined, - aliasTypeArguments: readonly Type[] | undefined): Type { + mapper: TypeMapper | undefined): Type { type = instantiateType(type, mapper); const flags = type.flags; if (flags & TypeFlags.IndexedAccess) { - // >> TODO: what's that extra alias stuff here doing? - const newAliasSymbol = aliasSymbol || type.aliasSymbol; - const newAliasTypeArguments = aliasSymbol || !mapper ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); const objectType = instantiateNarrowType((type as IndexedAccessType).objectType, narrowMapper, mapper); let indexType = instantiateNarrowType((type as IndexedAccessType).indexType, narrowMapper, mapper); let accessFlags = (type as IndexedAccessType).accessFlags; @@ -20045,10 +20029,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const result = getIndexedAccessType( objectType, indexType, - accessFlags, - /*accessNode*/ undefined, - newAliasSymbol, - newAliasTypeArguments); + accessFlags); // >> NOTE: We need to detect if result is different from just putting the already resolved types together return instantiateNarrowType(result, narrowMapper, mapper); } @@ -20056,9 +20037,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getNarrowConditionalTypeInstantiation( type as ConditionalType, narrowMapper, - mapper ? combineTypeMappers((type as ConditionalType).mapper, mapper) : (type as ConditionalType).mapper, - aliasSymbol, - aliasTypeArguments); + mapper ? combineTypeMappers((type as ConditionalType).mapper, mapper) : (type as ConditionalType).mapper); } return type; @@ -20078,9 +20057,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getNarrowConditionalTypeInstantiation( type: ConditionalType, narrowMapper: TypeMapper, - mapper: TypeMapper | undefined, - _aliasSymbol?: Symbol, - _aliasTypeArguments?: readonly Type[]): Type { + mapper: TypeMapper | undefined): Type { const root = type.root; if (root.outerTypeParameters) { // We are instantiating a conditional type that has one or more type parameters in scope. Apply the From 7d24d341202b25f95802730d54ee75342350d9d8 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 16 Jan 2024 17:04:41 -0800 Subject: [PATCH 47/90] move and add tests for indexed access return type narrowing --- src/compiler/checker.ts | 4 +- .../reference/dependentReturnType1.errors.txt | 56 +-- .../reference/dependentReturnType1.symbols | 343 ++++++------------ .../reference/dependentReturnType1.types | 102 ------ .../reference/dependentReturnType5.errors.txt | 110 ++++++ .../reference/dependentReturnType5.symbols | 220 +++++++++++ .../reference/dependentReturnType5.types | 228 ++++++++++++ tests/cases/compiler/dependentDebug.ts | 17 + tests/cases/compiler/dependentReturnType1.ts | 49 --- tests/cases/compiler/dependentReturnType5.ts | 99 +++++ 10 files changed, 798 insertions(+), 430 deletions(-) create mode 100644 tests/baselines/reference/dependentReturnType5.errors.txt create mode 100644 tests/baselines/reference/dependentReturnType5.symbols create mode 100644 tests/baselines/reference/dependentReturnType5.types create mode 100644 tests/cases/compiler/dependentDebug.ts create mode 100644 tests/cases/compiler/dependentReturnType5.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e45cf68d54685..3066047cd7ba1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20020,7 +20020,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let accessFlags = (type as IndexedAccessType).accessFlags; if (indexType.flags & TypeFlags.TypeParameter) { indexType = getMappedType(indexType, narrowMapper); - accessFlags |= AccessFlags.Writing; // Get the writing type + // If we're narrowing the index type, we need to get the write type, + // i.e. intersecting the results of distributing the indexed access over a union index. + accessFlags |= AccessFlags.Writing; } // >> NOTE: this possibly recurs forever; how do we break this recursion? is the below enough? if (indexType === (type as IndexedAccessType).indexType && objectType === (type as IndexedAccessType).objectType) { diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index 75f465018af2a..d84cddade4ae2 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -36,11 +36,10 @@ dependentReturnType1.ts(362,9): error TS2322: Type 'true' is not assignable to t dependentReturnType1.ts(364,5): error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. dependentReturnType1.ts(389,15): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. dependentReturnType1.ts(391,11): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. -dependentReturnType1.ts(433,13): error TS2322: Type '2' is not assignable to type '1'. -dependentReturnType1.ts(463,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. +dependentReturnType1.ts(414,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. -==== dependentReturnType1.ts (36 errors) ==== +==== dependentReturnType1.ts (35 errors) ==== interface A { 1: number; 2: string; @@ -507,57 +506,6 @@ dependentReturnType1.ts(463,13): error TS2322: Type 'R' is not assignable to typ return 0; } - // Indexed access tests - interface F { - "t": number, - "f": boolean, - } - - // Ok - function depLikeFun(str: T): F[T] { - if (str === "t") { - return 1; - } else { - return true; - } - } - - depLikeFun("t"); // has type number - depLikeFun("f"); // has type boolean - - type IndirectF = F[T]; - - // Ok - function depLikeFun2(str: T): IndirectF { - if (str === "t") { - return 1; - } else { - return true; - } - } - - - interface CComp { - foo: 1; - [s: string]: 1 | 2; - } - - function indexedCComp(x: T): CComp[T] { - if (x === "foo") { - if (Math.random()) { - return 2; // Error - ~~~~~~ -!!! error TS2322: Type '2' is not assignable to type '1'. - } - return 1; // Ok - } - return 2; // Ok - } - - function indexedCComp2(x: T): CComp[T] { - return 2; // Bad, unsafe - } - // From #33912 abstract class Operation { abstract perform(t: T): R; diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index fc526ef75b383..2b9c1e504713f 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -1084,237 +1084,132 @@ function* cbool(x: T): GeneratorF : Symbol(F, Decl(dependentReturnType1.ts, 392, 1)) - - "t": number, ->"t" : Symbol(F["t"], Decl(dependentReturnType1.ts, 395, 13)) - - "f": boolean, ->"f" : Symbol(F["f"], Decl(dependentReturnType1.ts, 396, 16)) -} - -// Ok -function depLikeFun(str: T): F[T] { ->depLikeFun : Symbol(depLikeFun, Decl(dependentReturnType1.ts, 398, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 401, 20)) ->str : Symbol(str, Decl(dependentReturnType1.ts, 401, 41)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 401, 20)) ->F : Symbol(F, Decl(dependentReturnType1.ts, 392, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 401, 20)) - - if (str === "t") { ->str : Symbol(str, Decl(dependentReturnType1.ts, 401, 41)) - - return 1; - } else { - return true; - } -} - -depLikeFun("t"); // has type number ->depLikeFun : Symbol(depLikeFun, Decl(dependentReturnType1.ts, 398, 1)) - -depLikeFun("f"); // has type boolean ->depLikeFun : Symbol(depLikeFun, Decl(dependentReturnType1.ts, 398, 1)) - -type IndirectF = F[T]; ->IndirectF : Symbol(IndirectF, Decl(dependentReturnType1.ts, 410, 16)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 412, 15)) ->F : Symbol(F, Decl(dependentReturnType1.ts, 392, 1)) ->F : Symbol(F, Decl(dependentReturnType1.ts, 392, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 412, 15)) - -// Ok -function depLikeFun2(str: T): IndirectF { ->depLikeFun2 : Symbol(depLikeFun2, Decl(dependentReturnType1.ts, 412, 41)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 415, 21)) ->str : Symbol(str, Decl(dependentReturnType1.ts, 415, 42)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 415, 21)) ->IndirectF : Symbol(IndirectF, Decl(dependentReturnType1.ts, 410, 16)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 415, 21)) - - if (str === "t") { ->str : Symbol(str, Decl(dependentReturnType1.ts, 415, 42)) - - return 1; - } else { - return true; - } -} - - -interface CComp { ->CComp : Symbol(CComp, Decl(dependentReturnType1.ts, 421, 1)) - - foo: 1; ->foo : Symbol(CComp.foo, Decl(dependentReturnType1.ts, 424, 17)) - - [s: string]: 1 | 2; ->s : Symbol(s, Decl(dependentReturnType1.ts, 426, 5)) -} - -function indexedCComp(x: T): CComp[T] { ->indexedCComp : Symbol(indexedCComp, Decl(dependentReturnType1.ts, 427, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 429, 22)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 429, 49)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 429, 22)) ->CComp : Symbol(CComp, Decl(dependentReturnType1.ts, 421, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 429, 22)) - - if (x === "foo") { ->x : Symbol(x, Decl(dependentReturnType1.ts, 429, 49)) - - if (Math.random()) { ->Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) ->Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) ->random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) - - return 2; // Error - } - return 1; // Ok - } - return 2; // Ok -} - -function indexedCComp2(x: T): CComp[T] { ->indexedCComp2 : Symbol(indexedCComp2, Decl(dependentReturnType1.ts, 437, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 439, 23)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 439, 50)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 439, 23)) ->CComp : Symbol(CComp, Decl(dependentReturnType1.ts, 421, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 439, 23)) - - return 2; // Bad, unsafe -} - // From #33912 abstract class Operation { ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 444, 25)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 444, 27)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 395, 25)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 395, 27)) abstract perform(t: T): R; ->perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 444, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 445, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 444, 25)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 444, 27)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 395, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 396, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 395, 25)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 395, 27)) } type ConditionalReturnType | undefined> = ->ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 446, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 448, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 448, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 448, 32)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 448, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 448, 29)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 397, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 399, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 399, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 399, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 399, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 399, 29)) EOp extends Operation ? R : EOp extends undefined ? T | R : T | R; ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 448, 32)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 448, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 448, 29)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 448, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 448, 32)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 448, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 448, 29)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 448, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 448, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 399, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 399, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 399, 29)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 399, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 399, 32)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 399, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 399, 29)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 399, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 399, 29)) class ConditionalOperation | undefined> extends Operation> { ->ConditionalOperation : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 449, 76)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 451, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 32)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 451, 29)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) ->ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 446, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 451, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 32)) +>ConditionalOperation : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 400, 76)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 402, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 402, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 402, 29)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 397, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 402, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 402, 32)) constructor( private predicate: (value: T) => boolean, ->predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 452, 16)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 453, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) +>predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 403, 16)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 404, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) private thenOp: Operation, ->thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 453, 49)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 451, 29)) +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 404, 49)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 402, 29)) private elseOp?: EOp ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 454, 40)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 32)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 405, 40)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 402, 32)) ) { super(); ->super : Symbol(Operation, Decl(dependentReturnType1.ts, 441, 1)) +>super : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) } perform(t: T): ConditionalReturnType { ->perform : Symbol(ConditionalOperation.perform, Decl(dependentReturnType1.ts, 458, 5)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 460, 12)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) ->ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 446, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 451, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 451, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 32)) +>perform : Symbol(ConditionalOperation.perform, Decl(dependentReturnType1.ts, 409, 5)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 411, 12)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 397, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 402, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 402, 32)) if (this.predicate(t)) { ->this.predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 452, 16)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 449, 76)) ->predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 452, 16)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 460, 12)) +>this.predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 403, 16)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 400, 76)) +>predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 403, 16)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 411, 12)) return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it ->this.thenOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 444, 32)) ->this.thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 453, 49)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 449, 76)) ->thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 453, 49)) ->perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 444, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 460, 12)) +>this.thenOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 395, 32)) +>this.thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 404, 49)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 400, 76)) +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 404, 49)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 395, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 411, 12)) } else if (typeof this.elseOp !== 'undefined') { ->this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 454, 40)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 449, 76)) ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 454, 40)) +>this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 405, 40)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 400, 76)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 405, 40)) return this.elseOp.perform(t); // Ok ->this.elseOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 444, 32)) ->this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 454, 40)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 449, 76)) ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 454, 40)) ->perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 444, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 460, 12)) +>this.elseOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 395, 32)) +>this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 405, 40)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 400, 76)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 405, 40)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 395, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 411, 12)) } else { return t; // Ok ->t : Symbol(t, Decl(dependentReturnType1.ts, 460, 12)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 411, 12)) } } } // Optional tuple element function tupl(x: [string, some?: T]): ->tupl : Symbol(tupl, Decl(dependentReturnType1.ts, 469, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 472, 14)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 472, 50)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 472, 14)) +>tupl : Symbol(tupl, Decl(dependentReturnType1.ts, 420, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 423, 14)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 423, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 423, 14)) T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 { ->T : Symbol(T, Decl(dependentReturnType1.ts, 472, 14)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 472, 14)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 423, 14)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 423, 14)) if (x[1]) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 472, 50)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 423, 50)) >1 : Symbol(1) return 1; @@ -1324,59 +1219,59 @@ function tupl(x: [string, some?: T]): // Return conditional expressions with parentheses function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { ->returnStuff1 : Symbol(returnStuff1, Decl(dependentReturnType1.ts, 478, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 481, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 481, 41)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 481, 48)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 481, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 481, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 481, 22)) +>returnStuff1 : Symbol(returnStuff1, Decl(dependentReturnType1.ts, 429, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 432, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 432, 41)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 432, 48)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 432, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 432, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 432, 22)) return (opts.x ? (1) : 2); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 481, 48)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 481, 41)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 481, 48)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 432, 48)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 432, 41)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 432, 48)) } function returnStuff2(opts: { x: T }): ->returnStuff2 : Symbol(returnStuff2, Decl(dependentReturnType1.ts, 483, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 485, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 485, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 485, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 485, 22)) +>returnStuff2 : Symbol(returnStuff2, Decl(dependentReturnType1.ts, 434, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 436, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 436, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 436, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 436, 22)) T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 { ->T : Symbol(T, Decl(dependentReturnType1.ts, 485, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 485, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 485, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 436, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 436, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 436, 22)) return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 485, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 485, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 485, 52)) ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 485, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 485, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 485, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 436, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 436, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 436, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 436, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 436, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 436, 52)) } // If the return type is written wrong, it still type checks function returnStuff3(opts: { x: T }): ->returnStuff3 : Symbol(returnStuff3, Decl(dependentReturnType1.ts, 488, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 491, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 491, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 491, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 491, 22)) +>returnStuff3 : Symbol(returnStuff3, Decl(dependentReturnType1.ts, 439, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 442, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 442, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 442, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 442, 22)) T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" { ->T : Symbol(T, Decl(dependentReturnType1.ts, 491, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 491, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 491, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 442, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 442, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 442, 22)) return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 491, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 491, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 491, 52)) ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 491, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 491, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 491, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 442, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 442, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 442, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 442, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 442, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 442, 52)) } diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index f151cdfd51965..075a905978816 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -1083,108 +1083,6 @@ function* cbool(x: T): Generator0 : 0 } -// Indexed access tests -interface F { - "t": number, ->"t" : number - - "f": boolean, ->"f" : boolean -} - -// Ok -function depLikeFun(str: T): F[T] { ->depLikeFun : (str: T) => F[T] ->str : T - - if (str === "t") { ->str === "t" : boolean ->str : T ->"t" : "t" - - return 1; ->1 : 1 - - } else { - return true; ->true : true - } -} - -depLikeFun("t"); // has type number ->depLikeFun("t") : number ->depLikeFun : (str: T) => F[T] ->"t" : "t" - -depLikeFun("f"); // has type boolean ->depLikeFun("f") : boolean ->depLikeFun : (str: T) => F[T] ->"f" : "f" - -type IndirectF = F[T]; ->IndirectF : IndirectF - -// Ok -function depLikeFun2(str: T): IndirectF { ->depLikeFun2 : (str: T) => IndirectF ->str : T - - if (str === "t") { ->str === "t" : boolean ->str : T ->"t" : "t" - - return 1; ->1 : 1 - - } else { - return true; ->true : true - } -} - - -interface CComp { - foo: 1; ->foo : 1 - - [s: string]: 1 | 2; ->s : string -} - -function indexedCComp(x: T): CComp[T] { ->indexedCComp : (x: T) => CComp[T] ->x : T - - if (x === "foo") { ->x === "foo" : boolean ->x : T ->"foo" : "foo" - - if (Math.random()) { ->Math.random() : number ->Math.random : () => number ->Math : Math ->random : () => number - - return 2; // Error ->2 : 2 - } - return 1; // Ok ->1 : 1 - } - return 2; // Ok ->2 : 2 -} - -function indexedCComp2(x: T): CComp[T] { ->indexedCComp2 : (x: T) => CComp[T] ->x : T - - return 2; // Bad, unsafe ->2 : 2 -} - // From #33912 abstract class Operation { >Operation : Operation diff --git a/tests/baselines/reference/dependentReturnType5.errors.txt b/tests/baselines/reference/dependentReturnType5.errors.txt new file mode 100644 index 0000000000000..2631fd3958865 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType5.errors.txt @@ -0,0 +1,110 @@ +dependentReturnType5.ts(48,13): error TS2322: Type '3' is not assignable to type '2'. +dependentReturnType5.ts(54,13): error TS2322: Type '2' is not assignable to type '3'. +dependentReturnType5.ts(65,5): error TS2322: Type '2' is not assignable to type 'Comp[T]'. + Type '2' is not assignable to type '3'. + + +==== dependentReturnType5.ts (3 errors) ==== + // Indexed access return type + interface A1 { + "prop": true; + [s: string]: boolean; + } + + // This was already allowed but is unsound. + function foo1(x: T): A1[T] { + return false; + } + const rfoo1 = foo1("prop"); // Type says true, but actually returns false. + + interface A2 { + "prop": true; + [n: number]: string; + } + + // We could soundly allow that, because `"prop"` and `[n: number]` are disjoint types. + function foo2(x: T): A2[T] { + if (x === "prop") { + return true; + } + return "some string"; + } + const rfoo2 = foo2("prop"); + const rfoo22 = foo2(34); + const rfoo222 = foo2(Math.random() ? "prop" : 34); + + interface A3 { + [s: string]: boolean; + } + + // No need for return type narrowing. + function foo3(x: T): A3[T] { + if (Math.random()) return true; + return false; + } + + interface Comp { + foo: 2; + [n: number]: 3; + [s: string]: 2 | 3 | 4; + } + + function indexedComp(x: T): Comp[T] { + if (x === "foo") { + if (Math.random()) { + return 3; // Error + ~~~~~~ +!!! error TS2322: Type '3' is not assignable to type '2'. + } + return 2; // Ok + } + if (typeof x === "number") { + if (Math.random()) { + return 2; // Error + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type '3'. + } + return 3; // Ok + } + return 4; // Ok + } + + function indexedComp2(x: T): Comp[T] { + if (Math.random()) { + return 3; // Bad, unsound + } + return 2; // Error + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type 'Comp[T]'. +!!! error TS2322: Type '2' is not assignable to type '3'. + } + + + // Most common case supported: + interface F { + "t": number, + "f": boolean, + } + + // Ok + function depLikeFun(str: T): F[T] { + if (str === "t") { + return 1; + } else { + return true; + } + } + + depLikeFun("t"); // has type number + depLikeFun("f"); // has type boolean + + type IndirectF = F[T]; + + // Ok + function depLikeFun2(str: T): IndirectF { + if (str === "t") { + return 1; + } else { + return true; + } + } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType5.symbols b/tests/baselines/reference/dependentReturnType5.symbols new file mode 100644 index 0000000000000..9061f59ff0c12 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType5.symbols @@ -0,0 +1,220 @@ +//// [tests/cases/compiler/dependentReturnType5.ts] //// + +=== dependentReturnType5.ts === +// Indexed access return type +interface A1 { +>A1 : Symbol(A1, Decl(dependentReturnType5.ts, 0, 0)) + + "prop": true; +>"prop" : Symbol(A1["prop"], Decl(dependentReturnType5.ts, 1, 14)) + + [s: string]: boolean; +>s : Symbol(s, Decl(dependentReturnType5.ts, 3, 5)) +} + +// This was already allowed but is unsound. +function foo1(x: T): A1[T] { +>foo1 : Symbol(foo1, Decl(dependentReturnType5.ts, 4, 1)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 7, 14)) +>x : Symbol(x, Decl(dependentReturnType5.ts, 7, 32)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 7, 14)) +>A1 : Symbol(A1, Decl(dependentReturnType5.ts, 0, 0)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 7, 14)) + + return false; +} +const rfoo1 = foo1("prop"); // Type says true, but actually returns false. +>rfoo1 : Symbol(rfoo1, Decl(dependentReturnType5.ts, 10, 5)) +>foo1 : Symbol(foo1, Decl(dependentReturnType5.ts, 4, 1)) + +interface A2 { +>A2 : Symbol(A2, Decl(dependentReturnType5.ts, 10, 27)) + + "prop": true; +>"prop" : Symbol(A2["prop"], Decl(dependentReturnType5.ts, 12, 14)) + + [n: number]: string; +>n : Symbol(n, Decl(dependentReturnType5.ts, 14, 5)) +} + +// We could soundly allow that, because `"prop"` and `[n: number]` are disjoint types. +function foo2(x: T): A2[T] { +>foo2 : Symbol(foo2, Decl(dependentReturnType5.ts, 15, 1)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 18, 14)) +>x : Symbol(x, Decl(dependentReturnType5.ts, 18, 41)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 18, 14)) +>A2 : Symbol(A2, Decl(dependentReturnType5.ts, 10, 27)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 18, 14)) + + if (x === "prop") { +>x : Symbol(x, Decl(dependentReturnType5.ts, 18, 41)) + + return true; + } + return "some string"; +} +const rfoo2 = foo2("prop"); +>rfoo2 : Symbol(rfoo2, Decl(dependentReturnType5.ts, 24, 5)) +>foo2 : Symbol(foo2, Decl(dependentReturnType5.ts, 15, 1)) + +const rfoo22 = foo2(34); +>rfoo22 : Symbol(rfoo22, Decl(dependentReturnType5.ts, 25, 5)) +>foo2 : Symbol(foo2, Decl(dependentReturnType5.ts, 15, 1)) + +const rfoo222 = foo2(Math.random() ? "prop" : 34); +>rfoo222 : Symbol(rfoo222, Decl(dependentReturnType5.ts, 26, 5)) +>foo2 : Symbol(foo2, Decl(dependentReturnType5.ts, 15, 1)) +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + +interface A3 { +>A3 : Symbol(A3, Decl(dependentReturnType5.ts, 26, 50)) + + [s: string]: boolean; +>s : Symbol(s, Decl(dependentReturnType5.ts, 29, 5)) +} + +// No need for return type narrowing. +function foo3(x: T): A3[T] { +>foo3 : Symbol(foo3, Decl(dependentReturnType5.ts, 30, 1)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 33, 14)) +>x : Symbol(x, Decl(dependentReturnType5.ts, 33, 32)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 33, 14)) +>A3 : Symbol(A3, Decl(dependentReturnType5.ts, 26, 50)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 33, 14)) + + if (Math.random()) return true; +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + return false; +} + +interface Comp { +>Comp : Symbol(Comp, Decl(dependentReturnType5.ts, 36, 1)) + + foo: 2; +>foo : Symbol(Comp.foo, Decl(dependentReturnType5.ts, 38, 16)) + + [n: number]: 3; +>n : Symbol(n, Decl(dependentReturnType5.ts, 40, 5)) + + [s: string]: 2 | 3 | 4; +>s : Symbol(s, Decl(dependentReturnType5.ts, 41, 5)) +} + +function indexedComp(x: T): Comp[T] { +>indexedComp : Symbol(indexedComp, Decl(dependentReturnType5.ts, 42, 1)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 44, 21)) +>x : Symbol(x, Decl(dependentReturnType5.ts, 44, 48)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 44, 21)) +>Comp : Symbol(Comp, Decl(dependentReturnType5.ts, 36, 1)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 44, 21)) + + if (x === "foo") { +>x : Symbol(x, Decl(dependentReturnType5.ts, 44, 48)) + + if (Math.random()) { +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + return 3; // Error + } + return 2; // Ok + } + if (typeof x === "number") { +>x : Symbol(x, Decl(dependentReturnType5.ts, 44, 48)) + + if (Math.random()) { +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + return 2; // Error + } + return 3; // Ok + } + return 4; // Ok +} + +function indexedComp2(x: T): Comp[T] { +>indexedComp2 : Symbol(indexedComp2, Decl(dependentReturnType5.ts, 58, 1)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 60, 22)) +>x : Symbol(x, Decl(dependentReturnType5.ts, 60, 49)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 60, 22)) +>Comp : Symbol(Comp, Decl(dependentReturnType5.ts, 36, 1)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 60, 22)) + + if (Math.random()) { +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + return 3; // Bad, unsound + } + return 2; // Error +} + + +// Most common case supported: +interface F { +>F : Symbol(F, Decl(dependentReturnType5.ts, 65, 1)) + + "t": number, +>"t" : Symbol(F["t"], Decl(dependentReturnType5.ts, 69, 13)) + + "f": boolean, +>"f" : Symbol(F["f"], Decl(dependentReturnType5.ts, 70, 16)) +} + +// Ok +function depLikeFun(str: T): F[T] { +>depLikeFun : Symbol(depLikeFun, Decl(dependentReturnType5.ts, 72, 1)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 75, 20)) +>str : Symbol(str, Decl(dependentReturnType5.ts, 75, 41)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 75, 20)) +>F : Symbol(F, Decl(dependentReturnType5.ts, 65, 1)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 75, 20)) + + if (str === "t") { +>str : Symbol(str, Decl(dependentReturnType5.ts, 75, 41)) + + return 1; + } else { + return true; + } +} + +depLikeFun("t"); // has type number +>depLikeFun : Symbol(depLikeFun, Decl(dependentReturnType5.ts, 72, 1)) + +depLikeFun("f"); // has type boolean +>depLikeFun : Symbol(depLikeFun, Decl(dependentReturnType5.ts, 72, 1)) + +type IndirectF = F[T]; +>IndirectF : Symbol(IndirectF, Decl(dependentReturnType5.ts, 84, 16)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 86, 15)) +>F : Symbol(F, Decl(dependentReturnType5.ts, 65, 1)) +>F : Symbol(F, Decl(dependentReturnType5.ts, 65, 1)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 86, 15)) + +// Ok +function depLikeFun2(str: T): IndirectF { +>depLikeFun2 : Symbol(depLikeFun2, Decl(dependentReturnType5.ts, 86, 41)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 89, 21)) +>str : Symbol(str, Decl(dependentReturnType5.ts, 89, 42)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 89, 21)) +>IndirectF : Symbol(IndirectF, Decl(dependentReturnType5.ts, 84, 16)) +>T : Symbol(T, Decl(dependentReturnType5.ts, 89, 21)) + + if (str === "t") { +>str : Symbol(str, Decl(dependentReturnType5.ts, 89, 42)) + + return 1; + } else { + return true; + } +} diff --git a/tests/baselines/reference/dependentReturnType5.types b/tests/baselines/reference/dependentReturnType5.types new file mode 100644 index 0000000000000..a1d5aa9991b27 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType5.types @@ -0,0 +1,228 @@ +//// [tests/cases/compiler/dependentReturnType5.ts] //// + +=== dependentReturnType5.ts === +// Indexed access return type +interface A1 { + "prop": true; +>"prop" : true +>true : true + + [s: string]: boolean; +>s : string +} + +// This was already allowed but is unsound. +function foo1(x: T): A1[T] { +>foo1 : (x: T) => A1[T] +>x : T + + return false; +>false : false +} +const rfoo1 = foo1("prop"); // Type says true, but actually returns false. +>rfoo1 : true +>foo1("prop") : true +>foo1 : (x: T) => A1[T] +>"prop" : "prop" + +interface A2 { + "prop": true; +>"prop" : true +>true : true + + [n: number]: string; +>n : number +} + +// We could soundly allow that, because `"prop"` and `[n: number]` are disjoint types. +function foo2(x: T): A2[T] { +>foo2 : (x: T) => A2[T] +>x : T + + if (x === "prop") { +>x === "prop" : boolean +>x : T +>"prop" : "prop" + + return true; +>true : true + } + return "some string"; +>"some string" : "some string" +} +const rfoo2 = foo2("prop"); +>rfoo2 : true +>foo2("prop") : true +>foo2 : (x: T) => A2[T] +>"prop" : "prop" + +const rfoo22 = foo2(34); +>rfoo22 : string +>foo2(34) : string +>foo2 : (x: T) => A2[T] +>34 : 34 + +const rfoo222 = foo2(Math.random() ? "prop" : 34); +>rfoo222 : string | true +>foo2(Math.random() ? "prop" : 34) : string | true +>foo2 : (x: T) => A2[T] +>Math.random() ? "prop" : 34 : "prop" | 34 +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>"prop" : "prop" +>34 : 34 + +interface A3 { + [s: string]: boolean; +>s : string +} + +// No need for return type narrowing. +function foo3(x: T): A3[T] { +>foo3 : (x: T) => A3[T] +>x : T + + if (Math.random()) return true; +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>true : true + + return false; +>false : false +} + +interface Comp { + foo: 2; +>foo : 2 + + [n: number]: 3; +>n : number + + [s: string]: 2 | 3 | 4; +>s : string +} + +function indexedComp(x: T): Comp[T] { +>indexedComp : (x: T) => Comp[T] +>x : T + + if (x === "foo") { +>x === "foo" : boolean +>x : T +>"foo" : "foo" + + if (Math.random()) { +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number + + return 3; // Error +>3 : 3 + } + return 2; // Ok +>2 : 2 + } + if (typeof x === "number") { +>typeof x === "number" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T +>"number" : "number" + + if (Math.random()) { +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number + + return 2; // Error +>2 : 2 + } + return 3; // Ok +>3 : 3 + } + return 4; // Ok +>4 : 4 +} + +function indexedComp2(x: T): Comp[T] { +>indexedComp2 : (x: T) => Comp[T] +>x : T + + if (Math.random()) { +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number + + return 3; // Bad, unsound +>3 : 3 + } + return 2; // Error +>2 : 2 +} + + +// Most common case supported: +interface F { + "t": number, +>"t" : number + + "f": boolean, +>"f" : boolean +} + +// Ok +function depLikeFun(str: T): F[T] { +>depLikeFun : (str: T) => F[T] +>str : T + + if (str === "t") { +>str === "t" : boolean +>str : T +>"t" : "t" + + return 1; +>1 : 1 + + } else { + return true; +>true : true + } +} + +depLikeFun("t"); // has type number +>depLikeFun("t") : number +>depLikeFun : (str: T) => F[T] +>"t" : "t" + +depLikeFun("f"); // has type boolean +>depLikeFun("f") : boolean +>depLikeFun : (str: T) => F[T] +>"f" : "f" + +type IndirectF = F[T]; +>IndirectF : IndirectF + +// Ok +function depLikeFun2(str: T): IndirectF { +>depLikeFun2 : (str: T) => IndirectF +>str : T + + if (str === "t") { +>str === "t" : boolean +>str : T +>"t" : "t" + + return 1; +>1 : 1 + + } else { + return true; +>true : true + } +} diff --git a/tests/cases/compiler/dependentDebug.ts b/tests/cases/compiler/dependentDebug.ts new file mode 100644 index 0000000000000..11663080b6347 --- /dev/null +++ b/tests/cases/compiler/dependentDebug.ts @@ -0,0 +1,17 @@ +// @strict: true +// @noEmit: true + +type HelperCond = + T extends A ? R1 + : T extends B ? R2 + : R1 | R2; + +// type IfBoolean = T extends true ? 1 : T extends false ? 0 : 1 | 0 + +function transform1(value: T): HelperCond { + if (value == null) return null; // Ok + if (typeof value !== 'string') { + throw new Error(); + } + return value.toLowerCase(); // Ok +} diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index f099cf706177c..6183755bd1908 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -396,55 +396,6 @@ function* cbool(x: T): Generator(str: T): F[T] { - if (str === "t") { - return 1; - } else { - return true; - } -} - -depLikeFun("t"); // has type number -depLikeFun("f"); // has type boolean - -type IndirectF = F[T]; - -// Ok -function depLikeFun2(str: T): IndirectF { - if (str === "t") { - return 1; - } else { - return true; - } -} - - -interface CComp { - foo: 1; - [s: string]: 1 | 2; -} - -function indexedCComp(x: T): CComp[T] { - if (x === "foo") { - if (Math.random()) { - return 2; // Error - } - return 1; // Ok - } - return 2; // Ok -} - -function indexedCComp2(x: T): CComp[T] { - return 2; // Bad, unsafe -} - // From #33912 abstract class Operation { abstract perform(t: T): R; diff --git a/tests/cases/compiler/dependentReturnType5.ts b/tests/cases/compiler/dependentReturnType5.ts new file mode 100644 index 0000000000000..137474217ecb7 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType5.ts @@ -0,0 +1,99 @@ +// @strict: true +// @noEmit: true + +// Indexed access return type +interface A1 { + "prop": true; + [s: string]: boolean; +} + +// This was already allowed but is unsound. +function foo1(x: T): A1[T] { + return false; +} +const rfoo1 = foo1("prop"); // Type says true, but actually returns false. + +interface A2 { + "prop": true; + [n: number]: string; +} + +// We could soundly allow that, because `"prop"` and `[n: number]` are disjoint types. +function foo2(x: T): A2[T] { + if (x === "prop") { + return true; + } + return "some string"; +} +const rfoo2 = foo2("prop"); +const rfoo22 = foo2(34); +const rfoo222 = foo2(Math.random() ? "prop" : 34); + +interface A3 { + [s: string]: boolean; +} + +// No need for return type narrowing. +function foo3(x: T): A3[T] { + if (Math.random()) return true; + return false; +} + +interface Comp { + foo: 2; + [n: number]: 3; + [s: string]: 2 | 3 | 4; +} + +function indexedComp(x: T): Comp[T] { + if (x === "foo") { + if (Math.random()) { + return 3; // Error + } + return 2; // Ok + } + if (typeof x === "number") { + if (Math.random()) { + return 2; // Error + } + return 3; // Ok + } + return 4; // Ok +} + +function indexedComp2(x: T): Comp[T] { + if (Math.random()) { + return 3; // Bad, unsound + } + return 2; // Error +} + + +// Most common case supported: +interface F { + "t": number, + "f": boolean, +} + +// Ok +function depLikeFun(str: T): F[T] { + if (str === "t") { + return 1; + } else { + return true; + } +} + +depLikeFun("t"); // has type number +depLikeFun("f"); // has type boolean + +type IndirectF = F[T]; + +// Ok +function depLikeFun2(str: T): IndirectF { + if (str === "t") { + return 1; + } else { + return true; + } +} \ No newline at end of file From 7d76043fd29acba837ac51c097ae3d123bcd5c35 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 16 Jan 2024 17:53:19 -0800 Subject: [PATCH 48/90] add never test --- .../reference/dependentReturnType2.errors.txt | 16 ++++++++++- .../reference/dependentReturnType2.symbols | 22 +++++++++++++++ .../reference/dependentReturnType2.types | 27 +++++++++++++++++++ tests/cases/compiler/dependentReturnType2.ts | 11 ++++++++ 4 files changed, 75 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/dependentReturnType2.errors.txt b/tests/baselines/reference/dependentReturnType2.errors.txt index 06126c47839fb..8505b82677d98 100644 --- a/tests/baselines/reference/dependentReturnType2.errors.txt +++ b/tests/baselines/reference/dependentReturnType2.errors.txt @@ -1,8 +1,9 @@ dependentReturnType2.ts(2,65): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. dependentReturnType2.ts(4,9): error TS2322: Type '3' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 3'. +dependentReturnType2.ts(16,5): error TS2322: Type 'number' is not assignable to type 'never'. -==== dependentReturnType2.ts (2 errors) ==== +==== dependentReturnType2.ts (3 errors) ==== // If during narrowing, one of the conditional types in the distribution doesn't narrow, then the whole type will not be narrowed function whoKnows(x: T): T extends true ? 1 : T extends false ? 2 : 3 { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -12,4 +13,17 @@ dependentReturnType2.ts(4,9): error TS2322: Type '3' is not assignable to type ' ~~~~~~ !!! error TS2322: Type '3' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 3'. } + } + + // If the conditional type's input is `never`, then it resolves to `never`: + function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : 1 | 2 { + if (x === true) { + return 1; + } + if (x === false) { + return 2; + } + return 1; + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'never'. } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType2.symbols b/tests/baselines/reference/dependentReturnType2.symbols index 26633ee247a7a..74f4ab3fb0345 100644 --- a/tests/baselines/reference/dependentReturnType2.symbols +++ b/tests/baselines/reference/dependentReturnType2.symbols @@ -16,3 +16,25 @@ function whoKnows(x: T): T extends true ? return 3; } } + +// If the conditional type's input is `never`, then it resolves to `never`: +function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : 1 | 2 { +>neverOk : Symbol(neverOk, Decl(dependentReturnType2.ts, 5, 1)) +>T : Symbol(T, Decl(dependentReturnType2.ts, 8, 17)) +>x : Symbol(x, Decl(dependentReturnType2.ts, 8, 36)) +>T : Symbol(T, Decl(dependentReturnType2.ts, 8, 17)) +>T : Symbol(T, Decl(dependentReturnType2.ts, 8, 17)) +>T : Symbol(T, Decl(dependentReturnType2.ts, 8, 17)) + + if (x === true) { +>x : Symbol(x, Decl(dependentReturnType2.ts, 8, 36)) + + return 1; + } + if (x === false) { +>x : Symbol(x, Decl(dependentReturnType2.ts, 8, 36)) + + return 2; + } + return 1; +} diff --git a/tests/baselines/reference/dependentReturnType2.types b/tests/baselines/reference/dependentReturnType2.types index bbb21bf78d73a..f498ff61be1b2 100644 --- a/tests/baselines/reference/dependentReturnType2.types +++ b/tests/baselines/reference/dependentReturnType2.types @@ -18,3 +18,30 @@ function whoKnows(x: T): T extends true ? >3 : 3 } } + +// If the conditional type's input is `never`, then it resolves to `never`: +function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : 1 | 2 { +>neverOk : (x: T) => T extends true ? 1 : T extends false ? 2 : 1 | 2 +>x : T +>true : true +>false : false + + if (x === true) { +>x === true : boolean +>x : T +>true : true + + return 1; +>1 : 1 + } + if (x === false) { +>x === false : boolean +>x : T +>false : false + + return 2; +>2 : 2 + } + return 1; +>1 : 1 +} diff --git a/tests/cases/compiler/dependentReturnType2.ts b/tests/cases/compiler/dependentReturnType2.ts index c8bc71238bc63..11a9b7a0194e6 100644 --- a/tests/cases/compiler/dependentReturnType2.ts +++ b/tests/cases/compiler/dependentReturnType2.ts @@ -6,4 +6,15 @@ function whoKnows(x: T): T extends true ? if (typeof x !== "string") { return 3; } +} + +// If the conditional type's input is `never`, then it resolves to `never`: +function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : 1 | 2 { + if (x === true) { + return 1; + } + if (x === false) { + return 2; + } + return 1; } \ No newline at end of file From c73638300f2e4621cb0538fd1123f8a2fdf9e5ab Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 17 Jan 2024 12:54:19 -0800 Subject: [PATCH 49/90] fix optional param detection under exact optional property types --- src/compiler/checker.ts | 5 +- .../reference/dependentReturnType4.symbols | 67 +++++++++++++++---- .../reference/dependentReturnType4.types | 43 +++++++++++- tests/cases/compiler/dependentReturnType4.ts | 20 +++++- 4 files changed, 116 insertions(+), 19 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3066047cd7ba1..a44a6f4c2b865 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44339,11 +44339,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (((symbol.valueDeclaration && isOptionalDeclaration(symbol.valueDeclaration)) || isOptionalTupleElementSymbol(symbol)) && type.flags & TypeFlags.Union - && (type as UnionType).types[0] === undefinedOrMissingType + && ((type as UnionType).types[0] === undefinedType + || exactOptionalPropertyTypes && (type as UnionType).types[0] === missingType) && (type as UnionType).types[1].flags & TypeFlags.TypeParameter) { const typeParam = (type as UnionType).types[1] as TypeParameter; const constraint = getConstraintOfTypeParameter(typeParam); - if (!constraint || containsUndefinedType(constraint)) { + if (!constraint || constraint.flags & TypeFlags.Unknown || containsUndefinedType(constraint)) { add(typeParam, symbol, reference); } } diff --git a/tests/baselines/reference/dependentReturnType4.symbols b/tests/baselines/reference/dependentReturnType4.symbols index 27a89d0d1c76a..b6b36579e9b8a 100644 --- a/tests/baselines/reference/dependentReturnType4.symbols +++ b/tests/baselines/reference/dependentReturnType4.symbols @@ -6,38 +6,81 @@ declare const rand: { a?: never }; >rand : Symbol(rand, Decl(dependentReturnType4.ts, 1, 13)) >a : Symbol(a, Decl(dependentReturnType4.ts, 1, 21)) -type MissingType = typeof rand.a; ->MissingType : Symbol(MissingType, Decl(dependentReturnType4.ts, 1, 34)) +type Missing = typeof rand.a; +>Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 1, 34)) >rand.a : Symbol(a, Decl(dependentReturnType4.ts, 1, 21)) >rand : Symbol(rand, Decl(dependentReturnType4.ts, 1, 13)) >a : Symbol(a, Decl(dependentReturnType4.ts, 1, 21)) declare function takesString(x: string): void; ->takesString : Symbol(takesString, Decl(dependentReturnType4.ts, 2, 33)) +>takesString : Symbol(takesString, Decl(dependentReturnType4.ts, 2, 29)) >x : Symbol(x, Decl(dependentReturnType4.ts, 3, 29)) -function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { +function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { >hasOwnP : Symbol(hasOwnP, Decl(dependentReturnType4.ts, 3, 46)) >T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) ->MissingType : Symbol(MissingType, Decl(dependentReturnType4.ts, 1, 34)) ->obj : Symbol(obj, Decl(dependentReturnType4.ts, 4, 49)) ->a : Symbol(a, Decl(dependentReturnType4.ts, 4, 55)) +>Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 1, 34)) +>obj : Symbol(obj, Decl(dependentReturnType4.ts, 4, 45)) +>a : Symbol(a, Decl(dependentReturnType4.ts, 4, 51)) >T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) >T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) >T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) if (obj.hasOwnProperty("a")) { >obj.hasOwnProperty : Symbol(Object.hasOwnProperty, Decl(lib.es5.d.ts, --, --)) ->obj : Symbol(obj, Decl(dependentReturnType4.ts, 4, 49)) +>obj : Symbol(obj, Decl(dependentReturnType4.ts, 4, 45)) >hasOwnProperty : Symbol(Object.hasOwnProperty, Decl(lib.es5.d.ts, --, --)) takesString(obj.a); ->takesString : Symbol(takesString, Decl(dependentReturnType4.ts, 2, 33)) ->obj.a : Symbol(a, Decl(dependentReturnType4.ts, 4, 55)) ->obj : Symbol(obj, Decl(dependentReturnType4.ts, 4, 49)) ->a : Symbol(a, Decl(dependentReturnType4.ts, 4, 55)) +>takesString : Symbol(takesString, Decl(dependentReturnType4.ts, 2, 29)) +>obj.a : Symbol(a, Decl(dependentReturnType4.ts, 4, 51)) +>obj : Symbol(obj, Decl(dependentReturnType4.ts, 4, 45)) +>a : Symbol(a, Decl(dependentReturnType4.ts, 4, 51)) return 1; } return 2; } + +function foo(opts: { x?: T }): +>foo : Symbol(foo, Decl(dependentReturnType4.ts, 10, 1)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 12, 13)) +>opts : Symbol(opts, Decl(dependentReturnType4.ts, 12, 43)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 12, 50)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 12, 13)) + + T extends undefined ? 0 : T extends string ? 1 : 0 | 1 { +>T : Symbol(T, Decl(dependentReturnType4.ts, 12, 13)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 12, 13)) + + if (opts.x === undefined) { +>opts.x : Symbol(x, Decl(dependentReturnType4.ts, 12, 50)) +>opts : Symbol(opts, Decl(dependentReturnType4.ts, 12, 43)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 12, 50)) +>undefined : Symbol(undefined) + + return 0; + } + return 1; +} + +function bar(x?: T ): +>bar : Symbol(bar, Decl(dependentReturnType4.ts, 18, 1)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 20, 13)) +>Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 1, 34)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 20, 41)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 20, 13)) + + T extends Missing ? 0 : T extends string ? 1 : 0 | 1 { +>T : Symbol(T, Decl(dependentReturnType4.ts, 20, 13)) +>Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 1, 34)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 20, 13)) + + if (x === undefined) { +>x : Symbol(x, Decl(dependentReturnType4.ts, 20, 41)) +>undefined : Symbol(undefined) + + return 0; + } + return 1; +} diff --git a/tests/baselines/reference/dependentReturnType4.types b/tests/baselines/reference/dependentReturnType4.types index 56fa754ec04e4..2576f41c4c0c8 100644 --- a/tests/baselines/reference/dependentReturnType4.types +++ b/tests/baselines/reference/dependentReturnType4.types @@ -6,8 +6,8 @@ declare const rand: { a?: never }; >rand : { a?: never; } >a : undefined -type MissingType = typeof rand.a; ->MissingType : undefined +type Missing = typeof rand.a; +>Missing : undefined >rand.a : undefined >rand : { a?: never; } >a : undefined @@ -16,7 +16,7 @@ declare function takesString(x: string): void; >takesString : (x: string) => void >x : string -function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { +function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { >hasOwnP : (obj: { a?: T;}) => T extends string ? 1 : T extends undefined ? 2 : 1 | 2 >obj : { a?: T; } >a : T | undefined @@ -41,3 +41,40 @@ function hasOwnP(obj: { a?: T }): T extends stri return 2; >2 : 2 } + +function foo(opts: { x?: T }): +>foo : (opts: { x?: T;}) => T extends undefined ? 0 : T extends string ? 1 : 0 | 1 +>opts : { x?: T; } +>x : T | undefined + + T extends undefined ? 0 : T extends string ? 1 : 0 | 1 { + if (opts.x === undefined) { +>opts.x === undefined : boolean +>opts.x : T | undefined +>opts : { x?: T; } +>x : T | undefined +>undefined : undefined + + return 0; +>0 : 0 + } + return 1; +>1 : 1 +} + +function bar(x?: T ): +>bar : (x?: T) => T extends Missing ? 0 : T extends string ? 1 : 0 | 1 +>x : T | undefined + + T extends Missing ? 0 : T extends string ? 1 : 0 | 1 { + if (x === undefined) { +>x === undefined : boolean +>x : T | undefined +>undefined : undefined + + return 0; +>0 : 0 + } + return 1; +>1 : 1 +} diff --git a/tests/cases/compiler/dependentReturnType4.ts b/tests/cases/compiler/dependentReturnType4.ts index ef4d29bdbbb14..8c4b89147c0d2 100644 --- a/tests/cases/compiler/dependentReturnType4.ts +++ b/tests/cases/compiler/dependentReturnType4.ts @@ -5,12 +5,28 @@ // Test narrowing through `hasOwnProperty` calls declare const rand: { a?: never }; -type MissingType = typeof rand.a; +type Missing = typeof rand.a; declare function takesString(x: string): void; -function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { +function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { if (obj.hasOwnProperty("a")) { takesString(obj.a); return 1; } return 2; +} + +function foo(opts: { x?: T }): + T extends undefined ? 0 : T extends string ? 1 : 0 | 1 { + if (opts.x === undefined) { + return 0; + } + return 1; +} + +function bar(x?: T ): + T extends Missing ? 0 : T extends string ? 1 : 0 | 1 { + if (x === undefined) { + return 0; + } + return 1; } \ No newline at end of file From 9fee0c0987ebc531b12f7844f984c91bb59eb15c Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 23 Jan 2024 15:28:38 -0800 Subject: [PATCH 50/90] remove unused type parameter property --- src/compiler/types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8f5d619286570..746b6f2e9499f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6621,8 +6621,6 @@ export interface TypeParameter extends InstantiableType { isThisType?: boolean; /** @internal */ resolvedDefaultType?: Type; - /** @internal */ - exprName?: EntityName | null; } /** @internal */ From 02184306d3a83153a4adfc191fa31493b33831b2 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 25 Jan 2024 21:27:08 -0800 Subject: [PATCH 51/90] WIP: fix breaking case where return stmt already assignable to unnarrowed type --- src/compiler/checker.ts | 145 ++++++++++-------- .../reference/dependentReturnType5.errors.txt | 5 +- 2 files changed, 80 insertions(+), 70 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a44a6f4c2b865..648decae9b399 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44112,7 +44112,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { expr: Expression | undefined): void { const functionFlags = getFunctionFlags(container); const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; - let actualReturnType = unwrappedReturnType; + // let actualReturnType = unwrappedReturnType; if (expr) { const unwrappedExpr = skipParentheses(expr); if (isConditionalExpression(unwrappedExpr)) { @@ -44120,65 +44120,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - if (isNarrowableReturnType(unwrappedReturnType)) { - /* Begin weird stuff */ - const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); - const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - const queryTypeParameters = typeParameters - && filterQueryTypeParameters((container as SignatureDeclaration), typeParameters); - // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: - // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: - // `function foo(...) { - // return cond ? |expr| : ... - // }` - // - When the return expression is undefined, or it is defined and it is not one of the branches of a conditional expression, then the position is the return statement itself: - // `function foo(...) { - // |return expr;| - // }` - // or - // `function foo(...) { - // |return;| - // }` - let narrowPosition = node; - let narrowFlowNode = node.flowNode; - if (expr && isConditionalExpression(expr.parent)) { - narrowFlowNode = expr.parent.whenTrue === expr ? expr.parent.flowNodeWhenTrue : expr.parent.flowNodeWhenFalse; - } - if (queryTypeParameters && narrowFlowNode) { - const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, ([tp, symbol, reference]) => { - const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. - // Don't reuse the original reference's node id, - // because that could cause us to get a type that was cached for the original reference. - narrowReference.id = undefined; - // Set the symbol of the synthetic reference. - // This allows us to get the type of the reference at a location where the reference is possibly shadowed. - getNodeLinks(narrowReference).resolvedSymbol = symbol; - setParent(narrowReference, narrowPosition.parent); - setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); - narrowReference.flowNode = narrowFlowNode; - const exprType = getTypeOfExpression(narrowReference); - // Don't narrow the return type if narrowing didn't produce a narrower type for the expression. - if (isTypeAny(exprType) || isErrorType(exprType) || exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { - return undefined; - } - return [tp, exprType]; - }); - const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); - actualReturnType = instantiateNarrowType( - unwrappedReturnType, - narrowMapper, - /*mapper*/ undefined - ); - } - - if (expr) { - const links = getNodeLinks(expr); - if (!links.contextualReturnType) { - links.contextualReturnType = actualReturnType; - } - } - /* End weird stuff */ - } const exprType = expr ? checkExpressionCached(expr) : undefinedType; const unwrappedExprType = functionFlags & FunctionFlags.Async ? checkAwaitedType( @@ -44187,13 +44128,85 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) : exprType; - if (actualReturnType) { - // If the function has a return type, but promisedType is - // undefined, an error will be reported in checkAsyncFunctionReturnType - // so we don't need to report one here. - const errorNode = node.expression && isConditionalExpression(skipParentheses(node.expression)) ? expr : node; - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, actualReturnType, errorNode, expr); + + const errorNode = node.expression && isConditionalExpression(skipParentheses(node.expression)) ? expr : node; + if (!isNarrowableReturnType(unwrappedReturnType)) { + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); + return; + } + + // Check if type of return expression is assignable to original return type; + // If so, we don't need to narrow. + if (checkTypeAssignableTo(unwrappedExprType, unwrappedReturnType, /*errorNode*/ undefined)) { + return; } + /* Begin narrowing */ + const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); + const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); + const queryTypeParameters = typeParameters + && filterQueryTypeParameters((container as SignatureDeclaration), typeParameters); + // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: + // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: + // `function foo(...) { + // return cond ? |expr| : ... + // }` + // - When the return expression is undefined, or it is defined and it is not one of the branches of a conditional expression, then the position is the return statement itself: + // `function foo(...) { + // |return expr;| + // }` + // or + // `function foo(...) { + // |return;| + // }` + let narrowPosition = node; + let narrowFlowNode = node.flowNode; + if (expr && isConditionalExpression(expr.parent)) { + narrowFlowNode = expr.parent.whenTrue === expr ? expr.parent.flowNodeWhenTrue : expr.parent.flowNodeWhenFalse; + } + let narrowedReturnType = unwrappedReturnType; + if (queryTypeParameters && narrowFlowNode) { + const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, ([tp, symbol, reference]) => { + const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. + // Don't reuse the original reference's node id, + // because that could cause us to get a type that was cached for the original reference. + narrowReference.id = undefined; + // Set the symbol of the synthetic reference. + // This allows us to get the type of the reference at a location where the reference is possibly shadowed. + getNodeLinks(narrowReference).resolvedSymbol = symbol; + setParent(narrowReference, narrowPosition.parent); + setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); + narrowReference.flowNode = narrowFlowNode; + const exprType = getTypeOfExpression(narrowReference); + // Don't narrow the return type if narrowing didn't produce a narrower type for the expression. + if (isTypeAny(exprType) || isErrorType(exprType) || exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { + return undefined; + } + return [tp, exprType]; + }); + const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); + narrowedReturnType = instantiateNarrowType( + unwrappedReturnType, + narrowMapper, + /*mapper*/ undefined + ); + } + + if (expr) { + const links = getNodeLinks(expr); + if (!links.contextualReturnType) { + links.contextualReturnType = narrowedReturnType; + } + } + + const narrowedExprType = expr ? checkExpression(expr) : undefinedType; + const narrowedUnwrappedExprType = functionFlags & FunctionFlags.Async + ? checkAwaitedType( + narrowedExprType, + /*withAlias*/ false, + node, + Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) + : narrowedExprType; + checkTypeAssignableToAndOptionallyElaborate(narrowedUnwrappedExprType, narrowedReturnType, errorNode, expr); } function checkReturnConditionalExpression( diff --git a/tests/baselines/reference/dependentReturnType5.errors.txt b/tests/baselines/reference/dependentReturnType5.errors.txt index 2631fd3958865..3c4812c2fc7b1 100644 --- a/tests/baselines/reference/dependentReturnType5.errors.txt +++ b/tests/baselines/reference/dependentReturnType5.errors.txt @@ -1,10 +1,9 @@ -dependentReturnType5.ts(48,13): error TS2322: Type '3' is not assignable to type '2'. dependentReturnType5.ts(54,13): error TS2322: Type '2' is not assignable to type '3'. dependentReturnType5.ts(65,5): error TS2322: Type '2' is not assignable to type 'Comp[T]'. Type '2' is not assignable to type '3'. -==== dependentReturnType5.ts (3 errors) ==== +==== dependentReturnType5.ts (2 errors) ==== // Indexed access return type interface A1 { "prop": true; @@ -53,8 +52,6 @@ dependentReturnType5.ts(65,5): error TS2322: Type '2' is not assignable to type if (x === "foo") { if (Math.random()) { return 3; // Error - ~~~~~~ -!!! error TS2322: Type '3' is not assignable to type '2'. } return 2; // Ok } From c90c2d98a2e213b3f73b6c22e2eb47874e9c16d9 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 26 Jan 2024 13:29:14 -0800 Subject: [PATCH 52/90] fix lint --- src/compiler/checker.ts | 8 ++++---- tests/cases/compiler/dependentDebug.ts | 17 ----------------- 2 files changed, 4 insertions(+), 21 deletions(-) delete mode 100644 tests/cases/compiler/dependentDebug.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 648decae9b399..c7a6c175fb4f3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44140,11 +44140,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (checkTypeAssignableTo(unwrappedExprType, unwrappedReturnType, /*errorNode*/ undefined)) { return; } - /* Begin narrowing */ - const outerTypeParameters = getOuterTypeParameters(container!, /*includeThisTypes*/ false); + const outerTypeParameters = getOuterTypeParameters(container, /*includeThisTypes*/ false); const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); const queryTypeParameters = typeParameters - && filterQueryTypeParameters((container as SignatureDeclaration), typeParameters); + && filterQueryTypeParameters(container, typeParameters); // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: // `function foo(...) { @@ -44158,10 +44157,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // `function foo(...) { // |return;| // }` - let narrowPosition = node; + let narrowPosition: Node = node; let narrowFlowNode = node.flowNode; if (expr && isConditionalExpression(expr.parent)) { narrowFlowNode = expr.parent.whenTrue === expr ? expr.parent.flowNodeWhenTrue : expr.parent.flowNodeWhenFalse; + narrowPosition = expr; } let narrowedReturnType = unwrappedReturnType; if (queryTypeParameters && narrowFlowNode) { diff --git a/tests/cases/compiler/dependentDebug.ts b/tests/cases/compiler/dependentDebug.ts deleted file mode 100644 index 11663080b6347..0000000000000 --- a/tests/cases/compiler/dependentDebug.ts +++ /dev/null @@ -1,17 +0,0 @@ -// @strict: true -// @noEmit: true - -type HelperCond = - T extends A ? R1 - : T extends B ? R2 - : R1 | R2; - -// type IfBoolean = T extends true ? 1 : T extends false ? 0 : 1 | 0 - -function transform1(value: T): HelperCond { - if (value == null) return null; // Ok - if (typeof value !== 'string') { - throw new Error(); - } - return value.toLowerCase(); // Ok -} From 96f879c1d924058af78d984e837b1a8f1fbd00e8 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 26 Jan 2024 13:44:09 -0800 Subject: [PATCH 53/90] format --- src/compiler/checker.ts | 55 ++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 669c5d5531d36..a867ad34b868d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20117,14 +20117,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } /** - * * @param narrowMapper special mapper that has mappings originating from type parameter narrowing, and should only be considered in some places * @param mapper the usual mapper that should be used for all instantiations */ function instantiateNarrowTypeWorker( type: Type, narrowMapper: TypeMapper, - mapper: TypeMapper | undefined): Type { + mapper: TypeMapper | undefined, + ): Type { type = instantiateType(type, mapper); const flags = type.flags; if (flags & TypeFlags.IndexedAccess) { @@ -20144,7 +20144,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const result = getIndexedAccessType( objectType, indexType, - accessFlags); + accessFlags, + ); // >> NOTE: We need to detect if result is different from just putting the already resolved types together return instantiateNarrowType(result, narrowMapper, mapper); } @@ -20152,7 +20153,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getNarrowConditionalTypeInstantiation( type as ConditionalType, narrowMapper, - mapper ? combineTypeMappers((type as ConditionalType).mapper, mapper) : (type as ConditionalType).mapper); + mapper ? combineTypeMappers((type as ConditionalType).mapper, mapper) : (type as ConditionalType).mapper, + ); } return type; @@ -20172,7 +20174,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getNarrowConditionalTypeInstantiation( type: ConditionalType, narrowMapper: TypeMapper, - mapper: TypeMapper | undefined): Type { + mapper: TypeMapper | undefined, + ): Type { const root = type.root; if (root.outerTypeParameters) { // We are instantiating a conditional type that has one or more type parameters in scope. Apply the @@ -20202,7 +20205,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { distributionType: Type, checkTypeVariable: TypeParameter, narrowMapper: TypeMapper, - mapper: TypeMapper): Type { + mapper: TypeMapper, + ): Type { distributionType = getReducedType(distributionType); if (distributionType.flags & TypeFlags.Never) { return distributionType; @@ -44366,7 +44370,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { container: SignatureDeclaration, returnType: Type, node: ReturnStatement, - expr: Expression | undefined): void { + expr: Expression | undefined, + ): void { const functionFlags = getFunctionFlags(container); const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; // let actualReturnType = unwrappedReturnType; @@ -44383,7 +44388,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { exprType, /*withAlias*/ false, node, - Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) + Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member, + ) : exprType; const errorNode = node.expression && isConditionalExpression(skipParentheses(node.expression)) ? expr : node; @@ -44444,7 +44450,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { narrowedReturnType = instantiateNarrowType( unwrappedReturnType, narrowMapper, - /*mapper*/ undefined + /*mapper*/ undefined, ); } @@ -44461,7 +44467,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { narrowedExprType, /*withAlias*/ false, node, - Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) + Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member, + ) : narrowedExprType; checkTypeAssignableToAndOptionallyElaborate(narrowedUnwrappedExprType, narrowedReturnType, errorNode, expr); } @@ -44470,7 +44477,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { container: SignatureDeclaration, returnType: Type, node: ReturnStatement, - expr: ConditionalExpression): void { + expr: ConditionalExpression, + ): void { checkExpression(expr.condition); checkReturnStatementExpression(container, returnType, node, expr.whenTrue); checkReturnStatementExpression(container, returnType, node, expr.whenFalse); @@ -44543,10 +44551,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // `node` is `expr.hasOwnExpression(prop)` if (isPropertyAccessExpression(callAccess = (node as CallExpression).expression) && isIdentifier(callAccess.name) && callAccess.name.escapedText === "hasOwnProperty" && (node as CallExpression).arguments.length === 1 && isStringLiteralLike((node as CallExpression).arguments[0])) { const propName = (node as CallExpression).arguments[0] as StringLiteralLike; - const synthPropertyAccess = - canUsePropertyAccess(propName.text, languageVersion) ? - factory.createPropertyAccessExpression(callAccess.expression, propName.text) : - factory.createElementAccessExpression(callAccess.expression, propName); + const synthPropertyAccess = canUsePropertyAccess(propName.text, languageVersion) ? + factory.createPropertyAccessExpression(callAccess.expression, propName.text) : + factory.createElementAccessExpression(callAccess.expression, propName); setParent(synthPropertyAccess, node.parent); addReference(synthPropertyAccess); } @@ -44573,16 +44580,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return; case SyntaxKind.MetaProperty: if ((node as MetaProperty).keywordToken === SyntaxKind.ImportKeyword) { - addReference((node as ImportMetaProperty)); + addReference(node as ImportMetaProperty); } // >> TODO: I think you can never actually have `import.meta` or // `new.target` involved in narrowing, so this might be pointless return; case SyntaxKind.ThisKeyword: - addReference((node as ThisExpression)); + addReference(node as ThisExpression); return; case SyntaxKind.Identifier: - addReference((node as Identifier)); + addReference(node as Identifier); return; case SyntaxKind.PrivateIdentifier: // Don't add private identifiers as a reference. @@ -44590,11 +44597,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // as in `this.#privateId`. return; case SyntaxKind.SuperKeyword: - addReference((node as SuperExpression)); + addReference(node as SuperExpression); return; case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: - addReference((node as PropertyAccessExpression | ElementAccessExpression)); + addReference(node as PropertyAccessExpression | ElementAccessExpression); return; } } @@ -44606,12 +44613,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Check if we have an optional parameter, an optional property, or an optional tuple element. // In this case, its type can be `T | undefined`, // and if `T` allows for the undefined type, then we can still narrow `T`. - if (((symbol.valueDeclaration && isOptionalDeclaration(symbol.valueDeclaration)) + if ( + ((symbol.valueDeclaration && isOptionalDeclaration(symbol.valueDeclaration)) || isOptionalTupleElementSymbol(symbol)) && type.flags & TypeFlags.Union && ((type as UnionType).types[0] === undefinedType || exactOptionalPropertyTypes && (type as UnionType).types[0] === missingType) - && (type as UnionType).types[1].flags & TypeFlags.TypeParameter) { + && (type as UnionType).types[1].flags & TypeFlags.TypeParameter + ) { const typeParam = (type as UnionType).types[1] as TypeParameter; const constraint = getConstraintOfTypeParameter(typeParam); if (!constraint || constraint.flags & TypeFlags.Unknown || containsUndefinedType(constraint)) { @@ -44641,7 +44650,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function visit(node: Node) { if (node.kind === SyntaxKind.ReturnStatement) { - const nodeContainer = getContainingFunctionOrClassStaticBlock((node as ReturnStatement)); + const nodeContainer = getContainingFunctionOrClassStaticBlock(node as ReturnStatement); if (nodeContainer === container) { visitConditionalReturnExpression((node as ReturnStatement).expression); const flowNode = (node as ReturnStatement).flowNode; From 7b2c74181037db9343f66fbf6bab7f7d87b8b152 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 26 Jan 2024 15:08:19 -0800 Subject: [PATCH 54/90] remove unused new parameter to mapTypes --- src/compiler/checker.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a867ad34b868d..a439228c9f456 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27453,9 +27453,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Apply a mapping function to a type and return the resulting type. If the source type // is a union type, the mapping function is applied to each constituent type and a union // of the resulting types is returned. - function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean, useIntersection?: boolean, skipIfAnyUnchanged?: boolean): Type; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean, useIntersection?: boolean, skipIfAnyUnchanged?: boolean): Type | undefined; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean, useIntersection = false): Type | undefined { + function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined { if (type.flags & TypeFlags.Never) { return type; } @@ -27467,7 +27467,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let mappedTypes: Type[] | undefined; let changed = false; for (const t of types) { - const mapped = t.flags & TypeFlags.Union ? mapType(t, mapper, noReductions, useIntersection) : mapper(t); + const mapped = t.flags & TypeFlags.Union ? mapType(t, mapper, noReductions) : mapper(t); changed ||= t !== mapped; if (mapped) { if (!mappedTypes) { @@ -27478,15 +27478,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } - return changed ? mappedTypes && (useIntersection ? getIntersectionType(mappedTypes) : getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal)) : type; + return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type; } - function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, useIntersection = false) { + function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { return type.flags & TypeFlags.Union && aliasSymbol ? - (useIntersection ? - getIntersectionType(map((type as UnionType).types, mapper), aliasSymbol, aliasTypeArguments) : - getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments)) : - mapType(type, mapper, /*noReductions*/ undefined, useIntersection); + getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments) : + mapType(type, mapper, /*noReductions*/ undefined); } function extractTypesOfKind(type: Type, kind: TypeFlags) { From ba18b412558e7150981950eff4df3bee7abe4bad Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 26 Jan 2024 18:15:44 -0800 Subject: [PATCH 55/90] renaming --- src/compiler/checker.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a439228c9f456..077830e24569f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1503,15 +1503,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var lastGetCombinedModifierFlagsNode: Declaration | undefined; var lastGetCombinedModifierFlagsResult = ModifierFlags.None; - type QueryReference = + type TypeParameterReference = | Identifier | PropertyAccessExpression | ElementAccessExpression | ImportMetaProperty | SuperExpression | ThisExpression; - type QueryReferences = Map>; - var queryTypeParameterReferencesCache = new Map(); + type TypeParameterToReference = Map>; + var typeParameterReferencesCache = new Map(); // for public members that accept a Node or one of its subtypes, we must guard against // synthetic nodes created during transformations by calling `getParseTreeNode`. // for most of these, we perform the guard only on `checker` to avoid any possible @@ -44403,8 +44403,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const outerTypeParameters = getOuterTypeParameters(container, /*includeThisTypes*/ false); const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - const queryTypeParameters = typeParameters - && filterQueryTypeParameters(container, typeParameters); + const narrowableTypeParameters = typeParameters + && filterNarrowableTypeParameters(container, typeParameters); // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: // `function foo(...) { @@ -44425,8 +44425,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { narrowPosition = expr; } let narrowedReturnType = unwrappedReturnType; - if (queryTypeParameters && narrowFlowNode) { - const narrowed: [TypeParameter, Type][] = mapDefined(queryTypeParameters, ([tp, symbol, reference]) => { + if (narrowableTypeParameters && narrowFlowNode) { + const narrowed: [TypeParameter, Type][] = mapDefined(narrowableTypeParameters, ([tp, symbol, reference]) => { const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. // Don't reuse the original reference's node id, // because that could cause us to get a type that was cached for the original reference. @@ -44482,11 +44482,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkReturnStatementExpression(container, returnType, node, expr.whenFalse); } - function filterQueryTypeParameters(container: SignatureDeclaration, typeParameters: TypeParameter[]): [TypeParameter, Symbol, QueryReference][] | undefined { - const queryTypeParameterReferences = collectQueryTypeParameterReferences(container); - const queryParameters: [TypeParameter, Symbol, QueryReference][] = []; + function filterNarrowableTypeParameters(container: SignatureDeclaration, typeParameters: TypeParameter[]): [TypeParameter, Symbol, TypeParameterReference][] | undefined { + const narrowableTypeParameterReferences = collectTypeParameterReferences(container); + const queryParameters: [TypeParameter, Symbol, TypeParameterReference][] = []; for (const typeParam of typeParameters) { - const symbolMap = queryTypeParameterReferences.get(typeParam); + const symbolMap = narrowableTypeParameterReferences.get(typeParam); if (!symbolMap) continue; if (symbolMap.size === 1) { const [symbol, reference] = firstIterator(symbolMap.entries()); @@ -44496,14 +44496,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return queryParameters.length ? queryParameters : undefined; } - function collectQueryTypeParameterReferences(container: SignatureDeclaration): QueryReferences { - const references: QueryReferences = new Map(); - if (!queryTypeParameterReferencesCache.has(container)) { - queryTypeParameterReferencesCache.set(container, references); + function collectTypeParameterReferences(container: SignatureDeclaration): TypeParameterToReference { + const references: TypeParameterToReference = new Map(); + if (!typeParameterReferencesCache.has(container)) { + typeParameterReferencesCache.set(container, references); const flowNodes = collectReturnStatementFlowNodes(container); visitFlowNodes(flowNodes, getNodeFromFlowNode); } - return queryTypeParameterReferencesCache.get(container)!; + return typeParameterReferencesCache.get(container)!; // Get the node from the flow node that could have references used for narrowing. function getNodeFromFlowNode(flow: FlowNode) { @@ -44604,7 +44604,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function addReference(reference: QueryReference): void { + function addReference(reference: TypeParameterReference): void { const symbol = getSymbolForExpression(reference); const type = symbol && getTypeOfSymbol(symbol); if (!type || isErrorType(type)) return; @@ -44629,7 +44629,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { add(type as TypeParameter, symbol, reference); } - function add(type: TypeParameter, symbol: Symbol, reference: QueryReference) { + function add(type: TypeParameter, symbol: Symbol, reference: TypeParameterReference) { if (!references.has(type)) { references.set(type, new Map()); } From edc2b664c58765851fcac4077ba35a271febccea Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 26 Jan 2024 20:43:06 -0800 Subject: [PATCH 56/90] support detecting aliased expressions --- src/compiler/checker.ts | 59 ++++++++------- .../reference/dependentReturnType4.errors.txt | 66 +++++++++++++++++ .../reference/dependentReturnType4.symbols | 70 ++++++++++++++++++ .../reference/dependentReturnType4.types | 72 +++++++++++++++++++ tests/cases/compiler/dependentReturnType4.ts | 25 +++++++ 5 files changed, 267 insertions(+), 25 deletions(-) create mode 100644 tests/baselines/reference/dependentReturnType4.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 077830e24569f..d16d21c24f7ef 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -29061,14 +29061,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // up to five levels of aliased conditional expressions that are themselves declared as const variables. if (!isMatchingReference(reference, expr) && inlineLevel < 5) { const symbol = getResolvedSymbol(expr as Identifier); - if (isConstantVariable(symbol)) { - const declaration = symbol.valueDeclaration; - if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isConstantReference(reference)) { - inlineLevel++; - const result = narrowType(type, declaration.initializer, assumeTrue); - inlineLevel--; - return result; - } + const inlineExpression = getNarrowableInlineExpression(symbol); + if (inlineExpression && isConstantReference(reference)) { + inlineLevel++; + const result = narrowType(type, inlineExpression, assumeTrue); + inlineLevel--; + return result; } } // falls through @@ -29105,6 +29103,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + function getNarrowableInlineExpression(symbol: Symbol): Expression | undefined { + if (isConstantVariable(symbol)) { + const declaration = symbol.valueDeclaration; + if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer) { + return declaration.initializer; + } + } + } + function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { symbol = getExportSymbolOfValueSymbolIfExported(symbol); @@ -44505,7 +44512,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return typeParameterReferencesCache.get(container)!; - // Get the node from the flow node that could have references used for narrowing. + // Get the node from the flow node that could have narrowable references. function getNodeFromFlowNode(flow: FlowNode) { const flags = flow.flags; // Based on `getTypeAtFlowX` functions. @@ -44531,11 +44538,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (node) getReferencesFromNode(node); } - // >> TODO: Based on `isMatchingReference`? - // >> TODO: needs to be based on `narrowType` as well. - // If `node` came from a flow condition's condition, then it is going to be analyzed by - // `narrowType`. - function getReferencesFromNode(node: Node): void { + // Based on `isMatchingReference` and `narrowType`. + function getReferencesFromNode(node: Node, inlineLevel = 0): void { switch (node.kind) { case SyntaxKind.ParenthesizedExpression: case SyntaxKind.NonNullExpression: @@ -44546,21 +44550,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return; case SyntaxKind.CallExpression: let callAccess; - // `node` is `expr.hasOwnExpression(prop)` + // `node` is `expr.hasOwnExpression(prop)`, so we create a synthetic `expr.prop` reference for narrowing. if (isPropertyAccessExpression(callAccess = (node as CallExpression).expression) && isIdentifier(callAccess.name) && callAccess.name.escapedText === "hasOwnProperty" && (node as CallExpression).arguments.length === 1 && isStringLiteralLike((node as CallExpression).arguments[0])) { const propName = (node as CallExpression).arguments[0] as StringLiteralLike; const synthPropertyAccess = canUsePropertyAccess(propName.text, languageVersion) ? factory.createPropertyAccessExpression(callAccess.expression, propName.text) : factory.createElementAccessExpression(callAccess.expression, propName); setParent(synthPropertyAccess, node.parent); - addReference(synthPropertyAccess); + addReference(synthPropertyAccess, inlineLevel); } - getReferencesFromNode((node as CallExpression).expression); // >> TODO: do we need this? - // This is relevant for type predicate-based narrowing. + getReferencesFromNode((node as CallExpression).expression); (node as CallExpression).arguments.forEach(getReferencesFromNode); return; case SyntaxKind.PrefixUnaryExpression: - case SyntaxKind.PostfixUnaryExpression: // >> TODO: do we need this? + case SyntaxKind.PostfixUnaryExpression: getReferencesFromNode((node as PrefixUnaryExpression | PostfixUnaryExpression).operand); return; case SyntaxKind.TypeOfExpression: @@ -44578,16 +44581,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return; case SyntaxKind.MetaProperty: if ((node as MetaProperty).keywordToken === SyntaxKind.ImportKeyword) { - addReference(node as ImportMetaProperty); + addReference(node as ImportMetaProperty, inlineLevel); } // >> TODO: I think you can never actually have `import.meta` or // `new.target` involved in narrowing, so this might be pointless return; case SyntaxKind.ThisKeyword: - addReference(node as ThisExpression); + addReference(node as ThisExpression, inlineLevel); return; case SyntaxKind.Identifier: - addReference(node as Identifier); + addReference(node as Identifier, inlineLevel); return; case SyntaxKind.PrivateIdentifier: // Don't add private identifiers as a reference. @@ -44595,16 +44598,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // as in `this.#privateId`. return; case SyntaxKind.SuperKeyword: - addReference(node as SuperExpression); + addReference(node as SuperExpression, inlineLevel); return; case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: - addReference(node as PropertyAccessExpression | ElementAccessExpression); + addReference(node as PropertyAccessExpression | ElementAccessExpression, inlineLevel); return; } } - function addReference(reference: TypeParameterReference): void { + function addReference(reference: TypeParameterReference, inlineLevel: number): void { const symbol = getSymbolForExpression(reference); const type = symbol && getTypeOfSymbol(symbol); if (!type || isErrorType(type)) return; @@ -44628,6 +44631,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else if (type.flags & TypeFlags.TypeParameter) { add(type as TypeParameter, symbol, reference); } + else if (inlineLevel < 5) { + const inlineExpression = getNarrowableInlineExpression(symbol); + if (inlineExpression) { + getReferencesFromNode(inlineExpression, inlineLevel + 1); + } + } function add(type: TypeParameter, symbol: Symbol, reference: TypeParameterReference) { if (!references.has(type)) { diff --git a/tests/baselines/reference/dependentReturnType4.errors.txt b/tests/baselines/reference/dependentReturnType4.errors.txt new file mode 100644 index 0000000000000..11e117ee1cadf --- /dev/null +++ b/tests/baselines/reference/dependentReturnType4.errors.txt @@ -0,0 +1,66 @@ +dependentReturnType4.ts(48,15): error TS2322: Type 'string | number' is not assignable to type 'string'. + Type 'number' is not assignable to type 'string'. +dependentReturnType4.ts(49,9): error TS2322: Type 'number' is not assignable to type 'T extends number ? string : T extends string ? number : string | number'. +dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to type 'T extends number ? string : T extends string ? number : string | number'. + + +==== dependentReturnType4.ts (3 errors) ==== + // Test narrowing through `hasOwnProperty` calls + declare const rand: { a?: never }; + type Missing = typeof rand.a; + declare function takesString(x: string): void; + function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { + if (obj.hasOwnProperty("a")) { + takesString(obj.a); + return 1; + } + return 2; + } + + function foo(opts: { x?: T }): + T extends undefined ? 0 : T extends string ? 1 : 0 | 1 { + if (opts.x === undefined) { + return 0; + } + return 1; + } + + function bar(x?: T ): + T extends Missing ? 0 : T extends string ? 1 : 0 | 1 { + if (x === undefined) { + return 0; + } + return 1; + } + + // Aliased narrowing + function inlined(x: T): T extends number ? string : T extends string ? number : string | number { + const t = typeof x === "string"; + if (t) { + const y: string = x; + return 1; + } + return "one"; + } + + // Don't narrow more than 5 levels of aliasing + function inlined6(x: T): T extends number ? string : T extends string ? number : string | number { + const t1 = typeof x === "string"; + const t2 = t1; + const t3 = t2; + const t4 = t3; + const t5 = t4; + const t6 = t5; + if (t6) { + const y: string = x; + ~ +!!! error TS2322: Type 'string | number' is not assignable to type 'string'. +!!! error TS2322: Type 'number' is not assignable to type 'string'. + return 1; + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'T extends number ? string : T extends string ? number : string | number'. + } + return "one"; + ~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'T extends number ? string : T extends string ? number : string | number'. + } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType4.symbols b/tests/baselines/reference/dependentReturnType4.symbols index b6b36579e9b8a..3fd9b5ba85089 100644 --- a/tests/baselines/reference/dependentReturnType4.symbols +++ b/tests/baselines/reference/dependentReturnType4.symbols @@ -84,3 +84,73 @@ function bar(x?: T ): } return 1; } + +// Aliased narrowing +function inlined(x: T): T extends number ? string : T extends string ? number : string | number { +>inlined : Symbol(inlined, Decl(dependentReturnType4.ts, 26, 1)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 29, 17)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 29, 44)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 29, 17)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 29, 17)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 29, 17)) + + const t = typeof x === "string"; +>t : Symbol(t, Decl(dependentReturnType4.ts, 30, 9)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 29, 44)) + + if (t) { +>t : Symbol(t, Decl(dependentReturnType4.ts, 30, 9)) + + const y: string = x; +>y : Symbol(y, Decl(dependentReturnType4.ts, 32, 13)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 29, 44)) + + return 1; + } + return "one"; +} + +// Don't narrow more than 5 levels of aliasing +function inlined6(x: T): T extends number ? string : T extends string ? number : string | number { +>inlined6 : Symbol(inlined6, Decl(dependentReturnType4.ts, 36, 1)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 39, 18)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 39, 45)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 39, 18)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 39, 18)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 39, 18)) + + const t1 = typeof x === "string"; +>t1 : Symbol(t1, Decl(dependentReturnType4.ts, 40, 9)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 39, 45)) + + const t2 = t1; +>t2 : Symbol(t2, Decl(dependentReturnType4.ts, 41, 9)) +>t1 : Symbol(t1, Decl(dependentReturnType4.ts, 40, 9)) + + const t3 = t2; +>t3 : Symbol(t3, Decl(dependentReturnType4.ts, 42, 9)) +>t2 : Symbol(t2, Decl(dependentReturnType4.ts, 41, 9)) + + const t4 = t3; +>t4 : Symbol(t4, Decl(dependentReturnType4.ts, 43, 9)) +>t3 : Symbol(t3, Decl(dependentReturnType4.ts, 42, 9)) + + const t5 = t4; +>t5 : Symbol(t5, Decl(dependentReturnType4.ts, 44, 9)) +>t4 : Symbol(t4, Decl(dependentReturnType4.ts, 43, 9)) + + const t6 = t5; +>t6 : Symbol(t6, Decl(dependentReturnType4.ts, 45, 9)) +>t5 : Symbol(t5, Decl(dependentReturnType4.ts, 44, 9)) + + if (t6) { +>t6 : Symbol(t6, Decl(dependentReturnType4.ts, 45, 9)) + + const y: string = x; +>y : Symbol(y, Decl(dependentReturnType4.ts, 47, 13)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 39, 45)) + + return 1; + } + return "one"; +} diff --git a/tests/baselines/reference/dependentReturnType4.types b/tests/baselines/reference/dependentReturnType4.types index 2576f41c4c0c8..140ce6e7984fa 100644 --- a/tests/baselines/reference/dependentReturnType4.types +++ b/tests/baselines/reference/dependentReturnType4.types @@ -78,3 +78,75 @@ function bar(x?: T ): return 1; >1 : 1 } + +// Aliased narrowing +function inlined(x: T): T extends number ? string : T extends string ? number : string | number { +>inlined : (x: T) => T extends number ? string : T extends string ? number : string | number +>x : T + + const t = typeof x === "string"; +>t : boolean +>typeof x === "string" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T +>"string" : "string" + + if (t) { +>t : boolean + + const y: string = x; +>y : string +>x : string + + return 1; +>1 : 1 + } + return "one"; +>"one" : "one" +} + +// Don't narrow more than 5 levels of aliasing +function inlined6(x: T): T extends number ? string : T extends string ? number : string | number { +>inlined6 : (x: T) => T extends number ? string : T extends string ? number : string | number +>x : T + + const t1 = typeof x === "string"; +>t1 : boolean +>typeof x === "string" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T +>"string" : "string" + + const t2 = t1; +>t2 : boolean +>t1 : boolean + + const t3 = t2; +>t3 : boolean +>t2 : boolean + + const t4 = t3; +>t4 : boolean +>t3 : boolean + + const t5 = t4; +>t5 : boolean +>t4 : boolean + + const t6 = t5; +>t6 : boolean +>t5 : boolean + + if (t6) { +>t6 : boolean + + const y: string = x; +>y : string +>x : string | number + + return 1; +>1 : 1 + } + return "one"; +>"one" : "one" +} diff --git a/tests/cases/compiler/dependentReturnType4.ts b/tests/cases/compiler/dependentReturnType4.ts index 8c4b89147c0d2..1fc5114e6b2d3 100644 --- a/tests/cases/compiler/dependentReturnType4.ts +++ b/tests/cases/compiler/dependentReturnType4.ts @@ -29,4 +29,29 @@ function bar(x?: T ): return 0; } return 1; +} + +// Aliased narrowing +function inlined(x: T): T extends number ? string : T extends string ? number : string | number { + const t = typeof x === "string"; + if (t) { + const y: string = x; + return 1; + } + return "one"; +} + +// Don't narrow more than 5 levels of aliasing +function inlined6(x: T): T extends number ? string : T extends string ? number : string | number { + const t1 = typeof x === "string"; + const t2 = t1; + const t3 = t2; + const t4 = t3; + const t5 = t4; + const t6 = t5; + if (t6) { + const y: string = x; + return 1; + } + return "one"; } \ No newline at end of file From a2bb3b6ad2aeafacbb15109ab6d56eac1c8722d2 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 29 Jan 2024 17:00:07 -0800 Subject: [PATCH 57/90] clean up + fix detection --- src/compiler/checker.ts | 55 ++++++++----------- .../reference/dependentReturnType4.errors.txt | 11 ++++ .../reference/dependentReturnType4.symbols | 36 ++++++++++++ .../reference/dependentReturnType4.types | 31 +++++++++++ tests/cases/compiler/dependentReturnType4.ts | 11 ++++ 5 files changed, 112 insertions(+), 32 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d16d21c24f7ef..76def42c430b5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -413,7 +413,6 @@ import { ImportClause, ImportDeclaration, ImportEqualsDeclaration, - ImportMetaProperty, ImportOrExportSpecifier, ImportsNotUsedAsValues, ImportSpecifier, @@ -1507,7 +1506,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | Identifier | PropertyAccessExpression | ElementAccessExpression - | ImportMetaProperty | SuperExpression | ThisExpression; type TypeParameterToReference = Map>; @@ -44538,20 +44536,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (node) getReferencesFromNode(node); } - // Based on `isMatchingReference` and `narrowType`. + // Collect narrowable references from the nodes associated to a flow node. + // A narrowable reference here means an expression whose type may be narrowed during + // control flow analysis, and whose unnarrowed type could be a type parameter. function getReferencesFromNode(node: Node, inlineLevel = 0): void { switch (node.kind) { case SyntaxKind.ParenthesizedExpression: case SyntaxKind.NonNullExpression: - return getReferencesFromNode((node as NonNullExpression | ParenthesizedExpression).expression); + return getReferencesFromNode( + (node as NonNullExpression | ParenthesizedExpression).expression, + inlineLevel, + ); case SyntaxKind.BinaryExpression: - getReferencesFromNode((node as BinaryExpression).left); - getReferencesFromNode((node as BinaryExpression).right); + getReferencesFromNode((node as BinaryExpression).left, inlineLevel); + getReferencesFromNode((node as BinaryExpression).right, inlineLevel); return; case SyntaxKind.CallExpression: let callAccess; // `node` is `expr.hasOwnExpression(prop)`, so we create a synthetic `expr.prop` reference for narrowing. - if (isPropertyAccessExpression(callAccess = (node as CallExpression).expression) && isIdentifier(callAccess.name) && callAccess.name.escapedText === "hasOwnProperty" && (node as CallExpression).arguments.length === 1 && isStringLiteralLike((node as CallExpression).arguments[0])) { + if ( + isPropertyAccessExpression(callAccess = (node as CallExpression).expression) + && isIdentifier(callAccess.name) + && callAccess.name.escapedText === "hasOwnProperty" + && (node as CallExpression).arguments.length === 1 + && isStringLiteralLike((node as CallExpression).arguments[0]) + ) { const propName = (node as CallExpression).arguments[0] as StringLiteralLike; const synthPropertyAccess = canUsePropertyAccess(propName.text, languageVersion) ? factory.createPropertyAccessExpression(callAccess.expression, propName.text) : @@ -44559,49 +44568,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { setParent(synthPropertyAccess, node.parent); addReference(synthPropertyAccess, inlineLevel); } - getReferencesFromNode((node as CallExpression).expression); - (node as CallExpression).arguments.forEach(getReferencesFromNode); + getReferencesFromNode((node as CallExpression).expression, inlineLevel); + (node as CallExpression).arguments.forEach(arg => getReferencesFromNode(arg, inlineLevel)); return; case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.PostfixUnaryExpression: - getReferencesFromNode((node as PrefixUnaryExpression | PostfixUnaryExpression).operand); + getReferencesFromNode((node as PrefixUnaryExpression | PostfixUnaryExpression).operand, inlineLevel); return; case SyntaxKind.TypeOfExpression: getReferencesFromNode((node as TypeOfExpression).expression); return; - case SyntaxKind.VoidExpression: - case SyntaxKind.AwaitExpression: - case SyntaxKind.YieldExpression: - // We won't narrow `e` in `await e`, `void e` or `yield e`. - return; - case SyntaxKind.BindingElement: - case SyntaxKind.VariableDeclaration: - // >> TODO: get name of variable declaration/binding element and use that as ref? - // Can we get away with skipping this? - return; - case SyntaxKind.MetaProperty: - if ((node as MetaProperty).keywordToken === SyntaxKind.ImportKeyword) { - addReference(node as ImportMetaProperty, inlineLevel); - } - // >> TODO: I think you can never actually have `import.meta` or - // `new.target` involved in narrowing, so this might be pointless - return; case SyntaxKind.ThisKeyword: addReference(node as ThisExpression, inlineLevel); return; case SyntaxKind.Identifier: addReference(node as Identifier, inlineLevel); return; - case SyntaxKind.PrivateIdentifier: - // Don't add private identifiers as a reference. - // They're only a narrowable reference when used in a property access expression, - // as in `this.#privateId`. - return; case SyntaxKind.SuperKeyword: addReference(node as SuperExpression, inlineLevel); return; case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: + getReferencesFromNode( + (node as PropertyAccessExpression | ElementAccessExpression).expression, + inlineLevel, + ); addReference(node as PropertyAccessExpression | ElementAccessExpression, inlineLevel); return; } diff --git a/tests/baselines/reference/dependentReturnType4.errors.txt b/tests/baselines/reference/dependentReturnType4.errors.txt index 11e117ee1cadf..c5e6cf232df81 100644 --- a/tests/baselines/reference/dependentReturnType4.errors.txt +++ b/tests/baselines/reference/dependentReturnType4.errors.txt @@ -63,4 +63,15 @@ dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to return "one"; ~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'T extends number ? string : T extends string ? number : string | number'. + } + + type A = { kind: "a", a: number }; + type B = { kind: "b", b: string }; + type AOrB = A | B; + + function subexpression(x: T): T extends A ? number : T extends B ? string : number | string { + if (x.kind === "b") { + return "some str"; + } + return 0; } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType4.symbols b/tests/baselines/reference/dependentReturnType4.symbols index 3fd9b5ba85089..06646f7ad078e 100644 --- a/tests/baselines/reference/dependentReturnType4.symbols +++ b/tests/baselines/reference/dependentReturnType4.symbols @@ -154,3 +154,39 @@ function inlined6(x: T): T extends number ? string : } return "one"; } + +type A = { kind: "a", a: number }; +>A : Symbol(A, Decl(dependentReturnType4.ts, 51, 1)) +>kind : Symbol(kind, Decl(dependentReturnType4.ts, 53, 10)) +>a : Symbol(a, Decl(dependentReturnType4.ts, 53, 21)) + +type B = { kind: "b", b: string }; +>B : Symbol(B, Decl(dependentReturnType4.ts, 53, 34)) +>kind : Symbol(kind, Decl(dependentReturnType4.ts, 54, 10)) +>b : Symbol(b, Decl(dependentReturnType4.ts, 54, 21)) + +type AOrB = A | B; +>AOrB : Symbol(AOrB, Decl(dependentReturnType4.ts, 54, 34)) +>A : Symbol(A, Decl(dependentReturnType4.ts, 51, 1)) +>B : Symbol(B, Decl(dependentReturnType4.ts, 53, 34)) + +function subexpression(x: T): T extends A ? number : T extends B ? string : number | string { +>subexpression : Symbol(subexpression, Decl(dependentReturnType4.ts, 55, 18)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 57, 23)) +>AOrB : Symbol(AOrB, Decl(dependentReturnType4.ts, 54, 34)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 57, 39)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 57, 23)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 57, 23)) +>A : Symbol(A, Decl(dependentReturnType4.ts, 51, 1)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 57, 23)) +>B : Symbol(B, Decl(dependentReturnType4.ts, 53, 34)) + + if (x.kind === "b") { +>x.kind : Symbol(kind, Decl(dependentReturnType4.ts, 53, 10), Decl(dependentReturnType4.ts, 54, 10)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 57, 39)) +>kind : Symbol(kind, Decl(dependentReturnType4.ts, 53, 10), Decl(dependentReturnType4.ts, 54, 10)) + + return "some str"; + } + return 0; +} diff --git a/tests/baselines/reference/dependentReturnType4.types b/tests/baselines/reference/dependentReturnType4.types index 140ce6e7984fa..7868e914a7ccd 100644 --- a/tests/baselines/reference/dependentReturnType4.types +++ b/tests/baselines/reference/dependentReturnType4.types @@ -150,3 +150,34 @@ function inlined6(x: T): T extends number ? string : return "one"; >"one" : "one" } + +type A = { kind: "a", a: number }; +>A : { kind: "a"; a: number; } +>kind : "a" +>a : number + +type B = { kind: "b", b: string }; +>B : { kind: "b"; b: string; } +>kind : "b" +>b : string + +type AOrB = A | B; +>AOrB : A | B + +function subexpression(x: T): T extends A ? number : T extends B ? string : number | string { +>subexpression : (x: T) => T extends A ? number : T extends B ? string : number | string +>x : T + + if (x.kind === "b") { +>x.kind === "b" : boolean +>x.kind : "a" | "b" +>x : AOrB +>kind : "a" | "b" +>"b" : "b" + + return "some str"; +>"some str" : "some str" + } + return 0; +>0 : 0 +} diff --git a/tests/cases/compiler/dependentReturnType4.ts b/tests/cases/compiler/dependentReturnType4.ts index 1fc5114e6b2d3..6eb2d6c1b3a80 100644 --- a/tests/cases/compiler/dependentReturnType4.ts +++ b/tests/cases/compiler/dependentReturnType4.ts @@ -54,4 +54,15 @@ function inlined6(x: T): T extends number ? string : return 1; } return "one"; +} + +type A = { kind: "a", a: number }; +type B = { kind: "b", b: string }; +type AOrB = A | B; + +function subexpression(x: T): T extends A ? number : T extends B ? string : number | string { + if (x.kind === "b") { + return "some str"; + } + return 0; } \ No newline at end of file From 816af8876e054f1e8dab4aba63cbfc1bff193ff0 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 29 Jan 2024 17:05:29 -0800 Subject: [PATCH 58/90] undo spurious changes --- src/compiler/checker.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 76def42c430b5..540b0c61225c1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27482,7 +27482,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { return type.flags & TypeFlags.Union && aliasSymbol ? getUnionType(map((type as UnionType).types, mapper), UnionReduction.Literal, aliasSymbol, aliasTypeArguments) : - mapType(type, mapper, /*noReductions*/ undefined); + mapType(type, mapper); } function extractTypesOfKind(type: Type, kind: TypeFlags) { @@ -30515,9 +30515,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function // Get the awaited type without the `Awaited` alias - const contextualAwaitedType = getAwaitedTypeNoAlias(contextualReturnType); - return contextualAwaitedType && - getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); + const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeNoAlias); + return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } return contextualReturnType; // Regular function or Generator function From 64e1566e66fef8bbc1ad307ba89adaaaa245553b Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 30 Jan 2024 12:25:21 -0800 Subject: [PATCH 59/90] support switch true detection --- src/compiler/checker.ts | 20 +++++++++++++++++-- .../reference/dependentReturnType4.errors.txt | 8 ++++++++ .../reference/dependentReturnType4.symbols | 17 ++++++++++++++++ .../reference/dependentReturnType4.types | 19 ++++++++++++++++++ tests/cases/compiler/dependentReturnType4.ts | 8 ++++++++ 5 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 540b0c61225c1..d2bd60204f71c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -476,6 +476,7 @@ import { isCallLikeOrFunctionLikeExpression, isCallOrNewExpression, isCallSignatureDeclaration, + isCaseClause, isCatchClause, isCatchClauseVariableDeclaration, isCatchClauseVariableDeclarationOrBindingElement, @@ -44376,7 +44377,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ): void { const functionFlags = getFunctionFlags(container); const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; - // let actualReturnType = unwrappedReturnType; if (expr) { const unwrappedExpr = skipParentheses(expr); if (isConditionalExpression(unwrappedExpr)) { @@ -44525,6 +44525,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } else if (flags & FlowFlags.SwitchClause) { node = (flow as FlowSwitchClause).switchStatement.expression; + // We have: + // `switch (true) { + // case expr1: ... + // case expr2: ... + // }` + // so we need to also gather narrowing references from the case expressions. + if (skipParentheses(node).kind === SyntaxKind.TrueKeyword) { + const switchStmt = node.parent as SwitchStatement; + for (const clause of switchStmt.caseBlock.clauses) { + if (isCaseClause(clause)) { + getReferencesFromNode(clause.expression); + } + } + + } } else if (flags & FlowFlags.ArrayMutation) { const callNode = (flow as FlowArrayMutation).node; @@ -44542,10 +44557,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { switch (node.kind) { case SyntaxKind.ParenthesizedExpression: case SyntaxKind.NonNullExpression: - return getReferencesFromNode( + getReferencesFromNode( (node as NonNullExpression | ParenthesizedExpression).expression, inlineLevel, ); + return; case SyntaxKind.BinaryExpression: getReferencesFromNode((node as BinaryExpression).left, inlineLevel); getReferencesFromNode((node as BinaryExpression).right, inlineLevel); diff --git a/tests/baselines/reference/dependentReturnType4.errors.txt b/tests/baselines/reference/dependentReturnType4.errors.txt index c5e6cf232df81..7e5429498df18 100644 --- a/tests/baselines/reference/dependentReturnType4.errors.txt +++ b/tests/baselines/reference/dependentReturnType4.errors.txt @@ -74,4 +74,12 @@ dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to return "some str"; } return 0; + } + + function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : 0 | 1 { + switch (true) { + case x: + return 1; + } + return 0; } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType4.symbols b/tests/baselines/reference/dependentReturnType4.symbols index 06646f7ad078e..4f52a8abe5e7b 100644 --- a/tests/baselines/reference/dependentReturnType4.symbols +++ b/tests/baselines/reference/dependentReturnType4.symbols @@ -190,3 +190,20 @@ function subexpression(x: T): T extends A ? number : T extends B } return 0; } + +function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : 0 | 1 { +>switchTrue : Symbol(switchTrue, Decl(dependentReturnType4.ts, 62, 1)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 64, 20)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 64, 39)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 64, 20)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 64, 20)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 64, 20)) + + switch (true) { + case x: +>x : Symbol(x, Decl(dependentReturnType4.ts, 64, 39)) + + return 1; + } + return 0; +} diff --git a/tests/baselines/reference/dependentReturnType4.types b/tests/baselines/reference/dependentReturnType4.types index 7868e914a7ccd..496d05cdcffcc 100644 --- a/tests/baselines/reference/dependentReturnType4.types +++ b/tests/baselines/reference/dependentReturnType4.types @@ -181,3 +181,22 @@ function subexpression(x: T): T extends A ? number : T extends B return 0; >0 : 0 } + +function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : 0 | 1 { +>switchTrue : (x: T) => T extends true ? 1 : T extends false ? 0 : 0 | 1 +>x : T +>true : true +>false : false + + switch (true) { +>true : true + + case x: +>x : T + + return 1; +>1 : 1 + } + return 0; +>0 : 0 +} diff --git a/tests/cases/compiler/dependentReturnType4.ts b/tests/cases/compiler/dependentReturnType4.ts index 6eb2d6c1b3a80..1d5e7a4d295e3 100644 --- a/tests/cases/compiler/dependentReturnType4.ts +++ b/tests/cases/compiler/dependentReturnType4.ts @@ -65,4 +65,12 @@ function subexpression(x: T): T extends A ? number : T extends B return "some str"; } return 0; +} + +function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : 0 | 1 { + switch (true) { + case x: + return 1; + } + return 0; } \ No newline at end of file From 747f74072c9c841cfd07ada69d193cc4d8f3b453 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 30 Jan 2024 19:07:20 -0800 Subject: [PATCH 60/90] add comment --- src/compiler/checker.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d2bd60204f71c..7f570b890f6c9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20093,7 +20093,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } /** - * This is similar to `instantiateType`, but with behavior specific to narrowing a return type based on control flow narrowing of expressions that have type parameter types. + * This is similar to `instantiateType`, but with behavior specific to narrowing + * a type based on control flow narrowing of expressions that have type parameter types. */ function instantiateNarrowType(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type { if (!couldContainTypeVariables(type)) { @@ -20116,7 +20117,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } /** - * @param narrowMapper special mapper that has mappings originating from type parameter narrowing, and should only be considered in some places + * @param narrowMapper special mapper that has mappings originating from type parameter narrowing, + * and should only be used for instantiation in some places. * @param mapper the usual mapper that should be used for all instantiations */ function instantiateNarrowTypeWorker( @@ -44538,10 +44540,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getReferencesFromNode(clause.expression); } } - } } else if (flags & FlowFlags.ArrayMutation) { + // `node` is either `arr.push(expr2)` (or other array method calls), + // or `arr[expr2] = expr3`, and in both we might narrow `arr`. const callNode = (flow as FlowArrayMutation).node; node = callNode.kind === SyntaxKind.CallExpression ? (callNode.expression as PropertyAccessExpression).expression : From 31c9a008f0069e65d122bdf930dcc9a274eaa991 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 31 Jan 2024 18:37:42 -0800 Subject: [PATCH 61/90] don't call get type of expression --- src/compiler/checker.ts | 4 ++- .../reference/dependentReturnType4.errors.txt | 10 +++++++ .../reference/dependentReturnType4.symbols | 27 +++++++++++++++++++ .../reference/dependentReturnType4.types | 24 +++++++++++++++++ tests/cases/compiler/dependentReturnType4.ts | 10 +++++++ 5 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7f570b890f6c9..fbb93d3cd9dfb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -44443,7 +44443,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { setParent(narrowReference, narrowPosition.parent); setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); narrowReference.flowNode = narrowFlowNode; - const exprType = getTypeOfExpression(narrowReference); + const initialType = getNarrowableTypeForReference(tp, narrowReference); + const flowType = getFlowTypeOfReference(narrowReference, initialType); + const exprType = getTypeFromFlowType(flowType); // Don't narrow the return type if narrowing didn't produce a narrower type for the expression. if (isTypeAny(exprType) || isErrorType(exprType) || exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { return undefined; diff --git a/tests/baselines/reference/dependentReturnType4.errors.txt b/tests/baselines/reference/dependentReturnType4.errors.txt index 7e5429498df18..6c5b298a65d77 100644 --- a/tests/baselines/reference/dependentReturnType4.errors.txt +++ b/tests/baselines/reference/dependentReturnType4.errors.txt @@ -82,4 +82,14 @@ dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to return 1; } return 0; + } + + // Don't raise errors when getting the narrowed type of synthesized nodes + type Ret = T extends string ? 1 : T extends number ? 2 : 1 | 2; + function f(x: T): Ret { + let y!: T; + if (typeof y === "string") { + return 1; + } + return 2; } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType4.symbols b/tests/baselines/reference/dependentReturnType4.symbols index 4f52a8abe5e7b..294e09d143be9 100644 --- a/tests/baselines/reference/dependentReturnType4.symbols +++ b/tests/baselines/reference/dependentReturnType4.symbols @@ -207,3 +207,30 @@ function switchTrue(x: T): T extends true ? 1 : T extends fal } return 0; } + +// Don't raise errors when getting the narrowed type of synthesized nodes +type Ret = T extends string ? 1 : T extends number ? 2 : 1 | 2; +>Ret : Symbol(Ret, Decl(dependentReturnType4.ts, 70, 1)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 73, 9)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 73, 9)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 73, 9)) + +function f(x: T): Ret { +>f : Symbol(f, Decl(dependentReturnType4.ts, 73, 90)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 74, 11)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 74, 38)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 74, 11)) +>Ret : Symbol(Ret, Decl(dependentReturnType4.ts, 70, 1)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 74, 11)) + + let y!: T; +>y : Symbol(y, Decl(dependentReturnType4.ts, 75, 7)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 74, 11)) + + if (typeof y === "string") { +>y : Symbol(y, Decl(dependentReturnType4.ts, 75, 7)) + + return 1; + } + return 2; +} diff --git a/tests/baselines/reference/dependentReturnType4.types b/tests/baselines/reference/dependentReturnType4.types index 496d05cdcffcc..b79740d3304a8 100644 --- a/tests/baselines/reference/dependentReturnType4.types +++ b/tests/baselines/reference/dependentReturnType4.types @@ -200,3 +200,27 @@ function switchTrue(x: T): T extends true ? 1 : T extends fal return 0; >0 : 0 } + +// Don't raise errors when getting the narrowed type of synthesized nodes +type Ret = T extends string ? 1 : T extends number ? 2 : 1 | 2; +>Ret : Ret + +function f(x: T): Ret { +>f : (x: T) => Ret +>x : T + + let y!: T; +>y : T + + if (typeof y === "string") { +>typeof y === "string" : boolean +>typeof y : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>y : T +>"string" : "string" + + return 1; +>1 : 1 + } + return 2; +>2 : 2 +} diff --git a/tests/cases/compiler/dependentReturnType4.ts b/tests/cases/compiler/dependentReturnType4.ts index 1d5e7a4d295e3..45593a04c6b47 100644 --- a/tests/cases/compiler/dependentReturnType4.ts +++ b/tests/cases/compiler/dependentReturnType4.ts @@ -73,4 +73,14 @@ function switchTrue(x: T): T extends true ? 1 : T extends fal return 1; } return 0; +} + +// Don't raise errors when getting the narrowed type of synthesized nodes +type Ret = T extends string ? 1 : T extends number ? 2 : 1 | 2; +function f(x: T): Ret { + let y!: T; + if (typeof y === "string") { + return 1; + } + return 2; } \ No newline at end of file From 23411b56d2ae4887f9237b96225ecb02b490ea59 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 9 Feb 2024 11:19:32 -0800 Subject: [PATCH 62/90] add out of scope test --- .../reference/dependentReturnType1.errors.txt | 34 +- .../reference/dependentReturnType1.symbols | 353 ++++++++++-------- .../reference/dependentReturnType1.types | 37 ++ tests/cases/compiler/dependentReturnType1.ts | 14 + 4 files changed, 271 insertions(+), 167 deletions(-) diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index d84cddade4ae2..d2fe19c338193 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -31,15 +31,17 @@ dependentReturnType1.ts(333,9): error TS2322: Type '2' is not assignable to type dependentReturnType1.ts(335,5): error TS2322: Type '1' is not assignable to type 'T extends string ? 1 : T extends undefined ? 2 : 1 | 2'. dependentReturnType1.ts(343,13): error TS2322: Type 'number' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : 1 | 2'. dependentReturnType1.ts(345,9): error TS2322: Type 'string' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : 1 | 2'. -dependentReturnType1.ts(353,13): error TS2322: Type 'number' is not assignable to type 'string'. -dependentReturnType1.ts(362,9): error TS2322: Type 'true' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. -dependentReturnType1.ts(364,5): error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. -dependentReturnType1.ts(389,15): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. -dependentReturnType1.ts(391,11): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. -dependentReturnType1.ts(414,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. +dependentReturnType1.ts(351,53): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. +dependentReturnType1.ts(359,9): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. +dependentReturnType1.ts(367,13): error TS2322: Type 'number' is not assignable to type 'string'. +dependentReturnType1.ts(376,9): error TS2322: Type 'true' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. +dependentReturnType1.ts(378,5): error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. +dependentReturnType1.ts(403,15): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. +dependentReturnType1.ts(405,11): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. +dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. -==== dependentReturnType1.ts (35 errors) ==== +==== dependentReturnType1.ts (37 errors) ==== interface A { 1: number; 2: string; @@ -450,6 +452,24 @@ dependentReturnType1.ts(414,13): error TS2322: Type 'R' is not assignable to typ } } + // If the narrowing reference is out of scope, we simply won't narrow its type + declare let someX: boolean; + function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'. + if ((true)) { + const someX = opts.a; + if (someX) { // We narrow `someX` and the return type here + return 1; + } + } + if (!someX) { // This is a different `someX`, so we don't narrow here + return 2; + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. + } + } + function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { if (x === 2) { let x: number = Math.random() ? 1 : 2; diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 2b9c1e504713f..27a745f13a121 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -978,25 +978,58 @@ function g(x: T): T extends 1 ? number : T extends 2 ? string : } } +// If the narrowing reference is out of scope, we simply won't narrow its type +declare let someX: boolean; +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 349, 11)) + +function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { +>scope2 : Symbol(scope2, Decl(dependentReturnType1.ts, 349, 27)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 350, 16)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 350, 35)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 350, 42)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 350, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 350, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 350, 16)) + + if ((true)) { + const someX = opts.a; +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 352, 13)) +>opts.a : Symbol(a, Decl(dependentReturnType1.ts, 350, 42)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 350, 35)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 350, 42)) + + if (someX) { // We narrow `someX` and the return type here +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 352, 13)) + + return 1; + } + } + if (!someX) { // This is a different `someX`, so we don't narrow here +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 349, 11)) + + return 2; + } +} + function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { ->h : Symbol(h, Decl(dependentReturnType1.ts, 346, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 348, 11)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 348, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 348, 11)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 348, 11)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 348, 11)) +>h : Symbol(h, Decl(dependentReturnType1.ts, 360, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 362, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 362, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 362, 11)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 362, 11)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 362, 11)) if (x === 2) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 348, 28)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 362, 28)) let x: number = Math.random() ? 1 : 2; ->x : Symbol(x, Decl(dependentReturnType1.ts, 350, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 364, 11)) >Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) >Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) >random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) if (x === 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 350, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 364, 11)) return 1; // Error } @@ -1006,17 +1039,17 @@ function h(x: T): T extends 1 ? number : T extends 2 ? string : } function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : string | boolean { ->withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 357, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 359, 48)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 359, 71)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 359, 71)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) +>withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 371, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 373, 19)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 373, 48)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 373, 19)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 373, 19)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 373, 71)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 373, 71)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 373, 19)) if (typeof x === "number") { ->x : Symbol(x, Decl(dependentReturnType1.ts, 359, 48)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 373, 48)) return true; } @@ -1024,22 +1057,22 @@ function withInfer(x: T): T extends [infer R] ? R : } const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. ->withInferResult : Symbol(withInferResult, Decl(dependentReturnType1.ts, 366, 5)) ->withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 357, 1)) +>withInferResult : Symbol(withInferResult, Decl(dependentReturnType1.ts, 380, 5)) +>withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 371, 1)) >const : Symbol(const) // Ok async function abool(x: T): Promise { ->abool : Symbol(abool, Decl(dependentReturnType1.ts, 366, 50)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 369, 45)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) +>abool : Symbol(abool, Decl(dependentReturnType1.ts, 380, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 383, 21)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 383, 45)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 383, 21)) >Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 383, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 383, 21)) if (x) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 369, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 383, 45)) return 1; } @@ -1048,17 +1081,17 @@ async function abool(x: T): Promise(x: T): Generator { ->bbool : Symbol(bbool, Decl(dependentReturnType1.ts, 374, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 377, 16)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 377, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 377, 16)) +>bbool : Symbol(bbool, Decl(dependentReturnType1.ts, 388, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 391, 16)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 391, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 391, 16)) >Generator : Symbol(Generator, Decl(lib.es2015.generator.d.ts, --, --)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 377, 16)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 377, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 391, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 391, 16)) yield 3; if (x) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 377, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 391, 40)) return 1; } @@ -1067,16 +1100,16 @@ function* bbool(x: T): Generator(x: T): Generator { ->cbool : Symbol(cbool, Decl(dependentReturnType1.ts, 383, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 386, 16)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 386, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 386, 16)) +>cbool : Symbol(cbool, Decl(dependentReturnType1.ts, 397, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 400, 16)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 400, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 400, 16)) >Generator : Symbol(Generator, Decl(lib.es2015.generator.d.ts, --, --)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 386, 16)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 386, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 400, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 400, 16)) if (x) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 386, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 400, 40)) yield 1; } @@ -1086,130 +1119,130 @@ function* cbool(x: T): Generator { ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 395, 25)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 395, 27)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 409, 25)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 409, 27)) abstract perform(t: T): R; ->perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 395, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 396, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 395, 25)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 395, 27)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 409, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 410, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 409, 25)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 409, 27)) } type ConditionalReturnType | undefined> = ->ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 397, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 399, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 399, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 399, 32)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 399, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 399, 29)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 411, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 413, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 413, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 413, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 413, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 413, 29)) EOp extends Operation ? R : EOp extends undefined ? T | R : T | R; ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 399, 32)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 399, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 399, 29)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 399, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 399, 32)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 399, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 399, 29)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 399, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 399, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 413, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 413, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 413, 29)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 413, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 413, 32)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 413, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 413, 29)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 413, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 413, 29)) class ConditionalOperation | undefined> extends Operation> { ->ConditionalOperation : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 400, 76)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 402, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 402, 32)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 402, 29)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) ->ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 397, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 402, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 402, 32)) +>ConditionalOperation : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 414, 76)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 416, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 416, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 416, 29)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 411, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 416, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 416, 32)) constructor( private predicate: (value: T) => boolean, ->predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 403, 16)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 404, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) +>predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 417, 16)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 418, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) private thenOp: Operation, ->thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 404, 49)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 402, 29)) +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 418, 49)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 416, 29)) private elseOp?: EOp ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 405, 40)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 402, 32)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 419, 40)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 416, 32)) ) { super(); ->super : Symbol(Operation, Decl(dependentReturnType1.ts, 392, 1)) +>super : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) } perform(t: T): ConditionalReturnType { ->perform : Symbol(ConditionalOperation.perform, Decl(dependentReturnType1.ts, 409, 5)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 411, 12)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) ->ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 397, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 402, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 402, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 402, 32)) +>perform : Symbol(ConditionalOperation.perform, Decl(dependentReturnType1.ts, 423, 5)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 425, 12)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 411, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 416, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 416, 32)) if (this.predicate(t)) { ->this.predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 403, 16)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 400, 76)) ->predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 403, 16)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 411, 12)) +>this.predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 417, 16)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 414, 76)) +>predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 417, 16)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 425, 12)) return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it ->this.thenOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 395, 32)) ->this.thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 404, 49)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 400, 76)) ->thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 404, 49)) ->perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 395, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 411, 12)) +>this.thenOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 409, 32)) +>this.thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 418, 49)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 414, 76)) +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 418, 49)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 409, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 425, 12)) } else if (typeof this.elseOp !== 'undefined') { ->this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 405, 40)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 400, 76)) ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 405, 40)) +>this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 419, 40)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 414, 76)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 419, 40)) return this.elseOp.perform(t); // Ok ->this.elseOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 395, 32)) ->this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 405, 40)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 400, 76)) ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 405, 40)) ->perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 395, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 411, 12)) +>this.elseOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 409, 32)) +>this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 419, 40)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 414, 76)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 419, 40)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 409, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 425, 12)) } else { return t; // Ok ->t : Symbol(t, Decl(dependentReturnType1.ts, 411, 12)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 425, 12)) } } } // Optional tuple element function tupl(x: [string, some?: T]): ->tupl : Symbol(tupl, Decl(dependentReturnType1.ts, 420, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 423, 14)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 423, 50)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 423, 14)) +>tupl : Symbol(tupl, Decl(dependentReturnType1.ts, 434, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 437, 14)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 437, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 437, 14)) T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 { ->T : Symbol(T, Decl(dependentReturnType1.ts, 423, 14)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 423, 14)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 437, 14)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 437, 14)) if (x[1]) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 423, 50)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 437, 50)) >1 : Symbol(1) return 1; @@ -1219,59 +1252,59 @@ function tupl(x: [string, some?: T]): // Return conditional expressions with parentheses function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { ->returnStuff1 : Symbol(returnStuff1, Decl(dependentReturnType1.ts, 429, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 432, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 432, 41)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 432, 48)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 432, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 432, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 432, 22)) +>returnStuff1 : Symbol(returnStuff1, Decl(dependentReturnType1.ts, 443, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 446, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 446, 41)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 446, 48)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 446, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 446, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 446, 22)) return (opts.x ? (1) : 2); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 432, 48)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 432, 41)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 432, 48)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 446, 48)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 446, 41)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 446, 48)) } function returnStuff2(opts: { x: T }): ->returnStuff2 : Symbol(returnStuff2, Decl(dependentReturnType1.ts, 434, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 436, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 436, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 436, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 436, 22)) +>returnStuff2 : Symbol(returnStuff2, Decl(dependentReturnType1.ts, 448, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 450, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 450, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 450, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 450, 22)) T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 { ->T : Symbol(T, Decl(dependentReturnType1.ts, 436, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 436, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 436, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 450, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 450, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 450, 22)) return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 436, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 436, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 436, 52)) ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 436, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 436, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 436, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 450, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 450, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 450, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 450, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 450, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 450, 52)) } // If the return type is written wrong, it still type checks function returnStuff3(opts: { x: T }): ->returnStuff3 : Symbol(returnStuff3, Decl(dependentReturnType1.ts, 439, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 442, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 442, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 442, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 442, 22)) +>returnStuff3 : Symbol(returnStuff3, Decl(dependentReturnType1.ts, 453, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 456, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 456, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 456, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 456, 22)) T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" { ->T : Symbol(T, Decl(dependentReturnType1.ts, 442, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 442, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 442, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 456, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 456, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 456, 22)) return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 442, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 442, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 442, 52)) ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 442, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 442, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 442, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 456, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 456, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 456, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 456, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 456, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 456, 52)) } diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 075a905978816..51d146b478e59 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -958,6 +958,43 @@ function g(x: T): T extends 1 ? number : T extends 2 ? string : } } +// If the narrowing reference is out of scope, we simply won't narrow its type +declare let someX: boolean; +>someX : boolean + +function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { +>scope2 : (opts: { a: T;}) => T extends true ? 1 : T extends false ? 2 : 1 | 2 +>opts : { a: T; } +>a : T +>true : true +>false : false + + if ((true)) { +>(true) : true +>true : true + + const someX = opts.a; +>someX : T +>opts.a : T +>opts : { a: T; } +>a : T + + if (someX) { // We narrow `someX` and the return type here +>someX : T + + return 1; +>1 : 1 + } + } + if (!someX) { // This is a different `someX`, so we don't narrow here +>!someX : boolean +>someX : boolean + + return 2; +>2 : 2 + } +} + function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { >h : (x: T) => T extends 1 ? number : T extends 2 ? string : 1 | 2 >x : T diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index 6183755bd1908..5aa24c4c2ca89 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -350,6 +350,20 @@ function g(x: T): T extends 1 ? number : T extends 2 ? string : } } +// If the narrowing reference is out of scope, we simply won't narrow its type +declare let someX: boolean; +function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { + if ((true)) { + const someX = opts.a; + if (someX) { // We narrow `someX` and the return type here + return 1; + } + } + if (!someX) { // This is a different `someX`, so we don't narrow here + return 2; + } +} + function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { if (x === 2) { let x: number = Math.random() ? 1 : 2; From 9c26a9ead28723c2f9b1d54f26400010f01b72b1 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 13 Feb 2024 11:26:02 -0800 Subject: [PATCH 63/90] cleanup --- src/compiler/checker.ts | 57 ++----------------- .../reference/dependentReturnType1.errors.txt | 14 +++-- .../reference/dependentReturnType1.symbols | 6 +- .../reference/dependentReturnType1.types | 6 +- tests/cases/compiler/dependentReturnType1.ts | 6 +- 5 files changed, 24 insertions(+), 65 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dc98e8ef59c40..bd107af6c6894 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18908,60 +18908,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) && length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements); - const forceEagerNarrowing = narrowableCheckTypeVariable && getMappedType(narrowableCheckTypeVariable, narrowMapper) !== narrowableCheckTypeVariable; // >> TODO: can probably optimize this - const checkTypeDeferred = isDeferredType(checkType, checkTuples) && !forceEagerNarrowing; - let combinedMapper: TypeMapper | undefined; - if (root.inferTypeParameters) { - // When we're looking at making an inference for an infer type, when we get its constraint, it'll automagically be - // instantiated with the context, so it doesn't need the mapper for the inference contex - however the constraint - // may refer to another _root_, _uncloned_ `infer` type parameter [1], or to something mapped by `mapper` [2]. - // [1] Eg, if we have `Foo` and `Foo` - `B` is constrained to `T`, which, in turn, has been instantiated - // as `number` - // Conversely, if we have `Foo`, `B` is still constrained to `T` and `T` is instantiated as `A` - // [2] Eg, if we have `Foo` and `Foo` where `Q` is mapped by `mapper` into `number` - `B` is constrained to `T` - // which is in turn instantiated as `Q`, which is in turn instantiated as `number`. - // So we need to: - // * Clone the type parameters so their constraints can be instantiated in the context of `mapper` (otherwise theyd only get inference context information) - // * Set the clones to both map the conditional's enclosing `mapper` and the original params - // * instantiate the extends type with the clones - // * incorporate all of the component mappers into the combined mapper for the true and false members - // This means we have three mappers that need applying: - // * The original `mapper` used to create this conditional - // * The mapper that maps the old root type parameter to the clone (`freshMapper`) - // * The mapper that maps the clone to its inference result (`context.mapper`) - const freshParams = sameMap(root.inferTypeParameters, maybeCloneTypeParameter); - const freshMapper = freshParams !== root.inferTypeParameters ? createTypeMapper(root.inferTypeParameters, freshParams) : undefined; - const context = createInferenceContext(freshParams, /*signature*/ undefined, InferenceFlags.None); - if (freshMapper) { - const freshCombinedMapper = combineTypeMappers(mapper, freshMapper); - for (const p of freshParams) { - if (root.inferTypeParameters.indexOf(p) === -1) { - p.mapper = freshCombinedMapper; - } - } - } - if (!checkTypeDeferred) { - // We don't want inferences from constraints as they may cause us to eagerly resolve the - // conditional type instead of deferring resolution. Also, we always want strict function - // types rules (i.e. proper contravariance) for inferences. - inferTypes(context.inferences, checkType, instantiateType(extendsType, freshMapper), InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); - } - const innerMapper = combineTypeMappers(freshMapper, context.mapper); - // It's possible for 'infer T' type paramteters to be given uninstantiated constraints when the - // those type parameters are used in type references (see getInferredTypeParameterConstraint). For - // that reason we need context.mapper to be first in the combined mapper. See #42636 for examples. - combinedMapper = mapper ? combineTypeMappers(innerMapper, mapper) : innerMapper; - } - // Instantiate the extends type including inferences for 'infer T' type parameters - const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; + const checkTypeDeferred = isDeferredType(checkType, checkTuples); // We attempt to resolve the conditional type only when the check and extends types are non-generic - // if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { - if (!checkTypeDeferred) { + if (!checkTypeDeferred && !isDeferredType(extendsType, checkTuples)) { // Return falseType for a definitely false extends check. We check an instantiation of the two // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, // then no instantiations will be and we can just return the false branch type. - if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { + if (!(extendsType.flags & TypeFlags.AnyOrUnknown) && !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(extendsType))) { Debug.assert(!(checkType.flags & TypeFlags.Any)); // If falseType is an immediately nested conditional type that has an // identical checkType, switch to that type and loop. @@ -18979,10 +18933,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // that has no constraint. This ensures that, for example, the type // type Foo = T extends { x: string } ? string : number // doesn't immediately resolve to 'string' instead of being deferred. - else if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { + else if (extendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { const trueType = getTypeFromTypeNode(root.node.trueType); - const trueMapper = combinedMapper || mapper; - result = instantiateNarrowType(trueType, narrowMapper, trueMapper); + result = instantiateNarrowType(trueType, narrowMapper, mapper); break; } } diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index d2fe19c338193..4ff800159dec1 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -5,7 +5,9 @@ dependentReturnType1.ts(70,9): error TS2322: Type '{ a: "a"; }' is not assignabl dependentReturnType1.ts(74,5): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. dependentReturnType1.ts(80,9): error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four'. dependentReturnType1.ts(84,22): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. +dependentReturnType1.ts(97,9): error TS2322: Type 'LeftOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. dependentReturnType1.ts(99,9): error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. +dependentReturnType1.ts(116,9): error TS2322: Type 'number' is not assignable to type 'T extends Dog ? number : string'. dependentReturnType1.ts(118,5): error TS2322: Type 'string' is not assignable to type 'T extends Dog ? number : string'. dependentReturnType1.ts(153,13): error TS2322: Type 'string' is not assignable to type 'T extends string ? this : string'. dependentReturnType1.ts(155,9): error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. @@ -41,7 +43,7 @@ dependentReturnType1.ts(405,11): error TS2322: Type '2' is not assignable to typ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. -==== dependentReturnType1.ts (37 errors) ==== +==== dependentReturnType1.ts (39 errors) ==== interface A { 1: number; 2: string; @@ -152,9 +154,11 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ { type OK = Arg extends LeftIn ? LeftOut : RightOut; if (cond(arg)) { - return produceLeftOut(arg); // Ok + return produceLeftOut(arg); // The narrowed conditional return type has deferred resolution, so this doesn't work. + ~~~~~~ +!!! error TS2322: Type 'LeftOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. } else { - return produceRightOut(arg as RightIn); // Error: Doesn't work because we don't narrow `arg` to `Arg & RightIn` here + return produceRightOut(arg as RightIn); // Error: Doesn't work because we can't narrow `arg` to `Arg & RightIn` here ~~~~~~ !!! error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. } @@ -173,7 +177,9 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ declare function doggy(x: Dog): number; function f12(x: T): T extends Dog ? number : string { if (isDog(x)) { // `x` has type `T & Dog` here - return doggy(x); // Ok + return doggy(x); // The narrowed conditional return type has deferred resolution, so this doesn't work. + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'T extends Dog ? number : string'. } return ""; // Error: Should not work because we can't express "not a Dog" in the type system ~~~~~~ diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 27a745f13a121..882077c986f5d 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -280,12 +280,12 @@ function conditionalProducingIfcond : Symbol(cond, Decl(dependentReturnType1.ts, 88, 13)) >arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) - return produceLeftOut(arg); // Ok + return produceLeftOut(arg); // The narrowed conditional return type has deferred resolution, so this doesn't work. >produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 89, 51)) >arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) } else { - return produceRightOut(arg as RightIn); // Error: Doesn't work because we don't narrow `arg` to `Arg & RightIn` here + return produceRightOut(arg as RightIn); // Error: Doesn't work because we can't narrow `arg` to `Arg & RightIn` here >produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 90, 45)) >arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) >RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) @@ -333,7 +333,7 @@ function f12(x: T): T extends Dog ? number : string { >isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 108, 1)) >x : Symbol(x, Decl(dependentReturnType1.ts, 113, 31)) - return doggy(x); // Ok + return doggy(x); // The narrowed conditional return type has deferred resolution, so this doesn't work. >doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 111, 44)) >x : Symbol(x, Decl(dependentReturnType1.ts, 113, 31)) } diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 51d146b478e59..2c7096c39fc6f 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -265,13 +265,13 @@ function conditionalProducingIfcond : (arg: LeftIn | RightIn) => arg is LeftIn >arg : Arg - return produceLeftOut(arg); // Ok + return produceLeftOut(arg); // The narrowed conditional return type has deferred resolution, so this doesn't work. >produceLeftOut(arg) : LeftOut >produceLeftOut : (arg: LeftIn) => LeftOut >arg : Arg & LeftIn } else { - return produceRightOut(arg as RightIn); // Error: Doesn't work because we don't narrow `arg` to `Arg & RightIn` here + return produceRightOut(arg as RightIn); // Error: Doesn't work because we can't narrow `arg` to `Arg & RightIn` here >produceRightOut(arg as RightIn) : RightOut >produceRightOut : (arg: RightIn) => RightOut >arg as RightIn : RightIn @@ -307,7 +307,7 @@ function f12(x: T): T extends Dog ? number : string { >isDog : (x: Animal) => x is Dog >x : T - return doggy(x); // Ok + return doggy(x); // The narrowed conditional return type has deferred resolution, so this doesn't work. >doggy(x) : number >doggy : (x: Dog) => number >x : T & Dog diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index 5aa24c4c2ca89..33c042a22af55 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -98,9 +98,9 @@ function conditionalProducingIf(x: T): T extends Dog ? number : string { if (isDog(x)) { // `x` has type `T & Dog` here - return doggy(x); // Ok + return doggy(x); // The narrowed conditional return type has deferred resolution, so this doesn't work. } return ""; // Error: Should not work because we can't express "not a Dog" in the type system } From c15eca8e81d84b81baff11fcb2dccbc84d97f533 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 16 Jul 2024 12:04:15 -0700 Subject: [PATCH 64/90] fix tests --- src/compiler/checker.ts | 4 +- .../reference/dependentReturnType1.types | 739 +++++++++++++++++- .../reference/dependentReturnType2.types | 22 + .../reference/dependentReturnType3.types | 382 ++++++++- .../reference/dependentReturnType4.types | 124 ++- .../reference/dependentReturnType5.types | 115 ++- 6 files changed, 1330 insertions(+), 56 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4a1d74afdbdba..a0e32807a415e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45707,8 +45707,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!node) return; node = skipParentheses(node); if (isConditionalExpression(node)) { - node.flowNodeWhenTrue && flowNodes.push(node.flowNodeWhenTrue); - node.flowNodeWhenFalse && flowNodes.push(node.flowNodeWhenFalse); + if (node.flowNodeWhenTrue) flowNodes.push(node.flowNodeWhenTrue); + if (node.flowNodeWhenFalse) flowNodes.push(node.flowNodeWhenFalse); visitConditionalReturnExpression(node.whenTrue); visitConditionalReturnExpression(node.whenFalse); } diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 2c7096c39fc6f..b8ac9cfc84a8a 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -4,830 +4,1271 @@ interface A { 1: number; >1 : number +> : ^^^^^^ 2: string; >2 : string +> : ^^^^^^ } function f1(x: T): A[T] { >f1 : (x: T) => A[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (x === 1) { >x === 1 : boolean +> : ^^^^^^^ >x : T +> : ^ >1 : 1 +> : ^ return 0; // Ok >0 : 0 +> : ^ } else { return 1; // Error >1 : 1 +> : ^ } } interface C { 1: number; >1 : number +> : ^^^^^^ 2: string; >2 : string +> : ^^^^^^ 3: boolean; >3 : boolean +> : ^^^^^^^ } function f2(x: T): C[T] { >f2 : (x: T) => C[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (x === 1) { >x === 1 : boolean +> : ^^^^^^^ >x : T +> : ^ >1 : 1 +> : ^ return 0; // Ok >0 : 0 +> : ^ } else { return ""; // Error, returned expression needs to have type string & boolean (= never) >"" : "" +> : ^^ } } function f3(x: T): T extends 1 ? number : T extends 2 ? string : T extends 3 ? boolean : never { >f3 : (x: T) => T extends 1 ? number : T extends 2 ? string : T extends 3 ? boolean : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (x === 1) { >x === 1 : boolean +> : ^^^^^^^ >x : T +> : ^ >1 : 1 +> : ^ return 0; // Ok >0 : 0 +> : ^ } else { return ""; // Error, returned expression needs to have type string & boolean (= never) >"" : "" +> : ^^ } } interface One { a: "a"; >a : "a" +> : ^^^ b: "b"; >b : "b" +> : ^^^ c: "c"; >c : "c" +> : ^^^ d: "d"; >d : "d" +> : ^^^ } interface Two { a: "a"; >a : "a" +> : ^^^ b: "b"; >b : "b" +> : ^^^ e: "e"; >e : "e" +> : ^^^ f: "f"; >f : "f" +> : ^^^ } interface Three { a: "a"; >a : "a" +> : ^^^ c: "c"; >c : "c" +> : ^^^ e: "e"; >e : "e" +> : ^^^ g: "g"; >g : "g" +> : ^^^ } interface Four { a: "a"; >a : "a" +> : ^^^ d: "d"; >d : "d" +> : ^^^ f: "f"; >f : "f" +> : ^^^ g: "g"; >g : "g" +> : ^^^ } function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { // Badly written conditional >f10 : (x: T) => T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (x === 1 || x === 2) { >x === 1 || x === 2 : boolean +> : ^^^^^^^ >x === 1 : boolean +> : ^^^^^^^ >x : T +> : ^ >1 : 1 +> : ^ >x === 2 : boolean +> : ^^^^^^^ >x : T +> : ^ >2 : 2 +> : ^ return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok >{ a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" } : { a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >a : "a" +> : ^^^ >"a" : "a" +> : ^^^ >b : "b" +> : ^^^ >"b" : "b" +> : ^^^ >c : "c" +> : ^^^ >"c" : "c" +> : ^^^ >d : "d" +> : ^^^ >"d" : "d" +> : ^^^ >e : "e" +> : ^^^ >"e" : "e" +> : ^^^ >f : "f" +> : ^^^ >"f" : "f" +> : ^^^ return { a: "a" }; // Error >{ a: "a" } : { a: "a"; } +> : ^^^^^^^^^^^ >a : "a" +> : ^^^ >"a" : "a" +> : ^^^ } // Excess property becomes a problem with the change, // because we now check assignability to a narrower type... return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error >{ a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" } : { a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >a : "a" +> : ^^^ >"a" : "a" +> : ^^^ >b : "b" +> : ^^^ >"b" : "b" +> : ^^^ >c : "c" +> : ^^^ >"c" : "c" +> : ^^^ >d : "d" +> : ^^^ >"d" : "d" +> : ^^^ >e : "e" +> : ^^^ >"e" : "e" +> : ^^^ >f : "f" +> : ^^^ >"f" : "f" +> : ^^^ >g : "g" +> : ^^^ >"g" : "g" +> : ^^^ } function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four { // Well written conditional >f101 : (x: T) => T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (x === 1 || x === 2) { >x === 1 || x === 2 : boolean +> : ^^^^^^^ >x === 1 : boolean +> : ^^^^^^^ >x : T +> : ^ >1 : 1 +> : ^ >x === 2 : boolean +> : ^^^^^^^ >x : T +> : ^ >2 : 2 +> : ^ return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok >{ a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" } : { a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >a : "a" +> : ^^^ >"a" : "a" +> : ^^^ >b : "b" +> : ^^^ >"b" : "b" +> : ^^^ >c : "c" +> : ^^^ >"c" : "c" +> : ^^^ >d : "d" +> : ^^^ >"d" : "d" +> : ^^^ >e : "e" +> : ^^^ >"e" : "e" +> : ^^^ >f : "f" +> : ^^^ >"f" : "f" +> : ^^^ return { a: "a" }; // Error >{ a: "a" } : { a: "a"; } +> : ^^^^^^^^^^^ >a : "a" +> : ^^^ >"a" : "a" +> : ^^^ } // Excess property becomes a problem with the change, // because we now check assignability to a narrower type... return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error >{ a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" } : { a: "a"; b: string; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >a : "a" +> : ^^^ >"a" : "a" +> : ^^^ >b : string +> : ^^^^^^ >"b" : "b" +> : ^^^ >c : "c" +> : ^^^ >"c" : "c" +> : ^^^ >d : "d" +> : ^^^ >"d" : "d" +> : ^^^ >e : "e" +> : ^^^ >"e" : "e" +> : ^^^ >f : "f" +> : ^^^ >"f" : "f" +> : ^^^ >g : "g" +> : ^^^ >"g" : "g" +> : ^^^ } // Asymmetry function conditionalProducingIf( >conditionalProducingIf : (arg: Arg, cond: (arg: LeftIn | RightIn) => arg is LeftIn, produceLeftOut: (arg: LeftIn) => LeftOut, produceRightOut: (arg: RightIn) => RightOut) => Arg extends LeftIn ? LeftOut : RightOut +> : ^ ^^ ^^ ^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ arg: Arg, >arg : Arg +> : ^^^ cond: (arg: LeftIn | RightIn) => arg is LeftIn, >cond : (arg: LeftIn | RightIn) => arg is LeftIn +> : ^ ^^ ^^^^^ >arg : LeftIn | RightIn +> : ^^^^^^^^^^^^^^^^ produceLeftOut: (arg: LeftIn) => LeftOut, >produceLeftOut : (arg: LeftIn) => LeftOut +> : ^ ^^ ^^^^^ >arg : LeftIn +> : ^^^^^^ produceRightOut: (arg: RightIn) => RightOut): >produceRightOut : (arg: RightIn) => RightOut +> : ^ ^^ ^^^^^ >arg : RightIn +> : ^^^^^^^ Arg extends LeftIn ? LeftOut : RightOut { type OK = Arg extends LeftIn ? LeftOut : RightOut; >OK : Arg extends LeftIn ? LeftOut : RightOut +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ if (cond(arg)) { >cond(arg) : boolean +> : ^^^^^^^ >cond : (arg: LeftIn | RightIn) => arg is LeftIn +> : ^ ^^ ^^^^^ >arg : Arg +> : ^^^ return produceLeftOut(arg); // The narrowed conditional return type has deferred resolution, so this doesn't work. >produceLeftOut(arg) : LeftOut +> : ^^^^^^^ >produceLeftOut : (arg: LeftIn) => LeftOut +> : ^ ^^ ^^^^^ >arg : Arg & LeftIn +> : ^^^^^^^^^^^^ } else { return produceRightOut(arg as RightIn); // Error: Doesn't work because we can't narrow `arg` to `Arg & RightIn` here >produceRightOut(arg as RightIn) : RightOut +> : ^^^^^^^^ >produceRightOut : (arg: RightIn) => RightOut +> : ^ ^^ ^^^^^ >arg as RightIn : RightIn +> : ^^^^^^^ >arg : Arg +> : ^^^ } } interface Animal { name: string; >name : string +> : ^^^^^^ } interface Dog extends Animal { bark: () => string; >bark : () => string +> : ^^^^^^ } // This is unsafe declare function isDog(x: Animal): x is Dog; >isDog : (x: Animal) => x is Dog +> : ^ ^^ ^^^^^ >x : Animal +> : ^^^^^^ declare function doggy(x: Dog): number; >doggy : (x: Dog) => number +> : ^ ^^ ^^^^^ >x : Dog +> : ^^^ function f12(x: T): T extends Dog ? number : string { >f12 : (x: T) => T extends Dog ? number : string +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (isDog(x)) { // `x` has type `T & Dog` here >isDog(x) : boolean +> : ^^^^^^^ >isDog : (x: Animal) => x is Dog +> : ^ ^^ ^^^^^ >x : T +> : ^ return doggy(x); // The narrowed conditional return type has deferred resolution, so this doesn't work. >doggy(x) : number +> : ^^^^^^ >doggy : (x: Dog) => number +> : ^ ^^ ^^^^^ >x : T & Dog +> : ^^^^^^^ } return ""; // Error: Should not work because we can't express "not a Dog" in the type system >"" : "" +> : ^^ } // Cannot narrow `keyof` too eagerly or something like the below breaks function f(entry: EntryId): Entry[EntryId] { >f : (entry: EntryId) => Entry[EntryId] +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^^^^ >index : string +> : ^^^^^^ >entry : EntryId +> : ^^^^^^^ const entries = {} as Entry; >entries : Entry +> : ^^^^^ >{} as Entry : Entry +> : ^^^^^ >{} : {} +> : ^^ return entries[entry]; >entries[entry] : Entry[EntryId] +> : ^^^^^^^^^^^^^^ >entries : Entry +> : ^^^^^ >entry : EntryId +> : ^^^^^^^ } // Works the same as before declare function takeA(val: 'A'): void; ->takeA : (val: 'A') => void +>takeA : (val: "A") => void +> : ^ ^^ ^^^^^ >val : "A" +> : ^^^ export function bounceAndTakeIfA(value: AB): AB { >bounceAndTakeIfA : (value: AB) => AB +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >value : AB +> : ^^ if (value === 'A') { >value === 'A' : boolean +> : ^^^^^^^ >value : AB +> : ^^ >'A' : "A" +> : ^^^ takeA(value); >takeA(value) : void +> : ^^^^ >takeA : (val: "A") => void +> : ^ ^^ ^^^^^ >value : "A" +> : ^^^ takeAB(value); >takeAB(value) : void +> : ^^^^ >takeAB : (val: AB) => void +> : ^ ^^ ^^^^^ >value : AB +> : ^^ return value; >value : AB +> : ^^ } return value; >value : AB +> : ^^ function takeAB(val: AB): void {} >takeAB : (val: AB) => void +> : ^ ^^ ^^^^^ >val : AB +> : ^^ } // Works the same as before export function bbb(value: AB): "a" { >bbb : (value: AB) => "a" +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >value : AB +> : ^^ if (value === "a") { >value === "a" : boolean +> : ^^^^^^^ >value : AB +> : ^^ >"a" : "a" +> : ^^^ return value; >value : "a" +> : ^^^ } return "a"; >"a" : "a" +> : ^^^ } class Unnamed { >Unnamed : Unnamed +> : ^^^^^^^ root!: { name: string }; >root : { name: string; } +> : ^^^^^^^^ ^^^ >name : string +> : ^^^^^^ // Error because parameter is optional name(name?: T): T extends string ? this : string { >name : (name?: T) => T extends string ? this : string +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ >name : T | undefined +> : ^^^^^^^^^^^^^ if (typeof name === 'undefined') { >typeof name === 'undefined' : boolean +> : ^^^^^^^ >typeof name : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >name : T | undefined +> : ^^^^^^^^^^^^^ >'undefined' : "undefined" +> : ^^^^^^^^^^^ return this.root.name; >this.root.name : string +> : ^^^^^^ >this.root : { name: string; } +> : ^^^^^^^^ ^^^ >this : this +> : ^^^^ >root : { name: string; } +> : ^^^^^^^^ ^^^ >name : string +> : ^^^^^^ } return this; >this : this +> : ^^^^ } // Error because parameter is optional? nameWithError(name?: T): T extends string ? this : string { >nameWithError : (name?: T) => T extends string ? this : string +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ >name : T | undefined +> : ^^^^^^^^^^^^^ return this; // Error: Investigate error message >this : this +> : ^^^^ } // Good conditional name2(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { >name2 : (name?: T) => T extends string ? this : T extends undefined ? string : this | undefined +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ >name : T | undefined +> : ^^^^^^^^^^^^^ if (typeof name === 'undefined') { >typeof name === 'undefined' : boolean +> : ^^^^^^^ >typeof name : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >name : T | undefined +> : ^^^^^^^^^^^^^ >'undefined' : "undefined" +> : ^^^^^^^^^^^ return this.root.name; // Ok >this.root.name : string +> : ^^^^^^ >this.root : { name: string; } +> : ^^^^^^^^ ^^^ >this : this +> : ^^^^ >root : { name: string; } +> : ^^^^^^^^ ^^^ >name : string +> : ^^^^^^ } this.root.name = name; >this.root.name = name : string +> : ^^^^^^ >this.root.name : string +> : ^^^^^^ >this.root : { name: string; } +> : ^^^^^^^^ ^^^ >this : this +> : ^^^^ >root : { name: string; } +> : ^^^^^^^^ ^^^ >name : string +> : ^^^^^^ >name : string +> : ^^^^^^ return this; // Ok >this : this +> : ^^^^ } // Good conditional, wrong return expressions name3(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { >name3 : (name?: T) => T extends string ? this : T extends undefined ? string : this | undefined +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ >name : T | undefined +> : ^^^^^^^^^^^^^ if (typeof name === 'undefined') { >typeof name === 'undefined' : boolean +> : ^^^^^^^ >typeof name : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >name : T | undefined +> : ^^^^^^^^^^^^^ >'undefined' : "undefined" +> : ^^^^^^^^^^^ return this; // Error >this : this +> : ^^^^ } this.root.name = name; >this.root.name = name : string +> : ^^^^^^ >this.root.name : string +> : ^^^^^^ >this.root : { name: string; } +> : ^^^^^^^^ ^^^ >this : this +> : ^^^^ >root : { name: string; } +> : ^^^^^^^^ ^^^ >name : string +> : ^^^^^^ >name : string +> : ^^^^^^ return name; // Error >name : T & {} +> : ^^^^^^ } } interface Aa { 1: number; >1 : number +> : ^^^^^^ 2: string; >2 : string +> : ^^^^^^ 3: string; >3 : string +> : ^^^^^^ } function trivialConditional(x: T): Aa[T] { >trivialConditional : (x: T) => Aa[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (x !== 1) { >x !== 1 : boolean +> : ^^^^^^^ >x : T +> : ^ >1 : 1 +> : ^ return x === 2 ? "" : `${x}`; >x === 2 ? "" : `${x}` : string +> : ^^^^^^ >x === 2 : boolean +> : ^^^^^^^ >x : T +> : ^ >2 : 2 +> : ^ >"" : "" +> : ^^ >`${x}` : string +> : ^^^^^^ >x : T +> : ^ } else { return 0; >0 : 0 +> : ^ } } // Conditional expressions function conditional(x: T): >conditional : (x: T) => T extends true ? 1 : T extends false ? 2 : 1 | 2 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ T extends true ? 1 : T extends false ? 2 : 1 | 2 { >true : true +> : ^^^^ >false : false +> : ^^^^^ return x ? 1 : 2; // Ok >x ? 1 : 2 : 1 | 2 +> : ^^^^^ >x : T +> : ^ >1 : 1 +> : ^ >2 : 2 +> : ^ } function contextualConditional(x: T): T extends "a" ? "a" : T extends "b" ? number : "a" | number { >contextualConditional : (x: T) => T extends "a" ? "a" : T extends "b" ? number : "a" | number +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ return x === "a" ? x : parseInt(x); // Ok >x === "a" ? x : parseInt(x) : number | "a" +> : ^^^^^^^^^^^^ >x === "a" : boolean +> : ^^^^^^^ >x : T +> : ^ >"a" : "a" +> : ^^^ >x : "a" +> : ^^^ >parseInt(x) : number ->parseInt : (string: string, radix?: number | undefined) => number +> : ^^^^^^ +>parseInt : (string: string, radix?: number) => number +> : ^ ^^ ^^ ^^^ ^^^^^ >x : "b" +> : ^^^ } function conditionalWithError(x: T): T extends "a" ? number : T extends "b" ? string : number | string { >conditionalWithError : (x: T) => T extends "a" ? number : T extends "b" ? string : number | string +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ return x === "a" ? x : parseInt(x); // Error >x === "a" ? x : parseInt(x) : number | "a" +> : ^^^^^^^^^^^^ >x === "a" : boolean +> : ^^^^^^^ >x : T +> : ^ >"a" : "a" +> : ^^^ >x : "a" +> : ^^^ >parseInt(x) : number ->parseInt : (string: string, radix?: number | undefined) => number +> : ^^^^^^ +>parseInt : (string: string, radix?: number) => number +> : ^ ^^ ^^ ^^^ ^^^^^ >x : "b" +> : ^^^ } // Multiple reductions interface BB { "a": number; >"a" : number +> : ^^^^^^ [y: number]: string; >y : number +> : ^^^^^^ } interface AA { "c": BB[T]; >"c" : BB[T] +> : ^^^^^ "d": boolean, >"d" : boolean +> : ^^^^^^^ } function reduction(x: T, y: U): AA[U] { >reduction : (x: T, y: U) => AA[U] +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ >x : T +> : ^ >y : U +> : ^ if (y === "c" && x === "a") { >y === "c" && x === "a" : boolean +> : ^^^^^^^ >y === "c" : boolean +> : ^^^^^^^ >y : U +> : ^ >"c" : "c" +> : ^^^ >x === "a" : boolean +> : ^^^^^^^ >x : T +> : ^ >"a" : "a" +> : ^^^ // AA[U='c'] -> BB[T] // BB[T='a'] -> number return 0; // Ok >0 : 0 +> : ^ } return undefined as never; >undefined as never : never +> : ^^^^^ >undefined : undefined +> : ^^^^^^^^^ } // Substitution types are not narrowed? function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { >subsCond : (x: T) => T extends 1 | 2 ? (T extends 1 ? string : boolean) : number +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (x === 1) { >x === 1 : boolean +> : ^^^^^^^ >x : T +> : ^ >1 : 1 +> : ^ return ""; >"" : "" +> : ^^ } } // Unsafe: supertype problem declare function q(x: object): x is { b: number }; >q : (x: object) => x is { b: number; } +> : ^ ^^ ^^^^^ >x : object +> : ^^^^^^ >b : number +> : ^^^^^^ function foo(x: T): T extends { a: string } ? number : (string | number) { ->foo : (x: T) => T extends { a: string;} ? number : (string | number) +>foo : (x: T) => T extends { a: string; } ? number : (string | number) +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >a : string +> : ^^^^^^ >b : number +> : ^^^^^^ >x : T +> : ^ >a : string +> : ^^^^^^ if (q(x)) { >q(x) : boolean +> : ^^^^^^^ >q : (x: object) => x is { b: number; } +> : ^ ^^ ^^^^^ >x : { a: string; } | { b: number; } +> : ^^^^^ ^^^^^^^^^^^ ^^^ x.b; >x.b : number +> : ^^^^^^ >x : { b: number; } +> : ^^^^^ ^^^ >b : number +> : ^^^^^^ return ""; >"" : "" +> : ^^ } } let y = { a: "", b: 1 } >y : { a: string; b: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ >{ a: "", b: 1 } : { a: string; b: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ >a : string +> : ^^^^^^ >"" : "" +> : ^^ >b : number +> : ^^^^^^ >1 : 1 +> : ^ const r = foo<{ a: string }>(y); // number >r : number +> : ^^^^^^ >foo<{ a: string }>(y) : number ->foo : (x: T) => T extends { a: string; } ? number : string | number +> : ^^^^^^ +>foo : (x: T) => T extends { a: string; } ? number : (string | number) +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >a : string +> : ^^^^^^ >y : { a: string; b: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ function lessBadFoo(x: T): T extends { b: number } ? string : T extends { a: string } ? number : (string | number) { ->lessBadFoo : (x: T) => T extends { b: number;} ? string : T extends { a: string;} ? number : (string | number) +>lessBadFoo : (x: T) => T extends { b: number; } ? string : T extends { a: string; } ? number : (string | number) +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >a : string +> : ^^^^^^ >b : number +> : ^^^^^^ >x : T +> : ^ >b : number +> : ^^^^^^ >a : string +> : ^^^^^^ if (q(x)) { >q(x) : boolean +> : ^^^^^^^ >q : (x: object) => x is { b: number; } +> : ^ ^^ ^^^^^ >x : { a: string; } | { b: number; } +> : ^^^^^ ^^^^^^^^^^^ ^^^ x.b; >x.b : number +> : ^^^^^^ >x : { b: number; } +> : ^^^^^ ^^^ >b : number +> : ^^^^^^ return ""; >"" : "" +> : ^^ } return 2; >2 : 2 +> : ^ } const r2 = lessBadFoo<{ a: string }>(y); // number, bad >r2 : number +> : ^^^^^^ >lessBadFoo<{ a: string }>(y) : number ->lessBadFoo : (x: T) => T extends { b: number; } ? string : T extends { a: string; } ? number : string | number +> : ^^^^^^ +>lessBadFoo : (x: T) => T extends { b: number; } ? string : T extends { a: string; } ? number : (string | number) +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >a : string +> : ^^^^^^ >y : { a: string; b: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; >HelperCond : HelperCond +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ // We don't narrow the return type because the conditionals are not distributive function foo2(x: U, y: V): >foo2 : (x: U, y: V) => HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2> +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ >x : U +> : ^ >y : V +> : ^ HelperCond<{ x: U, y: V }, >x : U +> : ^ >y : V +> : ^ { x: string, y: true }, 1, >x : string +> : ^^^^^^ >y : true +> : ^^^^ >true : true +> : ^^^^ { x: number, y: false }, 2> { >x : number +> : ^^^^^^ >y : false +> : ^^^^^ >false : false +> : ^^^^^ if (typeof x === "string" && y === true) { >typeof x === "string" && y === true : boolean +> : ^^^^^^^ >typeof x === "string" : boolean +> : ^^^^^^^ >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : U +> : ^ >"string" : "string" +> : ^^^^^^^^ >y === true : boolean +> : ^^^^^^^ >y : V +> : ^ >true : true +> : ^^^^ return 1; // Error >1 : 1 +> : ^ } if (typeof x === "number" && y === false) { >typeof x === "number" && y === false : boolean +> : ^^^^^^^ >typeof x === "number" : boolean +> : ^^^^^^^ >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : U +> : ^ >"number" : "number" +> : ^^^^^^^^ >y === false : boolean +> : ^^^^^^^ >y : V +> : ^ >false : false +> : ^^^^^ return 2; // Error >2 : 2 +> : ^ } return 0; // Error >0 : 0 +> : ^ } // From https://github.com/microsoft/TypeScript/issues/24929#issue-332087943 declare function isString(s: unknown): s is string; >isString : (s: unknown) => s is string +> : ^ ^^ ^^^^^ >s : unknown +> : ^^^^^^^ // capitalize a string or each element of an array of strings function capitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { >capitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : string[] | string +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >input : T +> : ^ if (isString(input)) { >isString(input) : boolean +> : ^^^^^^^ >isString : (s: unknown) => s is string +> : ^ ^^ ^^^^^ >input : string | string[] +> : ^^^^^^^^^^^^^^^^^ return input[0].toUpperCase() + input.slice(1); // Ok >input[0].toUpperCase() + input.slice(1) : string +> : ^^^^^^ >input[0].toUpperCase() : string +> : ^^^^^^ >input[0].toUpperCase : () => string +> : ^^^^^^ >input[0] : string +> : ^^^^^^ >input : string +> : ^^^^^^ >0 : 0 +> : ^ >toUpperCase : () => string +> : ^^^^^^ >input.slice(1) : string ->input.slice : (start?: number | undefined, end?: number | undefined) => string +> : ^^^^^^ +>input.slice : (start?: number, end?: number) => string +> : ^ ^^^ ^^ ^^^ ^^^^^ >input : string ->slice : (start?: number | undefined, end?: number | undefined) => string +> : ^^^^^^ +>slice : (start?: number, end?: number) => string +> : ^ ^^^ ^^ ^^^ ^^^^^ >1 : 1 +> : ^ } else { return input.map(elt => capitalize(elt)); // Ok >input.map(elt => capitalize(elt)) : string[] +> : ^^^^^^^^ >input.map : (callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[] +> : ^ ^^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^ >input : string[] +> : ^^^^^^^^ >map : (callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[] +> : ^ ^^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^ >elt => capitalize(elt) : (elt: string) => string +> : ^ ^^^^^^^^^^^^^^^^^^^ >elt : string +> : ^^^^^^ >capitalize(elt) : string ->capitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : string | string[] +> : ^^^^^^ +>capitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : string[] | string +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >elt : string +> : ^^^^^^ } } function badCapitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { >badCapitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : string[] | string +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >input : T +> : ^ if (isString(input)) { >isString(input) : boolean +> : ^^^^^^^ >isString : (s: unknown) => s is string +> : ^ ^^ ^^^^^ >input : string | string[] +> : ^^^^^^^^^^^^^^^^^ return input[0].toUpperCase() + input.slice(1); // Ok >input[0].toUpperCase() + input.slice(1) : string +> : ^^^^^^ >input[0].toUpperCase() : string +> : ^^^^^^ >input[0].toUpperCase : () => string +> : ^^^^^^ >input[0] : string +> : ^^^^^^ >input : string +> : ^^^^^^ >0 : 0 +> : ^ >toUpperCase : () => string +> : ^^^^^^ >input.slice(1) : string ->input.slice : (start?: number | undefined, end?: number | undefined) => string +> : ^^^^^^ +>input.slice : (start?: number, end?: number) => string +> : ^ ^^^ ^^ ^^^ ^^^^^ >input : string ->slice : (start?: number | undefined, end?: number | undefined) => string +> : ^^^^^^ +>slice : (start?: number, end?: number) => string +> : ^ ^^^ ^^ ^^^ ^^^^^ >1 : 1 +> : ^ } else { return input[0].toUpperCase() + input.slice(1); // Bad >input[0].toUpperCase() + input.slice(1) : string +> : ^^^^^^ >input[0].toUpperCase() : string +> : ^^^^^^ >input[0].toUpperCase : () => string +> : ^^^^^^ >input[0] : string +> : ^^^^^^ >input : string[] +> : ^^^^^^^^ >0 : 0 +> : ^ >toUpperCase : () => string +> : ^^^^^^ >input.slice(1) : string[] ->input.slice : (start?: number | undefined, end?: number | undefined) => string[] +> : ^^^^^^^^ +>input.slice : (start?: number, end?: number) => string[] +> : ^ ^^^ ^^ ^^^ ^^^^^^^^^^^^^ >input : string[] ->slice : (start?: number | undefined, end?: number | undefined) => string[] +> : ^^^^^^^^ +>slice : (start?: number, end?: number) => string[] +> : ^ ^^^ ^^ ^^^ ^^^^^^^^^^^^^ >1 : 1 +> : ^ } } @@ -835,47 +1276,70 @@ function badCapitalize(input: T): T extends string[ function voidRet(x: T): T extends {} ? void : T extends undefined ? number : void | number { >voidRet : (x: T) => T extends {} ? void : T extends undefined ? number : void | number +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >a : string +> : ^^^^^^ >x : T +> : ^ if (x) { >x : T +> : ^ return; // Ok } return 1; // Ok >1 : 1 +> : ^ } function woo(x: T, y: U): >woo : (x: T, y: U) => T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ >x : T +> : ^ >y : U +> : ^ T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 { if (typeof x === "number" && typeof y === "string") { >typeof x === "number" && typeof y === "string" : boolean +> : ^^^^^^^ >typeof x === "number" : boolean +> : ^^^^^^^ >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : T +> : ^ >"number" : "number" +> : ^^^^^^^^ >typeof y === "string" : boolean +> : ^^^^^^^ >typeof y : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >y : U +> : ^ >"string" : "string" +> : ^^^^^^^^ return 1; // Error >1 : 1 +> : ^ } return undefined as any; >undefined as any : any +> : ^^^ >undefined : undefined +> : ^^^^^^^^^ } function ttt(x: T, y: U): >ttt : (x: T, y: U) => T extends string ? number extends string ? 6 : U extends string ? 1 : 2 : U extends number ? 3 : 4 +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ >x : T +> : ^ >y : U +> : ^ T extends string ? number extends string @@ -888,412 +1352,639 @@ T extends string : 4 { if (typeof x === "string" && typeof y === "string") { >typeof x === "string" && typeof y === "string" : boolean +> : ^^^^^^^ >typeof x === "string" : boolean +> : ^^^^^^^ >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : T +> : ^ >"string" : "string" +> : ^^^^^^^^ >typeof y === "string" : boolean +> : ^^^^^^^ >typeof y : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >y : U +> : ^ >"string" : "string" +> : ^^^^^^^^ return 1; // Ok >1 : 1 +> : ^ } return undefined as any; >undefined as any : any +> : ^^^ >undefined : undefined +> : ^^^^^^^^^ } // We won't narrow `T` because it refers to the type of an optional parameter but doesn't allow for narrowing with `undefined` function opt(x?: T): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { >opt : (x?: T) => T extends string ? 1 : T extends undefined ? 2 : 1 | 2 +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ >x : T | undefined +> : ^^^^^^^^^^^^^ if (typeof x === "undefined") { >typeof x === "undefined" : boolean +> : ^^^^^^^ >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : T | undefined +> : ^^^^^^^^^^^^^ >"undefined" : "undefined" +> : ^^^^^^^^^^^ x; >x : undefined +> : ^^^^^^^^^ return 2; >2 : 2 +> : ^ } return 1; >1 : 1 +> : ^ } // Shadowing of the narrowed reference function g(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { >g : (x: T) => T extends 1 ? number : T extends 2 ? string : 1 | 2 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (true) { >true : true +> : ^^^^ let x: number = Math.random() ? 1 : 2; >x : number +> : ^^^^^^ >Math.random() ? 1 : 2 : 1 | 2 +> : ^^^^^ >Math.random() : number +> : ^^^^^^ >Math.random : () => number +> : ^^^^^^ >Math : Math +> : ^^^^ >random : () => number +> : ^^^^^^ >1 : 1 +> : ^ >2 : 2 +> : ^ if (x === 1) { >x === 1 : boolean +> : ^^^^^^^ >x : number +> : ^^^^^^ >1 : 1 +> : ^ return 1; // Error >1 : 1 +> : ^ } return ""; // Error >"" : "" +> : ^^ } } // If the narrowing reference is out of scope, we simply won't narrow its type declare let someX: boolean; >someX : boolean +> : ^^^^^^^ function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { ->scope2 : (opts: { a: T;}) => T extends true ? 1 : T extends false ? 2 : 1 | 2 +>scope2 : (opts: { a: T; }) => T extends true ? 1 : T extends false ? 2 : 1 | 2 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >opts : { a: T; } +> : ^^^^^ ^^^ >a : T +> : ^ >true : true +> : ^^^^ >false : false +> : ^^^^^ if ((true)) { >(true) : true +> : ^^^^ >true : true +> : ^^^^ const someX = opts.a; >someX : T +> : ^ >opts.a : T +> : ^ >opts : { a: T; } +> : ^^^^^ ^^^ >a : T +> : ^ if (someX) { // We narrow `someX` and the return type here >someX : T +> : ^ return 1; >1 : 1 +> : ^ } } if (!someX) { // This is a different `someX`, so we don't narrow here >!someX : boolean +> : ^^^^^^^ >someX : boolean +> : ^^^^^^^ return 2; >2 : 2 +> : ^ } } function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { >h : (x: T) => T extends 1 ? number : T extends 2 ? string : 1 | 2 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (x === 2) { >x === 2 : boolean +> : ^^^^^^^ >x : T +> : ^ >2 : 2 +> : ^ let x: number = Math.random() ? 1 : 2; >x : number +> : ^^^^^^ >Math.random() ? 1 : 2 : 1 | 2 +> : ^^^^^ >Math.random() : number +> : ^^^^^^ >Math.random : () => number +> : ^^^^^^ >Math : Math +> : ^^^^ >random : () => number +> : ^^^^^^ >1 : 1 +> : ^ >2 : 2 +> : ^ if (x === 1) { >x === 1 : boolean +> : ^^^^^^^ >x : number +> : ^^^^^^ >1 : 1 +> : ^ return 1; // Error >1 : 1 +> : ^ } return ""; // Ok >"" : "" +> : ^^ } return 0; // Ok >0 : 0 +> : ^ } function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : string | boolean { ->withInfer : (x: T) => T extends [infer R] ? R : T extends number ? boolean : string | boolean +>withInfer : (x: T) => T extends [infer R] ? R : T extends number ? boolean : string | boolean +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (typeof x === "number") { >typeof x === "number" : boolean +> : ^^^^^^^ >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : T +> : ^ >"number" : "number" +> : ^^^^^^^^ return true; >true : true +> : ^^^^ } return ""; >"" : "" +> : ^^ } const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. >withInferResult : "a" +> : ^^^ >withInfer(["a"] as const) : "a" ->withInfer : (x: T) => T extends [infer R] ? R : T extends number ? boolean : string | boolean +> : ^^^ +>withInfer : (x: T) => T extends [infer R] ? R : T extends number ? boolean : string | boolean +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >["a"] as const : ["a"] +> : ^^^^^ >["a"] : ["a"] +> : ^^^^^ >"a" : "a" +> : ^^^ // Ok async function abool(x: T): Promise { ->abool : (x: T) => Promise +>abool : (x: T) => Promise +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >true : true +> : ^^^^ >false : false +> : ^^^^^ >x : T +> : ^ >true : true +> : ^^^^ >false : false +> : ^^^^^ if (x) { >x : T +> : ^ return 1; >1 : 1 +> : ^ } return 2; >2 : 2 +> : ^ } // Ok function* bbool(x: T): Generator { ->bbool : (x: T) => Generator +>bbool : (x: T) => Generator +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >true : true +> : ^^^^ >false : false +> : ^^^^^ >x : T +> : ^ >true : true +> : ^^^^ >false : false +> : ^^^^^ yield 3; >yield 3 : unknown +> : ^^^^^^^ >3 : 3 +> : ^ if (x) { >x : T +> : ^ return 1; >1 : 1 +> : ^ } return 2; >2 : 2 +> : ^ } // We don't do the same type of narrowing for `yield` statements function* cbool(x: T): Generator { ->cbool : (x: T) => Generator +>cbool : (x: T) => Generator +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >true : true +> : ^^^^ >false : false +> : ^^^^^ >x : T +> : ^ >true : true +> : ^^^^ >false : false +> : ^^^^^ if (x) { >x : T +> : ^ yield 1; >yield 1 : unknown +> : ^^^^^^^ >1 : 1 +> : ^ } yield 2; >yield 2 : unknown +> : ^^^^^^^ >2 : 2 +> : ^ return 0; >0 : 0 +> : ^ } // From #33912 abstract class Operation { >Operation : Operation +> : ^^^^^^^^^^^^^^^ abstract perform(t: T): R; >perform : (t: T) => R +> : ^ ^^ ^^^^^ >t : T +> : ^ } type ConditionalReturnType | undefined> = >ConditionalReturnType : ConditionalReturnType +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ EOp extends Operation ? R : EOp extends undefined ? T | R : T | R; class ConditionalOperation | undefined> extends Operation> { >ConditionalOperation : ConditionalOperation +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >Operation : Operation> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructor( private predicate: (value: T) => boolean, >predicate : (value: T) => boolean +> : ^ ^^ ^^^^^ >value : T +> : ^ private thenOp: Operation, >thenOp : Operation +> : ^^^^^^^^^^^^^^^ private elseOp?: EOp >elseOp : EOp | undefined +> : ^^^^^^^^^^^^^^^ ) { super(); >super() : void +> : ^^^^ >super : typeof Operation +> : ^^^^^^^^^^^^^^^^ } perform(t: T): ConditionalReturnType { >perform : (t: T) => ConditionalReturnType +> : ^ ^^ ^^^^^ >t : T +> : ^ if (this.predicate(t)) { >this.predicate(t) : boolean +> : ^^^^^^^ >this.predicate : (value: T) => boolean +> : ^ ^^ ^^^^^ >this : this +> : ^^^^ >predicate : (value: T) => boolean +> : ^ ^^ ^^^^^ >t : T +> : ^ return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it >this.thenOp.perform(t) : R +> : ^ >this.thenOp.perform : (t: T) => R +> : ^ ^^^^^^^^^ >this.thenOp : Operation +> : ^^^^^^^^^^^^^^^ >this : this +> : ^^^^ >thenOp : Operation +> : ^^^^^^^^^^^^^^^ >perform : (t: T) => R +> : ^ ^^^^^^^^^ >t : T +> : ^ } else if (typeof this.elseOp !== 'undefined') { >typeof this.elseOp !== 'undefined' : boolean +> : ^^^^^^^ >typeof this.elseOp : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >this.elseOp : EOp | undefined +> : ^^^^^^^^^^^^^^^ >this : this +> : ^^^^ >elseOp : EOp | undefined +> : ^^^^^^^^^^^^^^^ >'undefined' : "undefined" +> : ^^^^^^^^^^^ return this.elseOp.perform(t); // Ok >this.elseOp.perform(t) : R +> : ^ >this.elseOp.perform : (t: T) => R +> : ^ ^^^^^^^^^ >this.elseOp : Operation +> : ^^^^^^^^^^^^^^^ >this : this +> : ^^^^ >elseOp : Operation +> : ^^^^^^^^^^^^^^^ >perform : (t: T) => R +> : ^ ^^^^^^^^^ >t : T +> : ^ } else { return t; // Ok >t : T +> : ^ } } } // Optional tuple element function tupl(x: [string, some?: T]): ->tupl : (x: [string, some?: T]) => T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 +>tupl : (x: [string, some?: T]) => T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >true : true +> : ^^^^ >false : false +> : ^^^^^ >x : [string, some?: T | undefined] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 { >true : true +> : ^^^^ >false : false +> : ^^^^^ if (x[1]) { >x[1] : T | undefined +> : ^^^^^^^^^^^^^ >x : [string, some?: T | undefined] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >1 : 1 +> : ^ return 1; >1 : 1 +> : ^ } return 2; >2 : 2 +> : ^ } // Return conditional expressions with parentheses function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { ->returnStuff1 : (opts: { x: T;}) => T extends true ? 1 : T extends false ? 2 : 1 | 2 +>returnStuff1 : (opts: { x: T; }) => T extends true ? 1 : T extends false ? 2 : 1 | 2 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >opts : { x: T; } +> : ^^^^^ ^^^ >x : T +> : ^ >true : true +> : ^^^^ >false : false +> : ^^^^^ return (opts.x ? (1) : 2); >(opts.x ? (1) : 2) : 1 | 2 +> : ^^^^^ >opts.x ? (1) : 2 : 1 | 2 +> : ^^^^^ >opts.x : T +> : ^ >opts : { x: T; } +> : ^^^^^ ^^^ >x : T +> : ^ >(1) : 1 +> : ^ >1 : 1 +> : ^ >2 : 2 +> : ^ } function returnStuff2(opts: { x: T }): ->returnStuff2 : (opts: { x: T;}) => T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 +>returnStuff2 : (opts: { x: T; }) => T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >opts : { x: T; } +> : ^^^^^ ^^^ >x : T +> : ^ T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 { return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); >(typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")) : 0 | "one" | "two" +> : ^^^^^^^^^^^^^^^^^ >typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two") : 0 | "one" | "two" +> : ^^^^^^^^^^^^^^^^^ >typeof opts.x === "string" : boolean +> : ^^^^^^^ >typeof opts.x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >opts.x : T +> : ^ >opts : { x: T; } +> : ^^^^^ ^^^ >x : T +> : ^ >"string" : "string" +> : ^^^^^^^^ >0 : 0 +> : ^ >(opts.x === 1 ? ("one") : "two") : "one" | "two" +> : ^^^^^^^^^^^^^ >opts.x === 1 ? ("one") : "two" : "one" | "two" +> : ^^^^^^^^^^^^^ >opts.x === 1 : boolean +> : ^^^^^^^ >opts.x : T +> : ^ >opts : { x: T; } +> : ^^^^^ ^^^ >x : T +> : ^ >1 : 1 +> : ^ >("one") : "one" +> : ^^^^^ >"one" : "one" +> : ^^^^^ >"two" : "two" +> : ^^^^^ } // If the return type is written wrong, it still type checks function returnStuff3(opts: { x: T }): ->returnStuff3 : (opts: { x: T;}) => T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" +>returnStuff3 : (opts: { x: T; }) => T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >opts : { x: T; } +> : ^^^^^ ^^^ >x : T +> : ^ T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" { return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); >(typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")) : 0 | "one" | "two" +> : ^^^^^^^^^^^^^^^^^ >typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two") : 0 | "one" | "two" +> : ^^^^^^^^^^^^^^^^^ >typeof opts.x === "string" : boolean +> : ^^^^^^^ >typeof opts.x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >opts.x : T +> : ^ >opts : { x: T; } +> : ^^^^^ ^^^ >x : T +> : ^ >"string" : "string" +> : ^^^^^^^^ >0 : 0 +> : ^ >(opts.x === 1 ? ("one") : "two") : "one" | "two" +> : ^^^^^^^^^^^^^ >opts.x === 1 ? ("one") : "two" : "one" | "two" +> : ^^^^^^^^^^^^^ >opts.x === 1 : boolean +> : ^^^^^^^ >opts.x : T +> : ^ >opts : { x: T; } +> : ^^^^^ ^^^ >x : T +> : ^ >1 : 1 +> : ^ >("one") : "one" +> : ^^^^^ >"one" : "one" +> : ^^^^^ >"two" : "two" +> : ^^^^^ } diff --git a/tests/baselines/reference/dependentReturnType2.types b/tests/baselines/reference/dependentReturnType2.types index f498ff61be1b2..680fec5a5fcfa 100644 --- a/tests/baselines/reference/dependentReturnType2.types +++ b/tests/baselines/reference/dependentReturnType2.types @@ -4,44 +4,66 @@ // If during narrowing, one of the conditional types in the distribution doesn't narrow, then the whole type will not be narrowed function whoKnows(x: T): T extends true ? 1 : T extends false ? 2 : 3 { >whoKnows : (x: T) => T extends true ? 1 : T extends false ? 2 : 3 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ >true : true +> : ^^^^ >false : false +> : ^^^^^ if (typeof x !== "string") { >typeof x !== "string" : boolean +> : ^^^^^^^ >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : T +> : ^ >"string" : "string" +> : ^^^^^^^^ return 3; >3 : 3 +> : ^ } } // If the conditional type's input is `never`, then it resolves to `never`: function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : 1 | 2 { >neverOk : (x: T) => T extends true ? 1 : T extends false ? 2 : 1 | 2 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ >true : true +> : ^^^^ >false : false +> : ^^^^^ if (x === true) { >x === true : boolean +> : ^^^^^^^ >x : T +> : ^ >true : true +> : ^^^^ return 1; >1 : 1 +> : ^ } if (x === false) { >x === false : boolean +> : ^^^^^^^ >x : T +> : ^ >false : false +> : ^^^^^ return 2; >2 : 2 +> : ^ } return 1; >1 : 1 +> : ^ } diff --git a/tests/baselines/reference/dependentReturnType3.types b/tests/baselines/reference/dependentReturnType3.types index be51c9981d11b..e00bda1641eab 100644 --- a/tests/baselines/reference/dependentReturnType3.types +++ b/tests/baselines/reference/dependentReturnType3.types @@ -5,6 +5,7 @@ type HelperCond = >HelperCond : HelperCond +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ T extends A ? R1 @@ -17,25 +18,33 @@ type HelperCond = interface IMessage { html?: string; >html : string | undefined +> : ^^^^^^^^^^^^^^^^^^ tokens?: {}[]; >tokens : {}[] | undefined +> : ^^^^^^^^^^^^^^^^ } class NewKatex { >NewKatex : NewKatex +> : ^^^^^^^^ render(s: string): string { >render : (s: string) => string +> : ^ ^^ ^^^^^ >s : string +> : ^^^^^^ return ""; >"" : "" +> : ^^ } renderMessage(message: T): >renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : (string | IMessage) +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >message : T +> : ^ T extends string ? string @@ -44,160 +53,251 @@ class NewKatex { : (string | IMessage) { if (typeof message === 'string') { >typeof message === 'string' : boolean +> : ^^^^^^^ >typeof message : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >message : T +> : ^ >'string' : "string" +> : ^^^^^^^^ return this.render(message); // Ok >this.render(message) : string +> : ^^^^^^ >this.render : (s: string) => string +> : ^ ^^ ^^^^^ >this : this +> : ^^^^ >render : (s: string) => string +> : ^ ^^ ^^^^^ >message : string +> : ^^^^^^ } if (!message.html?.trim()) { >!message.html?.trim() : boolean +> : ^^^^^^^ >message.html?.trim() : string | undefined +> : ^^^^^^^^^^^^^^^^^^ >message.html?.trim : (() => string) | undefined +> : ^^^^^^^ ^^^^^^^^^^^^^ >message.html : string | undefined +> : ^^^^^^^^^^^^^^^^^^ >message : IMessage +> : ^^^^^^^^ >html : string | undefined +> : ^^^^^^^^^^^^^^^^^^ >trim : (() => string) | undefined +> : ^^^^^^^ ^^^^^^^^^^^^^ return message; // Ok >message : IMessage +> : ^^^^^^^^ } if (!message.tokens) { >!message.tokens : boolean +> : ^^^^^^^ >message.tokens : {}[] | undefined +> : ^^^^^^^^^^^^^^^^ >message : IMessage +> : ^^^^^^^^ >tokens : {}[] | undefined +> : ^^^^^^^^^^^^^^^^ message.tokens = []; >message.tokens = [] : never[] +> : ^^^^^^^ >message.tokens : {}[] | undefined +> : ^^^^^^^^^^^^^^^^ >message : IMessage +> : ^^^^^^^^ >tokens : {}[] | undefined +> : ^^^^^^^^^^^^^^^^ >[] : never[] +> : ^^^^^^^ } message.html = this.render(message.html); >message.html = this.render(message.html) : string +> : ^^^^^^ >message.html : string | undefined +> : ^^^^^^^^^^^^^^^^^^ >message : IMessage +> : ^^^^^^^^ >html : string | undefined +> : ^^^^^^^^^^^^^^^^^^ >this.render(message.html) : string +> : ^^^^^^ >this.render : (s: string) => string +> : ^ ^^ ^^^^^ >this : this +> : ^^^^ >render : (s: string) => string +> : ^ ^^ ^^^^^ >message.html : string +> : ^^^^^^ >message : IMessage +> : ^^^^^^^^ >html : string +> : ^^^^^^ return message; // Ok >message : IMessage +> : ^^^^^^^^ } } export function createKatexMessageRendering( ->createKatexMessageRendering : (options: { dollarSyntax: boolean; parenthesisSyntax: boolean;}, _isMessage: T) => T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : ((message: string) => string) | ((message: IMessage) => IMessage) +>createKatexMessageRendering : (options: { dollarSyntax: boolean; parenthesisSyntax: boolean; }, _isMessage: T) => T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : (((message: string) => string) | ((message: IMessage) => IMessage)) +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ >true : true +> : ^^^^ >false : false +> : ^^^^^ options: { >options : { dollarSyntax: boolean; parenthesisSyntax: boolean; } +> : ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ dollarSyntax: boolean; >dollarSyntax : boolean +> : ^^^^^^^ parenthesisSyntax: boolean; >parenthesisSyntax : boolean +> : ^^^^^^^ }, _isMessage: T, >_isMessage : T +> : ^ ): T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : (((message: string) => string) | ((message: IMessage) => IMessage)) { >true : true +> : ^^^^ >message : IMessage +> : ^^^^^^^^ >false : false +> : ^^^^^ >message : string +> : ^^^^^^ >message : string +> : ^^^^^^ >message : IMessage +> : ^^^^^^^^ const instance = new NewKatex(); >instance : NewKatex +> : ^^^^^^^^ >new NewKatex() : NewKatex +> : ^^^^^^^^ >NewKatex : typeof NewKatex +> : ^^^^^^^^^^^^^^^ if (_isMessage) { >_isMessage : T +> : ^ return (message: IMessage): IMessage => instance.renderMessage(message); // Ok >(message: IMessage): IMessage => instance.renderMessage(message) : (message: IMessage) => IMessage +> : ^ ^^ ^^^^^ >message : IMessage +> : ^^^^^^^^ >instance.renderMessage(message) : IMessage ->instance.renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : string | IMessage +> : ^^^^^^^^ +>instance.renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : (string | IMessage) +> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ >instance : NewKatex ->renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : string | IMessage +> : ^^^^^^^^ +>renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : (string | IMessage) +> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ >message : IMessage +> : ^^^^^^^^ } return (message: string): string => instance.renderMessage(message); // Ok >(message: string): string => instance.renderMessage(message) : (message: string) => string +> : ^ ^^ ^^^^^ >message : string +> : ^^^^^^ >instance.renderMessage(message) : string ->instance.renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : string | IMessage +> : ^^^^^^ +>instance.renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : (string | IMessage) +> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ >instance : NewKatex ->renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : string | IMessage +> : ^^^^^^^^ +>renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : (string | IMessage) +> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ >message : string +> : ^^^^^^ } // File: Rocket.Chat/apps/meteor/app/settings/lib/settings.ts type SettingComposedValue = { key: string; value: T }; >SettingComposedValue : SettingComposedValue +> : ^^^^^^^^^^^^^^^^^^^^^^^ >key : string +> : ^^^^^^ >value : T +> : ^ type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void; ->SettingCallback : (key: string, value: SettingValue, initialLoad?: boolean) => void +>SettingCallback : SettingCallback +> : ^^^^^^^^^^^^^^^ >key : string +> : ^^^^^^ >value : object +> : ^^^^^^ >initialLoad : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ type SettingValue = object; >SettingValue : object +> : ^^^^^^ declare const Meteor: { settings: { [s: string]: any } }; >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >s : string +> : ^^^^^^ declare const _: { isRegExp(x: unknown): x is RegExp; }; >_ : { isRegExp(x: unknown): x is RegExp; } +> : ^^^^^^^^^^^ ^^ ^^^ ^^^ >isRegExp : (x: unknown) => x is RegExp +> : ^ ^^ ^^^^^ >x : unknown +> : ^^^^^^^ declare function takesRegExp(x: RegExp): void; >takesRegExp : (x: RegExp) => void +> : ^ ^^ ^^^^^ >x : RegExp +> : ^^^^^^ declare function takesString(x: string): void; >takesString : (x: string) => void +> : ^ ^^ ^^^^^ >x : string +> : ^^^^^^ class NewSettingsBase { >NewSettingsBase : NewSettingsBase +> : ^^^^^^^^^^^^^^^ public newGet( ->newGet : (_id: I, callback?: C) => HelperCond[]>> +>newGet : (_id: I, callback?: C) => HelperCond[]>> +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^^ ^^^^^ _id: I, >_id : I +> : ^ callback?: C, >callback : C | undefined +> : ^^^^^^^^^^^^^ ): HelperCond[]>> { if (callback !== undefined) { >callback !== undefined : boolean +> : ^^^^^^^ >callback : C | undefined +> : ^^^^^^^^^^^^^ >undefined : undefined +> : ^^^^^^^^^ if (!Meteor.settings) { >!Meteor.settings : false +> : ^^^^^ >Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ return; // Ok } if (_id === '*') { >_id === '*' : boolean +> : ^^^^^^^ >_id : I +> : ^ >'*' : "*" +> : ^^^ return Object.keys(Meteor.settings).forEach((key) => { // Ok >Object.keys(Meteor.settings).forEach((key) => { // Ok const value = Meteor.settings[key]; callback(key, value); }) : void +> : ^^^^ >Object.keys(Meteor.settings).forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void +> : ^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ >Object.keys(Meteor.settings) : string[] +> : ^^^^^^^^ >Object.keys : { (o: object): string[]; (o: {}): string[]; } +> : ^^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ >Object : ObjectConstructor +> : ^^^^^^^^^^^^^^^^^ >keys : { (o: object): string[]; (o: {}): string[]; } +> : ^^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ >Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void +> : ^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ >(key) => { // Ok const value = Meteor.settings[key]; callback(key, value); } : (key: string) => void +> : ^ ^^^^^^^^^^^^^^^^^ >key : string +> : ^^^^^^ const value = Meteor.settings[key]; >value : any +> : ^^^ >Meteor.settings[key] : any +> : ^^^ >Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >key : string +> : ^^^^^^ callback(key, value); >callback(key, value) : void +> : ^^^^ >callback : SettingCallback +> : ^^^^^^^^^^^^^^^ >key : string +> : ^^^^^^ >value : any +> : ^^^ }); } if (_.isRegExp(_id) && Meteor.settings) { >_.isRegExp(_id) && Meteor.settings : false | { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >_.isRegExp(_id) : boolean +> : ^^^^^^^ >_.isRegExp : (x: unknown) => x is RegExp +> : ^ ^^ ^^^^^ >_ : { isRegExp(x: unknown): x is RegExp; } +> : ^^^^^^^^^^^ ^^ ^^^ ^^^ >isRegExp : (x: unknown) => x is RegExp +> : ^ ^^ ^^^^^ >_id : string | RegExp +> : ^^^^^^^^^^^^^^^ >Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ return Object.keys(Meteor.settings).forEach((key) => { // Ok >Object.keys(Meteor.settings).forEach((key) => { // Ok if (!_id.test(key)) { return; } const value = Meteor.settings[key]; callback(key, value); }) : void +> : ^^^^ >Object.keys(Meteor.settings).forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void +> : ^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ >Object.keys(Meteor.settings) : string[] +> : ^^^^^^^^ >Object.keys : { (o: object): string[]; (o: {}): string[]; } +> : ^^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ >Object : ObjectConstructor +> : ^^^^^^^^^^^^^^^^^ >keys : { (o: object): string[]; (o: {}): string[]; } +> : ^^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ >Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void +> : ^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ >(key) => { // Ok if (!_id.test(key)) { return; } const value = Meteor.settings[key]; callback(key, value); } : (key: string) => void +> : ^ ^^^^^^^^^^^^^^^^^ >key : string +> : ^^^^^^ if (!_id.test(key)) { >!_id.test(key) : boolean +> : ^^^^^^^ >_id.test(key) : boolean +> : ^^^^^^^ >_id.test : (string: string) => boolean +> : ^ ^^ ^^^^^ >_id : RegExp +> : ^^^^^^ >test : (string: string) => boolean +> : ^ ^^ ^^^^^ >key : string +> : ^^^^^^ return; } const value = Meteor.settings[key]; >value : any +> : ^^^ >Meteor.settings[key] : any +> : ^^^ >Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >key : string +> : ^^^^^^ callback(key, value); >callback(key, value) : void +> : ^^^^ >callback : SettingCallback +> : ^^^^^^^^^^^^^^^ >key : string +> : ^^^^^^ >value : any +> : ^^^ }); } if (typeof _id === 'string') { >typeof _id === 'string' : boolean +> : ^^^^^^^ >typeof _id : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >_id : I +> : ^ >'string' : "string" +> : ^^^^^^^^ const value = Meteor.settings[_id]; >value : any +> : ^^^ >Meteor.settings[_id] : any +> : ^^^ >Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >_id : I & string +> : ^^^^^^^^^^ if (value != null) { >value != null : boolean +> : ^^^^^^^ >value : any +> : ^^^ callback(_id, Meteor.settings[_id]); >callback(_id, Meteor.settings[_id]) : void +> : ^^^^ >callback : SettingCallback +> : ^^^^^^^^^^^^^^^ >_id : string +> : ^^^^^^ >Meteor.settings[_id] : any +> : ^^^ >Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >_id : I & string +> : ^^^^^^^^^^ } return; // Ok } @@ -340,79 +529,127 @@ class NewSettingsBase { if (!Meteor.settings) { // Wrong: we don't know that _id is string here, cannot return undefined >!Meteor.settings : false +> : ^^^^^ >Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ return undefined; // Error >undefined : undefined +> : ^^^^^^^^^ } if (_.isRegExp(_id)) { >_.isRegExp(_id) : boolean +> : ^^^^^^^ >_.isRegExp : (x: unknown) => x is RegExp +> : ^ ^^ ^^^^^ >_ : { isRegExp(x: unknown): x is RegExp; } +> : ^^^^^^^^^^^ ^^ ^^^ ^^^ >isRegExp : (x: unknown) => x is RegExp +> : ^ ^^ ^^^^^ >_id : string | RegExp +> : ^^^^^^^^^^^^^^^ return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { >Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { const value = Meteor.settings[key]; if (_id.test(key)) { items.push({ key, value, }); } return items; }, []) : SettingComposedValue[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ >Object.keys(Meteor.settings).reduce : { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; (callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; } +> : ^^^ ^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^ >Object.keys(Meteor.settings) : string[] +> : ^^^^^^^^ >Object.keys : { (o: object): string[]; (o: {}): string[]; } +> : ^^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ >Object : ObjectConstructor +> : ^^^^^^^^^^^^^^^^^ >keys : { (o: object): string[]; (o: {}): string[]; } +> : ^^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ >Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >reduce : { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; (callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; } +> : ^^^ ^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^ >(items: SettingComposedValue[], key) => { const value = Meteor.settings[key]; if (_id.test(key)) { items.push({ key, value, }); } return items; } : (items: SettingComposedValue[], key: string) => SettingComposedValue[] +> : ^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >items : SettingComposedValue[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ >key : string +> : ^^^^^^ const value = Meteor.settings[key]; >value : any +> : ^^^ >Meteor.settings[key] : any +> : ^^^ >Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >key : string +> : ^^^^^^ if (_id.test(key)) { >_id.test(key) : boolean +> : ^^^^^^^ >_id.test : (string: string) => boolean +> : ^ ^^ ^^^^^ >_id : RegExp +> : ^^^^^^ >test : (string: string) => boolean +> : ^ ^^ ^^^^^ >key : string +> : ^^^^^^ items.push({ >items.push({ key, value, }) : number +> : ^^^^^^ >items.push : (...items: SettingComposedValue[]) => number +> : ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >items : SettingComposedValue[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ >push : (...items: SettingComposedValue[]) => number +> : ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >{ key, value, } : { key: string; value: any; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ key, >key : string +> : ^^^^^^ value, >value : any +> : ^^^ }); } return items; >items : SettingComposedValue[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ }, []); // Ok >[] : never[] +> : ^^^^^^^ } return Meteor.settings?.[_id]; // Error >Meteor.settings?.[_id] : any +> : ^^^ >Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ >settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ >_id : I +> : ^ // The indexing currently doesn't work because it doesn't use the narrowed type of `_id`. } @@ -421,99 +658,146 @@ class NewSettingsBase { // File: Rocket.Chat/apps/meteor/app/ui-utils/client/lib/messageBox.ts type MessageBoxAction = object; >MessageBoxAction : object +> : ^^^^^^ function getWithBug(group: T): >getWithBug : (group: T) => HelperCond> +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >group : T +> : ^ HelperCond> { if (!group) { >!group : boolean +> : ^^^^^^^ >group : T +> : ^ return {} as Record; // Error, could fall into this branch when group is empty string >{} as Record : Record +> : ^^^^^^^^^^^^^^^^^^^^^^^^ >{} : {} +> : ^^ } return [] as MessageBoxAction[]; // Ok >[] as MessageBoxAction[] : object[] +> : ^^^^^^^^ >[] : never[] +> : ^^^^^^^ } function getWithoutBug(group: T): >getWithoutBug : (group: T) => HelperCond> +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >group : T +> : ^ HelperCond> { if (group === undefined) { >group === undefined : boolean +> : ^^^^^^^ >group : T +> : ^ >undefined : undefined +> : ^^^^^^^^^ return {} as Record; // Ok >{} as Record : Record +> : ^^^^^^^^^^^^^^^^^^^^^^^^ >{} : {} +> : ^^ } return [] as MessageBoxAction[]; // Ok >[] as MessageBoxAction[] : object[] +> : ^^^^^^^^ >[] : never[] +> : ^^^^^^^ } // File: Rocket.Chat/apps/meteor/ee/server/lib/engagementDashboard/date.ts declare function mapDateForAPI(x: string): Date; >mapDateForAPI : (x: string) => Date +> : ^ ^^ ^^^^^ >x : string +> : ^^^^^^ export function transformDatesForAPI( ->transformDatesForAPI : (start: string, end?: T) => HelperCond +>transformDatesForAPI : (start: string, end?: T) => HelperCond +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^^^ start: string, >start : string +> : ^^^^^^ end?: T >end : T | undefined +> : ^^^^^^^^^^^^^ ): HelperCond { >start : Date +> : ^^^^ >end : Date +> : ^^^^ >start : Date +> : ^^^^ >end : undefined +> : ^^^^^^^^^ return end !== undefined ? // Ok >end !== undefined ? // Ok { start: mapDateForAPI(start), end: mapDateForAPI(end), } : { start: mapDateForAPI(start), end: undefined } : { start: Date; end: Date; } | { start: Date; end: undefined; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >end !== undefined : boolean +> : ^^^^^^^ >end : T | undefined +> : ^^^^^^^^^^^^^ >undefined : undefined +> : ^^^^^^^^^ { >{ start: mapDateForAPI(start), end: mapDateForAPI(end), } : { start: Date; end: Date; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ start: mapDateForAPI(start), >start : Date +> : ^^^^ >mapDateForAPI(start) : Date +> : ^^^^ >mapDateForAPI : (x: string) => Date +> : ^ ^^ ^^^^^ >start : string +> : ^^^^^^ end: mapDateForAPI(end), >end : Date +> : ^^^^ >mapDateForAPI(end) : Date +> : ^^^^ >mapDateForAPI : (x: string) => Date +> : ^ ^^ ^^^^^ >end : string +> : ^^^^^^ } : { >{ start: mapDateForAPI(start), end: undefined } : { start: Date; end: undefined; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ start: mapDateForAPI(start), >start : Date +> : ^^^^ >mapDateForAPI(start) : Date +> : ^^^^ >mapDateForAPI : (x: string) => Date +> : ^ ^^ ^^^^^ >start : string +> : ^^^^^^ end: undefined >end : undefined +> : ^^^^^^^^^ >undefined : undefined +> : ^^^^^^^^^ }; } @@ -521,101 +805,153 @@ export function transformDatesForAPI( // File: Rocket.Chat/packages/agenda/src/Agenda.ts type RepeatOptions = object; >RepeatOptions : object +> : ^^^^^^ type Job = object; >Job : object +> : ^^^^^^ type IJob = { data: object }; ->IJob : { data: object; } +>IJob : IJob +> : ^^^^ >data : object +> : ^^^^^^ class NewAgenda { >NewAgenda : NewAgenda +> : ^^^^^^^^^ public async _createIntervalJob(interval: string | number, name: string, data: IJob['data'], options: RepeatOptions): Promise { return undefined as any; } ->_createIntervalJob : (interval: string | number, name: string, data: IJob['data'], options: RepeatOptions) => Promise +>_createIntervalJob : (interval: string | number, name: string, data: IJob["data"], options: RepeatOptions) => Promise +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ >interval : string | number +> : ^^^^^^^^^^^^^^^ >name : string +> : ^^^^^^ >data : object +> : ^^^^^^ >options : object +> : ^^^^^^ >undefined as any : any +> : ^^^ >undefined : undefined +> : ^^^^^^^^^ private _createIntervalJobs( ->_createIntervalJobs : (interval: string | number, names: string[], data: IJob['data'], options: RepeatOptions) => Promise | undefined +>_createIntervalJobs : (interval: string | number, names: string[], data: IJob["data"], options: RepeatOptions) => Promise | undefined +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ interval: string | number, >interval : string | number +> : ^^^^^^^^^^^^^^^ names: string[], >names : string[] +> : ^^^^^^^^ data: IJob['data'], >data : object +> : ^^^^^^ options: RepeatOptions, >options : object +> : ^^^^^^ ): Promise | undefined { return undefined as any; } >undefined as any : any +> : ^^^ >undefined : undefined +> : ^^^^^^^^^ public async newEvery( ->newEvery : (interval: string | number, name: T, data: IJob['data'], options: RepeatOptions) => Promise> +>newEvery : (interval: string | number, name: T, data: IJob["data"], options: RepeatOptions) => Promise> +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ interval: string | number, >interval : string | number +> : ^^^^^^^^^^^^^^^ name: T, >name : T +> : ^ data: IJob['data'], >data : object +> : ^^^^^^ options: RepeatOptions): Promise> { >options : object +> : ^^^^^^ if (typeof name === 'string') { >typeof name === 'string' : boolean +> : ^^^^^^^ >typeof name : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >name : T +> : ^ >'string' : "string" +> : ^^^^^^^^ return this._createIntervalJob(interval, name, data, options); // Ok >this._createIntervalJob(interval, name, data, options) : Promise ->this._createIntervalJob : (interval: string | number, name: string, data: object, options: object) => Promise +> : ^^^^^^^^^^^^^^^ +>this._createIntervalJob : (interval: string | number, name: string, data: IJob["data"], options: RepeatOptions) => Promise +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ >this : this ->_createIntervalJob : (interval: string | number, name: string, data: object, options: object) => Promise +> : ^^^^ +>_createIntervalJob : (interval: string | number, name: string, data: IJob["data"], options: RepeatOptions) => Promise +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ >interval : string | number +> : ^^^^^^^^^^^^^^^ >name : string +> : ^^^^^^ >data : object +> : ^^^^^^ >options : object +> : ^^^^^^ } if (Array.isArray(name)) { >Array.isArray(name) : boolean +> : ^^^^^^^ >Array.isArray : (arg: any) => arg is any[] +> : ^ ^^ ^^^^^ >Array : ArrayConstructor +> : ^^^^^^^^^^^^^^^^ >isArray : (arg: any) => arg is any[] +> : ^ ^^ ^^^^^ >name : string[] +> : ^^^^^^^^ return this._createIntervalJobs(interval, name, data, options); // Ok >this._createIntervalJobs(interval, name, data, options) : Promise | undefined ->this._createIntervalJobs : (interval: string | number, names: string[], data: object, options: object) => Promise | undefined +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>this._createIntervalJobs : (interval: string | number, names: string[], data: IJob["data"], options: RepeatOptions) => Promise | undefined +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ >this : this ->_createIntervalJobs : (interval: string | number, names: string[], data: object, options: object) => Promise | undefined +> : ^^^^ +>_createIntervalJobs : (interval: string | number, names: string[], data: IJob["data"], options: RepeatOptions) => Promise | undefined +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ >interval : string | number +> : ^^^^^^^^^^^^^^^ >name : string[] +> : ^^^^^^^^ >data : object +> : ^^^^^^ >options : object +> : ^^^^^^ // Possible bug in original: createIntervalJobs can return undefined, but the original overload did not acount for that. } throw new Error('Unexpected error: Invalid job name(s)'); >new Error('Unexpected error: Invalid job name(s)') : Error +> : ^^^^^ >Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ >'Unexpected error: Invalid job name(s)' : "Unexpected error: Invalid job name(s)" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ } } @@ -623,27 +959,41 @@ class NewAgenda { function transform1(value: T): HelperCond { >transform1 : (value: T) => HelperCond +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >value : T +> : ^ if (value == null) return null; // Ok >value == null : boolean +> : ^^^^^^^ >value : T +> : ^ if (typeof value !== 'string') { >typeof value !== 'string' : boolean +> : ^^^^^^^ >typeof value : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >value : NonNullable +> : ^^^^^^^^^^^^^^ >'string' : "string" +> : ^^^^^^^^ throw new Error(); >new Error() : Error +> : ^^^^^ >Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ } return value.toLowerCase(); // Ok >value.toLowerCase() : string +> : ^^^^^^ >value.toLowerCase : () => string +> : ^^^^^^ >value : string +> : ^^^^^^ >toLowerCase : () => string +> : ^^^^^^ } diff --git a/tests/baselines/reference/dependentReturnType4.types b/tests/baselines/reference/dependentReturnType4.types index b79740d3304a8..276f3050fd34b 100644 --- a/tests/baselines/reference/dependentReturnType4.types +++ b/tests/baselines/reference/dependentReturnType4.types @@ -4,223 +4,331 @@ // Test narrowing through `hasOwnProperty` calls declare const rand: { a?: never }; >rand : { a?: never; } +> : ^^^^^^ ^^^ >a : undefined +> : ^^^^^^^^^ type Missing = typeof rand.a; >Missing : undefined +> : ^^^^^^^^^ >rand.a : undefined +> : ^^^^^^^^^ >rand : { a?: never; } +> : ^^^^^^ ^^^ >a : undefined +> : ^^^^^^^^^ declare function takesString(x: string): void; >takesString : (x: string) => void +> : ^ ^^ ^^^^^ >x : string +> : ^^^^^^ function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { ->hasOwnP : (obj: { a?: T;}) => T extends string ? 1 : T extends undefined ? 2 : 1 | 2 +>hasOwnP : (obj: { a?: T; }) => T extends string ? 1 : T extends undefined ? 2 : 1 | 2 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >obj : { a?: T; } +> : ^^^^^^ ^^^ >a : T | undefined +> : ^^^^^^^^^^^^^ if (obj.hasOwnProperty("a")) { >obj.hasOwnProperty("a") : boolean +> : ^^^^^^^ >obj.hasOwnProperty : (v: PropertyKey) => boolean +> : ^ ^^ ^^^^^ >obj : { a?: T; } +> : ^^^^^^ ^^^ >hasOwnProperty : (v: PropertyKey) => boolean +> : ^ ^^ ^^^^^ >"a" : "a" +> : ^^^ takesString(obj.a); >takesString(obj.a) : void +> : ^^^^ >takesString : (x: string) => void +> : ^ ^^ ^^^^^ >obj.a : string +> : ^^^^^^ >obj : { a?: T; } +> : ^^^^^^ ^^^ >a : string +> : ^^^^^^ return 1; >1 : 1 +> : ^ } return 2; >2 : 2 +> : ^ } function foo(opts: { x?: T }): ->foo : (opts: { x?: T;}) => T extends undefined ? 0 : T extends string ? 1 : 0 | 1 +>foo : (opts: { x?: T; }) => T extends undefined ? 0 : T extends string ? 1 : 0 | 1 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >opts : { x?: T; } +> : ^^^^^^ ^^^ >x : T | undefined +> : ^^^^^^^^^^^^^ T extends undefined ? 0 : T extends string ? 1 : 0 | 1 { if (opts.x === undefined) { >opts.x === undefined : boolean +> : ^^^^^^^ >opts.x : T | undefined +> : ^^^^^^^^^^^^^ >opts : { x?: T; } +> : ^^^^^^ ^^^ >x : T | undefined +> : ^^^^^^^^^^^^^ >undefined : undefined +> : ^^^^^^^^^ return 0; >0 : 0 +> : ^ } return 1; >1 : 1 +> : ^ } function bar(x?: T ): ->bar : (x?: T) => T extends Missing ? 0 : T extends string ? 1 : 0 | 1 +>bar : (x?: T) => T extends Missing ? 0 : T extends string ? 1 : 0 | 1 +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ >x : T | undefined +> : ^^^^^^^^^^^^^ T extends Missing ? 0 : T extends string ? 1 : 0 | 1 { if (x === undefined) { >x === undefined : boolean +> : ^^^^^^^ >x : T | undefined +> : ^^^^^^^^^^^^^ >undefined : undefined +> : ^^^^^^^^^ return 0; >0 : 0 +> : ^ } return 1; >1 : 1 +> : ^ } // Aliased narrowing function inlined(x: T): T extends number ? string : T extends string ? number : string | number { ->inlined : (x: T) => T extends number ? string : T extends string ? number : string | number +>inlined : (x: T) => T extends number ? string : T extends string ? number : string | number +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ const t = typeof x === "string"; >t : boolean +> : ^^^^^^^ >typeof x === "string" : boolean +> : ^^^^^^^ >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : T +> : ^ >"string" : "string" +> : ^^^^^^^^ if (t) { >t : boolean +> : ^^^^^^^ const y: string = x; >y : string +> : ^^^^^^ >x : string +> : ^^^^^^ return 1; >1 : 1 +> : ^ } return "one"; >"one" : "one" +> : ^^^^^ } // Don't narrow more than 5 levels of aliasing function inlined6(x: T): T extends number ? string : T extends string ? number : string | number { ->inlined6 : (x: T) => T extends number ? string : T extends string ? number : string | number +>inlined6 : (x: T) => T extends number ? string : T extends string ? number : string | number +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ const t1 = typeof x === "string"; >t1 : boolean +> : ^^^^^^^ >typeof x === "string" : boolean +> : ^^^^^^^ >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : T +> : ^ >"string" : "string" +> : ^^^^^^^^ const t2 = t1; >t2 : boolean +> : ^^^^^^^ >t1 : boolean +> : ^^^^^^^ const t3 = t2; >t3 : boolean +> : ^^^^^^^ >t2 : boolean +> : ^^^^^^^ const t4 = t3; >t4 : boolean +> : ^^^^^^^ >t3 : boolean +> : ^^^^^^^ const t5 = t4; >t5 : boolean +> : ^^^^^^^ >t4 : boolean +> : ^^^^^^^ const t6 = t5; >t6 : boolean +> : ^^^^^^^ >t5 : boolean +> : ^^^^^^^ if (t6) { >t6 : boolean +> : ^^^^^^^ const y: string = x; >y : string +> : ^^^^^^ >x : string | number +> : ^^^^^^^^^^^^^^^ return 1; >1 : 1 +> : ^ } return "one"; >"one" : "one" +> : ^^^^^ } type A = { kind: "a", a: number }; ->A : { kind: "a"; a: number; } +>A : A +> : ^ >kind : "a" +> : ^^^ >a : number +> : ^^^^^^ type B = { kind: "b", b: string }; ->B : { kind: "b"; b: string; } +>B : B +> : ^ >kind : "b" +> : ^^^ >b : string +> : ^^^^^^ type AOrB = A | B; ->AOrB : A | B +>AOrB : AOrB +> : ^^^^ function subexpression(x: T): T extends A ? number : T extends B ? string : number | string { >subexpression : (x: T) => T extends A ? number : T extends B ? string : number | string +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (x.kind === "b") { >x.kind === "b" : boolean +> : ^^^^^^^ >x.kind : "a" | "b" +> : ^^^^^^^^^ >x : AOrB +> : ^^^^ >kind : "a" | "b" +> : ^^^^^^^^^ >"b" : "b" +> : ^^^ return "some str"; >"some str" : "some str" +> : ^^^^^^^^^^ } return 0; >0 : 0 +> : ^ } function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : 0 | 1 { >switchTrue : (x: T) => T extends true ? 1 : T extends false ? 0 : 0 | 1 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ >true : true +> : ^^^^ >false : false +> : ^^^^^ switch (true) { >true : true +> : ^^^^ case x: >x : T +> : ^ return 1; >1 : 1 +> : ^ } return 0; >0 : 0 +> : ^ } // Don't raise errors when getting the narrowed type of synthesized nodes type Ret = T extends string ? 1 : T extends number ? 2 : 1 | 2; >Ret : Ret +> : ^^^^^^ function f(x: T): Ret { >f : (x: T) => Ret +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ let y!: T; >y : T +> : ^ if (typeof y === "string") { >typeof y === "string" : boolean +> : ^^^^^^^ >typeof y : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >y : T +> : ^ >"string" : "string" +> : ^^^^^^^^ return 1; >1 : 1 +> : ^ } return 2; >2 : 2 +> : ^ } diff --git a/tests/baselines/reference/dependentReturnType5.types b/tests/baselines/reference/dependentReturnType5.types index a1d5aa9991b27..25f542fec56de 100644 --- a/tests/baselines/reference/dependentReturnType5.types +++ b/tests/baselines/reference/dependentReturnType5.types @@ -5,165 +5,245 @@ interface A1 { "prop": true; >"prop" : true +> : ^^^^ >true : true +> : ^^^^ [s: string]: boolean; >s : string +> : ^^^^^^ } // This was already allowed but is unsound. function foo1(x: T): A1[T] { >foo1 : (x: T) => A1[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ return false; >false : false +> : ^^^^^ } const rfoo1 = foo1("prop"); // Type says true, but actually returns false. >rfoo1 : true +> : ^^^^ >foo1("prop") : true +> : ^^^^ >foo1 : (x: T) => A1[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >"prop" : "prop" +> : ^^^^^^ interface A2 { "prop": true; >"prop" : true +> : ^^^^ >true : true +> : ^^^^ [n: number]: string; >n : number +> : ^^^^^^ } // We could soundly allow that, because `"prop"` and `[n: number]` are disjoint types. function foo2(x: T): A2[T] { ->foo2 : (x: T) => A2[T] +>foo2 : (x: T) => A2[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (x === "prop") { >x === "prop" : boolean +> : ^^^^^^^ >x : T +> : ^ >"prop" : "prop" +> : ^^^^^^ return true; >true : true +> : ^^^^ } return "some string"; >"some string" : "some string" +> : ^^^^^^^^^^^^^ } const rfoo2 = foo2("prop"); >rfoo2 : true +> : ^^^^ >foo2("prop") : true ->foo2 : (x: T) => A2[T] +> : ^^^^ +>foo2 : (x: T) => A2[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >"prop" : "prop" +> : ^^^^^^ const rfoo22 = foo2(34); >rfoo22 : string +> : ^^^^^^ >foo2(34) : string ->foo2 : (x: T) => A2[T] +> : ^^^^^^ +>foo2 : (x: T) => A2[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >34 : 34 +> : ^^ const rfoo222 = foo2(Math.random() ? "prop" : 34); >rfoo222 : string | true +> : ^^^^^^^^^^^^^ >foo2(Math.random() ? "prop" : 34) : string | true ->foo2 : (x: T) => A2[T] +> : ^^^^^^^^^^^^^ +>foo2 : (x: T) => A2[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >Math.random() ? "prop" : 34 : "prop" | 34 +> : ^^^^^^^^^^^ >Math.random() : number +> : ^^^^^^ >Math.random : () => number +> : ^^^^^^ >Math : Math +> : ^^^^ >random : () => number +> : ^^^^^^ >"prop" : "prop" +> : ^^^^^^ >34 : 34 +> : ^^ interface A3 { [s: string]: boolean; >s : string +> : ^^^^^^ } // No need for return type narrowing. function foo3(x: T): A3[T] { >foo3 : (x: T) => A3[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (Math.random()) return true; >Math.random() : number +> : ^^^^^^ >Math.random : () => number +> : ^^^^^^ >Math : Math +> : ^^^^ >random : () => number +> : ^^^^^^ >true : true +> : ^^^^ return false; >false : false +> : ^^^^^ } interface Comp { foo: 2; >foo : 2 +> : ^ [n: number]: 3; >n : number +> : ^^^^^^ [s: string]: 2 | 3 | 4; >s : string +> : ^^^^^^ } function indexedComp(x: T): Comp[T] { ->indexedComp : (x: T) => Comp[T] +>indexedComp : (x: T) => Comp[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (x === "foo") { >x === "foo" : boolean +> : ^^^^^^^ >x : T +> : ^ >"foo" : "foo" +> : ^^^^^ if (Math.random()) { >Math.random() : number +> : ^^^^^^ >Math.random : () => number +> : ^^^^^^ >Math : Math +> : ^^^^ >random : () => number +> : ^^^^^^ return 3; // Error >3 : 3 +> : ^ } return 2; // Ok >2 : 2 +> : ^ } if (typeof x === "number") { >typeof x === "number" : boolean +> : ^^^^^^^ >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : T +> : ^ >"number" : "number" +> : ^^^^^^^^ if (Math.random()) { >Math.random() : number +> : ^^^^^^ >Math.random : () => number +> : ^^^^^^ >Math : Math +> : ^^^^ >random : () => number +> : ^^^^^^ return 2; // Error >2 : 2 +> : ^ } return 3; // Ok >3 : 3 +> : ^ } return 4; // Ok >4 : 4 +> : ^ } function indexedComp2(x: T): Comp[T] { ->indexedComp2 : (x: T) => Comp[T] +>indexedComp2 : (x: T) => Comp[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T +> : ^ if (Math.random()) { >Math.random() : number +> : ^^^^^^ >Math.random : () => number +> : ^^^^^^ >Math : Math +> : ^^^^ >random : () => number +> : ^^^^^^ return 3; // Bad, unsound >3 : 3 +> : ^ } return 2; // Error >2 : 2 +> : ^ } @@ -171,58 +251,81 @@ function indexedComp2(x: T): Comp[T] { interface F { "t": number, >"t" : number +> : ^^^^^^ "f": boolean, >"f" : boolean +> : ^^^^^^^ } // Ok function depLikeFun(str: T): F[T] { >depLikeFun : (str: T) => F[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >str : T +> : ^ if (str === "t") { >str === "t" : boolean +> : ^^^^^^^ >str : T +> : ^ >"t" : "t" +> : ^^^ return 1; >1 : 1 +> : ^ } else { return true; >true : true +> : ^^^^ } } depLikeFun("t"); // has type number >depLikeFun("t") : number +> : ^^^^^^ >depLikeFun : (str: T) => F[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >"t" : "t" +> : ^^^ depLikeFun("f"); // has type boolean >depLikeFun("f") : boolean +> : ^^^^^^^ >depLikeFun : (str: T) => F[T] +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >"f" : "f" +> : ^^^ type IndirectF = F[T]; >IndirectF : IndirectF +> : ^^^^^^^^^^^^ // Ok function depLikeFun2(str: T): IndirectF { >depLikeFun2 : (str: T) => IndirectF +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >str : T +> : ^ if (str === "t") { >str === "t" : boolean +> : ^^^^^^^ >str : T +> : ^ >"t" : "t" +> : ^^^ return 1; >1 : 1 +> : ^ } else { return true; >true : true +> : ^^^^ } } From 7cdf655c79f122fd40223d9faf7d15a1bf29fc24 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 19 Aug 2024 17:31:45 -0700 Subject: [PATCH 65/90] new validation for conditional type shape + updated tests --- src/compiler/checker.ts | 107 +- .../reference/dependentReturnType1.errors.txt | 365 +++-- .../reference/dependentReturnType1.symbols | 1446 +++++++++-------- .../reference/dependentReturnType1.types | 550 +++---- .../reference/dependentReturnType2.errors.txt | 29 - .../reference/dependentReturnType2.symbols | 40 - .../reference/dependentReturnType2.types | 69 - .../reference/dependentReturnType3.errors.txt | 22 +- .../reference/dependentReturnType3.symbols | 505 +++--- .../reference/dependentReturnType3.types | 45 +- .../reference/dependentReturnType4.errors.txt | 24 +- .../reference/dependentReturnType4.symbols | 16 +- .../reference/dependentReturnType4.types | 36 +- tests/cases/compiler/dependentReturnType1.ts | 230 +-- tests/cases/compiler/dependentReturnType2.ts | 20 - tests/cases/compiler/dependentReturnType3.ts | 15 +- tests/cases/compiler/dependentReturnType4.ts | 16 +- .../returnTypeNarrowingAfterCachingTypes.ts | 2 +- 18 files changed, 1792 insertions(+), 1745 deletions(-) delete mode 100644 tests/baselines/reference/dependentReturnType2.errors.txt delete mode 100644 tests/baselines/reference/dependentReturnType2.symbols delete mode 100644 tests/baselines/reference/dependentReturnType2.types delete mode 100644 tests/cases/compiler/dependentReturnType2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a0e32807a415e..1237a53d6ce46 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20633,8 +20633,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getNarrowConditionalType(type, narrowMapper, mapper); } - function isNarrowableReturnType(type: Type) { - return type.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional) && couldContainTypeVariables(type); + function isGenericIndexedOrConditionalReturnType(type: Type): type is IndexedAccessType | ConditionalType { + return !!(type.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional) && couldContainTypeVariables(type)); } function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) { @@ -45418,20 +45418,32 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { : exprType; const errorNode = node.expression && isConditionalExpression(skipParentheses(node.expression)) ? expr : node; - if (!isNarrowableReturnType(unwrappedReturnType)) { + if (!isGenericIndexedOrConditionalReturnType(unwrappedReturnType)) { checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); return; } - // Check if type of return expression is assignable to original return type; // If so, we don't need to narrow. if (checkTypeAssignableTo(unwrappedExprType, unwrappedReturnType, /*errorNode*/ undefined)) { return; } const outerTypeParameters = getOuterTypeParameters(container, /*includeThisTypes*/ false); - const typeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - const narrowableTypeParameters = typeParameters - && filterNarrowableTypeParameters(container, typeParameters); + const allTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); + const unionTypeParameters = allTypeParameters?.filter(tp => { + const constraint = getConstraintOfTypeParameter(tp); + return !!((constraint?.flags ?? 0) & TypeFlags.Union); + }); + const narrowableTypeParameters = unionTypeParameters + && filterNarrowableTypeParameters(container, unionTypeParameters); + + if ( + !narrowableTypeParameters || + !isNarrowableReturnType(narrowableTypeParameters.map(trio => trio[0]), unwrappedReturnType) + ) { + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); + return; + } + // There are two cases for obtaining a position in the control-flow graph on which references will be analyzed: // - When the return expression is defined, and it is one of the two branches of a conditional expression, then the position is the expression itself: // `function foo(...) { @@ -45451,7 +45463,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { narrowFlowNode = expr.parent.whenTrue === expr ? expr.parent.flowNodeWhenTrue : expr.parent.flowNodeWhenFalse; narrowPosition = expr; } - let narrowedReturnType = unwrappedReturnType; + let narrowedReturnType: Type = unwrappedReturnType; if (narrowableTypeParameters && narrowFlowNode) { const narrowed: [TypeParameter, Type][] = mapDefined(narrowableTypeParameters, ([tp, symbol, reference]) => { const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. @@ -45511,6 +45523,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkReturnStatementExpression(container, returnType, node, expr.whenFalse); } + // Narrowable type parameters are type parameters that are the type of a single reference in the function scope + // whose type could possibly narrowed. + // For instance, `T` is a narrowable type parameter in the function below because it is the type of reference `x`, + // reference `x` appears in the control flow graph for the function, and therefore `x`'s type could be narrowed, + // and `T` is not the type of any other such references in the scope of the function. + // ` + // function f(x: T, y: U): ... { + // if (typeof x === ...) { return ...; } + // } + // ` function filterNarrowableTypeParameters(container: SignatureDeclaration, typeParameters: TypeParameter[]): [TypeParameter, Symbol, TypeParameterReference][] | undefined { const narrowableTypeParameterReferences = collectTypeParameterReferences(container); const queryParameters: [TypeParameter, Symbol, TypeParameterReference][] = []; @@ -45762,6 +45784,75 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; } + // A valid conditional type will have the following shape: + // `T extends A ? TrueBranch : FalseBranch`, such that: + // (0) The conditional type's check type is a narrowable type parameter; + // (1) `A` is a type belonging to the constraint of the type parameter, + // or a union of types belonging to the constraint of the type parameter; + // (2) There are no `infer` type parameters in the conditional type; + // (3) `TrueBranch` and `FalseBranch` must be valid, recursively; + // >> TODO: + // - consider multiple type parameters at once + // - can/should we check exhaustiveness? + // - Problem: cond type nested in true branch with same type parameter is not considered distributive, + // because the type parameter is actually a substitution type... + function isNarrowableReturnType( + typeParameters: TypeParameter[], + returnType: IndexedAccessType | ConditionalType, + ): boolean { + return !isConditionalType(returnType) || isNarrowable(returnType, /*branch*/ undefined); + // `branch` can be `true` if `type` is the true type of a conditional, `false` if it's the false type of a conditional, + // and `undefined` if neither. + function isNarrowable(type: Type, branch: boolean | undefined): boolean { + if (!isConditionalType(type)) { + // This is type `R` in `T extends A ? R : ...` + if (branch === true) { + return true; + } + // This is type `never` in `T extends A ? R : never` + if (branch === false) { + return type === neverType; + } + return false; + } + // (0) + if (!(type.checkType.flags & TypeFlags.TypeParameter)) { + return false; + } + const typeParameter = typeParameters.find(tp => tp === type.checkType); + if (!typeParameter) { + return false; + } + const constraintType = getConstraintOfTypeParameter(typeParameter) as UnionType; + // (0) + if (!type.root.isDistributive) { // >> TODO: do we need this? depends on how narrowing instantiation is implemented + return false; + } + // (2) + if (type.root.inferTypeParameters?.length) { + return false; + } + // (1) + // >> TODO: should this be some sort of type comparison check instead of identity? + if ( + !everyType(type.extendsType, extendsType => + some( + constraintType.types, + constraintType => isTypeIdenticalTo(constraintType, extendsType), + )) + ) { + return false; + } + + return isNarrowable(getTrueTypeFromConditionalType(type), /*branch*/ true) && + isNarrowable(getFalseTypeFromConditionalType(type), /*branch*/ false); + } + } + + function isConditionalType(type: Type): type is ConditionalType { + return !!(type.flags & TypeFlags.Conditional); + } + function checkWithStatement(node: WithStatement) { // Grammar checking for withStatement if (!checkGrammarStatementInAmbientContext(node)) { diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index 4ff800159dec1..cab1643befe28 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -1,49 +1,45 @@ dependentReturnType1.ts(11,9): error TS2322: Type 'number' is not assignable to type 'string'. dependentReturnType1.ts(26,9): error TS2322: Type 'string' is not assignable to type 'never'. dependentReturnType1.ts(35,9): error TS2322: Type 'string' is not assignable to type 'never'. -dependentReturnType1.ts(70,9): error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. -dependentReturnType1.ts(74,5): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. -dependentReturnType1.ts(80,9): error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four'. -dependentReturnType1.ts(84,22): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. -dependentReturnType1.ts(97,9): error TS2322: Type 'LeftOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. -dependentReturnType1.ts(99,9): error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. -dependentReturnType1.ts(116,9): error TS2322: Type 'number' is not assignable to type 'T extends Dog ? number : string'. -dependentReturnType1.ts(118,5): error TS2322: Type 'string' is not assignable to type 'T extends Dog ? number : string'. -dependentReturnType1.ts(153,13): error TS2322: Type 'string' is not assignable to type 'T extends string ? this : string'. -dependentReturnType1.ts(155,9): error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. - Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. -dependentReturnType1.ts(159,9): error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. - Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. -dependentReturnType1.ts(174,13): error TS2322: Type 'this' is not assignable to type 'string'. +dependentReturnType1.ts(69,9): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. +dependentReturnType1.ts(71,5): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. +dependentReturnType1.ts(80,22): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. +dependentReturnType1.ts(93,9): error TS2322: Type 'LeftOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never'. +dependentReturnType1.ts(95,9): error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never'. +dependentReturnType1.ts(112,9): error TS2322: Type 'number' is not assignable to type 'T extends Dog ? number : string'. +dependentReturnType1.ts(114,5): error TS2322: Type 'string' is not assignable to type 'T extends Dog ? number : string'. +dependentReturnType1.ts(149,13): error TS2322: Type 'string' is not assignable to type 'T extends string ? this : T extends undefined ? string : never'. +dependentReturnType1.ts(151,9): error TS2322: Type 'this' is not assignable to type 'T extends string ? this : T extends undefined ? string : never'. + Type 'Unnamed' is not assignable to type 'T extends string ? this : T extends undefined ? string : never'. +dependentReturnType1.ts(166,13): error TS2322: Type 'this' is not assignable to type 'string'. Type 'Unnamed' is not assignable to type 'string'. -dependentReturnType1.ts(177,9): error TS2322: Type 'T & {}' is not assignable to type 'this'. +dependentReturnType1.ts(169,9): error TS2322: Type 'T & {}' is not assignable to type 'this'. 'this' could be instantiated with an arbitrary type which could be unrelated to 'T & {}'. -dependentReturnType1.ts(207,24): error TS2322: Type 'string' is not assignable to type 'number'. -dependentReturnType1.ts(207,28): error TS2322: Type 'number' is not assignable to type 'string'. -dependentReturnType1.ts(232,47): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. -dependentReturnType1.ts(234,9): error TS2322: Type '""' is not assignable to type 'T extends 1 ? string : boolean'. -dependentReturnType1.ts(240,62): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. -dependentReturnType1.ts(243,9): error TS2322: Type 'string' is not assignable to type 'T extends { a: string; } ? number : string | number'. -dependentReturnType1.ts(268,9): error TS2322: Type '1' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. -dependentReturnType1.ts(271,9): error TS2322: Type '2' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. -dependentReturnType1.ts(273,5): error TS2322: Type '0' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. -dependentReturnType1.ts(291,9): error TS2322: Type 'string' is not assignable to type 'string[]'. -dependentReturnType1.ts(307,9): error TS2322: Type '1' is not assignable to type 'T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4'. -dependentReturnType1.ts(333,9): error TS2322: Type '2' is not assignable to type 'T extends string ? 1 : T extends undefined ? 2 : 1 | 2'. -dependentReturnType1.ts(335,5): error TS2322: Type '1' is not assignable to type 'T extends string ? 1 : T extends undefined ? 2 : 1 | 2'. -dependentReturnType1.ts(343,13): error TS2322: Type 'number' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : 1 | 2'. -dependentReturnType1.ts(345,9): error TS2322: Type 'string' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : 1 | 2'. -dependentReturnType1.ts(351,53): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. -dependentReturnType1.ts(359,9): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. -dependentReturnType1.ts(367,13): error TS2322: Type 'number' is not assignable to type 'string'. -dependentReturnType1.ts(376,9): error TS2322: Type 'true' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. -dependentReturnType1.ts(378,5): error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. -dependentReturnType1.ts(403,15): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. -dependentReturnType1.ts(405,11): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. -dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. +dependentReturnType1.ts(203,24): error TS2322: Type 'string' is not assignable to type 'number'. +dependentReturnType1.ts(203,28): error TS2322: Type 'number' is not assignable to type 'string'. +dependentReturnType1.ts(240,9): error TS2322: Type '""' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. +dependentReturnType1.ts(242,9): error TS2322: Type 'true' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. +dependentReturnType1.ts(244,5): error TS2322: Type '3' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. +dependentReturnType1.ts(272,9): error TS2322: Type '1' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. +dependentReturnType1.ts(275,9): error TS2322: Type '2' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. +dependentReturnType1.ts(277,5): error TS2322: Type '0' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. +dependentReturnType1.ts(299,9): error TS2322: Type 'string' is not assignable to type 'string[]'. +dependentReturnType1.ts(308,9): error TS2322: Type 'undefined' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'. +dependentReturnType1.ts(310,5): error TS2322: Type 'number' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'. +dependentReturnType1.ts(331,9): error TS2322: Type '1' is not assignable to type '4'. +dependentReturnType1.ts(364,13): error TS2322: Type 'number' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : never'. +dependentReturnType1.ts(366,9): error TS2322: Type 'string' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : never'. +dependentReturnType1.ts(389,9): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +dependentReturnType1.ts(399,13): error TS2322: Type 'number' is not assignable to type 'string'. +dependentReturnType1.ts(409,9): error TS2322: Type 'true' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : never'. +dependentReturnType1.ts(411,5): error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : never'. +dependentReturnType1.ts(436,15): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +dependentReturnType1.ts(438,11): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +dependentReturnType1.ts(465,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. +dependentReturnType1.ts(501,5): error TS2322: Type 'number' is not assignable to type 'never'. -==== dependentReturnType1.ts (39 errors) ==== +==== dependentReturnType1.ts (36 errors) ==== interface A { 1: number; 2: string; @@ -115,31 +111,25 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ f: "f"; g: "g"; } - - function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { // Badly written conditional + // Badly written conditional return type, will not trigger narrowing + function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { if (x === 1 || x === 2) { - return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok - return { a: "a" }; // Error + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Error ~~~~~~ -!!! error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. +!!! error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. } - // Excess property becomes a problem with the change, - // because we now check assignability to a narrower type... return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error ~~~~~~ !!! error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. } - - function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four { // Well written conditional + // Well written conditional + function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : never { if (x === 1 || x === 2) { return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok - return { a: "a" }; // Error - ~~~~~~ -!!! error TS2322: Type '{ a: "a"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four'. } // Excess property becomes a problem with the change, // because we now check assignability to a narrower type... - return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // EPC Error ~ !!! error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. } @@ -150,17 +140,17 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ cond: (arg: LeftIn | RightIn) => arg is LeftIn, produceLeftOut: (arg: LeftIn) => LeftOut, produceRightOut: (arg: RightIn) => RightOut): - Arg extends LeftIn ? LeftOut : RightOut + Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never { type OK = Arg extends LeftIn ? LeftOut : RightOut; if (cond(arg)) { return produceLeftOut(arg); // The narrowed conditional return type has deferred resolution, so this doesn't work. ~~~~~~ -!!! error TS2322: Type 'LeftOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. +!!! error TS2322: Type 'LeftOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never'. } else { return produceRightOut(arg as RightIn); // Error: Doesn't work because we can't narrow `arg` to `Arg & RightIn` here ~~~~~~ -!!! error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : RightOut'. +!!! error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never'. } } @@ -172,12 +162,12 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ bark: () => string; } - // This is unsafe + // This would be unsafe to narrow. declare function isDog(x: Animal): x is Dog; declare function doggy(x: Dog): number; function f12(x: T): T extends Dog ? number : string { if (isDog(x)) { // `x` has type `T & Dog` here - return doggy(x); // The narrowed conditional return type has deferred resolution, so this doesn't work. + return doggy(x); ~~~~~~ !!! error TS2322: Type 'number' is not assignable to type 'T extends Dog ? number : string'. } @@ -215,28 +205,21 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ class Unnamed { root!: { name: string }; - // Error because parameter is optional - name(name?: T): T extends string ? this : string { + // Error: No narrowing because parameter is optional but `T` doesn't allow for undefined + name(name?: T): T extends string ? this : T extends undefined ? string : never { if (typeof name === 'undefined') { return this.root.name; ~~~~~~ -!!! error TS2322: Type 'string' is not assignable to type 'T extends string ? this : string'. +!!! error TS2322: Type 'string' is not assignable to type 'T extends string ? this : T extends undefined ? string : never'. } return this; ~~~~~~ -!!! error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. -!!! error TS2322: Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. - } - // Error because parameter is optional? - nameWithError(name?: T): T extends string ? this : string { - return this; // Error: Investigate error message - ~~~~~~ -!!! error TS2322: Type 'this' is not assignable to type 'T extends string ? this : string'. -!!! error TS2322: Type 'Unnamed' is not assignable to type 'T extends string ? this : string'. +!!! error TS2322: Type 'this' is not assignable to type 'T extends string ? this : T extends undefined ? string : never'. +!!! error TS2322: Type 'Unnamed' is not assignable to type 'T extends string ? this : T extends undefined ? string : never'. } // Good conditional - name2(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { + name2(name?: T): T extends string ? this : T extends undefined ? string : never { if (typeof name === 'undefined') { return this.root.name; // Ok } @@ -245,7 +228,7 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ } // Good conditional, wrong return expressions - name3(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { + name3(name?: T): T extends string ? this : T extends undefined ? string : never { if (typeof name === 'undefined') { return this; // Error ~~~~~~ @@ -260,32 +243,36 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ } } + // Conditional expressions interface Aa { 1: number; 2: string; - 3: string; + 3: boolean; } function trivialConditional(x: T): Aa[T] { if (x !== 1) { - return x === 2 ? "" : `${x}`; + return x === 2 ? "" : true; } else { return 0; } } - // Conditional expressions function conditional(x: T): - T extends true ? 1 : T extends false ? 2 : 1 | 2 { + T extends true ? 1 : T extends false ? 2 : never { return x ? 1 : 2; // Ok } - function contextualConditional(x: T): T extends "a" ? "a" : T extends "b" ? number : "a" | number { + function contextualConditional( + x: T + ): T extends "a" ? "a" : T extends "b" ? number : never { return x === "a" ? x : parseInt(x); // Ok } - function conditionalWithError(x: T): T extends "a" ? number : T extends "b" ? string : number | string { + function conditionalWithError( + x: T + ): T extends "a" ? number : T extends "b" ? string : never { return x === "a" ? x : parseInt(x); // Error ~ !!! error TS2322: Type 'string' is not assignable to type 'number'. @@ -293,7 +280,7 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ !!! error TS2322: Type 'number' is not assignable to type 'string'. } - // Multiple reductions + // Multiple indexed type reductions interface BB { "a": number; [y: number]: string; @@ -314,44 +301,50 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ return undefined as never; } - // Substitution types are not narrowed? - function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'. + // Substitution types are not narrowed + function subsCond( + x: T, + ): T extends 1 | 2 + ? T extends 1 + ? string + : T extends 2 + ? boolean + : never + : T extends 3 + ? number + : never { if (x === 1) { return ""; ~~~~~~ -!!! error TS2322: Type '""' is not assignable to type 'T extends 1 ? string : boolean'. - } - } - - // Unsafe: supertype problem - declare function q(x: object): x is { b: number }; - function foo(x: T): T extends { a: string } ? number : (string | number) { - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'. - if (q(x)) { - x.b; - return ""; +!!! error TS2322: Type '""' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. + } else if (x == 2) { + return true; ~~~~~~ -!!! error TS2322: Type 'string' is not assignable to type 'T extends { a: string; } ? number : string | number'. +!!! error TS2322: Type 'true' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. } + return 3; + ~~~~~~ +!!! error TS2322: Type '3' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. } - let y = { a: "", b: 1 } - const r = foo<{ a: string }>(y); // number - function lessBadFoo(x: T): T extends { b: number } ? string : T extends { a: string } ? number : (string | number) { + // Unsafe: check types overlap + declare function q(x: object): x is { b: number }; + function foo( + x: T, + ): T extends { a: string } ? number : T extends { b: number } ? string : never { if (q(x)) { x.b; return ""; } - return 2; + x.a; + return 1; } - const r2 = lessBadFoo<{ a: string }>(y); // number, bad + let y = { a: "", b: 1 } + const r = foo<{ a: string }>(y); // type says number but actually string - type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; + type HelperCond = T extends A ? R1 : T extends B ? R2 : never; // We don't narrow the return type because the conditionals are not distributive function foo2(x: U, y: V): @@ -376,7 +369,9 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ // From https://github.com/microsoft/TypeScript/issues/24929#issue-332087943 declare function isString(s: unknown): s is string; // capitalize a string or each element of an array of strings - function capitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { + function capitalize( + input: T + ): T extends string[] ? string[] : T extends string ? string : never { if (isString(input)) { return input[0].toUpperCase() + input.slice(1); // Ok } else { @@ -384,85 +379,107 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ } } - function badCapitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { + function badCapitalize( + input: T + ): T extends string[] ? string[] : T extends string ? string : never { if (isString(input)) { return input[0].toUpperCase() + input.slice(1); // Ok } else { - return input[0].toUpperCase() + input.slice(1); // Bad + return input[0].toUpperCase() + input.slice(1); // Bad, error ~~~~~~ !!! error TS2322: Type 'string' is not assignable to type 'string[]'. } } - // >> TODO: test non-tail recursive conditionals - - function voidRet(x: T): T extends {} ? void : T extends undefined ? number : void | number { + // No narrowing because conditional's extends type is different from type parameter constraint types + function voidRet( + x: T + ): T extends {} ? void : T extends undefined ? number : never { if (x) { - return; // Ok + return; + ~~~~~~ +!!! error TS2322: Type 'undefined' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'. } - return 1; // Ok - } - - function woo(x: T, y: U): - T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 { + return 1; + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'. + } + + // Multiple type parameters at once + function woo( + x: T, + y: U, + ): T extends string + ? U extends string + ? 1 + : U extends number + ? 2 + : never + : T extends number + ? U extends number + ? 3 + : U extends string + ? 4 + : never + : never { if (typeof x === "number" && typeof y === "string") { - return 1; // Error + return 1; // Good error ~~~~~~ -!!! error TS2322: Type '1' is not assignable to type 'T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4'. +!!! error TS2322: Type '1' is not assignable to type '4'. } return undefined as any; } - function ttt(x: T, y: U): - T extends string - ? number extends string - ? 6 - : U extends string - ? 1 - : 2 - : U extends number - ? 3 - : 4 { - if (typeof x === "string" && typeof y === "string") { - return 1; // Ok + function ttt( + x: T, + y: U, + ): T extends string + ? U extends string + ? 1 + : U extends number + ? 2 + : never + : T extends number + ? U extends number + ? 3 + : U extends string + ? 4 + : never + : never { + if (typeof x === "number" && typeof y === "string") { + return 4; // Ok } return undefined as any; } - // We won't narrow `T` because it refers to the type of an optional parameter but doesn't allow for narrowing with `undefined` - function opt(x?: T): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { - if (typeof x === "undefined") { - x; - return 2; - ~~~~~~ -!!! error TS2322: Type '2' is not assignable to type 'T extends string ? 1 : T extends undefined ? 2 : 1 | 2'. - } - return 1; - ~~~~~~ -!!! error TS2322: Type '1' is not assignable to type 'T extends string ? 1 : T extends undefined ? 2 : 1 | 2'. - } - // Shadowing of the narrowed reference - function g(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { + function shadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { if (true) { let x: number = Math.random() ? 1 : 2; if (x === 1) { return 1; // Error ~~~~~~ -!!! error TS2322: Type 'number' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : 1 | 2'. +!!! error TS2322: Type 'number' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : never'. } return ""; // Error ~~~~~~ -!!! error TS2322: Type 'string' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : 1 | 2'. +!!! error TS2322: Type 'string' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : never'. + } + } + + function noShadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { + if (true) { + if (x === 1) { + return 1; // Ok + } + return ""; // Ok } } // If the narrowing reference is out of scope, we simply won't narrow its type declare let someX: boolean; - function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'. + function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : never { if ((true)) { const someX = opts.a; if (someX) { // We narrow `someX` and the return type here @@ -472,11 +489,13 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ if (!someX) { // This is a different `someX`, so we don't narrow here return 2; ~~~~~~ -!!! error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. +!!! error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. } + + return undefined as any; } - function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { + function moreShadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { if (x === 2) { let x: number = Math.random() ? 1 : 2; if (x === 1) { @@ -489,21 +508,22 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ return 0; // Ok } - function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : string | boolean { + // This would be unsafe to narrow due to `infer` type. + function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : never { if (typeof x === "number") { return true; ~~~~~~ -!!! error TS2322: Type 'true' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. +!!! error TS2322: Type 'true' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : never'. } return ""; ~~~~~~ -!!! error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : string | boolean'. +!!! error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : never'. } const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. // Ok - async function abool(x: T): Promise { + async function abool(x: T): Promise { if (x) { return 1; } @@ -511,7 +531,7 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ } // Ok - function* bbool(x: T): Generator { + function* bbool(x: T): Generator { yield 3; if (x) { return 1; @@ -520,15 +540,15 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ } // We don't do the same type of narrowing for `yield` statements - function* cbool(x: T): Generator { + function* cbool(x: T): Generator { if (x) { yield 1; ~ -!!! error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. +!!! error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. } yield 2; ~ -!!! error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 1 | 2'. +!!! error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. return 0; } @@ -538,13 +558,17 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ } type ConditionalReturnType | undefined> = - EOp extends Operation ? R : EOp extends undefined ? T | R : T | R; + EOp extends Operation ? R : EOp extends undefined ? T | R : never; - class ConditionalOperation | undefined> extends Operation> { + class ConditionalOperation< + T, + R, + EOp extends Operation | undefined, + > extends Operation> { constructor( private predicate: (value: T) => boolean, private thenOp: Operation, - private elseOp?: EOp + private elseOp?: EOp, ) { super(); } @@ -554,7 +578,7 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it ~~~~~~ !!! error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. - } else if (typeof this.elseOp !== 'undefined') { + } else if (typeof this.elseOp !== "undefined") { return this.elseOp.perform(t); // Ok } else { return t; // Ok @@ -564,7 +588,7 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ // Optional tuple element function tupl(x: [string, some?: T]): - T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 { + T extends true ? 1 : T extends false | undefined ? 2 : never { if (x[1]) { return 1; } @@ -572,17 +596,24 @@ dependentReturnType1.ts(428,13): error TS2322: Type 'R' is not assignable to typ } // Return conditional expressions with parentheses - function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { + function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : never { return (opts.x ? (1) : 2); } function returnStuff2(opts: { x: T }): - T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 { + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never { return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); } - // If the return type is written wrong, it still type checks - function returnStuff3(opts: { x: T }): - T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" { - return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); + // If the conditional type's input is `never`, then it resolves to `never`: + function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : never { + if (x === true) { + return 1; + } + if (x === false) { + return 2; + } + return 1; + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'never'. } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 882077c986f5d..edbdf95e0c8a7 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -142,8 +142,8 @@ interface Four { g: "g"; >g : Symbol(Four.g, Decl(dependentReturnType1.ts, 62, 11)) } - -function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { // Badly written conditional +// Badly written conditional return type, will not trigger narrowing +function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { >f10 : Symbol(f10, Decl(dependentReturnType1.ts, 64, 1)) >T : Symbol(T, Decl(dependentReturnType1.ts, 66, 13)) >x : Symbol(x, Decl(dependentReturnType1.ts, 66, 38)) @@ -160,477 +160,464 @@ function f10(x: T): T extends 1 ? One : T extends 2 ? T >x : Symbol(x, Decl(dependentReturnType1.ts, 66, 38)) >x : Symbol(x, Decl(dependentReturnType1.ts, 66, 38)) - return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Error >a : Symbol(a, Decl(dependentReturnType1.ts, 68, 16)) >b : Symbol(b, Decl(dependentReturnType1.ts, 68, 24)) >c : Symbol(c, Decl(dependentReturnType1.ts, 68, 32)) >d : Symbol(d, Decl(dependentReturnType1.ts, 68, 40)) >e : Symbol(e, Decl(dependentReturnType1.ts, 68, 48)) >f : Symbol(f, Decl(dependentReturnType1.ts, 68, 56)) - - return { a: "a" }; // Error ->a : Symbol(a, Decl(dependentReturnType1.ts, 69, 16)) } - // Excess property becomes a problem with the change, - // because we now check assignability to a narrower type... return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error ->a : Symbol(a, Decl(dependentReturnType1.ts, 73, 12)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 73, 20)) ->c : Symbol(c, Decl(dependentReturnType1.ts, 73, 28)) ->d : Symbol(d, Decl(dependentReturnType1.ts, 73, 36)) ->e : Symbol(e, Decl(dependentReturnType1.ts, 73, 44)) ->f : Symbol(f, Decl(dependentReturnType1.ts, 73, 52)) ->g : Symbol(g, Decl(dependentReturnType1.ts, 73, 60)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 70, 12)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 70, 20)) +>c : Symbol(c, Decl(dependentReturnType1.ts, 70, 28)) +>d : Symbol(d, Decl(dependentReturnType1.ts, 70, 36)) +>e : Symbol(e, Decl(dependentReturnType1.ts, 70, 44)) +>f : Symbol(f, Decl(dependentReturnType1.ts, 70, 52)) +>g : Symbol(g, Decl(dependentReturnType1.ts, 70, 60)) } - -function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four { // Well written conditional ->f101 : Symbol(f101, Decl(dependentReturnType1.ts, 74, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 76, 14)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 76, 39)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 76, 14)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 76, 14)) ->One : Symbol(One, Decl(dependentReturnType1.ts, 36, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 76, 14)) ->Two : Symbol(Two, Decl(dependentReturnType1.ts, 43, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 76, 14)) ->Three : Symbol(Three, Decl(dependentReturnType1.ts, 50, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 76, 14)) ->Four : Symbol(Four, Decl(dependentReturnType1.ts, 57, 1)) +// Well written conditional +function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : never { +>f101 : Symbol(f101, Decl(dependentReturnType1.ts, 71, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 73, 14)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 73, 39)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 73, 14)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 73, 14)) >One : Symbol(One, Decl(dependentReturnType1.ts, 36, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 73, 14)) >Two : Symbol(Two, Decl(dependentReturnType1.ts, 43, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 73, 14)) >Three : Symbol(Three, Decl(dependentReturnType1.ts, 50, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 73, 14)) >Four : Symbol(Four, Decl(dependentReturnType1.ts, 57, 1)) if (x === 1 || x === 2) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 76, 39)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 76, 39)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 73, 39)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 73, 39)) return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok ->a : Symbol(a, Decl(dependentReturnType1.ts, 78, 16)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 78, 24)) ->c : Symbol(c, Decl(dependentReturnType1.ts, 78, 32)) ->d : Symbol(d, Decl(dependentReturnType1.ts, 78, 40)) ->e : Symbol(e, Decl(dependentReturnType1.ts, 78, 48)) ->f : Symbol(f, Decl(dependentReturnType1.ts, 78, 56)) - - return { a: "a" }; // Error ->a : Symbol(a, Decl(dependentReturnType1.ts, 79, 16)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 75, 16)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 75, 24)) +>c : Symbol(c, Decl(dependentReturnType1.ts, 75, 32)) +>d : Symbol(d, Decl(dependentReturnType1.ts, 75, 40)) +>e : Symbol(e, Decl(dependentReturnType1.ts, 75, 48)) +>f : Symbol(f, Decl(dependentReturnType1.ts, 75, 56)) } // Excess property becomes a problem with the change, // because we now check assignability to a narrower type... - return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error ->a : Symbol(a, Decl(dependentReturnType1.ts, 83, 12)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 83, 20)) ->c : Symbol(c, Decl(dependentReturnType1.ts, 83, 28)) ->d : Symbol(d, Decl(dependentReturnType1.ts, 83, 36)) ->e : Symbol(e, Decl(dependentReturnType1.ts, 83, 44)) ->f : Symbol(f, Decl(dependentReturnType1.ts, 83, 52)) ->g : Symbol(g, Decl(dependentReturnType1.ts, 83, 60)) + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // EPC Error +>a : Symbol(a, Decl(dependentReturnType1.ts, 79, 12)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 79, 20)) +>c : Symbol(c, Decl(dependentReturnType1.ts, 79, 28)) +>d : Symbol(d, Decl(dependentReturnType1.ts, 79, 36)) +>e : Symbol(e, Decl(dependentReturnType1.ts, 79, 44)) +>f : Symbol(f, Decl(dependentReturnType1.ts, 79, 52)) +>g : Symbol(g, Decl(dependentReturnType1.ts, 79, 60)) } // Asymmetry function conditionalProducingIf( ->conditionalProducingIf : Symbol(conditionalProducingIf, Decl(dependentReturnType1.ts, 84, 1)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 87, 48)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 87, 57)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 67)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) +>conditionalProducingIf : Symbol(conditionalProducingIf, Decl(dependentReturnType1.ts, 80, 1)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 83, 39)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 83, 48)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 83, 57)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 83, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 83, 39)) arg: Arg, ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 67)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 83, 98)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 83, 67)) cond: (arg: LeftIn | RightIn) => arg is LeftIn, ->cond : Symbol(cond, Decl(dependentReturnType1.ts, 88, 13)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 89, 11)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 89, 11)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) +>cond : Symbol(cond, Decl(dependentReturnType1.ts, 84, 13)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 85, 11)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 83, 39)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 85, 11)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) produceLeftOut: (arg: LeftIn) => LeftOut, ->produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 89, 51)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 90, 21)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 87, 48)) +>produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 85, 51)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 86, 21)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 83, 48)) produceRightOut: (arg: RightIn) => RightOut): ->produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 90, 45)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 91, 22)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 87, 57)) - - Arg extends LeftIn ? LeftOut : RightOut ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 67)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 87, 48)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 87, 57)) +>produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 86, 45)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 22)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 83, 39)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 83, 57)) + + Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 83, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 83, 48)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 83, 67)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 83, 39)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 83, 57)) { type OK = Arg extends LeftIn ? LeftOut : RightOut; ->OK : Symbol(OK, Decl(dependentReturnType1.ts, 93, 1)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 67)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 87, 48)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 87, 57)) +>OK : Symbol(OK, Decl(dependentReturnType1.ts, 89, 1)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 83, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 83, 48)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 83, 57)) if (cond(arg)) { ->cond : Symbol(cond, Decl(dependentReturnType1.ts, 88, 13)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) +>cond : Symbol(cond, Decl(dependentReturnType1.ts, 84, 13)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 83, 98)) return produceLeftOut(arg); // The narrowed conditional return type has deferred resolution, so this doesn't work. ->produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 89, 51)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) +>produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 85, 51)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 83, 98)) } else { return produceRightOut(arg as RightIn); // Error: Doesn't work because we can't narrow `arg` to `Arg & RightIn` here ->produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 90, 45)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) +>produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 86, 45)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 83, 98)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 83, 39)) } } interface Animal { ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 100, 1)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 96, 1)) name: string; ->name : Symbol(Animal.name, Decl(dependentReturnType1.ts, 102, 18)) +>name : Symbol(Animal.name, Decl(dependentReturnType1.ts, 98, 18)) } interface Dog extends Animal { ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 104, 1)) ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 100, 1)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 100, 1)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 96, 1)) bark: () => string; ->bark : Symbol(Dog.bark, Decl(dependentReturnType1.ts, 106, 30)) +>bark : Symbol(Dog.bark, Decl(dependentReturnType1.ts, 102, 30)) } -// This is unsafe +// This would be unsafe to narrow. declare function isDog(x: Animal): x is Dog; ->isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 108, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 111, 23)) ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 100, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 111, 23)) ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 104, 1)) +>isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 104, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 107, 23)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 96, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 107, 23)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 100, 1)) declare function doggy(x: Dog): number; ->doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 111, 44)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 112, 23)) ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 104, 1)) +>doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 107, 44)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 108, 23)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 100, 1)) function f12(x: T): T extends Dog ? number : string { ->f12 : Symbol(f12, Decl(dependentReturnType1.ts, 112, 39)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 113, 13)) ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 100, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 113, 31)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 113, 13)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 113, 13)) ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 104, 1)) +>f12 : Symbol(f12, Decl(dependentReturnType1.ts, 108, 39)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 109, 13)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 96, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 109, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 109, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 109, 13)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 100, 1)) if (isDog(x)) { // `x` has type `T & Dog` here ->isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 108, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 113, 31)) +>isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 104, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 109, 31)) - return doggy(x); // The narrowed conditional return type has deferred resolution, so this doesn't work. ->doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 111, 44)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 113, 31)) + return doggy(x); +>doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 107, 44)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 109, 31)) } return ""; // Error: Should not work because we can't express "not a Dog" in the type system } // Cannot narrow `keyof` too eagerly or something like the below breaks function f(entry: EntryId): Entry[EntryId] { ->f : Symbol(f, Decl(dependentReturnType1.ts, 118, 1)) ->Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 121, 11)) ->index : Symbol(index, Decl(dependentReturnType1.ts, 121, 28)) ->EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 121, 63)) ->Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 121, 11)) ->entry : Symbol(entry, Decl(dependentReturnType1.ts, 121, 93)) ->EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 121, 63)) ->Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 121, 11)) ->EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 121, 63)) +>f : Symbol(f, Decl(dependentReturnType1.ts, 114, 1)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 117, 11)) +>index : Symbol(index, Decl(dependentReturnType1.ts, 117, 28)) +>EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 117, 63)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 117, 11)) +>entry : Symbol(entry, Decl(dependentReturnType1.ts, 117, 93)) +>EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 117, 63)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 117, 11)) +>EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 117, 63)) const entries = {} as Entry; ->entries : Symbol(entries, Decl(dependentReturnType1.ts, 122, 9)) ->Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 121, 11)) +>entries : Symbol(entries, Decl(dependentReturnType1.ts, 118, 9)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 117, 11)) return entries[entry]; ->entries : Symbol(entries, Decl(dependentReturnType1.ts, 122, 9)) ->entry : Symbol(entry, Decl(dependentReturnType1.ts, 121, 93)) +>entries : Symbol(entries, Decl(dependentReturnType1.ts, 118, 9)) +>entry : Symbol(entry, Decl(dependentReturnType1.ts, 117, 93)) } // Works the same as before declare function takeA(val: 'A'): void; ->takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 124, 1)) ->val : Symbol(val, Decl(dependentReturnType1.ts, 127, 23)) +>takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 120, 1)) +>val : Symbol(val, Decl(dependentReturnType1.ts, 123, 23)) export function bounceAndTakeIfA(value: AB): AB { ->bounceAndTakeIfA : Symbol(bounceAndTakeIfA, Decl(dependentReturnType1.ts, 127, 39)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 128, 33)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 128, 55)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 128, 33)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 128, 33)) +>bounceAndTakeIfA : Symbol(bounceAndTakeIfA, Decl(dependentReturnType1.ts, 123, 39)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 124, 33)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 124, 55)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 124, 33)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 124, 33)) if (value === 'A') { ->value : Symbol(value, Decl(dependentReturnType1.ts, 128, 55)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 124, 55)) takeA(value); ->takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 124, 1)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 128, 55)) +>takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 120, 1)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 124, 55)) takeAB(value); ->takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 135, 17)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 128, 55)) +>takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 131, 17)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 124, 55)) return value; ->value : Symbol(value, Decl(dependentReturnType1.ts, 128, 55)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 124, 55)) } return value; ->value : Symbol(value, Decl(dependentReturnType1.ts, 128, 55)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 124, 55)) function takeAB(val: AB): void {} ->takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 135, 17)) ->val : Symbol(val, Decl(dependentReturnType1.ts, 136, 20)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 128, 33)) +>takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 131, 17)) +>val : Symbol(val, Decl(dependentReturnType1.ts, 132, 20)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 124, 33)) } // Works the same as before export function bbb(value: AB): "a" { ->bbb : Symbol(bbb, Decl(dependentReturnType1.ts, 137, 1)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 140, 20)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 140, 42)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 140, 20)) +>bbb : Symbol(bbb, Decl(dependentReturnType1.ts, 133, 1)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 136, 20)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 136, 42)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 136, 20)) if (value === "a") { ->value : Symbol(value, Decl(dependentReturnType1.ts, 140, 42)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 136, 42)) return value; ->value : Symbol(value, Decl(dependentReturnType1.ts, 140, 42)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 136, 42)) } return "a"; } class Unnamed { ->Unnamed : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) +>Unnamed : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) root!: { name: string }; ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) - - // Error because parameter is optional - name(name?: T): T extends string ? this : string { ->name : Symbol(Unnamed.name, Decl(dependentReturnType1.ts, 148, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 150, 9)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 150, 27)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 150, 9)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 150, 9)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) + + // Error: No narrowing because parameter is optional but `T` doesn't allow for undefined + name(name?: T): T extends string ? this : T extends undefined ? string : never { +>name : Symbol(Unnamed.name, Decl(dependentReturnType1.ts, 144, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 146, 9)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 146, 27)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 146, 9)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 146, 9)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 146, 9)) if (typeof name === 'undefined') { ->name : Symbol(name, Decl(dependentReturnType1.ts, 150, 27)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 146, 27)) return this.root.name; ->this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) ->this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) } return this; ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) - } - // Error because parameter is optional? - nameWithError(name?: T): T extends string ? this : string { ->nameWithError : Symbol(Unnamed.nameWithError, Decl(dependentReturnType1.ts, 155, 5)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 157, 18)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 157, 36)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 157, 18)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 157, 18)) - - return this; // Error: Investigate error message ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) } // Good conditional - name2(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { ->name2 : Symbol(Unnamed.name2, Decl(dependentReturnType1.ts, 159, 5)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 162, 10)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 162, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 162, 10)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 162, 10)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 162, 10)) + name2(name?: T): T extends string ? this : T extends undefined ? string : never { +>name2 : Symbol(Unnamed.name2, Decl(dependentReturnType1.ts, 151, 5)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 154, 10)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 154, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 154, 10)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 154, 10)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 154, 10)) if (typeof name === 'undefined') { ->name : Symbol(name, Decl(dependentReturnType1.ts, 162, 40)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 154, 40)) return this.root.name; // Ok ->this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) ->this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) } this.root.name = name; ->this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) ->this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 162, 40)) +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 154, 40)) return this; // Ok ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) } // Good conditional, wrong return expressions - name3(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { ->name3 : Symbol(Unnamed.name3, Decl(dependentReturnType1.ts, 168, 5)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 171, 10)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 171, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 171, 10)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 171, 10)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 171, 10)) + name3(name?: T): T extends string ? this : T extends undefined ? string : never { +>name3 : Symbol(Unnamed.name3, Decl(dependentReturnType1.ts, 160, 5)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 163, 10)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 163, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 163, 10)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 163, 10)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 163, 10)) if (typeof name === 'undefined') { ->name : Symbol(name, Decl(dependentReturnType1.ts, 171, 40)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 163, 40)) return this; // Error ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) } this.root.name = name; ->this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) ->this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 145, 1)) ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 147, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 148, 12)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 171, 40)) +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 163, 40)) return name; // Error ->name : Symbol(name, Decl(dependentReturnType1.ts, 171, 40)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 163, 40)) } } +// Conditional expressions interface Aa { ->Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 178, 1)) +>Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 170, 1)) 1: number; ->1 : Symbol(Aa[1], Decl(dependentReturnType1.ts, 180, 14)) +>1 : Symbol(Aa[1], Decl(dependentReturnType1.ts, 173, 14)) 2: string; ->2 : Symbol(Aa[2], Decl(dependentReturnType1.ts, 181, 14)) +>2 : Symbol(Aa[2], Decl(dependentReturnType1.ts, 174, 14)) - 3: string; ->3 : Symbol(Aa[3], Decl(dependentReturnType1.ts, 182, 14)) + 3: boolean; +>3 : Symbol(Aa[3], Decl(dependentReturnType1.ts, 175, 14)) } function trivialConditional(x: T): Aa[T] { ->trivialConditional : Symbol(trivialConditional, Decl(dependentReturnType1.ts, 184, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 186, 28)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 186, 49)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 186, 28)) ->Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 178, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 186, 28)) +>trivialConditional : Symbol(trivialConditional, Decl(dependentReturnType1.ts, 177, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 179, 28)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 179, 49)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 179, 28)) +>Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 170, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 179, 28)) if (x !== 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 186, 49)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 179, 49)) - return x === 2 ? "" : `${x}`; ->x : Symbol(x, Decl(dependentReturnType1.ts, 186, 49)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 186, 49)) + return x === 2 ? "" : true; +>x : Symbol(x, Decl(dependentReturnType1.ts, 179, 49)) } else { return 0; } } -// Conditional expressions function conditional(x: T): ->conditional : Symbol(conditional, Decl(dependentReturnType1.ts, 193, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 196, 21)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 196, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 196, 21)) +>conditional : Symbol(conditional, Decl(dependentReturnType1.ts, 186, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 188, 21)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 188, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 188, 21)) - T extends true ? 1 : T extends false ? 2 : 1 | 2 { ->T : Symbol(T, Decl(dependentReturnType1.ts, 196, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 196, 21)) + T extends true ? 1 : T extends false ? 2 : never { +>T : Symbol(T, Decl(dependentReturnType1.ts, 188, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 188, 21)) return x ? 1 : 2; // Ok ->x : Symbol(x, Decl(dependentReturnType1.ts, 196, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 188, 40)) } -function contextualConditional(x: T): T extends "a" ? "a" : T extends "b" ? number : "a" | number { ->contextualConditional : Symbol(contextualConditional, Decl(dependentReturnType1.ts, 199, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 201, 31)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 201, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 201, 31)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 201, 31)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 201, 31)) +function contextualConditional( +>contextualConditional : Symbol(contextualConditional, Decl(dependentReturnType1.ts, 191, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 193, 31)) + + x: T +>x : Symbol(x, Decl(dependentReturnType1.ts, 193, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 193, 31)) + +): T extends "a" ? "a" : T extends "b" ? number : never { +>T : Symbol(T, Decl(dependentReturnType1.ts, 193, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 193, 31)) return x === "a" ? x : parseInt(x); // Ok ->x : Symbol(x, Decl(dependentReturnType1.ts, 201, 52)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 201, 52)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 193, 52)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 193, 52)) >parseInt : Symbol(parseInt, Decl(lib.es5.d.ts, --, --)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 201, 52)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 193, 52)) } -function conditionalWithError(x: T): T extends "a" ? number : T extends "b" ? string : number | string { ->conditionalWithError : Symbol(conditionalWithError, Decl(dependentReturnType1.ts, 203, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 205, 30)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 205, 51)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 205, 30)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 205, 30)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 205, 30)) +function conditionalWithError( +>conditionalWithError : Symbol(conditionalWithError, Decl(dependentReturnType1.ts, 197, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 199, 30)) + + x: T +>x : Symbol(x, Decl(dependentReturnType1.ts, 199, 51)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 199, 30)) + +): T extends "a" ? number : T extends "b" ? string : never { +>T : Symbol(T, Decl(dependentReturnType1.ts, 199, 30)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 199, 30)) return x === "a" ? x : parseInt(x); // Error ->x : Symbol(x, Decl(dependentReturnType1.ts, 205, 51)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 205, 51)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 199, 51)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 199, 51)) >parseInt : Symbol(parseInt, Decl(lib.es5.d.ts, --, --)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 205, 51)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 199, 51)) } -// Multiple reductions +// Multiple indexed type reductions interface BB { ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 207, 1)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 203, 1)) "a": number; ->"a" : Symbol(BB["a"], Decl(dependentReturnType1.ts, 210, 14)) +>"a" : Symbol(BB["a"], Decl(dependentReturnType1.ts, 206, 14)) [y: number]: string; ->y : Symbol(y, Decl(dependentReturnType1.ts, 212, 5)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 208, 5)) } interface AA { ->AA : Symbol(AA, Decl(dependentReturnType1.ts, 213, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 215, 13)) ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 207, 1)) +>AA : Symbol(AA, Decl(dependentReturnType1.ts, 209, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 211, 13)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 203, 1)) "c": BB[T]; ->"c" : Symbol(AA["c"], Decl(dependentReturnType1.ts, 215, 34)) ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 207, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 215, 13)) +>"c" : Symbol(AA["c"], Decl(dependentReturnType1.ts, 211, 34)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 203, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 211, 13)) "d": boolean, ->"d" : Symbol(AA["d"], Decl(dependentReturnType1.ts, 216, 15)) +>"d" : Symbol(AA["d"], Decl(dependentReturnType1.ts, 212, 15)) } function reduction(x: T, y: U): AA[U] { ->reduction : Symbol(reduction, Decl(dependentReturnType1.ts, 218, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 220, 19)) ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 207, 1)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 220, 38)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 220, 60)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 220, 19)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 220, 65)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 220, 38)) ->AA : Symbol(AA, Decl(dependentReturnType1.ts, 213, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 220, 19)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 220, 38)) +>reduction : Symbol(reduction, Decl(dependentReturnType1.ts, 214, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 216, 19)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 203, 1)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 216, 38)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 216, 60)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 216, 19)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 216, 65)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 216, 38)) +>AA : Symbol(AA, Decl(dependentReturnType1.ts, 209, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 216, 19)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 216, 38)) if (y === "c" && x === "a") { ->y : Symbol(y, Decl(dependentReturnType1.ts, 220, 65)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 220, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 216, 65)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 216, 60)) // AA[U='c'] -> BB[T] // BB[T='a'] -> number @@ -641,145 +628,146 @@ function reduction(x: T, y: U): AA[U >undefined : Symbol(undefined) } -// Substitution types are not narrowed? -function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { ->subsCond : Symbol(subsCond, Decl(dependentReturnType1.ts, 228, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 231, 18)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 231, 39)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 231, 18)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 231, 18)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 231, 18)) +// Substitution types are not narrowed +function subsCond( +>subsCond : Symbol(subsCond, Decl(dependentReturnType1.ts, 224, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 227, 18)) - if (x === 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 231, 39)) + x: T, +>x : Symbol(x, Decl(dependentReturnType1.ts, 227, 39)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 227, 18)) - return ""; - } -} +): T extends 1 | 2 +>T : Symbol(T, Decl(dependentReturnType1.ts, 227, 18)) -// Unsafe: supertype problem -declare function q(x: object): x is { b: number }; ->q : Symbol(q, Decl(dependentReturnType1.ts, 235, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 238, 19)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 238, 19)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 238, 37)) - -function foo(x: T): T extends { a: string } ? number : (string | number) { ->foo : Symbol(foo, Decl(dependentReturnType1.ts, 238, 50)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 239, 13)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 239, 24)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 239, 40)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 239, 54)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 239, 13)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 239, 13)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 239, 72)) + ? T extends 1 +>T : Symbol(T, Decl(dependentReturnType1.ts, 227, 18)) - if (q(x)) { ->q : Symbol(q, Decl(dependentReturnType1.ts, 235, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 239, 54)) + ? string + : T extends 2 +>T : Symbol(T, Decl(dependentReturnType1.ts, 227, 18)) - x.b; ->x.b : Symbol(b, Decl(dependentReturnType1.ts, 239, 40)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 239, 54)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 239, 40)) + ? boolean + : never + : T extends 3 +>T : Symbol(T, Decl(dependentReturnType1.ts, 227, 18)) + + ? number + : never { + if (x === 1) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 227, 39)) return ""; + } else if (x == 2) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 227, 39)) + + return true; } + return 3; } -let y = { a: "", b: 1 } ->y : Symbol(y, Decl(dependentReturnType1.ts, 246, 3)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 246, 9)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 246, 16)) - -const r = foo<{ a: string }>(y); // number ->r : Symbol(r, Decl(dependentReturnType1.ts, 247, 5)) ->foo : Symbol(foo, Decl(dependentReturnType1.ts, 238, 50)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 247, 15)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 246, 3)) - -function lessBadFoo(x: T): T extends { b: number } ? string : T extends { a: string } ? number : (string | number) { ->lessBadFoo : Symbol(lessBadFoo, Decl(dependentReturnType1.ts, 247, 32)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 249, 20)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 249, 31)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 249, 47)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 249, 61)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 249, 20)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 249, 20)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 249, 79)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 249, 20)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 249, 114)) + +// Unsafe: check types overlap +declare function q(x: object): x is { b: number }; +>q : Symbol(q, Decl(dependentReturnType1.ts, 244, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 248, 19)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 248, 19)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 248, 37)) + +function foo( +>foo : Symbol(foo, Decl(dependentReturnType1.ts, 248, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 249, 13)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 249, 24)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 249, 40)) + + x: T, +>x : Symbol(x, Decl(dependentReturnType1.ts, 249, 54)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 249, 13)) + +): T extends { a: string } ? number : T extends { b: number } ? string : never { +>T : Symbol(T, Decl(dependentReturnType1.ts, 249, 13)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 251, 14)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 249, 13)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 251, 49)) if (q(x)) { ->q : Symbol(q, Decl(dependentReturnType1.ts, 235, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 249, 61)) +>q : Symbol(q, Decl(dependentReturnType1.ts, 244, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 249, 54)) x.b; ->x.b : Symbol(b, Decl(dependentReturnType1.ts, 249, 47)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 249, 61)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 249, 47)) +>x.b : Symbol(b, Decl(dependentReturnType1.ts, 249, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 249, 54)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 249, 40)) return ""; } - return 2; + x.a; +>x.a : Symbol(a, Decl(dependentReturnType1.ts, 249, 24)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 249, 54)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 249, 24)) + + return 1; } -const r2 = lessBadFoo<{ a: string }>(y); // number, bad ->r2 : Symbol(r2, Decl(dependentReturnType1.ts, 257, 5)) ->lessBadFoo : Symbol(lessBadFoo, Decl(dependentReturnType1.ts, 247, 32)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 257, 23)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 246, 3)) - -type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; ->HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 257, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 259, 16)) ->A : Symbol(A, Decl(dependentReturnType1.ts, 259, 18)) ->R1 : Symbol(R1, Decl(dependentReturnType1.ts, 259, 21)) ->B : Symbol(B, Decl(dependentReturnType1.ts, 259, 25)) ->R2 : Symbol(R2, Decl(dependentReturnType1.ts, 259, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 259, 16)) ->A : Symbol(A, Decl(dependentReturnType1.ts, 259, 18)) ->R1 : Symbol(R1, Decl(dependentReturnType1.ts, 259, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 259, 16)) ->B : Symbol(B, Decl(dependentReturnType1.ts, 259, 25)) ->R2 : Symbol(R2, Decl(dependentReturnType1.ts, 259, 28)) ->R1 : Symbol(R1, Decl(dependentReturnType1.ts, 259, 21)) ->R2 : Symbol(R2, Decl(dependentReturnType1.ts, 259, 28)) +let y = { a: "", b: 1 } +>y : Symbol(y, Decl(dependentReturnType1.ts, 260, 3)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 260, 9)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 260, 16)) + +const r = foo<{ a: string }>(y); // type says number but actually string +>r : Symbol(r, Decl(dependentReturnType1.ts, 261, 5)) +>foo : Symbol(foo, Decl(dependentReturnType1.ts, 248, 50)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 261, 15)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 260, 3)) + +type HelperCond = T extends A ? R1 : T extends B ? R2 : never; +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 261, 32)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 263, 16)) +>A : Symbol(A, Decl(dependentReturnType1.ts, 263, 18)) +>R1 : Symbol(R1, Decl(dependentReturnType1.ts, 263, 21)) +>B : Symbol(B, Decl(dependentReturnType1.ts, 263, 25)) +>R2 : Symbol(R2, Decl(dependentReturnType1.ts, 263, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 263, 16)) +>A : Symbol(A, Decl(dependentReturnType1.ts, 263, 18)) +>R1 : Symbol(R1, Decl(dependentReturnType1.ts, 263, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 263, 16)) +>B : Symbol(B, Decl(dependentReturnType1.ts, 263, 25)) +>R2 : Symbol(R2, Decl(dependentReturnType1.ts, 263, 28)) // We don't narrow the return type because the conditionals are not distributive function foo2(x: U, y: V): ->foo2 : Symbol(foo2, Decl(dependentReturnType1.ts, 259, 81)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 262, 14)) ->V : Symbol(V, Decl(dependentReturnType1.ts, 262, 40)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 262, 60)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 262, 14)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 262, 65)) ->V : Symbol(V, Decl(dependentReturnType1.ts, 262, 40)) +>foo2 : Symbol(foo2, Decl(dependentReturnType1.ts, 263, 79)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 266, 14)) +>V : Symbol(V, Decl(dependentReturnType1.ts, 266, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 266, 60)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 266, 14)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 266, 65)) +>V : Symbol(V, Decl(dependentReturnType1.ts, 266, 40)) HelperCond<{ x: U, y: V }, ->HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 257, 40)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 263, 16)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 262, 14)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 263, 22)) ->V : Symbol(V, Decl(dependentReturnType1.ts, 262, 40)) +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 261, 32)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 267, 16)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 266, 14)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 267, 22)) +>V : Symbol(V, Decl(dependentReturnType1.ts, 266, 40)) { x: string, y: true }, 1, ->x : Symbol(x, Decl(dependentReturnType1.ts, 264, 9)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 264, 20)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 268, 9)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 268, 20)) { x: number, y: false }, 2> { ->x : Symbol(x, Decl(dependentReturnType1.ts, 265, 9)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 265, 20)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 269, 9)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 269, 20)) if (typeof x === "string" && y === true) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 262, 60)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 262, 65)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 266, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 266, 65)) return 1; // Error } if (typeof x === "number" && y === false) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 262, 60)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 262, 65)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 266, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 266, 65)) return 2; // Error } @@ -788,189 +776,219 @@ function foo2(x: U, y: V): // From https://github.com/microsoft/TypeScript/issues/24929#issue-332087943 declare function isString(s: unknown): s is string; ->isString : Symbol(isString, Decl(dependentReturnType1.ts, 273, 1)) ->s : Symbol(s, Decl(dependentReturnType1.ts, 276, 26)) ->s : Symbol(s, Decl(dependentReturnType1.ts, 276, 26)) +>isString : Symbol(isString, Decl(dependentReturnType1.ts, 277, 1)) +>s : Symbol(s, Decl(dependentReturnType1.ts, 280, 26)) +>s : Symbol(s, Decl(dependentReturnType1.ts, 280, 26)) // capitalize a string or each element of an array of strings -function capitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { ->capitalize : Symbol(capitalize, Decl(dependentReturnType1.ts, 276, 51)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 278, 20)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 278, 49)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 278, 20)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 278, 20)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 278, 20)) +function capitalize( +>capitalize : Symbol(capitalize, Decl(dependentReturnType1.ts, 280, 51)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 282, 20)) + + input: T +>input : Symbol(input, Decl(dependentReturnType1.ts, 282, 49)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 282, 20)) + +): T extends string[] ? string[] : T extends string ? string : never { +>T : Symbol(T, Decl(dependentReturnType1.ts, 282, 20)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 282, 20)) if (isString(input)) { ->isString : Symbol(isString, Decl(dependentReturnType1.ts, 273, 1)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 278, 49)) +>isString : Symbol(isString, Decl(dependentReturnType1.ts, 277, 1)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 282, 49)) return input[0].toUpperCase() + input.slice(1); // Ok >input[0].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 278, 49)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 282, 49)) >toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) >input.slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 278, 49)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 282, 49)) >slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) } else { return input.map(elt => capitalize(elt)); // Ok >input.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 278, 49)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 282, 49)) >map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) ->elt : Symbol(elt, Decl(dependentReturnType1.ts, 282, 25)) ->capitalize : Symbol(capitalize, Decl(dependentReturnType1.ts, 276, 51)) ->elt : Symbol(elt, Decl(dependentReturnType1.ts, 282, 25)) +>elt : Symbol(elt, Decl(dependentReturnType1.ts, 288, 25)) +>capitalize : Symbol(capitalize, Decl(dependentReturnType1.ts, 280, 51)) +>elt : Symbol(elt, Decl(dependentReturnType1.ts, 288, 25)) } } -function badCapitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { ->badCapitalize : Symbol(badCapitalize, Decl(dependentReturnType1.ts, 284, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 286, 23)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 286, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 286, 23)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 286, 23)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 286, 23)) +function badCapitalize( +>badCapitalize : Symbol(badCapitalize, Decl(dependentReturnType1.ts, 290, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 292, 23)) + + input: T +>input : Symbol(input, Decl(dependentReturnType1.ts, 292, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 292, 23)) + +): T extends string[] ? string[] : T extends string ? string : never { +>T : Symbol(T, Decl(dependentReturnType1.ts, 292, 23)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 292, 23)) if (isString(input)) { ->isString : Symbol(isString, Decl(dependentReturnType1.ts, 273, 1)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 286, 52)) +>isString : Symbol(isString, Decl(dependentReturnType1.ts, 277, 1)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 292, 52)) return input[0].toUpperCase() + input.slice(1); // Ok >input[0].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 286, 52)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 292, 52)) >toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) >input.slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 286, 52)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 292, 52)) >slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) } else { - return input[0].toUpperCase() + input.slice(1); // Bad + return input[0].toUpperCase() + input.slice(1); // Bad, error >input[0].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 286, 52)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 292, 52)) >toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) >input.slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 286, 52)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 292, 52)) >slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --)) } } -// >> TODO: test non-tail recursive conditionals +// No narrowing because conditional's extends type is different from type parameter constraint types +function voidRet( +>voidRet : Symbol(voidRet, Decl(dependentReturnType1.ts, 300, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 303, 17)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 303, 28)) -function voidRet(x: T): T extends {} ? void : T extends undefined ? number : void | number { ->voidRet : Symbol(voidRet, Decl(dependentReturnType1.ts, 292, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 296, 17)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 296, 28)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 296, 54)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 296, 17)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 296, 17)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 296, 17)) + x: T +>x : Symbol(x, Decl(dependentReturnType1.ts, 303, 54)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 303, 17)) + +): T extends {} ? void : T extends undefined ? number : never { +>T : Symbol(T, Decl(dependentReturnType1.ts, 303, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 303, 17)) if (x) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 296, 54)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 303, 54)) - return; // Ok + return; } - return 1; // Ok + return 1; } -function woo(x: T, y: U): ->woo : Symbol(woo, Decl(dependentReturnType1.ts, 301, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 303, 13)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 303, 39)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 303, 67)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 303, 13)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 303, 72)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 303, 39)) +// Multiple type parameters at once +function woo( +>woo : Symbol(woo, Decl(dependentReturnType1.ts, 310, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 313, 13)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 313, 39)) -T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 { ->T : Symbol(T, Decl(dependentReturnType1.ts, 303, 13)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 303, 39)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 303, 39)) + x: T, +>x : Symbol(x, Decl(dependentReturnType1.ts, 313, 67)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 313, 13)) + y: U, +>y : Symbol(y, Decl(dependentReturnType1.ts, 314, 9)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 313, 39)) + +): T extends string +>T : Symbol(T, Decl(dependentReturnType1.ts, 313, 13)) + + ? U extends string +>U : Symbol(U, Decl(dependentReturnType1.ts, 313, 39)) + + ? 1 + : U extends number +>U : Symbol(U, Decl(dependentReturnType1.ts, 313, 39)) + + ? 2 + : never + : T extends number +>T : Symbol(T, Decl(dependentReturnType1.ts, 313, 13)) + + ? U extends number +>U : Symbol(U, Decl(dependentReturnType1.ts, 313, 39)) + + ? 3 + : U extends string +>U : Symbol(U, Decl(dependentReturnType1.ts, 313, 39)) + + ? 4 + : never + : never { if (typeof x === "number" && typeof y === "string") { ->x : Symbol(x, Decl(dependentReturnType1.ts, 303, 67)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 303, 72)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 313, 67)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 314, 9)) - return 1; // Error + return 1; // Good error } return undefined as any; >undefined : Symbol(undefined) } -function ttt(x: T, y: U): ->ttt : Symbol(ttt, Decl(dependentReturnType1.ts, 309, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 311, 13)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 311, 39)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 311, 67)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 311, 13)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 311, 72)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 311, 39)) - -T extends string ->T : Symbol(T, Decl(dependentReturnType1.ts, 311, 13)) - -? number extends string - ? 6 - : U extends string ->U : Symbol(U, Decl(dependentReturnType1.ts, 311, 39)) - - ? 1 - : 2 -: U extends number ->U : Symbol(U, Decl(dependentReturnType1.ts, 311, 39)) - - ? 3 - : 4 { - if (typeof x === "string" && typeof y === "string") { ->x : Symbol(x, Decl(dependentReturnType1.ts, 311, 67)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 311, 72)) - - return 1; // Ok - } - - return undefined as any; ->undefined : Symbol(undefined) -} +function ttt( +>ttt : Symbol(ttt, Decl(dependentReturnType1.ts, 333, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 335, 13)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 335, 39)) -// We won't narrow `T` because it refers to the type of an optional parameter but doesn't allow for narrowing with `undefined` -function opt(x?: T): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { ->opt : Symbol(opt, Decl(dependentReturnType1.ts, 326, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 329, 13)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 329, 31)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 329, 13)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 329, 13)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 329, 13)) + x: T, +>x : Symbol(x, Decl(dependentReturnType1.ts, 335, 67)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 335, 13)) - if (typeof x === "undefined") { ->x : Symbol(x, Decl(dependentReturnType1.ts, 329, 31)) + y: U, +>y : Symbol(y, Decl(dependentReturnType1.ts, 336, 9)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 335, 39)) - x; ->x : Symbol(x, Decl(dependentReturnType1.ts, 329, 31)) +): T extends string +>T : Symbol(T, Decl(dependentReturnType1.ts, 335, 13)) - return 2; + ? U extends string +>U : Symbol(U, Decl(dependentReturnType1.ts, 335, 39)) + + ? 1 + : U extends number +>U : Symbol(U, Decl(dependentReturnType1.ts, 335, 39)) + + ? 2 + : never + : T extends number +>T : Symbol(T, Decl(dependentReturnType1.ts, 335, 13)) + + ? U extends number +>U : Symbol(U, Decl(dependentReturnType1.ts, 335, 39)) + + ? 3 + : U extends string +>U : Symbol(U, Decl(dependentReturnType1.ts, 335, 39)) + + ? 4 + : never + : never { + if (typeof x === "number" && typeof y === "string") { +>x : Symbol(x, Decl(dependentReturnType1.ts, 335, 67)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 336, 9)) + + return 4; // Ok } - return 1; + + return undefined as any; +>undefined : Symbol(undefined) } // Shadowing of the narrowed reference -function g(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { ->g : Symbol(g, Decl(dependentReturnType1.ts, 335, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 338, 11)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 338, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 338, 11)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 338, 11)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 338, 11)) +function shadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { +>shadowing : Symbol(shadowing, Decl(dependentReturnType1.ts, 356, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 359, 36)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) if (true) { let x: number = Math.random() ? 1 : 2; ->x : Symbol(x, Decl(dependentReturnType1.ts, 340, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 361, 11)) >Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) >Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) >random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) if (x === 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 340, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 361, 11)) return 1; // Error } @@ -978,58 +996,79 @@ function g(x: T): T extends 1 ? number : T extends 2 ? string : } } +function noShadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { +>noShadowing : Symbol(noShadowing, Decl(dependentReturnType1.ts, 367, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 369, 38)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) + + if (true) { + if (x === 1) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 369, 38)) + + return 1; // Ok + } + return ""; // Ok + } +} + // If the narrowing reference is out of scope, we simply won't narrow its type declare let someX: boolean; ->someX : Symbol(someX, Decl(dependentReturnType1.ts, 349, 11)) +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 379, 11)) -function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { ->scope2 : Symbol(scope2, Decl(dependentReturnType1.ts, 349, 27)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 350, 16)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 350, 35)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 350, 42)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 350, 16)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 350, 16)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 350, 16)) +function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : never { +>scope2 : Symbol(scope2, Decl(dependentReturnType1.ts, 379, 27)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 380, 16)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 380, 35)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 380, 42)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 380, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 380, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 380, 16)) if ((true)) { const someX = opts.a; ->someX : Symbol(someX, Decl(dependentReturnType1.ts, 352, 13)) ->opts.a : Symbol(a, Decl(dependentReturnType1.ts, 350, 42)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 350, 35)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 350, 42)) +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 382, 13)) +>opts.a : Symbol(a, Decl(dependentReturnType1.ts, 380, 42)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 380, 35)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 380, 42)) if (someX) { // We narrow `someX` and the return type here ->someX : Symbol(someX, Decl(dependentReturnType1.ts, 352, 13)) +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 382, 13)) return 1; } } if (!someX) { // This is a different `someX`, so we don't narrow here ->someX : Symbol(someX, Decl(dependentReturnType1.ts, 349, 11)) +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 379, 11)) return 2; } + + return undefined as any; +>undefined : Symbol(undefined) } -function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { ->h : Symbol(h, Decl(dependentReturnType1.ts, 360, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 362, 11)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 362, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 362, 11)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 362, 11)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 362, 11)) +function moreShadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { +>moreShadowing : Symbol(moreShadowing, Decl(dependentReturnType1.ts, 392, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 394, 23)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 394, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 394, 23)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 394, 23)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 394, 23)) if (x === 2) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 362, 28)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 394, 40)) let x: number = Math.random() ? 1 : 2; ->x : Symbol(x, Decl(dependentReturnType1.ts, 364, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 396, 11)) >Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) >Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) >random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) if (x === 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 364, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 396, 11)) return 1; // Error } @@ -1038,18 +1077,19 @@ function h(x: T): T extends 1 ? number : T extends 2 ? string : return 0; // Ok } -function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : string | boolean { ->withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 371, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 373, 19)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 373, 48)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 373, 19)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 373, 19)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 373, 71)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 373, 71)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 373, 19)) +// This would be unsafe to narrow due to `infer` type. +function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : never { +>withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 403, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 406, 19)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 406, 48)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 406, 19)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 406, 19)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 406, 71)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 406, 71)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 406, 19)) if (typeof x === "number") { ->x : Symbol(x, Decl(dependentReturnType1.ts, 373, 48)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 406, 48)) return true; } @@ -1057,22 +1097,22 @@ function withInfer(x: T): T extends [infer R] ? R : } const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. ->withInferResult : Symbol(withInferResult, Decl(dependentReturnType1.ts, 380, 5)) ->withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 371, 1)) +>withInferResult : Symbol(withInferResult, Decl(dependentReturnType1.ts, 413, 5)) +>withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 403, 1)) >const : Symbol(const) // Ok -async function abool(x: T): Promise { ->abool : Symbol(abool, Decl(dependentReturnType1.ts, 380, 50)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 383, 21)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 383, 45)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 383, 21)) +async function abool(x: T): Promise { +>abool : Symbol(abool, Decl(dependentReturnType1.ts, 413, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 416, 21)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 416, 45)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 416, 21)) >Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 383, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 383, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 416, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 416, 21)) if (x) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 383, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 416, 45)) return 1; } @@ -1080,18 +1120,18 @@ async function abool(x: T): Promise(x: T): Generator { ->bbool : Symbol(bbool, Decl(dependentReturnType1.ts, 388, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 391, 16)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 391, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 391, 16)) +function* bbool(x: T): Generator { +>bbool : Symbol(bbool, Decl(dependentReturnType1.ts, 421, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 424, 16)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 424, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 424, 16)) >Generator : Symbol(Generator, Decl(lib.es2015.generator.d.ts, --, --)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 391, 16)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 391, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 424, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 424, 16)) yield 3; if (x) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 391, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 424, 40)) return 1; } @@ -1099,17 +1139,17 @@ function* bbool(x: T): Generator(x: T): Generator { ->cbool : Symbol(cbool, Decl(dependentReturnType1.ts, 397, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 400, 16)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 400, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 400, 16)) +function* cbool(x: T): Generator { +>cbool : Symbol(cbool, Decl(dependentReturnType1.ts, 430, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 433, 16)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 433, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 433, 16)) >Generator : Symbol(Generator, Decl(lib.es2015.generator.d.ts, --, --)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 400, 16)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 400, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 433, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 433, 16)) if (x) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 400, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 433, 40)) yield 1; } @@ -1119,130 +1159,136 @@ function* cbool(x: T): Generator { ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 409, 25)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 409, 27)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 442, 25)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 442, 27)) abstract perform(t: T): R; ->perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 409, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 410, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 409, 25)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 409, 27)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 442, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 443, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 442, 25)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 442, 27)) } type ConditionalReturnType | undefined> = ->ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 411, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 413, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 413, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 413, 32)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 413, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 413, 29)) - - EOp extends Operation ? R : EOp extends undefined ? T | R : T | R; ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 413, 32)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 413, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 413, 29)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 413, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 413, 32)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 413, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 413, 29)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 413, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 413, 29)) - -class ConditionalOperation | undefined> extends Operation> { ->ConditionalOperation : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 414, 76)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 416, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 416, 32)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 416, 29)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) ->ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 411, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 416, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 416, 32)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 444, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 446, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 446, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 446, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 446, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 446, 29)) + + EOp extends Operation ? R : EOp extends undefined ? T | R : never; +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 446, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 446, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 446, 29)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 446, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 446, 32)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 446, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 446, 29)) + +class ConditionalOperation< +>ConditionalOperation : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 447, 76)) + + T, +>T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) + + R, +>R : Symbol(R, Decl(dependentReturnType1.ts, 450, 6)) + + EOp extends Operation | undefined, +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 6)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 450, 6)) + +> extends Operation> { +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 444, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 450, 6)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 6)) constructor( private predicate: (value: T) => boolean, ->predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 417, 16)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 418, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) +>predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 454, 16)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 455, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) private thenOp: Operation, ->thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 418, 49)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 416, 29)) +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 455, 49)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 450, 6)) - private elseOp?: EOp ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 419, 40)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 416, 32)) + private elseOp?: EOp, +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 456, 40)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 6)) ) { super(); ->super : Symbol(Operation, Decl(dependentReturnType1.ts, 406, 1)) +>super : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) } perform(t: T): ConditionalReturnType { ->perform : Symbol(ConditionalOperation.perform, Decl(dependentReturnType1.ts, 423, 5)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 425, 12)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) ->ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 411, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 416, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 416, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 416, 32)) +>perform : Symbol(ConditionalOperation.perform, Decl(dependentReturnType1.ts, 460, 5)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 462, 12)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 444, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 450, 6)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 6)) if (this.predicate(t)) { ->this.predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 417, 16)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 414, 76)) ->predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 417, 16)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 425, 12)) +>this.predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 454, 16)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 447, 76)) +>predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 454, 16)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 462, 12)) return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it ->this.thenOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 409, 32)) ->this.thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 418, 49)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 414, 76)) ->thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 418, 49)) ->perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 409, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 425, 12)) - - } else if (typeof this.elseOp !== 'undefined') { ->this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 419, 40)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 414, 76)) ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 419, 40)) +>this.thenOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 442, 32)) +>this.thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 455, 49)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 447, 76)) +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 455, 49)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 442, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 462, 12)) + + } else if (typeof this.elseOp !== "undefined") { +>this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 456, 40)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 447, 76)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 456, 40)) return this.elseOp.perform(t); // Ok ->this.elseOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 409, 32)) ->this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 419, 40)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 414, 76)) ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 419, 40)) ->perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 409, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 425, 12)) +>this.elseOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 442, 32)) +>this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 456, 40)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 447, 76)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 456, 40)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 442, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 462, 12)) } else { return t; // Ok ->t : Symbol(t, Decl(dependentReturnType1.ts, 425, 12)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 462, 12)) } } } // Optional tuple element function tupl(x: [string, some?: T]): ->tupl : Symbol(tupl, Decl(dependentReturnType1.ts, 434, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 437, 14)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 437, 50)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 437, 14)) +>tupl : Symbol(tupl, Decl(dependentReturnType1.ts, 471, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 474, 14)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 474, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 474, 14)) - T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 { ->T : Symbol(T, Decl(dependentReturnType1.ts, 437, 14)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 437, 14)) + T extends true ? 1 : T extends false | undefined ? 2 : never { +>T : Symbol(T, Decl(dependentReturnType1.ts, 474, 14)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 474, 14)) if (x[1]) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 437, 50)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 474, 50)) >1 : Symbol(1) return 1; @@ -1251,60 +1297,60 @@ function tupl(x: [string, some?: T]): } // Return conditional expressions with parentheses -function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { ->returnStuff1 : Symbol(returnStuff1, Decl(dependentReturnType1.ts, 443, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 446, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 446, 41)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 446, 48)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 446, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 446, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 446, 22)) +function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : never { +>returnStuff1 : Symbol(returnStuff1, Decl(dependentReturnType1.ts, 480, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 483, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 483, 41)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 483, 48)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 483, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 483, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 483, 22)) return (opts.x ? (1) : 2); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 446, 48)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 446, 41)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 446, 48)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 483, 48)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 483, 41)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 483, 48)) } function returnStuff2(opts: { x: T }): ->returnStuff2 : Symbol(returnStuff2, Decl(dependentReturnType1.ts, 448, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 450, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 450, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 450, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 450, 22)) +>returnStuff2 : Symbol(returnStuff2, Decl(dependentReturnType1.ts, 485, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 487, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 487, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 487, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 487, 22)) - T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 { ->T : Symbol(T, Decl(dependentReturnType1.ts, 450, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 450, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 450, 22)) + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never { +>T : Symbol(T, Decl(dependentReturnType1.ts, 487, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 487, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 487, 22)) return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 450, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 450, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 450, 52)) ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 450, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 450, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 450, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 487, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 487, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 487, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 487, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 487, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 487, 52)) } -// If the return type is written wrong, it still type checks -function returnStuff3(opts: { x: T }): ->returnStuff3 : Symbol(returnStuff3, Decl(dependentReturnType1.ts, 453, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 456, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 456, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 456, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 456, 22)) +// If the conditional type's input is `never`, then it resolves to `never`: +function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : never { +>neverOk : Symbol(neverOk, Decl(dependentReturnType1.ts, 490, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 493, 17)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 493, 36)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 493, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 493, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 493, 17)) - T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" { ->T : Symbol(T, Decl(dependentReturnType1.ts, 456, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 456, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 456, 22)) + if (x === true) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 493, 36)) - return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 456, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 456, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 456, 52)) ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 456, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 456, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 456, 52)) + return 1; + } + if (x === false) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 493, 36)) + + return 2; + } + return 1; } diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index b8ac9cfc84a8a..11c0c8a27c432 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -171,8 +171,8 @@ interface Four { >g : "g" > : ^^^ } - -function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { // Badly written conditional +// Badly written conditional return type, will not trigger narrowing +function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { >f10 : (x: T) => T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T @@ -194,7 +194,7 @@ function f10(x: T): T extends 1 ? One : T extends 2 ? T >2 : 2 > : ^ - return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Error >{ a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" } : { a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; } > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >a : "a" @@ -220,18 +220,8 @@ function f10(x: T): T extends 1 ? One : T extends 2 ? T >f : "f" > : ^^^ >"f" : "f" -> : ^^^ - - return { a: "a" }; // Error ->{ a: "a" } : { a: "a"; } -> : ^^^^^^^^^^^ ->a : "a" -> : ^^^ ->"a" : "a" > : ^^^ } - // Excess property becomes a problem with the change, - // because we now check assignability to a narrower type... return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error >{ a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" } : { a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; } > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -264,10 +254,10 @@ function f10(x: T): T extends 1 ? One : T extends 2 ? T >"g" : "g" > : ^^^ } - -function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four { // Well written conditional ->f101 : (x: T) => T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +// Well written conditional +function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : never { +>f101 : (x: T) => T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T > : ^ @@ -313,19 +303,11 @@ function f101(x: T): T extends 1 ? One : T extends 2 ? >f : "f" > : ^^^ >"f" : "f" -> : ^^^ - - return { a: "a" }; // Error ->{ a: "a" } : { a: "a"; } -> : ^^^^^^^^^^^ ->a : "a" -> : ^^^ ->"a" : "a" > : ^^^ } // Excess property becomes a problem with the change, // because we now check assignability to a narrower type... - return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // EPC Error >{ a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" } : { a: "a"; b: string; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; } > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >a : "a" @@ -360,8 +342,8 @@ function f101(x: T): T extends 1 ? One : T extends 2 ? // Asymmetry function conditionalProducingIf( ->conditionalProducingIf : (arg: Arg, cond: (arg: LeftIn | RightIn) => arg is LeftIn, produceLeftOut: (arg: LeftIn) => LeftOut, produceRightOut: (arg: RightIn) => RightOut) => Arg extends LeftIn ? LeftOut : RightOut -> : ^ ^^ ^^ ^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ +>conditionalProducingIf : (arg: Arg, cond: (arg: LeftIn | RightIn) => arg is LeftIn, produceLeftOut: (arg: LeftIn) => LeftOut, produceRightOut: (arg: RightIn) => RightOut) => Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never +> : ^ ^^ ^^ ^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ arg: Arg, >arg : Arg @@ -385,7 +367,7 @@ function conditionalProducingIfarg : RightIn > : ^^^^^^^ - Arg extends LeftIn ? LeftOut : RightOut + Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never { type OK = Arg extends LeftIn ? LeftOut : RightOut; >OK : Arg extends LeftIn ? LeftOut : RightOut @@ -432,7 +414,7 @@ interface Dog extends Animal { > : ^^^^^^ } -// This is unsafe +// This would be unsafe to narrow. declare function isDog(x: Animal): x is Dog; >isDog : (x: Animal) => x is Dog > : ^ ^^ ^^^^^ @@ -459,7 +441,7 @@ function f12(x: T): T extends Dog ? number : string { >x : T > : ^ - return doggy(x); // The narrowed conditional return type has deferred resolution, so this doesn't work. + return doggy(x); >doggy(x) : number > : ^^^^^^ >doggy : (x: Dog) => number @@ -585,10 +567,10 @@ class Unnamed { >name : string > : ^^^^^^ - // Error because parameter is optional - name(name?: T): T extends string ? this : string { ->name : (name?: T) => T extends string ? this : string -> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ + // Error: No narrowing because parameter is optional but `T` doesn't allow for undefined + name(name?: T): T extends string ? this : T extends undefined ? string : never { +>name : (name?: T) => T extends string ? this : T extends undefined ? string : never +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ >name : T | undefined > : ^^^^^^^^^^^^^ @@ -616,24 +598,13 @@ class Unnamed { } return this; >this : this -> : ^^^^ - } - // Error because parameter is optional? - nameWithError(name?: T): T extends string ? this : string { ->nameWithError : (name?: T) => T extends string ? this : string -> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ ->name : T | undefined -> : ^^^^^^^^^^^^^ - - return this; // Error: Investigate error message ->this : this > : ^^^^ } // Good conditional - name2(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { ->name2 : (name?: T) => T extends string ? this : T extends undefined ? string : this | undefined -> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ + name2(name?: T): T extends string ? this : T extends undefined ? string : never { +>name2 : (name?: T) => T extends string ? this : T extends undefined ? string : never +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ >name : T | undefined > : ^^^^^^^^^^^^^ @@ -681,9 +652,9 @@ class Unnamed { } // Good conditional, wrong return expressions - name3(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { ->name3 : (name?: T) => T extends string ? this : T extends undefined ? string : this | undefined -> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ + name3(name?: T): T extends string ? this : T extends undefined ? string : never { +>name3 : (name?: T) => T extends string ? this : T extends undefined ? string : never +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ >name : T | undefined > : ^^^^^^^^^^^^^ @@ -723,6 +694,7 @@ class Unnamed { } } +// Conditional expressions interface Aa { 1: number; >1 : number @@ -732,9 +704,9 @@ interface Aa { >2 : string > : ^^^^^^ - 3: string; ->3 : string -> : ^^^^^^ + 3: boolean; +>3 : boolean +> : ^^^^^^^ } function trivialConditional(x: T): Aa[T] { @@ -751,9 +723,9 @@ function trivialConditional(x: T): Aa[T] { >1 : 1 > : ^ - return x === 2 ? "" : `${x}`; ->x === 2 ? "" : `${x}` : string -> : ^^^^^^ + return x === 2 ? "" : true; +>x === 2 ? "" : true : true | "" +> : ^^^^^^^^^ >x === 2 : boolean > : ^^^^^^^ >x : T @@ -762,10 +734,8 @@ function trivialConditional(x: T): Aa[T] { > : ^ >"" : "" > : ^^ ->`${x}` : string -> : ^^^^^^ ->x : T -> : ^ +>true : true +> : ^^^^ } else { return 0; @@ -774,14 +744,13 @@ function trivialConditional(x: T): Aa[T] { } } -// Conditional expressions function conditional(x: T): ->conditional : (x: T) => T extends true ? 1 : T extends false ? 2 : 1 | 2 +>conditional : (x: T) => T extends true ? 1 : T extends false ? 2 : never > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T > : ^ - T extends true ? 1 : T extends false ? 2 : 1 | 2 { + T extends true ? 1 : T extends false ? 2 : never { >true : true > : ^^^^ >false : false @@ -798,12 +767,15 @@ function conditional(x: T): > : ^ } -function contextualConditional(x: T): T extends "a" ? "a" : T extends "b" ? number : "a" | number { ->contextualConditional : (x: T) => T extends "a" ? "a" : T extends "b" ? number : "a" | number -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +function contextualConditional( +>contextualConditional : (x: T) => T extends "a" ? "a" : T extends "b" ? number : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ + + x: T >x : T > : ^ +): T extends "a" ? "a" : T extends "b" ? number : never { return x === "a" ? x : parseInt(x); // Ok >x === "a" ? x : parseInt(x) : number | "a" > : ^^^^^^^^^^^^ @@ -823,12 +795,15 @@ function contextualConditional(x: T): T extends "a" ? "a" : > : ^^^ } -function conditionalWithError(x: T): T extends "a" ? number : T extends "b" ? string : number | string { ->conditionalWithError : (x: T) => T extends "a" ? number : T extends "b" ? string : number | string -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +function conditionalWithError( +>conditionalWithError : (x: T) => T extends "a" ? number : T extends "b" ? string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ + + x: T >x : T > : ^ +): T extends "a" ? number : T extends "b" ? string : never { return x === "a" ? x : parseInt(x); // Error >x === "a" ? x : parseInt(x) : number | "a" > : ^^^^^^^^^^^^ @@ -848,7 +823,7 @@ function conditionalWithError(x: T): T extends "a" ? number > : ^^^ } -// Multiple reductions +// Multiple indexed type reductions interface BB { "a": number; >"a" : number @@ -907,13 +882,24 @@ function reduction(x: T, y: U): AA[U > : ^^^^^^^^^ } -// Substitution types are not narrowed? -function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { ->subsCond : (x: T) => T extends 1 | 2 ? (T extends 1 ? string : boolean) : number -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +// Substitution types are not narrowed +function subsCond( +>subsCond : (x: T) => T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ + + x: T, >x : T > : ^ +): T extends 1 | 2 + ? T extends 1 + ? string + : T extends 2 + ? boolean + : never + : T extends 3 + ? number + : never { if (x === 1) { >x === 1 : boolean > : ^^^^^^^ @@ -925,10 +911,26 @@ function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? s return ""; >"" : "" > : ^^ + + } else if (x == 2) { +>x == 2 : boolean +> : ^^^^^^^ +>x : T +> : ^ +>2 : 2 +> : ^ + + return true; +>true : true +> : ^^^^ } + return 3; +>3 : 3 +> : ^ } -// Unsafe: supertype problem + +// Unsafe: check types overlap declare function q(x: object): x is { b: number }; >q : (x: object) => x is { b: number; } > : ^ ^^ ^^^^^ @@ -937,16 +939,22 @@ declare function q(x: object): x is { b: number }; >b : number > : ^^^^^^ -function foo(x: T): T extends { a: string } ? number : (string | number) { ->foo : (x: T) => T extends { a: string; } ? number : (string | number) -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +function foo( +>foo : (x: T) => T extends { a: string; } ? number : T extends { b: number; } ? string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >a : string > : ^^^^^^ >b : number > : ^^^^^^ + + x: T, >x : T > : ^ + +): T extends { a: string } ? number : T extends { b: number } ? string : never { >a : string +> : ^^^^^^ +>b : number > : ^^^^^^ if (q(x)) { @@ -969,6 +977,17 @@ function foo(x: T): T extends { a: stri >"" : "" > : ^^ } + x.a; +>x.a : string +> : ^^^^^^ +>x : { a: string; } +> : ^^^^^ ^^^ +>a : string +> : ^^^^^^ + + return 1; +>1 : 1 +> : ^ } let y = { a: "", b: 1 } @@ -985,70 +1004,19 @@ let y = { a: "", b: 1 } >1 : 1 > : ^ -const r = foo<{ a: string }>(y); // number +const r = foo<{ a: string }>(y); // type says number but actually string >r : number > : ^^^^^^ >foo<{ a: string }>(y) : number > : ^^^^^^ ->foo : (x: T) => T extends { a: string; } ? number : (string | number) -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>foo : (x: T) => T extends { a: string; } ? number : T extends { b: number; } ? string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >a : string > : ^^^^^^ >y : { a: string; b: number; } > : ^^^^^^^^^^^^^^^^^^^^^^^^^ -function lessBadFoo(x: T): T extends { b: number } ? string : T extends { a: string } ? number : (string | number) { ->lessBadFoo : (x: T) => T extends { b: number; } ? string : T extends { a: string; } ? number : (string | number) -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->a : string -> : ^^^^^^ ->b : number -> : ^^^^^^ ->x : T -> : ^ ->b : number -> : ^^^^^^ ->a : string -> : ^^^^^^ - - if (q(x)) { ->q(x) : boolean -> : ^^^^^^^ ->q : (x: object) => x is { b: number; } -> : ^ ^^ ^^^^^ ->x : { a: string; } | { b: number; } -> : ^^^^^ ^^^^^^^^^^^ ^^^ - - x.b; ->x.b : number -> : ^^^^^^ ->x : { b: number; } -> : ^^^^^ ^^^ ->b : number -> : ^^^^^^ - - return ""; ->"" : "" -> : ^^ - } - return 2; ->2 : 2 -> : ^ -} - -const r2 = lessBadFoo<{ a: string }>(y); // number, bad ->r2 : number -> : ^^^^^^ ->lessBadFoo<{ a: string }>(y) : number -> : ^^^^^^ ->lessBadFoo : (x: T) => T extends { b: number; } ? string : T extends { a: string; } ? number : (string | number) -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->a : string -> : ^^^^^^ ->y : { a: string; b: number; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^ - -type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; +type HelperCond = T extends A ? R1 : T extends B ? R2 : never; >HelperCond : HelperCond > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1140,12 +1108,15 @@ declare function isString(s: unknown): s is string; > : ^^^^^^^ // capitalize a string or each element of an array of strings -function capitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { ->capitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : string[] | string -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +function capitalize( +>capitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ + + input: T >input : T > : ^ +): T extends string[] ? string[] : T extends string ? string : never { if (isString(input)) { >isString(input) : boolean > : ^^^^^^^ @@ -1196,19 +1167,22 @@ function capitalize(input: T): T extends string[] ? > : ^^^^^^ >capitalize(elt) : string > : ^^^^^^ ->capitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : string[] | string -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>capitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >elt : string > : ^^^^^^ } } -function badCapitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { ->badCapitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : string[] | string -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +function badCapitalize( +>badCapitalize : (input: T) => T extends string[] ? string[] : T extends string ? string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ + + input: T >input : T > : ^ +): T extends string[] ? string[] : T extends string ? string : never { if (isString(input)) { >isString(input) : boolean > : ^^^^^^^ @@ -1244,7 +1218,7 @@ function badCapitalize(input: T): T extends string[ > : ^ } else { - return input[0].toUpperCase() + input.slice(1); // Bad + return input[0].toUpperCase() + input.slice(1); // Bad, error >input[0].toUpperCase() + input.slice(1) : string > : ^^^^^^ >input[0].toUpperCase() : string @@ -1272,36 +1246,55 @@ function badCapitalize(input: T): T extends string[ } } -// >> TODO: test non-tail recursive conditionals - -function voidRet(x: T): T extends {} ? void : T extends undefined ? number : void | number { ->voidRet : (x: T) => T extends {} ? void : T extends undefined ? number : void | number -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +// No narrowing because conditional's extends type is different from type parameter constraint types +function voidRet( +>voidRet : (x: T) => T extends {} ? void : T extends undefined ? number : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >a : string > : ^^^^^^ + + x: T >x : T > : ^ +): T extends {} ? void : T extends undefined ? number : never { if (x) { >x : T > : ^ - return; // Ok + return; } - return 1; // Ok + return 1; >1 : 1 > : ^ } -function woo(x: T, y: U): ->woo : (x: T, y: U) => T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 -> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +// Multiple type parameters at once +function woo( +>woo : (x: T, y: U) => T extends string ? U extends string ? 1 : U extends number ? 2 : never : T extends number ? U extends number ? 3 : U extends string ? 4 : never : never +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ + + x: T, >x : T > : ^ + + y: U, >y : U > : ^ -T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 { +): T extends string + ? U extends string + ? 1 + : U extends number + ? 2 + : never + : T extends number + ? U extends number + ? 3 + : U extends string + ? 4 + : never + : never { if (typeof x === "number" && typeof y === "string") { >typeof x === "number" && typeof y === "string" : boolean > : ^^^^^^^ @@ -1322,7 +1315,7 @@ T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 { >"string" : "string" > : ^^^^^^^^ - return 1; // Error + return 1; // Good error >1 : 1 > : ^ } @@ -1333,33 +1326,41 @@ T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 { > : ^^^^^^^^^ } -function ttt(x: T, y: U): ->ttt : (x: T, y: U) => T extends string ? number extends string ? 6 : U extends string ? 1 : 2 : U extends number ? 3 : 4 -> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +function ttt( +>ttt : (x: T, y: U) => T extends string ? U extends string ? 1 : U extends number ? 2 : never : T extends number ? U extends number ? 3 : U extends string ? 4 : never : never +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ + + x: T, >x : T > : ^ + + y: U, >y : U > : ^ -T extends string -? number extends string - ? 6 - : U extends string - ? 1 - : 2 -: U extends number - ? 3 - : 4 { - if (typeof x === "string" && typeof y === "string") { ->typeof x === "string" && typeof y === "string" : boolean +): T extends string + ? U extends string + ? 1 + : U extends number + ? 2 + : never + : T extends number + ? U extends number + ? 3 + : U extends string + ? 4 + : never + : never { + if (typeof x === "number" && typeof y === "string") { +>typeof x === "number" && typeof y === "string" : boolean > : ^^^^^^^ ->typeof x === "string" : boolean +>typeof x === "number" : boolean > : ^^^^^^^ >typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : T > : ^ ->"string" : "string" +>"number" : "number" > : ^^^^^^^^ >typeof y === "string" : boolean > : ^^^^^^^ @@ -1370,8 +1371,8 @@ T extends string >"string" : "string" > : ^^^^^^^^ - return 1; // Ok ->1 : 1 + return 4; // Ok +>4 : 4 > : ^ } @@ -1382,40 +1383,10 @@ T extends string > : ^^^^^^^^^ } -// We won't narrow `T` because it refers to the type of an optional parameter but doesn't allow for narrowing with `undefined` -function opt(x?: T): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { ->opt : (x?: T) => T extends string ? 1 : T extends undefined ? 2 : 1 | 2 -> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ ->x : T | undefined -> : ^^^^^^^^^^^^^ - - if (typeof x === "undefined") { ->typeof x === "undefined" : boolean -> : ^^^^^^^ ->typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->x : T | undefined -> : ^^^^^^^^^^^^^ ->"undefined" : "undefined" -> : ^^^^^^^^^^^ - - x; ->x : undefined -> : ^^^^^^^^^ - - return 2; ->2 : 2 -> : ^ - } - return 1; ->1 : 1 -> : ^ -} - // Shadowing of the narrowed reference -function g(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { ->g : (x: T) => T extends 1 ? number : T extends 2 ? string : 1 | 2 -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +function shadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { +>shadowing : (x: T) => T extends 1 ? number : T extends 2 ? string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T > : ^ @@ -1459,13 +1430,41 @@ function g(x: T): T extends 1 ? number : T extends 2 ? string : } } +function noShadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { +>noShadowing : (x: T) => T extends 1 ? number : T extends 2 ? string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>x : T +> : ^ + + if (true) { +>true : true +> : ^^^^ + + if (x === 1) { +>x === 1 : boolean +> : ^^^^^^^ +>x : T +> : ^ +>1 : 1 +> : ^ + + return 1; // Ok +>1 : 1 +> : ^ + } + return ""; // Ok +>"" : "" +> : ^^ + } +} + // If the narrowing reference is out of scope, we simply won't narrow its type declare let someX: boolean; >someX : boolean > : ^^^^^^^ -function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { ->scope2 : (opts: { a: T; }) => T extends true ? 1 : T extends false ? 2 : 1 | 2 +function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : never { +>scope2 : (opts: { a: T; }) => T extends true ? 1 : T extends false ? 2 : never > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >opts : { a: T; } > : ^^^^^ ^^^ @@ -1511,11 +1510,17 @@ function scope2(opts: { a: T }): T extends true ? 1 : T exten >2 : 2 > : ^ } + + return undefined as any; +>undefined as any : any +> : ^^^ +>undefined : undefined +> : ^^^^^^^^^ } -function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { ->h : (x: T) => T extends 1 ? number : T extends 2 ? string : 1 | 2 -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +function moreShadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { +>moreShadowing : (x: T) => T extends 1 ? number : T extends 2 ? string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T > : ^ @@ -1566,9 +1571,10 @@ function h(x: T): T extends 1 ? number : T extends 2 ? string : > : ^ } -function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : string | boolean { ->withInfer : (x: T) => T extends [infer R] ? R : T extends number ? boolean : string | boolean -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +// This would be unsafe to narrow due to `infer` type. +function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : never { +>withInfer : (x: T) => T extends [infer R] ? R : T extends number ? boolean : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T > : ^ @@ -1596,8 +1602,8 @@ const withInferResult = withInfer(["a"] as const); // The type says it returns ` > : ^^^ >withInfer(["a"] as const) : "a" > : ^^^ ->withInfer : (x: T) => T extends [infer R] ? R : T extends number ? boolean : string | boolean -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>withInfer : (x: T) => T extends [infer R] ? R : T extends number ? boolean : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >["a"] as const : ["a"] > : ^^^^^ >["a"] : ["a"] @@ -1606,8 +1612,8 @@ const withInferResult = withInfer(["a"] as const); // The type says it returns ` > : ^^^ // Ok -async function abool(x: T): Promise { ->abool : (x: T) => Promise +async function abool(x: T): Promise { +>abool : (x: T) => Promise > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >true : true > : ^^^^ @@ -1634,8 +1640,8 @@ async function abool(x: T): Promise(x: T): Generator { ->bbool : (x: T) => Generator +function* bbool(x: T): Generator { +>bbool : (x: T) => Generator > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >true : true > : ^^^^ @@ -1668,8 +1674,8 @@ function* bbool(x: T): Generator(x: T): Generator { ->cbool : (x: T) => Generator +function* cbool(x: T): Generator { +>cbool : (x: T) => Generator > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >true : true > : ^^^^ @@ -1719,11 +1725,16 @@ type ConditionalReturnType | undefined> = >ConditionalReturnType : ConditionalReturnType > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - EOp extends Operation ? R : EOp extends undefined ? T | R : T | R; + EOp extends Operation ? R : EOp extends undefined ? T | R : never; -class ConditionalOperation | undefined> extends Operation> { +class ConditionalOperation< >ConditionalOperation : ConditionalOperation > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + T, + R, + EOp extends Operation | undefined, +> extends Operation> { >Operation : Operation> > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1738,7 +1749,7 @@ class ConditionalOperation | undefined> extend >thenOp : Operation > : ^^^^^^^^^^^^^^^ - private elseOp?: EOp + private elseOp?: EOp, >elseOp : EOp | undefined > : ^^^^^^^^^^^^^^^ @@ -1784,8 +1795,8 @@ class ConditionalOperation | undefined> extend >t : T > : ^ - } else if (typeof this.elseOp !== 'undefined') { ->typeof this.elseOp !== 'undefined' : boolean + } else if (typeof this.elseOp !== "undefined") { +>typeof this.elseOp !== "undefined" : boolean > : ^^^^^^^ >typeof this.elseOp : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1795,7 +1806,7 @@ class ConditionalOperation | undefined> extend > : ^^^^ >elseOp : EOp | undefined > : ^^^^^^^^^^^^^^^ ->'undefined' : "undefined" +>"undefined" : "undefined" > : ^^^^^^^^^^^ return this.elseOp.perform(t); // Ok @@ -1824,7 +1835,7 @@ class ConditionalOperation | undefined> extend // Optional tuple element function tupl(x: [string, some?: T]): ->tupl : (x: [string, some?: T]) => T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 +>tupl : (x: [string, some?: T]) => T extends true ? 1 : T extends false | undefined ? 2 : never > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >true : true > : ^^^^ @@ -1833,7 +1844,7 @@ function tupl(x: [string, some?: T]): >x : [string, some?: T | undefined] > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 { + T extends true ? 1 : T extends false | undefined ? 2 : never { >true : true > : ^^^^ >false : false @@ -1857,8 +1868,8 @@ function tupl(x: [string, some?: T]): } // Return conditional expressions with parentheses -function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { ->returnStuff1 : (opts: { x: T; }) => T extends true ? 1 : T extends false ? 2 : 1 | 2 +function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : never { +>returnStuff1 : (opts: { x: T; }) => T extends true ? 1 : T extends false ? 2 : never > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >opts : { x: T; } > : ^^^^^ ^^^ @@ -1889,14 +1900,14 @@ function returnStuff1(opts: { x: T }): T extends true ? 1 : T } function returnStuff2(opts: { x: T }): ->returnStuff2 : (opts: { x: T; }) => T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>returnStuff2 : (opts: { x: T; }) => T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >opts : { x: T; } > : ^^^^^ ^^^ >x : T > : ^ - T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 { + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never { return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); >(typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")) : 0 | "one" | "two" > : ^^^^^^^^^^^^^^^^^ @@ -1938,53 +1949,42 @@ function returnStuff2(opts: { x: T }): > : ^^^^^ } -// If the return type is written wrong, it still type checks -function returnStuff3(opts: { x: T }): ->returnStuff3 : (opts: { x: T; }) => T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->opts : { x: T; } -> : ^^^^^ ^^^ +// If the conditional type's input is `never`, then it resolves to `never`: +function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : never { +>neverOk : (x: T) => T extends true ? 1 : T extends false ? 2 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T > : ^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ - T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" { - return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); ->(typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")) : 0 | "one" | "two" -> : ^^^^^^^^^^^^^^^^^ ->typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two") : 0 | "one" | "two" -> : ^^^^^^^^^^^^^^^^^ ->typeof opts.x === "string" : boolean -> : ^^^^^^^ ->typeof opts.x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->opts.x : T -> : ^ ->opts : { x: T; } -> : ^^^^^ ^^^ + if (x === true) { +>x === true : boolean +> : ^^^^^^^ >x : T > : ^ ->"string" : "string" -> : ^^^^^^^^ ->0 : 0 +>true : true +> : ^^^^ + + return 1; +>1 : 1 > : ^ ->(opts.x === 1 ? ("one") : "two") : "one" | "two" -> : ^^^^^^^^^^^^^ ->opts.x === 1 ? ("one") : "two" : "one" | "two" -> : ^^^^^^^^^^^^^ ->opts.x === 1 : boolean -> : ^^^^^^^ ->opts.x : T -> : ^ ->opts : { x: T; } -> : ^^^^^ ^^^ + } + if (x === false) { +>x === false : boolean +> : ^^^^^^^ >x : T > : ^ +>false : false +> : ^^^^^ + + return 2; +>2 : 2 +> : ^ + } + return 1; >1 : 1 > : ^ ->("one") : "one" -> : ^^^^^ ->"one" : "one" -> : ^^^^^ ->"two" : "two" -> : ^^^^^ } diff --git a/tests/baselines/reference/dependentReturnType2.errors.txt b/tests/baselines/reference/dependentReturnType2.errors.txt deleted file mode 100644 index 8505b82677d98..0000000000000 --- a/tests/baselines/reference/dependentReturnType2.errors.txt +++ /dev/null @@ -1,29 +0,0 @@ -dependentReturnType2.ts(2,65): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. -dependentReturnType2.ts(4,9): error TS2322: Type '3' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 3'. -dependentReturnType2.ts(16,5): error TS2322: Type 'number' is not assignable to type 'never'. - - -==== dependentReturnType2.ts (3 errors) ==== - // If during narrowing, one of the conditional types in the distribution doesn't narrow, then the whole type will not be narrowed - function whoKnows(x: T): T extends true ? 1 : T extends false ? 2 : 3 { - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'. - if (typeof x !== "string") { - return 3; - ~~~~~~ -!!! error TS2322: Type '3' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : 3'. - } - } - - // If the conditional type's input is `never`, then it resolves to `never`: - function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : 1 | 2 { - if (x === true) { - return 1; - } - if (x === false) { - return 2; - } - return 1; - ~~~~~~ -!!! error TS2322: Type 'number' is not assignable to type 'never'. - } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType2.symbols b/tests/baselines/reference/dependentReturnType2.symbols deleted file mode 100644 index 74f4ab3fb0345..0000000000000 --- a/tests/baselines/reference/dependentReturnType2.symbols +++ /dev/null @@ -1,40 +0,0 @@ -//// [tests/cases/compiler/dependentReturnType2.ts] //// - -=== dependentReturnType2.ts === -// If during narrowing, one of the conditional types in the distribution doesn't narrow, then the whole type will not be narrowed -function whoKnows(x: T): T extends true ? 1 : T extends false ? 2 : 3 { ->whoKnows : Symbol(whoKnows, Decl(dependentReturnType2.ts, 0, 0)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 1, 18)) ->x : Symbol(x, Decl(dependentReturnType2.ts, 1, 57)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 1, 18)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 1, 18)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 1, 18)) - - if (typeof x !== "string") { ->x : Symbol(x, Decl(dependentReturnType2.ts, 1, 57)) - - return 3; - } -} - -// If the conditional type's input is `never`, then it resolves to `never`: -function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : 1 | 2 { ->neverOk : Symbol(neverOk, Decl(dependentReturnType2.ts, 5, 1)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 8, 17)) ->x : Symbol(x, Decl(dependentReturnType2.ts, 8, 36)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 8, 17)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 8, 17)) ->T : Symbol(T, Decl(dependentReturnType2.ts, 8, 17)) - - if (x === true) { ->x : Symbol(x, Decl(dependentReturnType2.ts, 8, 36)) - - return 1; - } - if (x === false) { ->x : Symbol(x, Decl(dependentReturnType2.ts, 8, 36)) - - return 2; - } - return 1; -} diff --git a/tests/baselines/reference/dependentReturnType2.types b/tests/baselines/reference/dependentReturnType2.types deleted file mode 100644 index 680fec5a5fcfa..0000000000000 --- a/tests/baselines/reference/dependentReturnType2.types +++ /dev/null @@ -1,69 +0,0 @@ -//// [tests/cases/compiler/dependentReturnType2.ts] //// - -=== dependentReturnType2.ts === -// If during narrowing, one of the conditional types in the distribution doesn't narrow, then the whole type will not be narrowed -function whoKnows(x: T): T extends true ? 1 : T extends false ? 2 : 3 { ->whoKnows : (x: T) => T extends true ? 1 : T extends false ? 2 : 3 -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->x : T -> : ^ ->true : true -> : ^^^^ ->false : false -> : ^^^^^ - - if (typeof x !== "string") { ->typeof x !== "string" : boolean -> : ^^^^^^^ ->typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->x : T -> : ^ ->"string" : "string" -> : ^^^^^^^^ - - return 3; ->3 : 3 -> : ^ - } -} - -// If the conditional type's input is `never`, then it resolves to `never`: -function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : 1 | 2 { ->neverOk : (x: T) => T extends true ? 1 : T extends false ? 2 : 1 | 2 -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->x : T -> : ^ ->true : true -> : ^^^^ ->false : false -> : ^^^^^ - - if (x === true) { ->x === true : boolean -> : ^^^^^^^ ->x : T -> : ^ ->true : true -> : ^^^^ - - return 1; ->1 : 1 -> : ^ - } - if (x === false) { ->x === false : boolean -> : ^^^^^^^ ->x : T -> : ^ ->false : false -> : ^^^^^ - - return 2; ->2 : 2 -> : ^ - } - return 1; ->1 : 1 -> : ^ -} diff --git a/tests/baselines/reference/dependentReturnType3.errors.txt b/tests/baselines/reference/dependentReturnType3.errors.txt index 2a2c949170da9..39a9326242262 100644 --- a/tests/baselines/reference/dependentReturnType3.errors.txt +++ b/tests/baselines/reference/dependentReturnType3.errors.txt @@ -1,6 +1,6 @@ -dependentReturnType3.ts(110,13): error TS2322: Type 'undefined' is not assignable to type 'HelperCond[]>'. -dependentReturnType3.ts(126,16): error TS2536: Type 'I' cannot be used to index type '{ [s: string]: any; }'. -dependentReturnType3.ts(137,9): error TS2322: Type 'Record' is not assignable to type 'HelperCond>'. +dependentReturnType3.ts(114,13): error TS2322: Type 'undefined' is not assignable to type 'HelperCond[]>'. +dependentReturnType3.ts(130,16): error TS2536: Type 'I' cannot be used to index type '{ [s: string]: any; }'. +dependentReturnType3.ts(141,9): error TS2322: Type 'Record' is not assignable to type 'HelperCond>'. ==== dependentReturnType3.ts (3 errors) ==== @@ -11,7 +11,7 @@ dependentReturnType3.ts(137,9): error TS2322: Type 'Record' is ? R1 : T extends B ? R2 - : (R1 | R2); + : never; // File: Rocket.Chat/apps/meteor/app/katex/client/index.ts @@ -30,7 +30,7 @@ dependentReturnType3.ts(137,9): error TS2322: Type 'Record' is ? string : T extends IMessage ? IMessage - : (string | IMessage) { + : never { if (typeof message === 'string') { return this.render(message); // Ok } @@ -54,7 +54,11 @@ dependentReturnType3.ts(137,9): error TS2322: Type 'Record' is parenthesisSyntax: boolean; }, _isMessage: T, - ): T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : (((message: string) => string) | ((message: IMessage) => IMessage)) { + ): T extends true + ? (message: IMessage) => IMessage + : T extends false + ? (message: string) => string + : never { const instance = new NewKatex(); if (_isMessage) { return (message: IMessage): IMessage => instance.renderMessage(message); // Ok @@ -217,8 +221,4 @@ dependentReturnType3.ts(137,9): error TS2322: Type 'Record' is throw new Error(); } return value.toLowerCase(); // Ok - } - - - - \ No newline at end of file + } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType3.symbols b/tests/baselines/reference/dependentReturnType3.symbols index 8358483e3d8c5..3d39bf1559fba 100644 --- a/tests/baselines/reference/dependentReturnType3.symbols +++ b/tests/baselines/reference/dependentReturnType3.symbols @@ -25,14 +25,12 @@ type HelperCond = ? R2 >R2 : Symbol(R2, Decl(dependentReturnType3.ts, 2, 28)) - : (R1 | R2); ->R1 : Symbol(R1, Decl(dependentReturnType3.ts, 2, 21)) ->R2 : Symbol(R2, Decl(dependentReturnType3.ts, 2, 28)) + : never; // File: Rocket.Chat/apps/meteor/app/katex/client/index.ts interface IMessage { ->IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 20)) html?: string; >html : Symbol(IMessage.html, Decl(dependentReturnType3.ts, 11, 20)) @@ -54,7 +52,7 @@ class NewKatex { renderMessage(message: T): >renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) >T : Symbol(T, Decl(dependentReturnType3.ts, 21, 18)) ->IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 20)) >message : Symbol(message, Decl(dependentReturnType3.ts, 21, 47)) >T : Symbol(T, Decl(dependentReturnType3.ts, 21, 18)) @@ -64,14 +62,12 @@ class NewKatex { ? string : T extends IMessage >T : Symbol(T, Decl(dependentReturnType3.ts, 21, 18)) ->IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 20)) ? IMessage ->IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) - - : (string | IMessage) { ->IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 20)) + : never { if (typeof message === 'string') { >message : Symbol(message, Decl(dependentReturnType3.ts, 21, 47)) @@ -138,225 +134,228 @@ export function createKatexMessageRendering( >_isMessage : Symbol(_isMessage, Decl(dependentReturnType3.ts, 48, 6)) >T : Symbol(T, Decl(dependentReturnType3.ts, 44, 44)) -): T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : (((message: string) => string) | ((message: IMessage) => IMessage)) { +): T extends true >T : Symbol(T, Decl(dependentReturnType3.ts, 44, 44)) ->message : Symbol(message, Decl(dependentReturnType3.ts, 50, 21)) ->IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) ->IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) + + ? (message: IMessage) => IMessage +>message : Symbol(message, Decl(dependentReturnType3.ts, 51, 7)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 20)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 20)) + + : T extends false >T : Symbol(T, Decl(dependentReturnType3.ts, 44, 44)) ->message : Symbol(message, Decl(dependentReturnType3.ts, 50, 73)) ->message : Symbol(message, Decl(dependentReturnType3.ts, 50, 105)) ->message : Symbol(message, Decl(dependentReturnType3.ts, 50, 137)) ->IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) ->IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) + ? (message: string) => string +>message : Symbol(message, Decl(dependentReturnType3.ts, 53, 9)) + + : never { const instance = new NewKatex(); ->instance : Symbol(instance, Decl(dependentReturnType3.ts, 51, 9)) +>instance : Symbol(instance, Decl(dependentReturnType3.ts, 55, 9)) >NewKatex : Symbol(NewKatex, Decl(dependentReturnType3.ts, 14, 1)) if (_isMessage) { >_isMessage : Symbol(_isMessage, Decl(dependentReturnType3.ts, 48, 6)) return (message: IMessage): IMessage => instance.renderMessage(message); // Ok ->message : Symbol(message, Decl(dependentReturnType3.ts, 53, 16)) ->IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) ->IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 24)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 57, 16)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 20)) +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 20)) >instance.renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) ->instance : Symbol(instance, Decl(dependentReturnType3.ts, 51, 9)) +>instance : Symbol(instance, Decl(dependentReturnType3.ts, 55, 9)) >renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) ->message : Symbol(message, Decl(dependentReturnType3.ts, 53, 16)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 57, 16)) } return (message: string): string => instance.renderMessage(message); // Ok ->message : Symbol(message, Decl(dependentReturnType3.ts, 55, 12)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 59, 12)) >instance.renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) ->instance : Symbol(instance, Decl(dependentReturnType3.ts, 51, 9)) +>instance : Symbol(instance, Decl(dependentReturnType3.ts, 55, 9)) >renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) ->message : Symbol(message, Decl(dependentReturnType3.ts, 55, 12)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 59, 12)) } // File: Rocket.Chat/apps/meteor/app/settings/lib/settings.ts type SettingComposedValue = { key: string; value: T }; ->SettingComposedValue : Symbol(SettingComposedValue, Decl(dependentReturnType3.ts, 56, 1)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 59, 26)) ->SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 60, 89)) ->SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 60, 89)) ->key : Symbol(key, Decl(dependentReturnType3.ts, 59, 68)) ->value : Symbol(value, Decl(dependentReturnType3.ts, 59, 81)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 59, 26)) +>SettingComposedValue : Symbol(SettingComposedValue, Decl(dependentReturnType3.ts, 60, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 63, 26)) +>SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 64, 89)) +>SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 64, 89)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 63, 68)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 63, 81)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 63, 26)) type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void; ->SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType3.ts, 59, 93)) ->key : Symbol(key, Decl(dependentReturnType3.ts, 60, 24)) ->value : Symbol(value, Decl(dependentReturnType3.ts, 60, 36)) ->SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 60, 89)) ->initialLoad : Symbol(initialLoad, Decl(dependentReturnType3.ts, 60, 57)) +>SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType3.ts, 63, 93)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 64, 24)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 64, 36)) +>SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 64, 89)) +>initialLoad : Symbol(initialLoad, Decl(dependentReturnType3.ts, 64, 57)) type SettingValue = object; ->SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 60, 89)) +>SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 64, 89)) declare const Meteor: { settings: { [s: string]: any } }; ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->s : Symbol(s, Decl(dependentReturnType3.ts, 63, 37)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>s : Symbol(s, Decl(dependentReturnType3.ts, 67, 37)) declare const _: { isRegExp(x: unknown): x is RegExp; }; ->_ : Symbol(_, Decl(dependentReturnType3.ts, 64, 13)) ->isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 64, 18)) ->x : Symbol(x, Decl(dependentReturnType3.ts, 64, 28)) ->x : Symbol(x, Decl(dependentReturnType3.ts, 64, 28)) +>_ : Symbol(_, Decl(dependentReturnType3.ts, 68, 13)) +>isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 68, 18)) +>x : Symbol(x, Decl(dependentReturnType3.ts, 68, 28)) +>x : Symbol(x, Decl(dependentReturnType3.ts, 68, 28)) >RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) declare function takesRegExp(x: RegExp): void; ->takesRegExp : Symbol(takesRegExp, Decl(dependentReturnType3.ts, 64, 56)) ->x : Symbol(x, Decl(dependentReturnType3.ts, 65, 29)) +>takesRegExp : Symbol(takesRegExp, Decl(dependentReturnType3.ts, 68, 56)) +>x : Symbol(x, Decl(dependentReturnType3.ts, 69, 29)) >RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) declare function takesString(x: string): void; ->takesString : Symbol(takesString, Decl(dependentReturnType3.ts, 65, 46)) ->x : Symbol(x, Decl(dependentReturnType3.ts, 66, 29)) +>takesString : Symbol(takesString, Decl(dependentReturnType3.ts, 69, 46)) +>x : Symbol(x, Decl(dependentReturnType3.ts, 70, 29)) class NewSettingsBase { ->NewSettingsBase : Symbol(NewSettingsBase, Decl(dependentReturnType3.ts, 66, 46)) +>NewSettingsBase : Symbol(NewSettingsBase, Decl(dependentReturnType3.ts, 70, 46)) public newGet( ->newGet : Symbol(NewSettingsBase.newGet, Decl(dependentReturnType3.ts, 68, 23)) ->C : Symbol(C, Decl(dependentReturnType3.ts, 69, 18)) ->SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType3.ts, 59, 93)) ->I : Symbol(I, Decl(dependentReturnType3.ts, 69, 56)) +>newGet : Symbol(NewSettingsBase.newGet, Decl(dependentReturnType3.ts, 72, 23)) +>C : Symbol(C, Decl(dependentReturnType3.ts, 73, 18)) +>SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType3.ts, 63, 93)) +>I : Symbol(I, Decl(dependentReturnType3.ts, 73, 56)) >RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 69, 83)) ->SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 60, 89)) ->SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 60, 89)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 73, 83)) +>SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 64, 89)) +>SettingValue : Symbol(SettingValue, Decl(dependentReturnType3.ts, 64, 89)) _id: I, ->_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) ->I : Symbol(I, Decl(dependentReturnType3.ts, 69, 56)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 73, 123)) +>I : Symbol(I, Decl(dependentReturnType3.ts, 73, 56)) callback?: C, ->callback : Symbol(callback, Decl(dependentReturnType3.ts, 70, 15)) ->C : Symbol(C, Decl(dependentReturnType3.ts, 69, 18)) +>callback : Symbol(callback, Decl(dependentReturnType3.ts, 74, 15)) +>C : Symbol(C, Decl(dependentReturnType3.ts, 73, 18)) ): HelperCondHelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) ->C : Symbol(C, Decl(dependentReturnType3.ts, 69, 18)) +>C : Symbol(C, Decl(dependentReturnType3.ts, 73, 18)) SettingCallback, void, ->SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType3.ts, 59, 93)) +>SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType3.ts, 63, 93)) undefined, HelperCondHelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) ->I : Symbol(I, Decl(dependentReturnType3.ts, 69, 56)) +>I : Symbol(I, Decl(dependentReturnType3.ts, 73, 56)) string, T | undefined, ->T : Symbol(T, Decl(dependentReturnType3.ts, 69, 83)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 73, 83)) RegExp, SettingComposedValue[]>> { >RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) ->SettingComposedValue : Symbol(SettingComposedValue, Decl(dependentReturnType3.ts, 56, 1)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 69, 83)) +>SettingComposedValue : Symbol(SettingComposedValue, Decl(dependentReturnType3.ts, 60, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 73, 83)) if (callback !== undefined) { ->callback : Symbol(callback, Decl(dependentReturnType3.ts, 70, 15)) +>callback : Symbol(callback, Decl(dependentReturnType3.ts, 74, 15)) >undefined : Symbol(undefined) if (!Meteor.settings) { ->Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) return; // Ok } if (_id === '*') { ->_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 73, 123)) return Object.keys(Meteor.settings).forEach((key) => { // Ok >Object.keys(Meteor.settings).forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) >Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) >Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) >forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) ->key : Symbol(key, Decl(dependentReturnType3.ts, 82, 61)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 86, 61)) const value = Meteor.settings[key]; ->value : Symbol(value, Decl(dependentReturnType3.ts, 83, 25)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->key : Symbol(key, Decl(dependentReturnType3.ts, 82, 61)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 87, 25)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 86, 61)) callback(key, value); ->callback : Symbol(callback, Decl(dependentReturnType3.ts, 70, 15)) ->key : Symbol(key, Decl(dependentReturnType3.ts, 82, 61)) ->value : Symbol(value, Decl(dependentReturnType3.ts, 83, 25)) +>callback : Symbol(callback, Decl(dependentReturnType3.ts, 74, 15)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 86, 61)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 87, 25)) }); } if (_.isRegExp(_id) && Meteor.settings) { ->_.isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 64, 18)) ->_ : Symbol(_, Decl(dependentReturnType3.ts, 64, 13)) ->isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 64, 18)) ->_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>_.isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 68, 18)) +>_ : Symbol(_, Decl(dependentReturnType3.ts, 68, 13)) +>isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 68, 18)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 73, 123)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) return Object.keys(Meteor.settings).forEach((key) => { // Ok >Object.keys(Meteor.settings).forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) >Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) >Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) >forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) ->key : Symbol(key, Decl(dependentReturnType3.ts, 88, 61)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 92, 61)) if (!_id.test(key)) { >_id.test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) ->_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 73, 123)) >test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) ->key : Symbol(key, Decl(dependentReturnType3.ts, 88, 61)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 92, 61)) return; } const value = Meteor.settings[key]; ->value : Symbol(value, Decl(dependentReturnType3.ts, 92, 25)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->key : Symbol(key, Decl(dependentReturnType3.ts, 88, 61)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 96, 25)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 92, 61)) callback(key, value); ->callback : Symbol(callback, Decl(dependentReturnType3.ts, 70, 15)) ->key : Symbol(key, Decl(dependentReturnType3.ts, 88, 61)) ->value : Symbol(value, Decl(dependentReturnType3.ts, 92, 25)) +>callback : Symbol(callback, Decl(dependentReturnType3.ts, 74, 15)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 92, 61)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 96, 25)) }); } if (typeof _id === 'string') { ->_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 73, 123)) const value = Meteor.settings[_id]; ->value : Symbol(value, Decl(dependentReturnType3.ts, 98, 21)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 102, 21)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 73, 123)) if (value != null) { ->value : Symbol(value, Decl(dependentReturnType3.ts, 98, 21)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 102, 21)) callback(_id, Meteor.settings[_id]); ->callback : Symbol(callback, Decl(dependentReturnType3.ts, 70, 15)) ->_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>callback : Symbol(callback, Decl(dependentReturnType3.ts, 74, 15)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 73, 123)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 73, 123)) } return; // Ok } @@ -365,71 +364,71 @@ class NewSettingsBase { } if (!Meteor.settings) { // Wrong: we don't know that _id is string here, cannot return undefined ->Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) return undefined; // Error >undefined : Symbol(undefined) } if (_.isRegExp(_id)) { ->_.isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 64, 18)) ->_ : Symbol(_, Decl(dependentReturnType3.ts, 64, 13)) ->isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 64, 18)) ->_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>_.isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 68, 18)) +>_ : Symbol(_, Decl(dependentReturnType3.ts, 68, 13)) +>isRegExp : Symbol(isRegExp, Decl(dependentReturnType3.ts, 68, 18)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 73, 123)) return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { >Object.keys(Meteor.settings).reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) >Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) >reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) ->items : Symbol(items, Decl(dependentReturnType3.ts, 113, 56)) ->SettingComposedValue : Symbol(SettingComposedValue, Decl(dependentReturnType3.ts, 56, 1)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 69, 83)) ->key : Symbol(key, Decl(dependentReturnType3.ts, 113, 89)) +>items : Symbol(items, Decl(dependentReturnType3.ts, 117, 56)) +>SettingComposedValue : Symbol(SettingComposedValue, Decl(dependentReturnType3.ts, 60, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 73, 83)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 117, 89)) const value = Meteor.settings[key]; ->value : Symbol(value, Decl(dependentReturnType3.ts, 114, 9)) ->Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->key : Symbol(key, Decl(dependentReturnType3.ts, 113, 89)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 118, 9)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 117, 89)) if (_id.test(key)) { >_id.test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) ->_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 73, 123)) >test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) ->key : Symbol(key, Decl(dependentReturnType3.ts, 113, 89)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 117, 89)) items.push({ >items.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) ->items : Symbol(items, Decl(dependentReturnType3.ts, 113, 56)) +>items : Symbol(items, Decl(dependentReturnType3.ts, 117, 56)) >push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) key, ->key : Symbol(key, Decl(dependentReturnType3.ts, 116, 17)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 120, 17)) value, ->value : Symbol(value, Decl(dependentReturnType3.ts, 117, 10)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 121, 10)) }); } return items; ->items : Symbol(items, Decl(dependentReturnType3.ts, 113, 56)) +>items : Symbol(items, Decl(dependentReturnType3.ts, 117, 56)) }, []); // Ok } return Meteor.settings?.[_id]; // Error ->Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 63, 13)) ->settings : Symbol(settings, Decl(dependentReturnType3.ts, 63, 23)) ->_id : Symbol(_id, Decl(dependentReturnType3.ts, 69, 123)) +>Meteor.settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>Meteor : Symbol(Meteor, Decl(dependentReturnType3.ts, 67, 13)) +>settings : Symbol(settings, Decl(dependentReturnType3.ts, 67, 23)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 73, 123)) // The indexing currently doesn't work because it doesn't use the narrowed type of `_id`. } @@ -437,110 +436,110 @@ class NewSettingsBase { // File: Rocket.Chat/apps/meteor/app/ui-utils/client/lib/messageBox.ts type MessageBoxAction = object; ->MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) function getWithBug(group: T): ->getWithBug : Symbol(getWithBug, Decl(dependentReturnType3.ts, 131, 31)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 133, 20)) ->group : Symbol(group, Decl(dependentReturnType3.ts, 133, 50)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 133, 20)) +>getWithBug : Symbol(getWithBug, Decl(dependentReturnType3.ts, 135, 31)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 137, 20)) +>group : Symbol(group, Decl(dependentReturnType3.ts, 137, 50)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 137, 20)) HelperCond> { >HelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 133, 20)) ->MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 137, 20)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) >Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) ->MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) if (!group) { ->group : Symbol(group, Decl(dependentReturnType3.ts, 133, 50)) +>group : Symbol(group, Decl(dependentReturnType3.ts, 137, 50)) return {} as Record; // Error, could fall into this branch when group is empty string >Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) ->MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) } return [] as MessageBoxAction[]; // Ok ->MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) } function getWithoutBug(group: T): ->getWithoutBug : Symbol(getWithoutBug, Decl(dependentReturnType3.ts, 140, 1)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 142, 23)) ->group : Symbol(group, Decl(dependentReturnType3.ts, 142, 53)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 142, 23)) +>getWithoutBug : Symbol(getWithoutBug, Decl(dependentReturnType3.ts, 144, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 146, 23)) +>group : Symbol(group, Decl(dependentReturnType3.ts, 146, 53)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 146, 23)) HelperCond> { >HelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 142, 23)) ->MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 146, 23)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) >Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) ->MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) if (group === undefined) { ->group : Symbol(group, Decl(dependentReturnType3.ts, 142, 53)) +>group : Symbol(group, Decl(dependentReturnType3.ts, 146, 53)) >undefined : Symbol(undefined) return {} as Record; // Ok >Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) ->MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) } return [] as MessageBoxAction[]; // Ok ->MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 128, 1)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) } // File: Rocket.Chat/apps/meteor/ee/server/lib/engagementDashboard/date.ts declare function mapDateForAPI(x: string): Date; ->mapDateForAPI : Symbol(mapDateForAPI, Decl(dependentReturnType3.ts, 149, 1)) ->x : Symbol(x, Decl(dependentReturnType3.ts, 152, 31)) +>mapDateForAPI : Symbol(mapDateForAPI, Decl(dependentReturnType3.ts, 153, 1)) +>x : Symbol(x, Decl(dependentReturnType3.ts, 156, 31)) >Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) export function transformDatesForAPI( ->transformDatesForAPI : Symbol(transformDatesForAPI, Decl(dependentReturnType3.ts, 152, 48)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 153, 37)) +>transformDatesForAPI : Symbol(transformDatesForAPI, Decl(dependentReturnType3.ts, 156, 48)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 157, 37)) start: string, ->start : Symbol(start, Decl(dependentReturnType3.ts, 153, 67)) +>start : Symbol(start, Decl(dependentReturnType3.ts, 157, 67)) end?: T ->end : Symbol(end, Decl(dependentReturnType3.ts, 154, 18)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 153, 37)) +>end : Symbol(end, Decl(dependentReturnType3.ts, 158, 18)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 157, 37)) ): HelperCond { >HelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 153, 37)) ->start : Symbol(start, Decl(dependentReturnType3.ts, 156, 26)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 157, 37)) +>start : Symbol(start, Decl(dependentReturnType3.ts, 160, 26)) >Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) ->end : Symbol(end, Decl(dependentReturnType3.ts, 156, 39)) +>end : Symbol(end, Decl(dependentReturnType3.ts, 160, 39)) >Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) ->start : Symbol(start, Decl(dependentReturnType3.ts, 156, 65)) +>start : Symbol(start, Decl(dependentReturnType3.ts, 160, 65)) >Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) ->end : Symbol(end, Decl(dependentReturnType3.ts, 156, 78)) +>end : Symbol(end, Decl(dependentReturnType3.ts, 160, 78)) return end !== undefined ? // Ok ->end : Symbol(end, Decl(dependentReturnType3.ts, 154, 18)) +>end : Symbol(end, Decl(dependentReturnType3.ts, 158, 18)) >undefined : Symbol(undefined) { start: mapDateForAPI(start), ->start : Symbol(start, Decl(dependentReturnType3.ts, 158, 9)) ->mapDateForAPI : Symbol(mapDateForAPI, Decl(dependentReturnType3.ts, 149, 1)) ->start : Symbol(start, Decl(dependentReturnType3.ts, 153, 67)) +>start : Symbol(start, Decl(dependentReturnType3.ts, 162, 9)) +>mapDateForAPI : Symbol(mapDateForAPI, Decl(dependentReturnType3.ts, 153, 1)) +>start : Symbol(start, Decl(dependentReturnType3.ts, 157, 67)) end: mapDateForAPI(end), ->end : Symbol(end, Decl(dependentReturnType3.ts, 159, 40)) ->mapDateForAPI : Symbol(mapDateForAPI, Decl(dependentReturnType3.ts, 149, 1)) ->end : Symbol(end, Decl(dependentReturnType3.ts, 154, 18)) +>end : Symbol(end, Decl(dependentReturnType3.ts, 163, 40)) +>mapDateForAPI : Symbol(mapDateForAPI, Decl(dependentReturnType3.ts, 153, 1)) +>end : Symbol(end, Decl(dependentReturnType3.ts, 158, 18)) } : { start: mapDateForAPI(start), ->start : Symbol(start, Decl(dependentReturnType3.ts, 162, 9)) ->mapDateForAPI : Symbol(mapDateForAPI, Decl(dependentReturnType3.ts, 149, 1)) ->start : Symbol(start, Decl(dependentReturnType3.ts, 153, 67)) +>start : Symbol(start, Decl(dependentReturnType3.ts, 166, 9)) +>mapDateForAPI : Symbol(mapDateForAPI, Decl(dependentReturnType3.ts, 153, 1)) +>start : Symbol(start, Decl(dependentReturnType3.ts, 157, 67)) end: undefined ->end : Symbol(end, Decl(dependentReturnType3.ts, 163, 40)) +>end : Symbol(end, Decl(dependentReturnType3.ts, 167, 40)) >undefined : Symbol(undefined) }; @@ -548,103 +547,103 @@ export function transformDatesForAPI( // File: Rocket.Chat/packages/agenda/src/Agenda.ts type RepeatOptions = object; ->RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 166, 1)) +>RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 170, 1)) type Job = object; ->Job : Symbol(Job, Decl(dependentReturnType3.ts, 169, 28)) +>Job : Symbol(Job, Decl(dependentReturnType3.ts, 173, 28)) type IJob = { data: object }; ->IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 170, 18)) ->data : Symbol(data, Decl(dependentReturnType3.ts, 171, 13)) +>IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 174, 18)) +>data : Symbol(data, Decl(dependentReturnType3.ts, 175, 13)) class NewAgenda { ->NewAgenda : Symbol(NewAgenda, Decl(dependentReturnType3.ts, 171, 29)) +>NewAgenda : Symbol(NewAgenda, Decl(dependentReturnType3.ts, 175, 29)) public async _createIntervalJob(interval: string | number, name: string, data: IJob['data'], options: RepeatOptions): Promise { return undefined as any; } ->_createIntervalJob : Symbol(NewAgenda._createIntervalJob, Decl(dependentReturnType3.ts, 172, 17)) ->interval : Symbol(interval, Decl(dependentReturnType3.ts, 173, 36)) ->name : Symbol(name, Decl(dependentReturnType3.ts, 173, 62)) ->data : Symbol(data, Decl(dependentReturnType3.ts, 173, 76)) ->IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 170, 18)) ->options : Symbol(options, Decl(dependentReturnType3.ts, 173, 96)) ->RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 166, 1)) +>_createIntervalJob : Symbol(NewAgenda._createIntervalJob, Decl(dependentReturnType3.ts, 176, 17)) +>interval : Symbol(interval, Decl(dependentReturnType3.ts, 177, 36)) +>name : Symbol(name, Decl(dependentReturnType3.ts, 177, 62)) +>data : Symbol(data, Decl(dependentReturnType3.ts, 177, 76)) +>IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 174, 18)) +>options : Symbol(options, Decl(dependentReturnType3.ts, 177, 96)) +>RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 170, 1)) >Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) ->Job : Symbol(Job, Decl(dependentReturnType3.ts, 169, 28)) +>Job : Symbol(Job, Decl(dependentReturnType3.ts, 173, 28)) >undefined : Symbol(undefined) private _createIntervalJobs( ->_createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(dependentReturnType3.ts, 173, 163)) +>_createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(dependentReturnType3.ts, 177, 163)) interval: string | number, ->interval : Symbol(interval, Decl(dependentReturnType3.ts, 174, 32)) +>interval : Symbol(interval, Decl(dependentReturnType3.ts, 178, 32)) names: string[], ->names : Symbol(names, Decl(dependentReturnType3.ts, 175, 34)) +>names : Symbol(names, Decl(dependentReturnType3.ts, 179, 34)) data: IJob['data'], ->data : Symbol(data, Decl(dependentReturnType3.ts, 176, 24)) ->IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 170, 18)) +>data : Symbol(data, Decl(dependentReturnType3.ts, 180, 24)) +>IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 174, 18)) options: RepeatOptions, ->options : Symbol(options, Decl(dependentReturnType3.ts, 177, 27)) ->RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 166, 1)) +>options : Symbol(options, Decl(dependentReturnType3.ts, 181, 27)) +>RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 170, 1)) ): Promise | undefined { return undefined as any; } >Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) ->Job : Symbol(Job, Decl(dependentReturnType3.ts, 169, 28)) +>Job : Symbol(Job, Decl(dependentReturnType3.ts, 173, 28)) >undefined : Symbol(undefined) public async newEvery( ->newEvery : Symbol(NewAgenda.newEvery, Decl(dependentReturnType3.ts, 179, 62)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 181, 26)) +>newEvery : Symbol(NewAgenda.newEvery, Decl(dependentReturnType3.ts, 183, 62)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 185, 26)) interval: string | number, ->interval : Symbol(interval, Decl(dependentReturnType3.ts, 181, 55)) +>interval : Symbol(interval, Decl(dependentReturnType3.ts, 185, 55)) name: T, ->name : Symbol(name, Decl(dependentReturnType3.ts, 182, 34)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 181, 26)) +>name : Symbol(name, Decl(dependentReturnType3.ts, 186, 34)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 185, 26)) data: IJob['data'], ->data : Symbol(data, Decl(dependentReturnType3.ts, 183, 16)) ->IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 170, 18)) +>data : Symbol(data, Decl(dependentReturnType3.ts, 187, 16)) +>IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 174, 18)) options: RepeatOptions): Promise> { ->options : Symbol(options, Decl(dependentReturnType3.ts, 184, 27)) ->RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 166, 1)) +>options : Symbol(options, Decl(dependentReturnType3.ts, 188, 27)) +>RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 170, 1)) >Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) >HelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 181, 26)) ->Job : Symbol(Job, Decl(dependentReturnType3.ts, 169, 28)) ->Job : Symbol(Job, Decl(dependentReturnType3.ts, 169, 28)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 185, 26)) +>Job : Symbol(Job, Decl(dependentReturnType3.ts, 173, 28)) +>Job : Symbol(Job, Decl(dependentReturnType3.ts, 173, 28)) if (typeof name === 'string') { ->name : Symbol(name, Decl(dependentReturnType3.ts, 182, 34)) +>name : Symbol(name, Decl(dependentReturnType3.ts, 186, 34)) return this._createIntervalJob(interval, name, data, options); // Ok ->this._createIntervalJob : Symbol(NewAgenda._createIntervalJob, Decl(dependentReturnType3.ts, 172, 17)) ->this : Symbol(NewAgenda, Decl(dependentReturnType3.ts, 171, 29)) ->_createIntervalJob : Symbol(NewAgenda._createIntervalJob, Decl(dependentReturnType3.ts, 172, 17)) ->interval : Symbol(interval, Decl(dependentReturnType3.ts, 181, 55)) ->name : Symbol(name, Decl(dependentReturnType3.ts, 182, 34)) ->data : Symbol(data, Decl(dependentReturnType3.ts, 183, 16)) ->options : Symbol(options, Decl(dependentReturnType3.ts, 184, 27)) +>this._createIntervalJob : Symbol(NewAgenda._createIntervalJob, Decl(dependentReturnType3.ts, 176, 17)) +>this : Symbol(NewAgenda, Decl(dependentReturnType3.ts, 175, 29)) +>_createIntervalJob : Symbol(NewAgenda._createIntervalJob, Decl(dependentReturnType3.ts, 176, 17)) +>interval : Symbol(interval, Decl(dependentReturnType3.ts, 185, 55)) +>name : Symbol(name, Decl(dependentReturnType3.ts, 186, 34)) +>data : Symbol(data, Decl(dependentReturnType3.ts, 187, 16)) +>options : Symbol(options, Decl(dependentReturnType3.ts, 188, 27)) } if (Array.isArray(name)) { >Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) >Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) >isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) ->name : Symbol(name, Decl(dependentReturnType3.ts, 182, 34)) +>name : Symbol(name, Decl(dependentReturnType3.ts, 186, 34)) return this._createIntervalJobs(interval, name, data, options); // Ok ->this._createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(dependentReturnType3.ts, 173, 163)) ->this : Symbol(NewAgenda, Decl(dependentReturnType3.ts, 171, 29)) ->_createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(dependentReturnType3.ts, 173, 163)) ->interval : Symbol(interval, Decl(dependentReturnType3.ts, 181, 55)) ->name : Symbol(name, Decl(dependentReturnType3.ts, 182, 34)) ->data : Symbol(data, Decl(dependentReturnType3.ts, 183, 16)) ->options : Symbol(options, Decl(dependentReturnType3.ts, 184, 27)) +>this._createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(dependentReturnType3.ts, 177, 163)) +>this : Symbol(NewAgenda, Decl(dependentReturnType3.ts, 175, 29)) +>_createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(dependentReturnType3.ts, 177, 163)) +>interval : Symbol(interval, Decl(dependentReturnType3.ts, 185, 55)) +>name : Symbol(name, Decl(dependentReturnType3.ts, 186, 34)) +>data : Symbol(data, Decl(dependentReturnType3.ts, 187, 16)) +>options : Symbol(options, Decl(dependentReturnType3.ts, 188, 27)) // Possible bug in original: createIntervalJobs can return undefined, but the original overload did not acount for that. } @@ -657,28 +656,24 @@ class NewAgenda { // File: angular/packages/common/src/pipes/case_conversion_pipes.ts function transform1(value: T): HelperCond { ->transform1 : Symbol(transform1, Decl(dependentReturnType3.ts, 197, 1)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 201, 20)) ->value : Symbol(value, Decl(dependentReturnType3.ts, 201, 57)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 201, 20)) +>transform1 : Symbol(transform1, Decl(dependentReturnType3.ts, 201, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 205, 20)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 205, 57)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 205, 20)) >HelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) ->T : Symbol(T, Decl(dependentReturnType3.ts, 201, 20)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 205, 20)) if (value == null) return null; // Ok ->value : Symbol(value, Decl(dependentReturnType3.ts, 201, 57)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 205, 57)) if (typeof value !== 'string') { ->value : Symbol(value, Decl(dependentReturnType3.ts, 201, 57)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 205, 57)) throw new Error(); >Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) } return value.toLowerCase(); // Ok >value.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) ->value : Symbol(value, Decl(dependentReturnType3.ts, 201, 57)) +>value : Symbol(value, Decl(dependentReturnType3.ts, 205, 57)) >toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) } - - - - diff --git a/tests/baselines/reference/dependentReturnType3.types b/tests/baselines/reference/dependentReturnType3.types index e00bda1641eab..ac94d85e9b9e3 100644 --- a/tests/baselines/reference/dependentReturnType3.types +++ b/tests/baselines/reference/dependentReturnType3.types @@ -11,7 +11,7 @@ type HelperCond = ? R1 : T extends B ? R2 - : (R1 | R2); + : never; // File: Rocket.Chat/apps/meteor/app/katex/client/index.ts @@ -41,8 +41,8 @@ class NewKatex { } renderMessage(message: T): ->renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : (string | IMessage) -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >message : T > : ^ @@ -50,7 +50,7 @@ class NewKatex { ? string : T extends IMessage ? IMessage - : (string | IMessage) { + : never { if (typeof message === 'string') { >typeof message === 'string' : boolean > : ^^^^^^^ @@ -149,8 +149,8 @@ class NewKatex { } export function createKatexMessageRendering( ->createKatexMessageRendering : (options: { dollarSyntax: boolean; parenthesisSyntax: boolean; }, _isMessage: T) => T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : (((message: string) => string) | ((message: IMessage) => IMessage)) -> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>createKatexMessageRendering : (options: { dollarSyntax: boolean; parenthesisSyntax: boolean; }, _isMessage: T) => T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ >true : true > : ^^^^ >false : false @@ -173,20 +173,23 @@ export function createKatexMessageRendering( >_isMessage : T > : ^ -): T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : (((message: string) => string) | ((message: IMessage) => IMessage)) { +): T extends true >true : true > : ^^^^ + + ? (message: IMessage) => IMessage >message : IMessage > : ^^^^^^^^ + + : T extends false >false : false > : ^^^^^ + + ? (message: string) => string >message : string > : ^^^^^^ ->message : string -> : ^^^^^^ ->message : IMessage -> : ^^^^^^^^ + : never { const instance = new NewKatex(); >instance : NewKatex > : ^^^^^^^^ @@ -206,12 +209,12 @@ export function createKatexMessageRendering( > : ^^^^^^^^ >instance.renderMessage(message) : IMessage > : ^^^^^^^^ ->instance.renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : (string | IMessage) -> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ +>instance.renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : never +> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ >instance : NewKatex > : ^^^^^^^^ ->renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : (string | IMessage) -> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ +>renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : never +> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ >message : IMessage > : ^^^^^^^^ } @@ -222,12 +225,12 @@ export function createKatexMessageRendering( > : ^^^^^^ >instance.renderMessage(message) : string > : ^^^^^^ ->instance.renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : (string | IMessage) -> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ +>instance.renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : never +> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ >instance : NewKatex > : ^^^^^^^^ ->renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : (string | IMessage) -> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ +>renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : never +> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ >message : string > : ^^^^^^ } @@ -995,7 +998,3 @@ function transform1(value: T): HelperCondtoLowerCase : () => string > : ^^^^^^ } - - - - diff --git a/tests/baselines/reference/dependentReturnType4.errors.txt b/tests/baselines/reference/dependentReturnType4.errors.txt index 6c5b298a65d77..bbee8dd400acd 100644 --- a/tests/baselines/reference/dependentReturnType4.errors.txt +++ b/tests/baselines/reference/dependentReturnType4.errors.txt @@ -1,7 +1,7 @@ dependentReturnType4.ts(48,15): error TS2322: Type 'string | number' is not assignable to type 'string'. Type 'number' is not assignable to type 'string'. -dependentReturnType4.ts(49,9): error TS2322: Type 'number' is not assignable to type 'T extends number ? string : T extends string ? number : string | number'. -dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to type 'T extends number ? string : T extends string ? number : string | number'. +dependentReturnType4.ts(49,9): error TS2322: Type 'number' is not assignable to type 'T extends number ? string : T extends string ? number : never'. +dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to type 'T extends number ? string : T extends string ? number : never'. ==== dependentReturnType4.ts (3 errors) ==== @@ -9,7 +9,7 @@ dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to declare const rand: { a?: never }; type Missing = typeof rand.a; declare function takesString(x: string): void; - function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { + function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : never { if (obj.hasOwnProperty("a")) { takesString(obj.a); return 1; @@ -18,7 +18,7 @@ dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to } function foo(opts: { x?: T }): - T extends undefined ? 0 : T extends string ? 1 : 0 | 1 { + T extends undefined ? 0 : T extends string ? 1 : never { if (opts.x === undefined) { return 0; } @@ -26,7 +26,7 @@ dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to } function bar(x?: T ): - T extends Missing ? 0 : T extends string ? 1 : 0 | 1 { + T extends Missing ? 0 : T extends string ? 1 : never { if (x === undefined) { return 0; } @@ -34,7 +34,7 @@ dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to } // Aliased narrowing - function inlined(x: T): T extends number ? string : T extends string ? number : string | number { + function inlined(x: T): T extends number ? string : T extends string ? number : never { const t = typeof x === "string"; if (t) { const y: string = x; @@ -44,7 +44,7 @@ dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to } // Don't narrow more than 5 levels of aliasing - function inlined6(x: T): T extends number ? string : T extends string ? number : string | number { + function inlined6(x: T): T extends number ? string : T extends string ? number : never { const t1 = typeof x === "string"; const t2 = t1; const t3 = t2; @@ -58,25 +58,25 @@ dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to !!! error TS2322: Type 'number' is not assignable to type 'string'. return 1; ~~~~~~ -!!! error TS2322: Type 'number' is not assignable to type 'T extends number ? string : T extends string ? number : string | number'. +!!! error TS2322: Type 'number' is not assignable to type 'T extends number ? string : T extends string ? number : never'. } return "one"; ~~~~~~ -!!! error TS2322: Type 'string' is not assignable to type 'T extends number ? string : T extends string ? number : string | number'. +!!! error TS2322: Type 'string' is not assignable to type 'T extends number ? string : T extends string ? number : never'. } type A = { kind: "a", a: number }; type B = { kind: "b", b: string }; type AOrB = A | B; - function subexpression(x: T): T extends A ? number : T extends B ? string : number | string { + function subexpression(x: T): T extends A ? number : T extends B ? string : never { if (x.kind === "b") { return "some str"; } return 0; } - function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : 0 | 1 { + function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : never { switch (true) { case x: return 1; @@ -85,7 +85,7 @@ dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to } // Don't raise errors when getting the narrowed type of synthesized nodes - type Ret = T extends string ? 1 : T extends number ? 2 : 1 | 2; + type Ret = T extends string ? 1 : T extends number ? 2 : never; function f(x: T): Ret { let y!: T; if (typeof y === "string") { diff --git a/tests/baselines/reference/dependentReturnType4.symbols b/tests/baselines/reference/dependentReturnType4.symbols index 294e09d143be9..1e71eadc57aef 100644 --- a/tests/baselines/reference/dependentReturnType4.symbols +++ b/tests/baselines/reference/dependentReturnType4.symbols @@ -16,7 +16,7 @@ declare function takesString(x: string): void; >takesString : Symbol(takesString, Decl(dependentReturnType4.ts, 2, 29)) >x : Symbol(x, Decl(dependentReturnType4.ts, 3, 29)) -function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { +function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : never { >hasOwnP : Symbol(hasOwnP, Decl(dependentReturnType4.ts, 3, 46)) >T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) >Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 1, 34)) @@ -49,7 +49,7 @@ function foo(opts: { x?: T }): >x : Symbol(x, Decl(dependentReturnType4.ts, 12, 50)) >T : Symbol(T, Decl(dependentReturnType4.ts, 12, 13)) - T extends undefined ? 0 : T extends string ? 1 : 0 | 1 { + T extends undefined ? 0 : T extends string ? 1 : never { >T : Symbol(T, Decl(dependentReturnType4.ts, 12, 13)) >T : Symbol(T, Decl(dependentReturnType4.ts, 12, 13)) @@ -71,7 +71,7 @@ function bar(x?: T ): >x : Symbol(x, Decl(dependentReturnType4.ts, 20, 41)) >T : Symbol(T, Decl(dependentReturnType4.ts, 20, 13)) - T extends Missing ? 0 : T extends string ? 1 : 0 | 1 { + T extends Missing ? 0 : T extends string ? 1 : never { >T : Symbol(T, Decl(dependentReturnType4.ts, 20, 13)) >Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 1, 34)) >T : Symbol(T, Decl(dependentReturnType4.ts, 20, 13)) @@ -86,7 +86,7 @@ function bar(x?: T ): } // Aliased narrowing -function inlined(x: T): T extends number ? string : T extends string ? number : string | number { +function inlined(x: T): T extends number ? string : T extends string ? number : never { >inlined : Symbol(inlined, Decl(dependentReturnType4.ts, 26, 1)) >T : Symbol(T, Decl(dependentReturnType4.ts, 29, 17)) >x : Symbol(x, Decl(dependentReturnType4.ts, 29, 44)) @@ -111,7 +111,7 @@ function inlined(x: T): T extends number ? string : T } // Don't narrow more than 5 levels of aliasing -function inlined6(x: T): T extends number ? string : T extends string ? number : string | number { +function inlined6(x: T): T extends number ? string : T extends string ? number : never { >inlined6 : Symbol(inlined6, Decl(dependentReturnType4.ts, 36, 1)) >T : Symbol(T, Decl(dependentReturnType4.ts, 39, 18)) >x : Symbol(x, Decl(dependentReturnType4.ts, 39, 45)) @@ -170,7 +170,7 @@ type AOrB = A | B; >A : Symbol(A, Decl(dependentReturnType4.ts, 51, 1)) >B : Symbol(B, Decl(dependentReturnType4.ts, 53, 34)) -function subexpression(x: T): T extends A ? number : T extends B ? string : number | string { +function subexpression(x: T): T extends A ? number : T extends B ? string : never { >subexpression : Symbol(subexpression, Decl(dependentReturnType4.ts, 55, 18)) >T : Symbol(T, Decl(dependentReturnType4.ts, 57, 23)) >AOrB : Symbol(AOrB, Decl(dependentReturnType4.ts, 54, 34)) @@ -191,7 +191,7 @@ function subexpression(x: T): T extends A ? number : T extends B return 0; } -function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : 0 | 1 { +function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : never { >switchTrue : Symbol(switchTrue, Decl(dependentReturnType4.ts, 62, 1)) >T : Symbol(T, Decl(dependentReturnType4.ts, 64, 20)) >x : Symbol(x, Decl(dependentReturnType4.ts, 64, 39)) @@ -209,7 +209,7 @@ function switchTrue(x: T): T extends true ? 1 : T extends fal } // Don't raise errors when getting the narrowed type of synthesized nodes -type Ret = T extends string ? 1 : T extends number ? 2 : 1 | 2; +type Ret = T extends string ? 1 : T extends number ? 2 : never; >Ret : Symbol(Ret, Decl(dependentReturnType4.ts, 70, 1)) >T : Symbol(T, Decl(dependentReturnType4.ts, 73, 9)) >T : Symbol(T, Decl(dependentReturnType4.ts, 73, 9)) diff --git a/tests/baselines/reference/dependentReturnType4.types b/tests/baselines/reference/dependentReturnType4.types index 276f3050fd34b..98d57a124daa3 100644 --- a/tests/baselines/reference/dependentReturnType4.types +++ b/tests/baselines/reference/dependentReturnType4.types @@ -24,8 +24,8 @@ declare function takesString(x: string): void; >x : string > : ^^^^^^ -function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { ->hasOwnP : (obj: { a?: T; }) => T extends string ? 1 : T extends undefined ? 2 : 1 | 2 +function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : never { +>hasOwnP : (obj: { a?: T; }) => T extends string ? 1 : T extends undefined ? 2 : never > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >obj : { a?: T; } > : ^^^^^^ ^^^ @@ -66,14 +66,14 @@ function hasOwnP(obj: { a?: T }): T extends string ? } function foo(opts: { x?: T }): ->foo : (opts: { x?: T; }) => T extends undefined ? 0 : T extends string ? 1 : 0 | 1 +>foo : (opts: { x?: T; }) => T extends undefined ? 0 : T extends string ? 1 : never > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >opts : { x?: T; } > : ^^^^^^ ^^^ >x : T | undefined > : ^^^^^^^^^^^^^ - T extends undefined ? 0 : T extends string ? 1 : 0 | 1 { + T extends undefined ? 0 : T extends string ? 1 : never { if (opts.x === undefined) { >opts.x === undefined : boolean > : ^^^^^^^ @@ -96,12 +96,12 @@ function foo(opts: { x?: T }): } function bar(x?: T ): ->bar : (x?: T) => T extends Missing ? 0 : T extends string ? 1 : 0 | 1 +>bar : (x?: T) => T extends Missing ? 0 : T extends string ? 1 : never > : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ >x : T | undefined > : ^^^^^^^^^^^^^ - T extends Missing ? 0 : T extends string ? 1 : 0 | 1 { + T extends Missing ? 0 : T extends string ? 1 : never { if (x === undefined) { >x === undefined : boolean > : ^^^^^^^ @@ -120,9 +120,9 @@ function bar(x?: T ): } // Aliased narrowing -function inlined(x: T): T extends number ? string : T extends string ? number : string | number { ->inlined : (x: T) => T extends number ? string : T extends string ? number : string | number -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +function inlined(x: T): T extends number ? string : T extends string ? number : never { +>inlined : (x: T) => T extends number ? string : T extends string ? number : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T > : ^ @@ -158,9 +158,9 @@ function inlined(x: T): T extends number ? string : T } // Don't narrow more than 5 levels of aliasing -function inlined6(x: T): T extends number ? string : T extends string ? number : string | number { ->inlined6 : (x: T) => T extends number ? string : T extends string ? number : string | number -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +function inlined6(x: T): T extends number ? string : T extends string ? number : never { +>inlined6 : (x: T) => T extends number ? string : T extends string ? number : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T > : ^ @@ -245,9 +245,9 @@ type AOrB = A | B; >AOrB : AOrB > : ^^^^ -function subexpression(x: T): T extends A ? number : T extends B ? string : number | string { ->subexpression : (x: T) => T extends A ? number : T extends B ? string : number | string -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +function subexpression(x: T): T extends A ? number : T extends B ? string : never { +>subexpression : (x: T) => T extends A ? number : T extends B ? string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T > : ^ @@ -272,8 +272,8 @@ function subexpression(x: T): T extends A ? number : T extends B > : ^ } -function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : 0 | 1 { ->switchTrue : (x: T) => T extends true ? 1 : T extends false ? 0 : 0 | 1 +function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : never { +>switchTrue : (x: T) => T extends true ? 1 : T extends false ? 0 : never > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T > : ^ @@ -300,7 +300,7 @@ function switchTrue(x: T): T extends true ? 1 : T extends fal } // Don't raise errors when getting the narrowed type of synthesized nodes -type Ret = T extends string ? 1 : T extends number ? 2 : 1 | 2; +type Ret = T extends string ? 1 : T extends number ? 2 : never; >Ret : Ret > : ^^^^^^ diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index 33c042a22af55..f393a4b451d15 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -67,25 +67,21 @@ interface Four { f: "f"; g: "g"; } - -function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { // Badly written conditional +// Badly written conditional return type, will not trigger narrowing +function f10(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four { if (x === 1 || x === 2) { - return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok - return { a: "a" }; // Error + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Error } - // Excess property becomes a problem with the change, - // because we now check assignability to a narrower type... return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error } - -function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : One | Two | Three | Four { // Well written conditional +// Well written conditional +function f101(x: T): T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : T extends 4 ? Four : never { if (x === 1 || x === 2) { return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f" }; // Ok - return { a: "a" }; // Error } // Excess property becomes a problem with the change, // because we now check assignability to a narrower type... - return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // EPC Error } // Asymmetry @@ -94,7 +90,7 @@ function conditionalProducingIf arg is LeftIn, produceLeftOut: (arg: LeftIn) => LeftOut, produceRightOut: (arg: RightIn) => RightOut): - Arg extends LeftIn ? LeftOut : RightOut + Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never { type OK = Arg extends LeftIn ? LeftOut : RightOut; if (cond(arg)) { @@ -112,12 +108,12 @@ interface Dog extends Animal { bark: () => string; } -// This is unsafe +// This would be unsafe to narrow. declare function isDog(x: Animal): x is Dog; declare function doggy(x: Dog): number; function f12(x: T): T extends Dog ? number : string { if (isDog(x)) { // `x` has type `T & Dog` here - return doggy(x); // The narrowed conditional return type has deferred resolution, so this doesn't work. + return doggy(x); } return ""; // Error: Should not work because we can't express "not a Dog" in the type system } @@ -151,20 +147,16 @@ export function bbb(value: AB): "a" { class Unnamed { root!: { name: string }; - // Error because parameter is optional - name(name?: T): T extends string ? this : string { + // Error: No narrowing because parameter is optional but `T` doesn't allow for undefined + name(name?: T): T extends string ? this : T extends undefined ? string : never { if (typeof name === 'undefined') { return this.root.name; } return this; } - // Error because parameter is optional? - nameWithError(name?: T): T extends string ? this : string { - return this; // Error: Investigate error message - } // Good conditional - name2(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { + name2(name?: T): T extends string ? this : T extends undefined ? string : never { if (typeof name === 'undefined') { return this.root.name; // Ok } @@ -173,7 +165,7 @@ class Unnamed { } // Good conditional, wrong return expressions - name3(name?: T): T extends string ? this : T extends undefined ? string : this | undefined { + name3(name?: T): T extends string ? this : T extends undefined ? string : never { if (typeof name === 'undefined') { return this; // Error } @@ -182,36 +174,40 @@ class Unnamed { } } +// Conditional expressions interface Aa { 1: number; 2: string; - 3: string; + 3: boolean; } function trivialConditional(x: T): Aa[T] { if (x !== 1) { - return x === 2 ? "" : `${x}`; + return x === 2 ? "" : true; } else { return 0; } } -// Conditional expressions function conditional(x: T): - T extends true ? 1 : T extends false ? 2 : 1 | 2 { + T extends true ? 1 : T extends false ? 2 : never { return x ? 1 : 2; // Ok } -function contextualConditional(x: T): T extends "a" ? "a" : T extends "b" ? number : "a" | number { +function contextualConditional( + x: T +): T extends "a" ? "a" : T extends "b" ? number : never { return x === "a" ? x : parseInt(x); // Ok } -function conditionalWithError(x: T): T extends "a" ? number : T extends "b" ? string : number | string { +function conditionalWithError( + x: T +): T extends "a" ? number : T extends "b" ? string : never { return x === "a" ? x : parseInt(x); // Error } -// Multiple reductions +// Multiple indexed type reductions interface BB { "a": number; [y: number]: string; @@ -232,36 +228,44 @@ function reduction(x: T, y: U): AA[U return undefined as never; } -// Substitution types are not narrowed? -function subsCond(x: T): T extends 1 | 2 ? (T extends 1 ? string : boolean) : number { +// Substitution types are not narrowed +function subsCond( + x: T, +): T extends 1 | 2 + ? T extends 1 + ? string + : T extends 2 + ? boolean + : never + : T extends 3 + ? number + : never { if (x === 1) { return ""; + } else if (x == 2) { + return true; } + return 3; } -// Unsafe: supertype problem + +// Unsafe: check types overlap declare function q(x: object): x is { b: number }; -function foo(x: T): T extends { a: string } ? number : (string | number) { +function foo( + x: T, +): T extends { a: string } ? number : T extends { b: number } ? string : never { if (q(x)) { x.b; return ""; } + x.a; + return 1; } let y = { a: "", b: 1 } -const r = foo<{ a: string }>(y); // number +const r = foo<{ a: string }>(y); // type says number but actually string -function lessBadFoo(x: T): T extends { b: number } ? string : T extends { a: string } ? number : (string | number) { - if (q(x)) { - x.b; - return ""; - } - return 2; -} - -const r2 = lessBadFoo<{ a: string }>(y); // number, bad - -type HelperCond = T extends A ? R1 : T extends B ? R2 : R1 | R2; +type HelperCond = T extends A ? R1 : T extends B ? R2 : never; // We don't narrow the return type because the conditionals are not distributive function foo2(x: U, y: V): @@ -280,7 +284,9 @@ function foo2(x: U, y: V): // From https://github.com/microsoft/TypeScript/issues/24929#issue-332087943 declare function isString(s: unknown): s is string; // capitalize a string or each element of an array of strings -function capitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { +function capitalize( + input: T +): T extends string[] ? string[] : T extends string ? string : never { if (isString(input)) { return input[0].toUpperCase() + input.slice(1); // Ok } else { @@ -288,59 +294,74 @@ function capitalize(input: T): T extends string[] ? } } -function badCapitalize(input: T): T extends string[] ? string[] : T extends string ? string : string[] | string { +function badCapitalize( + input: T +): T extends string[] ? string[] : T extends string ? string : never { if (isString(input)) { return input[0].toUpperCase() + input.slice(1); // Ok } else { - return input[0].toUpperCase() + input.slice(1); // Bad + return input[0].toUpperCase() + input.slice(1); // Bad, error } } -// >> TODO: test non-tail recursive conditionals - -function voidRet(x: T): T extends {} ? void : T extends undefined ? number : void | number { +// No narrowing because conditional's extends type is different from type parameter constraint types +function voidRet( + x: T +): T extends {} ? void : T extends undefined ? number : never { if (x) { - return; // Ok + return; } - return 1; // Ok + return 1; } -function woo(x: T, y: U): -T extends string ? U extends string ? 1 : 2 : U extends number ? 3 : 4 { +// Multiple type parameters at once +function woo( + x: T, + y: U, +): T extends string + ? U extends string + ? 1 + : U extends number + ? 2 + : never + : T extends number + ? U extends number + ? 3 + : U extends string + ? 4 + : never + : never { if (typeof x === "number" && typeof y === "string") { - return 1; // Error + return 1; // Good error } return undefined as any; } -function ttt(x: T, y: U): -T extends string -? number extends string - ? 6 - : U extends string - ? 1 - : 2 -: U extends number - ? 3 - : 4 { - if (typeof x === "string" && typeof y === "string") { - return 1; // Ok +function ttt( + x: T, + y: U, +): T extends string + ? U extends string + ? 1 + : U extends number + ? 2 + : never + : T extends number + ? U extends number + ? 3 + : U extends string + ? 4 + : never + : never { + if (typeof x === "number" && typeof y === "string") { + return 4; // Ok } return undefined as any; } -// We won't narrow `T` because it refers to the type of an optional parameter but doesn't allow for narrowing with `undefined` -function opt(x?: T): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { - if (typeof x === "undefined") { - x; - return 2; - } - return 1; -} - // Shadowing of the narrowed reference -function g(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { +function shadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { if (true) { let x: number = Math.random() ? 1 : 2; if (x === 1) { @@ -350,9 +371,18 @@ function g(x: T): T extends 1 ? number : T extends 2 ? string : } } +function noShadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { + if (true) { + if (x === 1) { + return 1; // Ok + } + return ""; // Ok + } +} + // If the narrowing reference is out of scope, we simply won't narrow its type declare let someX: boolean; -function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { +function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : never { if ((true)) { const someX = opts.a; if (someX) { // We narrow `someX` and the return type here @@ -362,9 +392,11 @@ function scope2(opts: { a: T }): T extends true ? 1 : T exten if (!someX) { // This is a different `someX`, so we don't narrow here return 2; } + + return undefined as any; } -function h(x: T): T extends 1 ? number : T extends 2 ? string : 1 | 2 { +function moreShadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { if (x === 2) { let x: number = Math.random() ? 1 : 2; if (x === 1) { @@ -375,7 +407,8 @@ function h(x: T): T extends 1 ? number : T extends 2 ? string : return 0; // Ok } -function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : string | boolean { +// This would be unsafe to narrow due to `infer` type. +function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : never { if (typeof x === "number") { return true; } @@ -385,7 +418,7 @@ function withInfer(x: T): T extends [infer R] ? R : const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. // Ok -async function abool(x: T): Promise { +async function abool(x: T): Promise { if (x) { return 1; } @@ -393,7 +426,7 @@ async function abool(x: T): Promise(x: T): Generator { +function* bbool(x: T): Generator { yield 3; if (x) { return 1; @@ -402,7 +435,7 @@ function* bbool(x: T): Generator(x: T): Generator { +function* cbool(x: T): Generator { if (x) { yield 1; } @@ -416,13 +449,17 @@ abstract class Operation { } type ConditionalReturnType | undefined> = - EOp extends Operation ? R : EOp extends undefined ? T | R : T | R; + EOp extends Operation ? R : EOp extends undefined ? T | R : never; -class ConditionalOperation | undefined> extends Operation> { +class ConditionalOperation< + T, + R, + EOp extends Operation | undefined, +> extends Operation> { constructor( private predicate: (value: T) => boolean, private thenOp: Operation, - private elseOp?: EOp + private elseOp?: EOp, ) { super(); } @@ -430,7 +467,7 @@ class ConditionalOperation | undefined> extend perform(t: T): ConditionalReturnType { if (this.predicate(t)) { return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it - } else if (typeof this.elseOp !== 'undefined') { + } else if (typeof this.elseOp !== "undefined") { return this.elseOp.perform(t); // Ok } else { return t; // Ok @@ -440,7 +477,7 @@ class ConditionalOperation | undefined> extend // Optional tuple element function tupl(x: [string, some?: T]): - T extends true ? 1 : T extends false | undefined ? 2 : 1 | 2 { + T extends true ? 1 : T extends false | undefined ? 2 : never { if (x[1]) { return 1; } @@ -448,17 +485,22 @@ function tupl(x: [string, some?: T]): } // Return conditional expressions with parentheses -function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { +function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : never { return (opts.x ? (1) : 2); } function returnStuff2(opts: { x: T }): - T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : "one" | "two" | 0 { + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never { return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); } -// If the return type is written wrong, it still type checks -function returnStuff3(opts: { x: T }): - T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : 1 | 2 | "zero" { - return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); +// If the conditional type's input is `never`, then it resolves to `never`: +function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : never { + if (x === true) { + return 1; + } + if (x === false) { + return 2; + } + return 1; } \ No newline at end of file diff --git a/tests/cases/compiler/dependentReturnType2.ts b/tests/cases/compiler/dependentReturnType2.ts deleted file mode 100644 index 11a9b7a0194e6..0000000000000 --- a/tests/cases/compiler/dependentReturnType2.ts +++ /dev/null @@ -1,20 +0,0 @@ -// @strict: true -// @noEmit: true - -// If during narrowing, one of the conditional types in the distribution doesn't narrow, then the whole type will not be narrowed -function whoKnows(x: T): T extends true ? 1 : T extends false ? 2 : 3 { - if (typeof x !== "string") { - return 3; - } -} - -// If the conditional type's input is `never`, then it resolves to `never`: -function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : 1 | 2 { - if (x === true) { - return 1; - } - if (x === false) { - return 2; - } - return 1; -} \ No newline at end of file diff --git a/tests/cases/compiler/dependentReturnType3.ts b/tests/cases/compiler/dependentReturnType3.ts index 2c0e021323e0f..df60c0677fd5d 100644 --- a/tests/cases/compiler/dependentReturnType3.ts +++ b/tests/cases/compiler/dependentReturnType3.ts @@ -9,7 +9,7 @@ type HelperCond = ? R1 : T extends B ? R2 - : (R1 | R2); + : never; // File: Rocket.Chat/apps/meteor/app/katex/client/index.ts @@ -28,7 +28,7 @@ class NewKatex { ? string : T extends IMessage ? IMessage - : (string | IMessage) { + : never { if (typeof message === 'string') { return this.render(message); // Ok } @@ -52,7 +52,11 @@ export function createKatexMessageRendering( parenthesisSyntax: boolean; }, _isMessage: T, -): T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : (((message: string) => string) | ((message: IMessage) => IMessage)) { +): T extends true + ? (message: IMessage) => IMessage + : T extends false + ? (message: string) => string + : never { const instance = new NewKatex(); if (_isMessage) { return (message: IMessage): IMessage => instance.renderMessage(message); // Ok @@ -209,7 +213,4 @@ function transform1(value: T): HelperCond(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : 1 | 2 { +function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : never { if (obj.hasOwnProperty("a")) { takesString(obj.a); return 1; @@ -16,7 +16,7 @@ function hasOwnP(obj: { a?: T }): T extends string ? } function foo(opts: { x?: T }): - T extends undefined ? 0 : T extends string ? 1 : 0 | 1 { + T extends undefined ? 0 : T extends string ? 1 : never { if (opts.x === undefined) { return 0; } @@ -24,7 +24,7 @@ function foo(opts: { x?: T }): } function bar(x?: T ): - T extends Missing ? 0 : T extends string ? 1 : 0 | 1 { + T extends Missing ? 0 : T extends string ? 1 : never { if (x === undefined) { return 0; } @@ -32,7 +32,7 @@ function bar(x?: T ): } // Aliased narrowing -function inlined(x: T): T extends number ? string : T extends string ? number : string | number { +function inlined(x: T): T extends number ? string : T extends string ? number : never { const t = typeof x === "string"; if (t) { const y: string = x; @@ -42,7 +42,7 @@ function inlined(x: T): T extends number ? string : T } // Don't narrow more than 5 levels of aliasing -function inlined6(x: T): T extends number ? string : T extends string ? number : string | number { +function inlined6(x: T): T extends number ? string : T extends string ? number : never { const t1 = typeof x === "string"; const t2 = t1; const t3 = t2; @@ -60,14 +60,14 @@ type A = { kind: "a", a: number }; type B = { kind: "b", b: string }; type AOrB = A | B; -function subexpression(x: T): T extends A ? number : T extends B ? string : number | string { +function subexpression(x: T): T extends A ? number : T extends B ? string : never { if (x.kind === "b") { return "some str"; } return 0; } -function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : 0 | 1 { +function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : never { switch (true) { case x: return 1; @@ -76,7 +76,7 @@ function switchTrue(x: T): T extends true ? 1 : T extends fal } // Don't raise errors when getting the narrowed type of synthesized nodes -type Ret = T extends string ? 1 : T extends number ? 2 : 1 | 2; +type Ret = T extends string ? 1 : T extends number ? 2 : never; function f(x: T): Ret { let y!: T; if (typeof y === "string") { diff --git a/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts b/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts index fd8c7da01abd4..7f6b147820a49 100644 --- a/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts +++ b/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts @@ -1,7 +1,7 @@ /// // @strict: true -//// function h(obj: { x: T }): T extends true ? 1 : T extends false ? 2 : 1 | 2 { +//// function h(obj: { x: T }): T extends true ? 1 : T extends false ? 2 : never { //// if (obj.x) { //// return 1; //// } From 67ccf1ac3b2bd89ec0d6e7af30fbf5528fb5284e Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 20 Aug 2024 14:43:11 -0700 Subject: [PATCH 66/90] minor fix --- src/compiler/checker.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1237a53d6ce46..64d8d61bb2f19 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20592,7 +20592,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // >> No caching yet const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); const checkTypeVariable = getNarrowableCheckTypeVariable(root, newMapper); - const distributionType = checkTypeVariable ? getMappedType(checkTypeVariable, narrowMapper) : undefined; + const distributionType = checkTypeVariable ? getReducedType(getMappedType(checkTypeVariable, narrowMapper)) : undefined; // Distributive conditional types are distributed over union types. For example, when the // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the // result is (A extends U ? X : Y) | (B extends U ? X : Y). @@ -20614,7 +20614,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { narrowMapper: TypeMapper, mapper: TypeMapper, ): Type { - distributionType = getReducedType(distributionType); if (distributionType.flags & TypeFlags.Never) { return distributionType; } @@ -45791,8 +45790,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // or a union of types belonging to the constraint of the type parameter; // (2) There are no `infer` type parameters in the conditional type; // (3) `TrueBranch` and `FalseBranch` must be valid, recursively; + // In particular, the false-most branch of the conditional type must be `never`. // >> TODO: - // - consider multiple type parameters at once // - can/should we check exhaustiveness? // - Problem: cond type nested in true branch with same type parameter is not considered distributive, // because the type parameter is actually a substitution type... From 8a1417ce31180a0c31cd653ca8b6499a99df24c4 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 26 Aug 2024 16:43:10 -0700 Subject: [PATCH 67/90] reuse instantiateType and other functions for narrowing instantiation --- src/compiler/checker.ts | 413 +++++++----------- .../conditionalTypeDoesntSpinForever.types | 2 +- .../reference/conditionalTypes1.types | 3 - .../reference/dependentReturnType1.errors.txt | 7 +- .../reference/dependentReturnType3.errors.txt | 5 +- .../divideAndConquerIntersections.types | 2 +- .../jsxCallElaborationCheckNoCrash1.types | 2 +- ...omplexSignatureHasApplicabilityError.types | 2 +- ...omponentsInstantiaionLimitNotReached.types | 2 +- .../reference/templateLiteralTypes4.types | 1 - 10 files changed, 185 insertions(+), 254 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 64d8d61bb2f19..58d172fe98304 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19097,7 +19097,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return isGenericType(type) || checkTuples && isTupleType(type) && some(getElementTypes(type), isGenericType); } - function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, forConstraint: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + function getConditionalType( + root: ConditionalRoot, + mapper: TypeMapper | undefined, + forConstraint: boolean, + aliasSymbol?: Symbol, + aliasTypeArguments?: readonly Type[], + narrowMapper?: TypeMapper | undefined, + ): Type { let result; let extraTypes: Type[] | undefined; let tailCount = 0; @@ -19111,7 +19118,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); return errorType; } - const checkType = instantiateType(getActualTypeVariable(root.checkType), mapper); + const checkType = getCheckType(root, mapper, narrowMapper); const extendsType = instantiateType(root.extendsType, mapper); if (checkType === errorType || extendsType === errorType) { return errorType; @@ -19229,7 +19236,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper); const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)); const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); - const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined; + const newCheckType = newRoot.isDistributive ? + getCheckType(newRoot, newRootMapper, narrowMapper) + : undefined; if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { root = newRoot; mapper = newRootMapper; @@ -19244,6 +19253,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return false; } + + function getCheckType(root: ConditionalRoot, mapper: TypeMapper | undefined, narrowMapper: TypeMapper | undefined): Type { + const checkTypeVariable = getActualTypeVariable(root.checkType); + if (root.isDistributive) { + const mappedCheckType = mapper ? getMappedType(checkTypeVariable, mapper) : checkTypeVariable; + if (narrowMapper) { + return getMappedType(mappedCheckType, narrowMapper); + } + return mappedCheckType; + } + return instantiateType(checkTypeVariable, mapper); + } } function getTrueTypeFromConditionalType(type: ConditionalType) { @@ -19305,72 +19326,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return links.resolvedType; } - function getNarrowConditionalType(type: ConditionalType, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type { - let root: ConditionalRoot = type.root; - let result; - - // We loop here for an immediately nested conditional type in the false position, effectively treating - // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for - // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of - // another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive - // cases we increment the tail recursion counter and stop after 1000 iterations. - while (true) { - // We instantiate a distributive checkType with the narrow mapper - const checkTypeVariable = getActualTypeVariable(root.checkType); - const narrowableCheckTypeVariable = getNarrowableCheckTypeVariable(root, mapper); - const checkType = narrowableCheckTypeVariable ? - getMappedType(narrowableCheckTypeVariable, narrowMapper) : - instantiateType(checkTypeVariable, mapper); - const extendsType = instantiateType(root.extendsType, mapper); - if (checkType === errorType || extendsType === errorType) { - return errorType; - } - if (checkType === wildcardType || extendsType === wildcardType) { - return wildcardType; - } - // When the check and extends types are simple tuple types of the same arity, we defer resolution of the - // conditional type when any tuple elements are generic. This is such that non-distributable conditional - // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. - const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) && - length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements); - const checkTypeDeferred = isDeferredType(checkType, checkTuples); - // We attempt to resolve the conditional type only when the check and extends types are non-generic - if (!checkTypeDeferred && !isDeferredType(extendsType, checkTuples)) { - // Return falseType for a definitely false extends check. We check an instantiation of the two - // types with type parameters mapped to the wildcard type, the most permissive instantiations - // possible (the wildcard type is assignable to and from all types). If those are not related, - // then no instantiations will be and we can just return the false branch type. - if (!(extendsType.flags & TypeFlags.AnyOrUnknown) && !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(extendsType))) { - Debug.assert(!(checkType.flags & TypeFlags.Any)); - // If falseType is an immediately nested conditional type that has an - // identical checkType, switch to that type and loop. - const falseType = getTypeFromTypeNode(root.node.falseType); - if (falseType.flags & TypeFlags.Conditional) { - const newRoot = (falseType as ConditionalType).root; - if (newRoot.node.parent === root.node && getNarrowableCheckTypeVariable(newRoot, mapper) === narrowableCheckTypeVariable) { - root = newRoot; - continue; - } - } - } - // Return trueType for a definitely true extends check. We check instantiations of the two - // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter - // that has no constraint. This ensures that, for example, the type - // type Foo = T extends { x: string } ? string : number - // doesn't immediately resolve to 'string' instead of being deferred. - else if (extendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { - const trueType = getTypeFromTypeNode(root.node.trueType); - result = instantiateNarrowType(trueType, narrowMapper, mapper); - break; - } - } - // Return the original type for a check that is not definitely true and could not be narrowed - result = type; - break; - } - return result; - } - function getTypeFromInferTypeNode(node: InferTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { @@ -20033,8 +19988,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return createTypeMapper(map(forwardInferences, i => i.typeParameter), map(forwardInferences, () => unknownType)); } - function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { - return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper2; + function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper; + function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper | undefined): TypeMapper | undefined; + function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper | undefined): TypeMapper | undefined { + if (mapper1 && mapper2) { + return makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2); + } + if (mapper1) { + return mapper1; + } + return mapper2; } function mergeTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { @@ -20376,39 +20339,70 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } - function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper, forConstraint: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + function getConditionalTypeInstantiation( + type: ConditionalType, + mapper: TypeMapper | undefined, + forConstraint: boolean, + aliasSymbol?: Symbol, + aliasTypeArguments?: readonly Type[], + narrowMapper?: TypeMapper | undefined, + ): Type { const root = type.root; if (root.outerTypeParameters) { // We are instantiating a conditional type that has one or more type parameters in scope. Apply the // mapper to the type parameters to produce the effective list of type arguments, and compute the // instantiation cache key from the type IDs of the type arguments. - const typeArguments = map(root.outerTypeParameters, t => getMappedType(t, mapper)); + const typeArguments = mapper ? map(root.outerTypeParameters, t => getMappedType(t, mapper)) : root.outerTypeParameters; const id = (forConstraint ? "C" : "") + getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); - let result = root.instantiations!.get(id); - if (!result) { + let result = root.instantiations!.get(id); // >> TODO: fix caching for narrowing instantiation + if (!result || narrowMapper) { const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); const checkType = root.checkType; - const distributionType = root.isDistributive ? getReducedType(getMappedType(checkType, newMapper)) : undefined; + const mappedCheckType = root.isDistributive ? getMappedType(checkType, newMapper) : undefined; + const distributionType = mappedCheckType && + (narrowMapper ? getReducedType(getMappedType(mappedCheckType, narrowMapper)) : getReducedType(mappedCheckType)); // Distributive conditional types are distributed over union types. For example, when the // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the // result is (A extends U ? X : Y) | (B extends U ? X : Y). - result = distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never) ? - mapTypeWithAlias(distributionType, t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint), aliasSymbol, aliasTypeArguments) : - getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments); - root.instantiations!.set(id, result); + if (distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { + result = narrowMapper ? + getNarrowedDistributedConditionalType( + type, + distributionType, + mappedCheckType, + narrowMapper, + newMapper, + ) : + mapTypeWithAlias( + distributionType, + t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint), + aliasSymbol, + aliasTypeArguments, + ); + } + else { + result = getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments, narrowMapper); + } + if (!narrowMapper) root.instantiations!.set(id, result); } return result; } return type; } - function instantiateType(type: Type, mapper: TypeMapper | undefined): Type; - function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined; - function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined { - return type && mapper ? instantiateTypeWithAlias(type, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : type; + function instantiateType(type: Type, mapper: TypeMapper | undefined, narrowingMapper?: TypeMapper | undefined): Type; + function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined, narrowingMapper?: TypeMapper | undefined): Type | undefined; + function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined, narrowingMapper?: TypeMapper | undefined): Type | undefined { + return type && (mapper || narrowingMapper) ? instantiateTypeWithAlias(type, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, narrowingMapper) : type; } - function instantiateTypeWithAlias(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { + function instantiateTypeWithAlias( + type: Type, + mapper: TypeMapper | undefined, + aliasSymbol: Symbol | undefined, + aliasTypeArguments: readonly Type[] | undefined, + narrowingMapper?: TypeMapper, + ): Type { if (!couldContainTypeVariables(type)) { return type; } @@ -20423,191 +20417,110 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { totalInstantiationCount++; instantiationCount++; instantiationDepth++; - const result = instantiateTypeWorker(type, mapper, aliasSymbol, aliasTypeArguments); + const result = instantiateTypeWorker(type, mapper, narrowingMapper, aliasSymbol, aliasTypeArguments); instantiationDepth--; return result; } - function instantiateTypeWorker(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { + function instantiateTypeWorker(type: Type, mapper: TypeMapper | undefined, narrowingMapper: TypeMapper | undefined, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { const flags = type.flags; - if (flags & TypeFlags.TypeParameter) { - return getMappedType(type, mapper); - } - if (flags & TypeFlags.Object) { - const objectFlags = (type as ObjectType).objectFlags; - if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - if (objectFlags & ObjectFlags.Reference && !(type as TypeReference).node) { - const resolvedTypeArguments = (type as TypeReference).resolvedTypeArguments; - const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); - return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type as TypeReference).target, newTypeArguments) : type; + if (mapper) { + if (flags & TypeFlags.TypeParameter) { + return getMappedType(type, mapper); + } + if (flags & TypeFlags.Object) { + const objectFlags = (type as ObjectType).objectFlags; + if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + if (objectFlags & ObjectFlags.Reference && !(type as TypeReference).node) { + const resolvedTypeArguments = (type as TypeReference).resolvedTypeArguments; + const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); + return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type as TypeReference).target, newTypeArguments) : type; + } + if (objectFlags & ObjectFlags.ReverseMapped) { + return instantiateReverseMappedType(type as ReverseMappedType, mapper); + } + return getObjectTypeInstantiation(type as TypeReference | AnonymousType | MappedType, mapper, aliasSymbol, aliasTypeArguments); } - if (objectFlags & ObjectFlags.ReverseMapped) { - return instantiateReverseMappedType(type as ReverseMappedType, mapper); + return type; + } + if (flags & TypeFlags.UnionOrIntersection) { + const origin = type.flags & TypeFlags.Union ? (type as UnionType).origin : undefined; + const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (origin as UnionOrIntersectionType).types : (type as UnionOrIntersectionType).types; + const newTypes = instantiateTypes(types, mapper); + if (newTypes === types && aliasSymbol === type.aliasSymbol) { + return type; } - return getObjectTypeInstantiation(type as TypeReference | AnonymousType | MappedType, mapper, aliasSymbol, aliasTypeArguments); + const newAliasSymbol = aliasSymbol || type.aliasSymbol; + const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ? + getIntersectionType(newTypes, IntersectionFlags.None, newAliasSymbol, newAliasTypeArguments) : + getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments); } - return type; - } - if (flags & TypeFlags.UnionOrIntersection) { - const origin = type.flags & TypeFlags.Union ? (type as UnionType).origin : undefined; - const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (origin as UnionOrIntersectionType).types : (type as UnionOrIntersectionType).types; - const newTypes = instantiateTypes(types, mapper); - if (newTypes === types && aliasSymbol === type.aliasSymbol) { - return type; + if (flags & TypeFlags.Index) { + return getIndexType(instantiateType((type as IndexType).type, mapper)); } - const newAliasSymbol = aliasSymbol || type.aliasSymbol; - const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ? - getIntersectionType(newTypes, IntersectionFlags.None, newAliasSymbol, newAliasTypeArguments) : - getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments); - } - if (flags & TypeFlags.Index) { - return getIndexType(instantiateType((type as IndexType).type, mapper)); - } - if (flags & TypeFlags.TemplateLiteral) { - return getTemplateLiteralType((type as TemplateLiteralType).texts, instantiateTypes((type as TemplateLiteralType).types, mapper)); - } - if (flags & TypeFlags.StringMapping) { - return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper)); - } - if (flags & TypeFlags.IndexedAccess) { - const newAliasSymbol = aliasSymbol || type.aliasSymbol; - const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - return getIndexedAccessType(instantiateType((type as IndexedAccessType).objectType, mapper), instantiateType((type as IndexedAccessType).indexType, mapper), (type as IndexedAccessType).accessFlags, /*accessNode*/ undefined, newAliasSymbol, newAliasTypeArguments); - } - if (flags & TypeFlags.Conditional) { - return getConditionalTypeInstantiation(type as ConditionalType, combineTypeMappers((type as ConditionalType).mapper, mapper), /*forConstraint*/ false, aliasSymbol, aliasTypeArguments); - } - if (flags & TypeFlags.Substitution) { - const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); - if (isNoInferType(type)) { - return getNoInferType(newBaseType); + if (flags & TypeFlags.TemplateLiteral) { + return getTemplateLiteralType((type as TemplateLiteralType).texts, instantiateTypes((type as TemplateLiteralType).types, mapper)); } - const newConstraint = instantiateType((type as SubstitutionType).constraint, mapper); - // A substitution type originates in the true branch of a conditional type and can be resolved - // to just the base type in the same cases as the conditional type resolves to its true branch - // (because the base type is then known to satisfy the constraint). - if (newBaseType.flags & TypeFlags.TypeVariable && isGenericType(newConstraint)) { - return getSubstitutionType(newBaseType, newConstraint); + if (flags & TypeFlags.StringMapping) { + return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper)); } - if (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { - return newBaseType; + if (flags & TypeFlags.Substitution) { + const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); + if (isNoInferType(type)) { + return getNoInferType(newBaseType); + } + const newConstraint = instantiateType((type as SubstitutionType).constraint, mapper); + // A substitution type originates in the true branch of a conditional type and can be resolved + // to just the base type in the same cases as the conditional type resolves to its true branch + // (because the base type is then known to satisfy the constraint). + if (newBaseType.flags & TypeFlags.TypeVariable && isGenericType(newConstraint)) { + return getSubstitutionType(newBaseType, newConstraint); + } + if (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { + return newBaseType; + } + return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); } - return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); - } - return type; - } - - /** - * This is similar to `instantiateType`, but with behavior specific to narrowing - * a type based on control flow narrowing of expressions that have type parameter types. - */ - function instantiateNarrowType(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type { - if (!couldContainTypeVariables(type)) { - return type; - } - if (instantiationDepth === 100 || instantiationCount >= 5000000) { - // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement - // or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types - // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. - tracing?.instant(tracing.Phase.CheckTypes, "instantiateNarrowType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); - error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); - return errorType; } - totalInstantiationCount++; - instantiationCount++; - instantiationDepth++; - const result = instantiateNarrowTypeWorker(type, narrowMapper, mapper); - instantiationDepth--; - return result; - } - - /** - * @param narrowMapper special mapper that has mappings originating from type parameter narrowing, - * and should only be used for instantiation in some places. - * @param mapper the usual mapper that should be used for all instantiations - */ - function instantiateNarrowTypeWorker( - type: Type, - narrowMapper: TypeMapper, - mapper: TypeMapper | undefined, - ): Type { - type = instantiateType(type, mapper); - const flags = type.flags; if (flags & TypeFlags.IndexedAccess) { - const objectType = instantiateNarrowType((type as IndexedAccessType).objectType, narrowMapper, mapper); - let indexType = instantiateNarrowType((type as IndexedAccessType).indexType, narrowMapper, mapper); + const newAliasSymbol = aliasSymbol || type.aliasSymbol; + // >> TODO: update this + const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : mapper ? instantiateTypes(type.aliasTypeArguments, mapper) : type.aliasTypeArguments; + const objectType = instantiateType((type as IndexedAccessType).objectType, mapper); + let indexType = instantiateType((type as IndexedAccessType).indexType, mapper); let accessFlags = (type as IndexedAccessType).accessFlags; - if (indexType.flags & TypeFlags.TypeParameter) { - indexType = getMappedType(indexType, narrowMapper); + if (narrowingMapper && indexType.flags & TypeFlags.TypeParameter) { + indexType = getMappedType(indexType, narrowingMapper); // If we're narrowing the index type, we need to get the write type, // i.e. intersecting the results of distributing the indexed access over a union index. accessFlags |= AccessFlags.Writing; } - // >> NOTE: this possibly recurs forever; how do we break this recursion? is the below enough? - if (indexType === (type as IndexedAccessType).indexType && objectType === (type as IndexedAccessType).objectType) { - return type; // No type reduction or narrowing happened; so don't do anything else to avoid infinite recursion - } - const result = getIndexedAccessType( + // >> TODO: this may not be enough to fully resolve possibly nested indexed accesses + return getIndexedAccessType( objectType, indexType, accessFlags, + /*accessNode*/ undefined, + newAliasSymbol, + newAliasTypeArguments, ); - // >> NOTE: We need to detect if result is different from just putting the already resolved types together - return instantiateNarrowType(result, narrowMapper, mapper); } if (flags & TypeFlags.Conditional) { - return getNarrowConditionalTypeInstantiation( + return getConditionalTypeInstantiation( type as ConditionalType, - narrowMapper, - mapper ? combineTypeMappers((type as ConditionalType).mapper, mapper) : (type as ConditionalType).mapper, + combineTypeMappers((type as ConditionalType).mapper, mapper), + /*forConstraint*/ false, + aliasSymbol, + aliasTypeArguments, + narrowingMapper, ); } - - return type; - } - - function getNarrowableCheckTypeVariable(root: ConditionalRoot, mapper: TypeMapper | undefined): TypeParameter | undefined { - if (!root.isDistributive || root.inferTypeParameters) { - return; - } - const checkType = mapper ? getMappedType(root.checkType, mapper) : root.checkType; - const variable = getActualTypeVariable(checkType); - if (variable.flags & TypeFlags.TypeParameter) { - return variable as TypeParameter; - } - } - - function getNarrowConditionalTypeInstantiation( - type: ConditionalType, - narrowMapper: TypeMapper, - mapper: TypeMapper | undefined, - ): Type { - const root = type.root; - if (root.outerTypeParameters) { - // We are instantiating a conditional type that has one or more type parameters in scope. Apply the - // mapper to the type parameters to produce the effective list of type arguments, and compute the - // instantiation cache key from the type IDs of the type arguments. - const typeArguments = mapper ? map(root.outerTypeParameters, t => getMappedType(t, mapper)) : root.outerTypeParameters; - // >> No caching yet - const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); - const checkTypeVariable = getNarrowableCheckTypeVariable(root, newMapper); - const distributionType = checkTypeVariable ? getReducedType(getMappedType(checkTypeVariable, narrowMapper)) : undefined; - // Distributive conditional types are distributed over union types. For example, when the - // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the - // result is (A extends U ? X : Y) | (B extends U ? X : Y). - if (distributionType && checkTypeVariable !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { - return getNarrowDistributedConditionalType(type, distributionType, checkTypeVariable!, narrowMapper, newMapper); - } - else { - return getNarrowConditionalType(type, narrowMapper, newMapper); - } - } return type; } // `distributionType` should be a union type (or never). - function getNarrowDistributedConditionalType( + function getNarrowedDistributedConditionalType( type: ConditionalType, distributionType: Type, checkTypeVariable: TypeParameter, @@ -20620,16 +20533,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (distributionType.flags & TypeFlags.Union) { const mappedTypes: Type[] = []; for (const t of (distributionType as UnionType).types) { - const result = getNarrowConditionalType(type, prependTypeMapping(checkTypeVariable, t, narrowMapper), mapper); + const result = getConditionalType( + type.root, + mapper, + /*forConstraint*/ false, + /*aliasSymbol*/ undefined, + /*aliasTypeArguments*/ undefined, + prependTypeMapping(checkTypeVariable, t, narrowMapper), + ); // If one of the component types could not be narrowed, then don't narrow the whole type - if (result === type) { + if (isConditionalType(result) && result.root === type.root) { // >> TODO: does this work?? return type; } mappedTypes.push(result); } return getIntersectionType(mappedTypes); } - return getNarrowConditionalType(type, narrowMapper, mapper); + return getConditionalType( + type.root, + mapper, + /*forConstraint*/ false, + /*aliasSymbol*/ undefined, + /*aliasTypeArguments*/ undefined, + narrowMapper, + ); } function isGenericIndexedOrConditionalReturnType(type: Type): type is IndexedAccessType | ConditionalType { @@ -45485,10 +45412,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return [tp, exprType]; }); const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); - narrowedReturnType = instantiateNarrowType( + narrowedReturnType = instantiateType( unwrappedReturnType, - narrowMapper, /*mapper*/ undefined, + narrowMapper, ); } diff --git a/tests/baselines/reference/conditionalTypeDoesntSpinForever.types b/tests/baselines/reference/conditionalTypeDoesntSpinForever.types index 73fb1f2d6e19d..0e223e2e7f985 100644 --- a/tests/baselines/reference/conditionalTypeDoesntSpinForever.types +++ b/tests/baselines/reference/conditionalTypeDoesntSpinForever.types @@ -2,7 +2,7 @@ === Performance Stats === Type Count: 1,000 -Instantiation count: 2,500 -> 5,000 +Instantiation count: 2,500 === conditionalTypeDoesntSpinForever.ts === // A *self-contained* demonstration of the problem follows... diff --git a/tests/baselines/reference/conditionalTypes1.types b/tests/baselines/reference/conditionalTypes1.types index 3aac0bf578434..821e5c5119164 100644 --- a/tests/baselines/reference/conditionalTypes1.types +++ b/tests/baselines/reference/conditionalTypes1.types @@ -1,8 +1,5 @@ //// [tests/cases/conformance/types/conditional/conditionalTypes1.ts] //// -=== Performance Stats === -Instantiation count: 1,000 - === conditionalTypes1.ts === type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d" >T00 : T00 diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index cab1643befe28..f823f3f45fe74 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -17,6 +17,8 @@ dependentReturnType1.ts(169,9): error TS2322: Type 'T & {}' is not assignable to 'this' could be instantiated with an arbitrary type which could be unrelated to 'T & {}'. dependentReturnType1.ts(203,24): error TS2322: Type 'string' is not assignable to type 'number'. dependentReturnType1.ts(203,28): error TS2322: Type 'number' is not assignable to type 'string'. +dependentReturnType1.ts(221,9): error TS2322: Type 'number' is not assignable to type 'BB[T]'. + Type 'number' is not assignable to type 'never'. dependentReturnType1.ts(240,9): error TS2322: Type '""' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. dependentReturnType1.ts(242,9): error TS2322: Type 'true' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. dependentReturnType1.ts(244,5): error TS2322: Type '3' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. @@ -39,7 +41,7 @@ dependentReturnType1.ts(465,13): error TS2322: Type 'R' is not assignable to typ dependentReturnType1.ts(501,5): error TS2322: Type 'number' is not assignable to type 'never'. -==== dependentReturnType1.ts (36 errors) ==== +==== dependentReturnType1.ts (37 errors) ==== interface A { 1: number; 2: string; @@ -296,6 +298,9 @@ dependentReturnType1.ts(501,5): error TS2322: Type 'number' is not assignable to // AA[U='c'] -> BB[T] // BB[T='a'] -> number return 0; // Ok + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'BB[T]'. +!!! error TS2322: Type 'number' is not assignable to type 'never'. } return undefined as never; diff --git a/tests/baselines/reference/dependentReturnType3.errors.txt b/tests/baselines/reference/dependentReturnType3.errors.txt index 39a9326242262..93875224c5664 100644 --- a/tests/baselines/reference/dependentReturnType3.errors.txt +++ b/tests/baselines/reference/dependentReturnType3.errors.txt @@ -1,9 +1,10 @@ dependentReturnType3.ts(114,13): error TS2322: Type 'undefined' is not assignable to type 'HelperCond[]>'. +dependentReturnType3.ts(118,13): error TS2322: Type 'SettingComposedValue[]' is not assignable to type 'HelperCond[]>'. dependentReturnType3.ts(130,16): error TS2536: Type 'I' cannot be used to index type '{ [s: string]: any; }'. dependentReturnType3.ts(141,9): error TS2322: Type 'Record' is not assignable to type 'HelperCond>'. -==== dependentReturnType3.ts (3 errors) ==== +==== dependentReturnType3.ts (4 errors) ==== // Adapted from ts-error-deltas repos type HelperCond = @@ -124,6 +125,8 @@ dependentReturnType3.ts(141,9): error TS2322: Type 'Record' is if (_.isRegExp(_id)) { return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { + ~~~~~~ +!!! error TS2322: Type 'SettingComposedValue[]' is not assignable to type 'HelperCond[]>'. const value = Meteor.settings[key]; if (_id.test(key)) { items.push({ diff --git a/tests/baselines/reference/divideAndConquerIntersections.types b/tests/baselines/reference/divideAndConquerIntersections.types index 880c5d4a95d5a..be6c29df2e156 100644 --- a/tests/baselines/reference/divideAndConquerIntersections.types +++ b/tests/baselines/reference/divideAndConquerIntersections.types @@ -2,7 +2,7 @@ === Performance Stats === Type Count: 2,500 -Instantiation count: 10,000 +Instantiation count: 5,000 === divideAndConquerIntersections.ts === type QQ = diff --git a/tests/baselines/reference/jsxCallElaborationCheckNoCrash1.types b/tests/baselines/reference/jsxCallElaborationCheckNoCrash1.types index ab87b840acde3..03a1aaae84634 100644 --- a/tests/baselines/reference/jsxCallElaborationCheckNoCrash1.types +++ b/tests/baselines/reference/jsxCallElaborationCheckNoCrash1.types @@ -3,7 +3,7 @@ === Performance Stats === Assignability cache: 2,500 Type Count: 10,000 -Instantiation count: 100,000 +Instantiation count: 50,000 Symbol count: 50,000 === jsxCallElaborationCheckNoCrash1.tsx === diff --git a/tests/baselines/reference/jsxComplexSignatureHasApplicabilityError.types b/tests/baselines/reference/jsxComplexSignatureHasApplicabilityError.types index ff3eaf01664be..278f90f5983ff 100644 --- a/tests/baselines/reference/jsxComplexSignatureHasApplicabilityError.types +++ b/tests/baselines/reference/jsxComplexSignatureHasApplicabilityError.types @@ -2,7 +2,7 @@ === Performance Stats === Type Count: 1,000 -Instantiation count: 2,500 +Instantiation count: 1,000 -> 2,500 === jsxComplexSignatureHasApplicabilityError.tsx === /// diff --git a/tests/baselines/reference/styledComponentsInstantiaionLimitNotReached.types b/tests/baselines/reference/styledComponentsInstantiaionLimitNotReached.types index 4aebbfa368533..9ab0659bad7a1 100644 --- a/tests/baselines/reference/styledComponentsInstantiaionLimitNotReached.types +++ b/tests/baselines/reference/styledComponentsInstantiaionLimitNotReached.types @@ -3,7 +3,7 @@ === Performance Stats === Assignability cache: 10,000 Type Count: 25,000 -Instantiation count: 250,000 -> 500,000 +Instantiation count: 250,000 Symbol count: 100,000 === styledComponentsInstantiaionLimitNotReached.ts === diff --git a/tests/baselines/reference/templateLiteralTypes4.types b/tests/baselines/reference/templateLiteralTypes4.types index ef54a53c3efc2..52ef0fe198052 100644 --- a/tests/baselines/reference/templateLiteralTypes4.types +++ b/tests/baselines/reference/templateLiteralTypes4.types @@ -2,7 +2,6 @@ === Performance Stats === Assignability cache: 1,000 -Instantiation count: 1,000 === templateLiteralTypes4.ts === // infer from number From d74630c524094d47ee41cfb229f6b29bfc28bb84 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 6 Sep 2024 19:33:17 -0700 Subject: [PATCH 68/90] use substitution types --- src/compiler/checker.ts | 491 ++++++++++++------ src/compiler/types.ts | 3 + .../conditionalTypeDoesntSpinForever.types | 2 +- .../reference/conditionalTypes1.types | 3 + .../reference/dependentReturnType1.errors.txt | 19 +- .../reference/dependentReturnType3.errors.txt | 5 +- .../reference/dependentReturnType5.errors.txt | 6 +- .../divideAndConquerIntersections.types | 2 +- .../jsxCallElaborationCheckNoCrash1.types | 2 +- ...omplexSignatureHasApplicabilityError.types | 2 +- ...omponentsInstantiaionLimitNotReached.types | 2 +- .../reference/templateLiteralTypes4.types | 1 + tests/cases/compiler/dependentReturnType1.ts | 28 +- 13 files changed, 367 insertions(+), 199 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 58d172fe98304..97b4dd4883731 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16584,14 +16584,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return !!(type.flags & TypeFlags.Substitution && (type as SubstitutionType).constraint.flags & TypeFlags.Unknown); } - function getSubstitutionType(baseType: Type, constraint: Type) { + function isNarrowingSubstitutionType(type: Type): boolean { + return !!(type.flags & TypeFlags.Substitution && (type as SubstitutionType).objectFlags & ObjectFlags.IsNarrowedType); + } + + function getSubstitutionType(baseType: Type, constraint: Type, isNarrowed?: boolean) { return constraint.flags & TypeFlags.AnyOrUnknown || constraint === baseType || baseType.flags & TypeFlags.Any ? baseType : - getOrCreateSubstitutionType(baseType, constraint); + getOrCreateSubstitutionType(baseType, constraint, isNarrowed); } - function getOrCreateSubstitutionType(baseType: Type, constraint: Type) { - const id = `${getTypeId(baseType)}>${getTypeId(constraint)}`; + function getOrCreateSubstitutionType(baseType: Type, constraint: Type, isNarrowed?: boolean) { + const id = `${getTypeId(baseType)}>${getTypeId(constraint)}${isNarrowed ? ">N" : ""}`; const cached = substitutionTypes.get(id); if (cached) { return cached; @@ -16599,6 +16603,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const result = createType(TypeFlags.Substitution) as SubstitutionType; result.baseType = baseType; result.constraint = constraint; + if (isNarrowed) { + result.objectFlags |= ObjectFlags.IsNarrowedType; + } substitutionTypes.set(id, result); return result; } @@ -17627,7 +17634,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // types are known not to circularly reference themselves (as is the case with union types created by // expression constructs such as array literals and the || and ?: operators). Named types can // circularly reference themselves and therefore cannot be subtype reduced during their declaration. - // For example, "type Item = string | (() => Item" is a named type that circularly references itself. + // For example, "type Item = string | (() => Item)" is a named type that circularly references itself. function getUnionType(types: readonly Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { if (types.length === 0) { return neverType; @@ -19103,7 +19110,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { forConstraint: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], - narrowMapper?: TypeMapper | undefined, ): Type { let result; let extraTypes: Type[] | undefined; @@ -19118,7 +19124,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); return errorType; } - const checkType = getCheckType(root, mapper, narrowMapper); + const checkType = instantiateType(getActualTypeVariable(root.checkType), mapper); const extendsType = instantiateType(root.extendsType, mapper); if (checkType === errorType || extendsType === errorType) { return errorType; @@ -19126,6 +19132,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (checkType === wildcardType || extendsType === wildcardType) { return wildcardType; } + const effectiveCheckType = isNarrowingSubstitutionType(checkType) ? (checkType as SubstitutionType).constraint : checkType; const checkTypeNode = skipTypeParentheses(root.node.checkType); const extendsTypeNode = skipTypeParentheses(root.node.extendsType); // When the check and extends types are simple tuple types of the same arity, we defer resolution of the @@ -19133,7 +19140,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. const checkTuples = isSimpleTupleType(checkTypeNode) && isSimpleTupleType(extendsTypeNode) && length((checkTypeNode as TupleTypeNode).elements) === length((extendsTypeNode as TupleTypeNode).elements); - const checkTypeDeferred = isDeferredType(checkType, checkTuples); + const checkTypeDeferred = isDeferredType(effectiveCheckType, checkTuples); let combinedMapper: TypeMapper | undefined; if (root.inferTypeParameters) { // When we're looking at making an inference for an infer type, when we get its constraint, it'll automagically be @@ -19173,13 +19180,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, // then no instantiations will be and we can just return the false branch type. - if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { + if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (effectiveCheckType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(effectiveCheckType), getPermissiveInstantiation(inferredExtendsType)))) { // Return union of trueType and falseType for 'any' since it matches anything. Furthermore, for a // distributive conditional type applied to the constraint of a type variable, include trueType if // there are possible values of the check type that are also possible values of the extends type. // We use a reverse assignability check as it is less expensive than the comparable relationship // and avoids false positives of a non-empty intersection check. - if (checkType.flags & TypeFlags.Any || forConstraint && !(inferredExtendsType.flags & TypeFlags.Never) && someType(getPermissiveInstantiation(inferredExtendsType), t => isTypeAssignableTo(t, getPermissiveInstantiation(checkType)))) { + if (effectiveCheckType.flags & TypeFlags.Any || forConstraint && !(inferredExtendsType.flags & TypeFlags.Never) && someType(getPermissiveInstantiation(inferredExtendsType), t => isTypeAssignableTo(t, getPermissiveInstantiation(effectiveCheckType)))) { (extraTypes || (extraTypes = [])).push(instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper)); } // If falseType is an immediately nested conditional type that isn't distributive or has an @@ -19203,7 +19210,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // that has no constraint. This ensures that, for example, the type // type Foo = T extends { x: string } ? string : number // doesn't immediately resolve to 'string' instead of being deferred. - if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { + if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(effectiveCheckType), getRestrictiveInstantiation(inferredExtendsType))) { const trueType = getTypeFromTypeNode(root.node.trueType); const trueMapper = combinedMapper || mapper; if (canTailRecurse(trueType, trueMapper)) { @@ -19236,9 +19243,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const typeParamMapper = combineTypeMappers((newType as ConditionalType).mapper, newMapper); const typeArguments = map(newRoot.outerTypeParameters, t => getMappedType(t, typeParamMapper)); const newRootMapper = createTypeMapper(newRoot.outerTypeParameters, typeArguments); - const newCheckType = newRoot.isDistributive ? - getCheckType(newRoot, newRootMapper, narrowMapper) - : undefined; + const newCheckType = newRoot.isDistributive ? getMappedType(newRoot.checkType, newRootMapper) : undefined; if (!newCheckType || newCheckType === newRoot.checkType || !(newCheckType.flags & (TypeFlags.Union | TypeFlags.Never))) { root = newRoot; mapper = newRootMapper; @@ -19253,18 +19258,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } return false; } - - function getCheckType(root: ConditionalRoot, mapper: TypeMapper | undefined, narrowMapper: TypeMapper | undefined): Type { - const checkTypeVariable = getActualTypeVariable(root.checkType); - if (root.isDistributive) { - const mappedCheckType = mapper ? getMappedType(checkTypeVariable, mapper) : checkTypeVariable; - if (narrowMapper) { - return getMappedType(mappedCheckType, narrowMapper); - } - return mappedCheckType; - } - return instantiateType(checkTypeVariable, mapper); - } } function getTrueTypeFromConditionalType(type: ConditionalType) { @@ -19326,6 +19319,72 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return links.resolvedType; } + function getNarrowConditionalType(type: ConditionalType, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type { + let root: ConditionalRoot = type.root; + let result; + + // We loop here for an immediately nested conditional type in the false position, effectively treating + // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for + // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of + // another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive + // cases we increment the tail recursion counter and stop after 1000 iterations. + while (true) { + // We instantiate a distributive checkType with the narrow mapper + const checkTypeVariable = getActualTypeVariable(root.checkType); + const narrowableCheckTypeVariable = getNarrowableCheckTypeVariable(root, mapper); + const checkType = narrowableCheckTypeVariable ? + getMappedType(narrowableCheckTypeVariable, narrowMapper) : + instantiateType(checkTypeVariable, mapper); + const extendsType = instantiateType(root.extendsType, mapper); + if (checkType === errorType || extendsType === errorType) { + return errorType; + } + if (checkType === wildcardType || extendsType === wildcardType) { + return wildcardType; + } + // When the check and extends types are simple tuple types of the same arity, we defer resolution of the + // conditional type when any tuple elements are generic. This is such that non-distributable conditional + // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. + const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) && + length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements); + const checkTypeDeferred = isDeferredType(checkType, checkTuples); + // We attempt to resolve the conditional type only when the check and extends types are non-generic + if (!checkTypeDeferred && !isDeferredType(extendsType, checkTuples)) { + // Return falseType for a definitely false extends check. We check an instantiation of the two + // types with type parameters mapped to the wildcard type, the most permissive instantiations + // possible (the wildcard type is assignable to and from all types). If those are not related, + // then no instantiations will be and we can just return the false branch type. + if (!(extendsType.flags & TypeFlags.AnyOrUnknown) && !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(extendsType))) { + Debug.assert(!(checkType.flags & TypeFlags.Any)); + // If falseType is an immediately nested conditional type that has an + // identical checkType, switch to that type and loop. + const falseType = getTypeFromTypeNode(root.node.falseType); + if (falseType.flags & TypeFlags.Conditional) { + const newRoot = (falseType as ConditionalType).root; + if (newRoot.node.parent === root.node && getNarrowableCheckTypeVariable(newRoot, mapper) === narrowableCheckTypeVariable) { + root = newRoot; + continue; + } + } + } + // Return trueType for a definitely true extends check. We check instantiations of the two + // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter + // that has no constraint. This ensures that, for example, the type + // type Foo = T extends { x: string } ? string : number + // doesn't immediately resolve to 'string' instead of being deferred. + else if (extendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { + const trueType = getTypeFromTypeNode(root.node.trueType); + result = instantiateNarrowType(trueType, narrowMapper, mapper); + break; + } + } + // Return the original type for a check that is not definitely true and could not be narrowed + result = type; + break; + } + return result; + } + function getTypeFromInferTypeNode(node: InferTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { @@ -19988,16 +20047,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return createTypeMapper(map(forwardInferences, i => i.typeParameter), map(forwardInferences, () => unknownType)); } - function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper; - function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper | undefined): TypeMapper | undefined; - function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper | undefined): TypeMapper | undefined { - if (mapper1 && mapper2) { - return makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2); - } - if (mapper1) { - return mapper1; - } - return mapper2; + function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { + return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper2; } function mergeTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { @@ -20341,68 +20392,60 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getConditionalTypeInstantiation( type: ConditionalType, - mapper: TypeMapper | undefined, + mapper: TypeMapper, forConstraint: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], - narrowMapper?: TypeMapper | undefined, ): Type { const root = type.root; if (root.outerTypeParameters) { // We are instantiating a conditional type that has one or more type parameters in scope. Apply the // mapper to the type parameters to produce the effective list of type arguments, and compute the // instantiation cache key from the type IDs of the type arguments. - const typeArguments = mapper ? map(root.outerTypeParameters, t => getMappedType(t, mapper)) : root.outerTypeParameters; + const typeArguments = map(root.outerTypeParameters, t => getMappedType(t, mapper)); const id = (forConstraint ? "C" : "") + getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments); - let result = root.instantiations!.get(id); // >> TODO: fix caching for narrowing instantiation - if (!result || narrowMapper) { + let result = root.instantiations!.get(id); + if (!result) { const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); const checkType = root.checkType; - const mappedCheckType = root.isDistributive ? getMappedType(checkType, newMapper) : undefined; - const distributionType = mappedCheckType && - (narrowMapper ? getReducedType(getMappedType(mappedCheckType, narrowMapper)) : getReducedType(mappedCheckType)); + let narrowingBaseType: Type | undefined; + let mappedCheckType = root.isDistributive ? getReducedType(getMappedType(checkType, newMapper)) : undefined; + if (mappedCheckType && isNarrowingSubstitutionType(mappedCheckType)) { + narrowingBaseType = (mappedCheckType as SubstitutionType).baseType; + mappedCheckType = getReducedType((mappedCheckType as SubstitutionType).constraint); + } + const distributionType = root.isDistributive ? mappedCheckType : undefined; // Distributive conditional types are distributed over union types. For example, when the // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the // result is (A extends U ? X : Y) | (B extends U ? X : Y). if (distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { - result = narrowMapper ? - getNarrowedDistributedConditionalType( - type, - distributionType, - mappedCheckType, - narrowMapper, - newMapper, - ) : - mapTypeWithAlias( - distributionType, - t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint), - aliasSymbol, - aliasTypeArguments, - ); + const mapperCallback = narrowingBaseType ? + (t: Type) => getConditionalType(root, prependTypeMapping(checkType, getSubstitutionType(narrowingBaseType, t, /*isNarrowed*/ true), newMapper), forConstraint) : + (t: Type) => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint); + if (narrowingBaseType) { + result = mapTypeToIntersection(distributionType, mapperCallback); + } + else { + result = mapTypeWithAlias(distributionType, mapperCallback, aliasSymbol, aliasTypeArguments); + } } else { - result = getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments, narrowMapper); + result = getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments); } - if (!narrowMapper) root.instantiations!.set(id, result); + root.instantiations!.set(id, result); } return result; } return type; } - function instantiateType(type: Type, mapper: TypeMapper | undefined, narrowingMapper?: TypeMapper | undefined): Type; - function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined, narrowingMapper?: TypeMapper | undefined): Type | undefined; - function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined, narrowingMapper?: TypeMapper | undefined): Type | undefined { - return type && (mapper || narrowingMapper) ? instantiateTypeWithAlias(type, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, narrowingMapper) : type; + function instantiateType(type: Type, mapper: TypeMapper | undefined): Type; + function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined; + function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined { + return type && mapper ? instantiateTypeWithAlias(type, mapper, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined) : type; } - function instantiateTypeWithAlias( - type: Type, - mapper: TypeMapper | undefined, - aliasSymbol: Symbol | undefined, - aliasTypeArguments: readonly Type[] | undefined, - narrowingMapper?: TypeMapper, - ): Type { + function instantiateTypeWithAlias(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { if (!couldContainTypeVariables(type)) { return type; } @@ -20417,110 +20460,202 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { totalInstantiationCount++; instantiationCount++; instantiationDepth++; - const result = instantiateTypeWorker(type, mapper, narrowingMapper, aliasSymbol, aliasTypeArguments); + const result = instantiateTypeWorker(type, mapper, aliasSymbol, aliasTypeArguments); instantiationDepth--; return result; } - function instantiateTypeWorker(type: Type, mapper: TypeMapper | undefined, narrowingMapper: TypeMapper | undefined, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { + function instantiateTypeWorker( + type: Type, + mapper: TypeMapper, + aliasSymbol: Symbol | undefined, + aliasTypeArguments: readonly Type[] | undefined, + ): Type { const flags = type.flags; - if (mapper) { - if (flags & TypeFlags.TypeParameter) { - return getMappedType(type, mapper); - } - if (flags & TypeFlags.Object) { - const objectFlags = (type as ObjectType).objectFlags; - if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - if (objectFlags & ObjectFlags.Reference && !(type as TypeReference).node) { - const resolvedTypeArguments = (type as TypeReference).resolvedTypeArguments; - const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); - return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type as TypeReference).target, newTypeArguments) : type; - } - if (objectFlags & ObjectFlags.ReverseMapped) { - return instantiateReverseMappedType(type as ReverseMappedType, mapper); - } - return getObjectTypeInstantiation(type as TypeReference | AnonymousType | MappedType, mapper, aliasSymbol, aliasTypeArguments); + if (flags & TypeFlags.TypeParameter) { + return getMappedType(type, mapper); + } + if (flags & TypeFlags.Object) { + const objectFlags = (type as ObjectType).objectFlags; + if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + if (objectFlags & ObjectFlags.Reference && !(type as TypeReference).node) { + const resolvedTypeArguments = (type as TypeReference).resolvedTypeArguments; + const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); + return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type as TypeReference).target, newTypeArguments) : type; } - return type; - } - if (flags & TypeFlags.UnionOrIntersection) { - const origin = type.flags & TypeFlags.Union ? (type as UnionType).origin : undefined; - const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (origin as UnionOrIntersectionType).types : (type as UnionOrIntersectionType).types; - const newTypes = instantiateTypes(types, mapper); - if (newTypes === types && aliasSymbol === type.aliasSymbol) { - return type; + if (objectFlags & ObjectFlags.ReverseMapped) { + return instantiateReverseMappedType(type as ReverseMappedType, mapper); } - const newAliasSymbol = aliasSymbol || type.aliasSymbol; - const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); - return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ? - getIntersectionType(newTypes, IntersectionFlags.None, newAliasSymbol, newAliasTypeArguments) : - getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments); + return getObjectTypeInstantiation(type as TypeReference | AnonymousType | MappedType, mapper, aliasSymbol, aliasTypeArguments); } - if (flags & TypeFlags.Index) { - return getIndexType(instantiateType((type as IndexType).type, mapper)); + return type; + } + if (flags & TypeFlags.UnionOrIntersection) { + const origin = type.flags & TypeFlags.Union ? (type as UnionType).origin : undefined; + const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (origin as UnionOrIntersectionType).types : (type as UnionOrIntersectionType).types; + const newTypes = instantiateTypes(types, mapper); + if (newTypes === types && aliasSymbol === type.aliasSymbol) { + return type; } - if (flags & TypeFlags.TemplateLiteral) { - return getTemplateLiteralType((type as TemplateLiteralType).texts, instantiateTypes((type as TemplateLiteralType).types, mapper)); + const newAliasSymbol = aliasSymbol || type.aliasSymbol; + const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + return flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ? + getIntersectionType(newTypes, IntersectionFlags.None, newAliasSymbol, newAliasTypeArguments) : + getUnionType(newTypes, UnionReduction.Literal, newAliasSymbol, newAliasTypeArguments); + } + if (flags & TypeFlags.Index) { + return getIndexType(instantiateType((type as IndexType).type, mapper)); + } + if (flags & TypeFlags.TemplateLiteral) { + return getTemplateLiteralType((type as TemplateLiteralType).texts, instantiateTypes((type as TemplateLiteralType).types, mapper)); + } + if (flags & TypeFlags.StringMapping) { + return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper)); + } + if (flags & TypeFlags.IndexedAccess) { + const newAliasSymbol = aliasSymbol || type.aliasSymbol; + const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : instantiateTypes(type.aliasTypeArguments, mapper); + return getIndexedAccessType(instantiateType((type as IndexedAccessType).objectType, mapper), instantiateType((type as IndexedAccessType).indexType, mapper), (type as IndexedAccessType).accessFlags, /*accessNode*/ undefined, newAliasSymbol, newAliasTypeArguments); + } + if (flags & TypeFlags.Conditional) { + return getConditionalTypeInstantiation( + type as ConditionalType, + combineTypeMappers((type as ConditionalType).mapper, mapper), + /*forConstraint*/ false, + aliasSymbol, + aliasTypeArguments, + ); + } + if (flags & TypeFlags.Substitution) { + const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); + if (isNoInferType(type)) { + return getNoInferType(newBaseType); } - if (flags & TypeFlags.StringMapping) { - return getStringMappingType((type as StringMappingType).symbol, instantiateType((type as StringMappingType).type, mapper)); + const newConstraint = instantiateType((type as SubstitutionType).constraint, mapper); + // A substitution type originates in the true branch of a conditional type and can be resolved + // to just the base type in the same cases as the conditional type resolves to its true branch + // (because the base type is then known to satisfy the constraint). + if (newBaseType.flags & TypeFlags.TypeVariable && isGenericType(newConstraint)) { + return getSubstitutionType(newBaseType, newConstraint); } - if (flags & TypeFlags.Substitution) { - const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); - if (isNoInferType(type)) { - return getNoInferType(newBaseType); - } - const newConstraint = instantiateType((type as SubstitutionType).constraint, mapper); - // A substitution type originates in the true branch of a conditional type and can be resolved - // to just the base type in the same cases as the conditional type resolves to its true branch - // (because the base type is then known to satisfy the constraint). - if (newBaseType.flags & TypeFlags.TypeVariable && isGenericType(newConstraint)) { - return getSubstitutionType(newBaseType, newConstraint); - } - if (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { - return newBaseType; - } - return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); + if (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { + return newBaseType; } + return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); + } + return type; + } + + /** + * This is similar to `instantiateType`, but with behavior specific to narrowing + * a type based on control flow narrowing of expressions that have type parameter types. + */ + function instantiateNarrowType(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type { + if (!couldContainTypeVariables(type)) { + return type; } + if (instantiationDepth === 100 || instantiationCount >= 5000000) { + // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement + // or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types + // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. + tracing?.instant(tracing.Phase.CheckTypes, "instantiateNarrowType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); + error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); + return errorType; + } + totalInstantiationCount++; + instantiationCount++; + instantiationDepth++; + const result = instantiateNarrowTypeWorker(type, narrowMapper, mapper); + instantiationDepth--; + return result; + } + + /** + * @param narrowMapper special mapper that has mappings originating from type parameter narrowing, + * and should only be used for instantiation in some places. + * @param mapper the usual mapper that should be used for all instantiations + */ + function instantiateNarrowTypeWorker( + type: Type, + narrowMapper: TypeMapper, + mapper: TypeMapper | undefined, + ): Type { + type = instantiateType(type, mapper); + const flags = type.flags; if (flags & TypeFlags.IndexedAccess) { - const newAliasSymbol = aliasSymbol || type.aliasSymbol; - // >> TODO: update this - const newAliasTypeArguments = aliasSymbol ? aliasTypeArguments : mapper ? instantiateTypes(type.aliasTypeArguments, mapper) : type.aliasTypeArguments; - const objectType = instantiateType((type as IndexedAccessType).objectType, mapper); - let indexType = instantiateType((type as IndexedAccessType).indexType, mapper); + const objectType = instantiateNarrowType((type as IndexedAccessType).objectType, narrowMapper, mapper); + let indexType = instantiateNarrowType((type as IndexedAccessType).indexType, narrowMapper, mapper); let accessFlags = (type as IndexedAccessType).accessFlags; - if (narrowingMapper && indexType.flags & TypeFlags.TypeParameter) { - indexType = getMappedType(indexType, narrowingMapper); + if (indexType.flags & TypeFlags.TypeParameter) { + indexType = getMappedType(indexType, narrowMapper); // If we're narrowing the index type, we need to get the write type, // i.e. intersecting the results of distributing the indexed access over a union index. accessFlags |= AccessFlags.Writing; } - // >> TODO: this may not be enough to fully resolve possibly nested indexed accesses - return getIndexedAccessType( + // >> NOTE: this possibly recurs forever; how do we break this recursion? is the below enough? + if (indexType === (type as IndexedAccessType).indexType && objectType === (type as IndexedAccessType).objectType) { + return type; // No type reduction or narrowing happened; so don't do anything else to avoid infinite recursion + } + const result = getIndexedAccessType( objectType, indexType, accessFlags, - /*accessNode*/ undefined, - newAliasSymbol, - newAliasTypeArguments, ); + // >> NOTE: We need to detect if result is different from just putting the already resolved types together + return instantiateNarrowType(result, narrowMapper, mapper); } if (flags & TypeFlags.Conditional) { - return getConditionalTypeInstantiation( + return getNarrowConditionalTypeInstantiation( type as ConditionalType, - combineTypeMappers((type as ConditionalType).mapper, mapper), - /*forConstraint*/ false, - aliasSymbol, - aliasTypeArguments, - narrowingMapper, + narrowMapper, + mapper ? combineTypeMappers((type as ConditionalType).mapper, mapper) : (type as ConditionalType).mapper, ); } + + return type; + } + + function getNarrowableCheckTypeVariable(root: ConditionalRoot, mapper: TypeMapper | undefined): TypeParameter | undefined { + if (!root.isDistributive || root.inferTypeParameters) { + return; + } + const checkType = mapper ? getMappedType(root.checkType, mapper) : root.checkType; + const variable = getActualTypeVariable(checkType); + if (variable.flags & TypeFlags.TypeParameter) { + return variable as TypeParameter; + } + } + + function getNarrowConditionalTypeInstantiation( + type: ConditionalType, + narrowMapper: TypeMapper, + mapper: TypeMapper | undefined, + ): Type { + const root = type.root; + if (root.outerTypeParameters) { + // We are instantiating a conditional type that has one or more type parameters in scope. Apply the + // mapper to the type parameters to produce the effective list of type arguments, and compute the + // instantiation cache key from the type IDs of the type arguments. + const typeArguments = mapper ? map(root.outerTypeParameters, t => getMappedType(t, mapper)) : root.outerTypeParameters; + // >> No caching yet + const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); + const checkTypeVariable = getNarrowableCheckTypeVariable(root, newMapper); + const distributionType = checkTypeVariable ? getReducedType(getMappedType(checkTypeVariable, narrowMapper)) : undefined; + // Distributive conditional types are distributed over union types. For example, when the + // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the + // result is (A extends U ? X : Y) | (B extends U ? X : Y). + if (distributionType && checkTypeVariable !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { + return getNarrowDistributedConditionalType(type, distributionType, checkTypeVariable!, narrowMapper, newMapper); + } + else { + return getNarrowConditionalType(type, narrowMapper, newMapper); + } + } return type; } // `distributionType` should be a union type (or never). - function getNarrowedDistributedConditionalType( + function getNarrowDistributedConditionalType( type: ConditionalType, distributionType: Type, checkTypeVariable: TypeParameter, @@ -20533,30 +20668,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (distributionType.flags & TypeFlags.Union) { const mappedTypes: Type[] = []; for (const t of (distributionType as UnionType).types) { - const result = getConditionalType( - type.root, - mapper, - /*forConstraint*/ false, - /*aliasSymbol*/ undefined, - /*aliasTypeArguments*/ undefined, - prependTypeMapping(checkTypeVariable, t, narrowMapper), - ); + const result = getNarrowConditionalType(type, prependTypeMapping(checkTypeVariable, t, narrowMapper), mapper); // If one of the component types could not be narrowed, then don't narrow the whole type - if (isConditionalType(result) && result.root === type.root) { // >> TODO: does this work?? + if (result === type) { return type; } mappedTypes.push(result); } return getIntersectionType(mappedTypes); } - return getConditionalType( - type.root, - mapper, - /*forConstraint*/ false, - /*aliasSymbol*/ undefined, - /*aliasTypeArguments*/ undefined, - narrowMapper, - ); + return getNarrowConditionalType(type, narrowMapper, mapper); } function isGenericIndexedOrConditionalReturnType(type: Type): type is IndexedAccessType | ConditionalType { @@ -21759,7 +21880,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (type.flags & TypeFlags.Intersection && shouldNormalizeIntersection(type as IntersectionType)) { // Normalization handles cases like // Partial[K] & ({} | null) ==> - // Partial[K] & {} | Partial[K} & null ==> + // Partial[K] & {} | Partial[K] & null ==> // (T[K] | undefined) & {} | (T[K] | undefined) & null ==> // T[K] & {} | undefined & {} | T[K] & null | undefined & null ==> // T[K] & {} | T[K] & null @@ -21774,10 +21895,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function shouldNormalizeIntersection(type: IntersectionType) { let hasInstantiable = false; let hasNullableOrEmpty = false; + let hasSubstitution = false; for (const t of type.types) { hasInstantiable ||= !!(t.flags & TypeFlags.Instantiable); hasNullableOrEmpty ||= !!(t.flags & TypeFlags.Nullable) || isEmptyAnonymousObjectType(t); - if (hasInstantiable && hasNullableOrEmpty) return true; + hasSubstitution ||= isNarrowingSubstitutionType(t); // This avoids displaying error messages with types like `T & T` + if (hasInstantiable && hasNullableOrEmpty || hasSubstitution) return true; } return false; } @@ -27934,6 +28057,32 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { mapType(type, mapper); } + // >> TODO: will we need to customize this further? e.g. improve detection of "changed"/unnarrowed? + // if not, reuse mapType with new parameter to control union/intersection, maybe passing "hasChanged" call back + function mapTypeToIntersection(type: Type, mapper: (t: Type) => Type): Type { + if (type.flags & TypeFlags.Never) { + return type; + } + if (!(type.flags & TypeFlags.Union)) { + return mapper(type); + } + const origin = (type as UnionType).origin; + const types = origin && origin.flags & TypeFlags.Union ? (origin as UnionType).types : (type as UnionType).types; + let mappedTypes: Type[] | undefined; + let changed = false; + for (const t of types) { + const mapped = t.flags & TypeFlags.Union ? mapTypeToIntersection(t, mapper) : mapper(t); + changed ||= t !== mapped; + if (!mappedTypes) { + mappedTypes = [mapped]; + } + else { + mappedTypes.push(mapped); + } + } + return changed ? getIntersectionType(mappedTypes!) : type; + } + function extractTypesOfKind(type: Type, kind: TypeFlags) { return filterType(type, t => (t.flags & kind) !== 0); } @@ -45402,19 +45551,33 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { setParent(narrowReference, narrowPosition.parent); setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); narrowReference.flowNode = narrowFlowNode; + // >> TODO: maybe inline call to `someType(tp, isGenericTypeWithUnionConstraint)` to avoid "synthesized" trick to force it const initialType = getNarrowableTypeForReference(tp, narrowReference); + // >> TODO: if `initialType` is simply `tp`, quit because narrowing will be useless const flowType = getFlowTypeOfReference(narrowReference, initialType); const exprType = getTypeFromFlowType(flowType); // Don't narrow the return type if narrowing didn't produce a narrower type for the expression. if (isTypeAny(exprType) || isErrorType(exprType) || exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { return undefined; } - return [tp, exprType]; + // We will sometimes narrow the type of a reference `x: T` to `T & NarrowType`, and other times to `NarrowType`. + let constraintType: Type; + // >> TODO: could it also be e.g. `(T & 1) | (T & 2)`?? if so, use `mapType` here maybe) + + if (exprType.flags & TypeFlags.Intersection && (exprType as IntersectionType).types.includes(tp)) { + // throw `WOOOHOO ${typeToString(exprType)}`; + const otherTypes = (exprType as IntersectionType).types.filter(t => t !== tp); + constraintType = getIntersectionType(otherTypes); + } + else { + constraintType = exprType; + } + const narrowedType = getSubstitutionType(tp, constraintType, /*isNarrowed*/ true); + return [tp, narrowedType]; }); const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); narrowedReturnType = instantiateType( unwrappedReturnType, - /*mapper*/ undefined, narrowMapper, ); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f2b6a5dcf82e7..607c21606ab8c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6382,6 +6382,8 @@ export const enum ObjectFlags { IsGenericIndexType = 1 << 23, // Union or intersection contains generic index type /** @internal */ IsGenericType = IsGenericObjectType | IsGenericIndexType, + /** @internal */ + IsNarrowedType = 1 << 24, // Substitution type that comes from type narrowing // Flags that require TypeFlags.Union /** @internal */ @@ -6791,6 +6793,7 @@ export interface SubstitutionType extends InstantiableType { objectFlags: ObjectFlags; baseType: Type; // Target type constraint: Type; // Constraint that target type is known to satisfy + /** @internal */ } /** @internal */ diff --git a/tests/baselines/reference/conditionalTypeDoesntSpinForever.types b/tests/baselines/reference/conditionalTypeDoesntSpinForever.types index 0e223e2e7f985..73fb1f2d6e19d 100644 --- a/tests/baselines/reference/conditionalTypeDoesntSpinForever.types +++ b/tests/baselines/reference/conditionalTypeDoesntSpinForever.types @@ -2,7 +2,7 @@ === Performance Stats === Type Count: 1,000 -Instantiation count: 2,500 +Instantiation count: 2,500 -> 5,000 === conditionalTypeDoesntSpinForever.ts === // A *self-contained* demonstration of the problem follows... diff --git a/tests/baselines/reference/conditionalTypes1.types b/tests/baselines/reference/conditionalTypes1.types index 821e5c5119164..3aac0bf578434 100644 --- a/tests/baselines/reference/conditionalTypes1.types +++ b/tests/baselines/reference/conditionalTypes1.types @@ -1,5 +1,8 @@ //// [tests/cases/conformance/types/conditional/conditionalTypes1.ts] //// +=== Performance Stats === +Instantiation count: 1,000 + === conditionalTypes1.ts === type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d" >T00 : T00 diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index f823f3f45fe74..35b59a8ac94d4 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -1,5 +1,7 @@ -dependentReturnType1.ts(11,9): error TS2322: Type 'number' is not assignable to type 'string'. -dependentReturnType1.ts(26,9): error TS2322: Type 'string' is not assignable to type 'never'. +dependentReturnType1.ts(11,9): error TS2322: Type 'number' is not assignable to type 'A[T]'. + Type 'number' is not assignable to type 'string'. +dependentReturnType1.ts(26,9): error TS2322: Type '""' is not assignable to type 'C[T]'. + Type 'string' is not assignable to type 'never'. dependentReturnType1.ts(35,9): error TS2322: Type 'string' is not assignable to type 'never'. dependentReturnType1.ts(69,9): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. dependentReturnType1.ts(71,5): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. @@ -17,8 +19,6 @@ dependentReturnType1.ts(169,9): error TS2322: Type 'T & {}' is not assignable to 'this' could be instantiated with an arbitrary type which could be unrelated to 'T & {}'. dependentReturnType1.ts(203,24): error TS2322: Type 'string' is not assignable to type 'number'. dependentReturnType1.ts(203,28): error TS2322: Type 'number' is not assignable to type 'string'. -dependentReturnType1.ts(221,9): error TS2322: Type 'number' is not assignable to type 'BB[T]'. - Type 'number' is not assignable to type 'never'. dependentReturnType1.ts(240,9): error TS2322: Type '""' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. dependentReturnType1.ts(242,9): error TS2322: Type 'true' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. dependentReturnType1.ts(244,5): error TS2322: Type '3' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. @@ -41,7 +41,7 @@ dependentReturnType1.ts(465,13): error TS2322: Type 'R' is not assignable to typ dependentReturnType1.ts(501,5): error TS2322: Type 'number' is not assignable to type 'never'. -==== dependentReturnType1.ts (37 errors) ==== +==== dependentReturnType1.ts (36 errors) ==== interface A { 1: number; 2: string; @@ -54,7 +54,8 @@ dependentReturnType1.ts(501,5): error TS2322: Type 'number' is not assignable to else { return 1; // Error ~~~~~~ -!!! error TS2322: Type 'number' is not assignable to type 'string'. +!!! error TS2322: Type 'number' is not assignable to type 'A[T]'. +!!! error TS2322: Type 'number' is not assignable to type 'string'. } } @@ -71,7 +72,8 @@ dependentReturnType1.ts(501,5): error TS2322: Type 'number' is not assignable to else { return ""; // Error, returned expression needs to have type string & boolean (= never) ~~~~~~ -!!! error TS2322: Type 'string' is not assignable to type 'never'. +!!! error TS2322: Type '""' is not assignable to type 'C[T]'. +!!! error TS2322: Type 'string' is not assignable to type 'never'. } } @@ -298,9 +300,6 @@ dependentReturnType1.ts(501,5): error TS2322: Type 'number' is not assignable to // AA[U='c'] -> BB[T] // BB[T='a'] -> number return 0; // Ok - ~~~~~~ -!!! error TS2322: Type 'number' is not assignable to type 'BB[T]'. -!!! error TS2322: Type 'number' is not assignable to type 'never'. } return undefined as never; diff --git a/tests/baselines/reference/dependentReturnType3.errors.txt b/tests/baselines/reference/dependentReturnType3.errors.txt index 93875224c5664..39a9326242262 100644 --- a/tests/baselines/reference/dependentReturnType3.errors.txt +++ b/tests/baselines/reference/dependentReturnType3.errors.txt @@ -1,10 +1,9 @@ dependentReturnType3.ts(114,13): error TS2322: Type 'undefined' is not assignable to type 'HelperCond[]>'. -dependentReturnType3.ts(118,13): error TS2322: Type 'SettingComposedValue[]' is not assignable to type 'HelperCond[]>'. dependentReturnType3.ts(130,16): error TS2536: Type 'I' cannot be used to index type '{ [s: string]: any; }'. dependentReturnType3.ts(141,9): error TS2322: Type 'Record' is not assignable to type 'HelperCond>'. -==== dependentReturnType3.ts (4 errors) ==== +==== dependentReturnType3.ts (3 errors) ==== // Adapted from ts-error-deltas repos type HelperCond = @@ -125,8 +124,6 @@ dependentReturnType3.ts(141,9): error TS2322: Type 'Record' is if (_.isRegExp(_id)) { return Object.keys(Meteor.settings).reduce((items: SettingComposedValue[], key) => { - ~~~~~~ -!!! error TS2322: Type 'SettingComposedValue[]' is not assignable to type 'HelperCond[]>'. const value = Meteor.settings[key]; if (_id.test(key)) { items.push({ diff --git a/tests/baselines/reference/dependentReturnType5.errors.txt b/tests/baselines/reference/dependentReturnType5.errors.txt index 3c4812c2fc7b1..ce2ada8762c9b 100644 --- a/tests/baselines/reference/dependentReturnType5.errors.txt +++ b/tests/baselines/reference/dependentReturnType5.errors.txt @@ -1,4 +1,5 @@ -dependentReturnType5.ts(54,13): error TS2322: Type '2' is not assignable to type '3'. +dependentReturnType5.ts(54,13): error TS2322: Type '2' is not assignable to type 'Comp[T]'. + Type '2' is not assignable to type '3'. dependentReturnType5.ts(65,5): error TS2322: Type '2' is not assignable to type 'Comp[T]'. Type '2' is not assignable to type '3'. @@ -59,7 +60,8 @@ dependentReturnType5.ts(65,5): error TS2322: Type '2' is not assignable to type if (Math.random()) { return 2; // Error ~~~~~~ -!!! error TS2322: Type '2' is not assignable to type '3'. +!!! error TS2322: Type '2' is not assignable to type 'Comp[T]'. +!!! error TS2322: Type '2' is not assignable to type '3'. } return 3; // Ok } diff --git a/tests/baselines/reference/divideAndConquerIntersections.types b/tests/baselines/reference/divideAndConquerIntersections.types index be6c29df2e156..880c5d4a95d5a 100644 --- a/tests/baselines/reference/divideAndConquerIntersections.types +++ b/tests/baselines/reference/divideAndConquerIntersections.types @@ -2,7 +2,7 @@ === Performance Stats === Type Count: 2,500 -Instantiation count: 5,000 +Instantiation count: 10,000 === divideAndConquerIntersections.ts === type QQ = diff --git a/tests/baselines/reference/jsxCallElaborationCheckNoCrash1.types b/tests/baselines/reference/jsxCallElaborationCheckNoCrash1.types index 03a1aaae84634..ab87b840acde3 100644 --- a/tests/baselines/reference/jsxCallElaborationCheckNoCrash1.types +++ b/tests/baselines/reference/jsxCallElaborationCheckNoCrash1.types @@ -3,7 +3,7 @@ === Performance Stats === Assignability cache: 2,500 Type Count: 10,000 -Instantiation count: 50,000 +Instantiation count: 100,000 Symbol count: 50,000 === jsxCallElaborationCheckNoCrash1.tsx === diff --git a/tests/baselines/reference/jsxComplexSignatureHasApplicabilityError.types b/tests/baselines/reference/jsxComplexSignatureHasApplicabilityError.types index 278f90f5983ff..ff3eaf01664be 100644 --- a/tests/baselines/reference/jsxComplexSignatureHasApplicabilityError.types +++ b/tests/baselines/reference/jsxComplexSignatureHasApplicabilityError.types @@ -2,7 +2,7 @@ === Performance Stats === Type Count: 1,000 -Instantiation count: 1,000 -> 2,500 +Instantiation count: 2,500 === jsxComplexSignatureHasApplicabilityError.tsx === /// diff --git a/tests/baselines/reference/styledComponentsInstantiaionLimitNotReached.types b/tests/baselines/reference/styledComponentsInstantiaionLimitNotReached.types index 9ab0659bad7a1..4aebbfa368533 100644 --- a/tests/baselines/reference/styledComponentsInstantiaionLimitNotReached.types +++ b/tests/baselines/reference/styledComponentsInstantiaionLimitNotReached.types @@ -3,7 +3,7 @@ === Performance Stats === Assignability cache: 10,000 Type Count: 25,000 -Instantiation count: 250,000 +Instantiation count: 250,000 -> 500,000 Symbol count: 100,000 === styledComponentsInstantiaionLimitNotReached.ts === diff --git a/tests/baselines/reference/templateLiteralTypes4.types b/tests/baselines/reference/templateLiteralTypes4.types index 52ef0fe198052..ef54a53c3efc2 100644 --- a/tests/baselines/reference/templateLiteralTypes4.types +++ b/tests/baselines/reference/templateLiteralTypes4.types @@ -2,6 +2,7 @@ === Performance Stats === Assignability cache: 1,000 +Instantiation count: 1,000 === templateLiteralTypes4.ts === // infer from number diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index f393a4b451d15..dcd084f096ddc 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -85,20 +85,20 @@ function f101(x: T): T extends 1 ? One : T extends 2 ? } // Asymmetry -function conditionalProducingIf( - arg: Arg, - cond: (arg: LeftIn | RightIn) => arg is LeftIn, - produceLeftOut: (arg: LeftIn) => LeftOut, - produceRightOut: (arg: RightIn) => RightOut): - Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never -{ - type OK = Arg extends LeftIn ? LeftOut : RightOut; - if (cond(arg)) { - return produceLeftOut(arg); // The narrowed conditional return type has deferred resolution, so this doesn't work. - } else { - return produceRightOut(arg as RightIn); // Error: Doesn't work because we can't narrow `arg` to `Arg & RightIn` here - } -} +// function conditionalProducingIf( +// arg: Arg, +// cond: (arg: LeftIn | RightIn) => arg is LeftIn, +// produceLeftOut: (arg: LeftIn) => LeftOut, +// produceRightOut: (arg: RightIn) => RightOut): +// Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never +// { +// type OK = Arg extends LeftIn ? LeftOut : RightOut; +// if (cond(arg)) { +// return produceLeftOut(arg); // The narrowed conditional return type has deferred resolution, so this doesn't work. +// } else { +// return produceRightOut(arg as RightIn); // Error: Doesn't work because we can't narrow `arg` to `Arg & RightIn` here +// } +// } interface Animal { name: string; From 10403cff5dbd75ac4880e17cdc7a9d6f181c8d02 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 9 Sep 2024 13:41:33 -0700 Subject: [PATCH 69/90] refactoring --- src/compiler/checker.ts | 141 +-- .../reference/dependentReturnType1.errors.txt | 71 +- .../reference/dependentReturnType1.symbols | 1067 ++++++++--------- .../reference/dependentReturnType1.types | 14 +- tests/cases/compiler/dependentReturnType1.ts | 33 +- 5 files changed, 647 insertions(+), 679 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 97b4dd4883731..deff2e478dedf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20423,7 +20423,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { (t: Type) => getConditionalType(root, prependTypeMapping(checkType, getSubstitutionType(narrowingBaseType, t, /*isNarrowed*/ true), newMapper), forConstraint) : (t: Type) => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint); if (narrowingBaseType) { - result = mapTypeToIntersection(distributionType, mapperCallback); + result = mapType(distributionType, mapperCallback, /*noReductions*/ undefined, /*toIntersection*/ true); } else { result = mapTypeWithAlias(distributionType, mapperCallback, aliasSymbol, aliasTypeArguments); @@ -28022,10 +28022,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Apply a mapping function to a type and return the resulting type. If the source type // is a union type, the mapping function is applied to each constituent type and a union - // of the resulting types is returned. - function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined { + // (or intersection, if `toIntersection` is set) of the resulting types is returned. + function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean, toIntersection?: boolean): Type; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean, toIntersection?: boolean): Type | undefined; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean, toIntersection?: boolean): Type | undefined { if (type.flags & TypeFlags.Never) { return type; } @@ -28048,7 +28048,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } - return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type; + return changed + ? mappedTypes && + (toIntersection + ? getIntersectionType(mappedTypes) + : getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal)) + : type; } function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { @@ -28057,32 +28062,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { mapType(type, mapper); } - // >> TODO: will we need to customize this further? e.g. improve detection of "changed"/unnarrowed? - // if not, reuse mapType with new parameter to control union/intersection, maybe passing "hasChanged" call back - function mapTypeToIntersection(type: Type, mapper: (t: Type) => Type): Type { - if (type.flags & TypeFlags.Never) { - return type; - } - if (!(type.flags & TypeFlags.Union)) { - return mapper(type); - } - const origin = (type as UnionType).origin; - const types = origin && origin.flags & TypeFlags.Union ? (origin as UnionType).types : (type as UnionType).types; - let mappedTypes: Type[] | undefined; - let changed = false; - for (const t of types) { - const mapped = t.flags & TypeFlags.Union ? mapTypeToIntersection(t, mapper) : mapper(t); - changed ||= t !== mapped; - if (!mappedTypes) { - mappedTypes = [mapped]; - } - else { - mappedTypes.push(mapped); - } - } - return changed ? getIntersectionType(mappedTypes!) : type; - } - function extractTypesOfKind(type: Type, kind: TypeFlags) { return filterType(type, t => (t.flags & kind) !== 0); } @@ -29952,7 +29931,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return contextualType && !isGenericType(contextualType); } - function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) { + function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode, forReturnTypeNarrowing?: boolean) { if (isNoInferType(type)) { type = (type as SubstitutionType).baseType; } @@ -29965,7 +29944,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'. const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) && someType(type, isGenericTypeWithUnionConstraint) && - ((reference.flags & NodeFlags.Synthesized) || isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode)); // >> TODO: find other way to signal this + (forReturnTypeNarrowing || isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode)); return substituteConstraints ? mapType(type, getBaseConstraintOrType) : type; } @@ -45493,17 +45472,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { : exprType; const errorNode = node.expression && isConditionalExpression(skipParentheses(node.expression)) ? expr : node; - if (!isGenericIndexedOrConditionalReturnType(unwrappedReturnType)) { - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); - return; - } // Check if type of return expression is assignable to original return type; // If so, we don't need to narrow. if (checkTypeAssignableTo(unwrappedExprType, unwrappedReturnType, /*errorNode*/ undefined)) { return; } - const outerTypeParameters = getOuterTypeParameters(container, /*includeThisTypes*/ false); - const allTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); + + if (!isGenericIndexedOrConditionalReturnType(unwrappedReturnType)) { + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); + return; + } + const allTypeParameters = appendTypeParameters(getOuterTypeParameters(container, /*includeThisTypes*/ false), getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); const unionTypeParameters = allTypeParameters?.filter(tp => { const constraint = getConstraintOfTypeParameter(tp); return !!((constraint?.flags ?? 0) & TypeFlags.Union); @@ -45538,49 +45517,40 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { narrowFlowNode = expr.parent.whenTrue === expr ? expr.parent.flowNodeWhenTrue : expr.parent.flowNodeWhenFalse; narrowPosition = expr; } - let narrowedReturnType: Type = unwrappedReturnType; - if (narrowableTypeParameters && narrowFlowNode) { - const narrowed: [TypeParameter, Type][] = mapDefined(narrowableTypeParameters, ([tp, symbol, reference]) => { - const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. - // Don't reuse the original reference's node id, - // because that could cause us to get a type that was cached for the original reference. - narrowReference.id = undefined; - // Set the symbol of the synthetic reference. - // This allows us to get the type of the reference at a location where the reference is possibly shadowed. - getNodeLinks(narrowReference).resolvedSymbol = symbol; - setParent(narrowReference, narrowPosition.parent); - setNodeFlags(narrowReference, narrowReference.flags | NodeFlags.Synthesized); - narrowReference.flowNode = narrowFlowNode; - // >> TODO: maybe inline call to `someType(tp, isGenericTypeWithUnionConstraint)` to avoid "synthesized" trick to force it - const initialType = getNarrowableTypeForReference(tp, narrowReference); - // >> TODO: if `initialType` is simply `tp`, quit because narrowing will be useless - const flowType = getFlowTypeOfReference(narrowReference, initialType); - const exprType = getTypeFromFlowType(flowType); - // Don't narrow the return type if narrowing didn't produce a narrower type for the expression. - if (isTypeAny(exprType) || isErrorType(exprType) || exprType === tp || exprType === mapType(tp, getBaseConstraintOrType)) { - return undefined; - } - // We will sometimes narrow the type of a reference `x: T` to `T & NarrowType`, and other times to `NarrowType`. - let constraintType: Type; - // >> TODO: could it also be e.g. `(T & 1) | (T & 2)`?? if so, use `mapType` here maybe) - - if (exprType.flags & TypeFlags.Intersection && (exprType as IntersectionType).types.includes(tp)) { - // throw `WOOOHOO ${typeToString(exprType)}`; - const otherTypes = (exprType as IntersectionType).types.filter(t => t !== tp); - constraintType = getIntersectionType(otherTypes); - } - else { - constraintType = exprType; - } - const narrowedType = getSubstitutionType(tp, constraintType, /*isNarrowed*/ true); - return [tp, narrowedType]; - }); - const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); - narrowedReturnType = instantiateType( - unwrappedReturnType, - narrowMapper, - ); + + if (!narrowFlowNode) { + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); + return; } + const narrowed: [TypeParameter, Type][] = mapDefined(narrowableTypeParameters, ([typeParam, symbol, reference]) => { + const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. + // Don't reuse the original reference's node id, + // because that could cause us to get a type that was cached for the original reference. + narrowReference.id = undefined; + // Set the symbol of the synthetic reference. + // This allows us to get the type of the reference at a location where the reference is possibly shadowed. + getNodeLinks(narrowReference).resolvedSymbol = symbol; + setParent(narrowReference, narrowPosition.parent); + narrowReference.flowNode = narrowFlowNode; + const initialType = getNarrowableTypeForReference(typeParam, narrowReference, /*checkMode*/ undefined, /*forReturnTypeNarrowing*/ true); + if (initialType === typeParam) { + return undefined; + } + const flowType = getFlowTypeOfReference(narrowReference, initialType); + const exprType = getTypeFromFlowType(flowType); + // Don't narrow the return type if narrowing didn't produce a narrower type for the expression. + if (isTypeAny(exprType) || isErrorType(exprType) || exprType === typeParam || exprType === mapType(typeParam, getBaseConstraintOrType)) { + return undefined; + } + const narrowedType = getSubstitutionType(typeParam, exprType, /*isNarrowed*/ true); + return [typeParam, narrowedType]; + }); + + const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); + const narrowedReturnType = instantiateType( + unwrappedReturnType, + narrowMapper, + ); if (expr) { const links = getNodeLinks(expr); @@ -45881,10 +45851,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // (2) There are no `infer` type parameters in the conditional type; // (3) `TrueBranch` and `FalseBranch` must be valid, recursively; // In particular, the false-most branch of the conditional type must be `never`. - // >> TODO: - // - can/should we check exhaustiveness? - // - Problem: cond type nested in true branch with same type parameter is not considered distributive, - // because the type parameter is actually a substitution type... function isNarrowableReturnType( typeParameters: TypeParameter[], returnType: IndexedAccessType | ConditionalType, @@ -45914,7 +45880,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const constraintType = getConstraintOfTypeParameter(typeParameter) as UnionType; // (0) - if (!type.root.isDistributive) { // >> TODO: do we need this? depends on how narrowing instantiation is implemented + if (!type.root.isDistributive) { return false; } // (2) @@ -45922,7 +45888,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; } // (1) - // >> TODO: should this be some sort of type comparison check instead of identity? if ( !everyType(type.extendsType, extendsType => some( diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index 35b59a8ac94d4..91a751b66ef3a 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -6,39 +6,39 @@ dependentReturnType1.ts(35,9): error TS2322: Type 'string' is not assignable to dependentReturnType1.ts(69,9): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. dependentReturnType1.ts(71,5): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. dependentReturnType1.ts(80,22): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. -dependentReturnType1.ts(93,9): error TS2322: Type 'LeftOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never'. -dependentReturnType1.ts(95,9): error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never'. -dependentReturnType1.ts(112,9): error TS2322: Type 'number' is not assignable to type 'T extends Dog ? number : string'. -dependentReturnType1.ts(114,5): error TS2322: Type 'string' is not assignable to type 'T extends Dog ? number : string'. -dependentReturnType1.ts(149,13): error TS2322: Type 'string' is not assignable to type 'T extends string ? this : T extends undefined ? string : never'. -dependentReturnType1.ts(151,9): error TS2322: Type 'this' is not assignable to type 'T extends string ? this : T extends undefined ? string : never'. +dependentReturnType1.ts(96,9): error TS2322: Type 'LeftOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never'. +dependentReturnType1.ts(98,9): error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never'. +dependentReturnType1.ts(115,9): error TS2322: Type 'number' is not assignable to type 'T extends Dog ? number : string'. +dependentReturnType1.ts(117,5): error TS2322: Type 'string' is not assignable to type 'T extends Dog ? number : string'. +dependentReturnType1.ts(152,13): error TS2322: Type 'string' is not assignable to type 'T extends string ? this : T extends undefined ? string : never'. +dependentReturnType1.ts(154,9): error TS2322: Type 'this' is not assignable to type 'T extends string ? this : T extends undefined ? string : never'. Type 'Unnamed' is not assignable to type 'T extends string ? this : T extends undefined ? string : never'. -dependentReturnType1.ts(166,13): error TS2322: Type 'this' is not assignable to type 'string'. +dependentReturnType1.ts(169,13): error TS2322: Type 'this' is not assignable to type 'string'. Type 'Unnamed' is not assignable to type 'string'. -dependentReturnType1.ts(169,9): error TS2322: Type 'T & {}' is not assignable to type 'this'. +dependentReturnType1.ts(172,9): error TS2322: Type 'T & {}' is not assignable to type 'this'. 'this' could be instantiated with an arbitrary type which could be unrelated to 'T & {}'. -dependentReturnType1.ts(203,24): error TS2322: Type 'string' is not assignable to type 'number'. -dependentReturnType1.ts(203,28): error TS2322: Type 'number' is not assignable to type 'string'. -dependentReturnType1.ts(240,9): error TS2322: Type '""' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. -dependentReturnType1.ts(242,9): error TS2322: Type 'true' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. -dependentReturnType1.ts(244,5): error TS2322: Type '3' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. -dependentReturnType1.ts(272,9): error TS2322: Type '1' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. -dependentReturnType1.ts(275,9): error TS2322: Type '2' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. -dependentReturnType1.ts(277,5): error TS2322: Type '0' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. -dependentReturnType1.ts(299,9): error TS2322: Type 'string' is not assignable to type 'string[]'. -dependentReturnType1.ts(308,9): error TS2322: Type 'undefined' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'. -dependentReturnType1.ts(310,5): error TS2322: Type 'number' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'. -dependentReturnType1.ts(331,9): error TS2322: Type '1' is not assignable to type '4'. -dependentReturnType1.ts(364,13): error TS2322: Type 'number' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : never'. -dependentReturnType1.ts(366,9): error TS2322: Type 'string' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : never'. -dependentReturnType1.ts(389,9): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. -dependentReturnType1.ts(399,13): error TS2322: Type 'number' is not assignable to type 'string'. -dependentReturnType1.ts(409,9): error TS2322: Type 'true' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : never'. -dependentReturnType1.ts(411,5): error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : never'. -dependentReturnType1.ts(436,15): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. -dependentReturnType1.ts(438,11): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. -dependentReturnType1.ts(465,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. -dependentReturnType1.ts(501,5): error TS2322: Type 'number' is not assignable to type 'never'. +dependentReturnType1.ts(206,24): error TS2322: Type 'string' is not assignable to type 'number'. +dependentReturnType1.ts(206,28): error TS2322: Type 'number' is not assignable to type 'string'. +dependentReturnType1.ts(243,9): error TS2322: Type '""' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. +dependentReturnType1.ts(245,9): error TS2322: Type 'true' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. +dependentReturnType1.ts(247,5): error TS2322: Type '3' is not assignable to type 'T extends 1 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. +dependentReturnType1.ts(275,9): error TS2322: Type '1' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. +dependentReturnType1.ts(278,9): error TS2322: Type '2' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. +dependentReturnType1.ts(280,5): error TS2322: Type '0' is not assignable to type 'HelperCond<{ x: U; y: V; }, { x: string; y: true; }, 1, { x: number; y: false; }, 2>'. +dependentReturnType1.ts(302,9): error TS2322: Type 'string' is not assignable to type 'string[]'. +dependentReturnType1.ts(311,9): error TS2322: Type 'undefined' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'. +dependentReturnType1.ts(313,5): error TS2322: Type 'number' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'. +dependentReturnType1.ts(334,9): error TS2322: Type '1' is not assignable to type '4'. +dependentReturnType1.ts(367,13): error TS2322: Type 'number' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : never'. +dependentReturnType1.ts(369,9): error TS2322: Type 'string' is not assignable to type 'T extends 1 ? number : T extends 2 ? string : never'. +dependentReturnType1.ts(392,9): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +dependentReturnType1.ts(402,13): error TS2322: Type 'number' is not assignable to type 'string'. +dependentReturnType1.ts(412,9): error TS2322: Type 'true' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : never'. +dependentReturnType1.ts(414,5): error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : never'. +dependentReturnType1.ts(439,15): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +dependentReturnType1.ts(441,11): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +dependentReturnType1.ts(468,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. +dependentReturnType1.ts(504,5): error TS2322: Type 'number' is not assignable to type 'never'. ==== dependentReturnType1.ts (36 errors) ==== @@ -138,7 +138,11 @@ dependentReturnType1.ts(501,5): error TS2322: Type 'number' is not assignable to !!! error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. } - // Asymmetry + // This will not work for several reasons: + // - first because the constraint of type parameter `Arg` is generic, + // so attempting to narrow the type of `arg` in the `if` would result in type `Arg & LeftIn`, + // which when substituted in the conditional return type, would not further resolve that conditional type + // - second because the `else` branch would never work because we don't narrow the type of `arg` to `Arg & RightIn` function conditionalProducingIf( arg: Arg, cond: (arg: LeftIn | RightIn) => arg is LeftIn, @@ -146,13 +150,12 @@ dependentReturnType1.ts(501,5): error TS2322: Type 'number' is not assignable to produceRightOut: (arg: RightIn) => RightOut): Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never { - type OK = Arg extends LeftIn ? LeftOut : RightOut; if (cond(arg)) { - return produceLeftOut(arg); // The narrowed conditional return type has deferred resolution, so this doesn't work. + return produceLeftOut(arg); ~~~~~~ !!! error TS2322: Type 'LeftOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never'. } else { - return produceRightOut(arg as RightIn); // Error: Doesn't work because we can't narrow `arg` to `Arg & RightIn` here + return produceRightOut(arg as RightIn); ~~~~~~ !!! error TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never'. } diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index edbdf95e0c8a7..7e91c51b97b9e 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -216,309 +216,306 @@ function f101(x: T): T extends 1 ? One : T extends 2 ? >g : Symbol(g, Decl(dependentReturnType1.ts, 79, 60)) } -// Asymmetry +// This will not work for several reasons: +// - first because the constraint of type parameter `Arg` is generic, +// so attempting to narrow the type of `arg` in the `if` would result in type `Arg & LeftIn`, +// which when substituted in the conditional return type, would not further resolve that conditional type +// - second because the `else` branch would never work because we don't narrow the type of `arg` to `Arg & RightIn` function conditionalProducingIf( >conditionalProducingIf : Symbol(conditionalProducingIf, Decl(dependentReturnType1.ts, 80, 1)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 83, 39)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 83, 48)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 83, 57)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 83, 67)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 83, 39)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 87, 48)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 87, 57)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) arg: Arg, ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 83, 98)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 83, 67)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 67)) cond: (arg: LeftIn | RightIn) => arg is LeftIn, ->cond : Symbol(cond, Decl(dependentReturnType1.ts, 84, 13)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 85, 11)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 83, 39)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 85, 11)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) +>cond : Symbol(cond, Decl(dependentReturnType1.ts, 88, 13)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 89, 11)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 89, 11)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) produceLeftOut: (arg: LeftIn) => LeftOut, ->produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 85, 51)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 86, 21)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 83, 48)) +>produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 89, 51)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 90, 21)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 87, 48)) produceRightOut: (arg: RightIn) => RightOut): ->produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 86, 45)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 22)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 83, 39)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 83, 57)) +>produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 90, 45)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 91, 22)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 87, 57)) Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 83, 67)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 83, 48)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 83, 67)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 83, 39)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 83, 57)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 67)) +>LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 87, 32)) +>LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 87, 48)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 67)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) +>RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 87, 57)) { - type OK = Arg extends LeftIn ? LeftOut : RightOut; ->OK : Symbol(OK, Decl(dependentReturnType1.ts, 89, 1)) ->Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 83, 67)) ->LeftIn : Symbol(LeftIn, Decl(dependentReturnType1.ts, 83, 32)) ->LeftOut : Symbol(LeftOut, Decl(dependentReturnType1.ts, 83, 48)) ->RightOut : Symbol(RightOut, Decl(dependentReturnType1.ts, 83, 57)) - if (cond(arg)) { ->cond : Symbol(cond, Decl(dependentReturnType1.ts, 84, 13)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 83, 98)) +>cond : Symbol(cond, Decl(dependentReturnType1.ts, 88, 13)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) - return produceLeftOut(arg); // The narrowed conditional return type has deferred resolution, so this doesn't work. ->produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 85, 51)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 83, 98)) + return produceLeftOut(arg); +>produceLeftOut : Symbol(produceLeftOut, Decl(dependentReturnType1.ts, 89, 51)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) } else { - return produceRightOut(arg as RightIn); // Error: Doesn't work because we can't narrow `arg` to `Arg & RightIn` here ->produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 86, 45)) ->arg : Symbol(arg, Decl(dependentReturnType1.ts, 83, 98)) ->RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 83, 39)) + return produceRightOut(arg as RightIn); +>produceRightOut : Symbol(produceRightOut, Decl(dependentReturnType1.ts, 90, 45)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 98)) +>RightIn : Symbol(RightIn, Decl(dependentReturnType1.ts, 87, 39)) } } interface Animal { ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 96, 1)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 99, 1)) name: string; ->name : Symbol(Animal.name, Decl(dependentReturnType1.ts, 98, 18)) +>name : Symbol(Animal.name, Decl(dependentReturnType1.ts, 101, 18)) } interface Dog extends Animal { ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 100, 1)) ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 96, 1)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 103, 1)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 99, 1)) bark: () => string; ->bark : Symbol(Dog.bark, Decl(dependentReturnType1.ts, 102, 30)) +>bark : Symbol(Dog.bark, Decl(dependentReturnType1.ts, 105, 30)) } // This would be unsafe to narrow. declare function isDog(x: Animal): x is Dog; ->isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 104, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 107, 23)) ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 96, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 107, 23)) ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 100, 1)) +>isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 107, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 110, 23)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 99, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 110, 23)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 103, 1)) declare function doggy(x: Dog): number; ->doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 107, 44)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 108, 23)) ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 100, 1)) +>doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 110, 44)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 111, 23)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 103, 1)) function f12(x: T): T extends Dog ? number : string { ->f12 : Symbol(f12, Decl(dependentReturnType1.ts, 108, 39)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 109, 13)) ->Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 96, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 109, 31)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 109, 13)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 109, 13)) ->Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 100, 1)) +>f12 : Symbol(f12, Decl(dependentReturnType1.ts, 111, 39)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 112, 13)) +>Animal : Symbol(Animal, Decl(dependentReturnType1.ts, 99, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 112, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 112, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 112, 13)) +>Dog : Symbol(Dog, Decl(dependentReturnType1.ts, 103, 1)) if (isDog(x)) { // `x` has type `T & Dog` here ->isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 104, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 109, 31)) +>isDog : Symbol(isDog, Decl(dependentReturnType1.ts, 107, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 112, 31)) return doggy(x); ->doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 107, 44)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 109, 31)) +>doggy : Symbol(doggy, Decl(dependentReturnType1.ts, 110, 44)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 112, 31)) } return ""; // Error: Should not work because we can't express "not a Dog" in the type system } // Cannot narrow `keyof` too eagerly or something like the below breaks function f(entry: EntryId): Entry[EntryId] { ->f : Symbol(f, Decl(dependentReturnType1.ts, 114, 1)) ->Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 117, 11)) ->index : Symbol(index, Decl(dependentReturnType1.ts, 117, 28)) ->EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 117, 63)) ->Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 117, 11)) ->entry : Symbol(entry, Decl(dependentReturnType1.ts, 117, 93)) ->EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 117, 63)) ->Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 117, 11)) ->EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 117, 63)) +>f : Symbol(f, Decl(dependentReturnType1.ts, 117, 1)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 120, 11)) +>index : Symbol(index, Decl(dependentReturnType1.ts, 120, 28)) +>EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 120, 63)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 120, 11)) +>entry : Symbol(entry, Decl(dependentReturnType1.ts, 120, 93)) +>EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 120, 63)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 120, 11)) +>EntryId : Symbol(EntryId, Decl(dependentReturnType1.ts, 120, 63)) const entries = {} as Entry; ->entries : Symbol(entries, Decl(dependentReturnType1.ts, 118, 9)) ->Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 117, 11)) +>entries : Symbol(entries, Decl(dependentReturnType1.ts, 121, 9)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 120, 11)) return entries[entry]; ->entries : Symbol(entries, Decl(dependentReturnType1.ts, 118, 9)) ->entry : Symbol(entry, Decl(dependentReturnType1.ts, 117, 93)) +>entries : Symbol(entries, Decl(dependentReturnType1.ts, 121, 9)) +>entry : Symbol(entry, Decl(dependentReturnType1.ts, 120, 93)) } // Works the same as before declare function takeA(val: 'A'): void; ->takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 120, 1)) ->val : Symbol(val, Decl(dependentReturnType1.ts, 123, 23)) +>takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 123, 1)) +>val : Symbol(val, Decl(dependentReturnType1.ts, 126, 23)) export function bounceAndTakeIfA(value: AB): AB { ->bounceAndTakeIfA : Symbol(bounceAndTakeIfA, Decl(dependentReturnType1.ts, 123, 39)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 124, 33)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 124, 55)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 124, 33)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 124, 33)) +>bounceAndTakeIfA : Symbol(bounceAndTakeIfA, Decl(dependentReturnType1.ts, 126, 39)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 127, 33)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 127, 55)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 127, 33)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 127, 33)) if (value === 'A') { ->value : Symbol(value, Decl(dependentReturnType1.ts, 124, 55)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 127, 55)) takeA(value); ->takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 120, 1)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 124, 55)) +>takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 123, 1)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 127, 55)) takeAB(value); ->takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 131, 17)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 124, 55)) +>takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 134, 17)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 127, 55)) return value; ->value : Symbol(value, Decl(dependentReturnType1.ts, 124, 55)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 127, 55)) } return value; ->value : Symbol(value, Decl(dependentReturnType1.ts, 124, 55)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 127, 55)) function takeAB(val: AB): void {} ->takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 131, 17)) ->val : Symbol(val, Decl(dependentReturnType1.ts, 132, 20)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 124, 33)) +>takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 134, 17)) +>val : Symbol(val, Decl(dependentReturnType1.ts, 135, 20)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 127, 33)) } // Works the same as before export function bbb(value: AB): "a" { ->bbb : Symbol(bbb, Decl(dependentReturnType1.ts, 133, 1)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 136, 20)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 136, 42)) ->AB : Symbol(AB, Decl(dependentReturnType1.ts, 136, 20)) +>bbb : Symbol(bbb, Decl(dependentReturnType1.ts, 136, 1)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 139, 20)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 139, 42)) +>AB : Symbol(AB, Decl(dependentReturnType1.ts, 139, 20)) if (value === "a") { ->value : Symbol(value, Decl(dependentReturnType1.ts, 136, 42)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 139, 42)) return value; ->value : Symbol(value, Decl(dependentReturnType1.ts, 136, 42)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 139, 42)) } return "a"; } class Unnamed { ->Unnamed : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) +>Unnamed : Symbol(Unnamed, Decl(dependentReturnType1.ts, 144, 1)) root!: { name: string }; ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 146, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 147, 12)) // Error: No narrowing because parameter is optional but `T` doesn't allow for undefined name(name?: T): T extends string ? this : T extends undefined ? string : never { ->name : Symbol(Unnamed.name, Decl(dependentReturnType1.ts, 144, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 146, 9)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 146, 27)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 146, 9)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 146, 9)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 146, 9)) +>name : Symbol(Unnamed.name, Decl(dependentReturnType1.ts, 147, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 149, 9)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 149, 27)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 149, 9)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 149, 9)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 149, 9)) if (typeof name === 'undefined') { ->name : Symbol(name, Decl(dependentReturnType1.ts, 146, 27)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 149, 27)) return this.root.name; ->this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) ->this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 147, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 146, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 144, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 146, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 147, 12)) } return this; ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 144, 1)) } // Good conditional name2(name?: T): T extends string ? this : T extends undefined ? string : never { ->name2 : Symbol(Unnamed.name2, Decl(dependentReturnType1.ts, 151, 5)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 154, 10)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 154, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 154, 10)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 154, 10)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 154, 10)) +>name2 : Symbol(Unnamed.name2, Decl(dependentReturnType1.ts, 154, 5)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 157, 10)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 157, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 157, 10)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 157, 10)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 157, 10)) if (typeof name === 'undefined') { ->name : Symbol(name, Decl(dependentReturnType1.ts, 154, 40)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 157, 40)) return this.root.name; // Ok ->this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) ->this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 147, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 146, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 144, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 146, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 147, 12)) } this.root.name = name; ->this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) ->this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 154, 40)) +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 147, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 146, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 144, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 146, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 147, 12)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 157, 40)) return this; // Ok ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 144, 1)) } // Good conditional, wrong return expressions name3(name?: T): T extends string ? this : T extends undefined ? string : never { ->name3 : Symbol(Unnamed.name3, Decl(dependentReturnType1.ts, 160, 5)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 163, 10)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 163, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 163, 10)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 163, 10)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 163, 10)) +>name3 : Symbol(Unnamed.name3, Decl(dependentReturnType1.ts, 163, 5)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 166, 10)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 166, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 166, 10)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 166, 10)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 166, 10)) if (typeof name === 'undefined') { ->name : Symbol(name, Decl(dependentReturnType1.ts, 163, 40)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 166, 40)) return this; // Error ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 144, 1)) } this.root.name = name; ->this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) ->this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 141, 1)) ->root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 143, 15)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 144, 12)) ->name : Symbol(name, Decl(dependentReturnType1.ts, 163, 40)) +>this.root.name : Symbol(name, Decl(dependentReturnType1.ts, 147, 12)) +>this.root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 146, 15)) +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 144, 1)) +>root : Symbol(Unnamed.root, Decl(dependentReturnType1.ts, 146, 15)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 147, 12)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 166, 40)) return name; // Error ->name : Symbol(name, Decl(dependentReturnType1.ts, 163, 40)) +>name : Symbol(name, Decl(dependentReturnType1.ts, 166, 40)) } } // Conditional expressions interface Aa { ->Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 170, 1)) +>Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 173, 1)) 1: number; ->1 : Symbol(Aa[1], Decl(dependentReturnType1.ts, 173, 14)) +>1 : Symbol(Aa[1], Decl(dependentReturnType1.ts, 176, 14)) 2: string; ->2 : Symbol(Aa[2], Decl(dependentReturnType1.ts, 174, 14)) +>2 : Symbol(Aa[2], Decl(dependentReturnType1.ts, 177, 14)) 3: boolean; ->3 : Symbol(Aa[3], Decl(dependentReturnType1.ts, 175, 14)) +>3 : Symbol(Aa[3], Decl(dependentReturnType1.ts, 178, 14)) } function trivialConditional(x: T): Aa[T] { ->trivialConditional : Symbol(trivialConditional, Decl(dependentReturnType1.ts, 177, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 179, 28)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 179, 49)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 179, 28)) ->Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 170, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 179, 28)) +>trivialConditional : Symbol(trivialConditional, Decl(dependentReturnType1.ts, 180, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 182, 28)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 182, 49)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 182, 28)) +>Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 173, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 182, 28)) if (x !== 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 179, 49)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 182, 49)) return x === 2 ? "" : true; ->x : Symbol(x, Decl(dependentReturnType1.ts, 179, 49)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 182, 49)) } else { return 0; @@ -526,98 +523,98 @@ function trivialConditional(x: T): Aa[T] { } function conditional(x: T): ->conditional : Symbol(conditional, Decl(dependentReturnType1.ts, 186, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 188, 21)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 188, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 188, 21)) +>conditional : Symbol(conditional, Decl(dependentReturnType1.ts, 189, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 191, 21)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 191, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 191, 21)) T extends true ? 1 : T extends false ? 2 : never { ->T : Symbol(T, Decl(dependentReturnType1.ts, 188, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 188, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 191, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 191, 21)) return x ? 1 : 2; // Ok ->x : Symbol(x, Decl(dependentReturnType1.ts, 188, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 191, 40)) } function contextualConditional( ->contextualConditional : Symbol(contextualConditional, Decl(dependentReturnType1.ts, 191, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 193, 31)) +>contextualConditional : Symbol(contextualConditional, Decl(dependentReturnType1.ts, 194, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 196, 31)) x: T ->x : Symbol(x, Decl(dependentReturnType1.ts, 193, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 193, 31)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 196, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 196, 31)) ): T extends "a" ? "a" : T extends "b" ? number : never { ->T : Symbol(T, Decl(dependentReturnType1.ts, 193, 31)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 193, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 196, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 196, 31)) return x === "a" ? x : parseInt(x); // Ok ->x : Symbol(x, Decl(dependentReturnType1.ts, 193, 52)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 193, 52)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 196, 52)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 196, 52)) >parseInt : Symbol(parseInt, Decl(lib.es5.d.ts, --, --)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 193, 52)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 196, 52)) } function conditionalWithError( ->conditionalWithError : Symbol(conditionalWithError, Decl(dependentReturnType1.ts, 197, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 199, 30)) +>conditionalWithError : Symbol(conditionalWithError, Decl(dependentReturnType1.ts, 200, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 202, 30)) x: T ->x : Symbol(x, Decl(dependentReturnType1.ts, 199, 51)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 199, 30)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 202, 51)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 202, 30)) ): T extends "a" ? number : T extends "b" ? string : never { ->T : Symbol(T, Decl(dependentReturnType1.ts, 199, 30)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 199, 30)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 202, 30)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 202, 30)) return x === "a" ? x : parseInt(x); // Error ->x : Symbol(x, Decl(dependentReturnType1.ts, 199, 51)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 199, 51)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 202, 51)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 202, 51)) >parseInt : Symbol(parseInt, Decl(lib.es5.d.ts, --, --)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 199, 51)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 202, 51)) } // Multiple indexed type reductions interface BB { ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 203, 1)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 206, 1)) "a": number; ->"a" : Symbol(BB["a"], Decl(dependentReturnType1.ts, 206, 14)) +>"a" : Symbol(BB["a"], Decl(dependentReturnType1.ts, 209, 14)) [y: number]: string; ->y : Symbol(y, Decl(dependentReturnType1.ts, 208, 5)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 211, 5)) } interface AA { ->AA : Symbol(AA, Decl(dependentReturnType1.ts, 209, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 211, 13)) ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 203, 1)) +>AA : Symbol(AA, Decl(dependentReturnType1.ts, 212, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 214, 13)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 206, 1)) "c": BB[T]; ->"c" : Symbol(AA["c"], Decl(dependentReturnType1.ts, 211, 34)) ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 203, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 211, 13)) +>"c" : Symbol(AA["c"], Decl(dependentReturnType1.ts, 214, 34)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 206, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 214, 13)) "d": boolean, ->"d" : Symbol(AA["d"], Decl(dependentReturnType1.ts, 212, 15)) +>"d" : Symbol(AA["d"], Decl(dependentReturnType1.ts, 215, 15)) } function reduction(x: T, y: U): AA[U] { ->reduction : Symbol(reduction, Decl(dependentReturnType1.ts, 214, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 216, 19)) ->BB : Symbol(BB, Decl(dependentReturnType1.ts, 203, 1)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 216, 38)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 216, 60)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 216, 19)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 216, 65)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 216, 38)) ->AA : Symbol(AA, Decl(dependentReturnType1.ts, 209, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 216, 19)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 216, 38)) +>reduction : Symbol(reduction, Decl(dependentReturnType1.ts, 217, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 219, 19)) +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 206, 1)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 219, 38)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 219, 60)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 219, 19)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 219, 65)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 219, 38)) +>AA : Symbol(AA, Decl(dependentReturnType1.ts, 212, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 219, 19)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 219, 38)) if (y === "c" && x === "a") { ->y : Symbol(y, Decl(dependentReturnType1.ts, 216, 65)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 216, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 219, 65)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 219, 60)) // AA[U='c'] -> BB[T] // BB[T='a'] -> number @@ -630,36 +627,36 @@ function reduction(x: T, y: U): AA[U // Substitution types are not narrowed function subsCond( ->subsCond : Symbol(subsCond, Decl(dependentReturnType1.ts, 224, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 227, 18)) +>subsCond : Symbol(subsCond, Decl(dependentReturnType1.ts, 227, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 230, 18)) x: T, ->x : Symbol(x, Decl(dependentReturnType1.ts, 227, 39)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 227, 18)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 230, 39)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 230, 18)) ): T extends 1 | 2 ->T : Symbol(T, Decl(dependentReturnType1.ts, 227, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 230, 18)) ? T extends 1 ->T : Symbol(T, Decl(dependentReturnType1.ts, 227, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 230, 18)) ? string : T extends 2 ->T : Symbol(T, Decl(dependentReturnType1.ts, 227, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 230, 18)) ? boolean : never : T extends 3 ->T : Symbol(T, Decl(dependentReturnType1.ts, 227, 18)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 230, 18)) ? number : never { if (x === 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 227, 39)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 230, 39)) return ""; } else if (x == 2) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 227, 39)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 230, 39)) return true; } @@ -669,105 +666,105 @@ function subsCond( // Unsafe: check types overlap declare function q(x: object): x is { b: number }; ->q : Symbol(q, Decl(dependentReturnType1.ts, 244, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 248, 19)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 248, 19)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 248, 37)) +>q : Symbol(q, Decl(dependentReturnType1.ts, 247, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 251, 19)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 251, 19)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 251, 37)) function foo( ->foo : Symbol(foo, Decl(dependentReturnType1.ts, 248, 50)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 249, 13)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 249, 24)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 249, 40)) +>foo : Symbol(foo, Decl(dependentReturnType1.ts, 251, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 252, 13)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 252, 24)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 252, 40)) x: T, ->x : Symbol(x, Decl(dependentReturnType1.ts, 249, 54)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 249, 13)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 252, 54)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 252, 13)) ): T extends { a: string } ? number : T extends { b: number } ? string : never { ->T : Symbol(T, Decl(dependentReturnType1.ts, 249, 13)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 251, 14)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 249, 13)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 251, 49)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 252, 13)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 254, 14)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 252, 13)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 254, 49)) if (q(x)) { ->q : Symbol(q, Decl(dependentReturnType1.ts, 244, 1)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 249, 54)) +>q : Symbol(q, Decl(dependentReturnType1.ts, 247, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 252, 54)) x.b; ->x.b : Symbol(b, Decl(dependentReturnType1.ts, 249, 40)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 249, 54)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 249, 40)) +>x.b : Symbol(b, Decl(dependentReturnType1.ts, 252, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 252, 54)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 252, 40)) return ""; } x.a; ->x.a : Symbol(a, Decl(dependentReturnType1.ts, 249, 24)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 249, 54)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 249, 24)) +>x.a : Symbol(a, Decl(dependentReturnType1.ts, 252, 24)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 252, 54)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 252, 24)) return 1; } let y = { a: "", b: 1 } ->y : Symbol(y, Decl(dependentReturnType1.ts, 260, 3)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 260, 9)) ->b : Symbol(b, Decl(dependentReturnType1.ts, 260, 16)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 263, 3)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 263, 9)) +>b : Symbol(b, Decl(dependentReturnType1.ts, 263, 16)) const r = foo<{ a: string }>(y); // type says number but actually string ->r : Symbol(r, Decl(dependentReturnType1.ts, 261, 5)) ->foo : Symbol(foo, Decl(dependentReturnType1.ts, 248, 50)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 261, 15)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 260, 3)) +>r : Symbol(r, Decl(dependentReturnType1.ts, 264, 5)) +>foo : Symbol(foo, Decl(dependentReturnType1.ts, 251, 50)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 264, 15)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 263, 3)) type HelperCond = T extends A ? R1 : T extends B ? R2 : never; ->HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 261, 32)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 263, 16)) ->A : Symbol(A, Decl(dependentReturnType1.ts, 263, 18)) ->R1 : Symbol(R1, Decl(dependentReturnType1.ts, 263, 21)) ->B : Symbol(B, Decl(dependentReturnType1.ts, 263, 25)) ->R2 : Symbol(R2, Decl(dependentReturnType1.ts, 263, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 263, 16)) ->A : Symbol(A, Decl(dependentReturnType1.ts, 263, 18)) ->R1 : Symbol(R1, Decl(dependentReturnType1.ts, 263, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 263, 16)) ->B : Symbol(B, Decl(dependentReturnType1.ts, 263, 25)) ->R2 : Symbol(R2, Decl(dependentReturnType1.ts, 263, 28)) +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 264, 32)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 266, 16)) +>A : Symbol(A, Decl(dependentReturnType1.ts, 266, 18)) +>R1 : Symbol(R1, Decl(dependentReturnType1.ts, 266, 21)) +>B : Symbol(B, Decl(dependentReturnType1.ts, 266, 25)) +>R2 : Symbol(R2, Decl(dependentReturnType1.ts, 266, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 266, 16)) +>A : Symbol(A, Decl(dependentReturnType1.ts, 266, 18)) +>R1 : Symbol(R1, Decl(dependentReturnType1.ts, 266, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 266, 16)) +>B : Symbol(B, Decl(dependentReturnType1.ts, 266, 25)) +>R2 : Symbol(R2, Decl(dependentReturnType1.ts, 266, 28)) // We don't narrow the return type because the conditionals are not distributive function foo2(x: U, y: V): ->foo2 : Symbol(foo2, Decl(dependentReturnType1.ts, 263, 79)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 266, 14)) ->V : Symbol(V, Decl(dependentReturnType1.ts, 266, 40)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 266, 60)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 266, 14)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 266, 65)) ->V : Symbol(V, Decl(dependentReturnType1.ts, 266, 40)) +>foo2 : Symbol(foo2, Decl(dependentReturnType1.ts, 266, 79)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 269, 14)) +>V : Symbol(V, Decl(dependentReturnType1.ts, 269, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 269, 60)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 269, 14)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 269, 65)) +>V : Symbol(V, Decl(dependentReturnType1.ts, 269, 40)) HelperCond<{ x: U, y: V }, ->HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 261, 32)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 267, 16)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 266, 14)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 267, 22)) ->V : Symbol(V, Decl(dependentReturnType1.ts, 266, 40)) +>HelperCond : Symbol(HelperCond, Decl(dependentReturnType1.ts, 264, 32)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 270, 16)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 269, 14)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 270, 22)) +>V : Symbol(V, Decl(dependentReturnType1.ts, 269, 40)) { x: string, y: true }, 1, ->x : Symbol(x, Decl(dependentReturnType1.ts, 268, 9)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 268, 20)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 271, 9)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 271, 20)) { x: number, y: false }, 2> { ->x : Symbol(x, Decl(dependentReturnType1.ts, 269, 9)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 269, 20)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 272, 9)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 272, 20)) if (typeof x === "string" && y === true) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 266, 60)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 266, 65)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 269, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 269, 65)) return 1; // Error } if (typeof x === "number" && y === false) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 266, 60)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 266, 65)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 269, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 269, 65)) return 2; // Error } @@ -776,97 +773,97 @@ function foo2(x: U, y: V): // From https://github.com/microsoft/TypeScript/issues/24929#issue-332087943 declare function isString(s: unknown): s is string; ->isString : Symbol(isString, Decl(dependentReturnType1.ts, 277, 1)) ->s : Symbol(s, Decl(dependentReturnType1.ts, 280, 26)) ->s : Symbol(s, Decl(dependentReturnType1.ts, 280, 26)) +>isString : Symbol(isString, Decl(dependentReturnType1.ts, 280, 1)) +>s : Symbol(s, Decl(dependentReturnType1.ts, 283, 26)) +>s : Symbol(s, Decl(dependentReturnType1.ts, 283, 26)) // capitalize a string or each element of an array of strings function capitalize( ->capitalize : Symbol(capitalize, Decl(dependentReturnType1.ts, 280, 51)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 282, 20)) +>capitalize : Symbol(capitalize, Decl(dependentReturnType1.ts, 283, 51)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 285, 20)) input: T ->input : Symbol(input, Decl(dependentReturnType1.ts, 282, 49)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 282, 20)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 285, 49)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 285, 20)) ): T extends string[] ? string[] : T extends string ? string : never { ->T : Symbol(T, Decl(dependentReturnType1.ts, 282, 20)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 282, 20)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 285, 20)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 285, 20)) if (isString(input)) { ->isString : Symbol(isString, Decl(dependentReturnType1.ts, 277, 1)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 282, 49)) +>isString : Symbol(isString, Decl(dependentReturnType1.ts, 280, 1)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 285, 49)) return input[0].toUpperCase() + input.slice(1); // Ok >input[0].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 282, 49)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 285, 49)) >toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) >input.slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 282, 49)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 285, 49)) >slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) } else { return input.map(elt => capitalize(elt)); // Ok >input.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 282, 49)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 285, 49)) >map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) ->elt : Symbol(elt, Decl(dependentReturnType1.ts, 288, 25)) ->capitalize : Symbol(capitalize, Decl(dependentReturnType1.ts, 280, 51)) ->elt : Symbol(elt, Decl(dependentReturnType1.ts, 288, 25)) +>elt : Symbol(elt, Decl(dependentReturnType1.ts, 291, 25)) +>capitalize : Symbol(capitalize, Decl(dependentReturnType1.ts, 283, 51)) +>elt : Symbol(elt, Decl(dependentReturnType1.ts, 291, 25)) } } function badCapitalize( ->badCapitalize : Symbol(badCapitalize, Decl(dependentReturnType1.ts, 290, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 292, 23)) +>badCapitalize : Symbol(badCapitalize, Decl(dependentReturnType1.ts, 293, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 295, 23)) input: T ->input : Symbol(input, Decl(dependentReturnType1.ts, 292, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 292, 23)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 295, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 295, 23)) ): T extends string[] ? string[] : T extends string ? string : never { ->T : Symbol(T, Decl(dependentReturnType1.ts, 292, 23)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 292, 23)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 295, 23)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 295, 23)) if (isString(input)) { ->isString : Symbol(isString, Decl(dependentReturnType1.ts, 277, 1)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 292, 52)) +>isString : Symbol(isString, Decl(dependentReturnType1.ts, 280, 1)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 295, 52)) return input[0].toUpperCase() + input.slice(1); // Ok >input[0].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 292, 52)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 295, 52)) >toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) >input.slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 292, 52)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 295, 52)) >slice : Symbol(String.slice, Decl(lib.es5.d.ts, --, --)) } else { return input[0].toUpperCase() + input.slice(1); // Bad, error >input[0].toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 292, 52)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 295, 52)) >toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) >input.slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --)) ->input : Symbol(input, Decl(dependentReturnType1.ts, 292, 52)) +>input : Symbol(input, Decl(dependentReturnType1.ts, 295, 52)) >slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --)) } } // No narrowing because conditional's extends type is different from type parameter constraint types function voidRet( ->voidRet : Symbol(voidRet, Decl(dependentReturnType1.ts, 300, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 303, 17)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 303, 28)) +>voidRet : Symbol(voidRet, Decl(dependentReturnType1.ts, 303, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 306, 17)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 306, 28)) x: T ->x : Symbol(x, Decl(dependentReturnType1.ts, 303, 54)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 303, 17)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 306, 54)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 306, 17)) ): T extends {} ? void : T extends undefined ? number : never { ->T : Symbol(T, Decl(dependentReturnType1.ts, 303, 17)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 303, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 306, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 306, 17)) if (x) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 303, 54)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 306, 54)) return; } @@ -875,46 +872,46 @@ function voidRet( // Multiple type parameters at once function woo( ->woo : Symbol(woo, Decl(dependentReturnType1.ts, 310, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 313, 13)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 313, 39)) +>woo : Symbol(woo, Decl(dependentReturnType1.ts, 313, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 316, 13)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 316, 39)) x: T, ->x : Symbol(x, Decl(dependentReturnType1.ts, 313, 67)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 313, 13)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 316, 67)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 316, 13)) y: U, ->y : Symbol(y, Decl(dependentReturnType1.ts, 314, 9)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 313, 39)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 317, 9)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 316, 39)) ): T extends string ->T : Symbol(T, Decl(dependentReturnType1.ts, 313, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 316, 13)) ? U extends string ->U : Symbol(U, Decl(dependentReturnType1.ts, 313, 39)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 316, 39)) ? 1 : U extends number ->U : Symbol(U, Decl(dependentReturnType1.ts, 313, 39)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 316, 39)) ? 2 : never : T extends number ->T : Symbol(T, Decl(dependentReturnType1.ts, 313, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 316, 13)) ? U extends number ->U : Symbol(U, Decl(dependentReturnType1.ts, 313, 39)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 316, 39)) ? 3 : U extends string ->U : Symbol(U, Decl(dependentReturnType1.ts, 313, 39)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 316, 39)) ? 4 : never : never { if (typeof x === "number" && typeof y === "string") { ->x : Symbol(x, Decl(dependentReturnType1.ts, 313, 67)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 314, 9)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 316, 67)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 317, 9)) return 1; // Good error } @@ -923,46 +920,46 @@ function woo( } function ttt( ->ttt : Symbol(ttt, Decl(dependentReturnType1.ts, 333, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 335, 13)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 335, 39)) +>ttt : Symbol(ttt, Decl(dependentReturnType1.ts, 336, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 338, 13)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 338, 39)) x: T, ->x : Symbol(x, Decl(dependentReturnType1.ts, 335, 67)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 335, 13)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 338, 67)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 338, 13)) y: U, ->y : Symbol(y, Decl(dependentReturnType1.ts, 336, 9)) ->U : Symbol(U, Decl(dependentReturnType1.ts, 335, 39)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 339, 9)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 338, 39)) ): T extends string ->T : Symbol(T, Decl(dependentReturnType1.ts, 335, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 338, 13)) ? U extends string ->U : Symbol(U, Decl(dependentReturnType1.ts, 335, 39)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 338, 39)) ? 1 : U extends number ->U : Symbol(U, Decl(dependentReturnType1.ts, 335, 39)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 338, 39)) ? 2 : never : T extends number ->T : Symbol(T, Decl(dependentReturnType1.ts, 335, 13)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 338, 13)) ? U extends number ->U : Symbol(U, Decl(dependentReturnType1.ts, 335, 39)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 338, 39)) ? 3 : U extends string ->U : Symbol(U, Decl(dependentReturnType1.ts, 335, 39)) +>U : Symbol(U, Decl(dependentReturnType1.ts, 338, 39)) ? 4 : never : never { if (typeof x === "number" && typeof y === "string") { ->x : Symbol(x, Decl(dependentReturnType1.ts, 335, 67)) ->y : Symbol(y, Decl(dependentReturnType1.ts, 336, 9)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 338, 67)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 339, 9)) return 4; // Ok } @@ -973,22 +970,22 @@ function ttt( // Shadowing of the narrowed reference function shadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { ->shadowing : Symbol(shadowing, Decl(dependentReturnType1.ts, 356, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 359, 36)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 359, 19)) +>shadowing : Symbol(shadowing, Decl(dependentReturnType1.ts, 359, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 362, 19)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 362, 36)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 362, 19)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 362, 19)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 362, 19)) if (true) { let x: number = Math.random() ? 1 : 2; ->x : Symbol(x, Decl(dependentReturnType1.ts, 361, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 364, 11)) >Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) >Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) >random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) if (x === 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 361, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 364, 11)) return 1; // Error } @@ -997,16 +994,16 @@ function shadowing(x: T): T extends 1 ? number : T extends 2 ? } function noShadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { ->noShadowing : Symbol(noShadowing, Decl(dependentReturnType1.ts, 367, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 369, 38)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 369, 21)) +>noShadowing : Symbol(noShadowing, Decl(dependentReturnType1.ts, 370, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 372, 21)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 372, 38)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 372, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 372, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 372, 21)) if (true) { if (x === 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 369, 38)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 372, 38)) return 1; // Ok } @@ -1016,32 +1013,32 @@ function noShadowing(x: T): T extends 1 ? number : T extends 2 // If the narrowing reference is out of scope, we simply won't narrow its type declare let someX: boolean; ->someX : Symbol(someX, Decl(dependentReturnType1.ts, 379, 11)) +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 382, 11)) function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : never { ->scope2 : Symbol(scope2, Decl(dependentReturnType1.ts, 379, 27)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 380, 16)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 380, 35)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 380, 42)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 380, 16)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 380, 16)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 380, 16)) +>scope2 : Symbol(scope2, Decl(dependentReturnType1.ts, 382, 27)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 383, 16)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 383, 35)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 383, 42)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 383, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 383, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 383, 16)) if ((true)) { const someX = opts.a; ->someX : Symbol(someX, Decl(dependentReturnType1.ts, 382, 13)) ->opts.a : Symbol(a, Decl(dependentReturnType1.ts, 380, 42)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 380, 35)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 380, 42)) +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 385, 13)) +>opts.a : Symbol(a, Decl(dependentReturnType1.ts, 383, 42)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 383, 35)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 383, 42)) if (someX) { // We narrow `someX` and the return type here ->someX : Symbol(someX, Decl(dependentReturnType1.ts, 382, 13)) +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 385, 13)) return 1; } } if (!someX) { // This is a different `someX`, so we don't narrow here ->someX : Symbol(someX, Decl(dependentReturnType1.ts, 379, 11)) +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 382, 11)) return 2; } @@ -1051,24 +1048,24 @@ function scope2(opts: { a: T }): T extends true ? 1 : T exten } function moreShadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { ->moreShadowing : Symbol(moreShadowing, Decl(dependentReturnType1.ts, 392, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 394, 23)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 394, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 394, 23)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 394, 23)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 394, 23)) +>moreShadowing : Symbol(moreShadowing, Decl(dependentReturnType1.ts, 395, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 397, 23)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 397, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 397, 23)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 397, 23)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 397, 23)) if (x === 2) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 394, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 397, 40)) let x: number = Math.random() ? 1 : 2; ->x : Symbol(x, Decl(dependentReturnType1.ts, 396, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 399, 11)) >Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) >Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) >random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) if (x === 1) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 396, 11)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 399, 11)) return 1; // Error } @@ -1079,17 +1076,17 @@ function moreShadowing(x: T): T extends 1 ? number : T extends // This would be unsafe to narrow due to `infer` type. function withInfer(x: T): T extends [infer R] ? R : T extends number ? boolean : never { ->withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 403, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 406, 19)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 406, 48)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 406, 19)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 406, 19)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 406, 71)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 406, 71)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 406, 19)) +>withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 406, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 409, 19)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 409, 48)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 409, 19)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 409, 19)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 409, 71)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 409, 71)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 409, 19)) if (typeof x === "number") { ->x : Symbol(x, Decl(dependentReturnType1.ts, 406, 48)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 409, 48)) return true; } @@ -1097,22 +1094,22 @@ function withInfer(x: T): T extends [infer R] ? R : } const withInferResult = withInfer(["a"] as const); // The type says it returns `"a"`, but the function actually returns `""`. ->withInferResult : Symbol(withInferResult, Decl(dependentReturnType1.ts, 413, 5)) ->withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 403, 1)) +>withInferResult : Symbol(withInferResult, Decl(dependentReturnType1.ts, 416, 5)) +>withInfer : Symbol(withInfer, Decl(dependentReturnType1.ts, 406, 1)) >const : Symbol(const) // Ok async function abool(x: T): Promise { ->abool : Symbol(abool, Decl(dependentReturnType1.ts, 413, 50)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 416, 21)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 416, 45)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 416, 21)) +>abool : Symbol(abool, Decl(dependentReturnType1.ts, 416, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 419, 21)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 419, 45)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 419, 21)) >Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 416, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 416, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 419, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 419, 21)) if (x) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 416, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 419, 45)) return 1; } @@ -1121,17 +1118,17 @@ async function abool(x: T): Promise(x: T): Generator { ->bbool : Symbol(bbool, Decl(dependentReturnType1.ts, 421, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 424, 16)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 424, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 424, 16)) +>bbool : Symbol(bbool, Decl(dependentReturnType1.ts, 424, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 427, 16)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 427, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 427, 16)) >Generator : Symbol(Generator, Decl(lib.es2015.generator.d.ts, --, --)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 424, 16)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 424, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 427, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 427, 16)) yield 3; if (x) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 424, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 427, 40)) return 1; } @@ -1140,16 +1137,16 @@ function* bbool(x: T): Generator(x: T): Generator { ->cbool : Symbol(cbool, Decl(dependentReturnType1.ts, 430, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 433, 16)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 433, 40)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 433, 16)) +>cbool : Symbol(cbool, Decl(dependentReturnType1.ts, 433, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 436, 16)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 436, 40)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 436, 16)) >Generator : Symbol(Generator, Decl(lib.es2015.generator.d.ts, --, --)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 433, 16)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 433, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 436, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 436, 16)) if (x) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 433, 40)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 436, 40)) yield 1; } @@ -1159,136 +1156,136 @@ function* cbool(x: T): Generator { ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 442, 25)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 442, 27)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 445, 25)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 445, 27)) abstract perform(t: T): R; ->perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 442, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 443, 21)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 442, 25)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 442, 27)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 446, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 445, 25)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 445, 27)) } type ConditionalReturnType | undefined> = ->ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 444, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 446, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 446, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 446, 32)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 446, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 446, 29)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 447, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 449, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 449, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 449, 29)) EOp extends Operation ? R : EOp extends undefined ? T | R : never; ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 446, 32)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 446, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 446, 29)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 446, 29)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 446, 32)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 446, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 446, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 449, 32)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 449, 29)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 449, 29)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 449, 32)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 449, 29)) class ConditionalOperation< ->ConditionalOperation : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 447, 76)) +>ConditionalOperation : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) T, ->T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) R, ->R : Symbol(R, Decl(dependentReturnType1.ts, 450, 6)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 453, 6)) EOp extends Operation | undefined, ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 6)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 450, 6)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 454, 6)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 453, 6)) > extends Operation> { ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) ->ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 444, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 450, 6)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 6)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 447, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 453, 6)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 454, 6)) constructor( private predicate: (value: T) => boolean, ->predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 454, 16)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 455, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) +>predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 457, 16)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 458, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) private thenOp: Operation, ->thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 455, 49)) ->Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 450, 6)) +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 458, 49)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 453, 6)) private elseOp?: EOp, ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 456, 40)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 6)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 459, 40)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 454, 6)) ) { super(); ->super : Symbol(Operation, Decl(dependentReturnType1.ts, 439, 1)) +>super : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) } perform(t: T): ConditionalReturnType { ->perform : Symbol(ConditionalOperation.perform, Decl(dependentReturnType1.ts, 460, 5)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 462, 12)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) ->ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 444, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 450, 6)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 451, 6)) +>perform : Symbol(ConditionalOperation.perform, Decl(dependentReturnType1.ts, 463, 5)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 465, 12)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 447, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 453, 6)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 454, 6)) if (this.predicate(t)) { ->this.predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 454, 16)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 447, 76)) ->predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 454, 16)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 462, 12)) +>this.predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 457, 16)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) +>predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 457, 16)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 465, 12)) return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it ->this.thenOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 442, 32)) ->this.thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 455, 49)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 447, 76)) ->thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 455, 49)) ->perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 442, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 462, 12)) +>this.thenOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) +>this.thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 458, 49)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 458, 49)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 465, 12)) } else if (typeof this.elseOp !== "undefined") { ->this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 456, 40)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 447, 76)) ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 456, 40)) +>this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 459, 40)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 459, 40)) return this.elseOp.perform(t); // Ok ->this.elseOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 442, 32)) ->this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 456, 40)) ->this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 447, 76)) ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 456, 40)) ->perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 442, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 462, 12)) +>this.elseOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) +>this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 459, 40)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 459, 40)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 465, 12)) } else { return t; // Ok ->t : Symbol(t, Decl(dependentReturnType1.ts, 462, 12)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 465, 12)) } } } // Optional tuple element function tupl(x: [string, some?: T]): ->tupl : Symbol(tupl, Decl(dependentReturnType1.ts, 471, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 474, 14)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 474, 50)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 474, 14)) +>tupl : Symbol(tupl, Decl(dependentReturnType1.ts, 474, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 477, 14)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 477, 50)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 477, 14)) T extends true ? 1 : T extends false | undefined ? 2 : never { ->T : Symbol(T, Decl(dependentReturnType1.ts, 474, 14)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 474, 14)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 477, 14)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 477, 14)) if (x[1]) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 474, 50)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 477, 50)) >1 : Symbol(1) return 1; @@ -1298,57 +1295,57 @@ function tupl(x: [string, some?: T]): // Return conditional expressions with parentheses function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : never { ->returnStuff1 : Symbol(returnStuff1, Decl(dependentReturnType1.ts, 480, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 483, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 483, 41)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 483, 48)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 483, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 483, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 483, 22)) +>returnStuff1 : Symbol(returnStuff1, Decl(dependentReturnType1.ts, 483, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 486, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 486, 41)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 486, 48)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 486, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 486, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 486, 22)) return (opts.x ? (1) : 2); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 483, 48)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 483, 41)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 483, 48)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 486, 48)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 486, 41)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 486, 48)) } function returnStuff2(opts: { x: T }): ->returnStuff2 : Symbol(returnStuff2, Decl(dependentReturnType1.ts, 485, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 487, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 487, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 487, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 487, 22)) +>returnStuff2 : Symbol(returnStuff2, Decl(dependentReturnType1.ts, 488, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 490, 22)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 490, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 490, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 490, 22)) T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never { ->T : Symbol(T, Decl(dependentReturnType1.ts, 487, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 487, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 487, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 490, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 490, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 490, 22)) return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 487, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 487, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 487, 52)) ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 487, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 487, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 487, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 490, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 490, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 490, 52)) +>opts.x : Symbol(x, Decl(dependentReturnType1.ts, 490, 52)) +>opts : Symbol(opts, Decl(dependentReturnType1.ts, 490, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 490, 52)) } // If the conditional type's input is `never`, then it resolves to `never`: function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : never { ->neverOk : Symbol(neverOk, Decl(dependentReturnType1.ts, 490, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 493, 17)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 493, 36)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 493, 17)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 493, 17)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 493, 17)) +>neverOk : Symbol(neverOk, Decl(dependentReturnType1.ts, 493, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 496, 17)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 496, 36)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 496, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 496, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 496, 17)) if (x === true) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 493, 36)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 496, 36)) return 1; } if (x === false) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 493, 36)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 496, 36)) return 2; } diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 11c0c8a27c432..641643029c9bc 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -340,7 +340,11 @@ function f101(x: T): T extends 1 ? One : T extends 2 ? > : ^^^ } -// Asymmetry +// This will not work for several reasons: +// - first because the constraint of type parameter `Arg` is generic, +// so attempting to narrow the type of `arg` in the `if` would result in type `Arg & LeftIn`, +// which when substituted in the conditional return type, would not further resolve that conditional type +// - second because the `else` branch would never work because we don't narrow the type of `arg` to `Arg & RightIn` function conditionalProducingIf( >conditionalProducingIf : (arg: Arg, cond: (arg: LeftIn | RightIn) => arg is LeftIn, produceLeftOut: (arg: LeftIn) => LeftOut, produceRightOut: (arg: RightIn) => RightOut) => Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never > : ^ ^^ ^^ ^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ @@ -369,10 +373,6 @@ function conditionalProducingIfOK : Arg extends LeftIn ? LeftOut : RightOut -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - if (cond(arg)) { >cond(arg) : boolean > : ^^^^^^^ @@ -381,7 +381,7 @@ function conditionalProducingIfarg : Arg > : ^^^ - return produceLeftOut(arg); // The narrowed conditional return type has deferred resolution, so this doesn't work. + return produceLeftOut(arg); >produceLeftOut(arg) : LeftOut > : ^^^^^^^ >produceLeftOut : (arg: LeftIn) => LeftOut @@ -390,7 +390,7 @@ function conditionalProducingIf : ^^^^^^^^^^^^ } else { - return produceRightOut(arg as RightIn); // Error: Doesn't work because we can't narrow `arg` to `Arg & RightIn` here + return produceRightOut(arg as RightIn); >produceRightOut(arg as RightIn) : RightOut > : ^^^^^^^^ >produceRightOut : (arg: RightIn) => RightOut diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index dcd084f096ddc..da275c6761404 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -84,21 +84,24 @@ function f101(x: T): T extends 1 ? One : T extends 2 ? return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // EPC Error } -// Asymmetry -// function conditionalProducingIf( -// arg: Arg, -// cond: (arg: LeftIn | RightIn) => arg is LeftIn, -// produceLeftOut: (arg: LeftIn) => LeftOut, -// produceRightOut: (arg: RightIn) => RightOut): -// Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never -// { -// type OK = Arg extends LeftIn ? LeftOut : RightOut; -// if (cond(arg)) { -// return produceLeftOut(arg); // The narrowed conditional return type has deferred resolution, so this doesn't work. -// } else { -// return produceRightOut(arg as RightIn); // Error: Doesn't work because we can't narrow `arg` to `Arg & RightIn` here -// } -// } +// This will not work for several reasons: +// - first because the constraint of type parameter `Arg` is generic, +// so attempting to narrow the type of `arg` in the `if` would result in type `Arg & LeftIn`, +// which when substituted in the conditional return type, would not further resolve that conditional type +// - second because the `else` branch would never work because we don't narrow the type of `arg` to `Arg & RightIn` +function conditionalProducingIf( + arg: Arg, + cond: (arg: LeftIn | RightIn) => arg is LeftIn, + produceLeftOut: (arg: LeftIn) => LeftOut, + produceRightOut: (arg: RightIn) => RightOut): + Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never +{ + if (cond(arg)) { + return produceLeftOut(arg); + } else { + return produceRightOut(arg as RightIn); + } +} interface Animal { name: string; From 99d3cd5350a8d8d31bbcfbfefa1e5f060f13eab1 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 10 Sep 2024 17:35:43 -0700 Subject: [PATCH 70/90] switch reference detection algorithm --- src/compiler/checker.ts | 319 ++++-------------- .../reference/dependentReturnType1.errors.txt | 53 ++- .../reference/dependentReturnType1.symbols | 225 ++++++------ .../reference/dependentReturnType1.types | 178 +++++----- .../reference/dependentReturnType4.errors.txt | 95 ++---- .../reference/dependentReturnType4.symbols | 246 +++----------- .../reference/dependentReturnType4.types | 299 ++-------------- tests/cases/compiler/dependentReturnType1.ts | 38 ++- tests/cases/compiler/dependentReturnType4.ts | 80 +---- .../returnTypeNarrowingAfterCachingTypes.ts | 6 +- 10 files changed, 456 insertions(+), 1083 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index deff2e478dedf..279f468529c33 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -485,7 +485,6 @@ import { isCallLikeOrFunctionLikeExpression, isCallOrNewExpression, isCallSignatureDeclaration, - isCaseClause, isCatchClause, isCatchClauseVariableDeclaration, isCatchClauseVariableDeclarationOrBindingElement, @@ -1011,7 +1010,6 @@ import { StructuredType, SubstitutionType, SuperCall, - SuperExpression, SwitchStatement, Symbol, SymbolAccessibility, @@ -1561,14 +1559,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { onSuccessfullyResolvedSymbol, }); - type TypeParameterReference = - | Identifier - | PropertyAccessExpression - | ElementAccessExpression - | SuperExpression - | ThisExpression; - type TypeParameterToReference = Map>; - var typeParameterReferencesCache = new Map(); var resolveNameForSymbolSuggestion = createNameResolver({ compilerOptions, requireSymbol, @@ -45483,15 +45473,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return; } const allTypeParameters = appendTypeParameters(getOuterTypeParameters(container, /*includeThisTypes*/ false), getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - const unionTypeParameters = allTypeParameters?.filter(tp => { - const constraint = getConstraintOfTypeParameter(tp); - return !!((constraint?.flags ?? 0) & TypeFlags.Union); - }); - const narrowableTypeParameters = unionTypeParameters - && filterNarrowableTypeParameters(container, unionTypeParameters); + const narrowableTypeParameters = allTypeParameters && getNarrowableTypeParameters(allTypeParameters); if ( !narrowableTypeParameters || + !narrowableTypeParameters.length || !isNarrowableReturnType(narrowableTypeParameters.map(trio => trio[0]), unwrappedReturnType) ) { checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); @@ -45582,267 +45568,78 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkReturnStatementExpression(container, returnType, node, expr.whenFalse); } - // Narrowable type parameters are type parameters that are the type of a single reference in the function scope - // whose type could possibly narrowed. - // For instance, `T` is a narrowable type parameter in the function below because it is the type of reference `x`, - // reference `x` appears in the control flow graph for the function, and therefore `x`'s type could be narrowed, - // and `T` is not the type of any other such references in the scope of the function. - // ` - // function f(x: T, y: U): ... { - // if (typeof x === ...) { return ...; } - // } - // ` - function filterNarrowableTypeParameters(container: SignatureDeclaration, typeParameters: TypeParameter[]): [TypeParameter, Symbol, TypeParameterReference][] | undefined { - const narrowableTypeParameterReferences = collectTypeParameterReferences(container); - const queryParameters: [TypeParameter, Symbol, TypeParameterReference][] = []; - for (const typeParam of typeParameters) { - const symbolMap = narrowableTypeParameterReferences.get(typeParam); - if (!symbolMap) continue; - if (symbolMap.size === 1) { - const [symbol, reference] = firstIterator(symbolMap.entries()); - queryParameters.push([typeParam, symbol, reference]); - } - } - return queryParameters.length ? queryParameters : undefined; - } - - function collectTypeParameterReferences(container: SignatureDeclaration): TypeParameterToReference { - const references: TypeParameterToReference = new Map(); - if (!typeParameterReferencesCache.has(container)) { - typeParameterReferencesCache.set(container, references); - const flowNodes = collectReturnStatementFlowNodes(container); - visitFlowNodes(flowNodes, getNodeFromFlowNode); - } - return typeParameterReferencesCache.get(container)!; - - // Get the node from the flow node that could have narrowable references. - function getNodeFromFlowNode(flow: FlowNode) { - const flags = flow.flags; - // Based on `getTypeAtFlowX` functions. - let node; - if (flags & FlowFlags.Assignment) { - node = (flow as FlowAssignment).node; - } - else if (flags & FlowFlags.Call) { - node = (flow as FlowCall).node; - } - else if (flags & FlowFlags.Condition) { - node = (flow as FlowCondition).node; - } - else if (flags & FlowFlags.SwitchClause) { - node = (flow as FlowSwitchClause).node.switchStatement.expression; - // We have: - // `switch (true) { - // case expr1: ... - // case expr2: ... - // }` - // so we need to also gather narrowing references from the case expressions. - if (skipParentheses(node).kind === SyntaxKind.TrueKeyword) { - const switchStmt = node.parent as SwitchStatement; - for (const clause of switchStmt.caseBlock.clauses) { - if (isCaseClause(clause)) { - getReferencesFromNode(clause.expression); + // Narrowable type parameters are type parameters that: + // (1) have a union type constraint; + // (2) are used as the type of a single parameter in the function, and nothing else + function getNarrowableTypeParameters(candidates: TypeParameter[]): [TypeParameter, Symbol, Identifier][] { + const narrowableParams: [TypeParameter, Symbol, Identifier][] = []; + for (const typeParam of candidates) { + const constraint = getConstraintOfTypeParameter(typeParam); + if (!constraint || !(constraint.flags & TypeFlags.Union)) continue; + if (typeParam.symbol && typeParam.symbol.declarations && typeParam.symbol.declarations.length === 1) { + const container = typeParam.symbol.declarations[0].parent; + if (!isFunctionLike(container)) continue; + let reference: Identifier | undefined; + let hasInvalidReference = false; + for (const paramDecl of container.parameters) { + const typeNode = paramDecl.type; + if (!typeNode) { + // Parameter's type could be inferred to a type that references the type parameter. + hasInvalidReference = true; + break; + } + if (isTypeParameterReferenced(typeParam, typeNode)) { + let candidateReference; + if (isTypeReferenceNode(typeNode) && + isReferenceToTypeParameter(typeParam, typeNode) && + (candidateReference = getValidParameterReference(paramDecl, constraint))) { + if (reference) { + hasInvalidReference = true; + break; + } + reference = candidateReference; + continue; } + hasInvalidReference = true; + break; } } - } - else if (flags & FlowFlags.ArrayMutation) { - // `node` is either `arr.push(expr2)` (or other array method calls), - // or `arr[expr2] = expr3`, and in both we might narrow `arr`. - const callNode = (flow as FlowArrayMutation).node; - node = callNode.kind === SyntaxKind.CallExpression ? - (callNode.expression as PropertyAccessExpression).expression : - (callNode.left as ElementAccessExpression).expression; - } - if (node) getReferencesFromNode(node); - } - - // Collect narrowable references from the nodes associated to a flow node. - // A narrowable reference here means an expression whose type may be narrowed during - // control flow analysis, and whose unnarrowed type could be a type parameter. - function getReferencesFromNode(node: Node, inlineLevel = 0): void { - switch (node.kind) { - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.NonNullExpression: - getReferencesFromNode( - (node as NonNullExpression | ParenthesizedExpression).expression, - inlineLevel, - ); - return; - case SyntaxKind.BinaryExpression: - getReferencesFromNode((node as BinaryExpression).left, inlineLevel); - getReferencesFromNode((node as BinaryExpression).right, inlineLevel); - return; - case SyntaxKind.CallExpression: - let callAccess; - // `node` is `expr.hasOwnExpression(prop)`, so we create a synthetic `expr.prop` reference for narrowing. - if ( - isPropertyAccessExpression(callAccess = (node as CallExpression).expression) - && isIdentifier(callAccess.name) - && callAccess.name.escapedText === "hasOwnProperty" - && (node as CallExpression).arguments.length === 1 - && isStringLiteralLike((node as CallExpression).arguments[0]) - ) { - const propName = (node as CallExpression).arguments[0] as StringLiteralLike; - const synthPropertyAccess = canUsePropertyAccess(propName.text, languageVersion) ? - factory.createPropertyAccessExpression(callAccess.expression, propName.text) : - factory.createElementAccessExpression(callAccess.expression, propName); - setParent(synthPropertyAccess, node.parent); - addReference(synthPropertyAccess, inlineLevel); - } - getReferencesFromNode((node as CallExpression).expression, inlineLevel); - (node as CallExpression).arguments.forEach(arg => getReferencesFromNode(arg, inlineLevel)); - return; - case SyntaxKind.PrefixUnaryExpression: - case SyntaxKind.PostfixUnaryExpression: - getReferencesFromNode((node as PrefixUnaryExpression | PostfixUnaryExpression).operand, inlineLevel); - return; - case SyntaxKind.TypeOfExpression: - getReferencesFromNode((node as TypeOfExpression).expression); - return; - case SyntaxKind.ThisKeyword: - addReference(node as ThisExpression, inlineLevel); - return; - case SyntaxKind.Identifier: - addReference(node as Identifier, inlineLevel); - return; - case SyntaxKind.SuperKeyword: - addReference(node as SuperExpression, inlineLevel); - return; - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - getReferencesFromNode( - (node as PropertyAccessExpression | ElementAccessExpression).expression, - inlineLevel, - ); - addReference(node as PropertyAccessExpression | ElementAccessExpression, inlineLevel); - return; - } - } - - function addReference(reference: TypeParameterReference, inlineLevel: number): void { - const symbol = getSymbolForExpression(reference); - const type = symbol && getTypeOfSymbol(symbol); - if (!type || isErrorType(type)) return; - // Check if we have an optional parameter, an optional property, or an optional tuple element. - // In this case, its type can be `T | undefined`, - // and if `T` allows for the undefined type, then we can still narrow `T`. - if ( - ((symbol.valueDeclaration && isOptionalDeclaration(symbol.valueDeclaration)) - || isOptionalTupleElementSymbol(symbol)) - && type.flags & TypeFlags.Union - && ((type as UnionType).types[0] === undefinedType - || exactOptionalPropertyTypes && (type as UnionType).types[0] === missingType) - && (type as UnionType).types[1].flags & TypeFlags.TypeParameter - ) { - const typeParam = (type as UnionType).types[1] as TypeParameter; - const constraint = getConstraintOfTypeParameter(typeParam); - if (!constraint || constraint.flags & TypeFlags.Unknown || containsUndefinedType(constraint)) { - add(typeParam, symbol, reference); - } - } - else if (type.flags & TypeFlags.TypeParameter) { - add(type as TypeParameter, symbol, reference); - } - else if (inlineLevel < 5) { - const inlineExpression = getNarrowableInlineExpression(symbol); - if (inlineExpression) { - getReferencesFromNode(inlineExpression, inlineLevel + 1); + if (!hasInvalidReference && reference) { + const symbol = getResolvedSymbol(reference); + if (symbol !== unknownSymbol) narrowableParams.push([typeParam, symbol, reference]); } } - - function add(type: TypeParameter, symbol: Symbol, reference: TypeParameterReference) { - if (!references.has(type)) { - references.set(type, new Map()); - } - references.get(type)!.set(symbol, reference); - } } - } - function collectReturnStatementFlowNodes(container: SignatureDeclaration): FlowNode[] { - const flowNodes: FlowNode[] = []; - visit(container); - if (isFunctionLikeDeclaration(container) && container.endFlowNode) { - flowNodes.push(container.endFlowNode); + return narrowableParams; + // For a parameter of declared type `T` to be a valid reference for narrowing, it must satisfy: + // - the parameter name is an identifier + // - if the parameter is optional, then `T`'s constraint must allow for undefined + function getValidParameterReference(paramDecl: ParameterDeclaration, constraint: Type): Identifier | undefined { + if (!isIdentifier(paramDecl.name)) return; + if (paramDecl.questionToken && !containsUndefinedType(constraint)) return; + return paramDecl.name; } - return flowNodes; - - function visit(node: Node) { - if (node.kind === SyntaxKind.ReturnStatement) { - const nodeContainer = getContainingFunctionOrClassStaticBlock(node as ReturnStatement); - if (nodeContainer === container) { - visitConditionalReturnExpression((node as ReturnStatement).expression); - const flowNode = (node as ReturnStatement).flowNode; - if (flowNode) { - flowNodes.push(flowNode); - } - } - return; - } - forEachChild(node, visit); + function isReferenceToTypeParameter(typeParam: TypeParameter, node: TypeReferenceNode) { + return getTypeFromTypeReference(node) === typeParam; } - function visitConditionalReturnExpression(node: Expression | undefined): void { - if (!node) return; - node = skipParentheses(node); - if (isConditionalExpression(node)) { - if (node.flowNodeWhenTrue) flowNodes.push(node.flowNodeWhenTrue); - if (node.flowNodeWhenFalse) flowNodes.push(node.flowNodeWhenFalse); - visitConditionalReturnExpression(node.whenTrue); - visitConditionalReturnExpression(node.whenFalse); - } - } - } - - type HasFlowAntecedent = - | FlowAssignment - | FlowCondition - | FlowSwitchClause - | FlowArrayMutation - | FlowCall - | FlowReduceLabel; - - function hasFlowAntecedent(flow: FlowNode): flow is HasFlowAntecedent { - return !!(flow.flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.SwitchClause | FlowFlags.ArrayMutation | FlowFlags.Call | FlowFlags.ReduceLabel)); - } + function isTypeParameterReferenced(typeParam: TypeParameter, node: TypeNode) { + return isReferenced(node); - function visitFlowNodes(flowNodes: FlowNode[], visit: (n: FlowNode) => void): void { - const visited = new Set(); - flowNodes.forEach(visitFlowNode); - function visitFlowNode(flow: FlowNode): void { - const id = getFlowNodeId(flow); - if (visited.has(id)) return; - visited.add(id); - - visit(flow); - const flags = flow.flags; - if (flags & FlowFlags.Label) { - return (flow as FlowLabel).antecedent?.forEach(visitFlowNode); - } - if (flags & FlowFlags.ReduceLabel) { - (flow as FlowReduceLabel).node.antecedents.forEach(visitFlowNode); - // >> TODO: also visit flow.target? - } - if (hasFlowAntecedent(flow)) { - return visitFlowNode((flow as HasFlowAntecedent).antecedent); + function isReferenced(node: Node): boolean { + if (isTypeReferenceNode(node)) { + return isReferenceToTypeParameter(typeParam, node); + } + if (isTypeQueryNode(node)) { + return isTypeParameterPossiblyReferenced(typeParam, node); + } + return !!forEachChild(node, isReferenced); } } } - function isOptionalTupleElementSymbol(symbol: Symbol): boolean { - if ( - isTransientSymbol(symbol) - && symbol.links.target - && isTransientSymbol(symbol.links.target) - && symbol.links.target.links.tupleLabelDeclaration - ) { - return !!symbol.links.target.links.tupleLabelDeclaration?.questionToken; - } - return false; - } - // A valid conditional type will have the following shape: // `T extends A ? TrueBranch : FalseBranch`, such that: // (0) The conditional type's check type is a narrowable type parameter; diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index 91a751b66ef3a..c0b297f03e3de 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -37,11 +37,14 @@ dependentReturnType1.ts(412,9): error TS2322: Type 'true' is not assignable to t dependentReturnType1.ts(414,5): error TS2322: Type '""' is not assignable to type 'T extends [infer R] ? R : T extends number ? boolean : never'. dependentReturnType1.ts(439,15): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. dependentReturnType1.ts(441,11): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. -dependentReturnType1.ts(468,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. -dependentReturnType1.ts(504,5): error TS2322: Type 'number' is not assignable to type 'never'. +dependentReturnType1.ts(470,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. +dependentReturnType1.ts(472,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. +dependentReturnType1.ts(474,13): error TS2322: Type 'T' is not assignable to type 'ConditionalReturnType'. +dependentReturnType1.ts(488,9): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. +dependentReturnType1.ts(514,5): error TS2322: Type 'number' is not assignable to type 'never'. -==== dependentReturnType1.ts (36 errors) ==== +==== dependentReturnType1.ts (39 errors) ==== interface A { 1: number; 2: string; @@ -486,9 +489,9 @@ dependentReturnType1.ts(504,5): error TS2322: Type 'number' is not assignable to // If the narrowing reference is out of scope, we simply won't narrow its type declare let someX: boolean; - function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : never { + function scope2(a: T): T extends true ? 1 : T extends false ? 2 : never { if ((true)) { - const someX = opts.a; + const someX = a; if (someX) { // We narrow `someX` and the return type here return 1; } @@ -567,6 +570,7 @@ dependentReturnType1.ts(504,5): error TS2322: Type 'number' is not assignable to type ConditionalReturnType | undefined> = EOp extends Operation ? R : EOp extends undefined ? T | R : never; + class ConditionalOperation< T, R, @@ -580,36 +584,51 @@ dependentReturnType1.ts(504,5): error TS2322: Type 'number' is not assignable to super(); } + // We won't try to narrow the return type because `T` is declared on the class and we don't analyze this case. perform(t: T): ConditionalReturnType { if (this.predicate(t)) { return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it ~~~~~~ !!! error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. } else if (typeof this.elseOp !== "undefined") { - return this.elseOp.perform(t); // Ok + return this.elseOp.perform(t); // Would be ok + ~~~~~~ +!!! error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. } else { - return t; // Ok + return t; // Would be ok + ~~~~~~ +!!! error TS2322: Type 'T' is not assignable to type 'ConditionalReturnType'. } } } - // Optional tuple element - function tupl(x: [string, some?: T]): - T extends true ? 1 : T extends false | undefined ? 2 : never { - if (x[1]) { - return 1; + // Like the version above, we will not attempt to narrow because there's more than one reference to `T`, + // because `T` shows up in the type of `predicate`. + function perform | undefined>( + t: T, + predicate: (value: T) => boolean, + thenOp: Operation, + elseOp?: EOp, + ): ConditionalReturnType { + if (predicate(t)) { + return thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it + ~~~~~~ +!!! error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. + } else if (elseOp !== undefined) { + return elseOp.perform(t); // Would be ok + } else { + return t; // Would be ok } - return 2; } // Return conditional expressions with parentheses - function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : never { - return (opts.x ? (1) : 2); + function returnStuff1(x: T ): T extends true ? 1 : T extends false ? 2 : never { + return (x ? (1) : 2); } - function returnStuff2(opts: { x: T }): + function returnStuff2(x: T ): T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never { - return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); + return (typeof x === "string" ? 0 : (x === 1 ? ("one") : "two")); } // If the conditional type's input is `never`, then it resolves to `never`: diff --git a/tests/baselines/reference/dependentReturnType1.symbols b/tests/baselines/reference/dependentReturnType1.symbols index 7e91c51b97b9e..958dafbc6129f 100644 --- a/tests/baselines/reference/dependentReturnType1.symbols +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -1015,21 +1015,18 @@ function noShadowing(x: T): T extends 1 ? number : T extends 2 declare let someX: boolean; >someX : Symbol(someX, Decl(dependentReturnType1.ts, 382, 11)) -function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : never { +function scope2(a: T): T extends true ? 1 : T extends false ? 2 : never { >scope2 : Symbol(scope2, Decl(dependentReturnType1.ts, 382, 27)) >T : Symbol(T, Decl(dependentReturnType1.ts, 383, 16)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 383, 35)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 383, 42)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 383, 35)) >T : Symbol(T, Decl(dependentReturnType1.ts, 383, 16)) >T : Symbol(T, Decl(dependentReturnType1.ts, 383, 16)) >T : Symbol(T, Decl(dependentReturnType1.ts, 383, 16)) if ((true)) { - const someX = opts.a; + const someX = a; >someX : Symbol(someX, Decl(dependentReturnType1.ts, 385, 13)) ->opts.a : Symbol(a, Decl(dependentReturnType1.ts, 383, 42)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 383, 35)) ->a : Symbol(a, Decl(dependentReturnType1.ts, 383, 42)) +>a : Symbol(a, Decl(dependentReturnType1.ts, 383, 35)) if (someX) { // We narrow `someX` and the return type here >someX : Symbol(someX, Decl(dependentReturnType1.ts, 385, 13)) @@ -1186,166 +1183,202 @@ type ConditionalReturnType | undefined> = >T : Symbol(T, Decl(dependentReturnType1.ts, 449, 27)) >R : Symbol(R, Decl(dependentReturnType1.ts, 449, 29)) + class ConditionalOperation< >ConditionalOperation : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) T, ->T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 453, 27)) R, ->R : Symbol(R, Decl(dependentReturnType1.ts, 453, 6)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 454, 6)) EOp extends Operation | undefined, ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 454, 6)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 455, 6)) >Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 453, 6)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 453, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 454, 6)) > extends Operation> { >Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 453, 27)) >ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 447, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 453, 6)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 454, 6)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 453, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 454, 6)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 455, 6)) constructor( private predicate: (value: T) => boolean, ->predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 457, 16)) ->value : Symbol(value, Decl(dependentReturnType1.ts, 458, 28)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) +>predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 458, 16)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 459, 28)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 453, 27)) private thenOp: Operation, ->thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 458, 49)) +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 459, 49)) >Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 453, 6)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 453, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 454, 6)) private elseOp?: EOp, ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 459, 40)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 454, 6)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 460, 40)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 455, 6)) ) { super(); >super : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) } + // We won't try to narrow the return type because `T` is declared on the class and we don't analyze this case. perform(t: T): ConditionalReturnType { ->perform : Symbol(ConditionalOperation.perform, Decl(dependentReturnType1.ts, 463, 5)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 465, 12)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) +>perform : Symbol(ConditionalOperation.perform, Decl(dependentReturnType1.ts, 464, 5)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 467, 12)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 453, 27)) >ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 447, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 452, 27)) ->R : Symbol(R, Decl(dependentReturnType1.ts, 453, 6)) ->EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 454, 6)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 453, 27)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 454, 6)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 455, 6)) if (this.predicate(t)) { ->this.predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 457, 16)) +>this.predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 458, 16)) >this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) ->predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 457, 16)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 465, 12)) +>predicate : Symbol(ConditionalOperation.predicate, Decl(dependentReturnType1.ts, 458, 16)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 467, 12)) return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it >this.thenOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) ->this.thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 458, 49)) +>this.thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 459, 49)) >this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) ->thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 458, 49)) +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 459, 49)) >perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 465, 12)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 467, 12)) } else if (typeof this.elseOp !== "undefined") { ->this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 459, 40)) +>this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 460, 40)) >this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 459, 40)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 460, 40)) - return this.elseOp.perform(t); // Ok + return this.elseOp.perform(t); // Would be ok >this.elseOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) ->this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 459, 40)) +>this.elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 460, 40)) >this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) ->elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 459, 40)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 460, 40)) >perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) ->t : Symbol(t, Decl(dependentReturnType1.ts, 465, 12)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 467, 12)) } else { - return t; // Ok ->t : Symbol(t, Decl(dependentReturnType1.ts, 465, 12)) + return t; // Would be ok +>t : Symbol(t, Decl(dependentReturnType1.ts, 467, 12)) } } } -// Optional tuple element -function tupl(x: [string, some?: T]): ->tupl : Symbol(tupl, Decl(dependentReturnType1.ts, 474, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 477, 14)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 477, 50)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 477, 14)) +// Like the version above, we will not attempt to narrow because there's more than one reference to `T`, +// because `T` shows up in the type of `predicate`. +function perform | undefined>( +>perform : Symbol(perform, Decl(dependentReturnType1.ts, 476, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 480, 17)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 480, 19)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 480, 22)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 480, 17)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 480, 19)) - T extends true ? 1 : T extends false | undefined ? 2 : never { ->T : Symbol(T, Decl(dependentReturnType1.ts, 477, 14)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 477, 14)) + t: T, +>t : Symbol(t, Decl(dependentReturnType1.ts, 480, 64)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 480, 17)) - if (x[1]) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 477, 50)) ->1 : Symbol(1) + predicate: (value: T) => boolean, +>predicate : Symbol(predicate, Decl(dependentReturnType1.ts, 481, 9)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 482, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 480, 17)) - return 1; + thenOp: Operation, +>thenOp : Symbol(thenOp, Decl(dependentReturnType1.ts, 482, 37)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 480, 17)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 480, 19)) + + elseOp?: EOp, +>elseOp : Symbol(elseOp, Decl(dependentReturnType1.ts, 483, 28)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 480, 22)) + + ): ConditionalReturnType { +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 447, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 480, 17)) +>R : Symbol(R, Decl(dependentReturnType1.ts, 480, 19)) +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 480, 22)) + + if (predicate(t)) { +>predicate : Symbol(predicate, Decl(dependentReturnType1.ts, 481, 9)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 480, 64)) + + return thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it +>thenOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) +>thenOp : Symbol(thenOp, Decl(dependentReturnType1.ts, 482, 37)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 480, 64)) + + } else if (elseOp !== undefined) { +>elseOp : Symbol(elseOp, Decl(dependentReturnType1.ts, 483, 28)) +>undefined : Symbol(undefined) + + return elseOp.perform(t); // Would be ok +>elseOp.perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) +>elseOp : Symbol(elseOp, Decl(dependentReturnType1.ts, 483, 28)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 480, 64)) + + } else { + return t; // Would be ok +>t : Symbol(t, Decl(dependentReturnType1.ts, 480, 64)) } - return 2; } // Return conditional expressions with parentheses -function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : never { ->returnStuff1 : Symbol(returnStuff1, Decl(dependentReturnType1.ts, 483, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 486, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 486, 41)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 486, 48)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 486, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 486, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 486, 22)) - - return (opts.x ? (1) : 2); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 486, 48)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 486, 41)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 486, 48)) +function returnStuff1(x: T ): T extends true ? 1 : T extends false ? 2 : never { +>returnStuff1 : Symbol(returnStuff1, Decl(dependentReturnType1.ts, 493, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 496, 22)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 496, 41)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 496, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 496, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 496, 22)) + + return (x ? (1) : 2); +>x : Symbol(x, Decl(dependentReturnType1.ts, 496, 41)) } -function returnStuff2(opts: { x: T }): ->returnStuff2 : Symbol(returnStuff2, Decl(dependentReturnType1.ts, 488, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 490, 22)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 490, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 490, 52)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 490, 22)) +function returnStuff2(x: T ): +>returnStuff2 : Symbol(returnStuff2, Decl(dependentReturnType1.ts, 498, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 500, 22)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 500, 45)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 500, 22)) T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never { ->T : Symbol(T, Decl(dependentReturnType1.ts, 490, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 490, 22)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 490, 22)) - - return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 490, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 490, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 490, 52)) ->opts.x : Symbol(x, Decl(dependentReturnType1.ts, 490, 52)) ->opts : Symbol(opts, Decl(dependentReturnType1.ts, 490, 45)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 490, 52)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 500, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 500, 22)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 500, 22)) + + return (typeof x === "string" ? 0 : (x === 1 ? ("one") : "two")); +>x : Symbol(x, Decl(dependentReturnType1.ts, 500, 45)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 500, 45)) } // If the conditional type's input is `never`, then it resolves to `never`: function neverOk(x: T): T extends true ? 1 : T extends false ? 2 : never { ->neverOk : Symbol(neverOk, Decl(dependentReturnType1.ts, 493, 1)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 496, 17)) ->x : Symbol(x, Decl(dependentReturnType1.ts, 496, 36)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 496, 17)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 496, 17)) ->T : Symbol(T, Decl(dependentReturnType1.ts, 496, 17)) +>neverOk : Symbol(neverOk, Decl(dependentReturnType1.ts, 503, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 506, 17)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 506, 36)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 506, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 506, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 506, 17)) if (x === true) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 496, 36)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 506, 36)) return 1; } if (x === false) { ->x : Symbol(x, Decl(dependentReturnType1.ts, 496, 36)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 506, 36)) return 2; } diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types index 641643029c9bc..96265db6b57be 100644 --- a/tests/baselines/reference/dependentReturnType1.types +++ b/tests/baselines/reference/dependentReturnType1.types @@ -1463,11 +1463,9 @@ declare let someX: boolean; >someX : boolean > : ^^^^^^^ -function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : never { ->scope2 : (opts: { a: T; }) => T extends true ? 1 : T extends false ? 2 : never -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->opts : { a: T; } -> : ^^^^^ ^^^ +function scope2(a: T): T extends true ? 1 : T extends false ? 2 : never { +>scope2 : (a: T) => T extends true ? 1 : T extends false ? 2 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >a : T > : ^ >true : true @@ -1481,13 +1479,9 @@ function scope2(opts: { a: T }): T extends true ? 1 : T exten >true : true > : ^^^^ - const someX = opts.a; + const someX = a; >someX : T > : ^ ->opts.a : T -> : ^ ->opts : { a: T; } -> : ^^^^^ ^^^ >a : T > : ^ @@ -1727,6 +1721,7 @@ type ConditionalReturnType | undefined> = EOp extends Operation ? R : EOp extends undefined ? T | R : never; + class ConditionalOperation< >ConditionalOperation : ConditionalOperation > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1761,6 +1756,7 @@ class ConditionalOperation< > : ^^^^^^^^^^^^^^^^ } + // We won't try to narrow the return type because `T` is declared on the class and we don't analyze this case. perform(t: T): ConditionalReturnType { >perform : (t: T) => ConditionalReturnType > : ^ ^^ ^^^^^ @@ -1809,7 +1805,7 @@ class ConditionalOperation< >"undefined" : "undefined" > : ^^^^^^^^^^^ - return this.elseOp.perform(t); // Ok + return this.elseOp.perform(t); // Would be ok >this.elseOp.perform(t) : R > : ^ >this.elseOp.perform : (t: T) => R @@ -1826,53 +1822,89 @@ class ConditionalOperation< > : ^ } else { - return t; // Ok + return t; // Would be ok >t : T > : ^ } } } -// Optional tuple element -function tupl(x: [string, some?: T]): ->tupl : (x: [string, some?: T]) => T extends true ? 1 : T extends false | undefined ? 2 : never -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->true : true -> : ^^^^ ->false : false -> : ^^^^^ ->x : [string, some?: T | undefined] -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// Like the version above, we will not attempt to narrow because there's more than one reference to `T`, +// because `T` shows up in the type of `predicate`. +function perform | undefined>( +>perform : | undefined>(t: T, predicate: (value: T) => boolean, thenOp: Operation, elseOp?: EOp) => ConditionalReturnType +> : ^ ^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^ ^^^^^ - T extends true ? 1 : T extends false | undefined ? 2 : never { ->true : true -> : ^^^^ ->false : false -> : ^^^^^ + t: T, +>t : T +> : ^ - if (x[1]) { ->x[1] : T | undefined -> : ^^^^^^^^^^^^^ ->x : [string, some?: T | undefined] -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->1 : 1 + predicate: (value: T) => boolean, +>predicate : (value: T) => boolean +> : ^ ^^ ^^^^^ +>value : T +> : ^ + + thenOp: Operation, +>thenOp : Operation +> : ^^^^^^^^^^^^^^^ + + elseOp?: EOp, +>elseOp : EOp | undefined +> : ^^^^^^^^^^^^^^^ + + ): ConditionalReturnType { + if (predicate(t)) { +>predicate(t) : boolean +> : ^^^^^^^ +>predicate : (value: T) => boolean +> : ^ ^^ ^^^^^ +>t : T > : ^ - return 1; ->1 : 1 + return thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it +>thenOp.perform(t) : R +> : ^ +>thenOp.perform : (t: T) => R +> : ^ ^^^^^^^^^ +>thenOp : Operation +> : ^^^^^^^^^^^^^^^ +>perform : (t: T) => R +> : ^ ^^^^^^^^^ +>t : T > : ^ - } - return 2; ->2 : 2 + + } else if (elseOp !== undefined) { +>elseOp !== undefined : boolean +> : ^^^^^^^ +>elseOp : EOp | undefined +> : ^^^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + + return elseOp.perform(t); // Would be ok +>elseOp.perform(t) : R +> : ^ +>elseOp.perform : (t: T) => R +> : ^ ^^^^^^^^^ +>elseOp : Operation +> : ^^^^^^^^^^^^^^^ +>perform : (t: T) => R +> : ^ ^^^^^^^^^ +>t : T +> : ^ + + } else { + return t; // Would be ok +>t : T > : ^ + } } // Return conditional expressions with parentheses -function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : never { ->returnStuff1 : (opts: { x: T; }) => T extends true ? 1 : T extends false ? 2 : never -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->opts : { x: T; } -> : ^^^^^ ^^^ +function returnStuff1(x: T ): T extends true ? 1 : T extends false ? 2 : never { +>returnStuff1 : (x: T) => T extends true ? 1 : T extends false ? 2 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T > : ^ >true : true @@ -1880,15 +1912,11 @@ function returnStuff1(opts: { x: T }): T extends true ? 1 : T >false : false > : ^^^^^ - return (opts.x ? (1) : 2); ->(opts.x ? (1) : 2) : 1 | 2 -> : ^^^^^ ->opts.x ? (1) : 2 : 1 | 2 -> : ^^^^^ ->opts.x : T -> : ^ ->opts : { x: T; } -> : ^^^^^ ^^^ + return (x ? (1) : 2); +>(x ? (1) : 2) : 1 | 2 +> : ^^^^^ +>x ? (1) : 2 : 1 | 2 +> : ^^^^^ >x : T > : ^ >(1) : 1 @@ -1899,44 +1927,34 @@ function returnStuff1(opts: { x: T }): T extends true ? 1 : T > : ^ } -function returnStuff2(opts: { x: T }): ->returnStuff2 : (opts: { x: T; }) => T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->opts : { x: T; } -> : ^^^^^ ^^^ +function returnStuff2(x: T ): +>returnStuff2 : (x: T) => T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ >x : T > : ^ T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never { - return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); ->(typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")) : 0 | "one" | "two" -> : ^^^^^^^^^^^^^^^^^ ->typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two") : 0 | "one" | "two" -> : ^^^^^^^^^^^^^^^^^ ->typeof opts.x === "string" : boolean -> : ^^^^^^^ ->typeof opts.x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->opts.x : T -> : ^ ->opts : { x: T; } -> : ^^^^^ ^^^ + return (typeof x === "string" ? 0 : (x === 1 ? ("one") : "two")); +>(typeof x === "string" ? 0 : (x === 1 ? ("one") : "two")) : 0 | "one" | "two" +> : ^^^^^^^^^^^^^^^^^ +>typeof x === "string" ? 0 : (x === 1 ? ("one") : "two") : 0 | "one" | "two" +> : ^^^^^^^^^^^^^^^^^ +>typeof x === "string" : boolean +> : ^^^^^^^ +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >x : T > : ^ >"string" : "string" > : ^^^^^^^^ >0 : 0 > : ^ ->(opts.x === 1 ? ("one") : "two") : "one" | "two" -> : ^^^^^^^^^^^^^ ->opts.x === 1 ? ("one") : "two" : "one" | "two" -> : ^^^^^^^^^^^^^ ->opts.x === 1 : boolean -> : ^^^^^^^ ->opts.x : T -> : ^ ->opts : { x: T; } -> : ^^^^^ ^^^ +>(x === 1 ? ("one") : "two") : "one" | "two" +> : ^^^^^^^^^^^^^ +>x === 1 ? ("one") : "two" : "one" | "two" +> : ^^^^^^^^^^^^^ +>x === 1 : boolean +> : ^^^^^^^ >x : T > : ^ >1 : 1 diff --git a/tests/baselines/reference/dependentReturnType4.errors.txt b/tests/baselines/reference/dependentReturnType4.errors.txt index bbee8dd400acd..5f06601f9fe98 100644 --- a/tests/baselines/reference/dependentReturnType4.errors.txt +++ b/tests/baselines/reference/dependentReturnType4.errors.txt @@ -1,31 +1,15 @@ -dependentReturnType4.ts(48,15): error TS2322: Type 'string | number' is not assignable to type 'string'. - Type 'number' is not assignable to type 'string'. -dependentReturnType4.ts(49,9): error TS2322: Type 'number' is not assignable to type 'T extends number ? string : T extends string ? number : never'. -dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to type 'T extends number ? string : T extends string ? number : never'. +dependentReturnType4.ts(28,9): error TS2322: Type '0' is not assignable to type 'T extends undefined ? 0 : T extends string ? 1 : never'. +dependentReturnType4.ts(30,5): error TS2322: Type '1' is not assignable to type 'T extends undefined ? 0 : T extends string ? 1 : never'. -==== dependentReturnType4.ts (3 errors) ==== - // Test narrowing through `hasOwnProperty` calls +==== dependentReturnType4.ts (2 errors) ==== declare const rand: { a?: never }; type Missing = typeof rand.a; - declare function takesString(x: string): void; - function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : never { - if (obj.hasOwnProperty("a")) { - takesString(obj.a); - return 1; - } - return 2; - } - function foo(opts: { x?: T }): - T extends undefined ? 0 : T extends string ? 1 : never { - if (opts.x === undefined) { - return 0; - } - return 1; - } + // Detection of valid optional parameter references - function bar(x?: T ): + // Ok, will narrow return type + function bar1(x?: T): T extends Missing ? 0 : T extends string ? 1 : never { if (x === undefined) { return 0; @@ -33,63 +17,24 @@ dependentReturnType4.ts(51,5): error TS2322: Type 'string' is not assignable to return 1; } - // Aliased narrowing - function inlined(x: T): T extends number ? string : T extends string ? number : never { - const t = typeof x === "string"; - if (t) { - const y: string = x; - return 1; + // Ok, will narrow return type + function bar2(x?: T): + T extends undefined ? 0 : T extends string ? 1 : never { + if (x === undefined) { + return 0; } - return "one"; + return 1; } - // Don't narrow more than 5 levels of aliasing - function inlined6(x: T): T extends number ? string : T extends string ? number : never { - const t1 = typeof x === "string"; - const t2 = t1; - const t3 = t2; - const t4 = t3; - const t5 = t4; - const t6 = t5; - if (t6) { - const y: string = x; - ~ -!!! error TS2322: Type 'string | number' is not assignable to type 'string'. -!!! error TS2322: Type 'number' is not assignable to type 'string'. - return 1; + // Not ok, will not narrow return type + function bar3(x?: T): + T extends undefined ? 0 : T extends string ? 1 : never { + if (x === undefined) { + return 0; ~~~~~~ -!!! error TS2322: Type 'number' is not assignable to type 'T extends number ? string : T extends string ? number : never'. +!!! error TS2322: Type '0' is not assignable to type 'T extends undefined ? 0 : T extends string ? 1 : never'. } - return "one"; + return 1; ~~~~~~ -!!! error TS2322: Type 'string' is not assignable to type 'T extends number ? string : T extends string ? number : never'. - } - - type A = { kind: "a", a: number }; - type B = { kind: "b", b: string }; - type AOrB = A | B; - - function subexpression(x: T): T extends A ? number : T extends B ? string : never { - if (x.kind === "b") { - return "some str"; - } - return 0; - } - - function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : never { - switch (true) { - case x: - return 1; - } - return 0; - } - - // Don't raise errors when getting the narrowed type of synthesized nodes - type Ret = T extends string ? 1 : T extends number ? 2 : never; - function f(x: T): Ret { - let y!: T; - if (typeof y === "string") { - return 1; - } - return 2; +!!! error TS2322: Type '1' is not assignable to type 'T extends undefined ? 0 : T extends string ? 1 : never'. } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType4.symbols b/tests/baselines/reference/dependentReturnType4.symbols index 1e71eadc57aef..77b99a29dd972 100644 --- a/tests/baselines/reference/dependentReturnType4.symbols +++ b/tests/baselines/reference/dependentReturnType4.symbols @@ -1,62 +1,53 @@ //// [tests/cases/compiler/dependentReturnType4.ts] //// === dependentReturnType4.ts === -// Test narrowing through `hasOwnProperty` calls declare const rand: { a?: never }; ->rand : Symbol(rand, Decl(dependentReturnType4.ts, 1, 13)) ->a : Symbol(a, Decl(dependentReturnType4.ts, 1, 21)) +>rand : Symbol(rand, Decl(dependentReturnType4.ts, 0, 13)) +>a : Symbol(a, Decl(dependentReturnType4.ts, 0, 21)) type Missing = typeof rand.a; ->Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 1, 34)) ->rand.a : Symbol(a, Decl(dependentReturnType4.ts, 1, 21)) ->rand : Symbol(rand, Decl(dependentReturnType4.ts, 1, 13)) ->a : Symbol(a, Decl(dependentReturnType4.ts, 1, 21)) +>Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 0, 34)) +>rand.a : Symbol(a, Decl(dependentReturnType4.ts, 0, 21)) +>rand : Symbol(rand, Decl(dependentReturnType4.ts, 0, 13)) +>a : Symbol(a, Decl(dependentReturnType4.ts, 0, 21)) -declare function takesString(x: string): void; ->takesString : Symbol(takesString, Decl(dependentReturnType4.ts, 2, 29)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 3, 29)) +// Detection of valid optional parameter references -function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : never { ->hasOwnP : Symbol(hasOwnP, Decl(dependentReturnType4.ts, 3, 46)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) ->Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 1, 34)) ->obj : Symbol(obj, Decl(dependentReturnType4.ts, 4, 45)) ->a : Symbol(a, Decl(dependentReturnType4.ts, 4, 51)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 4, 17)) +// Ok, will narrow return type +function bar1(x?: T): +>bar1 : Symbol(bar1, Decl(dependentReturnType4.ts, 1, 29)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 6, 14)) +>Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 0, 34)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 6, 42)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 6, 14)) - if (obj.hasOwnProperty("a")) { ->obj.hasOwnProperty : Symbol(Object.hasOwnProperty, Decl(lib.es5.d.ts, --, --)) ->obj : Symbol(obj, Decl(dependentReturnType4.ts, 4, 45)) ->hasOwnProperty : Symbol(Object.hasOwnProperty, Decl(lib.es5.d.ts, --, --)) + T extends Missing ? 0 : T extends string ? 1 : never { +>T : Symbol(T, Decl(dependentReturnType4.ts, 6, 14)) +>Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 0, 34)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 6, 14)) - takesString(obj.a); ->takesString : Symbol(takesString, Decl(dependentReturnType4.ts, 2, 29)) ->obj.a : Symbol(a, Decl(dependentReturnType4.ts, 4, 51)) ->obj : Symbol(obj, Decl(dependentReturnType4.ts, 4, 45)) ->a : Symbol(a, Decl(dependentReturnType4.ts, 4, 51)) + if (x === undefined) { +>x : Symbol(x, Decl(dependentReturnType4.ts, 6, 42)) +>undefined : Symbol(undefined) - return 1; + return 0; } - return 2; + return 1; } -function foo(opts: { x?: T }): ->foo : Symbol(foo, Decl(dependentReturnType4.ts, 10, 1)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 12, 13)) ->opts : Symbol(opts, Decl(dependentReturnType4.ts, 12, 43)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 12, 50)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 12, 13)) +// Ok, will narrow return type +function bar2(x?: T): +>bar2 : Symbol(bar2, Decl(dependentReturnType4.ts, 12, 1)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 15, 14)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 15, 44)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 15, 14)) T extends undefined ? 0 : T extends string ? 1 : never { ->T : Symbol(T, Decl(dependentReturnType4.ts, 12, 13)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 12, 13)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 15, 14)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 15, 14)) - if (opts.x === undefined) { ->opts.x : Symbol(x, Decl(dependentReturnType4.ts, 12, 50)) ->opts : Symbol(opts, Decl(dependentReturnType4.ts, 12, 43)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 12, 50)) + if (x === undefined) { +>x : Symbol(x, Decl(dependentReturnType4.ts, 15, 44)) >undefined : Symbol(undefined) return 0; @@ -64,173 +55,22 @@ function foo(opts: { x?: T }): return 1; } -function bar(x?: T ): ->bar : Symbol(bar, Decl(dependentReturnType4.ts, 18, 1)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 20, 13)) ->Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 1, 34)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 20, 41)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 20, 13)) +// Not ok, will not narrow return type +function bar3(x?: T): +>bar3 : Symbol(bar3, Decl(dependentReturnType4.ts, 21, 1)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 24, 14)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 24, 32)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 24, 14)) - T extends Missing ? 0 : T extends string ? 1 : never { ->T : Symbol(T, Decl(dependentReturnType4.ts, 20, 13)) ->Missing : Symbol(Missing, Decl(dependentReturnType4.ts, 1, 34)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 20, 13)) + T extends undefined ? 0 : T extends string ? 1 : never { +>T : Symbol(T, Decl(dependentReturnType4.ts, 24, 14)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 24, 14)) if (x === undefined) { ->x : Symbol(x, Decl(dependentReturnType4.ts, 20, 41)) +>x : Symbol(x, Decl(dependentReturnType4.ts, 24, 32)) >undefined : Symbol(undefined) return 0; } return 1; } - -// Aliased narrowing -function inlined(x: T): T extends number ? string : T extends string ? number : never { ->inlined : Symbol(inlined, Decl(dependentReturnType4.ts, 26, 1)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 29, 17)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 29, 44)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 29, 17)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 29, 17)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 29, 17)) - - const t = typeof x === "string"; ->t : Symbol(t, Decl(dependentReturnType4.ts, 30, 9)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 29, 44)) - - if (t) { ->t : Symbol(t, Decl(dependentReturnType4.ts, 30, 9)) - - const y: string = x; ->y : Symbol(y, Decl(dependentReturnType4.ts, 32, 13)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 29, 44)) - - return 1; - } - return "one"; -} - -// Don't narrow more than 5 levels of aliasing -function inlined6(x: T): T extends number ? string : T extends string ? number : never { ->inlined6 : Symbol(inlined6, Decl(dependentReturnType4.ts, 36, 1)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 39, 18)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 39, 45)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 39, 18)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 39, 18)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 39, 18)) - - const t1 = typeof x === "string"; ->t1 : Symbol(t1, Decl(dependentReturnType4.ts, 40, 9)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 39, 45)) - - const t2 = t1; ->t2 : Symbol(t2, Decl(dependentReturnType4.ts, 41, 9)) ->t1 : Symbol(t1, Decl(dependentReturnType4.ts, 40, 9)) - - const t3 = t2; ->t3 : Symbol(t3, Decl(dependentReturnType4.ts, 42, 9)) ->t2 : Symbol(t2, Decl(dependentReturnType4.ts, 41, 9)) - - const t4 = t3; ->t4 : Symbol(t4, Decl(dependentReturnType4.ts, 43, 9)) ->t3 : Symbol(t3, Decl(dependentReturnType4.ts, 42, 9)) - - const t5 = t4; ->t5 : Symbol(t5, Decl(dependentReturnType4.ts, 44, 9)) ->t4 : Symbol(t4, Decl(dependentReturnType4.ts, 43, 9)) - - const t6 = t5; ->t6 : Symbol(t6, Decl(dependentReturnType4.ts, 45, 9)) ->t5 : Symbol(t5, Decl(dependentReturnType4.ts, 44, 9)) - - if (t6) { ->t6 : Symbol(t6, Decl(dependentReturnType4.ts, 45, 9)) - - const y: string = x; ->y : Symbol(y, Decl(dependentReturnType4.ts, 47, 13)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 39, 45)) - - return 1; - } - return "one"; -} - -type A = { kind: "a", a: number }; ->A : Symbol(A, Decl(dependentReturnType4.ts, 51, 1)) ->kind : Symbol(kind, Decl(dependentReturnType4.ts, 53, 10)) ->a : Symbol(a, Decl(dependentReturnType4.ts, 53, 21)) - -type B = { kind: "b", b: string }; ->B : Symbol(B, Decl(dependentReturnType4.ts, 53, 34)) ->kind : Symbol(kind, Decl(dependentReturnType4.ts, 54, 10)) ->b : Symbol(b, Decl(dependentReturnType4.ts, 54, 21)) - -type AOrB = A | B; ->AOrB : Symbol(AOrB, Decl(dependentReturnType4.ts, 54, 34)) ->A : Symbol(A, Decl(dependentReturnType4.ts, 51, 1)) ->B : Symbol(B, Decl(dependentReturnType4.ts, 53, 34)) - -function subexpression(x: T): T extends A ? number : T extends B ? string : never { ->subexpression : Symbol(subexpression, Decl(dependentReturnType4.ts, 55, 18)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 57, 23)) ->AOrB : Symbol(AOrB, Decl(dependentReturnType4.ts, 54, 34)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 57, 39)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 57, 23)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 57, 23)) ->A : Symbol(A, Decl(dependentReturnType4.ts, 51, 1)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 57, 23)) ->B : Symbol(B, Decl(dependentReturnType4.ts, 53, 34)) - - if (x.kind === "b") { ->x.kind : Symbol(kind, Decl(dependentReturnType4.ts, 53, 10), Decl(dependentReturnType4.ts, 54, 10)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 57, 39)) ->kind : Symbol(kind, Decl(dependentReturnType4.ts, 53, 10), Decl(dependentReturnType4.ts, 54, 10)) - - return "some str"; - } - return 0; -} - -function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : never { ->switchTrue : Symbol(switchTrue, Decl(dependentReturnType4.ts, 62, 1)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 64, 20)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 64, 39)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 64, 20)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 64, 20)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 64, 20)) - - switch (true) { - case x: ->x : Symbol(x, Decl(dependentReturnType4.ts, 64, 39)) - - return 1; - } - return 0; -} - -// Don't raise errors when getting the narrowed type of synthesized nodes -type Ret = T extends string ? 1 : T extends number ? 2 : never; ->Ret : Symbol(Ret, Decl(dependentReturnType4.ts, 70, 1)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 73, 9)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 73, 9)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 73, 9)) - -function f(x: T): Ret { ->f : Symbol(f, Decl(dependentReturnType4.ts, 73, 90)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 74, 11)) ->x : Symbol(x, Decl(dependentReturnType4.ts, 74, 38)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 74, 11)) ->Ret : Symbol(Ret, Decl(dependentReturnType4.ts, 70, 1)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 74, 11)) - - let y!: T; ->y : Symbol(y, Decl(dependentReturnType4.ts, 75, 7)) ->T : Symbol(T, Decl(dependentReturnType4.ts, 74, 11)) - - if (typeof y === "string") { ->y : Symbol(y, Decl(dependentReturnType4.ts, 75, 7)) - - return 1; - } - return 2; -} diff --git a/tests/baselines/reference/dependentReturnType4.types b/tests/baselines/reference/dependentReturnType4.types index 98d57a124daa3..8b2b910a65fbb 100644 --- a/tests/baselines/reference/dependentReturnType4.types +++ b/tests/baselines/reference/dependentReturnType4.types @@ -1,7 +1,6 @@ //// [tests/cases/compiler/dependentReturnType4.ts] //// === dependentReturnType4.ts === -// Test narrowing through `hasOwnProperty` calls declare const rand: { a?: never }; >rand : { a?: never; } > : ^^^^^^ ^^^ @@ -18,69 +17,19 @@ type Missing = typeof rand.a; >a : undefined > : ^^^^^^^^^ -declare function takesString(x: string): void; ->takesString : (x: string) => void -> : ^ ^^ ^^^^^ ->x : string -> : ^^^^^^ +// Detection of valid optional parameter references -function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : never { ->hasOwnP : (obj: { a?: T; }) => T extends string ? 1 : T extends undefined ? 2 : never -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->obj : { a?: T; } -> : ^^^^^^ ^^^ ->a : T | undefined -> : ^^^^^^^^^^^^^ - - if (obj.hasOwnProperty("a")) { ->obj.hasOwnProperty("a") : boolean -> : ^^^^^^^ ->obj.hasOwnProperty : (v: PropertyKey) => boolean -> : ^ ^^ ^^^^^ ->obj : { a?: T; } -> : ^^^^^^ ^^^ ->hasOwnProperty : (v: PropertyKey) => boolean -> : ^ ^^ ^^^^^ ->"a" : "a" -> : ^^^ - - takesString(obj.a); ->takesString(obj.a) : void -> : ^^^^ ->takesString : (x: string) => void -> : ^ ^^ ^^^^^ ->obj.a : string -> : ^^^^^^ ->obj : { a?: T; } -> : ^^^^^^ ^^^ ->a : string -> : ^^^^^^ - - return 1; ->1 : 1 -> : ^ - } - return 2; ->2 : 2 -> : ^ -} - -function foo(opts: { x?: T }): ->foo : (opts: { x?: T; }) => T extends undefined ? 0 : T extends string ? 1 : never -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->opts : { x?: T; } -> : ^^^^^^ ^^^ +// Ok, will narrow return type +function bar1(x?: T): +>bar1 : (x?: T) => T extends Missing ? 0 : T extends string ? 1 : never +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ >x : T | undefined > : ^^^^^^^^^^^^^ - T extends undefined ? 0 : T extends string ? 1 : never { - if (opts.x === undefined) { ->opts.x === undefined : boolean -> : ^^^^^^^ ->opts.x : T | undefined -> : ^^^^^^^^^^^^^ ->opts : { x?: T; } -> : ^^^^^^ ^^^ + T extends Missing ? 0 : T extends string ? 1 : never { + if (x === undefined) { +>x === undefined : boolean +> : ^^^^^^^ >x : T | undefined > : ^^^^^^^^^^^^^ >undefined : undefined @@ -95,13 +44,14 @@ function foo(opts: { x?: T }): > : ^ } -function bar(x?: T ): ->bar : (x?: T) => T extends Missing ? 0 : T extends string ? 1 : never -> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ +// Ok, will narrow return type +function bar2(x?: T): +>bar2 : (x?: T) => T extends undefined ? 0 : T extends string ? 1 : never +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ >x : T | undefined > : ^^^^^^^^^^^^^ - T extends Missing ? 0 : T extends string ? 1 : never { + T extends undefined ? 0 : T extends string ? 1 : never { if (x === undefined) { >x === undefined : boolean > : ^^^^^^^ @@ -119,216 +69,27 @@ function bar(x?: T ): > : ^ } -// Aliased narrowing -function inlined(x: T): T extends number ? string : T extends string ? number : never { ->inlined : (x: T) => T extends number ? string : T extends string ? number : never -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->x : T -> : ^ - - const t = typeof x === "string"; ->t : boolean -> : ^^^^^^^ ->typeof x === "string" : boolean -> : ^^^^^^^ ->typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->x : T -> : ^ ->"string" : "string" -> : ^^^^^^^^ - - if (t) { ->t : boolean -> : ^^^^^^^ - - const y: string = x; ->y : string -> : ^^^^^^ ->x : string -> : ^^^^^^ - - return 1; ->1 : 1 -> : ^ - } - return "one"; ->"one" : "one" -> : ^^^^^ -} - -// Don't narrow more than 5 levels of aliasing -function inlined6(x: T): T extends number ? string : T extends string ? number : never { ->inlined6 : (x: T) => T extends number ? string : T extends string ? number : never -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->x : T -> : ^ - - const t1 = typeof x === "string"; ->t1 : boolean -> : ^^^^^^^ ->typeof x === "string" : boolean -> : ^^^^^^^ ->typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->x : T -> : ^ ->"string" : "string" -> : ^^^^^^^^ - - const t2 = t1; ->t2 : boolean -> : ^^^^^^^ ->t1 : boolean -> : ^^^^^^^ - - const t3 = t2; ->t3 : boolean -> : ^^^^^^^ ->t2 : boolean -> : ^^^^^^^ - - const t4 = t3; ->t4 : boolean -> : ^^^^^^^ ->t3 : boolean -> : ^^^^^^^ - - const t5 = t4; ->t5 : boolean -> : ^^^^^^^ ->t4 : boolean -> : ^^^^^^^ - - const t6 = t5; ->t6 : boolean -> : ^^^^^^^ ->t5 : boolean -> : ^^^^^^^ - - if (t6) { ->t6 : boolean -> : ^^^^^^^ - - const y: string = x; ->y : string -> : ^^^^^^ ->x : string | number -> : ^^^^^^^^^^^^^^^ - - return 1; ->1 : 1 -> : ^ - } - return "one"; ->"one" : "one" -> : ^^^^^ -} - -type A = { kind: "a", a: number }; ->A : A -> : ^ ->kind : "a" -> : ^^^ ->a : number -> : ^^^^^^ - -type B = { kind: "b", b: string }; ->B : B -> : ^ ->kind : "b" -> : ^^^ ->b : string -> : ^^^^^^ - -type AOrB = A | B; ->AOrB : AOrB -> : ^^^^ - -function subexpression(x: T): T extends A ? number : T extends B ? string : never { ->subexpression : (x: T) => T extends A ? number : T extends B ? string : never -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->x : T -> : ^ +// Not ok, will not narrow return type +function bar3(x?: T): +>bar3 : (x?: T) => T extends undefined ? 0 : T extends string ? 1 : never +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ +>x : T | undefined +> : ^^^^^^^^^^^^^ - if (x.kind === "b") { ->x.kind === "b" : boolean -> : ^^^^^^^ ->x.kind : "a" | "b" -> : ^^^^^^^^^ ->x : AOrB -> : ^^^^ ->kind : "a" | "b" -> : ^^^^^^^^^ ->"b" : "b" -> : ^^^ + T extends undefined ? 0 : T extends string ? 1 : never { + if (x === undefined) { +>x === undefined : boolean +> : ^^^^^^^ +>x : T | undefined +> : ^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ - return "some str"; ->"some str" : "some str" -> : ^^^^^^^^^^ - } - return 0; + return 0; >0 : 0 -> : ^ -} - -function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : never { ->switchTrue : (x: T) => T extends true ? 1 : T extends false ? 0 : never -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->x : T -> : ^ ->true : true -> : ^^^^ ->false : false -> : ^^^^^ - - switch (true) { ->true : true -> : ^^^^ - - case x: ->x : T -> : ^ - - return 1; ->1 : 1 > : ^ } - return 0; ->0 : 0 -> : ^ -} - -// Don't raise errors when getting the narrowed type of synthesized nodes -type Ret = T extends string ? 1 : T extends number ? 2 : never; ->Ret : Ret -> : ^^^^^^ - -function f(x: T): Ret { ->f : (x: T) => Ret -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->x : T -> : ^ - - let y!: T; ->y : T -> : ^ - - if (typeof y === "string") { ->typeof y === "string" : boolean -> : ^^^^^^^ ->typeof y : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->y : T -> : ^ ->"string" : "string" -> : ^^^^^^^^ - - return 1; + return 1; >1 : 1 -> : ^ - } - return 2; ->2 : 2 > : ^ } diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index da275c6761404..d640d44404465 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -385,9 +385,9 @@ function noShadowing(x: T): T extends 1 ? number : T extends 2 // If the narrowing reference is out of scope, we simply won't narrow its type declare let someX: boolean; -function scope2(opts: { a: T }): T extends true ? 1 : T extends false ? 2 : never { +function scope2(a: T): T extends true ? 1 : T extends false ? 2 : never { if ((true)) { - const someX = opts.a; + const someX = a; if (someX) { // We narrow `someX` and the return type here return 1; } @@ -454,6 +454,7 @@ abstract class Operation { type ConditionalReturnType | undefined> = EOp extends Operation ? R : EOp extends undefined ? T | R : never; + class ConditionalOperation< T, R, @@ -467,34 +468,43 @@ class ConditionalOperation< super(); } + // We won't try to narrow the return type because `T` is declared on the class and we don't analyze this case. perform(t: T): ConditionalReturnType { if (this.predicate(t)) { return this.thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it } else if (typeof this.elseOp !== "undefined") { - return this.elseOp.perform(t); // Ok + return this.elseOp.perform(t); // Would be ok } else { - return t; // Ok + return t; // Would be ok } } } -// Optional tuple element -function tupl(x: [string, some?: T]): - T extends true ? 1 : T extends false | undefined ? 2 : never { - if (x[1]) { - return 1; +// Like the version above, we will not attempt to narrow because there's more than one reference to `T`, +// because `T` shows up in the type of `predicate`. +function perform | undefined>( + t: T, + predicate: (value: T) => boolean, + thenOp: Operation, + elseOp?: EOp, + ): ConditionalReturnType { + if (predicate(t)) { + return thenOp.perform(t); // Bad: this is assignable to all of the branches of the conditional, but we still can't return it + } else if (elseOp !== undefined) { + return elseOp.perform(t); // Would be ok + } else { + return t; // Would be ok } - return 2; } // Return conditional expressions with parentheses -function returnStuff1(opts: { x: T }): T extends true ? 1 : T extends false ? 2 : never { - return (opts.x ? (1) : 2); +function returnStuff1(x: T ): T extends true ? 1 : T extends false ? 2 : never { + return (x ? (1) : 2); } -function returnStuff2(opts: { x: T }): +function returnStuff2(x: T ): T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never { - return (typeof opts.x === "string" ? 0 : (opts.x === 1 ? ("one") : "two")); + return (typeof x === "string" ? 0 : (x === 1 ? ("one") : "two")); } // If the conditional type's input is `never`, then it resolves to `never`: diff --git a/tests/cases/compiler/dependentReturnType4.ts b/tests/cases/compiler/dependentReturnType4.ts index 266d822437c60..166b8497fa3eb 100644 --- a/tests/cases/compiler/dependentReturnType4.ts +++ b/tests/cases/compiler/dependentReturnType4.ts @@ -3,27 +3,13 @@ // @target: ES2022 // @exactOptionalPropertyTypes: true -// Test narrowing through `hasOwnProperty` calls declare const rand: { a?: never }; type Missing = typeof rand.a; -declare function takesString(x: string): void; -function hasOwnP(obj: { a?: T }): T extends string ? 1 : T extends undefined ? 2 : never { - if (obj.hasOwnProperty("a")) { - takesString(obj.a); - return 1; - } - return 2; -} -function foo(opts: { x?: T }): - T extends undefined ? 0 : T extends string ? 1 : never { - if (opts.x === undefined) { - return 0; - } - return 1; -} +// Detection of valid optional parameter references -function bar(x?: T ): +// Ok, will narrow return type +function bar1(x?: T): T extends Missing ? 0 : T extends string ? 1 : never { if (x === undefined) { return 0; @@ -31,56 +17,20 @@ function bar(x?: T ): return 1; } -// Aliased narrowing -function inlined(x: T): T extends number ? string : T extends string ? number : never { - const t = typeof x === "string"; - if (t) { - const y: string = x; - return 1; - } - return "one"; -} - -// Don't narrow more than 5 levels of aliasing -function inlined6(x: T): T extends number ? string : T extends string ? number : never { - const t1 = typeof x === "string"; - const t2 = t1; - const t3 = t2; - const t4 = t3; - const t5 = t4; - const t6 = t5; - if (t6) { - const y: string = x; - return 1; - } - return "one"; -} - -type A = { kind: "a", a: number }; -type B = { kind: "b", b: string }; -type AOrB = A | B; - -function subexpression(x: T): T extends A ? number : T extends B ? string : never { - if (x.kind === "b") { - return "some str"; - } - return 0; -} - -function switchTrue(x: T): T extends true ? 1 : T extends false ? 0 : never { - switch (true) { - case x: - return 1; +// Ok, will narrow return type +function bar2(x?: T): + T extends undefined ? 0 : T extends string ? 1 : never { + if (x === undefined) { + return 0; } - return 0; + return 1; } -// Don't raise errors when getting the narrowed type of synthesized nodes -type Ret = T extends string ? 1 : T extends number ? 2 : never; -function f(x: T): Ret { - let y!: T; - if (typeof y === "string") { - return 1; +// Not ok, will not narrow return type +function bar3(x?: T): + T extends undefined ? 0 : T extends string ? 1 : never { + if (x === undefined) { + return 0; } - return 2; + return 1; } \ No newline at end of file diff --git a/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts b/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts index 7f6b147820a49..fad2f1452fd11 100644 --- a/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts +++ b/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts @@ -1,12 +1,12 @@ /// // @strict: true -//// function h(obj: { x: T }): T extends true ? 1 : T extends false ? 2 : never { -//// if (obj.x) { +//// function h(x: T): T extends true ? 1 : T extends false ? 2 : never { +//// if (x) { //// return 1; //// } //// return 2; //// } -verify.encodedSemanticClassificationsLength("2020", 27); +verify.encodedSemanticClassificationsLength("2020", 21); verify.noErrors(); \ No newline at end of file From 39d2f2bc1ddb80ffb8ab0322614214168399fe29 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 12 Sep 2024 18:50:48 -0700 Subject: [PATCH 71/90] refactor and add more tests --- src/compiler/checker.ts | 20 +- .../reference/dependentReturnType6.errors.txt | 165 +++++++ .../reference/dependentReturnType6.symbols | 297 ++++++++++++ .../reference/dependentReturnType6.types | 421 ++++++++++++++++++ tests/cases/compiler/dependentReturnType1.ts | 2 +- tests/cases/compiler/dependentReturnType6.ts | 110 +++++ 6 files changed, 1004 insertions(+), 11 deletions(-) create mode 100644 tests/baselines/reference/dependentReturnType6.errors.txt create mode 100644 tests/baselines/reference/dependentReturnType6.symbols create mode 100644 tests/baselines/reference/dependentReturnType6.types create mode 100644 tests/cases/compiler/dependentReturnType6.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 279f468529c33..37843034194a6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45583,11 +45583,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let hasInvalidReference = false; for (const paramDecl of container.parameters) { const typeNode = paramDecl.type; - if (!typeNode) { - // Parameter's type could be inferred to a type that references the type parameter. - hasInvalidReference = true; - break; - } + if (!typeNode) continue; if (isTypeParameterReferenced(typeParam, typeNode)) { let candidateReference; if (isTypeReferenceNode(typeNode) && @@ -45640,7 +45636,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - // A valid conditional type will have the following shape: + // A narrowable indexed access type is one that has the shape `A[T]`, + // where `T` is a narrowable type parameter. + // A narrowable conditional type is one that has the following shape: // `T extends A ? TrueBranch : FalseBranch`, such that: // (0) The conditional type's check type is a narrowable type parameter; // (1) `A` is a type belonging to the constraint of the type parameter, @@ -45652,10 +45650,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { typeParameters: TypeParameter[], returnType: IndexedAccessType | ConditionalType, ): boolean { - return !isConditionalType(returnType) || isNarrowable(returnType, /*branch*/ undefined); + return !isConditionalType(returnType) + && typeParameters.includes(returnType.indexType) + || isNarrowableConditionalType(returnType, /*branch*/ undefined); // `branch` can be `true` if `type` is the true type of a conditional, `false` if it's the false type of a conditional, // and `undefined` if neither. - function isNarrowable(type: Type, branch: boolean | undefined): boolean { + function isNarrowableConditionalType(type: Type, branch: boolean | undefined): boolean { if (!isConditionalType(type)) { // This is type `R` in `T extends A ? R : ...` if (branch === true) { @@ -45695,8 +45695,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; } - return isNarrowable(getTrueTypeFromConditionalType(type), /*branch*/ true) && - isNarrowable(getFalseTypeFromConditionalType(type), /*branch*/ false); + return isNarrowableConditionalType(getTrueTypeFromConditionalType(type), /*branch*/ true) && + isNarrowableConditionalType(getFalseTypeFromConditionalType(type), /*branch*/ false); } } diff --git a/tests/baselines/reference/dependentReturnType6.errors.txt b/tests/baselines/reference/dependentReturnType6.errors.txt new file mode 100644 index 0000000000000..552b4e79e5455 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType6.errors.txt @@ -0,0 +1,165 @@ +file.ts(26,26): error TS2322: Type 'true' is not assignable to type 'SomeInterface[U]'. + Type 'true' is not assignable to type 'T extends 1 ? true : T extends 2 ? false : never'. +file.ts(26,33): error TS2322: Type 'false' is not assignable to type 'SomeInterface[U]'. + Type 'false' is not assignable to type 'T extends 1 ? true : T extends 2 ? false : never'. +file.ts(28,16): error TS2322: Type '1' is not assignable to type 'SomeInterface[U]'. + Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +file.ts(28,20): error TS2322: Type '2' is not assignable to type 'SomeInterface[U]'. + Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +file.ts(65,13): error TS2322: Type '1' is not assignable to type 'this extends Sub1 ? 1 : this extends Sub2 ? 2 : never'. +file.ts(67,9): error TS2322: Type '2' is not assignable to type 'this extends Sub1 ? 1 : this extends Sub2 ? 2 : never'. +file.ts(71,5): error TS18028: Private identifiers are only available when targeting ECMAScript 2015 and higher. +file.ts(74,5): error TS18028: Private identifiers are only available when targeting ECMAScript 2015 and higher. +file.ts(79,16): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +file.ts(79,20): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +file.ts(83,100): error TS2322: Type '1 | 2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. + Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +file.ts(91,9): error TS2322: Type 'number' is not assignable to type 'SomeCond'. +file.ts(91,9): error TS2589: Type instantiation is excessively deep and possibly infinite. +file.ts(93,5): error TS2322: Type 'number' is not assignable to type 'SomeCond'. +file.ts(99,60): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. +file.ts(101,9): error TS2322: Type '"one"' is not assignable to type 'OtherCond | OtherCond'. + + +==== file.ts (16 errors) ==== + // Type parameter in outer scope + function outer(x: T): number { + return inner(); + + function inner(): T extends true ? 1 : T extends false ? 2 : never { + return x ? 1 : 2; + } + } + + // Overloads + function fun6(x: T, y: string): T extends true ? string : T extends false ? 2 : never; + function fun6(x: T, y: undefined): T extends true ? 1 : T extends false ? 2 : never; + function fun6(x: boolean): 1 | 2 | string; + function fun6(x: T, y?: string): T extends true ? 1 | string : T extends false ? 2 : never { + return x ? y !== undefined ? y : 1 : 2; + } + + // Indexed access with conditional inside - DOESN'T NARROW the nested conditional type + interface SomeInterface { + prop1: T extends 1 ? true : T extends 2 ? false : never; + prop2: T extends true ? 1 : T extends false ? 2 : never; + } + + function fun4>(x: T, y: U): SomeInterface[U] { + if (y === "prop1") { + return x === 1 ? true : false; + ~~~~ +!!! error TS2322: Type 'true' is not assignable to type 'SomeInterface[U]'. +!!! error TS2322: Type 'true' is not assignable to type 'T extends 1 ? true : T extends 2 ? false : never'. + ~~~~~ +!!! error TS2322: Type 'false' is not assignable to type 'SomeInterface[U]'. +!!! error TS2322: Type 'false' is not assignable to type 'T extends 1 ? true : T extends 2 ? false : never'. + } + return x ? 1 : 2; + ~ +!!! error TS2322: Type '1' is not assignable to type 'SomeInterface[U]'. +!!! error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. + ~ +!!! error TS2322: Type '2' is not assignable to type 'SomeInterface[U]'. +!!! error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. + } + + // Indexed access with indexed access inside - OK, narrows + interface BB { + "a": number; + "b": string; + } + + interface AA { + "c": BB[T]; + "d": boolean, + } + + function reduction>(x: T, y: U): AA[U] { + if (x === "a" && y === "c") { + return 0; // Ok + } + + return undefined as never; + } + + // Conditional with indexed access inside - OK, narrows + function fun5(x: T, y: U): T extends 1 ? BB[U] : T extends 2 ? boolean : never { + if (x === 1) { + if (y === "a") { + return 0; + } + return ""; + } + return true; + } + + // `this` type parameter - Doesn't narrow + abstract class SomeClass { + fun3(this: Sub1 | Sub2): this extends Sub1 ? 1 : this extends Sub2 ? 2 : never { + if (this instanceof Sub1) { + return 1; + ~~~~~~ +!!! error TS2322: Type '1' is not assignable to type 'this extends Sub1 ? 1 : this extends Sub2 ? 2 : never'. + } + return 2; + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type 'this extends Sub1 ? 1 : this extends Sub2 ? 2 : never'. + } + } + class Sub1 extends SomeClass { + #sub1!: symbol; + ~~~~~ +!!! error TS18028: Private identifiers are only available when targeting ECMAScript 2015 and higher. + }; + class Sub2 extends SomeClass { + #sub2!: symbol; + ~~~~~ +!!! error TS18028: Private identifiers are only available when targeting ECMAScript 2015 and higher. + }; + + // Detection of type parameter reference in presence of typeof + function fun2(x: T, y: typeof x): T extends true ? 1 : T extends false ? 2 : never { + return x ? 1 : 2; + ~ +!!! error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. + ~ +!!! error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. + } + + // Contextually-typed lambdas + const fun1: (x: T) => T extends true ? 1 : T extends false ? 2 : never = (x) => x ? 1 : 2; + ~~~~~~~~~ +!!! error TS2322: Type '1 | 2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +!!! error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +!!! related TS6502 file.ts:83:13: The expected type comes from the return type of this signature. + + + // Circular conditionals + type SomeCond = T extends true ? 1 : T extends false ? SomeCond : never; + + function f7(x: T): SomeCond { + if (x) { + return 1; + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'SomeCond'. + ~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + } + return 2; + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'SomeCond'. + } + + // Composite instantiation of conditional type + type OtherCond = T extends 1 ? "one" : T extends 2 ? "two" : T extends 3 ? "three" : T extends 4 ? "four" : never; + + function f8(x: U, y: V): OtherCond { + ~~~~~~~~~~~~~~~~ +!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'. + if (x === 1 && y === 3) { + return "one"; + ~~~~~~ +!!! error TS2322: Type '"one"' is not assignable to type 'OtherCond | OtherCond'. + } + } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType6.symbols b/tests/baselines/reference/dependentReturnType6.symbols new file mode 100644 index 0000000000000..594afebec58d9 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType6.symbols @@ -0,0 +1,297 @@ +//// [tests/cases/compiler/dependentReturnType6.ts] //// + +=== file.ts === +// Type parameter in outer scope +function outer(x: T): number { +>outer : Symbol(outer, Decl(file.ts, 0, 0)) +>T : Symbol(T, Decl(file.ts, 1, 15)) +>x : Symbol(x, Decl(file.ts, 1, 34)) +>T : Symbol(T, Decl(file.ts, 1, 15)) + + return inner(); +>inner : Symbol(inner, Decl(file.ts, 2, 19)) + + function inner(): T extends true ? 1 : T extends false ? 2 : never { +>inner : Symbol(inner, Decl(file.ts, 2, 19)) +>T : Symbol(T, Decl(file.ts, 1, 15)) +>T : Symbol(T, Decl(file.ts, 1, 15)) + + return x ? 1 : 2; +>x : Symbol(x, Decl(file.ts, 1, 34)) + } +} + +// Overloads +function fun6(x: T, y: string): T extends true ? string : T extends false ? 2 : never; +>fun6 : Symbol(fun6, Decl(file.ts, 7, 1), Decl(file.ts, 10, 105), Decl(file.ts, 11, 103), Decl(file.ts, 12, 42)) +>T : Symbol(T, Decl(file.ts, 10, 14)) +>x : Symbol(x, Decl(file.ts, 10, 33)) +>T : Symbol(T, Decl(file.ts, 10, 14)) +>y : Symbol(y, Decl(file.ts, 10, 38)) +>T : Symbol(T, Decl(file.ts, 10, 14)) +>T : Symbol(T, Decl(file.ts, 10, 14)) + +function fun6(x: T, y: undefined): T extends true ? 1 : T extends false ? 2 : never; +>fun6 : Symbol(fun6, Decl(file.ts, 7, 1), Decl(file.ts, 10, 105), Decl(file.ts, 11, 103), Decl(file.ts, 12, 42)) +>T : Symbol(T, Decl(file.ts, 11, 14)) +>x : Symbol(x, Decl(file.ts, 11, 33)) +>T : Symbol(T, Decl(file.ts, 11, 14)) +>y : Symbol(y, Decl(file.ts, 11, 38)) +>T : Symbol(T, Decl(file.ts, 11, 14)) +>T : Symbol(T, Decl(file.ts, 11, 14)) + +function fun6(x: boolean): 1 | 2 | string; +>fun6 : Symbol(fun6, Decl(file.ts, 7, 1), Decl(file.ts, 10, 105), Decl(file.ts, 11, 103), Decl(file.ts, 12, 42)) +>x : Symbol(x, Decl(file.ts, 12, 14)) + +function fun6(x: T, y?: string): T extends true ? 1 | string : T extends false ? 2 : never { +>fun6 : Symbol(fun6, Decl(file.ts, 7, 1), Decl(file.ts, 10, 105), Decl(file.ts, 11, 103), Decl(file.ts, 12, 42)) +>T : Symbol(T, Decl(file.ts, 13, 14)) +>x : Symbol(x, Decl(file.ts, 13, 33)) +>T : Symbol(T, Decl(file.ts, 13, 14)) +>y : Symbol(y, Decl(file.ts, 13, 38)) +>T : Symbol(T, Decl(file.ts, 13, 14)) +>T : Symbol(T, Decl(file.ts, 13, 14)) + + return x ? y !== undefined ? y : 1 : 2; +>x : Symbol(x, Decl(file.ts, 13, 33)) +>y : Symbol(y, Decl(file.ts, 13, 38)) +>undefined : Symbol(undefined) +>y : Symbol(y, Decl(file.ts, 13, 38)) +} + +// Indexed access with conditional inside - DOESN'T NARROW the nested conditional type +interface SomeInterface { +>SomeInterface : Symbol(SomeInterface, Decl(file.ts, 15, 1)) +>T : Symbol(T, Decl(file.ts, 18, 24)) + + prop1: T extends 1 ? true : T extends 2 ? false : never; +>prop1 : Symbol(SomeInterface.prop1, Decl(file.ts, 18, 28)) +>T : Symbol(T, Decl(file.ts, 18, 24)) +>T : Symbol(T, Decl(file.ts, 18, 24)) + + prop2: T extends true ? 1 : T extends false ? 2 : never; +>prop2 : Symbol(SomeInterface.prop2, Decl(file.ts, 19, 60)) +>T : Symbol(T, Decl(file.ts, 18, 24)) +>T : Symbol(T, Decl(file.ts, 18, 24)) +} + +function fun4>(x: T, y: U): SomeInterface[U] { +>fun4 : Symbol(fun4, Decl(file.ts, 21, 1)) +>T : Symbol(T, Decl(file.ts, 23, 14)) +>U : Symbol(U, Decl(file.ts, 23, 16)) +>SomeInterface : Symbol(SomeInterface, Decl(file.ts, 15, 1)) +>x : Symbol(x, Decl(file.ts, 23, 57)) +>T : Symbol(T, Decl(file.ts, 23, 14)) +>y : Symbol(y, Decl(file.ts, 23, 62)) +>U : Symbol(U, Decl(file.ts, 23, 16)) +>SomeInterface : Symbol(SomeInterface, Decl(file.ts, 15, 1)) +>T : Symbol(T, Decl(file.ts, 23, 14)) +>U : Symbol(U, Decl(file.ts, 23, 16)) + + if (y === "prop1") { +>y : Symbol(y, Decl(file.ts, 23, 62)) + + return x === 1 ? true : false; +>x : Symbol(x, Decl(file.ts, 23, 57)) + } + return x ? 1 : 2; +>x : Symbol(x, Decl(file.ts, 23, 57)) +} + +// Indexed access with indexed access inside - OK, narrows +interface BB { +>BB : Symbol(BB, Decl(file.ts, 28, 1)) + + "a": number; +>"a" : Symbol(BB["a"], Decl(file.ts, 31, 14)) + + "b": string; +>"b" : Symbol(BB["b"], Decl(file.ts, 32, 16)) +} + +interface AA { +>AA : Symbol(AA, Decl(file.ts, 34, 1)) +>T : Symbol(T, Decl(file.ts, 36, 13)) +>BB : Symbol(BB, Decl(file.ts, 28, 1)) + + "c": BB[T]; +>"c" : Symbol(AA["c"], Decl(file.ts, 36, 34)) +>BB : Symbol(BB, Decl(file.ts, 28, 1)) +>T : Symbol(T, Decl(file.ts, 36, 13)) + + "d": boolean, +>"d" : Symbol(AA["d"], Decl(file.ts, 37, 15)) +} + +function reduction>(x: T, y: U): AA[U] { +>reduction : Symbol(reduction, Decl(file.ts, 39, 1)) +>T : Symbol(T, Decl(file.ts, 41, 19)) +>BB : Symbol(BB, Decl(file.ts, 28, 1)) +>U : Symbol(U, Decl(file.ts, 41, 38)) +>AA : Symbol(AA, Decl(file.ts, 34, 1)) +>x : Symbol(x, Decl(file.ts, 41, 64)) +>T : Symbol(T, Decl(file.ts, 41, 19)) +>y : Symbol(y, Decl(file.ts, 41, 69)) +>U : Symbol(U, Decl(file.ts, 41, 38)) +>AA : Symbol(AA, Decl(file.ts, 34, 1)) +>T : Symbol(T, Decl(file.ts, 41, 19)) +>U : Symbol(U, Decl(file.ts, 41, 38)) + + if (x === "a" && y === "c") { +>x : Symbol(x, Decl(file.ts, 41, 64)) +>y : Symbol(y, Decl(file.ts, 41, 69)) + + return 0; // Ok + } + + return undefined as never; +>undefined : Symbol(undefined) +} + +// Conditional with indexed access inside - OK, narrows +function fun5(x: T, y: U): T extends 1 ? BB[U] : T extends 2 ? boolean : never { +>fun5 : Symbol(fun5, Decl(file.ts, 47, 1)) +>T : Symbol(T, Decl(file.ts, 50, 14)) +>U : Symbol(U, Decl(file.ts, 50, 30)) +>BB : Symbol(BB, Decl(file.ts, 28, 1)) +>x : Symbol(x, Decl(file.ts, 50, 51)) +>T : Symbol(T, Decl(file.ts, 50, 14)) +>y : Symbol(y, Decl(file.ts, 50, 56)) +>U : Symbol(U, Decl(file.ts, 50, 30)) +>T : Symbol(T, Decl(file.ts, 50, 14)) +>BB : Symbol(BB, Decl(file.ts, 28, 1)) +>U : Symbol(U, Decl(file.ts, 50, 30)) +>T : Symbol(T, Decl(file.ts, 50, 14)) + + if (x === 1) { +>x : Symbol(x, Decl(file.ts, 50, 51)) + + if (y === "a") { +>y : Symbol(y, Decl(file.ts, 50, 56)) + + return 0; + } + return ""; + } + return true; +} + +// `this` type parameter - Doesn't narrow +abstract class SomeClass { +>SomeClass : Symbol(SomeClass, Decl(file.ts, 58, 1)) + + fun3(this: Sub1 | Sub2): this extends Sub1 ? 1 : this extends Sub2 ? 2 : never { +>fun3 : Symbol(SomeClass.fun3, Decl(file.ts, 61, 26)) +>this : Symbol(this, Decl(file.ts, 62, 9)) +>Sub1 : Symbol(Sub1, Decl(file.ts, 68, 1)) +>Sub2 : Symbol(Sub2, Decl(file.ts, 71, 2)) +>Sub1 : Symbol(Sub1, Decl(file.ts, 68, 1)) +>Sub2 : Symbol(Sub2, Decl(file.ts, 71, 2)) + + if (this instanceof Sub1) { +>this : Symbol(this, Decl(file.ts, 62, 9)) +>Sub1 : Symbol(Sub1, Decl(file.ts, 68, 1)) + + return 1; + } + return 2; + } +} +class Sub1 extends SomeClass { +>Sub1 : Symbol(Sub1, Decl(file.ts, 68, 1)) +>SomeClass : Symbol(SomeClass, Decl(file.ts, 58, 1)) + + #sub1!: symbol; +>#sub1 : Symbol(Sub1.#sub1, Decl(file.ts, 69, 30)) + +}; +class Sub2 extends SomeClass { +>Sub2 : Symbol(Sub2, Decl(file.ts, 71, 2)) +>SomeClass : Symbol(SomeClass, Decl(file.ts, 58, 1)) + + #sub2!: symbol; +>#sub2 : Symbol(Sub2.#sub2, Decl(file.ts, 72, 30)) + +}; + +// Detection of type parameter reference in presence of typeof +function fun2(x: T, y: typeof x): T extends true ? 1 : T extends false ? 2 : never { +>fun2 : Symbol(fun2, Decl(file.ts, 74, 2)) +>T : Symbol(T, Decl(file.ts, 77, 14)) +>x : Symbol(x, Decl(file.ts, 77, 33)) +>T : Symbol(T, Decl(file.ts, 77, 14)) +>y : Symbol(y, Decl(file.ts, 77, 38)) +>x : Symbol(x, Decl(file.ts, 77, 33)) +>T : Symbol(T, Decl(file.ts, 77, 14)) +>T : Symbol(T, Decl(file.ts, 77, 14)) + + return x ? 1 : 2; +>x : Symbol(x, Decl(file.ts, 77, 33)) +} + +// Contextually-typed lambdas +const fun1: (x: T) => T extends true ? 1 : T extends false ? 2 : never = (x) => x ? 1 : 2; +>fun1 : Symbol(fun1, Decl(file.ts, 82, 5)) +>T : Symbol(T, Decl(file.ts, 82, 13)) +>x : Symbol(x, Decl(file.ts, 82, 32)) +>T : Symbol(T, Decl(file.ts, 82, 13)) +>T : Symbol(T, Decl(file.ts, 82, 13)) +>T : Symbol(T, Decl(file.ts, 82, 13)) +>x : Symbol(x, Decl(file.ts, 82, 93)) +>x : Symbol(x, Decl(file.ts, 82, 93)) + + +// Circular conditionals +type SomeCond = T extends true ? 1 : T extends false ? SomeCond : never; +>SomeCond : Symbol(SomeCond, Decl(file.ts, 82, 109)) +>T : Symbol(T, Decl(file.ts, 86, 14)) +>T : Symbol(T, Decl(file.ts, 86, 14)) +>T : Symbol(T, Decl(file.ts, 86, 14)) +>SomeCond : Symbol(SomeCond, Decl(file.ts, 82, 109)) +>T : Symbol(T, Decl(file.ts, 86, 14)) + +function f7(x: T): SomeCond { +>f7 : Symbol(f7, Decl(file.ts, 86, 78)) +>T : Symbol(T, Decl(file.ts, 88, 12)) +>x : Symbol(x, Decl(file.ts, 88, 31)) +>T : Symbol(T, Decl(file.ts, 88, 12)) +>SomeCond : Symbol(SomeCond, Decl(file.ts, 82, 109)) +>T : Symbol(T, Decl(file.ts, 88, 12)) + + if (x) { +>x : Symbol(x, Decl(file.ts, 88, 31)) + + return 1; + } + return 2; +} + +// Composite instantiation of conditional type +type OtherCond = T extends 1 ? "one" : T extends 2 ? "two" : T extends 3 ? "three" : T extends 4 ? "four" : never; +>OtherCond : Symbol(OtherCond, Decl(file.ts, 93, 1)) +>T : Symbol(T, Decl(file.ts, 96, 15)) +>T : Symbol(T, Decl(file.ts, 96, 15)) +>T : Symbol(T, Decl(file.ts, 96, 15)) +>T : Symbol(T, Decl(file.ts, 96, 15)) +>T : Symbol(T, Decl(file.ts, 96, 15)) + +function f8(x: U, y: V): OtherCond { +>f8 : Symbol(f8, Decl(file.ts, 96, 117)) +>U : Symbol(U, Decl(file.ts, 98, 12)) +>V : Symbol(V, Decl(file.ts, 98, 28)) +>x : Symbol(x, Decl(file.ts, 98, 46)) +>U : Symbol(U, Decl(file.ts, 98, 12)) +>y : Symbol(y, Decl(file.ts, 98, 51)) +>V : Symbol(V, Decl(file.ts, 98, 28)) +>OtherCond : Symbol(OtherCond, Decl(file.ts, 93, 1)) +>U : Symbol(U, Decl(file.ts, 98, 12)) +>V : Symbol(V, Decl(file.ts, 98, 28)) + + if (x === 1 && y === 3) { +>x : Symbol(x, Decl(file.ts, 98, 46)) +>y : Symbol(y, Decl(file.ts, 98, 51)) + + return "one"; + } +} diff --git a/tests/baselines/reference/dependentReturnType6.types b/tests/baselines/reference/dependentReturnType6.types new file mode 100644 index 0000000000000..b41f572391674 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType6.types @@ -0,0 +1,421 @@ +//// [tests/cases/compiler/dependentReturnType6.ts] //// + +=== Performance Stats === +Instantiation count: 5,000 + +=== file.ts === +// Type parameter in outer scope +function outer(x: T): number { +>outer : (x: T) => number +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>x : T +> : ^ + + return inner(); +>inner() : T extends true ? 1 : T extends false ? 2 : never +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>inner : () => T extends true ? 1 : T extends false ? 2 : never +> : ^^^^^^ + + function inner(): T extends true ? 1 : T extends false ? 2 : never { +>inner : () => T extends true ? 1 : T extends false ? 2 : never +> : ^^^^^^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ + + return x ? 1 : 2; +>x ? 1 : 2 : 1 | 2 +> : ^^^^^ +>x : T +> : ^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ + } +} + +// Overloads +function fun6(x: T, y: string): T extends true ? string : T extends false ? 2 : never; +>fun6 : { (x: T, y: string): T extends true ? string : T extends false ? 2 : never; (x: T_1, y: undefined): T_1 extends true ? 1 : T_1 extends false ? 2 : never; (x: boolean): 1 | 2 | string; } +> : ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^^^^^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ +>x : T +> : ^ +>y : string +> : ^^^^^^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ + +function fun6(x: T, y: undefined): T extends true ? 1 : T extends false ? 2 : never; +>fun6 : { (x: T_1, y: string): T_1 extends true ? string : T_1 extends false ? 2 : never; (x: T, y: undefined): T extends true ? 1 : T extends false ? 2 : never; (x: boolean): 1 | 2 | string; } +> : ^^^^^^^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ +>x : T +> : ^ +>y : undefined +> : ^^^^^^^^^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ + +function fun6(x: boolean): 1 | 2 | string; +>fun6 : { (x: T, y: string): T extends true ? string : T extends false ? 2 : never; (x: T, y: undefined): T extends true ? 1 : T extends false ? 2 : never; (x: boolean): 1 | 2 | string; } +> : ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ +>x : boolean +> : ^^^^^^^ + +function fun6(x: T, y?: string): T extends true ? 1 | string : T extends false ? 2 : never { +>fun6 : { (x: T_1, y: string): T_1 extends true ? string : T_1 extends false ? 2 : never; (x: T_1, y: undefined): T_1 extends true ? 1 : T_1 extends false ? 2 : never; (x: boolean): 1 | 2 | string; } +> : ^^^^^^^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^^^^^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ +>x : T +> : ^ +>y : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ + + return x ? y !== undefined ? y : 1 : 2; +>x ? y !== undefined ? y : 1 : 2 : string | 1 | 2 +> : ^^^^^^^^^^^^^^ +>x : T +> : ^ +>y !== undefined ? y : 1 : string | 1 +> : ^^^^^^^^^^ +>y !== undefined : boolean +> : ^^^^^^^ +>y : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ +>y : string +> : ^^^^^^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ +} + +// Indexed access with conditional inside - DOESN'T NARROW the nested conditional type +interface SomeInterface { + prop1: T extends 1 ? true : T extends 2 ? false : never; +>prop1 : T extends 1 ? true : T extends 2 ? false : never +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ + + prop2: T extends true ? 1 : T extends false ? 2 : never; +>prop2 : T extends true ? 1 : T extends false ? 2 : never +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ +} + +function fun4>(x: T, y: U): SomeInterface[U] { +>fun4 : >(x: T, y: U) => SomeInterface[U] +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>x : T +> : ^ +>y : U +> : ^ + + if (y === "prop1") { +>y === "prop1" : boolean +> : ^^^^^^^ +>y : U +> : ^ +>"prop1" : "prop1" +> : ^^^^^^^ + + return x === 1 ? true : false; +>x === 1 ? true : false : boolean +> : ^^^^^^^ +>x === 1 : boolean +> : ^^^^^^^ +>x : T +> : ^ +>1 : 1 +> : ^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ + } + return x ? 1 : 2; +>x ? 1 : 2 : 1 | 2 +> : ^^^^^ +>x : T +> : ^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ +} + +// Indexed access with indexed access inside - OK, narrows +interface BB { + "a": number; +>"a" : number +> : ^^^^^^ + + "b": string; +>"b" : string +> : ^^^^^^ +} + +interface AA { + "c": BB[T]; +>"c" : BB[T] +> : ^^^^^ + + "d": boolean, +>"d" : boolean +> : ^^^^^^^ +} + +function reduction>(x: T, y: U): AA[U] { +>reduction : >(x: T, y: U) => AA[U] +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>x : T +> : ^ +>y : U +> : ^ + + if (x === "a" && y === "c") { +>x === "a" && y === "c" : boolean +> : ^^^^^^^ +>x === "a" : boolean +> : ^^^^^^^ +>x : T +> : ^ +>"a" : "a" +> : ^^^ +>y === "c" : boolean +> : ^^^^^^^ +>y : U +> : ^ +>"c" : "c" +> : ^^^ + + return 0; // Ok +>0 : 0 +> : ^ + } + + return undefined as never; +>undefined as never : never +> : ^^^^^ +>undefined : undefined +> : ^^^^^^^^^ +} + +// Conditional with indexed access inside - OK, narrows +function fun5(x: T, y: U): T extends 1 ? BB[U] : T extends 2 ? boolean : never { +>fun5 : (x: T, y: U) => T extends 1 ? BB[U] : T extends 2 ? boolean : never +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>x : T +> : ^ +>y : U +> : ^ + + if (x === 1) { +>x === 1 : boolean +> : ^^^^^^^ +>x : T +> : ^ +>1 : 1 +> : ^ + + if (y === "a") { +>y === "a" : boolean +> : ^^^^^^^ +>y : U +> : ^ +>"a" : "a" +> : ^^^ + + return 0; +>0 : 0 +> : ^ + } + return ""; +>"" : "" +> : ^^ + } + return true; +>true : true +> : ^^^^ +} + +// `this` type parameter - Doesn't narrow +abstract class SomeClass { +>SomeClass : SomeClass +> : ^^^^^^^^^ + + fun3(this: Sub1 | Sub2): this extends Sub1 ? 1 : this extends Sub2 ? 2 : never { +>fun3 : (this: Sub1 | Sub2) => this extends Sub1 ? 1 : this extends Sub2 ? 2 : never +> : ^ ^^ ^^^^^ +>this : Sub1 | Sub2 +> : ^^^^^^^^^^^ + + if (this instanceof Sub1) { +>this instanceof Sub1 : boolean +> : ^^^^^^^ +>this : Sub1 | Sub2 +> : ^^^^^^^^^^^ +>Sub1 : typeof Sub1 +> : ^^^^^^^^^^^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ + } +} +class Sub1 extends SomeClass { +>Sub1 : Sub1 +> : ^^^^ +>SomeClass : SomeClass +> : ^^^^^^^^^ + + #sub1!: symbol; +>#sub1 : symbol +> : ^^^^^^ + +}; +class Sub2 extends SomeClass { +>Sub2 : Sub2 +> : ^^^^ +>SomeClass : SomeClass +> : ^^^^^^^^^ + + #sub2!: symbol; +>#sub2 : symbol +> : ^^^^^^ + +}; + +// Detection of type parameter reference in presence of typeof +function fun2(x: T, y: typeof x): T extends true ? 1 : T extends false ? 2 : never { +>fun2 : (x: T, y: typeof x) => T extends true ? 1 : T extends false ? 2 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>x : T +> : ^ +>y : T +> : ^ +>x : T +> : ^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ + + return x ? 1 : 2; +>x ? 1 : 2 : 1 | 2 +> : ^^^^^ +>x : T +> : ^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ +} + +// Contextually-typed lambdas +const fun1: (x: T) => T extends true ? 1 : T extends false ? 2 : never = (x) => x ? 1 : 2; +>fun1 : (x: T) => T extends true ? 1 : T extends false ? 2 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>x : T +> : ^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ +>(x) => x ? 1 : 2 : (x: T) => 1 | 2 +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^^^^^ +>x : T +> : ^ +>x ? 1 : 2 : 1 | 2 +> : ^^^^^ +>x : T +> : ^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ + + +// Circular conditionals +type SomeCond = T extends true ? 1 : T extends false ? SomeCond : never; +>SomeCond : SomeCond +> : ^^^^^^^^^^^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ + +function f7(x: T): SomeCond { +>f7 : (x: T) => SomeCond +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>x : T +> : ^ + + if (x) { +>x : T +> : ^ + + return 1; +>1 : 1 +> : ^ + } + return 2; +>2 : 2 +> : ^ +} + +// Composite instantiation of conditional type +type OtherCond = T extends 1 ? "one" : T extends 2 ? "two" : T extends 3 ? "three" : T extends 4 ? "four" : never; +>OtherCond : OtherCond +> : ^^^^^^^^^^^^ + +function f8(x: U, y: V): OtherCond { +>f8 : (x: U, y: V) => OtherCond +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>x : U +> : ^ +>y : V +> : ^ + + if (x === 1 && y === 3) { +>x === 1 && y === 3 : boolean +> : ^^^^^^^ +>x === 1 : boolean +> : ^^^^^^^ +>x : U +> : ^ +>1 : 1 +> : ^ +>y === 3 : boolean +> : ^^^^^^^ +>y : V +> : ^ +>3 : 3 +> : ^ + + return "one"; +>"one" : "one" +> : ^^^^^ + } +} diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts index d640d44404465..7527d3015bc3b 100644 --- a/tests/cases/compiler/dependentReturnType1.ts +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -1,6 +1,6 @@ // @strict: true // @noEmit: true -// @target: ES2022 +// @target: esnext interface A { 1: number; diff --git a/tests/cases/compiler/dependentReturnType6.ts b/tests/cases/compiler/dependentReturnType6.ts new file mode 100644 index 0000000000000..d377e21162d50 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType6.ts @@ -0,0 +1,110 @@ +// @strict: true +// @noEmit: true +// @target: esnext + +// Tests for when return type narrowing can and cannot happen + +// @filename: file.ts +// Type parameter in outer scope +function outer(x: T): number { + return inner(); + + function inner(): T extends true ? 1 : T extends false ? 2 : never { + return x ? 1 : 2; + } +} + +// Overloads +function fun6(x: T, y: string): T extends true ? string : T extends false ? 2 : never; +function fun6(x: T, y: undefined): T extends true ? 1 : T extends false ? 2 : never; +function fun6(x: boolean): 1 | 2 | string; +function fun6(x: T, y?: string): T extends true ? 1 | string : T extends false ? 2 : never { + return x ? y !== undefined ? y : 1 : 2; +} + +// Indexed access with conditional inside - DOESN'T NARROW the nested conditional type +interface SomeInterface { + prop1: T extends 1 ? true : T extends 2 ? false : never; + prop2: T extends true ? 1 : T extends false ? 2 : never; +} + +function fun4>(x: T, y: U): SomeInterface[U] { + if (y === "prop1") { + return x === 1 ? true : false; + } + return x ? 1 : 2; +} + +// Indexed access with indexed access inside - OK, narrows +interface BB { + "a": number; + "b": string; +} + +interface AA { + "c": BB[T]; + "d": boolean, +} + +function reduction>(x: T, y: U): AA[U] { + if (x === "a" && y === "c") { + return 0; // Ok + } + + return undefined as never; +} + +// Conditional with indexed access inside - OK, narrows +function fun5(x: T, y: U): T extends 1 ? BB[U] : T extends 2 ? boolean : never { + if (x === 1) { + if (y === "a") { + return 0; + } + return ""; + } + return true; +} + +// `this` type parameter - Doesn't narrow +abstract class SomeClass { + fun3(this: Sub1 | Sub2): this extends Sub1 ? 1 : this extends Sub2 ? 2 : never { + if (this instanceof Sub1) { + return 1; + } + return 2; + } +} +class Sub1 extends SomeClass { + #sub1!: symbol; +}; +class Sub2 extends SomeClass { + #sub2!: symbol; +}; + +// Detection of type parameter reference in presence of typeof +function fun2(x: T, y: typeof x): T extends true ? 1 : T extends false ? 2 : never { + return x ? 1 : 2; +} + +// Contextually-typed lambdas +const fun1: (x: T) => T extends true ? 1 : T extends false ? 2 : never = (x) => x ? 1 : 2; + + +// Circular conditionals +type SomeCond = T extends true ? 1 : T extends false ? SomeCond : never; + +function f7(x: T): SomeCond { + if (x) { + return 1; + } + return 2; +} + +// Composite instantiation of conditional type +type OtherCond = T extends 1 ? "one" : T extends 2 ? "two" : T extends 3 ? "three" : T extends 4 ? "four" : never; + +function f8(x: U, y: V): OtherCond { + if (x === 1 && y === 3) { + return "one"; + } +} \ No newline at end of file From 42f7a2d07f0dc48415f2a1f31f1a256b598a0ddd Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 12 Sep 2024 19:34:48 -0700 Subject: [PATCH 72/90] remove unused --- src/compiler/checker.ts | 200 ---------------------------------------- 1 file changed, 200 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 37843034194a6..10617a7a9712e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19309,72 +19309,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return links.resolvedType; } - function getNarrowConditionalType(type: ConditionalType, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type { - let root: ConditionalRoot = type.root; - let result; - - // We loop here for an immediately nested conditional type in the false position, effectively treating - // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for - // purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of - // another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive - // cases we increment the tail recursion counter and stop after 1000 iterations. - while (true) { - // We instantiate a distributive checkType with the narrow mapper - const checkTypeVariable = getActualTypeVariable(root.checkType); - const narrowableCheckTypeVariable = getNarrowableCheckTypeVariable(root, mapper); - const checkType = narrowableCheckTypeVariable ? - getMappedType(narrowableCheckTypeVariable, narrowMapper) : - instantiateType(checkTypeVariable, mapper); - const extendsType = instantiateType(root.extendsType, mapper); - if (checkType === errorType || extendsType === errorType) { - return errorType; - } - if (checkType === wildcardType || extendsType === wildcardType) { - return wildcardType; - } - // When the check and extends types are simple tuple types of the same arity, we defer resolution of the - // conditional type when any tuple elements are generic. This is such that non-distributable conditional - // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. - const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) && - length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements); - const checkTypeDeferred = isDeferredType(checkType, checkTuples); - // We attempt to resolve the conditional type only when the check and extends types are non-generic - if (!checkTypeDeferred && !isDeferredType(extendsType, checkTuples)) { - // Return falseType for a definitely false extends check. We check an instantiation of the two - // types with type parameters mapped to the wildcard type, the most permissive instantiations - // possible (the wildcard type is assignable to and from all types). If those are not related, - // then no instantiations will be and we can just return the false branch type. - if (!(extendsType.flags & TypeFlags.AnyOrUnknown) && !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(extendsType))) { - Debug.assert(!(checkType.flags & TypeFlags.Any)); - // If falseType is an immediately nested conditional type that has an - // identical checkType, switch to that type and loop. - const falseType = getTypeFromTypeNode(root.node.falseType); - if (falseType.flags & TypeFlags.Conditional) { - const newRoot = (falseType as ConditionalType).root; - if (newRoot.node.parent === root.node && getNarrowableCheckTypeVariable(newRoot, mapper) === narrowableCheckTypeVariable) { - root = newRoot; - continue; - } - } - } - // Return trueType for a definitely true extends check. We check instantiations of the two - // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter - // that has no constraint. This ensures that, for example, the type - // type Foo = T extends { x: string } ? string : number - // doesn't immediately resolve to 'string' instead of being deferred. - else if (extendsType.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { - const trueType = getTypeFromTypeNode(root.node.trueType); - result = instantiateNarrowType(trueType, narrowMapper, mapper); - break; - } - } - // Return the original type for a check that is not definitely true and could not be narrowed - result = type; - break; - } - return result; - } - function getTypeFromInferTypeNode(node: InferTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { @@ -20536,140 +20470,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } - /** - * This is similar to `instantiateType`, but with behavior specific to narrowing - * a type based on control flow narrowing of expressions that have type parameter types. - */ - function instantiateNarrowType(type: Type, narrowMapper: TypeMapper, mapper: TypeMapper | undefined): Type { - if (!couldContainTypeVariables(type)) { - return type; - } - if (instantiationDepth === 100 || instantiationCount >= 5000000) { - // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement - // or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types - // that perpetually generate new type identities, so we stop the recursion here by yielding the error type. - tracing?.instant(tracing.Phase.CheckTypes, "instantiateNarrowType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); - error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); - return errorType; - } - totalInstantiationCount++; - instantiationCount++; - instantiationDepth++; - const result = instantiateNarrowTypeWorker(type, narrowMapper, mapper); - instantiationDepth--; - return result; - } - - /** - * @param narrowMapper special mapper that has mappings originating from type parameter narrowing, - * and should only be used for instantiation in some places. - * @param mapper the usual mapper that should be used for all instantiations - */ - function instantiateNarrowTypeWorker( - type: Type, - narrowMapper: TypeMapper, - mapper: TypeMapper | undefined, - ): Type { - type = instantiateType(type, mapper); - const flags = type.flags; - if (flags & TypeFlags.IndexedAccess) { - const objectType = instantiateNarrowType((type as IndexedAccessType).objectType, narrowMapper, mapper); - let indexType = instantiateNarrowType((type as IndexedAccessType).indexType, narrowMapper, mapper); - let accessFlags = (type as IndexedAccessType).accessFlags; - if (indexType.flags & TypeFlags.TypeParameter) { - indexType = getMappedType(indexType, narrowMapper); - // If we're narrowing the index type, we need to get the write type, - // i.e. intersecting the results of distributing the indexed access over a union index. - accessFlags |= AccessFlags.Writing; - } - // >> NOTE: this possibly recurs forever; how do we break this recursion? is the below enough? - if (indexType === (type as IndexedAccessType).indexType && objectType === (type as IndexedAccessType).objectType) { - return type; // No type reduction or narrowing happened; so don't do anything else to avoid infinite recursion - } - const result = getIndexedAccessType( - objectType, - indexType, - accessFlags, - ); - // >> NOTE: We need to detect if result is different from just putting the already resolved types together - return instantiateNarrowType(result, narrowMapper, mapper); - } - if (flags & TypeFlags.Conditional) { - return getNarrowConditionalTypeInstantiation( - type as ConditionalType, - narrowMapper, - mapper ? combineTypeMappers((type as ConditionalType).mapper, mapper) : (type as ConditionalType).mapper, - ); - } - - return type; - } - - function getNarrowableCheckTypeVariable(root: ConditionalRoot, mapper: TypeMapper | undefined): TypeParameter | undefined { - if (!root.isDistributive || root.inferTypeParameters) { - return; - } - const checkType = mapper ? getMappedType(root.checkType, mapper) : root.checkType; - const variable = getActualTypeVariable(checkType); - if (variable.flags & TypeFlags.TypeParameter) { - return variable as TypeParameter; - } - } - - function getNarrowConditionalTypeInstantiation( - type: ConditionalType, - narrowMapper: TypeMapper, - mapper: TypeMapper | undefined, - ): Type { - const root = type.root; - if (root.outerTypeParameters) { - // We are instantiating a conditional type that has one or more type parameters in scope. Apply the - // mapper to the type parameters to produce the effective list of type arguments, and compute the - // instantiation cache key from the type IDs of the type arguments. - const typeArguments = mapper ? map(root.outerTypeParameters, t => getMappedType(t, mapper)) : root.outerTypeParameters; - // >> No caching yet - const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); - const checkTypeVariable = getNarrowableCheckTypeVariable(root, newMapper); - const distributionType = checkTypeVariable ? getReducedType(getMappedType(checkTypeVariable, narrowMapper)) : undefined; - // Distributive conditional types are distributed over union types. For example, when the - // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the - // result is (A extends U ? X : Y) | (B extends U ? X : Y). - if (distributionType && checkTypeVariable !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { - return getNarrowDistributedConditionalType(type, distributionType, checkTypeVariable!, narrowMapper, newMapper); - } - else { - return getNarrowConditionalType(type, narrowMapper, newMapper); - } - } - return type; - } - - // `distributionType` should be a union type (or never). - function getNarrowDistributedConditionalType( - type: ConditionalType, - distributionType: Type, - checkTypeVariable: TypeParameter, - narrowMapper: TypeMapper, - mapper: TypeMapper, - ): Type { - if (distributionType.flags & TypeFlags.Never) { - return distributionType; - } - if (distributionType.flags & TypeFlags.Union) { - const mappedTypes: Type[] = []; - for (const t of (distributionType as UnionType).types) { - const result = getNarrowConditionalType(type, prependTypeMapping(checkTypeVariable, t, narrowMapper), mapper); - // If one of the component types could not be narrowed, then don't narrow the whole type - if (result === type) { - return type; - } - mappedTypes.push(result); - } - return getIntersectionType(mappedTypes); - } - return getNarrowConditionalType(type, narrowMapper, mapper); - } - function isGenericIndexedOrConditionalReturnType(type: Type): type is IndexedAccessType | ConditionalType { return !!(type.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional) && couldContainTypeVariables(type)); } From 7e1fe01f6b76bec603ff9b32adadbcacd24f41d8 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 13 Sep 2024 13:48:29 -0700 Subject: [PATCH 73/90] update baselines --- .../reference/dependentReturnType1.errors.txt | 12 ++++++------ .../reference/dependentReturnType6.errors.txt | 8 +------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/tests/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt index c0b297f03e3de..4513ccb75cf29 100644 --- a/tests/baselines/reference/dependentReturnType1.errors.txt +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -1,8 +1,8 @@ dependentReturnType1.ts(11,9): error TS2322: Type 'number' is not assignable to type 'A[T]'. Type 'number' is not assignable to type 'string'. dependentReturnType1.ts(26,9): error TS2322: Type '""' is not assignable to type 'C[T]'. - Type 'string' is not assignable to type 'never'. -dependentReturnType1.ts(35,9): error TS2322: Type 'string' is not assignable to type 'never'. + Type '""' is not assignable to type 'never'. +dependentReturnType1.ts(35,9): error TS2322: Type '""' is not assignable to type 'never'. dependentReturnType1.ts(69,9): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. dependentReturnType1.ts(71,5): error TS2322: Type '{ a: "a"; b: "b"; c: "c"; d: "d"; e: "e"; f: "f"; g: "g"; }' is not assignable to type 'T extends 1 ? One : T extends 2 ? Two : T extends 3 ? Three : Four'. dependentReturnType1.ts(80,22): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. @@ -41,7 +41,7 @@ dependentReturnType1.ts(470,13): error TS2322: Type 'R' is not assignable to typ dependentReturnType1.ts(472,13): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. dependentReturnType1.ts(474,13): error TS2322: Type 'T' is not assignable to type 'ConditionalReturnType'. dependentReturnType1.ts(488,9): error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. -dependentReturnType1.ts(514,5): error TS2322: Type 'number' is not assignable to type 'never'. +dependentReturnType1.ts(514,5): error TS2322: Type '1' is not assignable to type 'never'. ==== dependentReturnType1.ts (39 errors) ==== @@ -76,7 +76,7 @@ dependentReturnType1.ts(514,5): error TS2322: Type 'number' is not assignable to return ""; // Error, returned expression needs to have type string & boolean (= never) ~~~~~~ !!! error TS2322: Type '""' is not assignable to type 'C[T]'. -!!! error TS2322: Type 'string' is not assignable to type 'never'. +!!! error TS2322: Type '""' is not assignable to type 'never'. } } @@ -87,7 +87,7 @@ dependentReturnType1.ts(514,5): error TS2322: Type 'number' is not assignable to else { return ""; // Error, returned expression needs to have type string & boolean (= never) ~~~~~~ -!!! error TS2322: Type 'string' is not assignable to type 'never'. +!!! error TS2322: Type '""' is not assignable to type 'never'. } } @@ -641,5 +641,5 @@ dependentReturnType1.ts(514,5): error TS2322: Type 'number' is not assignable to } return 1; ~~~~~~ -!!! error TS2322: Type 'number' is not assignable to type 'never'. +!!! error TS2322: Type '1' is not assignable to type 'never'. } \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType6.errors.txt b/tests/baselines/reference/dependentReturnType6.errors.txt index 552b4e79e5455..550500ed01466 100644 --- a/tests/baselines/reference/dependentReturnType6.errors.txt +++ b/tests/baselines/reference/dependentReturnType6.errors.txt @@ -8,8 +8,6 @@ file.ts(28,20): error TS2322: Type '2' is not assignable to type 'SomeInterface< Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. file.ts(65,13): error TS2322: Type '1' is not assignable to type 'this extends Sub1 ? 1 : this extends Sub2 ? 2 : never'. file.ts(67,9): error TS2322: Type '2' is not assignable to type 'this extends Sub1 ? 1 : this extends Sub2 ? 2 : never'. -file.ts(71,5): error TS18028: Private identifiers are only available when targeting ECMAScript 2015 and higher. -file.ts(74,5): error TS18028: Private identifiers are only available when targeting ECMAScript 2015 and higher. file.ts(79,16): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. file.ts(79,20): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. file.ts(83,100): error TS2322: Type '1 | 2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. @@ -21,7 +19,7 @@ file.ts(99,60): error TS2366: Function lacks ending return statement and return file.ts(101,9): error TS2322: Type '"one"' is not assignable to type 'OtherCond | OtherCond'. -==== file.ts (16 errors) ==== +==== file.ts (14 errors) ==== // Type parameter in outer scope function outer(x: T): number { return inner(); @@ -109,13 +107,9 @@ file.ts(101,9): error TS2322: Type '"one"' is not assignable to type 'OtherCond< } class Sub1 extends SomeClass { #sub1!: symbol; - ~~~~~ -!!! error TS18028: Private identifiers are only available when targeting ECMAScript 2015 and higher. }; class Sub2 extends SomeClass { #sub2!: symbol; - ~~~~~ -!!! error TS18028: Private identifiers are only available when targeting ECMAScript 2015 and higher. }; // Detection of type parameter reference in presence of typeof From bbaf88f900a31237ed1e0362c327f181ea34d19b Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 4 Oct 2024 15:35:07 -0700 Subject: [PATCH 74/90] refactor and fix validation of conditional return type --- src/compiler/checker.ts | 150 ++++++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 66 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ae084ccde8f26..0533717f4d376 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2367,6 +2367,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { [".jsx", ".jsx"], [".json", ".json"], ]; + + var narrowableReturnTypeCache = new Map; /* eslint-enable no-var */ initializeTypeChecker(); @@ -19266,6 +19268,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { forConstraint: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], + forNarrowing?: boolean, ): Type { let result; let extraTypes: Type[] | undefined; @@ -19288,7 +19291,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (checkType === wildcardType || extendsType === wildcardType) { return wildcardType; } - const effectiveCheckType = isNarrowingSubstitutionType(checkType) ? (checkType as SubstitutionType).constraint : checkType; + const effectiveCheckType = forNarrowing && isNarrowingSubstitutionType(checkType) + ? (checkType as SubstitutionType).constraint + : checkType; const checkTypeNode = skipTypeParentheses(root.node.checkType); const extendsTypeNode = skipTypeParentheses(root.node.extendsType); // When the check and extends types are simple tuple types of the same arity, we defer resolution of the @@ -20498,19 +20503,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!result) { const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); const checkType = root.checkType; + let distributionType = root.isDistributive ? getReducedType(getMappedType(checkType, newMapper)) : undefined; let narrowingBaseType: Type | undefined; - let mappedCheckType = root.isDistributive ? getReducedType(getMappedType(checkType, newMapper)) : undefined; - if (mappedCheckType && isNarrowingSubstitutionType(mappedCheckType)) { - narrowingBaseType = (mappedCheckType as SubstitutionType).baseType; - mappedCheckType = getReducedType((mappedCheckType as SubstitutionType).constraint); + const forNarrowing = distributionType && isNarrowingSubstitutionType(distributionType) && isNarrowableConditionalTypeWorker(type); + if (forNarrowing) { + narrowingBaseType = (distributionType as SubstitutionType).baseType; + distributionType = getReducedType((distributionType as SubstitutionType).constraint); } - const distributionType = root.isDistributive ? mappedCheckType : undefined; // Distributive conditional types are distributed over union types. For example, when the // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the // result is (A extends U ? X : Y) | (B extends U ? X : Y). if (distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { const mapperCallback = narrowingBaseType ? - (t: Type) => getConditionalType(root, prependTypeMapping(checkType, getSubstitutionType(narrowingBaseType, t, /*isNarrowed*/ true), newMapper), forConstraint) : + (t: Type) => getConditionalType( + root, + prependTypeMapping(checkType, getSubstitutionType(narrowingBaseType, t, /*isNarrowed*/ true), newMapper), + forConstraint, + /*aliasSymbol*/ undefined, + /*aliasTypeArguments*/ undefined, + forNarrowing, + ) : (t: Type) => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint); if (narrowingBaseType) { result = mapType(distributionType, mapperCallback, /*noReductions*/ undefined, /*toIntersection*/ true); @@ -20520,7 +20532,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else { - result = getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments); + result = getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments, forNarrowing); } root.instantiations!.set(id, result); } @@ -45763,10 +45775,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const allTypeParameters = appendTypeParameters(getOuterTypeParameters(container, /*includeThisTypes*/ false), getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); const narrowableTypeParameters = allTypeParameters && getNarrowableTypeParameters(allTypeParameters); + // >> TODO: another optimization would be to check if any of the narrowable type parameters + // match the types in the return type that can be narrowed if ( !narrowableTypeParameters || !narrowableTypeParameters.length || - !isNarrowableReturnType(narrowableTypeParameters.map(trio => trio[0]), unwrappedReturnType) + !isNarrowableReturnType(unwrappedReturnType) ) { checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); return; @@ -45926,66 +45940,70 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // A narrowable indexed access type is one that has the shape `A[T]`, // where `T` is a narrowable type parameter. + function isNarrowableReturnType(returnType: IndexedAccessType | ConditionalType): boolean { + return isConditionalType(returnType) + ? isNarrowableConditionalType(returnType) + : !!(returnType.indexType.flags & TypeFlags.TypeParameter); + } + + function isNarrowableConditionalType(type: ConditionalType): boolean { + let result = narrowableReturnTypeCache.get(type.id); + if (result === undefined) { + result = isNarrowableConditionalTypeWorker(type); + narrowableReturnTypeCache.set(type.id, result); + } + return result; + } + // A narrowable conditional type is one that has the following shape: - // `T extends A ? TrueBranch : FalseBranch`, such that: - // (0) The conditional type's check type is a narrowable type parameter; - // (1) `A` is a type belonging to the constraint of the type parameter, - // or a union of types belonging to the constraint of the type parameter; - // (2) There are no `infer` type parameters in the conditional type; - // (3) `TrueBranch` and `FalseBranch` must be valid, recursively; - // In particular, the false-most branch of the conditional type must be `never`. - function isNarrowableReturnType( - typeParameters: TypeParameter[], - returnType: IndexedAccessType | ConditionalType, - ): boolean { - return !isConditionalType(returnType) - && typeParameters.includes(returnType.indexType) - || isNarrowableConditionalType(returnType, /*branch*/ undefined); - // `branch` can be `true` if `type` is the true type of a conditional, `false` if it's the false type of a conditional, - // and `undefined` if neither. - function isNarrowableConditionalType(type: Type, branch: boolean | undefined): boolean { - if (!isConditionalType(type)) { - // This is type `R` in `T extends A ? R : ...` - if (branch === true) { - return true; - } - // This is type `never` in `T extends A ? R : never` - if (branch === false) { - return type === neverType; - } - return false; - } - // (0) - if (!(type.checkType.flags & TypeFlags.TypeParameter)) { - return false; - } - const typeParameter = typeParameters.find(tp => tp === type.checkType); - if (!typeParameter) { - return false; - } - const constraintType = getConstraintOfTypeParameter(typeParameter) as UnionType; - // (0) - if (!type.root.isDistributive) { - return false; - } - // (2) - if (type.root.inferTypeParameters?.length) { - return false; - } - // (1) - if ( - !everyType(type.extendsType, extendsType => - some( - constraintType.types, - constraintType => isTypeIdenticalTo(constraintType, extendsType), - )) - ) { - return false; - } + // `T extends A ? TrueBranch : FalseBranch`, in other words: + // (0) The conditional type is distributive; + // (1) The conditional type has no `infer` type parameters; + // (2) The conditional type's check type is a narrowable type parameter (i.e. a type parameter with a union constraint); + // (3) The extends type `A` is a type or a union of types belonging to the union constraint of the type parameter; + // (4) `TrueBranch` and `FalseBranch` must be valid, recursively. + // In particular, the false-most branch of the conditional type must be `never`. + function isNarrowableConditionalTypeWorker(type: ConditionalType): boolean { + // (0) + if (!type.root.isDistributive) { + return false; + } + // (1) + if (type.root.inferTypeParameters) { + return false; + } + + // (2) + if (!(type.checkType.flags & TypeFlags.TypeParameter)) { + return false; + } - return isNarrowableConditionalType(getTrueTypeFromConditionalType(type), /*branch*/ true) && - isNarrowableConditionalType(getFalseTypeFromConditionalType(type), /*branch*/ false); + // (2) + const constraintType = getConstraintOfTypeParameter(type.checkType as TypeParameter); + if (!constraintType || !(constraintType.flags & TypeFlags.Union)) { + return false; + } + // (3) + if ( + !everyType(type.extendsType, extendsType => + some( + (constraintType as UnionType).types, + constraintType => isTypeIdenticalTo(constraintType, extendsType), + )) + ) { + return false; } + + // (4) + const trueType = getTrueTypeFromConditionalType(type); + const falseType = getFalseTypeFromConditionalType(type); + const isValidTrueType = isConditionalType(trueType) + ? isNarrowableConditionalTypeWorker(trueType) + : true; + const isValidFalseType = isConditionalType(falseType) + ? isNarrowableConditionalTypeWorker(falseType) + : falseType === neverType; + return isValidTrueType && isValidFalseType; } function isConditionalType(type: Type): type is ConditionalType { From 47d8d3f929a21f0755ce9fbf6a8d0f7d63571d05 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 4 Oct 2024 15:52:49 -0700 Subject: [PATCH 75/90] fmt --- src/compiler/checker.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 57ec0057c79b5..604f3d6a47409 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2370,7 +2370,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { [".json", ".json"], ]; - var narrowableReturnTypeCache = new Map; + var narrowableReturnTypeCache = new Map(); /* eslint-enable no-var */ initializeTypeChecker(); @@ -20316,14 +20316,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // result is (A extends U ? X : Y) | (B extends U ? X : Y). if (distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { const mapperCallback = narrowingBaseType ? - (t: Type) => getConditionalType( - root, - prependTypeMapping(checkType, getSubstitutionType(narrowingBaseType, t, /*isNarrowed*/ true), newMapper), - forConstraint, - /*aliasSymbol*/ undefined, - /*aliasTypeArguments*/ undefined, - forNarrowing, - ) : + (t: Type) => + getConditionalType( + root, + prependTypeMapping(checkType, getSubstitutionType(narrowingBaseType, t, /*isNarrowed*/ true), newMapper), + forConstraint, + /*aliasSymbol*/ undefined, + /*aliasTypeArguments*/ undefined, + forNarrowing, + ) : (t: Type) => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint); if (narrowingBaseType) { result = mapType(distributionType, mapperCallback, /*noReductions*/ undefined, /*toIntersection*/ true); @@ -45807,9 +45808,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!typeNode) continue; if (isTypeParameterReferenced(typeParam, typeNode)) { let candidateReference; - if (isTypeReferenceNode(typeNode) && + if ( + isTypeReferenceNode(typeNode) && isReferenceToTypeParameter(typeParam, typeNode) && - (candidateReference = getValidParameterReference(paramDecl, constraint))) { + (candidateReference = getValidParameterReference(paramDecl, constraint)) + ) { if (reference) { hasInvalidReference = true; break; From 228148d5d044fcef4752ccbd3baac93cf1be567e Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 4 Oct 2024 16:01:02 -0700 Subject: [PATCH 76/90] cache intermediate results for conditional validation --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 604f3d6a47409..7851db9e2682e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45920,10 +45920,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const trueType = getTrueTypeFromConditionalType(type); const falseType = getFalseTypeFromConditionalType(type); const isValidTrueType = isConditionalType(trueType) - ? isNarrowableConditionalTypeWorker(trueType) + ? isNarrowableConditionalType(trueType) : true; const isValidFalseType = isConditionalType(falseType) - ? isNarrowableConditionalTypeWorker(falseType) + ? isNarrowableConditionalType(falseType) : falseType === neverType; return isValidTrueType && isValidFalseType; } From 76718d8755052f30d4f794e5d3a4c454e962b75d Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 7 Oct 2024 12:00:39 -0700 Subject: [PATCH 77/90] take mapper into consideration when validating conditional type --- src/compiler/checker.ts | 25 +- .../reference/dependentReturnType6.errors.txt | 82 +++-- .../reference/dependentReturnType6.symbols | 322 +++++++++++------- .../reference/dependentReturnType6.types | 101 +++++- tests/cases/compiler/dependentReturnType6.ts | 35 +- 5 files changed, 400 insertions(+), 165 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7851db9e2682e..42252a394fa7e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2370,7 +2370,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { [".json", ".json"], ]; - var narrowableReturnTypeCache = new Map(); + var narrowableReturnTypeCache = new Map(); /* eslint-enable no-var */ initializeTypeChecker(); @@ -20306,7 +20306,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const checkType = root.checkType; let distributionType = root.isDistributive ? getReducedType(getMappedType(checkType, newMapper)) : undefined; let narrowingBaseType: Type | undefined; - const forNarrowing = distributionType && isNarrowingSubstitutionType(distributionType) && isNarrowableConditionalTypeWorker(type); + const forNarrowing = distributionType && isNarrowingSubstitutionType(distributionType) && isNarrowableConditionalType(type, mapper); if (forNarrowing) { narrowingBaseType = (distributionType as SubstitutionType).baseType; distributionType = getReducedType((distributionType as SubstitutionType).constraint); @@ -45868,11 +45868,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { : !!(returnType.indexType.flags & TypeFlags.TypeParameter); } - function isNarrowableConditionalType(type: ConditionalType): boolean { - let result = narrowableReturnTypeCache.get(type.id); + function isNarrowableConditionalType(type: ConditionalType, mapper?: TypeMapper): boolean { + const typeArguments = mapper && map(type.root.outerTypeParameters, t => { + const mapped = getMappedType(t, mapper); + if (isNarrowingSubstitutionType(mapped)) { + return (mapped as SubstitutionType).baseType; + } + return mapped; + }); + const id = `${type.id}:${getTypeListId(typeArguments)}`; + let result = narrowableReturnTypeCache.get(id); if (result === undefined) { - result = isNarrowableConditionalTypeWorker(type); - narrowableReturnTypeCache.set(type.id, result); + const nonNarrowingMapper = type.root.outerTypeParameters + && typeArguments + && createTypeMapper(type.root.outerTypeParameters, typeArguments); + const instantiatedType = instantiateType(type, nonNarrowingMapper); + result = isConditionalType(instantiatedType) && isNarrowableConditionalTypeWorker(instantiatedType); + narrowableReturnTypeCache.set(id, result); } return result; } @@ -45905,6 +45917,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!constraintType || !(constraintType.flags & TypeFlags.Union)) { return false; } + // (3) if ( !everyType(type.extendsType, extendsType => diff --git a/tests/baselines/reference/dependentReturnType6.errors.txt b/tests/baselines/reference/dependentReturnType6.errors.txt index 550500ed01466..638c23268ad80 100644 --- a/tests/baselines/reference/dependentReturnType6.errors.txt +++ b/tests/baselines/reference/dependentReturnType6.errors.txt @@ -1,25 +1,27 @@ -file.ts(26,26): error TS2322: Type 'true' is not assignable to type 'SomeInterface[U]'. +file.ts(28,26): error TS2322: Type 'true' is not assignable to type 'SomeInterfaceBad[U]'. Type 'true' is not assignable to type 'T extends 1 ? true : T extends 2 ? false : never'. -file.ts(26,33): error TS2322: Type 'false' is not assignable to type 'SomeInterface[U]'. +file.ts(28,33): error TS2322: Type 'false' is not assignable to type 'SomeInterfaceBad[U]'. Type 'false' is not assignable to type 'T extends 1 ? true : T extends 2 ? false : never'. -file.ts(28,16): error TS2322: Type '1' is not assignable to type 'SomeInterface[U]'. +file.ts(30,16): error TS2322: Type '1' is not assignable to type 'SomeInterfaceBad[U]'. Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. -file.ts(28,20): error TS2322: Type '2' is not assignable to type 'SomeInterface[U]'. +file.ts(30,20): error TS2322: Type '2' is not assignable to type 'SomeInterfaceBad[U]'. Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. -file.ts(65,13): error TS2322: Type '1' is not assignable to type 'this extends Sub1 ? 1 : this extends Sub2 ? 2 : never'. -file.ts(67,9): error TS2322: Type '2' is not assignable to type 'this extends Sub1 ? 1 : this extends Sub2 ? 2 : never'. -file.ts(79,16): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. -file.ts(79,20): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. -file.ts(83,100): error TS2322: Type '1 | 2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +file.ts(80,13): error TS2322: Type '1' is not assignable to type 'this extends Sub1 ? 1 : this extends Sub2 ? 2 : never'. +file.ts(82,9): error TS2322: Type '2' is not assignable to type 'this extends Sub1 ? 1 : this extends Sub2 ? 2 : never'. +file.ts(94,16): error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +file.ts(94,20): error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. +file.ts(98,100): error TS2322: Type '1 | 2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. -file.ts(91,9): error TS2322: Type 'number' is not assignable to type 'SomeCond'. -file.ts(91,9): error TS2589: Type instantiation is excessively deep and possibly infinite. -file.ts(93,5): error TS2322: Type 'number' is not assignable to type 'SomeCond'. -file.ts(99,60): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. -file.ts(101,9): error TS2322: Type '"one"' is not assignable to type 'OtherCond | OtherCond'. +file.ts(106,9): error TS2322: Type 'number' is not assignable to type 'SomeCond'. +file.ts(106,9): error TS2589: Type instantiation is excessively deep and possibly infinite. +file.ts(108,5): error TS2322: Type 'number' is not assignable to type 'SomeCond'. +file.ts(114,60): error TS2366: Function lacks ending return statement and return type does not include 'undefined'. +file.ts(116,9): error TS2322: Type '"one"' is not assignable to type 'OtherCond | OtherCond'. +file.ts(126,9): error TS2322: Type '"a"' is not assignable to type 'T extends (infer P)[] ? P : T extends number ? undefined : never'. +file.ts(128,5): error TS2322: Type 'undefined' is not assignable to type 'T extends (infer P)[] ? P : T extends number ? undefined : never'. -==== file.ts (14 errors) ==== +==== file.ts (16 errors) ==== // Type parameter in outer scope function outer(x: T): number { return inner(); @@ -37,31 +39,46 @@ file.ts(101,9): error TS2322: Type '"one"' is not assignable to type 'OtherCond< return x ? y !== undefined ? y : 1 : 2; } - // Indexed access with conditional inside - DOESN'T NARROW the nested conditional type - interface SomeInterface { + // Indexed access with conditional inside + + // DOESN'T NARROW the nested conditional type of wrong shape + interface SomeInterfaceBad { prop1: T extends 1 ? true : T extends 2 ? false : never; prop2: T extends true ? 1 : T extends false ? 2 : never; } - function fun4>(x: T, y: U): SomeInterface[U] { + function fun4bad>(x: T, y: U): SomeInterfaceBad[U] { if (y === "prop1") { return x === 1 ? true : false; ~~~~ -!!! error TS2322: Type 'true' is not assignable to type 'SomeInterface[U]'. +!!! error TS2322: Type 'true' is not assignable to type 'SomeInterfaceBad[U]'. !!! error TS2322: Type 'true' is not assignable to type 'T extends 1 ? true : T extends 2 ? false : never'. ~~~~~ -!!! error TS2322: Type 'false' is not assignable to type 'SomeInterface[U]'. +!!! error TS2322: Type 'false' is not assignable to type 'SomeInterfaceBad[U]'. !!! error TS2322: Type 'false' is not assignable to type 'T extends 1 ? true : T extends 2 ? false : never'. } return x ? 1 : 2; ~ -!!! error TS2322: Type '1' is not assignable to type 'SomeInterface[U]'. +!!! error TS2322: Type '1' is not assignable to type 'SomeInterfaceBad[U]'. !!! error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. ~ -!!! error TS2322: Type '2' is not assignable to type 'SomeInterface[U]'. +!!! error TS2322: Type '2' is not assignable to type 'SomeInterfaceBad[U]'. !!! error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. } + // Narrows nested conditional type of right shape + interface SomeInterfaceGood { + prop1: T extends true ? 2 : T extends false ? 1 : never; + prop2: T extends true ? 1 : T extends false ? 2 : never; + } + + function fun4good>(x: T, y: U): SomeInterfaceGood[U] { + if (y === "prop1") { + return x ? 2 : 1; + } + return x ? 1 : 2; + } + // Indexed access with indexed access inside - OK, narrows interface BB { "a": number; @@ -126,7 +143,7 @@ file.ts(101,9): error TS2322: Type '"one"' is not assignable to type 'OtherCond< ~~~~~~~~~ !!! error TS2322: Type '1 | 2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. !!! error TS2322: Type '1' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. -!!! related TS6502 file.ts:83:13: The expected type comes from the return type of this signature. +!!! related TS6502 file.ts:98:13: The expected type comes from the return type of this signature. // Circular conditionals @@ -156,4 +173,21 @@ file.ts(101,9): error TS2322: Type '"one"' is not assignable to type 'OtherCond< ~~~~~~ !!! error TS2322: Type '"one"' is not assignable to type 'OtherCond | OtherCond'. } - } \ No newline at end of file + } + + // Conditionals with `infer` - will not narrow, it is not safe to infer from the narrowed type into an `infer` type parameter + function f9(x: T): T extends Array ? P : T extends number ? undefined : never { + if (Array.isArray(x)) { + // If we allowed narrowing of the conditional return type, when resolving the conditional `T & ("a"[] | "b"[]) extends Array ? P : ...`, + // we could infer `"a" | "b"` for `P`, and allow "a" to be returned. However, when calling `f10`, `T` could be instantiated with `"b"[]`, and the return type would be `"b"`, + // so allowing an `"a"` return would be unsound. + return "a"; + ~~~~~~ +!!! error TS2322: Type '"a"' is not assignable to type 'T extends (infer P)[] ? P : T extends number ? undefined : never'. + } + return undefined; + ~~~~~~ +!!! error TS2322: Type 'undefined' is not assignable to type 'T extends (infer P)[] ? P : T extends number ? undefined : never'. + } + + \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType6.symbols b/tests/baselines/reference/dependentReturnType6.symbols index 594afebec58d9..44fc28739e099 100644 --- a/tests/baselines/reference/dependentReturnType6.symbols +++ b/tests/baselines/reference/dependentReturnType6.symbols @@ -60,87 +60,128 @@ function fun6(x: T, y?: string): T extends true ? 1 | string >y : Symbol(y, Decl(file.ts, 13, 38)) } -// Indexed access with conditional inside - DOESN'T NARROW the nested conditional type -interface SomeInterface { ->SomeInterface : Symbol(SomeInterface, Decl(file.ts, 15, 1)) ->T : Symbol(T, Decl(file.ts, 18, 24)) +// Indexed access with conditional inside + +// DOESN'T NARROW the nested conditional type of wrong shape +interface SomeInterfaceBad { +>SomeInterfaceBad : Symbol(SomeInterfaceBad, Decl(file.ts, 15, 1)) +>T : Symbol(T, Decl(file.ts, 20, 27)) prop1: T extends 1 ? true : T extends 2 ? false : never; ->prop1 : Symbol(SomeInterface.prop1, Decl(file.ts, 18, 28)) ->T : Symbol(T, Decl(file.ts, 18, 24)) ->T : Symbol(T, Decl(file.ts, 18, 24)) +>prop1 : Symbol(SomeInterfaceBad.prop1, Decl(file.ts, 20, 31)) +>T : Symbol(T, Decl(file.ts, 20, 27)) +>T : Symbol(T, Decl(file.ts, 20, 27)) prop2: T extends true ? 1 : T extends false ? 2 : never; ->prop2 : Symbol(SomeInterface.prop2, Decl(file.ts, 19, 60)) ->T : Symbol(T, Decl(file.ts, 18, 24)) ->T : Symbol(T, Decl(file.ts, 18, 24)) +>prop2 : Symbol(SomeInterfaceBad.prop2, Decl(file.ts, 21, 60)) +>T : Symbol(T, Decl(file.ts, 20, 27)) +>T : Symbol(T, Decl(file.ts, 20, 27)) } -function fun4>(x: T, y: U): SomeInterface[U] { ->fun4 : Symbol(fun4, Decl(file.ts, 21, 1)) ->T : Symbol(T, Decl(file.ts, 23, 14)) ->U : Symbol(U, Decl(file.ts, 23, 16)) ->SomeInterface : Symbol(SomeInterface, Decl(file.ts, 15, 1)) ->x : Symbol(x, Decl(file.ts, 23, 57)) ->T : Symbol(T, Decl(file.ts, 23, 14)) ->y : Symbol(y, Decl(file.ts, 23, 62)) ->U : Symbol(U, Decl(file.ts, 23, 16)) ->SomeInterface : Symbol(SomeInterface, Decl(file.ts, 15, 1)) ->T : Symbol(T, Decl(file.ts, 23, 14)) ->U : Symbol(U, Decl(file.ts, 23, 16)) +function fun4bad>(x: T, y: U): SomeInterfaceBad[U] { +>fun4bad : Symbol(fun4bad, Decl(file.ts, 23, 1)) +>T : Symbol(T, Decl(file.ts, 25, 17)) +>U : Symbol(U, Decl(file.ts, 25, 19)) +>SomeInterfaceBad : Symbol(SomeInterfaceBad, Decl(file.ts, 15, 1)) +>x : Symbol(x, Decl(file.ts, 25, 63)) +>T : Symbol(T, Decl(file.ts, 25, 17)) +>y : Symbol(y, Decl(file.ts, 25, 68)) +>U : Symbol(U, Decl(file.ts, 25, 19)) +>SomeInterfaceBad : Symbol(SomeInterfaceBad, Decl(file.ts, 15, 1)) +>T : Symbol(T, Decl(file.ts, 25, 17)) +>U : Symbol(U, Decl(file.ts, 25, 19)) if (y === "prop1") { ->y : Symbol(y, Decl(file.ts, 23, 62)) +>y : Symbol(y, Decl(file.ts, 25, 68)) return x === 1 ? true : false; ->x : Symbol(x, Decl(file.ts, 23, 57)) +>x : Symbol(x, Decl(file.ts, 25, 63)) } return x ? 1 : 2; ->x : Symbol(x, Decl(file.ts, 23, 57)) +>x : Symbol(x, Decl(file.ts, 25, 63)) +} + +// Narrows nested conditional type of right shape +interface SomeInterfaceGood { +>SomeInterfaceGood : Symbol(SomeInterfaceGood, Decl(file.ts, 30, 1)) +>T : Symbol(T, Decl(file.ts, 33, 28)) + + prop1: T extends true ? 2 : T extends false ? 1 : never; +>prop1 : Symbol(SomeInterfaceGood.prop1, Decl(file.ts, 33, 32)) +>T : Symbol(T, Decl(file.ts, 33, 28)) +>T : Symbol(T, Decl(file.ts, 33, 28)) + + prop2: T extends true ? 1 : T extends false ? 2 : never; +>prop2 : Symbol(SomeInterfaceGood.prop2, Decl(file.ts, 34, 60)) +>T : Symbol(T, Decl(file.ts, 33, 28)) +>T : Symbol(T, Decl(file.ts, 33, 28)) +} + +function fun4good>(x: T, y: U): SomeInterfaceGood[U] { +>fun4good : Symbol(fun4good, Decl(file.ts, 36, 1)) +>T : Symbol(T, Decl(file.ts, 38, 18)) +>U : Symbol(U, Decl(file.ts, 38, 36)) +>SomeInterfaceGood : Symbol(SomeInterfaceGood, Decl(file.ts, 30, 1)) +>x : Symbol(x, Decl(file.ts, 38, 81)) +>T : Symbol(T, Decl(file.ts, 38, 18)) +>y : Symbol(y, Decl(file.ts, 38, 86)) +>U : Symbol(U, Decl(file.ts, 38, 36)) +>SomeInterfaceGood : Symbol(SomeInterfaceGood, Decl(file.ts, 30, 1)) +>T : Symbol(T, Decl(file.ts, 38, 18)) +>U : Symbol(U, Decl(file.ts, 38, 36)) + + if (y === "prop1") { +>y : Symbol(y, Decl(file.ts, 38, 86)) + + return x ? 2 : 1; +>x : Symbol(x, Decl(file.ts, 38, 81)) + } + return x ? 1 : 2; +>x : Symbol(x, Decl(file.ts, 38, 81)) } // Indexed access with indexed access inside - OK, narrows interface BB { ->BB : Symbol(BB, Decl(file.ts, 28, 1)) +>BB : Symbol(BB, Decl(file.ts, 43, 1)) "a": number; ->"a" : Symbol(BB["a"], Decl(file.ts, 31, 14)) +>"a" : Symbol(BB["a"], Decl(file.ts, 46, 14)) "b": string; ->"b" : Symbol(BB["b"], Decl(file.ts, 32, 16)) +>"b" : Symbol(BB["b"], Decl(file.ts, 47, 16)) } interface AA { ->AA : Symbol(AA, Decl(file.ts, 34, 1)) ->T : Symbol(T, Decl(file.ts, 36, 13)) ->BB : Symbol(BB, Decl(file.ts, 28, 1)) +>AA : Symbol(AA, Decl(file.ts, 49, 1)) +>T : Symbol(T, Decl(file.ts, 51, 13)) +>BB : Symbol(BB, Decl(file.ts, 43, 1)) "c": BB[T]; ->"c" : Symbol(AA["c"], Decl(file.ts, 36, 34)) ->BB : Symbol(BB, Decl(file.ts, 28, 1)) ->T : Symbol(T, Decl(file.ts, 36, 13)) +>"c" : Symbol(AA["c"], Decl(file.ts, 51, 34)) +>BB : Symbol(BB, Decl(file.ts, 43, 1)) +>T : Symbol(T, Decl(file.ts, 51, 13)) "d": boolean, ->"d" : Symbol(AA["d"], Decl(file.ts, 37, 15)) +>"d" : Symbol(AA["d"], Decl(file.ts, 52, 15)) } function reduction>(x: T, y: U): AA[U] { ->reduction : Symbol(reduction, Decl(file.ts, 39, 1)) ->T : Symbol(T, Decl(file.ts, 41, 19)) ->BB : Symbol(BB, Decl(file.ts, 28, 1)) ->U : Symbol(U, Decl(file.ts, 41, 38)) ->AA : Symbol(AA, Decl(file.ts, 34, 1)) ->x : Symbol(x, Decl(file.ts, 41, 64)) ->T : Symbol(T, Decl(file.ts, 41, 19)) ->y : Symbol(y, Decl(file.ts, 41, 69)) ->U : Symbol(U, Decl(file.ts, 41, 38)) ->AA : Symbol(AA, Decl(file.ts, 34, 1)) ->T : Symbol(T, Decl(file.ts, 41, 19)) ->U : Symbol(U, Decl(file.ts, 41, 38)) +>reduction : Symbol(reduction, Decl(file.ts, 54, 1)) +>T : Symbol(T, Decl(file.ts, 56, 19)) +>BB : Symbol(BB, Decl(file.ts, 43, 1)) +>U : Symbol(U, Decl(file.ts, 56, 38)) +>AA : Symbol(AA, Decl(file.ts, 49, 1)) +>x : Symbol(x, Decl(file.ts, 56, 64)) +>T : Symbol(T, Decl(file.ts, 56, 19)) +>y : Symbol(y, Decl(file.ts, 56, 69)) +>U : Symbol(U, Decl(file.ts, 56, 38)) +>AA : Symbol(AA, Decl(file.ts, 49, 1)) +>T : Symbol(T, Decl(file.ts, 56, 19)) +>U : Symbol(U, Decl(file.ts, 56, 38)) if (x === "a" && y === "c") { ->x : Symbol(x, Decl(file.ts, 41, 64)) ->y : Symbol(y, Decl(file.ts, 41, 69)) +>x : Symbol(x, Decl(file.ts, 56, 64)) +>y : Symbol(y, Decl(file.ts, 56, 69)) return 0; // Ok } @@ -151,24 +192,24 @@ function reduction>(x: T, y: U): AA< // Conditional with indexed access inside - OK, narrows function fun5(x: T, y: U): T extends 1 ? BB[U] : T extends 2 ? boolean : never { ->fun5 : Symbol(fun5, Decl(file.ts, 47, 1)) ->T : Symbol(T, Decl(file.ts, 50, 14)) ->U : Symbol(U, Decl(file.ts, 50, 30)) ->BB : Symbol(BB, Decl(file.ts, 28, 1)) ->x : Symbol(x, Decl(file.ts, 50, 51)) ->T : Symbol(T, Decl(file.ts, 50, 14)) ->y : Symbol(y, Decl(file.ts, 50, 56)) ->U : Symbol(U, Decl(file.ts, 50, 30)) ->T : Symbol(T, Decl(file.ts, 50, 14)) ->BB : Symbol(BB, Decl(file.ts, 28, 1)) ->U : Symbol(U, Decl(file.ts, 50, 30)) ->T : Symbol(T, Decl(file.ts, 50, 14)) +>fun5 : Symbol(fun5, Decl(file.ts, 62, 1)) +>T : Symbol(T, Decl(file.ts, 65, 14)) +>U : Symbol(U, Decl(file.ts, 65, 30)) +>BB : Symbol(BB, Decl(file.ts, 43, 1)) +>x : Symbol(x, Decl(file.ts, 65, 51)) +>T : Symbol(T, Decl(file.ts, 65, 14)) +>y : Symbol(y, Decl(file.ts, 65, 56)) +>U : Symbol(U, Decl(file.ts, 65, 30)) +>T : Symbol(T, Decl(file.ts, 65, 14)) +>BB : Symbol(BB, Decl(file.ts, 43, 1)) +>U : Symbol(U, Decl(file.ts, 65, 30)) +>T : Symbol(T, Decl(file.ts, 65, 14)) if (x === 1) { ->x : Symbol(x, Decl(file.ts, 50, 51)) +>x : Symbol(x, Decl(file.ts, 65, 51)) if (y === "a") { ->y : Symbol(y, Decl(file.ts, 50, 56)) +>y : Symbol(y, Decl(file.ts, 65, 56)) return 0; } @@ -179,19 +220,19 @@ function fun5(x: T, y: U): T extends 1 ? BB // `this` type parameter - Doesn't narrow abstract class SomeClass { ->SomeClass : Symbol(SomeClass, Decl(file.ts, 58, 1)) +>SomeClass : Symbol(SomeClass, Decl(file.ts, 73, 1)) fun3(this: Sub1 | Sub2): this extends Sub1 ? 1 : this extends Sub2 ? 2 : never { ->fun3 : Symbol(SomeClass.fun3, Decl(file.ts, 61, 26)) ->this : Symbol(this, Decl(file.ts, 62, 9)) ->Sub1 : Symbol(Sub1, Decl(file.ts, 68, 1)) ->Sub2 : Symbol(Sub2, Decl(file.ts, 71, 2)) ->Sub1 : Symbol(Sub1, Decl(file.ts, 68, 1)) ->Sub2 : Symbol(Sub2, Decl(file.ts, 71, 2)) +>fun3 : Symbol(SomeClass.fun3, Decl(file.ts, 76, 26)) +>this : Symbol(this, Decl(file.ts, 77, 9)) +>Sub1 : Symbol(Sub1, Decl(file.ts, 83, 1)) +>Sub2 : Symbol(Sub2, Decl(file.ts, 86, 2)) +>Sub1 : Symbol(Sub1, Decl(file.ts, 83, 1)) +>Sub2 : Symbol(Sub2, Decl(file.ts, 86, 2)) if (this instanceof Sub1) { ->this : Symbol(this, Decl(file.ts, 62, 9)) ->Sub1 : Symbol(Sub1, Decl(file.ts, 68, 1)) +>this : Symbol(this, Decl(file.ts, 77, 9)) +>Sub1 : Symbol(Sub1, Decl(file.ts, 83, 1)) return 1; } @@ -199,68 +240,68 @@ abstract class SomeClass { } } class Sub1 extends SomeClass { ->Sub1 : Symbol(Sub1, Decl(file.ts, 68, 1)) ->SomeClass : Symbol(SomeClass, Decl(file.ts, 58, 1)) +>Sub1 : Symbol(Sub1, Decl(file.ts, 83, 1)) +>SomeClass : Symbol(SomeClass, Decl(file.ts, 73, 1)) #sub1!: symbol; ->#sub1 : Symbol(Sub1.#sub1, Decl(file.ts, 69, 30)) +>#sub1 : Symbol(Sub1.#sub1, Decl(file.ts, 84, 30)) }; class Sub2 extends SomeClass { ->Sub2 : Symbol(Sub2, Decl(file.ts, 71, 2)) ->SomeClass : Symbol(SomeClass, Decl(file.ts, 58, 1)) +>Sub2 : Symbol(Sub2, Decl(file.ts, 86, 2)) +>SomeClass : Symbol(SomeClass, Decl(file.ts, 73, 1)) #sub2!: symbol; ->#sub2 : Symbol(Sub2.#sub2, Decl(file.ts, 72, 30)) +>#sub2 : Symbol(Sub2.#sub2, Decl(file.ts, 87, 30)) }; // Detection of type parameter reference in presence of typeof function fun2(x: T, y: typeof x): T extends true ? 1 : T extends false ? 2 : never { ->fun2 : Symbol(fun2, Decl(file.ts, 74, 2)) ->T : Symbol(T, Decl(file.ts, 77, 14)) ->x : Symbol(x, Decl(file.ts, 77, 33)) ->T : Symbol(T, Decl(file.ts, 77, 14)) ->y : Symbol(y, Decl(file.ts, 77, 38)) ->x : Symbol(x, Decl(file.ts, 77, 33)) ->T : Symbol(T, Decl(file.ts, 77, 14)) ->T : Symbol(T, Decl(file.ts, 77, 14)) +>fun2 : Symbol(fun2, Decl(file.ts, 89, 2)) +>T : Symbol(T, Decl(file.ts, 92, 14)) +>x : Symbol(x, Decl(file.ts, 92, 33)) +>T : Symbol(T, Decl(file.ts, 92, 14)) +>y : Symbol(y, Decl(file.ts, 92, 38)) +>x : Symbol(x, Decl(file.ts, 92, 33)) +>T : Symbol(T, Decl(file.ts, 92, 14)) +>T : Symbol(T, Decl(file.ts, 92, 14)) return x ? 1 : 2; ->x : Symbol(x, Decl(file.ts, 77, 33)) +>x : Symbol(x, Decl(file.ts, 92, 33)) } // Contextually-typed lambdas const fun1: (x: T) => T extends true ? 1 : T extends false ? 2 : never = (x) => x ? 1 : 2; ->fun1 : Symbol(fun1, Decl(file.ts, 82, 5)) ->T : Symbol(T, Decl(file.ts, 82, 13)) ->x : Symbol(x, Decl(file.ts, 82, 32)) ->T : Symbol(T, Decl(file.ts, 82, 13)) ->T : Symbol(T, Decl(file.ts, 82, 13)) ->T : Symbol(T, Decl(file.ts, 82, 13)) ->x : Symbol(x, Decl(file.ts, 82, 93)) ->x : Symbol(x, Decl(file.ts, 82, 93)) +>fun1 : Symbol(fun1, Decl(file.ts, 97, 5)) +>T : Symbol(T, Decl(file.ts, 97, 13)) +>x : Symbol(x, Decl(file.ts, 97, 32)) +>T : Symbol(T, Decl(file.ts, 97, 13)) +>T : Symbol(T, Decl(file.ts, 97, 13)) +>T : Symbol(T, Decl(file.ts, 97, 13)) +>x : Symbol(x, Decl(file.ts, 97, 93)) +>x : Symbol(x, Decl(file.ts, 97, 93)) // Circular conditionals type SomeCond = T extends true ? 1 : T extends false ? SomeCond : never; ->SomeCond : Symbol(SomeCond, Decl(file.ts, 82, 109)) ->T : Symbol(T, Decl(file.ts, 86, 14)) ->T : Symbol(T, Decl(file.ts, 86, 14)) ->T : Symbol(T, Decl(file.ts, 86, 14)) ->SomeCond : Symbol(SomeCond, Decl(file.ts, 82, 109)) ->T : Symbol(T, Decl(file.ts, 86, 14)) +>SomeCond : Symbol(SomeCond, Decl(file.ts, 97, 109)) +>T : Symbol(T, Decl(file.ts, 101, 14)) +>T : Symbol(T, Decl(file.ts, 101, 14)) +>T : Symbol(T, Decl(file.ts, 101, 14)) +>SomeCond : Symbol(SomeCond, Decl(file.ts, 97, 109)) +>T : Symbol(T, Decl(file.ts, 101, 14)) function f7(x: T): SomeCond { ->f7 : Symbol(f7, Decl(file.ts, 86, 78)) ->T : Symbol(T, Decl(file.ts, 88, 12)) ->x : Symbol(x, Decl(file.ts, 88, 31)) ->T : Symbol(T, Decl(file.ts, 88, 12)) ->SomeCond : Symbol(SomeCond, Decl(file.ts, 82, 109)) ->T : Symbol(T, Decl(file.ts, 88, 12)) +>f7 : Symbol(f7, Decl(file.ts, 101, 78)) +>T : Symbol(T, Decl(file.ts, 103, 12)) +>x : Symbol(x, Decl(file.ts, 103, 31)) +>T : Symbol(T, Decl(file.ts, 103, 12)) +>SomeCond : Symbol(SomeCond, Decl(file.ts, 97, 109)) +>T : Symbol(T, Decl(file.ts, 103, 12)) if (x) { ->x : Symbol(x, Decl(file.ts, 88, 31)) +>x : Symbol(x, Decl(file.ts, 103, 31)) return 1; } @@ -269,29 +310,58 @@ function f7(x: T): SomeCond { // Composite instantiation of conditional type type OtherCond = T extends 1 ? "one" : T extends 2 ? "two" : T extends 3 ? "three" : T extends 4 ? "four" : never; ->OtherCond : Symbol(OtherCond, Decl(file.ts, 93, 1)) ->T : Symbol(T, Decl(file.ts, 96, 15)) ->T : Symbol(T, Decl(file.ts, 96, 15)) ->T : Symbol(T, Decl(file.ts, 96, 15)) ->T : Symbol(T, Decl(file.ts, 96, 15)) ->T : Symbol(T, Decl(file.ts, 96, 15)) +>OtherCond : Symbol(OtherCond, Decl(file.ts, 108, 1)) +>T : Symbol(T, Decl(file.ts, 111, 15)) +>T : Symbol(T, Decl(file.ts, 111, 15)) +>T : Symbol(T, Decl(file.ts, 111, 15)) +>T : Symbol(T, Decl(file.ts, 111, 15)) +>T : Symbol(T, Decl(file.ts, 111, 15)) function f8(x: U, y: V): OtherCond { ->f8 : Symbol(f8, Decl(file.ts, 96, 117)) ->U : Symbol(U, Decl(file.ts, 98, 12)) ->V : Symbol(V, Decl(file.ts, 98, 28)) ->x : Symbol(x, Decl(file.ts, 98, 46)) ->U : Symbol(U, Decl(file.ts, 98, 12)) ->y : Symbol(y, Decl(file.ts, 98, 51)) ->V : Symbol(V, Decl(file.ts, 98, 28)) ->OtherCond : Symbol(OtherCond, Decl(file.ts, 93, 1)) ->U : Symbol(U, Decl(file.ts, 98, 12)) ->V : Symbol(V, Decl(file.ts, 98, 28)) +>f8 : Symbol(f8, Decl(file.ts, 111, 117)) +>U : Symbol(U, Decl(file.ts, 113, 12)) +>V : Symbol(V, Decl(file.ts, 113, 28)) +>x : Symbol(x, Decl(file.ts, 113, 46)) +>U : Symbol(U, Decl(file.ts, 113, 12)) +>y : Symbol(y, Decl(file.ts, 113, 51)) +>V : Symbol(V, Decl(file.ts, 113, 28)) +>OtherCond : Symbol(OtherCond, Decl(file.ts, 108, 1)) +>U : Symbol(U, Decl(file.ts, 113, 12)) +>V : Symbol(V, Decl(file.ts, 113, 28)) if (x === 1 && y === 3) { ->x : Symbol(x, Decl(file.ts, 98, 46)) ->y : Symbol(y, Decl(file.ts, 98, 51)) +>x : Symbol(x, Decl(file.ts, 113, 46)) +>y : Symbol(y, Decl(file.ts, 113, 51)) return "one"; } } + +// Conditionals with `infer` - will not narrow, it is not safe to infer from the narrowed type into an `infer` type parameter +function f9(x: T): T extends Array ? P : T extends number ? undefined : never { +>f9 : Symbol(f9, Decl(file.ts, 117, 1)) +>T : Symbol(T, Decl(file.ts, 120, 12)) +>x : Symbol(x, Decl(file.ts, 120, 46)) +>T : Symbol(T, Decl(file.ts, 120, 12)) +>T : Symbol(T, Decl(file.ts, 120, 12)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more) +>P : Symbol(P, Decl(file.ts, 120, 74)) +>P : Symbol(P, Decl(file.ts, 120, 74)) +>T : Symbol(T, Decl(file.ts, 120, 12)) + + if (Array.isArray(x)) { +>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more) +>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(file.ts, 120, 46)) + + // If we allowed narrowing of the conditional return type, when resolving the conditional `T & ("a"[] | "b"[]) extends Array ? P : ...`, + // we could infer `"a" | "b"` for `P`, and allow "a" to be returned. However, when calling `f10`, `T` could be instantiated with `"b"[]`, and the return type would be `"b"`, + // so allowing an `"a"` return would be unsound. + return "a"; + } + return undefined; +>undefined : Symbol(undefined) +} + + diff --git a/tests/baselines/reference/dependentReturnType6.types b/tests/baselines/reference/dependentReturnType6.types index b41f572391674..f1831992e691f 100644 --- a/tests/baselines/reference/dependentReturnType6.types +++ b/tests/baselines/reference/dependentReturnType6.types @@ -101,8 +101,10 @@ function fun6(x: T, y?: string): T extends true ? 1 | string > : ^ } -// Indexed access with conditional inside - DOESN'T NARROW the nested conditional type -interface SomeInterface { +// Indexed access with conditional inside + +// DOESN'T NARROW the nested conditional type of wrong shape +interface SomeInterfaceBad { prop1: T extends 1 ? true : T extends 2 ? false : never; >prop1 : T extends 1 ? true : T extends 2 ? false : never > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -120,9 +122,9 @@ interface SomeInterface { > : ^^^^^ } -function fun4>(x: T, y: U): SomeInterface[U] { ->fun4 : >(x: T, y: U) => SomeInterface[U] -> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +function fun4bad>(x: T, y: U): SomeInterfaceBad[U] { +>fun4bad : >(x: T, y: U) => SomeInterfaceBad[U] +> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ >x : T > : ^ >y : U @@ -161,6 +163,62 @@ function fun4>(x: T, y: U): SomeInterf > : ^ } +// Narrows nested conditional type of right shape +interface SomeInterfaceGood { + prop1: T extends true ? 2 : T extends false ? 1 : never; +>prop1 : T extends true ? 2 : T extends false ? 1 : never +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ + + prop2: T extends true ? 1 : T extends false ? 2 : never; +>prop2 : T extends true ? 1 : T extends false ? 2 : never +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>true : true +> : ^^^^ +>false : false +> : ^^^^^ +} + +function fun4good>(x: T, y: U): SomeInterfaceGood[U] { +>fun4good : >(x: T, y: U) => SomeInterfaceGood[U] +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>x : T +> : ^ +>y : U +> : ^ + + if (y === "prop1") { +>y === "prop1" : boolean +> : ^^^^^^^ +>y : U +> : ^ +>"prop1" : "prop1" +> : ^^^^^^^ + + return x ? 2 : 1; +>x ? 2 : 1 : 1 | 2 +> : ^^^^^ +>x : T +> : ^ +>2 : 2 +> : ^ +>1 : 1 +> : ^ + } + return x ? 1 : 2; +>x ? 1 : 2 : 1 | 2 +> : ^^^^^ +>x : T +> : ^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ +} + // Indexed access with indexed access inside - OK, narrows interface BB { "a": number; @@ -419,3 +477,36 @@ function f8(x: U, y: V): OtherCond { > : ^^^^^ } } + +// Conditionals with `infer` - will not narrow, it is not safe to infer from the narrowed type into an `infer` type parameter +function f9(x: T): T extends Array ? P : T extends number ? undefined : never { +>f9 : (x: T) => T extends Array ? P : T extends number ? undefined : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>x : T +> : ^ + + if (Array.isArray(x)) { +>Array.isArray(x) : boolean +> : ^^^^^^^ +>Array.isArray : (arg: any) => arg is any[] +> : ^ ^^ ^^^^^ +>Array : ArrayConstructor +> : ^^^^^^^^^^^^^^^^ +>isArray : (arg: any) => arg is any[] +> : ^ ^^ ^^^^^ +>x : number | "a"[] | "b"[] +> : ^^^^^^^^^^^^^^^^^^^^^^ + + // If we allowed narrowing of the conditional return type, when resolving the conditional `T & ("a"[] | "b"[]) extends Array ? P : ...`, + // we could infer `"a" | "b"` for `P`, and allow "a" to be returned. However, when calling `f10`, `T` could be instantiated with `"b"[]`, and the return type would be `"b"`, + // so allowing an `"a"` return would be unsound. + return "a"; +>"a" : "a" +> : ^^^ + } + return undefined; +>undefined : undefined +> : ^^^^^^^^^ +} + + diff --git a/tests/cases/compiler/dependentReturnType6.ts b/tests/cases/compiler/dependentReturnType6.ts index d377e21162d50..4a319fa7f401a 100644 --- a/tests/cases/compiler/dependentReturnType6.ts +++ b/tests/cases/compiler/dependentReturnType6.ts @@ -22,19 +22,34 @@ function fun6(x: T, y?: string): T extends true ? 1 | string return x ? y !== undefined ? y : 1 : 2; } -// Indexed access with conditional inside - DOESN'T NARROW the nested conditional type -interface SomeInterface { +// Indexed access with conditional inside + +// DOESN'T NARROW the nested conditional type of wrong shape +interface SomeInterfaceBad { prop1: T extends 1 ? true : T extends 2 ? false : never; prop2: T extends true ? 1 : T extends false ? 2 : never; } -function fun4>(x: T, y: U): SomeInterface[U] { +function fun4bad>(x: T, y: U): SomeInterfaceBad[U] { if (y === "prop1") { return x === 1 ? true : false; } return x ? 1 : 2; } +// Narrows nested conditional type of right shape +interface SomeInterfaceGood { + prop1: T extends true ? 2 : T extends false ? 1 : never; + prop2: T extends true ? 1 : T extends false ? 2 : never; +} + +function fun4good>(x: T, y: U): SomeInterfaceGood[U] { + if (y === "prop1") { + return x ? 2 : 1; + } + return x ? 1 : 2; +} + // Indexed access with indexed access inside - OK, narrows interface BB { "a": number; @@ -107,4 +122,16 @@ function f8(x: U, y: V): OtherCond { if (x === 1 && y === 3) { return "one"; } -} \ No newline at end of file +} + +// Conditionals with `infer` - will not narrow, it is not safe to infer from the narrowed type into an `infer` type parameter +function f9(x: T): T extends Array ? P : T extends number ? undefined : never { + if (Array.isArray(x)) { + // If we allowed narrowing of the conditional return type, when resolving the conditional `T & ("a"[] | "b"[]) extends Array ? P : ...`, + // we could infer `"a" | "b"` for `P`, and allow "a" to be returned. However, when calling `f10`, `T` could be instantiated with `"b"[]`, and the return type would be `"b"`, + // so allowing an `"a"` return would be unsound. + return "a"; + } + return undefined; +} + From 20b79eb8c8bdaf77a8a594ddd1d94646d7fc3d87 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 7 Oct 2024 13:36:11 -0700 Subject: [PATCH 78/90] refactor --- src/compiler/checker.ts | 84 +++++++------------ src/compiler/types.ts | 1 - src/compiler/utilities.ts | 6 +- .../returnTypeParameterWithModules.js | 4 +- .../returnTypeParameterWithModules.symbols | 40 ++++----- .../returnTypeParameterWithModules.types | 4 +- .../returnTypeParameterWithModules.ts | 4 +- 7 files changed, 59 insertions(+), 84 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 42252a394fa7e..35b0665f1b953 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20286,13 +20286,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } - function getConditionalTypeInstantiation( - type: ConditionalType, - mapper: TypeMapper, - forConstraint: boolean, - aliasSymbol?: Symbol, - aliasTypeArguments?: readonly Type[], - ): Type { + function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper, forConstraint: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { const root = type.root; if (root.outerTypeParameters) { // We are instantiating a conditional type that has one or more type parameters in scope. Apply the @@ -20315,22 +20309,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the // result is (A extends U ? X : Y) | (B extends U ? X : Y). if (distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { - const mapperCallback = narrowingBaseType ? - (t: Type) => - getConditionalType( - root, - prependTypeMapping(checkType, getSubstitutionType(narrowingBaseType, t, /*isNarrowed*/ true), newMapper), - forConstraint, - /*aliasSymbol*/ undefined, - /*aliasTypeArguments*/ undefined, - forNarrowing, - ) : - (t: Type) => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint); if (narrowingBaseType) { - result = mapType(distributionType, mapperCallback, /*noReductions*/ undefined, /*toIntersection*/ true); + result = mapType( + distributionType, + (t: Type) => + getConditionalType( + root, + prependTypeMapping(checkType, getSubstitutionType(narrowingBaseType, t, /*isNarrowed*/ true), newMapper), + forConstraint, + /*aliasSymbol*/ undefined, + /*aliasTypeArguments*/ undefined, + forNarrowing, + ), + /*noReductions*/ undefined, + /*toIntersection*/ true, + ); } else { - result = mapTypeWithAlias(distributionType, mapperCallback, aliasSymbol, aliasTypeArguments); + result = mapTypeWithAlias(distributionType, (t: Type) => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint), aliasSymbol, aliasTypeArguments); } } else { @@ -20369,12 +20365,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } - function instantiateTypeWorker( - type: Type, - mapper: TypeMapper, - aliasSymbol: Symbol | undefined, - aliasTypeArguments: readonly Type[] | undefined, - ): Type { + function instantiateTypeWorker(type: Type, mapper: TypeMapper, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined): Type { const flags = type.flags; if (flags & TypeFlags.TypeParameter) { return getMappedType(type, mapper); @@ -20450,10 +20441,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } - function isGenericIndexedOrConditionalReturnType(type: Type): type is IndexedAccessType | ConditionalType { - return !!(type.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional) && couldContainTypeVariables(type)); - } - function instantiateReverseMappedType(type: ReverseMappedType, mapper: TypeMapper) { const innerMappedType = instantiateType(type.mappedType, mapper); if (!(getObjectFlags(innerMappedType) & ObjectFlags.Mapped)) { @@ -21669,7 +21656,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { for (const t of type.types) { hasInstantiable ||= !!(t.flags & TypeFlags.Instantiable); hasNullableOrEmpty ||= !!(t.flags & TypeFlags.Nullable) || isEmptyAnonymousObjectType(t); - hasSubstitution ||= isNarrowingSubstitutionType(t); // This avoids displaying error messages with types like `T & T` + hasSubstitution ||= isNarrowingSubstitutionType(t); // This avoids displaying error messages with types like `T & T` when narrowing a return type if (hasInstantiable && hasNullableOrEmpty || hasSubstitution) return true; } return false; @@ -29441,12 +29428,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // up to five levels of aliased conditional expressions that are themselves declared as const variables. if (!isMatchingReference(reference, expr) && inlineLevel < 5) { const symbol = getResolvedSymbol(expr as Identifier); - const inlineExpression = getNarrowableInlineExpression(symbol); - if (inlineExpression && isConstantReference(reference)) { - inlineLevel++; - const result = narrowType(type, inlineExpression, assumeTrue); - inlineLevel--; - return result; + if (isConstantVariable(symbol)) { + const declaration = symbol.valueDeclaration; + if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isConstantReference(reference)) { + inlineLevel++; + const result = narrowType(type, declaration.initializer, assumeTrue); + inlineLevel--; + return result; + } } } // falls through @@ -29483,15 +29472,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function getNarrowableInlineExpression(symbol: Symbol): Expression | undefined { - if (isConstantVariable(symbol)) { - const declaration = symbol.valueDeclaration; - if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer) { - return declaration.initializer; - } - } - } - function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { symbol = getExportSymbolOfValueSymbolIfExported(symbol); @@ -45667,7 +45647,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (expr) { const unwrappedExpr = skipParentheses(expr); if (isConditionalExpression(unwrappedExpr)) { - return checkReturnConditionalExpression(container, returnType, node, unwrappedExpr); + return checkConditionalReturnExpression(container, returnType, node, unwrappedExpr); } } @@ -45688,19 +45668,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return; } - if (!isGenericIndexedOrConditionalReturnType(unwrappedReturnType)) { + if (!(unwrappedReturnType.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional)) || !couldContainTypeVariables(unwrappedReturnType)) { checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); return; } const allTypeParameters = appendTypeParameters(getOuterTypeParameters(container, /*includeThisTypes*/ false), getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); const narrowableTypeParameters = allTypeParameters && getNarrowableTypeParameters(allTypeParameters); - // >> TODO: another optimization would be to check if any of the narrowable type parameters - // match the types in the return type that can be narrowed if ( !narrowableTypeParameters || !narrowableTypeParameters.length || - !isNarrowableReturnType(unwrappedReturnType) + !isNarrowableReturnType(unwrappedReturnType as ConditionalType | IndexedAccessType) ) { checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); return; @@ -45779,7 +45757,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkTypeAssignableToAndOptionallyElaborate(narrowedUnwrappedExprType, narrowedReturnType, errorNode, expr); } - function checkReturnConditionalExpression( + function checkConditionalReturnExpression( container: SignatureDeclaration, returnType: Type, node: ReturnStatement, @@ -45860,8 +45838,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - // A narrowable indexed access type is one that has the shape `A[T]`, - // where `T` is a narrowable type parameter. function isNarrowableReturnType(returnType: IndexedAccessType | ConditionalType): boolean { return isConditionalType(returnType) ? isNarrowableConditionalType(returnType) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 70483f76ce91d..0e086e3498d41 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6917,7 +6917,6 @@ export interface SubstitutionType extends InstantiableType { objectFlags: ObjectFlags; baseType: Type; // Target type constraint: Type; // Constraint that target type is known to satisfy - /** @internal */ } /** @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c8c7e1b380183..fc55fb1411772 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4445,12 +4445,12 @@ export function canHaveFlowNode(node: Node): node is HasFlowNode { case SyntaxKind.Identifier: case SyntaxKind.ThisKeyword: case SyntaxKind.SuperKeyword: - case SyntaxKind.ElementAccessExpression: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.FunctionExpression: case SyntaxKind.QualifiedName: case SyntaxKind.MetaProperty: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.PropertyAccessExpression: case SyntaxKind.BindingElement: + case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: diff --git a/tests/baselines/reference/returnTypeParameterWithModules.js b/tests/baselines/reference/returnTypeParameterWithModules.js index 472ed3f7318e0..0ff8c9fdab3d8 100644 --- a/tests/baselines/reference/returnTypeParameterWithModules.js +++ b/tests/baselines/reference/returnTypeParameterWithModules.js @@ -10,8 +10,8 @@ module M2 { import A = M1 export function compose() { A.reduce(arguments, compose2); - }; - export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { + }; + export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { return function (x) { return g(f(x)); } }; }; diff --git a/tests/baselines/reference/returnTypeParameterWithModules.symbols b/tests/baselines/reference/returnTypeParameterWithModules.symbols index 51f03547d0e9e..35ae415b22a8c 100644 --- a/tests/baselines/reference/returnTypeParameterWithModules.symbols +++ b/tests/baselines/reference/returnTypeParameterWithModules.symbols @@ -44,30 +44,30 @@ module M2 { >A : Symbol(A, Decl(returnTypeParameterWithModules.ts, 5, 11)) >reduce : Symbol(A.reduce, Decl(returnTypeParameterWithModules.ts, 0, 11)) >arguments : Symbol(arguments) ->compose2 : Symbol(compose2, Decl(returnTypeParameterWithModules.ts, 9, 4)) +>compose2 : Symbol(compose2, Decl(returnTypeParameterWithModules.ts, 9, 6)) - }; - export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { ->compose2 : Symbol(compose2, Decl(returnTypeParameterWithModules.ts, 9, 4)) ->B : Symbol(B, Decl(returnTypeParameterWithModules.ts, 10, 27)) ->C : Symbol(C, Decl(returnTypeParameterWithModules.ts, 10, 29)) ->D : Symbol(D, Decl(returnTypeParameterWithModules.ts, 10, 32)) ->g : Symbol(g, Decl(returnTypeParameterWithModules.ts, 10, 36)) ->x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 10, 40)) ->B : Symbol(B, Decl(returnTypeParameterWithModules.ts, 10, 27)) ->C : Symbol(C, Decl(returnTypeParameterWithModules.ts, 10, 29)) ->f : Symbol(f, Decl(returnTypeParameterWithModules.ts, 10, 51)) ->x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 10, 56)) ->D : Symbol(D, Decl(returnTypeParameterWithModules.ts, 10, 32)) ->B : Symbol(B, Decl(returnTypeParameterWithModules.ts, 10, 27)) ->x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 10, 70)) ->D : Symbol(D, Decl(returnTypeParameterWithModules.ts, 10, 32)) ->C : Symbol(C, Decl(returnTypeParameterWithModules.ts, 10, 29)) + }; + export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { +>compose2 : Symbol(compose2, Decl(returnTypeParameterWithModules.ts, 9, 6)) +>B : Symbol(B, Decl(returnTypeParameterWithModules.ts, 10, 29)) +>C : Symbol(C, Decl(returnTypeParameterWithModules.ts, 10, 31)) +>D : Symbol(D, Decl(returnTypeParameterWithModules.ts, 10, 34)) +>g : Symbol(g, Decl(returnTypeParameterWithModules.ts, 10, 38)) +>x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 10, 42)) +>B : Symbol(B, Decl(returnTypeParameterWithModules.ts, 10, 29)) +>C : Symbol(C, Decl(returnTypeParameterWithModules.ts, 10, 31)) +>f : Symbol(f, Decl(returnTypeParameterWithModules.ts, 10, 53)) +>x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 10, 58)) +>D : Symbol(D, Decl(returnTypeParameterWithModules.ts, 10, 34)) +>B : Symbol(B, Decl(returnTypeParameterWithModules.ts, 10, 29)) +>x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 10, 72)) +>D : Symbol(D, Decl(returnTypeParameterWithModules.ts, 10, 34)) +>C : Symbol(C, Decl(returnTypeParameterWithModules.ts, 10, 31)) return function (x) { return g(f(x)); } >x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 11, 21)) ->g : Symbol(g, Decl(returnTypeParameterWithModules.ts, 10, 36)) ->f : Symbol(f, Decl(returnTypeParameterWithModules.ts, 10, 51)) +>g : Symbol(g, Decl(returnTypeParameterWithModules.ts, 10, 38)) +>f : Symbol(f, Decl(returnTypeParameterWithModules.ts, 10, 53)) >x : Symbol(x, Decl(returnTypeParameterWithModules.ts, 11, 21)) }; diff --git a/tests/baselines/reference/returnTypeParameterWithModules.types b/tests/baselines/reference/returnTypeParameterWithModules.types index 9ae9f7d801f07..a97badc7dc14b 100644 --- a/tests/baselines/reference/returnTypeParameterWithModules.types +++ b/tests/baselines/reference/returnTypeParameterWithModules.types @@ -70,8 +70,8 @@ module M2 { >compose2 : (g: (x: B) => C, f: (x: D) => B) => (x: D) => C > : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ - }; - export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { + }; + export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { >compose2 : (g: (x: B) => C, f: (x: D) => B) => (x: D) => C > : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ >g : (x: B) => C diff --git a/tests/cases/compiler/returnTypeParameterWithModules.ts b/tests/cases/compiler/returnTypeParameterWithModules.ts index 2ef8cd2eaa5de..113d45c6df8f2 100644 --- a/tests/cases/compiler/returnTypeParameterWithModules.ts +++ b/tests/cases/compiler/returnTypeParameterWithModules.ts @@ -7,8 +7,8 @@ module M2 { import A = M1 export function compose() { A.reduce(arguments, compose2); - }; - export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { + }; + export function compose2(g: (x: B) => C, f: (x: D) => B): (x: D) => C { return function (x) { return g(f(x)); } }; }; \ No newline at end of file From 3788ada9b9fb9dfdf92041455e1854c065bda2b8 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 8 Oct 2024 18:09:37 -0700 Subject: [PATCH 79/90] don't skip jsdoc type assertions --- src/compiler/checker.ts | 2 +- .../reference/dependentReturnType7.symbols | 34 ++++++++++ .../reference/dependentReturnType7.types | 65 +++++++++++++++++++ tests/cases/compiler/dependentReturnType7.ts | 20 ++++++ 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/dependentReturnType7.symbols create mode 100644 tests/baselines/reference/dependentReturnType7.types create mode 100644 tests/cases/compiler/dependentReturnType7.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 35b0665f1b953..a55946285ca40 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45645,7 +45645,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const functionFlags = getFunctionFlags(container); const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; if (expr) { - const unwrappedExpr = skipParentheses(expr); + const unwrappedExpr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); if (isConditionalExpression(unwrappedExpr)) { return checkConditionalReturnExpression(container, returnType, node, unwrappedExpr); } diff --git a/tests/baselines/reference/dependentReturnType7.symbols b/tests/baselines/reference/dependentReturnType7.symbols new file mode 100644 index 0000000000000..a0180628cf060 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType7.symbols @@ -0,0 +1,34 @@ +//// [tests/cases/compiler/dependentReturnType7.ts] //// + +=== file.js === +/** @type {Map} */ +const sources = new Map(); +>sources : Symbol(sources, Decl(file.js, 1, 5)) +>Map : Symbol(Map, Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + +/** + + * @param {string=} type the type of source that should be generated + * @returns {String} + */ +function source(type = "javascript") { +>source : Symbol(source, Decl(file.js, 1, 26)) +>type : Symbol(type, Decl(file.js, 7, 16)) + + return /** @type {String} */ ( + type +>type : Symbol(type, Decl(file.js, 7, 16)) + + ? sources.get(type) +>sources.get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) +>sources : Symbol(sources, Decl(file.js, 1, 5)) +>get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) +>type : Symbol(type, Decl(file.js, 7, 16)) + + : sources.get("some other thing") +>sources.get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) +>sources : Symbol(sources, Decl(file.js, 1, 5)) +>get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) + + ); +} diff --git a/tests/baselines/reference/dependentReturnType7.types b/tests/baselines/reference/dependentReturnType7.types new file mode 100644 index 0000000000000..5ba3dcd644f4f --- /dev/null +++ b/tests/baselines/reference/dependentReturnType7.types @@ -0,0 +1,65 @@ +//// [tests/cases/compiler/dependentReturnType7.ts] //// + +=== Performance Stats === +Type Count: 1,000 +Instantiation count: 2,500 + +=== file.js === +/** @type {Map} */ +const sources = new Map(); +>sources : Map +> : ^^^^^^^^^^^^^^^^^^^ +>new Map() : Map +> : ^^^^^^^^^^^^^ +>Map : MapConstructor +> : ^^^^^^^^^^^^^^ + +/** + + * @param {string=} type the type of source that should be generated + * @returns {String} + */ +function source(type = "javascript") { +>source : (type?: string | undefined) => string +> : ^ ^^^ ^^^^^^^^^^^^^^^^^^^^ +>type : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>"javascript" : "javascript" +> : ^^^^^^^^^^^^ + + return /** @type {String} */ ( +>( type ? sources.get(type) : sources.get("some other thing") ) : string +> : ^^^^^^ + + type +>type ? sources.get(type) : sources.get("some other thing") : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>type : string +> : ^^^^^^ + + ? sources.get(type) +>sources.get(type) : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>sources.get : (key: string) => string | undefined +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>sources : Map +> : ^^^^^^^^^^^^^^^^^^^ +>get : (key: string) => string | undefined +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>type : string +> : ^^^^^^ + + : sources.get("some other thing") +>sources.get("some other thing") : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>sources.get : (key: string) => string | undefined +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>sources : Map +> : ^^^^^^^^^^^^^^^^^^^ +>get : (key: string) => string | undefined +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>"some other thing" : "some other thing" +> : ^^^^^^^^^^^^^^^^^^ + + ); +} diff --git a/tests/cases/compiler/dependentReturnType7.ts b/tests/cases/compiler/dependentReturnType7.ts new file mode 100644 index 0000000000000..b4c7369322849 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType7.ts @@ -0,0 +1,20 @@ +// @strict: true +// @noEmit: true +// @target: esnext +// @checkJs: true +// @filename: file.js + +/** @type {Map} */ +const sources = new Map(); +/** + + * @param {string=} type the type of source that should be generated + * @returns {String} + */ +function source(type = "javascript") { + return /** @type {String} */ ( + type + ? sources.get(type) + : sources.get("some other thing") + ); +} \ No newline at end of file From 827962fab3a2dd03279dc0562c3be3cb7d6df151 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 9 Oct 2024 11:20:10 -0700 Subject: [PATCH 80/90] experiment: always set flow node in cond expr --- src/compiler/factory/nodeFactory.ts | 2 ++ src/compiler/types.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index fbc97d9aa4d9c..694262eeeb54a 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -3481,6 +3481,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode propagateChildFlags(node.whenTrue) | propagateChildFlags(node.colonToken) | propagateChildFlags(node.whenFalse); + node.flowNodeWhenFalse = undefined; + node.flowNodeWhenTrue = undefined; return node; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0e086e3498d41..76bcef0b6055b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2736,9 +2736,9 @@ export interface ConditionalExpression extends Expression { readonly colonToken: ColonToken; readonly whenFalse: Expression; /** @internal*/ - flowNodeWhenTrue?: FlowNode; + flowNodeWhenTrue: FlowNode | undefined; /** @internal */ - flowNodeWhenFalse?: FlowNode; + flowNodeWhenFalse: FlowNode | undefined; } export type FunctionBody = Block; From 4e773730b7b75e4600c7b22bc07345bfa89cc18b Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 9 Oct 2024 13:32:31 -0700 Subject: [PATCH 81/90] support jsdoc --- src/compiler/checker.ts | 8 ++++--- .../reference/dependentReturnType7.symbols | 13 +++++++++++ .../reference/dependentReturnType7.types | 22 +++++++++++++++++++ tests/cases/compiler/dependentReturnType7.ts | 9 ++++++++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a55946285ca40..076bf8cd03d6c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45777,12 +45777,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const constraint = getConstraintOfTypeParameter(typeParam); if (!constraint || !(constraint.flags & TypeFlags.Union)) continue; if (typeParam.symbol && typeParam.symbol.declarations && typeParam.symbol.declarations.length === 1) { - const container = typeParam.symbol.declarations[0].parent; + const declaration = typeParam.symbol.declarations[0]; + const container = isJSDocTemplateTag(declaration.parent) ? getJSDocHost(declaration.parent) : declaration.parent; if (!isFunctionLike(container)) continue; let reference: Identifier | undefined; let hasInvalidReference = false; for (const paramDecl of container.parameters) { - const typeNode = paramDecl.type; + const typeNode = getEffectiveTypeAnnotationNode(paramDecl); if (!typeNode) continue; if (isTypeParameterReferenced(typeParam, typeNode)) { let candidateReference; @@ -45815,7 +45816,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // - if the parameter is optional, then `T`'s constraint must allow for undefined function getValidParameterReference(paramDecl: ParameterDeclaration, constraint: Type): Identifier | undefined { if (!isIdentifier(paramDecl.name)) return; - if (paramDecl.questionToken && !containsUndefinedType(constraint)) return; + const isOptional = !!paramDecl.questionToken || isJSDocOptionalParameter(paramDecl); + if (isOptional && !containsUndefinedType(constraint)) return; return paramDecl.name; } diff --git a/tests/baselines/reference/dependentReturnType7.symbols b/tests/baselines/reference/dependentReturnType7.symbols index a0180628cf060..bce19a0622551 100644 --- a/tests/baselines/reference/dependentReturnType7.symbols +++ b/tests/baselines/reference/dependentReturnType7.symbols @@ -32,3 +32,16 @@ function source(type = "javascript") { ); } + +/** + * @template {boolean} T + * @param {T} b + * @returns {T extends true ? 1 : T extends false ? 2 : never} + */ +function simple(b) { +>simple : Symbol(simple, Decl(file.js, 13, 1)) +>b : Symbol(b, Decl(file.js, 20, 16)) + + return b ? 1 : 2; +>b : Symbol(b, Decl(file.js, 20, 16)) +} diff --git a/tests/baselines/reference/dependentReturnType7.types b/tests/baselines/reference/dependentReturnType7.types index 5ba3dcd644f4f..286ea23226ef8 100644 --- a/tests/baselines/reference/dependentReturnType7.types +++ b/tests/baselines/reference/dependentReturnType7.types @@ -63,3 +63,25 @@ function source(type = "javascript") { ); } + +/** + * @template {boolean} T + * @param {T} b + * @returns {T extends true ? 1 : T extends false ? 2 : never} + */ +function simple(b) { +>simple : (b: T) => T extends true ? 1 : T extends false ? 2 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>b : T +> : ^ + + return b ? 1 : 2; +>b ? 1 : 2 : 2 | 1 +> : ^^^^^ +>b : T +> : ^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ +} diff --git a/tests/cases/compiler/dependentReturnType7.ts b/tests/cases/compiler/dependentReturnType7.ts index b4c7369322849..35d69f806465b 100644 --- a/tests/cases/compiler/dependentReturnType7.ts +++ b/tests/cases/compiler/dependentReturnType7.ts @@ -17,4 +17,13 @@ function source(type = "javascript") { ? sources.get(type) : sources.get("some other thing") ); +} + +/** + * @template {boolean} T + * @param {T} b + * @returns {T extends true ? 1 : T extends false ? 2 : never} + */ +function simple(b) { + return b ? 1 : 2; } \ No newline at end of file From c076dda4e80d622e2bae1e32cae92bd275091047 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 9 Oct 2024 15:25:51 -0700 Subject: [PATCH 82/90] add more jsdoc tests --- .../reference/dependentReturnType2.errors.txt | 314 +++++ .../reference/dependentReturnType2.symbols | 594 ++++++++++ .../reference/dependentReturnType2.types | 1007 +++++++++++++++++ tests/cases/compiler/dependentReturnType2.ts | 307 +++++ 4 files changed, 2222 insertions(+) create mode 100644 tests/baselines/reference/dependentReturnType2.errors.txt create mode 100644 tests/baselines/reference/dependentReturnType2.symbols create mode 100644 tests/baselines/reference/dependentReturnType2.types create mode 100644 tests/cases/compiler/dependentReturnType2.ts diff --git a/tests/baselines/reference/dependentReturnType2.errors.txt b/tests/baselines/reference/dependentReturnType2.errors.txt new file mode 100644 index 0000000000000..4eddac31221f3 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType2.errors.txt @@ -0,0 +1,314 @@ +file.js(155,13): error TS2322: Type 'undefined' is not assignable to type 'HelperCond[]>'. +file.js(168,16): error TS2536: Type 'I' cannot be used to index type '{ [s: string]: any; }'. +file.js(185,9): error TS2322: Type 'Record' is not assignable to type 'HelperCond>'. + + +==== file.js (3 errors) ==== + // Adapted from ts-error-deltas repos + + /** + * @template T + * @template A + * @template R1 + * @template B + * @template R2 + * @typedef {T extends A ? R1 : T extends B ? R2 : never} HelperCond + */ + + /** + * @typedef IMessage + * @property {string} [html] + * @property {Object[]} [tokens] + */ + + class NewKatex { + /** + * @param {string} s + * @returns {string} + */ + render(s) { + return ""; + } + + /** + * @template {string | IMessage} T + * @param {T} message + * @returns {T extends string ? string : T extends IMessage ? IMessage : never} + */ + renderMessage(message) { + if (typeof message === 'string') { + return this.render(message); // Ok + } + + if (!message.html?.trim()) { + return message; // Ok + } + + if (!message.tokens) { + message.tokens = []; + } + + message.html = this.render(message.html); + return message; // Ok + } + } + + /** + * @template {true | false} T + * @param {{ dollarSyntax: boolean; parenthesisSyntax: boolean; }} options + * @param {T} _isMessage + * @returns {T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : never} + */ + function createKatexMessageRendering(options, _isMessage) { + const instance = new NewKatex(); + if (_isMessage) { + return (/** @type {IMessage} */ message) => instance.renderMessage(message); // Ok + } + return (/** @type {string} */ message) => instance.renderMessage(message); // Ok + } + + // File: Rocket.Chat/apps/meteor/app/settings/lib/settings.ts + + /** + * @typedef {Record} MyObj + */ + + + /** + * @typedef {MyObj} SettingValue + */ + + /** + * @template {SettingValue} T + * @typedef {Object} SettingComposedValue + * @property {string} key + * @property {SettingValue} value + */ + + /** + * @callback SettingCallback + * @param {string} key + * @param {SettingValue} value + * @param {boolean} [initialLoad] + * @returns {void} + */ + + /** @type {{ settings: { [s: string]: any } }} */ + const Meteor = /** @type {any} */ (undefined); + /** @type {{ isRegExp(x: unknown): x is RegExp; }} */ + const _ = /** @type {any} */ (undefined); + + /** + * @param {RegExp} x + * @returns {void} + */ + function takesRegExp(x) { + return /** @type {any} */ undefined; + } + /** + * @param {string} x + * @returns {void} + */ + function takesString(x) { + return /** @type {any} */ undefined; + } + + /** + * @class NewSettingsBase + */ + class NewSettingsBase { + /** + * @template {SettingCallback | undefined} C + * @template {string | RegExp} I + * @template {SettingValue} T + * @param {I} _id + * @param {C} [callback] + * @returns {HelperCond[]>>} + */ + newGet(_id, callback) { + if (callback !== undefined) { + if (!Meteor.settings) { + return; // Ok + } + if (_id === '*') { + return Object.keys(Meteor.settings).forEach((key) => { + const value = Meteor.settings[key]; + callback(key, value); + }); + } + if (_.isRegExp(_id) && Meteor.settings) { + return Object.keys(Meteor.settings).forEach((key) => { + if (!_id.test(key)) { + return; + } + const value = Meteor.settings[key]; + callback(key, value); + }); + } + + if (typeof _id === 'string') { + const value = Meteor.settings[_id]; + if (value != null) { + callback(_id, Meteor.settings[_id]); + } + return; // Ok + } + + return; // Ok, needed for exhaustiveness check + } + + if (!Meteor.settings) { + return undefined; // Error + ~~~~~~ +!!! error TS2322: Type 'undefined' is not assignable to type 'HelperCond[]>'. + } + + if (_.isRegExp(_id)) { + return Object.keys(Meteor.settings).reduce((/** @type {SettingComposedValue[]} */ items, key) => { + const value = Meteor.settings[key]; + if (_id.test(key)) { + items.push({ key, value }); + } + return items; + }, []); // Ok + } + + return Meteor.settings?.[_id]; // Error + ~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2536: Type 'I' cannot be used to index type '{ [s: string]: any; }'. + } + } + + // File: Rocket.Chat/apps/meteor/app/ui-utils/client/lib/messageBox.ts + + /** + * @typedef {MyObj} MessageBoxAction + */ + + /** + * @template {string | undefined} T + * @param {T} group + * @returns {HelperCond>} + */ + function getWithBug(group) { + if (!group) { + return /** @type {Record} */({}); // Error + ~~~~~~ +!!! error TS2322: Type 'Record' is not assignable to type 'HelperCond>'. + } + return /** @type {MessageBoxAction[]} */([]); // Ok + } + + /** + * @template {string | undefined} T + * @param {T} group + * @returns {HelperCond>} + */ + function getWithoutBug(group) { + if (group === undefined) { + return /** @type {Record} */({}); // Ok + } + return /** @type {MessageBoxAction[]} */([]); // Ok + } + + // File: Rocket.Chat/apps/meteor/ee/server/lib/engagementDashboard/date.ts + + /** + * @param {string} x + * @returns {Date} + */ + function mapDateForAPI(x) { + return /** @type {any} */ (undefined); + } + + /** + * @template {string | undefined} T + * @param {string} start + * @param {T} [end] + * @returns {HelperCond} + */ + function transformDatesForAPI(start, end) { + return end !== undefined ? + { + start: mapDateForAPI(start), + end: mapDateForAPI(end), + } : + { + start: mapDateForAPI(start), + end: undefined + }; + } + + // File: Rocket.Chat/packages/agenda/src/Agenda.ts + + /** + * @typedef {MyObj} RepeatOptions + */ + + /** + * @typedef {MyObj} Job + */ + + /** + * @typedef {Object} IJob + * @property {MyObj} data + */ + class NewAgenda { + /** + * @param {string | number} interval + * @param {string} name + * @param {IJob['data']} data + * @param {RepeatOptions} options + * @returns {Promise} + */ + async _createIntervalJob(interval, name, data, options) { + return /** @type {any} */ (undefined); + } + + /** + * @param {string | number} interval + * @param {string[]} names + * @param {IJob['data']} data + * @param {RepeatOptions} options + * @returns {Promise | undefined} + */ + _createIntervalJobs(interval, names, data, options) { + return undefined; + } + + /** + * @template {string | string[]} T + * @param {string | number} interval + * @param {T} name + * @param {IJob['data']} data + * @param {RepeatOptions} options + * @returns {Promise>} + */ + async newEvery(interval, name, data, options) { + if (typeof name === 'string') { + return this._createIntervalJob(interval, name, data, options); // Ok + } + + if (Array.isArray(name)) { + return this._createIntervalJobs(interval, name, data, options); // Ok + } + + throw new Error('Unexpected error: Invalid job name(s)'); + } + } + + // File: angular/packages/common/src/pipes/case_conversion_pipes.ts + + /** + * @template {string | null | undefined} T + * @param {T} value + * @returns {HelperCond} + */ + function transform1(value) { + if (value == null) return null; // Ok + if (typeof value !== 'string') { + throw new Error(); + } + return value.toLowerCase(); // Ok + } + \ No newline at end of file diff --git a/tests/baselines/reference/dependentReturnType2.symbols b/tests/baselines/reference/dependentReturnType2.symbols new file mode 100644 index 0000000000000..4b41f3fe744db --- /dev/null +++ b/tests/baselines/reference/dependentReturnType2.symbols @@ -0,0 +1,594 @@ +//// [tests/cases/compiler/dependentReturnType2.ts] //// + +=== file.js === +// Adapted from ts-error-deltas repos + +/** + * @template T + * @template A + * @template R1 + * @template B + * @template R2 + * @typedef {T extends A ? R1 : T extends B ? R2 : never} HelperCond + */ + +/** + * @typedef IMessage + * @property {string} [html] + * @property {Object[]} [tokens] + */ + +class NewKatex { +>NewKatex : Symbol(NewKatex, Decl(file.js, 0, 0)) + + /** + * @param {string} s + * @returns {string} + */ + render(s) { +>render : Symbol(NewKatex.render, Decl(file.js, 17, 16)) +>s : Symbol(s, Decl(file.js, 22, 11)) + + return ""; + } + + /** + * @template {string | IMessage} T + * @param {T} message + * @returns {T extends string ? string : T extends IMessage ? IMessage : never} + */ + renderMessage(message) { +>renderMessage : Symbol(NewKatex.renderMessage, Decl(file.js, 24, 5)) +>message : Symbol(message, Decl(file.js, 31, 18)) + + if (typeof message === 'string') { +>message : Symbol(message, Decl(file.js, 31, 18)) + + return this.render(message); // Ok +>this.render : Symbol(NewKatex.render, Decl(file.js, 17, 16)) +>this : Symbol(NewKatex, Decl(file.js, 0, 0)) +>render : Symbol(NewKatex.render, Decl(file.js, 17, 16)) +>message : Symbol(message, Decl(file.js, 31, 18)) + } + + if (!message.html?.trim()) { +>message.html?.trim : Symbol(String.trim, Decl(lib.es5.d.ts, --, --)) +>message.html : Symbol(html, Decl(file.js, 13, 3)) +>message : Symbol(message, Decl(file.js, 31, 18)) +>html : Symbol(html, Decl(file.js, 13, 3)) +>trim : Symbol(String.trim, Decl(lib.es5.d.ts, --, --)) + + return message; // Ok +>message : Symbol(message, Decl(file.js, 31, 18)) + } + + if (!message.tokens) { +>message.tokens : Symbol(tokens, Decl(file.js, 14, 3)) +>message : Symbol(message, Decl(file.js, 31, 18)) +>tokens : Symbol(tokens, Decl(file.js, 14, 3)) + + message.tokens = []; +>message.tokens : Symbol(tokens, Decl(file.js, 14, 3)) +>message : Symbol(message, Decl(file.js, 31, 18)) +>tokens : Symbol(tokens, Decl(file.js, 14, 3)) + } + + message.html = this.render(message.html); +>message.html : Symbol(html, Decl(file.js, 13, 3)) +>message : Symbol(message, Decl(file.js, 31, 18)) +>html : Symbol(html, Decl(file.js, 13, 3)) +>this.render : Symbol(NewKatex.render, Decl(file.js, 17, 16)) +>this : Symbol(NewKatex, Decl(file.js, 0, 0)) +>render : Symbol(NewKatex.render, Decl(file.js, 17, 16)) +>message.html : Symbol(html, Decl(file.js, 13, 3)) +>message : Symbol(message, Decl(file.js, 31, 18)) +>html : Symbol(html, Decl(file.js, 13, 3)) + + return message; // Ok +>message : Symbol(message, Decl(file.js, 31, 18)) + } +} + +/** + * @template {true | false} T + * @param {{ dollarSyntax: boolean; parenthesisSyntax: boolean; }} options + * @param {T} _isMessage + * @returns {T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : never} + */ +function createKatexMessageRendering(options, _isMessage) { +>createKatexMessageRendering : Symbol(createKatexMessageRendering, Decl(file.js, 47, 1)) +>options : Symbol(options, Decl(file.js, 55, 37)) +>_isMessage : Symbol(_isMessage, Decl(file.js, 55, 45)) + + const instance = new NewKatex(); +>instance : Symbol(instance, Decl(file.js, 56, 9)) +>NewKatex : Symbol(NewKatex, Decl(file.js, 0, 0)) + + if (_isMessage) { +>_isMessage : Symbol(_isMessage, Decl(file.js, 55, 45)) + + return (/** @type {IMessage} */ message) => instance.renderMessage(message); // Ok +>message : Symbol(message, Decl(file.js, 58, 16)) +>instance.renderMessage : Symbol(NewKatex.renderMessage, Decl(file.js, 24, 5)) +>instance : Symbol(instance, Decl(file.js, 56, 9)) +>renderMessage : Symbol(NewKatex.renderMessage, Decl(file.js, 24, 5)) +>message : Symbol(message, Decl(file.js, 58, 16)) + } + return (/** @type {string} */ message) => instance.renderMessage(message); // Ok +>message : Symbol(message, Decl(file.js, 60, 12)) +>instance.renderMessage : Symbol(NewKatex.renderMessage, Decl(file.js, 24, 5)) +>instance : Symbol(instance, Decl(file.js, 56, 9)) +>renderMessage : Symbol(NewKatex.renderMessage, Decl(file.js, 24, 5)) +>message : Symbol(message, Decl(file.js, 60, 12)) +} + +// File: Rocket.Chat/apps/meteor/app/settings/lib/settings.ts + +/** + * @typedef {Record} MyObj + */ + + +/** + * @typedef {MyObj} SettingValue + */ + +/** + * @template {SettingValue} T + * @typedef {Object} SettingComposedValue + * @property {string} key + * @property {SettingValue} value + */ + +/** + * @callback SettingCallback + * @param {string} key + * @param {SettingValue} value + * @param {boolean} [initialLoad] + * @returns {void} + */ + +/** @type {{ settings: { [s: string]: any } }} */ +const Meteor = /** @type {any} */ (undefined); +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>undefined : Symbol(undefined) + +/** @type {{ isRegExp(x: unknown): x is RegExp; }} */ +const _ = /** @type {any} */ (undefined); +>_ : Symbol(_, Decl(file.js, 92, 5)) +>undefined : Symbol(undefined) + +/** + * @param {RegExp} x + * @returns {void} + */ +function takesRegExp(x) { +>takesRegExp : Symbol(takesRegExp, Decl(file.js, 92, 41)) +>x : Symbol(x, Decl(file.js, 98, 21)) + + return /** @type {any} */ undefined; +>undefined : Symbol(undefined) +} +/** + * @param {string} x + * @returns {void} + */ +function takesString(x) { +>takesString : Symbol(takesString, Decl(file.js, 100, 1)) +>x : Symbol(x, Decl(file.js, 105, 21)) + + return /** @type {any} */ undefined; +>undefined : Symbol(undefined) +} + +/** + * @class NewSettingsBase + */ +class NewSettingsBase { +>NewSettingsBase : Symbol(NewSettingsBase, Decl(file.js, 107, 1)) + + /** + * @template {SettingCallback | undefined} C + * @template {string | RegExp} I + * @template {SettingValue} T + * @param {I} _id + * @param {C} [callback] + * @returns {HelperCond[]>>} + */ + newGet(_id, callback) { +>newGet : Symbol(NewSettingsBase.newGet, Decl(file.js, 112, 23)) +>_id : Symbol(_id, Decl(file.js, 121, 11)) +>callback : Symbol(callback, Decl(file.js, 121, 15)) + + if (callback !== undefined) { +>callback : Symbol(callback, Decl(file.js, 121, 15)) +>undefined : Symbol(undefined) + + if (!Meteor.settings) { +>Meteor.settings : Symbol(settings, Decl(file.js, 89, 12)) +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>settings : Symbol(settings, Decl(file.js, 89, 12)) + + return; // Ok + } + if (_id === '*') { +>_id : Symbol(_id, Decl(file.js, 121, 11)) + + return Object.keys(Meteor.settings).forEach((key) => { +>Object.keys(Meteor.settings).forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Meteor.settings : Symbol(settings, Decl(file.js, 89, 12)) +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>settings : Symbol(settings, Decl(file.js, 89, 12)) +>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(file.js, 127, 61)) + + const value = Meteor.settings[key]; +>value : Symbol(value, Decl(file.js, 128, 25)) +>Meteor.settings : Symbol(settings, Decl(file.js, 89, 12)) +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>settings : Symbol(settings, Decl(file.js, 89, 12)) +>key : Symbol(key, Decl(file.js, 127, 61)) + + callback(key, value); +>callback : Symbol(callback, Decl(file.js, 121, 15)) +>key : Symbol(key, Decl(file.js, 127, 61)) +>value : Symbol(value, Decl(file.js, 128, 25)) + + }); + } + if (_.isRegExp(_id) && Meteor.settings) { +>_.isRegExp : Symbol(isRegExp, Decl(file.js, 91, 12)) +>_ : Symbol(_, Decl(file.js, 92, 5)) +>isRegExp : Symbol(isRegExp, Decl(file.js, 91, 12)) +>_id : Symbol(_id, Decl(file.js, 121, 11)) +>Meteor.settings : Symbol(settings, Decl(file.js, 89, 12)) +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>settings : Symbol(settings, Decl(file.js, 89, 12)) + + return Object.keys(Meteor.settings).forEach((key) => { +>Object.keys(Meteor.settings).forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Meteor.settings : Symbol(settings, Decl(file.js, 89, 12)) +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>settings : Symbol(settings, Decl(file.js, 89, 12)) +>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(file.js, 133, 61)) + + if (!_id.test(key)) { +>_id.test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) +>_id : Symbol(_id, Decl(file.js, 121, 11)) +>test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(file.js, 133, 61)) + + return; + } + const value = Meteor.settings[key]; +>value : Symbol(value, Decl(file.js, 137, 25)) +>Meteor.settings : Symbol(settings, Decl(file.js, 89, 12)) +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>settings : Symbol(settings, Decl(file.js, 89, 12)) +>key : Symbol(key, Decl(file.js, 133, 61)) + + callback(key, value); +>callback : Symbol(callback, Decl(file.js, 121, 15)) +>key : Symbol(key, Decl(file.js, 133, 61)) +>value : Symbol(value, Decl(file.js, 137, 25)) + + }); + } + + if (typeof _id === 'string') { +>_id : Symbol(_id, Decl(file.js, 121, 11)) + + const value = Meteor.settings[_id]; +>value : Symbol(value, Decl(file.js, 143, 21)) +>Meteor.settings : Symbol(settings, Decl(file.js, 89, 12)) +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>settings : Symbol(settings, Decl(file.js, 89, 12)) +>_id : Symbol(_id, Decl(file.js, 121, 11)) + + if (value != null) { +>value : Symbol(value, Decl(file.js, 143, 21)) + + callback(_id, Meteor.settings[_id]); +>callback : Symbol(callback, Decl(file.js, 121, 15)) +>_id : Symbol(_id, Decl(file.js, 121, 11)) +>Meteor.settings : Symbol(settings, Decl(file.js, 89, 12)) +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>settings : Symbol(settings, Decl(file.js, 89, 12)) +>_id : Symbol(_id, Decl(file.js, 121, 11)) + } + return; // Ok + } + + return; // Ok, needed for exhaustiveness check + } + + if (!Meteor.settings) { +>Meteor.settings : Symbol(settings, Decl(file.js, 89, 12)) +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>settings : Symbol(settings, Decl(file.js, 89, 12)) + + return undefined; // Error +>undefined : Symbol(undefined) + } + + if (_.isRegExp(_id)) { +>_.isRegExp : Symbol(isRegExp, Decl(file.js, 91, 12)) +>_ : Symbol(_, Decl(file.js, 92, 5)) +>isRegExp : Symbol(isRegExp, Decl(file.js, 91, 12)) +>_id : Symbol(_id, Decl(file.js, 121, 11)) + + return Object.keys(Meteor.settings).reduce((/** @type {SettingComposedValue[]} */ items, key) => { +>Object.keys(Meteor.settings).reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --)) +>Meteor.settings : Symbol(settings, Decl(file.js, 89, 12)) +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>settings : Symbol(settings, Decl(file.js, 89, 12)) +>reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>items : Symbol(items, Decl(file.js, 158, 56)) +>key : Symbol(key, Decl(file.js, 158, 103)) + + const value = Meteor.settings[key]; +>value : Symbol(value, Decl(file.js, 159, 21)) +>Meteor.settings : Symbol(settings, Decl(file.js, 89, 12)) +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>settings : Symbol(settings, Decl(file.js, 89, 12)) +>key : Symbol(key, Decl(file.js, 158, 103)) + + if (_id.test(key)) { +>_id.test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) +>_id : Symbol(_id, Decl(file.js, 121, 11)) +>test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(file.js, 158, 103)) + + items.push({ key, value }); +>items.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>items : Symbol(items, Decl(file.js, 158, 56)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(file.js, 161, 32)) +>value : Symbol(value, Decl(file.js, 161, 37)) + } + return items; +>items : Symbol(items, Decl(file.js, 158, 56)) + + }, []); // Ok + } + + return Meteor.settings?.[_id]; // Error +>Meteor.settings : Symbol(settings, Decl(file.js, 89, 12)) +>Meteor : Symbol(Meteor, Decl(file.js, 90, 5)) +>settings : Symbol(settings, Decl(file.js, 89, 12)) +>_id : Symbol(_id, Decl(file.js, 121, 11)) + } +} + +// File: Rocket.Chat/apps/meteor/app/ui-utils/client/lib/messageBox.ts + +/** + * @typedef {MyObj} MessageBoxAction + */ + +/** + * @template {string | undefined} T + * @param {T} group + * @returns {HelperCond>} + */ +function getWithBug(group) { +>getWithBug : Symbol(getWithBug, Decl(file.js, 169, 1)) +>group : Symbol(group, Decl(file.js, 182, 20)) + + if (!group) { +>group : Symbol(group, Decl(file.js, 182, 20)) + + return /** @type {Record} */({}); // Error + } + return /** @type {MessageBoxAction[]} */([]); // Ok +} + +/** + * @template {string | undefined} T + * @param {T} group + * @returns {HelperCond>} + */ +function getWithoutBug(group) { +>getWithoutBug : Symbol(getWithoutBug, Decl(file.js, 187, 1)) +>group : Symbol(group, Decl(file.js, 194, 23)) + + if (group === undefined) { +>group : Symbol(group, Decl(file.js, 194, 23)) +>undefined : Symbol(undefined) + + return /** @type {Record} */({}); // Ok + } + return /** @type {MessageBoxAction[]} */([]); // Ok +} + +// File: Rocket.Chat/apps/meteor/ee/server/lib/engagementDashboard/date.ts + +/** + * @param {string} x + * @returns {Date} + */ +function mapDateForAPI(x) { +>mapDateForAPI : Symbol(mapDateForAPI, Decl(file.js, 199, 1)) +>x : Symbol(x, Decl(file.js, 207, 23)) + + return /** @type {any} */ (undefined); +>undefined : Symbol(undefined) +} + +/** + * @template {string | undefined} T + * @param {string} start + * @param {T} [end] + * @returns {HelperCond} + */ +function transformDatesForAPI(start, end) { +>transformDatesForAPI : Symbol(transformDatesForAPI, Decl(file.js, 209, 1)) +>start : Symbol(start, Decl(file.js, 217, 30)) +>end : Symbol(end, Decl(file.js, 217, 36)) + + return end !== undefined ? +>end : Symbol(end, Decl(file.js, 217, 36)) +>undefined : Symbol(undefined) + { + start: mapDateForAPI(start), +>start : Symbol(start, Decl(file.js, 219, 9)) +>mapDateForAPI : Symbol(mapDateForAPI, Decl(file.js, 199, 1)) +>start : Symbol(start, Decl(file.js, 217, 30)) + + end: mapDateForAPI(end), +>end : Symbol(end, Decl(file.js, 220, 40)) +>mapDateForAPI : Symbol(mapDateForAPI, Decl(file.js, 199, 1)) +>end : Symbol(end, Decl(file.js, 217, 36)) + + } : + { + start: mapDateForAPI(start), +>start : Symbol(start, Decl(file.js, 223, 9)) +>mapDateForAPI : Symbol(mapDateForAPI, Decl(file.js, 199, 1)) +>start : Symbol(start, Decl(file.js, 217, 30)) + + end: undefined +>end : Symbol(end, Decl(file.js, 224, 40)) +>undefined : Symbol(undefined) + + }; +} + +// File: Rocket.Chat/packages/agenda/src/Agenda.ts + +/** + * @typedef {MyObj} RepeatOptions + */ + +/** + * @typedef {MyObj} Job + */ + +/** + * @typedef {Object} IJob + * @property {MyObj} data + */ +class NewAgenda { +>NewAgenda : Symbol(NewAgenda, Decl(file.js, 227, 1)) + + /** + * @param {string | number} interval + * @param {string} name + * @param {IJob['data']} data + * @param {RepeatOptions} options + * @returns {Promise} + */ + async _createIntervalJob(interval, name, data, options) { +>_createIntervalJob : Symbol(NewAgenda._createIntervalJob, Decl(file.js, 243, 17)) +>interval : Symbol(interval, Decl(file.js, 251, 29)) +>name : Symbol(name, Decl(file.js, 251, 38)) +>data : Symbol(data, Decl(file.js, 251, 44)) +>options : Symbol(options, Decl(file.js, 251, 50)) + + return /** @type {any} */ (undefined); +>undefined : Symbol(undefined) + } + + /** + * @param {string | number} interval + * @param {string[]} names + * @param {IJob['data']} data + * @param {RepeatOptions} options + * @returns {Promise | undefined} + */ + _createIntervalJobs(interval, names, data, options) { +>_createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(file.js, 253, 5)) +>interval : Symbol(interval, Decl(file.js, 262, 24)) +>names : Symbol(names, Decl(file.js, 262, 33)) +>data : Symbol(data, Decl(file.js, 262, 40)) +>options : Symbol(options, Decl(file.js, 262, 46)) + + return undefined; +>undefined : Symbol(undefined) + } + + /** + * @template {string | string[]} T + * @param {string | number} interval + * @param {T} name + * @param {IJob['data']} data + * @param {RepeatOptions} options + * @returns {Promise>} + */ + async newEvery(interval, name, data, options) { +>newEvery : Symbol(NewAgenda.newEvery, Decl(file.js, 264, 5)) +>interval : Symbol(interval, Decl(file.js, 274, 19)) +>name : Symbol(name, Decl(file.js, 274, 28)) +>data : Symbol(data, Decl(file.js, 274, 34)) +>options : Symbol(options, Decl(file.js, 274, 40)) + + if (typeof name === 'string') { +>name : Symbol(name, Decl(file.js, 274, 28)) + + return this._createIntervalJob(interval, name, data, options); // Ok +>this._createIntervalJob : Symbol(NewAgenda._createIntervalJob, Decl(file.js, 243, 17)) +>this : Symbol(NewAgenda, Decl(file.js, 227, 1)) +>_createIntervalJob : Symbol(NewAgenda._createIntervalJob, Decl(file.js, 243, 17)) +>interval : Symbol(interval, Decl(file.js, 274, 19)) +>name : Symbol(name, Decl(file.js, 274, 28)) +>data : Symbol(data, Decl(file.js, 274, 34)) +>options : Symbol(options, Decl(file.js, 274, 40)) + } + + if (Array.isArray(name)) { +>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more) +>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>name : Symbol(name, Decl(file.js, 274, 28)) + + return this._createIntervalJobs(interval, name, data, options); // Ok +>this._createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(file.js, 253, 5)) +>this : Symbol(NewAgenda, Decl(file.js, 227, 1)) +>_createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(file.js, 253, 5)) +>interval : Symbol(interval, Decl(file.js, 274, 19)) +>name : Symbol(name, Decl(file.js, 274, 28)) +>data : Symbol(data, Decl(file.js, 274, 34)) +>options : Symbol(options, Decl(file.js, 274, 40)) + } + + throw new Error('Unexpected error: Invalid job name(s)'); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --)) + } +} + +// File: angular/packages/common/src/pipes/case_conversion_pipes.ts + +/** + * @template {string | null | undefined} T + * @param {T} value + * @returns {HelperCond} + */ +function transform1(value) { +>transform1 : Symbol(transform1, Decl(file.js, 285, 1)) +>value : Symbol(value, Decl(file.js, 294, 20)) + + if (value == null) return null; // Ok +>value : Symbol(value, Decl(file.js, 294, 20)) + + if (typeof value !== 'string') { +>value : Symbol(value, Decl(file.js, 294, 20)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --)) + } + return value.toLowerCase(); // Ok +>value.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +>value : Symbol(value, Decl(file.js, 294, 20)) +>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +} + diff --git a/tests/baselines/reference/dependentReturnType2.types b/tests/baselines/reference/dependentReturnType2.types new file mode 100644 index 0000000000000..1adf92c29a8fd --- /dev/null +++ b/tests/baselines/reference/dependentReturnType2.types @@ -0,0 +1,1007 @@ +//// [tests/cases/compiler/dependentReturnType2.ts] //// + +=== file.js === +// Adapted from ts-error-deltas repos + +/** + * @template T + * @template A + * @template R1 + * @template B + * @template R2 + * @typedef {T extends A ? R1 : T extends B ? R2 : never} HelperCond + */ + +/** + * @typedef IMessage + * @property {string} [html] + * @property {Object[]} [tokens] + */ + +class NewKatex { +>NewKatex : NewKatex +> : ^^^^^^^^ + + /** + * @param {string} s + * @returns {string} + */ + render(s) { +>render : (s: string) => string +> : ^ ^^ ^^^^^ +>s : string +> : ^^^^^^ + + return ""; +>"" : "" +> : ^^ + } + + /** + * @template {string | IMessage} T + * @param {T} message + * @returns {T extends string ? string : T extends IMessage ? IMessage : never} + */ + renderMessage(message) { +>renderMessage : (message: T) => T extends string ? string : T extends IMessage ? IMessage : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>message : T +> : ^ + + if (typeof message === 'string') { +>typeof message === 'string' : boolean +> : ^^^^^^^ +>typeof message : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>message : T +> : ^ +>'string' : "string" +> : ^^^^^^^^ + + return this.render(message); // Ok +>this.render(message) : string +> : ^^^^^^ +>this.render : (s: string) => string +> : ^ ^^ ^^^^^ +>this : this +> : ^^^^ +>render : (s: string) => string +> : ^ ^^ ^^^^^ +>message : string +> : ^^^^^^ + } + + if (!message.html?.trim()) { +>!message.html?.trim() : boolean +> : ^^^^^^^ +>message.html?.trim() : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>message.html?.trim : (() => string) | undefined +> : ^^^^^^^ ^^^^^^^^^^^^^ +>message.html : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>message : IMessage +> : ^^^^^^^^ +>html : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>trim : (() => string) | undefined +> : ^^^^^^^ ^^^^^^^^^^^^^ + + return message; // Ok +>message : IMessage +> : ^^^^^^^^ + } + + if (!message.tokens) { +>!message.tokens : boolean +> : ^^^^^^^ +>message.tokens : Object[] | undefined +> : ^^^^^^^^^^^^^^^^^^^^ +>message : IMessage +> : ^^^^^^^^ +>tokens : Object[] | undefined +> : ^^^^^^^^^^^^^^^^^^^^ + + message.tokens = []; +>message.tokens = [] : never[] +> : ^^^^^^^ +>message.tokens : Object[] | undefined +> : ^^^^^^^^^^^^^^^^^^^^ +>message : IMessage +> : ^^^^^^^^ +>tokens : Object[] | undefined +> : ^^^^^^^^^^^^^^^^^^^^ +>[] : never[] +> : ^^^^^^^ + } + + message.html = this.render(message.html); +>message.html = this.render(message.html) : string +> : ^^^^^^ +>message.html : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>message : IMessage +> : ^^^^^^^^ +>html : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>this.render(message.html) : string +> : ^^^^^^ +>this.render : (s: string) => string +> : ^ ^^ ^^^^^ +>this : this +> : ^^^^ +>render : (s: string) => string +> : ^ ^^ ^^^^^ +>message.html : string +> : ^^^^^^ +>message : IMessage +> : ^^^^^^^^ +>html : string +> : ^^^^^^ + + return message; // Ok +>message : IMessage +> : ^^^^^^^^ + } +} + +/** + * @template {true | false} T + * @param {{ dollarSyntax: boolean; parenthesisSyntax: boolean; }} options + * @param {T} _isMessage + * @returns {T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : never} + */ +function createKatexMessageRendering(options, _isMessage) { +>createKatexMessageRendering : (options: { dollarSyntax: boolean; parenthesisSyntax: boolean; }, _isMessage: T) => T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>options : { dollarSyntax: boolean; parenthesisSyntax: boolean; } +> : ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^ +>_isMessage : T +> : ^ + + const instance = new NewKatex(); +>instance : NewKatex +> : ^^^^^^^^ +>new NewKatex() : NewKatex +> : ^^^^^^^^ +>NewKatex : typeof NewKatex +> : ^^^^^^^^^^^^^^^ + + if (_isMessage) { +>_isMessage : T +> : ^ + + return (/** @type {IMessage} */ message) => instance.renderMessage(message); // Ok +>(/** @type {IMessage} */ message) => instance.renderMessage(message) : (message: IMessage) => IMessage +> : ^ ^^ ^^^^^^^^^^^^^ +>message : IMessage +> : ^^^^^^^^ +>instance.renderMessage(message) : IMessage +> : ^^^^^^^^ +>instance.renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : never +> : ^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>instance : NewKatex +> : ^^^^^^^^ +>renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : never +> : ^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>message : IMessage +> : ^^^^^^^^ + } + return (/** @type {string} */ message) => instance.renderMessage(message); // Ok +>(/** @type {string} */ message) => instance.renderMessage(message) : (message: string) => string +> : ^ ^^ ^^^^^^^^^^^ +>message : string +> : ^^^^^^ +>instance.renderMessage(message) : string +> : ^^^^^^ +>instance.renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : never +> : ^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>instance : NewKatex +> : ^^^^^^^^ +>renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : never +> : ^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>message : string +> : ^^^^^^ +} + +// File: Rocket.Chat/apps/meteor/app/settings/lib/settings.ts + +/** + * @typedef {Record} MyObj + */ + + +/** + * @typedef {MyObj} SettingValue + */ + +/** + * @template {SettingValue} T + * @typedef {Object} SettingComposedValue + * @property {string} key + * @property {SettingValue} value + */ + +/** + * @callback SettingCallback + * @param {string} key + * @param {SettingValue} value + * @param {boolean} [initialLoad] + * @returns {void} + */ + +/** @type {{ settings: { [s: string]: any } }} */ +const Meteor = /** @type {any} */ (undefined); +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>(undefined) : any +> : ^^^ +>undefined : undefined +> : ^^^^^^^^^ + +/** @type {{ isRegExp(x: unknown): x is RegExp; }} */ +const _ = /** @type {any} */ (undefined); +>_ : { isRegExp(x: unknown): x is RegExp; } +> : ^^^^^^^^^^^ ^^ ^^^ ^^^ +>(undefined) : any +> : ^^^ +>undefined : undefined +> : ^^^^^^^^^ + +/** + * @param {RegExp} x + * @returns {void} + */ +function takesRegExp(x) { +>takesRegExp : (x: RegExp) => void +> : ^ ^^ ^^^^^ +>x : RegExp +> : ^^^^^^ + + return /** @type {any} */ undefined; +>undefined : undefined +> : ^^^^^^^^^ +} +/** + * @param {string} x + * @returns {void} + */ +function takesString(x) { +>takesString : (x: string) => void +> : ^ ^^ ^^^^^ +>x : string +> : ^^^^^^ + + return /** @type {any} */ undefined; +>undefined : undefined +> : ^^^^^^^^^ +} + +/** + * @class NewSettingsBase + */ +class NewSettingsBase { +>NewSettingsBase : NewSettingsBase +> : ^^^^^^^^^^^^^^^ + + /** + * @template {SettingCallback | undefined} C + * @template {string | RegExp} I + * @template {SettingValue} T + * @param {I} _id + * @param {C} [callback] + * @returns {HelperCond[]>>} + */ + newGet(_id, callback) { +>newGet : (_id: I, callback?: C) => HelperCond[]>> +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^^^ +>_id : I +> : ^ +>callback : C | undefined +> : ^^^^^^^^^^^^^ + + if (callback !== undefined) { +>callback !== undefined : boolean +> : ^^^^^^^ +>callback : C | undefined +> : ^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + + if (!Meteor.settings) { +>!Meteor.settings : false +> : ^^^^^ +>Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ + + return; // Ok + } + if (_id === '*') { +>_id === '*' : boolean +> : ^^^^^^^ +>_id : I +> : ^ +>'*' : "*" +> : ^^^ + + return Object.keys(Meteor.settings).forEach((key) => { +>Object.keys(Meteor.settings).forEach((key) => { const value = Meteor.settings[key]; callback(key, value); }) : void +> : ^^^^ +>Object.keys(Meteor.settings).forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void +> : ^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ +>Object.keys(Meteor.settings) : string[] +> : ^^^^^^^^ +>Object.keys : { (o: object): string[]; (o: {}): string[]; } +> : ^^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ +>Object : ObjectConstructor +> : ^^^^^^^^^^^^^^^^^ +>keys : { (o: object): string[]; (o: {}): string[]; } +> : ^^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ +>Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void +> : ^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ +>(key) => { const value = Meteor.settings[key]; callback(key, value); } : (key: string) => void +> : ^ ^^^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ + + const value = Meteor.settings[key]; +>value : any +> : ^^^ +>Meteor.settings[key] : any +> : ^^^ +>Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ + + callback(key, value); +>callback(key, value) : void +> : ^^^^ +>callback : SettingCallback +> : ^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ +>value : any +> : ^^^ + + }); + } + if (_.isRegExp(_id) && Meteor.settings) { +>_.isRegExp(_id) && Meteor.settings : false | { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>_.isRegExp(_id) : boolean +> : ^^^^^^^ +>_.isRegExp : (x: unknown) => x is RegExp +> : ^ ^^ ^^^^^ +>_ : { isRegExp(x: unknown): x is RegExp; } +> : ^^^^^^^^^^^ ^^ ^^^ ^^^ +>isRegExp : (x: unknown) => x is RegExp +> : ^ ^^ ^^^^^ +>_id : string | RegExp +> : ^^^^^^^^^^^^^^^ +>Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ + + return Object.keys(Meteor.settings).forEach((key) => { +>Object.keys(Meteor.settings).forEach((key) => { if (!_id.test(key)) { return; } const value = Meteor.settings[key]; callback(key, value); }) : void +> : ^^^^ +>Object.keys(Meteor.settings).forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void +> : ^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ +>Object.keys(Meteor.settings) : string[] +> : ^^^^^^^^ +>Object.keys : { (o: object): string[]; (o: {}): string[]; } +> : ^^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ +>Object : ObjectConstructor +> : ^^^^^^^^^^^^^^^^^ +>keys : { (o: object): string[]; (o: {}): string[]; } +> : ^^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ +>Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>forEach : (callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any) => void +> : ^ ^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ +>(key) => { if (!_id.test(key)) { return; } const value = Meteor.settings[key]; callback(key, value); } : (key: string) => void +> : ^ ^^^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ + + if (!_id.test(key)) { +>!_id.test(key) : boolean +> : ^^^^^^^ +>_id.test(key) : boolean +> : ^^^^^^^ +>_id.test : (string: string) => boolean +> : ^ ^^ ^^^^^ +>_id : RegExp +> : ^^^^^^ +>test : (string: string) => boolean +> : ^ ^^ ^^^^^ +>key : string +> : ^^^^^^ + + return; + } + const value = Meteor.settings[key]; +>value : any +> : ^^^ +>Meteor.settings[key] : any +> : ^^^ +>Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ + + callback(key, value); +>callback(key, value) : void +> : ^^^^ +>callback : SettingCallback +> : ^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ +>value : any +> : ^^^ + + }); + } + + if (typeof _id === 'string') { +>typeof _id === 'string' : boolean +> : ^^^^^^^ +>typeof _id : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>_id : I +> : ^ +>'string' : "string" +> : ^^^^^^^^ + + const value = Meteor.settings[_id]; +>value : any +> : ^^^ +>Meteor.settings[_id] : any +> : ^^^ +>Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>_id : I & string +> : ^^^^^^^^^^ + + if (value != null) { +>value != null : boolean +> : ^^^^^^^ +>value : any +> : ^^^ + + callback(_id, Meteor.settings[_id]); +>callback(_id, Meteor.settings[_id]) : void +> : ^^^^ +>callback : SettingCallback +> : ^^^^^^^^^^^^^^^ +>_id : string +> : ^^^^^^ +>Meteor.settings[_id] : any +> : ^^^ +>Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>_id : I & string +> : ^^^^^^^^^^ + } + return; // Ok + } + + return; // Ok, needed for exhaustiveness check + } + + if (!Meteor.settings) { +>!Meteor.settings : false +> : ^^^^^ +>Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ + + return undefined; // Error +>undefined : undefined +> : ^^^^^^^^^ + } + + if (_.isRegExp(_id)) { +>_.isRegExp(_id) : boolean +> : ^^^^^^^ +>_.isRegExp : (x: unknown) => x is RegExp +> : ^ ^^ ^^^^^ +>_ : { isRegExp(x: unknown): x is RegExp; } +> : ^^^^^^^^^^^ ^^ ^^^ ^^^ +>isRegExp : (x: unknown) => x is RegExp +> : ^ ^^ ^^^^^ +>_id : string | RegExp +> : ^^^^^^^^^^^^^^^ + + return Object.keys(Meteor.settings).reduce((/** @type {SettingComposedValue[]} */ items, key) => { +>Object.keys(Meteor.settings).reduce((/** @type {SettingComposedValue[]} */ items, key) => { const value = Meteor.settings[key]; if (_id.test(key)) { items.push({ key, value }); } return items; }, []) : SettingComposedValue[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +>Object.keys(Meteor.settings).reduce : { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; (callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; } +> : ^^^ ^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^ +>Object.keys(Meteor.settings) : string[] +> : ^^^^^^^^ +>Object.keys : { (o: object): string[]; (o: {}): string[]; } +> : ^^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ +>Object : ObjectConstructor +> : ^^^^^^^^^^^^^^^^^ +>keys : { (o: object): string[]; (o: {}): string[]; } +> : ^^^ ^^ ^^^ ^^^ ^^ ^^^ ^^^ +>Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>reduce : { (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; (callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; (callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; } +> : ^^^ ^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^ ^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^ +>(/** @type {SettingComposedValue[]} */ items, key) => { const value = Meteor.settings[key]; if (_id.test(key)) { items.push({ key, value }); } return items; } : (items: SettingComposedValue[], key: string) => SettingComposedValue[] +> : ^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>items : SettingComposedValue[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ + + const value = Meteor.settings[key]; +>value : any +> : ^^^ +>Meteor.settings[key] : any +> : ^^^ +>Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ + + if (_id.test(key)) { +>_id.test(key) : boolean +> : ^^^^^^^ +>_id.test : (string: string) => boolean +> : ^ ^^ ^^^^^ +>_id : RegExp +> : ^^^^^^ +>test : (string: string) => boolean +> : ^ ^^ ^^^^^ +>key : string +> : ^^^^^^ + + items.push({ key, value }); +>items.push({ key, value }) : number +> : ^^^^^^ +>items.push : (...items: SettingComposedValue[]) => number +> : ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>items : SettingComposedValue[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +>push : (...items: SettingComposedValue[]) => number +> : ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>{ key, value } : { key: string; value: any; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ +>value : any +> : ^^^ + } + return items; +>items : SettingComposedValue[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ + + }, []); // Ok +>[] : never[] +> : ^^^^^^^ + } + + return Meteor.settings?.[_id]; // Error +>Meteor.settings?.[_id] : any +> : ^^^ +>Meteor.settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>Meteor : { settings: { [s: string]: any; }; } +> : ^^^^^^^^^^^^ ^^^ +>settings : { [s: string]: any; } +> : ^^^^^^^^^^^^^^^^^^^^^ +>_id : I +> : ^ + } +} + +// File: Rocket.Chat/apps/meteor/app/ui-utils/client/lib/messageBox.ts + +/** + * @typedef {MyObj} MessageBoxAction + */ + +/** + * @template {string | undefined} T + * @param {T} group + * @returns {HelperCond>} + */ +function getWithBug(group) { +>getWithBug : (group: T) => HelperCond> +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>group : T +> : ^ + + if (!group) { +>!group : boolean +> : ^^^^^^^ +>group : T +> : ^ + + return /** @type {Record} */({}); // Error +>({}) : Record +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>{} : {} +> : ^^ + } + return /** @type {MessageBoxAction[]} */([]); // Ok +>([]) : MyObj[] +> : ^^^^^^^ +>[] : never[] +> : ^^^^^^^ +} + +/** + * @template {string | undefined} T + * @param {T} group + * @returns {HelperCond>} + */ +function getWithoutBug(group) { +>getWithoutBug : (group: T) => HelperCond> +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>group : T +> : ^ + + if (group === undefined) { +>group === undefined : boolean +> : ^^^^^^^ +>group : T +> : ^ +>undefined : undefined +> : ^^^^^^^^^ + + return /** @type {Record} */({}); // Ok +>({}) : Record +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>{} : {} +> : ^^ + } + return /** @type {MessageBoxAction[]} */([]); // Ok +>([]) : MyObj[] +> : ^^^^^^^ +>[] : never[] +> : ^^^^^^^ +} + +// File: Rocket.Chat/apps/meteor/ee/server/lib/engagementDashboard/date.ts + +/** + * @param {string} x + * @returns {Date} + */ +function mapDateForAPI(x) { +>mapDateForAPI : (x: string) => Date +> : ^ ^^ ^^^^^ +>x : string +> : ^^^^^^ + + return /** @type {any} */ (undefined); +>(undefined) : any +> : ^^^ +>undefined : undefined +> : ^^^^^^^^^ +} + +/** + * @template {string | undefined} T + * @param {string} start + * @param {T} [end] + * @returns {HelperCond} + */ +function transformDatesForAPI(start, end) { +>transformDatesForAPI : (start: string, end?: T) => HelperCond +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^^^ +>start : string +> : ^^^^^^ +>end : T | undefined +> : ^^^^^^^^^^^^^ + + return end !== undefined ? +>end !== undefined ? { start: mapDateForAPI(start), end: mapDateForAPI(end), } : { start: mapDateForAPI(start), end: undefined } : { start: Date; end: Date; } | { start: Date; end: undefined; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>end !== undefined : boolean +> : ^^^^^^^ +>end : T | undefined +> : ^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + { +>{ start: mapDateForAPI(start), end: mapDateForAPI(end), } : { start: Date; end: Date; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + start: mapDateForAPI(start), +>start : Date +> : ^^^^ +>mapDateForAPI(start) : Date +> : ^^^^ +>mapDateForAPI : (x: string) => Date +> : ^ ^^ ^^^^^ +>start : string +> : ^^^^^^ + + end: mapDateForAPI(end), +>end : Date +> : ^^^^ +>mapDateForAPI(end) : Date +> : ^^^^ +>mapDateForAPI : (x: string) => Date +> : ^ ^^ ^^^^^ +>end : string +> : ^^^^^^ + + } : + { +>{ start: mapDateForAPI(start), end: undefined } : { start: Date; end: undefined; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + start: mapDateForAPI(start), +>start : Date +> : ^^^^ +>mapDateForAPI(start) : Date +> : ^^^^ +>mapDateForAPI : (x: string) => Date +> : ^ ^^ ^^^^^ +>start : string +> : ^^^^^^ + + end: undefined +>end : undefined +> : ^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + + }; +} + +// File: Rocket.Chat/packages/agenda/src/Agenda.ts + +/** + * @typedef {MyObj} RepeatOptions + */ + +/** + * @typedef {MyObj} Job + */ + +/** + * @typedef {Object} IJob + * @property {MyObj} data + */ +class NewAgenda { +>NewAgenda : NewAgenda +> : ^^^^^^^^^ + + /** + * @param {string | number} interval + * @param {string} name + * @param {IJob['data']} data + * @param {RepeatOptions} options + * @returns {Promise} + */ + async _createIntervalJob(interval, name, data, options) { +>_createIntervalJob : (interval: string | number, name: string, data: IJob["data"], options: RepeatOptions) => Promise +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ +>interval : string | number +> : ^^^^^^^^^^^^^^^ +>name : string +> : ^^^^^^ +>data : MyObj +> : ^^^^^ +>options : MyObj +> : ^^^^^ + + return /** @type {any} */ (undefined); +>(undefined) : any +> : ^^^ +>undefined : undefined +> : ^^^^^^^^^ + } + + /** + * @param {string | number} interval + * @param {string[]} names + * @param {IJob['data']} data + * @param {RepeatOptions} options + * @returns {Promise | undefined} + */ + _createIntervalJobs(interval, names, data, options) { +>_createIntervalJobs : (interval: string | number, names: string[], data: IJob["data"], options: RepeatOptions) => Promise | undefined +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ +>interval : string | number +> : ^^^^^^^^^^^^^^^ +>names : string[] +> : ^^^^^^^^ +>data : MyObj +> : ^^^^^ +>options : MyObj +> : ^^^^^ + + return undefined; +>undefined : undefined +> : ^^^^^^^^^ + } + + /** + * @template {string | string[]} T + * @param {string | number} interval + * @param {T} name + * @param {IJob['data']} data + * @param {RepeatOptions} options + * @returns {Promise>} + */ + async newEvery(interval, name, data, options) { +>newEvery : (interval: string | number, name: T, data: IJob["data"], options: RepeatOptions) => Promise> +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ +>interval : string | number +> : ^^^^^^^^^^^^^^^ +>name : T +> : ^ +>data : MyObj +> : ^^^^^ +>options : MyObj +> : ^^^^^ + + if (typeof name === 'string') { +>typeof name === 'string' : boolean +> : ^^^^^^^ +>typeof name : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>name : T +> : ^ +>'string' : "string" +> : ^^^^^^^^ + + return this._createIntervalJob(interval, name, data, options); // Ok +>this._createIntervalJob(interval, name, data, options) : Promise +> : ^^^^^^^^^^^^^^ +>this._createIntervalJob : (interval: string | number, name: string, data: IJob["data"], options: RepeatOptions) => Promise +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ +>this : this +> : ^^^^ +>_createIntervalJob : (interval: string | number, name: string, data: IJob["data"], options: RepeatOptions) => Promise +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ +>interval : string | number +> : ^^^^^^^^^^^^^^^ +>name : string +> : ^^^^^^ +>data : MyObj +> : ^^^^^ +>options : MyObj +> : ^^^^^ + } + + if (Array.isArray(name)) { +>Array.isArray(name) : boolean +> : ^^^^^^^ +>Array.isArray : (arg: any) => arg is any[] +> : ^ ^^ ^^^^^ +>Array : ArrayConstructor +> : ^^^^^^^^^^^^^^^^ +>isArray : (arg: any) => arg is any[] +> : ^ ^^ ^^^^^ +>name : string[] +> : ^^^^^^^^ + + return this._createIntervalJobs(interval, name, data, options); // Ok +>this._createIntervalJobs(interval, name, data, options) : Promise | undefined +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>this._createIntervalJobs : (interval: string | number, names: string[], data: IJob["data"], options: RepeatOptions) => Promise | undefined +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ +>this : this +> : ^^^^ +>_createIntervalJobs : (interval: string | number, names: string[], data: IJob["data"], options: RepeatOptions) => Promise | undefined +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ +>interval : string | number +> : ^^^^^^^^^^^^^^^ +>name : string[] +> : ^^^^^^^^ +>data : MyObj +> : ^^^^^ +>options : MyObj +> : ^^^^^ + } + + throw new Error('Unexpected error: Invalid job name(s)'); +>new Error('Unexpected error: Invalid job name(s)') : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ +>'Unexpected error: Invalid job name(s)' : "Unexpected error: Invalid job name(s)" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + } +} + +// File: angular/packages/common/src/pipes/case_conversion_pipes.ts + +/** + * @template {string | null | undefined} T + * @param {T} value + * @returns {HelperCond} + */ +function transform1(value) { +>transform1 : (value: T) => HelperCond +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>value : T +> : ^ + + if (value == null) return null; // Ok +>value == null : boolean +> : ^^^^^^^ +>value : T +> : ^ + + if (typeof value !== 'string') { +>typeof value !== 'string' : boolean +> : ^^^^^^^ +>typeof value : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>value : NonNullable +> : ^^^^^^^^^^^^^^ +>'string' : "string" +> : ^^^^^^^^ + + throw new Error(); +>new Error() : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + } + return value.toLowerCase(); // Ok +>value.toLowerCase() : string +> : ^^^^^^ +>value.toLowerCase : () => string +> : ^^^^^^ +>value : string +> : ^^^^^^ +>toLowerCase : () => string +> : ^^^^^^ +} + diff --git a/tests/cases/compiler/dependentReturnType2.ts b/tests/cases/compiler/dependentReturnType2.ts new file mode 100644 index 0000000000000..0f14e3f7faa87 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType2.ts @@ -0,0 +1,307 @@ +// @strict: true +// @noEmit: true +// @target: esnext +// @checkJs: true +// @filename: file.js + +// Adapted from ts-error-deltas repos + +/** + * @template T + * @template A + * @template R1 + * @template B + * @template R2 + * @typedef {T extends A ? R1 : T extends B ? R2 : never} HelperCond + */ + +/** + * @typedef IMessage + * @property {string} [html] + * @property {Object[]} [tokens] + */ + +class NewKatex { + /** + * @param {string} s + * @returns {string} + */ + render(s) { + return ""; + } + + /** + * @template {string | IMessage} T + * @param {T} message + * @returns {T extends string ? string : T extends IMessage ? IMessage : never} + */ + renderMessage(message) { + if (typeof message === 'string') { + return this.render(message); // Ok + } + + if (!message.html?.trim()) { + return message; // Ok + } + + if (!message.tokens) { + message.tokens = []; + } + + message.html = this.render(message.html); + return message; // Ok + } +} + +/** + * @template {true | false} T + * @param {{ dollarSyntax: boolean; parenthesisSyntax: boolean; }} options + * @param {T} _isMessage + * @returns {T extends true ? (message: IMessage) => IMessage : T extends false ? (message: string) => string : never} + */ +function createKatexMessageRendering(options, _isMessage) { + const instance = new NewKatex(); + if (_isMessage) { + return (/** @type {IMessage} */ message) => instance.renderMessage(message); // Ok + } + return (/** @type {string} */ message) => instance.renderMessage(message); // Ok +} + +// File: Rocket.Chat/apps/meteor/app/settings/lib/settings.ts + +/** + * @typedef {Record} MyObj + */ + + +/** + * @typedef {MyObj} SettingValue + */ + +/** + * @template {SettingValue} T + * @typedef {Object} SettingComposedValue + * @property {string} key + * @property {SettingValue} value + */ + +/** + * @callback SettingCallback + * @param {string} key + * @param {SettingValue} value + * @param {boolean} [initialLoad] + * @returns {void} + */ + +/** @type {{ settings: { [s: string]: any } }} */ +const Meteor = /** @type {any} */ (undefined); +/** @type {{ isRegExp(x: unknown): x is RegExp; }} */ +const _ = /** @type {any} */ (undefined); + +/** + * @param {RegExp} x + * @returns {void} + */ +function takesRegExp(x) { + return /** @type {any} */ undefined; +} +/** + * @param {string} x + * @returns {void} + */ +function takesString(x) { + return /** @type {any} */ undefined; +} + +/** + * @class NewSettingsBase + */ +class NewSettingsBase { + /** + * @template {SettingCallback | undefined} C + * @template {string | RegExp} I + * @template {SettingValue} T + * @param {I} _id + * @param {C} [callback] + * @returns {HelperCond[]>>} + */ + newGet(_id, callback) { + if (callback !== undefined) { + if (!Meteor.settings) { + return; // Ok + } + if (_id === '*') { + return Object.keys(Meteor.settings).forEach((key) => { + const value = Meteor.settings[key]; + callback(key, value); + }); + } + if (_.isRegExp(_id) && Meteor.settings) { + return Object.keys(Meteor.settings).forEach((key) => { + if (!_id.test(key)) { + return; + } + const value = Meteor.settings[key]; + callback(key, value); + }); + } + + if (typeof _id === 'string') { + const value = Meteor.settings[_id]; + if (value != null) { + callback(_id, Meteor.settings[_id]); + } + return; // Ok + } + + return; // Ok, needed for exhaustiveness check + } + + if (!Meteor.settings) { + return undefined; // Error + } + + if (_.isRegExp(_id)) { + return Object.keys(Meteor.settings).reduce((/** @type {SettingComposedValue[]} */ items, key) => { + const value = Meteor.settings[key]; + if (_id.test(key)) { + items.push({ key, value }); + } + return items; + }, []); // Ok + } + + return Meteor.settings?.[_id]; // Error + } +} + +// File: Rocket.Chat/apps/meteor/app/ui-utils/client/lib/messageBox.ts + +/** + * @typedef {MyObj} MessageBoxAction + */ + +/** + * @template {string | undefined} T + * @param {T} group + * @returns {HelperCond>} + */ +function getWithBug(group) { + if (!group) { + return /** @type {Record} */({}); // Error + } + return /** @type {MessageBoxAction[]} */([]); // Ok +} + +/** + * @template {string | undefined} T + * @param {T} group + * @returns {HelperCond>} + */ +function getWithoutBug(group) { + if (group === undefined) { + return /** @type {Record} */({}); // Ok + } + return /** @type {MessageBoxAction[]} */([]); // Ok +} + +// File: Rocket.Chat/apps/meteor/ee/server/lib/engagementDashboard/date.ts + +/** + * @param {string} x + * @returns {Date} + */ +function mapDateForAPI(x) { + return /** @type {any} */ (undefined); +} + +/** + * @template {string | undefined} T + * @param {string} start + * @param {T} [end] + * @returns {HelperCond} + */ +function transformDatesForAPI(start, end) { + return end !== undefined ? + { + start: mapDateForAPI(start), + end: mapDateForAPI(end), + } : + { + start: mapDateForAPI(start), + end: undefined + }; +} + +// File: Rocket.Chat/packages/agenda/src/Agenda.ts + +/** + * @typedef {MyObj} RepeatOptions + */ + +/** + * @typedef {MyObj} Job + */ + +/** + * @typedef {Object} IJob + * @property {MyObj} data + */ +class NewAgenda { + /** + * @param {string | number} interval + * @param {string} name + * @param {IJob['data']} data + * @param {RepeatOptions} options + * @returns {Promise} + */ + async _createIntervalJob(interval, name, data, options) { + return /** @type {any} */ (undefined); + } + + /** + * @param {string | number} interval + * @param {string[]} names + * @param {IJob['data']} data + * @param {RepeatOptions} options + * @returns {Promise | undefined} + */ + _createIntervalJobs(interval, names, data, options) { + return undefined; + } + + /** + * @template {string | string[]} T + * @param {string | number} interval + * @param {T} name + * @param {IJob['data']} data + * @param {RepeatOptions} options + * @returns {Promise>} + */ + async newEvery(interval, name, data, options) { + if (typeof name === 'string') { + return this._createIntervalJob(interval, name, data, options); // Ok + } + + if (Array.isArray(name)) { + return this._createIntervalJobs(interval, name, data, options); // Ok + } + + throw new Error('Unexpected error: Invalid job name(s)'); + } +} + +// File: angular/packages/common/src/pipes/case_conversion_pipes.ts + +/** + * @template {string | null | undefined} T + * @param {T} value + * @returns {HelperCond} + */ +function transform1(value) { + if (value == null) return null; // Ok + if (typeof value !== 'string') { + throw new Error(); + } + return value.toLowerCase(); // Ok +} From eb8841556276ba2df759d03828672df185695793 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 18 Oct 2024 16:21:37 -0700 Subject: [PATCH 83/90] support arrow expressions with expression body, small fixes --- src/compiler/binder.ts | 17 +++--- src/compiler/checker.ts | 56 ++++++++++--------- .../reference/dependentReturnType8.symbols | 29 ++++++++++ .../reference/dependentReturnType8.types | 37 ++++++++++++ ...inferFromGenericFunctionReturnTypes3.types | 3 - tests/cases/compiler/dependentReturnType8.ts | 11 ++++ 6 files changed, 117 insertions(+), 36 deletions(-) create mode 100644 tests/baselines/reference/dependentReturnType8.symbols create mode 100644 tests/baselines/reference/dependentReturnType8.types create mode 100644 tests/cases/compiler/dependentReturnType8.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 13c39914fec4c..b071f72b712c2 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -543,7 +543,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { var preSwitchCaseFlow: FlowNode | undefined; var activeLabelList: ActiveLabel | undefined; var hasExplicitReturn: boolean; - var inReturnStatement: boolean; + var inReturnPosition: boolean; var hasFlowEffects: boolean; // state used for emit helpers @@ -623,7 +623,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { currentExceptionTarget = undefined; activeLabelList = undefined; hasExplicitReturn = false; - inReturnStatement = false; + inReturnPosition = false; hasFlowEffects = false; inAssignmentPattern = false; emitFlags = NodeFlags.None; @@ -969,7 +969,9 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { const saveContainer = container; const saveThisParentContainer = thisParentContainer; const savedBlockScopeContainer = blockScopeContainer; + const savedInReturnPosition = inReturnPosition; + if (node.kind === SyntaxKind.ArrowFunction && node.body.kind !== SyntaxKind.Block) inReturnPosition = true; // Depending on what kind of node this is, we may have to adjust the current container // and block-container. If the current node is a container, then it is automatically // considered the current block-container as well. Also, for containers that we know @@ -1073,6 +1075,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { bindChildren(node); } + inReturnPosition = savedInReturnPosition; container = saveContainer; thisParentContainer = saveThisParentContainer; blockScopeContainer = savedBlockScopeContainer; @@ -1573,10 +1576,10 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { } function bindReturnOrThrow(node: ReturnStatement | ThrowStatement): void { - const oldInReturnStatement = inReturnStatement; - inReturnStatement = true; + const savedInReturnPosition = inReturnPosition; + inReturnPosition = true; bind(node.expression); - inReturnStatement = oldInReturnStatement; + inReturnPosition = savedInReturnPosition; if (node.kind === SyntaxKind.ReturnStatement) { hasExplicitReturn = true; if (currentReturnTarget) { @@ -2021,14 +2024,14 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { hasFlowEffects = false; bindCondition(node.condition, trueLabel, falseLabel); currentFlow = finishFlowLabel(trueLabel); - if (inReturnStatement) { + if (inReturnPosition) { node.flowNodeWhenTrue = currentFlow; } bind(node.questionToken); bind(node.whenTrue); addAntecedent(postExpressionLabel, currentFlow); currentFlow = finishFlowLabel(falseLabel); - if (inReturnStatement) { + if (inReturnPosition) { node.flowNodeWhenFalse = currentFlow; } bind(node.colonToken); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d14a7503ca508..a5962dbc17547 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -38894,17 +38894,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // should not be checking assignability of a promise to the return type. Instead, we need to // check assignability of the awaited type of the expression body against the promised type of // its return type annotation. - const exprType = checkExpression(node.body); const returnOrPromisedType = returnType && unwrapReturnType(returnType, functionFlags); if (returnOrPromisedType) { const effectiveCheckNode = getEffectiveCheckNode(node.body); - if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function - const awaitedType = checkAwaitedType(exprType, /*withAlias*/ false, effectiveCheckNode, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); - checkTypeAssignableToAndOptionallyElaborate(awaitedType, returnOrPromisedType, effectiveCheckNode, effectiveCheckNode); - } - else { // Normal function - checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, effectiveCheckNode, effectiveCheckNode); - } + checkReturnExpression(node, returnOrPromisedType, effectiveCheckNode, effectiveCheckNode); } } } @@ -45633,7 +45626,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else if (getReturnTypeFromAnnotation(container)) { - checkReturnStatementExpression(container, returnType, node, node.expression); + const unwrappedReturnType = unwrapReturnType(returnType, getFunctionFlags(container)) ?? returnType; + checkReturnExpression(container, unwrappedReturnType, node, node.expression); } } else if (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeUndefinedVoidOrAny(container, returnType)) { @@ -45642,22 +45636,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - function checkReturnStatementExpression( + // When checking an arrow expression such as `(x) => exp`, then `node` is the expression `exp`. + // Otherwise, `node` is a return statement. + function checkReturnExpression( container: SignatureDeclaration, - returnType: Type, - node: ReturnStatement, + unwrappedReturnType: Type, + node: ReturnStatement | Expression, expr: Expression | undefined, ): void { + const excludeJSDocTypeAssertions = isInJSFile(node); const functionFlags = getFunctionFlags(container); - const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; if (expr) { - const unwrappedExpr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + const unwrappedExpr = skipParentheses(expr, excludeJSDocTypeAssertions); if (isConditionalExpression(unwrappedExpr)) { - return checkConditionalReturnExpression(container, returnType, node, unwrappedExpr); + return checkConditionalReturnExpression(container, unwrappedReturnType, node, unwrappedExpr); } } - const exprType = expr ? checkExpressionCached(expr) : undefinedType; + const inReturnStatement = node.kind === SyntaxKind.ReturnStatement; + const exprType = expr + ? inReturnStatement + ? checkExpressionCached(expr) + : checkExpression(expr) + : undefinedType; const unwrappedExprType = functionFlags & FunctionFlags.Async ? checkAwaitedType( exprType, @@ -45667,17 +45668,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ) : exprType; - const errorNode = node.expression && isConditionalExpression(skipParentheses(node.expression)) ? expr : node; + const returnExpression = inReturnStatement ? (node as ReturnStatement).expression : node; + const isInConditionalExpression = returnExpression + && isConditionalExpression(skipParentheses(returnExpression, excludeJSDocTypeAssertions)); + const errorNode = isInConditionalExpression ? expr : node; + if (!(unwrappedReturnType.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional)) || !couldContainTypeVariables(unwrappedReturnType)) { + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); + return; + } // Check if type of return expression is assignable to original return type; - // If so, we don't need to narrow. + // If so, we don't need to narrow, even if we could. if (checkTypeAssignableTo(unwrappedExprType, unwrappedReturnType, /*errorNode*/ undefined)) { return; } - if (!(unwrappedReturnType.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional)) || !couldContainTypeVariables(unwrappedReturnType)) { - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); - return; - } const allTypeParameters = appendTypeParameters(getOuterTypeParameters(container, /*includeThisTypes*/ false), getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); const narrowableTypeParameters = allTypeParameters && getNarrowableTypeParameters(allTypeParameters); @@ -45704,7 +45708,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // |return;| // }` let narrowPosition: Node = node; - let narrowFlowNode = node.flowNode; + let narrowFlowNode = inReturnStatement && (node as ReturnStatement).flowNode; if (expr && isConditionalExpression(expr.parent)) { narrowFlowNode = expr.parent.whenTrue === expr ? expr.parent.flowNodeWhenTrue : expr.parent.flowNodeWhenFalse; narrowPosition = expr; @@ -45766,12 +45770,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function checkConditionalReturnExpression( container: SignatureDeclaration, returnType: Type, - node: ReturnStatement, + node: ReturnStatement | Expression, expr: ConditionalExpression, ): void { checkExpression(expr.condition); - checkReturnStatementExpression(container, returnType, node, expr.whenTrue); - checkReturnStatementExpression(container, returnType, node, expr.whenFalse); + checkReturnExpression(container, returnType, node, expr.whenTrue); + checkReturnExpression(container, returnType, node, expr.whenFalse); } // Narrowable type parameters are type parameters that: diff --git a/tests/baselines/reference/dependentReturnType8.symbols b/tests/baselines/reference/dependentReturnType8.symbols new file mode 100644 index 0000000000000..ad5429d59bac8 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType8.symbols @@ -0,0 +1,29 @@ +//// [tests/cases/compiler/dependentReturnType8.ts] //// + +=== dependentReturnType8.ts === +export {}; + +declare const record: Record; +>record : Symbol(record, Decl(dependentReturnType8.ts, 2, 13)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + +declare const array: string[]; +>array : Symbol(array, Decl(dependentReturnType8.ts, 3, 13)) + +const getObject = +>getObject : Symbol(getObject, Decl(dependentReturnType8.ts, 5, 5)) + + (group: T): T extends string ? string[] : T extends undefined ? Record : never => +>T : Symbol(T, Decl(dependentReturnType8.ts, 6, 5)) +>group : Symbol(group, Decl(dependentReturnType8.ts, 6, 35)) +>T : Symbol(T, Decl(dependentReturnType8.ts, 6, 5)) +>T : Symbol(T, Decl(dependentReturnType8.ts, 6, 5)) +>T : Symbol(T, Decl(dependentReturnType8.ts, 6, 5)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + + group === undefined ? record : array; +>group : Symbol(group, Decl(dependentReturnType8.ts, 6, 35)) +>undefined : Symbol(undefined) +>record : Symbol(record, Decl(dependentReturnType8.ts, 2, 13)) +>array : Symbol(array, Decl(dependentReturnType8.ts, 3, 13)) + diff --git a/tests/baselines/reference/dependentReturnType8.types b/tests/baselines/reference/dependentReturnType8.types new file mode 100644 index 0000000000000..e219fc43d209b --- /dev/null +++ b/tests/baselines/reference/dependentReturnType8.types @@ -0,0 +1,37 @@ +//// [tests/cases/compiler/dependentReturnType8.ts] //// + +=== dependentReturnType8.ts === +export {}; + +declare const record: Record; +>record : Record +> : ^^^^^^^^^^^^^^^^^^^^^^^^ + +declare const array: string[]; +>array : string[] +> : ^^^^^^^^ + +const getObject = +>getObject : (group: T) => T extends string ? string[] : T extends undefined ? Record : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ + + (group: T): T extends string ? string[] : T extends undefined ? Record : never => +>(group: T): T extends string ? string[] : T extends undefined ? Record : never => group === undefined ? record : array : (group: T) => T extends string ? string[] : T extends undefined ? Record : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>group : T +> : ^ + + group === undefined ? record : array; +>group === undefined ? record : array : string[] | Record +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>group === undefined : boolean +> : ^^^^^^^ +>group : T +> : ^ +>undefined : undefined +> : ^^^^^^^^^ +>record : Record +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>array : string[] +> : ^^^^^^^^ + diff --git a/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.types b/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.types index e20de1c2bbd86..c4a3eace14466 100644 --- a/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.types +++ b/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.types @@ -1,8 +1,5 @@ //// [tests/cases/compiler/inferFromGenericFunctionReturnTypes3.ts] //// -=== Performance Stats === -Type Count: 1,000 - === inferFromGenericFunctionReturnTypes3.ts === // Repros from #5487 diff --git a/tests/cases/compiler/dependentReturnType8.ts b/tests/cases/compiler/dependentReturnType8.ts new file mode 100644 index 0000000000000..3f21878742b81 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType8.ts @@ -0,0 +1,11 @@ +// @strict: true +// @noEmit: true + +export {}; + +declare const record: Record; +declare const array: string[]; + +const getObject = + (group: T): T extends string ? string[] : T extends undefined ? Record : never => + group === undefined ? record : array; \ No newline at end of file From 6ad3ee9ee49b80e2496da48670d0398c558f3b10 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 21 Oct 2024 19:40:59 -0700 Subject: [PATCH 84/90] fix checking of arrow expression --- src/compiler/checker.ts | 22 +++++++++---------- .../reference/arrowExpressionJs.symbols | 13 +++++++++++ .../reference/arrowExpressionJs.types | 22 +++++++++++++++++++ tests/cases/compiler/arrowExpressionJs.ts | 13 +++++++++++ 4 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 tests/baselines/reference/arrowExpressionJs.symbols create mode 100644 tests/baselines/reference/arrowExpressionJs.types create mode 100644 tests/cases/compiler/arrowExpressionJs.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8803c346423bc..7eb2bd07fe302 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -38896,8 +38896,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // its return type annotation. const returnOrPromisedType = returnType && unwrapReturnType(returnType, functionFlags); if (returnOrPromisedType) { - const effectiveCheckNode = getEffectiveCheckNode(node.body); - checkReturnExpression(node, returnOrPromisedType, effectiveCheckNode, effectiveCheckNode); + checkReturnExpression(node, returnOrPromisedType, node.body, node.body); } } } @@ -45643,6 +45642,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { unwrappedReturnType: Type, node: ReturnStatement | Expression, expr: Expression | undefined, + inConditionalExpression = false, ): void { const excludeJSDocTypeAssertions = isInJSFile(node); const functionFlags = getFunctionFlags(container); @@ -45652,6 +45652,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return checkConditionalReturnExpression(container, unwrappedReturnType, node, unwrappedExpr); } } + const effectiveExpr = expr && getEffectiveCheckNode(expr); // The effective expression for diagnostics purposes. const inReturnStatement = node.kind === SyntaxKind.ReturnStatement; const exprType = expr @@ -45668,12 +45669,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ) : exprType; - const returnExpression = inReturnStatement ? (node as ReturnStatement).expression : node; - const isInConditionalExpression = returnExpression - && isConditionalExpression(skipParentheses(returnExpression, excludeJSDocTypeAssertions)); - const errorNode = isInConditionalExpression ? expr : node; + const errorNode = inReturnStatement && !inConditionalExpression ? node : effectiveExpr; if (!(unwrappedReturnType.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional)) || !couldContainTypeVariables(unwrappedReturnType)) { - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, effectiveExpr); return; } // Check if type of return expression is assignable to original return type; @@ -45690,7 +45688,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { !narrowableTypeParameters.length || !isNarrowableReturnType(unwrappedReturnType as ConditionalType | IndexedAccessType) ) { - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, effectiveExpr); return; } @@ -45715,7 +45713,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } if (!narrowFlowNode) { - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, expr); + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, effectiveExpr); return; } const narrowed: [TypeParameter, Type][] = mapDefined(narrowableTypeParameters, ([typeParam, symbol, reference]) => { @@ -45764,7 +45762,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member, ) : narrowedExprType; - checkTypeAssignableToAndOptionallyElaborate(narrowedUnwrappedExprType, narrowedReturnType, errorNode, expr); + checkTypeAssignableToAndOptionallyElaborate(narrowedUnwrappedExprType, narrowedReturnType, errorNode, effectiveExpr); } function checkConditionalReturnExpression( @@ -45774,8 +45772,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { expr: ConditionalExpression, ): void { checkExpression(expr.condition); - checkReturnExpression(container, returnType, node, expr.whenTrue); - checkReturnExpression(container, returnType, node, expr.whenFalse); + checkReturnExpression(container, returnType, node, expr.whenTrue, /*inConditionalExpression*/ true); + checkReturnExpression(container, returnType, node, expr.whenFalse, /*inConditionalExpression*/ true); } // Narrowable type parameters are type parameters that: diff --git a/tests/baselines/reference/arrowExpressionJs.symbols b/tests/baselines/reference/arrowExpressionJs.symbols new file mode 100644 index 0000000000000..db7330acf0c1e --- /dev/null +++ b/tests/baselines/reference/arrowExpressionJs.symbols @@ -0,0 +1,13 @@ +//// [tests/cases/compiler/arrowExpressionJs.ts] //// + +=== mytest.js === +/** + * @template T + * @param {T|undefined} value value or not + * @returns {T} result value + */ +const cloneObjectGood = value => /** @type {T} */({ ...value }); +>cloneObjectGood : Symbol(cloneObjectGood, Decl(mytest.js, 5, 5)) +>value : Symbol(value, Decl(mytest.js, 5, 23)) +>value : Symbol(value, Decl(mytest.js, 5, 23)) + diff --git a/tests/baselines/reference/arrowExpressionJs.types b/tests/baselines/reference/arrowExpressionJs.types new file mode 100644 index 0000000000000..33fae21b8bd53 --- /dev/null +++ b/tests/baselines/reference/arrowExpressionJs.types @@ -0,0 +1,22 @@ +//// [tests/cases/compiler/arrowExpressionJs.ts] //// + +=== mytest.js === +/** + * @template T + * @param {T|undefined} value value or not + * @returns {T} result value + */ +const cloneObjectGood = value => /** @type {T} */({ ...value }); +>cloneObjectGood : (value: T | undefined) => T +> : ^ ^^ ^^ ^^^^^ +>value => /** @type {T} */({ ...value }) : (value: T | undefined) => T +> : ^ ^^ ^^ ^^^^^ +>value : T | undefined +> : ^^^^^^^^^^^^^ +>({ ...value }) : T +> : ^ +>{ ...value } : {} +> : ^^ +>value : T | undefined +> : ^^^^^^^^^^^^^ + diff --git a/tests/cases/compiler/arrowExpressionJs.ts b/tests/cases/compiler/arrowExpressionJs.ts new file mode 100644 index 0000000000000..11f38f7276a7c --- /dev/null +++ b/tests/cases/compiler/arrowExpressionJs.ts @@ -0,0 +1,13 @@ +// @strict: true +// @noEmit: true +// @checkJs: true +// @allowJs: true + +// @filename: mytest.js + +/** + * @template T + * @param {T|undefined} value value or not + * @returns {T} result value + */ +const cloneObjectGood = value => /** @type {T} */({ ...value }); \ No newline at end of file From ebd9c1b79c99bc0bfa1104b05dc4543b98a8ecb1 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 22 Oct 2024 11:52:59 -0700 Subject: [PATCH 85/90] fix not checking return expressions --- src/compiler/checker.ts | 22 +++++++------ ...inferFromGenericFunctionReturnTypes3.types | 3 ++ .../unusedLocalsInRecursiveReturn.symbols | 18 +++++++++++ .../unusedLocalsInRecursiveReturn.types | 31 +++++++++++++++++++ .../compiler/unusedLocalsInRecursiveReturn.ts | 8 +++++ 5 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 tests/baselines/reference/unusedLocalsInRecursiveReturn.symbols create mode 100644 tests/baselines/reference/unusedLocalsInRecursiveReturn.types create mode 100644 tests/cases/compiler/unusedLocalsInRecursiveReturn.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7eb2bd07fe302..31d39a60662bf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -38894,9 +38894,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // should not be checking assignability of a promise to the return type. Instead, we need to // check assignability of the awaited type of the expression body against the promised type of // its return type annotation. + const exprType = checkExpression(node.body); const returnOrPromisedType = returnType && unwrapReturnType(returnType, functionFlags); if (returnOrPromisedType) { - checkReturnExpression(node, returnOrPromisedType, node.body, node.body); + checkReturnExpression(node, returnOrPromisedType, node.body, node.body, exprType); } } } @@ -45613,6 +45614,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const signature = getSignatureFromDeclaration(container); const returnType = getReturnTypeOfSignature(signature); if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { + const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; if (container.kind === SyntaxKind.SetAccessor) { if (node.expression) { error(node, Diagnostics.Setters_cannot_return_a_value); @@ -45626,7 +45628,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } else if (getReturnTypeFromAnnotation(container)) { const unwrappedReturnType = unwrapReturnType(returnType, getFunctionFlags(container)) ?? returnType; - checkReturnExpression(container, unwrappedReturnType, node, node.expression); + checkReturnExpression(container, unwrappedReturnType, node, node.expression, exprType); } } else if (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeUndefinedVoidOrAny(container, returnType)) { @@ -45642,6 +45644,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { unwrappedReturnType: Type, node: ReturnStatement | Expression, expr: Expression | undefined, + exprType: Type, inConditionalExpression = false, ): void { const excludeJSDocTypeAssertions = isInJSFile(node); @@ -45655,11 +45658,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const effectiveExpr = expr && getEffectiveCheckNode(expr); // The effective expression for diagnostics purposes. const inReturnStatement = node.kind === SyntaxKind.ReturnStatement; - const exprType = expr - ? inReturnStatement - ? checkExpressionCached(expr) - : checkExpression(expr) - : undefinedType; + // const exprType = expr + // ? inReturnStatement + // ? checkExpressionCached(expr) + // : checkExpression(expr) + // : undefinedType; const unwrappedExprType = functionFlags & FunctionFlags.Async ? checkAwaitedType( exprType, @@ -45771,9 +45774,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { node: ReturnStatement | Expression, expr: ConditionalExpression, ): void { - checkExpression(expr.condition); - checkReturnExpression(container, returnType, node, expr.whenTrue, /*inConditionalExpression*/ true); - checkReturnExpression(container, returnType, node, expr.whenFalse, /*inConditionalExpression*/ true); + checkReturnExpression(container, returnType, node, expr.whenTrue, checkExpression(expr.whenTrue), /*inConditionalExpression*/ true); + checkReturnExpression(container, returnType, node, expr.whenFalse, checkExpression(expr.whenFalse), /*inConditionalExpression*/ true); } // Narrowable type parameters are type parameters that: diff --git a/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.types b/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.types index c4a3eace14466..e20de1c2bbd86 100644 --- a/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.types +++ b/tests/baselines/reference/inferFromGenericFunctionReturnTypes3.types @@ -1,5 +1,8 @@ //// [tests/cases/compiler/inferFromGenericFunctionReturnTypes3.ts] //// +=== Performance Stats === +Type Count: 1,000 + === inferFromGenericFunctionReturnTypes3.ts === // Repros from #5487 diff --git a/tests/baselines/reference/unusedLocalsInRecursiveReturn.symbols b/tests/baselines/reference/unusedLocalsInRecursiveReturn.symbols new file mode 100644 index 0000000000000..706e1d21ebd07 --- /dev/null +++ b/tests/baselines/reference/unusedLocalsInRecursiveReturn.symbols @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/unusedLocalsInRecursiveReturn.ts] //// + +=== unusedLocalsInRecursiveReturn.ts === +function recursive(arg: string, other: string) { +>recursive : Symbol(recursive, Decl(unusedLocalsInRecursiveReturn.ts, 0, 0)) +>arg : Symbol(arg, Decl(unusedLocalsInRecursiveReturn.ts, 0, 19)) +>other : Symbol(other, Decl(unusedLocalsInRecursiveReturn.ts, 0, 31)) + + const someLocalVar = arg + other; +>someLocalVar : Symbol(someLocalVar, Decl(unusedLocalsInRecursiveReturn.ts, 1, 9)) +>arg : Symbol(arg, Decl(unusedLocalsInRecursiveReturn.ts, 0, 19)) +>other : Symbol(other, Decl(unusedLocalsInRecursiveReturn.ts, 0, 31)) + + return recursive(someLocalVar, arg); +>recursive : Symbol(recursive, Decl(unusedLocalsInRecursiveReturn.ts, 0, 0)) +>someLocalVar : Symbol(someLocalVar, Decl(unusedLocalsInRecursiveReturn.ts, 1, 9)) +>arg : Symbol(arg, Decl(unusedLocalsInRecursiveReturn.ts, 0, 19)) +} diff --git a/tests/baselines/reference/unusedLocalsInRecursiveReturn.types b/tests/baselines/reference/unusedLocalsInRecursiveReturn.types new file mode 100644 index 0000000000000..c76afb1f77153 --- /dev/null +++ b/tests/baselines/reference/unusedLocalsInRecursiveReturn.types @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/unusedLocalsInRecursiveReturn.ts] //// + +=== unusedLocalsInRecursiveReturn.ts === +function recursive(arg: string, other: string) { +>recursive : (arg: string, other: string) => never +> : ^ ^^ ^^ ^^ ^^^^^^^^^^ +>arg : string +> : ^^^^^^ +>other : string +> : ^^^^^^ + + const someLocalVar = arg + other; +>someLocalVar : string +> : ^^^^^^ +>arg + other : string +> : ^^^^^^ +>arg : string +> : ^^^^^^ +>other : string +> : ^^^^^^ + + return recursive(someLocalVar, arg); +>recursive(someLocalVar, arg) : never +> : ^^^^^ +>recursive : (arg: string, other: string) => never +> : ^ ^^ ^^ ^^ ^^^^^^^^^^ +>someLocalVar : string +> : ^^^^^^ +>arg : string +> : ^^^^^^ +} diff --git a/tests/cases/compiler/unusedLocalsInRecursiveReturn.ts b/tests/cases/compiler/unusedLocalsInRecursiveReturn.ts new file mode 100644 index 0000000000000..8cc98c0498700 --- /dev/null +++ b/tests/cases/compiler/unusedLocalsInRecursiveReturn.ts @@ -0,0 +1,8 @@ +// @strict: true +// @noEmit: true +// @noUnusedLocals: true + +function recursive(arg: string, other: string) { + const someLocalVar = arg + other; + return recursive(someLocalVar, arg); +} \ No newline at end of file From a4ba3339e12f1a8584c620367c5b228768ade50a Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 22 Oct 2024 12:38:03 -0700 Subject: [PATCH 86/90] remove commented out code --- src/compiler/checker.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 31d39a60662bf..36be4f67d703e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45658,11 +45658,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const effectiveExpr = expr && getEffectiveCheckNode(expr); // The effective expression for diagnostics purposes. const inReturnStatement = node.kind === SyntaxKind.ReturnStatement; - // const exprType = expr - // ? inReturnStatement - // ? checkExpressionCached(expr) - // : checkExpression(expr) - // : undefinedType; const unwrappedExprType = functionFlags & FunctionFlags.Async ? checkAwaitedType( exprType, From bd4983401a1f439f62c0312c4d01469d017f49e4 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 29 Oct 2024 16:49:44 -0700 Subject: [PATCH 87/90] refactor tests --- .../reference/dependentReturnType8.symbols | 15 +++++----- .../reference/dependentReturnType8.types | 1 + ...urnConditionalExpressionJSDocCast.symbols} | 30 ++++++------------- ...eturnConditionalExpressionJSDocCast.types} | 25 ++-------------- .../unusedLocalsInRecursiveReturn.symbols | 15 +++++----- .../unusedLocalsInRecursiveReturn.types | 1 + tests/cases/compiler/dependentReturnType8.ts | 2 ++ ...> returnConditionalExpressionJSDocCast.ts} | 11 ++----- .../compiler/unusedLocalsInRecursiveReturn.ts | 1 + 9 files changed, 34 insertions(+), 67 deletions(-) rename tests/baselines/reference/{dependentReturnType7.symbols => returnConditionalExpressionJSDocCast.symbols} (53%) rename tests/baselines/reference/{dependentReturnType7.types => returnConditionalExpressionJSDocCast.types} (78%) rename tests/cases/compiler/{dependentReturnType7.ts => returnConditionalExpressionJSDocCast.ts} (69%) diff --git a/tests/baselines/reference/dependentReturnType8.symbols b/tests/baselines/reference/dependentReturnType8.symbols index ad5429d59bac8..9151c3c4dde80 100644 --- a/tests/baselines/reference/dependentReturnType8.symbols +++ b/tests/baselines/reference/dependentReturnType8.symbols @@ -10,19 +10,20 @@ declare const record: Record; declare const array: string[]; >array : Symbol(array, Decl(dependentReturnType8.ts, 3, 13)) +// Arrow function with expression body const getObject = ->getObject : Symbol(getObject, Decl(dependentReturnType8.ts, 5, 5)) +>getObject : Symbol(getObject, Decl(dependentReturnType8.ts, 6, 5)) (group: T): T extends string ? string[] : T extends undefined ? Record : never => ->T : Symbol(T, Decl(dependentReturnType8.ts, 6, 5)) ->group : Symbol(group, Decl(dependentReturnType8.ts, 6, 35)) ->T : Symbol(T, Decl(dependentReturnType8.ts, 6, 5)) ->T : Symbol(T, Decl(dependentReturnType8.ts, 6, 5)) ->T : Symbol(T, Decl(dependentReturnType8.ts, 6, 5)) +>T : Symbol(T, Decl(dependentReturnType8.ts, 7, 5)) +>group : Symbol(group, Decl(dependentReturnType8.ts, 7, 35)) +>T : Symbol(T, Decl(dependentReturnType8.ts, 7, 5)) +>T : Symbol(T, Decl(dependentReturnType8.ts, 7, 5)) +>T : Symbol(T, Decl(dependentReturnType8.ts, 7, 5)) >Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) group === undefined ? record : array; ->group : Symbol(group, Decl(dependentReturnType8.ts, 6, 35)) +>group : Symbol(group, Decl(dependentReturnType8.ts, 7, 35)) >undefined : Symbol(undefined) >record : Symbol(record, Decl(dependentReturnType8.ts, 2, 13)) >array : Symbol(array, Decl(dependentReturnType8.ts, 3, 13)) diff --git a/tests/baselines/reference/dependentReturnType8.types b/tests/baselines/reference/dependentReturnType8.types index e219fc43d209b..c42a8b0bccb77 100644 --- a/tests/baselines/reference/dependentReturnType8.types +++ b/tests/baselines/reference/dependentReturnType8.types @@ -11,6 +11,7 @@ declare const array: string[]; >array : string[] > : ^^^^^^^^ +// Arrow function with expression body const getObject = >getObject : (group: T) => T extends string ? string[] : T extends undefined ? Record : never > : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ diff --git a/tests/baselines/reference/dependentReturnType7.symbols b/tests/baselines/reference/returnConditionalExpressionJSDocCast.symbols similarity index 53% rename from tests/baselines/reference/dependentReturnType7.symbols rename to tests/baselines/reference/returnConditionalExpressionJSDocCast.symbols index bce19a0622551..1f49ce6b305cd 100644 --- a/tests/baselines/reference/dependentReturnType7.symbols +++ b/tests/baselines/reference/returnConditionalExpressionJSDocCast.symbols @@ -1,9 +1,10 @@ -//// [tests/cases/compiler/dependentReturnType7.ts] //// +//// [tests/cases/compiler/returnConditionalExpressionJSDocCast.ts] //// === file.js === +// Don't peek into conditional return expression if it's wrapped in a cast /** @type {Map} */ const sources = new Map(); ->sources : Symbol(sources, Decl(file.js, 1, 5)) +>sources : Symbol(sources, Decl(file.js, 2, 5)) >Map : Symbol(Map, Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) /** @@ -12,36 +13,23 @@ const sources = new Map(); * @returns {String} */ function source(type = "javascript") { ->source : Symbol(source, Decl(file.js, 1, 26)) ->type : Symbol(type, Decl(file.js, 7, 16)) +>source : Symbol(source, Decl(file.js, 2, 26)) +>type : Symbol(type, Decl(file.js, 8, 16)) return /** @type {String} */ ( type ->type : Symbol(type, Decl(file.js, 7, 16)) +>type : Symbol(type, Decl(file.js, 8, 16)) ? sources.get(type) >sources.get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) ->sources : Symbol(sources, Decl(file.js, 1, 5)) +>sources : Symbol(sources, Decl(file.js, 2, 5)) >get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) ->type : Symbol(type, Decl(file.js, 7, 16)) +>type : Symbol(type, Decl(file.js, 8, 16)) : sources.get("some other thing") >sources.get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) ->sources : Symbol(sources, Decl(file.js, 1, 5)) +>sources : Symbol(sources, Decl(file.js, 2, 5)) >get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) ); } - -/** - * @template {boolean} T - * @param {T} b - * @returns {T extends true ? 1 : T extends false ? 2 : never} - */ -function simple(b) { ->simple : Symbol(simple, Decl(file.js, 13, 1)) ->b : Symbol(b, Decl(file.js, 20, 16)) - - return b ? 1 : 2; ->b : Symbol(b, Decl(file.js, 20, 16)) -} diff --git a/tests/baselines/reference/dependentReturnType7.types b/tests/baselines/reference/returnConditionalExpressionJSDocCast.types similarity index 78% rename from tests/baselines/reference/dependentReturnType7.types rename to tests/baselines/reference/returnConditionalExpressionJSDocCast.types index 286ea23226ef8..4b98f032cb8e9 100644 --- a/tests/baselines/reference/dependentReturnType7.types +++ b/tests/baselines/reference/returnConditionalExpressionJSDocCast.types @@ -1,10 +1,11 @@ -//// [tests/cases/compiler/dependentReturnType7.ts] //// +//// [tests/cases/compiler/returnConditionalExpressionJSDocCast.ts] //// === Performance Stats === Type Count: 1,000 Instantiation count: 2,500 === file.js === +// Don't peek into conditional return expression if it's wrapped in a cast /** @type {Map} */ const sources = new Map(); >sources : Map @@ -63,25 +64,3 @@ function source(type = "javascript") { ); } - -/** - * @template {boolean} T - * @param {T} b - * @returns {T extends true ? 1 : T extends false ? 2 : never} - */ -function simple(b) { ->simple : (b: T) => T extends true ? 1 : T extends false ? 2 : never -> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ ->b : T -> : ^ - - return b ? 1 : 2; ->b ? 1 : 2 : 2 | 1 -> : ^^^^^ ->b : T -> : ^ ->1 : 1 -> : ^ ->2 : 2 -> : ^ -} diff --git a/tests/baselines/reference/unusedLocalsInRecursiveReturn.symbols b/tests/baselines/reference/unusedLocalsInRecursiveReturn.symbols index 706e1d21ebd07..f7ee6db9ba4aa 100644 --- a/tests/baselines/reference/unusedLocalsInRecursiveReturn.symbols +++ b/tests/baselines/reference/unusedLocalsInRecursiveReturn.symbols @@ -1,18 +1,19 @@ //// [tests/cases/compiler/unusedLocalsInRecursiveReturn.ts] //// === unusedLocalsInRecursiveReturn.ts === +// Test that we unconditionally check return expression, even if we don't need its type. function recursive(arg: string, other: string) { >recursive : Symbol(recursive, Decl(unusedLocalsInRecursiveReturn.ts, 0, 0)) ->arg : Symbol(arg, Decl(unusedLocalsInRecursiveReturn.ts, 0, 19)) ->other : Symbol(other, Decl(unusedLocalsInRecursiveReturn.ts, 0, 31)) +>arg : Symbol(arg, Decl(unusedLocalsInRecursiveReturn.ts, 1, 19)) +>other : Symbol(other, Decl(unusedLocalsInRecursiveReturn.ts, 1, 31)) const someLocalVar = arg + other; ->someLocalVar : Symbol(someLocalVar, Decl(unusedLocalsInRecursiveReturn.ts, 1, 9)) ->arg : Symbol(arg, Decl(unusedLocalsInRecursiveReturn.ts, 0, 19)) ->other : Symbol(other, Decl(unusedLocalsInRecursiveReturn.ts, 0, 31)) +>someLocalVar : Symbol(someLocalVar, Decl(unusedLocalsInRecursiveReturn.ts, 2, 9)) +>arg : Symbol(arg, Decl(unusedLocalsInRecursiveReturn.ts, 1, 19)) +>other : Symbol(other, Decl(unusedLocalsInRecursiveReturn.ts, 1, 31)) return recursive(someLocalVar, arg); >recursive : Symbol(recursive, Decl(unusedLocalsInRecursiveReturn.ts, 0, 0)) ->someLocalVar : Symbol(someLocalVar, Decl(unusedLocalsInRecursiveReturn.ts, 1, 9)) ->arg : Symbol(arg, Decl(unusedLocalsInRecursiveReturn.ts, 0, 19)) +>someLocalVar : Symbol(someLocalVar, Decl(unusedLocalsInRecursiveReturn.ts, 2, 9)) +>arg : Symbol(arg, Decl(unusedLocalsInRecursiveReturn.ts, 1, 19)) } diff --git a/tests/baselines/reference/unusedLocalsInRecursiveReturn.types b/tests/baselines/reference/unusedLocalsInRecursiveReturn.types index c76afb1f77153..c1b59613f34aa 100644 --- a/tests/baselines/reference/unusedLocalsInRecursiveReturn.types +++ b/tests/baselines/reference/unusedLocalsInRecursiveReturn.types @@ -1,6 +1,7 @@ //// [tests/cases/compiler/unusedLocalsInRecursiveReturn.ts] //// === unusedLocalsInRecursiveReturn.ts === +// Test that we unconditionally check return expression, even if we don't need its type. function recursive(arg: string, other: string) { >recursive : (arg: string, other: string) => never > : ^ ^^ ^^ ^^ ^^^^^^^^^^ diff --git a/tests/cases/compiler/dependentReturnType8.ts b/tests/cases/compiler/dependentReturnType8.ts index 3f21878742b81..169d5f540c2b6 100644 --- a/tests/cases/compiler/dependentReturnType8.ts +++ b/tests/cases/compiler/dependentReturnType8.ts @@ -1,11 +1,13 @@ // @strict: true // @noEmit: true + export {}; declare const record: Record; declare const array: string[]; +// Arrow function with expression body const getObject = (group: T): T extends string ? string[] : T extends undefined ? Record : never => group === undefined ? record : array; \ No newline at end of file diff --git a/tests/cases/compiler/dependentReturnType7.ts b/tests/cases/compiler/returnConditionalExpressionJSDocCast.ts similarity index 69% rename from tests/cases/compiler/dependentReturnType7.ts rename to tests/cases/compiler/returnConditionalExpressionJSDocCast.ts index 35d69f806465b..6141d73b86e54 100644 --- a/tests/cases/compiler/dependentReturnType7.ts +++ b/tests/cases/compiler/returnConditionalExpressionJSDocCast.ts @@ -4,6 +4,8 @@ // @checkJs: true // @filename: file.js + +// Don't peek into conditional return expression if it's wrapped in a cast /** @type {Map} */ const sources = new Map(); /** @@ -17,13 +19,4 @@ function source(type = "javascript") { ? sources.get(type) : sources.get("some other thing") ); -} - -/** - * @template {boolean} T - * @param {T} b - * @returns {T extends true ? 1 : T extends false ? 2 : never} - */ -function simple(b) { - return b ? 1 : 2; } \ No newline at end of file diff --git a/tests/cases/compiler/unusedLocalsInRecursiveReturn.ts b/tests/cases/compiler/unusedLocalsInRecursiveReturn.ts index 8cc98c0498700..5ad1bac3cb013 100644 --- a/tests/cases/compiler/unusedLocalsInRecursiveReturn.ts +++ b/tests/cases/compiler/unusedLocalsInRecursiveReturn.ts @@ -2,6 +2,7 @@ // @noEmit: true // @noUnusedLocals: true +// Test that we unconditionally check return expression, even if we don't need its type. function recursive(arg: string, other: string) { const someLocalVar = arg + other; return recursive(someLocalVar, arg); From 03c9881b6f39e74d9bb35ffd1c6b02b9e39241a1 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 29 Oct 2024 18:01:36 -0700 Subject: [PATCH 88/90] refactor: feedback --- src/compiler/checker.ts | 102 ++++++++++++++++++++++------------------ src/compiler/types.ts | 10 ++-- 2 files changed, 63 insertions(+), 49 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 36be4f67d703e..2352200b0f730 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16517,7 +16517,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function isNarrowingSubstitutionType(type: Type): boolean { - return !!(type.flags & TypeFlags.Substitution && (type as SubstitutionType).objectFlags & ObjectFlags.IsNarrowedType); + return !!(type.flags & TypeFlags.Substitution && (type as SubstitutionType).objectFlags & ObjectFlags.IsNarrowingType); } function getSubstitutionType(baseType: Type, constraint: Type, isNarrowed?: boolean) { @@ -16536,7 +16536,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { result.baseType = baseType; result.constraint = constraint; if (isNarrowed) { - result.objectFlags |= ObjectFlags.IsNarrowedType; + result.objectFlags |= ObjectFlags.IsNarrowingType; } substitutionTypes.set(id, result); return result; @@ -27844,11 +27844,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } + + if (toIntersection) { + return mappedTypes && getIntersectionType(mappedTypes); + } + return changed - ? mappedTypes && - (toIntersection - ? getIntersectionType(mappedTypes) - : getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal)) + ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type; } @@ -45652,10 +45654,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (expr) { const unwrappedExpr = skipParentheses(expr, excludeJSDocTypeAssertions); if (isConditionalExpression(unwrappedExpr)) { - return checkConditionalReturnExpression(container, unwrappedReturnType, node, unwrappedExpr); + checkReturnExpression(container, unwrappedReturnType, node, unwrappedExpr.whenTrue, checkExpression(unwrappedExpr.whenTrue), /*inConditionalExpression*/ true); + checkReturnExpression(container, unwrappedReturnType, node, unwrappedExpr.whenFalse, checkExpression(unwrappedExpr.whenFalse), /*inConditionalExpression*/ true); + return; } } - const effectiveExpr = expr && getEffectiveCheckNode(expr); // The effective expression for diagnostics purposes. const inReturnStatement = node.kind === SyntaxKind.ReturnStatement; const unwrappedExprType = functionFlags & FunctionFlags.Async @@ -45667,26 +45670,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ) : exprType; + const effectiveExpr = expr && getEffectiveCheckNode(expr); // The effective expression for diagnostics purposes. const errorNode = inReturnStatement && !inConditionalExpression ? node : effectiveExpr; + + // If the return type is not narrowable, we simply check if the return expression type is assignable to the return type. if (!(unwrappedReturnType.flags & (TypeFlags.IndexedAccess | TypeFlags.Conditional)) || !couldContainTypeVariables(unwrappedReturnType)) { checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, effectiveExpr); return; } - // Check if type of return expression is assignable to original return type; - // If so, we don't need to narrow, even if we could. - if (checkTypeAssignableTo(unwrappedExprType, unwrappedReturnType, /*errorNode*/ undefined)) { - return; - } - const allTypeParameters = appendTypeParameters(getOuterTypeParameters(container, /*includeThisTypes*/ false), getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); - const narrowableTypeParameters = allTypeParameters && getNarrowableTypeParameters(allTypeParameters); - - if ( - !narrowableTypeParameters || - !narrowableTypeParameters.length || - !isNarrowableReturnType(unwrappedReturnType as ConditionalType | IndexedAccessType) - ) { - checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, effectiveExpr); + // If type of return expression is assignable to original return type, we don't need to narrow the return type. + if (checkTypeAssignableTo(unwrappedExprType, unwrappedReturnType, /*errorNode*/ undefined)) { return; } @@ -45714,7 +45708,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, effectiveExpr); return; } - const narrowed: [TypeParameter, Type][] = mapDefined(narrowableTypeParameters, ([typeParam, symbol, reference]) => { + + const allTypeParameters = appendTypeParameters(getOuterTypeParameters(container, /*includeThisTypes*/ false), getEffectiveTypeParameterDeclarations(container as DeclarationWithTypeParameters)); + const narrowableTypeParameters = allTypeParameters && getNarrowableTypeParameters(allTypeParameters); + + if ( + !narrowableTypeParameters || + !narrowableTypeParameters.length || + !isNarrowableReturnType(unwrappedReturnType as ConditionalType | IndexedAccessType) + ) { + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, effectiveExpr); + return; + } + + const narrowedTypeParameters: TypeParameter[] = []; + const narrowedTypes: Type[] = []; + for (const [typeParam, symbol, reference] of narrowableTypeParameters) { const narrowReference = factory.cloneNode(reference); // Construct a reference that can be narrowed. // Don't reuse the original reference's node id, // because that could cause us to get a type that was cached for the original reference. @@ -45726,19 +45735,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { narrowReference.flowNode = narrowFlowNode; const initialType = getNarrowableTypeForReference(typeParam, narrowReference, /*checkMode*/ undefined, /*forReturnTypeNarrowing*/ true); if (initialType === typeParam) { - return undefined; + continue; } const flowType = getFlowTypeOfReference(narrowReference, initialType); const exprType = getTypeFromFlowType(flowType); - // Don't narrow the return type if narrowing didn't produce a narrower type for the expression. - if (isTypeAny(exprType) || isErrorType(exprType) || exprType === typeParam || exprType === mapType(typeParam, getBaseConstraintOrType)) { - return undefined; + // If attempting to narrow the expression type did not produce a narrower type, + // then discard this type parameter from narrowing. + if ( + exprType.flags & TypeFlags.AnyOrUnknown + || isErrorType(exprType) + || exprType === typeParam + || exprType === mapType(typeParam, getBaseConstraintOrType) + ) { + continue; } const narrowedType = getSubstitutionType(typeParam, exprType, /*isNarrowed*/ true); - return [typeParam, narrowedType]; - }); + narrowedTypeParameters.push(typeParam); + narrowedTypes.push(narrowedType); + } - const narrowMapper = createTypeMapper(narrowed.map(([tp, _]) => tp), narrowed.map(([_, t]) => t)); + const narrowMapper = createTypeMapper(narrowedTypeParameters, narrowedTypes); const narrowedReturnType = instantiateType( unwrappedReturnType, narrowMapper, @@ -45763,19 +45779,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { checkTypeAssignableToAndOptionallyElaborate(narrowedUnwrappedExprType, narrowedReturnType, errorNode, effectiveExpr); } - function checkConditionalReturnExpression( - container: SignatureDeclaration, - returnType: Type, - node: ReturnStatement | Expression, - expr: ConditionalExpression, - ): void { - checkReturnExpression(container, returnType, node, expr.whenTrue, checkExpression(expr.whenTrue), /*inConditionalExpression*/ true); - checkReturnExpression(container, returnType, node, expr.whenFalse, checkExpression(expr.whenFalse), /*inConditionalExpression*/ true); - } - - // Narrowable type parameters are type parameters that: - // (1) have a union type constraint; - // (2) are used as the type of a single parameter in the function, and nothing else + /** + * Narrowable type parameters are type parameters that: + * (1) have a union type constraint; + * (2) are used as the type of a single parameter in the function, and nothing else + */ function getNarrowableTypeParameters(candidates: TypeParameter[]): [TypeParameter, Symbol, Identifier][] { const narrowableParams: [TypeParameter, Symbol, Identifier][] = []; for (const typeParam of candidates) { @@ -45797,15 +45805,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { isReferenceToTypeParameter(typeParam, typeNode) && (candidateReference = getValidParameterReference(paramDecl, constraint)) ) { + // Type parameter has more than one valid reference. if (reference) { hasInvalidReference = true; break; } reference = candidateReference; - continue; } - hasInvalidReference = true; - break; + else { // Type parameter has invalid reference. + hasInvalidReference = true; + break; + } } } if (!hasInvalidReference && reference) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 76bcef0b6055b..f69c4ebe677cc 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6507,7 +6507,7 @@ export const enum ObjectFlags { /** @internal */ IsGenericType = IsGenericObjectType | IsGenericIndexType, /** @internal */ - IsNarrowedType = 1 << 24, // Substitution type that comes from type narrowing + IsNarrowingType = 1 << 24, // Substitution type that comes from type narrowing // Flags that require TypeFlags.Union /** @internal */ @@ -6907,12 +6907,16 @@ export interface StringMappingType extends InstantiableType { } // Type parameter substitution (TypeFlags.Substitution) -// Substitution types are created for type parameters or indexed access types that occur in the +// - Substitution types are created for type parameters or indexed access types that occur in the // true branch of a conditional type. For example, in 'T extends string ? Foo : Bar', the // reference to T in Foo is resolved as a substitution type that substitutes 'string & T' for T. // Thus, if Foo has a 'string' constraint on its type parameter, T will satisfy it. -// Substitution type are also created for NoInfer types. Those are represented as substitution +// - Substitution types are also created for NoInfer types. Those are represented as substitution // types where the constraint is type 'unknown' (which is never generated for the case above). +// - Substitution types are also created for return type narrowing: +// if a type parameter `T` is linked to a parameter `x` and `x`'s narrowed type is `S`, +// we represent that with a substitution type with base `T` and constraint `S`. +// The resulting substitution type has `ObjectFlags.IsNarrowedType` set. export interface SubstitutionType extends InstantiableType { objectFlags: ObjectFlags; baseType: Type; // Target type From 26f228390388fd149ffcaeb994aa46fbd3382d17 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 30 Oct 2024 17:26:49 -0700 Subject: [PATCH 89/90] short circuit conditional type validation --- src/compiler/checker.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2352200b0f730..ad3f5c3f2eb27 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45924,14 +45924,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // (4) const trueType = getTrueTypeFromConditionalType(type); - const falseType = getFalseTypeFromConditionalType(type); const isValidTrueType = isConditionalType(trueType) ? isNarrowableConditionalType(trueType) : true; + if (!isValidTrueType) return false; + const falseType = getFalseTypeFromConditionalType(type); const isValidFalseType = isConditionalType(falseType) ? isNarrowableConditionalType(falseType) : falseType === neverType; - return isValidTrueType && isValidFalseType; + return isValidFalseType; } function isConditionalType(type: Type): type is ConditionalType { From 86079ac4837e907f53265481d103a3c21dda3389 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 31 Oct 2024 10:53:30 -0700 Subject: [PATCH 90/90] use mapTypeToIntersection --- src/compiler/checker.ts | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ad3f5c3f2eb27..655588ff7bac5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20316,7 +20316,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // result is (A extends U ? X : Y) | (B extends U ? X : Y). if (distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { if (narrowingBaseType) { - result = mapType( + result = mapTypeToIntersection( distributionType, (t: Type) => getConditionalType( @@ -20327,8 +20327,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { /*aliasTypeArguments*/ undefined, forNarrowing, ), - /*noReductions*/ undefined, - /*toIntersection*/ true, ); } else { @@ -27818,10 +27816,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Apply a mapping function to a type and return the resulting type. If the source type // is a union type, the mapping function is applied to each constituent type and a union - // (or intersection, if `toIntersection` is set) of the resulting types is returned. - function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean, toIntersection?: boolean): Type; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean, toIntersection?: boolean): Type | undefined; - function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean, toIntersection?: boolean): Type | undefined { + // of the resulting types is returned. + function mapType(type: Type, mapper: (t: Type) => Type, noReductions?: boolean): Type; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined; + function mapType(type: Type, mapper: (t: Type) => Type | undefined, noReductions?: boolean): Type | undefined { if (type.flags & TypeFlags.Never) { return type; } @@ -27844,14 +27842,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } + return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type; + } - if (toIntersection) { - return mappedTypes && getIntersectionType(mappedTypes); + /** + * Similar to {@link mapType}, but creates an intersection with the result of mapping over a union type. + */ + function mapTypeToIntersection(type: Type, mapper: (t: Type) => Type): Type { + if (type.flags & TypeFlags.Never) { + return type; } + if (!(type.flags & TypeFlags.Union)) { + return mapper(type); + } + const origin = (type as UnionType).origin; + const types = origin && origin.flags & TypeFlags.Union ? (origin as UnionType).types : (type as UnionType).types; + const mappedTypes = types.map(t => t.flags & TypeFlags.Union ? mapTypeToIntersection(t, mapper) : mapper(t)); - return changed - ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) - : type; + return getIntersectionType(mappedTypes); } function mapTypeWithAlias(type: Type, mapper: (t: Type) => Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) {