Control flow analysis of aliased conditional expressions and discriminants (#44730)

* CFA inlining of conditional expressions referenced by const variables

* Accept new baselines

* Add tests

* Accept new baselines

* Increase inlining limit to 5 levels per design meeting discussion
This commit is contained in:
Anders Hejlsberg 2021-06-25 13:59:58 -07:00 committed by GitHub
parent 906cbd2715
commit 8058619aed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 2398 additions and 40 deletions

View File

@ -21999,9 +21999,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;
}
@ -22949,14 +22975,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++;
@ -23245,8 +23272,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));
@ -23403,19 +23431,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;
@ -23433,7 +23458,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)) {
@ -23448,7 +23473,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));
@ -23466,8 +23491,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;
}
@ -23525,11 +23551,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);
@ -23554,6 +23582,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;
}
@ -23960,8 +23999,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));
}
}
}
@ -23978,6 +24018,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 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;
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:
@ -24003,8 +24058,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;
}
@ -24082,7 +24138,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) */
@ -24314,12 +24370,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
@ -24334,7 +24390,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.
@ -27552,7 +27608,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
@ -38053,7 +38109,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;

View File

@ -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'.
}
}

View File

@ -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;

View File

@ -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))
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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;
}
}