Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add possibility to consistently get object property name #251

Open
DisDis opened this issue Oct 31, 2017 · 26 comments
Open

Add possibility to consistently get object property name #251

DisDis opened this issue Oct 31, 2017 · 26 comments
Labels
request Requests to resolve a particular developer problem

Comments

@DisDis
Copy link

DisDis commented Oct 31, 2017

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'

@matanlurey
Copy link
Contributor

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

@lrhn
Copy link
Member

lrhn commented Nov 1, 2017

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,

@ranquild
Copy link

ranquild commented Nov 1, 2017

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

@matanlurey
Copy link
Contributor

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).

@lrhn
Copy link
Member

lrhn commented Nov 2, 2017

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.

@DisDis
Copy link
Author

DisDis commented Nov 2, 2017

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

@jodinathan
Copy link

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);
  }
}

@kevmoo
Copy link
Member

kevmoo commented Mar 1, 2019

@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!

@munificent
Copy link
Member

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

@kevmoo
Copy link
Member

kevmoo commented Mar 1, 2019

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

Sure!

@kevmoo kevmoo transferred this issue from dart-lang/sdk Mar 1, 2019
@kevmoo kevmoo added the request Requests to resolve a particular developer problem label Mar 1, 2019
@eernstg
Copy link
Member

eernstg commented Mar 4, 2019

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".

@machinescream
Copy link

Any news guys?

@leafpetersen
Copy link
Member

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.

@tomdowdell
Copy link

tomdowdell commented Dec 14, 2019

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.

@raulchall
Copy link

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

@rrousselGit
Copy link

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

@festelo
Copy link

festelo commented Jun 23, 2020

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.

@zs-dima
Copy link

zs-dima commented Oct 12, 2020

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

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

@esDotDev
Copy link

esDotDev commented Dec 23, 2020

Fwiw, this would be nice for the new Restorable API in flutter, so we don't need to constantly repeat ourselves:
"someInt" is redundant here almost all the time:

RestorableInt someInt = RestorableInt(0);
...
@override
  void restoreState(RestorationBucket oldBucket, bool initialRestore) {
    registerForRestoration(someInt, "someInt");
   //registerForRestoration(someInt); // This should be enough, and is less prone to bugs
  }

Would be nice to just pass null, and have it internally use nameof(). Also prevents magic string bugs, so its actually better and more robust this way, and works better with refactoring.

The id in this case is a string that is supposed to be unique within the scope of a class, which is of course accomplished by just using it's property name;

@esDotDev
Copy link

Right, thanks for the correction. The longer method is still preferred as it's avoids the magic strings and lets the compiler keep the names in sync.

@esDotDev
Copy link

esDotDev commented Dec 23, 2020

Another real world use case we came across, We're trying to come up with some concise method of re-using state in a StatelessWidget in Flutter.

Landed on an API that needs a unique-in-scope string, very similar to Restoration:

build(){
  StatefulAnimation anim1 = getProp("anim1", create: (){ ... })
  ...
}

I think anim1 = getProp(nameOf(anim1), ...) would be preferred here.

@bannmann
Copy link

bannmann commented Jan 9, 2021

Another use case which was not yet mentioned is throwing exceptions that include the parameter name:

set foo(String value) {
  _foo = ArgumentError.checkNotNull(value, nameof(value));
}

void bar(String data) {
  if (!isValid(data)) {
    throw ArgumentError.value(data, nameof(data));
  }
  ...
}

void quux(String data) {
  if (data == null) {
    throw ArgumentError.notNull(nameof(data));
  }
  ...
}

@pchasco
Copy link

pchasco commented Oct 4, 2021

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.

Wouldn't nameof() be resolved before any minification, so property names would still match the keys in the serialized data?

@shtse8
Copy link

shtse8 commented Jan 12, 2023

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.

Wouldn't nameof() be resolved before any minification, so property names would still match the keys in the serialized data?

before minification would make it to be a const string, minification wouldn't take affect on that.

@codelovercc
Copy link

codelovercc commented Jan 3, 2024

A nameof expression produces the name of a variable, type, or member as the string constant. A nameof expression is evaluated at compile time and has no effect at run time. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/nameof
This is not about reflection and don't take affect on minification.
In my case, I have various models that use to request to the server, let me say, if a LoginModel has any validation error on the server side, the server will response with json: {"title":"validation error","errors":{"account":["Account is required."],"password":["Passowrd is required"]}}
Here is the dart class model:

class LoginModelProblemDetails {
  final List<String>? account;

  final List<String>? password;

  final List<String>? accountType;

  LoginModelProblemDetails({
    this.account,
    this.password,
    this.accountType,
  });

  bool hasAnyError(){
    return account?.isNotEmpty == true || password?.isNotEmpty == true || accountType?.isNotEmpty == true;
  }
  
  bool hasError(String fieldName){
    switch(fieldName){
      case "account":
        return account?.isNotEmpty == true;
      case "password":
        return password?.isNotEmpty == true;
      case "accountType":
        return accountType?.isNotEmpty == true;
      default:
        return false;
    }
  }

If I can use nameof keyword, that would be

class LoginModelProblemDetails {
  final List<String>? account;

  final List<String>? password;

  final List<String>? accountType;

  LoginModelProblemDetails({
    this.account,
    this.password,
    this.accountType,
  });

  bool hasAnyError(){
    return account?.isNotEmpty == true || password?.isNotEmpty == true || accountType?.isNotEmpty == true;
  }
  
  bool hasError(String fieldName){
    switch(fieldName){
      case nameof(account):
        return account?.isNotEmpty == true;
      case nameof(password):
        return password?.isNotEmpty == true;
      case nameof(accountType):
        return accountType?.isNotEmpty == true;
      default:
        return false;
    }
  }

check it:

final problemDetails = LoginModelProblemDetails.fronJson(jsonDecode('{"title":"validation error","errors":{"account":["Account is required."],"password":["Passowrd is required"]}} '));
print(problemDetails.hasError(nameof(account));

I have a lot of models ModelProblemDetails like, and it aways decode from json that the server responded, my UI layer use method hasError to toggle the validation state that display to user. I need keyword nameof to save my life.

@bdemeure
Copy link

+1 for nameof() equivalent that is 'resolved' before minification

currently we create manualy some static const that matches property names... but error can easily occurs if const value is not spelled exactly as the property

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests