Skip to content

Commit 40327b3

Browse files
authored
Merge pull request #11001 from Microsoft/silentNever
Fix control flow loop analysis with incomplete types
2 parents 40ea667 + 7980489 commit 40327b3

File tree

5 files changed

+254
-18
lines changed

5 files changed

+254
-18
lines changed

src/compiler/checker.ts

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ namespace ts {
132132
const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol");
133133
const voidType = createIntrinsicType(TypeFlags.Void, "void");
134134
const neverType = createIntrinsicType(TypeFlags.Never, "never");
135+
const silentNeverType = createIntrinsicType(TypeFlags.Never, "never");
135136

136137
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
137138
const emptyGenericType = <GenericType><ObjectType>createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
@@ -147,6 +148,7 @@ namespace ts {
147148
const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
148149
const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
149150
const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
151+
const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
150152

151153
const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true);
152154

@@ -8073,8 +8075,11 @@ namespace ts {
80738075
// we remove type string.
80748076
function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) {
80758077
if (declaredType !== assignedType) {
8078+
if (assignedType.flags & TypeFlags.Never) {
8079+
return assignedType;
8080+
}
80768081
const reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
8077-
if (reducedType !== neverType) {
8082+
if (!(reducedType.flags & TypeFlags.Never)) {
80788083
return reducedType;
80798084
}
80808085
}
@@ -8354,7 +8359,7 @@ namespace ts {
83548359
const visitedFlowStart = visitedFlowCount;
83558360
const result = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode));
83568361
visitedFlowCount = visitedFlowStart;
8357-
if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull) === neverType) {
8362+
if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) {
83588363
return declaredType;
83598364
}
83608365
return result;
@@ -8443,17 +8448,18 @@ namespace ts {
84438448
function getTypeAtFlowCondition(flow: FlowCondition): FlowType {
84448449
const flowType = getTypeAtFlowNode(flow.antecedent);
84458450
let type = getTypeFromFlowType(flowType);
8446-
if (type !== neverType) {
8451+
if (!(type.flags & TypeFlags.Never)) {
84478452
// If we have an antecedent type (meaning we're reachable in some way), we first
84488453
// attempt to narrow the antecedent type. If that produces the never type, and if
84498454
// the antecedent type is incomplete (i.e. a transient type in a loop), then we
84508455
// take the type guard as an indication that control *could* reach here once we
8451-
// have the complete type. We proceed by reverting to the declared type and then
8452-
// narrow that.
8456+
// have the complete type. We proceed by switching to the silent never type which
8457+
// doesn't report errors when operators are applied to it. Note that this is the
8458+
// *only* place a silent never type is ever generated.
84538459
const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0;
84548460
type = narrowType(type, flow.expression, assumeTrue);
8455-
if (type === neverType && isIncomplete(flowType)) {
8456-
type = narrowType(declaredType, flow.expression, assumeTrue);
8461+
if (type.flags & TypeFlags.Never && isIncomplete(flowType)) {
8462+
type = silentNeverType;
84578463
}
84588464
}
84598465
return createFlowType(type, isIncomplete(flowType));
@@ -8662,7 +8668,7 @@ namespace ts {
86628668
}
86638669
if (assumeTrue) {
86648670
const narrowedType = filterType(type, t => areTypesComparable(t, valueType));
8665-
return narrowedType !== neverType ? narrowedType : type;
8671+
return narrowedType.flags & TypeFlags.Never ? type : narrowedType;
86668672
}
86678673
return isUnitType(valueType) ? filterType(type, t => t !== valueType) : type;
86688674
}
@@ -8705,12 +8711,12 @@ namespace ts {
87058711
const clauseTypes = switchTypes.slice(clauseStart, clauseEnd);
87068712
const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType);
87078713
const discriminantType = getUnionType(clauseTypes);
8708-
const caseType = discriminantType === neverType ? neverType : filterType(type, t => isTypeComparableTo(discriminantType, t));
8714+
const caseType = discriminantType.flags & TypeFlags.Never ? neverType : filterType(type, t => isTypeComparableTo(discriminantType, t));
87098715
if (!hasDefaultClause) {
87108716
return caseType;
87118717
}
87128718
const defaultType = filterType(type, t => !(isUnitType(t) && contains(switchTypes, t)));
8713-
return caseType === neverType ? defaultType : getUnionType([caseType, defaultType]);
8719+
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
87148720
}
87158721

87168722
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
@@ -8774,7 +8780,7 @@ namespace ts {
87748780
// the candidate type. If one or more constituents remain, return a union of those.
87758781
if (type.flags & TypeFlags.Union) {
87768782
const assignableType = filterType(type, t => isTypeInstanceOf(t, candidate));
8777-
if (assignableType !== neverType) {
8783+
if (!(assignableType.flags & TypeFlags.Never)) {
87788784
return assignableType;
87798785
}
87808786
}
@@ -10892,7 +10898,7 @@ namespace ts {
1089210898

1089310899
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) {
1089410900
const type = checkNonNullExpression(left);
10895-
if (isTypeAny(type)) {
10901+
if (isTypeAny(type) || type === silentNeverType) {
1089610902
return type;
1089710903
}
1089810904

@@ -11039,8 +11045,8 @@ namespace ts {
1103911045
const objectType = getApparentType(checkNonNullExpression(node.expression));
1104011046
const indexType = node.argumentExpression ? checkExpression(node.argumentExpression) : unknownType;
1104111047

11042-
if (objectType === unknownType) {
11043-
return unknownType;
11048+
if (objectType === unknownType || objectType === silentNeverType) {
11049+
return objectType;
1104411050
}
1104511051

1104611052
const isConstEnum = isConstEnumObjectType(objectType);
@@ -12090,6 +12096,9 @@ namespace ts {
1209012096
}
1209112097

1209212098
const funcType = checkNonNullExpression(node.expression);
12099+
if (funcType === silentNeverType) {
12100+
return silentNeverSignature;
12101+
}
1209312102
const apparentType = getApparentType(funcType);
1209412103

1209512104
if (apparentType === unknownType) {
@@ -12162,6 +12171,9 @@ namespace ts {
1216212171
}
1216312172

1216412173
let expressionType = checkNonNullExpression(node.expression);
12174+
if (expressionType === silentNeverType) {
12175+
return silentNeverSignature;
12176+
}
1216512177

1216612178
// If expressionType's apparent type(section 3.8.1) is an object type with one or
1216712179
// more construct signatures, the expression is processed in the same manner as a
@@ -12721,7 +12733,7 @@ namespace ts {
1272112733
// the native Promise<T> type by the caller.
1272212734
type = checkAwaitedType(type, func, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member);
1272312735
}
12724-
if (type === neverType) {
12736+
if (type.flags & TypeFlags.Never) {
1272512737
hasReturnOfTypeNever = true;
1272612738
}
1272712739
else if (!contains(aggregatedTypes, type)) {
@@ -12771,7 +12783,7 @@ namespace ts {
1277112783

1277212784
const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn;
1277312785

12774-
if (returnType === neverType) {
12786+
if (returnType && returnType.flags & TypeFlags.Never) {
1277512787
error(func.type, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point);
1277612788
}
1277712789
else if (returnType && !hasExplicitReturn) {
@@ -13027,6 +13039,9 @@ namespace ts {
1302713039

1302813040
function checkPrefixUnaryExpression(node: PrefixUnaryExpression): Type {
1302913041
const operandType = checkExpression(node.operand);
13042+
if (operandType === silentNeverType) {
13043+
return silentNeverType;
13044+
}
1303013045
if (node.operator === SyntaxKind.MinusToken && node.operand.kind === SyntaxKind.NumericLiteral) {
1303113046
return getLiteralTypeForText(TypeFlags.NumberLiteral, "" + -(<LiteralExpression>node.operand).text);
1303213047
}
@@ -13060,6 +13075,9 @@ namespace ts {
1306013075

1306113076
function checkPostfixUnaryExpression(node: PostfixUnaryExpression): Type {
1306213077
const operandType = checkExpression(node.operand);
13078+
if (operandType === silentNeverType) {
13079+
return silentNeverType;
13080+
}
1306313081
const ok = checkArithmeticOperandType(node.operand, getNonNullableType(operandType),
1306413082
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type);
1306513083
if (ok) {
@@ -13124,6 +13142,9 @@ namespace ts {
1312413142
}
1312513143

1312613144
function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type {
13145+
if (leftType === silentNeverType || rightType === silentNeverType) {
13146+
return silentNeverType;
13147+
}
1312713148
// TypeScript 1.0 spec (April 2014): 4.15.4
1312813149
// The instanceof operator requires the left operand to be of type Any, an object type, or a type parameter type,
1312913150
// and the right operand to be of type Any or a subtype of the 'Function' interface type.
@@ -13140,6 +13161,9 @@ namespace ts {
1314013161
}
1314113162

1314213163
function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type {
13164+
if (leftType === silentNeverType || rightType === silentNeverType) {
13165+
return silentNeverType;
13166+
}
1314313167
// TypeScript 1.0 spec (April 2014): 4.15.5
1314413168
// The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type,
1314513169
// and the right operand to be of type Any, an object type, or a type parameter type.
@@ -13404,6 +13428,9 @@ namespace ts {
1340413428
case SyntaxKind.CaretEqualsToken:
1340513429
case SyntaxKind.AmpersandToken:
1340613430
case SyntaxKind.AmpersandEqualsToken:
13431+
if (leftType === silentNeverType || rightType === silentNeverType) {
13432+
return silentNeverType;
13433+
}
1340713434
// TypeScript 1.0 spec (April 2014): 4.19.1
1340813435
// These operators require their operands to be of type Any, the Number primitive type,
1340913436
// or an enum type. Operands of an enum type are treated
@@ -13436,6 +13463,9 @@ namespace ts {
1343613463
return numberType;
1343713464
case SyntaxKind.PlusToken:
1343813465
case SyntaxKind.PlusEqualsToken:
13466+
if (leftType === silentNeverType || rightType === silentNeverType) {
13467+
return silentNeverType;
13468+
}
1343913469
// TypeScript 1.0 spec (April 2014): 4.19.2
1344013470
// The binary + operator requires both operands to be of the Number primitive type or an enum type,
1344113471
// or at least one of the operands to be of type Any or the String primitive type.
@@ -16268,7 +16298,7 @@ namespace ts {
1626816298

1626916299
// Now that we've removed all the StringLike types, if no constituents remain, then the entire
1627016300
// arrayOrStringType was a string.
16271-
if (arrayType === neverType) {
16301+
if (arrayType.flags & TypeFlags.Never) {
1627216302
return stringType;
1627316303
}
1627416304
}
@@ -16329,7 +16359,7 @@ namespace ts {
1632916359
if (func) {
1633016360
const signature = getSignatureFromDeclaration(func);
1633116361
const returnType = getReturnTypeOfSignature(signature);
16332-
if (strictNullChecks || node.expression || returnType === neverType) {
16362+
if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) {
1633316363
const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType;
1633416364

1633516365
if (func.asteriskToken) {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//// [controlFlowWithIncompleteTypes.ts]
2+
// Repro from #11000
3+
4+
declare var cond: boolean;
5+
6+
function foo1() {
7+
let x: string | number | boolean = 0;
8+
while (cond) {
9+
if (typeof x === "string") {
10+
x = x.slice();
11+
}
12+
else {
13+
x = "abc";
14+
}
15+
}
16+
}
17+
18+
function foo2() {
19+
let x: string | number | boolean = 0;
20+
while (cond) {
21+
if (typeof x === "number") {
22+
x = "abc";
23+
}
24+
else {
25+
x = x.slice();
26+
}
27+
}
28+
}
29+
30+
//// [controlFlowWithIncompleteTypes.js]
31+
// Repro from #11000
32+
function foo1() {
33+
var x = 0;
34+
while (cond) {
35+
if (typeof x === "string") {
36+
x = x.slice();
37+
}
38+
else {
39+
x = "abc";
40+
}
41+
}
42+
}
43+
function foo2() {
44+
var x = 0;
45+
while (cond) {
46+
if (typeof x === "number") {
47+
x = "abc";
48+
}
49+
else {
50+
x = x.slice();
51+
}
52+
}
53+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
=== tests/cases/compiler/controlFlowWithIncompleteTypes.ts ===
2+
// Repro from #11000
3+
4+
declare var cond: boolean;
5+
>cond : Symbol(cond, Decl(controlFlowWithIncompleteTypes.ts, 2, 11))
6+
7+
function foo1() {
8+
>foo1 : Symbol(foo1, Decl(controlFlowWithIncompleteTypes.ts, 2, 26))
9+
10+
let x: string | number | boolean = 0;
11+
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 5, 7))
12+
13+
while (cond) {
14+
>cond : Symbol(cond, Decl(controlFlowWithIncompleteTypes.ts, 2, 11))
15+
16+
if (typeof x === "string") {
17+
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 5, 7))
18+
19+
x = x.slice();
20+
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 5, 7))
21+
>x.slice : Symbol(String.slice, Decl(lib.d.ts, --, --))
22+
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 5, 7))
23+
>slice : Symbol(String.slice, Decl(lib.d.ts, --, --))
24+
}
25+
else {
26+
x = "abc";
27+
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 5, 7))
28+
}
29+
}
30+
}
31+
32+
function foo2() {
33+
>foo2 : Symbol(foo2, Decl(controlFlowWithIncompleteTypes.ts, 14, 1))
34+
35+
let x: string | number | boolean = 0;
36+
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 17, 7))
37+
38+
while (cond) {
39+
>cond : Symbol(cond, Decl(controlFlowWithIncompleteTypes.ts, 2, 11))
40+
41+
if (typeof x === "number") {
42+
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 17, 7))
43+
44+
x = "abc";
45+
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 17, 7))
46+
}
47+
else {
48+
x = x.slice();
49+
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 17, 7))
50+
>x.slice : Symbol(String.slice, Decl(lib.d.ts, --, --))
51+
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 17, 7))
52+
>slice : Symbol(String.slice, Decl(lib.d.ts, --, --))
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)