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

Frontend complains about non-matching type from mixin and base class #31984

Closed
aam opened this issue Jan 26, 2018 · 49 comments
Closed

Frontend complains about non-matching type from mixin and base class #31984

aam opened this issue Jan 26, 2018 · 49 comments
Assignees
Labels
legacy-area-front-end Legacy: Use area-dart-model instead. P0 A serious issue requiring immediate resolution

Comments

@aam
Copy link
Contributor

aam commented Jan 26, 2018

From #31975 (comment)

╰─➤  cat mixin.dart                                                                                                                                                                                          
class StatefulWidget {}

abstract class State<T extends StatefulWidget> {
  T get widget => _widget;
  T _widget;
}

class _AnimatedCrossFadeState extends State<AnimatedCrossFade> with TickerProviderStateMixin {}

class AnimatedCrossFade extends StatefulWidget {}

abstract class TickerProvider {}

class TickerProviderStateMixin extends State<dynamic> implements TickerProvider {
  factory TickerProviderStateMixin._() => null;
}

void main() {}%                                                                                                                                                                                                    ╰─➤  $DH/sdk/pkg/vm/tool/dart2 mixin.dart

mixin.dart: Error: The parameter '_' of the method '_AnimatedCrossFadeState::_widget' has type #lib1::AnimatedCrossFade, which does not match the corresponding type in the overridden method (dynamic).
Change to a supertype of dynamic (or, for a covariant parameter, a subtype).
mixin.dart:15:7: Error: This is the overriden method ('_widget').
class TickerProviderStateMixin extends State<dynamic> implements TickerProvider {
      ^
mixin.dart: Error: The parameter '_' of the method '_AnimatedCrossFadeState::_widget' has type #lib1::AnimatedCrossFade, which does not match the corresponding type in the overridden method (dynamic).
Change to a supertype of dynamic (or, for a covariant parameter, a subtype).
mixin.dart:15:7: Error: This is the overriden method ('_widget').
class TickerProviderStateMixin extends State<dynamic> implements TickerProvider {
      ^

Changing TickerProviderStateMixin to extend State<StatefulWidget> doesn't fix the problem.

Looking at generated kernel though, everything looks as expected, and only #error points to the error.

library;
import self as self;
import "dart:core" as core;

class StatefulWidget extends core::Object {
  synthetic constructor •() → void
    : super core::Object::•()
    ;
}
abstract class State<T extends self::StatefulWidget> extends core::Object {
  generic-covariant-impl generic-covariant-interface field self::State::T _widget = null;
  synthetic constructor •() → void
    : super core::Object::•()
    ;
  get widget() → self::State::T
    return this.{self::State::_widget};
}
abstract class _State&TickerProviderStateMixin^#U0^<#U0 extends core::Object> extends self::State<self::_State&TickerProviderStateMixin^#U0^::#U0> implements self::TickerProviderStateMixin {
  synthetic constructor •() → void
    : super self::State::•()
    ;
  static factory _() → self::TickerProviderStateMixin
    return null;
}
class _AnimatedCrossFadeState extends self::_State&TickerProviderStateMixin^#U0^<self::AnimatedCrossFade> {
  synthetic constructor •() → void
    : super self::State::•()
    ;
  abstract forwarding-stub set _widget(generic-covariant-impl self::AnimatedCrossFade _) → void;
}
class AnimatedCrossFade extends self::StatefulWidget {
  synthetic constructor •() → void
    : super self::StatefulWidget::•()
    ;
}
abstract class TickerProvider extends core::Object {
  synthetic constructor •() → void
    : super core::Object::•()
    ;
}
class TickerProviderStateMixin extends self::State<self::StatefulWidget> implements self::TickerProvider {
  static factory _() → self::TickerProviderStateMixin
    return null;
  abstract forwarding-stub set _widget(generic-covariant-impl self::StatefulWidget _) → void;
}
static const field dynamic #errors = const <dynamic>["mixin.dart: \u0027[31mError: The parameter '_' of the method '_AnimatedCrossFadeState::_widget' has type #lib1::AnimatedCrossFade, which does not match the corresponding type in the overridden method (#lib1::StatefulWidget).\nChange to a supertype of #lib1::StatefulWidget (or, for a covariant parameter, a subtype).\u0027[39;49m"]/* from null */;
static method main() → void {}

Assigning to @peter-ahe-google , please reassign if there is somebody else who is better suited to look at this.

@mraleph mraleph added the P1 A high priority bug; for example, a single project is unusable or has many test failures label Jan 26, 2018
@mraleph
Copy link
Member

mraleph commented Jan 26, 2018

If I understand correctly the error is reported because _AnimatedCrossFadeState implements self::TickerProviderStateMixin which implements self::State<self::StatefulWidget> which declares member set _widget(self::StatefulWidget _) [there is also a forwarding stub with such signature in TickerProviderStateMixin].

At the same time _AnimatedCrossFadeState extends self::State<self:: AnimatedCrossFade> and gets set _widget(self::AnimatedCrossFade _) as a result.

Overriding set _widget(self::StatefulWidget _) with set _widget(self::AnimatedCrossFade _) is incorrect.

However in reality we are override set _widget(self::StatefulWidget _) with abstract forwarding-stub set _widget(generic-covariant-impl self::AnimatedCrossFade _) → void; so I am not sure that any soundness properties are violated.

/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.

@leafpetersen
Copy link
Member

Presumably this code:

class TickerProviderStateMixin extends State<dynamic> implements TickerProvider {
  factory TickerProviderStateMixin._() => null;
}

Should have extends State<StatefulWidget> instead of extends State<dynamic>, since otherwise their are other issues.

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...

  1. I believe the general principle is that we check the concrete method implementations against all explicit super-interfaces. As such, we need to check the implied setter for _widget against all super-interfaces.
  • I believe that the concrete setter has type AnimatedCrossFade -> void
  1. The question is whether or not State<StatefulWidget> should be considered an explicit super-interface of _AnimatedCrossFade or not.
  • If so, then we need to check against the inherited signature from there, which is StatefulWidget -> void, and this is not a valid override.
  • If not, then it's ok

If I think about this in terms of the eventual explicit mixin syntax that @lrhn has proposed as the replacement for super-mixins, then it's not clear to me that we should think of State<StatefulWidget> as an explicit super-interface of _AnimatedCrossFadeState. It's really just a constraint on the super-class to which the mixin gets applied. But in the current system in which TickerProviderStateMixin is also a class/interface, this is a bit odd, since it is an explicit super-interface of that class, and I would expect a transitivity property here.

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.

@mraleph
Copy link
Member

mraleph commented Jan 29, 2018

@leafpetersen the most confusing part here is that _AnimatedCrossFadeState does not actually override the setter itself. Implementation is coming from State<AnimatedCrossFade>, while "interface" is coming from State<StatefulWidget>.

Our generics are covariant (an instance of type State<AnimatedCrossFade> then such an instance is also an instance of type State<StatefulWidget>) so in this context it seems completely in line to accept the override - the setter would be checking inputs against T=AnimatedCrossFade anyways due to covariance.

/fyi @stereotype441

@mraleph
Copy link
Member

mraleph commented Jan 29, 2018

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).

@stereotype441
Copy link
Member

@leafpetersen the most confusing part here is that _AnimatedCrossFadeState does not actually override the setter itself. Implementation is coming from State<AnimatedCrossFade>, while "interface" is coming from State<StatefulWidget>.

Our generics are covariant (an instance of type State<AnimatedCrossFade> then such an instance is also an instance of type State<StatefulWidget>) so in this context it seems completely in line to accept the override - the setter would be checking inputs against T=AnimatedCrossFade anyways due to covariance.

@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 covariant keyword from covariance that arises from covariance of class generics; the latter is more limited, and makes it possible to identify call sites that can skip the check (assuming the back end supports doing so). I think. For example, assuming TickerProviderStateMixin is declared like this:

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: TickerProviderStateMixin extends State<StatefulWidget> means something stronger than TickerProviderStateMixin <: State<StatefulWidget>. It means that whatever concrete implementation of the _widget setter is reached at runtime, its declaration must have satisfied the interface contract of State<StatefulWidget>, which means that the static type of the setter must take the form (T) -> void, where StatefulWidget <: T. Therefore, if the _widget setter performs a covariance check solely because of class generics, that check is guaranteed to succeed (since s has static type StatefulWidget). So the front end generates metadata indicating to the back-end that in the call from f to the _widget setter, covariance checks that arise from class generics can be safely skipped.

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 this. I think that one might have to be removed too. I don't know whether the VM implements that one or not, but I know that DDC does in some circumstances, and I believe that removing it would cause a big performance hit for them.

I'll keep thinking about this and try to have a more airtight analysis of the situation in a few days.

@a-siva
Copy link
Contributor

a-siva commented Jan 29, 2018

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.

@leafpetersen
Copy link
Member

The front end engages in a reasoning process that looks something like this: TickerProviderStateMixin extends State means something stronger than TickerProviderStateMixin <: State.

Presumably this isn't specific to extends right? That is, the reasoning should only depend on State<StatefulWidget> being among the named interfaces that the concrete implementations get match against (since presumably whatever object you get for m could only be implementing TickerProviderStateMixin anyway).

So the front end generates metadata indicating to the back-end that in the call from f to the _widget setter, covariance checks that arise from class generics can be safely skipped.

If we choose to accept the override, we will have to remove this optimization.

If I understand what you're saying correctly, treating State<StatefulWidget> as a superclass constraint rather than a named super-interface disables this optimization. I think this is only true for interfaces which are used as mixins though, yes? If/when we only allow super-mixins using the named mixin syntax, then even in a modular compilation setting you would know which interfaces you could trust and which you couldn't?

For example, there is also an optimization allowing covariance checks that arise from class generics to be skipped when the call is through this. I think that one might have to be removed too.

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.

@leafpetersen
Copy link
Member

Assuming that:

  • We can keep this calls safe for DDC now (since it doesn't support super-mixins)
  • And we believe that we can restore the ability to make this calls safe for code not injected via mixins when we get the new mixin syntax

then I would be comfortable moving ahead with a solution based on treating the extends clause solely as a super-class constraint.

@lrhn thoughts?

@lrhn
Copy link
Member

lrhn commented Jan 30, 2018

The problem here is:

class TickerProviderStateMixin extends State<dynamic> implements TickerProvider { ... }
class A extends State<AnimatedCrossFade> with TickerProviderStateMixin {}

which makes A implement both State<AnimatedCrossFade> and State<dynamic>.

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 implements clause. (Which means that the superclass/implementation chain must be invariant, but it may claim to implement less specific generic types as well).

There is no implements clause here, so we need to look at the meaning of the mixin application.
A mixin application creates a new class, so State<AnimatedCrossFade> with TickerProviderStateMixin is the class to look at (it's the superclass of A, A itself is trivial and shouldn't have any problems).

A mixin application extends its superclass State<AnimatedCrossFade> with the declarations derived from the mixin class TIckerProviderStateMixin, and it implements the interface of the mixin class. We could just agree that that is actually literally what it does, because that would make this a non-problem - it will have an implicit implements clause and then the mixin application class is correct because its superclass has a more specific version of State than the implemented interface provides.

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 TickerProviderStateMixin, you are also a State<dynamic> - can't get one without the other.

(I really would like to get to the point where there is no difference between "T implements Foo<X>" and "T <: Foo<X>" - that is, implementing a generic type is equivalent to implementing all of its supertypes - but that doesn't seem to be within short-term reach).

@mraleph
Copy link
Member

mraleph commented Jan 30, 2018

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 @mixin, which is detected by the front-end and alters how this class is emitted in Kernel and how warning/errors are reported for it?

Given the class like this:

@mixin 
class M extends B<Ts> implements Is {
  // methods
  m() { /* body */ }
}

We actually would emit in Kernel:

abstract class M implements B<Ts>, Is {
  // methods
  m(); // no body! 
}

// If we need to support modular compilation we also would emit
// artificial class to host mixin methods bodies for module compilation 
@internal.MixinBody
class M%Bodies ... {
  //
}

Mixin applications would copy bodies from M%Bodies and declare that they implement M.

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.

@lrhn:

We could also remove the problem by declaring it as:

I have PR doing exactly that and @Hixie is not in favor - because it does make code more verbose. See flutter/flutter#14327 (comment)

@a-siva:

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.

We don't really use any of those optimizations so we are not affected at all.

@stereotype441
Copy link
Member

@leafpetersen

The front end engages in a reasoning process that looks something like this: TickerProviderStateMixin extends State means something stronger than TickerProviderStateMixin <: State.

Presumably this isn't specific to extends right? That is, the reasoning should only depend on State<StatefulWidget> being among the named interfaces that the concrete implementations get match against (since presumably whatever object you get for m could only be implementing TickerProviderStateMixin anyway).

Correct, it applies to extends, implements, and (for now at least) with.

So the front end generates metadata indicating to the back-end that in the call from f to the _widget setter, covariance checks that arise from class generics can be safely skipped.

If we choose to accept the override, we will have to remove this optimization.

If I understand what you're saying correctly, treating State<StatefulWidget> as a superclass constraint rather than a named super-interface disables this optimization.

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 void set _widget(AnimatedCrossFade _) (which comes from State<AnimatedCrossFade>) as a valid implementation of the interface State<StatefulWidget>, due to the fact that the setter parameter is covariant, therefore the implementation must contain a runtime check. I was saying that we can do that only if we eliminate the optimization that allows the runtime check to be skipped sometimes.

For example, there is also an optimization allowing covariance checks that arise from class generics to be skipped when the call is through this. I think that one might have to be removed too.

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.

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 extends clause to be a superclass constraint rather than a named super-interface, then I believe that we would only have to stop trusting this calls in methods copied from the mixin, and ordinary this calls would still be ok. But I think that would require us to start supporting the new mixin syntax immediately, and reject super-mixins that use the old syntax. So I'm not sure this is feasible yet.

@stereotype441
Copy link
Member

@mraleph I don't see how your suggestion of introducing the @mixin annotation addresses the optimization issues. Or were you thinking that we would automatically disable the optimization when a call was made via an interface annotated with @mixin? That might work.

@peter-ahe-google
Copy link
Contributor

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:

class Box<T> {
  T get t => null;
   void set t(T value) {}
}

Now ask the following, is Box<String> a subtype of Box<Object>? The answer is no. So this code should report an error:

Box<String> s = new Box<String>();
Box<Object> o = s;
o.t = 1;

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.

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 StatefulWidget> extends State<T>
    implements TickerProvider {
  factory TickerProviderStateMixin._() => null;
}

void main() {}

@a-siva a-siva added P0 A serious issue requiring immediate resolution and removed P1 A high priority bug; for example, a single project is unusable or has many test failures labels Jan 30, 2018
@leafpetersen
Copy link
Member

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 TickerProviderStateMixin, you are also a State<dynamic> - can't get one without the other.

I'm not sure I understand this. You are always a State<dynamic> because your superclass will always be a State<Something> where Something <: dynamic The only difference is whether State<dynamic> is considered one of the named super-interfaces of the result of the mixin, or whether it is only considered a super-type via covariance.

@Hixie
Copy link
Contributor

Hixie commented Jan 30, 2018

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, dynamic used to be a wildcard that would fit here (in the declaration of M). The language changes seem to have removed this wildcard. This is one example of where this is a problem; there have been others, e.g. the cases where we now have to say <Null> instead of <dynamic>.

@vsmenon
Copy link
Member

vsmenon commented Jan 30, 2018

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.

It's not exactly clear what that means. E.g., you could mean this:

abstract class M<T> extends A<T> {}

or

abstract class M extends A<X> {}

We have to pick one. The latter is the interpretation we pick today in Dart.

@Hixie
Copy link
Contributor

Hixie commented Jan 30, 2018

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 M&A%Y is a compiler-generated class that, to the developer, appears to be M (e.g. is M is true).

@leafpetersen
Copy link
Member

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:

  1. Instantiating M directly is not allowed (it's only a mixin)
  2. the mixin definition of M either induces no implicit interface for M, or else induces an implicit interface which does not include A among its super-interfaces.

Since all instances of M would then arise from mixing it in, x is A will be true whenever x is M is true, but statically you would not be able to do M y = ...; A x = y because M and A are statically unrelated.

The point is that M is really a kind of function from classes to classes. It takes a class that is some kind of A, and produces a class that looks like M. It doesn't really make sense to me to treat it as having an implicit interface, but insofar as it does, it feels like it should only cover the methods that it provides. The A part of its signature isn't part of what it produces: it's part of what it consumes.

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 M to be made generic, but to fill in the missing type arguments at the mixin point via inference. So the code looks like this:

// 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 { }

@Hixie
Copy link
Contributor

Hixie commented Jan 30, 2018

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 M as @optionalTypeArgs.

So both SGTM.

@eernstg
Copy link
Member

eernstg commented Jan 31, 2018

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 this has foo invoked with an argument of type X. Because this code is in the body of that instance and the receiver is this, the argument type of the given declaration of foo is known to be X (rather than \exists Y <: X. Y), and this means that the actual argument with static type X is guaranteed to be appropriate for an invocation of that implementation of foo. So, presumably, we can call an entry point for foo whose body skips the type check on the actual argument. In contrast, we would have to call the checked entry point for situations like C<num> c = ...; num n = ...; c.foo(n);, because c might be a C<double>, and n might be an int, and there must be a dynamic type check that the actual argument has a type which is appropriate for the actual method implementation which is executed.

However, this line of reasoning is only valid for the given implementation of foo (and overriding ones, if similar), and here's an example where it is incorrect to call an entry point that does not check the type of its actual argument (so the invocation at (*) can't be unchecked):

class D extends C<num> {
  void foo(int i) => print(i is int);
}

In this example, the type int of i is acceptable according to the feature specs on covariant parameters, because that parameter is covariant (it's covariant-from-class rather than by means of a covariant modifier, but we have no exceptions preventing a declaration like D.foo).

However, checking with DDC, I can see that the above declarations are actually rejected claiming that D.foo is an incorrect override of C.foo.

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 D.foo like that.

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 D.foo such that it actually checks for int (we do have the guarantee that this entry point is only called when the argument is known to be a num, but we still need to check that it is an int). There could then be a "really unchecked entry point" that doesn't check its argument at all, to be invoked when the argument is statically known to be an int (and that might again by overridden in a subclass by a "really unchecked entry point that checks for Null" in a subclass, etc.). But that's just the kind of set up that we have had all the time for parameters which are covariant due to an explicit covariant modifier, and the applicable techniques should be similar.

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.

@a-siva
Copy link
Contributor

a-siva commented Jan 31, 2018

from @lrhn email I gather that the language team has decided on the second option, what are the next steps

  • changes in FE to do the type inference (who owns this ?)
  • changes to Flutter framework code to make the required classes generic (we could start on this if we all agree on this resolution).

@kmillikin
Copy link

From an email thread, @leafpetersen suggested:

If there are pragmatic concerns about re-using the type inference code, we could even specify this more simply:

Given a mixin with superclass constraint A<T0, ..., Tn>, find the unique A<U0, ..., Un> in the actual superclass (if there isn't a unique one, fail), and set T0, ..., Tn to U0, ..., Un.

I think that does the same thing?

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.

a-siva added a commit to flutter/flutter that referenced this issue Feb 2, 2018
@leafpetersen
Copy link
Member

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 RenderProxyBoxMixin itself), and so this mixin should not be able to be applied to anything non-trivial, at least based on my understanding of super mixins, cross referenced with a quick read through the spec.

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 RenderBox and RenderObjectWithChildMixin<T> (rather than a subtype of the anonymous class that results from mixing in the latter onto the former, of which there are none). This seems most likely to be sound, and might capture the intended user intuition.

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.

@eernstg
Copy link
Member

eernstg commented Feb 2, 2018

It looks like RenderProxyBoxMixin should have a body (to make it useful), so I'll assume it's just omitted.

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 RenderBox, and it must somewhere have mixed in RenderObjectWithChildMixin<T>.

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 mixin construct where all the properties of a mixin can be specified in a way that fits for this purpose. If we are going to preserve support for using a regular class as a mixin, it should be for simple usages only. So I'd suggest that we don't make an attempt to make it denote a composite mixin.

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 RenderProxyBoxMixin<T>?:

abstract class RenderProxyBoxMixin<T extends RenderBox>
   implements RenderBox, RenderObjectWithChildMixin<T> {...}

The only difference I can see between this design and a design where RenderBox with RenderObjectWithChildMixin<T> is considered to be a constraint on the superclass is that certain superinvocations will be known to be possible in the latter case, and not in the former.

Is that crucial in the given setup?

@lrhn
Copy link
Member

lrhn commented Feb 2, 2018

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 B, M1 and M2, and not only on one that implements the anonymous mixin application class B&M1&M2 (which no class except M actually does).
Since we are retaining super mixins only behind a flag for keeping Flutter code running, and only until we have a replacement syntax that can actually specify multiple super-class constraints, we'll keep the incorrect behavior for now. This is all specified in the mixin migration document.

@eernstg
Copy link
Member

eernstg commented Feb 2, 2018

Do you mean implements as in implements, or as in "must have an implementation such that the following superinvocations can safely be made: ... /* concrete methods in M1, M2, maybe also B */"?

@lrhn
Copy link
Member

lrhn commented Feb 2, 2018

I mean "implements" as in "is a subtype of". If the superclass that the mixin derived from M is applied to is not abstract, that also guarantees that super-invocations will work. If the superclass is abstract, we need to check each mixed-in super-invocation individually for whether it's now hitting a valid target.

@kmillikin kmillikin added legacy-area-front-end Legacy: Use area-dart-model instead. and removed area-kernel labels Feb 5, 2018
@a-siva
Copy link
Contributor

a-siva commented Feb 5, 2018

Can somebody post a summary of what was agreed upon so that we are in sync on what to expect.

@vsmenon
Copy link
Member

vsmenon commented Feb 5, 2018

My current understanding:

  • @leafpetersen has proposed a simplified form of inference that fixes the supermixin examples in Flutter, and the team has agreed to go forward with that.
  • @peter-ahe-google will implement in fasta / CFE.
  • @stereotype441 will implement in the analyzer.

In addition, the current CFE restriction that a class cannot implement multiple versions of the same generic class (e.g., A<int> and A<dynamic>) will be enforced in the analyzer as well as part of their Dart 2 back-porting.

@leafpetersen
Copy link
Member

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.

@mit-mit
Copy link
Member

mit-mit commented Feb 12, 2018

@kmillikin may we have an update, please?

@kmillikin
Copy link

CL is out for review: https://dart-review.googlesource.com/c/sdk/+/40661

dart-bot pushed a commit that referenced this issue Feb 14, 2018
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>
@a-siva
Copy link
Contributor

a-siva commented Feb 15, 2018

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.

@a-siva a-siva closed this as completed Feb 15, 2018
@leafpetersen
Copy link
Member

Woohoo!

@kentcb
Copy link

kentcb commented May 24, 2018

I am still getting this:

image

This is despite seemingly having the fix in my flutter install:

image

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)?

@DanTup
Copy link
Collaborator

DanTup commented May 24, 2018

@kentcb have you set enableSuperMixins? See flutter/flutter#14317 (comment)

@kentcb
Copy link

kentcb commented May 24, 2018

@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...

@DanTup
Copy link
Collaborator

DanTup commented May 24, 2018

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
Copy link

kentcb commented May 24, 2018

😄

OK, so there's still a problem here (perhaps in VS Code, but not sure). As soon as I modify analysis_options.yaml and save it, these errors (and only these errors) pop up again:

image

If I exit Code and start again, they're gone again until I modify the analysis options again.

@DanTup
Copy link
Collaborator

DanTup commented May 24, 2018

@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).

@kentcb
Copy link

kentcb commented May 25, 2018

@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 analysis_options.yaml very often (I am at the moment because I'm just setting it up).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
legacy-area-front-end Legacy: Use area-dart-model instead. P0 A serious issue requiring immediate resolution
Projects
None yet
Development

No branches or pull requests