language icon indicating copy to clipboard operation
language copied to clipboard

Create Static Enough Metaprogramming proposal

Open mraleph opened this issue 7 months ago • 2 comments

This moves content from https://github.com/dart-lang/language/issues/4271 into a markdown file in the repository to make discussion and revisions easier.

I have incorporated some of the feedback from discussions on the issue - but I continue to maintain focus on this as a toolchain feature. I have added some remarks that analyzer can't constant fold everything anyway because it does not have access to the compilation environment.

I would like to collect a few rounds of feedback and then rejuvenate the prototype implementation to get something experimental working across all platforms in the SDK so that we can have an idea of how well this could work in a real world.

mraleph avatar May 14 '25 11:05 mraleph

I have made some updates based on comments. PTAL.

mraleph avatar May 16 '25 12:05 mraleph

I also find it confusing to see what function calls (or other syntax) is part of the generated code vs. compile-time evaluation. For example, if I change the toJson example from the proposal to this:

Map<String, Object?> toJson<@konst T>(T value) => {
    for (@konst final field in TypeInfo.of<T>().fields)
      if (!field.isStatic) processFieldName(field.name): processFieldValue(field.getFrom(value)),
  };

(processFieldName and processFieldValue are new)

Just by looking at this code (without seeing processFieldName and processFieldValue definitions) I can't tell whether these function calls are evaluated in compile time or runtime.

I wonder if we could make stages explicit. There are many languages that do this that we could look for inspiration. For example Common Lisp's backquote and comma, MetaOCaml's (.< expr >.) and escape (.~expr) make the stages explicit.

osa1 avatar Jun 03 '25 13:06 osa1

Something else that I just realized is that if you evaluate some code in runtime in some cases and compile time in others, you can get runtime error in some cases and compile-time error in others.

For example, consider this silly code: (modified from the operator == example in the proposal)

mixin DataClass<@konst T> {
  @override
  blah(Object? other) {
    final typeInfo = TypeInfo.of<T>();
    for (@konst final field in typeInfo.fields) {
      final value2 = field.getFrom(other as T);
      ...
    }
    return true;
  }
}

Here the line other as T will fail then other's type is not right, but when the failure happens will depend on when you evaluate other as T.

Does the proposal address this? (sorry if I can't find it..)

osa1 avatar Jun 24 '25 09:06 osa1

@osa1

But the text doesn't mention monomorphisation at all. Am I confused?

If this allows monomorphisation (and it has to, if I get it right), then that's a pretty big capability for the langauge that's also worth mentioning.

There is specifically this paragraph which talks about it though it does not explicitly call it monomorphisation.

When @konst is applied to parameters (including type parameters) it turns functions into templates: compiler will require that annotated parameter is a constant known at compile time and clone the function for a specific combination of parameters. The original function is removed from the program: it is impossible to invoke it dynamically or tear it off. To annotate this as @konst developer will need to place @konst on the declaration of the function itself.

I also find it confusing to see what function calls (or other syntax) is part of the generated code vs. compile-time evaluation.

That's fair, though its somewhat intentional:

  1. to avoid any syntactic changes altogether;
  2. to keep code runnable as-is in environments which choose to not implement this feature;

I am open for suggestions on how to make this better if you have any ideas. I think it would need some syntactic designator, but I don't immediately see anything that would be very readable.

I wonder if we could make stages explicit.

I think MetaOCaml's or other similar systems approach (Template Haskell, Scala macros, etc) are somewhat different because they operate on pieces of code into which you splice other pieces of code (quotes and splices are two building blocks), so syntax is naturally very clear around what is code and what is a hole in that code into which you can splice more code.

This proposal describes (optional) compile time specializaton - so I think the fact that syntax looks the same is kinda by design.

Something else that I just realized is that if you evaluate some code in runtime in some cases and compile time in others, you can get runtime error in some cases and compile-time error in others.

Yep, I think this is by design as well. Though maybe not written as explicitly.

mraleph avatar Jun 24 '25 21:06 mraleph