Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 48 additions & 18 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ namespace ts {
const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol");
const voidType = createIntrinsicType(TypeFlags.Void, "void");
const neverType = createIntrinsicType(TypeFlags.Never, "never");
const silentNeverType = createIntrinsicType(TypeFlags.Never, "never");

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

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

Expand Down Expand Up @@ -8070,8 +8072,11 @@ namespace ts {
// we remove type string.
function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) {
if (declaredType !== assignedType) {
if (assignedType.flags & TypeFlags.Never) {
return assignedType;
}
const reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t));
if (reducedType !== neverType) {
if (!(reducedType.flags & TypeFlags.Never)) {
return reducedType;
}
}
Expand Down Expand Up @@ -8351,7 +8356,7 @@ namespace ts {
const visitedFlowStart = visitedFlowCount;
const result = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode));
visitedFlowCount = visitedFlowStart;
if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull) === neverType) {
if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) {
return declaredType;
}
return result;
Expand Down Expand Up @@ -8440,17 +8445,18 @@ namespace ts {
function getTypeAtFlowCondition(flow: FlowCondition): FlowType {
const flowType = getTypeAtFlowNode(flow.antecedent);
let type = getTypeFromFlowType(flowType);
if (type !== neverType) {
if (!(type.flags & TypeFlags.Never)) {
// 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 never type, and if
// the antecedent type is incomplete (i.e. a transient type in a loop), then we
// take the type guard as an indication that control *could* reach here once we
// have the complete type. We proceed by reverting to the declared type and then
// narrow that.
// have the complete type. We proceed by switching to the silent never type which
// doesn't report errors when operators are applied to it. Note that this is the
// *only* place a silent never type is ever generated.
const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0;
type = narrowType(type, flow.expression, assumeTrue);
if (type === neverType && isIncomplete(flowType)) {
type = narrowType(declaredType, flow.expression, assumeTrue);
if (type.flags & TypeFlags.Never && isIncomplete(flowType)) {
type = silentNeverType;
}
}
return createFlowType(type, isIncomplete(flowType));
Expand Down Expand Up @@ -8659,7 +8665,7 @@ namespace ts {
}
if (assumeTrue) {
const narrowedType = filterType(type, t => areTypesComparable(t, valueType));
return narrowedType !== neverType ? narrowedType : type;
return narrowedType.flags & TypeFlags.Never ? type : narrowedType;
}
return isUnitType(valueType) ? filterType(type, t => t !== valueType) : type;
}
Expand Down Expand Up @@ -8702,12 +8708,12 @@ namespace ts {
const clauseTypes = switchTypes.slice(clauseStart, clauseEnd);
const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType);
const discriminantType = getUnionType(clauseTypes);
const caseType = discriminantType === neverType ? neverType : filterType(type, t => isTypeComparableTo(discriminantType, t));
const caseType = discriminantType.flags & TypeFlags.Never ? neverType : filterType(type, t => isTypeComparableTo(discriminantType, t));
if (!hasDefaultClause) {
return caseType;
}
const defaultType = filterType(type, t => !(isUnitType(t) && contains(switchTypes, t)));
return caseType === neverType ? defaultType : getUnionType([caseType, defaultType]);
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
}

function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
Expand Down Expand Up @@ -8771,7 +8777,7 @@ namespace ts {
// the candidate type. If one or more constituents remain, return a union of those.
if (type.flags & TypeFlags.Union) {
const assignableType = filterType(type, t => isTypeInstanceOf(t, candidate));
if (assignableType !== neverType) {
if (!(assignableType.flags & TypeFlags.Never)) {
return assignableType;
}
}
Expand Down Expand Up @@ -10889,7 +10895,7 @@ namespace ts {

function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) {
const type = checkNonNullExpression(left);
if (isTypeAny(type)) {
if (isTypeAny(type) || type === silentNeverType) {
return type;
}

Expand Down Expand Up @@ -11036,8 +11042,8 @@ namespace ts {
const objectType = getApparentType(checkNonNullExpression(node.expression));
const indexType = node.argumentExpression ? checkExpression(node.argumentExpression) : unknownType;

if (objectType === unknownType) {
return unknownType;
if (objectType === unknownType || objectType === silentNeverType) {
return objectType;
}

const isConstEnum = isConstEnumObjectType(objectType);
Expand Down Expand Up @@ -12087,6 +12093,9 @@ namespace ts {
}

const funcType = checkNonNullExpression(node.expression);
if (funcType === silentNeverType) {
return silentNeverSignature;
}
const apparentType = getApparentType(funcType);

if (apparentType === unknownType) {
Expand Down Expand Up @@ -12159,6 +12168,9 @@ namespace ts {
}

let expressionType = checkNonNullExpression(node.expression);
if (expressionType === silentNeverType) {
return silentNeverSignature;
}

// If expressionType's apparent type(section 3.8.1) is an object type with one or
// more construct signatures, the expression is processed in the same manner as a
Expand Down Expand Up @@ -12718,7 +12730,7 @@ namespace ts {
// the native Promise<T> type by the caller.
type = checkAwaitedType(type, func, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member);
}
if (type === neverType) {
if (type.flags & TypeFlags.Never) {
hasReturnOfTypeNever = true;
}
else if (!contains(aggregatedTypes, type)) {
Expand Down Expand Up @@ -12768,7 +12780,7 @@ namespace ts {

const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn;

if (returnType === neverType) {
if (returnType && returnType.flags & TypeFlags.Never) {
error(func.type, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point);
}
else if (returnType && !hasExplicitReturn) {
Expand Down Expand Up @@ -13024,6 +13036,9 @@ namespace ts {

function checkPrefixUnaryExpression(node: PrefixUnaryExpression): Type {
const operandType = checkExpression(node.operand);
if (operandType === silentNeverType) {
return silentNeverType;
}
if (node.operator === SyntaxKind.MinusToken && node.operand.kind === SyntaxKind.NumericLiteral) {
return getLiteralTypeForText(TypeFlags.NumberLiteral, "" + -(<LiteralExpression>node.operand).text);
}
Expand Down Expand Up @@ -13057,6 +13072,9 @@ namespace ts {

function checkPostfixUnaryExpression(node: PostfixUnaryExpression): Type {
const operandType = checkExpression(node.operand);
if (operandType === silentNeverType) {
return silentNeverType;
}
const ok = checkArithmeticOperandType(node.operand, getNonNullableType(operandType),
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type);
if (ok) {
Expand Down Expand Up @@ -13121,6 +13139,9 @@ namespace ts {
}

function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type {
if (leftType === silentNeverType || rightType === silentNeverType) {
return silentNeverType;
}
// TypeScript 1.0 spec (April 2014): 4.15.4
// The instanceof operator requires the left operand to be of type Any, an object type, or a type parameter type,
// and the right operand to be of type Any or a subtype of the 'Function' interface type.
Expand All @@ -13137,6 +13158,9 @@ namespace ts {
}

function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type {
if (leftType === silentNeverType || rightType === silentNeverType) {
return silentNeverType;
}
// TypeScript 1.0 spec (April 2014): 4.15.5
// The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type,
// and the right operand to be of type Any, an object type, or a type parameter type.
Expand Down Expand Up @@ -13401,6 +13425,9 @@ namespace ts {
case SyntaxKind.CaretEqualsToken:
case SyntaxKind.AmpersandToken:
case SyntaxKind.AmpersandEqualsToken:
if (leftType === silentNeverType || rightType === silentNeverType) {
return silentNeverType;
}
// TypeScript 1.0 spec (April 2014): 4.19.1
// These operators require their operands to be of type Any, the Number primitive type,
// or an enum type. Operands of an enum type are treated
Expand Down Expand Up @@ -13433,6 +13460,9 @@ namespace ts {
return numberType;
case SyntaxKind.PlusToken:
case SyntaxKind.PlusEqualsToken:
if (leftType === silentNeverType || rightType === silentNeverType) {
return silentNeverType;
}
// TypeScript 1.0 spec (April 2014): 4.19.2
// The binary + operator requires both operands to be of the Number primitive type or an enum type,
// or at least one of the operands to be of type Any or the String primitive type.
Expand Down Expand Up @@ -16265,7 +16295,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 === neverType) {
if (arrayType.flags & TypeFlags.Never) {
return stringType;
}
}
Expand Down Expand Up @@ -16326,7 +16356,7 @@ namespace ts {
if (func) {
const signature = getSignatureFromDeclaration(func);
const returnType = getReturnTypeOfSignature(signature);
if (strictNullChecks || node.expression || returnType === neverType) {
if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) {
const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType;

if (func.asteriskToken) {
Expand Down
53 changes: 53 additions & 0 deletions tests/baselines/reference/controlFlowWithIncompleteTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//// [controlFlowWithIncompleteTypes.ts]
// Repro from #11000

declare var cond: boolean;

function foo1() {
let x: string | number | boolean = 0;
while (cond) {
if (typeof x === "string") {
x = x.slice();
}
else {
x = "abc";
}
}
}

function foo2() {
let x: string | number | boolean = 0;
while (cond) {
if (typeof x === "number") {
x = "abc";
}
else {
x = x.slice();
}
}
}

//// [controlFlowWithIncompleteTypes.js]
// Repro from #11000
function foo1() {
var x = 0;
while (cond) {
if (typeof x === "string") {
x = x.slice();
}
else {
x = "abc";
}
}
}
function foo2() {
var x = 0;
while (cond) {
if (typeof x === "number") {
x = "abc";
}
else {
x = x.slice();
}
}
}
55 changes: 55 additions & 0 deletions tests/baselines/reference/controlFlowWithIncompleteTypes.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
=== tests/cases/compiler/controlFlowWithIncompleteTypes.ts ===
// Repro from #11000

declare var cond: boolean;
>cond : Symbol(cond, Decl(controlFlowWithIncompleteTypes.ts, 2, 11))

function foo1() {
>foo1 : Symbol(foo1, Decl(controlFlowWithIncompleteTypes.ts, 2, 26))

let x: string | number | boolean = 0;
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 5, 7))

while (cond) {
>cond : Symbol(cond, Decl(controlFlowWithIncompleteTypes.ts, 2, 11))

if (typeof x === "string") {
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 5, 7))

x = x.slice();
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 5, 7))
>x.slice : Symbol(String.slice, Decl(lib.d.ts, --, --))
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 5, 7))
>slice : Symbol(String.slice, Decl(lib.d.ts, --, --))
}
else {
x = "abc";
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 5, 7))
}
}
}

function foo2() {
>foo2 : Symbol(foo2, Decl(controlFlowWithIncompleteTypes.ts, 14, 1))

let x: string | number | boolean = 0;
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 17, 7))

while (cond) {
>cond : Symbol(cond, Decl(controlFlowWithIncompleteTypes.ts, 2, 11))

if (typeof x === "number") {
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 17, 7))

x = "abc";
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 17, 7))
}
else {
x = x.slice();
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 17, 7))
>x.slice : Symbol(String.slice, Decl(lib.d.ts, --, --))
>x : Symbol(x, Decl(controlFlowWithIncompleteTypes.ts, 17, 7))
>slice : Symbol(String.slice, Decl(lib.d.ts, --, --))
}
}
}
Loading