From 7e7b271586f1b09da071da66b70b526d311607a7 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Mon, 11 Apr 2022 19:31:23 +0000 Subject: [PATCH] Support for element model tests with macros. Change-Id: Ief2397b79aec91ff975f15d731ef251be9d6e075 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/237859 Reviewed-by: Samuel Rawlins Reviewed-by: Brian Wilkerson Commit-Queue: Konstantin Shcheglov --- .../lib/src/summary2/library_builder.dart | 11 +- pkg/analyzer/lib/src/summary2/link.dart | 17 ++ pkg/analyzer/lib/src/summary2/macro.dart | 2 + .../lib/src/summary2/macro_application.dart | 3 +- .../test/src/summary/elements_base.dart | 179 +++++++++++++++++- pkg/analyzer/test/src/summary/macro_test.dart | 112 +++++++++++ 6 files changed, 320 insertions(+), 4 deletions(-) diff --git a/pkg/analyzer/lib/src/summary2/library_builder.dart b/pkg/analyzer/lib/src/summary2/library_builder.dart index db1a3ed4473..3f1f4a4f847 100644 --- a/pkg/analyzer/lib/src/summary2/library_builder.dart +++ b/pkg/analyzer/lib/src/summary2/library_builder.dart @@ -203,7 +203,8 @@ class LibraryBuilder { lineInfo: parseResult.lineInfo, ) ..enclosingElement = element - ..isSynthetic = true; + ..isSynthetic = true + ..uri = unitUri.toString(); var unitReference = reference.getChild('@unit').getChild('$unitUri'); _bindReference(unitReference, unitElement); @@ -224,6 +225,14 @@ class LibraryBuilder { element: unitElement, ), ); + + linker.macroGeneratedUnits.add( + LinkMacroGeneratedUnit( + uri: unitUri, + content: parseResult.content, + unit: parseResult.unit, + ), + ); } void resolveConstructors() { diff --git a/pkg/analyzer/lib/src/summary2/link.dart b/pkg/analyzer/lib/src/summary2/link.dart index e7c7a536e19..3afe40b733a 100644 --- a/pkg/analyzer/lib/src/summary2/link.dart +++ b/pkg/analyzer/lib/src/summary2/link.dart @@ -37,6 +37,7 @@ Future link( await linker.link(inputLibraries); return LinkResult( resolutionBytes: linker.resolutionBytes, + macroGeneratedUnits: linker.macroGeneratedUnits, ); } @@ -62,6 +63,8 @@ class Linker { late Uint8List resolutionBytes; + final List macroGeneratedUnits = []; + Linker(this.elementFactory, this.macroExecutor); AnalysisContextImpl get analysisContext { @@ -293,10 +296,24 @@ class LinkInputUnit { String get uriStr => '$uri'; } +class LinkMacroGeneratedUnit { + final Uri uri; + final String content; + final ast.CompilationUnit unit; + + LinkMacroGeneratedUnit({ + required this.uri, + required this.content, + required this.unit, + }); +} + class LinkResult { final Uint8List resolutionBytes; + final List macroGeneratedUnits; LinkResult({ required this.resolutionBytes, + required this.macroGeneratedUnits, }); } diff --git a/pkg/analyzer/lib/src/summary2/macro.dart b/pkg/analyzer/lib/src/summary2/macro.dart index e9fa100e329..74f3692e034 100644 --- a/pkg/analyzer/lib/src/summary2/macro.dart +++ b/pkg/analyzer/lib/src/summary2/macro.dart @@ -129,4 +129,6 @@ class MacroLibrary { required this.path, required this.classes, }); + + String get uriStr => uri.toString(); } diff --git a/pkg/analyzer/lib/src/summary2/macro_application.dart b/pkg/analyzer/lib/src/summary2/macro_application.dart index e1988a5fc4c..5ef25c04cf7 100644 --- a/pkg/analyzer/lib/src/summary2/macro_application.dart +++ b/pkg/analyzer/lib/src/summary2/macro_application.dart @@ -68,11 +68,12 @@ class LibraryMacroApplier { var macroExecutor = libraryBuilder.linker.macroExecutor; if (macroExecutor != null && macroResults.isNotEmpty) { - return macroExecutor.buildAugmentationLibrary( + var code = macroExecutor.buildAugmentationLibrary( macroResults, _resolveIdentifier, _inferOmittedType, ); + return code.trim(); } return null; } diff --git a/pkg/analyzer/test/src/summary/elements_base.dart b/pkg/analyzer/test/src/summary/elements_base.dart index f3d209592a7..d0e24164b60 100644 --- a/pkg/analyzer/test/src/summary/elements_base.dart +++ b/pkg/analyzer/test/src/summary/elements_base.dart @@ -4,12 +4,15 @@ import 'dart:typed_data'; +import 'package:_fe_analyzer_shared/src/macros/executor/multi_executor.dart' + as macro; import 'package:analyzer/dart/analysis/declared_variables.dart'; import 'package:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/src/context/context.dart'; import 'package:analyzer/src/dart/analysis/session.dart'; +import 'package:analyzer/src/dart/ast/ast.dart'; import 'package:analyzer/src/dart/element/class_hierarchy.dart'; import 'package:analyzer/src/dart/element/element.dart'; import 'package:analyzer/src/dart/element/inheritance_manager3.dart'; @@ -22,10 +25,12 @@ import 'package:analyzer/src/summary2/bundle_reader.dart'; import 'package:analyzer/src/summary2/informative_data.dart'; import 'package:analyzer/src/summary2/link.dart'; import 'package:analyzer/src/summary2/linked_element_factory.dart'; +import 'package:analyzer/src/summary2/macro.dart'; import 'package:analyzer/src/summary2/reference.dart'; import 'package:analyzer/src/test_utilities/mock_sdk.dart'; import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart'; import 'package:analyzer/src/util/uri.dart'; +import 'package:path/path.dart' as package_path; import 'package:test_reflective_loader/test_reflective_loader.dart'; import '../../util/feature_sets.dart'; @@ -37,6 +42,9 @@ abstract class ElementsBaseTest with ResourceProviderMixin { /// The shared SDK bundle, computed once and shared among test invocations. static _SdkBundle? _sdkBundle; + MacroKernelBuilder? macroKernelBuilder; + macro.MultiMacroExecutor? macroExecutor; + /// The set of features enabled in this test. FeatureSet featureSet = FeatureSets.latestWithExperiments; @@ -113,7 +121,9 @@ abstract class ElementsBaseTest with ResourceProviderMixin { String get testPackageLibPath => '$testPackageRootPath/lib'; - String get testPackageRootPath => '/home/test'; + String get testPackageRootPath => '$workspaceRootPath/test'; + + String get workspaceRootPath => '/home'; void addSource(String path, String contents) { newFile2(path, contents); @@ -123,6 +133,7 @@ abstract class ElementsBaseTest with ResourceProviderMixin { String text, { bool allowErrors = false, bool dumpSummaries = false, + List>? preBuildSequence, }) async { var testFile = newFile2(testFilePath, text); var testUri = sourceFactory.pathToUri(testFile.path)!; @@ -160,7 +171,22 @@ abstract class ElementsBaseTest with ResourceProviderMixin { ), ); - var linkResult = await link(elementFactory, inputLibraries); + _linkConfiguredLibraries( + elementFactory, + inputLibraries, + preBuildSequence, + ); + + var linkResult = await link( + elementFactory, + inputLibraries, + macroExecutor: macroExecutor, + ); + + for (var macroUnit in linkResult.macroGeneratedUnits) { + var informativeBytes = writeUnitInformative(macroUnit.unit); + unitsInformativeBytes[macroUnit.uri] = informativeBytes; + } if (!keepLinkingLibraries) { elementFactory.removeBundle( @@ -279,6 +305,79 @@ abstract class ElementsBaseTest with ResourceProviderMixin { } } + /// If there are any [macroLibraries], build the kernel and prepare for + /// execution. + void _buildMacroLibraries( + LinkedElementFactory elementFactory, + List macroLibraries, + ) { + if (macroLibraries.isEmpty) { + return; + } + + final macroKernelBuilder = this.macroKernelBuilder; + if (macroKernelBuilder == null) { + return; + } + + final macroExecutor = this.macroExecutor; + if (macroExecutor == null) { + return; + } + + var macroKernelBytes = macroKernelBuilder.build( + fileSystem: _MacroFileSystem(resourceProvider), + libraries: macroLibraries, + ); + + var bundleMacroExecutor = BundleMacroExecutor( + macroExecutor: macroExecutor, + kernelBytes: macroKernelBytes, + libraries: macroLibraries.map((e) => e.uri).toSet(), + ); + + for (var macroLibrary in macroLibraries) { + var uriStr = macroLibrary.uriStr; + var element = elementFactory.libraryOfUri2(uriStr); + element.bundleMacroExecutor = bundleMacroExecutor; + } + } + + /// If there are any libraries in the [uriStrSetList], link these subsets + /// of [inputLibraries] (and remove from it), build macro kernels, prepare + /// for executing macros. + void _linkConfiguredLibraries( + LinkedElementFactory elementFactory, + List inputLibraries, + List>? uriStrSetList, + ) { + if (uriStrSetList == null) { + return; + } + + for (var uriStrSet in uriStrSetList) { + var cycleInputLibraries = []; + var macroLibraries = []; + for (var inputLibrary in inputLibraries) { + if (uriStrSet.contains(inputLibrary.uriStr)) { + cycleInputLibraries.add(inputLibrary); + _addMacroLibrary(macroLibraries, inputLibrary); + } + } + + link( + elementFactory, + cycleInputLibraries, + macroExecutor: macroExecutor, + ); + + _buildMacroLibraries(elementFactory, macroLibraries); + + // Remove libraries that we just linked. + cycleInputLibraries.forEach(inputLibraries.remove); + } + } + String _readSafely(String path) { try { var file = resourceProvider.getFile(path); @@ -287,6 +386,53 @@ abstract class ElementsBaseTest with ResourceProviderMixin { return ''; } } + + /// If there are any macros in the [inputLibrary], add it. + static void _addMacroLibrary( + List macroLibraries, + LinkInputLibrary inputLibrary, + ) { + var macroClasses = []; + for (var inputUnit in inputLibrary.units) { + for (var declaration in inputUnit.unit.declarations) { + if (declaration is ClassDeclarationImpl && + declaration.macroKeyword != null) { + var constructors = + declaration.members.whereType().toList(); + if (constructors.isEmpty) { + macroClasses.add( + MacroClass( + name: declaration.name.name, + constructors: [''], + ), + ); + } else { + var constructorNames = constructors + .map((e) => e.name?.name ?? '') + .where((e) => !e.startsWith('_')) + .toList(); + if (constructorNames.isNotEmpty) { + macroClasses.add( + MacroClass( + name: declaration.name.name, + constructors: constructorNames, + ), + ); + } + } + } + } + } + if (macroClasses.isNotEmpty) { + macroLibraries.add( + MacroLibrary( + uri: inputLibrary.uri, + path: inputLibrary.source.fullName, + classes: macroClasses, + ), + ); + } + } } class _AnalysisSessionForLinking implements AnalysisSessionImpl { @@ -300,6 +446,35 @@ class _AnalysisSessionForLinking implements AnalysisSessionImpl { noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } +/// [MacroFileEntry] adapter for [File]. +class _MacroFileEntry implements MacroFileEntry { + final File file; + + _MacroFileEntry(this.file); + + @override + String get content => file.readAsStringSync(); + + @override + bool get exists => file.exists; +} + +/// [MacroFileSystem] adapter for [ResourceProvider]. +class _MacroFileSystem implements MacroFileSystem { + final ResourceProvider resourceProvider; + + _MacroFileSystem(this.resourceProvider); + + @override + package_path.Context get pathContext => resourceProvider.pathContext; + + @override + MacroFileEntry getFile(String path) { + var file = resourceProvider.getFile(path); + return _MacroFileEntry(file); + } +} + class _SdkBundle { final Uint8List resolutionBytes; diff --git a/pkg/analyzer/test/src/summary/macro_test.dart b/pkg/analyzer/test/src/summary/macro_test.dart index be36b5a3a3d..bd7e3fe5279 100644 --- a/pkg/analyzer/test/src/summary/macro_test.dart +++ b/pkg/analyzer/test/src/summary/macro_test.dart @@ -2,12 +2,23 @@ // 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/executor/multi_executor.dart' + as macro; +import 'package:analyzer/src/test_utilities/package_config_file_builder.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'element_text.dart'; import 'elements_base.dart'; +import 'repository_macro_kernel_builder.dart'; main() { + try { + MacrosEnvironment.instance; + } catch (_) { + print('Cannot initialize environment. Skip macros tests.'); + return; + } + defineReflectiveSuite(() { defineReflectiveTests(MacroElementsKeepLinkingTest); defineReflectiveTests(MacroElementsFromBytesTest); @@ -30,6 +41,79 @@ class MacroElementsTest extends ElementsBaseTest { @override bool get keepLinkingLibraries => false; + /// The path for external packages. + String get packagesRootPath => '/packages'; + + Future setUp() async { + writeTestPackageConfig( + PackageConfigFileBuilder(), + macrosEnvironment: MacrosEnvironment.instance, + ); + + macroKernelBuilder = DartRepositoryMacroKernelBuilder( + MacrosEnvironment.instance.platformDillBytes, + ); + + macroExecutor = macro.MultiMacroExecutor(); + } + + Future tearDown() async { + await macroExecutor?.close(); + } + + test_build_types() async { + newFile2('$testPackageLibPath/a.dart', r''' +import 'dart:async'; +import 'package:_fe_analyzer_shared/src/macros/api.dart'; + +macro class MyMacro implements ClassTypesMacro { + FutureOr buildTypesForClass(clazz, builder) { + builder.declareType( + 'MyClass', + DeclarationCode.fromString('class MyClass {}'), + ); + } +} +'''); + + var library = await buildLibrary(r''' +import 'a.dart'; + +@MyMacro() +class A {} +''', preBuildSequence: [ + {'package:test/a.dart'} + ]); + + checkElementText(library, r''' +library + imports + package:test/a.dart + definingUnit + classes + class A @35 + metadata + Annotation + atSign: @ @18 + name: SimpleIdentifier + token: MyMacro @19 + staticElement: package:test/a.dart::@class::MyMacro + staticType: null + arguments: ArgumentList + leftParenthesis: ( @26 + rightParenthesis: ) @27 + element: package:test/a.dart::@class::MyMacro::@constructor::• + constructors + synthetic @-1 + parts + package:test/_macro_types.dart + classes + class MyClass @6 + constructors + synthetic @-1 +'''); + } + test_class_macro() async { var library = await buildLibrary(r''' macro class A {} @@ -72,4 +156,32 @@ library Object '''); } + + void writeTestPackageConfig( + PackageConfigFileBuilder config, { + MacrosEnvironment? macrosEnvironment, + }) { + config = config.copy(); + + config.add( + name: 'test', + rootPath: testPackageRootPath, + ); + + if (macrosEnvironment != null) { + var packagesRootFolder = getFolder(packagesRootPath); + macrosEnvironment.packageSharedFolder.copyTo(packagesRootFolder); + config.add( + name: '_fe_analyzer_shared', + rootPath: getFolder('$packagesRootPath/_fe_analyzer_shared').path, + ); + } + + newPackageConfigJsonFile( + testPackageRootPath, + config.toContent( + toUriStr: toUriStr, + ), + ); + } }