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
Class parameter incorrectly treated as bivariantΒ #56225
The issue here is that variance computation classifies the proto type parameter as independent (i.e. unwitnessed, which is true for all types except those that extend AbstractableConstructor). Not clear that we can do any better here, after all the only use of proto is as the source of an inference in InstanceOf and we're simply not able to measure variance for that conditional type.
Like Ryan Cavanaugh (@RyanCavanaugh) said, the workaround is trivial once you know that this is happening, but getting to that point? Probably much less so in a real-world scenario.
Mateusz BurzyΕski (@Andarist) mentioned that it might be related to the variance being "unmeasurable." If it can't be inferred accurately, I'd rather have to explicitly cast if I want to allow an assignment like this than get a false positive when I don't.
Is there any way in this situation to treat the parameter as invariant rather than bivariant without breaking an unacceptable amount of existing code?
We could potentially consider some sort of diagnostic when variance is measured as independent as that is generally not intended. However, that no doubt would be a breaking change and would need to be under (yet another) compiler switch.
Alternatively this could be a lint thing, provided the necessary type checker information is exposed in our API.
Hmm, actually, we measure InstanceOf to be bivariant and therefore Proto also ends up measuring bivariant. The bivariance comes from effects of the rule:
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,// and Y1 is related to Y2.
It does seem a bit arbitrary to pick bivariance as we don't end up measuring any differences in outcomes between the marker types we use for variance probing. Effectively, we're saying that when we can't measure variance for a conditional type, we regard that conditional type to be bivariant.
InstanceOf is treated as covariant correctly here.
Right, what's going on here is that InstanceOf<AbstractableConstructor> and InstanceOf<typeof Foo> are resolved to {} and Foo respectably before the conditional type is evaluated. So, it's like writing {} extends Foo ? true : false, and at that point we don't even consult the variance computed for InstanceOf.
But in the case of relating two instances of Proto we do consult the variance information, and, since it claims bivariance, we consider the Proto<AbstractableConstructor> and Proto<typeof Foo> to be related in both directions. You can see the difference by forcing a structural comparison:
Ideally we'd detect when conditional type variance is unmeasurable and instead force structural comparisons, but we've tried that in the past with pretty disastrous results for performance.
Another workaround, without manual variance annotations, is to extract the inline type expression into a new type variable.
// change this:classProto<protoextendsAbstractableConstructor=AbstractableConstructor>{declareinstance: InstanceOf<proto>}// to this:classProto<protoextendsAbstractableConstructor=AbstractableConstructor,T=InstanceOf<proto>>{declareinstance: T}
or, in the reduced example:
// change thisclassBox<B>{declarevalue: ConditionalId<B>}// to thisclassBox<B,T=ConditionalId<B>>{declarevalue: T}
That is, the type is conditionally independent of proto/B, given T. This seems much cheaper than structural comparison and pretty straightforward to automate.
Activity
RyanCavanaugh commentedon Oct 26, 2023
Pretty weird. Thankfully easy to work around by putting an
outannotation onprotoahejlsberg commentedon Oct 26, 2023
The issue here is that variance computation classifies the
prototype parameter as independent (i.e. unwitnessed, which is true for all types except those that extendAbstractableConstructor). Not clear that we can do any better here, after all the only use ofprotois as the source of an inference inInstanceOfand we're simply not able to measure variance for that conditional type.ssalbdivad commentedon Oct 26, 2023
Like Ryan Cavanaugh (@RyanCavanaugh) said, the workaround is trivial once you know that this is happening, but getting to that point? Probably much less so in a real-world scenario.
Mateusz BurzyΕski (@Andarist) mentioned that it might be related to the variance being "unmeasurable." If it can't be inferred accurately, I'd rather have to explicitly cast if I want to allow an assignment like this than get a false positive when I don't.
Is there any way in this situation to treat the parameter as invariant rather than bivariant without breaking an unacceptable amount of existing code?
ahejlsberg commentedon Oct 26, 2023
We could potentially consider some sort of diagnostic when variance is measured as independent as that is generally not intended. However, that no doubt would be a breaking change and would need to be under (yet another) compiler switch.
Alternatively this could be a lint thing, provided the necessary type checker information is exposed in our API.
ahejlsberg commentedon Oct 26, 2023
Hmm, actually, we measure
InstanceOfto be bivariant and thereforeProtoalso ends up measuring bivariant. The bivariance comes from effects of the rule:It does seem a bit arbitrary to pick bivariance as we don't end up measuring any differences in outcomes between the marker types we use for variance probing. Effectively, we're saying that when we can't measure variance for a conditional type, we regard that conditional type to be bivariant.
whzx5byb commentedon Oct 26, 2023
Anders Hejlsberg (@ahejlsberg)
InstanceOfis treated as covariant correctly here.ahejlsberg commentedon Oct 27, 2023
Right, what's going on here is that
InstanceOf<AbstractableConstructor>andInstanceOf<typeof Foo>are resolved to{}andFoorespectably before the conditional type is evaluated. So, it's like writing{} extends Foo ? true : false, and at that point we don't even consult the variance computed forInstanceOf.But in the case of relating two instances of
Protowe do consult the variance information, and, since it claims bivariance, we consider theProto<AbstractableConstructor>andProto<typeof Foo>to be related in both directions. You can see the difference by forcing a structural comparison:Ideally we'd detect when conditional type variance is unmeasurable and instead force structural comparisons, but we've tried that in the past with pretty disastrous results for performance.
ethanresnick commentedon Nov 1, 2023
I found this a bit hard to understand, so put together a simpler repro. Figured I'd post it here in case it helps someone else:
rotu commentedon Feb 22, 2024
Another workaround, without manual variance annotations, is to extract the inline type expression into a new type variable.
or, in the reduced example:
That is, the type is conditionally independent of
proto/B, givenT. This seems much cheaper than structural comparison and pretty straightforward to automate.