- "I guess if I want to stay consistent with myself and not be a hypocrite for the third time today." "You're counting the times?" "Yeah... I don't want to go for the hat trick."
We took a look at a proposal around enhancing nullable support for async
methods that return Task
-like types, as existing nullability attributes are often incorrectly
applied for this type of method. There are some immediate concerns that come to mind:
- There are a lot of methods that defer awaiting a
Task
orTask
-like type until after the returned thing. For example,ConfigureAwait
,ValueTask.AsTask
,Task.WhenAll
, user libraries to add awaiters toValueTuple
s of tasks, etc. We could special case the BCL methods in the compiler, but that is a lot of special cases and wouldn't help any user-definedTask
-like types or methods. - Some post-condition attributes, such as
NotNull
on parameters, might be checked up front by the method, before it hits anawait
, and be perfectly fine today (particularly sinceasync
methods cannot haveref
parameters). The proposal currently suggests changing this, but that doesn't seem generally correct. - We don't have a good way to know whether a method is
async
today, so we can only use a broad heuristic to determine whether to applyMemberNotNull
and other attributes before or afterawait
ing the result. Further, whether or not a method isasync
isn't quite the heuristic we're looking for: what we're actually looking for is whether or not the returnedTask
-like type needs to beawait
ed before the nullability conditions are propagated. For example, a helper method to fill in common parameters and otherwise pass through a call to another member is very possibly not marked asasync
, but should have the same behavior as theasync
method it's proxying.
Another spitballed proposal gained a bit of traction as we were discussing the above 3 points: adding a new RequireAwait
property to our nullability attributes, with some
appropriate defaults, and using that to propagate information on nullability. This has some advantages in being very precise, and likely being easy to guide users towards
(the warning for await
ing without fulfilling a MemberNotNull
could mention setting that property, for example, or if a user sets that property and doesn't need to we
could warn), and not requiring significant new amounts of metadata to put on every async
method to mark it as such.
This will go back to the working group for now to continue iterating on the above idea and address the other contention points.
There was some initial confusion on this discussion, as the current implementation actually missed the quoted line in the spec forbidding ;
on non-record
types. Further,
we realized that the spec, as written today, is actually a breaking change; it would make the following code illegal:
record R1; // Legal today
We think there are potential cases where the ;
body would be useful, mainly in partial
types that have generated implementations. There are some potential concerns with
future interference on top-level statements, as we may someday allow statements to follow type declarations. That would change class C; {}
from a guaranteed error to
potentially introducing top-level statements in the file, likely causing confusing compilation errors. However, we trust ourselves to handle this when the time comes.
We also considered the two other declaration types that currently do not permit ;
bodies: interface
s, and enum
s. interface
s have similar justification to class
es
and struct
s, where a partial
interface could add new attributes and generate the rest. enum
s are not allowed to be partial
today, so it is unlikely to get much use,
but at the same time we don't see a good reason to leave the syntax inconsistent and special case enum
s here where no other type
declaration is.
Allow semicolon bodies for all type declarations.