-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Experimental @sealed annotation #27372
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
Comments
#25462 (comment) seems related |
Preventing anyone from implementing your class' interface isn't solving any problems. It might be preventing problems if the class isn't expecting other implementations, and it assumes that all instances of the class that it sees will extend itself. Maybe because the class has private members that it accesses on instances other than Preventing anybody from extending or implementing If possible, I do recommend basing hash code and equals on publicly available data of a class - or just using identity, if the class isn't really defined by its visible state (or is mutable). Obviously that's not always possible, and in that case, you will have a class where you cannot make an a subclass with objects equal to the original class, but that's hardly a problem. Do let other programmers mock your class - if they do it, they probably have a problem that they solve that way. If you prevent them, they will curse at you for preventing them from solving their problem for no apparent reason (to them, even if it might be apparent to you). TL;DR: You won't know how someone else wants or needs to use your class, so preemptively removing their choices probably won't make anybody happy. It isn't really solving any problems for yourself - you can already not extend or implement your class if you don' t want to. You might be creating problems for others, when they decided that they do need to make a wrapper for your class, or mock it for testing, or something else that neither of us have even imagined yet. Also, you may like the syntax: factory DoNotSub() = DoNotSub._sealed; |
A couple of questions: Do you want a single annotation that prevents classes from extending, mixing in or implementing a class, or do you want more granular control? Do you want this to apply to all code, or only code outside your package? I can tell you my motivation for wanting something like this, and you can tell me if there's a better way to accomplish my goal. What I really want is better control over what changes will and won't be a breaking change. If I have a class and allow it to be extended, but don't allow it to be mixed in or implemented, then I think I can add a new concrete method to that class without breaking clients. But if I allow it to be implemented, then any client that implements the class is broken by the addition of a method they don't implement. |
True, to some extent. If you add to the interface of a class, other implementations of that interface will now be incomplete. That's not necessarily breaking immediately - it's only if someone starts using the new feature that it becomes a problem. It's not a problem that can be avoided in general. The same problem happens if you have a bona fide interface and add a member to it. There shouldn't be any problem with allowing the class to be mixed in. There are many other problems with using a class as a mixin if it isn't designed for it, but if you add a member to a mixin class, using it as a mixin should also get you that member. It may conflict with something in the superclass it's mixed onto, but that's a different problem. All in all, I don't think it's a problem worth solving by disabling the the interface of the class. I find that cure to be worse than the disease. |
Another use-case for |
For a given class If we want to promise that any particular set of changes will not break existing usages then we would certainly need to aim for a better foundation for knowing that this is true, and in which sense. I suspect that developers would have very little wiggle room if that notion is made strict and precise, so we would probably keep on being somewhat loose on this topic. However, the original issue was actually focusing on preventing both I think it would be quite easy to implement support for a In any case, it makes sense to try to see whether we can work around the current lack of There may be cases where you can make the class that you want to protect private. Let's call it The missing bit here is that (1) clients can still get in trouble because it's a convention and not enforced that no classes can implement I don't know if you consider it sufficient to know that an implementation could use techniques (like the JVM) whereby a running program is optimized, based on the fact that there is global information available at that point: If the actual program does not include any other subtypes of |
@eernstg I hear you on preventing code re-use, and I can be convinced it's not a good idea. Maybe the minimum amount of (easy, agreeable) work would be to de-sugar something like: class Point {
final int x;
final int y;
const sealed Point(this.x, this.y);
} into class Point {
final int x;
final int y;
factory const Point(int x, int y) = Point._sealed;
const Point._sealed(this.x, this.y);
} I've opened another issue, #27389, to track a proposal for |
This has come up again in the form of #30871. |
Related issue from our friends on Flutter, @yjbanov found that making their heavily used |
I'd like to implement this. @matanlurey and @yjbanov, can you answer Brian's two questions above? That is:
|
I think @leafpetersen is supportive here, as well. Would like to this his take.
Let's be careful about calling this an official language feature. It isn't. I want to make sure that this is somewhere between an experimental language feature and an analyzer lint. If and when backends start using That being said, I just want 1 annotation: @sealed class ThemeData {}
No. If we want to track this as a potential language feature, it's not possible (or at least desirable) to add a bunch of loopholes into how it doesn't actually work. Imagine that you could sub-class You could always get around this yourself: // Go ahead and implement/extend/mix-in this in this library.
class _SecretTypeThatIsInheritable {
}
@sealed class PublicTypeThatIsSealed implements _SecretTypeThatIsInheritable {}
We have no concept of the word "package" in the language. I hope that changes, but until then it isn't helpful (and if you look at other languages with |
@lrhn and I have been talking a bit about trying to get this in as a language feature for a medium term release, and prototyping with annotations would be a great way to get started. @bwilkerson and I chatted a bit about this last time I was in Portland. @srawlins maybe you could start pulling together some ideas and requirements for discussion? I think we may have some brainstorming docs from previous language team discussions to pull from as well. @munificent did you have a proposal for this once upon a time? |
Lol ok I'll be careful. I'll just word it as this annotation vetting the concept, for a possible official language feature. |
Not a full proposal around semantics, but there was some discussion between Erik and I for syntax. I'll send it your way. |
If this is going to be cased on annotations please consider building it on top of |
I'm guessing we are a ways away before asking the runtimes to understand the annotation (I think this is intended to be an analysis-only lint/warning until we decide to move forward/get feedback/etc). |
@matanlurey I will rephrase: if we want to also at some point do a quick experiment with what VM could potentially gain from such an annotation then it would be good to have it in a form that VM already understands to a certain degree. |
This annotation will act as an easy way for users to experiment with a language feature under consideration, called "sealed classes." Bug: #27372 Change-Id: Ieb8bc70edaf8c11c41f0f47c01951e8311736c1f Reviewed-on: https://dart-review.googlesource.com/69601 Commit-Queue: Samuel Rawlins <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Lasse R.H. Nielsen <[email protected]> Reviewed-by: Erik Ernst <[email protected]> Reviewed-by: Leaf Petersen <[email protected]>
Bug: #27372 Change-Id: If532ad81e833589642b696b5ef0c291ccaac1e3e Reviewed-on: https://dart-review.googlesource.com/71121 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
This has been released in Dart SDK release 2.1.1; the CHANGELOG notes the two new codes: |
(I actually wish this was a language feature, but we can start somewhere...)
All over even the Dart SDK codebase I see code like:
There is nothing actually preventing users from doing this.
I've started more aggressively using factory constructors to prevent subclassing:
But this still doesn't prevent
implements
, which, IMO is a serious problem for:Data models/structures that are not meant to be mocked:
Classes that implement
hashCode
andequals
comparing private state:cc @yjbanov
The text was updated successfully, but these errors were encountered: