From a3028a282344ab5f459ad3c660c9d773d45b8efb Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 8 Mar 2021 16:25:09 -0800 Subject: [PATCH 01/14] Narrow type variables with union constraints when merited by contextual type --- src/compiler/checker.ts | 81 ++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e5756b54b6ac9..7b54206e11ea1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23569,6 +23569,32 @@ namespace ts { return type; } + function containsTypeVariable(type: Type, typeVariable: TypeVariable): boolean { + return type === typeVariable || + !!(type.flags & TypeFlags.UnionOrIntersection) && some((type).types, t => containsTypeVariable(t, typeVariable)); + } + + function getConstraintForReference(type: Type, reference: Identifier | ElementAccessExpression | PropertyAccessExpression | QualifiedName, checkMode: CheckMode | undefined) { + // When the type of a reference is a type variable with a union type constraint, and when the reference + // is contextually typed by a type that doesn't contain that type variable, we substitute the union type + // constraint for the type variable to give control flow analysis an opportunity to narrow it further. + // (This transformation causes no type errors because a type variable with a union constraint is only + // assignable to itself and its constraint, and we already know the type variable doesn't occur in the + // contextual type.) For example, for a reference of a type parameter type 'T extends string | undefined' + // with a contextual type 'string', we substitute 'string | undefined' to give control flow analysis the + // opportunity to narrow to type 'string'. + if (type.flags & TypeFlags.TypeVariable && reference.kind !== SyntaxKind.QualifiedName && !(checkMode && checkMode & CheckMode.Inferential)) { + const constraint = getConstraintOfType(type); + if (constraint && constraint.flags & TypeFlags.Union) { + const contextualType = getContextualType(reference); + if (contextualType && !containsTypeVariable(contextualType, type)) { + return constraint; + } + } + } + return getConstraintForLocation(type, reference); + } + function isExportOrExportExpression(location: Node) { return !!findAncestor(location, e => e.parent && isExportAssignment(e.parent) && e.parent.expression === e && isEntityNameExpression(e)); } @@ -23593,7 +23619,7 @@ namespace ts { } } - function checkIdentifier(node: Identifier): Type { + function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type { const symbol = getResolvedSymbol(node); if (symbol === unknownSymbol) { return errorType; @@ -23671,7 +23697,7 @@ namespace ts { checkNestedBlockScopedBinding(node, symbol); - const type = getConstraintForLocation(getTypeOfSymbol(localOrExportSymbol), node); + let type = getTypeOfSymbol(localOrExportSymbol); const assignmentKind = getAssignmentTargetKind(node); if (assignmentKind) { @@ -23711,6 +23737,8 @@ namespace ts { return type; } + type = getConstraintForReference(type, node, checkMode); + // The declaration container is the innermost function that encloses the declaration of the variable // or parameter. The flow container is the innermost function starting with which we analyze the control // flow graph to determine the control flow based type. @@ -26598,19 +26626,19 @@ namespace ts { return nonNullType; } - function checkPropertyAccessExpression(node: PropertyAccessExpression) { - return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain) : - checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name); + function checkPropertyAccessExpression(node: PropertyAccessExpression, checkMode: CheckMode | undefined) { + return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain, checkMode) : + checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name, checkMode); } - function checkPropertyAccessChain(node: PropertyAccessChain) { + function checkPropertyAccessChain(node: PropertyAccessChain, checkMode: CheckMode | undefined) { const leftType = checkExpression(node.expression); const nonOptionalType = getOptionalExpressionType(leftType, node.expression); - return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), node, nonOptionalType !== leftType); + return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name, checkMode), node, nonOptionalType !== leftType); } - function checkQualifiedName(node: QualifiedName) { - return checkPropertyAccessExpressionOrQualifiedName(node, node.left, checkNonNullExpression(node.left), node.right); + function checkQualifiedName(node: QualifiedName, checkMode: CheckMode | undefined) { + return checkPropertyAccessExpressionOrQualifiedName(node, node.left, checkNonNullExpression(node.left), node.right, checkMode); } function isMethodAccessForCall(node: Node) { @@ -26702,7 +26730,7 @@ namespace ts { && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop); } - function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier) { + function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier, checkMode: CheckMode | undefined) { const parentSymbol = getNodeLinks(left).resolvedSymbol; const assignmentKind = getAssignmentTargetKind(node); const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); @@ -26787,12 +26815,12 @@ namespace ts { error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right)); return errorType; } - propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : getConstraintForLocation(getTypeOfSymbol(prop), node); + propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : getTypeOfSymbol(prop); } - return getFlowTypeOfAccessExpression(node, prop, propType, right); + return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode); } - function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node) { + function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node, checkMode: CheckMode | undefined) { // Only compute control flow type if this is a property access expression that isn't an // assignment target, and the referenced property was declared as a variable, property, // accessor, or optional method. @@ -26804,6 +26832,7 @@ namespace ts { if (propType === autoType) { return getFlowTypeOfProperty(node, prop); } + propType = getConstraintForReference(propType, node, checkMode); // If strict null checks and strict property initialization checks are enabled, if we have // a this.xxx property access, if the property is an instance property without an initializer, // and if we are in a constructor of the same class as the property declaration, assume that @@ -27221,18 +27250,18 @@ namespace ts { return false; } - function checkIndexedAccess(node: ElementAccessExpression): Type { - return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain) : - checkElementAccessExpression(node, checkNonNullExpression(node.expression)); + function checkIndexedAccess(node: ElementAccessExpression, checkMode: CheckMode | undefined): Type { + return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain, checkMode) : + checkElementAccessExpression(node, checkNonNullExpression(node.expression), checkMode); } - function checkElementAccessChain(node: ElementAccessChain) { + function checkElementAccessChain(node: ElementAccessChain, checkMode: CheckMode | undefined) { const exprType = checkExpression(node.expression); const nonOptionalType = getOptionalExpressionType(exprType, node.expression); - return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), node, nonOptionalType !== exprType); + return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression), checkMode), node, nonOptionalType !== exprType); } - function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type): Type { + function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type, checkMode: CheckMode | undefined): Type { const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType; const indexExpression = node.argumentExpression; const indexType = checkExpression(indexExpression); @@ -27251,7 +27280,7 @@ namespace ts { AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : AccessFlags.None; const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, /*noUncheckedIndexedAccessCandidate*/ undefined, node, accessFlags | AccessFlags.ExpressionPosition) || errorType; - return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node); + return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node); } function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement { @@ -32070,7 +32099,7 @@ namespace ts { } switch (kind) { case SyntaxKind.Identifier: - return checkIdentifier(node); + return checkIdentifier(node, checkMode); case SyntaxKind.ThisKeyword: return checkThisExpression(node); case SyntaxKind.SuperKeyword: @@ -32099,11 +32128,11 @@ namespace ts { case SyntaxKind.ObjectLiteralExpression: return checkObjectLiteral(node, checkMode); case SyntaxKind.PropertyAccessExpression: - return checkPropertyAccessExpression(node); + return checkPropertyAccessExpression(node, checkMode); case SyntaxKind.QualifiedName: - return checkQualifiedName(node); + return checkQualifiedName(node, checkMode); case SyntaxKind.ElementAccessExpression: - return checkIndexedAccess(node); + return checkIndexedAccess(node, checkMode); case SyntaxKind.CallExpression: if ((node).expression.kind === SyntaxKind.ImportKeyword) { return checkImportCallExpression(node); @@ -38467,10 +38496,10 @@ namespace ts { } if (name.kind === SyntaxKind.PropertyAccessExpression) { - checkPropertyAccessExpression(name); + checkPropertyAccessExpression(name, CheckMode.Normal); } else { - checkQualifiedName(name); + checkQualifiedName(name, CheckMode.Normal); } return links.resolvedSymbol; } From df7d7be2c6479154d3b9405be33930d7ac09263e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Mar 2021 08:00:46 -0800 Subject: [PATCH 02/14] Narrow generics with union type constraints as indicated by contextual type --- src/compiler/checker.ts | 60 ++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7b54206e11ea1..1165472fb8ad9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20532,7 +20532,7 @@ namespace ts { function isStringLiteralTypeValueParsableAsType(s: StringLiteralType, target: Type): boolean { if (target.flags & TypeFlags.Union) { - return !!forEachType(target, t => isStringLiteralTypeValueParsableAsType(s, t)); + return someType(target, t => isStringLiteralTypeValueParsableAsType(s, t)); } switch (target) { case stringType: return true; @@ -21975,6 +21975,10 @@ namespace ts { return type.flags & TypeFlags.Union ? forEach((type).types, f) : f(type); } + function someType(type: Type, f: (t: Type) => boolean): boolean { + return type.flags & TypeFlags.Union ? some((type).types, f) : f(type); + } + function everyType(type: Type, f: (t: Type) => boolean): boolean { return type.flags & TypeFlags.Union ? every((type).types, f) : f(type); } @@ -23553,7 +23557,7 @@ namespace ts { } function typeHasNullableConstraint(type: Type) { - return type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Nullable); + return !!(type.flags & TypeFlags.InstantiableNonPrimitive) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Nullable); } function getConstraintForLocation(type: Type, node: Node): Type; @@ -23563,36 +23567,38 @@ namespace ts { // and the type of the node includes type variables with constraints that are nullable, we fetch the // apparent type of the node *before* performing control flow analysis such that narrowings apply to // the constraint type. - if (type && isConstraintPosition(node) && forEachType(type, typeHasNullableConstraint)) { + if (type && isConstraintPosition(node) && someType(type, typeHasNullableConstraint)) { return mapType(getWidenedType(type), getBaseConstraintOrType); } return type; } - function containsTypeVariable(type: Type, typeVariable: TypeVariable): boolean { - return type === typeVariable || - !!(type.flags & TypeFlags.UnionOrIntersection) && some((type).types, t => containsTypeVariable(t, typeVariable)); + function isTypeVariableWithUnionConstraint(type: Type) { + return !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & TypeFlags.Union); + } + + function containsTypeVariable(type: Type): boolean { + return !!(type.flags & TypeFlags.Instantiable || type.flags & TypeFlags.UnionOrIntersection && some((type).types, containsTypeVariable)); + } + + function hasContextualTypeWithNoTypeVariables(node: Expression) { + const contextualType = getContextualType(node); + return contextualType && !someType(contextualType, containsTypeVariable); } function getConstraintForReference(type: Type, reference: Identifier | ElementAccessExpression | PropertyAccessExpression | QualifiedName, checkMode: CheckMode | undefined) { - // When the type of a reference is a type variable with a union type constraint, and when the reference - // is contextually typed by a type that doesn't contain that type variable, we substitute the union type - // constraint for the type variable to give control flow analysis an opportunity to narrow it further. - // (This transformation causes no type errors because a type variable with a union constraint is only - // assignable to itself and its constraint, and we already know the type variable doesn't occur in the - // contextual type.) For example, for a reference of a type parameter type 'T extends string | undefined' - // with a contextual type 'string', we substitute 'string | undefined' to give control flow analysis the - // opportunity to narrow to type 'string'. - if (type.flags & TypeFlags.TypeVariable && reference.kind !== SyntaxKind.QualifiedName && !(checkMode && checkMode & CheckMode.Inferential)) { - const constraint = getConstraintOfType(type); - if (constraint && constraint.flags & TypeFlags.Union) { - const contextualType = getContextualType(reference); - if (contextualType && !containsTypeVariable(contextualType, type)) { - return constraint; - } - } - } - return getConstraintForLocation(type, reference); + // When the type of a reference is or contains an instantiable type with a union type constraint, and + // when the reference is in a constraint position (where it is known we'll obtain the apparent type) or + // has a contextual type containing no instantiables (meaning constraints will determine assignability), + // we substitute constraints for all instantiables in the type of the reference to give control flow + // analysis an opportunity to narrow it further. For example, for a reference of a type parameter type + // 'T extends string | undefined' with a contextual type 'string', we substitute 'string | undefined' + // to give control flow analysis the opportunity to narrow to type 'string'. + const substituteConstraints = reference.kind !== SyntaxKind.QualifiedName && + !(checkMode && checkMode & CheckMode.Inferential) && + someType(type, isTypeVariableWithUnionConstraint) && + (isConstraintPosition(reference) || hasContextualTypeWithNoTypeVariables(reference)); + return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; } function isExportOrExportExpression(location: Node) { @@ -25470,7 +25476,7 @@ namespace ts { if (inDestructuringPattern) { return createTupleType(elementTypes, elementFlags); } - if (forceTuple || inConstContext || contextualType && forEachType(contextualType, isTupleLikeType)) { + if (forceTuple || inConstContext || contextualType && someType(contextualType, isTupleLikeType)) { return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext)); } return createArrayLiteralType(createArrayType(elementTypes.length ? @@ -25956,7 +25962,7 @@ namespace ts { // If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process const childrenPropSymbol = createSymbol(SymbolFlags.Property, jsxChildrenPropertyName); childrenPropSymbol.type = childrenTypes.length === 1 ? childrenTypes[0] : - childrenContextualType && forEachType(childrenContextualType, isTupleLikeType) ? createTupleType(childrenTypes) : + childrenContextualType && someType(childrenContextualType, isTupleLikeType) ? createTupleType(childrenTypes) : createArrayType(getUnionType(childrenTypes)); // Fake up a property declaration for the children childrenPropSymbol.valueDeclaration = factory.createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined); @@ -41383,7 +41389,7 @@ namespace ts { } function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) { - if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && forEachType(unionTarget, isArrayLikeType)) { + if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && someType(unionTarget, isArrayLikeType)) { return find(unionTarget.types, t => !isArrayLikeType(t)); } } From 13cf64e581075f6fbf6fc3e6afd2709196cc35f7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Mar 2021 08:01:06 -0800 Subject: [PATCH 03/14] Accept new baselines --- .../assignmentNonObjectTypeConstraints.types | 2 +- .../reference/conditionalTypes1.errors.txt | 16 +---- .../reference/conditionalTypes1.types | 8 +-- .../reference/deeplyNestedConstraints.types | 2 +- .../reference/enumPropertyAccess.errors.txt | 4 +- .../reference/enumPropertyAccess.types | 4 +- ...unctionCallOnConstrainedTypeVariable.types | 8 +-- .../baselines/reference/intrinsicTypes.types | 2 +- .../reference/keyofAndIndexedAccess.types | 4 +- .../reference/keyofAndIndexedAccess2.types | 2 +- .../narrowingByTypeofInSwitch.symbols | 4 +- .../reference/narrowingByTypeofInSwitch.types | 60 +++++++++---------- .../noUncheckedIndexedAccess.errors.txt | 10 ++-- .../reference/noUncheckedIndexedAccess.types | 2 +- .../reference/nonNullMappedType.types | 4 +- ...terExtendingStringAssignableToString.types | 8 +-- .../reference/recursiveTypeRelations.types | 4 +- .../reference/restInvalidArgumentType.types | 2 +- .../switchWithConstrainedTypeVariable.types | 4 +- .../reference/templateLiteralTypes1.types | 4 +- .../typeParameterExtendingUnion1.types | 4 +- .../typeParameterExtendingUnion2.types | 4 +- .../reference/unionWithIndexSignature.types | 4 +- .../voidReturnIndexUnionInference.types | 8 +-- 24 files changed, 79 insertions(+), 95 deletions(-) diff --git a/tests/baselines/reference/assignmentNonObjectTypeConstraints.types b/tests/baselines/reference/assignmentNonObjectTypeConstraints.types index 06af56203d4ca..fde1913876fb2 100644 --- a/tests/baselines/reference/assignmentNonObjectTypeConstraints.types +++ b/tests/baselines/reference/assignmentNonObjectTypeConstraints.types @@ -40,7 +40,7 @@ function bar(x: T) { var y: A | B = x; // Ok >y : A | B ->x : T +>x : A | B } bar(new A); diff --git a/tests/baselines/reference/conditionalTypes1.errors.txt b/tests/baselines/reference/conditionalTypes1.errors.txt index fb07520a3d4d5..810520c71030a 100644 --- a/tests/baselines/reference/conditionalTypes1.errors.txt +++ b/tests/baselines/reference/conditionalTypes1.errors.txt @@ -2,17 +2,11 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(12,5): error TS23 tests/cases/conformance/types/conditional/conditionalTypes1.ts(17,5): error TS2322: Type 'T' is not assignable to type 'NonNullable'. Type 'string | undefined' is not assignable to type 'NonNullable'. Type 'undefined' is not assignable to type 'NonNullable'. -tests/cases/conformance/types/conditional/conditionalTypes1.ts(18,9): error TS2322: Type 'T' is not assignable to type 'string'. - Type 'string | undefined' is not assignable to type 'string'. - Type 'undefined' is not assignable to type 'string'. tests/cases/conformance/types/conditional/conditionalTypes1.ts(24,5): error TS2322: Type 'T[keyof T] | undefined' is not assignable to type 'NonNullable[keyof T]>'. Type 'undefined' is not assignable to type 'NonNullable[keyof T]>'. tests/cases/conformance/types/conditional/conditionalTypes1.ts(29,5): error TS2322: Type 'T["x"]' is not assignable to type 'NonNullable'. Type 'string | undefined' is not assignable to type 'NonNullable'. Type 'undefined' is not assignable to type 'NonNullable'. -tests/cases/conformance/types/conditional/conditionalTypes1.ts(30,9): error TS2322: Type 'T["x"]' is not assignable to type 'string'. - Type 'string | undefined' is not assignable to type 'string'. - Type 'undefined' is not assignable to type 'string'. tests/cases/conformance/types/conditional/conditionalTypes1.ts(103,5): error TS2322: Type 'FunctionProperties' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'FunctionProperties'. tests/cases/conformance/types/conditional/conditionalTypes1.ts(104,5): error TS2322: Type 'NonFunctionProperties' is not assignable to type 'T'. @@ -63,7 +57,7 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS Type 'boolean' is not assignable to type 'true'. -==== tests/cases/conformance/types/conditional/conditionalTypes1.ts (22 errors) ==== +==== tests/cases/conformance/types/conditional/conditionalTypes1.ts (20 errors) ==== type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d" type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c" @@ -88,10 +82,6 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS !!! error TS2322: Type 'string | undefined' is not assignable to type 'NonNullable'. !!! error TS2322: Type 'undefined' is not assignable to type 'NonNullable'. let s1: string = x; // Error - ~~ -!!! error TS2322: Type 'T' is not assignable to type 'string'. -!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'. -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. let s2: string = y; } @@ -111,10 +101,6 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS !!! error TS2322: Type 'string | undefined' is not assignable to type 'NonNullable'. !!! error TS2322: Type 'undefined' is not assignable to type 'NonNullable'. let s1: string = x; // Error - ~~ -!!! error TS2322: Type 'T["x"]' is not assignable to type 'string'. -!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'. -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. let s2: string = y; } diff --git a/tests/baselines/reference/conditionalTypes1.types b/tests/baselines/reference/conditionalTypes1.types index c331e1fc79566..e360b3dd7f33b 100644 --- a/tests/baselines/reference/conditionalTypes1.types +++ b/tests/baselines/reference/conditionalTypes1.types @@ -51,7 +51,7 @@ function f2(x: T, y: NonNullable) { let s1: string = x; // Error >s1 : string ->x : T +>x : string let s2: string = y; >s2 : string @@ -92,7 +92,7 @@ function f4(x: T["x"], y: NonNullables1 : string ->x : T["x"] +>x : string let s2: string = y; >s2 : string @@ -476,11 +476,11 @@ function f21(x: T, y: ZeroOf) { let z1: number | string = y; >z1 : string | number ->y : ZeroOf +>y : "" | 0 let z2: 0 | "" = y; >z2 : "" | 0 ->y : ZeroOf +>y : "" | 0 x = y; // Error >x = y : ZeroOf diff --git a/tests/baselines/reference/deeplyNestedConstraints.types b/tests/baselines/reference/deeplyNestedConstraints.types index 1020455f24164..2d8d34729068e 100644 --- a/tests/baselines/reference/deeplyNestedConstraints.types +++ b/tests/baselines/reference/deeplyNestedConstraints.types @@ -17,7 +17,7 @@ class BufferPool> { array.length; // Requires exploration of >5 levels of constraints >array.length : number ->array : Extract> +>array : string | number[] >length : number } } diff --git a/tests/baselines/reference/enumPropertyAccess.errors.txt b/tests/baselines/reference/enumPropertyAccess.errors.txt index fe670a3ef3105..bfd260a78ce7d 100644 --- a/tests/baselines/reference/enumPropertyAccess.errors.txt +++ b/tests/baselines/reference/enumPropertyAccess.errors.txt @@ -1,5 +1,5 @@ tests/cases/compiler/enumPropertyAccess.ts(7,11): error TS2339: Property 'Green' does not exist on type 'Colors.Red'. -tests/cases/compiler/enumPropertyAccess.ts(12,7): error TS2339: Property 'Green' does not exist on type 'B'. +tests/cases/compiler/enumPropertyAccess.ts(12,7): error TS2339: Property 'Green' does not exist on type 'Colors'. ==== tests/cases/compiler/enumPropertyAccess.ts (2 errors) ==== @@ -18,6 +18,6 @@ tests/cases/compiler/enumPropertyAccess.ts(12,7): error TS2339: Property 'Green' function fill(f: B) { f.Green; // error ~~~~~ -!!! error TS2339: Property 'Green' does not exist on type 'B'. +!!! error TS2339: Property 'Green' does not exist on type 'Colors'. f.toFixed(); // ok } \ No newline at end of file diff --git a/tests/baselines/reference/enumPropertyAccess.types b/tests/baselines/reference/enumPropertyAccess.types index 2fad0c84e9717..354dfdbd7f8b1 100644 --- a/tests/baselines/reference/enumPropertyAccess.types +++ b/tests/baselines/reference/enumPropertyAccess.types @@ -34,12 +34,12 @@ function fill(f: B) { f.Green; // error >f.Green : any ->f : B +>f : Colors >Green : any f.toFixed(); // ok >f.toFixed() : string >f.toFixed : (fractionDigits?: number) => string ->f : B +>f : Colors >toFixed : (fractionDigits?: number) => string } diff --git a/tests/baselines/reference/functionCallOnConstrainedTypeVariable.types b/tests/baselines/reference/functionCallOnConstrainedTypeVariable.types index cc053d93d0592..fb0318f14ddd3 100644 --- a/tests/baselines/reference/functionCallOnConstrainedTypeVariable.types +++ b/tests/baselines/reference/functionCallOnConstrainedTypeVariable.types @@ -37,24 +37,24 @@ function callN(p: T) { p.a("s"); // Error >p.a("s") : string >p.a : ((x: number) => string) | ((x: boolean) => string) ->p : T +>p : A | B >a : ((x: number) => string) | ((x: boolean) => string) >"s" : "s" var a: T["a"] = p.a; >a : T["a"] >p.a : ((x: number) => string) | ((x: boolean) => string) ->p : T +>p : A | B >a : ((x: number) => string) | ((x: boolean) => string) a(""); // Error >a("") : string ->a : T["a"] +>a : ((x: number) => string) | ((x: boolean) => string) >"" : "" a("", "", "", ""); // Error >a("", "", "", "") : string ->a : T["a"] +>a : ((x: number) => string) | ((x: boolean) => string) >"" : "" >"" : "" >"" : "" diff --git a/tests/baselines/reference/intrinsicTypes.types b/tests/baselines/reference/intrinsicTypes.types index e57a5f22ed35e..00123d307e0f7 100644 --- a/tests/baselines/reference/intrinsicTypes.types +++ b/tests/baselines/reference/intrinsicTypes.types @@ -132,7 +132,7 @@ function foo2(x: Uppercase) { let s: 'FOO' | 'BAR' = x; >s : "FOO" | "BAR" ->x : Uppercase +>x : "FOO" | "BAR" } declare function foo3(x: Uppercase): T; diff --git a/tests/baselines/reference/keyofAndIndexedAccess.types b/tests/baselines/reference/keyofAndIndexedAccess.types index f2dda4a889774..c90e635247a7b 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.types +++ b/tests/baselines/reference/keyofAndIndexedAccess.types @@ -630,7 +630,7 @@ function f50(k: keyof T, s: string) { const x2 = k as string; >x2 : string >k as string : string ->k : keyof T +>k : string | number | symbol } function f51(k: K, s: string) { @@ -646,7 +646,7 @@ function f51(k: K, s: string) { const x2 = k as string; >x2 : string >k as string : string ->k : K +>k : string | number | symbol } function f52(obj: { [x: string]: boolean }, k: Exclude, s: string, n: number) { diff --git a/tests/baselines/reference/keyofAndIndexedAccess2.types b/tests/baselines/reference/keyofAndIndexedAccess2.types index a9722ceddf0d1..048e11cad674d 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess2.types +++ b/tests/baselines/reference/keyofAndIndexedAccess2.types @@ -452,7 +452,7 @@ function fn} | {elements: Array}>(par >cb : (element: T["elements"][number]) => void >param.elements[0] : string | number >param.elements : string[] | number[] ->param : T +>param : { elements: string[]; } | { elements: number[]; } >elements : string[] | number[] >0 : 0 } diff --git a/tests/baselines/reference/narrowingByTypeofInSwitch.symbols b/tests/baselines/reference/narrowingByTypeofInSwitch.symbols index ea1282393fca1..0fae3d9f31cc8 100644 --- a/tests/baselines/reference/narrowingByTypeofInSwitch.symbols +++ b/tests/baselines/reference/narrowingByTypeofInSwitch.symbols @@ -404,9 +404,9 @@ function exhaustiveChecksGenerics(x: T): stri >x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 146, 69)) case 'number': return x.toString(2); ->x.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --) ... and 2 more) +>x.toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) >x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 146, 69)) ->toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --) ... and 2 more) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) case 'string': return x; >x : Symbol(x, Decl(narrowingByTypeofInSwitch.ts, 146, 69)) diff --git a/tests/baselines/reference/narrowingByTypeofInSwitch.types b/tests/baselines/reference/narrowingByTypeofInSwitch.types index 42078591ba1a9..2b028aad6bd16 100644 --- a/tests/baselines/reference/narrowingByTypeofInSwitch.types +++ b/tests/baselines/reference/narrowingByTypeofInSwitch.types @@ -167,48 +167,48 @@ function testExtendsUnion(x: T) { >'number' : "number" >assertNumber(x) : number >assertNumber : (x: number) => number ->x : T & number +>x : number case 'boolean': assertBoolean(x); return; >'boolean' : "boolean" >assertBoolean(x) : boolean >assertBoolean : (x: boolean) => boolean ->x : T & boolean +>x : boolean case 'function': assertAll(x); return; >'function' : "function" >assertAll(x) : Basic >assertAll : (x: Basic) => Basic ->x : T & Function +>x : Function case 'symbol': assertSymbol(x); return; >'symbol' : "symbol" >assertSymbol(x) : symbol >assertSymbol : (x: symbol) => symbol ->x : T & symbol +>x : symbol case 'object': assertAll(x); return; >'object' : "object" >assertAll(x) : Basic >assertAll : (x: Basic) => Basic ->x : T +>x : object case 'string': assertString(x); return; >'string' : "string" >assertString(x) : string >assertString : (x: string) => string ->x : T & string +>x : string case 'undefined': assertUndefined(x); return; >'undefined' : "undefined" >assertUndefined(x) : undefined >assertUndefined : (x: undefined) => undefined ->x : T & undefined +>x : undefined } assertAll(x); >assertAll(x) : Basic >assertAll : (x: Basic) => Basic ->x : T +>x : never } function testAny(x: any) { @@ -364,30 +364,30 @@ function testExtendsExplicitDefault(x: T) { >'number' : "number" >assertNumber(x) : number >assertNumber : (x: number) => number ->x : T & number +>x : number case 'boolean': assertBoolean(x); return; >'boolean' : "boolean" >assertBoolean(x) : boolean >assertBoolean : (x: boolean) => boolean ->x : T & boolean +>x : boolean case 'function': assertAll(x); return; >'function' : "function" >assertAll(x) : Basic >assertAll : (x: Basic) => Basic ->x : T & Function +>x : Function case 'symbol': assertSymbol(x); return; >'symbol' : "symbol" >assertSymbol(x) : symbol >assertSymbol : (x: symbol) => symbol ->x : T & symbol +>x : symbol default: assertAll(x); return; >assertAll(x) : Basic >assertAll : (x: Basic) => Basic ->x : T +>x : string | object | undefined } } @@ -404,30 +404,30 @@ function testExtendsImplicitDefault(x: T) { >'number' : "number" >assertNumber(x) : number >assertNumber : (x: number) => number ->x : T & number +>x : number case 'boolean': assertBoolean(x); return; >'boolean' : "boolean" >assertBoolean(x) : boolean >assertBoolean : (x: boolean) => boolean ->x : T & boolean +>x : boolean case 'function': assertAll(x); return; >'function' : "function" >assertAll(x) : Basic >assertAll : (x: Basic) => Basic ->x : T & Function +>x : Function case 'symbol': assertSymbol(x); return; >'symbol' : "symbol" >assertSymbol(x) : symbol >assertSymbol : (x: symbol) => symbol ->x : T & symbol +>x : symbol } return assertAll(x); >assertAll(x) : Basic >assertAll : (x: Basic) => Basic ->x : T +>x : string | object | undefined } type L = (x: number) => string; @@ -484,21 +484,21 @@ function exhaustiveChecksGenerics(x: T): stri case 'number': return x.toString(2); >'number' : "number" >x.toString(2) : string ->x.toString : ((radix?: number | undefined) => string) | ((() => string) & ((radix?: number | undefined) => string)) | ((() => string) & ((radix?: number | undefined) => string)) | ((() => string) & ((radix?: number | undefined) => string)) ->x : T & number ->toString : ((radix?: number | undefined) => string) | ((() => string) & ((radix?: number | undefined) => string)) | ((() => string) & ((radix?: number | undefined) => string)) | ((() => string) & ((radix?: number | undefined) => string)) +>x.toString : (radix?: number | undefined) => string +>x : number +>toString : (radix?: number | undefined) => string >2 : 2 case 'string': return x; >'string' : "string" ->x : T & string +>x : string case 'function': return (x as L)(42); // Can't narrow generic >'function' : "function" >(x as L)(42) : string >(x as L) : L >x as L : L ->x : T +>x : L >42 : 42 case 'object': return (x as R).x; // Can't narrow generic @@ -506,7 +506,7 @@ function exhaustiveChecksGenerics(x: T): stri >(x as R).x : string >(x as R) : R >x as R : R ->x : T +>x : R >x : string } } @@ -783,7 +783,7 @@ function keyofNarrowing(k: keyof S) { >'number' : "number" >assertNumber(k) : number >assertNumber : (x: number) => number ->k : keyof S & number +>k : number >assertKeyofS(k) : void >assertKeyofS : (k1: keyof S) => void >k : keyof S & number @@ -792,7 +792,7 @@ function keyofNarrowing(k: keyof S) { >'symbol' : "symbol" >assertSymbol(k) : symbol >assertSymbol : (x: symbol) => symbol ->k : keyof S & symbol +>k : symbol >assertKeyofS(k) : void >assertKeyofS : (k1: keyof S) => void >k : keyof S & symbol @@ -801,7 +801,7 @@ function keyofNarrowing(k: keyof S) { >'string' : "string" >assertString(k) : string >assertString : (x: string) => string ->k : keyof S & string +>k : string >assertKeyofS(k) : void >assertKeyofS : (k1: keyof S) => void >k : keyof S & string @@ -1055,7 +1055,7 @@ function keyofNarrowingWithTemplate(k: key >`number` : "number" >assertNumber(k) : number >assertNumber : (x: number) => number ->k : keyof S & number +>k : number >assertKeyofS(k) : void >assertKeyofS : (k1: keyof S) => void >k : keyof S & number @@ -1064,7 +1064,7 @@ function keyofNarrowingWithTemplate(k: key >`symbol` : "symbol" >assertSymbol(k) : symbol >assertSymbol : (x: symbol) => symbol ->k : keyof S & symbol +>k : symbol >assertKeyofS(k) : void >assertKeyofS : (k1: keyof S) => void >k : keyof S & symbol @@ -1073,7 +1073,7 @@ function keyofNarrowingWithTemplate(k: key >`string` : "string" >assertString(k) : string >assertString : (x: string) => string ->k : keyof S & string +>k : string >assertKeyofS(k) : void >assertKeyofS : (k1: keyof S) => void >k : keyof S & string diff --git a/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt b/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt index 39bf01fc816ec..39a4e9aeb4442 100644 --- a/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt +++ b/tests/baselines/reference/noUncheckedIndexedAccess.errors.txt @@ -32,9 +32,8 @@ tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(90,7): error TS2322 Type 'undefined' is not assignable to type 'string'. tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(98,5): error TS2322: Type 'undefined' is not assignable to type '{ [key: string]: string; a: string; b: string; }[Key]'. Type 'undefined' is not assignable to type 'string'. -tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS2322: Type '{ [key: string]: string; a: string; b: string; }[Key]' is not assignable to type 'string'. - Type 'string | undefined' is not assignable to type 'string'. - Type 'undefined' is not assignable to type 'string'. +tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS2322: Type 'string | undefined' is not assignable to type 'string'. + Type 'undefined' is not assignable to type 'string'. ==== tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts (31 errors) ==== @@ -202,9 +201,8 @@ tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS232 !!! error TS2322: Type 'undefined' is not assignable to type 'string'. const v: string = myRecord2[key]; // Should error ~ -!!! error TS2322: Type '{ [key: string]: string; a: string; b: string; }[Key]' is not assignable to type 'string'. -!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'. -!!! error TS2322: Type 'undefined' is not assignable to type 'string'. +!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'. +!!! error TS2322: Type 'undefined' is not assignable to type 'string'. }; \ No newline at end of file diff --git a/tests/baselines/reference/noUncheckedIndexedAccess.types b/tests/baselines/reference/noUncheckedIndexedAccess.types index f6eb20a0e7bf8..9dc76ff0d887a 100644 --- a/tests/baselines/reference/noUncheckedIndexedAccess.types +++ b/tests/baselines/reference/noUncheckedIndexedAccess.types @@ -412,7 +412,7 @@ const fn3 = (key: Key) => { const v: string = myRecord2[key]; // Should error >v : string ->myRecord2[key] : { [key: string]: string; a: string; b: string; }[Key] +>myRecord2[key] : string | undefined >myRecord2 : { [key: string]: string; a: string; b: string; } >key : Key diff --git a/tests/baselines/reference/nonNullMappedType.types b/tests/baselines/reference/nonNullMappedType.types index ea2f40062490f..34acdc432a4a0 100644 --- a/tests/baselines/reference/nonNullMappedType.types +++ b/tests/baselines/reference/nonNullMappedType.types @@ -6,8 +6,8 @@ function f(p0: { [key in A]: {} | undefined }, p1: A) { const v: {} = p0[p1]!; >v : {} ->p0[p1]! : NonNullable<{ [key in A]: {} | undefined; }[A]> ->p0[p1] : { [key in A]: {} | undefined; }[A] +>p0[p1]! : {} +>p0[p1] : {} | undefined >p0 : { [key in A]: {} | undefined; } >p1 : A } diff --git a/tests/baselines/reference/nonNullParameterExtendingStringAssignableToString.types b/tests/baselines/reference/nonNullParameterExtendingStringAssignableToString.types index b5cac2410f060..5f9783c5f902f 100644 --- a/tests/baselines/reference/nonNullParameterExtendingStringAssignableToString.types +++ b/tests/baselines/reference/nonNullParameterExtendingStringAssignableToString.types @@ -19,8 +19,8 @@ function fn(one: T, two: U) { foo(one!); >foo(one!) : void >foo : (p: string) => void ->one! : NonNullable ->one : T +>one! : string +>one : string | undefined foo(two!); >foo(two!) : void @@ -31,6 +31,6 @@ function fn(one: T, two: U) { foo(three!); // this line is the important one >foo(three!) : void >foo : (p: string) => void ->three! : NonNullable | NonNullable ->three : T | U +>three! : string +>three : string | undefined } diff --git a/tests/baselines/reference/recursiveTypeRelations.types b/tests/baselines/reference/recursiveTypeRelations.types index df02f2668f492..1413169d70418 100644 --- a/tests/baselines/reference/recursiveTypeRelations.types +++ b/tests/baselines/reference/recursiveTypeRelations.types @@ -88,11 +88,11 @@ export function css(styles: S, ...classNam >key : keyof S obj[exportedClassName] = (arg as ClassNameMap)[key]; ->obj[exportedClassName] = (arg as ClassNameMap)[key] : ClassNameMap[keyof S] +>obj[exportedClassName] = (arg as ClassNameMap)[key] : boolean >obj[exportedClassName] : any >obj : any >exportedClassName : S[keyof S] ->(arg as ClassNameMap)[key] : ClassNameMap[keyof S] +>(arg as ClassNameMap)[key] : boolean >(arg as ClassNameMap) : ClassNameMap >arg as ClassNameMap : ClassNameMap >arg : ClassNameObjectMap diff --git a/tests/baselines/reference/restInvalidArgumentType.types b/tests/baselines/reference/restInvalidArgumentType.types index 0e44cdfe95ccb..119b0394726da 100644 --- a/tests/baselines/reference/restInvalidArgumentType.types +++ b/tests/baselines/reference/restInvalidArgumentType.types @@ -84,7 +84,7 @@ function f(p1: T, p2: T[]) { var {...r5} = k; // Error, index >r5 : any ->k : keyof T +>k : string | number | symbol var {...r6} = mapped_generic; // Error, generic mapped object type >r6 : { [P in keyof T]: T[P]; } diff --git a/tests/baselines/reference/switchWithConstrainedTypeVariable.types b/tests/baselines/reference/switchWithConstrainedTypeVariable.types index 50e37d9011076..4dc84310405e9 100644 --- a/tests/baselines/reference/switchWithConstrainedTypeVariable.types +++ b/tests/baselines/reference/switchWithConstrainedTypeVariable.types @@ -14,7 +14,7 @@ function function1(key: T) { key.toLowerCase(); >key.toLowerCase() : string >key.toLowerCase : () => string ->key : T +>key : "a" >toLowerCase : () => string break; @@ -22,7 +22,7 @@ function function1(key: T) { key.toLowerCase(); >key.toLowerCase() : string >key.toLowerCase : () => string ->key : T +>key : "b" >toLowerCase : () => string break; diff --git a/tests/baselines/reference/templateLiteralTypes1.types b/tests/baselines/reference/templateLiteralTypes1.types index 3ab499d448bd1..85ef5f05cd579 100644 --- a/tests/baselines/reference/templateLiteralTypes1.types +++ b/tests/baselines/reference/templateLiteralTypes1.types @@ -76,11 +76,11 @@ function test(name: `get${Capitalize}`) { let s1: string = name; >s1 : string ->name : `get${Capitalize}` +>name : "getFoo" | "getBar" let s2: 'getFoo' | 'getBar' = name; >s2 : "getFoo" | "getBar" ->name : `get${Capitalize}` +>name : "getFoo" | "getBar" } function fa1(x: T, y: { [P in keyof T]: T[P] }, z: { [P in keyof T & string as `p_${P}`]: T[P] }) { diff --git a/tests/baselines/reference/typeParameterExtendingUnion1.types b/tests/baselines/reference/typeParameterExtendingUnion1.types index e3eb4c14ca032..5cc8d010b78b9 100644 --- a/tests/baselines/reference/typeParameterExtendingUnion1.types +++ b/tests/baselines/reference/typeParameterExtendingUnion1.types @@ -31,11 +31,11 @@ function f(a: T) { a.run(); >a.run() : void >a.run : (() => void) | (() => void) ->a : T +>a : Cat | Dog >run : (() => void) | (() => void) run(a); >run(a) : void >run : (a: Animal) => void ->a : T +>a : Cat | Dog } diff --git a/tests/baselines/reference/typeParameterExtendingUnion2.types b/tests/baselines/reference/typeParameterExtendingUnion2.types index 074ac3b3fff21..475e418f4103a 100644 --- a/tests/baselines/reference/typeParameterExtendingUnion2.types +++ b/tests/baselines/reference/typeParameterExtendingUnion2.types @@ -31,11 +31,11 @@ function f(a: T) { a.run(); >a.run() : void >a.run : (() => void) | (() => void) ->a : T +>a : Cat | Dog >run : (() => void) | (() => void) run(a); >run(a) : void >run : (a: Cat | Dog) => void ->a : T +>a : Cat | Dog } diff --git a/tests/baselines/reference/unionWithIndexSignature.types b/tests/baselines/reference/unionWithIndexSignature.types index e3acdf8150589..60eba708cec19 100644 --- a/tests/baselines/reference/unionWithIndexSignature.types +++ b/tests/baselines/reference/unionWithIndexSignature.types @@ -51,11 +51,11 @@ export function flatten(arr: T) { if (isTypedArray(arr)) { >isTypedArray(arr) : boolean >isTypedArray : (a: {}) => a is Int32Array | Uint8Array ->arr : T +>arr : number | TypedArray arr[1]; >arr[1] : number ->arr : T & (Int32Array | Uint8Array) +>arr : TypedArray >1 : 1 } } diff --git a/tests/baselines/reference/voidReturnIndexUnionInference.types b/tests/baselines/reference/voidReturnIndexUnionInference.types index fa7d87b8f63af..23e7ef5c5c1fd 100644 --- a/tests/baselines/reference/voidReturnIndexUnionInference.types +++ b/tests/baselines/reference/voidReturnIndexUnionInference.types @@ -43,9 +43,9 @@ function bad

(props: Readonly

) { safeInvoke(props.onFoo, "blah"); >safeInvoke(props.onFoo, "blah") : boolean | undefined >safeInvoke : (func: ((arg1: A1) => R) | null | undefined, arg1: A1) => R | undefined ->props.onFoo : P["onFoo"] | undefined +>props.onFoo : ((value: string) => boolean) | undefined >props : Readonly

->onFoo : P["onFoo"] | undefined +>onFoo : ((value: string) => boolean) | undefined >"blah" : "blah" // ERROR HERE!!! @@ -53,9 +53,9 @@ function bad

(props: Readonly

) { safeInvoke(props.onBar, "blah"); >safeInvoke(props.onBar, "blah") : void | undefined >safeInvoke : (func: ((arg1: A1) => R) | null | undefined, arg1: A1) => R | undefined ->props.onBar : P["onBar"] | undefined +>props.onBar : ((value: string) => void) | undefined >props : Readonly

->onBar : P["onBar"] | undefined +>onBar : ((value: string) => void) | undefined >"blah" : "blah" } From a0fbf5cb084a50a78ae34ea9bec65a96465298ff Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Mar 2021 08:14:51 -0800 Subject: [PATCH 04/14] Add tests --- .../controlFlowGenericTypes.errors.txt | 114 ++++++++ .../reference/controlFlowGenericTypes.js | 170 +++++++++++ .../reference/controlFlowGenericTypes.symbols | 269 +++++++++++++++++ .../reference/controlFlowGenericTypes.types | 274 ++++++++++++++++++ .../controlFlow/controlFlowGenericTypes.ts | 99 +++++++ 5 files changed, 926 insertions(+) create mode 100644 tests/baselines/reference/controlFlowGenericTypes.errors.txt create mode 100644 tests/baselines/reference/controlFlowGenericTypes.js create mode 100644 tests/baselines/reference/controlFlowGenericTypes.symbols create mode 100644 tests/baselines/reference/controlFlowGenericTypes.types create mode 100644 tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts diff --git a/tests/baselines/reference/controlFlowGenericTypes.errors.txt b/tests/baselines/reference/controlFlowGenericTypes.errors.txt new file mode 100644 index 0000000000000..373d93b6fe0ad --- /dev/null +++ b/tests/baselines/reference/controlFlowGenericTypes.errors.txt @@ -0,0 +1,114 @@ +tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(49,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'. + Property 'foo' does not exist on type 'AA'. +tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(58,44): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. +tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(59,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'. + Property 'foo' does not exist on type 'AA'. + + +==== tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts (3 errors) ==== + function f1(x: T, y: { a: T }, z: [T]): string { + if (x) { + x; + x.length; + return x; + } + if (y.a) { + y.a.length; + return y.a; + } + if (z[0]) { + z[0].length; + return z[0]; + } + return "hello"; + } + + function f2(x: Extract | null): string { + if (x) { + x; + x.length; + return x; + } + return "hello"; + } + + // Repro from #13995 + + declare function takeA(val: 'A'): void; + export function bounceAndTakeIfA(value: AB): AB { + if (value === 'A') { + takeA(value); + return value; + } + else { + return value; + } + } + + // Repro from #13995 + + type Common = { id: number }; + type AA = { tag: 'A', id: number }; + type BB = { tag: 'B', id: number, foo: number }; + + type MyUnion = AA | BB; + + const fn = (value: MyUnion) => { + value.foo; // Error + ~~~ +!!! error TS2339: Property 'foo' does not exist on type 'MyUnion'. +!!! error TS2339: Property 'foo' does not exist on type 'AA'. + if ('foo' in value) { + value.foo; + } + if (value.tag === 'B') { + value.foo; + } + }; + + const fn2 = (value: T): MyUnion => { + ~~~~~~~ +!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. + value.foo; // Error + ~~~ +!!! error TS2339: Property 'foo' does not exist on type 'MyUnion'. +!!! error TS2339: Property 'foo' does not exist on type 'AA'. + if ('foo' in value) { + value.foo; + } + if (value.tag === 'B') { + value.foo; + } + }; + + // Repro from #13995 + + type A1 = { + testable: true + doTest: () => void + } + type B1 = { + testable: false + }; + + type Union = A1 | B1 + + function notWorking(object: T) { + if (!object.testable) return; + object.doTest(); + } + + // Repro from #42939 + + interface A { + a: number | null; + }; + + function get(key: K, obj: A): number { + const value = obj[key]; + if (value !== null) { + return value; + } + return 0; + }; + \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowGenericTypes.js b/tests/baselines/reference/controlFlowGenericTypes.js new file mode 100644 index 0000000000000..9b5da0b56eb62 --- /dev/null +++ b/tests/baselines/reference/controlFlowGenericTypes.js @@ -0,0 +1,170 @@ +//// [controlFlowGenericTypes.ts] +function f1(x: T, y: { a: T }, z: [T]): string { + if (x) { + x; + x.length; + return x; + } + if (y.a) { + y.a.length; + return y.a; + } + if (z[0]) { + z[0].length; + return z[0]; + } + return "hello"; +} + +function f2(x: Extract | null): string { + if (x) { + x; + x.length; + return x; + } + return "hello"; +} + +// Repro from #13995 + +declare function takeA(val: 'A'): void; +export function bounceAndTakeIfA(value: AB): AB { + if (value === 'A') { + takeA(value); + return value; + } + else { + return value; + } +} + +// Repro from #13995 + +type Common = { id: number }; +type AA = { tag: 'A', id: number }; +type BB = { tag: 'B', id: number, foo: number }; + +type MyUnion = AA | BB; + +const fn = (value: MyUnion) => { + value.foo; // Error + if ('foo' in value) { + value.foo; + } + if (value.tag === 'B') { + value.foo; + } +}; + +const fn2 = (value: T): MyUnion => { + value.foo; // Error + if ('foo' in value) { + value.foo; + } + if (value.tag === 'B') { + value.foo; + } +}; + +// Repro from #13995 + +type A1 = { + testable: true + doTest: () => void +} +type B1 = { + testable: false +}; + +type Union = A1 | B1 + +function notWorking(object: T) { + if (!object.testable) return; + object.doTest(); +} + +// Repro from #42939 + +interface A { + a: number | null; +}; + +function get(key: K, obj: A): number { + const value = obj[key]; + if (value !== null) { + return value; + } + return 0; +}; + + +//// [controlFlowGenericTypes.js] +"use strict"; +exports.__esModule = true; +exports.bounceAndTakeIfA = void 0; +function f1(x, y, z) { + if (x) { + x; + x.length; + return x; + } + if (y.a) { + y.a.length; + return y.a; + } + if (z[0]) { + z[0].length; + return z[0]; + } + return "hello"; +} +function f2(x) { + if (x) { + x; + x.length; + return x; + } + return "hello"; +} +function bounceAndTakeIfA(value) { + if (value === 'A') { + takeA(value); + return value; + } + else { + return value; + } +} +exports.bounceAndTakeIfA = bounceAndTakeIfA; +var fn = function (value) { + value.foo; // Error + if ('foo' in value) { + value.foo; + } + if (value.tag === 'B') { + value.foo; + } +}; +var fn2 = function (value) { + value.foo; // Error + if ('foo' in value) { + value.foo; + } + if (value.tag === 'B') { + value.foo; + } +}; +function notWorking(object) { + if (!object.testable) + return; + object.doTest(); +} +; +function get(key, obj) { + var value = obj[key]; + if (value !== null) { + return value; + } + return 0; +} +; diff --git a/tests/baselines/reference/controlFlowGenericTypes.symbols b/tests/baselines/reference/controlFlowGenericTypes.symbols new file mode 100644 index 0000000000000..cdd67096a6b72 --- /dev/null +++ b/tests/baselines/reference/controlFlowGenericTypes.symbols @@ -0,0 +1,269 @@ +=== tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts === +function f1(x: T, y: { a: T }, z: [T]): string { +>f1 : Symbol(f1, Decl(controlFlowGenericTypes.ts, 0, 0)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 0, 12)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 0, 42)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 0, 12)) +>y : Symbol(y, Decl(controlFlowGenericTypes.ts, 0, 47)) +>a : Symbol(a, Decl(controlFlowGenericTypes.ts, 0, 52)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 0, 12)) +>z : Symbol(z, Decl(controlFlowGenericTypes.ts, 0, 60)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 0, 12)) + + if (x) { +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 0, 42)) + + x; +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 0, 42)) + + x.length; +>x.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 0, 42)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + + return x; +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 0, 42)) + } + if (y.a) { +>y.a : Symbol(a, Decl(controlFlowGenericTypes.ts, 0, 52)) +>y : Symbol(y, Decl(controlFlowGenericTypes.ts, 0, 47)) +>a : Symbol(a, Decl(controlFlowGenericTypes.ts, 0, 52)) + + y.a.length; +>y.a.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>y.a : Symbol(a, Decl(controlFlowGenericTypes.ts, 0, 52)) +>y : Symbol(y, Decl(controlFlowGenericTypes.ts, 0, 47)) +>a : Symbol(a, Decl(controlFlowGenericTypes.ts, 0, 52)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + + return y.a; +>y.a : Symbol(a, Decl(controlFlowGenericTypes.ts, 0, 52)) +>y : Symbol(y, Decl(controlFlowGenericTypes.ts, 0, 47)) +>a : Symbol(a, Decl(controlFlowGenericTypes.ts, 0, 52)) + } + if (z[0]) { +>z : Symbol(z, Decl(controlFlowGenericTypes.ts, 0, 60)) +>0 : Symbol(0) + + z[0].length; +>z[0].length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>z : Symbol(z, Decl(controlFlowGenericTypes.ts, 0, 60)) +>0 : Symbol(0) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + + return z[0]; +>z : Symbol(z, Decl(controlFlowGenericTypes.ts, 0, 60)) +>0 : Symbol(0) + } + return "hello"; +} + +function f2(x: Extract | null): string { +>f2 : Symbol(f2, Decl(controlFlowGenericTypes.ts, 15, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 17, 12)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 17, 15)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 17, 12)) + + if (x) { +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 17, 15)) + + x; +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 17, 15)) + + x.length; +>x.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 17, 15)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + + return x; +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 17, 15)) + } + return "hello"; +} + +// Repro from #13995 + +declare function takeA(val: 'A'): void; +>takeA : Symbol(takeA, Decl(controlFlowGenericTypes.ts, 24, 1)) +>val : Symbol(val, Decl(controlFlowGenericTypes.ts, 28, 23)) + +export function bounceAndTakeIfA(value: AB): AB { +>bounceAndTakeIfA : Symbol(bounceAndTakeIfA, Decl(controlFlowGenericTypes.ts, 28, 39)) +>AB : Symbol(AB, Decl(controlFlowGenericTypes.ts, 29, 33)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 29, 55)) +>AB : Symbol(AB, Decl(controlFlowGenericTypes.ts, 29, 33)) +>AB : Symbol(AB, Decl(controlFlowGenericTypes.ts, 29, 33)) + + if (value === 'A') { +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 29, 55)) + + takeA(value); +>takeA : Symbol(takeA, Decl(controlFlowGenericTypes.ts, 24, 1)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 29, 55)) + + return value; +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 29, 55)) + } + else { + return value; +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 29, 55)) + } +} + +// Repro from #13995 + +type Common = { id: number }; +>Common : Symbol(Common, Decl(controlFlowGenericTypes.ts, 37, 1)) +>id : Symbol(id, Decl(controlFlowGenericTypes.ts, 41, 15)) + +type AA = { tag: 'A', id: number }; +>AA : Symbol(AA, Decl(controlFlowGenericTypes.ts, 41, 29)) +>tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 42, 11)) +>id : Symbol(id, Decl(controlFlowGenericTypes.ts, 42, 21)) + +type BB = { tag: 'B', id: number, foo: number }; +>BB : Symbol(BB, Decl(controlFlowGenericTypes.ts, 42, 35)) +>tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 43, 11)) +>id : Symbol(id, Decl(controlFlowGenericTypes.ts, 43, 21)) +>foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) + +type MyUnion = AA | BB; +>MyUnion : Symbol(MyUnion, Decl(controlFlowGenericTypes.ts, 43, 48)) +>AA : Symbol(AA, Decl(controlFlowGenericTypes.ts, 41, 29)) +>BB : Symbol(BB, Decl(controlFlowGenericTypes.ts, 42, 35)) + +const fn = (value: MyUnion) => { +>fn : Symbol(fn, Decl(controlFlowGenericTypes.ts, 47, 5)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 47, 12)) +>MyUnion : Symbol(MyUnion, Decl(controlFlowGenericTypes.ts, 43, 48)) + + value.foo; // Error +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 47, 12)) + + if ('foo' in value) { +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 47, 12)) + + value.foo; +>value.foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 47, 12)) +>foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) + } + if (value.tag === 'B') { +>value.tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 42, 11), Decl(controlFlowGenericTypes.ts, 43, 11)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 47, 12)) +>tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 42, 11), Decl(controlFlowGenericTypes.ts, 43, 11)) + + value.foo; +>value.foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 47, 12)) +>foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) + } +}; + +const fn2 = (value: T): MyUnion => { +>fn2 : Symbol(fn2, Decl(controlFlowGenericTypes.ts, 57, 5)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 57, 13)) +>MyUnion : Symbol(MyUnion, Decl(controlFlowGenericTypes.ts, 43, 48)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 57, 32)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 57, 13)) +>MyUnion : Symbol(MyUnion, Decl(controlFlowGenericTypes.ts, 43, 48)) + + value.foo; // Error +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 57, 32)) + + if ('foo' in value) { +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 57, 32)) + + value.foo; +>value.foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 57, 32)) +>foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) + } + if (value.tag === 'B') { +>value.tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 42, 11), Decl(controlFlowGenericTypes.ts, 43, 11)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 57, 32)) +>tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 42, 11), Decl(controlFlowGenericTypes.ts, 43, 11)) + + value.foo; +>value.foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 57, 32)) +>foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) + } +}; + +// Repro from #13995 + +type A1 = { +>A1 : Symbol(A1, Decl(controlFlowGenericTypes.ts, 65, 2)) + + testable: true +>testable : Symbol(testable, Decl(controlFlowGenericTypes.ts, 69, 11)) + + doTest: () => void +>doTest : Symbol(doTest, Decl(controlFlowGenericTypes.ts, 70, 18)) +} +type B1 = { +>B1 : Symbol(B1, Decl(controlFlowGenericTypes.ts, 72, 1)) + + testable: false +>testable : Symbol(testable, Decl(controlFlowGenericTypes.ts, 73, 11)) + +}; + +type Union = A1 | B1 +>Union : Symbol(Union, Decl(controlFlowGenericTypes.ts, 75, 2)) +>A1 : Symbol(A1, Decl(controlFlowGenericTypes.ts, 65, 2)) +>B1 : Symbol(B1, Decl(controlFlowGenericTypes.ts, 72, 1)) + +function notWorking(object: T) { +>notWorking : Symbol(notWorking, Decl(controlFlowGenericTypes.ts, 77, 20)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 79, 20)) +>Union : Symbol(Union, Decl(controlFlowGenericTypes.ts, 75, 2)) +>object : Symbol(object, Decl(controlFlowGenericTypes.ts, 79, 37)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 79, 20)) + + if (!object.testable) return; +>object.testable : Symbol(testable, Decl(controlFlowGenericTypes.ts, 69, 11), Decl(controlFlowGenericTypes.ts, 73, 11)) +>object : Symbol(object, Decl(controlFlowGenericTypes.ts, 79, 37)) +>testable : Symbol(testable, Decl(controlFlowGenericTypes.ts, 69, 11), Decl(controlFlowGenericTypes.ts, 73, 11)) + + object.doTest(); +>object.doTest : Symbol(doTest, Decl(controlFlowGenericTypes.ts, 70, 18)) +>object : Symbol(object, Decl(controlFlowGenericTypes.ts, 79, 37)) +>doTest : Symbol(doTest, Decl(controlFlowGenericTypes.ts, 70, 18)) +} + +// Repro from #42939 + +interface A { +>A : Symbol(A, Decl(controlFlowGenericTypes.ts, 82, 1)) + + a: number | null; +>a : Symbol(A.a, Decl(controlFlowGenericTypes.ts, 86, 13)) + +}; + +function get(key: K, obj: A): number { +>get : Symbol(get, Decl(controlFlowGenericTypes.ts, 88, 2)) +>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 90, 13)) +>A : Symbol(A, Decl(controlFlowGenericTypes.ts, 82, 1)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 90, 32)) +>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 90, 13)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 90, 39)) +>A : Symbol(A, Decl(controlFlowGenericTypes.ts, 82, 1)) + + const value = obj[key]; +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 91, 9)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 90, 39)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 90, 32)) + + if (value !== null) { +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 91, 9)) + + return value; +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 91, 9)) + } + return 0; +}; + diff --git a/tests/baselines/reference/controlFlowGenericTypes.types b/tests/baselines/reference/controlFlowGenericTypes.types new file mode 100644 index 0000000000000..56171ffb1e2ac --- /dev/null +++ b/tests/baselines/reference/controlFlowGenericTypes.types @@ -0,0 +1,274 @@ +=== tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts === +function f1(x: T, y: { a: T }, z: [T]): string { +>f1 : (x: T, y: { a: T;}, z: [T]) => string +>x : T +>y : { a: T; } +>a : T +>z : [T] + + if (x) { +>x : T + + x; +>x : T + + x.length; +>x.length : number +>x : string +>length : number + + return x; +>x : string + } + if (y.a) { +>y.a : T +>y : { a: T; } +>a : T + + y.a.length; +>y.a.length : number +>y.a : string +>y : { a: T; } +>a : string +>length : number + + return y.a; +>y.a : string +>y : { a: T; } +>a : string + } + if (z[0]) { +>z[0] : T +>z : [T] +>0 : 0 + + z[0].length; +>z[0].length : number +>z[0] : string +>z : [T] +>0 : 0 +>length : number + + return z[0]; +>z[0] : string +>z : [T] +>0 : 0 + } + return "hello"; +>"hello" : "hello" +} + +function f2(x: Extract | null): string { +>f2 : (x: Extract | null) => string +>x : Extract | null +>null : null + + if (x) { +>x : Extract | null + + x; +>x : Extract + + x.length; +>x.length : number +>x : string +>length : number + + return x; +>x : string + } + return "hello"; +>"hello" : "hello" +} + +// Repro from #13995 + +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" + + return value; +>value : AB + } + else { + return value; +>value : AB + } +} + +// Repro from #13995 + +type Common = { id: number }; +>Common : Common +>id : number + +type AA = { tag: 'A', id: number }; +>AA : AA +>tag : "A" +>id : number + +type BB = { tag: 'B', id: number, foo: number }; +>BB : BB +>tag : "B" +>id : number +>foo : number + +type MyUnion = AA | BB; +>MyUnion : MyUnion + +const fn = (value: MyUnion) => { +>fn : (value: MyUnion) => void +>(value: MyUnion) => { value.foo; // Error if ('foo' in value) { value.foo; } if (value.tag === 'B') { value.foo; }} : (value: MyUnion) => void +>value : MyUnion + + value.foo; // Error +>value.foo : any +>value : MyUnion +>foo : any + + if ('foo' in value) { +>'foo' in value : boolean +>'foo' : "foo" +>value : MyUnion + + value.foo; +>value.foo : number +>value : BB +>foo : number + } + if (value.tag === 'B') { +>value.tag === 'B' : boolean +>value.tag : "A" | "B" +>value : MyUnion +>tag : "A" | "B" +>'B' : "B" + + value.foo; +>value.foo : number +>value : BB +>foo : number + } +}; + +const fn2 = (value: T): MyUnion => { +>fn2 : (value: T) => MyUnion +>(value: T): MyUnion => { value.foo; // Error if ('foo' in value) { value.foo; } if (value.tag === 'B') { value.foo; }} : (value: T) => MyUnion +>value : T + + value.foo; // Error +>value.foo : any +>value : MyUnion +>foo : any + + if ('foo' in value) { +>'foo' in value : boolean +>'foo' : "foo" +>value : T + + value.foo; +>value.foo : number +>value : BB +>foo : number + } + if (value.tag === 'B') { +>value.tag === 'B' : boolean +>value.tag : "A" | "B" +>value : MyUnion +>tag : "A" | "B" +>'B' : "B" + + value.foo; +>value.foo : number +>value : BB +>foo : number + } +}; + +// Repro from #13995 + +type A1 = { +>A1 : A1 + + testable: true +>testable : true +>true : true + + doTest: () => void +>doTest : () => void +} +type B1 = { +>B1 : B1 + + testable: false +>testable : false +>false : false + +}; + +type Union = A1 | B1 +>Union : Union + +function notWorking(object: T) { +>notWorking : (object: T) => void +>object : T + + if (!object.testable) return; +>!object.testable : boolean +>object.testable : boolean +>object : Union +>testable : boolean + + object.doTest(); +>object.doTest() : void +>object.doTest : () => void +>object : A1 +>doTest : () => void +} + +// Repro from #42939 + +interface A { + a: number | null; +>a : number | null +>null : null + +}; + +function get(key: K, obj: A): number { +>get : (key: K, obj: A) => number +>key : K +>obj : A + + const value = obj[key]; +>value : A[K] +>obj[key] : A[K] +>obj : A +>key : K + + if (value !== null) { +>value !== null : boolean +>value : A[K] +>null : null + + return value; +>value : number + } + return 0; +>0 : 0 + +}; + diff --git a/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts b/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts new file mode 100644 index 0000000000000..78441f0df860c --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts @@ -0,0 +1,99 @@ +// @strict: true + +function f1(x: T, y: { a: T }, z: [T]): string { + if (x) { + x; + x.length; + return x; + } + if (y.a) { + y.a.length; + return y.a; + } + if (z[0]) { + z[0].length; + return z[0]; + } + return "hello"; +} + +function f2(x: Extract | null): string { + if (x) { + x; + x.length; + return x; + } + return "hello"; +} + +// Repro from #13995 + +declare function takeA(val: 'A'): void; +export function bounceAndTakeIfA(value: AB): AB { + if (value === 'A') { + takeA(value); + return value; + } + else { + return value; + } +} + +// Repro from #13995 + +type Common = { id: number }; +type AA = { tag: 'A', id: number }; +type BB = { tag: 'B', id: number, foo: number }; + +type MyUnion = AA | BB; + +const fn = (value: MyUnion) => { + value.foo; // Error + if ('foo' in value) { + value.foo; + } + if (value.tag === 'B') { + value.foo; + } +}; + +const fn2 = (value: T): MyUnion => { + value.foo; // Error + if ('foo' in value) { + value.foo; + } + if (value.tag === 'B') { + value.foo; + } +}; + +// Repro from #13995 + +type A1 = { + testable: true + doTest: () => void +} +type B1 = { + testable: false +}; + +type Union = A1 | B1 + +function notWorking(object: T) { + if (!object.testable) return; + object.doTest(); +} + +// Repro from #42939 + +interface A { + a: number | null; +}; + +function get(key: K, obj: A): number { + const value = obj[key]; + if (value !== null) { + return value; + } + return 0; +}; From 1a695e96cfb47cebc95a41ddf65a666a0e25a72f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Mar 2021 14:05:39 -0800 Subject: [PATCH 05/14] Fix circularity for JSX elements --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1165472fb8ad9..e9558cfc95cbe 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23582,7 +23582,7 @@ namespace ts { } function hasContextualTypeWithNoTypeVariables(node: Expression) { - const contextualType = getContextualType(node); + const contextualType = !((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) && getContextualType(node); return contextualType && !someType(contextualType, containsTypeVariable); } From a05ff0b713104bd3fd85d09787be667166a46131 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Mar 2021 16:23:21 -0800 Subject: [PATCH 06/14] Remove unnecessary isConstraintPosition information from flow cache key --- src/compiler/checker.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e9558cfc95cbe..61674f7ecaaaa 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21398,14 +21398,12 @@ namespace ts { // Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers // separated by dots). The key consists of the id of the symbol referenced by the // leftmost identifier followed by zero or more property names separated by dots. - // The result is undefined if the reference isn't a dotted name. We prefix nodes - // occurring in an apparent type position with '@' because the control flow type - // of such nodes may be based on the apparent type instead of the declared type. + // The result is undefined if the reference isn't a dotted name. function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined { switch (node.kind) { case SyntaxKind.Identifier: const symbol = getResolvedSymbol(node); - return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${isConstraintPosition(node) ? "@" : ""}${getSymbolId(symbol)}` : undefined; + return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${getSymbolId(symbol)}` : undefined; case SyntaxKind.ThisKeyword: return `0|${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}`; case SyntaxKind.NonNullExpression: From 0e7672023abfbfbfe7819bad32f86aecab54068c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Mar 2021 16:30:53 -0800 Subject: [PATCH 07/14] Update comment --- src/compiler/checker.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 61674f7ecaaaa..65ed815483da1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23587,11 +23587,11 @@ namespace ts { function getConstraintForReference(type: Type, reference: Identifier | ElementAccessExpression | PropertyAccessExpression | QualifiedName, checkMode: CheckMode | undefined) { // When the type of a reference is or contains an instantiable type with a union type constraint, and // when the reference is in a constraint position (where it is known we'll obtain the apparent type) or - // has a contextual type containing no instantiables (meaning constraints will determine assignability), - // we substitute constraints for all instantiables in the type of the reference to give control flow - // analysis an opportunity to narrow it further. For example, for a reference of a type parameter type - // 'T extends string | undefined' with a contextual type 'string', we substitute 'string | undefined' - // to give control flow analysis the opportunity to narrow to type 'string'. + // has a contextual type containing no top-level instantiables (meaning constraints will determine + // assignability), we substitute constraints for all instantiables in the type of the reference to give + // control flow analysis an opportunity to narrow it further. For example, for a reference of a type + // parameter type 'T extends string | undefined' with a contextual type 'string', we substitute + // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'. const substituteConstraints = reference.kind !== SyntaxKind.QualifiedName && !(checkMode && checkMode & CheckMode.Inferential) && someType(type, isTypeVariableWithUnionConstraint) && From 1f720d379bdf027ce87ef4fd14262bb7be6a518c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 10 Mar 2021 16:31:02 -0800 Subject: [PATCH 08/14] Add additional tests --- .../controlFlowGenericTypes.errors.txt | 46 ++- .../reference/controlFlowGenericTypes.js | 52 ++++ .../reference/controlFlowGenericTypes.symbols | 276 ++++++++++++------ .../reference/controlFlowGenericTypes.types | 83 ++++++ .../controlFlow/controlFlowGenericTypes.ts | 32 ++ 5 files changed, 397 insertions(+), 92 deletions(-) diff --git a/tests/baselines/reference/controlFlowGenericTypes.errors.txt b/tests/baselines/reference/controlFlowGenericTypes.errors.txt index 373d93b6fe0ad..09eb8c96198cb 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.errors.txt +++ b/tests/baselines/reference/controlFlowGenericTypes.errors.txt @@ -1,11 +1,13 @@ -tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(49,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'. +tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(49,15): error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box'. +tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(55,15): error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box'. +tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(81,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'. Property 'foo' does not exist on type 'AA'. -tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(58,44): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. -tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(59,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'. +tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(90,44): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. +tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(91,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'. Property 'foo' does not exist on type 'AA'. -==== tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts (3 errors) ==== +==== tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts (5 errors) ==== function f1(x: T, y: { a: T }, z: [T]): string { if (x) { x; @@ -32,6 +34,42 @@ tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(59,11): error TS2 return "hello"; } + interface Box { + item: T; + } + + declare function isBox(x: any): x is Box; + declare function isUndefined(x: unknown): x is undefined; + declare function unbox(x: Box): T; + + function g1 | undefined>(x: T) { + if (isBox(x)) { + unbox(x); + } + } + + function g2 | undefined>(x: T) { + if (!isUndefined(x)) { + unbox(x); + } + } + + function g3 | undefined>(x: T) { + if (!isBox(x)) { + unbox(x); // Error + ~ +!!! error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box'. + } + } + + function g4 | undefined>(x: T) { + if (isUndefined(x)) { + unbox(x); // Error + ~ +!!! error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box'. + } + } + // Repro from #13995 declare function takeA(val: 'A'): void; diff --git a/tests/baselines/reference/controlFlowGenericTypes.js b/tests/baselines/reference/controlFlowGenericTypes.js index 9b5da0b56eb62..8372b15dae476 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.js +++ b/tests/baselines/reference/controlFlowGenericTypes.js @@ -25,6 +25,38 @@ function f2(x: Extract | null): string { return "hello"; } +interface Box { + item: T; +} + +declare function isBox(x: any): x is Box; +declare function isUndefined(x: unknown): x is undefined; +declare function unbox(x: Box): T; + +function g1 | undefined>(x: T) { + if (isBox(x)) { + unbox(x); + } +} + +function g2 | undefined>(x: T) { + if (!isUndefined(x)) { + unbox(x); + } +} + +function g3 | undefined>(x: T) { + if (!isBox(x)) { + unbox(x); // Error + } +} + +function g4 | undefined>(x: T) { + if (isUndefined(x)) { + unbox(x); // Error + } +} + // Repro from #13995 declare function takeA(val: 'A'): void; @@ -126,6 +158,26 @@ function f2(x) { } return "hello"; } +function g1(x) { + if (isBox(x)) { + unbox(x); + } +} +function g2(x) { + if (!isUndefined(x)) { + unbox(x); + } +} +function g3(x) { + if (!isBox(x)) { + unbox(x); // Error + } +} +function g4(x) { + if (isUndefined(x)) { + unbox(x); // Error + } +} function bounceAndTakeIfA(value) { if (value === 'A') { takeA(value); diff --git a/tests/baselines/reference/controlFlowGenericTypes.symbols b/tests/baselines/reference/controlFlowGenericTypes.symbols index cdd67096a6b72..8c689bd468ac5 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.symbols +++ b/tests/baselines/reference/controlFlowGenericTypes.symbols @@ -82,187 +82,287 @@ function f2(x: Extract | null): string { return "hello"; } +interface Box { +>Box : Symbol(Box, Decl(controlFlowGenericTypes.ts, 24, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 26, 14)) + + item: T; +>item : Symbol(Box.item, Decl(controlFlowGenericTypes.ts, 26, 18)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 26, 14)) +} + +declare function isBox(x: any): x is Box; +>isBox : Symbol(isBox, Decl(controlFlowGenericTypes.ts, 28, 1)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 30, 23)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 30, 23)) +>Box : Symbol(Box, Decl(controlFlowGenericTypes.ts, 24, 1)) + +declare function isUndefined(x: unknown): x is undefined; +>isUndefined : Symbol(isUndefined, Decl(controlFlowGenericTypes.ts, 30, 50)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 31, 29)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 31, 29)) + +declare function unbox(x: Box): T; +>unbox : Symbol(unbox, Decl(controlFlowGenericTypes.ts, 31, 57)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 32, 23)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 32, 26)) +>Box : Symbol(Box, Decl(controlFlowGenericTypes.ts, 24, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 32, 23)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 32, 23)) + +function g1 | undefined>(x: T) { +>g1 : Symbol(g1, Decl(controlFlowGenericTypes.ts, 32, 40)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 34, 12)) +>Box : Symbol(Box, Decl(controlFlowGenericTypes.ts, 24, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 34, 12)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 34, 42)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 34, 12)) + + if (isBox(x)) { +>isBox : Symbol(isBox, Decl(controlFlowGenericTypes.ts, 28, 1)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 34, 42)) + + unbox(x); +>unbox : Symbol(unbox, Decl(controlFlowGenericTypes.ts, 31, 57)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 34, 42)) + } +} + +function g2 | undefined>(x: T) { +>g2 : Symbol(g2, Decl(controlFlowGenericTypes.ts, 38, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 40, 12)) +>Box : Symbol(Box, Decl(controlFlowGenericTypes.ts, 24, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 40, 12)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 40, 42)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 40, 12)) + + if (!isUndefined(x)) { +>isUndefined : Symbol(isUndefined, Decl(controlFlowGenericTypes.ts, 30, 50)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 40, 42)) + + unbox(x); +>unbox : Symbol(unbox, Decl(controlFlowGenericTypes.ts, 31, 57)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 40, 42)) + } +} + +function g3 | undefined>(x: T) { +>g3 : Symbol(g3, Decl(controlFlowGenericTypes.ts, 44, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 46, 12)) +>Box : Symbol(Box, Decl(controlFlowGenericTypes.ts, 24, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 46, 12)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 46, 42)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 46, 12)) + + if (!isBox(x)) { +>isBox : Symbol(isBox, Decl(controlFlowGenericTypes.ts, 28, 1)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 46, 42)) + + unbox(x); // Error +>unbox : Symbol(unbox, Decl(controlFlowGenericTypes.ts, 31, 57)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 46, 42)) + } +} + +function g4 | undefined>(x: T) { +>g4 : Symbol(g4, Decl(controlFlowGenericTypes.ts, 50, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 52, 12)) +>Box : Symbol(Box, Decl(controlFlowGenericTypes.ts, 24, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 52, 12)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 52, 42)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 52, 12)) + + if (isUndefined(x)) { +>isUndefined : Symbol(isUndefined, Decl(controlFlowGenericTypes.ts, 30, 50)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 52, 42)) + + unbox(x); // Error +>unbox : Symbol(unbox, Decl(controlFlowGenericTypes.ts, 31, 57)) +>x : Symbol(x, Decl(controlFlowGenericTypes.ts, 52, 42)) + } +} + // Repro from #13995 declare function takeA(val: 'A'): void; ->takeA : Symbol(takeA, Decl(controlFlowGenericTypes.ts, 24, 1)) ->val : Symbol(val, Decl(controlFlowGenericTypes.ts, 28, 23)) +>takeA : Symbol(takeA, Decl(controlFlowGenericTypes.ts, 56, 1)) +>val : Symbol(val, Decl(controlFlowGenericTypes.ts, 60, 23)) export function bounceAndTakeIfA(value: AB): AB { ->bounceAndTakeIfA : Symbol(bounceAndTakeIfA, Decl(controlFlowGenericTypes.ts, 28, 39)) ->AB : Symbol(AB, Decl(controlFlowGenericTypes.ts, 29, 33)) ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 29, 55)) ->AB : Symbol(AB, Decl(controlFlowGenericTypes.ts, 29, 33)) ->AB : Symbol(AB, Decl(controlFlowGenericTypes.ts, 29, 33)) +>bounceAndTakeIfA : Symbol(bounceAndTakeIfA, Decl(controlFlowGenericTypes.ts, 60, 39)) +>AB : Symbol(AB, Decl(controlFlowGenericTypes.ts, 61, 33)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 61, 55)) +>AB : Symbol(AB, Decl(controlFlowGenericTypes.ts, 61, 33)) +>AB : Symbol(AB, Decl(controlFlowGenericTypes.ts, 61, 33)) if (value === 'A') { ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 29, 55)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 61, 55)) takeA(value); ->takeA : Symbol(takeA, Decl(controlFlowGenericTypes.ts, 24, 1)) ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 29, 55)) +>takeA : Symbol(takeA, Decl(controlFlowGenericTypes.ts, 56, 1)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 61, 55)) return value; ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 29, 55)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 61, 55)) } else { return value; ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 29, 55)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 61, 55)) } } // Repro from #13995 type Common = { id: number }; ->Common : Symbol(Common, Decl(controlFlowGenericTypes.ts, 37, 1)) ->id : Symbol(id, Decl(controlFlowGenericTypes.ts, 41, 15)) +>Common : Symbol(Common, Decl(controlFlowGenericTypes.ts, 69, 1)) +>id : Symbol(id, Decl(controlFlowGenericTypes.ts, 73, 15)) type AA = { tag: 'A', id: number }; ->AA : Symbol(AA, Decl(controlFlowGenericTypes.ts, 41, 29)) ->tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 42, 11)) ->id : Symbol(id, Decl(controlFlowGenericTypes.ts, 42, 21)) +>AA : Symbol(AA, Decl(controlFlowGenericTypes.ts, 73, 29)) +>tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 74, 11)) +>id : Symbol(id, Decl(controlFlowGenericTypes.ts, 74, 21)) type BB = { tag: 'B', id: number, foo: number }; ->BB : Symbol(BB, Decl(controlFlowGenericTypes.ts, 42, 35)) ->tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 43, 11)) ->id : Symbol(id, Decl(controlFlowGenericTypes.ts, 43, 21)) ->foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) +>BB : Symbol(BB, Decl(controlFlowGenericTypes.ts, 74, 35)) +>tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 75, 11)) +>id : Symbol(id, Decl(controlFlowGenericTypes.ts, 75, 21)) +>foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 75, 33)) type MyUnion = AA | BB; ->MyUnion : Symbol(MyUnion, Decl(controlFlowGenericTypes.ts, 43, 48)) ->AA : Symbol(AA, Decl(controlFlowGenericTypes.ts, 41, 29)) ->BB : Symbol(BB, Decl(controlFlowGenericTypes.ts, 42, 35)) +>MyUnion : Symbol(MyUnion, Decl(controlFlowGenericTypes.ts, 75, 48)) +>AA : Symbol(AA, Decl(controlFlowGenericTypes.ts, 73, 29)) +>BB : Symbol(BB, Decl(controlFlowGenericTypes.ts, 74, 35)) const fn = (value: MyUnion) => { ->fn : Symbol(fn, Decl(controlFlowGenericTypes.ts, 47, 5)) ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 47, 12)) ->MyUnion : Symbol(MyUnion, Decl(controlFlowGenericTypes.ts, 43, 48)) +>fn : Symbol(fn, Decl(controlFlowGenericTypes.ts, 79, 5)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 79, 12)) +>MyUnion : Symbol(MyUnion, Decl(controlFlowGenericTypes.ts, 75, 48)) value.foo; // Error ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 47, 12)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 79, 12)) if ('foo' in value) { ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 47, 12)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 79, 12)) value.foo; ->value.foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 47, 12)) ->foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) +>value.foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 75, 33)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 79, 12)) +>foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 75, 33)) } if (value.tag === 'B') { ->value.tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 42, 11), Decl(controlFlowGenericTypes.ts, 43, 11)) ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 47, 12)) ->tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 42, 11), Decl(controlFlowGenericTypes.ts, 43, 11)) +>value.tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 74, 11), Decl(controlFlowGenericTypes.ts, 75, 11)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 79, 12)) +>tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 74, 11), Decl(controlFlowGenericTypes.ts, 75, 11)) value.foo; ->value.foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 47, 12)) ->foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) +>value.foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 75, 33)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 79, 12)) +>foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 75, 33)) } }; const fn2 = (value: T): MyUnion => { ->fn2 : Symbol(fn2, Decl(controlFlowGenericTypes.ts, 57, 5)) ->T : Symbol(T, Decl(controlFlowGenericTypes.ts, 57, 13)) ->MyUnion : Symbol(MyUnion, Decl(controlFlowGenericTypes.ts, 43, 48)) ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 57, 32)) ->T : Symbol(T, Decl(controlFlowGenericTypes.ts, 57, 13)) ->MyUnion : Symbol(MyUnion, Decl(controlFlowGenericTypes.ts, 43, 48)) +>fn2 : Symbol(fn2, Decl(controlFlowGenericTypes.ts, 89, 5)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 89, 13)) +>MyUnion : Symbol(MyUnion, Decl(controlFlowGenericTypes.ts, 75, 48)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 89, 32)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 89, 13)) +>MyUnion : Symbol(MyUnion, Decl(controlFlowGenericTypes.ts, 75, 48)) value.foo; // Error ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 57, 32)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 89, 32)) if ('foo' in value) { ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 57, 32)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 89, 32)) value.foo; ->value.foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 57, 32)) ->foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) +>value.foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 75, 33)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 89, 32)) +>foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 75, 33)) } if (value.tag === 'B') { ->value.tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 42, 11), Decl(controlFlowGenericTypes.ts, 43, 11)) ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 57, 32)) ->tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 42, 11), Decl(controlFlowGenericTypes.ts, 43, 11)) +>value.tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 74, 11), Decl(controlFlowGenericTypes.ts, 75, 11)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 89, 32)) +>tag : Symbol(tag, Decl(controlFlowGenericTypes.ts, 74, 11), Decl(controlFlowGenericTypes.ts, 75, 11)) value.foo; ->value.foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 57, 32)) ->foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 43, 33)) +>value.foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 75, 33)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 89, 32)) +>foo : Symbol(foo, Decl(controlFlowGenericTypes.ts, 75, 33)) } }; // Repro from #13995 type A1 = { ->A1 : Symbol(A1, Decl(controlFlowGenericTypes.ts, 65, 2)) +>A1 : Symbol(A1, Decl(controlFlowGenericTypes.ts, 97, 2)) testable: true ->testable : Symbol(testable, Decl(controlFlowGenericTypes.ts, 69, 11)) +>testable : Symbol(testable, Decl(controlFlowGenericTypes.ts, 101, 11)) doTest: () => void ->doTest : Symbol(doTest, Decl(controlFlowGenericTypes.ts, 70, 18)) +>doTest : Symbol(doTest, Decl(controlFlowGenericTypes.ts, 102, 18)) } type B1 = { ->B1 : Symbol(B1, Decl(controlFlowGenericTypes.ts, 72, 1)) +>B1 : Symbol(B1, Decl(controlFlowGenericTypes.ts, 104, 1)) testable: false ->testable : Symbol(testable, Decl(controlFlowGenericTypes.ts, 73, 11)) +>testable : Symbol(testable, Decl(controlFlowGenericTypes.ts, 105, 11)) }; type Union = A1 | B1 ->Union : Symbol(Union, Decl(controlFlowGenericTypes.ts, 75, 2)) ->A1 : Symbol(A1, Decl(controlFlowGenericTypes.ts, 65, 2)) ->B1 : Symbol(B1, Decl(controlFlowGenericTypes.ts, 72, 1)) +>Union : Symbol(Union, Decl(controlFlowGenericTypes.ts, 107, 2)) +>A1 : Symbol(A1, Decl(controlFlowGenericTypes.ts, 97, 2)) +>B1 : Symbol(B1, Decl(controlFlowGenericTypes.ts, 104, 1)) function notWorking(object: T) { ->notWorking : Symbol(notWorking, Decl(controlFlowGenericTypes.ts, 77, 20)) ->T : Symbol(T, Decl(controlFlowGenericTypes.ts, 79, 20)) ->Union : Symbol(Union, Decl(controlFlowGenericTypes.ts, 75, 2)) ->object : Symbol(object, Decl(controlFlowGenericTypes.ts, 79, 37)) ->T : Symbol(T, Decl(controlFlowGenericTypes.ts, 79, 20)) +>notWorking : Symbol(notWorking, Decl(controlFlowGenericTypes.ts, 109, 20)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 111, 20)) +>Union : Symbol(Union, Decl(controlFlowGenericTypes.ts, 107, 2)) +>object : Symbol(object, Decl(controlFlowGenericTypes.ts, 111, 37)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 111, 20)) if (!object.testable) return; ->object.testable : Symbol(testable, Decl(controlFlowGenericTypes.ts, 69, 11), Decl(controlFlowGenericTypes.ts, 73, 11)) ->object : Symbol(object, Decl(controlFlowGenericTypes.ts, 79, 37)) ->testable : Symbol(testable, Decl(controlFlowGenericTypes.ts, 69, 11), Decl(controlFlowGenericTypes.ts, 73, 11)) +>object.testable : Symbol(testable, Decl(controlFlowGenericTypes.ts, 101, 11), Decl(controlFlowGenericTypes.ts, 105, 11)) +>object : Symbol(object, Decl(controlFlowGenericTypes.ts, 111, 37)) +>testable : Symbol(testable, Decl(controlFlowGenericTypes.ts, 101, 11), Decl(controlFlowGenericTypes.ts, 105, 11)) object.doTest(); ->object.doTest : Symbol(doTest, Decl(controlFlowGenericTypes.ts, 70, 18)) ->object : Symbol(object, Decl(controlFlowGenericTypes.ts, 79, 37)) ->doTest : Symbol(doTest, Decl(controlFlowGenericTypes.ts, 70, 18)) +>object.doTest : Symbol(doTest, Decl(controlFlowGenericTypes.ts, 102, 18)) +>object : Symbol(object, Decl(controlFlowGenericTypes.ts, 111, 37)) +>doTest : Symbol(doTest, Decl(controlFlowGenericTypes.ts, 102, 18)) } // Repro from #42939 interface A { ->A : Symbol(A, Decl(controlFlowGenericTypes.ts, 82, 1)) +>A : Symbol(A, Decl(controlFlowGenericTypes.ts, 114, 1)) a: number | null; ->a : Symbol(A.a, Decl(controlFlowGenericTypes.ts, 86, 13)) +>a : Symbol(A.a, Decl(controlFlowGenericTypes.ts, 118, 13)) }; function get(key: K, obj: A): number { ->get : Symbol(get, Decl(controlFlowGenericTypes.ts, 88, 2)) ->K : Symbol(K, Decl(controlFlowGenericTypes.ts, 90, 13)) ->A : Symbol(A, Decl(controlFlowGenericTypes.ts, 82, 1)) ->key : Symbol(key, Decl(controlFlowGenericTypes.ts, 90, 32)) ->K : Symbol(K, Decl(controlFlowGenericTypes.ts, 90, 13)) ->obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 90, 39)) ->A : Symbol(A, Decl(controlFlowGenericTypes.ts, 82, 1)) +>get : Symbol(get, Decl(controlFlowGenericTypes.ts, 120, 2)) +>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 122, 13)) +>A : Symbol(A, Decl(controlFlowGenericTypes.ts, 114, 1)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 122, 32)) +>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 122, 13)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 122, 39)) +>A : Symbol(A, Decl(controlFlowGenericTypes.ts, 114, 1)) const value = obj[key]; ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 91, 9)) ->obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 90, 39)) ->key : Symbol(key, Decl(controlFlowGenericTypes.ts, 90, 32)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 123, 9)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 122, 39)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 122, 32)) if (value !== null) { ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 91, 9)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 123, 9)) return value; ->value : Symbol(value, Decl(controlFlowGenericTypes.ts, 91, 9)) +>value : Symbol(value, Decl(controlFlowGenericTypes.ts, 123, 9)) } return 0; }; diff --git a/tests/baselines/reference/controlFlowGenericTypes.types b/tests/baselines/reference/controlFlowGenericTypes.types index 56171ffb1e2ac..66f895f245d34 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.types +++ b/tests/baselines/reference/controlFlowGenericTypes.types @@ -81,6 +81,89 @@ function f2(x: Extract | null): string { >"hello" : "hello" } +interface Box { + item: T; +>item : T +} + +declare function isBox(x: any): x is Box; +>isBox : (x: any) => x is Box +>x : any + +declare function isUndefined(x: unknown): x is undefined; +>isUndefined : (x: unknown) => x is undefined +>x : unknown + +declare function unbox(x: Box): T; +>unbox : (x: Box) => T +>x : Box + +function g1 | undefined>(x: T) { +>g1 : | undefined>(x: T) => void +>x : T + + if (isBox(x)) { +>isBox(x) : boolean +>isBox : (x: any) => x is Box +>x : Box | undefined + + unbox(x); +>unbox(x) : T +>unbox : (x: Box) => T +>x : Box + } +} + +function g2 | undefined>(x: T) { +>g2 : | undefined>(x: T) => void +>x : T + + if (!isUndefined(x)) { +>!isUndefined(x) : boolean +>isUndefined(x) : boolean +>isUndefined : (x: unknown) => x is undefined +>x : Box | undefined + + unbox(x); +>unbox(x) : T +>unbox : (x: Box) => T +>x : Box + } +} + +function g3 | undefined>(x: T) { +>g3 : | undefined>(x: T) => void +>x : T + + if (!isBox(x)) { +>!isBox(x) : boolean +>isBox(x) : boolean +>isBox : (x: any) => x is Box +>x : Box | undefined + + unbox(x); // Error +>unbox(x) : T +>unbox : (x: Box) => T +>x : undefined + } +} + +function g4 | undefined>(x: T) { +>g4 : | undefined>(x: T) => void +>x : T + + if (isUndefined(x)) { +>isUndefined(x) : boolean +>isUndefined : (x: unknown) => x is undefined +>x : Box | undefined + + unbox(x); // Error +>unbox(x) : unknown +>unbox : (x: Box) => T +>x : undefined + } +} + // Repro from #13995 declare function takeA(val: 'A'): void; diff --git a/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts b/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts index 78441f0df860c..29ea2d6814a1b 100644 --- a/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts +++ b/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts @@ -26,6 +26,38 @@ function f2(x: Extract | null): string { return "hello"; } +interface Box { + item: T; +} + +declare function isBox(x: any): x is Box; +declare function isUndefined(x: unknown): x is undefined; +declare function unbox(x: Box): T; + +function g1 | undefined>(x: T) { + if (isBox(x)) { + unbox(x); + } +} + +function g2 | undefined>(x: T) { + if (!isUndefined(x)) { + unbox(x); + } +} + +function g3 | undefined>(x: T) { + if (!isBox(x)) { + unbox(x); // Error + } +} + +function g4 | undefined>(x: T) { + if (isUndefined(x)) { + unbox(x); // Error + } +} + // Repro from #13995 declare function takeA(val: 'A'): void; From a96cbab7d354b8eea05db82d1bce88227860da42 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 12 Mar 2021 16:21:36 -0800 Subject: [PATCH 09/14] Rename to getNarrowableTypeForReference, remove getConstraintForLocation --- src/compiler/checker.ts | 53 +++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 65ed815483da1..d49c2454eda25 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8167,7 +8167,7 @@ namespace ts { // Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) const name = declaration.propertyName || declaration.name; const indexType = getLiteralTypeFromPropertyName(name); - const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, name, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, AccessFlags.ExpressionPosition), declaration.name); + const declaredType = getNarrowableTypeForReference(getIndexedAccessType(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, name, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, AccessFlags.ExpressionPosition), declaration.name); type = getFlowTypeOfDestructuring(declaration, declaredType); } } @@ -8188,7 +8188,7 @@ namespace ts { else if (isArrayLikeType(parentType)) { const indexType = getLiteralType(index); const accessFlags = hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0; - const declaredType = getConstraintForLocation(getIndexedAccessTypeOrUndefined(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, declaration.name, accessFlags | AccessFlags.ExpressionPosition) || errorType, declaration.name); + const declaredType = getNarrowableTypeForReference(getIndexedAccessTypeOrUndefined(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, declaration.name, accessFlags | AccessFlags.ExpressionPosition) || errorType, declaration.name); type = getFlowTypeOfDestructuring(declaration, declaredType); } else { @@ -21762,7 +21762,8 @@ namespace ts { const nameType = getLiteralTypeFromPropertyName(name); if (!isTypeUsableAsPropertyName(nameType)) return errorType; const text = getPropertyNameFromType(nameType); - return getConstraintForLocation(getTypeOfPropertyOfType(type, text), name) || + const propType = getTypeOfPropertyOfType(type, text); + return propType && getNarrowableTypeForReference(propType, name) || isNumericLiteralName(text) && includeUndefinedInIndexSignature(getIndexTypeOfType(type, IndexKind.Number)) || includeUndefinedInIndexSignature(getIndexTypeOfType(type, IndexKind.String)) || errorType; @@ -22549,7 +22550,7 @@ namespace ts { function getInitialOrAssignedType(flow: FlowAssignment) { const node = flow.node; - return getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? + return getNarrowableTypeForReference(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? getInitialType(node) : getAssignedType(node), reference); } @@ -23554,37 +23555,22 @@ namespace ts { parent.kind === SyntaxKind.BindingElement && (parent).name === node && !!(parent).initializer; } - function typeHasNullableConstraint(type: Type) { - return !!(type.flags & TypeFlags.InstantiableNonPrimitive) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Nullable); - } - - function getConstraintForLocation(type: Type, node: Node): Type; - function getConstraintForLocation(type: Type | undefined, node: Node): Type | undefined; - function getConstraintForLocation(type: Type, node: Node): Type | undefined { - // When a node is the left hand expression of a property access, element access, or call expression, - // and the type of the node includes type variables with constraints that are nullable, we fetch the - // apparent type of the node *before* performing control flow analysis such that narrowings apply to - // the constraint type. - if (type && isConstraintPosition(node) && someType(type, typeHasNullableConstraint)) { - return mapType(getWidenedType(type), getBaseConstraintOrType); - } - return type; - } - - function isTypeVariableWithUnionConstraint(type: Type) { + function isGenericTypeWithUnionConstraint(type: Type) { return !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & TypeFlags.Union); } - function containsTypeVariable(type: Type): boolean { - return !!(type.flags & TypeFlags.Instantiable || type.flags & TypeFlags.UnionOrIntersection && some((type).types, containsTypeVariable)); + function containsGenericType(type: Type): boolean { + return !!(type.flags & TypeFlags.Instantiable || type.flags & TypeFlags.UnionOrIntersection && some((type).types, containsGenericType)); } - function hasContextualTypeWithNoTypeVariables(node: Expression) { - const contextualType = !((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) && getContextualType(node); - return contextualType && !someType(contextualType, containsTypeVariable); + function hasContextualTypeWithNoGenericTypes(node: Node) { + const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) && + !((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) && + getContextualType(node); + return contextualType && !someType(contextualType, containsGenericType); } - function getConstraintForReference(type: Type, reference: Identifier | ElementAccessExpression | PropertyAccessExpression | QualifiedName, checkMode: CheckMode | undefined) { + function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) { // When the type of a reference is or contains an instantiable type with a union type constraint, and // when the reference is in a constraint position (where it is known we'll obtain the apparent type) or // has a contextual type containing no top-level instantiables (meaning constraints will determine @@ -23592,10 +23578,9 @@ namespace ts { // control flow analysis an opportunity to narrow it further. For example, for a reference of a type // parameter type 'T extends string | undefined' with a contextual type 'string', we substitute // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'. - const substituteConstraints = reference.kind !== SyntaxKind.QualifiedName && - !(checkMode && checkMode & CheckMode.Inferential) && - someType(type, isTypeVariableWithUnionConstraint) && - (isConstraintPosition(reference) || hasContextualTypeWithNoTypeVariables(reference)); + const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) && + someType(type, isGenericTypeWithUnionConstraint) && + (isConstraintPosition(reference) || hasContextualTypeWithNoGenericTypes(reference)); return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; } @@ -23741,7 +23726,7 @@ namespace ts { return type; } - type = getConstraintForReference(type, node, checkMode); + type = getNarrowableTypeForReference(type, node, checkMode); // The declaration container is the innermost function that encloses the declaration of the variable // or parameter. The flow container is the innermost function starting with which we analyze the control @@ -26836,7 +26821,7 @@ namespace ts { if (propType === autoType) { return getFlowTypeOfProperty(node, prop); } - propType = getConstraintForReference(propType, node, checkMode); + propType = getNarrowableTypeForReference(propType, node, checkMode); // If strict null checks and strict property initialization checks are enabled, if we have // a this.xxx property access, if the property is an instance property without an initializer, // and if we are in a constructor of the same class as the property declaration, assume that From d1c23ae3635b235804557c2795d40db6d18c1da7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 12 Mar 2021 16:30:03 -0800 Subject: [PATCH 10/14] Add comment --- src/compiler/checker.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d49c2454eda25..4a1ee1a588c7b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23564,6 +23564,8 @@ namespace ts { } function hasContextualTypeWithNoGenericTypes(node: Node) { + // Computing the contextual type for a child of a JSX element involves resolving the type of the + // element's tag name, so we exclude that here to avoid circularities. const contextualType = (isIdentifier(node) || isPropertyAccessExpression(node) || isElementAccessExpression(node)) && !((isJsxOpeningElement(node.parent) || isJsxSelfClosingElement(node.parent)) && node.parent.tagName === node) && getContextualType(node); From 1648bcdc3132a8973bd04635f75d6f27fc47e5d1 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 19 Mar 2021 09:03:15 -0700 Subject: [PATCH 11/14] Fix removal of undefined in destructurings with initializers --- src/compiler/checker.ts | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4a1ee1a588c7b..6257c6de17857 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8066,6 +8066,15 @@ namespace ts { return result; } + function isGenericTypeWithUndefinedConstraint(type: Type) { + return !!(type.flags & TypeFlags.Instantiable) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Undefined); + } + + function getNonUndefinedType(type: Type) { + const typeOrConstraint = someType(type, isGenericTypeWithUndefinedConstraint) ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; + return getTypeWithFacts(typeOrConstraint, TypeFacts.NEUndefined); + } + // Determine the control flow type associated with a destructuring declaration or assignment. The following // forms of destructuring are possible: // let { x } = obj; // BindingElement @@ -8167,7 +8176,7 @@ namespace ts { // Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) const name = declaration.propertyName || declaration.name; const indexType = getLiteralTypeFromPropertyName(name); - const declaredType = getNarrowableTypeForReference(getIndexedAccessType(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, name, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, AccessFlags.ExpressionPosition), declaration.name); + const declaredType = getIndexedAccessType(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, name, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, AccessFlags.ExpressionPosition); type = getFlowTypeOfDestructuring(declaration, declaredType); } } @@ -8188,7 +8197,7 @@ namespace ts { else if (isArrayLikeType(parentType)) { const indexType = getLiteralType(index); const accessFlags = hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0; - const declaredType = getNarrowableTypeForReference(getIndexedAccessTypeOrUndefined(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, declaration.name, accessFlags | AccessFlags.ExpressionPosition) || errorType, declaration.name); + const declaredType = getIndexedAccessTypeOrUndefined(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, declaration.name, accessFlags | AccessFlags.ExpressionPosition) || errorType; type = getFlowTypeOfDestructuring(declaration, declaredType); } else { @@ -8201,11 +8210,9 @@ namespace ts { if (getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration))) { // In strict null checking mode, if a default value of a non-undefined type is specified, remove // undefined from the final type. - return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ? - getTypeWithFacts(type, TypeFacts.NEUndefined) : - type; + return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ? getNonUndefinedType(type) : type; } - return widenTypeInferredFromInitializer(declaration, getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], UnionReduction.Subtype)); + return widenTypeInferredFromInitializer(declaration, getUnionType([getNonUndefinedType(type), checkDeclarationInitializer(declaration)], UnionReduction.Subtype)); } function getTypeForDeclarationFromJSDocComment(declaration: Node) { @@ -21751,19 +21758,16 @@ namespace ts { } function getTypeWithDefault(type: Type, defaultExpression: Expression) { - if (defaultExpression) { - const defaultType = getTypeOfExpression(defaultExpression); - return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), defaultType]); - } - return type; + return defaultExpression ? + getUnionType([getNonUndefinedType(type), getTypeOfExpression(defaultExpression)]) : + type; } function getTypeOfDestructuredProperty(type: Type, name: PropertyName) { const nameType = getLiteralTypeFromPropertyName(name); if (!isTypeUsableAsPropertyName(nameType)) return errorType; const text = getPropertyNameFromType(nameType); - const propType = getTypeOfPropertyOfType(type, text); - return propType && getNarrowableTypeForReference(propType, name) || + return getTypeOfPropertyOfType(type, text) || isNumericLiteralName(text) && includeUndefinedInIndexSignature(getIndexTypeOfType(type, IndexKind.Number)) || includeUndefinedInIndexSignature(getIndexTypeOfType(type, IndexKind.String)) || errorType; @@ -23551,8 +23555,7 @@ namespace ts { const parent = node.parent; return parent.kind === SyntaxKind.PropertyAccessExpression || parent.kind === SyntaxKind.CallExpression && (parent).expression === node || - parent.kind === SyntaxKind.ElementAccessExpression && (parent).expression === node || - parent.kind === SyntaxKind.BindingElement && (parent).name === node && !!(parent).initializer; + parent.kind === SyntaxKind.ElementAccessExpression && (parent).expression === node; } function isGenericTypeWithUnionConstraint(type: Type) { From 4e0baf31933e1b2944b5d6f36e1e00b94f09182e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 19 Mar 2021 11:47:06 -0700 Subject: [PATCH 12/14] Use getContextFreeTypeOfExpression in discriminateContextualTypeByObjectMembers --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e1161cfc439a0..aa04491f157a3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -24994,7 +24994,7 @@ namespace ts { return getMatchingUnionConstituentForObjectLiteral(contextualType, node) || discriminateTypeByDiscriminableItems(contextualType, map( filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)), - prop => ([() => checkExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String]) + prop => ([() => getContextFreeTypeOfExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String]) ), isTypeAssignableTo, contextualType From 4f3fd5615c3ed567e476e6d53c552e32ce318242 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 19 Mar 2021 12:52:53 -0700 Subject: [PATCH 13/14] In obj[x], use constraint of obj's type only when x's type is non-generic --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index aa04491f157a3..a7e86e0981444 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23632,7 +23632,8 @@ namespace ts { const parent = node.parent; return parent.kind === SyntaxKind.PropertyAccessExpression || parent.kind === SyntaxKind.CallExpression && (parent).expression === node || - parent.kind === SyntaxKind.ElementAccessExpression && (parent).expression === node; + parent.kind === SyntaxKind.ElementAccessExpression && (parent).expression === node && + !isGenericIndexType(getTypeOfExpression((parent).argumentExpression)); } function isGenericTypeWithUnionConstraint(type: Type) { From 3c6616b7f69fa2ed4d9fec4de192f397282eed5d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 19 Mar 2021 13:11:40 -0700 Subject: [PATCH 14/14] Add comment --- src/compiler/checker.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a7e86e0981444..714ab35aad9d1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23630,6 +23630,9 @@ namespace ts { function isConstraintPosition(node: Node) { const parent = node.parent; + // In an element access obj[x], we consider obj to be in a constraint position only when x is not + // of a generic type. This is because when both obj and x are of generic types T and K, we want + // the resulting type to be T[K]. return parent.kind === SyntaxKind.PropertyAccessExpression || parent.kind === SyntaxKind.CallExpression && (parent).expression === node || parent.kind === SyntaxKind.ElementAccessExpression && (parent).expression === node &&