-
Notifications
You must be signed in to change notification settings - Fork 220
Extend private named parameters to apply to positional parameters too. #4486
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -374,6 +374,12 @@ void pricyHammer() => Hammer(price: 200); | |
|
||
## Static semantics | ||
|
||
Prior to this proposal, a parameter only had one name which was used everywhere | ||
the parameter can be referred to. With this proposal, "parameter name" can be | ||
ambiguous, so we introduce some terminology. | ||
|
||
### Public and private names | ||
|
||
An identifier is a **private name** if it starts with an underscore (`_`), | ||
otherwise it's a **public name**. | ||
|
||
|
@@ -385,33 +391,60 @@ underscore does not leave something which is is a valid identifier *(as in `_` | |
or `_2x`)* or leaves another private name *(as in `__x`)*, then the private name | ||
has no corresponding public name. | ||
|
||
### Declared and access names | ||
|
||
* A parameter's **declared name** is the original identifier used to introduce | ||
the formal parameter. It's the name of the local variable used to access the | ||
argument value bound to a parameter inside the body of a function or | ||
constructor initializer list. | ||
|
||
If the parameter initializes or declares an instance variable, the declared | ||
name determines the name of the corresponding instance variable. | ||
|
||
* A parameter's **access name** is the name that allows users of surrounding | ||
declaration to work with the parameter. It's the name written at a callsite | ||
to pass a named argument to the function or constructor. | ||
|
||
It's the name that should be shown for the parameter in generated | ||
documentation from tools like [dartdoc][]. When other documentation wants to | ||
refer to the parameter using the `[squareBracket]` identifier syntax, that | ||
doc comment should refer to it by its access name. | ||
|
||
If a Dart implementation refers to the parameter in any automatically | ||
generated code or strings, those should use its access name. For example, | ||
implicit implementations of `toString()`, runtime error messages, stack | ||
traces, etc. | ||
|
||
In short, for all *users* of some code, it should appear as if the parameter | ||
only has its access name. | ||
munificent marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
[dartdoc]: https://dart.dev/tools/dart-doc | ||
|
||
Unless otherwise specified, the access name of a parameter is its declared name. | ||
The exceptions are: | ||
|
||
### Private named parameters | ||
|
||
Given a named initializing formal or field parameter (for a primary constructor) | ||
with private name *p* in constructor C: | ||
munificent marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
* If *p* has no corresponding public name *n*, then compile-time error. *You | ||
can't use a private name for a named parameter unless there is a valid | ||
public name that could be used at the call site.* | ||
|
||
* If any other parameter in C has declared name *p* or *n*, then | ||
compile-time error. *If removing the `_` leads to a collision with | ||
another parameter, then there is a conflict.* | ||
|
||
If there is no error then: | ||
|
||
* The parameter name of the parameter in the constructor is the public name | ||
*n*. This means that the parameter has a public name in the constructor's | ||
function signature, and arguments for this parameter are given using the | ||
public name. All uses of the constructor, outside of its own code, see only | ||
the public name. | ||
* If any other parameter in C has declared name *n*, then compile-time error. | ||
*If removing the `_` leads to a collision with another parameter, then there | ||
is a conflict. It is of course already an error if any other parameter has | ||
declared name _p_. That's a normal parameter name collision error.* | ||
munificent marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
* The local variable introduced by the parameter, accessible only in the | ||
initializer list, still has the private name *p*. *Inside the body of the | ||
constructor, uses of _p_ refer to the instance variable, not the parameter.* | ||
|
||
* The instance variable initialized by the parameter (and declared by it, if | ||
the parameter is a field parameter), has the private name *p*. | ||
```dart | ||
class C { | ||
C(int _a, {this._a}); // Already an error for parameter names to collide. | ||
C(int a, {this._a}); // New error because the public name collides. | ||
} | ||
``` | ||
|
||
* Else the field parameter induces an instance field with name *p*. | ||
* Otherwise, the access name of the parameter is *n*. | ||
|
||
*For example:* | ||
|
||
|
@@ -431,25 +464,85 @@ main() { | |
} | ||
``` | ||
|
||
*Note that the proposal only applies named parameters and only to ones which are | ||
*Note that this section only applies to named parameters that are also | ||
initializing formals or field parameters. A named parameter can only have a | ||
private name in a context where it is _useful_ to do so because it corresponds | ||
to a private instance field. For all other named parameters it is still a | ||
compile-time error to have a private name.* | ||
|
||
### Private positional parameters | ||
|
||
Dart already allows a positional parameter to have a private name. This is | ||
useful for a positional constructor parameter that is an initializing formal or | ||
a declaring parameter for a private instance variable. For other parameters, | ||
it's non-idiomatic but harmless to give them private names. *(Dart also allows | ||
munificent marked this conversation as resolved.
Show resolved
Hide resolved
|
||
local variables to have private names, which is unusual but harmless. A lint | ||
suggests that users don't do this.)* | ||
|
||
However, the user experience of the language is more than just the semantics. | ||
Generated documentation, code navigation in doc comment references, and runtime | ||
error messages all reflect a function or constructor's parameters back to the | ||
user. | ||
|
||
In all of those contexts, we want it to be an implementation detail that a | ||
positional parameter happens to have a private name. To that end: | ||
|
||
Given a positional parameter with private name *p* in formal parameter list L: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd still only do this for initializing formals and field parameters, where there is a reason to have a private name. If you ask for it otherwise, you're on your own. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why make things egregiously inconsistent? If we're going to do this (I'm not convinced) we can at least be consistent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe mention that |
||
|
||
* If *p* has corresponding public name *n* and no other parameter in L has | ||
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And maybe also make it explicit that it's a compile-time error if any other parameter has declared name p. (But it is true that every named parameter introduces a binding into the initializer list scope, for its private name, so we will get a "same name twice in one scope" error.) |
||
declared name *n*, then the access name of the parameter is *n*. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... , otherwise it's p. And for all other parameter declarations, the parameter name is the declared name. Can't fall back on defaults without an "otherwise", because we don't want to hit the default definition at all, which would says that the access name is the declared name. If we can hit that by simple fallthrough, then we'll also hit it from the clause that says the access name is n, and then it has two access names. Exhaust all the cases! So, in total:
Is that about right? |
||
|
||
Since a positional parameter can't be passed using a named argument, this | ||
doesn't affect the language semantics. It does mean that generated docs, error | ||
messages, inferred super parameter names, etc. should all use the public name | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "inferred super parameter names" are the ones of anonymous mixin applications? Or maybe drop it, it's not an exhaustive list, and it's hard to explain. |
||
of the parameter. For example:* | ||
|
||
```dart | ||
const int foo = 0; | ||
|
||
class C { | ||
int _foo; | ||
|
||
/// Uses [foo] to set [_foo]. | ||
C(this._foo); | ||
} | ||
``` | ||
|
||
Here, the generated documentation for C should show the constructor signature | ||
like `C(int foo)`. If a user clicks the `[foo]` reference in the doc comment | ||
and navigates to its definition, their IDE should take them to the constructor | ||
parameter `_foo` and not the unrelated top level constant `foo`. | ||
|
||
In short, a class author should never feel that can't use a private name for | ||
a positional initializing formal or declaring parameter because doing so might | ||
degrade the user experience of anyone working with the class or reveal the | ||
implementation detail that the parameter happens to initialize a private field. | ||
|
||
*Unlike with named parameters, this section applies to all positional | ||
parameters, regardless of whether they are initializing formals, declaring | ||
parameters, or even constructor parameters at all. This is because the language | ||
already allows using private names in all positional parameters, and we want a | ||
consistent user experience for all of them.* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it's a good idea. Maybe it's inconsistent that it doesn't work for other kinds of named parameters too. I could see myself wanting to write I think it's an easier story to tell if it only works for initializing formals and field parameters, parameters whose name also refers to something else, and that other thing might want to be private. For everything else, if you choose a private name, it's because you wanted one, and you could just use a public name. |
||
|
||
## Runtime semantics | ||
|
||
There are no runtime semantics for this feature. It's purely a compile-time | ||
renaming. | ||
|
||
## Compatibility | ||
|
||
This proposal takes code that it is currently a compile-time error (a private | ||
named parameter) and makes it valid in some circumstances (when the named | ||
parameter is an initializing formal or field parameter). Since it simply expands | ||
the set of valid programs, it is backwards compatible. Even so, it should be | ||
language versioned so that users don't inadvertently use this feature while | ||
their program allows being run on older pre-feature SDKs. | ||
For named parameters, this proposal takes code that it is currently a | ||
compile-time error (a private name) and makes it valid in some circumstances | ||
(when the named parameter is an initializing formal or field parameter). Since | ||
it simply expands the set of valid programs, it is backwards compatible. | ||
|
||
For positional parameters with private names, it falls back to continuing to use | ||
the private name as the access name in the rare case that there is a collision. | ||
That avoids breaking existing uses of private names in parameter lists. | ||
|
||
Even though non-breaking, this feature should be language versioned so that | ||
users don't inadvertently use this feature while their program allows being run | ||
on older pre-feature SDKs. | ||
|
||
## Tooling | ||
|
||
|
@@ -459,18 +552,33 @@ normative*, but is merely suggestions and ideas for the implementation teams. | |
They may wish to implement all, some, or none of this, and will likely have | ||
further ideas for additional warnings, lints, and quick fixes. | ||
|
||
### Error messages | ||
|
||
Compile errors and runtime exceptions (from things like invalid dynamic calls) | ||
may sometimes show a function's signature to the user or otherwise refer to a | ||
formal parameter. When tools generate these error strings, they should use the | ||
access name of each parameter if the error relates to a *use* of the parameter | ||
list. *For example, an error related to calling a function with an incorrect | ||
parameter type.* | ||
|
||
Errors that refer to accessing the parameter from within the function or | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... accessing the parameter variable ... (to make a distinction between the parameter and its introduced variable.) |
||
constructor should use its declared name. *For example, an error related to | ||
trying to assign to a formal parameter marked `final`, or an initializing formal | ||
with no corresponding instance variable.* | ||
|
||
### API documentation generation | ||
|
||
Authors documenting an API that uses this feature should refer to the | ||
constructor parameter by its public name since that's what users will pass. | ||
Likewise, doc generators like [`dart doc`][dartdoc] should document the | ||
constructor's parameter with its public name. The fact that the parameter | ||
initializes or declares a private field is an implementation detail of the | ||
class. What a user of the class cares about is the corresponding public name for | ||
the constructor parameter. | ||
If they don't already, doc generators like [`dart doc`][dartdoc] should be | ||
updated to always use a parameter's access name when referring to the parameter. | ||
|
||
[dartdoc]: https://dart.dev/tools/dart-doc | ||
|
||
### In-editor documentation | ||
|
||
IDEs will often show inline generated documentation or signatures when hovering | ||
over declarations or callsites. When an editor or IDE synthesizes a signature | ||
for a formal parameter list, it should use the access name of every parameter. | ||
|
||
The language already allows a *positional* parameter to have a private name | ||
since doing so has no effect on call sites. Doc generators are encouraged to | ||
also show the public name for those parameters in generated docs too. The fact | ||
|
@@ -524,6 +632,11 @@ can help users learn the feature. | |
|
||
## Changelog | ||
|
||
### 0.3 | ||
|
||
- Apply the same renaming to private positional parameters too (for doc | ||
generators, error messages, etc.) (#4479). | ||
|
||
### 0.2 | ||
|
||
- Add section about concerns for learnability and mitigations. | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure "access name" works for me. It feels like the name you use to read it, not to set (pass) it.
I'd just use "parameter name", the name of the parameter in the function type of the function/constructor.
A parameter declaration has a declared name, which is the syntactic identifier used to introduce the formal parameter. (Or it might not, if it's declared identifier is
_
.)Examples of parameters where
name
is the declared name:name
,int name
,this.name
,final int name
,super.name
, and (for completeness, not encouragement)int name()
.The declaration introduces a local parameter variable into the parameter scope (normal parameters only) and the initializer list scope (all kinds of parameter), bound to the argument value passed to that parameter.
The parameter variable's name is the same as the declared name.
Example where
x
is in the initializer list scope anddeltaY
is in the parameterscope:
The parameter declaration also introduces a function parameter to the constructors function signature. This name occurs in the function signature, but not in any scope, and it is the name used to refer to the parameter from outside of the constructor declaration. For named parameters, it is the name used to pass an argument at call sites. For positional parameters, the name is never used in code to denote the parameter outside of the function. Both can be referenced indirectly by tools that refer to source names, like DartDoc, in error messages, or while debugging.
Example with references to the names of
value
andnegated
:For non-constructor parameters, there is also a lint to ensure that an overriding method has the same positional parameter names as the
function it overrides.
... and the parameter name of field parameters and initializing formals
is now not always the declared name ...
(Now I waxed didacticly again. I really need to learn to stop writing what I'm thinking. Or while I'm thinking. And I'm doing it again!)
Point is: "access name" feels like the opposite of "name used to pass an argument.
I like "parameter name" because that's what we already call the name of the parameter of a function from the outside, which is what this name is.
Use what you want of the above, or ignore it. I tend towards over-explaining things.
Today we use "parameter" and occasionally "parameter name" for internal local variables today, because we haven't made a distinction. Now we should make that distinction, so I suggest "parameter" and "parameter variable", and "parameter name" and "parameter variable name". And for field parameters also a "field name" of the instance variable declared by the field parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me either, but it was the best I could think of. I thought about it some more and came up with "visible name". I think that works well. It's not exactly the same as "public" (so we can distinguish a public identifier from a visible parameter), but it conveys pretty much the same thing.
<3 :D