Skip to content

Create Static Enough Metaprogramming proposal #4374

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

mraleph
Copy link
Member

@mraleph mraleph commented May 14, 2025

This moves content from #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.

introspect program structure which are _required_ to execute in compile time
toolchain supports that. These two together should give enough expressive power
to solve a wide range of problems where metaprogramming is currently wanted. cc
@dart-lang/language-team
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No cc needed here.

@@ -0,0 +1,1222 @@
tldr: I propose we follow the lead of **D**, **Zig** and **C++26** when it comes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd give this file a header.

  • A title line at the top.
  • An author line: Author: @mralpeh.
  • Consider a version line: version: 1.0 and a version section/changelog at the end, to track changes.

Copy link
Member

@eernstg eernstg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. (Very interesting indeed!)

I could suggest the addition of 'a' or 'the' in many locations, but decided that this wouldn't be the top priority at this time.

to metaprogramming. We introduce an optional (toolchain) feature to force
compile time execution of certain constructs. We add library functions to
introspect program structure which are _required_ to execute in compile time
toolchain supports that. These two together should give enough expressive power
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps?:

Suggested change
toolchain supports that. These two together should give enough expressive power
if the toolchain supports that. These two together should give enough expressive power

Also, 'at compile time' seems to be more common than 'in compile time'.

# History of `dart:mirrors`

In the first days of 2017 I have written a blog post ["The fear of
`dart:mirrors`"][the-fear-of-dartmirrors] which contained started with the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`dart:mirrors`"][the-fear-of-dartmirrors] which contained started with the
`dart:mirrors`"][the-fear-of-dartmirrors] which started with the

> fog of uncertainty and marked as _Status: Unstable_ in the documentation -
> even though APIs have not changed for a very long time.
In 2017 type system was still optional, AOT was a glorified
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In 2017 type system was still optional, AOT was a glorified
In 2017 the type system was still optional, AOT was a glorified

_"ahead-off-time-JIT"_, and the team maintained at least 3 different Dart
front-ends (VM, dart2js and analyzer). Things really started shifting with Dart
2 release: it had replaced optional types with a static type system and
introduced _common front-end (CFE)_ infrastructure to be shared by all backends.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
introduced _common front-end (CFE)_ infrastructure to be shared by all backends.
introduced a _common front-end (CFE)_ infrastructure to be shared by all backends.

and necessity to use _AOT-compilation_ for deployment.

Dart 1 was all in on dynamic typing. You don't know what a variable contains -
but you can do anything with it. Can pass it anywhere. Can all any methods on
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps?:

Suggested change
but you can do anything with it. Can pass it anywhere. Can all any methods on
but you can do anything with it. Can pass it anywhere. Can call any methods on

>
> - [Reflection Analysis for Java][paper-java-suif-reflection]
> - [Understanding and Analyzing Java Reflection][paper-java-reflection-2019]
> - [Challenges for Static Analysis of Java Reflection – Literature Review and Empirical Study](paper-java-reflection-challenges)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just guessing:

Suggested change
> - [Challenges for Static Analysis of Java Reflection – Literature Review and Empirical Study](paper-java-reflection-challenges)
> - [Challenges for Static Analysis of Java Reflection – Literature Review and Empirical Study][paper-java-reflection-challenges]

> [!NOTE]
>
> Another exploration similar in nature was
> (go/const-tree-shakeable-reflection-objects)[http://go/const-tree-shakeable-reflection-objects].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps?:

Suggested change
> (go/const-tree-shakeable-reflection-objects)[http://go/const-tree-shakeable-reflection-objects].
> [go/const-tree-shakeable-reflection-objects](http://go/const-tree-shakeable-reflection-objects).

very small and excludes a lot of expressions which feel like they should
actually be included. It just feels wrong that `const x = [].length` is invalid
while `const x = "".length` is valid. For some seemingly arbitrary reason
`String.length` is the only blessed property which can't be accessed in a
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`String.length` is the only blessed property which can't be accessed in a
`String.length` is the only blessed property which can be accessed in a

> Another exploration similar in nature was
> (go/const-tree-shakeable-reflection-objects)[http://go/const-tree-shakeable-reflection-objects].
Fundamentally both of these approaches were dead ends and Web applications
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fwiw, I wouldn't say that the const reflection proposal was a dead end at all, we just simply didn't end up doing it but there wasn't any sort of blocker or anything that I remember encountering.

> This proposal does not suffer the same issue, even if we decide to support
> `@konst` evaluation in these tools: as `@konst` actually provides a syntactic
> marker which partitions the program. Only bodies of methods with `@konst`
> parameters need to be included into the outline.
Copy link
Contributor

@jakemac53 jakemac53 May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these functions only invoke other @konst functions? This was a big problem with enhanced const - it quickly ends up being that all functions end up being marked as "const" just because some other const function completely unrelated to your function wants to invoke them, and your function could be const so you say sure, but now all these functions end up in outlines.

I think you need something like @konst imports so it is defined at the usage site and not the definition site which functions can be called by @konst functions.

Dart source code generation or which are supported by macro systems in other
programming languages. For example the following capabilities are out of scope:

- injecting new declarations into the program;
Copy link
Contributor

@jakemac53 jakemac53 May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This definitely does drastically simplify the proposal - but it also makes it a lot less useful. Ultimately trying to accomplish this was the downfall of the original macros proposal though.

But, I think it is important to look at the original motivations for macros, and which of those use cases are covered by this.

Ultimately, I think you can end up with some fairly decent solutions for automatic encoding/decoding, equality, toString, etc. But not copyWith or constructors due to the signatures being dependent on the shape of the class (you could generate the bodies of these but that's only half the boilerplate).

Edit: I see you have an interesting copyWith idea using records, that is fairly reasonable actually.

> }
> ```
>
> Though usability of this method will be questionable as there will be good
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> Though usability of this method will be questionable as there will be good
> Though usability of this method will be questionable as there will be no good

- Average code size overhead per class: 270 bytes
- Average JIT kernel generation overhead per-class: 0.2ms (cost of producing
specialized functions using Kernel-to-Kernel AST transformation)
- Average AOT compilation overhead per-class: 2.6ms
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the baseline?

code:

```dart
mixin DataClass<@konst T> {
Copy link
Contributor

@jakemac53 jakemac53 May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be 🔥 (and help a lot with the reduction of boilerplate)


This requires the definition of constant expression to be expanded to cover a
significantly larger subset of Dart than it currently includes. Such feature
does however exist in other programming languages, most notably **C++**, **D**,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
does however exist in other programming languages, most notably **C++**, **D**,
do however exist in other programming languages, most notably **C++**, **D**,

}
```

Note that all methods are annotated with `@konst` so if compiler supports
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Note that all methods are annotated with `@konst` so if compiler supports
Note that all methods are annotated with `@konst` so if the compiler supports

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

Successfully merging this pull request may close these issues.

5 participants