Language: Add possibility to consistently get object property name

Created on 31 Oct 2017  Â·  19Comments  Â·  Source: dart-lang/language

We need to get object property name in cases like Angular 2 ngOnChanges (when you need to compare key from changes map with object property name):

ngOnChanges(Map<String, SimpleChange> changes);
It would be great to have something like nameof in C#, so that we could get property name and don't be afraid that someone will rename property.

nameof(obj.propertyName); // 'propertyName'

request

Most helpful comment

In our big project. Main priority is code support. We have 'toName', 'toSymbol' an analog of nameof. It is made using a transformer and the special code for run vm test.
We very often use this for:
Deserialization/Serialization, Angular.Dart, noSuchMethod

// Deserialization
class AccountDeserializer extends EntityDeserializer {
  static final _rightsSymbol = toSymbol((Account o) => o.rights);
  static final List<DeserializeField> _fields = [
    new DeserializeSingleField("isOwner", toSymbol((Account o) => o.isOwner)),
    new DeserializeSingleField("isAdmin", toSymbol((Account o) => o.isAdmin)),
....

// Angular
ngOnChanges(Map<String, SimpleChange> changes){
   if (changes.containsKey(nameof(field)) ....
}

Interface

toSymbols([o.field1, o.field2]);// => [#field1,#field2]
toSymbol(o.field1);// => #field1
toSymbols((Task o)=>[o.field1, o.field2]);// => [#field1,#field2]
toSymbol((Task o)=>o.field1);// => #field1

toNames([o.field1, o.field2]);// => ['field1','field2']
toName(o.field1);// => 'field1'
toNames((Task o)=>[o.field1, o.field2]);// => ['field1','field2']
toName((Task o)=>o.field1);// => 'field1'

All this is directed to ensure that the code is connected at all parts. Developers should receive a real "Find usages". Developers should safely refactor the code. Also in our company, literals are forbidden. Since literals are potential errors.
Now the transformer has become a problem, as we move to build. 'nameof' solves the problems at the language level.

@matanlurey @lrhn

All 19 comments

Considering that you can't _use_ this information (i.e. there is no reflective support), what would this accomplish?

You can use it in noSuchMethod.
It might even help you with the named arguments of Function.apply, but only if you are somehow allowed to denote a named parameter of a function signature.

It's a good idea. The current symbol literals are stupid - they're basically shorthands for strings, there is no relation to the identifier you actually want to match. That makes typos hard to detect and refactoring impossible to automate. Example:

abstract class Foo {
  int bar();
}
class MockFoo implements Foo {
  noSuchMethod(i) {
    if (i.memberName == #baar) return 42;   // Whoops
    return super.noSuchMethod(i);
  }
}

I wouldn't use a macro like nameof (Dart doesn't have precompiler macros otherwise), but I would definitely like something that takes qualified name in scope and provides the symbol for that name. It should be recognizable in renaming and other refactorings.

We can't use #foo since that already works for things that are not in scope.
So, for the giggles, let's try ##foo. It would have a grammar like a symbol, but be interpreted differently:

import "self.dart" as self;
abstract class C {
  static foo({bar});
  baz({qux});
  get toto;
}
main() {
  var c = ##C;
  var cFoo = ##C.foo;
  var cFooBar = ##C.foo.bar; // maybe.
  var cFooBar2 = ##self.C.foo.bar;
  print(identical(cFooBar, cFooBar2));  // true, yey!
  // var wrong = ##C.biff;  // Invalid, no biff declared in C.
}

and

class MockFoo implements Foo {
  noSuchMethod(i) {
    if (i.memberName == ##Foo.bar) return 42;  
    // or just `== ##bar`, using the dynamic this-scope since "bar" isn't otherwise in scope.
    return super.noSuchMethod(i);
  }
}

Other possible, but probably too odd, syntaxes: #foo.bar#, #:foo.bar, #!foo.bar ...
No great syntax so far :(

See also #30518,

@matanlurey if (changes.containsKey(nameof(field)) ....

At least for that particular API, we decided it was probably wrong.

I don't know if I'd push for a language feature to fix the API we created (personally).

There are many places where using symbols is just making life harder for yourself, but they are still useful in a few places (noSuchMethod is the primary culpit). It is annoying that you can't link a reference to a particular variable or declaration, it means that you don't even detect typos and refactorings won't work. Even DartDoc does better than that.

The language feature by itself makes sense. Whether it carries its own weight, and whether it is worth prioritizing over other language features, are different questions.

In our big project. Main priority is code support. We have 'toName', 'toSymbol' an analog of nameof. It is made using a transformer and the special code for run vm test.
We very often use this for:
Deserialization/Serialization, Angular.Dart, noSuchMethod

// Deserialization
class AccountDeserializer extends EntityDeserializer {
  static final _rightsSymbol = toSymbol((Account o) => o.rights);
  static final List<DeserializeField> _fields = [
    new DeserializeSingleField("isOwner", toSymbol((Account o) => o.isOwner)),
    new DeserializeSingleField("isAdmin", toSymbol((Account o) => o.isAdmin)),
....

// Angular
ngOnChanges(Map<String, SimpleChange> changes){
   if (changes.containsKey(nameof(field)) ....
}

Interface

toSymbols([o.field1, o.field2]);// => [#field1,#field2]
toSymbol(o.field1);// => #field1
toSymbols((Task o)=>[o.field1, o.field2]);// => [#field1,#field2]
toSymbol((Task o)=>o.field1);// => #field1

toNames([o.field1, o.field2]);// => ['field1','field2']
toName(o.field1);// => 'field1'
toNames((Task o)=>[o.field1, o.field2]);// => ['field1','field2']
toName((Task o)=>o.field1);// => 'field1'

All this is directed to ensure that the code is connected at all parts. Developers should receive a real "Find usages". Developers should safely refactor the code. Also in our company, literals are forbidden. Since literals are potential errors.
Now the transformer has become a problem, as we move to build. 'nameof' solves the problems at the language level.

@matanlurey @lrhn

this would help me so much.

All my models are like:

class SomeClass extends Model {
  int _myProp;

  int get myProp => _myProp;
  set myProp(int x) {
    this.modified('myProp', _myProp, _myProp = x);
  }
}

would be amazing to do something like:

class SomeClass extends Model {
  int _myProp;

  set myProp(int x) {
    this.modified(##SomeClass.myProp, _myProp, _myProp = x);
    // becomes
    this.modified('myProp', _myProp, _myProp = x);
  }
}

@lrhn @munificent @leafpetersen – is this more of a language request?

I've been hitting this a lot in the last few days – would make a lot of folks very happy!

Yes, this sounds like a language request to me. Want to move it over to the language repo?

Yes, this sounds like a language request to me. Want to move it over to the language repo?

Sure!

I guess the desired guarantee here (cf. the C# nameof) would be that a given symbol corresponding to a single identifier or operator does in fact exist as the name of a declared member of some type.

If we make this a built-in property of certain symbols (say, by spelling them as ##foo rather than #foo), it is not immediately obvious to me how we could support specification of that type. We could of course just require that there is some type, somewhere, that has a member called foo, and if there is no such type then ##foo is a compile-time error.

But that's a rather weak guarantee. So maybe the following would be more practical:

main() {
  const s1 = memberSymbol<A>(#foo); // OK, s1 gets the value `#foo`.
  var s2 = memberSymbol<B>(#bar); // OK, memberSymbol(...) is always const.
  var s2 = memberSymbol<B>(#foo); // OK, `foo` is in the interface of `B`.
  var s3 = memberSymbol<B>(#baz, staticMember: true); // OK.
  var s4 = memberSymbol<A>(#qux); // Compile-time error.
  memberSymbol(#s1); // Error, refuses to infer a type argument, and `s1` not a member.
}

abstract class A {
  foo();
  Symbol get test => memberSymbol(#foo); // OK, type argument `A` inferred.
}

class B extends /*or implements, or mixes in*/ A {
  int get bar => 42;
  static void baz() {}
}

This would allow implementations of noSuchMethod to use a plain memberSymbol and rely on inference (the desired and inferred type argument is always the enclosing class), and all other usages could give an explicit type argument if needed.

The memberSymbol "function" would be special in that an application of it is a constant expression, and its argument must (of course) be a constant expression, and the evaluation just returns its argument if the given type does have a member with that name; with an argument which is not a member, like memberSymbol<A>(#qux) above, the expression is a compile-time error.

We could extend this idea slightly to allow for an optional argument staticMember which would allow us to check that a given symbol is the name of a static member of the given type (which must then be a class type, of course); we could also just omit support for that kind of check, or we could allow it without any special marker (so we'd simply use memberSymbol<B>(baz)).

We could of course also consider checking other symbols at compile-time (classSymbol(#String) ok, classSymbol(#NoSuchClass) compile-time error), and we might be able to offer these services in a more unified manner (using a single isSymbol with various optional arguments), but the main point is that we are likely to want a little bit more than just "this symbol corresponds to a declared name in this program".

Any news guys?

Any news guys?

I'd say that this request is still open, and seems to have some level of support, but there are a lot of higher priority things occupying both the language team and the implementation teams right now, so I don't expect this to bubble to the top of the queue in the short term.

The C# nameof() operator is definitely useful for those of us who create professional developer tools. Please add this feature!

The following example is not trying to prove the benefit, it just shows a use case for a minimal implementation of nameof(member-field) that would satisfy my primary needs:

class Ant {
  String firstName;

  Bat() {
  var s = nameof(firstName);
  }
}

I'm trying to create a library for Flutter. Without nameof(member-field) users will have to hard-code dozens to hundreds of field names that, obviously, will need to be updated manually should fields name actually change versus having it update automatically thanks to the magic of an IDE member renaming feature.

+1 to this, just couple days into Flutter + Firestore started looking for this

Maybe an alternate solution is to create a new lint rule that warns when a symbol's name doesn't match any variable available?

There can be a problem with code minification. Imagine we have client-server app, client and server are both on dart. We also have some shared code that used by both.

Shared code:

class SomeModel {
    final name;

    SomeModel.fromMap(map):
        name = map[nameof(name)];

    toMap() => {
        nameof(name): name
    };
}

Flutter web application built in release will not be able to deserialize code serialized by Server and vice versa.

Client's toMap will produce map

{ "mF": "some name" }

Server's toMap will produce map

{"name": "some name"}

Because type and field names are minified in flutter web, but not minified in server. Even when the actual code is the same.
Should nameof(*) return real member name or member name from source code?

Code like (SomeModel).toString() currently returns different values on flutter web debug and release.

nameof(obj.propertyName); // 'propertyName' - this feature more then welcome:

map[nameof(obj.propertyName)] = SomeData();
* * *
final data = map[nameof(obj.propertyName)];

Dart doesn't have precompiler macros otherwise

What if instead of useless symbols dart can introduce precompiler macros, that is: instead of #bar, we will write #compiledNameOf(this.bar)? It's a general pattern: if we want an equivalent to C#'s nameof, we can write it as #sourceNameOf(this.bar). Then the syntax can be applied to other "problematic" cases, e.g.

#tearOff(Foo); // tear-off of Foo constructor
#tearOff(x.myGetter);
#tearOff(x.mySetter=); 
Was this page helpful?
0 / 5 - 0 ratings

Related issues

leonsenft picture leonsenft  Â·  4Comments

moneer-muntazah picture moneer-muntazah  Â·  3Comments

wytesk133 picture wytesk133  Â·  4Comments

listepo picture listepo  Â·  3Comments

dev-aentgs picture dev-aentgs  Â·  3Comments