You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Problem:
There is no way to get a literal type value without explicit type annotation.
yes
yes
yes
Reacted by Tom Crockett, Zoey, Patrick Lienau, Jay Wick, Samuel Sonne, Charles Samborski, Ruslan Arkhipau, Igor Bezkrovnyi, Luca Steeb, Piotr Witek and 11 more
Ah, gotcha. Yeah, I hardly ever use implicit types, I manually type everything explicitly. I get the need for this proposal, but I personally think the proposed syntax is unexpected behavior. I don't think it will break anything, but there seem to be a lot of edge cases and as a user, I don't expect (true) to type differently than true. I don't like surprises in my type checking.
Personally, I would just write this and make the intent explicit:
constvalue=['a',1]as[string,number];
Of note, you can't use literal types in "as" notation, which I would propose to fix this issue personally:
A quick search of my code base of the previous big project I did in Typescript gives me this:
varborderWidth=(4);
...
borderWidth=12;
I assume this breaks with your proposal? In which case, yes, your proposal would break the last big codebase I worked on. Why are those parenthesis there? I assume at one point it said something like borderWidth = (4 * someOtherValue);. Real life code bases are messy, someone forgot to take those parenthesis out. I wouldn't mind something like that breaking with an update of TypeScript btw, just saying that people do casually type stuff like that.
you are lucky to find one place that, well, was left unattended, rather than crafted the way it is on purpose, and it's just one scalepan... - your unintentionally overlooked code, the other scalepan is a new feature that enables the whole new world of exciting opportunities and universal happiness, now what exactly are we arguing about?
Reacted by Carlos Roso, Bryan Lee and Anthony Gallo
Reacted by Piotr Witek, Artur Grzesiak and Leo Y. Li
We're not arguing, I said from the beginning I like the idea of the feature 😉
I'm just not sure if the proposed syntax is to my liking, seems a bit unexpected. Then again, as I said, I don't use implicit typing, so it really doesn't mean much to me.
Just one more observation: if I have to manually type those parenthesis, then I'm explicitly annotating that literal. I don't think your proposal is implicit annotation at all, it's just a shorthand explicit annotation. The shorthand is universal and saves you from explicitly mentioning the type, but it's explicit regardless. Anyway, I'm knee deep in physics integrators right now, time to get back.
hmmm how about a new operator? := This way, if the compiler, by any chance can infer the value it will explicitly assing the type.
consta :=1;// a is 1// Can work with expressionconstb :=(Math.random()*0);// b is 0// As it won't be confused with explicit parentheses like:constb=(Math.random()*0)+1;// b is number even though b is always 1constc :=Math.random();// c is numberconstd :='hello';// d is 'hello'conste :=!false;// e is true
:= kinda aliases explicit type annotation const a: 1 = 1 => const a := 1, the syntax is new and kinda expected 😁
Reacted by Sean Vieira, Jessica Fiedler, Tom Marius, Ed Staub, Nurbol Alpysbayev, Frank Sandqvist, Abraham White, Roland Groza, Ken Mueller, Albert Mañosa and 1 more
Reacted by Ilia Choly, Yann Massard, Ghabriel Nunes, Chiri Vulpes, Alec Larson, Amin Pakseresht, csha, Marcin Wojnarowski, Khalid Sabry, Simon Reinisch and 1 more
Yup, I get it now. I looked over your initial thread too. I'll give this a thumbs up, I do think it would be useful and it does have a parallel to the arrow syntax.
Igor Oleinikov (@Igorbek) Well that's true in that particular case, but let's take a look at another example that I just encountered:
functionselect<T,Rextends{[PinValueKey]: T},ValueKeyextendsstring="code">(item: T,values: R[],options: {valueKey: ValueKey}={valueKey: "code"}asany){returnvalues.find(value=>value[options.valueKey]===item);}select(1,[{code: 1,label: "hello"}]);// Works, ValueKey = "code" which is the default.select(1,[{id: 1,label: "hello"}],{valueKey: "id"});// Error, ValueKey = string, understands that values should be {[key: string]: number} and label is a stringselect(1,[{id: 1,label: "hello"}],{valueKey: "id"as"id"});// Works, ValueKey = "id"
Of course, if the compiler could infer the type properly that would be great, but I'm not really sure how possible this is, especially if we care about backward compatibility.
I hope my PR #17785 would address this, by allowing people to reuse the const vs. let distinction to indicate whether they want [1,2,3] or number[]. There's obviously no silver bullet (what of [number]? (1|2|3)[]?), so there will always be cases where you may need casts. I think where this PR adds value though is by increasing user control.
I saw some comments proposing syntax that would enable literal inference only for assignation (:=), this is not enough. I type most of my declarations explicitly but I still had issues because of lack of literal type narrowing when I updated one of my libraries to use mapped types. Mapped types improved the "correctness" of the types by removing some anys but require the types of the various objects to be better inferred.
Here is a minimal example exposing the issue:
// Lib part: Provides classes to build schemas and test them at runtimeinterfaceMetaType<T>{test(val: any): val is T;}// Represents a specific variant from an enumclassEnumLit<T>implementsMetaType<T>{variant: T;constructor(enumVariant: T){this.variant=enumVariant;}test(val: any): val is T{returnval===this.variant;}}// Represents an object with multiple properties, each with their own typeclassDoc<Textends{}>implementsMetaType<T>{props: {[PinkeyofT]: MetaType<T[P]>};constructor(props: {[PinkeyofT]: MetaType<T[P]>}){this.props=props;}test(val: any): val is T{for(constkinthis.props){if(!this.props[k].test(val[k])){returnfalse;}}returntrue;}}// User codeenumAnimalName{Duck,Cat,}interfaceDuck{name: AnimalName.Duck;}// This breaks because {name: MetaType<AnimalName>} is not assignable to {name: MetaType<AnimalName.Duck>}// This worked previously because Doc.params was just `{[P in keyof T]: MetaType<any>}`const$Duck=newDoc<Duck>({name: newEnumLit(AnimalName.Duck)});// You have to explicitly state the generic parameter of EnumLit (really heavy due to repetition)const$Duck2=newDoc<Duck>({name: newEnumLit<AnimalName.Duck>(AnimalName.Duck)});
Complete error:
error TS2345: Argument of type '{ name: EnumLit<AnimalName>; }' is not assignable to parameter of type '{ name: MetaType<AnimalName.Duck>; }'.
Types of property 'name' are incompatible.
Type 'EnumLit<AnimalName>' is not assignable to type 'MetaType<AnimalName.Duck>'.
Types of property 'test' are incompatible.
Type '(val: any) => val is AnimalName' is not assignable to type '(val: any) => val is AnimalName.Duck'.
Type predicate 'val is AnimalName' is not assignable to 'val is AnimalName.Duck'.
Type 'AnimalName' is not assignable to type 'AnimalName.Duck'.
Regarding the syntax bikeshedding, the idea of parens is nice but I agree that it can be confusing and may break many code generation tools.
I'd propose an addition similar to the ! assertion operator: add a unary "literal type narrowing" operator. For example @ or # are unused currently.
Here is a comparison of what the various propositions may look like in my example:
Charles Samborski (@demurgos) I'd argue specific types shouldn't require additional effort as type widening is mostly useful under specific circumstances (mutable variable, i.e. var/let assignment), meaning we already have a decent idea what default makes sense when.
While it only works for a limited amount of cases, I would suggest overloading the ! postfix operator when used on literal value expressions; since we know that literally 1! would never be nullable, this now means that it's exactly one. I would also think that this has lower impact than the parens idea, since the postfix ! is already a typescript-only syntax, and the only people writing 1! would be doing it as a typo.
So, some examples using postfix !
(filtering out those that have been solved by #10676)
constfoo='foo'// 'foo'constbar=[foo!]!// would still be [string]constvalue={a: foo!}// still {a: string}
The other syntax solution I can think of (since I like keywords more than characters :P ) is to use as const as a postfix, for example
constvalue={a: fooasconst}
Or, maybe both? Allow postfix ! to narrow when it's a literal, and as const for more complex mappings?
Edit (2019-01-14): m93a as submitted this as a separate issue as #26979
Reacted by Ryan Cavanaugh, Charles Samborski, Tom Crockett, falsandtru, Piotr Witek, Christian Svensson, Sean M. Vieira, Andrey Shcherbinin, Patrick Lienau, Robbie Speed and 5 more
good thing is that we can piggyback ride on the existing TypeScript only expression level syntax ! (which is so much at odds with its design goals but who cares right?)
bad part is that there is no way to see what is going on in const value = {a: foo!} without knowing what foo is, it's going to be a nightmare for code reviewers like myself
My interpretation was that ! would only have the literalizing effect on true literal expressions; even ("foo")! should be a no-op IMO. Otherwise you get into a ridiculous situation when expr: "foo" | null - do you then have to write expr!! to prevent it widening?
Reacted by Emily Marigold Klassen, Charles Samborski, interphx, Robbie Speed, Alec Larson, JongChan Choi (Rieul) and S. B. Tam
Maybe the problem could be mitigated by telling the compiler more precisely which objects (i.e. standard objects and arrays) are actually constant and which should be changeable. Thus, the as const proposal by Emily Marigold Klassen (@forivall) seems to be a reasonable solution (although a bit cumbersome to use - which could be eased by having some sort of top-down cascading behaviour for, say as const!).
I proposed something like this too in #20195, which is to extend the readonly operator to be applicable to object (and array) literals, e.g.
Activity
SimonMeskens commentedon Aug 7, 2016
Can you explain what the problem with this syntax is? Too verbose? This works in Typescript today.
In fact, just tested and this works too:
I assume the problem here is that you need to manually annotate the value? You want sane implicit typing of literals?
zpdDG4gta8XKpMCd commentedon Aug 7, 2016
yes
yes
yes
SimonMeskens commentedon Aug 7, 2016
Ah, gotcha. Yeah, I hardly ever use implicit types, I manually type everything explicitly. I get the need for this proposal, but I personally think the proposed syntax is unexpected behavior. I don't think it will break anything, but there seem to be a lot of edge cases and as a user, I don't expect (true) to type differently than true. I don't like surprises in my type checking.
Personally, I would just write this and make the intent explicit:
Of note, you can't use literal types in "as" notation, which I would propose to fix this issue personally:
zpdDG4gta8XKpMCd commentedon Aug 7, 2016
could you give me an example when you casually type
(true), please?zpdDG4gta8XKpMCd commentedon Aug 7, 2016
point is it's the waste of syntax, no one types
(true)in their everyday work, why not to give it a better use?SimonMeskens commentedon Aug 7, 2016
A quick search of my code base of the previous big project I did in Typescript gives me this:
I assume this breaks with your proposal? In which case, yes, your proposal would break the last big codebase I worked on. Why are those parenthesis there? I assume at one point it said something like
borderWidth = (4 * someOtherValue);. Real life code bases are messy, someone forgot to take those parenthesis out. I wouldn't mind something like that breaking with an update of TypeScript btw, just saying that people do casually type stuff like that.zpdDG4gta8XKpMCd commentedon Aug 7, 2016
you are lucky to find one place that, well, was left unattended, rather than crafted the way it is on purpose, and it's just one scalepan... - your unintentionally overlooked code, the other scalepan is a new feature that enables the whole new world of exciting opportunities and universal happiness, now what exactly are we arguing about?
SimonMeskens commentedon Aug 7, 2016
We're not arguing, I said from the beginning I like the idea of the feature 😉
I'm just not sure if the proposed syntax is to my liking, seems a bit unexpected. Then again, as I said, I don't use implicit typing, so it really doesn't mean much to me.
zpdDG4gta8XKpMCd commentedon Aug 7, 2016
then give me some thumb's up's!
unexpectedness of the syntax is already spotted (in the parent proposal), admitted and listed here under "Shortcomings"
i confess i lived a sinful life, the proposal is not 100% perfect
SimonMeskens commentedon Aug 7, 2016
Just one more observation: if I have to manually type those parenthesis, then I'm explicitly annotating that literal. I don't think your proposal is implicit annotation at all, it's just a shorthand explicit annotation. The shorthand is universal and saves you from explicitly mentioning the type, but it's explicit regardless. Anyway, I'm knee deep in physics integrators right now, time to get back.
alitaheri commentedon Aug 7, 2016
hmmm how about a new operator?
:=This way, if the compiler, by any chance can infer the value it will explicitly assing the type.:=kinda aliases explicit type annotationconst a: 1 = 1=>const a := 1, the syntax is new and kinda expected 😁zpdDG4gta8XKpMCd commentedon Aug 7, 2016
well yeah, i never said i wanted it implicit, all i want it to get rid of
while still being explicit about my intentions at defining a literal value
SimonMeskens commentedon Aug 7, 2016
Yup, I get it now. I looked over your initial thread too. I'll give this a thumbs up, I do think it would be useful and it does have a parallel to the arrow syntax.
zpdDG4gta8XKpMCd commentedon Aug 7, 2016
Ali Taheri Moghaddar (@alitaheri) good catch!
i agree the condition expression of the ternary operator is usually tend to be braced in parenthesis
here is the thing though:
a literal expression is an expression whose terms DO NOT contain variables
i hate the flatness of this statement but nevertheless it would enable what's required it if accepted
zpdDG4gta8XKpMCd commentedon Aug 7, 2016
Ali Taheri Moghaddar (@alitaheri)
:=is limiting it to assignment cases onlyhow about binding arguments to parameters?
86 remaining items
[-]syntax for literal types literals[/-][+]Syntax for hinting literal type inference[/+]JabX commentedon May 4, 2017
Igor Oleinikov (@Igorbek) Well that's true in that particular case, but let's take a look at another example that I just encountered:
Of course, if the compiler could infer the type properly that would be great, but I'm not really sure how possible this is, especially if we care about backward compatibility.
KiaraGrouwstra commentedon Aug 26, 2017
I hope my PR #17785 would address this, by allowing people to reuse the
constvs.letdistinction to indicate whether they want[1,2,3]ornumber[]. There's obviously no silver bullet (what of[number]?(1|2|3)[]?), so there will always be cases where you may need casts. I think where this PR adds value though is by increasing user control.demurgos commentedon Jan 29, 2018
I saw some comments proposing syntax that would enable literal inference only for assignation (
:=), this is not enough. I type most of my declarations explicitly but I still had issues because of lack of literal type narrowing when I updated one of my libraries to use mapped types. Mapped types improved the "correctness" of the types by removing someanys but require the types of the various objects to be better inferred.Here is a minimal example exposing the issue:
Complete error:
Regarding the syntax bikeshedding, the idea of parens is nice but I agree that it can be confusing and may break many code generation tools.
I'd propose an addition similar to the
!assertion operator: add a unary "literal type narrowing" operator. For example@or#are unused currently.Here is a comparison of what the various propositions may look like in my example:
This operator could also be applied to expressions to ask the compiler to resolve the most specific type.
For example
@(1 + 2)would be typed as3.KiaraGrouwstra commentedon Feb 11, 2018
Charles Samborski (@demurgos) I'd argue specific types shouldn't require additional effort as type widening is mostly useful under specific circumstances (mutable variable, i.e. var/let assignment), meaning we already have a decent idea what default makes sense when.
Seems they didn't like this, see #15645.
forivall commentedon Mar 14, 2018
While it only works for a limited amount of cases, I would suggest overloading the
!postfix operator when used on literal value expressions; since we know that literally1!would never be nullable, this now means that it's exactly one. I would also think that this has lower impact than the parens idea, since the postfix!is already a typescript-only syntax, and the only people writing1!would be doing it as a typo.So, some examples using postfix !
(filtering out those that have been solved by #10676)
cases that it doesn't solve
The other syntax solution I can think of (since I like keywords more than characters :P ) is to use
as constas a postfix, for exampleOr, maybe both? Allow postfix
!to narrow when it's a literal, andas constfor more complex mappings?Edit (2019-01-14): m93a as submitted this as a separate issue as #26979
zpdDG4gta8XKpMCd commentedon Mar 14, 2018
good thing is that we can piggyback ride on the existing TypeScript only expression level syntax
!(which is so much at odds with its design goals but who cares right?)bad part is that there is no way to see what is going on in
const value = {a: foo!}without knowing whatfoois, it's going to be a nightmare for code reviewers like myselfRyanCavanaugh commentedon Mar 14, 2018
My interpretation was that
!would only have the literalizing effect on true literal expressions; even("foo")!should be a no-op IMO. Otherwise you get into a ridiculous situation whenexpr: "foo" | null- do you then have to writeexpr!!to prevent it widening?forivall commentedon Mar 14, 2018
Yup, exactly what Ryan is saying. The
!would only apply on literals, and soconst value = {a: foo!}would unambiguously be a non-null assertion.falsandtru commentedon Mar 26, 2018
Emily Marigold Klassen (@forivall) I coincidentally opened an issue about that syntax. Could you discuss about that in #22872 if you prefer?
aminpaks commentedon Dec 8, 2018
This is great, why can't we focus on just string literal type for now?
pelotom commentedon Jan 13, 2019
Bastian (@aloifolia)
I proposed something like this too in #20195, which is to extend the
readonlyoperator to be applicable to object (and array) literals, e.g.