From 5a2f5ab33c87bbb69f71929058d4204c87429733 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Thu, 5 May 2022 21:30:04 +0000 Subject: [PATCH] Support more parts of ClassDeclaration in DeclarationBuilderFromElement. Change-Id: I6d6c0344924a19e8b8b01cf54a51b69fd61e0122 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243849 Reviewed-by: Brian Wilkerson Commit-Queue: Konstantin Shcheglov --- .../lib/src/summary2/macro_application.dart | 2 +- .../lib/src/summary2/macro_declarations.dart | 73 ++++++-- .../src/summary/macro/declaration_text.dart | 141 +-------------- .../macro/introspect_declarations_phase.dart | 66 ++++++- .../src/summary/macro/introspect_shared.dart | 141 +++++++++++++++ pkg/analyzer/test/src/summary/macro_test.dart | 164 ++++++++++++++++-- 6 files changed, 417 insertions(+), 170 deletions(-) create mode 100644 pkg/analyzer/test/src/summary/macro/introspect_shared.dart diff --git a/pkg/analyzer/lib/src/summary2/macro_application.dart b/pkg/analyzer/lib/src/summary2/macro_application.dart index 6b7dd6c59f4..42ac2f391be 100644 --- a/pkg/analyzer/lib/src/summary2/macro_application.dart +++ b/pkg/analyzer/lib/src/summary2/macro_application.dart @@ -486,7 +486,7 @@ class _ClassIntrospector implements macro.ClassIntrospector { return null; } - return declarationBuilder.fromElement.classDeclaration( + return declarationBuilder.fromElement.classElement( superType.element, ); } diff --git a/pkg/analyzer/lib/src/summary2/macro_declarations.dart b/pkg/analyzer/lib/src/summary2/macro_declarations.dart index 47bcc2b5cbe..21707bc6b0e 100644 --- a/pkg/analyzer/lib/src/summary2/macro_declarations.dart +++ b/pkg/analyzer/lib/src/summary2/macro_declarations.dart @@ -8,6 +8,8 @@ import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart' as macro; import 'package:analyzer/dart/ast/ast.dart' as ast; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/nullability_suffix.dart'; +import 'package:analyzer/dart/element/type.dart'; class ClassDeclarationImpl extends macro.ClassDeclarationImpl { late final ClassElement element; @@ -43,7 +45,7 @@ class DeclarationBuilder { /// corresponding elements. So, we can access them uniformly via interfaces, /// mixins, etc. void transferToElements() { - for (final entry in fromNode._classNodes.entries) { + for (final entry in fromNode._classMap.entries) { final element = entry.key.declaredElement as ClassElement; final declaration = entry.value; declaration.element = element; @@ -54,51 +56,86 @@ class DeclarationBuilder { class DeclarationBuilderFromElement { final Map _classMap = Map.identity(); + final Map + _typeParameterMap = Map.identity(); - macro.ClassDeclarationImpl classDeclaration(ClassElement element) { - return _classMap[element] ??= _classDeclaration(element); + macro.ClassDeclarationImpl classElement(ClassElement element) { + return _classMap[element] ??= _classElement(element); } - ClassDeclarationImpl _classDeclaration(ClassElement element) { + macro.TypeParameterDeclarationImpl typeParameter( + TypeParameterElement element, + ) { + return _typeParameterMap[element] ??= _typeParameter(element); + } + + ClassDeclarationImpl _classElement(ClassElement element) { assert(!_classMap.containsKey(element)); return ClassDeclarationImpl._( id: macro.RemoteInstance.uniqueId, - identifier: _identifierFromName(element.name), - typeParameters: [], // TODO _buildTypeParameters(node.typeParameters), - interfaces: [], // TODO _buildTypeAnnotations(node.implementsClause?.interfaces), - isAbstract: false, // TODO node.abstractKeyword != null, + identifier: _identifier(element.name), + typeParameters: element.typeParameters.map(_typeParameter).toList(), + interfaces: element.interfaces.map(_dartType).toList(), + isAbstract: element.isAbstract, isExternal: false, - mixins: [], // TODO _buildTypeAnnotations(node.withClause?.mixinTypes), - superclass: null, - // TODO(scheglov) implement - // superclass: node.extendsClause?.superclass.mapOrNull( - // _buildTypeAnnotation, - // ), + mixins: element.mixins.map(_dartType).toList(), + superclass: element.supertype.mapOrNull(_dartType), )..element = element; } - macro.IdentifierImpl _identifierFromName(String name) { + macro.TypeAnnotationImpl _dartType(DartType type) { + if (type is InterfaceType) { + return macro.NamedTypeAnnotationImpl( + id: macro.RemoteInstance.uniqueId, + isNullable: type.nullabilitySuffix == NullabilitySuffix.question, + identifier: _identifier(type.element.name), + typeArguments: type.typeArguments.map(_dartType).toList(), + ); + } else if (type is TypeParameterType) { + return macro.NamedTypeAnnotationImpl( + id: macro.RemoteInstance.uniqueId, + isNullable: type.nullabilitySuffix == NullabilitySuffix.question, + identifier: _identifier(type.element.name), + typeArguments: const [], + ); + } else { + // TODO(scheglov) other types + throw UnimplementedError('(${type.runtimeType}) $type'); + } + } + + macro.IdentifierImpl _identifier(String name) { return _IdentifierImpl( id: macro.RemoteInstance.uniqueId, name: name, ); } + + macro.TypeParameterDeclarationImpl _typeParameter( + TypeParameterElement element, + ) { + return macro.TypeParameterDeclarationImpl( + id: macro.RemoteInstance.uniqueId, + identifier: _identifier(element.name), + bound: element.bound.mapOrNull(_dartType), + ); + } } class DeclarationBuilderFromNode { - final Map _classNodes = + final Map _classMap = Map.identity(); macro.ClassDeclarationImpl classDeclaration( ast.ClassDeclaration node, ) { - return _classNodes[node] ??= _classDeclaration(node); + return _classMap[node] ??= _classDeclaration(node); } ClassDeclarationImpl _classDeclaration( ast.ClassDeclaration node, ) { - assert(!_classNodes.containsKey(node)); + assert(!_classMap.containsKey(node)); return ClassDeclarationImpl._( id: macro.RemoteInstance.uniqueId, identifier: _identifier(node.name), diff --git a/pkg/analyzer/test/src/summary/macro/declaration_text.dart b/pkg/analyzer/test/src/summary/macro/declaration_text.dart index f438eec6645..265f2308e75 100644 --- a/pkg/analyzer/test/src/summary/macro/declaration_text.dart +++ b/pkg/analyzer/test/src/summary/macro/declaration_text.dart @@ -6,6 +6,8 @@ import 'dart:async'; import 'package:_fe_analyzer_shared/src/macros/api.dart'; +import 'introspect_shared.dart'; + /*macro*/ class DeclarationTextMacro implements ClassTypesMacro { const DeclarationTextMacro(); @@ -88,15 +90,14 @@ class _DeclarationPrinter { _sink.write('$name: '); if (type != null) { - _writeln(_typeStr(type)); + _writeln(type.asString); } else { _writeln('null'); } } void _writeTypeAnnotationLine(TypeAnnotation type) { - var typeStr = _typeStr(type); - _writelnWithIndent(typeStr); + _writelnWithIndent(type.asString); } void _writeTypeAnnotations(String name, Iterable elements) { @@ -117,138 +118,4 @@ class _DeclarationPrinter { void _writeTypeParameters(Iterable elements) { _writeElements('typeParameters', elements, _writeTypeParameter); } - - static String _typeStr(TypeAnnotation type) { - final buffer = StringBuffer(); - _TypeStringBuilder(buffer).write(type); - return buffer.toString(); - } -} - -class _TypeStringBuilder { - final StringSink _sink; - - _TypeStringBuilder(this._sink); - - void write(TypeAnnotation type) { - if (type is FunctionTypeAnnotation) { - _writeFunctionTypeAnnotation(type); - } else if (type is NamedTypeAnnotation) { - _writeNamedTypeAnnotation(type); - } else if (type is OmittedTypeAnnotation) { - _sink.write('OmittedType'); - } else { - throw UnimplementedError('(${type.runtimeType}) $type'); - } - if (type.isNullable) { - _sink.write('?'); - } - } - - void _writeFormalParameter(FunctionTypeParameter node) { - final String closeSeparator; - if (node.isNamed) { - _sink.write('{'); - closeSeparator = '}'; - if (node.isRequired) { - _sink.write('required '); - } - } else if (!node.isRequired) { - _sink.write('['); - closeSeparator = ']'; - } else { - closeSeparator = ''; - } - - write(node.type); - if (node.name != null) { - _sink.write(' '); - _sink.write(node.name); - } - - _sink.write(closeSeparator); - } - - void _writeFunctionTypeAnnotation(FunctionTypeAnnotation type) { - write(type.returnType); - _sink.write(' Function'); - - _sink.writeList( - elements: type.typeParameters, - write: _writeTypeParameter, - separator: ', ', - open: '<', - close: '>', - ); - - _sink.write('('); - var hasFormalParameter = false; - for (final formalParameter in type.positionalParameters) { - if (hasFormalParameter) { - _sink.write(', '); - } - _writeFormalParameter(formalParameter); - hasFormalParameter = true; - } - for (final formalParameter in type.namedParameters) { - if (hasFormalParameter) { - _sink.write(', '); - } - _writeFormalParameter(formalParameter); - hasFormalParameter = true; - } - _sink.write(')'); - } - - void _writeNamedTypeAnnotation(NamedTypeAnnotation type) { - _sink.write(type.identifier.name); - _sink.writeList( - elements: type.typeArguments, - write: write, - separator: ', ', - open: '<', - close: '>', - ); - } - - void _writeTypeParameter(TypeParameterDeclaration node) { - _sink.write(node.identifier.name); - - final bound = node.bound; - if (bound != null) { - _sink.write(' extends '); - write(bound); - } - } -} - -extension on StringSink { - void writeList({ - required Iterable elements, - required void Function(T element) write, - required String separator, - String? open, - String? close, - }) { - elements = elements.toList(); - if (elements.isEmpty) { - return; - } - - if (open != null) { - this.write(open); - } - var isFirst = true; - for (var element in elements) { - if (isFirst) { - isFirst = false; - } else { - this.write(separator); - } - write(element); - } - if (close != null) { - this.write(close); - } - } } 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 3a2515a0660..d5889ae8ca1 100644 --- a/pkg/analyzer/test/src/summary/macro/introspect_declarations_phase.dart +++ b/pkg/analyzer/test/src/summary/macro/introspect_declarations_phase.dart @@ -6,6 +6,8 @@ import 'dart:async'; import 'package:_fe_analyzer_shared/src/macros/api.dart'; +import 'introspect_shared.dart'; + const introspectMacro = IntrospectDeclarationsPhaseMacro(); /*macro*/ class IntrospectDeclarationsPhaseMacro @@ -19,6 +21,7 @@ const introspectMacro = IntrospectDeclarationsPhaseMacro(); ) async { final printer = _DeclarationPrinter( classIntrospector: builder, + typeResolver: builder, ); await printer.writeClassDeclaration(declaration); final text = printer._sink.toString(); @@ -34,11 +37,13 @@ const introspectMacro = IntrospectDeclarationsPhaseMacro(); class _DeclarationPrinter { final ClassIntrospector classIntrospector; + final TypeResolver typeResolver; final StringBuffer _sink = StringBuffer(); String _indent = ''; _DeclarationPrinter({ required this.classIntrospector, + required this.typeResolver, }); Future writeClassDeclaration(ClassDeclaration e) async { @@ -55,10 +60,9 @@ class _DeclarationPrinter { await _withIndent(() => writeClassDeclaration(superclass)); } - // TODO(scheglov) implement - // _writeTypeParameters(e.typeParameters); - // _writeTypeAnnotations('mixins', e.mixins); - // _writeTypeAnnotations('interfaces', e.interfaces); + await _writeTypeParameters(e.typeParameters); + await _writeTypeAnnotations('mixins', e.mixins); + await _writeTypeAnnotations('interfaces', e.interfaces); }); } @@ -69,6 +73,21 @@ class _DeclarationPrinter { _indent = savedIndent; } + Future _writeElements( + String name, + Iterable elements, + Future Function(T) f, + ) async { + if (elements.isNotEmpty) { + _writelnWithIndent(name); + await _withIndent(() async { + for (var element in elements) { + await f(element); + } + }); + } + } + void _writeIf(bool flag, String str) { if (flag) { _sink.write(str); @@ -83,4 +102,43 @@ class _DeclarationPrinter { _sink.write(_indent); _sink.writeln(line); } + + void _writeTypeAnnotation(String name, TypeAnnotation? type) { + _sink.write(_indent); + _sink.write('$name: '); + + if (type != null) { + _writeln(type.asString); + } else { + _writeln('null'); + } + } + + Future _writeTypeAnnotationLine(TypeAnnotation type) async { + _writelnWithIndent(type.asString); + } + + Future _writeTypeAnnotations( + String name, + Iterable elements, + ) async { + await _writeElements(name, elements, _writeTypeAnnotationLine); + } + + Future _writeTypeParameter(TypeParameterDeclaration e) async { + _writelnWithIndent(e.identifier.name); + + await _withIndent(() async { + var bound = e.bound; + if (bound != null) { + _writeTypeAnnotation('bound', bound); + } + }); + } + + Future _writeTypeParameters( + Iterable elements, + ) async { + await _writeElements('typeParameters', elements, _writeTypeParameter); + } } diff --git a/pkg/analyzer/test/src/summary/macro/introspect_shared.dart b/pkg/analyzer/test/src/summary/macro/introspect_shared.dart new file mode 100644 index 00000000000..b1c5b7f6c50 --- /dev/null +++ b/pkg/analyzer/test/src/summary/macro/introspect_shared.dart @@ -0,0 +1,141 @@ +// Copyright (c) 2022, 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'; + +class _TypeAnnotationStringBuilder { + final StringSink _sink; + + _TypeAnnotationStringBuilder(this._sink); + + void write(TypeAnnotation type) { + if (type is FunctionTypeAnnotation) { + _writeFunctionTypeAnnotation(type); + } else if (type is NamedTypeAnnotation) { + _writeNamedTypeAnnotation(type); + } else if (type is OmittedTypeAnnotation) { + _sink.write('OmittedType'); + } else { + throw UnimplementedError('(${type.runtimeType}) $type'); + } + if (type.isNullable) { + _sink.write('?'); + } + } + + void _writeFormalParameter(FunctionTypeParameter node) { + final String closeSeparator; + if (node.isNamed) { + _sink.write('{'); + closeSeparator = '}'; + if (node.isRequired) { + _sink.write('required '); + } + } else if (!node.isRequired) { + _sink.write('['); + closeSeparator = ']'; + } else { + closeSeparator = ''; + } + + write(node.type); + if (node.name != null) { + _sink.write(' '); + _sink.write(node.name); + } + + _sink.write(closeSeparator); + } + + void _writeFunctionTypeAnnotation(FunctionTypeAnnotation type) { + write(type.returnType); + _sink.write(' Function'); + + _sink.writeList( + elements: type.typeParameters, + write: _writeTypeParameter, + separator: ', ', + open: '<', + close: '>', + ); + + _sink.write('('); + var hasFormalParameter = false; + for (final formalParameter in type.positionalParameters) { + if (hasFormalParameter) { + _sink.write(', '); + } + _writeFormalParameter(formalParameter); + hasFormalParameter = true; + } + for (final formalParameter in type.namedParameters) { + if (hasFormalParameter) { + _sink.write(', '); + } + _writeFormalParameter(formalParameter); + hasFormalParameter = true; + } + _sink.write(')'); + } + + void _writeNamedTypeAnnotation(NamedTypeAnnotation type) { + _sink.write(type.identifier.name); + _sink.writeList( + elements: type.typeArguments, + write: write, + separator: ', ', + open: '<', + close: '>', + ); + } + + void _writeTypeParameter(TypeParameterDeclaration node) { + _sink.write(node.identifier.name); + + final bound = node.bound; + if (bound != null) { + _sink.write(' extends '); + write(bound); + } + } +} + +extension on StringSink { + void writeList({ + required Iterable elements, + required void Function(T element) write, + required String separator, + String? open, + String? close, + }) { + elements = elements.toList(); + if (elements.isEmpty) { + return; + } + + if (open != null) { + this.write(open); + } + var isFirst = true; + for (var element in elements) { + if (isFirst) { + isFirst = false; + } else { + this.write(separator); + } + write(element); + } + if (close != null) { + this.write(close); + } + } +} + +extension E on TypeAnnotation { + String get asString { + final buffer = StringBuffer(); + _TypeAnnotationStringBuilder(buffer).write(this); + return buffer.toString(); + } +} diff --git a/pkg/analyzer/test/src/summary/macro_test.dart b/pkg/analyzer/test/src/summary/macro_test.dart index a775b53fc6d..27d3339e9c6 100644 --- a/pkg/analyzer/test/src/summary/macro_test.dart +++ b/pkg/analyzer/test/src/summary/macro_test.dart @@ -65,6 +65,12 @@ class MacroElementsTest extends ElementsBaseTest { return code.replaceAll('/*macro*/', 'macro'); } + String get _introspectSharedCode { + return MacrosEnvironment.instance.packageAnalyzerFolder + .getChildAssumingFile('test/src/summary/macro/introspect_shared.dart') + .readAsStringSync(); + } + Future setUp() async { writeTestPackageConfig( PackageConfigFileBuilder(), @@ -1152,6 +1158,133 @@ library '''); } + test_introspect_declarations_ClassDeclaration_imported_interfaces() async { + newFile('$testPackageLibPath/a.dart', r''' +class A {} +class B {} +class C implements A, B {} +'''); + + await _assertIntrospectDeclarationsText(r''' +import 'a.dart'; + +@introspectMacro +class X extends C {} +''', r''' +class X + superclass + class C + superclass + class Object + interfaces + A + B +'''); + } + + test_introspect_declarations_ClassDeclaration_imported_isAbstract() async { + newFile('$testPackageLibPath/a.dart', r''' +abstract class A {} +'''); + + await _assertIntrospectDeclarationsText(r''' +import 'a.dart'; + +@introspectMacro +class X extends A {} +''', r''' +class X + superclass + abstract class A + superclass + class Object +'''); + } + + test_introspect_declarations_ClassDeclaration_imported_mixins() async { + newFile('$testPackageLibPath/a.dart', r''' +mixin M1 {} +mixin M2 {} +class C with M1, M2 {} +'''); + + await _assertIntrospectDeclarationsText(r''' +import 'a.dart'; + +@introspectMacro +class X extends C {} +''', r''' +class X + superclass + class C + superclass + class Object + mixins + M1 + M2 +'''); + } + + test_introspect_declarations_ClassDeclaration_imported_superclass() async { + newFile('$testPackageLibPath/a.dart', r''' +class A {} +class B extends A {} +'''); + + await _assertIntrospectDeclarationsText(r''' +import 'a.dart'; + +@introspectMacro +class X extends B {} +''', r''' +class X + superclass + class B + superclass + class A + superclass + class Object +'''); + } + + test_introspect_declarations_ClassDeclaration_imported_typeParameters() async { + newFile('$testPackageLibPath/a.dart', r''' +class A> {} +'''); + + await _assertIntrospectDeclarationsText(r''' +import 'a.dart'; + +@introspectMacro +class X extends A {} +''', r''' +class X + superclass + class A + superclass + class Object + typeParameters + T + U + bound: List +'''); + } + + test_introspect_declarations_ClassDeclaration_superclassOf() async { + await _assertIntrospectDeclarationsText(r''' +class A {} + +@introspectMacro +class X extends A {} +''', r''' +class X + superclass + class A + superclass + class Object +'''); + } + test_introspect_declarations_ClassDeclaration_superclassOf_implicit() async { await _assertIntrospectDeclarationsText(r''' @introspectMacro @@ -1163,18 +1296,14 @@ class X '''); } - test_introspect_declarations_ClassDeclaration_superclassOf_local() async { + test_introspect_declarations_ClassDeclaration_superclassOf_unresolved() async { await _assertIntrospectDeclarationsText(r''' -class A {} - @introspectMacro -class X extends A {} +class X extends A {} ''', r''' class X superclass - class A - superclass - class Object + class Object '''); } @@ -1597,6 +1726,11 @@ class A {} /// the textual dump of the introspection information for the first /// declaration. Future _getDeclarationText(String declarationCode) async { + newFile( + '$testPackageLibPath/introspect_shared.dart', + _introspectSharedCode, + ); + newFile( '$testPackageLibPath/declaration_text.dart', _declarationTextCode, @@ -1608,7 +1742,10 @@ import 'declaration_text.dart'; @DeclarationTextMacro() $declarationCode ''', preBuildSequence: [ - {'package:test/declaration_text.dart'} + { + 'package:test/introspect_shared.dart', + 'package:test/declaration_text.dart', + } ]); _assertNoErrorsForClassElement( @@ -1626,6 +1763,11 @@ $declarationCode /// that contain textual dump of the introspection information for /// macro annotated declarations. Future _getIntrospectDeclarationsText(String declarationCode) async { + newFile( + '$testPackageLibPath/introspect_shared.dart', + _introspectSharedCode, + ); + newFile( '$testPackageLibPath/introspect_declarations_phase.dart', _introspectDeclarationsPhaseCode, @@ -1633,10 +1775,12 @@ $declarationCode var library = await buildLibrary(''' import 'introspect_declarations_phase.dart'; - $declarationCode ''', preBuildSequence: [ - {'package:test/introspect_declarations_phase.dart'} + { + 'package:test/introspect_shared.dart', + 'package:test/introspect_declarations_phase.dart', + } ]); for (final class_ in library.definingCompilationUnit.classes) {