Description
We've pulled in a new feature called --strictOptionalProperties
which sits under the --strict
family of flags. If we were to start TypeScript over again, we believe the behavior of strictOptionalProperties
would be on by default; however, this strictness option comes at a time when the community is much more mature with lots of existing code.
I've opened up this issue to discuss some broader concerns so that we can ensure that --strictOptionalProperties
ships smoothly and so we can hear some feedback from users.
Off the bat, here's a few of my own concerns:
Discoverability of the Break
A user who upgrades TypeScript with --strict
on currently has no idea why their code has broken. At best, they will get an error like
Type 'T | U | V | undefined' is not assignable to type 'T | U | V'.
Type 'undefined' is not assignable to type 'T | U | V'.
We can do better here, which is why I've opened up #44403.
Manual Upgrade Woes
The most permissive thing to do to fix breaks from --strictOptionalProperties
is to tack on an | undefined
onto every single ?
-marked optional property; however, this is incredibly tedious for an existing project.
We'd like to provide some automated tooling to help alleviate this, which is why I've opened up #44419.
Surrounding Community Upgrade Woes
There are really 3 places that are outside most users' control that need to be upgraded to work correctly with strictOptionalProperties
:
lib.d.ts
- DefinitelyTyped (
@types
packages) - Libraries shipping their own types
The last one is the hardest to solve; however, the first two are way more automatable, and we can have the core team help with it, but it's definitely time taken away from other work, and that's a delicate tradeoff.
I haven't created an issue to manage this one, but I think it might be worth distributing segments of lib.d.ts
and some number of top packages to add | undefined
to the definitions and see if that helps. Ideally, automation from #44419 could help. We'd also have to update the lib.dom.d.ts
generator to emit | undefined
on most optional properties.
I'd like to get @RyanCavanaugh's thoughts on this one.
Other Feedback
I'm curious to hear feedback from users on this one - is it a useful option? Do you feel good about it and its purpose? Did it catch any interesting bugs? Let us know!
Activity
DanielRosenwasser commentedon Jun 4, 2021
New Errors list from last night #44427 👀
jakebailey commentedon Jun 4, 2021
Last night I tried updating pyright/pylance to a build of TS that included this to see how it would go. I'll try and sum up my thoughts...
I'm not sure that this check is going to end up being widely usable any time soon; I get loads of messages coming from the types of libraries I don't control, especially from the LSP client library (and spec), where they treat
?
as equivalent toundefined
. I think that this pattern is very common and unlikely go away.As for trying to fix our own code, the messaging is definitely bad; I found it very hard to try and locate the actual place I needed to make the change in a large project. I ended up starting tsc in watch mode, opening a file with an error in VS Code, then I see the problems (as VSC only shows errors in open files). Then, either the related diagnostic pointed back to the declaration (so I could jump and add
| undefined
), or confusingly did not, in which case I had to go-to-def on the error'd expression to figure out what to change.I'm sure it could be improved to say something like "property had a
?
but noundefined
", but that is going to push everyone to add| undefined
, in which case, is this check helping?Before I gave up, I still had hundreds of errors to fix. It seemed almost random which properties I needed to change, and I was left with a number of interfaces where some properties had
| undefined
and some did not, which tells me that at some point in the future, I may use a property in a new way and have to fix them.I'm not sure I've actually been able to find any bugs with this so far; maybe I'm too inclined to just try and stick
| undefined
everywhere. I don't think we do anyin
checks, and only care about truthiness.I'm pretty surprised that this is on by default; it feels really noisy (far noisier than the not-default
noPropertyAccessFromIndexSignature
) and I expect to have to disable it when I upgrade. I think it will be very surprising to users with"strict": true
to see this enabled.glen-84 commentedon Jun 4, 2021
Is the
Required
utility type supposed to be removing an explicitundefined
?https://www.typescriptlang.org/play?strictOptionalProperties=true&strict=true&ts=4.4.0-dev.20210602#code/JYOwLgpgTgZghgYwgAgKIA84FsAOAbFAbwChkzk4B+ALmQGcwpQBzZAH2QFcQATCGUBB4BuYgF9ixMAE8cKAILIAvMgAKwBAGsAPBmz4IAGmQAiOCYB8omXOTyASsuT2IAR07AoQ7fKuSEAPYgDMjotA5OhHC03HwCIEJiokA
I thought that it was supposed to just remove the
?
.OoDeLally commentedon Jun 6, 2021
I believe language features should be a right balance between exactness and usability, i.e. a health dose of pragmatism.
With this
strictOptionalProperties
, I feel like we are sacrificing a lot of usability and readability, just for the purpose of exactness.From my experience there are very little cases (in fact, I've never encountered any) where I care about whether a field is
undefined
or missing (and if I ever did, there are ways to assert that).Now, because I use the
strict
option, I have to clutter all my types with| undefined
.I guess the issue here stems from the fact that there are many levels of strictness, and because
strict
is binary, thetrue
value meansall possible strictness rules
. Maybe there could be several levels.fatcerberus commentedon Jun 6, 2021
There are - set the individual flags you need. Multiple
strict
levels + individual options would just be way too much cognitive overhead IMO. If I'm at the point where I'm asking myself "Which level of strictness do I need?" I should probably be researching what exactly my options are anyway.fatcerberus commentedon Jun 6, 2021
Fun fact: When I used to write plain JS, I did
"foo" in obj
tests all the time. TS forced me out of that habit (very begrudgingly) because it had, until recently, no type-level way to distinguish between?
and| undefined
. So while I consider myself pragmatic, I'm hesitant to throw around pragmatism as an excuse here because I never considered the lack ofstrictOptionalProperties
to be very pragmatic 😉Pajn commentedon Jun 7, 2021
It does, you can create a type like
{foo: string} | {}
if you wantfoo
to be optional but not allowundefined
as a value so the question is more about final type is more desirable and thus should be represented with the most ergonomic{foo?: string}
syntax. Personally I beleave the old way is more common partly because of this statement in the initial post:Which I also believe would be true for most third party libraries as well. If the new
strictOptionalProperties
behaviour was more common it would have been a widely requested feature up until now. However, now that I know about it I can just addstrictOptionalProperties: false
to all my tsconfigs so it's not a massive problem. Though if library authors start addingstrictOptionalProperties: false
they would not catch this and release typings that causes problems forstrictOptionalProperties: true
so there is a small risk that this causes a lot of churn for both users and library authors.jcalz commentedon Jun 7, 2021
Playground link
Excess property checks only happen in specific situations, and they never happen for the empty object type
{}
. There aren't any truly exact types (#12936) so even with excess property checking you could find fun ways to sneakundefined
(or evenunknown
) property values in there.I guess "widely" is a subjective term, but #13195 does have a decent number of positive reactions, comments, and linked issues.
Pajn commentedon Jun 7, 2021
{ foo: string } | { foo: never }
then, it's always possible to type it if you actually need it. But I'm not arguing against the existence of this, as I said, I'll just disable it and move on.26 remaining items
lobsterkatie commentedon Dec 10, 2021
@DanielRosenwasser @RyanCavanaugh - Pinging on this. Any chance you'd consider the above?
(Or perhaps I'm wrong about this?
)
RyanCavanaugh commentedon Dec 13, 2021
Every strictness flag we've added since day 1 has implied work for upstream maintainers. We assessed the impact of this on DefinitelyTyped and other code bases and felt it was a worthwhile trade-off.
I'm not sure what sort of behavior you're describing with the suggestion. A concrete proposal of what specifically you think could work differently would be a good next step.
lobsterkatie commentedon Jan 19, 2022
Sorry for not being clear. What I mean is this:
@sentry/browser
doesn't make the distinction between missing values and values set toundefined
. If we were only building for ourselves, we'd setexactOptionalPropertyTypes
to false and continue to just use optional properties in our types, and all would be well.But my understanding is that if anyone using
@sentry/browser
hasexactOptionalPropertyTypes
set to true, and they passundefined
in for some value we've marked as optional, they'll get an error. Currently, the only way for us to help such a user is to add| undefined
to all of our optional properties (or at least, any which might conceivably get passedundefined
). Correct?What I'd like is not to have to do that, because IMHO it makes our types cluttered and harder to read, all in order to preserve a distinction our code doesn't make. So I wish there were a setting we could set along the lines of
pretendAllOptionalPropertiesInAnyTypesWeExportHaveAnOrUndefinedOnTheEnd
. In other words, I'd like to be able to give users of@sentry/browser
blanket permission to passundefined
in for any optional property in our types, even if those users haveexactOptionalPropertyTypes
set to true.Hopefully that makes more sense. Failing such an option, perhaps there could be a new
??
designation for properties, so thatsomeProp?: string
would mean "pass a string or nothing at all" andsomeProp??: string
would mean "pass a string or undefined or nothing at all" (with??
essentially playing the role?
used to play). Quite frankly I'd take anything which would make this work in a way which doesn't sacrifice so much readability in order to solve a problem which (as far as usage of our SDK is concerned) doesn't exist.Thanks.
vbezhenar commentedon Feb 14, 2023
Just my 2 cents. I'm starting with typescript and stumbled upon this issue when trying to use this flag.
Here's my simple function:
I didn't find any ergonomic way to implement this code with new flag. Here's one way I've come with but it's ugly:
I think there should be a language feature to support this compiler flag to allow for more ergonomic usage.
In this particular case with AbortSignal, there's actually another way:
signal: signal ?? null
because RequestInit declares signal assignal?: AbortSignal | null;
. But I think that API like that is not always available.That said, I like this flag because it helped me to spot some weird API I designed (like using
x?: X
instead ofx: X | undefined
.RyanCavanaugh commentedon Feb 14, 2023
@vbezhenar I don't understand what's wrong with the upper snippet in your comment. It maybe sounds like what you're saying is that you want a way to construct an object literal that conditionally has a property slot?
vbezhenar commentedon Feb 15, 2023
@RyanCavanaugh it does not compile with exactOptionalPropertyTypes.
Here's some declarations from lib.dom.d.ts:
So with exactOptionalPropertyTypes enabled I can't pass
undefined
tosignal
.One work-around is to use second snippet: add property conditionally.
Another work-around is to pass
null
because this particular API allows to passnull
, but not every API allows that.So, yes, a way to construct object that conditionally has a property slot with more ergonomic syntax would help.
I think someone already suggested syntax like:
Now I understand that TypeScript's philosophy is not to introduce syntax which deviates from JavaScript so I guess it's better to direct that kind of feedback to JavaScript standardization committee.
bodograumann commentedon Feb 15, 2023
This has been mentioned above already, but in case you missed it, #39376 is the relevant typescript issue which was rejected and here is the tc39 discussion: https://es.discourse.group/t/optional-property-shorthand-in-object-literal/196
exactOptionalPropertyTypes
cloudscape-design/documenter#25phaux commentedon Oct 19, 2023
For working with exactOptionalPropertyTypes code base I found this utility function to be super useful:
undefined
value mui/mui-x#14446