Repository navigation
Open
Labels
Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.This issue needs a plan that clarifies the finer details of how it could be implemented.SuggestionAn idea for TypeScriptAn idea for TypeScript
Description
Suggestion
π Search Terms
List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.
β Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
β Suggestion
π Motivating Example
https://twitter.com/Rich_Harris/status/1437490023763415043
is roughly a confusion on narrowed vs storage type of a given field/binding
// _: any
let _;
// _: any
_ = new Date();
// _: any, but completions from Date
// type doesn't match usability, only assignability
_.getFullYear();π» Use Cases
Current UI / typing only shows what can be assigned to a value storage location given a source text location.
Need UI to show what can be used from a value storage location given a source text location.
Much of the time these should be the same but showing something about the narrowing would apparently remove some confusions, for example (bikeshed):
let _;
_ = new Date();
// _: any
_;
// becomes...
// _: any as Date
_;Reacted by Daniel Rosenwasser, Justin Grant, whzx5byb and Matt Kantor
Reacted by Rich Harris, Justin Grant and Alexey Iskhakov
Metadata
Metadata
Assignees
Labels
Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.This issue needs a plan that clarifies the finer details of how it could be implemented.SuggestionAn idea for TypeScriptAn idea for TypeScript
Activity
MartinJohns commentedon Sep 14, 2021
After assigning a value the type changes:

Trying to pass it to an incompatible type will fail as well:

It's not
anyanymore.orta commentedon Sep 14, 2021
I think this Workbench Repro shows the oddness being discussed pretty well:
MartinJohns commentedon Sep 14, 2021
Oh, I see, I misunderstood. It's about the tooltip on the assignment side.
DanielRosenwasser commentedon Sep 14, 2021
Since accessors can now diverge in their types and have the same issue, I could imagine a notation like
DanielRosenwasser commentedon Sep 14, 2021
One technical problem (I believe) is that in
by the point of assignment, control flow analysis hasn't changed the type of
x- it's only after the assignment ofxthat we'd ever be able to show a different type. As a user, I would expect to seeDate, but for all intents and purposes, I think you'd still end up withany.justingrant commentedon Sep 30, 2021
Really nice improvement! Would solving this issue also fix the narrowed-by-assignment code below? (playground)
Or is this a different problem? If "different problem" then is there a better solution than declaring new variables (which has a runtime footprint that we'd prefer to avoid) or liberally using type assertions downstream from a type-narrowing mutation (which is verbose and bug-prone)?
I'm asking because we're midway through porting the Temporal polyfill from JS to TS. We're just now getting to the part where we add type annotations to function return types and function parameters. But a lot of the old JS polyfill code mutates
letvariables and (especially) function parameters like in the trivial example above.MartinJohns commentedon Sep 30, 2021
Justin Grant (@justingrant) I don't see how a local variable has any relevant runtime footprint, but you can use a Type Assertion here:
That an
unknownvariable does not narrow on assignment is intentional.justingrant commentedon Oct 2, 2021
My naive expectation would be that because
if (typeof x === 'string')andif (x)both narrowx, thenx = 42would also narrow it untilxis reassigned again.Is there a good issue or PR to read about the rationale for why one pattern narrows but the other doesn't? TS usually has good reasons for things that seem counter-intuitive, and I assume the same is true here too.
Probably no significant perf footprint (and avoiding reassignment might even be better perf depending on how engines optimize), but:
constassignments may impact bundle size somewhat (although hopefully not too much once gzipped)Yep, adding hundreds of type assertions is the runtime-invisible way to work around this problem and avoids the problems in the bullet points above. But it's not cost-free either:
durationneeds to be annotated 10 times.[-]Show both narrowed type and storage type[/-][+]Show both narrowed type and declared type[/+]Andarist commentedon Jun 9, 2023
Daniel Rosenwasser (@DanielRosenwasser) is there any specific reason why CFA doesn't change the type of
xdirectly at the assignment?I have a hacky fix for this locally that creates a fake
FlowFlags.Assignmentmutation (but in the checker whereas usually all of that happens in the binder) to proxy a situation in which I read the"post-assignment" type. I don't particularly like this hack though π It feels that it would be easier to just change the type directly at the assignment but I'm not yet sure what that would change conceptually. Without knowing much about CFA's requirements and inner workings it almost feels like this should be a benign change (one that perhaps would require some code changes here and there but still). I have a feeling that I might be missing something here though.