-
Notifications
You must be signed in to change notification settings - Fork 214
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
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 00b27df
code review fixes
jakemac53 fafae7a
remove outdated comment
jakemac53 992d95b
add skeleton
jakemac53 9113b2b
fix typo
jakemac53 58082b6
add basic loadMacro support
jakemac53 400507c
add a basic test and fix a cast
jakemac53 b30b095
add support for instantiating macros
jakemac53 cf53bd1
implement basic execution of definition macros
jakemac53 9be4958
remove package config constructor
jakemac53 402b572
Merge branch 'protocol' into protocol-impl
jakemac53 25da18e
remove unused import
jakemac53 f623f71
code review updates
jakemac53 3eec82a
code review updates
jakemac53 878b792
Merge branch 'protocol' into protocol-impl
jakemac53 7fc8d52
support multiple requests in flight at a time
jakemac53 65f93a8
fix optional positional parameter filter
jakemac53 fdb4ef4
add StaticType class, move type checker apis to it
jakemac53 b5c6cbe
Merge branch 'protocol' into protocol-impl
jakemac53 732b6f0
update to lastest protocol
jakemac53 6f94cd9
futurify some apis
jakemac53 2d111cc
Merge branch 'protocol' into protocol-impl
jakemac53 1b8618b
update api
jakemac53 888d77d
instantiate changes: require typeArguments an isNullabe, document tha…
jakemac53 857b296
Merge branch 'protocol' into protocol-impl
jakemac53 7e1cdf5
Merge branch 'master' into protocol-impl
jakemac53 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,8 @@ | |
*.vcxproj.filters | ||
/*.vcxproj.user | ||
*.stamp | ||
.dart_tool | ||
.packages | ||
|
||
# Gyp generated files | ||
*.xcodeproj | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
126
working/macros/api/src/protocol/isolate_mirror_executor.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
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!; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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.