blob: 3b906dc6e204812311f6581384c801af110595da [file] [log] [blame]
// Copyright (c) 2021, 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:io' show Directory, Platform;
import 'package:_fe_analyzer_shared/src/macros/api.dart';
import 'package:_fe_analyzer_shared/src/macros/executor.dart';
import 'package:_fe_analyzer_shared/src/macros/executor/multi_executor.dart';
import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
import 'package:_fe_analyzer_shared/src/testing/features.dart';
import 'package:_fe_analyzer_shared/src/testing/id.dart' show ActualData, Id;
import 'package:_fe_analyzer_shared/src/testing/id_testing.dart';
import 'package:front_end/src/api_prototype/compiler_options.dart';
import 'package:front_end/src/api_prototype/experimental_flags.dart';
import 'package:front_end/src/fasta/builder/class_builder.dart';
import 'package:front_end/src/fasta/builder/library_builder.dart';
import 'package:front_end/src/fasta/builder/member_builder.dart';
import 'package:front_end/src/fasta/kernel/macro/macro.dart';
import 'package:front_end/src/testing/id_testing_helper.dart';
import 'package:front_end/src/testing/id_testing_utils.dart';
import 'package:kernel/ast.dart' hide Arguments;
Future<void> main(List<String> args) async {
enableMacros = true;
Directory dataDir =
new Directory.fromUri(Platform.script.resolve('data/tests'));
await runTests<Features>(dataDir,
args: args,
createUriForFileName: createUriForFileName,
onFailure: onFailure,
runTest: runTestFor(const MacroDataComputer(), [new MacroTestConfig()]));
}
class MacroTestConfig extends TestConfig {
MacroTestConfig()
: super(cfeMarker, 'cfe',
explicitExperimentalFlags: {ExperimentalFlag.macros: true},
packageConfigUri:
Platform.script.resolve('data/package_config.json'));
@override
TestMacroExecutor customizeCompilerOptions(
CompilerOptions options, TestData testData) {
TestMacroExecutor testExecutor =
options.macroExecutor = new TestMacroExecutor();
testExecutor.registerExecutorFactory(() => testExecutor,
{Uri.parse('package:precompiled_macro/precompiled_macro.dart')});
return testExecutor;
}
}
class MacroDataComputer extends DataComputer<Features> {
const MacroDataComputer();
@override
void computeMemberData(TestResultData testResultData, Member member,
Map<Id, ActualData<Features>> actualMap,
{bool? verbose}) {
member.accept(new MacroDataExtractor(testResultData, actualMap));
}
@override
void computeClassData(TestResultData testResultData, Class cls,
Map<Id, ActualData<Features>> actualMap,
{bool? verbose}) {
new MacroDataExtractor(testResultData, actualMap).computeForClass(cls);
}
@override
void computeLibraryData(TestResultData testResultData, Library library,
Map<Id, ActualData<Features>> actualMap,
{bool? verbose}) {
new MacroDataExtractor(testResultData, actualMap)
.computeForLibrary(library);
}
@override
bool get supportsErrors => true;
@override
Features? computeErrorData(
TestResultData testResultData, Id id, List<FormattedMessage> errors) {
Features features = new Features();
features[Tags.error] = errorsToText(errors, useCodes: true);
return features;
}
@override
DataInterpreter<Features> get dataValidator =>
const FeaturesDataInterpreter();
}
class Tags {
static const String macrosAreAvailable = 'macrosAreAvailable';
static const String macrosAreApplied = 'macrosAreApplied';
static const String compilationSequence = 'compilationSequence';
static const String neededPrecompilations = 'neededPrecompilations';
static const String declaredMacros = 'declaredMacros';
static const String appliedMacros = 'appliedMacros';
static const String macroInstanceIds = 'macroInstanceIds';
static const String error = 'error';
}
String constructorNameToString(String constructorName) {
return constructorName == '' ? 'new' : constructorName;
}
String importUriToString(Uri importUri) {
if (importUri.isScheme('package')) {
return importUri.toString();
} else if (importUri.isScheme('dart')) {
return importUri.toString();
} else {
return importUri.pathSegments.last;
}
}
String libraryToString(Library library) => importUriToString(library.importUri);
String strongComponentToString(Iterable<Uri> uris) {
List<String> list = uris.map(importUriToString).toList();
list.sort();
return list.join('|');
}
class MacroDataExtractor extends CfeDataExtractor<Features> {
final TestResultData testResultData;
MacroDataExtractor(
this.testResultData, Map<Id, ActualData<Features>> actualMap)
: super(testResultData.compilerResult, actualMap);
TestMacroExecutor get macroExecutor => testResultData.customData;
MacroDeclarationData get macroDeclarationData => testResultData.compilerResult
.kernelTargetForTesting!.loader.dataForTesting!.macroDeclarationData;
MacroApplicationDataForTesting get macroApplicationData => testResultData
.compilerResult
.kernelTargetForTesting!
.loader
.dataForTesting!
.macroApplicationData;
LibraryMacroApplicationData? getLibraryMacroApplicationData(Library library) {
for (MapEntry<LibraryBuilder, LibraryMacroApplicationData> entry
in macroApplicationData.libraryData.entries) {
if (entry.key.library == library) {
return entry.value;
}
}
return null;
}
ClassMacroApplicationData? getClassMacroApplicationData(Class cls) {
LibraryMacroApplicationData? applicationData =
getLibraryMacroApplicationData(cls.enclosingLibrary);
if (applicationData != null) {
for (MapEntry<ClassBuilder, ClassMacroApplicationData> entry
in applicationData.classData.entries) {
if (entry.key.cls == cls) {
return entry.value;
}
}
}
return null;
}
List<MacroApplication>? getClassMacroApplications(Class cls) {
return getClassMacroApplicationData(cls)
?.classApplications
?.macroApplications;
}
List<MacroApplication>? getMemberMacroApplications(Member member) {
Class? enclosingClass = member.enclosingClass;
Map<MemberBuilder, ApplicationData>? memberApplications;
if (enclosingClass != null) {
memberApplications =
getClassMacroApplicationData(enclosingClass)?.memberApplications;
} else {
memberApplications =
getLibraryMacroApplicationData(member.enclosingLibrary)
?.memberApplications;
}
if (memberApplications != null) {
for (MapEntry<MemberBuilder, ApplicationData> entry
in memberApplications.entries) {
if (entry.key.member == member) {
return entry.value.macroApplications;
}
}
}
return null;
}
void registerMacroApplications(
Features features, List<MacroApplication>? macroApplications) {
if (macroApplications != null) {
for (MacroApplication application in macroApplications) {
features.addElement(Tags.appliedMacros, application.toString());
}
}
}
@override
Features computeClassValue(Id id, Class node) {
Features features = new Features();
if (getClassMacroApplicationData(node) != null) {
features.add(Tags.macrosAreApplied);
}
registerMacroApplications(features, getClassMacroApplications(node));
return features;
}
@override
Features computeLibraryValue(Id id, Library node) {
Features features = new Features();
if (macroDeclarationData.macrosAreAvailable) {
features.add(Tags.macrosAreAvailable);
}
if (node == compilerResult.component!.mainMethod!.enclosingLibrary) {
if (macroDeclarationData.compilationSequence != null) {
features.markAsUnsorted(Tags.compilationSequence);
for (List<Uri> component in macroDeclarationData.compilationSequence!) {
features.addElement(
Tags.compilationSequence, strongComponentToString(component));
}
}
for (Map<Uri, Map<String, List<String>>> precompilation
in macroDeclarationData.neededPrecompilations) {
Map<String, Map<String, List<String>>> converted =
new Map.fromIterables(precompilation.keys.map(importUriToString),
precompilation.values);
List<String> uris = converted.keys.toList()..sort();
StringBuffer sb = new StringBuffer();
for (String uri in uris) {
sb.write(uri);
sb.write('=');
Map<String, List<String>> macros = converted[uri]!;
List<String> classes = macros.keys.toList()..sort();
String delimiter = '';
for (String cls in classes) {
List<String> constructorNames =
macros[cls]!.map(constructorNameToString).toList()..sort();
sb.write(delimiter);
sb.write(cls);
sb.write('(');
sb.write(constructorNames.join('/'));
sb.write(')');
delimiter = '|';
}
}
features.addElement(Tags.neededPrecompilations, sb.toString());
}
for (_MacroInstanceIdentifier id in macroExecutor.macroInstances) {
features.addElement(Tags.macroInstanceIds, id.toText());
}
}
List<String>? macroClasses =
macroDeclarationData.macroDeclarations[node.importUri];
if (macroClasses != null) {
for (String clsName in macroClasses) {
features.addElement(Tags.declaredMacros, clsName);
}
}
if (getLibraryMacroApplicationData(node) != null) {
features.add(Tags.macrosAreApplied);
}
return features;
}
@override
Features computeMemberValue(Id id, Member node) {
Features features = new Features();
registerMacroApplications(features, getMemberMacroApplications(node));
return features;
}
}
class TestMacroExecutor extends MultiMacroExecutor {
List<_MacroInstanceIdentifier> macroInstances = [];
@override
String buildAugmentationLibrary(
Iterable<MacroExecutionResult> macroResults,
ResolvedIdentifier Function(Identifier) resolveIdentifier,
TypeAnnotation? Function(OmittedTypeAnnotation) inferOmittedType,
{Map<OmittedTypeAnnotation, String>? omittedTypes}) {
return '';
}
@override
Future<void> close() async {
// TODO: implement close
}
@override
Future<MacroExecutionResult> executeDeclarationsPhase(
MacroInstanceIdentifier macro,
Declaration declaration,
IdentifierResolver identifierResolver,
TypeResolver typeResolver,
ClassIntrospector classIntrospector) async {
return new _MacroExecutionResult();
}
@override
Future<MacroExecutionResult> executeDefinitionsPhase(
MacroInstanceIdentifier macro,
Declaration declaration,
IdentifierResolver identifierResolver,
TypeResolver typeResolver,
ClassIntrospector classIntrospector,
TypeDeclarationResolver typeDeclarationResolver,
TypeInferrer typeInferrer) async {
return new _MacroExecutionResult();
}
@override
Future<MacroExecutionResult> executeTypesPhase(MacroInstanceIdentifier macro,
Declaration declaration, IdentifierResolver identifierResolver) async {
return new _MacroExecutionResult();
}
@override
Future<MacroInstanceIdentifier> instantiateMacro(
Uri library, String name, String constructor, Arguments arguments) async {
_MacroInstanceIdentifier id =
new _MacroInstanceIdentifier(library, name, constructor, arguments);
macroInstances.add(id);
return id;
}
}
class _MacroInstanceIdentifier implements MacroInstanceIdentifier {
final Uri library;
final String name;
final String constructor;
final Arguments arguments;
_MacroInstanceIdentifier(
this.library, this.name, this.constructor, this.arguments);
String toText() => '${importUriToString(library)}/${name}/'
'${constructor}${arguments.toText()}';
@override
void serialize(Serializer serializer) => throw UnimplementedError();
@override
bool shouldExecute(DeclarationKind declarationKind, Phase phase) => false;
@override
bool supportsDeclarationKind(DeclarationKind declarationKind) => false;
}
class _MacroExecutionResult implements MacroExecutionResult {
@override
Map<String, Iterable<DeclarationCode>> classAugmentations = const {};
@override
Iterable<DeclarationCode> libraryAugmentations = const [];
@override
Iterable<String> newTypeNames = const [];
@override
void serialize(Serializer serializer) {
throw UnimplementedError();
}
}
extension on Arguments {
String toText() {
StringBuffer sb = new StringBuffer();
sb.write('(');
String comma = '';
for (Object? positional in positional) {
sb.write(comma);
sb.write(positional);
comma = ',';
}
for (MapEntry<String, Object?> named in named.entries) {
sb.write(comma);
sb.write(named.key);
sb.write(':');
sb.write(named.value);
comma = ',';
}
sb.write(')');
return sb.toString();
}
}