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

Refactor for turning typedef into extension type #59897

Open
FMorschel opened this issue Jan 13, 2025 · 3 comments
Open

Refactor for turning typedef into extension type #59897

FMorschel opened this issue Jan 13, 2025 · 3 comments
Labels
analyzer-refactoring analyzer-server area-analyzer Use area-analyzer for Dart analyzer issues, including the analysis server and code completion. P3 A lower priority bug or feature request

Comments

@FMorschel
Copy link
Contributor

In my company and personal projects I've seen typedefs for things like List<SomeType> and other types with generics. Some of the time they come with extensions for helper methods.

I'd like to request a refactor that would convert a typedef into an extension type so that in cases like this we can move the extension declarations over to the new extension type to increase code clarity and enhance type safety by consolidating related functionality into a unified structure.

For most types we could add implements Type but we'd need to be careful with Records and Functions since that is not possible - Never too even though I doubt someone has ever used typedef with it (dart-lang/language#3839).

Asking for a refactor and not an assist since we'd probably need to add constructors around existing implicit assignments. E.g:

typedef Id = String;

Id getId() => 'foo';

Id otherId() => 1 == 1 ? 'bar' : getId();

Would become:

extension type Id(String inner) implements String {}

Id getId() => Id('foo');

Id otherId() => Id(1 == 1 ? 'bar' : getId());

Not sure about the placement here but that would be a discussion here.


Somewhat related:

@FMorschel FMorschel added the area-analyzer Use area-analyzer for Dart analyzer issues, including the analysis server and code completion. label Jan 13, 2025
@scheglov
Copy link
Contributor

Only InterfaceType is allowed as a superinterface of an extension type, see TypeSystemImpl.isValidExtensionTypeSuperinterface(). So, yes, you cannot use FunctionType or RecordType.

Theoretically it could be made work for FunctionType, just don't implement it.

extension type MyBuilder(void Function() it) {
  void build() {
    it();
  }
}

void f(MyBuilder builder) {
  builder.build();
}

Finding all places to invoke the constructor seems straightforward, all places where the target has DartType.alias associated with the old typedef.

@FMorschel
Copy link
Contributor Author

Theoretically it could be made work for FunctionType, just don't implement it.

I'm aware, but that would make the second part (placing the constructor) do more work and it might not give as much value since, at least with the use-case, we usually don't create extensions on FunctionType (at least I never did and expect few uses of this).

@scheglov scheglov added P3 A lower priority bug or feature request analyzer-server analyzer-refactoring labels Jan 13, 2025
@eernstg
Copy link
Member

eernstg commented Jan 14, 2025

This is not an actionable comment because we don't have this feature (yet), but...

I can't help mentioning that this would be a good fit for implicit constructors:

typedef Id = String;

extension on Id {
  ... // Members.
}

Id getId() => 'foo';
Id otherId() => 1 == 1 ? 'bar' : getId();

would then become

extension type Id implements String {
  implicit Id(String this.it);
  ... // Members.
}

Id getId() => 'foo';
Id otherId() => 1 == 1 ? 'bar' : getId();

The point is that we don't need to call the extension type constructor when this feature is available and the context type is the extension type—'foo' and 'bar' will implicitly be turned into Id('foo') and Id('bar') because the context type is Id.

Note that the implicit constructor allows expressions to enter the extension type (that is, to get the static type Id) implicitly, and implements String allows them to exit the extension type implicitly again (that is, getting the type String). In other words, there's no friction.

If you're using the approach as described in this issue, and implicit constructors are added to the language at some point later, then nothing will break, but code written from that point will be able to omit a lot of extension type constructor invocations, and it may or may not be worthwhile to remove them from existing code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
analyzer-refactoring analyzer-server area-analyzer Use area-analyzer for Dart analyzer issues, including the analysis server and code completion. P3 A lower priority bug or feature request
Projects
None yet
Development

No branches or pull requests

3 participants