Skip to content

Commit

Permalink
add auto dispose macro example (#2210)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakemac53 authored Apr 21, 2022
1 parent 0df870d commit 6cfc61d
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 38 deletions.
1 change: 1 addition & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ analyzer:
exclude:
- accepted/2.3/spread-collections/examples/**
# TODO: remove these when the analyzer supports macros
- working/macros/example/lib/auto_dispose.dart
- working/macros/example/lib/data_class.dart
- working/macros/example/lib/observable.dart
- working/macros/example/bin/user_main.dart
10 changes: 10 additions & 0 deletions working/macros/example/bin/run.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,21 @@ void main() async {
log('Bootstrapping macro program (${bootstrapFile.path}).');
var dataClassUri = Uri.parse('package:macro_proposal/data_class.dart');
var observableUri = Uri.parse('package:macro_proposal/observable.dart');
var autoDisposableUri = Uri.parse('package:macro_proposal/auto_dispose.dart');
var bootstrapContent = bootstrapMacroIsolate({
dataClassUri.toString(): {
'AutoConstructor': [''],
'CopyWith': [''],
'DataClass': [''],
'HashCode': [''],
'ToString': [''],
},
observableUri.toString(): {
'Observable': [''],
},
autoDisposableUri.toString(): {
'AutoDispose': [''],
},
}, SerializationMode.byteDataClient);
bootstrapFile.writeAsStringSync(bootstrapContent);
var bootstrapKernelFile =
Expand Down Expand Up @@ -85,6 +93,8 @@ void main() async {
'$dataClassUri;${bootstrapKernelFile.path}',
'--precompiled-macro',
'$observableUri;${bootstrapKernelFile.path}',
'--precompiled-macro',
'$autoDisposableUri;${bootstrapKernelFile.path}',
'--macro-serialization-mode=bytedata',
'--input-linked',
bootstrapKernelFile.path,
Expand Down
33 changes: 33 additions & 0 deletions working/macros/example/bin/user_main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:math';

import 'package:macro_proposal/auto_dispose.dart';
import 'package:macro_proposal/data_class.dart';
import 'package:macro_proposal/observable.dart';

Expand All @@ -27,6 +28,9 @@ void main() {
observableUser
..age = 11
..name = 'Greg';

var state = MyState.gen(a: ADisposable(), b: BDisposable(), c: 'hello world');
state.dispose();
}

@DataClass()
Expand Down Expand Up @@ -54,3 +58,32 @@ class ObservableUser {
}) : _age = age,
_name = name;
}

// TODO: remove @AutoConstructor once we can, today it is required.
@AutoConstructor()
class State {
void dispose() {
print('disposing of State $this');
}
}

@AutoDispose()
@AutoConstructor()
class MyState extends State {
final ADisposable a;
final ADisposable? a2;
final BDisposable b;
final String c;
}

class ADisposable implements Disposable {
void dispose() {
print('disposing of ADisposable');
}
}

class BDisposable implements Disposable {
void dispose() {
print('disposing of BDisposable');
}
}
66 changes: 66 additions & 0 deletions working/macros/example/lib/auto_dispose.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// There is no public API exposed yet, the in progress api lives here.
import 'package:_fe_analyzer_shared/src/macros/api.dart';

// Interface for disposable things.
abstract class Disposable {
void dispose();
}

macro class AutoDispose implements ClassDeclarationsMacro, ClassDefinitionMacro {
const AutoDispose();

@override
void buildDeclarationsForClass(
ClassDeclaration clazz, ClassMemberDeclarationBuilder builder) async {
var methods = await builder.methodsOf(clazz);
if (methods.any((d) => d.identifier.name == 'dispose')) {
// Don't need to add the dispose method, it already exists.
return;
}

builder.declareInClass(DeclarationCode.fromParts([
'external void dispose();',
]));
}

@override
Future<void> buildDefinitionForClass(
ClassDeclaration clazz, ClassDefinitionBuilder builder) async {
var disposableIdentifier =
// ignore: deprecated_member_use
await builder.resolveIdentifier(
Uri.parse('package:macro_proposal/auto_dispose.dart'),
'Disposable');
var disposableType = await builder
.resolve(NamedTypeAnnotationCode(name: disposableIdentifier));

var disposeCalls = <Code>[];
var fields = await builder.fieldsOf(clazz);
for (var field in fields) {
var type = await builder.resolve(field.type.code);
if (!await type.isSubtypeOf(disposableType)) continue;
disposeCalls.add(Code.fromParts([
'\n',
field.identifier,
if (field.type.isNullable) '?',
'.dispose();',
]));
}
// Augment the dispose method by injecting all the new dispose calls after
// the call to `augment super()`, which should be calling `super.dispose()`
// already.
var disposeMethod = (await builder.methodsOf(clazz))
.firstWhere((method) => method.identifier.name == 'dispose');
var disposeBuilder = await builder.buildMethod(disposeMethod.identifier);
disposeBuilder.augment(FunctionBodyCode.fromParts([
'{\n',
if (disposeMethod.isExternal) 'super.dispose();' else 'augment super();',
...disposeCalls,
'}',
]));
}
}
81 changes: 43 additions & 38 deletions working/macros/example/lib/data_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,27 @@ macro class DataClass
Future<void> buildDeclarationsForClass(
ClassDeclaration clazz, ClassMemberDeclarationBuilder context) async {
await Future.wait([
autoConstructor.buildDeclarationsForClass(clazz, context),
copyWith.buildDeclarationsForClass(clazz, context),
hashCode.buildDeclarationsForClass(clazz, context),
equality.buildDeclarationsForClass(clazz, context),
toString.buildDeclarationsForClass(clazz, context),
AutoConstructor().buildDeclarationsForClass(clazz, context),

This comment has been minimized.

Copy link
@johnniwinther

johnniwinther May 12, 2022

Member

These should be const AutoConstructor() etc to avoid creating these objects repeatedly.

This comment has been minimized.

Copy link
@jakemac53

jakemac53 May 12, 2022

Author Contributor

sure, I can do that

CopyWith().buildDeclarationsForClass(clazz, context),
HashCode().buildDeclarationsForClass(clazz, context),
Equality().buildDeclarationsForClass(clazz, context),
ToString().buildDeclarationsForClass(clazz, context),
]);
}

@override
Future<void> buildDefinitionForClass(
ClassDeclaration clazz, ClassDefinitionBuilder builder) async {
await Future.wait([
hashCode.buildDefinitionForClass(clazz, builder),
equality.buildDefinitionForClass(clazz, builder),
toString.buildDefinitionForClass(clazz, builder),
HashCode().buildDefinitionForClass(clazz, builder),
Equality().buildDefinitionForClass(clazz, builder),
ToString().buildDefinitionForClass(clazz, builder),
]);
}
}

const autoConstructor = _AutoConstructor();

macro class _AutoConstructor implements ClassDeclarationsMacro {
const _AutoConstructor();
macro class AutoConstructor implements ClassDeclarationsMacro {
const AutoConstructor();

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

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

// The object type from dart:core.
Expand All @@ -78,22 +77,32 @@ macro class _AutoConstructor implements ClassDeclarationsMacro {
// parameters in this constructor.
for (var param in superconstructor.positionalParameters) {
var requiredKeyword = param.isRequired ? 'required' : '';
parts.addAll([
params.addAll([
'\n$requiredKeyword',
param.type.code,
' ${param.identifier.name},',
]);
}
for (var param in superconstructor.namedParameters) {
var requiredKeyword = param.isRequired ? '' : 'required ';
parts.addAll([
params.addAll([
'\n$requiredKeyword',
param.type.code,
' ${param.identifier.name},',
]);
}
}
parts.add('\n})');

bool hasParams = params.isNotEmpty;
List<Object> parts = [
// Don't use the identifier here because it should just be the raw name.
clazz.identifier.name,
'.gen(',
if (hasParams) '{',
...params,
if (hasParams) '}',
')',
];
if (superconstructor != null) {
parts.addAll([' : super.', superconstructor.identifier.name, '(']);
for (var param in superconstructor.positionalParameters) {
Expand All @@ -111,11 +120,9 @@ macro class _AutoConstructor implements ClassDeclarationsMacro {
}
}

const copyWith = _CopyWith();

// TODO: How to deal with overriding nullable fields to `null`?
macro class _CopyWith implements ClassDeclarationsMacro {
const _CopyWith();
macro class CopyWith implements ClassDeclarationsMacro {
const CopyWith();

@override
Future<void> buildDeclarationsForClass(
Expand All @@ -141,24 +148,25 @@ macro class _CopyWith implements ClassDeclarationsMacro {
field.identifier,
]),
];
var hasParams = namedParams.isNotEmpty;
builder.declareInClass(DeclarationCode.fromParts([
clazz.identifier,
' copyWith({',
' copyWith(',
if (hasParams) '{',
...namedParams.joinAsCode(', '),
',})',
if (hasParams) '}',
')',
// TODO: We assume this constructor exists, but should check
'=> ', clazz.identifier, '.gen(',
...args.joinAsCode(', '),
', );',
');',
]));
}
}

const hashCode = _HashCode();

macro class _HashCode
macro class HashCode
implements ClassDeclarationsMacro, ClassDefinitionMacro {
const _HashCode();
const HashCode();

@override
Future<void> buildDeclarationsForClass(
Expand Down Expand Up @@ -189,11 +197,9 @@ macro class _HashCode
}
}

const equality = _Equality();

macro class _Equality
macro class Equality
implements ClassDeclarationsMacro, ClassDefinitionMacro {
const _Equality();
const Equality();

@override
Future<void> buildDeclarationsForClass(
Expand Down Expand Up @@ -234,11 +240,10 @@ macro class _Equality
}
}

const toString = _ToString();

macro class _ToString
macro class ToString
implements ClassDeclarationsMacro, ClassDefinitionMacro {
const _ToString();
const ToString();

@override
Future<void> buildDeclarationsForClass(
Expand Down

0 comments on commit 6cfc61d

Please sign in to comment.