From 18a5451781a24136362248b56f2017531d42075c Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Wed, 8 Dec 2021 12:06:23 -0800 Subject: [PATCH 1/9] add initial macro expansion protocol --- .gitignore | 2 + working/macros/api/builders.dart | 19 ++-- working/macros/api/expansion_protocol.dart | 109 +++++++++++++++++++++ working/macros/pubspec.yaml | 6 ++ 4 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 working/macros/api/expansion_protocol.dart create mode 100644 working/macros/pubspec.yaml diff --git a/.gitignore b/.gitignore index 48a1d61d7d..4b1fa4460f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ *.vcxproj.filters /*.vcxproj.user *.stamp +.dart_tool +.packages # Gyp generated files *.xcodeproj diff --git a/working/macros/api/builders.dart b/working/macros/api/builders.dart index a2307ec80f..5465c0a858 100644 --- a/working/macros/api/builders.dart +++ b/working/macros/api/builders.dart @@ -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); + + /// Retruns 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. diff --git a/working/macros/api/expansion_protocol.dart b/working/macros/api/expansion_protocol.dart new file mode 100644 index 0000000000..4a94d2e7ed --- /dev/null +++ b/working/macros/api/expansion_protocol.dart @@ -0,0 +1,109 @@ +import 'package:package_config/package_config.dart'; + +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, as well as how (and where) they are written to disk. +abstract class MacroExecutor { + /// Creating an executor will require at least a package config, in order to + /// resolve macros by URI. + MacroExpander(PackageConfig packageConfig); + + /// 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 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 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 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 executeDeclarationsPhase( + MacroInstanceIdentifier macro, + Declaration declaration, + TypeComparator typeComparator); + + /// Runs the definitions phase for [macro] on a given [declaration]. + /// + /// Throws an exception if there is an error executing the macro. + Future 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 buildAugmentationLibrary( + Iterable macroResults); +} + +/// 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 positionalArgs; + + final Map namedArgs; + + Arguments(this.positionalArgs, this.namedArgs); +} + +/// 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 get imports; + + /// Any augmentations that should be applied as a result of executing a macro. + Iterable 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 +} diff --git a/working/macros/pubspec.yaml b/working/macros/pubspec.yaml new file mode 100644 index 0000000000..c1c583bb5e --- /dev/null +++ b/working/macros/pubspec.yaml @@ -0,0 +1,6 @@ +name: macro_proposal +publish_to: none +dev_dependencies: + package_config: ^2.0.0 +environment: + sdk: ">=2.12.0 <3.0.0" From 00b27dfafbb7580ad6a0fdecebbdd5235e0e005e Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Wed, 8 Dec 2021 13:23:20 -0800 Subject: [PATCH 2/9] code review fixes --- working/macros/api/builders.dart | 2 +- working/macros/api/expansion_protocol.dart | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/working/macros/api/builders.dart b/working/macros/api/builders.dart index 5465c0a858..b5fc270cd7 100644 --- a/working/macros/api/builders.dart +++ b/working/macros/api/builders.dart @@ -27,7 +27,7 @@ abstract class TypeComparator { /// 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]. + /// Returns true if [leftType] is an identical type to [rightType]. bool isExactly(TypeAnnotation leftType, TypeAnnotation rightType); } diff --git a/working/macros/api/expansion_protocol.dart b/working/macros/api/expansion_protocol.dart index 4a94d2e7ed..2acf2198cb 100644 --- a/working/macros/api/expansion_protocol.dart +++ b/working/macros/api/expansion_protocol.dart @@ -46,7 +46,8 @@ abstract class MacroExecutor { Future executeDeclarationsPhase( MacroInstanceIdentifier macro, Declaration declaration, - TypeComparator typeComparator); + TypeComparator typeComparator, + ClassIntrospector classIntrospector); /// Runs the definitions phase for [macro] on a given [declaration]. /// @@ -69,11 +70,11 @@ abstract class MacroExecutor { /// 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 positionalArgs; + final List positional; - final Map namedArgs; + final Map named; - Arguments(this.positionalArgs, this.namedArgs); + Arguments(this.positional, this.named); } /// An opaque identifier for a macro class, retrieved by From fafae7a3c79ac149c225333869bb6ae6386ea5ff Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Wed, 8 Dec 2021 13:56:28 -0800 Subject: [PATCH 3/9] remove outdated comment --- working/macros/api/expansion_protocol.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/working/macros/api/expansion_protocol.dart b/working/macros/api/expansion_protocol.dart index 2acf2198cb..55d2788c2f 100644 --- a/working/macros/api/expansion_protocol.dart +++ b/working/macros/api/expansion_protocol.dart @@ -10,7 +10,7 @@ import 'introspection.dart'; /// /// This class more clearly defines the role of a Dart language implementation /// during macro discovery and expansion, and unifies how augmentation libraries -/// are produced, as well as how (and where) they are written to disk. +/// are produced. abstract class MacroExecutor { /// Creating an executor will require at least a package config, in order to /// resolve macros by URI. From 9113b2bf0393c29b9393b003cbfe2b352ef6cecc Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Thu, 9 Dec 2021 08:54:48 -0800 Subject: [PATCH 4/9] fix typo --- working/macros/api/expansion_protocol.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/working/macros/api/expansion_protocol.dart b/working/macros/api/expansion_protocol.dart index 55d2788c2f..04831bd2d5 100644 --- a/working/macros/api/expansion_protocol.dart +++ b/working/macros/api/expansion_protocol.dart @@ -14,7 +14,7 @@ import 'introspection.dart'; abstract class MacroExecutor { /// Creating an executor will require at least a package config, in order to /// resolve macros by URI. - MacroExpander(PackageConfig packageConfig); + MacroExecutor(PackageConfig packageConfig); /// Invoked when an implementation discovers a new macro definition in a /// library, and prepares this executor to run the macro. From 9be4958b83ddcee990a9ff66414cf3d667c2b7df Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Thu, 9 Dec 2021 13:41:08 -0800 Subject: [PATCH 5/9] remove package config constructor --- working/macros/api/expansion_protocol.dart | 6 ------ working/macros/pubspec.yaml | 2 -- 2 files changed, 8 deletions(-) diff --git a/working/macros/api/expansion_protocol.dart b/working/macros/api/expansion_protocol.dart index 04831bd2d5..21f2355eae 100644 --- a/working/macros/api/expansion_protocol.dart +++ b/working/macros/api/expansion_protocol.dart @@ -1,5 +1,3 @@ -import 'package:package_config/package_config.dart'; - import 'builders.dart'; import 'code.dart'; import 'introspection.dart'; @@ -12,10 +10,6 @@ import 'introspection.dart'; /// during macro discovery and expansion, and unifies how augmentation libraries /// are produced. abstract class MacroExecutor { - /// Creating an executor will require at least a package config, in order to - /// resolve macros by URI. - MacroExecutor(PackageConfig packageConfig); - /// Invoked when an implementation discovers a new macro definition in a /// library, and prepares this executor to run the macro. /// diff --git a/working/macros/pubspec.yaml b/working/macros/pubspec.yaml index c1c583bb5e..ffb160bdce 100644 --- a/working/macros/pubspec.yaml +++ b/working/macros/pubspec.yaml @@ -1,6 +1,4 @@ name: macro_proposal publish_to: none -dev_dependencies: - package_config: ^2.0.0 environment: sdk: ">=2.12.0 <3.0.0" From 3eec82afa478e4e9cc37481d119875acb2a480d3 Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Fri, 10 Dec 2021 07:38:27 -0800 Subject: [PATCH 6/9] code review updates --- working/macros/api/expansion_protocol.dart | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/working/macros/api/expansion_protocol.dart b/working/macros/api/expansion_protocol.dart index 21f2355eae..9121b3d087 100644 --- a/working/macros/api/expansion_protocol.dart +++ b/working/macros/api/expansion_protocol.dart @@ -11,7 +11,7 @@ import 'introspection.dart'; /// 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. + /// [library] with [name], 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 @@ -93,12 +93,17 @@ abstract class MacroExecutionResult { Iterable get imports; /// Any augmentations that should be applied as a result of executing a macro. - Iterable get agumentations; + Iterable get augmentations; } /// 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 + /// Only new types are added in this phase. + types, + + /// New non-type declarations are added in this phase. + declarations, + + /// This phase allows augmenting existing declarations. + defintions, } From fdb4ef46f76a6c07e0e66e959770a73b125ce621 Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Tue, 14 Dec 2021 07:40:16 -0800 Subject: [PATCH 7/9] add StaticType class, move type checker apis to it --- working/macros/api/builders.dart | 82 ++++++++++++---------- working/macros/api/expansion_protocol.dart | 6 +- working/macros/api/introspection.dart | 22 +++++- 3 files changed, 68 insertions(+), 42 deletions(-) diff --git a/working/macros/api/builders.dart b/working/macros/api/builders.dart index b5fc270cd7..5dd6456b45 100644 --- a/working/macros/api/builders.dart +++ b/working/macros/api/builders.dart @@ -6,14 +6,7 @@ import 'macros.dart'; // For dart docs. /// The base interface used to add declarations to the program as well /// as augment existing ones. -abstract class Builder { - /// Used to construct a [TypeAnnotation] to a runtime type available to the - /// the macro implementation code. - /// - /// This can be used to emit a reference to it in generated code, or do - /// subtype checks (depending on support in the current phase). - TypeAnnotation typeAnnotationOf(); -} +abstract class Builder {} /// The api used by [Macro]s to contribute new type declarations to the /// current library, and get [TypeAnnotation]s from runtime [Type] objects. @@ -22,33 +15,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. +/// The interface to resolve a [TypeAnnotation] to a [StaticType]. /// -/// Can also be used to do subtype checks on types. -abstract class DeclarationBuilder implements Builder, TypeComparator { - /// Adds a new regular declaration to the surrounding library. - /// - /// Note that type declarations are not supported. - void declareInLibrary(DeclarationCode declaration); -} - -/// The api used by [Macro]s to contribute new members to a class. -abstract class ClassMemberDeclarationBuilder implements DeclarationBuilder { - /// Adds a new declaration to the surrounding class. - void declareInClass(DeclarationCode declaration); +/// The [StaticType]s can be compared against other [StaticType]s to see how +/// they relate to each other. +/// +/// This api is only available to the declaration and definition phases of +/// macro expansion. +abstract class TypeResolver { + /// Resolves [typeAnnotation] to a [StaticType]. + StaticType resolve(TypeAnnotation typeAnnotation); } /// The api used to introspect on a [ClassDeclaration]. +/// +/// Available in the declaration and definition phases, but limited in the +/// declaration phase to immediately annotated [ClassDeclaration]s. This is +/// done by limiting the access to the [TypeDeclarationResolver] to the +/// definition phase. abstract class ClassIntrospector { /// The fields available for [clazz]. /// @@ -78,23 +62,47 @@ abstract class ClassIntrospector { Future> interfacesOf(ClassDeclaration clazz); } +/// 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, TypeResolver, ClassIntrospector { + /// Adds a new regular declaration to the surrounding library. + /// + /// Note that type declarations are not supported. + void declareInLibrary(DeclarationCode declaration); +} + +/// The api used by [Macro]s to contribute new members to a class. +abstract class ClassMemberDeclarationBuilder implements DeclarationBuilder { + /// Adds a new declaration to the surrounding class. + void declareInClass(DeclarationCode declaration); +} + /// The api used by [Macro]s to reflect on the currently available /// members, superclass, and mixins for a given [ClassDeclaration] abstract class ClassDeclarationBuilder implements ClassMemberDeclarationBuilder, ClassIntrospector {} -/// The api used by [Macro] to get a [TypeDeclaration] for any given -/// [TypeAnnotation]. -abstract class TypeIntrospector { - /// Resolves a [NamedTypeAnnotation] to its declaration. - Future resolve(NamedTypeAnnotation annotation); +/// The interface used by [Macro]s to resolve any [NamedStaticType] to its +/// declaration. +/// +/// Only available in the definition phase of macro expansion. +abstract class TypeDeclarationResolver { + /// Resolves a [NamedStaticType] to its [TypeDeclaration]. + Future declarationOf(NamedStaticType annotation); } /// The base class for builders in the definition phase. These can convert /// any [TypeAnnotation] into its corresponding [TypeDeclaration], and also /// reflect more deeply on those. abstract class DefinitionBuilder - implements Builder, ClassIntrospector, TypeIntrospector {} + implements + Builder, + TypeResolver, + ClassIntrospector, + TypeDeclarationResolver {} /// The apis used by [Macro]s that run on classes, to fill in the definitions /// of any external declarations within that class. diff --git a/working/macros/api/expansion_protocol.dart b/working/macros/api/expansion_protocol.dart index 9121b3d087..b95fea1fbe 100644 --- a/working/macros/api/expansion_protocol.dart +++ b/working/macros/api/expansion_protocol.dart @@ -40,7 +40,7 @@ abstract class MacroExecutor { Future executeDeclarationsPhase( MacroInstanceIdentifier macro, Declaration declaration, - TypeComparator typeComparator, + TypeResolver typeResolver, ClassIntrospector classIntrospector); /// Runs the definitions phase for [macro] on a given [declaration]. @@ -49,9 +49,9 @@ abstract class MacroExecutor { Future executeDefinitionsPhase( MacroInstanceIdentifier macro, Declaration declaration, - TypeComparator typeComparator, + TypeResolver typeResolver, ClassIntrospector classIntrospector, - TypeIntrospector typeIntrospector); + TypeDeclarationResolver typeDeclarationResolver); /// Combines multiple [MacroExecutionResult]s into a single library /// augmentation file, and returns a [String] representing that file. diff --git a/working/macros/api/introspection.dart b/working/macros/api/introspection.dart index 47eec863f1..f3992b2ea4 100644 --- a/working/macros/api/introspection.dart +++ b/working/macros/api/introspection.dart @@ -39,6 +39,24 @@ abstract class NamedTypeAnnotation implements TypeAnnotation { Iterable get typeArguments; } +/// The interface representing a resolved type. +/// +/// Resolved types understand exactly what type they represent, and can be +/// compared to other static types. +abstract class StaticType { + /// Returns true if this is a subtype of [other]. + bool isSubtypeOf(StaticType other); + + /// Returns true if this is an identical type to [other]. + bool isExactly(StaticType other); +} + +/// A subtype of [StaticType] representing types that can be resolved by name +/// to a concrete declaration. +abstract class NamedStaticType implements StaticType { + String get name; +} + /// The base class for all declarations. abstract class Declaration { /// The name of this declaration. @@ -50,8 +68,8 @@ abstract class TypeDeclaration implements Declaration { /// The type parameters defined for this type declaration. Iterable get typeParameters; - /// Create a type annotation representing this type with [typeArguments]. - TypeAnnotation instantiate({List typeArguments}); + /// Create a static type representing this type with [typeArguments]. + StaticType instantiate({List typeArguments}); } /// Class (and enum) introspection information. From 6f94cd98eb894912e661cde61d8f6872d7e2cad9 Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Tue, 14 Dec 2021 12:35:36 -0800 Subject: [PATCH 8/9] futurify some apis --- working/macros/api/builders.dart | 8 +++++++- working/macros/api/introspection.dart | 10 +++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/working/macros/api/builders.dart b/working/macros/api/builders.dart index 5dd6456b45..551cfc80bd 100644 --- a/working/macros/api/builders.dart +++ b/working/macros/api/builders.dart @@ -24,7 +24,13 @@ abstract class TypeBuilder implements Builder { /// macro expansion. abstract class TypeResolver { /// Resolves [typeAnnotation] to a [StaticType]. - StaticType resolve(TypeAnnotation typeAnnotation); + /// + /// Throws an error if the type annotation cannot be resolved. This should + /// only happen in the case of incomplete or invalid programs, but macros + /// may be asked to run in this state during the development cycle. It is + /// helpful for users if macros provide a best effort implementation in that + /// case or handle the error in a useful way. + Future resolve(TypeAnnotation typeAnnotation); } /// The api used to introspect on a [ClassDeclaration]. diff --git a/working/macros/api/introspection.dart b/working/macros/api/introspection.dart index f3992b2ea4..9e49f04e9e 100644 --- a/working/macros/api/introspection.dart +++ b/working/macros/api/introspection.dart @@ -45,10 +45,10 @@ abstract class NamedTypeAnnotation implements TypeAnnotation { /// compared to other static types. abstract class StaticType { /// Returns true if this is a subtype of [other]. - bool isSubtypeOf(StaticType other); + Future isSubtypeOf(StaticType other); /// Returns true if this is an identical type to [other]. - bool isExactly(StaticType other); + Future isExactly(StaticType other); } /// A subtype of [StaticType] representing types that can be resolved by name @@ -69,7 +69,11 @@ abstract class TypeDeclaration implements Declaration { Iterable get typeParameters; /// Create a static type representing this type with [typeArguments]. - StaticType instantiate({List typeArguments}); + /// + /// If [isNullable] is `true`, then this type will behave as if it has a + /// trailing `?`. + Future instantiate( + {List typeArguments, bool isNullable = false}); } /// Class (and enum) introspection information. From 888d77deefece1b478a163513ed8024e94f201b4 Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Wed, 15 Dec 2021 08:43:33 -0800 Subject: [PATCH 9/9] instantiate changes: require typeArguments an isNullabe, document that it may throw --- working/macros/api/introspection.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/working/macros/api/introspection.dart b/working/macros/api/introspection.dart index 9e49f04e9e..8a48b09f07 100644 --- a/working/macros/api/introspection.dart +++ b/working/macros/api/introspection.dart @@ -72,8 +72,12 @@ abstract class TypeDeclaration implements Declaration { /// /// If [isNullable] is `true`, then this type will behave as if it has a /// trailing `?`. + /// + /// Throws an exception if the type could not be instantiated, typically due + /// to one of the type arguments not matching the bounds of the corresponding + /// type parameter. Future instantiate( - {List typeArguments, bool isNullable = false}); + {required List typeArguments, required bool isNullable}); } /// Class (and enum) introspection information.