Skip to content

Rethink restrictions around redundant implementations and mixinsΒ #4542

@munificent

Description

@munificent

Working on augmentations raises some questions around which compile errors are defined on a single syntactic declaration or the resulting semantic declaration after all augmentations are merged. For most errors, the distinction doesn't matter. But digging into this has revealed some corners of the language that feel inconsistent and somewhat arbitrary. So this issue to see if we want to make some changes to the language to be more consistent.

abstract class I {}

class C implements I, I {}

Today, this is an error:

Error: 'I' can only be implemented once.

However, this is fine:

abstract class I {}

class B implements I
class C extends B implements I {}

So you can implement the same interface twice if you go through a level of indirection. That makes sense because it's harmless to do so and would otherwise mean that adding an implements clause to a class could be a breaking change if a subclass already implemented the same interface.

Also, this is fine:

mixin M {}

class C with M, M {}

I suppose there is maybe an argument that allowing applying the same mixin multiple times is potentially useful for a mixin with certain carefully crafted methods and uses of super().

This is also allowed:

mixin class M {}

class C extends M with M {}

But this is an error:

class B {}

class C extends B implements B {}

Yields:

Error: 'B' can't be used in both 'extends' and 'implements' clauses.

But again a layer of indirection permits it:

class B {}

class C extends B {}

class D extends C implements B {}

No error here.

Most curiously, this is allowed:

mixin M {}

class C with M implements M {}

I can think of absolutely no reason to allow this while disallowing extends M implements C. I assume it was just an oversight.

With augmentations

Adding in augmentations complicates this. Consider:

abstract class I {}

class C implements I {}

augment class C implements I {}

Is that an error?

  • If not, then we'll have to explicitly specify that redundant interfaces in implements are restricted to a single syntactic declaration before augmentations are applied. But that may be hard because augmentations are generally applied early in the compilation process before resolution and type checking has happened.

  • If it is an error, then a code generator has to be careful to omit a redundant implements clause if the hand-authored class happens to already specify implementing the same interface.

No redundant implements errors

I think a better approach is to eliminate these errors completely. So these classes would all be valid:

mixin class M {}

class C implements M, M {}
class D extends M implements M {}
class E with M implements M {}

The third one is already valid. This would make the first two consistent.

Yes, it means you can write something that's a little redundant and pointless. But... we have empty statements in the language. You can often write code that doesn't do anything. Making this specific flavor of redundancy an error seems to carry little positive value and makes augmentations more complicated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    augmentationsIssues related to the augmentations proposal.featureProposed language feature that solves one or more problemsfeature-completenessA special or edge case of another feature which isn't supported

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions