dart-lang / language

Design of the Dart language
Other
2.65k stars 202 forks source link

Can a constructor augmentation be a redirecting constructor? #3553

Closed munificent closed 2 months ago

munificent commented 8 months ago

Consider:

// c.dart
import augment 'c_aug.dart';
class C {
  C();
}

// c_aug.dart
library augment 'c.dart';

augment class C {
  augment C() = D; // <-- Is this allowed?
}

The augmentation libraries proposal currently says:

TODO: What about redirecting constructors?

The grammar in the proposal does allow it. But there are no specified semantics. We should resolve that.

I suggest that the answer is "no". An augmentation of a constructor can't be a redirecting constructor.

We probably also need to think through whether a generative constructor can augment a factory constructor or vice versa. I suspect the right answer for that is also "no".

lrhn commented 8 months ago

What would you write if you want a constructor to be augmented to a redirecting (generative) constructor, redirecting to a constructor that hasn't been generated yet? Is that not going to be possible?

The model so far seems to be that you can add anything that hasn't been added before, and doesn't contradict something that has been added before. That suggests that C(); is the most undefined generative constructor. (It's not a factory constructor, because someone might look at the signature and decide to use it as a super-constructor. So the most undefined factory constructor is factory C();.)

Then you can augment that with what it actually does. I'd allow adding either an initializer list and/or body (: x = x or {body;}) making it a non-redirecting constructor, or a redirection : this(args) making it a redirecting constructor.

If you do nothing, the default at the end is that it's a non-redirecting constructor, since that's the only one that can do nothing (except implicitly call super()).

So, C(), C.new() and C.name() are unspecified generative constructors until \<some phase> where it needs to have been decided (and will default to non-redirecting if nothing has been chosen). Until then, you can go either way.

(But I probably wouldn't allow

augment C() = D;

since it is missing the factory, and likely won't allow changing a generative constructor to being a factory construct.)

rrousselGit commented 8 months ago

Freezed would definitely benefit from being able to augment a plain constructor into a redirecting factory

munificent commented 6 months ago

What would you write if you want a constructor to be augmented to a redirecting (generative) constructor, redirecting to a constructor that hasn't been generated yet?

Several times, we've discussed whether we need some specific notation to mean "I'm declaring a member but not defining it because the definition will be provided by an augmentation". In some places you can sort of use abstract for that. Or you can sort of use external. But those already are intended to be defined by other mechanisms (by subclasses or by the runtime, respectively), so it feels a little strange to reuse either of those keywords for augmentations.

I could see us coming up with some notion of a "missing body" for a member that should be filled in by an augmentation. If we had that, we could use it for constructors too:

class C {
  C(int x, int y) somekeyword;
}

But I have no idea what actual syntax I'd want there. I've tried, but I've never come up with anything I like. Maybe we can just allow ; even in non-abstract classes and on static members too?

The model so far seems to be that you can add anything that hasn't been added before, and doesn't contradict something that has been added before.

Yes. "Add" can be ambiguous, so I'd phrase it more like "everything you can do before an augmentation is applied, you can still do after it's applied".

I think we could allow augmenting a factory constructor into a generative one. But I would be surprised if it doesn't turn out to break something to do that. I'm inclined to disallow that and say that a constructor's "generativity" (generative vs factory) is fixed by the initial declaration in both directions.

Freezed would definitely benefit from being able to augment a plain constructor into a redirecting factory

Can you give some more details on this? Turning a generative constructor into a factory one is a breaking change because the former could be chained to by a subclass's constructor but the latter can't.

lrhn commented 6 months ago

"everything you can do before an augmentation is applied, you can still do after it's applied"

That probably doesn't include applying another augmentation. They can't both add a superclass, or an optional positional parameter (if an augmentation can add that). (They probably should be allowed to both add the same interface, without hitting the "don't have duplicates in implements clause" rule. That one should be per implements keyword, not per fully-augmented class.)

Augmenting a factory constructor into a generative constructor only makes sense if the factory constructor is so under-declared that it has no body or redirect, which are required for a factory constructor. Which means it's not really a factory constructor yet, it's an "undecided" constructor. If it can go either way, it's because it's not really either yet.

I'd be fine with allowing abstract Foo(); as a way to declare an undecided constructor. Any augmentation which locks in an implementation (adds factory, or adds a body without adding factory, or adds an initializer-list/generative-redirect) gets to decide what it is. Until then, it's not (definitely) a generative constructor, so you can't use it as a super-constructor in a subclass. If that ever matters, because no other class should see the class without all its augmentations.

Generally, we could use abstract in front of non-classes to mean "partially declared". Even abstract int x; which currently introduces an implicit interface getter and setter, could be consistently augmented into either a variable or a setter/getter implementation.

jakemac53 commented 2 months ago

@munificent any chance I could ask you to look at this one? In general there is a more fleshed out section on constructors now, but this one still needs a look.

jakemac53 commented 2 months ago

Actually, I didn't realize https://github.com/dart-lang/language/pull/3737 still hasn't landed. I probably should finish that first, or maybe it answers some of this. Will have to page it back in when I get back.

jakemac53 commented 2 months ago

Closing this based on https://github.com/dart-lang/language/pull/3737 which outlines what is allowed here or not (tldr; it defines "potentially redirecting" constructors which can be augmented with redirecting constructors, but other constructors cannot be).