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

Consider allowing extension types to implement non super type #54295

Closed
Zekfad opened this issue Dec 9, 2023 · 3 comments
Closed

Consider allowing extension types to implement non super type #54295

Zekfad opened this issue Dec 9, 2023 · 3 comments

Comments

@Zekfad
Copy link

Zekfad commented Dec 9, 2023

Currently extension types can only "implement" super type of representation or representation type itself.
This renders example at https://github.com/dart-lang/sdk#2727 as invalid.

I think this restricts a powerful tool to just a way to hide members from a type, other than that it seems more like a simple extension.

Most obvious usage if restriction would be lifted is to use sealed hierarchy to achieve compile-only union of different types with full static analysis rather than dynamic.

@lrhn
Copy link
Member

lrhn commented Dec 9, 2023

If extension types are compile time only, then they cannot soundly be allowed to implement any interface that their representation object doesn't implement.

Extension types are not able to implement union types, because they are erased at runtime, and there are no union types they can be erased to which are still sound.

@eernstg
Copy link
Member

eernstg commented Dec 9, 2023

Agreeing with @lrhn, I'd like to add a little bit of background information.

@Zekfad wrote:

Currently extension types can only "implement" super type of representation or representation type itself.
This renders example at https://github.com/dart-lang/sdk#2727 as invalid.

I guess you meant dart-lang/language#2727?

Thanks for catching that! The IdNumber example in dart-lang/language#2727 was indeed obsolete because the relevant rules have changed (several times ;-) since this example was written. I've just updated it such that it is again working code.

A clause like implements T in those older proposals did support values of T that weren't supertypes of the representation type. For example, we could have IdNumber ... implements Comparable<IdNumber>. That is no longer possible, because the representation type int isn't a subtype of Comparable<IdNumber> (and it is not even a subtype of the erasure Comparable<int>).

The crucial difference between the old proposals where implements Comparable<IdNumber> was allowed and the current specification where it is an error is that we have a subtype relationship in the current specification. The older proposals did not give rise to that subtype relationship, and this means that you wouldn't be able to do things like this in the old proposals:

// According to old rules.

void main() {
  IdNumber id = ...;
  Comparable<IdNumber> comp = id; // Compile-time error!
}

In the old proposals, implements was simply used to require that a bunch of members exist and have the expected signature. In the current specification, we actually have a subtype relationship:

// Modified example, using current rules.

extension type IdNumber(int i) implements Comparable<num> {
  bool operator <(IdNumber other) => i < other.i;
  int compareTo(IdNumber other) => i - other.i;
  bool verify({required int age}) => true; // TODO: Implement.
}

void main() {
  IdNumber id = ...;
  Comparable<num> comp = id; // OK!
}

Note that implements T creates a subtype relationship in class declarations, mixin declarations, enum declarations, etc., and we're just returning to normal business by using the new (and stricter) rules that allow us to have this subtype relationship for extension types as well. Here's the reason why we must use stricter rules:

When there is an actual subtype relationship, it must be sound to have a situation where the value of a variable like comp is the object denoted by id. But at run time the object denoted by id is an int (that's the main motivation for having extension types in the first place: the apparent wrapper object does not exist). The subtype relationship int <: Comparable<num> holds, so it's perfectly fine to specify that we also have the subtype relationship IdNumber <: Comparable<num>.

In the current specification, extension type E2 ... implements ... E1 ... has a completely different meaning when E1 is another extension type. This again creates a subtype relationship, but the main purpose of that kind of declaration is that it allows E2 to "inherit" the members of E1 that E2 doesn't redeclare (we say "redeclare" when E2 declares a member whose name is also the name of a member of E1, or some other superinterface of E2).

So, lots of things happened with implements in extension type declarations over time, and given that we want to maintain the subtype relationship from an extension type to each of its superinterfaces, there's no way we could allow a non-extension type superinterface if it isn't sound to have that subtype relationship, and that property is ensured by insisting that it must be a supertype of the representation type.

@mraleph
Copy link
Member

mraleph commented Dec 11, 2023

I am going to close this given @lrhn and @eernstg answers.

@mraleph mraleph closed this as completed Dec 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants