Skip to content

Commit 89506c1

Browse files
committed
Merge pull request #8548 from Microsoft/typeGuardAsAssertion
Type guards as assertions
2 parents b68e939 + 9f30d9f commit 89506c1

11 files changed

+1099
-44
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -696,23 +696,6 @@ namespace ts {
696696
};
697697
}
698698

699-
function skipSimpleConditionalFlow(flow: FlowNode) {
700-
// We skip over simple conditional flows of the form 'x ? aaa : bbb', where 'aaa' and 'bbb' contain
701-
// no constructs that affect control flow type analysis. Such simple flows have no effect on the
702-
// code paths that follow and ignoring them means we'll do less work.
703-
if (flow.flags & FlowFlags.BranchLabel && (<FlowLabel>flow).antecedents.length === 2) {
704-
const a = (<FlowLabel>flow).antecedents[0];
705-
const b = (<FlowLabel>flow).antecedents[1];
706-
if ((a.flags & FlowFlags.TrueCondition && b.flags & FlowFlags.FalseCondition ||
707-
a.flags & FlowFlags.FalseCondition && b.flags & FlowFlags.TrueCondition) &&
708-
(<FlowCondition>a).antecedent === (<FlowCondition>b).antecedent &&
709-
(<FlowCondition>a).expression === (<FlowCondition>b).expression) {
710-
return (<FlowCondition>a).antecedent;
711-
}
712-
}
713-
return flow;
714-
}
715-
716699
function finishFlowLabel(flow: FlowLabel): FlowNode {
717700
const antecedents = flow.antecedents;
718701
if (!antecedents) {
@@ -721,7 +704,7 @@ namespace ts {
721704
if (antecedents.length === 1) {
722705
return antecedents[0];
723706
}
724-
return skipSimpleConditionalFlow(flow);
707+
return flow;
725708
}
726709

727710
function isStatementCondition(node: Node) {

src/compiler/checker.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ namespace ts {
122122
const unknownType = createIntrinsicType(TypeFlags.Any, "unknown");
123123

124124
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
125-
const emptyUnionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
125+
const nothingType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
126126
const emptyGenericType = <GenericType><ObjectType>createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
127127
emptyGenericType.instantiations = {};
128128

@@ -2030,7 +2030,7 @@ namespace ts {
20302030
writeUnionOrIntersectionType(<UnionOrIntersectionType>type, flags);
20312031
}
20322032
else if (type.flags & TypeFlags.Anonymous) {
2033-
if (type === emptyUnionType) {
2033+
if (type === nothingType) {
20342034
writer.writeKeyword("nothing");
20352035
}
20362036
else {
@@ -5006,7 +5006,7 @@ namespace ts {
50065006
if (type.flags & TypeFlags.Undefined) typeSet.containsUndefined = true;
50075007
if (type.flags & TypeFlags.Null) typeSet.containsNull = true;
50085008
}
5009-
else if (type !== emptyUnionType && !contains(typeSet, type)) {
5009+
else if (type !== nothingType && !contains(typeSet, type)) {
50105010
typeSet.push(type);
50115011
}
50125012
}
@@ -5047,7 +5047,10 @@ namespace ts {
50475047
// a named type that circularly references itself.
50485048
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
50495049
if (types.length === 0) {
5050-
return emptyUnionType;
5050+
return nothingType;
5051+
}
5052+
if (types.length === 1) {
5053+
return types[0];
50515054
}
50525055
const typeSet = [] as TypeSet;
50535056
addTypesToSet(typeSet, types, TypeFlags.Union);
@@ -5064,7 +5067,7 @@ namespace ts {
50645067
if (typeSet.length === 0) {
50655068
return typeSet.containsNull ? nullType :
50665069
typeSet.containsUndefined ? undefinedType :
5067-
emptyUnionType;
5070+
nothingType;
50685071
}
50695072
else if (typeSet.length === 1) {
50705073
return typeSet[0];
@@ -7483,7 +7486,7 @@ namespace ts {
74837486

74847487
function getTypeWithFacts(type: Type, include: TypeFacts) {
74857488
if (!(type.flags & TypeFlags.Union)) {
7486-
return getTypeFacts(type) & include ? type : emptyUnionType;
7489+
return getTypeFacts(type) & include ? type : nothingType;
74877490
}
74887491
let firstType: Type;
74897492
let types: Type[];
@@ -7500,7 +7503,7 @@ namespace ts {
75007503
}
75017504
}
75027505
}
7503-
return firstType ? types ? getUnionType(types, /*noSubtypeReduction*/ true) : firstType : emptyUnionType;
7506+
return firstType ? types ? getUnionType(types, /*noSubtypeReduction*/ true) : firstType : nothingType;
75047507
}
75057508

75067509
function getTypeWithDefault(type: Type, defaultExpression: Expression) {
@@ -7618,6 +7621,9 @@ namespace ts {
76187621
const visitedFlowStart = visitedFlowCount;
76197622
const result = getTypeAtFlowNode(reference.flowNode);
76207623
visitedFlowCount = visitedFlowStart;
7624+
if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull) === nothingType) {
7625+
return declaredType;
7626+
}
76217627
return result;
76227628

76237629
function getTypeAtFlowNode(flow: FlowNode): Type {
@@ -7702,7 +7708,22 @@ namespace ts {
77027708
}
77037709

77047710
function getTypeAtFlowCondition(flow: FlowCondition) {
7705-
return narrowType(getTypeAtFlowNode(flow.antecedent), flow.expression, (flow.flags & FlowFlags.TrueCondition) !== 0);
7711+
let type = getTypeAtFlowNode(flow.antecedent);
7712+
if (type !== nothingType) {
7713+
// If we have an antecedent type (meaning we're reachable in some way), we first
7714+
// attempt to narrow the antecedent type. If that produces the nothing type, then
7715+
// we take the type guard as an indication that control could reach here in a
7716+
// manner not understood by the control flow analyzer (e.g. a function argument
7717+
// has an invalid type, or a nested function has possibly made an assignment to a
7718+
// captured variable). We proceed by reverting to the declared type and then
7719+
// narrow that.
7720+
const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0;
7721+
type = narrowType(type, flow.expression, assumeTrue);
7722+
if (type === nothingType) {
7723+
type = narrowType(declaredType, flow.expression, assumeTrue);
7724+
}
7725+
}
7726+
return type;
77067727
}
77077728

77087729
function getTypeAtFlowBranchLabel(flow: FlowLabel) {
@@ -7720,7 +7741,7 @@ namespace ts {
77207741
antecedentTypes.push(type);
77217742
}
77227743
}
7723-
return antecedentTypes.length === 1 ? antecedentTypes[0] : getUnionType(antecedentTypes);
7744+
return getUnionType(antecedentTypes);
77247745
}
77257746

77267747
function getTypeAtFlowLoopLabel(flow: FlowLabel) {
@@ -7770,7 +7791,7 @@ namespace ts {
77707791
antecedentTypes.push(type);
77717792
}
77727793
}
7773-
return cache[key] = antecedentTypes.length === 1 ? antecedentTypes[0] : getUnionType(antecedentTypes);
7794+
return cache[key] = getUnionType(antecedentTypes);
77747795
}
77757796

77767797
function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
@@ -7921,7 +7942,7 @@ namespace ts {
79217942
const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type;
79227943
return isTypeAssignableTo(candidate, targetType) ? candidate :
79237944
isTypeAssignableTo(type, candidate) ? type :
7924-
emptyUnionType;
7945+
nothingType;
79257946
}
79267947

79277948
function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
@@ -14726,7 +14747,7 @@ namespace ts {
1472614747
arrayType = getUnionType(filter((arrayOrStringType as UnionType).types, t => !(t.flags & TypeFlags.StringLike)));
1472714748
}
1472814749
else if (arrayOrStringType.flags & TypeFlags.StringLike) {
14729-
arrayType = emptyUnionType;
14750+
arrayType = nothingType;
1473014751
}
1473114752
const hasStringConstituent = arrayOrStringType !== arrayType;
1473214753
let reportedError = false;
@@ -14738,7 +14759,7 @@ namespace ts {
1473814759

1473914760
// Now that we've removed all the StringLike types, if no constituents remain, then the entire
1474014761
// arrayOrStringType was a string.
14741-
if (arrayType === emptyUnionType) {
14762+
if (arrayType === nothingType) {
1474214763
return stringType;
1474314764
}
1474414765
}

tests/baselines/reference/controlFlowBinaryOrExpression.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ if (isNodeList(sourceObj)) {
8787
if (isHTMLCollection(sourceObj)) {
8888
>isHTMLCollection(sourceObj) : boolean
8989
>isHTMLCollection : (sourceObj: any) => sourceObj is HTMLCollection
90-
>sourceObj : { a: string; } | HTMLCollection
90+
>sourceObj : HTMLCollection | { a: string; }
9191

9292
sourceObj.length;
9393
>sourceObj.length : number
@@ -99,7 +99,7 @@ if (isNodeList(sourceObj) || isHTMLCollection(sourceObj)) {
9999
>isNodeList(sourceObj) || isHTMLCollection(sourceObj) : boolean
100100
>isNodeList(sourceObj) : boolean
101101
>isNodeList : (sourceObj: any) => sourceObj is NodeList
102-
>sourceObj : { a: string; } | HTMLCollection
102+
>sourceObj : HTMLCollection | { a: string; }
103103
>isHTMLCollection(sourceObj) : boolean
104104
>isHTMLCollection : (sourceObj: any) => sourceObj is HTMLCollection
105105
>sourceObj : { a: string; }

tests/baselines/reference/typeGuardEnums.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ else {
2727
if (typeof x !== "number") {
2828
>typeof x !== "number" : boolean
2929
>typeof x : string
30-
>x : number | string | E | V
30+
>x : number | string
3131
>"number" : string
3232

3333
x; // string
3434
>x : string
3535
}
3636
else {
3737
x; // number|E|V
38-
>x : number | E | V
38+
>x : number
3939
}
4040

tests/baselines/reference/typeGuardNesting.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ if ((typeof strOrBool === 'boolean' && !strOrBool) || typeof strOrBool === 'stri
3434
>(typeof strOrBool === 'boolean') : boolean
3535
>typeof strOrBool === 'boolean' : boolean
3636
>typeof strOrBool : string
37-
>strOrBool : boolean | string
37+
>strOrBool : string | boolean
3838
>'boolean' : string
3939
>strOrBool : boolean
4040
>false : boolean
@@ -56,7 +56,7 @@ if ((typeof strOrBool === 'boolean' && !strOrBool) || typeof strOrBool === 'stri
5656
>(typeof strOrBool !== 'string') : boolean
5757
>typeof strOrBool !== 'string' : boolean
5858
>typeof strOrBool : string
59-
>strOrBool : boolean | string
59+
>strOrBool : string | boolean
6060
>'string' : string
6161
>strOrBool : boolean
6262
>false : boolean
@@ -94,7 +94,7 @@ if ((typeof strOrBool !== 'string' && !strOrBool) || typeof strOrBool !== 'boole
9494
>(typeof strOrBool === 'boolean') : boolean
9595
>typeof strOrBool === 'boolean' : boolean
9696
>typeof strOrBool : string
97-
>strOrBool : boolean | string
97+
>strOrBool : string | boolean
9898
>'boolean' : string
9999
>strOrBool : boolean
100100
>false : boolean
@@ -116,7 +116,7 @@ if ((typeof strOrBool !== 'string' && !strOrBool) || typeof strOrBool !== 'boole
116116
>(typeof strOrBool !== 'string') : boolean
117117
>typeof strOrBool !== 'string' : boolean
118118
>typeof strOrBool : string
119-
>strOrBool : boolean | string
119+
>strOrBool : string | boolean
120120
>'string' : string
121121
>strOrBool : boolean
122122
>false : boolean

tests/baselines/reference/typeGuardTautologicalConsistiency.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ if (typeof stringOrNumber === "number") {
1515
>"number" : string
1616

1717
stringOrNumber;
18-
>stringOrNumber : nothing
18+
>stringOrNumber : string
1919
}
2020
}
2121

@@ -31,6 +31,6 @@ if (typeof stringOrNumber === "number" && typeof stringOrNumber !== "number") {
3131
>"number" : string
3232

3333
stringOrNumber;
34-
>stringOrNumber : nothing
34+
>stringOrNumber : string
3535
}
3636

tests/baselines/reference/typeGuardTypeOfUndefined.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function test2(a: any) {
4747
>"boolean" : string
4848

4949
a;
50-
>a : nothing
50+
>a : boolean
5151
}
5252
else {
5353
a;
@@ -129,7 +129,7 @@ function test5(a: boolean | void) {
129129
}
130130
else {
131131
a;
132-
>a : nothing
132+
>a : void
133133
}
134134
}
135135
else {
@@ -188,7 +188,7 @@ function test7(a: boolean | void) {
188188
}
189189
else {
190190
a;
191-
>a : nothing
191+
>a : void
192192
}
193193
}
194194

0 commit comments

Comments
 (0)