4.6 KiB
C# Language Design Notes for July 9, 2014
Agenda
- Detailed design of nameof <details settled>
- Design of #pragma warning extensions <allow identifiers>
Details of nameof
The overall design of nameof
was decided in the design meeting on Oct 7, 2013. However, a number of issues weren’t addressed at the time.
Syntactic ambiguity
The use of nameof(…)
as an expression can be ambiguous, as it looks like an invocation. In order to stay compatible, if there’s an invokable nameof
in scope we’ll treat it as an invocation, regardless of whether that invocation is valid. This means that in those cases there is no way to apply the nameof operator. The recommendation of course will be to get rid of any use of nameof
as an identifier, and we should think about having diagnostics helping with that.
Which operands are allowed?
The symbols recognized in a nameof expression must represent locals, range variables, parameters, type parameters, members, types or namespaces. Labels and preprocessor symbols are not allowed in a nameof expression.
In general, free-standing identifiers are looked up like simple names, and dotted rightmost identifiers are looked up like member access. It is thus an error to reference locals before their declaration, or to reference inaccessible members. However, there are some exceptions:
All members are treated as if they were static members. This means that instance members are accessed by dotting off the type rather than an instance expression. It also means that the accessibility rules around protected instance members are the simpler rules that apply to static members.
Generic types are recognized by name only. Normally there needs to be a type parameter list (or at least dimension specifier) to disambiguate, but type parameter lists or dimension specifiers are not needed, and in fact not allowed, on the rightmost identifier in a nameof.
Ambiguities are not an error. Even if multiple entities with the same name are found, nameof will succeed. For instance, if a property named M
is inherited through one interface and a method named M
is inherited through another, the usual ambiguity error will not occur.
The referenced set
Because ambiguities are allowed, a nameof operator can reference a set of different entities at the same time. The precise set of referenced entities in the presence of ambiguity can be loosely defined as “those it would be ambiguous between”. Thus, shadowed members or other entities that wouldn’t normally be found by lookup, e.g. because they are in a base class or an enclosing scope of where an entity is found, will not be part of the referenced set.
The notion of referenced set has little importance for the language-level semantics, but is important for the tooling experience, e.g. for refactorings, go-to-definition, etc.
Reference to some entities, e.g. obsolete members, Finalize
or ‘op_
’ methods, is normally an error. However, it is not an error in nameof(…)
unless all members of the referenced set would give an error. If all non-error references give warnings, then a warning is given.
The resulting string
C# doesn’t actually have a notion of canonical name. Instead, equality between names is currently defined directly between names that may contain special symbols.
For nameof(… i)
we want the resulting string to be the identifier I
given, except that formatting characters are omitted, and Unicode escapes are resolved. Also, any leading @
is removed.
In the case of aliases, this means that those are not resolved to their underlying meaning: the identifier is that of the alias itself.
As a result, the meaning of the identifier is always only used to check if it is valid, never to decide what the resulting string is. There is no semantic component to determining the result of a nameof operator, only to determining if it is allowed.
Pragma warning directives
Now that custom diagnostics are on their way, we want to allow users to turn these on and off from source code, just as we do with the compiler’s own diagnostics today. To allow this, we need to extend the model of how a diagnostic is identified: today a number is used, but that is not a scalable model when multiple diagnostic providers are involved.
Instead the design is that diagnostics are identified by an identifier. For compatibility the C# compiler’s own diagnostics can still be referenced with a number, but can also be referred to with the pattern CS1234
:
#pragma warning disable AsyncCoreSet
#pragma warning disable CS1234