Skip to content

Commit 6cfc61d

Browse files
authored
add auto dispose macro example (#2210)
1 parent 0df870d commit 6cfc61d

File tree

5 files changed

+153
-38
lines changed

5 files changed

+153
-38
lines changed

analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ analyzer:
22
exclude:
33
- accepted/2.3/spread-collections/examples/**
44
# TODO: remove these when the analyzer supports macros
5+
- working/macros/example/lib/auto_dispose.dart
56
- working/macros/example/lib/data_class.dart
67
- working/macros/example/lib/observable.dart
78
- working/macros/example/bin/user_main.dart

working/macros/example/bin/run.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,21 @@ void main() async {
2020
log('Bootstrapping macro program (${bootstrapFile.path}).');
2121
var dataClassUri = Uri.parse('package:macro_proposal/data_class.dart');
2222
var observableUri = Uri.parse('package:macro_proposal/observable.dart');
23+
var autoDisposableUri = Uri.parse('package:macro_proposal/auto_dispose.dart');
2324
var bootstrapContent = bootstrapMacroIsolate({
2425
dataClassUri.toString(): {
26+
'AutoConstructor': [''],
27+
'CopyWith': [''],
2528
'DataClass': [''],
29+
'HashCode': [''],
30+
'ToString': [''],
2631
},
2732
observableUri.toString(): {
2833
'Observable': [''],
2934
},
35+
autoDisposableUri.toString(): {
36+
'AutoDispose': [''],
37+
},
3038
}, SerializationMode.byteDataClient);
3139
bootstrapFile.writeAsStringSync(bootstrapContent);
3240
var bootstrapKernelFile =
@@ -85,6 +93,8 @@ void main() async {
8593
'$dataClassUri;${bootstrapKernelFile.path}',
8694
'--precompiled-macro',
8795
'$observableUri;${bootstrapKernelFile.path}',
96+
'--precompiled-macro',
97+
'$autoDisposableUri;${bootstrapKernelFile.path}',
8898
'--macro-serialization-mode=bytedata',
8999
'--input-linked',
90100
bootstrapKernelFile.path,

working/macros/example/bin/user_main.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:math';
66

7+
import 'package:macro_proposal/auto_dispose.dart';
78
import 'package:macro_proposal/data_class.dart';
89
import 'package:macro_proposal/observable.dart';
910

@@ -27,6 +28,9 @@ void main() {
2728
observableUser
2829
..age = 11
2930
..name = 'Greg';
31+
32+
var state = MyState.gen(a: ADisposable(), b: BDisposable(), c: 'hello world');
33+
state.dispose();
3034
}
3135

3236
@DataClass()
@@ -54,3 +58,32 @@ class ObservableUser {
5458
}) : _age = age,
5559
_name = name;
5660
}
61+
62+
// TODO: remove @AutoConstructor once we can, today it is required.
63+
@AutoConstructor()
64+
class State {
65+
void dispose() {
66+
print('disposing of State $this');
67+
}
68+
}
69+
70+
@AutoDispose()
71+
@AutoConstructor()
72+
class MyState extends State {
73+
final ADisposable a;
74+
final ADisposable? a2;
75+
final BDisposable b;
76+
final String c;
77+
}
78+
79+
class ADisposable implements Disposable {
80+
void dispose() {
81+
print('disposing of ADisposable');
82+
}
83+
}
84+
85+
class BDisposable implements Disposable {
86+
void dispose() {
87+
print('disposing of BDisposable');
88+
}
89+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// There is no public API exposed yet, the in progress api lives here.
6+
import 'package:_fe_analyzer_shared/src/macros/api.dart';
7+
8+
// Interface for disposable things.
9+
abstract class Disposable {
10+
void dispose();
11+
}
12+
13+
macro class AutoDispose implements ClassDeclarationsMacro, ClassDefinitionMacro {
14+
const AutoDispose();
15+
16+
@override
17+
void buildDeclarationsForClass(
18+
ClassDeclaration clazz, ClassMemberDeclarationBuilder builder) async {
19+
var methods = await builder.methodsOf(clazz);
20+
if (methods.any((d) => d.identifier.name == 'dispose')) {
21+
// Don't need to add the dispose method, it already exists.
22+
return;
23+
}
24+
25+
builder.declareInClass(DeclarationCode.fromParts([
26+
'external void dispose();',
27+
]));
28+
}
29+
30+
@override
31+
Future<void> buildDefinitionForClass(
32+
ClassDeclaration clazz, ClassDefinitionBuilder builder) async {
33+
var disposableIdentifier =
34+
// ignore: deprecated_member_use
35+
await builder.resolveIdentifier(
36+
Uri.parse('package:macro_proposal/auto_dispose.dart'),
37+
'Disposable');
38+
var disposableType = await builder
39+
.resolve(NamedTypeAnnotationCode(name: disposableIdentifier));
40+
41+
var disposeCalls = <Code>[];
42+
var fields = await builder.fieldsOf(clazz);
43+
for (var field in fields) {
44+
var type = await builder.resolve(field.type.code);
45+
if (!await type.isSubtypeOf(disposableType)) continue;
46+
disposeCalls.add(Code.fromParts([
47+
'\n',
48+
field.identifier,
49+
if (field.type.isNullable) '?',
50+
'.dispose();',
51+
]));
52+
}
53+
// Augment the dispose method by injecting all the new dispose calls after
54+
// the call to `augment super()`, which should be calling `super.dispose()`
55+
// already.
56+
var disposeMethod = (await builder.methodsOf(clazz))
57+
.firstWhere((method) => method.identifier.name == 'dispose');
58+
var disposeBuilder = await builder.buildMethod(disposeMethod.identifier);
59+
disposeBuilder.augment(FunctionBodyCode.fromParts([
60+
'{\n',
61+
if (disposeMethod.isExternal) 'super.dispose();' else 'augment super();',
62+
...disposeCalls,
63+
'}',
64+
]));
65+
}
66+
}

working/macros/example/lib/data_class.dart

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,27 @@ macro class DataClass
1313
Future<void> buildDeclarationsForClass(
1414
ClassDeclaration clazz, ClassMemberDeclarationBuilder context) async {
1515
await Future.wait([
16-
autoConstructor.buildDeclarationsForClass(clazz, context),
17-
copyWith.buildDeclarationsForClass(clazz, context),
18-
hashCode.buildDeclarationsForClass(clazz, context),
19-
equality.buildDeclarationsForClass(clazz, context),
20-
toString.buildDeclarationsForClass(clazz, context),
16+
AutoConstructor().buildDeclarationsForClass(clazz, context),
17+
CopyWith().buildDeclarationsForClass(clazz, context),
18+
HashCode().buildDeclarationsForClass(clazz, context),
19+
Equality().buildDeclarationsForClass(clazz, context),
20+
ToString().buildDeclarationsForClass(clazz, context),
2121
]);
2222
}
2323

2424
@override
2525
Future<void> buildDefinitionForClass(
2626
ClassDeclaration clazz, ClassDefinitionBuilder builder) async {
2727
await Future.wait([
28-
hashCode.buildDefinitionForClass(clazz, builder),
29-
equality.buildDefinitionForClass(clazz, builder),
30-
toString.buildDefinitionForClass(clazz, builder),
28+
HashCode().buildDefinitionForClass(clazz, builder),
29+
Equality().buildDefinitionForClass(clazz, builder),
30+
ToString().buildDefinitionForClass(clazz, builder),
3131
]);
3232
}
3333
}
3434

35-
const autoConstructor = _AutoConstructor();
36-
37-
macro class _AutoConstructor implements ClassDeclarationsMacro {
38-
const _AutoConstructor();
35+
macro class AutoConstructor implements ClassDeclarationsMacro {
36+
const AutoConstructor();
3937

4038
@override
4139
Future<void> buildDeclarationsForClass(
@@ -46,13 +44,14 @@ macro class _AutoConstructor implements ClassDeclarationsMacro {
4644
'Cannot generate an unnamed constructor because one already exists');
4745
}
4846

49-
// Don't use the identifier here because it should just be the raw name.
50-
var parts = <Object>[clazz.identifier.name, '.gen({'];
47+
var params = <Object>[];
5148
// Add all the fields of `declaration` as named parameters.
5249
var fields = await builder.fieldsOf(clazz);
53-
for (var field in fields) {
54-
var requiredKeyword = field.type.isNullable ? '' : 'required ';
55-
parts.addAll(['\n${requiredKeyword}', field.identifier, ',']);
50+
if (fields.isNotEmpty) {
51+
for (var field in fields) {
52+
var requiredKeyword = field.type.isNullable ? '' : 'required ';
53+
params.addAll(['\n${requiredKeyword}', field.identifier, ',']);
54+
}
5655
}
5756

5857
// The object type from dart:core.
@@ -78,22 +77,32 @@ macro class _AutoConstructor implements ClassDeclarationsMacro {
7877
// parameters in this constructor.
7978
for (var param in superconstructor.positionalParameters) {
8079
var requiredKeyword = param.isRequired ? 'required' : '';
81-
parts.addAll([
80+
params.addAll([
8281
'\n$requiredKeyword',
8382
param.type.code,
8483
' ${param.identifier.name},',
8584
]);
8685
}
8786
for (var param in superconstructor.namedParameters) {
8887
var requiredKeyword = param.isRequired ? '' : 'required ';
89-
parts.addAll([
88+
params.addAll([
9089
'\n$requiredKeyword',
9190
param.type.code,
9291
' ${param.identifier.name},',
9392
]);
9493
}
9594
}
96-
parts.add('\n})');
95+
96+
bool hasParams = params.isNotEmpty;
97+
List<Object> parts = [
98+
// Don't use the identifier here because it should just be the raw name.
99+
clazz.identifier.name,
100+
'.gen(',
101+
if (hasParams) '{',
102+
...params,
103+
if (hasParams) '}',
104+
')',
105+
];
97106
if (superconstructor != null) {
98107
parts.addAll([' : super.', superconstructor.identifier.name, '(']);
99108
for (var param in superconstructor.positionalParameters) {
@@ -111,11 +120,9 @@ macro class _AutoConstructor implements ClassDeclarationsMacro {
111120
}
112121
}
113122

114-
const copyWith = _CopyWith();
115-
116123
// TODO: How to deal with overriding nullable fields to `null`?
117-
macro class _CopyWith implements ClassDeclarationsMacro {
118-
const _CopyWith();
124+
macro class CopyWith implements ClassDeclarationsMacro {
125+
const CopyWith();
119126

120127
@override
121128
Future<void> buildDeclarationsForClass(
@@ -141,24 +148,25 @@ macro class _CopyWith implements ClassDeclarationsMacro {
141148
field.identifier,
142149
]),
143150
];
151+
var hasParams = namedParams.isNotEmpty;
144152
builder.declareInClass(DeclarationCode.fromParts([
145153
clazz.identifier,
146-
' copyWith({',
154+
' copyWith(',
155+
if (hasParams) '{',
147156
...namedParams.joinAsCode(', '),
148-
',})',
157+
if (hasParams) '}',
158+
')',
149159
// TODO: We assume this constructor exists, but should check
150160
'=> ', clazz.identifier, '.gen(',
151161
...args.joinAsCode(', '),
152-
', );',
162+
');',
153163
]));
154164
}
155165
}
156166

157-
const hashCode = _HashCode();
158-
159-
macro class _HashCode
167+
macro class HashCode
160168
implements ClassDeclarationsMacro, ClassDefinitionMacro {
161-
const _HashCode();
169+
const HashCode();
162170

163171
@override
164172
Future<void> buildDeclarationsForClass(
@@ -189,11 +197,9 @@ macro class _HashCode
189197
}
190198
}
191199

192-
const equality = _Equality();
193-
194-
macro class _Equality
200+
macro class Equality
195201
implements ClassDeclarationsMacro, ClassDefinitionMacro {
196-
const _Equality();
202+
const Equality();
197203

198204
@override
199205
Future<void> buildDeclarationsForClass(
@@ -234,11 +240,10 @@ macro class _Equality
234240
}
235241
}
236242

237-
const toString = _ToString();
238243

239-
macro class _ToString
244+
macro class ToString
240245
implements ClassDeclarationsMacro, ClassDefinitionMacro {
241-
const _ToString();
246+
const ToString();
242247

243248
@override
244249
Future<void> buildDeclarationsForClass(

0 commit comments

Comments
 (0)