- CancellationToken in iterators
- Implied nullable constraints in nullable disabled code
- Inheriting constraints in nullable disabled code
- Declarations with constraints declared in #nullable disabled code
- Result type of
??=
expression - Use annotation context to compute the annotations?
- Follow-up decisions for pattern-based Index/Range
The proposal is that we allow a parameter of type CancellationToken to an iterator to be decorated with an attribute that specifies that the parameter is to be used in the state machine for cancellation if none is provided.
Conclusion
Approved. We will also allow the token in WithCancellation to override the token provided to the
iterator. We will mitigate potentially surprising behavior by making the attribute named
DefaultCancellation
or similar.
Consider the following declaration:
#nullable disable
interface I
{
T M<T>(T arg);
}
If you provide an implementation,
#nullable enable
class A : I
{
public T M<T>(T arg) where T : object
{
}
}
this will provide a warning because currently the implied constraint on I.M<T>
is object?
.
The proposal is that the implicit constraint should instead be "oblivous object
", meaning that
an implicit interface implementation will not provide a warning, and an explicit interface
implementation will inherit the oblivious constraint.
Conclusion
Yes, the implied constraint in a disabled context is oblivious. We previously did not consider context when deciding the implicit constraint, but seeing this example, we have decided differently for disabled context.
#nullable enable
public class A
{
public virtual void F2<T>(T y) where T : class?
{
}
}
class B : A
{
#nullable disable
public override void F2<U>(U y)
{
...
}
}
What is the implied constraint for the type parameter U
?
Conclusion
The constraint is inherited from the definition, which is class?
. Since you cannot write a new
constraint in the override, there's no point in doing anything but inheriting.
#nullable disable
class A<T1, T2, T3> where T2 : class where T3 : object
{
#nullable enable
void M2()
{
T1 x2 = default; // warning?
T2 y2 = default; // warning?
T3 z2 = default; // warning?
}
}
Proposal 1: The declarations using the type parameters are treated as unconstrained. All lines require a warning by previous decisions.
Proposal 2: The declarations using the type parameters are treated as oblivious. None of the lines produce warnings.
Conclusion
Proposal 2 seems to fit with the philosophy of oblivious, where we do not provide warnings and trust the user to know the correct nullability of their types. Proposal 2 accepted.
Right now the result type of ??=
is always the type of the LHS of the assignment. This is
consistent with all other assignment operators in the language. However, there's been some desire
for a closer analogy to the ??
instead of to the assignment operators.
That change would mean the following:
int? b = null;
var c = b ??= 5;
// b is int?
// c is int
Conclusion
We will accept the design change to be closer to the behavior of ??
. This means that we will no
longer maintain the principle that an assignment can be replaced with the variable being assigned
in an expression context. While that is unfortunate, we think the use cases are good enough to
justify the drawback. The result of the ??=
expression will be the result of an implicit
conversion from the RHS to the underlying type of the LHS, if one exists. Spec note: if possible
it would be cleaner to specify this in terms of ??
.
The current LDM decisions have a tentative decision to prohibit inferring oblivious in type inference, as a part of the principle that type inference does not infer "unspeakable" types.
The proposal is that we change the spec to remove this requirement. We do not seem to have any definitive examples of where this rule would reduce confusion or provide a lot of value, and it seems to be fairly complicated both to implement and also to spec the results of all of the situations in which oblivious states can enter into type inference.
Conclusion
While the principle is valuable, it doesn't seem to carry over to this area completely. It's agreed that we will remove this part of the spec and keep our current implementation.
A small note that there were the following changes or refinements for pattern-based Index/Range:
- All members in the pattern must be instance members
- If a Length method is found but it has the wrong return type, continue looking for Count
- The indexer used for the Index pattern must have exactly one int parameter
- The Slice method used for the Range pattern must have exactly two int parameters - When looking for the pattern members, we look for original definitions, not constructed members