dart macros チートシート
環境
Flutter 3.22.0-36.0.pre.1
Dart 3.5.0 (build 3.5.0-154.0.dev)
interface class Macro
※Doc参照
Every macro interface is a subtype of a root Macro marker interface. There are interfaces for each kind of declaration macros can be applied to: class, function, etc. Then, for each of those, there is an interface for each macro phase: type, declaration, and definition. A single macro class can implement as many of these interfaces as it wants to. This allows a single macro to participate in multiple phases and to support being applied to multiple kinds of declarations.
- すべての
Macro Interface
はroot Macro
,marker interface
サブタイプである - macro classを生成する場合に最低でも一つ、上記サブタイプを適用し、単一メソッド宣言 / 実装する必要がある
- また、サブタイプは複数宣言することも可能
interface Macro sub type
interface Macroのサブタイプは複数存在している
主に以下が用意されている
- library
- Function
- Variable
- Class
- Enum
- EnumValue
- Field
- Method
- Constructor
- Mixin
- Extension
- ExtensionType
- TypeAlias
ここに、各々以下が用意されている
-
TypesMacro
- 新しい型宣言を追加したい場合
-
DeclarationsMacro
- 新しい非型宣言を追加したい場合
-
DefinitionMacro
- {サブタイプ}内の宣言の定義を提供する
※library
だったら以下のように定義されている
/// The interface for [Macro]s that can be applied to a library directive, and
/// want to contribute new type declarations to the library.
abstract interface class LibraryTypesMacro implements Macro {
FutureOr<void> buildTypesForLibrary(Library library, TypeBuilder builder);
}
/// The interface for [Macro]s that can be applied to a library directive, and
/// want to contribute new non-type declarations to the library.
abstract interface class LibraryDeclarationsMacro implements Macro {
FutureOr<void> buildDeclarationsForLibrary(
Library library, DeclarationBuilder builder);
}
/// The interface for [Macro]s that can be applied to a library directive, and
/// want to provide definitions for declarations in the library.
abstract interface class LibraryDefinitionMacro implements Macro {
FutureOr<void> buildDefinitionForLibrary(
Library library, LibraryDefinitionBuilder builder);
}
argument
- ここでいう引数とは
interface Macro
で宣言されているメソッドの引数- 上記例の
buildDefinitionForLibrary
のLibrary library
,LibraryDefinitionBuilder builder
の部分
- 上記例の
第一引数(Declaration argument)
The first argument to a builder method is an object describing the declaration it is applied to. This argument contains only essentially the parsed AST for the declaration itself, and does not include nested declarations.
For example, in ClassDeclarationsMacro, the introspection object is a ClassDeclaration. This gives you access to the name of the class and access to the immediate superclass, as well as any immediate mixins or interfaces, but not its members or entire class hierarchy.
- 適応される宣言を記述できるオブジェクト
- クラスの名前 / 直接のスーパークラス / 直接のミックスインやインターフェースにアクセスが可能
- ただそのメンバーやクラス階層全体にはアクセスできない
例
class Sample implements ClassDeclarationsMacro {
const Sample();
Future<FutureOr<void>> buildDeclarationsForClass(
ClassDeclaration classDeclaration, MemberDeclarationBuilder builder,
) async {
// macroを適用したクラスのクラス名を取得
final className = classDeclaration.identifier.name;
// クラス内で宣言した変数 / 定数の一覧を取得
final fields = await builder.fieldsOf(classDeclaration);
// クラス内で宣言した変数 / 定数の型を取得
final fieldType = (field.type.code as NamedTypeAnnotationCode).name.name;
// ...
// ...
}
ClassDeclarationの親子関係
ClassDeclaration
に限らず、interface Macro
のサブタイプに応じて~Declaration
が用意されている、が今回はClassDeclaration
に焦点を置く
- 階層
.
└── Annotatable / MacroTarget
└── Declaration
└── TypeDeclaration
└── ParameterizedTypeDeclaration
└── ClassDeclaration
基本となるベースはDeclaration
とされている
Declaration
から、library
/ identifier
にアクセスが可能となり、フィールド情報やクラス情報などを取得参照できるようになる。
※その他は、サブタイプに紐づいた(marker interfaceや型パラメーターを持つ)interface
となっている
ClassDeclarationのfield
/// このクラスに `external` 修飾子があるかどうか。
bool get hasExternal;
/// このクラスに `final` 修飾子があるかどうか。
bool get hasFinal;
/// このクラスに `interface` 修飾子があるかどうか。
bool get hasInterface;
/// このクラスに `mixin` 修飾子があるかどうか。
bool get hasMixin;
/// このクラスに `sealed` 修飾子があるかどうか。
bool get hasSealed;
/// `extends` 型注釈 (存在する場合)。
NamedTypeAnnotation? get superclass;
/// すべての `implements` 型注釈。
Iterable<NamedTypeAnnotation> get interfaces;
/// すべての `with` 型注釈。
Iterable<NamedTypeAnnotation> get mixins;
第二引数(Builder argument)
The second argument is an instance of a builder class. It exposes both methods to contribute new code to the program, as well as phase specific introspection capabilities.
In ClassDeclarationsMacro, the builder is a ClassDeclarationBuilder. Its primary method is declareInClass, which the macro can call to add a new member to the class. It also implements the ClassIntrospector interface, which allows you to get the members of the class, as well as its entire class hierarchy.
- プログラムに新しいコードを追加するメソッドと、フェーズ固有のイントロスペクション機能の両方を公開する
- 要は、
Declaration argument
で取得したオブジェクトの詳細情報を取得するメソッドなどを提供してくれている- ※先ほどの例の
fieldsOf
のようなもの
- ※先ほどの例の
-
builder
は~~PhaseIntrospector
を継承したオブジェクトとなっているため、そこに準拠した機能を使用することができる
- 要は、
※fieldsOf
を例に紐解くと
/// The fields available for [type].
///
/// This may be incomplete if additional declaration macros are going to run
/// on [type].
Future<List<FieldDeclaration>> fieldsOf(covariant TypeDeclaration type);
このTypeDeclaration
というのがmarker Interface
のこと。
marker Interface
は第一引数のオブジェクトが継承している大元のinterface
となり、
builder
インスタンスのメソッド(fieldsOf
など)を使用することにより、フィールド情報を参照することが可能となる。
MemberDeclarationBuilderの親子関係
~Declaration
と同様サブタイプに応じて~DeclarationBuilder
が用意されている
.
└── TypePhaseIntrospector
└── Builder / DeclarationPhaseIntrospector
└── DeclarationBuilder
└── MemberDeclarationBuilder
基本となるベースはDeclarationPhaseIntrospector
となっている
※DeclarationPhaseIntrospector
の他にもTypePhaseIntrospector
/ DefinitionPhaseIntrospector
も用意されており、interface Macro
に応じて使用するinterfaceが異なっている
DeclarationPhaseIntrospectorのfield
/// 指定された [type] アノテーションの新しい [StaticType] をインスタンス化します。
///
/// [type] が [RawTypeAnnotationCode] の場合、生の [Identifier] は許可されていないため、より具体的なサブタイプを使用する必要がある
Future<StaticType> resolve(TypeAnnotationCode type);
/// 引数[enuum] で使用可能な値
/// - enum情報を取得することができる
Future<List<EnumValueDeclaration>> valuesOf(covariant EnumDeclaration enuum);
/// 引数[type] で使用できるフィールド
/// - フィールド情報を取得することができる
Future<List<FieldDeclaration>> fieldsOf(covariant TypeDeclaration type);
/// 引数[type] で使用できるメソッド。
/// - メソッド情報を取得することができる
Future<List<MethodDeclaration>> methodsOf(covariant TypeDeclaration type);
/// 引数[type] で使用できるコンストラクター
/// - constructor情報を取得することができる
Future<List<ConstructorDeclaration>>constructorsOf(
covariant TypeDeclaration type);
/// [library] で宣言されているすべての型の [TypeDeclaration]が取得参照できる
Future<List<TypeDeclaration>> typesOf(covariant Library library);
/// 引数[identifier] を [TypeDeclaration] に解決 / 変換
///
/// [identifier] が [TypeDeclaration] に解決 / 変換されない場合は、
/// [MacroImplementationException] がスローされる
Future<TypeDeclaration> typeDeclarationOf(covariant Identifier identifier);
TypeAnnotation
型参照インターフェース
/// null(?)が含まれているかどうか
bool get isNullable;
/// sealed classである[code]オブジェクトを取得する
TypeAnnotationCode get code;
TypeAnnotationCode
Code
を継承したインターフェース。
サブタイプでは、TypeAnnotationCode
を継承。
// 型注釈の null 可能バージョンを表すコード
// `underlyingType`でnull可能な方を取得する
NullableTypeAnnotationCode
// 名前付き型の参照を表すコード
// `name`で型名を取得
NamedTypeAnnotationCode
// 関数名を取得するコード
FunctionTypeAnnotationCode
// レコード型名を取得するコード
RecordTypeAnnotationCode
// 省略された型注釈のコード
OmittedTypeAnnotationCode
Library
ライブラリのイントロスペクション情報
// ライブラリの言語バージョン
LanguageVersion get languageVersion;
// ライブラリを識別するURI
Uri get uri;
LanguageVersion
// メジャーバージョン
int get major;
// マイナーバージョン
int get minor;
参考
Discussion