Skip to content

Change more elements of augmenting type declarations to be optional? #3694

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

Closed
eernstg opened this issue Apr 5, 2024 · 4 comments · Fixed by #4003
Closed

Change more elements of augmenting type declarations to be optional? #3694

eernstg opened this issue Apr 5, 2024 · 4 comments · Fixed by #4003
Assignees
Labels
augmentations Issues related to the augmentations proposal. question Further information is requested

Comments

@eernstg
Copy link
Member

eernstg commented Apr 5, 2024

Consider the following program:

// --- Library 'main.dart'.
import augment 'augment.dart';

extension type ET(int i) {}

// --- Augmentation library 'augment.dart'.
augment library 'main.dart';

augment extension type ET(int i) implements int {}

This program could be considered well-formed because the augmenting type declaration (concretely, extension type declaration, but this kind of consideration would apply to classes, mixins, etc. as well) repeats the "header" of the declaration, and it is then able to add new members or augment existing ones by adding declarations to the body.

However, we could also consider the syntax (int i) in the header of the declarations of ET to be a syntactic abbreviation of an instance variable declaration and a constructor declaration, following the nature of the primary constructor proposal. If we adopt this perspective then we would have a conflict because the original declaration of ET declares a final instance variable named i, and so does the augmenting declaration of ET, and that's a conflict even in the case where the two declarations are identical. Similarly for the constructor.

We could solve this problem by adding support for (augment int i), which would mean that the declaration of i in the augmenting declaration of ET is an augmentation of the one in the initial declaration of ET (and it has no effect).

Alternatively, we could allow a declaration like augment extension type ET implements int {} which would correspond to a declaration of ET that does not contribute that final instance variable and constructor declaration, so there's no conflict.

Another example is augmenting enum declarations. We might want to allow those to omit the declarations of values, which would be useful in the case where the augmenting declaration is intended to provide additional instance or static members, but no new values are added, and no augmentation is performed on any of the existing members:

// --- Library 'main2.dart'.
import augment 'augment2.dart';

enum E {one}

// --- Augmentation library 'augment2.dart'.
augment library 'main2.dart';

augment enum E implements int {
  void someInstanceMethod() {}
  static void someStaticMethod() {}
}

This is currently a syntax error because the list of <enumEntry> declarations is required. We could specify a new syntax for an augment enum declaration such that this list is optional.

@lrhn
Copy link
Member

lrhn commented Apr 5, 2024

Allowing omitting values from enums is tricky, because an enum value declaration can look like a function declaration:

enum Baz with Qux {
  // Banana!
  foo(x);
  final int x;
  const Baz(this.x);
}
const x = 42;
mixin Qux {
  int foo(int x) => x;
}

Here the Baz enum was really trying to add a new comment to the inherited foo method, but it ended up looking precisly like declaring a value named foo. (Which will then be invalid because until #1711, or just allowing a static even if inheriting a non-static with the same name, as long as there are not two declarations with the same name in the same scope.)

TL;DR: Making the values of an enum optional makes the grammar ambiguous.

We can allow an empty value list, ended by ;. If there is any non enum-value in the declaration, then there must be a ;,
so the above would be:

enum Baz with Qux {
  ; // No values yet.
  // Banana!
  foo(x);
  final int x;
  const Baz(this.x);
}

Then we'll make it a compile-time error if a fully augmented enum declaration has no enum values.
(Or not, although it is a pretty useless class that has no instance and cannot be subclassed. If you just want a scope for static members, use extension ScopeName on Never {}!)

For extension types, I'd choose to not be too strict.
We can allow augmentations to omit the representation declaration. That's safe.
I wouldn't complain if an augmentation repeats the same representation declaration, that would just needlessly punish people who want to be explicit. If it's the same declaration (exact same type, not just Norm equivalent, and same identifier), then there is no conflict, and they simply both define the same representation declaration.

If we don't introduce the representation getter until we have the fully augmented declaration, then there is no problem with adding it more than once. (And we need the fully augmented declaration to check for conflicts anyway.)

That will not allow an extension type to keep the representation declaration abstract, omitting it in the initial declaration, and have the representation type and name declared by an augmentation.
We can allow that too:

  • Allow an extension type declaration to omit the representation declaration.
  • Allow an extension type augmentation to add a representation type declaration, if there is none already. (Or repeat an existing one as documentation).
  • Make it a compile-time error if a fully augmented extension type "declaration stack" does not introduce a representation type declaration.
  • Maybe allow a declaration to omit one of the name or type from the representation declaration, and allow a later augmentation to fill in the blank (and repeat the other part).
    • That's trickier, because extension type Foo(bar) doesn't say whether bar is the type or the name. Maybe we could allow bar _ as a place-holder where we haven't given it a name yet.

@davidmorgan
Copy link
Contributor

I think this is related to #3879 Consider whether to loosen "must match the original" in augmentation spec.

@eernstg
Copy link
Member Author

eernstg commented Jun 28, 2024

Yes, this issue is basically a special case of the topic in #3879.

@jakemac53
Copy link
Contributor

jakemac53 commented Jun 28, 2024

  • Allow an extension type declaration to omit the representation declaration.

Sounds fine to me.

  • Allow an extension type augmentation to add a representation type declaration, if there is none already. (Or repeat an existing one as documentation).

Maybe, I don't see an issue with it at least, although I am not sure of the use case.

  • Make it a compile-time error if a fully augmented extension type "declaration stack" does not introduce a representation type declaration.

Agreed

  • Maybe allow a declaration to omit one of the name or type from the representation declaration, and allow a later augmentation to fill in the blank (and repeat the other part).

I would allow inheriting the type possibly, but not filling it in. We don't allow augmentations of functions to fill in missing types on parameters, and this sounds the same as that.

  • That's trickier, because extension type Foo(bar) doesn't say whether bar is the type or the name. Maybe we could allow bar _ as a place-holder where we haven't given it a name yet.

I just wouldn't allow this, I don't see a need and the same argument regarding function augmentations applies.

@jakemac53 jakemac53 self-assigned this Jul 31, 2024
@jakemac53 jakemac53 moved this from Todo to In Progress in Static Metaprogramming - design/prototype Jul 31, 2024
jakemac53 added a commit that referenced this issue Aug 1, 2024
Closes #3694.

I went for the simplest answer here, since I don't see a motivating use case for anything more complex.

- The "original" extension type declaration _must_ include the representation type.
- Augmentations of extension types _must not_ include the representation type.

There is a TODO for updating the grammar, I think the simplest option is to make the representation type optional for both regular extension type declarations and augmenting ones, and have the error introduced at a later stage. This would have better behavior for tooling so that the file can still be parsed (ie: the formatter can still format in the face of this error). But, that is just a suggestion and I don't feel strongly if we would prefer to make the grammar more complex and have special rules for each.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
augmentations Issues related to the augmentations proposal. question Further information is requested
Projects
Development

Successfully merging a pull request may close this issue.

4 participants