Skip to content

Commit d221e6b

Browse files
committed
optimise and support switch typeof
1 parent d2c3d15 commit d221e6b

21 files changed

+1111
-160
lines changed

src/compiler/checker.ts

Lines changed: 57 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -19725,6 +19725,11 @@ namespace ts {
1972519725
return TypeFacts.All;
1972619726
}
1972719727

19728+
/**
19729+
* It might work differently from what you think.
19730+
* For example, fact1 is "it is number1", fact2 is "it is not number1". Now given a number and facts including both fact1 and fact2,
19731+
* if you get correct facts from the number, then all numbers meets the condition, for 1 meets facts1, and others meets fact2.
19732+
*/
1972819733
function getTypeWithFacts(type: Type, include: TypeFacts) {
1972919734
return filterType(type, t => (getTypeFacts(t) & include) !== 0);
1973019735
}
@@ -20614,7 +20619,7 @@ namespace ts {
2061420619
if (isMatchingReference(reference, expr)) {
2061520620
type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
2061620621
}
20617-
else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
20622+
else if (expr.kind === SyntaxKind.TypeOfExpression){// && isMatchingReference(reference, (expr as TypeOfExpression).expression)) {
2061820623
type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
2061920624
}
2062020625
else {
@@ -20623,10 +20628,10 @@ namespace ts {
2062320628
type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd,
2062420629
t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never)));
2062520630
}
20626-
else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) {
20627-
type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd,
20628-
t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (<StringLiteralType>t).value === "undefined"));
20629-
}
20631+
// else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) {
20632+
// type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd,
20633+
// t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (<StringLiteralType>t).value === "undefined"));
20634+
// }
2063020635
}
2063120636
if (isMatchingReferenceDiscriminant(expr, type)) {
2063220637
type = narrowTypeByDiscriminant(type, expr as AccessExpression,
@@ -21150,9 +21155,10 @@ namespace ts {
2115021155

2115121156
const nonCallExpressionWithOutKeyword = expressionWithOutKeyword;
2115221157
// getTypeOfNode
21153-
// check some condition, if not meet, it means we could not handle this confition.
21158+
// check some condition, if not meet, it means we could not handle this confition.
2115421159

2115521160
if ((nonCallExpressionWithOutKeyword.kind !== SyntaxKind.Identifier && !isAccessExpression(nonCallExpressionWithOutKeyword))) {// || (<AccessExpression>expressionWithOutKeyword).expression.kind === SyntaxKind.ThisKeyword) {
21161+
console.log("Error1\n");
2115621162
return undefined;
2115721163
}
2115821164
const depth = getTypeDepthIfMatch(nonCallExpressionWithOutKeyword, type);
@@ -21162,9 +21168,10 @@ namespace ts {
2116221168
const propertyPaths = getPropertyPathsOfAccessExpression(<AccessExpression>nonCallExpressionWithOutKeyword, depth);
2116321169
if (!propertyPaths) {
2116421170
// Not expected here should return. But for the situation not considered, I add this.
21171+
console.log("Error2\n");
2116521172
return undefined;
2116621173
}
21167-
const propertyTypeArray = type.types.map(type => getPropertyTypeFromReferenceAccordingToPath(type, propertyPaths!, callExpressionFlag));
21174+
const propertyTypeArray = type.types.map(type => getPropertyTypeFromReferenceAccordingToPath(type, propertyPaths, callExpressionFlag));
2116821175
// if propertyTypeArray has unedfined value, it means sometype in the union type could not reach the path.
2116921176
// This should be an error which is not handled by this function, and we just not continue filter tyopes.
2117021177
if (propertyTypeArray.some(type => !type)) {
@@ -21193,47 +21200,37 @@ namespace ts {
2119321200
if (!isMatchingReference(reference, target)) {
2119421201
if (type.flags & TypeFlags.Union) {
2119521202
let propertyTypeArray: Type[] | undefined;
21196-
let secondFilter = false;
21203+
let notNullOrUndefinedFilter = false; // the aim of this filter is type has 'undefined',filter it out from result.
2119721204
const isExpressionContainOptionalChain = isAccessExpressionContainOptionalChain(typeOfExpr.expression);
2119821205
// ~undefined means other values except undefiend. boolean, bigint....
21199-
if (assumeTrue && literal.text !== "undefined") {
21206+
if ((assumeTrue && literal.text !== "undefined") || (!assumeTrue && literal.text === "undefined")) {
21207+
// !== undefined
2120021208
// === ~undefined
2120121209
// use full expression to narrow
21202-
propertyTypeArray = narrowUnionTypeWithPropertyPathAndExpression(<UnionType>type, typeOfExpr.expression, false);
21203-
secondFilter = true; // the aim of this filter is type has 'undefined',filter it out from result.
21210+
propertyTypeArray = narrowUnionTypeWithPropertyPathAndExpression(<UnionType>type, typeOfExpr.expression,/* optionalChainSlice */ false);
21211+
notNullOrUndefinedFilter = true;
2120421212
}
2120521213
else {
21206-
// !== ~undefined, === undefined, !==undefined
21214+
// !== ~undefined, === undefined
2120721215
// use non-OptionalChain part to narrow
21208-
propertyTypeArray = narrowUnionTypeWithPropertyPathAndExpression(<UnionType>type, typeOfExpr.expression, true);
21209-
if (!assumeTrue && literal.text === "undefined") {
21210-
// !==undefined
21211-
secondFilter = true;
21212-
}
21216+
propertyTypeArray = narrowUnionTypeWithPropertyPathAndExpression(<UnionType>type, typeOfExpr.expression,/* optionalChainSlice */ true);
2121321217
if (isExpressionContainOptionalChain) {
21214-
facts = TypeFacts.All;
21218+
facts = TypeFacts.None; // The aim is no filter.
2121521219
}
2121621220
}
2121721221
if (!propertyTypeArray) {
2121821222
return type;
2121921223
}
2122021224
const tmp2 = propertyTypeArray.map(propertyType => {
21221-
const filteredType = getTypeWithFacts(propertyType, facts);
21222-
const filteredType2 = getTypeWithFacts(propertyType, TypeFacts.NEUndefined);
21223-
const filteredType3 = getTypeWithFacts(propertyType, TypeFacts.NENull);
21224-
if (secondFilter) {
21225-
if ((filteredType.flags & TypeFlags.Never) || (filteredType2.flags & TypeFlags.Never) || (filteredType3.flags & TypeFlags.Never)) {
21226-
return false;
21227-
}
21228-
else {
21229-
return true;
21230-
}
21225+
const propertyTypeFacts = getTypeFacts(propertyType);
21226+
if (notNullOrUndefinedFilter) {
21227+
facts |= TypeFacts.NEUndefined | TypeFacts.NENull;
2123121228
}
21232-
if (filteredType.flags & TypeFlags.Never) {
21233-
return false;
21229+
if ((propertyTypeFacts & facts) === facts) {
21230+
return true;
2123421231
}
2123521232
else {
21236-
return true;
21233+
return false;
2123721234
}
2123821235
});
2123921236
const result = (<UnionType>type).types.filter((_t, index) => {
@@ -21397,55 +21394,53 @@ namespace ts {
2139721394
*/
2139821395
const tmpExpression = (<TypeOfExpression>switchStatement.expression).expression;
2139921396
const impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofGuard(type, text) || type)), switchFacts);
21400-
if (isMatchingReference(reference, tmpExpression)){
21397+
if (isMatchingReference(reference, tmpExpression)) {
2140121398
if (hasDefaultClause) {
2140221399
return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts);
2140321400
}
2140421401
return getTypeWithFacts(mapType(type, narrowUnionMemberByTypeof(impliedType)), switchFacts);
2140521402
}
2140621403
let propertyTypeArray: Type[] | undefined;
21407-
let secondFilter = false;
21404+
let notNullOrUndefinedFilter = false;
2140821405
const isExpressionContainOptionalChain = isAccessExpressionContainOptionalChain(tmpExpression);
2140921406
let facts = switchFacts;
21407+
2141021408
// ~undefined means other values except undefiend. boolean, bigint....
21411-
if (switchFacts & TypeFacts.NEUndefined) {
21412-
// === ~undefined
21409+
if (facts & 0b111111 && !(facts & TypeFacts.EQUndefined)) {
21410+
// === ~undefined and not === undefiend
2141321411
// use full expression to narrow
21414-
propertyTypeArray = narrowUnionTypeWithPropertyPathAndExpression(<UnionType>type, tmpExpression, false);
21415-
secondFilter = true; // the aim of this filter is type has 'undefined',filter it out from result.
21412+
propertyTypeArray = narrowUnionTypeWithPropertyPathAndExpression(<UnionType>type, tmpExpression, /* optionalChainSlice */ false);
21413+
notNullOrUndefinedFilter = true;
2141621414
}
2141721415
else {
2141821416
// !== ~undefined, === undefined, !==undefined
2141921417
// use non-OptionalChain part to narrow
21420-
propertyTypeArray = narrowUnionTypeWithPropertyPathAndExpression(<UnionType>type, tmpExpression, true);
21418+
propertyTypeArray = narrowUnionTypeWithPropertyPathAndExpression(<UnionType>type, tmpExpression, /* optionalChainSlice */ true);
21419+
if (facts & TypeFacts.NEUndefined) {
21420+
// !== undefined
21421+
notNullOrUndefinedFilter = true;
21422+
}
2142121423
if (isExpressionContainOptionalChain) {
21422-
facts = TypeFacts.All;
21424+
facts = TypeFacts.All; // The aim is no filter.
2142321425
}
2142421426
}
2142521427
if (!propertyTypeArray) {
21426-
return type;
21428+
return type; // return type;
2142721429
}
2142821430
const tmp2 = propertyTypeArray.map(propertyType => {
21429-
let filteredType;
21430-
if (hasDefaultClause) {
21431-
filteredType = filterType(propertyType, t => (getTypeFacts(t) & switchFacts) === switchFacts);
21432-
} else {
21433-
filteredType = getTypeWithFacts(mapType(propertyType, narrowUnionMemberByTypeof(impliedType)), facts);
21431+
const propertyTypeFacts = getTypeFacts(propertyType);
21432+
let secondFacts: TypeFacts;
21433+
if (notNullOrUndefinedFilter) {
21434+
secondFacts = TypeFacts.NEUndefinedOrNull;
2143421435
}
21435-
const filteredType2 = getTypeWithFacts(propertyType, TypeFacts.NEUndefined);
21436-
if (secondFilter) {
21437-
if ((filteredType.flags & TypeFlags.Never) || (filteredType2.flags & TypeFlags.Never)) {
21438-
return false;
21439-
}
21440-
else {
21441-
return true;
21442-
}
21436+
else {
21437+
secondFacts = TypeFacts.None; // The aim is no filter.
2144321438
}
21444-
if (filteredType.flags & TypeFlags.Never) {
21445-
return false;
21439+
if ((propertyTypeFacts & facts) && (propertyTypeFacts & secondFacts) === secondFacts) {
21440+
return true;
2144621441
}
2144721442
else {
21448-
return true;
21443+
return false;
2144921444
}
2145021445
});
2145121446
const result = (<UnionType>type).types.filter((_t, index) => {
@@ -29675,12 +29670,12 @@ namespace ts {
2967529670
}
2967629671
// If a type has been cached for the node, return it.
2967729672
// Note: this is not only cache, without this, some test case would always runs, such as binaryArithmeticControlFlowGraphNotTooLarge.
29678-
// if (node.flags & NodeFlags.TypeCached && flowTypeCache) {
29679-
// const cachedType = flowTypeCache[getNodeId(node)];
29680-
// if (cachedType) {
29681-
// return cachedType;
29682-
// }
29683-
// }
29673+
if (node.flags & NodeFlags.TypeCached && flowTypeCache) {
29674+
const cachedType = flowTypeCache[getNodeId(node)];
29675+
if (cachedType) {
29676+
return cachedType;
29677+
}
29678+
}
2968429679
const startInvocationCount = flowInvocationCount;
2968529680
const type = checkExpression(node);
2968629681
// If control flow analysis was required to determine the type, it is worth caching.

tests/baselines/reference/controlFlowOptionalChain.errors.txt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,10 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(463,9): error TS
5858
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(498,13): error TS2532: Object is possibly 'undefined'.
5959
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(501,13): error TS2532: Object is possibly 'undefined'.
6060
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(515,13): error TS2532: Object is possibly 'undefined'.
61-
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(518,13): error TS2532: Object is possibly 'undefined'.
6261
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(567,21): error TS2532: Object is possibly 'undefined'.
6362

6463

65-
==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (62 errors) ====
64+
==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (61 errors) ====
6665
// assignments in shortcutting chain
6766
declare const o: undefined | {
6867
[key: string]: any;
@@ -700,9 +699,7 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(567,21): error T
700699
!!! error TS2532: Object is possibly 'undefined'.
701700
break;
702701
default:
703-
o.foo; // Error
704-
~
705-
!!! error TS2532: Object is possibly 'undefined'.
702+
o.foo;
706703
break;
707704
}
708705
}

tests/baselines/reference/controlFlowOptionalChain.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ function f41(o: Thing | undefined) {
516516
o.foo; // Error
517517
break;
518518
default:
519-
o.foo; // Error
519+
o.foo;
520520
break;
521521
}
522522
}
@@ -1049,7 +1049,7 @@ function f41(o) {
10491049
o.foo; // Error
10501050
break;
10511051
default:
1052-
o.foo; // Error
1052+
o.foo;
10531053
break;
10541054
}
10551055
}

tests/baselines/reference/controlFlowOptionalChain.symbols

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1642,7 +1642,7 @@ function f41(o: Thing | undefined) {
16421642

16431643
break;
16441644
default:
1645-
o.foo; // Error
1645+
o.foo;
16461646
>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14))
16471647
>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 505, 13))
16481648
>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14))

tests/baselines/reference/controlFlowOptionalChain.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1858,9 +1858,9 @@ function f41(o: Thing | undefined) {
18581858

18591859
break;
18601860
default:
1861-
o.foo; // Error
1861+
o.foo;
18621862
>o.foo : never
1863-
>o : Thing | undefined
1863+
>o : Thing
18641864
>foo : never
18651865

18661866
break;

tests/baselines/reference/nonNullReferenceMatching.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ class Component {
4949
>this.props.thumbYProps!.elementRef(ref) : void
5050
>this.props.thumbYProps!.elementRef : ElementRef
5151
>this.props.thumbYProps! : ThumbProps
52-
>this.props.thumbYProps : ThumbProps | undefined
52+
>this.props.thumbYProps : ThumbProps
5353
>this.props : ComponentProps
5454
>this : this
5555
>props : ComponentProps
56-
>thumbYProps : ThumbProps | undefined
56+
>thumbYProps : ThumbProps
5757
>elementRef : ElementRef
5858
>ref : HTMLElement | null
5959

tests/baselines/reference/typeGuardAccordingToProperty.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ function f1_(u: Union1) {
269269

270270

271271
//// [typeGuardAccordingToProperty.js]
272+
"use strict";
272273
// Primitive value ---- boolean bigint number string symbol undefined function object
273274
// ts special type ---- any, void, unknown, union, intersection
274275
;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//// [typeGuardAccordingToPropertyBySwitch.ts]
2+
interface Foo1 {
3+
firstKey: number,
4+
inner: {
5+
secondKey: number,
6+
f1: number
7+
}
8+
}
9+
10+
interface Foo2 {
11+
firstKey: boolean,
12+
inner: {
13+
secondKey: boolean,
14+
f2: number
15+
}
16+
}
17+
18+
interface Foo3 {
19+
firstKey: string;
20+
}
21+
22+
type Union = Foo1 | Foo2 | Foo3;
23+
24+
function f(u: Union) {
25+
switch (typeof u.firstKey){
26+
case 'number':
27+
case 'boolean':
28+
u;
29+
switch(typeof u.inner.secondKey){
30+
case 'boolean':
31+
u;
32+
}
33+
}
34+
}
35+
36+
type Union2 = Foo1 | Foo2;
37+
38+
function f2(u: Union2) {
39+
switch(typeof u.inner["secondKey"]){
40+
case 'boolean':
41+
u;
42+
case 'bigint':
43+
u;
44+
default:
45+
u;
46+
}
47+
}
48+
49+
50+
//// [typeGuardAccordingToPropertyBySwitch.js]
51+
"use strict";
52+
function f(u) {
53+
switch (typeof u.firstKey) {
54+
case 'number':
55+
case 'boolean':
56+
u;
57+
switch (typeof u.inner.secondKey) {
58+
case 'boolean':
59+
u;
60+
}
61+
}
62+
}
63+
function f2(u) {
64+
switch (typeof u.inner["secondKey"]) {
65+
case 'boolean':
66+
u;
67+
case 'bigint':
68+
u;
69+
default:
70+
u;
71+
}
72+
}

0 commit comments

Comments
 (0)