Skip to content

Macro expansion protocol implementation example #2022

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

Merged
merged 26 commits into from
Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
18a5451
add initial macro expansion protocol
jakemac53 Dec 8, 2021
00b27df
code review fixes
jakemac53 Dec 8, 2021
fafae7a
remove outdated comment
jakemac53 Dec 8, 2021
992d95b
add skeleton
jakemac53 Dec 9, 2021
9113b2b
fix typo
jakemac53 Dec 9, 2021
58082b6
add basic loadMacro support
jakemac53 Dec 9, 2021
400507c
add a basic test and fix a cast
jakemac53 Dec 9, 2021
b30b095
add support for instantiating macros
jakemac53 Dec 9, 2021
cf53bd1
implement basic execution of definition macros
jakemac53 Dec 9, 2021
9be4958
remove package config constructor
jakemac53 Dec 9, 2021
402b572
Merge branch 'protocol' into protocol-impl
jakemac53 Dec 9, 2021
25da18e
remove unused import
jakemac53 Dec 9, 2021
f623f71
code review updates
jakemac53 Dec 10, 2021
3eec82a
code review updates
jakemac53 Dec 10, 2021
878b792
Merge branch 'protocol' into protocol-impl
jakemac53 Dec 10, 2021
7fc8d52
support multiple requests in flight at a time
jakemac53 Dec 10, 2021
65f93a8
fix optional positional parameter filter
jakemac53 Dec 13, 2021
fdb4ef4
add StaticType class, move type checker apis to it
jakemac53 Dec 14, 2021
b5c6cbe
Merge branch 'protocol' into protocol-impl
jakemac53 Dec 14, 2021
732b6f0
update to lastest protocol
jakemac53 Dec 14, 2021
6f94cd9
futurify some apis
jakemac53 Dec 14, 2021
2d111cc
Merge branch 'protocol' into protocol-impl
jakemac53 Dec 14, 2021
1b8618b
update api
jakemac53 Dec 14, 2021
888d77d
instantiate changes: require typeArguments an isNullabe, document tha…
jakemac53 Dec 15, 2021
857b296
Merge branch 'protocol' into protocol-impl
jakemac53 Dec 15, 2021
7e1cdf5
Merge branch 'master' into protocol-impl
jakemac53 Dec 15, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
*.vcxproj.filters
/*.vcxproj.user
*.stamp
.dart_tool
.packages

# Gyp generated files
*.xcodeproj
Expand Down
19 changes: 11 additions & 8 deletions working/macros/api/builders.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,24 @@ abstract class TypeBuilder implements Builder {
void declareType(DeclarationCode typeDeclaration);
}

/// The interface for checking if a type implements another type.
abstract class TypeComparator {
/// Returns true if [leftType] is a subtype of [rightType].
bool isSubtypeOf(TypeAnnotation leftType, TypeAnnotation rightType);

/// Returns true if [leftType] is an identical type to [rightType].
bool isExactly(TypeAnnotation leftType, TypeAnnotation rightType);
}

/// The api used by [Macro]s to contribute new (non-type)
/// declarations to the current library.
///
/// Can also be used to do subtype checks on types.
abstract class DeclarationBuilder implements Builder {
abstract class DeclarationBuilder implements Builder, TypeComparator {
/// Adds a new regular declaration to the surrounding library.
///
/// Note that type declarations are not supported.
Declaration declareInLibrary(DeclarationCode declaration);

/// Returns true if [leftType] is a subtype of [rightType].
bool isSubtypeOf(TypeAnnotation leftType, TypeAnnotation rightType);

/// Retruns true if [leftType] is an identical type to [rightType].
bool isExactly(TypeAnnotation leftType, TypeAnnotation rightType);
void declareInLibrary(DeclarationCode declaration);
}

/// The api used by [Macro]s to contribute new members to a class.
Expand Down
108 changes: 108 additions & 0 deletions working/macros/api/expansion_protocol.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import 'builders.dart';
import 'code.dart';
import 'introspection.dart';

/// The interface used by Dart language implementations, in order to load
/// and execute macros, as well as produce library augmentations from those
/// macro applications.
///
/// This class more clearly defines the role of a Dart language implementation
/// during macro discovery and expansion, and unifies how augmentation libraries
/// are produced.
abstract class MacroExecutor {
/// Invoked when an implementation discovers a new macro definition in a
/// library, and prepares this executor to run the macro.
///
/// May be invoked more than once for the same macro, which will cause the
/// macro to be re-loaded. Previous [MacroClassIdentifier]s and
/// [MacroInstanceIdentifier]s given for this macro will be invalid after
/// that point and should be discarded.
///
/// Throws an exception if the macro fails to load.
Future<MacroClassIdentifier> loadMacro(Uri library, String name);

/// Creates an instance of [macroClass] in the executor, and returns an
/// identifier for that instance.
///
/// Throws an exception if an instance is not created.
Future<MacroInstanceIdentifier> instantiateMacro(
MacroClassIdentifier macroClass, String constructor, Arguments arguments);

/// Runs the type phase for [macro] on a given [declaration].
///
/// Throws an exception if there is an error executing the macro.
Future<MacroExecutionResult> executeTypesPhase(
MacroInstanceIdentifier macro, Declaration declaration);

/// Runs the declarations phase for [macro] on a given [declaration].
///
/// Throws an exception if there is an error executing the macro.
Future<MacroExecutionResult> executeDeclarationsPhase(
MacroInstanceIdentifier macro,
Declaration declaration,
TypeComparator typeComparator,
ClassIntrospector classIntrospector);

/// Runs the definitions phase for [macro] on a given [declaration].
///
/// Throws an exception if there is an error executing the macro.
Future<MacroExecutionResult> executeDefinitionsPhase(
MacroInstanceIdentifier macro,
Declaration declaration,
TypeComparator typeComparator,
ClassIntrospector classIntrospector,
TypeIntrospector typeIntrospector);

/// Combines multiple [MacroExecutionResult]s into a single library
/// augmentation file, and returns a [String] representing that file.
Future<String> buildAugmentationLibrary(
Iterable<MacroExecutionResult> macroResults);

/// Tell the executor to shut down and clean up any resources it may have
/// allocated.
void close();
}

/// The arguments passed to a macro constructor.
///
/// All argument instances must be of type [Code] or a built-in value type that
/// is serializable (num, bool, String, null, etc).
class Arguments {
final List<Object?> positional;

final Map<String, Object?> named;

Arguments(this.positional, this.named);
}

/// An opaque identifier for a macro class, retrieved by
/// [MacroExecutor.loadMacro].
///
/// Used to execute or reload this macro in the future.
abstract class MacroClassIdentifier {}

/// An opaque identifier for an instance of a macro class, retrieved by
/// [MacroExecutor.instantiateMacro].
///
/// Used to execute or reload this macro in the future.
abstract class MacroInstanceIdentifier {}

/// A summary of the results of running a macro in a given phase.
///
/// All modifications are expressed in terms of library augmentation
/// declarations.
abstract class MacroExecutionResult {
/// Any library imports that should be added to support the code used in
/// the augmentations.
Iterable<DeclarationCode> get imports;

/// Any augmentations that should be applied as a result of executing a macro.
Iterable<DeclarationCode> get agumentations;
}

/// Each of the different macro execution phases.
enum Phase {
types, // Only new types are added in this phase
declarations, // New non-type declarations are added in this phase
defintions, // This phase allows augmenting existing declarations
}
126 changes: 126 additions & 0 deletions working/macros/api/src/protocol/isolate_mirror_executor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import 'dart:async';
import 'dart:isolate';
import 'dart:mirrors';

import 'isolate_mirror_impl.dart';
import 'protocol.dart';
import '../../builders.dart';
import '../../expansion_protocol.dart';
import '../../introspection.dart';

/// A [MacroExecutor] implementation which relies on [IsolateMirror.loadUri]
/// in order to load macros libraries.
///
/// All actual work happens in a separate [Isolate], and this class serves as
/// a bridge between that isolate and the language frontends.
class IsolateMirrorMacroExecutor implements MacroExecutor {
/// The actual isolate doing macro loading and execution.
final Isolate _macroIsolate;

/// The channel used to send requests to the [_macroIsolate].
final SendPort _sendPort;

/// The stream of responses from the [_macroIsolate].
final Stream<GenericResponse> _responseStream;

/// The completer for the next response to come along the stream.
Completer<GenericResponse>? _nextResponseCompleter;

/// A function that should be invoked when shutting down this executor
/// to perform any necessary cleanup.
final void Function() _onClose;

IsolateMirrorMacroExecutor._(
this._macroIsolate, this._sendPort, this._responseStream, this._onClose) {
_responseStream.listen((event) {
assert(_nextResponseCompleter != null);
_nextResponseCompleter!.complete(event);
_nextResponseCompleter = null;
});
}

/// Initialize an [IsolateMirrorMacroExecutor] and return it once ready.
///
/// Spawns the macro isolate and sets up a communication channel.
static Future<MacroExecutor> start() async {
var receivePort = ReceivePort();
var sendPortCompleter = Completer<SendPort>();
var responseStreamController =
StreamController<GenericResponse>(sync: true);
receivePort.listen((message) {
if (!sendPortCompleter.isCompleted) {
sendPortCompleter.complete(message as SendPort);
} else {
responseStreamController.add(message as GenericResponse);
}
}).onDone(responseStreamController.close);
var macroIsolate = await Isolate.spawn(spawn, receivePort.sendPort);

return IsolateMirrorMacroExecutor._(
macroIsolate,
await sendPortCompleter.future,
responseStreamController.stream,
receivePort.close);
}

@override
Future<String> buildAugmentationLibrary(
Iterable<MacroExecutionResult> macroResults) {
// TODO: implement buildAugmentationLibrary
throw UnimplementedError();
}

@override
void close() {
_macroIsolate.kill();
}

@override
Future<MacroExecutionResult> executeDeclarationsPhase(
MacroInstanceIdentifier macro,
Declaration declaration,
TypeComparator typeComparator,
ClassIntrospector classIntrospector) {
// TODO: implement executeDeclarationsPhase
throw UnimplementedError();
}

@override
Future<MacroExecutionResult> executeDefinitionsPhase(
MacroInstanceIdentifier macro,
Declaration declaration,
TypeComparator typeComparator,
ClassIntrospector classIntrospector,
TypeIntrospector typeIntrospector) =>
_sendRequest(ExecuteDefinitionsPhaseRequest(macro, declaration,
Copy link
Contributor

Choose a reason for hiding this comment

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

We cannot send TypeComparator for example to another isolate, can we? It potentially a large data structure that references a big portion of analyzer or CFE. We probably have to marshal requests back and forth.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct - a more real implementation would I think have its own internal rpcs to handle this type of introspection - which would delegate on the executor end of things to these objects. In fact in my actual original prototype that is exactly what it does.

This is just a starting point, more illustrative of how things are expected to work together than anything else at the moment.

typeComparator, classIntrospector, typeIntrospector));

@override
Future<MacroExecutionResult> executeTypesPhase(
MacroInstanceIdentifier macro, Declaration declaration) {
// TODO: implement executeTypesPhase
throw UnimplementedError();
}

@override
Future<MacroInstanceIdentifier> instantiateMacro(
MacroClassIdentifier macroClass,
String constructor,
Arguments arguments) =>
_sendRequest(InstantiateMacroRequest(macroClass, constructor, arguments));

@override
Future<MacroClassIdentifier> loadMacro(Uri library, String name) =>
_sendRequest(LoadMacroRequest(library, name));

/// Sends a request and returns the response, casting it to the expected
/// type.
Future<T> _sendRequest<T>(Object request) async {
_sendPort.send(request);
var next = _nextResponseCompleter = Completer<GenericResponse<T>>();
var response = await next.future;
var result = response.response;
if (result != null) return result;
throw response.error!;
}
}
Loading