Skip to content

Commit e9ffb51

Browse files
committed
add a real macro example, restructure the example code
1 parent d16704a commit e9ffb51

File tree

8 files changed

+403
-81
lines changed

8 files changed

+403
-81
lines changed

analysis_options.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
analyzer:
22
exclude:
33
- accepted/2.3/spread-collections/examples/**
4+
# TODO: remove these when the analyzer supports macros
5+
- working/macros/example/lib/data_class.dart
6+
- working/macros/example/bin/user_main.dart

working/macros/example/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
**DISCLAIMER**: All code in this package is experimental and should be treated
2+
as such.
3+
4+
This package some example macros (under `lib`), as well as some utilities to try
5+
actually running those examples.
6+
7+
## Setup
8+
9+
You may need to edit the `pubspec.yaml` file to run these examples. It requires
10+
a path dependency on the SDK packages to work (until
11+
https://github.com/dart-lang/pub/issues/3336 is resolved). The current paths
12+
assume the dart sdk is in a sibling directory to the language repo, in a
13+
directory called `dart-lang-sdk`.
14+
15+
Your SDK will also need to be very recent, in particular it must include
16+
commit 54e773.
17+
18+
## Benchmarks
19+
20+
There is a basic benchmark at `benchmark/simple.dart`. You can run this tool
21+
directly, and it allows toggling some options via command line flags. You can
22+
also AOT compile the benchmark script itself to simulate an AOT compiled host
23+
environment (compiler).
24+
25+
This benchmark uses a synthetic program, and only benchmarks the overhead of
26+
running the macro itself, and the communication to and from the host program.
27+
28+
## Examples
29+
30+
There is an example program at `bin/user_main.dart`. This _cannot_ be directly
31+
executed but you can compile and execute it with the `bin/run.dart` script.
32+
33+
**NOTE**: This is not meant to be a representative example of how a script using
34+
macros would be compiled and ran in the real world, but it does allow you to
35+
execute the program.

working/macros/example/run.dart renamed to working/macros/example/benchmark/simple.dart

Lines changed: 125 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
// Run this script to print out the generated augmentation library for an
6-
// example class.
6+
// example class, created with fake data, and get some basic timing info:
77
//
8-
// This is primarily for illustration purposes, so we can get an idea of how
9-
// things would work on a real-ish example.
10-
library language.working.macros.example.run;
8+
// dart benchmark/simple.dart
9+
//
10+
// You can also compile this benchmark to exe and run it as follows:
11+
//
12+
// dart compile exe benchmark/simple.dart && ./benchmark/simple.exe
13+
//
14+
// Pass `--help` for usage and configuration options.
15+
library language.working.macros.benchmark.simple;
1116

1217
import 'dart:io';
1318

19+
import 'package:args/args.dart';
1420
import 'package:dart_style/dart_style.dart';
1521

1622
// There is no public API exposed yet, the in progress api lives here.
@@ -22,6 +28,8 @@ import 'package:_fe_analyzer_shared/src/macros/executor.dart';
2228
import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
2329
import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
2430
import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
31+
import 'package:_fe_analyzer_shared/src/macros/executor/isolated_executor.dart'
32+
as isolatedExecutor;
2533
import 'package:_fe_analyzer_shared/src/macros/executor/process_executor.dart'
2634
as processExecutor;
2735

@@ -30,22 +38,71 @@ void _log(String message) {
3038
print('${_watch.elapsed}: $message');
3139
}
3240

33-
const clientSerializationMode = SerializationMode.byteDataClient;
34-
const serverSerializationMode = SerializationMode.byteDataServer;
41+
final argParser = ArgParser()
42+
..addOption('serialization-strategy',
43+
allowed: ['bytedata', 'json'],
44+
defaultsTo: 'bytedata',
45+
help: 'The serialization strategy to use when talking to macro programs.')
46+
..addOption('macro-execution-strategy',
47+
allowed: ['aot', 'isolate'],
48+
defaultsTo: 'aot',
49+
help: 'The execution strategy for precompiled macros.')
50+
..addFlag('help', negatable: false, hide: true);
3551

3652
// Run this script to print out the generated augmentation library for an example class.
37-
void main() async {
38-
_log('Preparing to run macros.');
53+
void main(List<String> args) async {
54+
var parsedArgs = argParser.parse(args);
55+
56+
if (parsedArgs['help'] == true) {
57+
print(argParser.usage);
58+
return;
59+
}
60+
61+
// Set up all of our options
62+
var parsedSerializationStrategy =
63+
parsedArgs['serialization-strategy'] as String;
64+
SerializationMode clientSerializationMode;
65+
SerializationMode serverSerializationMode;
66+
switch (parsedSerializationStrategy) {
67+
case 'bytedata':
68+
clientSerializationMode = SerializationMode.byteDataClient;
69+
serverSerializationMode = SerializationMode.byteDataServer;
70+
break;
71+
case 'json':
72+
clientSerializationMode = SerializationMode.jsonClient;
73+
serverSerializationMode = SerializationMode.jsonServer;
74+
break;
75+
default:
76+
throw ArgumentError(
77+
'Unrecognized serialization mode $parsedSerializationStrategy');
78+
}
79+
80+
var macroExecutionStrategy = parsedArgs['macro-execution-strategy'] as String;
81+
var hostMode = Platform.script.path.endsWith('.dart') ||
82+
Platform.script.path.endsWith('.dill')
83+
? 'jit'
84+
: 'aot';
85+
_log('''
86+
Running with the following options:
87+
88+
Serialization strategy: $parsedSerializationStrategy
89+
Macro execution strategy: $macroExecutionStrategy
90+
Host app mode: $hostMode
91+
''');
92+
3993
// You must run from the `macros` directory, paths are relative to that.
40-
var thisFile = File('example/data_class.dart');
41-
if (!thisFile.existsSync()) {
94+
var dataClassFile = File('lib/data_class.dart');
95+
if (!dataClassFile.existsSync()) {
4296
print('This script must be ran from the `macros` directory.');
4397
exit(1);
4498
}
45-
var executor = await processExecutor.start(serverSerializationMode);
99+
_log('Preparing to run macros');
100+
var executor = macroExecutionStrategy == 'aot'
101+
? await processExecutor.start(serverSerializationMode)
102+
: await isolatedExecutor.start(serverSerializationMode);
46103
var tmpDir = Directory.systemTemp.createTempSync('data_class_macro_example');
47104
try {
48-
var macroUri = thisFile.absolute.uri;
105+
var macroUri = Uri.parse('package:macro_proposal/data_class.dart');
49106
var macroName = 'DataClass';
50107

51108
var bootstrapContent = bootstrapMacroIsolate({
@@ -61,8 +118,9 @@ void main() async {
61118
_log('Compiling DataClass macro');
62119
var buildSnapshotResult = await Process.run('dart', [
63120
'compile',
64-
'exe',
121+
macroExecutionStrategy == 'aot' ? 'exe' : 'jit-snapshot',
65122
'--packages=.dart_tool/package_config.json',
123+
'--enable-experiment=macros',
66124
bootstrapFile.uri.toFilePath(),
67125
'-o',
68126
kernelOutputFile.uri.toFilePath(),
@@ -88,26 +146,32 @@ void main() async {
88146
late Duration firstRunEnd;
89147
late Duration first11RunsEnd;
90148
for (var i = 1; i <= 111; i++) {
91-
var _shouldLog = i == 1 || i == 10 || i == 100;
149+
var _shouldLog = i == 1 || i == 11 || i == 111;
92150
if (_shouldLog) _log('Running DataClass macro for the ${i}th time');
93151
if (instanceId.shouldExecute(DeclarationKind.clazz, Phase.types)) {
94152
if (_shouldLog) _log('Running types phase');
95-
var result = await executor.executeTypesPhase(instanceId, myClass);
153+
var result = await executor.executeTypesPhase(
154+
instanceId, myClass, SimpleIdentifierResolver());
96155
if (i == 1) results.add(result);
97156
}
98157
if (instanceId.shouldExecute(DeclarationKind.clazz, Phase.declarations)) {
99158
if (_shouldLog) _log('Running declarations phase');
100159
var result = await executor.executeDeclarationsPhase(
101-
instanceId, myClass, FakeTypeResolver(), FakeClassIntrospector());
160+
instanceId,
161+
myClass,
162+
SimpleIdentifierResolver(),
163+
SimpleTypeResolver(),
164+
SimpleClassIntrospector());
102165
if (i == 1) results.add(result);
103166
}
104167
if (instanceId.shouldExecute(DeclarationKind.clazz, Phase.definitions)) {
105168
if (_shouldLog) _log('Running definitions phase');
106169
var result = await executor.executeDefinitionsPhase(
107170
instanceId,
108171
myClass,
109-
FakeTypeResolver(),
110-
FakeClassIntrospector(),
172+
SimpleIdentifierResolver(),
173+
SimpleTypeResolver(),
174+
SimpleClassIntrospector(),
111175
FakeTypeDeclarationResolver());
112176
if (i == 1) results.add(result);
113177
}
@@ -123,10 +187,7 @@ void main() async {
123187

124188
_log('Building augmentation library');
125189
var library = executor.buildAugmentationLibrary(results, (identifier) {
126-
if (identifier == boolIdentifier ||
127-
identifier == objectIdentifier ||
128-
identifier == stringIdentifier ||
129-
identifier == intIdentifier) {
190+
if (['bool', 'Object', 'String', 'int'].contains(identifier.name)) {
130191
return ResolvedIdentifier(
131192
kind: IdentifierKind.topLevelMember,
132193
name: identifier.name,
@@ -139,7 +200,7 @@ void main() async {
139200
: IdentifierKind.instanceMember,
140201
name: identifier.name,
141202
staticScope: null,
142-
uri: Platform.script.resolve('data_class.dart'));
203+
uri: Uri.parse('package:app/main.dart'));
143204
}
144205
});
145206
executor.close();
@@ -299,7 +360,8 @@ abstract class Fake {
299360
throw UnimplementedError(invocation.memberName.toString());
300361
}
301362

302-
class FakeClassIntrospector extends Fake implements ClassIntrospector {
363+
/// Returns data as if everything was [myClass].
364+
class SimpleClassIntrospector extends Fake implements ClassIntrospector {
303365
@override
304366
Future<List<ConstructorDeclaration>> constructorsOf(
305367
covariant ClassDeclaration clazz) async =>
@@ -308,23 +370,59 @@ class FakeClassIntrospector extends Fake implements ClassIntrospector {
308370
@override
309371
Future<List<FieldDeclaration>> fieldsOf(
310372
covariant ClassDeclaration clazz) async =>
311-
myClassFields;
373+
clazz == myClass ? myClassFields : [];
312374

313375
@override
314376
Future<List<MethodDeclaration>> methodsOf(
315377
covariant ClassDeclaration clazz) async =>
316-
myClassMethods;
378+
clazz == myClass ? myClassMethods : [];
317379

318380
@override
319381
Future<ClassDeclaration?> superclassOf(
320382
covariant ClassDeclaration clazz) async =>
321383
clazz == myClass ? objectClass : null;
322384
}
323385

386+
/// This is a very basic identifier resolver, it does no actual resolution.
387+
class SimpleIdentifierResolver implements IdentifierResolver {
388+
/// Just returns a new [Identifier] whose name is [name].
389+
@override
390+
Future<Identifier> resolveIdentifier(Uri library, String name) async =>
391+
IdentifierImpl(id: RemoteInstance.uniqueId, name: name);
392+
}
393+
324394
class FakeTypeDeclarationResolver extends Fake
325395
implements TypeDeclarationResolver {}
326396

327-
class FakeTypeResolver extends Fake implements TypeResolver {}
397+
/// Only supports named types with no type arguments.
398+
class SimpleTypeResolver implements TypeResolver {
399+
@override
400+
Future<StaticType> resolve(TypeAnnotationCode type) async {
401+
if (type is! NamedTypeAnnotationCode) {
402+
throw UnsupportedError('Only named type annotations are supported');
403+
}
404+
if (type.typeArguments.isNotEmpty) {
405+
throw UnsupportedError('Type arguments are not supported');
406+
}
407+
return SimpleNamedStaticType(type.name.name, isNullable: type.isNullable);
408+
}
409+
}
410+
411+
/// Only supports exact matching, and only goes off of the name and nullability.
412+
class SimpleNamedStaticType implements NamedStaticType {
413+
final bool isNullable;
414+
final String name;
415+
416+
SimpleNamedStaticType(this.name, {this.isNullable = false});
417+
418+
@override
419+
Future<bool> isExactly(covariant SimpleNamedStaticType other) async =>
420+
isNullable == other.isNullable && name == other.name;
421+
422+
@override
423+
Future<bool> isSubtypeOf(covariant StaticType other) =>
424+
throw UnimplementedError();
425+
}
328426

329427
extension _ on Duration {
330428
Duration dividedBy(int amount) =>

0 commit comments

Comments
 (0)