-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Inconsistent narrowing in arrow function #32300
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
Comments
Also:
|
I believe that would fall under #31734 which relates to what type |
The root cause is that control flow narrowing applies to arrows and function expressions. It's not unique to arrows: const fexpr = function () { return state } Now The reason is that function and class declarations are hoisted -- so control flow analysis doesn't apply because the compiler isn't sure when they will run. They use the declared type of |
Thanks for the explanation, @sandersn. It makes sense to me that hoisted values would be handled differently, but I don't 100% understand this difference. I need to sit down and formulate some follow-up questions. Is this an acceptable place to ask them or should I create an SO question for follow-up? Also, does the fact that hovering over |
This is an OK place for follow-up since there's discussion here already, but use SO as the default for future questions.
|
I know this isn't really relevant to the issue but: are they? This is a runtime error, e.g.: new C(); // ReferenceError: Cannot access 'C' before initialization
class C {} I guess the |
Right, just as with let C;
new C();
C = class C {}; |
Right, but fn(); // hunky dory
function fn() { console.log("Hello world!"); } Which is why your |
Yep, my mistake about the @sandersn, looking at this a bit more, I'm still not clear on the originally discussed typing difference. If it's a limitation, I can understand, but it's not clicking for me why it would be intended behavior. To be clear, my intention is not to nit over this issue's classification, but to understand TS at a deeper level. More specifically, I'm getting caught up on "control flow analysis doesn't apply because the compiler isn't sure when they will run". If In short, I don't see any cases where Additionally, as @fatcerberus pointed out, class implementations are not hoisted so it seems like they would be treated in a similar manner to arrow/function expressions. |
I’m actually a bit confused myself because I had thought local narrowings were always discarded at function boundaries, except for IIFEs where the narrowings are retained inside the function body for obvious reasons. Your case doesn’t involve an IIFE so I’d have expected even the arrow function to automatically widen to |
The underlying question here is whether, in a given position, TS should use the declared type or the control-flowed type. For a declaration, which inherently does not belong in a control flow graph, it doesn't make any sense to talk about the control-flowed type, because it has no place in the graph from which to compute which narrowings are in effect. The code inside those declarations can run at any "time" (including times at which a This isn't the case for an expression that initialized a You can call these behaviors inconsistent, which they are, except that different code really should cause different behavior. "For consistency, this program should be treated identically to this nearly-equivalent program" is always at most two hops away from a paradox |
Thanks, Ryan, that helped to bring the issue into focus a bit more. Now I understand why accessing a For |
It's really unknowable which behavior you "wanted" when you I think more to the point, if you wrote const x: T = e; instead of const x = e; when |
I agree with this, but the correlary to that is: why should |
People write code like this and expect it to work: type Method = "GET" | "POST" | "PATCH";
declare function doSomething(x: "GET" | "POST") { ... }
// later
const x: Method = "GET";
doSomething(x); It'd be really weird to have |
I disagree that there's necessarily intent there. interface Foo {
bar?: number
}
const foo = { baz: 3 } as Foo // Compiles; allows for typo
const foo2: Foo = { baz: 3 } // Error; catches typo Perhaps I need to be doing both. #7481 would also remedy this if it were implemented with a prefix notation. FWIW, I was also surprised that It was particularly surprising that hovering over |
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
TypeScript Version: 3.6.0-dev.20190704
Search Terms: inconsistent union narrow const initialization arrow function
Code
Expected behavior:
Arrow === Fn === Method === BoundMethod
What that type should be is #31734
Actual behavior:
Arrow === () => ClosedState
Fn === Method === BoundMethod === () => State
Playground Link: Playground
Related Issues: #31734, #8513, possibly dupe of #29260 but I don't think so
A couple things to highlight:
const state
tolet state
achieves the expected behavior with a value ofState
.const state
indicates that it isState
, even thoughtypeof state === ClosedState
. I did not create a separate issue for that because it seems likely that that is a symptom of this issue rather than a distinct issue with the language server.The text was updated successfully, but these errors were encountered: