blob: e544e2931eae9e52c17c0d1bb4275a831d5f1514 [file] [log] [blame]
// 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.
import 'dart:async';
import '../api.dart';
import '../executor.dart';
import 'builder_impls.dart';
import 'exception_impls.dart';
import 'introspection_impls.dart';
/// Runs [macro] in the types phase and returns a [MacroExecutionResult].
Future<MacroExecutionResult> executeTypesMacro(
Macro macro, Object target, TypePhaseIntrospector introspector) async {
// Must be assigned, used for error reporting.
late final TypeBuilderBase builder;
// TODO(jakemac): More robust handling for unawaited async errors?
try {
// Shared code for most branches. If we do create it, assign it to
// `builder`.
late final TypeBuilderImpl typeBuilder =
builder = TypeBuilderImpl(introspector);
switch ((target, macro)) {
case (Library target, LibraryTypesMacro macro):
await macro.buildTypesForLibrary(target, typeBuilder);
case (ConstructorDeclaration target, ConstructorTypesMacro macro):
await macro.buildTypesForConstructor(target, typeBuilder);
case (MethodDeclaration target, MethodTypesMacro macro):
await macro.buildTypesForMethod(target, typeBuilder);
case (FunctionDeclaration target, FunctionTypesMacro macro):
await macro.buildTypesForFunction(target, typeBuilder);
case (FieldDeclaration target, FieldTypesMacro macro):
await macro.buildTypesForField(target, typeBuilder);
case (VariableDeclaration target, VariableTypesMacro macro):
await macro.buildTypesForVariable(target, typeBuilder);
case (ClassDeclaration target, ClassTypesMacro macro):
await macro.buildTypesForClass(
target,
builder = ClassTypeBuilderImpl(
target.identifier as IdentifierImpl, introspector));
case (EnumDeclaration target, EnumTypesMacro macro):
await macro.buildTypesForEnum(
target,
builder = EnumTypeBuilderImpl(
target.identifier as IdentifierImpl, introspector));
case (ExtensionDeclaration target, ExtensionTypesMacro macro):
await macro.buildTypesForExtension(target, typeBuilder);
case (ExtensionTypeDeclaration target, ExtensionTypeTypesMacro macro):
await macro.buildTypesForExtensionType(target, typeBuilder);
case (MixinDeclaration target, MixinTypesMacro macro):
await macro.buildTypesForMixin(
target,
builder = MixinTypeBuilderImpl(
target.identifier as IdentifierImpl, introspector));
case (EnumValueDeclaration target, EnumValueTypesMacro macro):
await macro.buildTypesForEnumValue(target, typeBuilder);
case (TypeAliasDeclaration target, TypeAliasTypesMacro macro):
await macro.buildTypesForTypeAlias(target, typeBuilder);
default:
throw UnsupportedError('Unsupported macro type or invalid target:\n'
'macro: $macro\ntarget: $target');
}
} catch (e, s) {
_handleError(e, s, builder);
}
return builder.result;
}
/// Runs [macro] in the declaration phase and returns a [MacroExecutionResult].
Future<MacroExecutionResult> executeDeclarationsMacro(Macro macro,
Object target, DeclarationPhaseIntrospector introspector) async {
// Must be assigned, used for error reporting.
late final DeclarationBuilderBase builder;
// At most one of these will be used below.
late MemberDeclarationBuilderImpl memberBuilder =
builder = MemberDeclarationBuilderImpl(
switch (target) {
MemberDeclaration() => target.definingType as IdentifierImpl,
TypeDeclarationImpl() => target.identifier,
_ => throw StateError(
'Can only create member declaration builders for types or '
'member declarations, but got $target'),
},
introspector);
late DeclarationBuilderImpl topLevelBuilder =
builder = DeclarationBuilderImpl(introspector);
late EnumDeclarationBuilderImpl enumBuilder =
builder = EnumDeclarationBuilderImpl(
switch (target) {
EnumDeclarationImpl() => target.identifier,
EnumValueDeclarationImpl() => target.definingEnum,
_ => throw StateError(
'Can only create enum declaration builders for enum or enum '
'value declarations, but got $target'),
},
introspector);
// TODO(jakemac): More robust handling for unawaited async errors?
try {
switch ((target, macro)) {
case (Library target, LibraryDeclarationsMacro macro):
await macro.buildDeclarationsForLibrary(target, topLevelBuilder);
case (ClassDeclaration target, ClassDeclarationsMacro macro):
await macro.buildDeclarationsForClass(target, memberBuilder);
case (EnumDeclaration target, EnumDeclarationsMacro macro):
await macro.buildDeclarationsForEnum(target, enumBuilder);
case (ExtensionDeclaration target, ExtensionDeclarationsMacro macro):
await macro.buildDeclarationsForExtension(target, memberBuilder);
case (
ExtensionTypeDeclaration target,
ExtensionTypeDeclarationsMacro macro
):
await macro.buildDeclarationsForExtensionType(target, memberBuilder);
case (MixinDeclaration target, MixinDeclarationsMacro macro):
await macro.buildDeclarationsForMixin(target, memberBuilder);
case (EnumValueDeclaration target, EnumValueDeclarationsMacro macro):
await macro.buildDeclarationsForEnumValue(target, enumBuilder);
case (ConstructorDeclaration target, ConstructorDeclarationsMacro macro):
await macro.buildDeclarationsForConstructor(target, memberBuilder);
case (MethodDeclaration target, MethodDeclarationsMacro macro):
await macro.buildDeclarationsForMethod(target, memberBuilder);
case (FieldDeclaration target, FieldDeclarationsMacro macro):
await macro.buildDeclarationsForField(target, memberBuilder);
case (FunctionDeclaration target, FunctionDeclarationsMacro macro):
await macro.buildDeclarationsForFunction(target, topLevelBuilder);
case (VariableDeclaration target, VariableDeclarationsMacro macro):
await macro.buildDeclarationsForVariable(target, topLevelBuilder);
case (TypeAliasDeclaration target, TypeAliasDeclarationsMacro macro):
await macro.buildDeclarationsForTypeAlias(target, topLevelBuilder);
default:
throw UnsupportedError('Unsupported macro type or invalid target:\n'
'macro: $macro\ntarget: $target');
}
} catch (e, s) {
_handleError(e, s, builder);
}
return builder.result;
}
/// Runs [macro] in the definition phase and returns a [MacroExecutionResult].
Future<MacroExecutionResult> executeDefinitionMacro(Macro macro, Object target,
DefinitionPhaseIntrospector introspector) async {
// Must be assigned, used for error reporting and returning a value.
late final DefinitionBuilderBase builder;
// At most one of these will be used below.
late FunctionDefinitionBuilderImpl functionBuilder = builder =
FunctionDefinitionBuilderImpl(
target as FunctionDeclarationImpl, introspector);
late VariableDefinitionBuilderImpl variableBuilder = builder =
VariableDefinitionBuilderImpl(
target as VariableDeclaration, introspector);
late TypeDefinitionBuilderImpl typeBuilder = builder =
TypeDefinitionBuilderImpl(target as TypeDeclaration, introspector);
// TODO(jakemac): More robust handling for unawaited async errors?
try {
switch ((target, macro)) {
case (Library target, LibraryDefinitionMacro macro):
LibraryDefinitionBuilderImpl libraryBuilder =
builder = LibraryDefinitionBuilderImpl(target, introspector);
await macro.buildDefinitionForLibrary(target, libraryBuilder);
case (ClassDeclaration target, ClassDefinitionMacro macro):
await macro.buildDefinitionForClass(target, typeBuilder);
case (EnumDeclaration target, EnumDefinitionMacro macro):
EnumDefinitionBuilderImpl enumBuilder =
builder = EnumDefinitionBuilderImpl(target, introspector);
await macro.buildDefinitionForEnum(target, enumBuilder);
case (ExtensionDeclaration target, ExtensionDefinitionMacro macro):
await macro.buildDefinitionForExtension(target, typeBuilder);
case (
ExtensionTypeDeclaration target,
ExtensionTypeDefinitionMacro macro
):
await macro.buildDefinitionForExtensionType(target, typeBuilder);
case (MixinDeclaration target, MixinDefinitionMacro macro):
await macro.buildDefinitionForMixin(target, typeBuilder);
case (EnumValueDeclaration target, EnumValueDefinitionMacro macro):
EnumValueDefinitionBuilderImpl enumValueBuilder = builder =
EnumValueDefinitionBuilderImpl(
target as EnumValueDeclarationImpl, introspector);
await macro.buildDefinitionForEnumValue(target, enumValueBuilder);
case (ConstructorDeclaration target, ConstructorDefinitionMacro macro):
ConstructorDefinitionBuilderImpl constructorBuilder = builder =
ConstructorDefinitionBuilderImpl(
target as ConstructorDeclarationImpl, introspector);
await macro.buildDefinitionForConstructor(target, constructorBuilder);
case (MethodDeclaration target, MethodDefinitionMacro macro):
await macro.buildDefinitionForMethod(
target as MethodDeclarationImpl, functionBuilder);
case (FieldDeclaration target, FieldDefinitionMacro macro):
await macro.buildDefinitionForField(target, variableBuilder);
case (FunctionDeclaration target, FunctionDefinitionMacro macro):
await macro.buildDefinitionForFunction(target, functionBuilder);
case (VariableDeclaration target, VariableDefinitionMacro macro):
await macro.buildDefinitionForVariable(target, variableBuilder);
default:
throw UnsupportedError('Unsupported macro type or invalid target:\n'
'macro: $macro\ntarget: $target');
}
} catch (e, s) {
_handleError(e, s, builder);
}
return builder.result;
}
/// Handles macro execution errors, specifically handling [DiagnosticException]s
/// and [MacroException]s in the expected ways.
///
/// Also unwraps [ParallelWaitError]s and [AsyncError]s, such that we can
/// recognize properly the nested errors if they are of specially handled types.
void _handleError(
Object error, StackTrace stackTrace, TypeBuilderBase builder) {
switch (error) {
case ParallelWaitError(errors: List<Object?> errors):
_handleErrors(errors, stackTrace, builder);
case ParallelWaitError(errors: (var e1,)):
_handleErrors([e1], stackTrace, builder);
case ParallelWaitError(
errors: (
var e1,
var e2,
)
):
_handleErrors([e1, e2], stackTrace, builder);
case ParallelWaitError(
errors: (
var e1,
var e2,
var e3,
)
):
_handleErrors([e1, e2, e3], stackTrace, builder);
case ParallelWaitError(
errors: (
var e1,
var e2,
var e3,
var e4,
)
):
_handleErrors([e1, e2, e3, e4], stackTrace, builder);
case ParallelWaitError(
errors: (
var e1,
var e2,
var e3,
var e4,
var e5,
)
):
_handleErrors([e1, e2, e3, e4, e5], stackTrace, builder);
case ParallelWaitError(
errors: (
var e1,
var e2,
var e3,
var e4,
var e5,
var e6,
)
):
_handleErrors([e1, e2, e3, e4, e5, e6], stackTrace, builder);
case ParallelWaitError(
errors: (
var e1,
var e2,
var e3,
var e4,
var e5,
var e6,
var e7,
)
):
_handleErrors([e1, e2, e3, e4, e5, e6, e7], stackTrace, builder);
case ParallelWaitError(
errors: (
var e1,
var e2,
var e3,
var e4,
var e5,
var e6,
var e7,
var e8,
)
):
_handleErrors([e1, e2, e3, e4, e5, e6, e7, e8], stackTrace, builder);
case ParallelWaitError(
errors: (
var e1,
var e2,
var e3,
var e4,
var e5,
var e6,
var e7,
var e8,
var e9,
)
):
_handleErrors([e1, e2, e3, e4, e5, e6, e7, e8, e9], stackTrace, builder);
// Unwrap async errors.
case AsyncError():
_handleError(error.error, error.stackTrace, builder);
// Custom diagnostics from macros, these should just be reported.
case DiagnosticException():
builder.report(error.diagnostic);
// Preserve `MacroException`s thrown by SDK tools.
case MacroExceptionImpl():
builder.failWithException(error);
case _:
// Convert exceptions thrown by macro implementations into diagnostics.
builder.report(_unexpectedExceptionDiagnostic(error, stackTrace));
}
}
/// Handles a number of [errors], ignoring null values.
///
/// This is used for parallel wait scenarios such as [Future.wait].
void _handleErrors(
List<Object?> errors, StackTrace outerStackTrace, TypeBuilderBase builder) {
for (var error in errors) {
if (error == null) continue;
// Passing the outerStackTrace here is the best we can do - but most of the
// time `error` will actually be an `AsyncError`, and we will end up using
// that stack trace anyways.
_handleError(error, outerStackTrace, builder);
}
}
// It's a bug in the macro but we need to show something to the user; put the
// debug detail in a context message and suggest reporting to the author.
Diagnostic _unexpectedExceptionDiagnostic(
Object thrown, StackTrace stackTrace) =>
Diagnostic(
DiagnosticMessage(
'Macro application failed due to a bug in the macro.'),
Severity.error,
contextMessages: [
DiagnosticMessage('$thrown\n$stackTrace'),
],
correctionMessage: 'Try reporting the failure to the macro author.');