Language: Allow using a class with an empty const constructor as a mixin

Created on 6 Jul 2020  路  7Comments  路  Source: dart-lang/language

If a class does not define any constructor, no class which extends it may define a const constructor, however adding an empty const constructor prevents using that class as a mixin. It is therefore a breaking change to add a const constructor, and it is not possible to write a class which satisfies both use cases.

request

Most helpful comment

On a similar note, I find it surprising that a class cannot declare a const constructor if a mixin has an abstract property

mixin Mixin {
  int get abstract;
}

class Class with Mixin {
  const Class();

  @override
  final int abstract = 42;
}

This could be a separate way of solving the problem described here

All 7 comments

The concrete use case in which this came up was we wanted to add a const constructor to Mockito's Fake class (as part of moving it into package:test), but we couldn't because of existing code that used Fake as a mixin.

On a similar note, I find it surprising that a class cannot declare a const constructor if a mixin has an abstract property

mixin Mixin {
  int get abstract;
}

class Class with Mixin {
  const Class();

  @override
  final int abstract = 42;
}

This could be a separate way of solving the problem described here

We don't particularly want to promote using class declarations as mixins, so I'd be wary about adding features just to make that more useful.
I'd expect that feature to go away with Dart 3.0 (if that ever happens)

Declare your mixins with mixin, and then change extends Fake to with Fake, and you will get a const constructor forwarding to the Object const constructor.

@rrousselGit That's a bug, it should work. Since https://github.com/dart-lang/sdk/issues/37810 is closed, maybe it does work now on tip-of-tree.

The relevant rules are:

  • You can derive a mixin from a class declaration if the class declaration:

    • Has Object as superclass.

    • Declares no generative constructors.

  • A mixin application gets a forwarding constructor for each accessible generative constructor in its superclass.
  • A mixin application forwarding constructor is const if:

    • the corresponding superclass constructor is const, and

    • the mixin declares no instance variables (aka "fields").

If the behavior you see does not match this, it's time to file a bug (or recognize that someone already did).

And now, an ugly workaround: Declare your class as Object with Mixin, then it gets a constructor, but doesn't declare a constructor. Since Object has a const constructor, the constructor will even be const, and the mixin application class satisfies the rules for being used as a mixin (Object as superclass, declares no constructors).

mixin _Fake { 
   int fakeIt() => 42;
}
class Fake = Object with _Fake;  // Has a const constructor, does not *declare* a constructor.

class Base {
  const Base();
}

class Sub extends Base with Fake {
  const Sub();
}

class Sub2 extends Fake {
  const Sub2();
}

void main() {
  print(const Sub().fakeIt());
  print(const Sub2().fakeIt());
}

This runs in dart2js in the current dartpad.dev.
The analyzer in DartPad gives an error, but tip of tree doesn't (it was in the wrong place anyway, so I guess they fixed the bug).

I'm not absolutely sure this should work, but it seems safe.
The only constructor you can get this way is one which does nothing and forwards to Object(), which behaves exactly the same as a default constructor, and we allow that one.

We don't particularly want to promote using class declarations as mixins, so I'd be wary about adding features just to make that more useful.

+1. I don't find the Fake case here that compelling. I think it's a simpler mental model to just say use mixin for mixins and class for class and don't cross the streams.

I think it's a simpler mental model to just say use mixin for mixins and class for class and don't cross the streams.

That's fine for _new_ designs and usages - the goal here was to allow smoothly upgrading existing designs. Since some code _did_ use this class as a mixin, now we _can't_ add a const constructor to allow new usages.

I guess we can add "adding an empty const constructor" to the list of changes that _seem_ like they should be non-breaking, but may be breaking in practice depending on usage.

The real issue here is that it's still possible to use classes as mixins without their permission, and that there is no easy migration path off a class that is being used as both.

The offered migration path is that you convert the class to a mixin, and change all extends of the class to with.
That's a breaking change.

There have been requests that we allow extends Mixin to mean extends Object with Mixin, at least temporarily.
Then you can change your class Mixin to mixin Mixin (it already declares no generative constructors and has Object as superclass), and client code will keep working, whether it uses extends or with.

We can then report the extends Mixin as deprecated (immediately or a little later) to force gradual migration to with Mixin.
At the same time we can deprecate with Class, forcing people to change to declaring it as a mixin if they intend it to be used as one.

We can also just add the deprecations without any help, but we've recommended not using classes as mixins since 2.0 and nothing has really happened.

I think that without an easier migration path, the migration off mixin classes won't happen by itself. If we want them gone (and we do, because, for example, they make it a breaking change to add a generative constructor to the class, and it's outside the control of the class author whether someone does use it as a mixin, unless they defensively add a constructor).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dev-aentgs picture dev-aentgs  路  3Comments

eernstg picture eernstg  路  5Comments

kevmoo picture kevmoo  路  3Comments

Cat-sushi picture Cat-sushi  路  3Comments

creativecreatorormaybenot picture creativecreatorormaybenot  路  3Comments