Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The base modifier is contagious, even if you don't want it to. #2908

Closed
lrhn opened this issue Mar 13, 2023 · 4 comments
Closed

The base modifier is contagious, even if you don't want it to. #2908

lrhn opened this issue Mar 13, 2023 · 4 comments
Labels
class-modifiers Issues related to "base", "final", "interface", and "mixin" modifiers on classes and mixins.

Comments

@lrhn
Copy link
Member

lrhn commented Mar 13, 2023

I'm adding modifiers to platform classes.
I'm generally adding base to things like SetBase, ListMixin or UnmodifiableListView because they are not intended as interfaces to implement. (If you ever want to do implements SetBase, someone missed a point somewhere, either you or someone whose code you need to interact with. Just use implements Set.)

However, since base makes every subclass not have an implementable interface, I cannot do something like:

class SuperSet<T> extends SetBase<T> {
  // More great APIs on top of `Set`, and this is the default implementation.
}

and allow someone to implements SuperSet.

Instead I have to do

abstract interface SuperSet<T> implements Set<T> {
  // More great APIs ....
}
// and 
base/*or final*/ class SuperSetImpl<T> extends SetBase<T> implements SuperSet<T> { 
  // Implementation of the great APIs.
}

That's not necessarily bad, but it does force a separation into interface and implementation that Dart has so far not forced.

The obvious alternative is to not make SetBase be base. It's not dangerous if someone implements SetBase, it's just unnecessary and confusing.

What would work better is the ability to make SetBase not introduce an implementable interface, but not force it to be inherited by subclasses.

Strawman: A modifier called "nointerface" which allows the same direct operations as base (aka: not implement), but does not force subtypes to be base or final.

Because sometimes that's all you want to say: "This type is not important, always use the supertype instead, or an important subtype."

@lrhn lrhn added the class-modifiers Issues related to "base", "final", "interface", and "mixin" modifiers on classes and mixins. label Mar 13, 2023
@eernstg
Copy link
Member

eernstg commented Mar 13, 2023

Sounds like the job could be handled by a lint that kicks in if a declaration has implements ... T ... where T is a class C or a type C<T1 .. Tk>, and the class C "repeats an interface", that is: There exists a superinterface S of C such that every member signature declared by C is also a member signature in the interface of S (same member name, same signature). When C is in a different library, we'd probably just say 'every public member signature'.

In this situation, it is probably an improvement to change implements ... T ... to implements ... S1 ..., where S1 is S or S1 is S<U1 .. Um>, such that the interface is unchanged.

@lrhn
Copy link
Member Author

lrhn commented Mar 13, 2023

Maybe that is the answer: Don't mark these declarations as base, even though using them as an interface is an antipattern.
It's not unsafe if people do so anyway, just unnecessary.

We can then consider adding a modifier later, or an annotation if we don't think it's important that it's in the language.

(And, to be honest, it's only the base-subtypes-must-be-base requirement that stopped me from declaring Object as base.
You never need to write implements Object. The same argument could apply to other types too.)

@leafpetersen
Copy link
Member

We discussed this general area early on. There was a lot of focus on the idea that base class Foo was about enforcing the invariant that all implementations inherit from Foo, and it was never clear to me (and I asked at the time) why we thought that all uses of base fell into this category. For better or for worse, we've moved in the direction of making base fundamentally about "though shalt inherit from me", so I think this is going to be where we are. I really can't see adding new flavors of base for all of the different invariants you might consider. I'm going to close this out unless we want to use this as a discussion forum for changing the behavior of base, since I really don't think I see us adding something like nointerface.

@lrhn
Copy link
Member Author

lrhn commented Mar 14, 2023

I don't want base to stop being transitive. There is a use-case for that, where you do control/prevent the ability of others to create objects of your type without inheriting your implementation.
We need to support that use-case, and I think the current base behavior works to do that.

So this is a request for another modifier. And I can see that unless there turns out to be a large public demand for it, it's unlikely to happen.
So count this as the first public demand 😉.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
class-modifiers Issues related to "base", "final", "interface", and "mixin" modifiers on classes and mixins.
Projects
None yet
Development

No branches or pull requests

3 participants