Skip to content

Commit 59f269c

Browse files
committed
Merge pull request #8652 from Microsoft/neverType
Add 'never' type
2 parents 9a22d08 + bfd8704 commit 59f269c

37 files changed

+861
-90
lines changed

src/compiler/checker.ts

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,9 @@ namespace ts {
121121
const nullType = createIntrinsicType(TypeFlags.Null | nullableWideningFlags, "null");
122122
const emptyArrayElementType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined");
123123
const unknownType = createIntrinsicType(TypeFlags.Any, "unknown");
124+
const neverType = createIntrinsicType(TypeFlags.Never, "never");
124125

125126
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
126-
const nothingType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
127127
const emptyGenericType = <GenericType><ObjectType>createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
128128
emptyGenericType.instantiations = {};
129129

@@ -2030,12 +2030,7 @@ namespace ts {
20302030
writeUnionOrIntersectionType(<UnionOrIntersectionType>type, flags);
20312031
}
20322032
else if (type.flags & TypeFlags.Anonymous) {
2033-
if (type === nothingType) {
2034-
writer.writeKeyword("nothing");
2035-
}
2036-
else {
2037-
writeAnonymousType(<ObjectType>type, flags);
2038-
}
2033+
writeAnonymousType(<ObjectType>type, flags);
20392034
}
20402035
else if (type.flags & TypeFlags.StringLiteral) {
20412036
writer.writeStringLiteral(`"${escapeString((<StringLiteralType>type).text)}"`);
@@ -3676,6 +3671,7 @@ namespace ts {
36763671
case SyntaxKind.VoidKeyword:
36773672
case SyntaxKind.UndefinedKeyword:
36783673
case SyntaxKind.NullKeyword:
3674+
case SyntaxKind.NeverKeyword:
36793675
case SyntaxKind.StringLiteralType:
36803676
return true;
36813677
case SyntaxKind.ArrayType:
@@ -5011,7 +5007,7 @@ namespace ts {
50115007
if (type.flags & TypeFlags.Undefined) typeSet.containsUndefined = true;
50125008
if (type.flags & TypeFlags.Null) typeSet.containsNull = true;
50135009
}
5014-
else if (type !== nothingType && !contains(typeSet, type)) {
5010+
else if (type !== neverType && !contains(typeSet, type)) {
50155011
typeSet.push(type);
50165012
}
50175013
}
@@ -5052,7 +5048,7 @@ namespace ts {
50525048
// a named type that circularly references itself.
50535049
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
50545050
if (types.length === 0) {
5055-
return nothingType;
5051+
return neverType;
50565052
}
50575053
if (types.length === 1) {
50585054
return types[0];
@@ -5072,7 +5068,7 @@ namespace ts {
50725068
if (typeSet.length === 0) {
50735069
return typeSet.containsNull ? nullType :
50745070
typeSet.containsUndefined ? undefinedType :
5075-
nothingType;
5071+
neverType;
50765072
}
50775073
else if (typeSet.length === 1) {
50785074
return typeSet[0];
@@ -5220,6 +5216,8 @@ namespace ts {
52205216
return undefinedType;
52215217
case SyntaxKind.NullKeyword:
52225218
return nullType;
5219+
case SyntaxKind.NeverKeyword:
5220+
return neverType;
52235221
case SyntaxKind.ThisType:
52245222
case SyntaxKind.ThisKeyword:
52255223
return getTypeFromThisTypeNode(node);
@@ -5865,28 +5863,28 @@ namespace ts {
58655863
return isIdenticalTo(source, target);
58665864
}
58675865

5868-
if (target.flags & TypeFlags.Any) return Ternary.True;
5869-
if (source.flags & TypeFlags.Undefined) {
5870-
if (!strictNullChecks || target.flags & (TypeFlags.Undefined | TypeFlags.Void) || source === emptyArrayElementType) return Ternary.True;
5871-
}
5872-
if (source.flags & TypeFlags.Null) {
5873-
if (!strictNullChecks || target.flags & TypeFlags.Null) return Ternary.True;
5874-
}
5875-
if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True;
5876-
if (source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum) {
5877-
if (result = enumRelatedTo(source, target, reportErrors)) {
5878-
return result;
5866+
if (!(target.flags & TypeFlags.Never)) {
5867+
if (target.flags & TypeFlags.Any) return Ternary.True;
5868+
if (source.flags & TypeFlags.Undefined) {
5869+
if (!strictNullChecks || target.flags & (TypeFlags.Undefined | TypeFlags.Void) || source === emptyArrayElementType) return Ternary.True;
5870+
}
5871+
if (source.flags & TypeFlags.Null) {
5872+
if (!strictNullChecks || target.flags & TypeFlags.Null) return Ternary.True;
5873+
}
5874+
if (source.flags & TypeFlags.Enum && target === numberType) return Ternary.True;
5875+
if (source.flags & TypeFlags.Enum && target.flags & TypeFlags.Enum) {
5876+
if (result = enumRelatedTo(source, target, reportErrors)) {
5877+
return result;
5878+
}
5879+
}
5880+
if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True;
5881+
if (relation === assignableRelation || relation === comparableRelation) {
5882+
if (source.flags & (TypeFlags.Any | TypeFlags.Never)) return Ternary.True;
5883+
if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True;
5884+
}
5885+
if (source.flags & TypeFlags.Boolean && target.flags & TypeFlags.Boolean) {
5886+
return Ternary.True;
58795887
}
5880-
}
5881-
if (source.flags & TypeFlags.StringLiteral && target === stringType) return Ternary.True;
5882-
5883-
if (relation === assignableRelation || relation === comparableRelation) {
5884-
if (isTypeAny(source)) return Ternary.True;
5885-
if (source === numberType && target.flags & TypeFlags.Enum) return Ternary.True;
5886-
}
5887-
5888-
if (source.flags & TypeFlags.Boolean && target.flags & TypeFlags.Boolean) {
5889-
return Ternary.True;
58905888
}
58915889

58925890
if (source.flags & TypeFlags.FreshObjectLiteral) {
@@ -7491,7 +7489,7 @@ namespace ts {
74917489

74927490
function getTypeWithFacts(type: Type, include: TypeFacts) {
74937491
if (!(type.flags & TypeFlags.Union)) {
7494-
return getTypeFacts(type) & include ? type : nothingType;
7492+
return getTypeFacts(type) & include ? type : neverType;
74957493
}
74967494
let firstType: Type;
74977495
let types: Type[];
@@ -7508,7 +7506,7 @@ namespace ts {
75087506
}
75097507
}
75107508
}
7511-
return firstType ? types ? getUnionType(types, /*noSubtypeReduction*/ true) : firstType : nothingType;
7509+
return firstType ? types ? getUnionType(types, /*noSubtypeReduction*/ true) : firstType : neverType;
75127510
}
75137511

75147512
function getTypeWithDefault(type: Type, defaultExpression: Expression) {
@@ -7629,7 +7627,7 @@ namespace ts {
76297627
const visitedFlowStart = visitedFlowCount;
76307628
const result = getTypeAtFlowNode(reference.flowNode);
76317629
visitedFlowCount = visitedFlowStart;
7632-
if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull) === nothingType) {
7630+
if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull) === neverType) {
76337631
return declaredType;
76347632
}
76357633
return result;
@@ -7717,7 +7715,7 @@ namespace ts {
77177715

77187716
function getTypeAtFlowCondition(flow: FlowCondition) {
77197717
let type = getTypeAtFlowNode(flow.antecedent);
7720-
if (type !== nothingType) {
7718+
if (type !== neverType) {
77217719
// If we have an antecedent type (meaning we're reachable in some way), we first
77227720
// attempt to narrow the antecedent type. If that produces the nothing type, then
77237721
// we take the type guard as an indication that control could reach here in a
@@ -7727,7 +7725,7 @@ namespace ts {
77277725
// narrow that.
77287726
const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0;
77297727
type = narrowType(type, flow.expression, assumeTrue);
7730-
if (type === nothingType) {
7728+
if (type === neverType) {
77317729
type = narrowType(declaredType, flow.expression, assumeTrue);
77327730
}
77337731
}
@@ -7949,7 +7947,7 @@ namespace ts {
79497947
const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type;
79507948
return isTypeAssignableTo(candidate, targetType) ? candidate :
79517949
isTypeAssignableTo(type, candidate) ? type :
7952-
nothingType;
7950+
neverType;
79537951
}
79547952

79557953
function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
@@ -11561,6 +11559,9 @@ namespace ts {
1156111559
else {
1156211560
const hasImplicitReturn = !!(func.flags & NodeFlags.HasImplicitReturn);
1156311561
types = checkAndAggregateReturnExpressionTypes(<Block>func.body, contextualMapper, isAsync, hasImplicitReturn);
11562+
if (!types) {
11563+
return neverType;
11564+
}
1156411565
if (types.length === 0) {
1156511566
if (isAsync) {
1156611567
// For an async function, the return type will not be void, but rather a Promise for void.
@@ -11569,12 +11570,9 @@ namespace ts {
1156911570
error(func, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type);
1157011571
return unknownType;
1157111572
}
11572-
1157311573
return promiseType;
1157411574
}
11575-
else {
11576-
return voidType;
11577-
}
11575+
return voidType;
1157811576
}
1157911577
}
1158011578
// When yield/return statements are contextually typed we allow the return type to be a union type.
@@ -11654,14 +11652,17 @@ namespace ts {
1165411652
// the native Promise<T> type by the caller.
1165511653
type = checkAwaitedType(type, body.parent, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member);
1165611654
}
11657-
if (!contains(aggregatedTypes, type)) {
11655+
if (type !== neverType && !contains(aggregatedTypes, type)) {
1165811656
aggregatedTypes.push(type);
1165911657
}
1166011658
}
1166111659
else {
1166211660
hasOmittedExpressions = true;
1166311661
}
1166411662
});
11663+
if (aggregatedTypes.length === 0 && !hasOmittedExpressions && !hasImplicitReturn) {
11664+
return undefined;
11665+
}
1166511666
if (strictNullChecks && aggregatedTypes.length && (hasOmittedExpressions || hasImplicitReturn)) {
1166611667
if (!contains(aggregatedTypes, undefinedType)) {
1166711668
aggregatedTypes.push(undefinedType);
@@ -11697,7 +11698,10 @@ namespace ts {
1169711698

1169811699
const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn;
1169911700

11700-
if (returnType && !hasExplicitReturn) {
11701+
if (returnType === neverType) {
11702+
error(func.type, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point);
11703+
}
11704+
else if (returnType && !hasExplicitReturn) {
1170111705
// minimal check: function has syntactic return type annotation and no explicit return statements in the body
1170211706
// this function does not conform to the specification.
1170311707
// NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present
@@ -14761,7 +14765,7 @@ namespace ts {
1476114765
arrayType = getUnionType(filter((arrayOrStringType as UnionType).types, t => !(t.flags & TypeFlags.StringLike)));
1476214766
}
1476314767
else if (arrayOrStringType.flags & TypeFlags.StringLike) {
14764-
arrayType = nothingType;
14768+
arrayType = neverType;
1476514769
}
1476614770
const hasStringConstituent = arrayOrStringType !== arrayType;
1476714771
let reportedError = false;
@@ -14773,7 +14777,7 @@ namespace ts {
1477314777

1477414778
// Now that we've removed all the StringLike types, if no constituents remain, then the entire
1477514779
// arrayOrStringType was a string.
14776-
if (arrayType === nothingType) {
14780+
if (arrayType === neverType) {
1477714781
return stringType;
1477814782
}
1477914783
}
@@ -14834,7 +14838,7 @@ namespace ts {
1483414838
if (func) {
1483514839
const signature = getSignatureFromDeclaration(func);
1483614840
const returnType = getReturnTypeOfSignature(signature);
14837-
if (strictNullChecks || node.expression) {
14841+
if (strictNullChecks || node.expression || returnType === neverType) {
1483814842
const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType;
1483914843

1484014844
if (func.asteriskToken) {

src/compiler/declarationEmitter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ namespace ts {
395395
case SyntaxKind.VoidKeyword:
396396
case SyntaxKind.UndefinedKeyword:
397397
case SyntaxKind.NullKeyword:
398+
case SyntaxKind.NeverKeyword:
398399
case SyntaxKind.ThisType:
399400
case SyntaxKind.StringLiteralType:
400401
return writeTextOfNode(currentText, type);

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,6 +1747,10 @@
17471747
"category": "Error",
17481748
"code": 2533
17491749
},
1750+
"A function returning 'never' cannot have a reachable end point.": {
1751+
"category": "Error",
1752+
"code": 2534
1753+
},
17501754
"JSX element attributes type '{0}' may not be a union type.": {
17511755
"category": "Error",
17521756
"code": 2600

src/compiler/parser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2368,6 +2368,7 @@ namespace ts {
23682368
case SyntaxKind.BooleanKeyword:
23692369
case SyntaxKind.SymbolKeyword:
23702370
case SyntaxKind.UndefinedKeyword:
2371+
case SyntaxKind.NeverKeyword:
23712372
// If these are followed by a dot, then parse these out as a dotted type reference instead.
23722373
const node = tryParse(parseKeywordAndNoDot);
23732374
return node || parseTypeReference();
@@ -2410,6 +2411,7 @@ namespace ts {
24102411
case SyntaxKind.NullKeyword:
24112412
case SyntaxKind.ThisKeyword:
24122413
case SyntaxKind.TypeOfKeyword:
2414+
case SyntaxKind.NeverKeyword:
24132415
case SyntaxKind.OpenBraceToken:
24142416
case SyntaxKind.OpenBracketToken:
24152417
case SyntaxKind.LessThanToken:

src/compiler/scanner.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ namespace ts {
9191
"let": SyntaxKind.LetKeyword,
9292
"module": SyntaxKind.ModuleKeyword,
9393
"namespace": SyntaxKind.NamespaceKeyword,
94+
"never": SyntaxKind.NeverKeyword,
9495
"new": SyntaxKind.NewKeyword,
9596
"null": SyntaxKind.NullKeyword,
9697
"number": SyntaxKind.NumberKeyword,

src/compiler/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ namespace ts {
164164
IsKeyword,
165165
ModuleKeyword,
166166
NamespaceKeyword,
167+
NeverKeyword,
167168
ReadonlyKeyword,
168169
RequireKeyword,
169170
NumberKeyword,
@@ -2171,11 +2172,12 @@ namespace ts {
21712172
ESSymbol = 0x01000000, // Type of symbol primitive introduced in ES6
21722173
ThisType = 0x02000000, // This type
21732174
ObjectLiteralPatternWithComputedProperties = 0x04000000, // Object literal type implied by binding pattern has computed properties
2175+
Never = 0x08000000, // Never type
21742176

21752177
/* @internal */
21762178
Nullable = Undefined | Null,
21772179
/* @internal */
2178-
Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null,
2180+
Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null | Never,
21792181
/* @internal */
21802182
Primitive = String | Number | Boolean | ESSymbol | Void | Undefined | Null | StringLiteral | Enum,
21812183
StringLike = String | StringLiteral,

src/compiler/utilities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ namespace ts {
613613
case SyntaxKind.BooleanKeyword:
614614
case SyntaxKind.SymbolKeyword:
615615
case SyntaxKind.UndefinedKeyword:
616+
case SyntaxKind.NeverKeyword:
616617
return true;
617618
case SyntaxKind.VoidKeyword:
618619
return node.parent.kind !== SyntaxKind.VoidExpression;

tests/baselines/reference/controlFlowIteration.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ let cond: boolean;
44
>cond : boolean
55

66
function ff() {
7-
>ff : () => void
7+
>ff : () => never
88

99
let x: string | undefined;
1010
>x : string | undefined

tests/baselines/reference/duplicateLabel3.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ while (true) {
77
>true : boolean
88

99
function f() {
10-
>f : () => void
10+
>f : () => never
1111

1212
target:
1313
>target : any

tests/baselines/reference/forStatementsMultipleValidDecl.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ for (var x = <number>undefined; ;) { }
1616

1717
// new declaration space, making redeclaring x as a string valid
1818
function declSpace() {
19-
>declSpace : () => void
19+
>declSpace : () => never
2020

2121
for (var x = 'this is a string'; ;) { }
2222
>x : string

tests/baselines/reference/instanceOfAssignability.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ function fn5(x: Derived1) {
133133
// 1.5: y: Derived1
134134
// Want: ???
135135
let y = x;
136-
>y : nothing
137-
>x : nothing
136+
>y : never
137+
>x : never
138138
}
139139
}
140140

tests/baselines/reference/narrowingOfDottedNames.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function isB(x: any): x is B {
4242
}
4343

4444
function f1(x: A | B) {
45-
>f1 : (x: A | B) => void
45+
>f1 : (x: A | B) => never
4646
>x : A | B
4747
>A : A
4848
>B : B
@@ -78,7 +78,7 @@ function f1(x: A | B) {
7878
}
7979

8080
function f2(x: A | B) {
81-
>f2 : (x: A | B) => void
81+
>f2 : (x: A | B) => never
8282
>x : A | B
8383
>A : A
8484
>B : B

0 commit comments

Comments
 (0)