dart-lang / dart-lang/language
Inferring required named parameters without making function types a pitfall
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
In a function declaration like:
```dart
foo({int x = 1, int y}) { ... }
```
The declaration of `x` is fine. It's non-nullable but optional, since it has a default value. The declaration of `y` is an error since it's optional (no `required`), has a non-nullable type, and no default value.
If we wanted to make `y` *not* an error, the obvious interpretation is to infer `required`. We don't do that because that would lead to an asymmetry in function types:
```dart
typedef TakeTwo = Function({int x, int y});
```
This typedef is fine and both `x` and `y` are optional even though they have non-nullable types and no default value.
We could say that function declarations and function types just work differently: We infer required in the former and optional in the latter. But that's a pitfall for users. If they take a function declaration and copy/paste its signature to a function type or typedef the resulting type might be different.
Function declarations are *much* more common than function types. So this rule punishes the common case in order to avoid problems with the rare case. I have an idea for how we could make function declarations smarter and shorter while avoiding this pitfall.
## Proposal
The proposal is pretty simple:
* Infer `required` for named parameters in function declarations that have potentially non-nullable types and no default value. So in this declaration:
```dart
foo({int x = 1, int y}) { ... }
```
There is no error and `y` is a required named parameter.
* Require function types to be *explicit* for potentially non-nullable named parameters. Instead of inferring optional (current) or required (what I propose doing for function declarations), it just forces the user to choose. This is an error:
```dart
typedef TakeTwo = Function({int x, int y});
```
It doesn't know whether `x` and/or `y` should be required. To resolve this, you must explicitly mark the parameter as being required *or optional*. If you change it to:
```dart
typedef TakeTwo = Function({optional int x, required int y});
```
Now there is no error. The first parameter is optional and the second is required.
Note that this only applies to potentially non-nullable parameter types. Nullable parameter types are inferred optional if there is no `required` as they are today and as they are in function declarations.
This will make function *types* that have optional named parameters of potentially non-nullable types more verbose. In return, it will make function *declarations* that have required named parameters of non-nullable types more succinct.
For example, this class in Flutter gallery:
```dart
class _ColorPickerSwatch extends StatelessWidget {
const _ColorPickerSwatch({
required this.color,
required this.selected,
this.onTap,
});
final Color color;
final bool selected;
final Function? onTap;
...
}
```
Becomes:
```dart
class _ColorPickerSwatch extends StatelessWidget {
const _ColorPickerSwatch({
this.color,
this.selected,
this.onTap,
});
final Color color;
final bool selected;
final Function? onTap;
...
}
```
If we also do primary constructors, it goes from:
```dart
class const _ColorPickerSwatch({
required final Color color,
required final bool selected,
final Function? onTap,
}) extends StatelessWidget {
...
}
```
To:
```dart
class const _ColorPickerSwatch({
final Color color,
final bool selected,
final Function? onTap,
}) extends StatelessWidget {
...
}
```
I'm generally not in favor of lashing two levers together a user might want to operate independently. That's why, for example, we have both `final` and `sealed` on classes because you might want to prevent subclassing independently of opting in to exhaustiveness checks.
But in this case, you can still operate the levers independently. You can have required nullable parameters and optional non-nullable ones. It's just that if the user doesn't pull the optional/required lever at all, we infer the completely obvious answer in places where we have the context to do so.
In places where there isn't that context, we make the user state it explicitly. This is arguably even better than the current behavior because when someone is reading a function type today, they might not realize that the language defaults to optional for all named parameters.
Contributor guide
Research direction
Start with the proposal in issue #3287 and compare its rules for named parameters in function declarations and function types. Trace how Dart currently handles potentially non-nullable parameters, defaults, and required or optional markers. Done means the design is resolved and the corresponding language specification changes are defined, including the function-type examples and compatibility implications.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100