From 30979c2651e5b0aa8ab583bbd8161cac3d0ea317 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 5 Nov 2024 18:18:24 -0800 Subject: [PATCH] Narrow generic conditional and indexed access return types when checking return statements (#56941) --- src/compiler/binder.ts | 14 + src/compiler/checker.ts | 453 +++- src/compiler/factory/nodeFactory.ts | 2 + src/compiler/types.ts | 15 +- .../reference/arrowExpressionJs.symbols | 13 + .../reference/arrowExpressionJs.types | 22 + .../reference/dependentReturnType1.errors.txt | 645 ++++++ .../reference/dependentReturnType1.symbols | 1386 ++++++++++++ .../reference/dependentReturnType1.types | 2008 +++++++++++++++++ .../reference/dependentReturnType2.errors.txt | 314 +++ .../reference/dependentReturnType2.symbols | 594 +++++ .../reference/dependentReturnType2.types | 1007 +++++++++ .../reference/dependentReturnType3.errors.txt | 224 ++ .../reference/dependentReturnType3.symbols | 679 ++++++ .../reference/dependentReturnType3.types | 1000 ++++++++ .../reference/dependentReturnType4.errors.txt | 40 + .../reference/dependentReturnType4.symbols | 76 + .../reference/dependentReturnType4.types | 95 + .../reference/dependentReturnType5.errors.txt | 109 + .../reference/dependentReturnType5.symbols | 220 ++ .../reference/dependentReturnType5.types | 331 +++ .../reference/dependentReturnType6.errors.txt | 193 ++ .../reference/dependentReturnType6.symbols | 367 +++ .../reference/dependentReturnType6.types | 512 +++++ .../reference/dependentReturnType8.symbols | 30 + .../reference/dependentReturnType8.types | 38 + ...turnConditionalExpressionJSDocCast.symbols | 35 + ...returnConditionalExpressionJSDocCast.types | 66 + .../unusedLocalsInRecursiveReturn.symbols | 19 + .../unusedLocalsInRecursiveReturn.types | 32 + tests/cases/compiler/arrowExpressionJs.ts | 13 + tests/cases/compiler/dependentReturnType1.ts | 519 +++++ tests/cases/compiler/dependentReturnType2.ts | 307 +++ tests/cases/compiler/dependentReturnType3.ts | 216 ++ tests/cases/compiler/dependentReturnType4.ts | 36 + tests/cases/compiler/dependentReturnType5.ts | 99 + tests/cases/compiler/dependentReturnType6.ts | 137 ++ tests/cases/compiler/dependentReturnType8.ts | 13 + .../returnConditionalExpressionJSDocCast.ts | 22 + .../compiler/unusedLocalsInRecursiveReturn.ts | 9 + .../returnTypeNarrowingAfterCachingTypes.ts | 12 + 41 files changed, 11878 insertions(+), 44 deletions(-) create mode 100644 tests/baselines/reference/arrowExpressionJs.symbols create mode 100644 tests/baselines/reference/arrowExpressionJs.types create mode 100644 tests/baselines/reference/dependentReturnType1.errors.txt create mode 100644 tests/baselines/reference/dependentReturnType1.symbols create mode 100644 tests/baselines/reference/dependentReturnType1.types 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/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/baselines/reference/dependentReturnType4.errors.txt create mode 100644 tests/baselines/reference/dependentReturnType4.symbols create mode 100644 tests/baselines/reference/dependentReturnType4.types 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/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/baselines/reference/dependentReturnType8.symbols create mode 100644 tests/baselines/reference/dependentReturnType8.types create mode 100644 tests/baselines/reference/returnConditionalExpressionJSDocCast.symbols create mode 100644 tests/baselines/reference/returnConditionalExpressionJSDocCast.types create mode 100644 tests/baselines/reference/unusedLocalsInRecursiveReturn.symbols create mode 100644 tests/baselines/reference/unusedLocalsInRecursiveReturn.types create mode 100644 tests/cases/compiler/arrowExpressionJs.ts create mode 100644 tests/cases/compiler/dependentReturnType1.ts create mode 100644 tests/cases/compiler/dependentReturnType2.ts create mode 100644 tests/cases/compiler/dependentReturnType3.ts create mode 100644 tests/cases/compiler/dependentReturnType4.ts create mode 100644 tests/cases/compiler/dependentReturnType5.ts create mode 100644 tests/cases/compiler/dependentReturnType6.ts create mode 100644 tests/cases/compiler/dependentReturnType8.ts create mode 100644 tests/cases/compiler/returnConditionalExpressionJSDocCast.ts create mode 100644 tests/cases/compiler/unusedLocalsInRecursiveReturn.ts create mode 100644 tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 7f61986a80f..b071f72b712 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -543,6 +543,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { var preSwitchCaseFlow: FlowNode | undefined; var activeLabelList: ActiveLabel | undefined; var hasExplicitReturn: boolean; + var inReturnPosition: boolean; var hasFlowEffects: boolean; // state used for emit helpers @@ -622,6 +623,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { currentExceptionTarget = undefined; activeLabelList = undefined; hasExplicitReturn = false; + inReturnPosition = false; hasFlowEffects = false; inAssignmentPattern = false; emitFlags = NodeFlags.None; @@ -967,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 @@ -1071,6 +1075,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { bindChildren(node); } + inReturnPosition = savedInReturnPosition; container = saveContainer; thisParentContainer = saveThisParentContainer; blockScopeContainer = savedBlockScopeContainer; @@ -1571,7 +1576,10 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { } function bindReturnOrThrow(node: ReturnStatement | ThrowStatement): void { + const savedInReturnPosition = inReturnPosition; + inReturnPosition = true; bind(node.expression); + inReturnPosition = savedInReturnPosition; if (node.kind === SyntaxKind.ReturnStatement) { hasExplicitReturn = true; if (currentReturnTarget) { @@ -2016,10 +2024,16 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { hasFlowEffects = false; bindCondition(node.condition, trueLabel, falseLabel); currentFlow = finishFlowLabel(trueLabel); + if (inReturnPosition) { + node.flowNodeWhenTrue = currentFlow; + } bind(node.questionToken); bind(node.whenTrue); addAntecedent(postExpressionLabel, currentFlow); currentFlow = finishFlowLabel(falseLabel); + if (inReturnPosition) { + node.flowNodeWhenFalse = currentFlow; + } bind(node.colonToken); bind(node.whenFalse); addAntecedent(postExpressionLabel, currentFlow); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e45e56a6160..f196ae78b16 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -513,6 +513,7 @@ import { isCompoundAssignment, isComputedNonLiteralName, isComputedPropertyName, + isConditionalExpression, isConditionalTypeNode, isConstAssertion, isConstructorDeclaration, @@ -2369,6 +2370,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { [".jsx", ".jsx"], [".json", ".json"], ]; + + var narrowableReturnTypeCache = new Map(); /* eslint-enable no-var */ initializeTypeChecker(); @@ -16580,14 +16583,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) { - return constraint.flags & TypeFlags.AnyOrUnknown || constraint === baseType || baseType.flags & TypeFlags.Any ? - baseType : - getOrCreateSubstitutionType(baseType, constraint); + function isNarrowingSubstitutionType(type: Type): boolean { + return !!(type.flags & TypeFlags.Substitution && (type as SubstitutionType).objectFlags & ObjectFlags.IsNarrowingType); } - function getOrCreateSubstitutionType(baseType: Type, constraint: Type) { - const id = `${getTypeId(baseType)}>${getTypeId(constraint)}`; + function getSubstitutionType(baseType: Type, constraint: Type, isNarrowed?: boolean) { + return constraint.flags & TypeFlags.AnyOrUnknown || constraint === baseType || baseType.flags & TypeFlags.Any ? + baseType : + getOrCreateSubstitutionType(baseType, constraint, isNarrowed); + } + + 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; @@ -16595,6 +16602,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.IsNarrowingType; + } substitutionTypes.set(id, result); return result; } @@ -17655,7 +17665,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; @@ -19126,7 +19136,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[], + forNarrowing?: boolean, + ): Type { let result; let extraTypes: Type[] | undefined; let tailCount = 0; @@ -19148,6 +19165,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (checkType === wildcardType || extendsType === wildcardType) { return wildcardType; } + 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 @@ -19155,7 +19175,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 @@ -19191,17 +19211,17 @@ 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. - 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 @@ -19225,7 +19245,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)) { @@ -20351,13 +20371,38 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!result) { const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); const checkType = root.checkType; - const distributionType = root.isDistributive ? getReducedType(getMappedType(checkType, newMapper)) : undefined; + let distributionType = root.isDistributive ? getReducedType(getMappedType(checkType, newMapper)) : undefined; + let narrowingBaseType: Type | undefined; + const forNarrowing = distributionType && isNarrowingSubstitutionType(distributionType) && isNarrowableConditionalType(type, mapper); + if (forNarrowing) { + narrowingBaseType = (distributionType as SubstitutionType).baseType; + distributionType = getReducedType((distributionType as SubstitutionType).constraint); + } // 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); + if (distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never)) { + if (narrowingBaseType) { + result = mapTypeToIntersection( + distributionType, + (t: Type) => + getConditionalType( + root, + prependTypeMapping(checkType, getSubstitutionType(narrowingBaseType, t, /*isNarrowed*/ true), newMapper), + forConstraint, + /*aliasSymbol*/ undefined, + /*aliasTypeArguments*/ undefined, + forNarrowing, + ), + ); + } + else { + result = mapTypeWithAlias(distributionType, (t: Type) => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint), aliasSymbol, aliasTypeArguments); + } + } + else { + result = getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments, forNarrowing); + } root.instantiations!.set(id, result); } return result; @@ -20439,7 +20484,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { 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); + 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); @@ -21657,7 +21708,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 @@ -21672,10 +21723,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` when narrowing a return type + if (hasInstantiable && hasNullableOrEmpty || hasSubstitution) return true; } return false; } @@ -27859,6 +27912,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return changed ? mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal) : type; } + /** + * 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 getIntersectionType(mappedTypes); + } + 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) : @@ -29743,7 +29813,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; } @@ -29756,7 +29826,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)); + (forReturnTypeNarrowing || isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode)); return substituteConstraints ? mapType(type, getBaseConstraintOrType) : type; } @@ -31282,9 +31352,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) { @@ -32035,6 +32112,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (index >= 0) { return contextualTypes[index]; } + const links = getNodeLinks(node); + if (links.contextualReturnType) { + if (node.flags & NodeFlags.AwaitContext) { + return getUnionType([links.contextualReturnType, createPromiseLikeType(links.contextualReturnType)]); + } + return links.contextualReturnType; + } const { parent } = node; switch (parent.kind) { case SyntaxKind.VariableDeclaration: @@ -38902,14 +38986,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { 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, node.body, node.body, exprType); } } } @@ -45631,7 +45708,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) { const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; if (container.kind === SyntaxKind.SetAccessor) { @@ -45640,21 +45716,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } 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; - 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 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); - } + const unwrappedReturnType = unwrapReturnType(returnType, getFunctionFlags(container)) ?? returnType; + checkReturnExpression(container, unwrappedReturnType, node, node.expression, exprType); } } else if (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeUndefinedVoidOrAny(container, returnType)) { @@ -45663,6 +45732,306 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + // 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, + unwrappedReturnType: Type, + node: ReturnStatement | Expression, + expr: Expression | undefined, + exprType: Type, + inConditionalExpression = false, + ): void { + const excludeJSDocTypeAssertions = isInJSFile(node); + const functionFlags = getFunctionFlags(container); + if (expr) { + const unwrappedExpr = skipParentheses(expr, excludeJSDocTypeAssertions); + if (isConditionalExpression(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 inReturnStatement = node.kind === SyntaxKind.ReturnStatement; + 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 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; + } + + // 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; + } + + // 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 = node; + 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; + } + + if (!narrowFlowNode) { + checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, errorNode, effectiveExpr); + 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); + 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. + 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) { + continue; + } + const flowType = getFlowTypeOfReference(narrowReference, initialType); + const exprType = getTypeFromFlowType(flowType); + // 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); + narrowedTypeParameters.push(typeParam); + narrowedTypes.push(narrowedType); + } + + const narrowMapper = createTypeMapper(narrowedTypeParameters, narrowedTypes); + const narrowedReturnType = instantiateType( + unwrappedReturnType, + narrowMapper, + ); + + 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, effectiveExpr); + } + + /** + * 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 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 = getEffectiveTypeAnnotationNode(paramDecl); + if (!typeNode) continue; + if (isTypeParameterReferenced(typeParam, typeNode)) { + let candidateReference; + if ( + isTypeReferenceNode(typeNode) && + isReferenceToTypeParameter(typeParam, typeNode) && + (candidateReference = getValidParameterReference(paramDecl, constraint)) + ) { + // Type parameter has more than one valid reference. + if (reference) { + hasInvalidReference = true; + break; + } + reference = candidateReference; + } + else { // Type parameter has invalid reference. + hasInvalidReference = true; + break; + } + } + } + if (!hasInvalidReference && reference) { + const symbol = getResolvedSymbol(reference); + if (symbol !== unknownSymbol) narrowableParams.push([typeParam, symbol, reference]); + } + } + } + + 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; + const isOptional = !!paramDecl.questionToken || isJSDocOptionalParameter(paramDecl); + if (isOptional && !containsUndefinedType(constraint)) return; + return paramDecl.name; + } + + function isReferenceToTypeParameter(typeParam: TypeParameter, node: TypeReferenceNode) { + return getTypeFromTypeReference(node) === typeParam; + } + + function isTypeParameterReferenced(typeParam: TypeParameter, node: TypeNode) { + return isReferenced(node); + + function isReferenced(node: Node): boolean { + if (isTypeReferenceNode(node)) { + return isReferenceToTypeParameter(typeParam, node); + } + if (isTypeQueryNode(node)) { + return isTypeParameterPossiblyReferenced(typeParam, node); + } + return !!forEachChild(node, isReferenced); + } + } + } + + function isNarrowableReturnType(returnType: IndexedAccessType | ConditionalType): boolean { + return isConditionalType(returnType) + ? isNarrowableConditionalType(returnType) + : !!(returnType.indexType.flags & TypeFlags.TypeParameter); + } + + 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) { + 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; + } + + // A narrowable conditional type is one that has the following shape: + // `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; + } + + // (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 isValidTrueType = isConditionalType(trueType) + ? isNarrowableConditionalType(trueType) + : true; + if (!isValidTrueType) return false; + const falseType = getFalseTypeFromConditionalType(type); + const isValidFalseType = isConditionalType(falseType) + ? isNarrowableConditionalType(falseType) + : falseType === neverType; + return isValidFalseType; + } + + 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/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index fbc97d9aa4d..694262eeeb5 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 a719a5b5048..9b4915f1cc2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2735,6 +2735,10 @@ export interface ConditionalExpression extends Expression { readonly whenTrue: Expression; readonly colonToken: ColonToken; readonly whenFalse: Expression; + /** @internal*/ + flowNodeWhenTrue: FlowNode | undefined; + /** @internal */ + flowNodeWhenFalse: FlowNode | undefined; } export type FunctionBody = Block; @@ -6240,6 +6244,7 @@ export interface NodeLinks { decoratorSignature?: Signature; // Signature for decorator as if invoked by the runtime. 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". + contextualReturnType?: Type; // If the node is a return statement's expression, then this is the contextual return type. fakeScopeForSignatureDeclaration?: "params" | "typeParams"; // If present, this is a fake scope injected into an enclosing declaration chain. assertionExpressionType?: Type; // Cached type of the expression of a type assertion potentialThisCollisions?: Node[]; @@ -6506,6 +6511,8 @@ export const enum ObjectFlags { IsGenericIndexType = 1 << 23, // Union or intersection contains generic index type /** @internal */ IsGenericType = IsGenericObjectType | IsGenericIndexType, + /** @internal */ + IsNarrowingType = 1 << 24, // Substitution type that comes from type narrowing // Flags that require TypeFlags.Union /** @internal */ @@ -6905,12 +6912,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 diff --git a/tests/baselines/reference/arrowExpressionJs.symbols b/tests/baselines/reference/arrowExpressionJs.symbols new file mode 100644 index 00000000000..db7330acf0c --- /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 00000000000..33fae21b8bd --- /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/baselines/reference/dependentReturnType1.errors.txt b/tests/baselines/reference/dependentReturnType1.errors.txt new file mode 100644 index 00000000000..4513ccb75cf --- /dev/null +++ b/tests/baselines/reference/dependentReturnType1.errors.txt @@ -0,0 +1,645 @@ +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 '""' 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'. +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(169,13): error TS2322: Type 'this' is not assignable to type 'string'. + Type 'Unnamed' is not assignable to type 'string'. +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(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(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 '1' is not assignable to type 'never'. + + +==== dependentReturnType1.ts (39 errors) ==== + interface A { + 1: number; + 2: string; + } + + function f1(x: T): A[T] { + if (x === 1) { + return 0; // Ok + } + else { + return 1; // Error + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'A[T]'. +!!! 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; // Ok + } + else { + return ""; // Error, returned expression needs to have type string & boolean (= never) + ~~~~~~ +!!! error TS2322: Type '""' is not assignable to type 'C[T]'. +!!! error TS2322: Type '""' 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; // Ok + } + else { + return ""; // Error, returned expression needs to have type string & boolean (= never) + ~~~~~~ +!!! error TS2322: Type '""' 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"; + } + // 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" }; // Error + ~~~~~~ +!!! 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'. + } + 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'. + } + // 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 + } + // 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" }; // EPC Error + ~ +!!! error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'Three & Four'. + } + + // 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); + ~~~~~~ +!!! 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 TS2322: Type 'RightOut' is not assignable to type 'Arg extends LeftIn ? LeftOut : Arg extends RightIn ? RightOut : never'. + } + } + + interface Animal { + name: string; + } + + interface Dog extends Animal { + bark: () => string; + } + + // 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); + ~~~~~~ +!!! 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 + ~~~~~~ +!!! 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 }; + // 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 : T extends undefined ? string : never'. + } + return this; + ~~~~~~ +!!! 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 : never { + 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 : never { + 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 & {}'. + } + } + + // Conditional expressions + interface Aa { + 1: number; + 2: string; + 3: boolean; + } + + function trivialConditional(x: T): Aa[T] { + if (x !== 1) { + return x === 2 ? "" : true; + } + else { + return 0; + } + } + + function conditional(x: T): + 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 : never { + return x === "a" ? x : parseInt(x); // Ok + } + + 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'. + ~~~~~~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + } + + // Multiple indexed type 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; // Ok + } + + return undefined as never; + } + + // 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 | 2 ? T extends 1 ? string : T extends 2 ? boolean : never : T extends 3 ? number : never'. + } else if (x == 2) { + return true; + ~~~~~~ +!!! 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'. + } + + + // 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 ""; + } + x.a; + return 1; + } + + 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 : never; + + // 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; // 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; // 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; // 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>'. + } + + // 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 : never { + 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 : never { + if (isString(input)) { + return input[0].toUpperCase() + input.slice(1); // Ok + } else { + return input[0].toUpperCase() + input.slice(1); // Bad, error + ~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'string[]'. + } + } + + // 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; + ~~~~~~ +!!! error TS2322: Type 'undefined' is not assignable to type 'T extends {} ? void : T extends undefined ? number : never'. + } + 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; // Good error + ~~~~~~ +!!! error TS2322: Type '1' is not assignable to type '4'. + } + return undefined as any; + } + + 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; + } + + // Shadowing of the narrowed reference + 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 : never'. + } + return ""; // Error + ~~~~~~ +!!! 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(a: T): T extends true ? 1 : T extends false ? 2 : never { + if ((true)) { + const someX = 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 : never'. + } + + return undefined as any; + } + + 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) { + return 1; // Error + ~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + } + return ""; // Ok + } + return 0; // Ok + } + + // 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 : never'. + } + return ""; + ~~~~~~ +!!! 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 { + 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 : never'. + } + yield 2; + ~ +!!! error TS2322: Type '2' is not assignable to type 'T extends true ? 1 : T extends false ? 2 : never'. + return 0; + } + + // From #33912 + abstract class Operation { + abstract perform(t: T): R; + } + + type ConditionalReturnType | undefined> = + EOp extends Operation ? R : EOp extends undefined ? T | R : never; + + + class ConditionalOperation< + T, + R, + EOp extends Operation | undefined, + > extends Operation> { + constructor( + private predicate: (value: T) => boolean, + private thenOp: Operation, + private elseOp?: EOp, + ) { + 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); // Would be ok + ~~~~~~ +!!! error TS2322: Type 'R' is not assignable to type 'ConditionalReturnType'. + } else { + return t; // Would be ok + ~~~~~~ +!!! error TS2322: Type 'T' is not assignable to type 'ConditionalReturnType'. + } + } + } + + // 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 conditional expressions with parentheses + function returnStuff1(x: T ): T extends true ? 1 : T extends false ? 2 : never { + return (x ? (1) : 2); + } + + function returnStuff2(x: T ): + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never { + return (typeof x === "string" ? 0 : (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 '1' 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 new file mode 100644 index 00000000000..958dafbc612 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType1.symbols @@ -0,0 +1,1386 @@ +//// [tests/cases/compiler/dependentReturnType1.ts] //// + +=== dependentReturnType1.ts === +interface A { +>A : Symbol(A, Decl(dependentReturnType1.ts, 0, 0)) + + 1: number; +>1 : Symbol(A[1], Decl(dependentReturnType1.ts, 0, 13)) + + 2: string; +>2 : Symbol(A[2], Decl(dependentReturnType1.ts, 1, 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)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 5, 12)) + + if (x === 1) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 5, 29)) + + return 0; // Ok + } + else { + return 1; // Error + } +} + +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; // Ok + } + 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; // Ok + } + 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)) +} +// 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)) +>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" }; // 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", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error +>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)) +} +// 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, 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, 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" }; // 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)) +} + +// 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, 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, 87, 98)) +>Arg : Symbol(Arg, Decl(dependentReturnType1.ts, 87, 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)) + + 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)) + + 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 : Arg extends RightIn ? RightOut : never +>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)) +{ + if (cond(arg)) { +>cond : Symbol(cond, Decl(dependentReturnType1.ts, 88, 13)) +>arg : Symbol(arg, Decl(dependentReturnType1.ts, 87, 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); +>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, 99, 1)) + + name: string; +>name : Symbol(Animal.name, Decl(dependentReturnType1.ts, 101, 18)) +} + +interface Dog extends Animal { +>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, 105, 30)) +} + +// This would be unsafe to narrow. +declare function isDog(x: Animal): x is Dog; +>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, 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, 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, 107, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 112, 31)) + + return doggy(x); +>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, 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, 121, 9)) +>Entry : Symbol(Entry, Decl(dependentReturnType1.ts, 120, 11)) + + return entries[entry]; +>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, 123, 1)) +>val : Symbol(val, Decl(dependentReturnType1.ts, 126, 23)) + +export function bounceAndTakeIfA(value: AB): AB { +>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, 127, 55)) + + takeA(value); +>takeA : Symbol(takeA, Decl(dependentReturnType1.ts, 123, 1)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 127, 55)) + + takeAB(value); +>takeAB : Symbol(takeAB, Decl(dependentReturnType1.ts, 134, 17)) +>value : Symbol(value, Decl(dependentReturnType1.ts, 127, 55)) + + return value; +>value : Symbol(value, Decl(dependentReturnType1.ts, 127, 55)) + } + + return value; +>value : Symbol(value, Decl(dependentReturnType1.ts, 127, 55)) + + function takeAB(val: AB): void {} +>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, 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, 139, 42)) + + return value; +>value : Symbol(value, Decl(dependentReturnType1.ts, 139, 42)) + } + return "a"; +} + +class Unnamed { +>Unnamed : Symbol(Unnamed, Decl(dependentReturnType1.ts, 144, 1)) + + root!: { name: string }; +>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, 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, 149, 27)) + + return this.root.name; +>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, 144, 1)) + } + + // Good conditional + name2(name?: T): T extends string ? this : T extends undefined ? string : never { +>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, 157, 40)) + + return this.root.name; // Ok +>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, 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, 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, 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, 166, 40)) + + return this; // Error +>this : Symbol(Unnamed, Decl(dependentReturnType1.ts, 144, 1)) + } + this.root.name = name; +>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, 166, 40)) + } +} + +// Conditional expressions +interface Aa { +>Aa : Symbol(Aa, Decl(dependentReturnType1.ts, 173, 1)) + + 1: number; +>1 : Symbol(Aa[1], Decl(dependentReturnType1.ts, 176, 14)) + + 2: string; +>2 : Symbol(Aa[2], Decl(dependentReturnType1.ts, 177, 14)) + + 3: boolean; +>3 : Symbol(Aa[3], Decl(dependentReturnType1.ts, 178, 14)) +} + +function trivialConditional(x: T): Aa[T] { +>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, 182, 49)) + + return x === 2 ? "" : true; +>x : Symbol(x, Decl(dependentReturnType1.ts, 182, 49)) + } + else { + return 0; + } +} + +function conditional(x: T): +>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, 191, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 191, 21)) + + return x ? 1 : 2; // Ok +>x : Symbol(x, Decl(dependentReturnType1.ts, 191, 40)) +} + +function contextualConditional( +>contextualConditional : Symbol(contextualConditional, Decl(dependentReturnType1.ts, 194, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 196, 31)) + + x: T +>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, 196, 31)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 196, 31)) + + return x === "a" ? x : parseInt(x); // Ok +>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, 196, 52)) +} + +function conditionalWithError( +>conditionalWithError : Symbol(conditionalWithError, Decl(dependentReturnType1.ts, 200, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 202, 30)) + + x: T +>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, 202, 30)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 202, 30)) + + return x === "a" ? x : parseInt(x); // Error +>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, 202, 51)) +} + +// Multiple indexed type reductions +interface BB { +>BB : Symbol(BB, Decl(dependentReturnType1.ts, 206, 1)) + + "a": number; +>"a" : Symbol(BB["a"], Decl(dependentReturnType1.ts, 209, 14)) + + [y: number]: string; +>y : Symbol(y, Decl(dependentReturnType1.ts, 211, 5)) +} + +interface AA { +>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, 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, 215, 15)) +} + +function reduction(x: T, y: U): AA[U] { +>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, 219, 65)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 219, 60)) + + // AA[U='c'] -> BB[T] + // BB[T='a'] -> number + return 0; // Ok + } + + return undefined as never; +>undefined : Symbol(undefined) +} + +// Substitution types are not narrowed +function subsCond( +>subsCond : Symbol(subsCond, Decl(dependentReturnType1.ts, 227, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 230, 18)) + + x: T, +>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, 230, 18)) + + ? T extends 1 +>T : Symbol(T, Decl(dependentReturnType1.ts, 230, 18)) + + ? string + : T extends 2 +>T : Symbol(T, Decl(dependentReturnType1.ts, 230, 18)) + + ? boolean + : never + : T extends 3 +>T : Symbol(T, Decl(dependentReturnType1.ts, 230, 18)) + + ? number + : never { + if (x === 1) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 230, 39)) + + return ""; + } else if (x == 2) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 230, 39)) + + return true; + } + return 3; +} + + +// Unsafe: check types overlap +declare function q(x: object): x is { b: number }; +>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, 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, 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, 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, 247, 1)) +>x : Symbol(x, Decl(dependentReturnType1.ts, 252, 54)) + + x.b; +>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, 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, 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, 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, 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, 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, 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, 271, 9)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 271, 20)) + + { x: number, y: false }, 2> { +>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, 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, 269, 60)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 269, 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, 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, 283, 51)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 285, 20)) + + input: T +>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, 285, 20)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 285, 20)) + + if (isString(input)) { +>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, 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, 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, 285, 49)) +>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) +>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, 293, 1)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 295, 23)) + + input: T +>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, 295, 23)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 295, 23)) + + if (isString(input)) { +>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, 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, 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, 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, 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, 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, 306, 54)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 306, 17)) + +): T extends {} ? void : T extends undefined ? number : never { +>T : Symbol(T, Decl(dependentReturnType1.ts, 306, 17)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 306, 17)) + + if (x) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 306, 54)) + + return; + } + return 1; +} + +// Multiple type parameters at once +function woo( +>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, 316, 67)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 316, 13)) + + y: U, +>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, 316, 13)) + + ? U extends string +>U : Symbol(U, Decl(dependentReturnType1.ts, 316, 39)) + + ? 1 + : U extends number +>U : Symbol(U, Decl(dependentReturnType1.ts, 316, 39)) + + ? 2 + : never + : T extends number +>T : Symbol(T, Decl(dependentReturnType1.ts, 316, 13)) + + ? U extends number +>U : Symbol(U, Decl(dependentReturnType1.ts, 316, 39)) + + ? 3 + : U extends string +>U : Symbol(U, Decl(dependentReturnType1.ts, 316, 39)) + + ? 4 + : never + : never { + if (typeof x === "number" && typeof y === "string") { +>x : Symbol(x, Decl(dependentReturnType1.ts, 316, 67)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 317, 9)) + + return 1; // Good error + } + return undefined as any; +>undefined : Symbol(undefined) +} + +function ttt( +>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, 338, 67)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 338, 13)) + + y: U, +>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, 338, 13)) + + ? U extends string +>U : Symbol(U, Decl(dependentReturnType1.ts, 338, 39)) + + ? 1 + : U extends number +>U : Symbol(U, Decl(dependentReturnType1.ts, 338, 39)) + + ? 2 + : never + : T extends number +>T : Symbol(T, Decl(dependentReturnType1.ts, 338, 13)) + + ? U extends number +>U : Symbol(U, Decl(dependentReturnType1.ts, 338, 39)) + + ? 3 + : U extends string +>U : Symbol(U, Decl(dependentReturnType1.ts, 338, 39)) + + ? 4 + : never + : never { + if (typeof x === "number" && typeof y === "string") { +>x : Symbol(x, Decl(dependentReturnType1.ts, 338, 67)) +>y : Symbol(y, Decl(dependentReturnType1.ts, 339, 9)) + + return 4; // Ok + } + + return undefined as any; +>undefined : Symbol(undefined) +} + +// Shadowing of the narrowed reference +function shadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { +>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, 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, 364, 11)) + + return 1; // Error + } + return ""; // Error + } +} + +function noShadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { +>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, 372, 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, 382, 11)) + +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)) +>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 = a; +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 385, 13)) +>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)) + + return 1; + } + } + if (!someX) { // This is a different `someX`, so we don't narrow here +>someX : Symbol(someX, Decl(dependentReturnType1.ts, 382, 11)) + + return 2; + } + + return undefined as any; +>undefined : Symbol(undefined) +} + +function moreShadowing(x: T): T extends 1 ? number : T extends 2 ? string : never { +>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, 397, 40)) + + let x: number = Math.random() ? 1 : 2; +>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, 399, 11)) + + return 1; // Error + } + return ""; // Ok + } + return 0; // Ok +} + +// 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, 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, 409, 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, 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, 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, 419, 21)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 419, 21)) + + if (x) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 419, 45)) + + return 1; + } + return 2; +} + +// Ok +function* bbool(x: T): Generator { +>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, 427, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 427, 16)) + + yield 3; + if (x) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 427, 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, 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, 436, 16)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 436, 16)) + + if (x) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 436, 40)) + + yield 1; + } + yield 2; + return 0; +} + +// From #33912 +abstract class Operation { +>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, 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, 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, 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, 450, 76)) + + T, +>T : Symbol(T, Decl(dependentReturnType1.ts, 453, 27)) + + R, +>R : Symbol(R, Decl(dependentReturnType1.ts, 454, 6)) + + EOp extends Operation | undefined, +>EOp : Symbol(EOp, Decl(dependentReturnType1.ts, 455, 6)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) +>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, 453, 27)) +>ConditionalReturnType : Symbol(ConditionalReturnType, Decl(dependentReturnType1.ts, 447, 1)) +>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, 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, 459, 49)) +>Operation : Symbol(Operation, Decl(dependentReturnType1.ts, 442, 1)) +>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, 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, 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, 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, 458, 16)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) +>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, 459, 49)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) +>thenOp : Symbol(ConditionalOperation.thenOp, Decl(dependentReturnType1.ts, 459, 49)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 467, 12)) + + } else if (typeof this.elseOp !== "undefined") { +>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, 460, 40)) + + 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, 460, 40)) +>this : Symbol(ConditionalOperation, Decl(dependentReturnType1.ts, 450, 76)) +>elseOp : Symbol(ConditionalOperation.elseOp, Decl(dependentReturnType1.ts, 460, 40)) +>perform : Symbol(Operation.perform, Decl(dependentReturnType1.ts, 445, 32)) +>t : Symbol(t, Decl(dependentReturnType1.ts, 467, 12)) + + } else { + return t; // Would be ok +>t : Symbol(t, Decl(dependentReturnType1.ts, 467, 12)) + } + } +} + +// 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: T, +>t : Symbol(t, Decl(dependentReturnType1.ts, 480, 64)) +>T : Symbol(T, Decl(dependentReturnType1.ts, 480, 17)) + + 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)) + + 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 conditional expressions with parentheses +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(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, 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, 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, 506, 36)) + + return 1; + } + if (x === false) { +>x : Symbol(x, Decl(dependentReturnType1.ts, 506, 36)) + + return 2; + } + return 1; +} diff --git a/tests/baselines/reference/dependentReturnType1.types b/tests/baselines/reference/dependentReturnType1.types new file mode 100644 index 00000000000..96265db6b57 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType1.types @@ -0,0 +1,2008 @@ +//// [tests/cases/compiler/dependentReturnType1.ts] //// + +=== dependentReturnType1.ts === +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" +> : ^^^ +} +// 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 +> : ^ + + 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" }; // 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" +> : ^^^ +>"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", 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" +> : ^^^ +} +// 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 +> : ^ + + 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" +> : ^^^ + } + // 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" }; // 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" +> : ^^^ +>"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" +> : ^^^ +} + +// 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 +> : ^ ^^ ^^ ^^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ + + 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 : Arg extends RightIn ? RightOut : never +{ + 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); +>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 would be unsafe to narrow. +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); +>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 +> : ^ ^^ ^^^^^ +>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: 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 +> : ^^^^^^^^^^^^^ + + 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 +> : ^^^^ + } + + // Good conditional + 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 +> : ^^^^^^^^^^^^^ + + 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 : never { +>name3 : (name?: T) => T extends string ? this : T extends undefined ? string : never +> : ^ ^^^^^^^^^ ^^ ^^^ ^^^^^ +>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 & {} +> : ^^^^^^ + } +} + +// Conditional expressions +interface Aa { + 1: number; +>1 : number +> : ^^^^^^ + + 2: string; +>2 : string +> : ^^^^^^ + + 3: boolean; +>3 : boolean +> : ^^^^^^^ +} + +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 ? "" : true; +>x === 2 ? "" : true : true | "" +> : ^^^^^^^^^ +>x === 2 : boolean +> : ^^^^^^^ +>x : T +> : ^ +>2 : 2 +> : ^ +>"" : "" +> : ^^ +>true : true +> : ^^^^ + } + else { + return 0; +>0 : 0 +> : ^ + } +} + +function conditional(x: T): +>conditional : (x: T) => T extends true ? 1 : T extends false ? 2 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>x : T +> : ^ + + T extends true ? 1 : T extends false ? 2 : never { +>true : true +> : ^^^^ +>false : false +> : ^^^^^ + + return x ? 1 : 2; // Ok +>x ? 1 : 2 : 1 | 2 +> : ^^^^^ +>x : T +> : ^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ +} + +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" +> : ^^^^^^^^^^^^ +>x === "a" : boolean +> : ^^^^^^^ +>x : T +> : ^ +>"a" : "a" +> : ^^^ +>x : "a" +> : ^^^ +>parseInt(x) : number +> : ^^^^^^ +>parseInt : (string: string, radix?: number) => number +> : ^ ^^ ^^ ^^^ ^^^^^ +>x : "b" +> : ^^^ +} + +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" +> : ^^^^^^^^^^^^ +>x === "a" : boolean +> : ^^^^^^^ +>x : T +> : ^ +>"a" : "a" +> : ^^^ +>x : "a" +> : ^^^ +>parseInt(x) : number +> : ^^^^^^ +>parseInt : (string: string, radix?: number) => number +> : ^ ^^ ^^ ^^^ ^^^^^ +>x : "b" +> : ^^^ +} + +// Multiple indexed type 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( +>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 +> : ^^^^^^^ +>x : T +> : ^ +>1 : 1 +> : ^ + + return ""; +>"" : "" +> : ^^ + + } else if (x == 2) { +>x == 2 : boolean +> : ^^^^^^^ +>x : T +> : ^ +>2 : 2 +> : ^ + + return true; +>true : true +> : ^^^^ + } + return 3; +>3 : 3 +> : ^ +} + + +// Unsafe: check types overlap +declare function q(x: object): x is { b: number }; +>q : (x: object) => x is { b: number; } +> : ^ ^^ ^^^^^ +>x : object +> : ^^^^^^ +>b : 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)) { +>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 ""; +>"" : "" +> : ^^ + } + x.a; +>x.a : string +> : ^^^^^^ +>x : { a: string; } +> : ^^^^^ ^^^ +>a : string +> : ^^^^^^ + + return 1; +>1 : 1 +> : ^ +} + +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); // type says number but actually string +>r : number +> : ^^^^^^ +>foo<{ a: string }>(y) : number +> : ^^^^^^ +>foo : (x: T) => T extends { a: string; } ? number : T extends { b: number; } ? string : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>a : string +> : ^^^^^^ +>y : { a: string; b: number; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ + +type HelperCond = T extends A ? R1 : T extends B ? R2 : never; +>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( +>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 +> : ^^^^^^^ +>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, end?: number) => string +> : ^ ^^^ ^^ ^^^ ^^^^^ +>input : 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 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>elt : 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 +> : ^^^^^^^ +>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, end?: number) => string +> : ^ ^^^ ^^ ^^^ ^^^^^ +>input : string +> : ^^^^^^ +>slice : (start?: number, end?: number) => string +> : ^ ^^^ ^^ ^^^ ^^^^^ +>1 : 1 +> : ^ + + } else { + return input[0].toUpperCase() + input.slice(1); // Bad, error +>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, end?: number) => string[] +> : ^ ^^^ ^^ ^^^ ^^^^^^^^^^^^^ +>input : string[] +> : ^^^^^^^^ +>slice : (start?: number, end?: number) => string[] +> : ^ ^^^ ^^ ^^^ ^^^^^^^^^^^^^ +>1 : 1 +> : ^ + } +} + +// 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; + } + return 1; +>1 : 1 +> : ^ +} + +// 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 + : 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 === "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; // Good error +>1 : 1 +> : ^ + } + return undefined as any; +>undefined as any : any +> : ^^^ +>undefined : undefined +> : ^^^^^^^^^ +} + +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 + ? 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 === "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 4; // Ok +>4 : 4 +> : ^ + } + + return undefined as any; +>undefined as any : any +> : ^^^ +>undefined : undefined +> : ^^^^^^^^^ +} + +// Shadowing of the narrowed reference +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 +> : ^ + + 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 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(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 +> : ^^^^ +>false : false +> : ^^^^^ + + if ((true)) { +>(true) : true +> : ^^^^ +>true : true +> : ^^^^ + + const someX = a; +>someX : 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 +> : ^ + } + + return undefined as any; +>undefined as any : any +> : ^^^ +>undefined : undefined +> : ^^^^^^^^^ +} + +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 +> : ^ + + 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 +> : ^ +} + +// 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 +> : ^ + + 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 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>["a"] as const : ["a"] +> : ^^^^^ +>["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 +> : ^ +} + +// 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 : never; + + +class ConditionalOperation< +>ConditionalOperation : ConditionalOperation +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + T, + R, + EOp extends Operation | undefined, +> extends Operation> { +>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 +> : ^^^^^^^^^^^^^^^^ + } + + // 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 +> : ^ ^^ ^^^^^ +>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); // Would be 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; // Would be ok +>t : T +> : ^ + } + } +} + +// 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: T, +>t : T +> : ^ + + 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 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 +> : ^ + + } 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(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 +> : ^^^^ +>false : false +> : ^^^^^ + + return (x ? (1) : 2); +>(x ? (1) : 2) : 1 | 2 +> : ^^^^^ +>x ? (1) : 2 : 1 | 2 +> : ^^^^^ +>x : T +> : ^ +>(1) : 1 +> : ^ +>1 : 1 +> : ^ +>2 : 2 +> : ^ +} + +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 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 +> : ^ +>(x === 1 ? ("one") : "two") : "one" | "two" +> : ^^^^^^^^^^^^^ +>x === 1 ? ("one") : "two" : "one" | "two" +> : ^^^^^^^^^^^^^ +>x === 1 : boolean +> : ^^^^^^^ +>x : T +> : ^ +>1 : 1 +> : ^ +>("one") : "one" +> : ^^^^^ +>"one" : "one" +> : ^^^^^ +>"two" : "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 { +>neverOk : (x: T) => T extends true ? 1 : T extends false ? 2 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>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/dependentReturnType2.errors.txt b/tests/baselines/reference/dependentReturnType2.errors.txt new file mode 100644 index 00000000000..4eddac31221 --- /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 00000000000..4b41f3fe744 --- /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 00000000000..1adf92c29a8 --- /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/baselines/reference/dependentReturnType3.errors.txt b/tests/baselines/reference/dependentReturnType3.errors.txt new file mode 100644 index 00000000000..39a93262422 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType3.errors.txt @@ -0,0 +1,224 @@ +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) ==== + // Adapted from ts-error-deltas repos + + type HelperCond = + T extends A + ? R1 + : T extends B + ? R2 + : never; + + + // 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 + : never { + 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 + : never { + 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 00000000000..3d39bf1559f --- /dev/null +++ b/tests/baselines/reference/dependentReturnType3.symbols @@ -0,0 +1,679 @@ +//// [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)) + + : never; + + +// File: Rocket.Chat/apps/meteor/app/katex/client/index.ts +interface IMessage { +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 20)) + + 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, 20)) +>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, 20)) + + ? IMessage +>IMessage : Symbol(IMessage, Decl(dependentReturnType3.ts, 7, 20)) + + : never { + 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 +>T : Symbol(T, Decl(dependentReturnType3.ts, 44, 44)) + + ? (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: string) => string +>message : Symbol(message, Decl(dependentReturnType3.ts, 53, 9)) + + : never { + const instance = new NewKatex(); +>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, 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, 55, 9)) +>renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) +>message : Symbol(message, Decl(dependentReturnType3.ts, 57, 16)) + } + return (message: string): string => instance.renderMessage(message); // Ok +>message : Symbol(message, Decl(dependentReturnType3.ts, 59, 12)) +>instance.renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) +>instance : Symbol(instance, Decl(dependentReturnType3.ts, 55, 9)) +>renderMessage : Symbol(NewKatex.renderMessage, Decl(dependentReturnType3.ts, 19, 5)) +>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, 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, 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, 64, 89)) + +declare const Meteor: { settings: { [s: string]: any } }; +>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, 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, 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, 69, 46)) +>x : Symbol(x, Decl(dependentReturnType3.ts, 70, 29)) + +class NewSettingsBase { +>NewSettingsBase : Symbol(NewSettingsBase, Decl(dependentReturnType3.ts, 70, 46)) + + public newGet( +>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, 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, 73, 123)) +>I : Symbol(I, Decl(dependentReturnType3.ts, 73, 56)) + + callback?: C, +>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, 73, 18)) + + SettingCallback, void, +>SettingCallback : Symbol(SettingCallback, Decl(dependentReturnType3.ts, 63, 93)) + + undefined, HelperCondHelperCond : Symbol(HelperCond, Decl(dependentReturnType3.ts, 0, 0)) +>I : Symbol(I, Decl(dependentReturnType3.ts, 73, 56)) + + string, T | undefined, +>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, 60, 1)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 73, 83)) + + if (callback !== undefined) { +>callback : Symbol(callback, Decl(dependentReturnType3.ts, 74, 15)) +>undefined : Symbol(undefined) + + if (!Meteor.settings) { +>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, 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, 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, 86, 61)) + + const value = Meteor.settings[key]; +>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, 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, 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, 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, 92, 61)) + + if (!_id.test(key)) { +>_id.test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) +>_id : Symbol(_id, Decl(dependentReturnType3.ts, 73, 123)) +>test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(dependentReturnType3.ts, 92, 61)) + + return; + } + const value = Meteor.settings[key]; +>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, 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, 73, 123)) + + const value = Meteor.settings[_id]; +>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, 102, 21)) + + callback(_id, Meteor.settings[_id]); +>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 + } + + 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, 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, 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, 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, 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, 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, 73, 123)) +>test : Symbol(RegExp.test, Decl(lib.es5.d.ts, --, --)) +>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, 117, 56)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) + + key, +>key : Symbol(key, Decl(dependentReturnType3.ts, 120, 17)) + + value, +>value : Symbol(value, Decl(dependentReturnType3.ts, 121, 10)) + + }); + } + return items; +>items : Symbol(items, Decl(dependentReturnType3.ts, 117, 56)) + + }, []); // Ok + } + + return Meteor.settings?.[_id]; // Error +>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`. + } +} + +// File: Rocket.Chat/apps/meteor/app/ui-utils/client/lib/messageBox.ts +type MessageBoxAction = object; +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) + +function getWithBug(group: T): +>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, 137, 20)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) + + if (!group) { +>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, 132, 1)) + } + + return [] as MessageBoxAction[]; // Ok +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) +} + +function getWithoutBug(group: T): +>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, 146, 23)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>MessageBoxAction : Symbol(MessageBoxAction, Decl(dependentReturnType3.ts, 132, 1)) + + if (group === undefined) { +>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, 132, 1)) + } + + return [] as MessageBoxAction[]; // Ok +>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, 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, 156, 48)) +>T : Symbol(T, Decl(dependentReturnType3.ts, 157, 37)) + + start: string, +>start : Symbol(start, Decl(dependentReturnType3.ts, 157, 67)) + + end?: T +>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, 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, 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, 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, 160, 78)) + + return end !== undefined ? // Ok +>end : Symbol(end, Decl(dependentReturnType3.ts, 158, 18)) +>undefined : Symbol(undefined) + { + start: mapDateForAPI(start), +>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, 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, 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, 167, 40)) +>undefined : Symbol(undefined) + + }; +} + +// File: Rocket.Chat/packages/agenda/src/Agenda.ts +type RepeatOptions = object; +>RepeatOptions : Symbol(RepeatOptions, Decl(dependentReturnType3.ts, 170, 1)) + +type Job = object; +>Job : Symbol(Job, Decl(dependentReturnType3.ts, 173, 28)) + +type IJob = { data: object }; +>IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 174, 18)) +>data : Symbol(data, Decl(dependentReturnType3.ts, 175, 13)) + +class NewAgenda { +>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, 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, 173, 28)) +>undefined : Symbol(undefined) + + private _createIntervalJobs( +>_createIntervalJobs : Symbol(NewAgenda._createIntervalJobs, Decl(dependentReturnType3.ts, 177, 163)) + + interval: string | number, +>interval : Symbol(interval, Decl(dependentReturnType3.ts, 178, 32)) + + names: string[], +>names : Symbol(names, Decl(dependentReturnType3.ts, 179, 34)) + + data: IJob['data'], +>data : Symbol(data, Decl(dependentReturnType3.ts, 180, 24)) +>IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 174, 18)) + + options: RepeatOptions, +>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, 173, 28)) +>undefined : Symbol(undefined) + + public async newEvery( +>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, 185, 55)) + + name: T, +>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, 187, 16)) +>IJob : Symbol(IJob, Decl(dependentReturnType3.ts, 174, 18)) + + options: RepeatOptions): Promise> { +>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, 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, 186, 34)) + + return this._createIntervalJob(interval, name, data, options); // Ok +>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, 186, 34)) + + return this._createIntervalJobs(interval, name, data, options); // Ok +>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. + } + + 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, 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, 205, 20)) + + if (value == null) return null; // Ok +>value : Symbol(value, Decl(dependentReturnType3.ts, 205, 57)) + + if (typeof value !== 'string') { +>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, 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 new file mode 100644 index 00000000000..ac94d85e9b9 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType3.types @@ -0,0 +1,1000 @@ +//// [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 + : never; + + +// 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 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ +>message : T +> : ^ + + T extends string + ? string + : T extends IMessage + ? IMessage + : never { + 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 : never +> : ^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^^^^ +>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 +>true : true +> : ^^^^ + + ? (message: IMessage) => IMessage +>message : IMessage +> : ^^^^^^^^ + + : T extends false +>false : false +> : ^^^^^ + + ? (message: string) => string +>message : string +> : ^^^^^^ + + : never { + 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 : never +> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ +>instance : NewKatex +> : ^^^^^^^^ +>renderMessage : (message: T_1) => T_1 extends string ? string : T_1 extends IMessage ? IMessage : never +> : ^^^^^^^^^^^^^ ^^ ^^ ^^^^^ +>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 : 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 +type SettingComposedValue = { key: string; value: T }; +>SettingComposedValue : SettingComposedValue +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>key : string +> : ^^^^^^ +>value : T +> : ^ + +type 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[]>> +> : ^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^ ^^^^^^^^^ ^^^^^^^^^^^ ^^ ^^ ^^^ ^^^^^ + + _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 : 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 +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ +>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: 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 : 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: 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 : 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/baselines/reference/dependentReturnType4.errors.txt b/tests/baselines/reference/dependentReturnType4.errors.txt new file mode 100644 index 00000000000..5f06601f9fe --- /dev/null +++ b/tests/baselines/reference/dependentReturnType4.errors.txt @@ -0,0 +1,40 @@ +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 (2 errors) ==== + declare const rand: { a?: never }; + type Missing = typeof rand.a; + + // Detection of valid optional parameter references + + // Ok, will narrow return type + function bar1(x?: T): + T extends Missing ? 0 : T extends string ? 1 : never { + if (x === undefined) { + return 0; + } + 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 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 '0' is not assignable to type 'T extends undefined ? 0 : T extends string ? 1 : never'. + } + return 1; + ~~~~~~ +!!! 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 new file mode 100644 index 00000000000..77b99a29dd9 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType4.symbols @@ -0,0 +1,76 @@ +//// [tests/cases/compiler/dependentReturnType4.ts] //// + +=== dependentReturnType4.ts === +declare const rand: { a?: never }; +>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, 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)) + +// Detection of valid optional parameter references + +// 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)) + + 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)) + + if (x === undefined) { +>x : Symbol(x, Decl(dependentReturnType4.ts, 6, 42)) +>undefined : Symbol(undefined) + + return 0; + } + return 1; +} + +// 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, 15, 14)) +>T : Symbol(T, Decl(dependentReturnType4.ts, 15, 14)) + + if (x === undefined) { +>x : Symbol(x, Decl(dependentReturnType4.ts, 15, 44)) +>undefined : Symbol(undefined) + + return 0; + } + return 1; +} + +// 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 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, 24, 32)) +>undefined : Symbol(undefined) + + return 0; + } + return 1; +} diff --git a/tests/baselines/reference/dependentReturnType4.types b/tests/baselines/reference/dependentReturnType4.types new file mode 100644 index 00000000000..8b2b910a65f --- /dev/null +++ b/tests/baselines/reference/dependentReturnType4.types @@ -0,0 +1,95 @@ +//// [tests/cases/compiler/dependentReturnType4.ts] //// + +=== dependentReturnType4.ts === +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 +> : ^^^^^^^^^ + +// Detection of valid optional parameter references + +// 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 Missing ? 0 : T extends string ? 1 : never { + if (x === undefined) { +>x === undefined : boolean +> : ^^^^^^^ +>x : T | undefined +> : ^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + + return 0; +>0 : 0 +> : ^ + } + return 1; +>1 : 1 +> : ^ +} + +// 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 undefined ? 0 : T extends string ? 1 : never { + if (x === undefined) { +>x === undefined : boolean +> : ^^^^^^^ +>x : T | undefined +> : ^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + + return 0; +>0 : 0 +> : ^ + } + return 1; +>1 : 1 +> : ^ +} + +// 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 +> : ^^^^^^^^^^^^^ + + T extends undefined ? 0 : T extends string ? 1 : never { + if (x === undefined) { +>x === undefined : boolean +> : ^^^^^^^ +>x : T | undefined +> : ^^^^^^^^^^^^^ +>undefined : undefined +> : ^^^^^^^^^ + + return 0; +>0 : 0 +> : ^ + } + return 1; +>1 : 1 +> : ^ +} diff --git a/tests/baselines/reference/dependentReturnType5.errors.txt b/tests/baselines/reference/dependentReturnType5.errors.txt new file mode 100644 index 00000000000..ce2ada8762c --- /dev/null +++ b/tests/baselines/reference/dependentReturnType5.errors.txt @@ -0,0 +1,109 @@ +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'. + + +==== dependentReturnType5.ts (2 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 + } + return 2; // Ok + } + if (typeof x === "number") { + if (Math.random()) { + return 2; // Error + ~~~~~~ +!!! error TS2322: Type '2' is not assignable to type 'Comp[T]'. +!!! 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 00000000000..9061f59ff0c --- /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 00000000000..25f542fec56 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType5.types @@ -0,0 +1,331 @@ +//// [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/baselines/reference/dependentReturnType6.errors.txt b/tests/baselines/reference/dependentReturnType6.errors.txt new file mode 100644 index 00000000000..638c23268ad --- /dev/null +++ b/tests/baselines/reference/dependentReturnType6.errors.txt @@ -0,0 +1,193 @@ +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(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(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(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(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(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 (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 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 fun4bad>(x: T, y: U): SomeInterfaceBad[U] { + if (y === "prop1") { + return x === 1 ? true : false; + ~~~~ +!!! 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 '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 '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 '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; + "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; + }; + 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; + ~ +!!! 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:98: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'. + } + } + + // 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 new file mode 100644 index 00000000000..44fc28739e0 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType6.symbols @@ -0,0 +1,367 @@ +//// [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 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(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(SomeInterfaceBad.prop2, Decl(file.ts, 21, 60)) +>T : Symbol(T, Decl(file.ts, 20, 27)) +>T : Symbol(T, Decl(file.ts, 20, 27)) +} + +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, 25, 68)) + + return x === 1 ? true : false; +>x : Symbol(x, Decl(file.ts, 25, 63)) + } + return x ? 1 : 2; +>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, 43, 1)) + + "a": number; +>"a" : Symbol(BB["a"], Decl(file.ts, 46, 14)) + + "b": string; +>"b" : Symbol(BB["b"], Decl(file.ts, 47, 16)) +} + +interface AA { +>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, 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, 52, 15)) +} + +function reduction>(x: T, y: U): AA[U] { +>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, 56, 64)) +>y : Symbol(y, Decl(file.ts, 56, 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, 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, 65, 51)) + + if (y === "a") { +>y : Symbol(y, Decl(file.ts, 65, 56)) + + return 0; + } + return ""; + } + return true; +} + +// `this` type parameter - Doesn't narrow +abstract class SomeClass { +>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, 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, 77, 9)) +>Sub1 : Symbol(Sub1, Decl(file.ts, 83, 1)) + + return 1; + } + return 2; + } +} +class Sub1 extends SomeClass { +>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, 84, 30)) + +}; +class Sub2 extends SomeClass { +>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, 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, 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, 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, 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, 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, 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, 103, 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, 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, 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, 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 new file mode 100644 index 00000000000..f1831992e69 --- /dev/null +++ b/tests/baselines/reference/dependentReturnType6.types @@ -0,0 +1,512 @@ +//// [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 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 +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>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 fun4bad>(x: T, y: U): SomeInterfaceBad[U] { +>fun4bad : >(x: T, y: U) => SomeInterfaceBad[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 +> : ^ +} + +// 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; +>"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" +> : ^^^^^ + } +} + +// 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/baselines/reference/dependentReturnType8.symbols b/tests/baselines/reference/dependentReturnType8.symbols new file mode 100644 index 00000000000..9151c3c4dde --- /dev/null +++ b/tests/baselines/reference/dependentReturnType8.symbols @@ -0,0 +1,30 @@ +//// [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)) + +// Arrow function with expression body +const getObject = +>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, 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, 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 new file mode 100644 index 00000000000..c42a8b0bccb --- /dev/null +++ b/tests/baselines/reference/dependentReturnType8.types @@ -0,0 +1,38 @@ +//// [tests/cases/compiler/dependentReturnType8.ts] //// + +=== dependentReturnType8.ts === +export {}; + +declare const record: Record; +>record : Record +> : ^^^^^^^^^^^^^^^^^^^^^^^^ + +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 +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^ + + (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/returnConditionalExpressionJSDocCast.symbols b/tests/baselines/reference/returnConditionalExpressionJSDocCast.symbols new file mode 100644 index 00000000000..1f49ce6b305 --- /dev/null +++ b/tests/baselines/reference/returnConditionalExpressionJSDocCast.symbols @@ -0,0 +1,35 @@ +//// [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, 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, --, --)) + +/** + + * @param {string=} type the type of source that should be generated + * @returns {String} + */ +function source(type = "javascript") { +>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, 8, 16)) + + ? sources.get(type) +>sources.get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) +>sources : Symbol(sources, Decl(file.js, 2, 5)) +>get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) +>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, 2, 5)) +>get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) + + ); +} diff --git a/tests/baselines/reference/returnConditionalExpressionJSDocCast.types b/tests/baselines/reference/returnConditionalExpressionJSDocCast.types new file mode 100644 index 00000000000..4b98f032cb8 --- /dev/null +++ b/tests/baselines/reference/returnConditionalExpressionJSDocCast.types @@ -0,0 +1,66 @@ +//// [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 +> : ^^^^^^^^^^^^^^^^^^^ +>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/baselines/reference/unusedLocalsInRecursiveReturn.symbols b/tests/baselines/reference/unusedLocalsInRecursiveReturn.symbols new file mode 100644 index 00000000000..f7ee6db9ba4 --- /dev/null +++ b/tests/baselines/reference/unusedLocalsInRecursiveReturn.symbols @@ -0,0 +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, 1, 19)) +>other : Symbol(other, Decl(unusedLocalsInRecursiveReturn.ts, 1, 31)) + + const someLocalVar = arg + other; +>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, 2, 9)) +>arg : Symbol(arg, Decl(unusedLocalsInRecursiveReturn.ts, 1, 19)) +} diff --git a/tests/baselines/reference/unusedLocalsInRecursiveReturn.types b/tests/baselines/reference/unusedLocalsInRecursiveReturn.types new file mode 100644 index 00000000000..c1b59613f34 --- /dev/null +++ b/tests/baselines/reference/unusedLocalsInRecursiveReturn.types @@ -0,0 +1,32 @@ +//// [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 +> : ^ ^^ ^^ ^^ ^^^^^^^^^^ +>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/arrowExpressionJs.ts b/tests/cases/compiler/arrowExpressionJs.ts new file mode 100644 index 00000000000..11f38f7276a --- /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 diff --git a/tests/cases/compiler/dependentReturnType1.ts b/tests/cases/compiler/dependentReturnType1.ts new file mode 100644 index 00000000000..7527d3015bc --- /dev/null +++ b/tests/cases/compiler/dependentReturnType1.ts @@ -0,0 +1,519 @@ +// @strict: true +// @noEmit: true +// @target: esnext + +interface A { + 1: number; + 2: string; +} + +function f1(x: T): A[T] { + if (x === 1) { + return 0; // Ok + } + else { + return 1; // Error + } +} + +interface C { + 1: number; + 2: string; + 3: boolean; +} + +function f2(x: T): C[T] { + if (x === 1) { + return 0; // Ok + } + 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; // Ok + } + 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"; +} +// 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" }; // Error + } + return { a: "a", b: "b", c: "c", d: "d", e: "e", f: "f", g: "g" }; // Error +} +// 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 + } + // 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" }; // EPC Error +} + +// 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; +} + +interface Dog extends Animal { + bark: () => string; +} + +// 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); + } + 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] { + 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 }; + // 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; + } + + // Good conditional + name2(name?: T): T extends string ? this : T extends undefined ? string : never { + 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 : never { + if (typeof name === 'undefined') { + return this; // Error + } + this.root.name = name; + return name; // Error + } +} + +// Conditional expressions +interface Aa { + 1: number; + 2: string; + 3: boolean; +} + +function trivialConditional(x: T): Aa[T] { + if (x !== 1) { + return x === 2 ? "" : true; + } + else { + return 0; + } +} + +function conditional(x: T): + 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 : never { + return x === "a" ? x : parseInt(x); // Ok +} + +function conditionalWithError( + x: T +): T extends "a" ? number : T extends "b" ? string : never { + return x === "a" ? x : parseInt(x); // Error +} + +// Multiple indexed type 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; // Ok + } + + return undefined as never; +} + +// 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: 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 ""; + } + x.a; + return 1; +} + +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 : never; + +// 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; // Error + } + if (typeof x === "number" && y === false) { + 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 : never { + 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 : never { + if (isString(input)) { + return input[0].toUpperCase() + input.slice(1); // Ok + } else { + return input[0].toUpperCase() + input.slice(1); // Bad, error + } +} + +// 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; + } + return 1; +} + +// 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; // Good error + } + return undefined as any; +} + +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; +} + +// Shadowing of the narrowed reference +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 + } + return ""; // Error + } +} + +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(a: T): T extends true ? 1 : T extends false ? 2 : never { + if ((true)) { + const someX = 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; + } + + return undefined as any; +} + +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) { + return 1; // Error + } + return ""; // Ok + } + return 0; // Ok +} + +// 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; + } + return ""; +} + +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; +} + +// From #33912 +abstract class Operation { + abstract perform(t: T): R; +} + +type ConditionalReturnType | undefined> = + EOp extends Operation ? R : EOp extends undefined ? T | R : never; + + +class ConditionalOperation< + T, + R, + EOp extends Operation | undefined, +> extends Operation> { + constructor( + private predicate: (value: T) => boolean, + private thenOp: Operation, + private elseOp?: EOp, + ) { + 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); // Would be ok + } else { + return t; // Would be ok + } + } +} + +// 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 conditional expressions with parentheses +function returnStuff1(x: T ): T extends true ? 1 : T extends false ? 2 : never { + return (x ? (1) : 2); +} + +function returnStuff2(x: T ): + T extends 1 ? "one" : T extends 2 ? "two" : T extends "a" ? 0 : never { + return (typeof x === "string" ? 0 : (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 new file mode 100644 index 00000000000..0f14e3f7faa --- /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 +} diff --git a/tests/cases/compiler/dependentReturnType3.ts b/tests/cases/compiler/dependentReturnType3.ts new file mode 100644 index 00000000000..df60c0677fd --- /dev/null +++ b/tests/cases/compiler/dependentReturnType3.ts @@ -0,0 +1,216 @@ +// @strict: true +// @noEmit: true +// @target: ES6 + +// Adapted from ts-error-deltas repos + +type HelperCond = + T extends A + ? R1 + : T extends B + ? R2 + : never; + + +// 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 + : never { + 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 + : never { + 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 +} \ No newline at end of file diff --git a/tests/cases/compiler/dependentReturnType4.ts b/tests/cases/compiler/dependentReturnType4.ts new file mode 100644 index 00000000000..166b8497fa3 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType4.ts @@ -0,0 +1,36 @@ +// @strict: true +// @noEmit: true +// @target: ES2022 +// @exactOptionalPropertyTypes: true + +declare const rand: { a?: never }; +type Missing = typeof rand.a; + +// Detection of valid optional parameter references + +// Ok, will narrow return type +function bar1(x?: T): + T extends Missing ? 0 : T extends string ? 1 : never { + if (x === undefined) { + return 0; + } + 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 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 1; +} \ No newline at end of file diff --git a/tests/cases/compiler/dependentReturnType5.ts b/tests/cases/compiler/dependentReturnType5.ts new file mode 100644 index 00000000000..137474217ec --- /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 diff --git a/tests/cases/compiler/dependentReturnType6.ts b/tests/cases/compiler/dependentReturnType6.ts new file mode 100644 index 00000000000..4a319fa7f40 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType6.ts @@ -0,0 +1,137 @@ +// @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 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 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; + "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"; + } +} + +// 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; +} + diff --git a/tests/cases/compiler/dependentReturnType8.ts b/tests/cases/compiler/dependentReturnType8.ts new file mode 100644 index 00000000000..169d5f540c2 --- /dev/null +++ b/tests/cases/compiler/dependentReturnType8.ts @@ -0,0 +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/returnConditionalExpressionJSDocCast.ts b/tests/cases/compiler/returnConditionalExpressionJSDocCast.ts new file mode 100644 index 00000000000..6141d73b86e --- /dev/null +++ b/tests/cases/compiler/returnConditionalExpressionJSDocCast.ts @@ -0,0 +1,22 @@ +// @strict: true +// @noEmit: true +// @target: esnext +// @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(); +/** + + * @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 diff --git a/tests/cases/compiler/unusedLocalsInRecursiveReturn.ts b/tests/cases/compiler/unusedLocalsInRecursiveReturn.ts new file mode 100644 index 00000000000..5ad1bac3cb0 --- /dev/null +++ b/tests/cases/compiler/unusedLocalsInRecursiveReturn.ts @@ -0,0 +1,9 @@ +// @strict: true +// @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); +} \ No newline at end of file diff --git a/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts b/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts new file mode 100644 index 00000000000..fad2f1452fd --- /dev/null +++ b/tests/cases/fourslash/returnTypeNarrowingAfterCachingTypes.ts @@ -0,0 +1,12 @@ +/// + +// @strict: true +//// function h(x: T): T extends true ? 1 : T extends false ? 2 : never { +//// if (x) { +//// return 1; +//// } +//// return 2; +//// } + +verify.encodedSemanticClassificationsLength("2020", 21); +verify.noErrors(); \ No newline at end of file