An IOne<A> is immutable when its type argument A is immutable (otherwise it is not).
An ITwo<A, B> likewise is immutable when both of its type arguments are immutable.
However, ITwo<A, B> also extends IOne<A> which will cause a problem:
Impl implements ITwo<int, List<int>>. It is never judged (itself) to be immutable, because it's Y property is a mutable List<int>.
If you refer to it as an ITwo<int, List<int>> it will not be considered immutable due to the OnlyIf on ITwo's B type parameter.
However, every ITwo<int, List<int>> is an IOne<int>, which is always considered to be immutable, which is problematic.
We could detect cases from one immutable type to another to cover many of the consequences of this, but I don't think that would be sufficient. The smuggling could happen outside of code we are able to analyze (e.g. in the .NET standard library), e.g. via covariance on wrapper classes (ISomeInnocentThing<out T> being able to turn an ISomeInnocentThing<ITwo<int, List<int>>> into an ISomeInnocentThing<IOne<int>>) or even worse a method (e.g. TReturn SomeInnocentThing<TInput, TReturn>( TInput input ) internally having casting).
Prior to ConditionallyImmutable we have accepted that we can't scan all code (e.g. the standard library) but we still want reasonable guarantees -- so I worry that hunting for cases like casts takes us far away from that.
One idea is to ban ITwo<[OnlyIf]A, [OnlyIf]B> from implementing IOne<[OnlyIf]A> on the rationale that it is meaningfully "less immutable". It's immutability is qualified on things that IOne<A>'s isn't, and so it makes sense to complain. The exact rules may need some thinking. To make a more concrete and slightly more complicated case:
This one ought to work because every e.g. every IReadOnlyDictionary<int, int> is immutable and so is IReadOnlyCollection<KeyValuePair<int, int>>... but IReadOnlyDictionary<int, object> looks not-immutable, but so does IReadOnlyCollection<KeyValuePair<int, object>> because KeyValuePair<int, object> is not immutable...
An
IOne<A>
is immutable when its type argumentA
is immutable (otherwise it is not). AnITwo<A, B>
likewise is immutable when both of its type arguments are immutable.However,
ITwo<A, B>
also extendsIOne<A>
which will cause a problem:Impl
implementsITwo<int, List<int>>
. It is never judged (itself) to be immutable, because it's Y property is a mutableList<int>
.If you refer to it as an
ITwo<int, List<int>>
it will not be considered immutable due to theOnlyIf
onITwo
'sB
type parameter.However, every
ITwo<int, List<int>>
is anIOne<int>
, which is always considered to be immutable, which is problematic.We could detect cases from one immutable type to another to cover many of the consequences of this, but I don't think that would be sufficient. The smuggling could happen outside of code we are able to analyze (e.g. in the .NET standard library), e.g. via covariance on wrapper classes (
ISomeInnocentThing<out T>
being able to turn anISomeInnocentThing<ITwo<int, List<int>>>
into anISomeInnocentThing<IOne<int>>
) or even worse a method (e.g.TReturn SomeInnocentThing<TInput, TReturn>( TInput input )
internally having casting).Prior to
ConditionallyImmutable
we have accepted that we can't scan all code (e.g. the standard library) but we still want reasonable guarantees -- so I worry that hunting for cases like casts takes us far away from that.One idea is to ban
ITwo<[OnlyIf]A, [OnlyIf]B>
from implementingIOne<[OnlyIf]A>
on the rationale that it is meaningfully "less immutable". It's immutability is qualified on things thatIOne<A>
's isn't, and so it makes sense to complain. The exact rules may need some thinking. To make a more concrete and slightly more complicated case:This one ought to work because every e.g. every
IReadOnlyDictionary<int, int>
is immutable and so isIReadOnlyCollection<KeyValuePair<int, int>>
... butIReadOnlyDictionary<int, object>
looks not-immutable, but so doesIReadOnlyCollection<KeyValuePair<int, object>>
becauseKeyValuePair<int, object>
is not immutable...