dart-lang / language

Design of the Dart language
Other
2.65k stars 202 forks source link

Parameter name of implicit setter. #4022

Open lrhn opened 1 month ago

lrhn commented 1 month ago

The augmentation rules say that an augmenting declaration must have the same parameter names as the declaration it augments.

an augmenting declaration with a parameter list which must have the same parameters (names, positions, optionality and types) as its augmented declaration

You are allowed to augment the implicit setter of a variable declaration like:

int x;
augment set x(...) { ...}

The problem is that you cannot declare a parameter with the same name as the augmented implicit setter, since it has no name. (Or if it has a name, nobody ever specified what that name is, so you still can't specify it.)

I suggest that we build on top of "wildcard names" (really unnamed declarations), and say that

We can maybe do the same for type parameters. It's trickier since we might then not be able to express an inherited type, fx:

T id<T>(T value) => value;
augment id<_>(v) { log("id!"); return augmented(v); }

The result of that augmentation application is effectively T id<T>(T v) { ... } because the types are inherited, and the type parameter name is inherited, the name is just not in scope in the augmenting declaration, but since it doesn't use the name, everything is inferred anyway, that doesn't matter. And then you can write void foo<_>(); and let an augmentation make that inot void foo<T>() { ... using T ...}. Tricky, but consistent.

(Have to be very careful when specifying this, to make sure an inherited type or bound has the correct type variable name. Or maybe it's easy, because if an inherited type refers to any name, then that will also be the name of the type parameter in the result.)

jakemac53 commented 1 month ago

We could also say that for setters you are allowed to use any name you like? Setters are inherently different... they aren't invoked ever by passing an argument list. So, even if we allowed passing all arguments by name (even positional ones), we wouldn't have an issue.

lrhn commented 1 month ago

We could remove the restriction just for setters. Seems s little inconsistent.

We could remove the restriction for any positional parameter, and always let the publicly visible name be that of the original non-augmenting declaration. Then the augmenting declaration can write any parameter name for positional parameters, but that only affects the name of the introduced local variable, not the name of the parameter, on cars that ever begins mattering. (May be confusing if that augmenting declaration adds dartdoc too, not that's really outside of the language.)

Not sure how that would work for non-redirecting generative constructors, where the augmented body is executed in the same scope. Can probably be handled by saying that an access to the second personal parameter variable works the same, no matter which name its accessed using, that different names are aliases for the same variable.

jakemac53 commented 1 month ago

We could remove the restriction just for setters. Seems s little inconsistent.

I agree it does seems bit inconsistent but I also think there is some reasonable justification. Not totally sold on the idea though.

We could remove the restriction for any positional parameter, and always let the publicly visible name be that of the original non-augmenting declaration.

For regular parameters I think it potentially paints us into a corner. Even if we say the public name is the original one, that just seems very weird (if somebody is looking at an augmentation they wouldn't be able to use that name, if we allow passing positional args by name, and that would be unfortunate).

jakemac53 commented 1 month ago

Fwiw, I am not sure about unnamed params being given a name later on because I think it makes macros very weird. They do not have control over parameter names (or the entire method "header") when augmenting methods, but they do need to refer to them. They only provide a body.

We could rework the API but I think the existing API is good in this regard.

If we let them define the names of parameters etc then it will introduce additional ordering concerns that don't today exist I think.

jakemac53 commented 1 month ago

The more I think about this one, the more I think that just specifying the name might be the best option? If we can some up with a sensible name for it.

We could choose the name of the field, so for example the variable int x; would have a setter like void set x(int x). A bit weird that it causes shadowing, but probably fine. Otherwise we are just stuck calling it value or something like that, which might be more likely to shadow other members, that you might actually be more likely to want to access.

lrhn commented 1 month ago

If we can some up with a sensible name for it.

That's a big "if".

The problem is that the name matters.

With augmentations, you have to repeat the name. We can change that (maybe we should, allowing any augmenting declaration to rename positional parameters, it just dosn't change the parameter name, only the local variable name for the parameter.)

With the "don't-rename" lint, you have to repeat the name in a subclass. (It probably already exclud implicit setters, since they have no name.)

And if you want to refer to the parameter in DartDoc (you don't, ever, setters are documented differently from functions) it has to have some visible name.

We could give it a name, probably one of value, it or the base name of the setter.

I don't like the base name because it might be a private name, and I don't want to implicitly give a public parameter a private name. (If the name matters, it shoulnd't be private. If it doesn't, we don't need to give it one.) We can use the "corresponding public name" here (which is another use-case for separating the parameter name from the local variable name).

I don't like value. If you have to repeat it in an augmentation, it might shadow a real thing.

I don't mind it much, but it's (pun intended) not something we've done before.

On the other hand, now that we do have unnamed parameters, we might as well make it unnamed.

int x;
external int get x;  // implicit getter.
external void set x(int _);  // implicit setter.

It's possible to write an unnamed parameter manually, so everything in augmentations (and macros) should be ready to see unnamed parameters.

That's what I suggest:

The second option can be confusing, what if two macros both think they're the first to augment an unnamed parameter?

On the other hand, allowing an augmenting declaration to have a name different from the augmented/resulting definition means that that's possible. Then we might as well always allow the augmenting declaration to have a different declared parameter name, and only use it for the local variable name. (Which also makes it much simper to write augmenting declarations, you can rename parameters as you see fit for your body.)

jakemac53 commented 1 month ago

Then we might as well always allow the augmenting declaration to have a different declared parameter name, and only use it for the local variable name.

Maybe we should just do this, we can always lint on it too.