Skip to content

Commit

Permalink
Merge pull request #8767 from Microsoft/neverTypeInference
Browse files Browse the repository at this point in the history
Only infer 'never' in function expressions and arrow functions
  • Loading branch information
ahejlsberg committed May 23, 2016
2 parents 1527499 + 10d331e commit 74e2154
Show file tree
Hide file tree
Showing 14 changed files with 314 additions and 133 deletions.
31 changes: 18 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11586,7 +11586,7 @@ namespace ts {
let types: Type[];
const funcIsGenerator = !!func.asteriskToken;
if (funcIsGenerator) {
types = checkAndAggregateYieldOperandTypes(<Block>func.body, contextualMapper);
types = checkAndAggregateYieldOperandTypes(func, contextualMapper);
if (types.length === 0) {
const iterableIteratorAny = createIterableIteratorType(anyType);
if (compilerOptions.noImplicitAny) {
Expand All @@ -11597,8 +11597,7 @@ namespace ts {
}
}
else {
const hasImplicitReturn = !!(func.flags & NodeFlags.HasImplicitReturn);
types = checkAndAggregateReturnExpressionTypes(<Block>func.body, contextualMapper, isAsync, hasImplicitReturn);
types = checkAndAggregateReturnExpressionTypes(func, contextualMapper);
if (!types) {
return neverType;
}
Expand Down Expand Up @@ -11656,10 +11655,10 @@ namespace ts {
}
}

function checkAndAggregateYieldOperandTypes(body: Block, contextualMapper?: TypeMapper): Type[] {
function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, contextualMapper: TypeMapper): Type[] {
const aggregatedTypes: Type[] = [];

forEachYieldExpression(body, yieldExpression => {
forEachYieldExpression(<Block>func.body, yieldExpression => {
const expr = yieldExpression.expression;
if (expr) {
let type = checkExpressionCached(expr, contextualMapper);
Expand All @@ -11678,10 +11677,12 @@ namespace ts {
return aggregatedTypes;
}

function checkAndAggregateReturnExpressionTypes(body: Block, contextualMapper: TypeMapper, isAsync: boolean, hasImplicitReturn: boolean): Type[] {
function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, contextualMapper: TypeMapper): Type[] {
const isAsync = isAsyncFunctionLike(func);
const aggregatedTypes: Type[] = [];
let hasOmittedExpressions = false;
forEachReturnStatement(body, returnStatement => {
let hasReturnWithNoExpression = !!(func.flags & NodeFlags.HasImplicitReturn);
let hasReturnOfTypeNever = false;
forEachReturnStatement(<Block>func.body, returnStatement => {
const expr = returnStatement.expression;
if (expr) {
let type = checkExpressionCached(expr, contextualMapper);
Expand All @@ -11690,20 +11691,24 @@ namespace ts {
// Promise/A+ compatible implementation will always assimilate any foreign promise, so the
// return type of the body should be unwrapped to its awaited type, which should be wrapped in
// 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);
type = checkAwaitedType(type, func, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member);
}
if (type === neverType) {
hasReturnOfTypeNever = true;
}
if (type !== neverType && !contains(aggregatedTypes, type)) {
else if (!contains(aggregatedTypes, type)) {
aggregatedTypes.push(type);
}
}
else {
hasOmittedExpressions = true;
hasReturnWithNoExpression = true;
}
});
if (aggregatedTypes.length === 0 && !hasOmittedExpressions && !hasImplicitReturn) {
if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever ||
func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction)) {
return undefined;
}
if (strictNullChecks && aggregatedTypes.length && (hasOmittedExpressions || hasImplicitReturn)) {
if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression) {
if (!contains(aggregatedTypes, undefinedType)) {
aggregatedTypes.push(undefinedType);
}
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 : () => never
>ff : () => void

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 : () => never
>f : () => void

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 : () => never
>declSpace : () => void

for (var x = 'this is a string'; ;) { }
>x : string
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) => never
>f1 : (x: A | B) => void
>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) => never
>f2 : (x: A | B) => void
>x : A | B
>A : A
>B : B
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/nestedBlockScopedBindings3.types
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
=== tests/cases/compiler/nestedBlockScopedBindings3.ts ===
function a0() {
>a0 : () => never
>a0 : () => void
{
for (let x = 0; x < 1; ) {
>x : number
Expand All @@ -26,7 +26,7 @@ function a0() {
}

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

for (let x; x < 1;) {
>x : any
Expand All @@ -48,7 +48,7 @@ function a1() {
}

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

for (let x; x < 1;) {
>x : any
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/nestedBlockScopedBindings4.types
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
=== tests/cases/compiler/nestedBlockScopedBindings4.ts ===
function a0() {
>a0 : () => never
>a0 : () => void

for (let x; x < 1;) {
>x : any
Expand Down Expand Up @@ -28,7 +28,7 @@ function a0() {
}

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

for (let x; x < 1;) {
>x : any
Expand Down Expand Up @@ -60,7 +60,7 @@ function a1() {
}

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

for (let x; x < 1;) {
>x : any
Expand Down Expand Up @@ -93,7 +93,7 @@ function a2() {


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

for (let x; x < 1;) {
>x : any
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/nestedBlockScopedBindings6.types
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
=== tests/cases/compiler/nestedBlockScopedBindings6.ts ===
function a0() {
>a0 : () => never
>a0 : () => void

for (let x of [1]) {
>x : number
Expand All @@ -27,7 +27,7 @@ function a0() {
}

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

for (let x of [1]) {
>x : number
Expand Down Expand Up @@ -58,7 +58,7 @@ function a1() {
}

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

for (let x of [1]) {
>x : number
Expand Down Expand Up @@ -89,7 +89,7 @@ function a2() {
}

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

for (let x of [1]) {
>x : number
Expand Down
93 changes: 75 additions & 18 deletions tests/baselines/reference/neverType.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
//// [neverType.ts]

function error(message: string) {

function error(message: string): never {
throw new Error(message);
}

function errorVoid(message: string) {
throw new Error(message);
}

function fail() {
return error("Something failed");
}

function infiniteLoop() {
function failOrThrow(shouldFail: boolean) {
if (shouldFail) {
return fail();
}
throw new Error();
}

function infiniteLoop1() {
while (true) {
}
}

function infiniteLoop2(): never {
while (true) {
}
}
Expand All @@ -33,6 +50,21 @@ function check<T>(x: T | undefined) {
return x || error("Undefined value");
}

class C {
void1() {
throw new Error();
}
void2() {
while (true) {}
}
never1(): never {
throw new Error();
}
never2(): never {
while (true) {}
}
}

function f1(x: string | number) {
if (typeof x === "boolean") {
x; // never
Expand All @@ -47,13 +79,6 @@ function f2(x: string | number) {
}
}

function failOrThrow(shouldFail: boolean) {
if (shouldFail) {
return fail();
}
throw new Error();
}

function test(cb: () => string) {
let s = cb();
return s;
Expand All @@ -71,10 +96,23 @@ test(errorCallback);
function error(message) {
throw new Error(message);
}
function errorVoid(message) {
throw new Error(message);
}
function fail() {
return error("Something failed");
}
function infiniteLoop() {
function failOrThrow(shouldFail) {
if (shouldFail) {
return fail();
}
throw new Error();
}
function infiniteLoop1() {
while (true) {
}
}
function infiniteLoop2() {
while (true) {
}
}
Expand All @@ -95,6 +133,23 @@ function move2(direction) {
function check(x) {
return x || error("Undefined value");
}
var C = (function () {
function C() {
}
C.prototype.void1 = function () {
throw new Error();
};
C.prototype.void2 = function () {
while (true) { }
};
C.prototype.never1 = function () {
throw new Error();
};
C.prototype.never2 = function () {
while (true) { }
};
return C;
}());
function f1(x) {
if (typeof x === "boolean") {
x; // never
Expand All @@ -107,12 +162,6 @@ function f2(x) {
}
}
}
function failOrThrow(shouldFail) {
if (shouldFail) {
return fail();
}
throw new Error();
}
function test(cb) {
var s = cb();
return s;
Expand All @@ -126,13 +175,21 @@ test(errorCallback);

//// [neverType.d.ts]
declare function error(message: string): never;
declare function errorVoid(message: string): void;
declare function fail(): never;
declare function infiniteLoop(): never;
declare function failOrThrow(shouldFail: boolean): never;
declare function infiniteLoop1(): void;
declare function infiniteLoop2(): never;
declare function move1(direction: "up" | "down"): number;
declare function move2(direction: "up" | "down"): number;
declare function check<T>(x: T | undefined): T;
declare class C {
void1(): void;
void2(): void;
never1(): never;
never2(): never;
}
declare function f1(x: string | number): void;
declare function f2(x: string | number): never;
declare function failOrThrow(shouldFail: boolean): never;
declare function test(cb: () => string): string;
declare let errorCallback: () => never;
Loading

0 comments on commit 74e2154

Please sign in to comment.