-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
throw
in arrow functions forces a void
return type
#7538
Comments
so is the proposal to disable the check if there is a contextual type? |
I suppose this is currently according-to-spec. Options:
I don't think those are observably different |
I suppose if all code path in a function result in a throw, the function could just return |
I lack the insight into the TS type inference engine to make a useful, concrete suggestion, but hand-wavingly, This seems to work as expected for |
The difference isn't between // OK
const bar: () => number = (): number => {
throw new Error('WHYUNO?!?');
}
function foo() {
throw new Error('Everything is gonna be alright!');
}
// Error
var f: () => number = foo; |
Okay, I see. Still, I guess
Is there a chance we can get the same behaviour for |
As Ryan said, nothing special about arrow functions here. Here is another example: // compiles
function foo(): number {
throw new Error('Everything is gonna be alright!');
}
// compiles
const bar = (): number => {
throw new Error('okay');
} MoreFrom #7538 (comment) For that code to compile one of two things can be done. I don't like either. Here are the reasons: 1.) var retVoid: () => void;
var retNum: () => number = retsVoid; // Currently Error I actually like this error and feel removing it is going to go in the inverse direction of safety. 2.) // Infers the return type as `null` which is the same as `any` in the current type system
const retAny = function() { return null; }
// Infers the return type as `void`
const retVoid = function() { throw new Error('err'); } Again I actually prefer the inference of |
@basarat I agree that the first approach is not desirable. For the second case though, why would you argue that throwing an error should be This principle is called ex falso quodlibet, meaning that if we deal with an impossibility (it's impossible to get a return value from a function that unconditionally throws), we can infer anything from it. |
To reference some prior art from other programming languages that I would describe as having a reasonably sound type system: Java (compiles): public class HelloWorld{
public int foo() {
throw new RuntimeException();
}
} Haskell: In Haskell the type of throw :: forall a. Exception e => e -> a Notice the universal quantification here! All return types are valid, since there is no return value. Rust (compiles): fn foo() -> i32 {
panic!("OMG");
} C++ (compiles):
|
@srijs Thanks for sharing that. I learnt something 🌹 For the examples: fn foo() -> i32 {
panic!("OMG");
} int foo() {
throw false;
} TypeScript compiles as well: function foo(): number {
throw new Error('Everything is gonna be alright!');
} But the question is what should it infer in the absence of a return type annotation. PS : My opinions are my own. I might be wrong and am fine what whatever everyone else chooses. But don't mind me continuing to discuss this 🌹 |
Maybe I'm a little spoiled by Hindley-Milner, but I really like concept that type annotations don't change the type of an expression, but just aid the type checker in inferring types and can only ever narrow it down (so I can specialise I would like it to infer the most general type possible by default, and allow me to narrow it down rather than require me to add annotations to make it more general. |
Consider: function whatType(x : boolean) {
if (x) {
return false;
} else {
throw new Error("asdf");
}
} The inferred return type is function whatType(x : boolean) {
if (x) {
} else {
throw new Error("asdf");
}
} I feel the return type should be what it is the normal return path i.e. |
I discussed this more at work. The key difference from Haskell (I don't know that much but do know F#) is that JavaScript is not an expression oriented langauge. F# infers the type to be let f x = if x then x else failwith "asdf";; Basically there is an implicit function whatType(x : boolean) {
throw new Error("asdf");
} It isn't function whatType(x : boolean) {
// WARNING: Compile error just as a proof of idea
return throw new Error("asdf");
} It is actually: function whatType(x : boolean) {
throw new Error("asdf");
return void 0;
} Hence the |
I think I brought up Java and C++ as well, which are probably more in the spirit of TypeScript (imperative, statement-oriented). I guess TypeScript is in an interesting spot here where it is one of probably very few imperative languages that have some form of type inference. Don't get me wrong, I'm happy that I can achieve the universal quantification by adding additional type annotations. I was just pointing out that the compiler is inferring a less general signature which works in fewer cases and occasionally adds a bit of confusion (such as in my case). Ideally I'd like to get away with as few explicit type annotations as possible (with |
Basic idea for a neat fix here is that we can have a special This enables a bunch of nice patterns -- you can e.g. |
I think we'll want to call it |
Ref #3076 in case there's some synergy there |
Isn't this the same as the |
Fixed in #8652. |
TypeScript Version:
1.8
Code
Expected behavior:
Functions
foo
andbar
type-check, because an exception is thrown in their body which effectively means "bottom", so any return type should be permitted.Actual behavior:
While function
foo
type-checks,bar
does not.The text was updated successfully, but these errors were encountered: