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
{{ message }}
Repository navigation
Discriminant property type guard not applied with bracket notation #10530
interfaceSquare{kind: "square";size: number;}interfaceRectangle{kind: "rectangle";width: number;height: number;}interfaceCircle{kind: "circle";radius: number;}typeShape=Square|Rectangle|Circle;functionarea(s: Shape){// In the following switch statement, the type of s is narrowed in each case clause// according to the value of the discriminant property, thus allowing the other properties// of that variant to be accessed without a type assertion.switch(s['kind']){case"square": returns.size*s.size;case"rectangle": returns.width*s.height;case"circle": returnMath.PI*s.radius*s.radius;}}
Expected behavior:
The code compiles without errors.
Actual behavior:
sample.ts(24,33): error TS2339: Property 'size' does not exist on type 'Square | Rectangle | Circle'.
sample.ts(24,42): error TS2339: Property 'size' does not exist on type 'Square | Rectangle | Circle'.
sample.ts(25,36): error TS2339: Property 'width' does not exist on type 'Square | Rectangle | Circle'.
sample.ts(25,46): error TS2339: Property 'height' does not exist on type 'Square | Rectangle | Circle'.
sample.ts(26,43): error TS2339: Property 'radius' does not exist on type 'Square | Rectangle | Circle'.
sample.ts(26,54): error TS2339: Property 'radius' does not exist on type 'Square | Rectangle | Circle'.
Why this is bad:
I am trying to work with Dropbox's new 2.0 SDK, which heavily uses tagged union types (especially for API errors). The discriminant property is named .tag, so it can only be accessed via bracket notation. I generated TypeScript typings for their new JavaScript SDK, and discovered this bug the hard way. :(
👍 Bracketed property access should work the same as dotted property access for the purposes of type guards when the indexed name matches a known property
Reacted by Aluan Haddad, Mikhail Cheshkov, Sean Poulter, David Stensland, Olivia, Máté Solymosi, Leo Borai, Aleksey Levenstein, Olzhas Alexandrov, wbt and 8 more
Update from the PR at #10530: narrowing on element access, even just for strings and not numbers, adds 6.5% time to the checker, so this is not likely to go into 2.1 unless we come up with a way to reduce its cost.
the other examples in this thread seem to be slightly different, perhaps the original issue should be updated with a more up to date example or closed in favor of a different issue (such as #51368)?
Ryan Cavanaugh (@RyanCavanaugh), as DetachHead mentioned - the original example from this issue has been fixed a long time ago. A lot of other examples posted here are also working just fine today. I would propose closing this issue in favor of other ones that are reporting more specific things.
Reacted by DetachHead, Oleksandr Tarasiuk, martdavidson, Jakub Neander, Paweł Dymara and Zzzen
If we're not going to close this, could someone edit the title and description to reflect the current purpose of the issue? Right now it's just generating noise in every issue closed as a duplicate of this one.
Reacted by DetachHead, Avi Nerenberg, Paweł Dymara and Sam Lindstrom
As others have mentioned, this issue title/description isn't particularly clear, but from the issues marked as duplicate I'm guessing this issue is the same root cause as the following?
declareconstapple: {[key: string]: boolean|undefined}constbanana: Record<string,boolean>=apple// error// Type '{ [key: string]: boolean | undefined; }' is not assignable to type 'Record<string, boolean>'.// 'string' index signatures are incompatible.// Type 'boolean | undefined' is not assignable to type 'boolean'.// Type 'undefined' is not assignable to type 'boolean'.(2322)
Micah Zoltu (@MicahZoltu) I don't think that's the same issue, and I don't think the example that you shared would be considered an issue in TypeScript. Although a Record<string, boolean> and a Record<string, boolean | undefined> act very similarly, they differ in whether TS allows undefined to be explicitly assigned to properties, and they're consistently different in how TS does type checks of property values. See this playground link.
To anybody following this issue and confused like me, also the more complicated topic of variable index access type guards was fixed this June by ahejlsberg himself with #57847 in TS 5.5 :)
the difference being that key is a let instead of a const. in #57847 it mentions that it should work on both const and let:
With this PR we perform control flow analysis for element access expressions obj[key] where key is a const variable, or a let variable or parameter that is never targeted in an assignment.
i see, thanks. it also seems like attempting it to narrow it with data.a then accessing it with data[key] also doesn't work, so i had to change that too:
+ export {}+
interface Data {
a?: number
}
declare const data: Data
declare let key: 'a'
- if (data.a !== undefined) {+ if (data[key] !== undefined) {
key // "a"
const a = data[key] // number | undefined
const b = data['a'] // number
}
Activity
RyanCavanaugh commentedon Aug 25, 2016
👍 Bracketed property access should work the same as dotted property access for the purposes of type guards when the indexed name matches a known property
sandersn commentedon Aug 26, 2016
Fix is up at #10565
sandersn commentedon Oct 13, 2016
Update from the PR at #10530: narrowing on element access, even just for strings and not numbers, adds 6.5% time to the checker, so this is not likely to go into 2.1 unless we come up with a way to reduce its cost.
jvilk commentedon Oct 17, 2016
Nathan Shively-Sanders (@sandersn) thanks for the update. That's unfortunate, but understandable. :(
32 remaining items
DetachHead commentedon Nov 1, 2022
the original example from this issue seems to have been fixed at some point between typescript 2.0 and 3.3.3333
playground
the other examples in this thread seem to be slightly different, perhaps the original issue should be updated with a more up to date example or closed in favor of a different issue (such as #51368)?
Andarist commentedon Jan 8, 2023
Ryan Cavanaugh (@RyanCavanaugh), as DetachHead mentioned - the original example from this issue has been fixed a long time ago. A lot of other examples posted here are also working just fine today. I would propose closing this issue in favor of other ones that are reporting more specific things.
jcalz commentedon Apr 1, 2023
If we're not going to close this, could someone edit the title and description to reflect the current purpose of the issue? Right now it's just generating noise in every issue closed as a duplicate of this one.
MicahZoltu commentedon Sep 12, 2023
As others have mentioned, this issue title/description isn't particularly clear, but from the issues marked as duplicate I'm guessing this issue is the same root cause as the following?
joshkel commentedon Sep 12, 2023
Micah Zoltu (@MicahZoltu) I don't think that's the same issue, and I don't think the example that you shared would be considered an issue in TypeScript. Although a
Record<string, boolean>and aRecord<string, boolean | undefined>act very similarly, they differ in whether TS allowsundefinedto be explicitly assigned to properties, and they're consistently different in how TS does type checks of property values. See this playground link.RyanCavanaugh commentedon Nov 13, 2023
Closing since the examples cited here work as expected
DetachHead commentedon Nov 13, 2023
Ryan Cavanaugh (@RyanCavanaugh) in that case can any of the issues that were closed as a duplicate of this one be re-opened? Eg. #51368
RyanCavanaugh commentedon Nov 13, 2023
DetachHead let's just get a fresh issue with a clear statement of the problem for clarity
jcalz commentedon Nov 14, 2023
Well, I opened #56389... not sure if it covers all the loose ends, though.
phil294 commentedon Oct 15, 2024
To anybody following this issue and confused like me, also the more complicated topic of variable index access type guards was fixed this June by ahejlsberg himself with #57847 in TS 5.5 :)
DetachHead commentedon Oct 16, 2024
the issue i raised (#51368) which was closed as a duplicate of this one still doesn't work:
playground
the difference being that
keyis aletinstead of aconst. in #57847 it mentions that it should work on bothconstandlet:or is there something i'm missing?
jcalz commentedon Oct 16, 2024
You're missing that the
letis in the global scope and so other files might affect it, see #58972 (comment)DetachHead commentedon Oct 16, 2024
i see, thanks. it also seems like attempting it to narrow it with
data.athen accessing it withdata[key]also doesn't work, so i had to change that too: