Support for element model tests with macros.
Change-Id: Ief2397b79aec91ff975f15d731ef251be9d6e075 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/237859 Reviewed-by: Samuel Rawlins <srawlins@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
committed by
Commit Bot
parent
9a8b63d6fa
commit
7e7b271586
@@ -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() {
|
||||
|
||||
@@ -37,6 +37,7 @@ Future<LinkResult> link(
|
||||
await linker.link(inputLibraries);
|
||||
return LinkResult(
|
||||
resolutionBytes: linker.resolutionBytes,
|
||||
macroGeneratedUnits: linker.macroGeneratedUnits,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,6 +63,8 @@ class Linker {
|
||||
|
||||
late Uint8List resolutionBytes;
|
||||
|
||||
final List<LinkMacroGeneratedUnit> 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<LinkMacroGeneratedUnit> macroGeneratedUnits;
|
||||
|
||||
LinkResult({
|
||||
required this.resolutionBytes,
|
||||
required this.macroGeneratedUnits,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -129,4 +129,6 @@ class MacroLibrary {
|
||||
required this.path,
|
||||
required this.classes,
|
||||
});
|
||||
|
||||
String get uriStr => uri.toString();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<Set<String>>? 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<MacroLibrary> 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<LinkInputLibrary> inputLibraries,
|
||||
List<Set<String>>? uriStrSetList,
|
||||
) {
|
||||
if (uriStrSetList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var uriStrSet in uriStrSetList) {
|
||||
var cycleInputLibraries = <LinkInputLibrary>[];
|
||||
var macroLibraries = <MacroLibrary>[];
|
||||
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<MacroLibrary> macroLibraries,
|
||||
LinkInputLibrary inputLibrary,
|
||||
) {
|
||||
var macroClasses = <MacroClass>[];
|
||||
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<ConstructorDeclaration>().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;
|
||||
|
||||
|
||||
@@ -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<void> setUp() async {
|
||||
writeTestPackageConfig(
|
||||
PackageConfigFileBuilder(),
|
||||
macrosEnvironment: MacrosEnvironment.instance,
|
||||
);
|
||||
|
||||
macroKernelBuilder = DartRepositoryMacroKernelBuilder(
|
||||
MacrosEnvironment.instance.platformDillBytes,
|
||||
);
|
||||
|
||||
macroExecutor = macro.MultiMacroExecutor();
|
||||
}
|
||||
|
||||
Future<void> 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<void> 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user