7

I try to compile:

const value = "20"
const x : string | never = "10" === value ? throw Error("bad things") : "hello"

... and get an error on throw - expression expected. I can resolve this using an inline method invoked in place, but that does not look nice. ((() => {throw Error("bad things"})())

Why is not OK to throw in a branch of the ternary operator? Or is there another syntax that works, perhaps compile options I'm missing?

Throw does not seem to work without curly brackets in the function body either, in the work-around, ((() => throw Error("bad things")()).

Jørgen Tvedt
  • 1,214
  • 3
  • 12
  • 23

3 Answers3

6

It is still an open issue.

https://github.com/microsoft/TypeScript/issues/18535

Based on the issue, the implementation won't begin until ECMA TC39 reaches some conclusion.

Xwtek
  • 1,151
  • 1
  • 9
  • 19
5

It's a syntactic quirk of the language.

The statement throw ex can be regarded as an expression that has the type never, because never is the type of functions that don't return, which is exactly what happens when you throw. In many languages that have a bottom type (the technical term for what never is -- it's not merely an "odd keyword").

The statement throw ex is different from, for example, the statement for (let x of ...) because the latter cannot be understood to return anything, whereas throw ex can be understood to return never.

Community
  • 1
  • 1
GregRos
  • 8,667
  • 3
  • 37
  • 63
  • Good answer, I agree with you. With the introduction of `never` in the language, as the return type of functions that throws - it would be natural to assume throw is an expression that returns `never`. – Jørgen Tvedt Nov 13 '16 at 15:24
  • I strongly disagree. `throw` is a statement just like a `for` that don't increment or a `while(true)`, and any return type of those statement would only make sense in the context of a subroutine call. The statement `throw Error()` is simply *not* an expression and *does not* have any type, not even `never`. A call to a subroutine **containing** that statement, or any other logic that never returns, is an expression and have the type `never`. – Alex Nov 13 '16 at 17:37
  • @JørgenTvedt An expression is supposed to *evaluate* to a value, while a statement is supposed to *do* something. I don't see much sense in trying to see a `throw` as having a value, but it has much more sense in doing something that changes the path of the code execution. There is just as much sense in `var x = throw Error()` as there is in `var x = while(true){}` or `var x = if(x == y) {...}`. – Alex Nov 13 '16 at 17:42
  • @Alex Thank you for your valuable feedback! Your opinion is obviously shared with the language team - and it should be no problem adapting now that I understand the rationale behind it. – Jørgen Tvedt Nov 14 '16 at 06:40
  • @JørgenTvedt Not only the typescript team but all major languages like C#, Java, Python, PHP, Javascript... `throw` or equivalent `statements` to raise exceptions are never expressions. The type `never`, which some languages represent, is the result of an expression as a call to *a function* that never returns, not the `throw`-statement itself. Sorry, but this answer is plain wrong. – Alex Nov 14 '16 at 09:40
  • @Alex Which part of the answer is wrong? The `throw` statement *can* be regarded as an expression of the type `never` with no contradiction. The fact you can just wrap it in a function and have it work is demonstrative proof of that. All the languages you mentioned above *do not actually have a bottom type*. Two of them are dynamically typed, and in those `never` does not even make sense. – GregRos Nov 17 '16 at 10:51
  • So what is the context of this question? Is it a universe of endless possibilities, or is it the current established domain of programming? 1. `throw` is a statement, not an expression, in TS and in all other languages. 2. `never` is used for expressions (subroutines) that never return. If you want to treat `throw` as an expression, you should treat *all* statements (except assignment statements that actually are expressions as well) as an expression that never return. There is nothing special about `throw`. 3. Your "proof"` don't make sense, it's the very function that makes it an expression. – Alex Nov 17 '16 at 11:08
  • Check out wiki for bottom type: *It is used to represent the return type of a **function** that does not return a value: for instance, one which loops forever, signals an exception, or exits.* – Alex Nov 17 '16 at 11:09
  • @Alex In *all* other languages? You just listed several languages, none of which have a bottom type. In Scala, that does have a bottom type, `throw` actually *is* an expression. Moreover, I have to commend you on a great job of quoting Wikipedia out of context. That is *one use* for the bottom type; it's not defined that way. The Wikipedia article actually gives the `throw` construct as an *example* of an expression that returns the bottom type. – GregRos Nov 17 '16 at 12:22
  • Alright, almost all languages. And whether or not a language represents a bottom type or not has no bearing on how it should handle statements versus expressions, those are two different concerns. And sure, one theoretical article from -97 cited on wikipedia thinks that the bottom type is natural for raising an exception, and the rest of that sentence says just what I commented above, that we then should handle all statements as expressions returning the bottom type. *..and similarly for other control structures. Intuitively, Bot here is the type of computations that do not return an answer.* – Alex Nov 17 '16 at 12:46
  • What I mean to say is that 1. Almost all languages treats raising an exception as a statement. It's not a "syntactic quirk" of TS. 2. There is no significant theoretical difference between `throw` and `for (let x of ...)` in regards of whether or not it's logical to think of those statements having a return type. – Alex Nov 17 '16 at 12:53
  • And as you yourself wrote in your answer: `because never is the type of functions that don't return, which is exactly what happens when you throw`. First part right, second part wrong in TS and in most other languages. Raising an exception is a statement, calling a function (whatever it returns, if it returns anything or if it does not return at all) is an expression, where it is relevant to talk about a type. If that `throw` would have been inside a function, then it's relevant to talk about that parent function having `never` as a type, not the statement itself. – Alex Nov 17 '16 at 13:03
  • 1
    @Alex There is a significant theoretical difference between `throw` and `for` statement. You don't return in `throw` statement (or expression in some language that supports that). So you can assign that a Never type. You do return in `for` statement, but there is nothing valuable to return. (How to make a for statement return something? What if the for statement block never executed? What if it executes a looping break? What if the loop breaks because the condition for the next loop is false?). So, even if for statement is a expression, the only possible type is Void type, which is useless. – Xwtek Jun 07 '20 at 09:20
  • @Alex that above statements use Typescript terminology. – Xwtek Jun 07 '20 at 09:21
  • @Alex On the other hand, throw statements having a type is certainly useful. Because never type is a subtype of all types. The example of the usage is on the OP's question. – Xwtek Jun 07 '20 at 09:25
4

This is how many languages are constructed, you have statements and expressions. In this case you are assigning x a value, but throwing an error is not a value/expression, it's a statement/operation.

You can't do this for just the same reason why you cant write something like:

const x : string | never = "10" === value ? for(var i in myArray) { /* do stuff */ } : "hello".

It's simply against the rules of the language, even if you can "hack" around it using a self evaluation function, which theoretically evaluates and is an expression.

Check out this question for a better explanation of statements and expressions.

The never-keyword in TS is an odd type connected to their control flow analysis. It does not allow you to suddently treat statements as expressions, so if you don't want your immediately invoked function expression, you could just use a regular function:

const value = "20"
var thrower = () => { throw Error("bad things"); }
const x: string | never = "10" === value ? thrower() : "hello";

If this really is what you want to do. This would be much more readable:

//...

if(value !== "10")
    throw Error("bad things");

const x: string = "hello";

//...
Community
  • 1
  • 1
Alex
  • 14,104
  • 11
  • 54
  • 77