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

Only infer 'never' in function expressions and arrow functions #8767

Merged
merged 4 commits into from
May 23, 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
31 changes: 18 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11568,7 +11568,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 @@ -11579,8 +11579,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 @@ -11638,10 +11637,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 @@ -11660,10 +11659,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 @@ -11672,20 +11673,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