Repository navigation
Narrow object by property #33205
Description
Activity
I am against this being allowed.
const banana: {prop:2} = apple apple.prop = 5 console.log(banana.prop) //5, not 2, whoops
Reacted by Jógvan OlsenI updated original to make
apple.propreadonly. Though, your example does show why this may be harder to implement than I original thought, since it needs to only narrow for readonly properties.Copy-pasting from Gitter,
I understand your pain because, in some cases, you know that
apple's props won't be modified for the duration thatbananais in scope. So, at that point, the assignment tobananais sound.Also,
readonlyisn't a guarantee that the property won't be modified, even if TS started handling readonly "properly".const writable = { prop : 2 } const apple : { readonly prop : number } = writable //Snip narrow apple.prop to 2 const banana : { prop : 2 } = apple writable.prop = 9001 console.log(banana.prop) //9001, not 2, whoops
We can't modify
apple.propbecause it isreadonlybutwritable.propis fair game.AnyhowStep Is the fact that you can assign
writabletoapplein your example a bug? It feels like a bug since you are narrowing writable without any explicit assertion or type guard.It's not a bug.
{ prop : number }is assignable to{ readonly prop : number }The current meaning of
readonlyis,This property (
prop) of the object is not modifiable through the identifier (apple) pointing to the object.We are not saying,
All references to the object cannot modify this property
If
readonlymeant the latter, then it would be a bug to allow the assignment fromwritabletoapple.
Perhaps more surprising is that
{ readonly prop : number }is assignable to{ prop : number }(if memory serves, I'm not on the computer)But if we keep in mind what
readonlycurrently means, then it "makes sense".It does limit the usefulness of
readonly, in my opinion, butreadonlyis still useful, despite its shortcomingsconst apple : { readonly prop : number } = { prop : 2 } const writable : { prop : number } = apple writable.prop = 1337
Perhaps more surprising is that { readonly prop : number } is assignable to { prop : number } (if memory serves, I'm not on the computer)
IIRC this is historical.
readonlywas a later addition to the language and there’s not yet awriteableannotation, so for compatibility with existing code, it can’t disallow the assignment because there was (at one time) no way to distinguish read-only from writable.I’m reasonably sure most of the unsoundness surrounding
readonlytraces to the above historical note.Reacted by AnyhowStepjack-williams commented
on Sep 3, 2019 CollaboratorMore actionsMicah Zoltu (@MicahZoltu) can you give an example that doesn't involve numbers? Right now it sounds like you want
numberto be treated as the union of all numbers. This is not feasible in our current system but might be possible by introducing numeric ranges into the type system.I also do not understand the intent of disallowing
typeof apple↠{ prop: 2 }but allowing
(typeof apple)['prop']↠2.Did the second example predate the addition of readonly?
- addedNeeds 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
on Sep 3, 2019 typeof apple↠{ prop: 2 }should be disallowed because,Nathan Shively-Sanders (@sandersn) From the discussion on Gitter that led to this issue, the main point of contention was that
apple.propitself gets narrowed to2buttypeof appledoesn’t change. So depending on how you useappleafter narrowing, you’re in an odd state wherepropis simultaneously narrowed to2or not, depending if you rely on the narrowing directly or indirectly.What Micah Zoltu (@MicahZoltu) wanted, essentially, was for
typeof appleto becomeApple & { prop: 2 }after the typeguard.Reacted by Micah ZoltuBruce Pascoe (@fatcerberus) is correct. To help exemplify the problem:
declare const apple: { readonly prop: number } if (apple.prop !== 2) throw new Error() const appleProp: 2 = apple.prop // no error // Type '{ readonly prop: number; }' is not assignable to type '{ readonly prop: 2; }'. // Types of property 'prop' are incompatible. // Type 'number' is not assignable to type '2'. const banana: {readonly prop:2} = apple // error const cherry: {readonly prop:2} = {prop: apple.prop}
The assignment to
cherryshows that clearly TS knows thatapple.propis of type2, notnumber, yet when I try to assign the object itself it doesn't agree with that. I suspect this is because of the reasons that AnyhowStep went into above in thatreadonlydoesn't actually meanreadonlyin TypeScript, which makes my blood boil but at least explains the oddity here.Reading the issues linked above, a comment from Jack Williams (@jack-williams) in one of them indicates this is a design constraint:
Type guards do not propagate type narrowings to parent objects. The narrowing is only applied upon access of the narrowed property which is why the destructing function works, but the reference function does not. Narrowing the parent would involve synthesizing new types which would be expensive.
Also, it would seem (based on AnyhowStep's commentary above) that TypeScript thinks that while
apple.propis currently2, there is no guarantee that it will remain2indefinitely. This is becausereadonlyappears to not mean what one thinks it means, and in factapple.propcan be assigned any number later on, so TS cannot narrowapple.propto2.So IIUC, the current behavior is a design constraint, and AnyhowStep thinks that even if it wasn't, it should not be fixed because
readonlyis a lie.Micah Zoltu (@MicahZoltu) Oh, I misunderstood your idea, and also didn't know that we already narrow numbers to numeric literals. Anyway, as Jack Williams (@jack-williams) says, this is exactly a duplicate of previous issues. And since it would require the compiler to synthesise new types, we haven't done it because of the performance implications.
- addedDuplicateAn existing issue was already createdAn existing issue was already createdand removedNeeds 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
on Sep 4, 2019 For future readers, this is a duplicate of #31755, which is a duplicate of #30506, which is closed as a Design Limitation (though it is missing a tag).
The crux of the problem is:
Type guards do not propagate type narrowings to parent objects. The narrowing is only applied upon access of the narrowed property which is why the destructing function works, but the reference function does not.
The reason this is a Design Limitation is because:
Narrowing the parent would involve synthesizing new types which would be expensive.
Reacted by Philipp Faster-Malkovtypescript-bot commented
on Sep 7, 2019 ContributorMore actionsThis issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
typescript-bot commented
on Sep 10, 2019 ContributorMore actionsThis issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
Reacted by AnyhowStep- locked as resolved and limited conversation to collaborators
on Oct 21, 2025
Search Terms
narrow object by property
(unfortunately, this returned way too many results to look through, so sorry if this is a dupe!)
Suggestion
Narrow objects based on checks of their properties.
Use Cases
Some function receives an object as input and then does validation on that object prior to passing it on to some other function, or returning it as a narrowed object.
Examples
In this example, we can see that the compiler is tracking the narrowed type of
apple.propbecause it allows us to assign it to a variable of type2. However, when we try to assign the containing object to something that wants a narrowedpropwe get a type error.Checklist
My suggestion meets these guidelines: