Support for top-level getters that are instances of macros.

Change-Id: Ieda42930afa965ddc5170cea2755eb2559b8f34a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243381
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Konstantin Shcheglov 2022-05-03 00:19:26 +00:00 committed by Commit Bot
parent 706e62a1bf
commit 404e568787
2 changed files with 227 additions and 38 deletions

View file

@ -12,6 +12,7 @@ import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart'
as macro;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/summary2/library_builder.dart';
import 'package:analyzer/src/summary2/link.dart';
@ -22,7 +23,7 @@ class LibraryMacroApplier {
final MultiMacroExecutor macroExecutor;
final LibraryBuilder libraryBuilder;
final Map<MacroTargetElement, List<MacroApplication>> _applications =
final Map<MacroTargetElement, List<_MacroApplication>> _applications =
Map.identity();
final Map<ClassDeclaration, macro.ClassDeclaration> _classDeclarations = {};
@ -147,36 +148,31 @@ class LibraryMacroApplier {
List<Annotation> annotations,
macro.Declaration Function() getDeclaration,
) async {
final applications = <MacroApplication>[];
final applications = <_MacroApplication>[];
for (var i = 0; i < annotations.length; i++) {
final annotation = annotations[i];
final macroElement = _importedMacroElement(annotation.name);
final argumentsNode = annotation.arguments;
if (macroElement is ClassElementImpl && argumentsNode != null) {
final importedLibrary = macroElement.library;
Future<MacroClassInstance?> instantiateSingle({
required ClassElementImpl macroClass,
required String constructorName,
required ArgumentList argumentsNode,
}) async {
final importedLibrary = macroClass.library;
final macroExecutor = importedLibrary.bundleMacroExecutor;
if (macroExecutor != null) {
await _runWithCatchingExceptions(
return await _runWithCatchingExceptions(
() async {
final arguments = _buildArguments(
annotationIndex: i,
node: argumentsNode,
);
final declaration = getDeclaration();
final macroInstance = await macroExecutor.instantiate(
libraryUri: macroElement.librarySource.uri,
className: macroElement.name,
constructorName: '', // TODO
return await macroExecutor.instantiate(
libraryUri: macroClass.librarySource.uri,
className: macroClass.name,
constructorName: constructorName,
arguments: arguments,
identifierResolver: _IdentifierResolver(),
declarationKind: macro.DeclarationKind.clazz,
declaration: declaration,
);
applications.add(
MacroApplication(
annotationIndex: i,
instance: macroInstance,
),
declaration: getDeclaration(),
);
},
annotationIndex: i,
@ -185,6 +181,43 @@ class LibraryMacroApplier {
},
);
}
return null;
}
final annotation = annotations[i];
final macroInstance = await _importedMacroDeclaration(
annotation.name,
whenClass: ({
required macroClass,
}) async {
final argumentsNode = annotation.arguments;
if (argumentsNode != null) {
return await instantiateSingle(
macroClass: macroClass,
constructorName: '', // TODO(scheglov) implement
argumentsNode: argumentsNode,
);
}
},
whenGetter: ({
required macroClass,
required instanceCreation,
}) async {
return await instantiateSingle(
macroClass: macroClass,
constructorName: '', // TODO(scheglov) implement
argumentsNode: instanceCreation.argumentList,
);
},
);
if (macroInstance != null) {
applications.add(
_MacroApplication(
annotationIndex: i,
instance: macroInstance,
),
);
}
}
if (applications.isNotEmpty) {
@ -192,8 +225,19 @@ class LibraryMacroApplier {
}
}
/// Return the macro element referenced by the [node].
ElementImpl? _importedMacroElement(Identifier node) {
/// If [node] references a macro, invokes the right callback.
Future<R?> _importedMacroDeclaration<R>(
Identifier node, {
required Future<R?> Function({
required ClassElementImpl macroClass,
})
whenClass,
required Future<R?> Function({
required ClassElementImpl macroClass,
required InstanceCreationExpression instanceCreation,
})
whenGetter,
}) async {
final String? prefix;
final String name;
if (node is PrefixedIdentifier) {
@ -224,8 +268,28 @@ class LibraryMacroApplier {
final lookupResult = importedLibrary.scope.lookup(name);
final element = lookupResult.getter;
if (element is ClassElementImpl && element.isMacro) {
return element;
if (element is ClassElementImpl) {
if (element.isMacro) {
return await whenClass(macroClass: element);
}
} 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;
@ -380,13 +444,13 @@ class LibraryMacroApplier {
}
/// Run the [body], report exceptions as [MacroApplicationError]s to [onError].
static Future<void> _runWithCatchingExceptions<T>(
static Future<T?> _runWithCatchingExceptions<T>(
Future<T> Function() body, {
required int annotationIndex,
required void Function(MacroApplicationError) onError,
}) async {
try {
await body();
return await body();
} on MacroApplicationError catch (e) {
onError(e);
} on macro.RemoteException catch (e) {
@ -406,21 +470,10 @@ class LibraryMacroApplier {
),
);
}
return null;
}
}
class MacroApplication {
final int annotationIndex;
final MacroClassInstance instance;
MacroApplication({
required this.annotationIndex,
required this.instance,
});
bool shouldExecute(macro.Phase phase) => instance.shouldExecute(phase);
}
/// Helper class for evaluating arguments for a single constructor based
/// macro application.
class _ArgumentEvaluation {
@ -551,6 +604,18 @@ class _IdentifierResolver extends macro.IdentifierResolver {
}
}
class _MacroApplication {
final int annotationIndex;
final MacroClassInstance instance;
_MacroApplication({
required this.annotationIndex,
required this.instance,
});
bool shouldExecute(macro.Phase phase) => instance.shouldExecute(phase);
}
class _TypeResolver implements macro.TypeResolver {
@override
Future<macro.StaticType> resolve(macro.TypeAnnotationCode type) {

View file

@ -63,6 +63,130 @@ class MacroElementsTest extends ElementsBaseTest {
);
}
test_application_getter_withoutPrefix_withoutArguments() async {
newFile('$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 {}'),
);
}
}
const myMacro = MyMacro();
''');
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 @33
metadata
Annotation
atSign: @ @18
name: SimpleIdentifier
token: myMacro @19
staticElement: package:test/a.dart::@getter::myMacro
staticType: null
element: package:test/a.dart::@getter::myMacro
constructors
synthetic @-1
parts
package:test/_macro_types.dart
classes
class MyClass @6
constructors
synthetic @-1
exportScope
A: package:test/test.dart;A
MyClass: package:test/test.dart;package:test/_macro_types.dart;MyClass
''',
withExportScope: true);
}
test_application_getter_withPrefix_withoutArguments() async {
newFile('$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 {}'),
);
}
}
const myMacro = MyMacro();
''');
var library = await buildLibrary(r'''
import 'a.dart' as prefix;
@prefix.myMacro
class A {}
''', preBuildSequence: [
{'package:test/a.dart'}
]);
checkElementText(
library,
r'''
library
imports
package:test/a.dart as prefix @19
definingUnit
classes
class A @50
metadata
Annotation
atSign: @ @28
name: PrefixedIdentifier
prefix: SimpleIdentifier
token: prefix @29
staticElement: self::@prefix::prefix
staticType: null
period: . @35
identifier: SimpleIdentifier
token: myMacro @36
staticElement: package:test/a.dart::@getter::myMacro
staticType: null
staticElement: package:test/a.dart::@getter::myMacro
staticType: null
element: package:test/a.dart::@getter::myMacro
constructors
synthetic @-1
parts
package:test/_macro_types.dart
classes
class MyClass @6
constructors
synthetic @-1
exportScope
A: package:test/test.dart;A
MyClass: package:test/test.dart;package:test/_macro_types.dart;MyClass
''',
withExportScope: true);
}
test_application_newInstance_withoutPrefix() async {
newFile('$testPackageLibPath/a.dart', r'''
import 'dart:async';