mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 16:41:07 +00:00
Macro. Support for mixins.
Change-Id: Icab52054c230ab97c3129b0bb32ed3ad55740fbc Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/333046 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Phil Quitslund <pquitslund@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
parent
718f9076bd
commit
0544ecba9a
|
@ -8,7 +8,6 @@ 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' as ast;
|
||||
import 'package:analyzer/dart/ast/token.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/nullability_suffix.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/src/dart/ast/ast.dart' as ast;
|
||||
|
@ -91,15 +90,27 @@ class LibraryMacroApplier {
|
|||
for (final declaration in unit.declarations.reversed) {
|
||||
switch (declaration) {
|
||||
case ast.ClassDeclaration():
|
||||
final element = declaration.declaredElement!;
|
||||
final element = declaration.declaredElement;
|
||||
element as ClassElementImpl;
|
||||
await _addClassLike(
|
||||
container: container,
|
||||
targetElement: element.declarationElement as MacroTargetElement,
|
||||
targetElement: element.declarationElement,
|
||||
classNode: declaration,
|
||||
classDeclarationKind: macro.DeclarationKind.classType,
|
||||
classAnnotations: declaration.metadata,
|
||||
members: declaration.members,
|
||||
);
|
||||
case ast.MixinDeclaration():
|
||||
final element = declaration.declaredElement;
|
||||
element as MixinElementImpl;
|
||||
await _addClassLike(
|
||||
container: container,
|
||||
targetElement: element.declarationElement,
|
||||
classNode: declaration,
|
||||
classDeclarationKind: macro.DeclarationKind.mixinType,
|
||||
classAnnotations: declaration.metadata,
|
||||
members: declaration.members,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -285,6 +296,8 @@ class LibraryMacroApplier {
|
|||
return fromNode.classDeclaration(targetNode);
|
||||
case ast.MethodDeclaration():
|
||||
return fromNode.methodDeclaration(targetNode);
|
||||
case ast.MixinDeclaration():
|
||||
return fromNode.mixinDeclaration(targetNode);
|
||||
default:
|
||||
// TODO(scheglov) incomplete
|
||||
throw UnimplementedError('${targetNode.runtimeType}');
|
||||
|
@ -395,6 +408,8 @@ class LibraryMacroApplier {
|
|||
final element = (identifier as IdentifierImpl).element;
|
||||
if (element is ClassElementImpl) {
|
||||
return declarationBuilder.fromElement.classElement(element);
|
||||
} else if (element is MixinElementImpl) {
|
||||
return declarationBuilder.fromElement.mixinElement(element);
|
||||
} else {
|
||||
throw ArgumentError('element: $element');
|
||||
}
|
||||
|
@ -583,13 +598,19 @@ class _DeclarationPhaseIntrospector extends _TypePhaseIntrospector
|
|||
Future<List<macro.FieldDeclaration>> fieldsOf(
|
||||
covariant macro.IntrospectableType type,
|
||||
) async {
|
||||
if (type is! IntrospectableClassDeclarationImpl) {
|
||||
throw UnsupportedError('Only introspection on classes is supported');
|
||||
switch (type) {
|
||||
case IntrospectableClassDeclarationImpl():
|
||||
return type.element.fields
|
||||
.where((e) => !e.isSynthetic)
|
||||
.map(declarationBuilder.fromElement.fieldElement)
|
||||
.toList();
|
||||
case IntrospectableMixinDeclarationImpl():
|
||||
return type.element.fields
|
||||
.where((e) => !e.isSynthetic)
|
||||
.map(declarationBuilder.fromElement.fieldElement)
|
||||
.toList();
|
||||
}
|
||||
return type.element.fields
|
||||
.where((e) => !e.isSynthetic)
|
||||
.map(declarationBuilder.fromElement.fieldElement)
|
||||
.toList();
|
||||
throw UnsupportedError('Only introspection on classes is supported');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -716,7 +737,7 @@ extension on macro.MacroExecutionResult {
|
|||
typeAugmentations.isNotEmpty;
|
||||
}
|
||||
|
||||
extension<T extends InstanceElement> on T {
|
||||
extension<T extends InstanceElementImpl> on T {
|
||||
T get declarationElement {
|
||||
switch (augmented) {
|
||||
case T(:final T declaration):
|
||||
|
|
|
@ -117,7 +117,8 @@ class UnknownMacroApplicationError extends MacroApplicationError {
|
|||
|
||||
@override
|
||||
String toStringForTest() {
|
||||
return 'Unknown(annotation: $annotationIndex, message: $message)';
|
||||
return 'Unknown(annotation: $annotationIndex, '
|
||||
'message: $message, stackTrace: $stackTrace)';
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -61,6 +61,9 @@ class DeclarationBuilderFromElement {
|
|||
final Map<ClassElement, IntrospectableClassDeclarationImpl> _classMap =
|
||||
Map.identity();
|
||||
|
||||
final Map<MixinElement, IntrospectableMixinDeclarationImpl> _mixinMap =
|
||||
Map.identity();
|
||||
|
||||
final Map<FieldElement, FieldDeclarationImpl> _fieldMap = Map.identity();
|
||||
|
||||
final Map<ExecutableElement, MethodDeclarationImpl> _methodMap =
|
||||
|
@ -108,6 +111,10 @@ class DeclarationBuilderFromElement {
|
|||
return _methodMap[element] ??= _methodElement(element);
|
||||
}
|
||||
|
||||
macro.IntrospectableMixinDeclarationImpl mixinElement(MixinElement element) {
|
||||
return _mixinMap[element] ??= _introspectableMixinElement(element);
|
||||
}
|
||||
|
||||
macro.TypeParameterDeclarationImpl typeParameter(
|
||||
TypeParameterElement element,
|
||||
) {
|
||||
|
@ -146,7 +153,7 @@ class DeclarationBuilderFromElement {
|
|||
}
|
||||
|
||||
FieldDeclarationImpl _fieldElement(FieldElement element) {
|
||||
final enclosingClass = element.enclosingElement as ClassElement;
|
||||
final enclosingElement = element.enclosingElement;
|
||||
return FieldDeclarationImpl(
|
||||
id: macro.RemoteInstance.uniqueId,
|
||||
identifier: identifier(element),
|
||||
|
@ -156,7 +163,7 @@ class DeclarationBuilderFromElement {
|
|||
hasFinal: element.isFinal,
|
||||
hasLate: element.isLate,
|
||||
type: _dartType(element.type),
|
||||
definingType: identifier(enclosingClass),
|
||||
definingType: identifier(enclosingElement),
|
||||
isStatic: element.isStatic,
|
||||
);
|
||||
}
|
||||
|
@ -192,6 +199,22 @@ class DeclarationBuilderFromElement {
|
|||
);
|
||||
}
|
||||
|
||||
IntrospectableMixinDeclarationImpl _introspectableMixinElement(
|
||||
MixinElement element) {
|
||||
return IntrospectableMixinDeclarationImpl._(
|
||||
id: macro.RemoteInstance.uniqueId,
|
||||
identifier: identifier(element),
|
||||
library: library(element),
|
||||
metadata: _buildMetadata(element),
|
||||
typeParameters: element.typeParameters.map(_typeParameter).toList(),
|
||||
hasBase: element.isBase,
|
||||
interfaces: element.interfaces.map(_interfaceType).toList(),
|
||||
superclassConstraints:
|
||||
element.superclassConstraints.map(_interfaceType).toList(),
|
||||
element: element,
|
||||
);
|
||||
}
|
||||
|
||||
MethodDeclarationImpl _methodElement(MethodElement element) {
|
||||
final enclosingClass = element.enclosingElement as ClassElement;
|
||||
return MethodDeclarationImpl._(
|
||||
|
@ -278,6 +301,12 @@ class DeclarationBuilderFromNode {
|
|||
return _methodDeclaration(node);
|
||||
}
|
||||
|
||||
macro.MixinDeclarationImpl mixinDeclaration(
|
||||
ast.MixinDeclaration node,
|
||||
) {
|
||||
return _introspectableMixinDeclaration(node);
|
||||
}
|
||||
|
||||
List<macro.MetadataAnnotationImpl> _buildMetadata(
|
||||
List<ast.Annotation> elements,
|
||||
) {
|
||||
|
@ -294,6 +323,23 @@ class DeclarationBuilderFromNode {
|
|||
);
|
||||
}
|
||||
|
||||
macro.IdentifierImpl _definingType(ast.AstNode node) {
|
||||
final parentNode = node.parent;
|
||||
switch (parentNode) {
|
||||
case ast.ClassDeclaration():
|
||||
final parentElement = parentNode.declaredElement!;
|
||||
final typeElement = parentElement.augmentationTarget ?? parentElement;
|
||||
return _declaredIdentifier(parentNode.name, typeElement);
|
||||
case ast.MixinDeclaration():
|
||||
final parentElement = parentNode.declaredElement!;
|
||||
final typeElement = parentElement.augmentationTarget ?? parentElement;
|
||||
return _declaredIdentifier(parentNode.name, typeElement);
|
||||
default:
|
||||
// TODO(scheglov) other parents
|
||||
throw UnimplementedError('(${parentNode.runtimeType}) $parentNode');
|
||||
}
|
||||
}
|
||||
|
||||
macro.ParameterDeclarationImpl _formalParameter(ast.FormalParameter node) {
|
||||
if (node is ast.DefaultFormalParameter) {
|
||||
node = node.parameter;
|
||||
|
@ -385,15 +431,45 @@ class DeclarationBuilderFromNode {
|
|||
);
|
||||
}
|
||||
|
||||
IntrospectableMixinDeclarationImpl _introspectableMixinDeclaration(
|
||||
ast.MixinDeclaration node,
|
||||
) {
|
||||
final element = node.declaredElement as MixinElementImpl;
|
||||
|
||||
final onNodes = <ast.NamedType>[];
|
||||
final interfaceNodes = <ast.NamedType>[];
|
||||
for (var current = node;;) {
|
||||
if (current.onClause case final clause?) {
|
||||
onNodes.addAll(clause.superclassConstraints);
|
||||
}
|
||||
if (current.implementsClause case final clause?) {
|
||||
interfaceNodes.addAll(clause.interfaces);
|
||||
}
|
||||
final nextElement = current.declaredElement?.augmentation;
|
||||
final nextNode = declarationBuilder.nodeOfElement(nextElement);
|
||||
if (nextNode is! ast.MixinDeclaration) {
|
||||
break;
|
||||
}
|
||||
current = nextNode;
|
||||
}
|
||||
|
||||
return IntrospectableMixinDeclarationImpl._(
|
||||
id: macro.RemoteInstance.uniqueId,
|
||||
identifier: _declaredIdentifier(node.name, element),
|
||||
library: library(element),
|
||||
metadata: _buildMetadata(node.metadata),
|
||||
typeParameters: _typeParameters(node.typeParameters),
|
||||
hasBase: node.baseKeyword != null,
|
||||
interfaces: _namedTypes(interfaceNodes),
|
||||
superclassConstraints: _namedTypes(onNodes),
|
||||
element: element,
|
||||
);
|
||||
}
|
||||
|
||||
MethodDeclarationImpl _methodDeclaration(
|
||||
ast.MethodDeclaration node,
|
||||
) {
|
||||
// TODO(scheglov) other parents
|
||||
final parentNode = node.parent as ast.ClassDeclaration;
|
||||
final parentElement = parentNode.declaredElement!;
|
||||
final typeElement = parentElement.augmentationTarget ?? parentElement;
|
||||
final definingType = _declaredIdentifier(parentNode.name, typeElement);
|
||||
|
||||
final definingType = _definingType(node);
|
||||
final element = node.declaredElement!;
|
||||
|
||||
return MethodDeclarationImpl._(
|
||||
|
@ -591,6 +667,23 @@ class IntrospectableClassDeclarationImpl
|
|||
});
|
||||
}
|
||||
|
||||
class IntrospectableMixinDeclarationImpl
|
||||
extends macro.IntrospectableMixinDeclarationImpl {
|
||||
final MixinElement element;
|
||||
|
||||
IntrospectableMixinDeclarationImpl._({
|
||||
required super.id,
|
||||
required super.identifier,
|
||||
required super.library,
|
||||
required super.metadata,
|
||||
required super.typeParameters,
|
||||
required super.hasBase,
|
||||
required super.interfaces,
|
||||
required super.superclassConstraints,
|
||||
required this.element,
|
||||
});
|
||||
}
|
||||
|
||||
abstract class LibraryImpl extends macro.LibraryImpl {
|
||||
LibraryImpl({
|
||||
required super.id,
|
||||
|
|
46
pkg/analyzer/test/src/summary/macro/append.dart
Normal file
46
pkg/analyzer/test/src/summary/macro/append.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2023, 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';
|
||||
|
||||
Future<NamedTypeAnnotationCode> _codeA(TypePhaseIntrospector builder) async {
|
||||
return NamedTypeAnnotationCode(
|
||||
// ignore:deprecated_member_use
|
||||
name: await builder.resolveIdentifier(
|
||||
Uri.parse('package:test/append.dart'),
|
||||
'A',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class A {}
|
||||
|
||||
/*macro*/ class AppendInterfaceA implements ClassTypesMacro, MixinTypesMacro {
|
||||
const AppendInterfaceA();
|
||||
|
||||
@override
|
||||
buildTypesForClass(clazz, builder) async {
|
||||
builder.appendInterfaces([
|
||||
await _codeA(builder),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
buildTypesForMixin(clazz, builder) async {
|
||||
builder.appendInterfaces([
|
||||
await _codeA(builder),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/*macro*/ class AppendMixinA implements ClassTypesMacro {
|
||||
const AppendMixinA();
|
||||
|
||||
@override
|
||||
buildTypesForClass(clazz, builder) async {
|
||||
builder.appendMixins([
|
||||
await _codeA(builder),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import 'package:_fe_analyzer_shared/src/macros/api.dart';
|
|||
import 'introspect_shared.dart';
|
||||
|
||||
/*macro*/ class DeclarationTextMacro
|
||||
implements ClassTypesMacro, MethodTypesMacro {
|
||||
implements ClassTypesMacro, MethodTypesMacro, MixinTypesMacro {
|
||||
const DeclarationTextMacro();
|
||||
|
||||
@override
|
||||
|
@ -55,6 +55,28 @@ import 'introspect_shared.dart';
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> buildTypesForMixin(declaration, builder) async {
|
||||
final buffer = StringBuffer();
|
||||
final sink = TreeStringSink(
|
||||
sink: buffer,
|
||||
indent: '',
|
||||
);
|
||||
|
||||
final printer = _DeclarationPrinter(
|
||||
sink: sink,
|
||||
);
|
||||
await printer.writeMixinDeclaration(declaration);
|
||||
final text = buffer.toString();
|
||||
|
||||
builder.declareType(
|
||||
'x',
|
||||
DeclarationCode.fromString(
|
||||
'const x = r"""$text""";',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DeclarationPrinter {
|
||||
|
@ -106,6 +128,23 @@ class _DeclarationPrinter {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> writeMixinDeclaration(MixinDeclaration e) async {
|
||||
sink.writelnWithIndent('mixin ${e.identifier.name}');
|
||||
|
||||
await sink.withIndent(() async {
|
||||
await sink.writeFlags({
|
||||
'hasBase': e.hasBase,
|
||||
});
|
||||
|
||||
await _writeTypeParameters(e.typeParameters);
|
||||
await _writeTypeAnnotations(
|
||||
'superclassConstraints',
|
||||
e.superclassConstraints,
|
||||
);
|
||||
await _writeTypeAnnotations('interfaces', e.interfaces);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _writeFormalParameter(ParameterDeclaration e) async {
|
||||
sink.writelnWithIndent(e.identifier.name);
|
||||
await sink.withIndent(() async {
|
||||
|
|
|
@ -9,7 +9,7 @@ import 'package:_fe_analyzer_shared/src/macros/api.dart';
|
|||
import 'introspect_shared.dart';
|
||||
|
||||
/*macro*/ class IntrospectDeclarationsPhaseMacro
|
||||
implements ClassDeclarationsMacro {
|
||||
implements ClassDeclarationsMacro, MixinDeclarationsMacro {
|
||||
final Set<Object?> withDetailsFor;
|
||||
|
||||
const IntrospectDeclarationsPhaseMacro({
|
||||
|
@ -42,6 +42,33 @@ import 'introspect_shared.dart';
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> buildDeclarationsForMixin(
|
||||
IntrospectableMixinDeclaration declaration,
|
||||
MemberDeclarationBuilder builder,
|
||||
) async {
|
||||
final buffer = StringBuffer();
|
||||
final sink = TreeStringSink(
|
||||
sink: buffer,
|
||||
indent: '',
|
||||
);
|
||||
|
||||
final printer = _DeclarationPrinter(
|
||||
sink: sink,
|
||||
withDetailsFor: withDetailsFor.cast(),
|
||||
declarationPhaseIntrospector: builder,
|
||||
);
|
||||
await printer.writeMixinDeclaration(declaration);
|
||||
final text = buffer.toString();
|
||||
|
||||
final resultName = 'introspect_${declaration.identifier.name}';
|
||||
builder.declareInLibrary(
|
||||
DeclarationCode.fromString(
|
||||
'const $resultName = r"""$text""";',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DeclarationPrinter {
|
||||
|
@ -104,6 +131,34 @@ class _DeclarationPrinter {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> writeMixinDeclaration(IntrospectableMixinDeclaration e) async {
|
||||
sink.writelnWithIndent('mixin ${e.identifier.name}');
|
||||
|
||||
if (!_shouldWriteDetailsFor(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await sink.withIndent(() async {
|
||||
await sink.writeFlags({
|
||||
'hasBase': e.hasBase,
|
||||
});
|
||||
|
||||
await _writeTypeParameters(e.typeParameters);
|
||||
await _writeTypeAnnotations(
|
||||
'superclassConstraints',
|
||||
e.superclassConstraints,
|
||||
);
|
||||
await _writeTypeAnnotations('interfaces', e.interfaces);
|
||||
|
||||
_enclosingDeclarationIdentifier = e.identifier;
|
||||
await sink.writeElements<FieldDeclaration>(
|
||||
'fields',
|
||||
await declarationPhaseIntrospector.fieldsOf(e),
|
||||
_writeField,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _assertEnclosingClass(MemberDeclaration e) {
|
||||
if (e.definingType != _enclosingDeclarationIdentifier) {
|
||||
throw StateError('Mismatch: definingClass');
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart'
|
|||
as macro;
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/visitor.dart';
|
||||
import 'package:analyzer/file_system/file_system.dart';
|
||||
import 'package:analyzer/file_system/physical_file_system.dart';
|
||||
import 'package:analyzer/src/dart/element/element.dart';
|
||||
|
@ -610,12 +611,11 @@ import 'arguments_text.dart';
|
|||
class A {}
|
||||
''');
|
||||
|
||||
final A = library.definingCompilationUnit.getClass('A')!;
|
||||
if (expectedErrors != null) {
|
||||
expect(A.errorsStrForClassElement, expectedErrors);
|
||||
expect(library.macroErrorsStr, expectedErrors);
|
||||
return;
|
||||
} else {
|
||||
A.assertNoErrorsForClassElement();
|
||||
library.assertNoMacroErrors();
|
||||
}
|
||||
|
||||
if (expected != null) {
|
||||
|
@ -641,6 +641,13 @@ class MacroDeclarationsIntrospectTest extends MacroElementsBaseTest {
|
|||
@override
|
||||
bool get keepLinkingLibraries => true;
|
||||
|
||||
String get _appendMacrosCode {
|
||||
var code = MacrosEnvironment.instance.packageAnalyzerFolder
|
||||
.getChildAssumingFile('test/src/summary/macro/append.dart')
|
||||
.readAsStringSync();
|
||||
return code.replaceAll('/*macro*/', 'macro');
|
||||
}
|
||||
|
||||
/// Return the code for `IntrospectDeclarationsPhaseMacro`.
|
||||
String get _introspectDeclarationsPhaseCode {
|
||||
final path = 'test/src/summary/macro/introspect_declarations_phase.dart';
|
||||
|
@ -651,36 +658,13 @@ class MacroDeclarationsIntrospectTest extends MacroElementsBaseTest {
|
|||
}
|
||||
|
||||
test_class_appendInterfaces() async {
|
||||
newFile('$testPackageLibPath/a.dart', r'''
|
||||
class A {}
|
||||
''');
|
||||
|
||||
newFile('$testPackageLibPath/b.dart', r'''
|
||||
import 'dart:async';
|
||||
import 'package:_fe_analyzer_shared/src/macros/api.dart';
|
||||
import 'a.dart';
|
||||
|
||||
macro class AddInterfaceA implements ClassTypesMacro {
|
||||
const AddInterfaceA();
|
||||
|
||||
FutureOr<void> buildTypesForClass(clazz, builder) async {
|
||||
builder.appendInterfaces([
|
||||
NamedTypeAnnotationCode(
|
||||
name: await builder.resolveIdentifier(
|
||||
Uri.parse('package:test/a.dart'),
|
||||
'A',
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
''');
|
||||
_newAppendMacrosFile();
|
||||
|
||||
await _assertIntrospectDeclarationsText(r'''
|
||||
import 'b.dart';
|
||||
import 'append.dart';
|
||||
|
||||
@AddInterfaceA()
|
||||
@IntrospectDeclarationsPhaseMacro()
|
||||
@AppendInterfaceA()
|
||||
class X {}
|
||||
''', r'''
|
||||
class X
|
||||
|
@ -690,36 +674,13 @@ class X
|
|||
}
|
||||
|
||||
test_class_appendMixins() async {
|
||||
newFile('$testPackageLibPath/a.dart', r'''
|
||||
mixin A {}
|
||||
''');
|
||||
|
||||
newFile('$testPackageLibPath/b.dart', r'''
|
||||
import 'dart:async';
|
||||
import 'package:_fe_analyzer_shared/src/macros/api.dart';
|
||||
import 'a.dart';
|
||||
|
||||
macro class AddInterfaceA implements ClassTypesMacro {
|
||||
const AddInterfaceA();
|
||||
|
||||
FutureOr<void> buildTypesForClass(clazz, builder) async {
|
||||
builder.appendMixins([
|
||||
NamedTypeAnnotationCode(
|
||||
name: await builder.resolveIdentifier(
|
||||
Uri.parse('package:test/a.dart'),
|
||||
'A',
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
''');
|
||||
_newAppendMacrosFile();
|
||||
|
||||
await _assertIntrospectDeclarationsText(r'''
|
||||
import 'b.dart';
|
||||
import 'append.dart';
|
||||
|
||||
@AddInterfaceA()
|
||||
@IntrospectDeclarationsPhaseMacro()
|
||||
@AppendMixinA()
|
||||
class X {}
|
||||
''', r'''
|
||||
class X
|
||||
|
@ -728,6 +689,106 @@ class X
|
|||
''');
|
||||
}
|
||||
|
||||
test_class_fieldDeclaration_isExternal() async {
|
||||
await _assertIntrospectDeclarationsText(r'''
|
||||
@IntrospectDeclarationsPhaseMacro(
|
||||
withDetailsFor: {'X'},
|
||||
)
|
||||
class X {
|
||||
external int a;
|
||||
int b = 0;
|
||||
}
|
||||
''', r'''
|
||||
class X
|
||||
fields
|
||||
a
|
||||
flags: hasExternal
|
||||
type: int
|
||||
b
|
||||
type: int
|
||||
''');
|
||||
}
|
||||
|
||||
test_class_fieldDeclaration_isFinal() async {
|
||||
await _assertIntrospectDeclarationsText(r'''
|
||||
@IntrospectDeclarationsPhaseMacro(
|
||||
withDetailsFor: {'X'},
|
||||
)
|
||||
class X {
|
||||
final int a = 0;
|
||||
int b = 0;
|
||||
}
|
||||
''', r'''
|
||||
class X
|
||||
fields
|
||||
a
|
||||
flags: hasFinal
|
||||
type: int
|
||||
b
|
||||
type: int
|
||||
''');
|
||||
}
|
||||
|
||||
test_class_fieldDeclaration_isLate() async {
|
||||
await _assertIntrospectDeclarationsText(r'''
|
||||
@IntrospectDeclarationsPhaseMacro(
|
||||
withDetailsFor: {'X'},
|
||||
)
|
||||
class X {
|
||||
late final int a;
|
||||
final int b = 0;
|
||||
}
|
||||
''', r'''
|
||||
class X
|
||||
fields
|
||||
a
|
||||
flags: hasFinal hasLate
|
||||
type: int
|
||||
b
|
||||
flags: hasFinal
|
||||
type: int
|
||||
''');
|
||||
}
|
||||
|
||||
test_class_fieldDeclaration_isStatic() async {
|
||||
await _assertIntrospectDeclarationsText(r'''
|
||||
@IntrospectDeclarationsPhaseMacro(
|
||||
withDetailsFor: {'X'},
|
||||
)
|
||||
class X {
|
||||
static int a = 0;
|
||||
int b = 0;
|
||||
}
|
||||
''', r'''
|
||||
class X
|
||||
fields
|
||||
a
|
||||
flags: isStatic
|
||||
type: int
|
||||
b
|
||||
type: int
|
||||
''');
|
||||
}
|
||||
|
||||
test_class_fieldDeclaration_type_explicit() async {
|
||||
await _assertIntrospectDeclarationsText(r'''
|
||||
@IntrospectDeclarationsPhaseMacro(
|
||||
withDetailsFor: {'X'},
|
||||
)
|
||||
class X {
|
||||
int a = 0;
|
||||
List<String> b = [];
|
||||
}
|
||||
''', r'''
|
||||
class X
|
||||
fields
|
||||
a
|
||||
type: int
|
||||
b
|
||||
type: List<String>
|
||||
''');
|
||||
}
|
||||
|
||||
test_classDeclaration_imported_interfaces() async {
|
||||
newFile('$testPackageLibPath/a.dart', r'''
|
||||
class A {}
|
||||
|
@ -876,37 +937,33 @@ class X
|
|||
''');
|
||||
}
|
||||
|
||||
test_fieldDeclaration_isExternal() async {
|
||||
test_mixin_appendInterfaces() async {
|
||||
_newAppendMacrosFile();
|
||||
|
||||
await _assertIntrospectDeclarationsText(r'''
|
||||
@IntrospectDeclarationsPhaseMacro(
|
||||
withDetailsFor: {'X'},
|
||||
)
|
||||
class X {
|
||||
external int a;
|
||||
int b = 0;
|
||||
}
|
||||
import 'append.dart';
|
||||
|
||||
@IntrospectDeclarationsPhaseMacro()
|
||||
@AppendInterfaceA()
|
||||
mixin X {}
|
||||
''', r'''
|
||||
class X
|
||||
fields
|
||||
a
|
||||
flags: hasExternal
|
||||
type: int
|
||||
b
|
||||
type: int
|
||||
mixin X
|
||||
interfaces
|
||||
A
|
||||
''');
|
||||
}
|
||||
|
||||
test_fieldDeclaration_isFinal() async {
|
||||
test_mixin_fieldDeclaration_isFinal() async {
|
||||
await _assertIntrospectDeclarationsText(r'''
|
||||
@IntrospectDeclarationsPhaseMacro(
|
||||
withDetailsFor: {'X'},
|
||||
)
|
||||
class X {
|
||||
mixin X {
|
||||
final int a = 0;
|
||||
int b = 0;
|
||||
}
|
||||
''', r'''
|
||||
class X
|
||||
mixin X
|
||||
fields
|
||||
a
|
||||
flags: hasFinal
|
||||
|
@ -916,66 +973,6 @@ class X
|
|||
''');
|
||||
}
|
||||
|
||||
test_fieldDeclaration_isLate() async {
|
||||
await _assertIntrospectDeclarationsText(r'''
|
||||
@IntrospectDeclarationsPhaseMacro(
|
||||
withDetailsFor: {'X'},
|
||||
)
|
||||
class X {
|
||||
late final int a;
|
||||
final int b = 0;
|
||||
}
|
||||
''', r'''
|
||||
class X
|
||||
fields
|
||||
a
|
||||
flags: hasFinal hasLate
|
||||
type: int
|
||||
b
|
||||
flags: hasFinal
|
||||
type: int
|
||||
''');
|
||||
}
|
||||
|
||||
test_fieldDeclaration_isStatic() async {
|
||||
await _assertIntrospectDeclarationsText(r'''
|
||||
@IntrospectDeclarationsPhaseMacro(
|
||||
withDetailsFor: {'X'},
|
||||
)
|
||||
class X {
|
||||
static int a = 0;
|
||||
int b = 0;
|
||||
}
|
||||
''', r'''
|
||||
class X
|
||||
fields
|
||||
a
|
||||
flags: isStatic
|
||||
type: int
|
||||
b
|
||||
type: int
|
||||
''');
|
||||
}
|
||||
|
||||
test_fieldDeclaration_type_explicit() async {
|
||||
await _assertIntrospectDeclarationsText(r'''
|
||||
@IntrospectDeclarationsPhaseMacro(
|
||||
withDetailsFor: {'X'},
|
||||
)
|
||||
class X {
|
||||
int a = 0;
|
||||
List<String> b = [];
|
||||
}
|
||||
''', r'''
|
||||
class X
|
||||
fields
|
||||
a
|
||||
type: int
|
||||
b
|
||||
type: List<String>
|
||||
''');
|
||||
}
|
||||
|
||||
/// Assert that the textual dump of the introspection information for
|
||||
/// annotated declarations is the same as [expected].
|
||||
Future<void> _assertIntrospectDeclarationsText(
|
||||
|
@ -1008,9 +1005,7 @@ import 'introspect_declarations_phase.dart';
|
|||
$declarationCode
|
||||
''');
|
||||
|
||||
for (final class_ in library.definingCompilationUnit.classes) {
|
||||
class_.assertNoErrorsForClassElement();
|
||||
}
|
||||
library.assertNoMacroErrors();
|
||||
|
||||
return library.topLevelElements
|
||||
.whereType<ConstTopLevelVariableElementImpl>()
|
||||
|
@ -1018,6 +1013,13 @@ $declarationCode
|
|||
.map((e) => (e.constantInitializer as SimpleStringLiteral).value)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
void _newAppendMacrosFile() {
|
||||
newFile(
|
||||
'$testPackageLibPath/append.dart',
|
||||
_appendMacrosCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class MacroDeclarationsTest extends MacroElementsBaseTest {
|
||||
|
@ -1918,6 +1920,19 @@ foo
|
|||
''');
|
||||
}
|
||||
|
||||
test_class_mixinDeclaration_method() async {
|
||||
await _assertTypesPhaseIntrospectionText(r'''
|
||||
mixin A {
|
||||
@DeclarationTextMacro()
|
||||
void foo() {}
|
||||
}
|
||||
''', r'''
|
||||
foo
|
||||
flags: hasBody
|
||||
returnType: void
|
||||
''');
|
||||
}
|
||||
|
||||
test_classDeclaration_interfaces() async {
|
||||
await _assertTypesPhaseIntrospectionText(r'''
|
||||
@DeclarationTextMacro()
|
||||
|
@ -2076,6 +2091,83 @@ class A
|
|||
''');
|
||||
}
|
||||
|
||||
test_mixin_methodDeclaration_getter() async {
|
||||
await _assertTypesPhaseIntrospectionText(r'''
|
||||
mixin A {
|
||||
@DeclarationTextMacro()
|
||||
int get foo => 0;
|
||||
}
|
||||
''', r'''
|
||||
foo
|
||||
flags: hasBody isGetter
|
||||
returnType: int
|
||||
''');
|
||||
}
|
||||
|
||||
test_mixin_methodDeclaration_setter() async {
|
||||
await _assertTypesPhaseIntrospectionText(r'''
|
||||
mixin A {
|
||||
@DeclarationTextMacro()
|
||||
set foo(int value) {}
|
||||
}
|
||||
''', r'''
|
||||
foo
|
||||
flags: hasBody isSetter
|
||||
positionalParameters
|
||||
value
|
||||
flags: isRequired
|
||||
type: int
|
||||
returnType: OmittedType
|
||||
''');
|
||||
}
|
||||
|
||||
test_mixinDeclaration_flags_hasBase() async {
|
||||
await _assertTypesPhaseIntrospectionText(r'''
|
||||
@DeclarationTextMacro()
|
||||
base mixin A {}
|
||||
''', r'''
|
||||
mixin A
|
||||
flags: hasBase
|
||||
''');
|
||||
}
|
||||
|
||||
test_mixinDeclaration_interfaces() async {
|
||||
await _assertTypesPhaseIntrospectionText(r'''
|
||||
@DeclarationTextMacro()
|
||||
mixin A implements B, C {}
|
||||
''', r'''
|
||||
mixin A
|
||||
interfaces
|
||||
B
|
||||
C
|
||||
''');
|
||||
}
|
||||
|
||||
test_mixinDeclaration_superclassConstraints() async {
|
||||
await _assertTypesPhaseIntrospectionText(r'''
|
||||
@DeclarationTextMacro()
|
||||
mixin A on B, C {}
|
||||
''', r'''
|
||||
mixin A
|
||||
superclassConstraints
|
||||
B
|
||||
C
|
||||
''');
|
||||
}
|
||||
|
||||
test_mixinDeclaration_typeParameters() async {
|
||||
await _assertTypesPhaseIntrospectionText(r'''
|
||||
@DeclarationTextMacro()
|
||||
mixin A<T, U extends List<T>> {}
|
||||
''', r'''
|
||||
mixin A
|
||||
typeParameters
|
||||
T
|
||||
U
|
||||
bound: List<T>
|
||||
''');
|
||||
}
|
||||
|
||||
test_namedTypeAnnotation_prefixed() async {
|
||||
await _assertTypesPhaseIntrospectionText(r'''
|
||||
@DeclarationTextMacro()
|
||||
|
@ -2123,8 +2215,7 @@ import 'declaration_text.dart';
|
|||
$declarationCode
|
||||
''');
|
||||
|
||||
final A = library.definingCompilationUnit.getClass('A')!;
|
||||
A.assertNoErrorsForClassElement();
|
||||
library.assertNoMacroErrors();
|
||||
|
||||
final macroAugmentation = library.augmentations.first;
|
||||
final macroUnit = macroAugmentation.definingCompilationUnit;
|
||||
|
@ -3126,17 +3217,35 @@ class MacroTypesTest_keepLinking extends MacroTypesTest {
|
|||
bool get keepLinkingLibraries => true;
|
||||
}
|
||||
|
||||
extension on ClassElement {
|
||||
String get errorsStrForClassElement {
|
||||
final element = this as ClassElementImpl;
|
||||
return element.macroApplicationErrors.map((e) {
|
||||
class _MacroApplicationErrorsCollector
|
||||
extends GeneralizingElementVisitor<void> {
|
||||
final List<MacroApplicationError> errors = [];
|
||||
|
||||
@override
|
||||
void visitElement(Element element) {
|
||||
if (element case final MacroTargetElement element) {
|
||||
errors.addAll(element.macroApplicationErrors);
|
||||
}
|
||||
|
||||
super.visitElement(element);
|
||||
}
|
||||
}
|
||||
|
||||
extension on LibraryElement {
|
||||
List<MacroApplicationError> get macroErrors {
|
||||
final collector = _MacroApplicationErrorsCollector();
|
||||
accept(collector);
|
||||
return collector.errors;
|
||||
}
|
||||
|
||||
String get macroErrorsStr {
|
||||
return macroErrors.map((e) {
|
||||
return e.toStringForTest();
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
void assertNoErrorsForClassElement() {
|
||||
final actual = errorsStrForClassElement;
|
||||
expect(actual, isEmpty);
|
||||
void assertNoMacroErrors() {
|
||||
expect(macroErrorsStr, isEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue