From 0544ecba9a301030a91fae8b399bdae5deb67c0e Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Tue, 31 Oct 2023 23:54:49 +0000 Subject: [PATCH] Macro. Support for mixins. Change-Id: Icab52054c230ab97c3129b0bb32ed3ad55740fbc Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/333046 Reviewed-by: Brian Wilkerson Reviewed-by: Phil Quitslund Commit-Queue: Konstantin Shcheglov --- .../lib/src/summary2/macro_application.dart | 41 +- .../src/summary2/macro_application_error.dart | 3 +- .../lib/src/summary2/macro_declarations.dart | 109 ++++- .../test/src/summary/macro/append.dart | 46 ++ .../src/summary/macro/declaration_text.dart | 41 +- .../macro/introspect_declarations_phase.dart | 57 ++- pkg/analyzer/test/src/summary/macro_test.dart | 399 +++++++++++------- 7 files changed, 530 insertions(+), 166 deletions(-) create mode 100644 pkg/analyzer/test/src/summary/macro/append.dart diff --git a/pkg/analyzer/lib/src/summary2/macro_application.dart b/pkg/analyzer/lib/src/summary2/macro_application.dart index 8bb165ce805..f3434af3224 100644 --- a/pkg/analyzer/lib/src/summary2/macro_application.dart +++ b/pkg/analyzer/lib/src/summary2/macro_application.dart @@ -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> 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 on T { +extension on T { T get declarationElement { switch (augmented) { case T(:final T declaration): diff --git a/pkg/analyzer/lib/src/summary2/macro_application_error.dart b/pkg/analyzer/lib/src/summary2/macro_application_error.dart index 46e7dcd2adc..142569d53a6 100644 --- a/pkg/analyzer/lib/src/summary2/macro_application_error.dart +++ b/pkg/analyzer/lib/src/summary2/macro_application_error.dart @@ -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 diff --git a/pkg/analyzer/lib/src/summary2/macro_declarations.dart b/pkg/analyzer/lib/src/summary2/macro_declarations.dart index b0b93ca5c1b..e4e36bb869c 100644 --- a/pkg/analyzer/lib/src/summary2/macro_declarations.dart +++ b/pkg/analyzer/lib/src/summary2/macro_declarations.dart @@ -61,6 +61,9 @@ class DeclarationBuilderFromElement { final Map _classMap = Map.identity(); + final Map _mixinMap = + Map.identity(); + final Map _fieldMap = Map.identity(); final Map _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 _buildMetadata( List 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 = []; + final interfaceNodes = []; + 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, diff --git a/pkg/analyzer/test/src/summary/macro/append.dart b/pkg/analyzer/test/src/summary/macro/append.dart new file mode 100644 index 00000000000..8af2308ff29 --- /dev/null +++ b/pkg/analyzer/test/src/summary/macro/append.dart @@ -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 _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), + ]); + } +} diff --git a/pkg/analyzer/test/src/summary/macro/declaration_text.dart b/pkg/analyzer/test/src/summary/macro/declaration_text.dart index 2db12e50ce3..322079cf1c7 100644 --- a/pkg/analyzer/test/src/summary/macro/declaration_text.dart +++ b/pkg/analyzer/test/src/summary/macro/declaration_text.dart @@ -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 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 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 _writeFormalParameter(ParameterDeclaration e) async { sink.writelnWithIndent(e.identifier.name); await sink.withIndent(() async { diff --git a/pkg/analyzer/test/src/summary/macro/introspect_declarations_phase.dart b/pkg/analyzer/test/src/summary/macro/introspect_declarations_phase.dart index 78672a63543..f55c1ef6d8f 100644 --- a/pkg/analyzer/test/src/summary/macro/introspect_declarations_phase.dart +++ b/pkg/analyzer/test/src/summary/macro/introspect_declarations_phase.dart @@ -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 withDetailsFor; const IntrospectDeclarationsPhaseMacro({ @@ -42,6 +42,33 @@ import 'introspect_shared.dart'; ), ); } + + @override + Future 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 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( + 'fields', + await declarationPhaseIntrospector.fieldsOf(e), + _writeField, + ); + }); + } + void _assertEnclosingClass(MemberDeclaration e) { if (e.definingType != _enclosingDeclarationIdentifier) { throw StateError('Mismatch: definingClass'); diff --git a/pkg/analyzer/test/src/summary/macro_test.dart b/pkg/analyzer/test/src/summary/macro_test.dart index e72ce43bba1..934f9ff5ef3 100644 --- a/pkg/analyzer/test/src/summary/macro_test.dart +++ b/pkg/analyzer/test/src/summary/macro_test.dart @@ -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 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 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 b = []; +} +''', r''' +class X + fields + a + type: int + b + type: List +'''); + } + 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 b = []; -} -''', r''' -class X - fields - a - type: int - b - type: List -'''); - } - /// Assert that the textual dump of the introspection information for /// annotated declarations is the same as [expected]. Future _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() @@ -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> {} +''', r''' +mixin A + typeParameters + T + U + bound: List +'''); + } + 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 { + final List errors = []; + + @override + void visitElement(Element element) { + if (element case final MacroTargetElement element) { + errors.addAll(element.macroApplicationErrors); + } + + super.visitElement(element); + } +} + +extension on LibraryElement { + List 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); } }