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