blob: 82605933715061078b3f59d51f9c7a463f70a53b [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 'package:_fe_analyzer_shared/src/macros/api.dart' as macro;
import 'package:_fe_analyzer_shared/src/macros/executor.dart' as macro;
import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart'
as macro;
import 'package:_fe_analyzer_shared/src/macros/executor/multi_executor.dart';
import 'package:_fe_analyzer_shared/src/macros/executor/protocol.dart' as macro;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/summary2/library_builder.dart';
import 'package:analyzer/src/summary2/link.dart';
import 'package:analyzer/src/summary2/linked_element_factory.dart';
import 'package:analyzer/src/summary2/macro_application_error.dart';
import 'package:analyzer/src/summary2/macro_declarations.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
class LibraryMacroApplier {
final DeclarationBuilder declarationBuilder;
final LibraryBuilder libraryBuilder;
final MultiMacroExecutor macroExecutor;
final List<_MacroTarget> _targets = [];
late final macro.IdentifierResolver _identifierResolver =
_IdentifierResolver(_linker.elementFactory, declarationBuilder);
late final macro.TypeDeclarationResolver _typeDeclarationResolver =
_TypeDeclarationResolver(declarationBuilder);
late final macro.TypeIntrospector _typeIntrospector =
_TypeIntrospector(declarationBuilder);
late final macro.TypeResolver _typeResolver = _TypeResolver(
typeSystem: libraryBuilder.element.typeSystem,
);
LibraryMacroApplier({
required this.macroExecutor,
required this.declarationBuilder,
required this.libraryBuilder,
});
Linker get _linker => libraryBuilder.linker;
/// Fill [_targets]s with macro applications.
Future<void> buildApplications({
required OperationPerformanceImpl performance,
}) async {
final collector = _MacroTargetElementCollector();
libraryBuilder.element.accept(collector);
for (final targetElement in collector.targets) {
final targetNode = _linker.elementNodes[targetElement];
// TODO(scheglov) support other declarations
if (targetNode is ClassDeclaration) {
await performance.runAsync(
'forClassDeclaration',
(performance) async {
await _buildApplications(
targetElement,
targetNode.metadata,
macro.DeclarationKind.clazz,
() => declarationBuilder.fromNode.classDeclaration(targetNode),
performance: performance,
);
},
);
}
}
}
Future<String?> executeDeclarationsPhase() async {
final results = <macro.MacroExecutionResult>[];
for (final target in _targets) {
for (final application in target.applications) {
if (application.shouldExecute(macro.Phase.declarations)) {
await _runWithCatchingExceptions(
() async {
final result = await application.executeDeclarationsPhase();
if (result.isNotEmpty) {
results.add(result);
}
},
annotationIndex: application.annotationIndex,
onError: (error) {
target.element.macroApplicationErrors.add(error);
},
);
}
}
}
return _buildAugmentationLibrary(results);
}
Future<String?> executeTypesPhase() async {
final results = <macro.MacroExecutionResult>[];
for (final target in _targets) {
for (final application in target.applications) {
if (application.shouldExecute(macro.Phase.types)) {
await _runWithCatchingExceptions(
() async {
final result = await application.executeTypesPhase();
if (result.isNotEmpty) {
results.add(result);
}
},
annotationIndex: application.annotationIndex,
onError: (error) {
target.element.macroApplicationErrors.add(error);
},
);
}
}
}
return _buildAugmentationLibrary(results);
}
/// If there are any macro applications in [annotations], add a new
/// element into [_targets].
Future<void> _buildApplications(
MacroTargetElement targetElement,
List<Annotation> annotations,
macro.DeclarationKind declarationKind,
macro.DeclarationImpl Function() getDeclaration, {
required OperationPerformanceImpl performance,
}) async {
final applications = <_MacroApplication>[];
for (var i = 0; i < annotations.length; i++) {
Future<macro.MacroInstanceIdentifier?> instantiateSingle({
required ClassElementImpl macroClass,
required String constructorName,
required ArgumentList argumentsNode,
}) async {
final importedLibrary = macroClass.library;
final macroExecutor = importedLibrary.bundleMacroExecutor;
if (macroExecutor != null) {
return await _runWithCatchingExceptions(
() async {
final arguments = _buildArguments(
annotationIndex: i,
node: argumentsNode,
);
return await performance.runAsync('instantiate', (_) {
return macroExecutor.instantiate(
libraryUri: macroClass.librarySource.uri,
className: macroClass.name,
constructorName: constructorName,
arguments: arguments,
);
});
},
annotationIndex: i,
onError: (error) {
targetElement.macroApplicationErrors.add(error);
},
);
}
return null;
}
final annotation = annotations[i];
final macroInstance = await _importedMacroDeclaration(
annotation,
whenClass: ({
required macroClass,
required constructorName,
}) async {
final argumentsNode = annotation.arguments;
if (argumentsNode != null) {
return await instantiateSingle(
macroClass: macroClass,
constructorName: constructorName ?? '',
argumentsNode: argumentsNode,
);
}
},
whenGetter: ({
required macroClass,
required instanceCreation,
}) async {
return await instantiateSingle(
macroClass: macroClass,
constructorName: instanceCreation.constructorName.name?.name ?? '',
argumentsNode: instanceCreation.argumentList,
);
},
);
if (macroInstance != null) {
applications.add(
_MacroApplication(
annotationIndex: i,
instanceIdentifier: macroInstance,
),
);
}
}
if (applications.isNotEmpty) {
_targets.add(
_MacroTarget(
applier: this,
element: targetElement,
declarationKind: declarationKind,
declaration: getDeclaration(),
applications: applications,
),
);
}
}
/// If there are any [results], builds the augmentation library with them.
String? _buildAugmentationLibrary(
List<macro.MacroExecutionResult> results,
) {
if (results.isEmpty) {
return null;
}
final code = macroExecutor.buildAugmentationLibrary(
results,
_resolveIdentifier,
_inferOmittedType,
);
return code.trim();
}
/// If [annotation] references a macro, invokes the right callback.
Future<R?> _importedMacroDeclaration<R>(
Annotation annotation, {
required Future<R?> Function({
required ClassElementImpl macroClass,
required String? constructorName,
})
whenClass,
required Future<R?> Function({
required ClassElementImpl macroClass,
required InstanceCreationExpression instanceCreation,
})
whenGetter,
}) async {
final String? prefix;
final String name;
final String? constructorName;
final nameNode = annotation.name;
if (nameNode is SimpleIdentifier) {
prefix = null;
name = nameNode.name;
constructorName = annotation.constructorName?.name;
} else if (nameNode is PrefixedIdentifier) {
final importPrefixCandidate = nameNode.prefix.name;
final hasImportPrefix = libraryBuilder.element.imports
.any((import) => import.prefix?.name == importPrefixCandidate);
if (hasImportPrefix) {
prefix = importPrefixCandidate;
name = nameNode.identifier.name;
constructorName = annotation.constructorName?.name;
} else {
prefix = null;
name = nameNode.prefix.name;
constructorName = nameNode.identifier.name;
}
} else {
throw StateError('${nameNode.runtimeType} $nameNode');
}
for (final import in libraryBuilder.element.imports) {
if (import.prefix?.name != prefix) {
continue;
}
final importedLibrary = import.importedLibrary;
if (importedLibrary == null) {
continue;
}
// Skip if a library that is being linked.
final importedUri = importedLibrary.source.uri;
if (_linker.builders.containsKey(importedUri)) {
continue;
}
final lookupResult = importedLibrary.scope.lookup(name);
final element = lookupResult.getter;
if (element is ClassElementImpl) {
if (element.isMacro) {
return await whenClass(
macroClass: element,
constructorName: constructorName,
);
}
} else if (element is PropertyAccessorElementImpl &&
element.isGetter &&
element.isSynthetic) {
final variable = element.variable;
final variableType = variable.type;
if (variable is ConstTopLevelVariableElementImpl &&
variableType is InterfaceType) {
final macroClass = variableType.element;
final initializer = variable.constantInitializer;
if (macroClass is ClassElementImpl &&
macroClass.isMacro &&
initializer is InstanceCreationExpression) {
return await whenGetter(
macroClass: macroClass,
instanceCreation: initializer,
);
}
}
}
}
return null;
}
macro.TypeAnnotation _inferOmittedType(
macro.OmittedTypeAnnotation omittedType,
) {
throw UnimplementedError();
}
macro.ResolvedIdentifier _resolveIdentifier(macro.Identifier identifier) {
throw UnimplementedError();
}
static macro.Arguments _buildArguments({
required int annotationIndex,
required ArgumentList node,
}) {
final positional = <Object?>[];
final named = <String, Object?>{};
for (var i = 0; i < node.arguments.length; ++i) {
final argument = node.arguments[i];
final evaluation = _ArgumentEvaluation(
annotationIndex: annotationIndex,
argumentIndex: i,
);
if (argument is NamedExpression) {
final value = evaluation.evaluate(argument.expression);
named[argument.name.label.name] = value;
} else {
final value = evaluation.evaluate(argument);
positional.add(value);
}
}
return macro.Arguments(positional, named);
}
/// Run the [body], report exceptions as [MacroApplicationError]s to [onError].
static Future<T?> _runWithCatchingExceptions<T>(
Future<T> Function() body, {
required int annotationIndex,
required void Function(MacroApplicationError) onError,
}) async {
try {
return await body();
} on MacroApplicationError catch (e) {
onError(e);
} on macro.RemoteException catch (e) {
onError(
UnknownMacroApplicationError(
annotationIndex: annotationIndex,
message: e.error,
stackTrace: e.stackTrace ?? '<null>',
),
);
} catch (e, stackTrace) {
onError(
UnknownMacroApplicationError(
annotationIndex: annotationIndex,
message: e.toString(),
stackTrace: stackTrace.toString(),
),
);
}
return null;
}
}
/// Helper class for evaluating arguments for a single constructor based
/// macro application.
class _ArgumentEvaluation {
final int annotationIndex;
final int argumentIndex;
_ArgumentEvaluation({
required this.annotationIndex,
required this.argumentIndex,
});
Object? evaluate(Expression node) {
if (node is AdjacentStrings) {
return node.strings.map(evaluate).join('');
} else if (node is BooleanLiteral) {
return node.value;
} else if (node is DoubleLiteral) {
return node.value;
} else if (node is IntegerLiteral) {
return node.value;
} else if (node is ListLiteral) {
return node.elements.cast<Expression>().map(evaluate).toList();
} else if (node is NullLiteral) {
return null;
} else if (node is PrefixExpression &&
node.operator.type == TokenType.MINUS) {
final operandValue = evaluate(node.operand);
if (operandValue is double) {
return -operandValue;
} else if (operandValue is int) {
return -operandValue;
}
} else if (node is SetOrMapLiteral) {
return _setOrMapLiteral(node);
} else if (node is SimpleStringLiteral) {
return node.value;
}
_throwError(node, 'Not supported: ${node.runtimeType}');
}
Object _setOrMapLiteral(SetOrMapLiteral node) {
if (node.elements.every((e) => e is Expression)) {
final result = <Object?>{};
for (final element in node.elements) {
if (element is! Expression) {
_throwError(element, 'Expression expected');
}
final value = evaluate(element);
result.add(value);
}
return result;
}
final result = <Object?, Object?>{};
for (final element in node.elements) {
if (element is! MapLiteralEntry) {
_throwError(element, 'MapLiteralEntry expected');
}
final key = evaluate(element.key);
final value = evaluate(element.value);
result[key] = value;
}
return result;
}
Never _throwError(AstNode node, String message) {
throw ArgumentMacroApplicationError(
annotationIndex: annotationIndex,
argumentIndex: argumentIndex,
message: message,
);
}
}
class _IdentifierResolver extends macro.IdentifierResolver {
final LinkedElementFactory elementFactory;
final DeclarationBuilder declarationBuilder;
_IdentifierResolver(
this.elementFactory,
this.declarationBuilder,
);
@override
Future<macro.Identifier> resolveIdentifier(Uri library, String name) async {
final libraryElement = elementFactory.libraryOfUri2(library);
final element = libraryElement.scope.lookup(name).getter!;
return declarationBuilder.fromElement.identifier(element);
}
}
class _MacroApplication {
late final _MacroTarget target;
final int annotationIndex;
final macro.MacroInstanceIdentifier instanceIdentifier;
_MacroApplication({
required this.annotationIndex,
required this.instanceIdentifier,
});
Future<macro.MacroExecutionResult> executeDeclarationsPhase() async {
final applier = target.applier;
final executor = applier.macroExecutor;
return await executor.executeDeclarationsPhase(
instanceIdentifier,
target.declaration,
applier._identifierResolver,
applier._typeDeclarationResolver,
applier._typeResolver,
applier._typeIntrospector,
);
}
Future<macro.MacroExecutionResult> executeTypesPhase() async {
final applier = target.applier;
final executor = applier.macroExecutor;
return await executor.executeTypesPhase(
instanceIdentifier,
target.declaration,
applier._identifierResolver,
);
}
bool shouldExecute(macro.Phase phase) {
return instanceIdentifier.shouldExecute(target.declarationKind, phase);
}
}
class _MacroTarget {
final LibraryMacroApplier applier;
final MacroTargetElement element;
final macro.DeclarationKind declarationKind;
final macro.DeclarationImpl declaration;
final List<_MacroApplication> applications;
_MacroTarget({
required this.applier,
required this.element,
required this.declarationKind,
required this.declaration,
required this.applications,
}) {
for (final application in applications) {
application.target = this;
}
}
}
class _MacroTargetElementCollector extends GeneralizingElementVisitor<void> {
final List<MacroTargetElement> targets = [];
@override
void visitElement(covariant ElementImpl element) {
if (element is MacroTargetElement) {
targets.add(element as MacroTargetElement);
}
if (element is MacroTargetElementContainer) {
element.visitChildren(this);
}
}
}
class _StaticTypeImpl extends macro.StaticType {
final TypeSystemImpl typeSystem;
final DartType type;
_StaticTypeImpl(this.typeSystem, this.type);
@override
Future<bool> isExactly(_StaticTypeImpl other) {
// TODO: implement isExactly
throw UnimplementedError();
}
@override
Future<bool> isSubtypeOf(_StaticTypeImpl other) {
// TODO(scheglov) write tests
return Future.value(
typeSystem.isSubtypeOf(type, other.type),
);
}
}
class _TypeDeclarationResolver implements macro.TypeDeclarationResolver {
final DeclarationBuilder declarationBuilder;
_TypeDeclarationResolver(this.declarationBuilder);
@override
Future<macro.TypeDeclaration> declarationOf(
covariant IdentifierImpl identifier,
) async {
final element = identifier.element;
if (element is ClassElementImpl) {
return declarationBuilder.fromElement.classElement(element);
} else {
throw ArgumentError('element: $element');
}
}
}
class _TypeIntrospector implements macro.TypeIntrospector {
final DeclarationBuilder declarationBuilder;
_TypeIntrospector(this.declarationBuilder);
@override
Future<List<macro.ConstructorDeclaration>> constructorsOf(
covariant macro.IntrospectableType type) {
// TODO: implement constructorsOf
throw UnimplementedError();
}
@override
Future<List<macro.FieldDeclaration>> fieldsOf(
covariant macro.IntrospectableType type,
) async {
if (type is! IntrospectableClassDeclarationImpl) {
throw UnsupportedError('Only introspection on classes is supported');
}
return type.element.fields
.where((e) => !e.isSynthetic)
.map(declarationBuilder.fromElement.fieldElement)
.toList();
}
@override
Future<List<macro.MethodDeclaration>> methodsOf(
covariant macro.IntrospectableType clazz) {
// TODO: implement methodsOf
throw UnimplementedError();
}
}
class _TypeResolver implements macro.TypeResolver {
final TypeSystemImpl typeSystem;
_TypeResolver({
required this.typeSystem,
});
@override
Future<macro.StaticType> resolve(macro.TypeAnnotationCode type) async {
var dartType = _resolve(type);
return _StaticTypeImpl(typeSystem, dartType);
}
DartType _resolve(macro.TypeAnnotationCode type) {
// TODO(scheglov) write tests
if (type is macro.NamedTypeAnnotationCode) {
final identifier = type.name as IdentifierImpl;
final element = identifier.element;
if (element is ClassElementImpl) {
return element.instantiate(
typeArguments: type.typeArguments.map(_resolve).toList(),
nullabilitySuffix: type.isNullable
? NullabilitySuffix.question
: NullabilitySuffix.none,
);
} else {
// TODO(scheglov) Implement other elements.
throw UnimplementedError('(${element.runtimeType}) $element');
}
} else {
// TODO(scheglov) Implement other types.
throw UnimplementedError('(${type.runtimeType}) $type');
}
}
}
extension on macro.MacroExecutionResult {
bool get isNotEmpty =>
libraryAugmentations.isNotEmpty || classAugmentations.isNotEmpty;
}