Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'never' type #8652

Merged
merged 6 commits into from
May 18, 2016
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
96 changes: 50 additions & 46 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,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 @@ -2029,12 +2029,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 @@ -3670,6 +3665,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 @@ -5005,7 +5001,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 @@ -5046,7 +5042,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 @@ -5066,7 +5062,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 @@ -5214,6 +5210,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 @@ -5859,28 +5857,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 @@ -7485,7 +7483,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 @@ -7502,7 +7500,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 @@ -7622,7 +7620,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 @@ -7710,7 +7708,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 @@ -7720,7 +7718,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 @@ -7942,7 +7940,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 @@ -11547,6 +11545,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 @@ -11555,12 +11556,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 @@ -11640,14 +11638,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 @@ -11683,7 +11684,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 @@ -14747,7 +14751,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 @@ -14759,7 +14763,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 @@ -14820,7 +14824,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 @@ -1751,6 +1751,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 @@ -2170,11 +2171,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