Skip to content

Add exhaustiveness checking for try, catch, re-throw. #32952

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

Closed
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
22 changes: 14 additions & 8 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23648,15 +23648,21 @@ namespace ts {
return eachTypeContainedIn(mapType(type, getRegularTypeOfLiteralType), switchTypes);
}

function functionHasImplicitReturn(func: FunctionLikeDeclaration) {
if (!(func.flags & NodeFlags.HasImplicitReturn)) {
return false;
}
function isTotalStatement(statement: Statement): boolean {
return statement.kind === SyntaxKind.SwitchStatement && isExhaustiveSwitchStatement(<SwitchStatement>statement) ||
statement.kind === SyntaxKind.TryStatement &&
((<TryStatement>statement).catchClause !== undefined) &&
some((<TryStatement>statement).catchClause!.block.statements, statement => statement.kind === SyntaxKind.ThrowStatement) &&
some((<TryStatement>statement).tryBlock.statements, isTotalStatement);
}

if (some((<Block>func.body).statements, statement => statement.kind === SyntaxKind.SwitchStatement && isExhaustiveSwitchStatement(<SwitchStatement>statement))) {
return false;
}
return true;
function functionHasImplicitReturn(func: FunctionLikeDeclaration) {
// A function has an implicit return if its body contains no total statements.
// A total statement explicitly returns or diverges, with the following cases recognized:
// (1) an exhaustive switch statement.
// (2) a try-catch statement where the catch clause always
// throws and the try block contains a total statement.
return !!(func.flags & NodeFlags.HasImplicitReturn) && !some((<Block>func.body).statements, isTotalStatement);
}

/** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
tests/cases/compiler/exhaustiveSwitchImplicitReturn.ts(35,32): error TS7030: Not all code paths return a value.
tests/cases/compiler/exhaustiveSwitchImplicitReturn.ts(82,28): error TS7030: Not all code paths return a value.


==== tests/cases/compiler/exhaustiveSwitchImplicitReturn.ts (1 errors) ====
==== tests/cases/compiler/exhaustiveSwitchImplicitReturn.ts (2 errors) ====
function foo1(bar: "a"): number {
switch(bar) {
case "a":
Expand Down Expand Up @@ -44,4 +45,63 @@ tests/cases/compiler/exhaustiveSwitchImplicitReturn.ts(35,32): error TS7030: Not
return 1;
}
}

// Repro from #32905.

enum Foo {
One,
Two,
Three
}

function test2(type: Foo): number {
try {
switch (type) {
case Foo.One:
return 0;
case Foo.Two:
return 0;
case Foo.Three:
return 0;
}
} catch (e) {
throw new Error('some error')
}
}

function test3(type: Foo): number {
try {
console.log('some switch')
switch (type) {
case Foo.One:
return 0;
case Foo.Two:
return 0;
case Foo.Three:
return 0;
}
} catch (e) {
console.log('some error')
throw new Error('some error')
}
}

function test4(type: Foo): number {
~~~~~~
!!! error TS7030: Not all code paths return a value.
try {
console.log('some switch')
switch (type) {
case Foo.One:
return 0;
case Foo.Two:
return 0;
case Foo.Three:
0;
}
} catch (e) {
console.log('some error')
throw new Error('some error')
}
}

113 changes: 113 additions & 0 deletions tests/baselines/reference/exhaustiveSwitchImplicitReturn.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,63 @@ function foo5(bar: "a" | "b"): number {
return 1;
}
}

// Repro from #32905.

enum Foo {
One,
Two,
Three
}

function test2(type: Foo): number {
try {
switch (type) {
case Foo.One:
return 0;
case Foo.Two:
return 0;
case Foo.Three:
return 0;
}
} catch (e) {
throw new Error('some error')
}
}

function test3(type: Foo): number {
try {
console.log('some switch')
switch (type) {
case Foo.One:
return 0;
case Foo.Two:
return 0;
case Foo.Three:
return 0;
}
} catch (e) {
console.log('some error')
throw new Error('some error')
}
}

function test4(type: Foo): number {
try {
console.log('some switch')
switch (type) {
case Foo.One:
return 0;
case Foo.Two:
return 0;
case Foo.Three:
0;
}
} catch (e) {
console.log('some error')
throw new Error('some error')
}
}


//// [exhaustiveSwitchImplicitReturn.js]
Expand Down Expand Up @@ -75,3 +132,59 @@ function foo5(bar) {
return 1;
}
}
// Repro from #32905.
var Foo;
(function (Foo) {
Foo[Foo["One"] = 0] = "One";
Foo[Foo["Two"] = 1] = "Two";
Foo[Foo["Three"] = 2] = "Three";
})(Foo || (Foo = {}));
function test2(type) {
try {
switch (type) {
case Foo.One:
return 0;
case Foo.Two:
return 0;
case Foo.Three:
return 0;
}
}
catch (e) {
throw new Error('some error');
}
}
function test3(type) {
try {
console.log('some switch');
switch (type) {
case Foo.One:
return 0;
case Foo.Two:
return 0;
case Foo.Three:
return 0;
}
}
catch (e) {
console.log('some error');
throw new Error('some error');
}
}
function test4(type) {
try {
console.log('some switch');
switch (type) {
case Foo.One:
return 0;
case Foo.Two:
return 0;
case Foo.Three:
0;
}
}
catch (e) {
console.log('some error');
throw new Error('some error');
}
}
143 changes: 143 additions & 0 deletions tests/baselines/reference/exhaustiveSwitchImplicitReturn.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,146 @@ function foo5(bar: "a" | "b"): number {
}
}

// Repro from #32905.

enum Foo {
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))

One,
>One : Symbol(Foo.One, Decl(exhaustiveSwitchImplicitReturn.ts, 43, 10))

Two,
>Two : Symbol(Foo.Two, Decl(exhaustiveSwitchImplicitReturn.ts, 44, 8))

Three
>Three : Symbol(Foo.Three, Decl(exhaustiveSwitchImplicitReturn.ts, 45, 8))
}

function test2(type: Foo): number {
>test2 : Symbol(test2, Decl(exhaustiveSwitchImplicitReturn.ts, 47, 1))
>type : Symbol(type, Decl(exhaustiveSwitchImplicitReturn.ts, 49, 15))
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))

try {
switch (type) {
>type : Symbol(type, Decl(exhaustiveSwitchImplicitReturn.ts, 49, 15))

case Foo.One:
>Foo.One : Symbol(Foo.One, Decl(exhaustiveSwitchImplicitReturn.ts, 43, 10))
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))
>One : Symbol(Foo.One, Decl(exhaustiveSwitchImplicitReturn.ts, 43, 10))

return 0;
case Foo.Two:
>Foo.Two : Symbol(Foo.Two, Decl(exhaustiveSwitchImplicitReturn.ts, 44, 8))
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))
>Two : Symbol(Foo.Two, Decl(exhaustiveSwitchImplicitReturn.ts, 44, 8))

return 0;
case Foo.Three:
>Foo.Three : Symbol(Foo.Three, Decl(exhaustiveSwitchImplicitReturn.ts, 45, 8))
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))
>Three : Symbol(Foo.Three, Decl(exhaustiveSwitchImplicitReturn.ts, 45, 8))

return 0;
}
} catch (e) {
>e : Symbol(e, Decl(exhaustiveSwitchImplicitReturn.ts, 59, 13))

throw new Error('some error')
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
}

function test3(type: Foo): number {
>test3 : Symbol(test3, Decl(exhaustiveSwitchImplicitReturn.ts, 62, 1))
>type : Symbol(type, Decl(exhaustiveSwitchImplicitReturn.ts, 64, 15))
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))

try {
console.log('some switch')
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))

switch (type) {
>type : Symbol(type, Decl(exhaustiveSwitchImplicitReturn.ts, 64, 15))

case Foo.One:
>Foo.One : Symbol(Foo.One, Decl(exhaustiveSwitchImplicitReturn.ts, 43, 10))
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))
>One : Symbol(Foo.One, Decl(exhaustiveSwitchImplicitReturn.ts, 43, 10))

return 0;
case Foo.Two:
>Foo.Two : Symbol(Foo.Two, Decl(exhaustiveSwitchImplicitReturn.ts, 44, 8))
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))
>Two : Symbol(Foo.Two, Decl(exhaustiveSwitchImplicitReturn.ts, 44, 8))

return 0;
case Foo.Three:
>Foo.Three : Symbol(Foo.Three, Decl(exhaustiveSwitchImplicitReturn.ts, 45, 8))
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))
>Three : Symbol(Foo.Three, Decl(exhaustiveSwitchImplicitReturn.ts, 45, 8))

return 0;
}
} catch (e) {
>e : Symbol(e, Decl(exhaustiveSwitchImplicitReturn.ts, 75, 13))

console.log('some error')
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))

throw new Error('some error')
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
}

function test4(type: Foo): number {
>test4 : Symbol(test4, Decl(exhaustiveSwitchImplicitReturn.ts, 79, 1))
>type : Symbol(type, Decl(exhaustiveSwitchImplicitReturn.ts, 81, 15))
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))

try {
console.log('some switch')
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))

switch (type) {
>type : Symbol(type, Decl(exhaustiveSwitchImplicitReturn.ts, 81, 15))

case Foo.One:
>Foo.One : Symbol(Foo.One, Decl(exhaustiveSwitchImplicitReturn.ts, 43, 10))
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))
>One : Symbol(Foo.One, Decl(exhaustiveSwitchImplicitReturn.ts, 43, 10))

return 0;
case Foo.Two:
>Foo.Two : Symbol(Foo.Two, Decl(exhaustiveSwitchImplicitReturn.ts, 44, 8))
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))
>Two : Symbol(Foo.Two, Decl(exhaustiveSwitchImplicitReturn.ts, 44, 8))

return 0;
case Foo.Three:
>Foo.Three : Symbol(Foo.Three, Decl(exhaustiveSwitchImplicitReturn.ts, 45, 8))
>Foo : Symbol(Foo, Decl(exhaustiveSwitchImplicitReturn.ts, 39, 1))
>Three : Symbol(Foo.Three, Decl(exhaustiveSwitchImplicitReturn.ts, 45, 8))

0;
}
} catch (e) {
>e : Symbol(e, Decl(exhaustiveSwitchImplicitReturn.ts, 92, 13))

console.log('some error')
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))

throw new Error('some error')
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
}

Loading