From 08b6e2e0fe8d1cb1b5b3d563244a22e82e8e91df Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 24 Jun 2021 10:27:10 -0700 Subject: [PATCH 1/5] CFA inlining of conditional expressions referenced by const variables --- src/compiler/checker.ts | 126 +++++++++++++++++++++++++++++----------- 1 file changed, 91 insertions(+), 35 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2a25f0daa7817..8fec4c8e912e4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21998,9 +21998,35 @@ namespace ts { (containsTruthyCheck(source, (target as BinaryExpression).left) || containsTruthyCheck(source, (target as BinaryExpression).right))); } - function getAccessedPropertyName(access: AccessExpression): __String | undefined { + function getPropertyAccess(expr: Expression) { + if (isAccessExpression(expr)) { + return expr; + } + if (isIdentifier(expr)) { + const symbol = getResolvedSymbol(expr); + if (isConstVariable(symbol)) { + const declaration = symbol.valueDeclaration!; + // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind' + if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer)) { + return declaration.initializer; + } + // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind' + if (isBindingElement(declaration) && !declaration.initializer) { + const parent = declaration.parent.parent; + if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer))) { + return declaration; + } + } + } + } + return undefined; + } + + function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined { + let propertyName; return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText : - isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) : + access.kind === SyntaxKind.ElementAccessExpression && isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) : + access.kind === SyntaxKind.BindingElement && (propertyName = getDestructuringPropertyName(access)) ? escapeLeadingUnderscores(propertyName) : undefined; } @@ -22948,14 +22974,15 @@ namespace ts { } } - function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) { + function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, isConstant?: boolean, flowContainer?: Node) { let key: string | undefined; let isKeySet = false; let flowDepth = 0; + let inlineLevel = 0; if (flowAnalysisDisabled) { return errorType; } - if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) { + if (!reference.flowNode) { return declaredType; } flowInvocationCount++; @@ -23244,8 +23271,9 @@ namespace ts { t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined")); } } - if (isMatchingReferenceDiscriminant(expr, type)) { - type = narrowTypeBySwitchOnDiscriminantProperty(type, expr as AccessExpression, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + const access = getDiscriminantPropertyAccess(expr, type); + if (access) { + type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.switchStatement, flow.clauseStart, flow.clauseEnd); } } return createFlowType(type, isIncomplete(flowType)); @@ -23402,19 +23430,16 @@ namespace ts { return result; } - function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) { + function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { + let access, name; const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType; - if (!(type.flags & TypeFlags.Union) || !isAccessExpression(expr)) { - return false; - } - const name = getAccessedPropertyName(expr); - if (name === undefined) { - return false; - } - return isMatchingReference(reference, expr.expression) && isDiscriminantProperty(type, name); + return type.flags & TypeFlags.Union && (access = getPropertyAccess(expr)) && (name = getAccessedPropertyName(access)) && + isMatchingReference(reference, isAccessExpression(access) ? access.expression : access.parent.parent.initializer!) && + isDiscriminantProperty(type, name) ? + access : undefined; } - function narrowTypeByDiscriminant(type: Type, access: AccessExpression, narrowType: (t: Type) => Type): Type { + function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement, narrowType: (t: Type) => Type): Type { const propName = getAccessedPropertyName(access); if (propName === undefined) { return type; @@ -23432,7 +23457,7 @@ namespace ts { }); } - function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression, operator: SyntaxKind, value: Expression, assumeTrue: boolean) { + function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, operator: SyntaxKind, value: Expression, assumeTrue: boolean) { if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && type.flags & TypeFlags.Union) { const keyPropertyName = getKeyPropertyName(type as UnionType); if (keyPropertyName && keyPropertyName === getAccessedPropertyName(access)) { @@ -23447,7 +23472,7 @@ namespace ts { return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); } - function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { + function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { if (clauseStart < clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) { const clauseTypes = getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd); const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType)); @@ -23465,8 +23490,9 @@ namespace ts { if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) { type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } - if (isMatchingReferenceDiscriminant(expr, type)) { - return narrowTypeByDiscriminant(type, expr as AccessExpression, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); + const access = getDiscriminantPropertyAccess(expr, type); + if (access) { + return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); } return type; } @@ -23524,11 +23550,13 @@ namespace ts { type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue); } } - if (isMatchingReferenceDiscriminant(left, type)) { - return narrowTypeByDiscriminantProperty(type, left as AccessExpression, operator, right, assumeTrue); + const leftAccess = getDiscriminantPropertyAccess(left, type); + if (leftAccess) { + return narrowTypeByDiscriminantProperty(type, leftAccess, operator, right, assumeTrue); } - if (isMatchingReferenceDiscriminant(right, type)) { - return narrowTypeByDiscriminantProperty(type, right as AccessExpression, operator, left, assumeTrue); + const rightAccess = getDiscriminantPropertyAccess(right, type); + if (rightAccess) { + return narrowTypeByDiscriminantProperty(type, rightAccess, operator, left, assumeTrue); } if (isMatchingConstructorReference(left)) { return narrowTypeByConstructor(type, operator, right, assumeTrue); @@ -23553,6 +23581,17 @@ namespace ts { break; case SyntaxKind.CommaToken: return narrowType(type, expr.right, assumeTrue); + // Ordinarily we won't see && and || expressions in control flow analysis because the Binder breaks those + // expressions down to individual conditional control flows. However, we may encounter them when analyzing + // aliased conditional expressions. + case SyntaxKind.AmpersandAmpersandToken: + return assumeTrue ? + narrowType(narrowType(type, expr.left, /*assumeTrue*/ true), expr.right, /*assumeTrue*/ true) : + getUnionType([narrowType(type, expr.left, /*assumeTrue*/ false), narrowType(type, expr.right, /*assumeTrue*/ false)]); + case SyntaxKind.BarBarToken: + return assumeTrue ? + getUnionType([narrowType(type, expr.left, /*assumeTrue*/ true), narrowType(type, expr.right, /*assumeTrue*/ true)]) : + narrowType(narrowType(type, expr.left, /*assumeTrue*/ false), expr.right, /*assumeTrue*/ false); } return type; } @@ -23959,8 +23998,9 @@ namespace ts { !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) { type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } - if (isMatchingReferenceDiscriminant(predicateArgument, type)) { - return narrowTypeByDiscriminant(type, predicateArgument as AccessExpression, t => getNarrowedType(t, predicate.type!, assumeTrue, isTypeSubtypeOf)); + const access = getDiscriminantPropertyAccess(predicateArgument, type); + if (access) { + return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, isTypeSubtypeOf)); } } } @@ -23977,6 +24017,21 @@ namespace ts { } switch (expr.kind) { case SyntaxKind.Identifier: + // When narrowing a reference to a const variable, non-assigned parameter, or readonly property, we inline + // up to two levels of aliased conditional expressions that are themselves declared as const variables. + if (isConstant && !isMatchingReference(reference, expr) && inlineLevel < 2) { + const symbol = getResolvedSymbol(expr as Identifier); + if (isConstVariable(symbol)) { + const declaration = symbol.valueDeclaration; + if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer) { + inlineLevel++; + const result = narrowType(type, declaration.initializer, assumeTrue); + inlineLevel--; + return result; + } + } + } + // falls through case SyntaxKind.ThisKeyword: case SyntaxKind.SuperKeyword: case SyntaxKind.PropertyAccessExpression: @@ -24002,8 +24057,9 @@ namespace ts { if (isMatchingReference(reference, expr)) { return getTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull); } - if (isMatchingReferenceDiscriminant(expr, type)) { - return narrowTypeByDiscriminant(type, expr as AccessExpression, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull)); + const access = getDiscriminantPropertyAccess(expr, type); + if (access) { + return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull)); } return type; } @@ -24081,7 +24137,7 @@ namespace ts { } function isConstVariable(symbol: Symbol) { - return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0 && getTypeOfSymbol(symbol) !== autoArrayType; + return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0; } /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ @@ -24313,12 +24369,12 @@ namespace ts { const isOuterVariable = flowContainer !== declarationContainer; const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent); const isModuleExports = symbol.flags & SymbolFlags.ModuleExports; + const isConstant = isConstVariable(localOrExportSymbol) && getTypeOfSymbol(localOrExportSymbol) !== autoArrayType || isParameter && !isParameterAssigned(localOrExportSymbol); // When the control flow originates in a function expression or arrow function and we are referencing // a const variable or parameter from an outer function, we extend the origin of the control flow // analysis to include the immediately enclosing function. - while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || - flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethod(flowContainer)) && - (isConstVariable(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) { + while (isConstant && flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || + flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethod(flowContainer))) { flowContainer = getControlFlowContainer(flowContainer); } // We only look for uninitialized variables in strict null checking mode, and only when we can analyze @@ -24333,7 +24389,7 @@ namespace ts { const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : type === autoType || type === autoArrayType ? undefinedType : getOptionalType(type); - const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer, !assumeInitialized); + const flowType = getFlowTypeOfReference(node, type, initialType, isConstant, flowContainer); // A variable is considered uninitialized when it is possible to analyze the entire control flow graph // from declaration to use, and when the variable's declared type doesn't include undefined but the // control flow based type does include undefined. @@ -27547,7 +27603,7 @@ namespace ts { getControlFlowContainer(node) === getControlFlowContainer(prop.valueDeclaration)) { assumeUninitialized = true; } - const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); + const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType, prop && isReadonlySymbol(prop)); if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 // Return the declared type to reduce follow-on errors @@ -37991,7 +38047,7 @@ namespace ts { error(node.name, Diagnostics.Augmentations_for_the_global_scope_should_have_declare_modifier_unless_they_appear_in_already_ambient_context); } - const isAmbientExternalModule = isAmbientModule(node); + const isAmbientExternalModule: boolean = isAmbientModule(node); const contextErrorMessage = isAmbientExternalModule ? Diagnostics.An_ambient_module_declaration_is_only_allowed_at_the_top_level_in_a_file : Diagnostics.A_namespace_declaration_is_only_allowed_in_a_namespace_or_module; From 0bed0574083cdec49833cec1fc342e2e7481e93a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 24 Jun 2021 10:27:32 -0700 Subject: [PATCH 2/5] Accept new baselines --- .../controlFlowCommaExpressionAssertionWithinTernary.types | 2 +- tests/baselines/reference/logicalAndOperatorStrictMode.types | 2 +- .../baselines/reference/logicalAndOperatorWithEveryType.types | 2 +- tests/baselines/reference/voidIsInitialized.types | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/baselines/reference/controlFlowCommaExpressionAssertionWithinTernary.types b/tests/baselines/reference/controlFlowCommaExpressionAssertionWithinTernary.types index 8ad9e7c81f03e..1957bf5b51fe2 100644 --- a/tests/baselines/reference/controlFlowCommaExpressionAssertionWithinTernary.types +++ b/tests/baselines/reference/controlFlowCommaExpressionAssertionWithinTernary.types @@ -23,7 +23,7 @@ function foo2(param: number | null | undefined): number | null { >assert(param !== undefined) : void >assert : (value: any) => asserts value >param !== undefined : boolean ->param : number | null | undefined +>param : number | null >undefined : undefined >param : number | null >null : null diff --git a/tests/baselines/reference/logicalAndOperatorStrictMode.types b/tests/baselines/reference/logicalAndOperatorStrictMode.types index 6bf592c8b7b31..2d7ef648aeeac 100644 --- a/tests/baselines/reference/logicalAndOperatorStrictMode.types +++ b/tests/baselines/reference/logicalAndOperatorStrictMode.types @@ -256,7 +256,7 @@ const v5 = v && v; >v5 : void >v && v : void >v : void ->v : void +>v : never const v6 = v && u; >v6 : void diff --git a/tests/baselines/reference/logicalAndOperatorWithEveryType.types b/tests/baselines/reference/logicalAndOperatorWithEveryType.types index 7595ce5dd5567..d1b63e21682e1 100644 --- a/tests/baselines/reference/logicalAndOperatorWithEveryType.types +++ b/tests/baselines/reference/logicalAndOperatorWithEveryType.types @@ -300,7 +300,7 @@ var re5 = a5 && a5; >re5 : void >a5 && a5 : void >a5 : void ->a5 : void +>a5 : never var re6 = a6 && a5; >re6 : void diff --git a/tests/baselines/reference/voidIsInitialized.types b/tests/baselines/reference/voidIsInitialized.types index a8b5767d2c47b..897fef9570f35 100644 --- a/tests/baselines/reference/voidIsInitialized.types +++ b/tests/baselines/reference/voidIsInitialized.types @@ -14,7 +14,7 @@ if(typeof x === "undefined") { >"undefined" : "undefined" x // no error: assume x2 is initialised ->x : void +>x : undefined } if(typeof y !== "undefined") { @@ -24,6 +24,6 @@ if(typeof y !== "undefined") { >"undefined" : "undefined" y // no error: do not narrow void ->y : void +>y : never } From ad811607f7aa18fd041b1205765477a327268757 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 25 Jun 2021 09:40:03 -0700 Subject: [PATCH 3/5] Add tests --- .../controlFlow/controlFlowAliasing.ts | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 tests/cases/conformance/controlFlow/controlFlowAliasing.ts diff --git a/tests/cases/conformance/controlFlow/controlFlowAliasing.ts b/tests/cases/conformance/controlFlow/controlFlowAliasing.ts new file mode 100644 index 0000000000000..9ac9e98c3f2bc --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowAliasing.ts @@ -0,0 +1,215 @@ +// @strict: true +// @declaration: true + +// Narrowing by aliased conditional expressions + +function f10(x: string | number) { + const isString = typeof x === "string"; + if (isString) { + let t: string = x; + } + else { + let t: number = x; + } +} + +function f11(x: unknown) { + const isString = typeof x === "string"; + if (isString) { + let t: string = x; + } +} + +function f12(x: string | number | boolean) { + const isString = typeof x === "string"; + const isNumber = typeof x === "number"; + if (isString || isNumber) { + let t: string | number = x; + } + else { + let t: boolean = x; + } +} + +function f13(x: string | number | boolean) { + const isString = typeof x === "string"; + const isNumber = typeof x === "number"; + const isStringOrNumber = isString || isNumber; + if (isStringOrNumber) { + let t: string | number = x; + } + else { + let t: boolean = x; + } +} + +function f14(x: number | null | undefined): number | null { + const notUndefined = x !== undefined; + return notUndefined ? x : 0; +} + +function f20(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; + } + else { + obj.bar; + } +} + +function f21(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const isFoo: boolean = obj.kind === 'foo'; + if (isFoo) { + obj.foo; // Not narrowed because isFoo has type annotation + } + else { + obj.bar; // Not narrowed because isFoo has type annotation + } +} + +function f22(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + let isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; // Not narrowed because isFoo is mutable + } + else { + obj.bar; // Not narrowed because isFoo is mutable + } +} + +function f23(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const isFoo = obj.kind === 'foo'; + obj = obj; + if (isFoo) { + obj.foo; // Not narrowed because obj is assigned in function body + } + else { + obj.bar; // Not narrowed because obj is assigned in function body + } +} + +function f24(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const obj = arg; + const isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; + } + else { + obj.bar; + } +} + +function f25(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + let obj = arg; + const isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; // Not narrowed because obj is mutable + } + else { + obj.bar; // Not narrowed because obj is mutable + } +} + +function f26(outer: { readonly obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number } }) { + const isFoo = outer.obj.kind === 'foo'; + if (isFoo) { + outer.obj.foo; + } + else { + outer.obj.bar; + } +} + +function f27(outer: { obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number } }) { + const isFoo = outer.obj.kind === 'foo'; + if (isFoo) { + outer.obj.foo; // Not narrowed because obj is mutable + } + else { + outer.obj.bar; // Not narrowed because obj is mutable + } +} + +function f28(obj?: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const isFoo = obj && obj.kind === 'foo'; + const isBar = obj && obj.kind === 'bar'; + if (isFoo) { + obj.foo; + } + if (isBar) { + obj.bar; + } +} + +// Narrowing by aliased discriminant property access + +function f30(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const kind = obj.kind; + if (kind === 'foo') { + obj.foo; + } + else { + obj.bar; + } +} + +function f31(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const { kind } = obj; + if (kind === 'foo') { + obj.foo; + } + else { + obj.bar; + } +} + +function f32(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const { kind: k } = obj; + if (k === 'foo') { + obj.foo; + } + else { + obj.bar; + } +} + +function f33(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const { kind } = obj; + switch (kind) { + case 'foo': obj.foo; break; + case 'bar': obj.bar; break; + } +} + +// Mixing of aliased discriminants and conditionals + +function f40(obj: { kind: 'foo', foo?: string } | { kind: 'bar', bar?: number }) { + const { kind } = obj; + const isFoo = kind == 'foo'; + if (isFoo && obj.foo) { + let t: string = obj.foo; + } +} + +// Unsupported narrowing of destructured payload by destructured discriminant + +type Data = { kind: 'str', payload: string } | { kind: 'num', payload: number }; + +function gg2(obj: Data) { + if (obj.kind === 'str') { + let t: string = obj.payload; + } + else { + let t: number = obj.payload; + } +} + +function foo({ kind, payload }: Data) { + if (kind === 'str') { + let t: string = payload; + } + else { + let t: number = payload; + } +} From 82c601e9b9ce440a46ce4f6c6371c6bdf16a8951 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 25 Jun 2021 09:40:15 -0700 Subject: [PATCH 4/5] Accept new baselines --- .../reference/controlFlowAliasing.errors.txt | 276 +++++++ .../reference/controlFlowAliasing.js | 526 ++++++++++++++ .../reference/controlFlowAliasing.symbols | 602 +++++++++++++++ .../reference/controlFlowAliasing.types | 683 ++++++++++++++++++ 4 files changed, 2087 insertions(+) create mode 100644 tests/baselines/reference/controlFlowAliasing.errors.txt create mode 100644 tests/baselines/reference/controlFlowAliasing.js create mode 100644 tests/baselines/reference/controlFlowAliasing.symbols create mode 100644 tests/baselines/reference/controlFlowAliasing.types diff --git a/tests/baselines/reference/controlFlowAliasing.errors.txt b/tests/baselines/reference/controlFlowAliasing.errors.txt new file mode 100644 index 0000000000000..d747a3215928a --- /dev/null +++ b/tests/baselines/reference/controlFlowAliasing.errors.txt @@ -0,0 +1,276 @@ +tests/cases/conformance/controlFlow/controlFlowAliasing.ts(61,13): error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. + Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'. +tests/cases/conformance/controlFlow/controlFlowAliasing.ts(64,13): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. + Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'. +tests/cases/conformance/controlFlow/controlFlowAliasing.ts(71,13): error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. + Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'. +tests/cases/conformance/controlFlow/controlFlowAliasing.ts(74,13): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. + Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'. +tests/cases/conformance/controlFlow/controlFlowAliasing.ts(82,13): error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. + Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'. +tests/cases/conformance/controlFlow/controlFlowAliasing.ts(85,13): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. + Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'. +tests/cases/conformance/controlFlow/controlFlowAliasing.ts(104,13): error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. + Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'. +tests/cases/conformance/controlFlow/controlFlowAliasing.ts(107,13): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. + Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'. +tests/cases/conformance/controlFlow/controlFlowAliasing.ts(124,19): error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. + Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'. +tests/cases/conformance/controlFlow/controlFlowAliasing.ts(127,19): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. + Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'. +tests/cases/conformance/controlFlow/controlFlowAliasing.ts(207,13): error TS2322: Type 'string | number' is not assignable to type 'string'. + Type 'number' is not assignable to type 'string'. +tests/cases/conformance/controlFlow/controlFlowAliasing.ts(210,13): error TS2322: Type 'string | number' is not assignable to type 'number'. + Type 'string' is not assignable to type 'number'. + + +==== tests/cases/conformance/controlFlow/controlFlowAliasing.ts (12 errors) ==== + // Narrowing by aliased conditional expressions + + function f10(x: string | number) { + const isString = typeof x === "string"; + if (isString) { + let t: string = x; + } + else { + let t: number = x; + } + } + + function f11(x: unknown) { + const isString = typeof x === "string"; + if (isString) { + let t: string = x; + } + } + + function f12(x: string | number | boolean) { + const isString = typeof x === "string"; + const isNumber = typeof x === "number"; + if (isString || isNumber) { + let t: string | number = x; + } + else { + let t: boolean = x; + } + } + + function f13(x: string | number | boolean) { + const isString = typeof x === "string"; + const isNumber = typeof x === "number"; + const isStringOrNumber = isString || isNumber; + if (isStringOrNumber) { + let t: string | number = x; + } + else { + let t: boolean = x; + } + } + + function f14(x: number | null | undefined): number | null { + const notUndefined = x !== undefined; + return notUndefined ? x : 0; + } + + function f20(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; + } + else { + obj.bar; + } + } + + function f21(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const isFoo: boolean = obj.kind === 'foo'; + if (isFoo) { + obj.foo; // Not narrowed because isFoo has type annotation + ~~~ +!!! error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. +!!! error TS2339: Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'. + } + else { + obj.bar; // Not narrowed because isFoo has type annotation + ~~~ +!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. +!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'. + } + } + + function f22(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + let isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; // Not narrowed because isFoo is mutable + ~~~ +!!! error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. +!!! error TS2339: Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'. + } + else { + obj.bar; // Not narrowed because isFoo is mutable + ~~~ +!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. +!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'. + } + } + + function f23(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const isFoo = obj.kind === 'foo'; + obj = obj; + if (isFoo) { + obj.foo; // Not narrowed because obj is assigned in function body + ~~~ +!!! error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. +!!! error TS2339: Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'. + } + else { + obj.bar; // Not narrowed because obj is assigned in function body + ~~~ +!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. +!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'. + } + } + + function f24(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const obj = arg; + const isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; + } + else { + obj.bar; + } + } + + function f25(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + let obj = arg; + const isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; // Not narrowed because obj is mutable + ~~~ +!!! error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. +!!! error TS2339: Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'. + } + else { + obj.bar; // Not narrowed because obj is mutable + ~~~ +!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. +!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'. + } + } + + function f26(outer: { readonly obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number } }) { + const isFoo = outer.obj.kind === 'foo'; + if (isFoo) { + outer.obj.foo; + } + else { + outer.obj.bar; + } + } + + function f27(outer: { obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number } }) { + const isFoo = outer.obj.kind === 'foo'; + if (isFoo) { + outer.obj.foo; // Not narrowed because obj is mutable + ~~~ +!!! error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. +!!! error TS2339: Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'. + } + else { + outer.obj.bar; // Not narrowed because obj is mutable + ~~~ +!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'. +!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'. + } + } + + function f28(obj?: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const isFoo = obj && obj.kind === 'foo'; + const isBar = obj && obj.kind === 'bar'; + if (isFoo) { + obj.foo; + } + if (isBar) { + obj.bar; + } + } + + // Narrowing by aliased discriminant property access + + function f30(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const kind = obj.kind; + if (kind === 'foo') { + obj.foo; + } + else { + obj.bar; + } + } + + function f31(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const { kind } = obj; + if (kind === 'foo') { + obj.foo; + } + else { + obj.bar; + } + } + + function f32(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const { kind: k } = obj; + if (k === 'foo') { + obj.foo; + } + else { + obj.bar; + } + } + + function f33(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const { kind } = obj; + switch (kind) { + case 'foo': obj.foo; break; + case 'bar': obj.bar; break; + } + } + + // Mixing of aliased discriminants and conditionals + + function f40(obj: { kind: 'foo', foo?: string } | { kind: 'bar', bar?: number }) { + const { kind } = obj; + const isFoo = kind == 'foo'; + if (isFoo && obj.foo) { + let t: string = obj.foo; + } + } + + // Unsupported narrowing of destructured payload by destructured discriminant + + type Data = { kind: 'str', payload: string } | { kind: 'num', payload: number }; + + function gg2(obj: Data) { + if (obj.kind === 'str') { + let t: string = obj.payload; + } + else { + let t: number = obj.payload; + } + } + + function foo({ kind, payload }: Data) { + if (kind === 'str') { + let t: string = payload; + ~ +!!! error TS2322: Type 'string | number' is not assignable to type 'string'. +!!! error TS2322: Type 'number' is not assignable to type 'string'. + } + else { + let t: number = payload; + ~ +!!! error TS2322: Type 'string | number' is not assignable to type 'number'. +!!! error TS2322: Type 'string' is not assignable to type 'number'. + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowAliasing.js b/tests/baselines/reference/controlFlowAliasing.js new file mode 100644 index 0000000000000..91615ba43e139 --- /dev/null +++ b/tests/baselines/reference/controlFlowAliasing.js @@ -0,0 +1,526 @@ +//// [controlFlowAliasing.ts] +// Narrowing by aliased conditional expressions + +function f10(x: string | number) { + const isString = typeof x === "string"; + if (isString) { + let t: string = x; + } + else { + let t: number = x; + } +} + +function f11(x: unknown) { + const isString = typeof x === "string"; + if (isString) { + let t: string = x; + } +} + +function f12(x: string | number | boolean) { + const isString = typeof x === "string"; + const isNumber = typeof x === "number"; + if (isString || isNumber) { + let t: string | number = x; + } + else { + let t: boolean = x; + } +} + +function f13(x: string | number | boolean) { + const isString = typeof x === "string"; + const isNumber = typeof x === "number"; + const isStringOrNumber = isString || isNumber; + if (isStringOrNumber) { + let t: string | number = x; + } + else { + let t: boolean = x; + } +} + +function f14(x: number | null | undefined): number | null { + const notUndefined = x !== undefined; + return notUndefined ? x : 0; +} + +function f20(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; + } + else { + obj.bar; + } +} + +function f21(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const isFoo: boolean = obj.kind === 'foo'; + if (isFoo) { + obj.foo; // Not narrowed because isFoo has type annotation + } + else { + obj.bar; // Not narrowed because isFoo has type annotation + } +} + +function f22(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + let isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; // Not narrowed because isFoo is mutable + } + else { + obj.bar; // Not narrowed because isFoo is mutable + } +} + +function f23(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const isFoo = obj.kind === 'foo'; + obj = obj; + if (isFoo) { + obj.foo; // Not narrowed because obj is assigned in function body + } + else { + obj.bar; // Not narrowed because obj is assigned in function body + } +} + +function f24(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const obj = arg; + const isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; + } + else { + obj.bar; + } +} + +function f25(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + let obj = arg; + const isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; // Not narrowed because obj is mutable + } + else { + obj.bar; // Not narrowed because obj is mutable + } +} + +function f26(outer: { readonly obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number } }) { + const isFoo = outer.obj.kind === 'foo'; + if (isFoo) { + outer.obj.foo; + } + else { + outer.obj.bar; + } +} + +function f27(outer: { obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number } }) { + const isFoo = outer.obj.kind === 'foo'; + if (isFoo) { + outer.obj.foo; // Not narrowed because obj is mutable + } + else { + outer.obj.bar; // Not narrowed because obj is mutable + } +} + +function f28(obj?: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const isFoo = obj && obj.kind === 'foo'; + const isBar = obj && obj.kind === 'bar'; + if (isFoo) { + obj.foo; + } + if (isBar) { + obj.bar; + } +} + +// Narrowing by aliased discriminant property access + +function f30(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const kind = obj.kind; + if (kind === 'foo') { + obj.foo; + } + else { + obj.bar; + } +} + +function f31(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const { kind } = obj; + if (kind === 'foo') { + obj.foo; + } + else { + obj.bar; + } +} + +function f32(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const { kind: k } = obj; + if (k === 'foo') { + obj.foo; + } + else { + obj.bar; + } +} + +function f33(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { + const { kind } = obj; + switch (kind) { + case 'foo': obj.foo; break; + case 'bar': obj.bar; break; + } +} + +// Mixing of aliased discriminants and conditionals + +function f40(obj: { kind: 'foo', foo?: string } | { kind: 'bar', bar?: number }) { + const { kind } = obj; + const isFoo = kind == 'foo'; + if (isFoo && obj.foo) { + let t: string = obj.foo; + } +} + +// Unsupported narrowing of destructured payload by destructured discriminant + +type Data = { kind: 'str', payload: string } | { kind: 'num', payload: number }; + +function gg2(obj: Data) { + if (obj.kind === 'str') { + let t: string = obj.payload; + } + else { + let t: number = obj.payload; + } +} + +function foo({ kind, payload }: Data) { + if (kind === 'str') { + let t: string = payload; + } + else { + let t: number = payload; + } +} + + +//// [controlFlowAliasing.js] +"use strict"; +// Narrowing by aliased conditional expressions +function f10(x) { + var isString = typeof x === "string"; + if (isString) { + var t = x; + } + else { + var t = x; + } +} +function f11(x) { + var isString = typeof x === "string"; + if (isString) { + var t = x; + } +} +function f12(x) { + var isString = typeof x === "string"; + var isNumber = typeof x === "number"; + if (isString || isNumber) { + var t = x; + } + else { + var t = x; + } +} +function f13(x) { + var isString = typeof x === "string"; + var isNumber = typeof x === "number"; + var isStringOrNumber = isString || isNumber; + if (isStringOrNumber) { + var t = x; + } + else { + var t = x; + } +} +function f14(x) { + var notUndefined = x !== undefined; + return notUndefined ? x : 0; +} +function f20(obj) { + var isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; + } + else { + obj.bar; + } +} +function f21(obj) { + var isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; // Not narrowed because isFoo has type annotation + } + else { + obj.bar; // Not narrowed because isFoo has type annotation + } +} +function f22(obj) { + var isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; // Not narrowed because isFoo is mutable + } + else { + obj.bar; // Not narrowed because isFoo is mutable + } +} +function f23(obj) { + var isFoo = obj.kind === 'foo'; + obj = obj; + if (isFoo) { + obj.foo; // Not narrowed because obj is assigned in function body + } + else { + obj.bar; // Not narrowed because obj is assigned in function body + } +} +function f24(arg) { + var obj = arg; + var isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; + } + else { + obj.bar; + } +} +function f25(arg) { + var obj = arg; + var isFoo = obj.kind === 'foo'; + if (isFoo) { + obj.foo; // Not narrowed because obj is mutable + } + else { + obj.bar; // Not narrowed because obj is mutable + } +} +function f26(outer) { + var isFoo = outer.obj.kind === 'foo'; + if (isFoo) { + outer.obj.foo; + } + else { + outer.obj.bar; + } +} +function f27(outer) { + var isFoo = outer.obj.kind === 'foo'; + if (isFoo) { + outer.obj.foo; // Not narrowed because obj is mutable + } + else { + outer.obj.bar; // Not narrowed because obj is mutable + } +} +function f28(obj) { + var isFoo = obj && obj.kind === 'foo'; + var isBar = obj && obj.kind === 'bar'; + if (isFoo) { + obj.foo; + } + if (isBar) { + obj.bar; + } +} +// Narrowing by aliased discriminant property access +function f30(obj) { + var kind = obj.kind; + if (kind === 'foo') { + obj.foo; + } + else { + obj.bar; + } +} +function f31(obj) { + var kind = obj.kind; + if (kind === 'foo') { + obj.foo; + } + else { + obj.bar; + } +} +function f32(obj) { + var k = obj.kind; + if (k === 'foo') { + obj.foo; + } + else { + obj.bar; + } +} +function f33(obj) { + var kind = obj.kind; + switch (kind) { + case 'foo': + obj.foo; + break; + case 'bar': + obj.bar; + break; + } +} +// Mixing of aliased discriminants and conditionals +function f40(obj) { + var kind = obj.kind; + var isFoo = kind == 'foo'; + if (isFoo && obj.foo) { + var t = obj.foo; + } +} +function gg2(obj) { + if (obj.kind === 'str') { + var t = obj.payload; + } + else { + var t = obj.payload; + } +} +function foo(_a) { + var kind = _a.kind, payload = _a.payload; + if (kind === 'str') { + var t = payload; + } + else { + var t = payload; + } +} + + +//// [controlFlowAliasing.d.ts] +declare function f10(x: string | number): void; +declare function f11(x: unknown): void; +declare function f12(x: string | number | boolean): void; +declare function f13(x: string | number | boolean): void; +declare function f14(x: number | null | undefined): number | null; +declare function f20(obj: { + kind: 'foo'; + foo: string; +} | { + kind: 'bar'; + bar: number; +}): void; +declare function f21(obj: { + kind: 'foo'; + foo: string; +} | { + kind: 'bar'; + bar: number; +}): void; +declare function f22(obj: { + kind: 'foo'; + foo: string; +} | { + kind: 'bar'; + bar: number; +}): void; +declare function f23(obj: { + kind: 'foo'; + foo: string; +} | { + kind: 'bar'; + bar: number; +}): void; +declare function f24(arg: { + kind: 'foo'; + foo: string; +} | { + kind: 'bar'; + bar: number; +}): void; +declare function f25(arg: { + kind: 'foo'; + foo: string; +} | { + kind: 'bar'; + bar: number; +}): void; +declare function f26(outer: { + readonly obj: { + kind: 'foo'; + foo: string; + } | { + kind: 'bar'; + bar: number; + }; +}): void; +declare function f27(outer: { + obj: { + kind: 'foo'; + foo: string; + } | { + kind: 'bar'; + bar: number; + }; +}): void; +declare function f28(obj?: { + kind: 'foo'; + foo: string; +} | { + kind: 'bar'; + bar: number; +}): void; +declare function f30(obj: { + kind: 'foo'; + foo: string; +} | { + kind: 'bar'; + bar: number; +}): void; +declare function f31(obj: { + kind: 'foo'; + foo: string; +} | { + kind: 'bar'; + bar: number; +}): void; +declare function f32(obj: { + kind: 'foo'; + foo: string; +} | { + kind: 'bar'; + bar: number; +}): void; +declare function f33(obj: { + kind: 'foo'; + foo: string; +} | { + kind: 'bar'; + bar: number; +}): void; +declare function f40(obj: { + kind: 'foo'; + foo?: string; +} | { + kind: 'bar'; + bar?: number; +}): void; +declare type Data = { + kind: 'str'; + payload: string; +} | { + kind: 'num'; + payload: number; +}; +declare function gg2(obj: Data): void; +declare function foo({ kind, payload }: Data): void; diff --git a/tests/baselines/reference/controlFlowAliasing.symbols b/tests/baselines/reference/controlFlowAliasing.symbols new file mode 100644 index 0000000000000..c8db499c15ac2 --- /dev/null +++ b/tests/baselines/reference/controlFlowAliasing.symbols @@ -0,0 +1,602 @@ +=== tests/cases/conformance/controlFlow/controlFlowAliasing.ts === +// Narrowing by aliased conditional expressions + +function f10(x: string | number) { +>f10 : Symbol(f10, Decl(controlFlowAliasing.ts, 0, 0)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 2, 13)) + + const isString = typeof x === "string"; +>isString : Symbol(isString, Decl(controlFlowAliasing.ts, 3, 9)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 2, 13)) + + if (isString) { +>isString : Symbol(isString, Decl(controlFlowAliasing.ts, 3, 9)) + + let t: string = x; +>t : Symbol(t, Decl(controlFlowAliasing.ts, 5, 11)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 2, 13)) + } + else { + let t: number = x; +>t : Symbol(t, Decl(controlFlowAliasing.ts, 8, 11)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 2, 13)) + } +} + +function f11(x: unknown) { +>f11 : Symbol(f11, Decl(controlFlowAliasing.ts, 10, 1)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 12, 13)) + + const isString = typeof x === "string"; +>isString : Symbol(isString, Decl(controlFlowAliasing.ts, 13, 9)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 12, 13)) + + if (isString) { +>isString : Symbol(isString, Decl(controlFlowAliasing.ts, 13, 9)) + + let t: string = x; +>t : Symbol(t, Decl(controlFlowAliasing.ts, 15, 11)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 12, 13)) + } +} + +function f12(x: string | number | boolean) { +>f12 : Symbol(f12, Decl(controlFlowAliasing.ts, 17, 1)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 19, 13)) + + const isString = typeof x === "string"; +>isString : Symbol(isString, Decl(controlFlowAliasing.ts, 20, 9)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 19, 13)) + + const isNumber = typeof x === "number"; +>isNumber : Symbol(isNumber, Decl(controlFlowAliasing.ts, 21, 9)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 19, 13)) + + if (isString || isNumber) { +>isString : Symbol(isString, Decl(controlFlowAliasing.ts, 20, 9)) +>isNumber : Symbol(isNumber, Decl(controlFlowAliasing.ts, 21, 9)) + + let t: string | number = x; +>t : Symbol(t, Decl(controlFlowAliasing.ts, 23, 11)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 19, 13)) + } + else { + let t: boolean = x; +>t : Symbol(t, Decl(controlFlowAliasing.ts, 26, 11)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 19, 13)) + } +} + +function f13(x: string | number | boolean) { +>f13 : Symbol(f13, Decl(controlFlowAliasing.ts, 28, 1)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 30, 13)) + + const isString = typeof x === "string"; +>isString : Symbol(isString, Decl(controlFlowAliasing.ts, 31, 9)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 30, 13)) + + const isNumber = typeof x === "number"; +>isNumber : Symbol(isNumber, Decl(controlFlowAliasing.ts, 32, 9)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 30, 13)) + + const isStringOrNumber = isString || isNumber; +>isStringOrNumber : Symbol(isStringOrNumber, Decl(controlFlowAliasing.ts, 33, 9)) +>isString : Symbol(isString, Decl(controlFlowAliasing.ts, 31, 9)) +>isNumber : Symbol(isNumber, Decl(controlFlowAliasing.ts, 32, 9)) + + if (isStringOrNumber) { +>isStringOrNumber : Symbol(isStringOrNumber, Decl(controlFlowAliasing.ts, 33, 9)) + + let t: string | number = x; +>t : Symbol(t, Decl(controlFlowAliasing.ts, 35, 11)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 30, 13)) + } + else { + let t: boolean = x; +>t : Symbol(t, Decl(controlFlowAliasing.ts, 38, 11)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 30, 13)) + } +} + +function f14(x: number | null | undefined): number | null { +>f14 : Symbol(f14, Decl(controlFlowAliasing.ts, 40, 1)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 42, 13)) + + const notUndefined = x !== undefined; +>notUndefined : Symbol(notUndefined, Decl(controlFlowAliasing.ts, 43, 9)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 42, 13)) +>undefined : Symbol(undefined) + + return notUndefined ? x : 0; +>notUndefined : Symbol(notUndefined, Decl(controlFlowAliasing.ts, 43, 9)) +>x : Symbol(x, Decl(controlFlowAliasing.ts, 42, 13)) +} + +function f20(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f20 : Symbol(f20, Decl(controlFlowAliasing.ts, 45, 1)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 47, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 47, 19)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 47, 32)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 47, 50)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 47, 63)) + + const isFoo = obj.kind === 'foo'; +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 48, 9)) +>obj.kind : Symbol(kind, Decl(controlFlowAliasing.ts, 47, 19), Decl(controlFlowAliasing.ts, 47, 50)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 47, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 47, 19), Decl(controlFlowAliasing.ts, 47, 50)) + + if (isFoo) { +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 48, 9)) + + obj.foo; +>obj.foo : Symbol(foo, Decl(controlFlowAliasing.ts, 47, 32)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 47, 13)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 47, 32)) + } + else { + obj.bar; +>obj.bar : Symbol(bar, Decl(controlFlowAliasing.ts, 47, 63)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 47, 13)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 47, 63)) + } +} + +function f21(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f21 : Symbol(f21, Decl(controlFlowAliasing.ts, 55, 1)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 57, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 57, 19)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 57, 32)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 57, 50)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 57, 63)) + + const isFoo: boolean = obj.kind === 'foo'; +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 58, 9)) +>obj.kind : Symbol(kind, Decl(controlFlowAliasing.ts, 57, 19), Decl(controlFlowAliasing.ts, 57, 50)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 57, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 57, 19), Decl(controlFlowAliasing.ts, 57, 50)) + + if (isFoo) { +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 58, 9)) + + obj.foo; // Not narrowed because isFoo has type annotation +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 57, 13)) + } + else { + obj.bar; // Not narrowed because isFoo has type annotation +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 57, 13)) + } +} + +function f22(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f22 : Symbol(f22, Decl(controlFlowAliasing.ts, 65, 1)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 67, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 67, 19)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 67, 32)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 67, 50)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 67, 63)) + + let isFoo = obj.kind === 'foo'; +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 68, 7)) +>obj.kind : Symbol(kind, Decl(controlFlowAliasing.ts, 67, 19), Decl(controlFlowAliasing.ts, 67, 50)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 67, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 67, 19), Decl(controlFlowAliasing.ts, 67, 50)) + + if (isFoo) { +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 68, 7)) + + obj.foo; // Not narrowed because isFoo is mutable +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 67, 13)) + } + else { + obj.bar; // Not narrowed because isFoo is mutable +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 67, 13)) + } +} + +function f23(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f23 : Symbol(f23, Decl(controlFlowAliasing.ts, 75, 1)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 77, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 77, 19)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 77, 32)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 77, 50)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 77, 63)) + + const isFoo = obj.kind === 'foo'; +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 78, 9)) +>obj.kind : Symbol(kind, Decl(controlFlowAliasing.ts, 77, 19), Decl(controlFlowAliasing.ts, 77, 50)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 77, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 77, 19), Decl(controlFlowAliasing.ts, 77, 50)) + + obj = obj; +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 77, 13)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 77, 13)) + + if (isFoo) { +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 78, 9)) + + obj.foo; // Not narrowed because obj is assigned in function body +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 77, 13)) + } + else { + obj.bar; // Not narrowed because obj is assigned in function body +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 77, 13)) + } +} + +function f24(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f24 : Symbol(f24, Decl(controlFlowAliasing.ts, 86, 1)) +>arg : Symbol(arg, Decl(controlFlowAliasing.ts, 88, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 88, 19)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 88, 32)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 88, 50)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 88, 63)) + + const obj = arg; +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 89, 9)) +>arg : Symbol(arg, Decl(controlFlowAliasing.ts, 88, 13)) + + const isFoo = obj.kind === 'foo'; +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 90, 9)) +>obj.kind : Symbol(kind, Decl(controlFlowAliasing.ts, 88, 19), Decl(controlFlowAliasing.ts, 88, 50)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 89, 9)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 88, 19), Decl(controlFlowAliasing.ts, 88, 50)) + + if (isFoo) { +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 90, 9)) + + obj.foo; +>obj.foo : Symbol(foo, Decl(controlFlowAliasing.ts, 88, 32)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 89, 9)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 88, 32)) + } + else { + obj.bar; +>obj.bar : Symbol(bar, Decl(controlFlowAliasing.ts, 88, 63)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 89, 9)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 88, 63)) + } +} + +function f25(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f25 : Symbol(f25, Decl(controlFlowAliasing.ts, 97, 1)) +>arg : Symbol(arg, Decl(controlFlowAliasing.ts, 99, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 99, 19)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 99, 32)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 99, 50)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 99, 63)) + + let obj = arg; +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 100, 7)) +>arg : Symbol(arg, Decl(controlFlowAliasing.ts, 99, 13)) + + const isFoo = obj.kind === 'foo'; +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 101, 9)) +>obj.kind : Symbol(kind, Decl(controlFlowAliasing.ts, 99, 19), Decl(controlFlowAliasing.ts, 99, 50)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 100, 7)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 99, 19), Decl(controlFlowAliasing.ts, 99, 50)) + + if (isFoo) { +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 101, 9)) + + obj.foo; // Not narrowed because obj is mutable +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 100, 7)) + } + else { + obj.bar; // Not narrowed because obj is mutable +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 100, 7)) + } +} + +function f26(outer: { readonly obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number } }) { +>f26 : Symbol(f26, Decl(controlFlowAliasing.ts, 108, 1)) +>outer : Symbol(outer, Decl(controlFlowAliasing.ts, 110, 13)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 110, 21)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 110, 37)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 110, 50)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 110, 68)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 110, 81)) + + const isFoo = outer.obj.kind === 'foo'; +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 111, 9)) +>outer.obj.kind : Symbol(kind, Decl(controlFlowAliasing.ts, 110, 37), Decl(controlFlowAliasing.ts, 110, 68)) +>outer.obj : Symbol(obj, Decl(controlFlowAliasing.ts, 110, 21)) +>outer : Symbol(outer, Decl(controlFlowAliasing.ts, 110, 13)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 110, 21)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 110, 37), Decl(controlFlowAliasing.ts, 110, 68)) + + if (isFoo) { +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 111, 9)) + + outer.obj.foo; +>outer.obj.foo : Symbol(foo, Decl(controlFlowAliasing.ts, 110, 50)) +>outer.obj : Symbol(obj, Decl(controlFlowAliasing.ts, 110, 21)) +>outer : Symbol(outer, Decl(controlFlowAliasing.ts, 110, 13)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 110, 21)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 110, 50)) + } + else { + outer.obj.bar; +>outer.obj.bar : Symbol(bar, Decl(controlFlowAliasing.ts, 110, 81)) +>outer.obj : Symbol(obj, Decl(controlFlowAliasing.ts, 110, 21)) +>outer : Symbol(outer, Decl(controlFlowAliasing.ts, 110, 13)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 110, 21)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 110, 81)) + } +} + +function f27(outer: { obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number } }) { +>f27 : Symbol(f27, Decl(controlFlowAliasing.ts, 118, 1)) +>outer : Symbol(outer, Decl(controlFlowAliasing.ts, 120, 13)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 120, 21)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 120, 28)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 120, 41)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 120, 59)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 120, 72)) + + const isFoo = outer.obj.kind === 'foo'; +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 121, 9)) +>outer.obj.kind : Symbol(kind, Decl(controlFlowAliasing.ts, 120, 28), Decl(controlFlowAliasing.ts, 120, 59)) +>outer.obj : Symbol(obj, Decl(controlFlowAliasing.ts, 120, 21)) +>outer : Symbol(outer, Decl(controlFlowAliasing.ts, 120, 13)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 120, 21)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 120, 28), Decl(controlFlowAliasing.ts, 120, 59)) + + if (isFoo) { +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 121, 9)) + + outer.obj.foo; // Not narrowed because obj is mutable +>outer.obj : Symbol(obj, Decl(controlFlowAliasing.ts, 120, 21)) +>outer : Symbol(outer, Decl(controlFlowAliasing.ts, 120, 13)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 120, 21)) + } + else { + outer.obj.bar; // Not narrowed because obj is mutable +>outer.obj : Symbol(obj, Decl(controlFlowAliasing.ts, 120, 21)) +>outer : Symbol(outer, Decl(controlFlowAliasing.ts, 120, 13)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 120, 21)) + } +} + +function f28(obj?: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f28 : Symbol(f28, Decl(controlFlowAliasing.ts, 128, 1)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 130, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 130, 20)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 130, 33)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 130, 51)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 130, 64)) + + const isFoo = obj && obj.kind === 'foo'; +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 131, 9)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 130, 13)) +>obj.kind : Symbol(kind, Decl(controlFlowAliasing.ts, 130, 20), Decl(controlFlowAliasing.ts, 130, 51)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 130, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 130, 20), Decl(controlFlowAliasing.ts, 130, 51)) + + const isBar = obj && obj.kind === 'bar'; +>isBar : Symbol(isBar, Decl(controlFlowAliasing.ts, 132, 9)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 130, 13)) +>obj.kind : Symbol(kind, Decl(controlFlowAliasing.ts, 130, 20), Decl(controlFlowAliasing.ts, 130, 51)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 130, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 130, 20), Decl(controlFlowAliasing.ts, 130, 51)) + + if (isFoo) { +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 131, 9)) + + obj.foo; +>obj.foo : Symbol(foo, Decl(controlFlowAliasing.ts, 130, 33)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 130, 13)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 130, 33)) + } + if (isBar) { +>isBar : Symbol(isBar, Decl(controlFlowAliasing.ts, 132, 9)) + + obj.bar; +>obj.bar : Symbol(bar, Decl(controlFlowAliasing.ts, 130, 64)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 130, 13)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 130, 64)) + } +} + +// Narrowing by aliased discriminant property access + +function f30(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f30 : Symbol(f30, Decl(controlFlowAliasing.ts, 139, 1)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 143, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 143, 19)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 143, 32)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 143, 50)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 143, 63)) + + const kind = obj.kind; +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 144, 9)) +>obj.kind : Symbol(kind, Decl(controlFlowAliasing.ts, 143, 19), Decl(controlFlowAliasing.ts, 143, 50)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 143, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 143, 19), Decl(controlFlowAliasing.ts, 143, 50)) + + if (kind === 'foo') { +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 144, 9)) + + obj.foo; +>obj.foo : Symbol(foo, Decl(controlFlowAliasing.ts, 143, 32)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 143, 13)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 143, 32)) + } + else { + obj.bar; +>obj.bar : Symbol(bar, Decl(controlFlowAliasing.ts, 143, 63)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 143, 13)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 143, 63)) + } +} + +function f31(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f31 : Symbol(f31, Decl(controlFlowAliasing.ts, 151, 1)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 153, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 153, 19)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 153, 32)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 153, 50)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 153, 63)) + + const { kind } = obj; +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 154, 11)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 153, 13)) + + if (kind === 'foo') { +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 154, 11)) + + obj.foo; +>obj.foo : Symbol(foo, Decl(controlFlowAliasing.ts, 153, 32)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 153, 13)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 153, 32)) + } + else { + obj.bar; +>obj.bar : Symbol(bar, Decl(controlFlowAliasing.ts, 153, 63)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 153, 13)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 153, 63)) + } +} + +function f32(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f32 : Symbol(f32, Decl(controlFlowAliasing.ts, 161, 1)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 163, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 163, 19)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 163, 32)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 163, 50)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 163, 63)) + + const { kind: k } = obj; +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 163, 19), Decl(controlFlowAliasing.ts, 163, 50)) +>k : Symbol(k, Decl(controlFlowAliasing.ts, 164, 11)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 163, 13)) + + if (k === 'foo') { +>k : Symbol(k, Decl(controlFlowAliasing.ts, 164, 11)) + + obj.foo; +>obj.foo : Symbol(foo, Decl(controlFlowAliasing.ts, 163, 32)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 163, 13)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 163, 32)) + } + else { + obj.bar; +>obj.bar : Symbol(bar, Decl(controlFlowAliasing.ts, 163, 63)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 163, 13)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 163, 63)) + } +} + +function f33(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f33 : Symbol(f33, Decl(controlFlowAliasing.ts, 171, 1)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 173, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 173, 19)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 173, 32)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 173, 50)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 173, 63)) + + const { kind } = obj; +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 174, 11)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 173, 13)) + + switch (kind) { +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 174, 11)) + + case 'foo': obj.foo; break; +>obj.foo : Symbol(foo, Decl(controlFlowAliasing.ts, 173, 32)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 173, 13)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 173, 32)) + + case 'bar': obj.bar; break; +>obj.bar : Symbol(bar, Decl(controlFlowAliasing.ts, 173, 63)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 173, 13)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 173, 63)) + } +} + +// Mixing of aliased discriminants and conditionals + +function f40(obj: { kind: 'foo', foo?: string } | { kind: 'bar', bar?: number }) { +>f40 : Symbol(f40, Decl(controlFlowAliasing.ts, 179, 1)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 183, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 183, 19)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 183, 32)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 183, 51)) +>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 183, 64)) + + const { kind } = obj; +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 184, 11)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 183, 13)) + + const isFoo = kind == 'foo'; +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 185, 9)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 184, 11)) + + if (isFoo && obj.foo) { +>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 185, 9)) +>obj.foo : Symbol(foo, Decl(controlFlowAliasing.ts, 183, 32)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 183, 13)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 183, 32)) + + let t: string = obj.foo; +>t : Symbol(t, Decl(controlFlowAliasing.ts, 187, 11)) +>obj.foo : Symbol(foo, Decl(controlFlowAliasing.ts, 183, 32)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 183, 13)) +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 183, 32)) + } +} + +// Unsupported narrowing of destructured payload by destructured discriminant + +type Data = { kind: 'str', payload: string } | { kind: 'num', payload: number }; +>Data : Symbol(Data, Decl(controlFlowAliasing.ts, 189, 1)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 193, 13)) +>payload : Symbol(payload, Decl(controlFlowAliasing.ts, 193, 26)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 193, 48)) +>payload : Symbol(payload, Decl(controlFlowAliasing.ts, 193, 61)) + +function gg2(obj: Data) { +>gg2 : Symbol(gg2, Decl(controlFlowAliasing.ts, 193, 80)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 195, 13)) +>Data : Symbol(Data, Decl(controlFlowAliasing.ts, 189, 1)) + + if (obj.kind === 'str') { +>obj.kind : Symbol(kind, Decl(controlFlowAliasing.ts, 193, 13), Decl(controlFlowAliasing.ts, 193, 48)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 195, 13)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 193, 13), Decl(controlFlowAliasing.ts, 193, 48)) + + let t: string = obj.payload; +>t : Symbol(t, Decl(controlFlowAliasing.ts, 197, 11)) +>obj.payload : Symbol(payload, Decl(controlFlowAliasing.ts, 193, 26)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 195, 13)) +>payload : Symbol(payload, Decl(controlFlowAliasing.ts, 193, 26)) + } + else { + let t: number = obj.payload; +>t : Symbol(t, Decl(controlFlowAliasing.ts, 200, 11)) +>obj.payload : Symbol(payload, Decl(controlFlowAliasing.ts, 193, 61)) +>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 195, 13)) +>payload : Symbol(payload, Decl(controlFlowAliasing.ts, 193, 61)) + } +} + +function foo({ kind, payload }: Data) { +>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 202, 1)) +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 204, 14)) +>payload : Symbol(payload, Decl(controlFlowAliasing.ts, 204, 20)) +>Data : Symbol(Data, Decl(controlFlowAliasing.ts, 189, 1)) + + if (kind === 'str') { +>kind : Symbol(kind, Decl(controlFlowAliasing.ts, 204, 14)) + + let t: string = payload; +>t : Symbol(t, Decl(controlFlowAliasing.ts, 206, 11)) +>payload : Symbol(payload, Decl(controlFlowAliasing.ts, 204, 20)) + } + else { + let t: number = payload; +>t : Symbol(t, Decl(controlFlowAliasing.ts, 209, 11)) +>payload : Symbol(payload, Decl(controlFlowAliasing.ts, 204, 20)) + } +} + diff --git a/tests/baselines/reference/controlFlowAliasing.types b/tests/baselines/reference/controlFlowAliasing.types new file mode 100644 index 0000000000000..a3af9609566a1 --- /dev/null +++ b/tests/baselines/reference/controlFlowAliasing.types @@ -0,0 +1,683 @@ +=== tests/cases/conformance/controlFlow/controlFlowAliasing.ts === +// Narrowing by aliased conditional expressions + +function f10(x: string | number) { +>f10 : (x: string | number) => void +>x : string | number + + const isString = typeof x === "string"; +>isString : boolean +>typeof x === "string" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : string | number +>"string" : "string" + + if (isString) { +>isString : boolean + + let t: string = x; +>t : string +>x : string + } + else { + let t: number = x; +>t : number +>x : number + } +} + +function f11(x: unknown) { +>f11 : (x: unknown) => void +>x : unknown + + const isString = typeof x === "string"; +>isString : boolean +>typeof x === "string" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : unknown +>"string" : "string" + + if (isString) { +>isString : boolean + + let t: string = x; +>t : string +>x : string + } +} + +function f12(x: string | number | boolean) { +>f12 : (x: string | number | boolean) => void +>x : string | number | boolean + + const isString = typeof x === "string"; +>isString : boolean +>typeof x === "string" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : string | number | boolean +>"string" : "string" + + const isNumber = typeof x === "number"; +>isNumber : boolean +>typeof x === "number" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : string | number | boolean +>"number" : "number" + + if (isString || isNumber) { +>isString || isNumber : boolean +>isString : boolean +>isNumber : boolean + + let t: string | number = x; +>t : string | number +>x : string | number + } + else { + let t: boolean = x; +>t : boolean +>x : boolean + } +} + +function f13(x: string | number | boolean) { +>f13 : (x: string | number | boolean) => void +>x : string | number | boolean + + const isString = typeof x === "string"; +>isString : boolean +>typeof x === "string" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : string | number | boolean +>"string" : "string" + + const isNumber = typeof x === "number"; +>isNumber : boolean +>typeof x === "number" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : string | number | boolean +>"number" : "number" + + const isStringOrNumber = isString || isNumber; +>isStringOrNumber : boolean +>isString || isNumber : boolean +>isString : boolean +>isNumber : boolean + + if (isStringOrNumber) { +>isStringOrNumber : boolean + + let t: string | number = x; +>t : string | number +>x : string | number + } + else { + let t: boolean = x; +>t : boolean +>x : boolean + } +} + +function f14(x: number | null | undefined): number | null { +>f14 : (x: number | null | undefined) => number | null +>x : number | null | undefined +>null : null +>null : null + + const notUndefined = x !== undefined; +>notUndefined : boolean +>x !== undefined : boolean +>x : number | null | undefined +>undefined : undefined + + return notUndefined ? x : 0; +>notUndefined ? x : 0 : number | null +>notUndefined : boolean +>x : number | null +>0 : 0 +} + +function f20(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f20 : (obj: { kind: 'foo'; foo: string;} | { kind: 'bar'; bar: number;}) => void +>obj : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + const isFoo = obj.kind === 'foo'; +>isFoo : boolean +>obj.kind === 'foo' : boolean +>obj.kind : "foo" | "bar" +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>kind : "foo" | "bar" +>'foo' : "foo" + + if (isFoo) { +>isFoo : boolean + + obj.foo; +>obj.foo : string +>obj : { kind: "foo"; foo: string; } +>foo : string + } + else { + obj.bar; +>obj.bar : number +>obj : { kind: "bar"; bar: number; } +>bar : number + } +} + +function f21(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f21 : (obj: { kind: 'foo'; foo: string;} | { kind: 'bar'; bar: number;}) => void +>obj : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + const isFoo: boolean = obj.kind === 'foo'; +>isFoo : boolean +>obj.kind === 'foo' : boolean +>obj.kind : "foo" | "bar" +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>kind : "foo" | "bar" +>'foo' : "foo" + + if (isFoo) { +>isFoo : boolean + + obj.foo; // Not narrowed because isFoo has type annotation +>obj.foo : any +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>foo : any + } + else { + obj.bar; // Not narrowed because isFoo has type annotation +>obj.bar : any +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>bar : any + } +} + +function f22(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f22 : (obj: { kind: 'foo'; foo: string;} | { kind: 'bar'; bar: number;}) => void +>obj : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + let isFoo = obj.kind === 'foo'; +>isFoo : boolean +>obj.kind === 'foo' : boolean +>obj.kind : "foo" | "bar" +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>kind : "foo" | "bar" +>'foo' : "foo" + + if (isFoo) { +>isFoo : boolean + + obj.foo; // Not narrowed because isFoo is mutable +>obj.foo : any +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>foo : any + } + else { + obj.bar; // Not narrowed because isFoo is mutable +>obj.bar : any +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>bar : any + } +} + +function f23(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f23 : (obj: { kind: 'foo'; foo: string;} | { kind: 'bar'; bar: number;}) => void +>obj : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + const isFoo = obj.kind === 'foo'; +>isFoo : boolean +>obj.kind === 'foo' : boolean +>obj.kind : "foo" | "bar" +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>kind : "foo" | "bar" +>'foo' : "foo" + + obj = obj; +>obj = obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } + + if (isFoo) { +>isFoo : boolean + + obj.foo; // Not narrowed because obj is assigned in function body +>obj.foo : any +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>foo : any + } + else { + obj.bar; // Not narrowed because obj is assigned in function body +>obj.bar : any +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>bar : any + } +} + +function f24(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f24 : (arg: { kind: 'foo'; foo: string;} | { kind: 'bar'; bar: number;}) => void +>arg : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + const obj = arg; +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>arg : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } + + const isFoo = obj.kind === 'foo'; +>isFoo : boolean +>obj.kind === 'foo' : boolean +>obj.kind : "foo" | "bar" +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>kind : "foo" | "bar" +>'foo' : "foo" + + if (isFoo) { +>isFoo : boolean + + obj.foo; +>obj.foo : string +>obj : { kind: "foo"; foo: string; } +>foo : string + } + else { + obj.bar; +>obj.bar : number +>obj : { kind: "bar"; bar: number; } +>bar : number + } +} + +function f25(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f25 : (arg: { kind: 'foo'; foo: string;} | { kind: 'bar'; bar: number;}) => void +>arg : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + let obj = arg; +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>arg : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } + + const isFoo = obj.kind === 'foo'; +>isFoo : boolean +>obj.kind === 'foo' : boolean +>obj.kind : "foo" | "bar" +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>kind : "foo" | "bar" +>'foo' : "foo" + + if (isFoo) { +>isFoo : boolean + + obj.foo; // Not narrowed because obj is mutable +>obj.foo : any +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>foo : any + } + else { + obj.bar; // Not narrowed because obj is mutable +>obj.bar : any +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>bar : any + } +} + +function f26(outer: { readonly obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number } }) { +>f26 : (outer: { readonly obj: { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; };}) => void +>outer : { readonly obj: { kind: 'foo'; foo: string;} | { kind: 'bar'; bar: number;}; } +>obj : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + const isFoo = outer.obj.kind === 'foo'; +>isFoo : boolean +>outer.obj.kind === 'foo' : boolean +>outer.obj.kind : "foo" | "bar" +>outer.obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>outer : { readonly obj: { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }; } +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>kind : "foo" | "bar" +>'foo' : "foo" + + if (isFoo) { +>isFoo : boolean + + outer.obj.foo; +>outer.obj.foo : string +>outer.obj : { kind: "foo"; foo: string; } +>outer : { readonly obj: { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }; } +>obj : { kind: "foo"; foo: string; } +>foo : string + } + else { + outer.obj.bar; +>outer.obj.bar : number +>outer.obj : { kind: "bar"; bar: number; } +>outer : { readonly obj: { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }; } +>obj : { kind: "bar"; bar: number; } +>bar : number + } +} + +function f27(outer: { obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number } }) { +>f27 : (outer: { obj: { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; };}) => void +>outer : { obj: { kind: 'foo'; foo: string;} | { kind: 'bar'; bar: number;}; } +>obj : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + const isFoo = outer.obj.kind === 'foo'; +>isFoo : boolean +>outer.obj.kind === 'foo' : boolean +>outer.obj.kind : "foo" | "bar" +>outer.obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>outer : { obj: { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }; } +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>kind : "foo" | "bar" +>'foo' : "foo" + + if (isFoo) { +>isFoo : boolean + + outer.obj.foo; // Not narrowed because obj is mutable +>outer.obj.foo : any +>outer.obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>outer : { obj: { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }; } +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>foo : any + } + else { + outer.obj.bar; // Not narrowed because obj is mutable +>outer.obj.bar : any +>outer.obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>outer : { obj: { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }; } +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>bar : any + } +} + +function f28(obj?: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f28 : (obj?: { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } | undefined) => void +>obj : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } | undefined +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + const isFoo = obj && obj.kind === 'foo'; +>isFoo : boolean | undefined +>obj && obj.kind === 'foo' : boolean | undefined +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } | undefined +>obj.kind === 'foo' : boolean +>obj.kind : "foo" | "bar" +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>kind : "foo" | "bar" +>'foo' : "foo" + + const isBar = obj && obj.kind === 'bar'; +>isBar : boolean | undefined +>obj && obj.kind === 'bar' : boolean | undefined +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } | undefined +>obj.kind === 'bar' : boolean +>obj.kind : "foo" | "bar" +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>kind : "foo" | "bar" +>'bar' : "bar" + + if (isFoo) { +>isFoo : boolean | undefined + + obj.foo; +>obj.foo : string +>obj : { kind: "foo"; foo: string; } +>foo : string + } + if (isBar) { +>isBar : boolean | undefined + + obj.bar; +>obj.bar : number +>obj : { kind: "bar"; bar: number; } +>bar : number + } +} + +// Narrowing by aliased discriminant property access + +function f30(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f30 : (obj: { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; }) => void +>obj : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + const kind = obj.kind; +>kind : "foo" | "bar" +>obj.kind : "foo" | "bar" +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } +>kind : "foo" | "bar" + + if (kind === 'foo') { +>kind === 'foo' : boolean +>kind : "foo" | "bar" +>'foo' : "foo" + + obj.foo; +>obj.foo : string +>obj : { kind: "foo"; foo: string; } +>foo : string + } + else { + obj.bar; +>obj.bar : number +>obj : { kind: "bar"; bar: number; } +>bar : number + } +} + +function f31(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f31 : (obj: { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; }) => void +>obj : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + const { kind } = obj; +>kind : "foo" | "bar" +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } + + if (kind === 'foo') { +>kind === 'foo' : boolean +>kind : "foo" | "bar" +>'foo' : "foo" + + obj.foo; +>obj.foo : string +>obj : { kind: "foo"; foo: string; } +>foo : string + } + else { + obj.bar; +>obj.bar : number +>obj : { kind: "bar"; bar: number; } +>bar : number + } +} + +function f32(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f32 : (obj: { kind: 'foo'; foo: string;} | { kind: 'bar'; bar: number;}) => void +>obj : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + const { kind: k } = obj; +>kind : any +>k : "foo" | "bar" +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } + + if (k === 'foo') { +>k === 'foo' : boolean +>k : "foo" | "bar" +>'foo' : "foo" + + obj.foo; +>obj.foo : string +>obj : { kind: "foo"; foo: string; } +>foo : string + } + else { + obj.bar; +>obj.bar : number +>obj : { kind: "bar"; bar: number; } +>bar : number + } +} + +function f33(obj: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) { +>f33 : (obj: { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; }) => void +>obj : { kind: 'foo'; foo: string; } | { kind: 'bar'; bar: number; } +>kind : "foo" +>foo : string +>kind : "bar" +>bar : number + + const { kind } = obj; +>kind : "foo" | "bar" +>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; } + + switch (kind) { +>kind : "foo" | "bar" + + case 'foo': obj.foo; break; +>'foo' : "foo" +>obj.foo : string +>obj : { kind: "foo"; foo: string; } +>foo : string + + case 'bar': obj.bar; break; +>'bar' : "bar" +>obj.bar : number +>obj : { kind: "bar"; bar: number; } +>bar : number + } +} + +// Mixing of aliased discriminants and conditionals + +function f40(obj: { kind: 'foo', foo?: string } | { kind: 'bar', bar?: number }) { +>f40 : (obj: { kind: 'foo'; foo?: string | undefined; } | { kind: 'bar'; bar?: number | undefined; }) => void +>obj : { kind: 'foo'; foo?: string | undefined; } | { kind: 'bar'; bar?: number | undefined; } +>kind : "foo" +>foo : string | undefined +>kind : "bar" +>bar : number | undefined + + const { kind } = obj; +>kind : "foo" | "bar" +>obj : { kind: "foo"; foo?: string | undefined; } | { kind: "bar"; bar?: number | undefined; } + + const isFoo = kind == 'foo'; +>isFoo : boolean +>kind == 'foo' : boolean +>kind : "foo" | "bar" +>'foo' : "foo" + + if (isFoo && obj.foo) { +>isFoo && obj.foo : string | false | undefined +>isFoo : boolean +>obj.foo : string | undefined +>obj : { kind: "foo"; foo?: string | undefined; } +>foo : string | undefined + + let t: string = obj.foo; +>t : string +>obj.foo : string +>obj : { kind: "foo"; foo?: string | undefined; } +>foo : string + } +} + +// Unsupported narrowing of destructured payload by destructured discriminant + +type Data = { kind: 'str', payload: string } | { kind: 'num', payload: number }; +>Data : Data +>kind : "str" +>payload : string +>kind : "num" +>payload : number + +function gg2(obj: Data) { +>gg2 : (obj: Data) => void +>obj : Data + + if (obj.kind === 'str') { +>obj.kind === 'str' : boolean +>obj.kind : "str" | "num" +>obj : Data +>kind : "str" | "num" +>'str' : "str" + + let t: string = obj.payload; +>t : string +>obj.payload : string +>obj : { kind: "str"; payload: string; } +>payload : string + } + else { + let t: number = obj.payload; +>t : number +>obj.payload : number +>obj : { kind: "num"; payload: number; } +>payload : number + } +} + +function foo({ kind, payload }: Data) { +>foo : ({ kind, payload }: Data) => void +>kind : "str" | "num" +>payload : string | number + + if (kind === 'str') { +>kind === 'str' : boolean +>kind : "str" | "num" +>'str' : "str" + + let t: string = payload; +>t : string +>payload : string | number + } + else { + let t: number = payload; +>t : number +>payload : string | number + } +} + From 9a68c4f4435116d656f36587e871e11124f9debe Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 25 Jun 2021 13:38:23 -0700 Subject: [PATCH 5/5] Increase inlining limit to 5 levels per design meeting discussion --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8fec4c8e912e4..571e725f8809b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -24018,8 +24018,8 @@ namespace ts { switch (expr.kind) { case SyntaxKind.Identifier: // When narrowing a reference to a const variable, non-assigned parameter, or readonly property, we inline - // up to two levels of aliased conditional expressions that are themselves declared as const variables. - if (isConstant && !isMatchingReference(reference, expr) && inlineLevel < 2) { + // up to five levels of aliased conditional expressions that are themselves declared as const variables. + if (isConstant && !isMatchingReference(reference, expr) && inlineLevel < 5) { const symbol = getResolvedSymbol(expr as Identifier); if (isConstVariable(symbol)) { const declaration = symbol.valueDeclaration;