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

Allow var type id in non-declaring constructor parameter lists? #4179

Open
lrhn opened this issue Nov 26, 2024 · 2 comments
Open

Allow var type id in non-declaring constructor parameter lists? #4179

lrhn opened this issue Nov 26, 2024 · 2 comments
Labels
feature Proposed language feature that solves one or more problems primary-constructors

Comments

@lrhn
Copy link
Member

lrhn commented Nov 26, 2024

@munificent has a glorious write-up of declaring constructors: #4169

It allows var int x as a parameter in declaring constructors, as a way to introduce a mutable instance variable. 🎉
The grammar allows 'var' <type> <identifier> in every parameter, and the feature spec then disallows a var+type from occurring in a non-declaring constructor using prose (it's a compile-time error if a parameter containing var occurs in a non-declaring constructor parameter list).

I suggest we just allow it everywhere. It's easier than disallowing it, and the meaning is obvious. It makes var and final (finally) be symmetric.
It is unnecessary, because var int x means the same as int x, but var x and x also means the same thing in parameters, and we allow both.

One argument against is that if anyone does write var int x as a normal parameter, someone else might initially read it as a field parameter, even though it's not a declaring constructor.
Most likley, nobody will write that. They don't write var x today, even though it's valid. They don't write final int x in parametes, even in code that insists on every local variable being final if it can be.
I don't think we'll see an up-tick in occurrences of final int x in normal parameters, or occurrences of var int x at all, so the confusion risk should be minimal.
That also means that there is no benefit to allowing the syntax. (Hypothetically, allowing var int x everywhere means that you can safely change any final to var in a variable declaration, without having to do anything else. Might be easier for code generators. But I don't expect users to just blindly replace final with var without looking at a declaration.)

It does mean that the same syntax means different things in different places, which is usually bad.
That's already the case for final int x in declaring constructors, and this way, the issue would be symmetric in var and final. It'll be one less exception to remember, you can think of var and final as symmetric.
(Rather than: "You can use var int x and final int x in declaring constructors to declare a var/final field. In other parameters var int x is an error. In other constructors final int x means a final parameter.", it becomes "You can use (var|final) int x in declaring constructors to declare a var/final field. In other parameters, (var|final) int x just introduces a var/final parameter variable." It's symmetric, if you understand the difference between var and final, you can apply the consistenly, so I'd count this as a complexity score of 2 vs 3 when disallowing var int x elsewhere. It all counts!)

It's only parameters, though, you can't actually declare a field as var int x;. But you can write var (int x) = .... I'd be fine with allowing var int x; for variables too.
(Allowing var int x; may be a stepping stone to final-by-default, if we ever want that. I do, don't know about "we".)

Options:

  • Allow var int x as parameter everywhere. (My proposal.)
    • Go all in: Allow it in variable declarations too, late var int x = 42;. (Maybe, eventually.)
  • Disallow in parameters not in a declaring constructor. (Specified in feature spec.)
    • Also, eventually, disallow final in non-declaring constructor parameters. (Suggested in feature spec.)
    • Or lint against it. (Suggested in feature spec.)

WDYT?
@dart-lang/language-team

@lrhn lrhn added feature Proposed language feature that solves one or more problems primary-constructors labels Nov 26, 2024
@eernstg
Copy link
Member

eernstg commented Nov 28, 2024

One argument against is that if anyone does write var int x as a normal parameter, someone else might initially read it as a field parameter, even though it's not a declaring constructor.

We're talking about the ability to put the same keywords in many different locations, meaning one thing in some locations, another thing in other locations, or even nothing in some locations. I tend to think that this will worsen the readability of the code. Even in the situation where none of those "var with no effect" keywords exist in a million lines of code, it is still necessary for developers who are working with this code to think about the meaning of var every single time they encounter it in a parameter list.

Similarly for final, which might mean "make this parameter final" or "this parameter implicitly induces a final instance variable (and make this parameter final)".

I'd prefer to use a fresh keyword for the new feature. This means that developers will need to look it up if they don't know about this feature, but this is better than the situation where it's a well-known word that just slips through because it is completely unclear that it triggers a new feature.

Even better, the fact that parameters in general induce instance variables should be implied by the context ("we're in a class header, so this is a primary/declaring constructor", or "this declaration starts with this, so it's a primary/declaring constructor"), and then the parameter-that-doesn't-induce-a-variable would have a keyword that stands out and refers to the actual feature. Perhaps parameter, param, parm, or par would remind the reader that this is "just a parameter"; alternatively novariable or novar could remind the reader that this parameter does not induce a variable.

This yields the most concise form for the case where most or all parameters of a primary/declaring constructor are introducing a variable. Conciseness is the point here.

@tatumizer
Copy link

The problem of two different interpretations of "final/var" exists only in the case where the implementation of the "declaring constructor" is placed in the body of the class. If it resides in the header, these two interpretations become the same.
E.g. if we have

class Foo(final int x, var int y) {
   int get sum => x + y;
}

then the parameters x, y, viewed from the inside of the class, look like they are coming from a kind of "closure". The fact that the fields for x and y are created as part of the object structure is an implementation detail - it doesn't change the semantics.

That's how Kotlin implements primary constructors (see Kotlin's documentation). It covers most of the features expected from the primary constructors (except that there's no analog to novar). It's impossible (and not desirable) to copy the exact syntax from Kotlin, but a reasonable equivalent can be found IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems primary-constructors
Projects
None yet
Development

No branches or pull requests

3 participants