-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Frontend complains about non-matching type from mixin and base class #31984
Comments
If I understand correctly the error is reported because At the same time Overriding However in reality we are override /cc @leafpetersen @lrhn @eernstg - could you provide the guidance here? I think to avoid the language kerfuffle we should consider if we can rewrite this code as class StatefulWidget {}
abstract class State<T extends StatefulWidget> {
T get widget => _widget;
T _widget;
}
class _AnimatedCrossFadeState extends State<AnimatedCrossFade> with TickerProviderStateMixin<AnimatedCrossFade> {}
class AnimatedCrossFade extends StatefulWidget {}
abstract class TickerProvider {}
class TickerProviderStateMixin<T> extends State<T> implements TickerProvider {
factory TickerProviderStateMixin._() => null;
} but it requires changing public APIs. |
Presumably this code:
Should have Assuming that, then this is a slightly ropy area, since we don't have a spec for super mixins (they're not an official part of Dart 2). @lrhn and @stereotype441 should check me on my analysis of the overriding rules, but...
If I think about this in terms of the eventual explicit But I'm not sure I have the right mental model for super mixins, so I'm interested in hearing what @lrhn has to say here. |
@leafpetersen the most confusing part here is that Our generics are covariant (an instance of type /fyi @stereotype441 |
Here is PR against Flutter flutter/flutter#14327 which fixes all reported override violations by parameterizing involved mixins. This PR is provided to show extent of the changes required (and how boilerplate-y they are). |
@mraleph this is a really interesting example and I'm not done thinking about it. Here's a snapshot of my thought process right now. The front end distinguishes covariance that arises due to the class TickerProviderStateMixin extends State<StatefulWidget> implements TickerProvider { ... } Consider the following code: void f(TickerProviderStateMixin m, StatefulWidget s) {
m._widget = s;
} The front end engages in a reasoning process that looks something like this: If we choose to accept the override, we will have to remove this optimization. That's not necessarily a bad thing--as I understand it the VM currently doesn't implement this optimization anyhow, since the runtime overhead of communicating from the caller to the callee that the check can be skipped proved to be more expensive than the cost of the redundant check. What's not yet clear to me is whether there are other optimizations that would also have to be removed. For example, there is also an optimization allowing covariance checks that arise from class generics to be skipped when the call is through I'll keep thinking about this and try to have a more airtight analysis of the situation in a few days. |
I was told that DDC will continue to use the existing analyzer based front end in the near term (time frame of flutter beta) and plans to switch to the new front end only after that. Considering this and the fact that the VM is blocked on a resolution for this I would vote for removing these optimizations for now to unblock flutter. |
Presumably this isn't specific to extends right? That is, the reasoning should only depend on
If I understand what you're saying correctly, treating
I am very resistant to losing this. But it's not clear to me why this is the case, other than for code which is injected into a class via a mixin application (for which I think we already lose various nice properties: e.g. that we can trust super calls). So if we only lose this for methods that are injected via mixins, then this might be tolerable. But if this entirely breaks the ability to trust this calls, that's a big price to pay. |
Assuming that:
then I would be comfortable moving ahead with a solution based on treating the @lrhn thoughts? |
The problem here is: class TickerProviderStateMixin extends State<dynamic> implements TickerProvider { ... }
class A extends State<AnimatedCrossFade> with TickerProviderStateMixin {} which makes A implement both The current plan for allowing multiple instantiations of the same generic class is to only allow that if the superclass is more specific than any interface added by an There is no A mixin application extends its superclass We could also remove the problem by declaring it as: class TickerProviderStateMixin<T> extends State<T> implements TickerProvider { ... }
class A extends State<AnimatedCrossFade> with TickerProviderStateMixin<AnimatedCrossFade> {} As for treating the extends clause of the class that a mixin is derived from as only a superclass constraint, not an interface, I think that will be too breaking. The mixin application class is expected to implement the interface of the mixin class, and I believe people are relying on that. We can't magically remove a super-interface from that interface - if you are a (I really would like to get to the point where there is no difference between "T implements |
It seems our major problem stems from the fact that mixins are normal classes (even though people using mixins don't use them as normal classes). How about we introduce an annotation, say Given the class like this: @mixin
class M extends B<Ts> implements Is {
// methods
m() { /* body */ }
} We actually would emit in Kernel:
Mixin applications would copy bodies from Communicating precise intent (this class is used as a mixin) seems to solve most of the issues we are having, including optimizations. @stereotype441: Thanks for the example. Indeed, I was not thinking about it that way.
I have PR doing exactly that and @Hixie is not in favor - because it does make code more verbose. See flutter/flutter#14327 (comment)
We don't really use any of those optimizations so we are not affected at all. |
Correct, it applies to
That wasn't precisely what I was saying. I was responding to @mraleph's suggestion that it might be ok to relax the rules for what constitutes a valid override, and treat the concrete setter
Hmm, I think you may be right--if we reject the idea of relaxing the rules for what constitutes a valid override, and instead consider the mixin's |
@mraleph I don't see how your suggestion of introducing the |
As far as I can tell, the error message is correct, and this has nothing to do with mixins. You have a class that's relying on covariant override without marking the override as covariant. At the end of this message, I've included a version that works, and generally, represents the correct type-safe way to fix this problem: add type parameters on the mixins. We're currently attempting to hack around these problems by allowing classes to implement the same interface with different type arguments. This isn't sound, and this error message demonstrates why that is: any type argument used in a contra-variant position will cause problems like this. Let's consider this class:
Now ask the following, is
In Dart, we've chosen to report this error at runtime on the last line. In Java and C#, this would result in an error on line 2. This is because we don't want to introduce use-site variance (aka wildcards as in Java). In Dart, we're allowing the assignment because that matches a pervasive incorrect intuition, not because it's type safe. What we've done is make all type annotations implicit use-site covariant. However, that doesn't extend to extends clauses, and you only get garbage if you try to hack around that.
|
I'm not sure I understand this. You are always a |
The basic case is this: // framework code
class X { } class Y extends X { }
class A<T extends X> {
void foo() { }
T get bar => null;
}
abstract class M extends A {
void baz() { foo(); }
}
// third-party code
class B extends A<Y> with M { } It doesn't make any sense to me that M has to specify the type argument to A. It in no way is affected by that type argument. It wants to work with every type argument. We definitely don't want to require that B have to specify a type on M, that would be hugely verbose and make the framework much less usable. Previously, |
It's not exactly clear what that means. E.g., you could mean this:
or
We have to pick one. The latter is the interpretation we pick today in Dart. |
What I want is for the end result to be: class B extends M&A%Y { }
abstract class M&A%Y extends A<Y> { }
abstract class A<T extends X> { } ...where |
There are two solutions that make sense to me right now. The first is to allow: // framework code
class X { } class Y extends X { }
class A<T extends X> {
void foo() { }
T get bar => null;
}
@mixin
abstract class M extends A {
void baz() { foo(); }
}
// third-party code
class B extends A<Y> with M { } where:
Since all instances of The point is that I think this solution means that the code injected via a mixin can't trust its super type, but everything else should be hunky-dory. The second possibility (proposed by @peter-ahe-google and @mraleph) is to require // framework code
class X { } class Y extends X { }
class A<T extends X> {
void foo() { }
T get bar => null;
}
abstract class M<T> extends A<T> {
void baz() { foo(); }
}
// third-party code
class B extends A<Y> with M { } |
I think there's places in the codebase where we currently use the way that a class, interface, and mixin are the same thing, which may make applying the first solution tricky, but in principle that one seems sane to me, and iirc is consistent with the long-term plan for the language. The second solution also makes sense to me if the inference is reliable and if we explicitly mark So both SGTM. |
The discussion above mentions several optimizations and their validity for modular compilation of mixin methods, but we don't actually have to mention any mixins to see a conflict. Here's how I interpret the optimization which is applied to self-sends to methods with parameters that are covariant-by-class, by example: abstract class C<X> {
X x;
C(this.x);
void foo(X x);
void bar() => foo(x); // *
} At (*), the receiver However, this line of reasoning is only valid for the given implementation of class D extends C<num> {
void foo(int i) => print(i is int);
} In this example, the type However, checking with DDC, I can see that the above declarations are actually rejected claiming that So we may wish to consider this as a bug in DDC, or we may wish to change the feature specs such that it becomes an error to declare If we modify DDC (including the CFE) then it would need to accept this kind of covariant parameter declaration, and it would need to stop using the self-send optimization, or it would need to generate code accordingly for the relevant methods. E.g., we would generate code for the overriding version of the "unchecked entry point" for Otherwise, we would need to adjust the feature specs to have two different notions of covariant parameters, each with their own associated override rules, such that the self-send optimization is sound. |
from @lrhn email I gather that the language team has decided on the second option, what are the next steps
|
From an email thread, @leafpetersen suggested:
I started implementing that simple suggestion today but I've run out of time. I will finish it tomorrow so we can try it out. |
…ised in dart-lang/sdk#31984" This reverts commit 1c236d5.
I just looked over the PR that @a-siva landed. There is a wrinkle in that there is one example that looks like this: abstract class RenderProxyBoxMixin<T extends RenderBox> extends RenderBox with RenderObjectWithChildMixin<T> I don't really know what this is supposed to mean. I looked at the spec, and it does not, as far as I can tell, cover this. Note that here the superclass constraint is itself a mixin application, and as such there are no subtypes of it (except maybe I've been discussing this with the flutter team to figure out what is intended here (and whether this can be written some other way), and will follow up further. I would suggest that for the time being we interpret this as defining two separate superclass constraints. That is, that this mixin must be applied to something that is a subtype of both Given that interpretation, an implementation in terms of the existing type inference logic is straightforward: just run both subtype matching queries. For the simpler version that @kmillikin and @stereotype441 are proposing to prototype for now, I would suggest that you run the proposed algorithm using both (all) of the superclass constraints (treating each mixin in the superclass as a separate constraint). If all of the either don't constrain the result, or constrain it to the same thing, succeed, else fail. |
It looks like It's probably a good idea to hear what the intention is behind this declaration, rather than guesstimating it. ;-) Nevertheless, two interpretations come to mind: A mixin defined in terms of another mixin and a new body could be an attempt to create a composite mixin (adding both sets of members to any given mixin application); or, it could be an attempt to specify constraints on the superclass (that it must be a subclass of But we shouldn't add a lot of complexity to the rules about how-to-understand-a-class-used-as-a-mixin, given that we are going to have a separate For the situation where it is a constraint on the superclass, wouldn't the developers using it be served equally well if it were expressed as a constraint on any concrete class created on the basis of abstract class RenderProxyBoxMixin<T extends RenderBox>
implements RenderBox, RenderObjectWithChildMixin<T> {...} The only difference I can see between this design and a design where Is that crucial in the given setup? |
The Flutter code used the (spec-incorrect) interpretation that a mixin derived from class M extends B with M1, M2 { ... body ... } must be used on a superclass that implements all of |
Do you mean implements as in |
I mean "implements" as in "is a subtype of". If the superclass that the mixin derived from |
Can somebody post a summary of what was agreed upon so that we are in sync on what to expect. |
My current understanding:
In addition, the current CFE restriction that a class cannot implement multiple versions of the same generic class (e.g., |
I wrote up a quick draft of a feature specification for the feature as I understand it here: https://dart-review.googlesource.com/c/sdk/+/38940 I believe that @kmillikin is doing the CFE implementation. |
@kmillikin may we have an update, please? |
CL is out for review: https://dart-review.googlesource.com/c/sdk/+/40661 |
Infer missing type arguments to generic mixin classes in mixin applications based on mixin supertype constraints. The implementation directly follows the draft feature specification at https://dart-review.googlesource.com/c/sdk/+/38940. Bug: #31984 Change-Id: I7caba037b1b0e73c44f71aaa958f3ff3e8f3c1f0 Reviewed-on: https://dart-review.googlesource.com/40661 Commit-Queue: Kevin Millikin <kmillikin@google.com> Reviewed-by: Paul Berry <paulberry@google.com> Reviewed-by: Leaf Petersen <leafp@google.com>
I have rolled dart into flutter/engine and my local-engine test of flutter gallery with changes flutter/flutter#14392 in flutter/flutter works without any compile time or runtime errors. |
Woohoo! |
I am still getting this: This is despite seemingly having the fix in my flutter install: Is this known to be an outstanding problem? Am I doing something obviously wrong here? Is it perhaps an issue with VS Code analysis integration (cc @DanTup)? |
@kentcb have you set |
@DanTup Thanks very much! No, I was not aware of that. As an aside, it seems to be difficult to track down comprehensive docs on all the analyzer options... |
I only knew about it because someone else asked a few days ago and when I Google'd the message that was the top result 😄 |
@kentcb Sounds strange; could you open an issue in the Dart-Code repo about this? It might not be a bug there, but I can take a look. Please include details of the changes you're making to the file and an analyzer instrumentation file (note: this will include bits of your source code, so ensure there's nothing sensitive). |
@DanTup the same thing happens in IntelliJ, so I guess it's a problem with the analyzer. Anyway, it's no biggie because I won't be modifying the |
From #31975 (comment)
Changing
TickerProviderStateMixin
to extendState<StatefulWidget>
doesn't fix the problem.Looking at generated kernel though, everything looks as expected, and only
#error
points to the error.Assigning to @peter-ahe-google , please reassign if there is somebody else who is better suited to look at this.
The text was updated successfully, but these errors were encountered: