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
exactOptionalPropertyTypes does not include undefined in the type for assignment operators like ??=Β #53305
// @exactOptionalPropertyTypesdeclareconstxx: {prop?: number};xx.prop;// ^?// typeof xx.prop === number | undefined β functionaa(){xx.prop.toFixed();// ^?// typeof xx.prop === number | undefined β }functionbb(){xx.prop+=1;// ^?// typeof xx.prop === number | undefined β }functioncc(){xx.prop??=1;// ^?// typeof xx.prop === number β}functiondd(){xx.prop??(xx.prop=1);// ^?// typeof xx.prop === number | undefined β }functionee(){xx.prop=1;// ^?// typeof xx.prop === number β}
π Actual behavior
TS reports the type of xx.prop to be just number when using the ??= assignment operator.
This is incorrect - xx.prop might be undefined until AFTER the assignment and also is different behaviour compared to other assignment operators and different to how the expanded assignment is typed.
π Expected behavior
TS reports the type of xx.prop to be number | undefined before the assignment.
Additional info
In addition to the above - this behaviour makes it difficult for us to write lint rules because when we attempt to check if the LHS of the assignment is undefined, the type system reports that it is not. Example issue typescript-eslint/typescript-eslint#6635
Ryan Cavanaugh (@RyanCavanaugh) I think this is different from #53306; this one is more about consistency. It appears the left-hand side of an augmentation operator (+=, ??=, etc) is showing a different type based on what operator is used. When you inspect the type via intellisense, sometimes it reports the type βbeforeβ the assignment, and sometimes βafterβ.
In the examples above, for xx.prop += 1, why is xx.prop showing number | undefined, whereas in xx.prop = 1 itβs showing just number? In both expressions, the type of xx.prop is number | undefined before the assignment and is number afterward. Shouldnβt both situations be handled the same?
I guess I disagree with the OP about where the problem in this sample is.
In all cases where an expression is an assignment target, the intended behavior is to show the write type, which is actually neither the pre-assignment CFA type nor the post-assignment CFA type. So in
xx.prop+=1;// xx.prop: number | undefined
This is marked in the repro as β , but the bug is that this is the wrong one, it should be number (under EOPT only) since that's the write type of xx.prop. This line is β.
The thing I'm looking at is whether nor not TS can have a read and a write type?
For example for noUncheckedIndexAccess if you write to a property then undefined is disallowed, but if you read from it then it could be undefined.
But based on what Ryan has said - it sounds like the type of the left expression that we are seeing is the write type - hence it isn't undefined.
Originally I had assumed the read/write differentiation was done as part of the assignment checking logic - but from what I'm reading here that's incorrect, and instead TS resolves the type to be the write type and then the assignment logic doesn't need to worry about the difference.
Which is fair, but does suck for us as we don't have the infra to be able to understand that we could have an undefined in other cases.
Activity
RyanCavanaugh commentedon Mar 17, 2023
Same as the other one
chharvey commentedon Mar 17, 2023
Ryan Cavanaugh (@RyanCavanaugh) I think this is different from #53306; this one is more about consistency. It appears the left-hand side of an augmentation operator (
+=,??=, etc) is showing a different type based on what operator is used. When you inspect the type via intellisense, sometimes it reports the type βbeforeβ the assignment, and sometimes βafterβ.In the examples above, for
xx.prop += 1, why isxx.propshowingnumber | undefined, whereas inxx.prop = 1itβs showing justnumber? In both expressions, the type ofxx.propisnumber | undefinedbefore the assignment and isnumberafterward. Shouldnβt both situations be handled the same?RyanCavanaugh commentedon Mar 17, 2023
I guess I disagree with the OP about where the problem in this sample is.
In all cases where an expression is an assignment target, the intended behavior is to show the write type, which is actually neither the pre-assignment CFA type nor the post-assignment CFA type. So in
This is marked in the repro as β , but the bug is that this is the wrong one, it should be
number(under EOPT only) since that's the write type ofxx.prop. This line is β.Conversely, this one
is actually the intended behavior, it's β
chharvey commentedon Mar 18, 2023
Agreed, under EOPT it should be
numberonly, to prevent things likexx.prop = undefined. Brad Zacher (@bradzacher), looks like typescript-eslint/typescript-eslint#6635 will have to find an independent solutionbradzacher commentedon Mar 18, 2023
The thing I'm looking at is whether nor not TS can have a read and a write type?
For example for
noUncheckedIndexAccessif you write to a property thenundefinedis disallowed, but if you read from it then it could beundefined.But based on what Ryan has said - it sounds like the type of the left expression that we are seeing is the write type - hence it isn't
undefined.Originally I had assumed the read/write differentiation was done as part of the assignment checking logic - but from what I'm reading here that's incorrect, and instead TS resolves the type to be the write type and then the assignment logic doesn't need to worry about the difference.
Which is fair, but does suck for us as we don't have the infra to be able to understand that we could have an undefined in other cases.