Repository navigation
Suggestion: a way to disable type widening in object literals #20195
Description
I understand the use of type widening with let and var variables (I use const ubiquitously so this is never a problem). But with object literals I pretty much never want the fields to be widened, and yet that's the default behavior:
const o = { x: 3 }; // inferred type is { x: number }The only way I know of to work around this is with casts or type annotations, or by passing the object literal immediately to a function expecting the narrower type, thereby guiding type inference (although if the literal is being returned from a function even that technique won't work).
This is an eternal headache and it crops up in so many places. Would it be possible to add a compiler flag that makes it so that object literals don't get widened? I'm happy to be responsible for adding type annotations in the extremely rare cases where I actually want the widened type.
Edit
It looks like compiler flags are generally frowned upon to solve this problem, so let me amend my suggestion to take account of the discussion below. Mohamed Hegazy (@mhegazy) says,
We have talked in the past about a
readonlymodifier on property declarations.. e.g.const o = { readonly x: 3 };this allows the compiler to understand the intent of this object literal property, and not widen.
This would certainly be handy, but on its own it's still not ideal because one ends up writing readonly in a lot of places. Take for example CSS-in-JS objects, which involve many properties whose types are unions of string literals. Every single one of those properties needs to then be marked readonly in order to defeat the scourge of type widening. But if one could declare an entire object literal to have only read-only properties like so:
const o = readonly { x: 3, y: 'hello' };
// o: { readonly x: 3; readonly y: 'hello' }now this starts to look like a workable solution! So, let this be my amended suggestion: readonly modifiers for both object literal properties as well as object literals themselves, which has the side effect of disabling type widening.
To the argument that readonly and type-widening are separate concerns and should be treated independently, to some extent I agree, but I also feel they are related; this is why the let vs. const rules for type widening exist, and I think that rationale makes sense. I also personally don't mind conflating readonly and type narrowing, because I prefer immutable objects everywhere too, so a syntax like
const o = readonly { x: 3, y: 'hello' };would be attractive because it kills 2 birds with one stone. I currently don't use the readonly keyword much (even though in spirit I want everything to be read-only) because the cost-benefit ratio of annotating every single property in every single object literal is too high.
Activity
mhegazy commentedon Nov 21, 2017
We have talked in the past about a
readonlymodifier on property declarations.. e.g.this allows the compiler to understand the intent of this object literal property, and not widen.
zpdDG4gta8XKpMCd commentedon Nov 21, 2017
works today:
mhegazy commentedon Nov 21, 2017
I do not think the flag is right thing to do. such behaviors should not controlled by flags. The flags we have that alter the behavior are meant to be transitional, in other words, we would like everyone to turn them on, but acknowledge the need for a migration path. In this case, i am not convinced that
constdeclaration should mean that all the properties, however deep they are are immutable.. that is not the spec'ed behavior ofconst, nor what the compiler asserts.pelotom commentedon Nov 21, 2017
Mohamed Hegazy (@mhegazy) that's still less than ideal, because in the limit it requires writing
readonlymany many times when what I really want is just a blanket statement across my entire code base.pelotom commentedon Nov 21, 2017
If
readonlycould used as a modifier of object literals to make all their propertiesreadonly, I think that would be usable:Or if it were possible to write a function
makeReadonlythat could do the samezpdDG4gta8XKpMCd commentedon Nov 21, 2017
Tom Crockett (@pelotom) you should make it clear of what exactly you expect when you say
readonly x: 3, because one thing is to have the type3another thing is makingxproperty readonlyreading/writing is orthogonal to widening/narrowing
it's by accident that
readonlymodifier makes the property type narrowed to the type of the value used for initializingpelotom commentedon Nov 21, 2017
Aleksey-Bykov I agree, my concern here is with disabling type widening, and
readonlywas Mohamed Hegazy (@mhegazy)'s proposed means to that end. If there's another way to disable type widening that doesn't require me to use a keyword for every property in an object literal, I'm all for it.zpdDG4gta8XKpMCd commentedon Nov 21, 2017
also consider this
zpdDG4gta8XKpMCd commentedon Nov 21, 2017
because
is arguably more typing than
pelotom commentedon Nov 21, 2017
Also I should add that I personally don't mind conflating
readonlyand type narrowing, because I also prefer immutable objects everywhere, so a syntax likewould be attractive because it kills 2 birds with one stone. I currently don't use the
readonlykeyword much (even though in spirit I want everything to be read-only) because the cost-benefit ratio of annotating every single property in every single object literal is too high.zpdDG4gta8XKpMCd commentedon Nov 21, 2017
be aware there was a discussion on literal literals (no puns): #10195
pelotom commentedon Nov 21, 2017
Aleksey-Bykov I appreciate all of your proposed work-arounds; believe me when I say that I'm already making heavy use of such things, and find them unsatisfactory. This issue is for proposing an augmentation of the language which would make such workarounds unnecessary.
zpdDG4gta8XKpMCd commentedon Nov 21, 2017
i hear you, first class support would be ideal, i am convinced that literal type literals is the way forward
17 remaining items
dmitrysteblyuk commentedon Oct 18, 2018
What about a compiler option to make all declared objects readonly by default (with a type flag
mutableto undo that)? Would be useful for React+redux I think. As everything is readonly there - props, states, stores etc.KiaraGrouwstra commentedon Oct 18, 2018
dmitrysteblyuk #17785 (comment)
dmitrysteblyuk commentedon Oct 19, 2018
@tycho01 well, that's not what I meant. Currently objects properties are mutable by default and we have
readonlymodifier to change that. I suggested to have a compiler option to kinda reverse that behaviour - objects properties (and arrays) readonly by default, mutable if specified so directly in type declaration.cshaa commentedon Oct 19, 2018
We understand your proposal. The comment still applies. The proposal is neither a temporary patch for a breaking change in behavior (since mutable-by-default is the prefered way to go), nor a “stricter behaviors that we think users should move to”. That means it's a no-go for the maintainers of TypeScript.
dmitrysteblyuk commentedon Oct 19, 2018
m93a
Preferred by whom? The absolutely major part of code in React projects forbids mutating anything. Angular also doesn't do well with mutability (since it checks only object references changes now). So why do you think it's not stricter behaviours that users should move to?
KiaraGrouwstra commentedon Oct 20, 2018
heck, any static typing system doesn't do well with mutation.
for the record, I do, but they're stuck with all types of JS users ("any valid JS is valid TS!") and don't want their language to bifurcate. let's hope Wasm will save us from this.
that said, I think the Flow guys are pretty big on FP, though it wouldn't help Angular.
pelotom commentedon Jan 5, 2019
This continues to be a huge stumbling block for people trying to learn the language:
https://twitter.com/kentcdodds/status/1081333326290415618
(As well as an annoyance for those who understand what’s going on!)
Meligy commentedon Jan 21, 2019
I've been googling for the right issue for a while. Hopefully this is it.
A variation of the main issue is when the value is provided directly.
For example:
Ignoring the question about the return of the function
createAction, the main problem in this example is thatcreateActionsees the coming argument as a string, not as the string literal type"DO_STUFF".The main characteristic of this example is that the string is defined in-place where it's passed directly to the function.
There are workarounds, like
createAction<"DO_STUFF">("DO_STUFF")orcreateAction("DO_STUFF" as "DO_STUFF"), or evenconst type = "DO_STUFF"; createAction(type);, but these are ugly to require in a public API.This is inspired by this tweet which is about Redux Starter Kit.
stepanhruda commentedon Jan 22, 2019
Definitely a common problem. As a workaround, I sometimes use
asto narrow the type back to the intended constant. It's definitely not ideal:Works with e.g.
as 3oras trueas well.qm3ster commentedon Jan 22, 2019
Also a weird case:
😕
pelotom commentedon Jan 23, 2019
Everyone continuing to comment on this issue should check out #29510.