Skip to content

Commit bf35bb1

Browse files
authored
Add a proposal about inference of the modifier required (#3779)
Add a feature specification about a piece of syntactic sugar that implicitly induces the modifier `required` in the cases where the corresponding named formal parameter has a potentially non-nullable type (which means that it is an error to omit `required`). Note that the mechanism is only applicable to the formal parameter lists of concrete functions, it can not be used with abstract instance method declarations, and it can not be used with function types.
1 parent 6143f35 commit bf35bb1

File tree

1 file changed

+224
-0
lines changed

1 file changed

+224
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# Infer requiredness in concrete parameter lists
2+
3+
Author: Erik Ernst
4+
5+
Status: Draft
6+
7+
Version 1.0 (see the [CHANGELOG](#CHANGELOG))
8+
9+
Experimental flag: `--enable-experiment=infer-required`
10+
11+
## Summary
12+
13+
This proposal is built on a large number of issues expressing the desire to
14+
avoid writing `required` in the declaration of required named formal
15+
parameters, when possible. This started all the way back in issue number 15
16+
in the language repository, even before `required` was a modifier in the
17+
language. In addition to making this point, the issues contain many
18+
concrete ideas about how this could be turned into an actual language
19+
feature, up to rather complete proposals, in particular issue 3287. This
20+
proposal is just a consolidation of this body of prior work.
21+
22+
* [#15 Problem: Syntax for optional parameters and required named parameters is verbose and unfamiliar](https://github.com/dart-lang/language/issues/15)
23+
* [#878 [proposal] non-nullable named parameters required by default](https://github.com/dart-lang/language/issues/878)
24+
* [#938 Should NNBD type inference infer required when necessary?](https://github.com/dart-lang/language/issues/938)
25+
* [#1103 Replace current "@required this.name" in the named parameters with "this.name!"](https://github.com/dart-lang/language/issues/1103)
26+
* [#1502 Is the "required" keyword really necessary with non nullable types?](https://github.com/dart-lang/language/issues/1502)
27+
* [#1546 Allow a parameter to be required or not based on a generic type](https://github.com/dart-lang/language/issues/1546)
28+
* [#2050 remove the requirement for required on nnbd arguments](https://github.com/dart-lang/language/issues/2050)
29+
* [#2574 Alternative for "required" keyword](https://github.com/dart-lang/language/issues/2574)
30+
* [#2989 Purpose of required before named non-nullable arguments](https://github.com/dart-lang/language/issues/2989)
31+
* [#3206 Rethink required to be optional for non-nullable named parameters without default values](https://github.com/dart-lang/language/issues/3206)
32+
* [#3287 Inferring required named parameters without making function types a pitfall](https://github.com/dart-lang/language/issues/3287)
33+
34+
## Motivation
35+
36+
For brevity, it is desirable to be able to omit the rather long modifier
37+
`required` on the declaration of a named formal parameter. Of course, the
38+
resulting declaration should still be unambiguous, but this is indeed
39+
possible in some cases.
40+
41+
This section mentions a different proposal as well, namely
42+
[primary constructors](https://github.com/dart-lang/language/blob/main/working/2364%20-%20primary%20constructors/feature-specification.md).
43+
The reason for this is that this proposal about eliminating `required`
44+
turns out to be even more significant when the underlying declarations are
45+
concise, and primary constructors will do just that.
46+
47+
Note that all parameters in this document are named, because the proposal
48+
is specifically concerned with the rules about named parameters.
49+
50+
For example:
51+
52+
```dart
53+
// Current form.
54+
55+
class Point {
56+
final int x;
57+
final int y;
58+
const Point({required this.x, required this.y});
59+
}
60+
61+
// If this proposal is supported.
62+
63+
class Point {
64+
final int x;
65+
final int y;
66+
const Point({this.x, this.y});
67+
}
68+
69+
// If primary constructors are supported.
70+
71+
class const Point({required int x, required int y});
72+
73+
// With primary constructors plus this proposal.
74+
75+
class const Point({int x, int y});
76+
```
77+
78+
Here is a larger example (from issue 878):
79+
80+
```dart
81+
// Today.
82+
83+
class User {
84+
final String id;
85+
final String email;
86+
final String userName;
87+
final String address;
88+
final String phoneNumber;
89+
final String name;
90+
final String? avatarUrl;
91+
92+
User({
93+
required this.id,
94+
required this.email,
95+
required this.userName,
96+
required this.address,
97+
required this.phoneNumber,
98+
required this.name,
99+
this.avatarUrl,
100+
});
101+
}
102+
103+
// With this proposal.
104+
105+
class User {
106+
final String id;
107+
final String email;
108+
final String userName;
109+
final String address;
110+
final String phoneNumber;
111+
final String name;
112+
final String? avatarUrl;
113+
114+
User({
115+
this.id,
116+
this.email,
117+
this.userName,
118+
this.address,
119+
this.phoneNumber,
120+
this.name,
121+
this.avatarUrl,
122+
});
123+
}
124+
125+
// With this proposal and primary constructors.
126+
127+
class const User({
128+
String id,
129+
String email,
130+
String userName,
131+
String address,
132+
String phoneNumber,
133+
String name,
134+
String? avatarUrl,
135+
});
136+
```
137+
138+
It seems likely that it will be true quite often that the required, named
139+
parameters are exactly the ones whose type is potentially non-nullable,
140+
which means that the migration will simply be to delete every occurrence of
141+
`required`. A tool based migration would of course have to preserve the
142+
occurrences of `required` that have an effect (that is, the ones that are
143+
associated with a parameter whose declared type is nullable).
144+
145+
We aren't forced to have a migration at all. It might be more convenient in
146+
some cases to leave the code unchanged, at least for a while. The given
147+
developer or organization may also prefer to have `required` on every
148+
required named parameter as a matter of style.
149+
150+
On the other hand, it should be noted that we can't allow every occurrence
151+
of `required` to be inferred.
152+
153+
In a function type and in an abstract instance member declaration, the
154+
modifier `required` on a formal parameter declaration is crucial: It can
155+
be omitted, and the properties of the formal parameter will be different:
156+
157+
```dart
158+
abstract class A {
159+
void foo({int i});
160+
}
161+
162+
void Function({int i}) fun = ({int i = 0}) {};
163+
164+
typedef void F({int i});
165+
```
166+
167+
For the abstract instance method `foo`, the named parameter `i` is
168+
optional, and every implementation of the method must either specify a
169+
default value and/or a more general nullable parameter type (which implies
170+
that it has the default value null).
171+
172+
The variable `fun` must have a value which is a function object whose type
173+
is a subtype of `void Function({int i})`.
174+
175+
For the function type `F`, which is the same as `void Function({int i})`,
176+
the named parameter `i` is again optional, and values of that type must
177+
again have a default value or a more general parameter type.
178+
179+
In short, in these cases the absence of the modifier `required` is already
180+
taken to imply that the parameter is optional, and hence we cannot infer
181+
`required` just because it is absent. Hence, `required` must always be
182+
specified explicitly in these cases.
183+
184+
## Specification
185+
186+
### Static processing
187+
188+
Assume that _D_ is a declaration of a function or a concrete method with a
189+
named, formal parameter declaration `p` that does not have the modifier
190+
`required` and does not have a default value, and whose declared type is
191+
potentially non-nullable. In this situation _D_ is transformed such that
192+
`p` is replaced by `required p`.
193+
194+
This rule is applicable to `external` functions and methods, too.
195+
196+
### Dynamic semantics
197+
198+
There is no dynamic semantics of this mechanism, all behaviors are
199+
determined by the current rules of the language applied to the program
200+
where the compile-time transformation has taken place.
201+
202+
## Discussion
203+
204+
The proposal in issue 3287 includes the keyword `optional`, and uses it in
205+
the cases where this proposal just specifies that `required` is not
206+
inferred (because the omission of `required` is allowed, and has a
207+
different meaning).
208+
209+
The approach where `optional` is used implies that there will be a long
210+
modifier on _every_ potentially non-nullable named parameter in every
211+
function type and every abstract method declaration. This proposal is based
212+
on the assumption that the use of a modifier in that manner is too verbose.
213+
214+
However, if we prefer the added safety of explicitness then we can easily
215+
adjust this proposal to require `optional` in said cases.
216+
217+
We could also support a default value of the form `= _` in function types
218+
and abstract instance member declarations, in order to specify that every
219+
implementation must have a default value (which could, though, be an
220+
implicit null).
221+
222+
## CHANGELOG
223+
224+
* Version 1.0: First public version.

0 commit comments

Comments
 (0)