-
Notifications
You must be signed in to change notification settings - Fork 213
Take a detour via a legacy library in order to break class modifier rules? #3019
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
I'll disagree on that 😉
Not OK, specification is completely clear on that. Whether we want to reconsider is another issue, but it's definitely what is specified, and intentionally so. The problem is not the subclassing, but using Doing The rule that the The only exception to that rule is if the implementing library is a pre-feature library, and all the
See: dart-lang/sdk#52078 |
Ah, of course. I did not get any errors, but we do have the transitive requirement about
Assuming the pre-edit version of the example, I would expect the same rule to kick in: This is an error because we had But now I've edited the example, and it uses That's the actual intended topic in this issue. We have the following in the feature specification:
The notion of transitive restrictions is only mentioned in the non-normative section Transitive restrictions, so it's not quite clear what that would mean. For example, it may or may not imply that the following is an error: // ----- Library 'legacy.dart'.
// @dart = 2.19
// `Runes` is just an example, so we rename it.
typedef FinalPlatformClass = Runes;
// OK, due to special platform/legacy exception.
class C implements FinalPlatformClass {
C(): super('');
noSuchMethod(Invocation _) => throw 0;
}
// ----- Library 'main.dart'.
import 'legacy.dart';
final class D extends C {}
void main() {} |
There is no error in the new example. The The class it directly extends is not declared
That quote is itself non-normative. It has to be because it doesn't actually say anything. The "transitive restrictions" are the ones that are not just about direct super-dependences: The one about The four rules in the specification which can cause a compile-time error due to modifiers are:
The last two of those are "transitive", the first two only relates to immediate dependencies. The remaining errors are about mixin class declarations and mixing in non-mixins. None of these four rules apply to
So completely specified. There is no rule that you cannot extend a class in a second library which implements a The pre-feature library will have to make a (possibly breaking) change when it updates to 3.0. At that point, down-stream code will have to adapt. Until then, everything is exactly as unsafe as the pre-feature library itself implementing a platform library It's more likely to be actually useful to remove the "must be |
Can we close this? It sounds like the spec is where we intend it to be? |
Yes. The worry is that we have a path through a pre-feature library that allows something in a post-feature library that it couldn't do directly. The answer is that the pre-feature library does something it cannot keep doing when it migrates to 3.0, so that problem will go away. The only way to be absolutely safe is to do dependency order migration. As always. |
Yes, it's OK to close it. The main reason why it is no big deal after all is that this is only about platform classes, and most of those have very special rules (like The main reason why I was worried was that the special permission for legacy classes to break these rules would allow new (Dart 3.0) code to exist, based on legacy classes, and those Dart 3.0 classes would be "secretly legacy". This means that it could be quite difficult to assess the amount of migration work needed in the situation where a million line package has even just a few lines of pre-3.0 code left. It seemed safer to me if we would make it an error for new code to be a subtype of those "bad" legacy classes. However, there's no way we can do anything about it right now. Also, we can introduce a lint to flag those "secretly legacy" classes in Dart 3.0 code. If we don't do anything then we may end up with a lot of packages including just a few lines of legacy code, because that's a loophole that allows the rest of the package, ostensibly "Dart 3.0", to continue to break the rules about class modifiers, which is particularly unfortunate if it is done by accident. But, as I said above, it's only a very small number of platform classes, and it's probably never going to be a big issue. |
I think the following situation might not be fully clarified in the feature spec about class modifiers.
[Edit: The example should use
extends
, does that now.]The current situation
The modifier
final
onD
satisfies even the strongest requirements onD
itself, which means that we can concentrate on the permission to declareD
at all, noting that it is a subtype ofFinalPlatformClass
in a post-feature library.Currently, no tools report an error for the declaration of
D
.Analysis
The fact that
legacy.dart
exists and has@dart = 2.19
is a necessary precondition for the existence of a class which is a subtype ofFinalPlatformClass
, given thatFinalPlatformClass
isfinal
and no subtypes are declared in the same library. However, it is not so obvious that we should allow further subtypes ofFinalPlatformClass
to be declared in new code (given that they could not be declared at all without a legacy library likelegacy.dart
).Note that we do insist that indirect violations of other rules must be flagged as compile-time errors:
What to do?
We could of course say that the declaration of class
D
is not an error, which is how the tools currently behave.The rationale would be that it makes no real difference whether or not we can have instances of
C
whose run-time type is a subtype ofFinalPlatformClass
, or we can have instances ofC
as well asD
. In any case, no code can assume that the subtype graph underFinalPlatformClass
is just the rootFinalPlatformClass
itself.However, a counterargument could be that if we allow a legacy library to introduce a loophole in new code then we're essentially turning parts of the new code into legacy code, with no explicit heads-up to the author of the code at all. This could make it harder to assess the effort associated with a migration of any legacy libraries.
One approach to get these errors on classes like
D
could be the following: Whenever a class likeC
is analyzed or compiled, it gets a flag saying that this class is anerrorButForLegacy
, and it would then be a compile-time error to use that class as a superinterface in new libraries.The text was updated successfully, but these errors were encountered: