Open pq opened 2 years ago
SGTM
Interestingly, we have MixinElementImpl.superInvokedNames
, but here we would add a method specific flag, maybe invokedSuperSelf
.
Hm... So, we will track, at least for mixins, whether each methods call super-self, and remember as a flag.
And in MustCallSuperVerifier._findOverriddenMemberWithMustCallSuper
we walk the hierarchy and looking for a @mustCallSuper
annotations. Could this also be a flag on the method element?
[Edit: Haha, the lint already works the way I suggested. So the text below just spells out what I was thinking about, it doesn't propose anything new.]
We just discussed this issue at the language meeting, and I had a comment on it. Here's some more detail on that.
I think it makes sense to consider two different cases here:
ShouldScream
.Case 1 perfectly matches the description in the initial text of this issue.
Case 2 allows for early error detection, and it occurs when a concrete member declaration in the mixin overrides a member in an on
type whose declaration has the @mustCallSuper
annotation:
abstract class HowToScream {
@mustCallSuper
void scream() {
print('Aaaaaaaaaaaaaaaaaaaaaaaaaaaa!');
}
}
mixin DontScream on HowToScream {
@override
void scream() { // LINT.
print('Sssssh!');
}
}
We're using the on
type that enables a superinvocation that targets a declaration that has @mustCallSuper
as an indicator: No matter which actual superclass this mixin is applied to, its scream
method is designed to be part of a superinvocation chain. So we treat DontScream.scream
as an overriding declaration (that inherits @mustCallSuper
, as if the on
type had been a superclass—it's the whole point of each on
type to represent parts of the superclass). So we lint the body if it does not perform the required superinvocation.
It could be argued that @mustCallSuper
is concerned with a specific implementation, and an on
class is an interface, but I do think that the notion of "being part of a superinvocation chain" needs to be associated with the on
types of a mixin: There is nothing that matches more precisely, it is unsurprising that the declaration-site role of the superclass is played by the on
clause, and it is a missed opportunity if @mustCallSuper
annotations in on
types are ignored.
Duplicate of https://github.com/dart-lang/sdk/issues/36657 but now this has more history.
Hm... So, we will track, at least for mixins, whether each methods call super-self, and remember as a flag.
And in MustCallSuperVerifier._findOverriddenMemberWithMustCallSuper we walk the hierarchy and looking for a @mustCallSuper annotations. Could this also be a flag on the method element?
I think so. Specifically, a flag like invokesSuperSelf
on MethodElement(Impl)
?
I thought about auto-inheriting hasMustCallSuper
, we have something like this for hasDoNotStore
and hasOrInheritsDoNotStore
- although these are not hierarchy based, but enclosingElement
based. It could be MethodElement.hasOrInheritsMustCallSuper
.
Or we could repurpose hasMustCallSuper
to return the "effective" state of the flag. But then on the element model level we would not know whether it was declared with @mustCallSuper
or inherited it. OTOH, we already do so for all types - omitted types are inherited.
I believe that all of the hasFoo
getters should have the same semantic: return true
if this element is annotated with the foo
annotation. If we want a getter for the "inherited" form of hasMustCallSuper
, I'd propose a different name, such as hasOrOverridesMustCallSuper
. Or maybe we can find something better. :-/
There are two arguments to keep hasMustCallSuper
as is - first, the name says "has", and second, as you mentioned, the consistency. For name, maybe bool get mustCallSuper
? It looks grammatically correct to me, but I might be wrong here as well. And it is the same name as the annotation itself, for better or worse.
One question that bothers me is whether we need the current hasMustCallSuper
at all, maybe we always need the higher level semantics? From searches in the analyzer and google3, we use hasMustCallSuper
only in MustCallSuperVerifier
, so we only need the semantics that is useful there.
Oh... Actually, the element that has @mustCallSuper
itself does not have to call super
. Its overrides have to. Hm... Maybe we are better leave it all as is.
One question that bothers me is whether we need the current
hasMustCallSuper
at all, maybe we always need the higher level semantics?
I don't know whether we need the current semantics, but if not I'd want to leave the name unused in case we had a need for it in the future so that it could still be consistent.
Actually, the element that has
@mustCallSuper
itself does not have to call super.
Yeah, in hindsight it might have been better to name the annotation overridesMustCallSuper
. It's longer, but more accurate. But we'd still be in the same position when trying to name getters on MethodElement
.
https://dart-review.googlesource.com/c/sdk/+/240652 adds ExecutableElementImpl.invokesSuperSelf
, which I think might be enough to implement a fix on top of it.
This is fantastic. Looking now. Thanks!
https://dart-review.googlesource.com/c/sdk/+/240880 adds the diagnostic and https://github.com/flutter/flutter/pull/102328 updates DiagnosticableTreeMixin
in flutter which it flags.
~My initial attempt to fix DiagnosticableTreeMixin
hit a snag. To ensure mustCallSuper semantics, DiagnosticableTreeMixin
has to be on Diagnosticable
but if we do that, DiagnosticableTreeMixin
can only be mixed into classes that mix in Diagnosticable
and that would be breaking (in flutter and downstream).~
UPDATE: the flutter issue was actually a false positive; working on a fix.
Mixins that implement methods that are marked
@mustCallSuper
can be mixed into classes that break the@mustCallSuper
contract (by not callingsuper
).motivating example
implementation
To produce a diagnostic on
ShouldScream
, we need information that is not currently in the element model. Since on-the-fly non-local analysis is expensive, we propose adding the required information to the element model.Specifically, we'd like to:
invokesSuper
), that@mustCallSuper
and verify that super is invoked by the mixin method.This will require us to:
invokesSuper
flag bitmask(Unless @scheglov, you see a better approach?)
/fyi @bwilkerson