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

Macros: Allow anonymous classes/functions for the sake of being used by generated code #3817

Open
rrousselGit opened this issue May 17, 2024 · 10 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@rrousselGit
Copy link

Hello!

With macros coming around, it's likely going to be common for folks to define a class/function that is never meant to be used directly.
Take the idea of a macro that generates a Widget from a function:

@Functional()
Widget example(...) => ...;

In this scneario, we never want to use the example function. Rather, we'll want to use the generated Example class.
As such, this is both error prone and pollutes the public API.

Proposal: Allow functions/classes to be unnamed, and referenced by macros

We could allow functions to not have a name.
Then, rather than macro rely on the function name to generate the class name, we could pass the desired named as input to the macro:

@Functional(Example) // Using an Identifier/Code argument
Widget (...) => ...;

This way, the function is no-longer directly usable. Users of the macro will have to use it through the generated Example class.

@rrousselGit rrousselGit added the feature Proposed language feature that solves one or more problems label May 17, 2024
@jakemac53
Copy link
Contributor

Why not just make the function private? Macro code doesn't have capabilities not available to user code - so there needs to be some identifier for this function that a macro, or any other code in the library, can refer to.

@rrousselGit
Copy link
Author

Making the function private isn't enough.

  • Folks may still use it. That happens regularly with functional_widget users
  • The syntax gets weird when folks want to generate a private class.

Users may want to generate a public widget, or a private widget.

At the moment, the way I've done it is require users to use _ twice:

@widget
Widget _foo(...); // generates Foo

@widget
Widget __foo(..); // Generates _Foo

But that's super awkward IMO.

@jakemac53
Copy link
Contributor

jakemac53 commented May 17, 2024

  • Folks may still use it. That happens regularly with functional_widget users

I could imagine an annotation + lint that ensures user code doesn't use a member, even within the same library? The macro could add the annotation (augmentations do allow this, even if we don't have an API for it today in macros).

You could even just add @deprecated or something to these functions, it is kinda hacky, but would send the right signal.

At the moment, the way I've done it is require users to use _ twice

That is pretty awkward for sure, I could imagine a private boolean option to the macro but that is more verbose. Or you allow users to specify the name explicitly similar to the example above, but this gets repetitive too. So, __ is probably the least bad option today.

@rrousselGit
Copy link
Author

rrousselGit commented May 17, 2024

There are possible workarounds, of course. But I think they are less ideal overall.
It's likely going to be very common, so I think it's worth considering a more official solution.

There's also the case of renaming.
With the current workarounds, renaming Example wouldn't work and we'd have to rename example ; which requires going to the widget definition.

With such an approach, renaming Example could work by default, as there's only a single identifier defined.

@jakemac53
Copy link
Contributor

jakemac53 commented May 17, 2024

With such an approach, renaming Example could work by default, as there's only a single identifier defined.

Interesting, so would do initiate the rename from the Identifier in the macro annotation @Widget(Example /* rename by right clicking on this */)? We have a separate issue filed about renaming/refactoring, but this is an interesting idea.

@rrousselGit
Copy link
Author

Indeed. There's also the case where the renaming is performed in a different place:

@Macro(Example)
Widget () => ...;

...

@override
Widget build(context) {
  // We could rename `Example` here, and it would rename `@Macro(Example)` accordingly,
  // as this is the same identifier.
  return Example(...); 
}

@rrousselGit
Copy link
Author

rrousselGit commented May 17, 2024

An alternative syntax could be:

@Macro(Example, () {
  return Container();
});

But we'd need to support declaring macros that's not attached to an element.

Although the syntax is a bit less pleasing to the eye IMO. And I think it'd be valuable to be able to define return values ; which closures lack.

@rrousselGit
Copy link
Author

Going back to this, this could solve another problem with macro composition:

Users will regularly want to apply macros on the generated identifier. Take this functional_widget issue as example: rrousselGit/functional_widget#119
(Freezed has a similar problem too)

When users define:

@stateless
Widget fn(context) => ...

Some may want to generate:

@MyMacro()
class Fn extends StatelessWidget {...}

Using this proposal, we could consider supporting specifying annotations on Identifier parameters:

@Stateless(
  @MyMacro()
  Example
)
Widget () => ...;

The Stateless macro would then paste the annotations placed on Example onto the generated class Example.

@arkare
Copy link

arkare commented May 24, 2024

Why not:

// @MyOtherMacro()
@Stateless()
Widget Example() => Container(...);

The top-level function Example could be transformed into a class and the function body transported to the build method.

  • Renaming will work since the final/generated class is the actual user provided function.

  • This solves the need to apply macros to the generated class because the function itself is the class, so just apply the nested macro in the right order.

So instead of changing the lexical structure of the language (which IMHO seems awkward to change) to fulfill this need, we could support transforming functions to classes with macros (I don't know if this is possible though, just throwing ideas).

@rrousselGit
Copy link
Author

I suggested that before. But there's quite a bit of pushback against the idea of changing the prototype of objects in breaking way.

For example, I wanted macros to be able to add new parameters to functions, instead of having to generate a function. But it's pretty clear the team doesn't want that :P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

3 participants