- "You're opinionated against opinionated features" "I see the irony, but..."
We wanted to revisit this design decision after a few years and some reports of customer confusion around this attribute.
While it's not an overwhelming amount of confusion, it's non-zero. When last this attribute was
discussed, we decided that we couldn't use it to broadly enforce the concept of
a method that does not return: instead, it could only influence nullable analysis. Much like the rest of the features
nullable analysis added, it cannot make strong guarantees. Changing this behavior now wouldn't really solve any of the
issues with it, as code exists today that is attributed with DoesNotReturn
that cannot be statically-proven to never
return.
Instead, we think that #538 would be needed to make progress here: once we have
a never
type, these guarantees can be statically proven, the runtime can abort if an instance of never
is ever created,
and we can introduce a warning wave at that point to suggest that methods attributed with DoesNotReturn
should have a
return type of never
. This would prevent us from introducing another loose generalized analysis that can't be statically
relied upon before later adding a more strict version that can be relied upon.
No changes.
This is an issue that we've been kicking around for a long time to try and protect language design space, and we've recently
started to make more breaks in this space. For example, C# 9 introduced record
as a type specifier, and made it illegal
to declare a type named record
without escaping it. We've heard no complaints about this change, and this has given us
more confidence to go ahead with a warning in this area.
And important point is that this change is not motivated by style. C# as a language tries hard not to be opinionated on
coding styles; we have defaults for formatting that are part of VS and dotnet new editorconfig
, but these are highly
customizable and we try not to punish users for taking advantage of that customizability. Instead, this warning is about
trying to ensure that C# has design space to work with that doesn't break users who upgrade to new C# versions. This goal
means we're trying to help 2 sets of customers:
- People who author types with lowercase names. These authors should be informed that they could be causing issues for their users by using a lowercase name.
- People who use types with lowercase names. These users should be informed that, if a future version of C# adds the type they're using as a keyword, their code could change meaning or no longer compile.
Some other points of consideration we brought up:
- Should we do this for just public types?
- Answer: we are trying to protect users from both themselves and others. This means we want to warn everywhere.
- Should we have codefixers to prepend
@
in front of identifiers?- Ultimately this will be up to the IDE team, but we think that each set of customers above should have a different answer
here. For set 1, we don't want to encourage this type of naming, even when escaped, so we would not want to see a fixer on
type definitions. However, for set 2, they're just using a type someone else defined. We don't want to unreasonably punish
them, so a fixer to prepend
@
for these users seems appropriate.
- Ultimately this will be up to the IDE team, but we think that each set of customers above should have a different answer
here. For set 1, we don't want to encourage this type of naming, even when escaped, so we would not want to see a fixer on
type definitions. However, for set 2, they're just using a type someone else defined. We don't want to unreasonably punish
them, so a fixer to prepend
The compiler will issue a diagnostic for types named all lowercase letters. We may simplify that to be just types named all ASCII lowercase characters, to avoid worrying about lowercase characters in other encodings and because we don't believe C# will be interested in adding non-ASCII keywords.
There are user tradeoffs here. In our previous discussion, we expressed a desire to have a similar ability to nullable, whereby
a missing negative Length test would not count against exhaustive matching, but if one was present it would then cause negative
values to need to be fully matched against. However, describing these rules and implementing them is quite complex, to the point
that we are concerned both about the code complexity and the explanation of the rules to users. We therefore think that the
simpler implementation, where we just consider the domain of Count
/Length
for indexable and countable types to be non-negative
integers, to be the better approach.
While we were discussing this, we also brought up the inherent flaw of structural typing, where types that do not satisfy the contract
of indexable and countable but still have the correct shape end up having incorrect behavior. For example, the BCL recently approved
a Vector2<T>
type. That type has an indexer, and it has a property called Length
that returns an integer. This Length
property
is not the length of the Vector: that's always 2. Instead, this is the euclidean length of the vector, ie the distance from (0, 0).
Our knowledge of Length
and combining subsumption with list patterns will behave incorrectly here. However, we note that this type
is already broken with structural typing in C# today. For example, vector[^1]
would work on the type, but it wouldn't have the
expected behavior. Instead of trying to solve something here, we instead think we should see what other uses customers have for opting
out of structural typing, and started a discussion about that here.
We will accept the existing breaking change on subsumption for indexable and countable types, and treat Length/Count on types that are both indexable and countable as if they can only have non-negative values. We would like to get this change in preview sooner rather than later so that we can receive feedback on the change.