Skip to content

Commit

Permalink
Merge pull request #8652 from Microsoft/neverType
Browse files Browse the repository at this point in the history
Add 'never' type
  • Loading branch information
ahejlsberg committed May 18, 2016
2 parents 9a22d08 + bfd8704 commit 59f269c
Show file tree
Hide file tree
Showing 37 changed files with 861 additions and 90 deletions.
96 changes: 50 additions & 46 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ namespace ts {
const nullType = createIntrinsicType(TypeFlags.Null | nullableWideningFlags, "null");
const emptyArrayElementType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined");
const unknownType = createIntrinsicType(TypeFlags.Any, "unknown");
const neverType = createIntrinsicType(TypeFlags.Never, "never");

const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
const nothingType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
const emptyGenericType = <GenericType><ObjectType>createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
emptyGenericType.instantiations = {};

Expand Down Expand Up @@ -2030,12 +2030,7 @@ namespace ts {
writeUnionOrIntersectionType(<UnionOrIntersectionType>type, flags);
}
else if (type.flags & TypeFlags.Anonymous) {
if (type === nothingType) {
writer.writeKeyword("nothing");
}
else {
writeAnonymousType(<ObjectType>type, flags);
}
writeAnonymousType(<ObjectType>type, flags);
}
else if (type.flags & TypeFlags.StringLiteral) {
writer.writeStringLiteral(`"${escapeString((<StringLiteralType>type).text)}"`);
Expand Down Expand Up @@ -3676,6 +3671,7 @@ namespace ts {
case SyntaxKind.VoidKeyword:
case SyntaxKind.UndefinedKeyword:
case SyntaxKind.NullKeyword:
case SyntaxKind.NeverKeyword:
case SyntaxKind.StringLiteralType:
return true;
case SyntaxKind.ArrayType:
Expand Down Expand Up @@ -5011,7 +5007,7 @@ namespace ts {
if (type.flags & TypeFlags.Undefined) typeSet.containsUndefined = true;
if (type.flags & TypeFlags.Null) typeSet.containsNull = true;
}
else if (type !== nothingType && !contains(typeSet, type)) {
else if (type !== neverType && !contains(typeSet, type)) {
typeSet.push(type);
}
}
Expand Down Expand Up @@ -5052,7 +5048,7 @@ namespace ts {
// a named type that circularly references itself.
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
if (types.length === 0) {
return nothingType;
return neverType;
}
if (types.length === 1) {
return types[0];
Expand All @@ -5072,7 +5068,7 @@ namespace ts {
if (typeSet.length === 0) {
return typeSet.containsNull ? nullType :
typeSet.containsUndefined ? undefinedType :
nothingType;
neverType;
}
else if (typeSet.length === 1) {
return typeSet[0];
Expand Down Expand Up @@ -5220,6 +5216,8 @@ namespace ts {
return undefinedType;
case SyntaxKind.NullKeyword:
return nullType;
case SyntaxKind.NeverKeyword:
return neverType;
case SyntaxKind.ThisType:
case SyntaxKind.ThisKeyword:
return getTypeFromThisTypeNode(node);
Expand Down Expand Up @@ -5865,28 +5863,28 @@ namespace ts {
return isIdenticalTo(source, target);
}

if (target.flags & TypeFlags.Any) return Ternary.True;
if (source.flags & TypeFlags.Undefined) {
if (!strictNullChecks || target.flags & (TypeFlags.Undefined | TypeFlags.Void) || source === emptyArrayElementType) return Ternary.True;
}
if (source.flags & TypeFlags.Null) {
if (!strictNullChecks || target.flags & TypeFlags.Null) return Ternary.True;
}
if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True;
if (source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum) {
if (result = enumRelatedTo(source, target, reportErrors)) {
return result;
if (!(target.flags & TypeFlags.Never)) {
if (target.flags & TypeFlags.Any) return Ternary.True;
if (source.flags & TypeFlags.Undefined) {
if (!strictNullChecks || target.flags & (TypeFlags.Undefined | TypeFlags.Void) || source === emptyArrayElementType) return Ternary.True;
}
if (source.flags & TypeFlags.Null) {
if (!strictNullChecks || target.flags & TypeFlags.Null) return Ternary.True;
}
if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True;
if (source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum) {
if (result = enumRelatedTo(source, target, reportErrors)) {
return result;
}
}
if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True;
if (relation === assignableRelation || relation === comparableRelation) {
if (source.flags & (TypeFlags.Any | TypeFlags.Never)) return Ternary.True;
if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True;
}
if (source.flags & TypeFlags.Boolean && target.flags & TypeFlags.Boolean) {
return Ternary.True;
}
}
if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True;

if (relation === assignableRelation || relation === comparableRelation) {
if (isTypeAny(source)) return Ternary.True;
if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True;
}

if (source.flags & TypeFlags.Boolean && target.flags & TypeFlags.Boolean) {
return Ternary.True;
}

if (source.flags & TypeFlags.FreshObjectLiteral) {
Expand Down Expand Up @@ -7491,7 +7489,7 @@ namespace ts {

function getTypeWithFacts(type: Type, include: TypeFacts) {
if (!(type.flags & TypeFlags.Union)) {
return getTypeFacts(type) & include ? type : nothingType;
return getTypeFacts(type) & include ? type : neverType;
}
let firstType: Type;
let types: Type[];
Expand All @@ -7508,7 +7506,7 @@ namespace ts {
}
}
}
return firstType ? types ? getUnionType(types, /*noSubtypeReduction*/ true) : firstType : nothingType;
return firstType ? types ? getUnionType(types, /*noSubtypeReduction*/ true) : firstType : neverType;
}

function getTypeWithDefault(type: Type, defaultExpression: Expression) {
Expand Down Expand Up @@ -7629,7 +7627,7 @@ namespace ts {
const visitedFlowStart = visitedFlowCount;
const result = getTypeAtFlowNode(reference.flowNode);
visitedFlowCount = visitedFlowStart;
if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull) === nothingType) {
if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull) === neverType) {
return declaredType;
}
return result;
Expand Down Expand Up @@ -7717,7 +7715,7 @@ namespace ts {

function getTypeAtFlowCondition(flow: FlowCondition) {
let type = getTypeAtFlowNode(flow.antecedent);
if (type !== nothingType) {
if (type !== neverType) {
// 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
Expand All @@ -7727,7 +7725,7 @@ namespace ts {
// narrow that.
const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0;
type = narrowType(type, flow.expression, assumeTrue);
if (type === nothingType) {
if (type === neverType) {
type = narrowType(declaredType, flow.expression, assumeTrue);
}
}
Expand Down Expand Up @@ -7949,7 +7947,7 @@ namespace ts {
const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type;
return isTypeAssignableTo(candidate, targetType) ? candidate :
isTypeAssignableTo(type, candidate) ? type :
nothingType;
neverType;
}

function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
Expand Down Expand Up @@ -11561,6 +11559,9 @@ namespace ts {
else {
const hasImplicitReturn = !!(func.flags & NodeFlags.HasImplicitReturn);
types = checkAndAggregateReturnExpressionTypes(<Block>func.body, contextualMapper, isAsync, hasImplicitReturn);
if (!types) {
return neverType;
}
if (types.length === 0) {
if (isAsync) {
// For an async function, the return type will not be void, but rather a Promise for void.
Expand All @@ -11569,12 +11570,9 @@ namespace ts {
error(func, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type);
return unknownType;
}

return promiseType;
}
else {
return voidType;
}
return voidType;
}
}
// When yield/return statements are contextually typed we allow the return type to be a union type.
Expand Down Expand Up @@ -11654,14 +11652,17 @@ namespace ts {
// the native Promise<T> type by the caller.
type = checkAwaitedType(type, body.parent, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member);
}
if (!contains(aggregatedTypes, type)) {
if (type !== neverType && !contains(aggregatedTypes, type)) {
aggregatedTypes.push(type);
}
}
else {
hasOmittedExpressions = true;
}
});
if (aggregatedTypes.length === 0 && !hasOmittedExpressions && !hasImplicitReturn) {
return undefined;
}
if (strictNullChecks && aggregatedTypes.length && (hasOmittedExpressions || hasImplicitReturn)) {
if (!contains(aggregatedTypes, undefinedType)) {
aggregatedTypes.push(undefinedType);
Expand Down Expand Up @@ -11697,7 +11698,10 @@ namespace ts {

const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn;

if (returnType && !hasExplicitReturn) {
if (returnType === neverType) {
error(func.type, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point);
}
else if (returnType && !hasExplicitReturn) {
// minimal check: function has syntactic return type annotation and no explicit return statements in the body
// this function does not conform to the specification.
// NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present
Expand Down Expand Up @@ -14761,7 +14765,7 @@ namespace ts {
arrayType = getUnionType(filter((arrayOrStringType as UnionType).types, t => !(t.flags & TypeFlags.StringLike)));
}
else if (arrayOrStringType.flags & TypeFlags.StringLike) {
arrayType = nothingType;
arrayType = neverType;
}
const hasStringConstituent = arrayOrStringType !== arrayType;
let reportedError = false;
Expand All @@ -14773,7 +14777,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 === nothingType) {
if (arrayType === neverType) {
return stringType;
}
}
Expand Down Expand Up @@ -14834,7 +14838,7 @@ namespace ts {
if (func) {
const signature = getSignatureFromDeclaration(func);
const returnType = getReturnTypeOfSignature(signature);
if (strictNullChecks || node.expression) {
if (strictNullChecks || node.expression || returnType === neverType) {
const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType;

if (func.asteriskToken) {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/declarationEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ namespace ts {
case SyntaxKind.VoidKeyword:
case SyntaxKind.UndefinedKeyword:
case SyntaxKind.NullKeyword:
case SyntaxKind.NeverKeyword:
case SyntaxKind.ThisType:
case SyntaxKind.StringLiteralType:
return writeTextOfNode(currentText, type);
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1747,6 +1747,10 @@
"category": "Error",
"code": 2533
},
"A function returning 'never' cannot have a reachable end point.": {
"category": "Error",
"code": 2534
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2368,6 +2368,7 @@ namespace ts {
case SyntaxKind.BooleanKeyword:
case SyntaxKind.SymbolKeyword:
case SyntaxKind.UndefinedKeyword:
case SyntaxKind.NeverKeyword:
// If these are followed by a dot, then parse these out as a dotted type reference instead.
const node = tryParse(parseKeywordAndNoDot);
return node || parseTypeReference();
Expand Down Expand Up @@ -2410,6 +2411,7 @@ namespace ts {
case SyntaxKind.NullKeyword:
case SyntaxKind.ThisKeyword:
case SyntaxKind.TypeOfKeyword:
case SyntaxKind.NeverKeyword:
case SyntaxKind.OpenBraceToken:
case SyntaxKind.OpenBracketToken:
case SyntaxKind.LessThanToken:
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ namespace ts {
"let": SyntaxKind.LetKeyword,
"module": SyntaxKind.ModuleKeyword,
"namespace": SyntaxKind.NamespaceKeyword,
"never": SyntaxKind.NeverKeyword,
"new": SyntaxKind.NewKeyword,
"null": SyntaxKind.NullKeyword,
"number": SyntaxKind.NumberKeyword,
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ namespace ts {
IsKeyword,
ModuleKeyword,
NamespaceKeyword,
NeverKeyword,
ReadonlyKeyword,
RequireKeyword,
NumberKeyword,
Expand Down Expand Up @@ -2171,11 +2172,12 @@ namespace ts {
ESSymbol = 0x01000000, // Type of symbol primitive introduced in ES6
ThisType = 0x02000000, // This type
ObjectLiteralPatternWithComputedProperties = 0x04000000, // Object literal type implied by binding pattern has computed properties
Never = 0x08000000, // Never type

/* @internal */
Nullable = Undefined | Null,
/* @internal */
Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null,
Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null | Never,
/* @internal */
Primitive = String | Number | Boolean | ESSymbol | Void | Undefined | Null | StringLiteral | Enum,
StringLike = String | StringLiteral,
Expand Down
1 change: 1 addition & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ namespace ts {
case SyntaxKind.BooleanKeyword:
case SyntaxKind.SymbolKeyword:
case SyntaxKind.UndefinedKeyword:
case SyntaxKind.NeverKeyword:
return true;
case SyntaxKind.VoidKeyword:
return node.parent.kind !== SyntaxKind.VoidExpression;
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/controlFlowIteration.types
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ let cond: boolean;
>cond : boolean

function ff() {
>ff : () => void
>ff : () => never

let x: string | undefined;
>x : string | undefined
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/duplicateLabel3.types
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ while (true) {
>true : boolean

function f() {
>f : () => void
>f : () => never

target:
>target : any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ for (var x = <number>undefined; ;) { }

// new declaration space, making redeclaring x as a string valid
function declSpace() {
>declSpace : () => void
>declSpace : () => never

for (var x = 'this is a string'; ;) { }
>x : string
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/instanceOfAssignability.types
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ function fn5(x: Derived1) {
// 1.5: y: Derived1
// Want: ???
let y = x;
>y : nothing
>x : nothing
>y : never
>x : never
}
}

Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/narrowingOfDottedNames.types
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function isB(x: any): x is B {
}

function f1(x: A | B) {
>f1 : (x: A | B) => void
>f1 : (x: A | B) => never
>x : A | B
>A : A
>B : B
Expand Down Expand Up @@ -78,7 +78,7 @@ function f1(x: A | B) {
}

function f2(x: A | B) {
>f2 : (x: A | B) => void
>f2 : (x: A | B) => never
>x : A | B
>A : A
>B : B
Expand Down
Loading

0 comments on commit 59f269c

Please sign in to comment.