Description
Consider someone creating a Flutter package that extends a bunch of widgets with an extra argument, for example adding an isEnabled
argument to every button, which changes how the onPressed
argument gets forwarded.
Currently, if they do this, every time the superclass constructor is changed, they have to republish their package with an update. This leads to the likelihood that the package will eventually be abandoned and won't be updated (since that is work). It also means the package will only work with some versions (those that match the API the package expected).
Imagine if instead they could provide a constructor that is defined to have all the arguments of the superclass, with just a few "edits", as in:
class MySuperButton extends Button {
MySuperButton(...super, { bool isEnabled })
: super(
onPressed: isEnabled ? onPressed : null,
...super,
);
}
...where ...super
stands for "fill in everything from the superclass that isn't overridden here".
See also dart-lang/sdk#22274 which is similar but doesn't involve overriding. This proposal would make that one irrelevant (it just becomes MySuperButton(...super)
or some such).
Activity
eernstg commentedon Aug 5, 2019
And see also #418 which contains a broader discussion about how to abbreviate code that implements forwarding or "near-forwarding" invocations.
yjbanov commentedon Aug 5, 2019
A close relative to the
super
use-case is factory-to-constructor and factory-to-factory forwarding, which, despite supporting a forwarding syntax, requires that you duplicate the signature of the constructor you are forwarding to:However, the
super
keyword doesn't seem to make sense for factories. It would be nice to have something that works in both cases, and is perhaps extensible to some of #418 features in the future./cc @mdebbar, who has been thinking about this same issue in the past
apps-transround commentedon Jul 14, 2021
A partial solution: similarly to @Hixie 's proposal let's use super arguments in child constructors but instead of the automatic …super, let developers use super arguments one by one as needed, the same way as this works.
For example:
It is just syntactic sugar and
but
Multi-level inheritance can be simplified to one level up as it works with the :super(y) calls: if the child exposes a parent argument in its constructor than that argument is offered to the grandchild as a super.y argument.
eernstg commentedon Jul 14, 2021
That's cool! I can see how
super.p
could be unambiguous for named parameters, so let's consider a sub-proposal where this only applies to named parameters.We would say that named parameters in corresponding class/superclass constructors correspond to each other if and only if they have the same name, but that's probably a good constraint because it's easier to read and understand a whole class hierarchy if the constructors use the same named parameters to mean the same thing. With that in place, we can handle a named parameter of the form
super.p
orT super.p
as follows:At the end of the constructor there's a superconstructor invocation
super(...)
orsuper.name(...)
, let k be the denoted superconstructor. LetS
be the type of the named parameter namedp
in k. (We'd have some errors ifS
isn't a supertype ofT
and stuff, but let's skip that for now.) Thensuper.p
is desugared toS p
andT super.p
is desugared toT p
, and an extra parameterp: p
is added to the superconstructor invocation, further addingrequired
and any default value thatp
has, based on the declaration ofp
in k.This is a feature that fits into the current language, and it would allow us to simplify cases like the following:
Levi-Lesches commentedon Jul 14, 2021
Especially when overriding a class with a lot of parameters, like some Flutter widgets, using
super.param
would make things SO much easier.For positional and non-named optional parameters, we can try to resolve the ambiguity with the following rules:
There can be a lint similar to
avoid_renaming_method_parameters
that ensures we keep the names consistent, but that simple name changes don't become breaking. With that in mind, here are some examples:Of course, if one requires advanced usage, such as omitting a parameter, they are free to use
super()
manually. Here's the only case I can think of that causes problems, not just for positional, but named parameters as well:I think in cases like these (specifically, no identically-named super constructor), Dart should force a manual
super()
instead of trying to guess.EDIT:
I revised this proposal by allowing non-super parameters to come before and in-between super parameters, see above.
apps-transround commentedon Jul 15, 2021
Having multiple super constructors is a common case so we should deal with it.
Exactly identifying the super constructor via mixing the new and the existing language elements?
eernstg commentedon Jul 15, 2021
@apps-transround wrote:
Definitely—I proposed that the
super.
parameters should be added to the superconstructor invocation which is already present in any generative constructor declaration (it's there explicitly, orsuper()
is added implicitly). This means that theB(...)
constructor could invokeA.name1
as follows:This makes it possible to connect
B
withA.named
,B.named
withA
, etc., all combinations, unambiguously.If we stick to named parameters as
super.
parameters, it also allowssuper.name1
to pass actual arguments (positional or named) as needed, and it still allows for processing allsuper.
parameters without introducing additional rules for how to do it.@Levi-Lesches wrote:
I'm sure we could sort out the details. I have the impression that named parameters a really, really straightforward here, and positional parameters give rise to a number of ambiguities that we'd need to sort out carefully.
The argument in favor of supporting positional parameters (including or excluding optional ones) is convenience and completeness, but the rules about how it actually works may be tricky to remember.
The argument in favor of only supporting named parameters is simplicity and comprehensibility, but there may be added verbosity, especially for constructors with many positional arguments. Note that we might use optionally named parameters to allow many more constructors to use more named parameters without requiring callers to call them "namedly".
We'll never get rid of that dichotomy. ;-)
apps-transround commentedon May 11, 2022
@eernstg @Hixie
Well, I did not expect any mentions but now I'm a bit disappointed that the copycat of my proposal was named as the source.
Dart 2.17: Productivity and integration
Just for the record, https://github.com/dart-lang/language/issues/493#issuecomment-879624528 is an earlier comment (from @apps-transround) where the use of super.p is proposed with the same motivation and semantics as in this issue.
mit-mit commentedon May 11, 2022
@apps-transround sorry, I missed that. Blog post has been updated!
apps-transround commentedon May 11, 2022
@mit-mit Thanks a lot! I truly appreciate your immediate response.