meetings/2014/LDM-2014-10-15.md
The nameof(.) operator has the form nameof(expression). The expression must have a name, and may refer to either a single symbol or a method-group or a property-group. Depending on what the argument refers to, it can include static, instance and extension members.
This is v5 of the spec for the "nameof" operator. [v1, v2, v3, v4]. The key decisions and rationales are summarized below. Please let us know what you think!
Question: why do we keep going back-and-forth on this feature?
Answer: I think we're converging on a design. It's how you do language design! (1) make a proposal, (2) spec it out to flush out corner cases and make sure you've understood all implications, (3) implement the spec to flush out more corner cases, (4) try it in practice, (5) if what you hear or learn at any stage raises concerns, goto 1.
This particular "nameof v5" proposal came from a combined VB + C# LDM on 2014.10.22. We went through the key decisions:
using foo=X.Y.Z; nameof(foo) will return "foo". Also string f<T>() => nameof(T) will return "T". There are pros and cons to this decision. In its favor, it keeps the rules of nameof very simple and predictable. In the case of nameof(member), it would be a hindrance in most (not all) bread-and-butter cases to give a fully qualified member name. Also it's convention that "System.Type.Name" refers to an unqualified name. Therefore also nameof(type) should be unqualified. If ever you want a fully-qualified type name you can use typeof(). If you want to use nameof on a type but also get generic type parameters or arguments then you can construct them yourself, e.g. nameof(List<int>) + "`2". Also the languages have no current notion of metadata name, and metadata name can change with obfuscation.I should say, we're not looking for unanimous consensus -- neither amongst the language design team nor amongst the codeplex OSS community! We hear and respect that some people would like something closer to infoof, or would make different tradeoffs, or have different use-cases. On the language design team we're stewards of VB/C#: we have a responsibility to listen to and understand every opinion, and then use our own judgment to weigh up the tradeoffs and use-cases. I'm glad we get to do language design in the open like this. We've all been reading the comments on codeplex and elsewhere, our opinions have been swayed, we've learned about new scenarios and issues, and we'll end up with a better language design thanks to the transparency and openness. I'm actually posting these notes on codeplex as my staging ground for sharing them with the rest of the team, so the codeplex audience really does see everything "in the raw".
expression: ... | nameof-expression
name-of-expression:
nameof ( expression )
In addition to the syntax indicated by the grammar, there are some additional syntactic constraints: (1) the argument expression can only be made up out of simple-name, member-access, base-access, or "this", and (2) cannot be simply "this" or "base" on its own. These constraints ensure that the argument looks like it has a name, and doesn't look like it will be evaluated or have side effects. I found it easier to write the constraints in prose than in the grammar.
[clarification update] Note that member-access has three forms, E.I<A1...AK> and predefined-type.I<A1...AK> and qualified-alias-member.I. All three forms are allowed, although the first case E must only be made out of allowed forms of expression.
If the argument to nameof at its top level has an unacceptable form of expression, then it gives the error "This expression does not have a name". If the argument contains an unacceptable form of expression deeper within itself, then it gives the error "This sub-expression cannot be used as an argument to nameof".
It is helpful to list some things not allowed as the nameof argument:
invocation-expression e(args)
assignment x += 15
query-expression from y in z select y
lambda-expression () => e
conditional-expression a ? b : c
null-coalescing-expression a?? b
binary-expression ||, &&, |, ^, &, ==, !=,
<, >, <=, >=, is, as, <<,
>>, +, -, *, /, %
prefix-expression +, -, !, ~, ++, --,
*, &, (T)e
postfix-expression ++, --
array-creation-expression new C[…]
object-creation-expression new C(…)
delegate-creation-expression new Action(…)
anonymous-object-creation-expression new {…}
typeof-expression typeof(int)
checked-expression checked(…)
unchecked-expression unchecked(…)
default-value-expression default(…)
anonymous-method-expression delegate {…}
pointer-member-access e->x
sizeof-expression sizeof(int)
literal "hello", 15
parenthesized-expression (x)
element-access e[i]
base-access-indexed base[i]
await-expression await e
nameof-expression nameof(e)
vb-dictionary-lookup e!foo
Note that there are some types which are not counted as expressions by the C# grammar. These are not allowed as nameof arguments (since the nameof syntax only allows expressions for its argument). There's no need to spell out that the following things are not valid expressions, since that's already said by the language syntax, but here's a selection of some of the types that are not expressions:
predefined-type int, bool, float, object,
dynamic, string
nullable-type Customer?
array-type Customer[,]
pointer-type Buffer*, void*
qualified-alias-member A::B
void void
unbound-type-name Dictionary<,>
The nameof expression is a constant. In all cases, nameof(...) is evaluated at compile-time to produce a string. Its argument is not evaluated at runtime, and is considered unreachable code (however it does not emit an "unreachable code" warning).
Definite assignment. The same rules of definite assignment apply to nameof arguments as they do to all other unreachable expressions.
Name lookup. In the following sections we will be discussing member lookup. This is discussed in $7.4 of the C# spec, and is left unspecified in VB. To understand nameof it is useful to know that the existing member lookup rules in both languages either return a single type, or a single instance/static field, or a single instance/static event, or a property-group consisting of overloaded instance/static properties of the same name (VB), or a method-group consisting of overloaded instance/static/extension methods of the same name. Or, lookup might fail either because no symbol was found or because ambiguous conflicting symbols were found that didn't fall within the above list of possibilities.
Argument binding. The nameof argument refers to one or more symbols as follows.
nameof(simple-name), of the form I or I<A1...AK> The normal rules of simple name lookup $7.6.2 are used but with one difference...
nameof(member-access), of the form E.I or E.I<A1...AK> The normal rules of expression binding are used to evaluate "E", with no changes. After E has been evaluated, then E.I<A1...AK> is evaluated as per the normal rules of member access $7.6.4 but with some differences...
nameof(base-access-named), of the form base.I or base.I<A1...AK> This is treated as nameof(B.I) or nameof(B.I<A1...AK> where B is the base class of the class or struct in which the construct occurs.
Result of nameof. The result of nameof is the identifier "I" with the standard identifier transformations. Note that, at the top level, every possible argument of nameof has "I<A1...AK>".
[update that was added after the initial v5 spec] If "I" binds to a method-group and the argument of nameof has generic type arguments at the top level, then it produces an error "Do not use generic type arguments to specify the name of methods". Likewise for property-groups.
The standard identifier transformations in C# are detailed in $2.4.2 of the C# spec: first any leading @ is removed, then Unicode escape sequences are transformed, and then any formatting-characters are removed. This of course still happens at compile-time. In VB, any surrounding [] is removed
In C#, nameof is stored in a normal InvocationExpressionSyntax node with a single argument. That is because in C# 'nameof' is a contextual keyword, which will only become the "nameof" operator if it doesn't already bind to a programmatic symbol named "nameof". TO BE DECIDED: what does its "Symbol" bind to?
In VB, NameOf is a reserved keyword. It therefore has its own node:
Class NameOfExpressionSyntax : Inherits ExpressionSyntax
Public ReadOnly Property Argument As ExpressionSyntax
End Class
TO BE DECIDED: Maybe VB should just be the same as C#. Or maybe C# should do the same as VB.
What is the return value from var r = semanticModel.GetSymbolInfo(argument)? In all cases, r.Candidates is the list of symbol. If there is only one symbol then it is in r.Symbol; otherwise r.Symbol is null and the reason is "ambiguity".
Analyzers and the IDE will just have to deal with this case.
class C {
[3 references] static void f(int i) {...nameof(f)...}
[3 references] void f(string s) {...nameof(this.f)...}
[3 references] void f(object o) {...nameof(C.f)...}
}
static class E {
[2 references] public static void f(this C c, double d) {}
}
Highlight symbol from argument: When you set your cursor on an argument to nameof, it highlights all symbols that the argument bound to. In the above examples, the simple name "nameof(f)" binds to the three members inside C. The two member access "nameof(this.f)" and "nameof(C.f)" both bind to extension members as well.
Highlight symbol from declaration: When you set your cursor on any declaration of f, it highlights all nameof arguments that bind to that declaration. Setting your cursor on the extension declaration will highlight only "this.f" and "C.f". Setting your cursor on any member of C will highlight both all three nameof arguments.
Goto Definition: When you right-click on an argument to nameof in the above code and do GoToDef, it pops up a FindAllReferences dialog to let you chose which declaration. (If the nameof argument bound to only one symbol then it would go straight to that without the FAR dialog.)
Rename declaration: If you do a rename-refactor on one of the declarations of f in the above code, the rename will only rename this declaration (and will not rename any of the nameof arguments); the rename dialog will show informational text warning you about this. If you do a rename-refactor on the last remaining declaration of f, then the rename will also rename nameof arguments. Note that if you turn on the "Rename All Overloads" checkbox of rename-refactor, then it will end up renaming all arguments.
Rename argument: If you do a rename-refactor on one of the nameof arguments in the above code, the rename dialog will by default check the "Rename All Overloads" button.
Expand-reduce: The IDE is free to rename "nameof(p)" to "nameof(this.p)" if it needs to do so to remove ambiguity during a rename. This might make nameof now bind to more things than it used to...
Codelens: We've articulated the rules about what the argument of nameof binds to. The CodeLens reference counts above are a straightforward consequence of this.
// Validate parameters
void f(string s) {
if (s == null) throw new ArgumentNullException(nameof(s));
}
// MVC Action links
<%= Html.ActionLink("Sign up",
@typeof(UserController),
@nameof(UserController.SignUp))
%>
// INotifyPropertyChanged
int p {
get { return this._p; }
set { this._p = value; PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.p)); }
}
// also allowed: just nameof(p)
// XAML dependency property
public static DependencyProperty AgeProperty = DependencyProperty.Register(nameof(Age), typeof(int), typeof(C));
// Logging
void f(int i) {
Log(nameof(f), "method entry");
}
// Attributes
[DebuggerDisplay("={" + nameof(getString) + "()}")]
class C {
string getString() { ... }
}
void f(int x) {
nameof(x)
}
// result "x": Parameter (simple name lookup)
int x=2; nameof(x)
// result "x": Local (simple name lookup)
const x=2; nameof(x)
// result "x": Constant (simple name lookup)
class C {
int x;
... nameof(x)
}
// result "x": Member (simple name lookup)
class C {
void f() {}
nameof(f)
}
// result "f": Member (simple name lookup)
class C {
void f() {}
nameof(f())
}
// result error "This expression does not have a name"
class C {
void f(){}
void f(int i){}
nameof(f)
}
// result "f": Method-group (simple name lookup)
Customer c; ... nameof(c.Age)
// result "Age": Property (member access)
Customer c; ... nameof(c._Age)
// result error "_Age is inaccessible due to its protection level: member access
nameof(Tuple.Create)
// result "Create": method-group (member access)
nameof(System.Tuple)
// result "Tuple": Type (member access). This binds to the non-generic Tuple class; not to all of the Tuple classes.
nameof(System.Exception)
// result "Exception": Type (member access)
nameof(List<int>)
// result "List": Type (simple name lookup)
nameof(List<>)
// result error "type expected": Unbound types are not valid expressions
nameof(List<int>.Length)
// result "Length": Member (Member access)
nameof(default(List<int>))
// result error "This expression doesn't have a name": Not one of the allowed forms of nameof
nameof(default(List<int>).Length)
// result error "This expression cannot be used for nameof": default isn't one of the allowed forms
nameof(int)
// result error "Invalid expression term 'int'": Not an expression. Note that 'int' is a keyword, not a name.
nameof(System.Int32)
// result "Int32": Type (member access)
using foo=System.Int32;
nameof(foo)
// result "foo": Type (simple name lookup)
nameof(System.Globalization)
// result "Globalization": Namespace (member access)
nameof(x[2])
nameof("hello")
nameof(1+2)
// error "This expression does not have a name": Not one of the allowed forms of nameof
NameOf(a!Foo)
' error "This expression does not have a name": VB-specific. Not one of the allowed forms of NameOf.
NameOf(dict("Foo"))
' error "This expression does not have a name": VB-specific. This is a default property access, which is not one of the allowed forms.
NameOf(dict.Item("Foo"))
' error "This expression does not have a name": VB-specific. This is an index of a property, which is not one of the allowed forms.
NameOf(arr(2))
' error "This expression does not have a name": VB-specific. This is an array element index, which is not one of the allowed forms.
Dim x = Nothing
NameOf(x.ToString(2))
' error "This expression does not have a name": VB-specific. This resolves to .ToString()(2), which is not one of the allowed forms.
Dim o = Nothing
NameOf(o.Equals)
' result "Equals". Method-group. Warning "Access of static member of instance; instance will not be evaluated": VB-specific. VB allows access to static members off instances, but emits a warning.
[Foo(nameof(C))]
class C {}
// result "C": Nameof works fine in attributes, using the normal name lookup rules.
[Foo(nameof(D))]
class C { class D {} }
// result "D": Members of a class are in scope for attributes on that class
[Foo(nameof(f))]
class C { void f() {} }
// result "f": Members of a class are in scope for attributes on that class
[Foo(nameof(T))]
class C<T> {}
// result error "T is not defined": A class type parameter is not in scope in an attribute on that class
[Foo(nameof(T))] void f<T> { }
// result error "T not defined": A method type parameter is not in scope in an attribute on that method
void f([Attr(nameof(x))] int x) {}
// result error "x is not defined": A parameter is not in scope in an attribute on that parameter, or any parameter in the method
Function f()
nameof(f)
End Function
' result "f": VB-specific. This is resolved as an expression which binds to the implicit function return variable
NameOf(New)
' result error "this expression does not have a name": VB-specific. Not one of the allowed forms of nameof. Note that New is not a name; it is a keyword used for construction.
Class C
Dim x As Integer
Dim s As String = NameOf(x)
End Class
' result "x": Field (simple name lookup)
class C {
int x;
string s = nameof(x);
}
// result "x". Field (simple name lookup)
class C {
static int x;
string s = nameof(x);
}
// result "x". Field (simple name lookup)
class C {
int x;
string s = nameof(C.x);
}
// result "x". Member (member access)
class C {
int x;
string s = nameof(default(C).x);
}
// result error "This expression isn't allowed in a nameof argument" - default.
struct S {
int x;
S() {var s = nameof(x); ...}
}
// result "x": Field access (simple name lookup). Nameof argument is considered unreachable, and so this doesn't violate definite assignment.
int x; ... nameof(x); x=1;
// result "x": Local access (simple name lookup). Nameof argument is unreachable, and so this doesn't violate definite assignment.
int x; nameof(f(ref x));
// result error "this expression does not have a name".
var @int=5; nameof(@int)
// result "int": C#-specific. Local (simple name lookup). The leading @ is removed.
nameof(m\u200c\u0065)
// result "me": C#-specific. The Unicode escapes are first resolved, and the formatting character \u200c is removed.
Dim [Sub]=5 : NameOf([Sub])
' result "Sub": VB-specific. Local (simple name lookup). The surrounding [.] is removed.
class C {
class D {}
class D<T> {}
nameof(C.D)
}
// result "D" and binds to the non-generic form: member access only finds the type with the matching arity.
class C<T> where T:Exception {
... nameof(C<string>)
}
// result error: the type 'string' doesn't satisfy the constraints
An interpolated string is a way to construct a value of type String (or IFormattable) by writing the text of the string along with expressions that will fill in "holes" in the string. The compiler constructs a format string and a sequence of fill-in values from the interpolated string.
When it is treated as a value of type String, it is a shorthand for an invocation of
String.Format(string format, params object args[])
When it is converted to the type IFormattable, the result of the string interpolation is an object that stores a compiler-constructed format string along with an array storing the evaluated expressions. The object's implementation of
IFormattable.ToString(string format, IFormatProvider formatProvider)
is an invocation of
String.Format(IFormatProviders provider, String format, params object args[])
By taking advantage of the conversion from an interpolated string expression to IFormattable, the user can cause the formatting to take place later in a selected locale. See the section System.Runtime.CompilerServices.FormattedString for details.
Note: the converted interpolated string may have more "holes" in the format string than there were interpolated expression holes in the interpolated string. That is because some characters (such as "\{" "}") may be translated into a hole and a corresponding compiler-generated fill-in.
An interpolated string is treated initially as a token with the following lexical grammar:
interpolated-string:
$ " "
$ " interpolated-string-literal-characters "
interpolated-string-literal-characters:
interpolated-string-literal-part interpolated-string-literal-parts
interpolated-string-literal-part
interpolated-string-literal-part:
single-interpolated-string-literal-character
simple-escape-sequence
hexadecimal-escape-sequence
unicode-escape-sequence
interpolation
simple-escape-sequence: one of
\' \" \\ \0 \a \b \f \n \r \t \v \{ \}
single-interpolated-string-literal-character:
Any character except " (U+0022), \ (U+005C), { (U+007B) and new-line-character
interpolation:
{ interpolation-contents }
interpolation-contents:
balanced-text
balanced-text : interpolation-format
balanced-text:
balanced-text-part
balanced-text-part balanced-text
balanced-text-part
Any character except ", (, [, {, /, \ and new-line-character
( balanced-text )
{ balanced-text }
[ balanced-text ]
regular-string-literal
delimited-comment
unicode-escape-sequence
/ after-slash
after-slash
Any character except ", (, [, {, /, \, * and new-line-character
( balanced-text )
{ balanced-text }
[ balanced-text ]
regular-string-literal
* delimited-comment-text[opt] asterisks /
unicode-escape-sequence
interpolation-format:
regular-string-literal
literal-interpolation-format
literal-interpolation-format:
interpolation-format-part
interpolation-format-part literal-interpolation-format
interpolation-format-part
Any character except ", :, \, } and new-line-character
With the additional restriction that a delimited-comment-text that is a balanced-text-part may not contain a new-line-character.
This lexical grammar is ambiguous in that it allows a colon appearing in interpolation-contents to be considered part of the balanced-text, or as the separator between the balanced-text and the interpolation-format. This ambiguity is resolved by considering it to be a separator between the balanced-text and interpolation-format.
An interpolated-string token is reclassified, and portions of it are reprocessed lexically and syntactically, during syntactic analysis as follows:
expression:
interpolated-string-expression
interpolated-string-expression:
interpolated-string-start interpolations interpolated-string-end
interpolations:
single-interpolation
single-interpolation interpolated-string-mid interpolations
single-interpolation:
interpolation-start
interpolation-start : regular-string-literal
interpolation-start:
expression
expression , expression
An interpolated-string-expression has type string, but there is an implicit conversion from expression from an interpolated-string-expression to the type System.IFormattable. By the existing rules of the language (7.5.3.3 Better conversion from expression), the conversion to string is a better conversion from expression.
An interpolated-string-expression is translated into an intermediate format string and object array which capture the contents of the interpolated string using the semantics of Composite Formatting. If treated as a value of type string, the formatting is performed using string.Format(string format, params object[] args) or equivalent code. If it is converted to System.IFormattable, an object of type System.Runtime.CompilerServices.FormattedString is constructed using the format string and argument array, and that object is the value of the interpolated-string-expression.
The format string is constructed of the literal portions of the interpolated-string-start, interpolated-string-mid, and interpolated-string-end portions of the expression, with special treatment for { and } characters (see Notes).
The evaluation order needs to be specified.
The definite assignment rules need to be specified.
This section should describe in detail the construction of a format item from a single-interpolation, and the corresponding element of the object array.
If an interpolation-start has a comma and a second expression, the second expression must evaluate to a compile-time constant of type int, which is used as the alignment of a format item.
If a single-interpolation has a colon and a regular-string-literal, then the string literal is used as the formatString of a format item.
The compiler is free to translate an interpolated string into a format string and object array where the number of objects in the object array is not the same as the number of interpolations in the interpolated-string-expression. In particular, the compiler may translate { and } characters into a fill-in in the format string and a corresponding string literal containing the character. For example, the interpolated string $"\{ {n} \}" may be translated to String.Format("{0} {1} {2}", "{", n, "}").
The compiler is free to use any overload of String.Format in the translated code, as long as doing so preserves the semantics of calling string.Format(string format, params object[] args).
The interpolated string
$"{hello}, {world}!"
is translated to
String.Format("{0}, {1}!", hello, world)
The interpolated string
$"Name = {myName}, hours = {DateTime.Now:hh}"
is translated to
String.Format("Name = {0}, hours = {1:hh}", myName, DateTime.Now)
The interpolated string
$"\{{6234:D}\}"
is translated to
String.Format("{0}{1:D}{2}", "{", 6234, "}")
For example, if you want to format something in the invariant locale, you can do so using this helper method
public static string INV(IFormattable formattable)
{
return formattable.ToString(null, System.Globalization.CultureInfo.InvariantCulture);
}
and writing your interpolated strings this way
string coordinates = INV("longitude={longitude}; latitude={latitude}");
The following platform class is used to translate an interpolated string to the type System.IFormattable.
namespace System.Runtime.CompilerServices
{
public class FormattedString : System.IFormattable
{
private readonly String format;
private readonly object[] args;
public FormattedString(String format, params object[] args)
{
this.format = format;
this.args = args;
}
string IFormattable.ToString(string ignored, IFormatProvider formatProvider)
{
return String.Format(formatProvider, format, args);
}
}
}
IFormattable because it is a string literal. It should have such a conversion.