diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index fb24f4cc45e6c..05dbd47006b16 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -696,23 +696,6 @@ namespace ts { }; } - function skipSimpleConditionalFlow(flow: FlowNode) { - // We skip over simple conditional flows of the form 'x ? aaa : bbb', where 'aaa' and 'bbb' contain - // no constructs that affect control flow type analysis. Such simple flows have no effect on the - // code paths that follow and ignoring them means we'll do less work. - if (flow.flags & FlowFlags.BranchLabel && (flow).antecedents.length === 2) { - const a = (flow).antecedents[0]; - const b = (flow).antecedents[1]; - if ((a.flags & FlowFlags.TrueCondition && b.flags & FlowFlags.FalseCondition || - a.flags & FlowFlags.FalseCondition && b.flags & FlowFlags.TrueCondition) && - (a).antecedent === (b).antecedent && - (a).expression === (b).expression) { - return (a).antecedent; - } - } - return flow; - } - function finishFlowLabel(flow: FlowLabel): FlowNode { const antecedents = flow.antecedents; if (!antecedents) { @@ -721,7 +704,7 @@ namespace ts { if (antecedents.length === 1) { return antecedents[0]; } - return skipSimpleConditionalFlow(flow); + return flow; } function isStatementCondition(node: Node) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ab1433320f524..85e78324cc737 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -122,7 +122,7 @@ namespace ts { const unknownType = createIntrinsicType(TypeFlags.Any, "unknown"); const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - const emptyUnionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); + const nothingType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); emptyGenericType.instantiations = {}; @@ -2030,7 +2030,7 @@ namespace ts { writeUnionOrIntersectionType(type, flags); } else if (type.flags & TypeFlags.Anonymous) { - if (type === emptyUnionType) { + if (type === nothingType) { writer.writeKeyword("nothing"); } else { @@ -5006,7 +5006,7 @@ namespace ts { if (type.flags & TypeFlags.Undefined) typeSet.containsUndefined = true; if (type.flags & TypeFlags.Null) typeSet.containsNull = true; } - else if (type !== emptyUnionType && !contains(typeSet, type)) { + else if (type !== nothingType && !contains(typeSet, type)) { typeSet.push(type); } } @@ -5047,7 +5047,10 @@ namespace ts { // a named type that circularly references itself. function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type { if (types.length === 0) { - return emptyUnionType; + return nothingType; + } + if (types.length === 1) { + return types[0]; } const typeSet = [] as TypeSet; addTypesToSet(typeSet, types, TypeFlags.Union); @@ -5064,7 +5067,7 @@ namespace ts { if (typeSet.length === 0) { return typeSet.containsNull ? nullType : typeSet.containsUndefined ? undefinedType : - emptyUnionType; + nothingType; } else if (typeSet.length === 1) { return typeSet[0]; @@ -7483,7 +7486,7 @@ namespace ts { function getTypeWithFacts(type: Type, include: TypeFacts) { if (!(type.flags & TypeFlags.Union)) { - return getTypeFacts(type) & include ? type : emptyUnionType; + return getTypeFacts(type) & include ? type : nothingType; } let firstType: Type; let types: Type[]; @@ -7500,7 +7503,7 @@ namespace ts { } } } - return firstType ? types ? getUnionType(types, /*noSubtypeReduction*/ true) : firstType : emptyUnionType; + return firstType ? types ? getUnionType(types, /*noSubtypeReduction*/ true) : firstType : nothingType; } function getTypeWithDefault(type: Type, defaultExpression: Expression) { @@ -7618,6 +7621,9 @@ namespace ts { const visitedFlowStart = visitedFlowCount; const result = getTypeAtFlowNode(reference.flowNode); visitedFlowCount = visitedFlowStart; + if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull) === nothingType) { + return declaredType; + } return result; function getTypeAtFlowNode(flow: FlowNode): Type { @@ -7702,7 +7708,22 @@ namespace ts { } function getTypeAtFlowCondition(flow: FlowCondition) { - return narrowType(getTypeAtFlowNode(flow.antecedent), flow.expression, (flow.flags & FlowFlags.TrueCondition) !== 0); + let type = getTypeAtFlowNode(flow.antecedent); + if (type !== nothingType) { + // If we have an antecedent type (meaning we're reachable in some way), we first + // attempt to narrow the antecedent type. If that produces the nothing type, then + // we take the type guard as an indication that control could reach here in a + // manner not understood by the control flow analyzer (e.g. a function argument + // has an invalid type, or a nested function has possibly made an assignment to a + // captured variable). We proceed by reverting to the declared type and then + // narrow that. + const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0; + type = narrowType(type, flow.expression, assumeTrue); + if (type === nothingType) { + type = narrowType(declaredType, flow.expression, assumeTrue); + } + } + return type; } function getTypeAtFlowBranchLabel(flow: FlowLabel) { @@ -7720,7 +7741,7 @@ namespace ts { antecedentTypes.push(type); } } - return antecedentTypes.length === 1 ? antecedentTypes[0] : getUnionType(antecedentTypes); + return getUnionType(antecedentTypes); } function getTypeAtFlowLoopLabel(flow: FlowLabel) { @@ -7770,7 +7791,7 @@ namespace ts { antecedentTypes.push(type); } } - return cache[key] = antecedentTypes.length === 1 ? antecedentTypes[0] : getUnionType(antecedentTypes); + return cache[key] = getUnionType(antecedentTypes); } function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { @@ -7921,7 +7942,7 @@ namespace ts { const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type; return isTypeAssignableTo(candidate, targetType) ? candidate : isTypeAssignableTo(type, candidate) ? type : - emptyUnionType; + nothingType; } function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { @@ -14726,7 +14747,7 @@ namespace ts { arrayType = getUnionType(filter((arrayOrStringType as UnionType).types, t => !(t.flags & TypeFlags.StringLike))); } else if (arrayOrStringType.flags & TypeFlags.StringLike) { - arrayType = emptyUnionType; + arrayType = nothingType; } const hasStringConstituent = arrayOrStringType !== arrayType; let reportedError = false; @@ -14738,7 +14759,7 @@ namespace ts { // Now that we've removed all the StringLike types, if no constituents remain, then the entire // arrayOrStringType was a string. - if (arrayType === emptyUnionType) { + if (arrayType === nothingType) { return stringType; } } diff --git a/tests/baselines/reference/controlFlowBinaryOrExpression.types b/tests/baselines/reference/controlFlowBinaryOrExpression.types index c7651a09d1eb5..8c1cd32d8d9d7 100644 --- a/tests/baselines/reference/controlFlowBinaryOrExpression.types +++ b/tests/baselines/reference/controlFlowBinaryOrExpression.types @@ -87,7 +87,7 @@ if (isNodeList(sourceObj)) { if (isHTMLCollection(sourceObj)) { >isHTMLCollection(sourceObj) : boolean >isHTMLCollection : (sourceObj: any) => sourceObj is HTMLCollection ->sourceObj : { a: string; } | HTMLCollection +>sourceObj : HTMLCollection | { a: string; } sourceObj.length; >sourceObj.length : number @@ -99,7 +99,7 @@ if (isNodeList(sourceObj) || isHTMLCollection(sourceObj)) { >isNodeList(sourceObj) || isHTMLCollection(sourceObj) : boolean >isNodeList(sourceObj) : boolean >isNodeList : (sourceObj: any) => sourceObj is NodeList ->sourceObj : { a: string; } | HTMLCollection +>sourceObj : HTMLCollection | { a: string; } >isHTMLCollection(sourceObj) : boolean >isHTMLCollection : (sourceObj: any) => sourceObj is HTMLCollection >sourceObj : { a: string; } diff --git a/tests/baselines/reference/typeGuardEnums.types b/tests/baselines/reference/typeGuardEnums.types index 1d39a81d78acd..2bef691504606 100644 --- a/tests/baselines/reference/typeGuardEnums.types +++ b/tests/baselines/reference/typeGuardEnums.types @@ -27,7 +27,7 @@ else { if (typeof x !== "number") { >typeof x !== "number" : boolean >typeof x : string ->x : number | string | E | V +>x : number | string >"number" : string x; // string @@ -35,6 +35,6 @@ if (typeof x !== "number") { } else { x; // number|E|V ->x : number | E | V +>x : number } diff --git a/tests/baselines/reference/typeGuardNesting.types b/tests/baselines/reference/typeGuardNesting.types index afd060284d95f..2b18e232412e9 100644 --- a/tests/baselines/reference/typeGuardNesting.types +++ b/tests/baselines/reference/typeGuardNesting.types @@ -34,7 +34,7 @@ if ((typeof strOrBool === 'boolean' && !strOrBool) || typeof strOrBool === 'stri >(typeof strOrBool === 'boolean') : boolean >typeof strOrBool === 'boolean' : boolean >typeof strOrBool : string ->strOrBool : boolean | string +>strOrBool : string | boolean >'boolean' : string >strOrBool : boolean >false : boolean @@ -56,7 +56,7 @@ if ((typeof strOrBool === 'boolean' && !strOrBool) || typeof strOrBool === 'stri >(typeof strOrBool !== 'string') : boolean >typeof strOrBool !== 'string' : boolean >typeof strOrBool : string ->strOrBool : boolean | string +>strOrBool : string | boolean >'string' : string >strOrBool : boolean >false : boolean @@ -94,7 +94,7 @@ if ((typeof strOrBool !== 'string' && !strOrBool) || typeof strOrBool !== 'boole >(typeof strOrBool === 'boolean') : boolean >typeof strOrBool === 'boolean' : boolean >typeof strOrBool : string ->strOrBool : boolean | string +>strOrBool : string | boolean >'boolean' : string >strOrBool : boolean >false : boolean @@ -116,7 +116,7 @@ if ((typeof strOrBool !== 'string' && !strOrBool) || typeof strOrBool !== 'boole >(typeof strOrBool !== 'string') : boolean >typeof strOrBool !== 'string' : boolean >typeof strOrBool : string ->strOrBool : boolean | string +>strOrBool : string | boolean >'string' : string >strOrBool : boolean >false : boolean diff --git a/tests/baselines/reference/typeGuardTautologicalConsistiency.types b/tests/baselines/reference/typeGuardTautologicalConsistiency.types index 256eaec829315..8e681deb07369 100644 --- a/tests/baselines/reference/typeGuardTautologicalConsistiency.types +++ b/tests/baselines/reference/typeGuardTautologicalConsistiency.types @@ -15,7 +15,7 @@ if (typeof stringOrNumber === "number") { >"number" : string stringOrNumber; ->stringOrNumber : nothing +>stringOrNumber : string } } @@ -31,6 +31,6 @@ if (typeof stringOrNumber === "number" && typeof stringOrNumber !== "number") { >"number" : string stringOrNumber; ->stringOrNumber : nothing +>stringOrNumber : string } diff --git a/tests/baselines/reference/typeGuardTypeOfUndefined.types b/tests/baselines/reference/typeGuardTypeOfUndefined.types index ac648d36f01e4..167d6204a9898 100644 --- a/tests/baselines/reference/typeGuardTypeOfUndefined.types +++ b/tests/baselines/reference/typeGuardTypeOfUndefined.types @@ -47,7 +47,7 @@ function test2(a: any) { >"boolean" : string a; ->a : nothing +>a : boolean } else { a; @@ -129,7 +129,7 @@ function test5(a: boolean | void) { } else { a; ->a : nothing +>a : void } } else { @@ -188,7 +188,7 @@ function test7(a: boolean | void) { } else { a; ->a : nothing +>a : void } } diff --git a/tests/baselines/reference/typeGuardsAsAssertions.js b/tests/baselines/reference/typeGuardsAsAssertions.js new file mode 100644 index 0000000000000..84fc996c3ab50 --- /dev/null +++ b/tests/baselines/reference/typeGuardsAsAssertions.js @@ -0,0 +1,229 @@ +//// [typeGuardsAsAssertions.ts] + +// Repro from #8513 + +let cond: boolean; + +export type Optional = Some | None; + +export interface None { readonly none: string; } +export interface Some { readonly some: a; } + +export const none : None = { none: '' }; + +export function isSome(value: Optional): value is Some { + return 'some' in value; +} + +function someFrom(some: a) { + return { some }; +} + +export function fn(makeSome: () => r): void { + let result: Optional = none; + result; // None + while (cond) { + result; // Some | None + result = someFrom(isSome(result) ? result.some : makeSome()); + result; // Some + } +} + +function foo1() { + let x: string | number | boolean = 0; + x; // number + while (cond) { + x; // number, then string | number + x = typeof x === "string" ? x.slice() : "abc"; + x; // string + } + x; +} + +function foo2() { + let x: string | number | boolean = 0; + x; // number + while (cond) { + x; // number, then string | number + if (typeof x === "string") { + x = x.slice(); + } + else { + x = "abc"; + } + x; // string + } + x; +} + +// Type guards as assertions + +function f1() { + let x: string | number | undefined = undefined; + x; // undefined + if (x) { + x; // string | number (guard as assertion) + } + x; // string | number | undefined +} + +function f2() { + let x: string | number | undefined = undefined; + x; // undefined + if (typeof x === "string") { + x; // string (guard as assertion) + } + x; // string | undefined +} + +function f3() { + let x: string | number | undefined = undefined; + x; // undefined + if (!x) { + return; + } + x; // string | number (guard as assertion) +} + +function f4() { + let x: string | number | undefined = undefined; + x; // undefined + if (typeof x === "boolean") { + x; // nothing (boolean not in declared type) + } + x; // undefined +} + +function f5(x: string | number) { + if (typeof x === "string" && typeof x === "number") { + x; // number (guard as assertion) + } + else { + x; // string | number + } + x; // string | number +} + +function f6() { + let x: string | undefined | null; + x!.slice(); + x = ""; + x!.slice(); + x = undefined; + x!.slice(); + x = null; + x!.slice(); + x = undefined; + x!.slice(); + x = ""; + x!.slice(); + x = ""; + x!.slice(); +} + + +//// [typeGuardsAsAssertions.js] +// Repro from #8513 +"use strict"; +var cond; +exports.none = { none: '' }; +function isSome(value) { + return 'some' in value; +} +exports.isSome = isSome; +function someFrom(some) { + return { some: some }; +} +function fn(makeSome) { + var result = exports.none; + result; // None + while (cond) { + result; // Some | None + result = someFrom(isSome(result) ? result.some : makeSome()); + result; // Some + } +} +exports.fn = fn; +function foo1() { + var x = 0; + x; // number + while (cond) { + x; // number, then string | number + x = typeof x === "string" ? x.slice() : "abc"; + x; // string + } + x; +} +function foo2() { + var x = 0; + x; // number + while (cond) { + x; // number, then string | number + if (typeof x === "string") { + x = x.slice(); + } + else { + x = "abc"; + } + x; // string + } + x; +} +// Type guards as assertions +function f1() { + var x = undefined; + x; // undefined + if (x) { + x; // string | number (guard as assertion) + } + x; // string | number | undefined +} +function f2() { + var x = undefined; + x; // undefined + if (typeof x === "string") { + x; // string (guard as assertion) + } + x; // string | undefined +} +function f3() { + var x = undefined; + x; // undefined + if (!x) { + return; + } + x; // string | number (guard as assertion) +} +function f4() { + var x = undefined; + x; // undefined + if (typeof x === "boolean") { + x; // nothing (boolean not in declared type) + } + x; // undefined +} +function f5(x) { + if (typeof x === "string" && typeof x === "number") { + x; // number (guard as assertion) + } + else { + x; // string | number + } + x; // string | number +} +function f6() { + var x; + x.slice(); + x = ""; + x.slice(); + x = undefined; + x.slice(); + x = null; + x.slice(); + x = undefined; + x.slice(); + x = ""; + x.slice(); + x = ""; + x.slice(); +} diff --git a/tests/baselines/reference/typeGuardsAsAssertions.symbols b/tests/baselines/reference/typeGuardsAsAssertions.symbols new file mode 100644 index 0000000000000..5079f3b8cb44a --- /dev/null +++ b/tests/baselines/reference/typeGuardsAsAssertions.symbols @@ -0,0 +1,315 @@ +=== tests/cases/conformance/controlFlow/typeGuardsAsAssertions.ts === + +// Repro from #8513 + +let cond: boolean; +>cond : Symbol(cond, Decl(typeGuardsAsAssertions.ts, 3, 3)) + +export type Optional = Some | None; +>Optional : Symbol(Optional, Decl(typeGuardsAsAssertions.ts, 3, 18)) +>a : Symbol(a, Decl(typeGuardsAsAssertions.ts, 5, 21)) +>Some : Symbol(Some, Decl(typeGuardsAsAssertions.ts, 7, 48)) +>a : Symbol(a, Decl(typeGuardsAsAssertions.ts, 5, 21)) +>None : Symbol(None, Decl(typeGuardsAsAssertions.ts, 5, 41)) + +export interface None { readonly none: string; } +>None : Symbol(None, Decl(typeGuardsAsAssertions.ts, 5, 41)) +>none : Symbol(None.none, Decl(typeGuardsAsAssertions.ts, 7, 23)) + +export interface Some { readonly some: a; } +>Some : Symbol(Some, Decl(typeGuardsAsAssertions.ts, 7, 48)) +>a : Symbol(a, Decl(typeGuardsAsAssertions.ts, 8, 22)) +>some : Symbol(Some.some, Decl(typeGuardsAsAssertions.ts, 8, 26)) +>a : Symbol(a, Decl(typeGuardsAsAssertions.ts, 8, 22)) + +export const none : None = { none: '' }; +>none : Symbol(none, Decl(typeGuardsAsAssertions.ts, 10, 12)) +>None : Symbol(None, Decl(typeGuardsAsAssertions.ts, 5, 41)) +>none : Symbol(none, Decl(typeGuardsAsAssertions.ts, 10, 28)) + +export function isSome(value: Optional): value is Some { +>isSome : Symbol(isSome, Decl(typeGuardsAsAssertions.ts, 10, 40)) +>a : Symbol(a, Decl(typeGuardsAsAssertions.ts, 12, 23)) +>value : Symbol(value, Decl(typeGuardsAsAssertions.ts, 12, 26)) +>Optional : Symbol(Optional, Decl(typeGuardsAsAssertions.ts, 3, 18)) +>a : Symbol(a, Decl(typeGuardsAsAssertions.ts, 12, 23)) +>value : Symbol(value, Decl(typeGuardsAsAssertions.ts, 12, 26)) +>Some : Symbol(Some, Decl(typeGuardsAsAssertions.ts, 7, 48)) +>a : Symbol(a, Decl(typeGuardsAsAssertions.ts, 12, 23)) + + return 'some' in value; +>value : Symbol(value, Decl(typeGuardsAsAssertions.ts, 12, 26)) +} + +function someFrom(some: a) { +>someFrom : Symbol(someFrom, Decl(typeGuardsAsAssertions.ts, 14, 1)) +>a : Symbol(a, Decl(typeGuardsAsAssertions.ts, 16, 18)) +>some : Symbol(some, Decl(typeGuardsAsAssertions.ts, 16, 21)) +>a : Symbol(a, Decl(typeGuardsAsAssertions.ts, 16, 18)) + + return { some }; +>some : Symbol(some, Decl(typeGuardsAsAssertions.ts, 17, 12)) +} + +export function fn(makeSome: () => r): void { +>fn : Symbol(fn, Decl(typeGuardsAsAssertions.ts, 18, 1)) +>r : Symbol(r, Decl(typeGuardsAsAssertions.ts, 20, 19)) +>makeSome : Symbol(makeSome, Decl(typeGuardsAsAssertions.ts, 20, 22)) +>r : Symbol(r, Decl(typeGuardsAsAssertions.ts, 20, 19)) + + let result: Optional = none; +>result : Symbol(result, Decl(typeGuardsAsAssertions.ts, 21, 7)) +>Optional : Symbol(Optional, Decl(typeGuardsAsAssertions.ts, 3, 18)) +>r : Symbol(r, Decl(typeGuardsAsAssertions.ts, 20, 19)) +>none : Symbol(none, Decl(typeGuardsAsAssertions.ts, 10, 12)) + + result; // None +>result : Symbol(result, Decl(typeGuardsAsAssertions.ts, 21, 7)) + + while (cond) { +>cond : Symbol(cond, Decl(typeGuardsAsAssertions.ts, 3, 3)) + + result; // Some | None +>result : Symbol(result, Decl(typeGuardsAsAssertions.ts, 21, 7)) + + result = someFrom(isSome(result) ? result.some : makeSome()); +>result : Symbol(result, Decl(typeGuardsAsAssertions.ts, 21, 7)) +>someFrom : Symbol(someFrom, Decl(typeGuardsAsAssertions.ts, 14, 1)) +>isSome : Symbol(isSome, Decl(typeGuardsAsAssertions.ts, 10, 40)) +>result : Symbol(result, Decl(typeGuardsAsAssertions.ts, 21, 7)) +>result.some : Symbol(Some.some, Decl(typeGuardsAsAssertions.ts, 8, 26)) +>result : Symbol(result, Decl(typeGuardsAsAssertions.ts, 21, 7)) +>some : Symbol(Some.some, Decl(typeGuardsAsAssertions.ts, 8, 26)) +>makeSome : Symbol(makeSome, Decl(typeGuardsAsAssertions.ts, 20, 22)) + + result; // Some +>result : Symbol(result, Decl(typeGuardsAsAssertions.ts, 21, 7)) + } +} + +function foo1() { +>foo1 : Symbol(foo1, Decl(typeGuardsAsAssertions.ts, 28, 1)) + + let x: string | number | boolean = 0; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 31, 7)) + + x; // number +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 31, 7)) + + while (cond) { +>cond : Symbol(cond, Decl(typeGuardsAsAssertions.ts, 3, 3)) + + x; // number, then string | number +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 31, 7)) + + x = typeof x === "string" ? x.slice() : "abc"; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 31, 7)) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 31, 7)) +>x.slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 31, 7)) +>slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) + + x; // string +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 31, 7)) + } + x; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 31, 7)) +} + +function foo2() { +>foo2 : Symbol(foo2, Decl(typeGuardsAsAssertions.ts, 39, 1)) + + let x: string | number | boolean = 0; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 42, 7)) + + x; // number +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 42, 7)) + + while (cond) { +>cond : Symbol(cond, Decl(typeGuardsAsAssertions.ts, 3, 3)) + + x; // number, then string | number +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 42, 7)) + + if (typeof x === "string") { +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 42, 7)) + + x = x.slice(); +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 42, 7)) +>x.slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 42, 7)) +>slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) + } + else { + x = "abc"; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 42, 7)) + } + x; // string +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 42, 7)) + } + x; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 42, 7)) +} + +// Type guards as assertions + +function f1() { +>f1 : Symbol(f1, Decl(typeGuardsAsAssertions.ts, 55, 1)) + + let x: string | number | undefined = undefined; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 60, 7)) +>undefined : Symbol(undefined) + + x; // undefined +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 60, 7)) + + if (x) { +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 60, 7)) + + x; // string | number (guard as assertion) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 60, 7)) + } + x; // string | number | undefined +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 60, 7)) +} + +function f2() { +>f2 : Symbol(f2, Decl(typeGuardsAsAssertions.ts, 66, 1)) + + let x: string | number | undefined = undefined; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 69, 7)) +>undefined : Symbol(undefined) + + x; // undefined +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 69, 7)) + + if (typeof x === "string") { +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 69, 7)) + + x; // string (guard as assertion) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 69, 7)) + } + x; // string | undefined +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 69, 7)) +} + +function f3() { +>f3 : Symbol(f3, Decl(typeGuardsAsAssertions.ts, 75, 1)) + + let x: string | number | undefined = undefined; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 78, 7)) +>undefined : Symbol(undefined) + + x; // undefined +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 78, 7)) + + if (!x) { +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 78, 7)) + + return; + } + x; // string | number (guard as assertion) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 78, 7)) +} + +function f4() { +>f4 : Symbol(f4, Decl(typeGuardsAsAssertions.ts, 84, 1)) + + let x: string | number | undefined = undefined; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 87, 7)) +>undefined : Symbol(undefined) + + x; // undefined +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 87, 7)) + + if (typeof x === "boolean") { +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 87, 7)) + + x; // nothing (boolean not in declared type) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 87, 7)) + } + x; // undefined +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 87, 7)) +} + +function f5(x: string | number) { +>f5 : Symbol(f5, Decl(typeGuardsAsAssertions.ts, 93, 1)) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 95, 12)) + + if (typeof x === "string" && typeof x === "number") { +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 95, 12)) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 95, 12)) + + x; // number (guard as assertion) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 95, 12)) + } + else { + x; // string | number +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 95, 12)) + } + x; // string | number +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 95, 12)) +} + +function f6() { +>f6 : Symbol(f6, Decl(typeGuardsAsAssertions.ts, 103, 1)) + + let x: string | undefined | null; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) + + x!.slice(); +>x!.slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) +>slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) + + x = ""; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) + + x!.slice(); +>x!.slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) +>slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) + + x = undefined; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) +>undefined : Symbol(undefined) + + x!.slice(); +>x!.slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) +>slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) + + x = null; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) + + x!.slice(); +>x!.slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) +>slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) + + x = undefined; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) +>undefined : Symbol(undefined) + + x!.slice(); +>x!.slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) +>slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) + + x = ""; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) + + x!.slice(); +>x!.slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) +>slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) + + x = ""; +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) + + x!.slice(); +>x!.slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(typeGuardsAsAssertions.ts, 106, 7)) +>slice : Symbol(String.slice, Decl(lib.d.ts, --, --)) +} + diff --git a/tests/baselines/reference/typeGuardsAsAssertions.types b/tests/baselines/reference/typeGuardsAsAssertions.types new file mode 100644 index 0000000000000..6ec84ada1f482 --- /dev/null +++ b/tests/baselines/reference/typeGuardsAsAssertions.types @@ -0,0 +1,385 @@ +=== tests/cases/conformance/controlFlow/typeGuardsAsAssertions.ts === + +// Repro from #8513 + +let cond: boolean; +>cond : boolean + +export type Optional = Some | None; +>Optional : Some | None +>a : a +>Some : Some +>a : a +>None : None + +export interface None { readonly none: string; } +>None : None +>none : string + +export interface Some { readonly some: a; } +>Some : Some +>a : a +>some : a +>a : a + +export const none : None = { none: '' }; +>none : None +>None : None +>{ none: '' } : { none: string; } +>none : string +>'' : string + +export function isSome(value: Optional): value is Some { +>isSome : (value: Some | None) => value is Some +>a : a +>value : Some | None +>Optional : Some | None +>a : a +>value : any +>Some : Some +>a : a + + return 'some' in value; +>'some' in value : boolean +>'some' : string +>value : Some | None +} + +function someFrom(some: a) { +>someFrom : (some: a) => { some: a; } +>a : a +>some : a +>a : a + + return { some }; +>{ some } : { some: a; } +>some : a +} + +export function fn(makeSome: () => r): void { +>fn : (makeSome: () => r) => void +>r : r +>makeSome : () => r +>r : r + + let result: Optional = none; +>result : Some | None +>Optional : Some | None +>r : r +>none : None + + result; // None +>result : None + + while (cond) { +>cond : boolean + + result; // Some | None +>result : None | Some + + result = someFrom(isSome(result) ? result.some : makeSome()); +>result = someFrom(isSome(result) ? result.some : makeSome()) : { some: r; } +>result : Some | None +>someFrom(isSome(result) ? result.some : makeSome()) : { some: r; } +>someFrom : (some: a) => { some: a; } +>isSome(result) ? result.some : makeSome() : r +>isSome(result) : boolean +>isSome : (value: Some | None) => value is Some +>result : None | Some +>result.some : r +>result : Some +>some : r +>makeSome() : r +>makeSome : () => r + + result; // Some +>result : Some + } +} + +function foo1() { +>foo1 : () => void + + let x: string | number | boolean = 0; +>x : string | number | boolean +>0 : number + + x; // number +>x : number + + while (cond) { +>cond : boolean + + x; // number, then string | number +>x : number | string + + x = typeof x === "string" ? x.slice() : "abc"; +>x = typeof x === "string" ? x.slice() : "abc" : string +>x : string | number | boolean +>typeof x === "string" ? x.slice() : "abc" : string +>typeof x === "string" : boolean +>typeof x : string +>x : number | string +>"string" : string +>x.slice() : string +>x.slice : (start?: number | undefined, end?: number | undefined) => string +>x : string +>slice : (start?: number | undefined, end?: number | undefined) => string +>"abc" : string + + x; // string +>x : string + } + x; +>x : number | string +} + +function foo2() { +>foo2 : () => void + + let x: string | number | boolean = 0; +>x : string | number | boolean +>0 : number + + x; // number +>x : number + + while (cond) { +>cond : boolean + + x; // number, then string | number +>x : number | string + + if (typeof x === "string") { +>typeof x === "string" : boolean +>typeof x : string +>x : number | string +>"string" : string + + x = x.slice(); +>x = x.slice() : string +>x : string | number | boolean +>x.slice() : string +>x.slice : (start?: number | undefined, end?: number | undefined) => string +>x : string +>slice : (start?: number | undefined, end?: number | undefined) => string + } + else { + x = "abc"; +>x = "abc" : string +>x : string | number | boolean +>"abc" : string + } + x; // string +>x : string + } + x; +>x : number | string +} + +// Type guards as assertions + +function f1() { +>f1 : () => void + + let x: string | number | undefined = undefined; +>x : string | number | undefined +>undefined : undefined + + x; // undefined +>x : undefined + + if (x) { +>x : undefined + + x; // string | number (guard as assertion) +>x : string | number + } + x; // string | number | undefined +>x : string | number | undefined +} + +function f2() { +>f2 : () => void + + let x: string | number | undefined = undefined; +>x : string | number | undefined +>undefined : undefined + + x; // undefined +>x : undefined + + if (typeof x === "string") { +>typeof x === "string" : boolean +>typeof x : string +>x : undefined +>"string" : string + + x; // string (guard as assertion) +>x : string + } + x; // string | undefined +>x : string | undefined +} + +function f3() { +>f3 : () => void + + let x: string | number | undefined = undefined; +>x : string | number | undefined +>undefined : undefined + + x; // undefined +>x : undefined + + if (!x) { +>!x : boolean +>x : undefined + + return; + } + x; // string | number (guard as assertion) +>x : string | number +} + +function f4() { +>f4 : () => void + + let x: string | number | undefined = undefined; +>x : string | number | undefined +>undefined : undefined + + x; // undefined +>x : undefined + + if (typeof x === "boolean") { +>typeof x === "boolean" : boolean +>typeof x : string +>x : undefined +>"boolean" : string + + x; // nothing (boolean not in declared type) +>x : nothing + } + x; // undefined +>x : undefined +} + +function f5(x: string | number) { +>f5 : (x: string | number) => void +>x : string | number + + if (typeof x === "string" && typeof x === "number") { +>typeof x === "string" && typeof x === "number" : boolean +>typeof x === "string" : boolean +>typeof x : string +>x : string | number +>"string" : string +>typeof x === "number" : boolean +>typeof x : string +>x : string +>"number" : string + + x; // number (guard as assertion) +>x : number + } + else { + x; // string | number +>x : number | string + } + x; // string | number +>x : number | string +} + +function f6() { +>f6 : () => void + + let x: string | undefined | null; +>x : string | null | undefined +>null : null + + x!.slice(); +>x!.slice() : string +>x!.slice : (start?: number | undefined, end?: number | undefined) => string +>x! : string +>x : string | null | undefined +>slice : (start?: number | undefined, end?: number | undefined) => string + + x = ""; +>x = "" : string +>x : string | null | undefined +>"" : string + + x!.slice(); +>x!.slice() : string +>x!.slice : (start?: number | undefined, end?: number | undefined) => string +>x! : string +>x : string +>slice : (start?: number | undefined, end?: number | undefined) => string + + x = undefined; +>x = undefined : undefined +>x : string | null | undefined +>undefined : undefined + + x!.slice(); +>x!.slice() : string +>x!.slice : (start?: number | undefined, end?: number | undefined) => string +>x! : string +>x : string | null | undefined +>slice : (start?: number | undefined, end?: number | undefined) => string + + x = null; +>x = null : null +>x : string | null | undefined +>null : null + + x!.slice(); +>x!.slice() : string +>x!.slice : (start?: number | undefined, end?: number | undefined) => string +>x! : string +>x : string | null | undefined +>slice : (start?: number | undefined, end?: number | undefined) => string + + x = undefined; +>x = undefined : null | undefined +>x : string | null | undefined +>undefined : null | undefined +>null : null +>undefined : undefined + + x!.slice(); +>x!.slice() : string +>x!.slice : (start?: number | undefined, end?: number | undefined) => string +>x! : string +>x : string | null | undefined +>slice : (start?: number | undefined, end?: number | undefined) => string + + x = ""; +>x = "" : string | undefined +>x : string | null | undefined +>"" : string | undefined +>"" : string + + x!.slice(); +>x!.slice() : string +>x!.slice : (start?: number | undefined, end?: number | undefined) => string +>x! : string +>x : string | undefined +>slice : (start?: number | undefined, end?: number | undefined) => string + + x = ""; +>x = "" : string | null +>x : string | null | undefined +>"" : string | null +>null : null +>"" : string + + x!.slice(); +>x!.slice() : string +>x!.slice : (start?: number | undefined, end?: number | undefined) => string +>x! : string +>x : string | null +>slice : (start?: number | undefined, end?: number | undefined) => string +} + diff --git a/tests/cases/conformance/controlFlow/typeGuardsAsAssertions.ts b/tests/cases/conformance/controlFlow/typeGuardsAsAssertions.ts new file mode 100644 index 0000000000000..912eaa64e80de --- /dev/null +++ b/tests/cases/conformance/controlFlow/typeGuardsAsAssertions.ts @@ -0,0 +1,122 @@ +// @strictNullChecks: true + +// Repro from #8513 + +let cond: boolean; + +export type Optional = Some | None; + +export interface None { readonly none: string; } +export interface Some { readonly some: a; } + +export const none : None = { none: '' }; + +export function isSome(value: Optional): value is Some { + return 'some' in value; +} + +function someFrom(some: a) { + return { some }; +} + +export function fn(makeSome: () => r): void { + let result: Optional = none; + result; // None + while (cond) { + result; // Some | None + result = someFrom(isSome(result) ? result.some : makeSome()); + result; // Some + } +} + +function foo1() { + let x: string | number | boolean = 0; + x; // number + while (cond) { + x; // number, then string | number + x = typeof x === "string" ? x.slice() : "abc"; + x; // string + } + x; +} + +function foo2() { + let x: string | number | boolean = 0; + x; // number + while (cond) { + x; // number, then string | number + if (typeof x === "string") { + x = x.slice(); + } + else { + x = "abc"; + } + x; // string + } + x; +} + +// Type guards as assertions + +function f1() { + let x: string | number | undefined = undefined; + x; // undefined + if (x) { + x; // string | number (guard as assertion) + } + x; // string | number | undefined +} + +function f2() { + let x: string | number | undefined = undefined; + x; // undefined + if (typeof x === "string") { + x; // string (guard as assertion) + } + x; // string | undefined +} + +function f3() { + let x: string | number | undefined = undefined; + x; // undefined + if (!x) { + return; + } + x; // string | number (guard as assertion) +} + +function f4() { + let x: string | number | undefined = undefined; + x; // undefined + if (typeof x === "boolean") { + x; // nothing (boolean not in declared type) + } + x; // undefined +} + +function f5(x: string | number) { + if (typeof x === "string" && typeof x === "number") { + x; // number (guard as assertion) + } + else { + x; // string | number + } + x; // string | number +} + +function f6() { + let x: string | undefined | null; + x!.slice(); + x = ""; + x!.slice(); + x = undefined; + x!.slice(); + x = null; + x!.slice(); + x = undefined; + x!.slice(); + x = ""; + x!.slice(); + x = ""; + x!.slice(); +}