From e5c685e669f95ee3ce1a1ee2f09c477c4374e3e3 Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Thu, 10 Oct 2024 15:57:45 +0000 Subject: [PATCH] Make AugmentResponse more like MacroExecutionResult in existing apis. In preparation for adding builder types, and making AugmentResponse not visible in the macro APIs any more. Will do that after #104 is also merged, as we really need both. --- .../lib/macro_implementation.dart | 30 ++++++++- .../_cfe_macros/lib/macro_implementation.dart | 30 ++++++++- pkgs/_macro_client/lib/macro_client.dart | 2 +- pkgs/_macro_host/test/macro_host_test.dart | 22 ++++--- pkgs/_test_macros/lib/declare_x_macro.dart | 8 ++- pkgs/_test_macros/lib/json_codable.dart | 16 ++--- pkgs/_test_macros/lib/query_class.dart | 7 ++- .../lib/src/macro_service.g.dart | 46 ++++++++++++-- schemas/macro_service.schema.json | 61 ++++++++++++++++++- .../dart_model_generator/lib/definitions.dart | 47 +++++++++++++- .../lib/generate_dart_model.dart | 5 +- 11 files changed, 233 insertions(+), 41 deletions(-) diff --git a/pkgs/_analyzer_macros/lib/macro_implementation.dart b/pkgs/_analyzer_macros/lib/macro_implementation.dart index 09b48b08..2ada92d6 100644 --- a/pkgs/_analyzer_macros/lib/macro_implementation.dart +++ b/pkgs/_analyzer_macros/lib/macro_implementation.dart @@ -143,10 +143,34 @@ class AnalyzerMacroExecutionResult static Future dartModelToInjected( macros_api_v1.MacroTarget target, AugmentResponse augmentResponse) async { final declarations = []; - for (final augmentation in augmentResponse.augmentations) { - declarations.add(macros_api_v1.DeclarationCode.fromParts( - await _resolveNames(augmentation.code))); + if (augmentResponse.typeAugmentations?.isNotEmpty == true) { + // TODO: Handle multiple type augmentations, or augmentations where the + // target is itself a member of a type and not the type. + final entry = augmentResponse.typeAugmentations!.entries.single; + if (entry.key != target.qualifiedName.name) { + throw UnimplementedError( + 'Type augmentations are only implemented when the type is the ' + 'target of the augmentation.'); + } + for (final augmentation in entry.value) { + declarations.add(macros_api_v1.DeclarationCode.fromParts( + await _resolveNames(augmentation.code))); + } } + + if (augmentResponse.enumValueAugmentations?.isNotEmpty == true) { + throw UnimplementedError('Enum value augmentations are not implemented'); + } + if (augmentResponse.extendsTypeAugmentations?.isNotEmpty == true || + augmentResponse.interfaceAugmentations?.isNotEmpty == true || + augmentResponse.mixinAugmentations?.isNotEmpty == true) { + throw UnimplementedError('Type augmentations are not implemented'); + } + if (augmentResponse.libraryAugmentations?.isNotEmpty == true || + augmentResponse.newTypeNames?.isNotEmpty == true) { + throw UnimplementedError('Library augmentations are not implemented'); + } + return AnalyzerMacroExecutionResult(target, declarations); } diff --git a/pkgs/_cfe_macros/lib/macro_implementation.dart b/pkgs/_cfe_macros/lib/macro_implementation.dart index 746e494b..5742a655 100644 --- a/pkgs/_cfe_macros/lib/macro_implementation.dart +++ b/pkgs/_cfe_macros/lib/macro_implementation.dart @@ -145,10 +145,34 @@ class CfeMacroExecutionResult implements macros_api_v1.MacroExecutionResult { static Future dartModelToInjected( macros_api_v1.MacroTarget target, AugmentResponse augmentResponse) async { final declarations = []; - for (final augmentation in augmentResponse.augmentations) { - declarations.add(macros_api_v1.DeclarationCode.fromParts( - await _resolveNames(augmentation.code))); + if (augmentResponse.typeAugmentations?.isNotEmpty == true) { + // TODO: Handle multiple type augmentations, or augmentations where the + // target is itself a member of a type and not the type. + final entry = augmentResponse.typeAugmentations!.entries.single; + if (entry.key != target.qualifiedName.name) { + throw UnimplementedError( + 'Type augmentations are only implemented when the type is the ' + 'target of the augmentation.'); + } + for (final augmentation in entry.value) { + declarations.add(macros_api_v1.DeclarationCode.fromParts( + await _resolveNames(augmentation.code))); + } } + + if (augmentResponse.enumValueAugmentations?.isNotEmpty == true) { + throw UnimplementedError('Enum value augmentations are not implemented'); + } + if (augmentResponse.extendsTypeAugmentations?.isNotEmpty == true || + augmentResponse.interfaceAugmentations?.isNotEmpty == true || + augmentResponse.mixinAugmentations?.isNotEmpty == true) { + throw UnimplementedError('Type augmentations are not implemented'); + } + if (augmentResponse.libraryAugmentations?.isNotEmpty == true || + augmentResponse.newTypeNames?.isNotEmpty == true) { + throw UnimplementedError('Library augmentations are not implemented'); + } + return CfeMacroExecutionResult(target, declarations); } diff --git a/pkgs/_macro_client/lib/macro_client.dart b/pkgs/_macro_client/lib/macro_client.dart index 39ecb573..23c2ab33 100644 --- a/pkgs/_macro_client/lib/macro_client.dart +++ b/pkgs/_macro_client/lib/macro_client.dart @@ -131,7 +131,7 @@ class MacroClient { 'Unexpected phase ${augmentRequest.phase}, ' 'expected 1, 2, or 3.') } ?? - AugmentResponse(augmentations: []), + AugmentResponse(), requestId: hostRequest.id))); } default: diff --git a/pkgs/_macro_host/test/macro_host_test.dart b/pkgs/_macro_host/test/macro_host_test.dart index c8c1da83..ba6ce12e 100644 --- a/pkgs/_macro_host/test/macro_host_test.dart +++ b/pkgs/_macro_host/test/macro_host_test.dart @@ -39,9 +39,10 @@ void main() { phase: 2, target: QualifiedName( name: 'Foo', uri: 'package:foo/foo.dart'))), - Scope.macro.run(() => AugmentResponse(augmentations: [ - Augmentation(code: [Code.string('int get x => 3;')]) - ]))); + Scope.macro.run(() => AugmentResponse() + ..typeAugmentations!['Foo'] = [ + Augmentation(code: [Code.string('int get x => 3;')]) + ])); }); test('hosts a macro, responds to queries', () async { @@ -67,13 +68,14 @@ void main() { phase: 3, target: QualifiedName( uri: 'package:foo/foo.dart', name: 'Foo'))), - Scope.macro.run(() => AugmentResponse(augmentations: [ - Augmentation(code: [ - Code.string( - '// {"uris":{"package:foo/foo.dart":{"scopes":{"Foo":{' - '"members":{},"properties":{"isClass":true}}}}}}') - ]) - ]))); + Scope.macro.run(() => AugmentResponse() + ..typeAugmentations!['Foo'] = [ + Augmentation(code: [ + Code.string( + '// {"uris":{"package:foo/foo.dart":{"scopes":{"Foo":{' + '"members":{},"properties":{"isClass":true}}}}}}') + ]) + ])); }); test('hosts two macros', () async { diff --git a/pkgs/_test_macros/lib/declare_x_macro.dart b/pkgs/_test_macros/lib/declare_x_macro.dart index c51d0573..711bf3bc 100644 --- a/pkgs/_test_macros/lib/declare_x_macro.dart +++ b/pkgs/_test_macros/lib/declare_x_macro.dart @@ -26,10 +26,12 @@ class DeclareXImplementation implements ClassDeclarationsMacro { Host host, AugmentRequest request) async { // TODO(davidmorgan): make the host only run in the phases requested so // that this is not needed. - if (request.phase != 2) return AugmentResponse(augmentations: []); + if (request.phase != 2) return AugmentResponse(); // TODO(davidmorgan): still need to pass through the augment target. - return AugmentResponse( - augmentations: [Augmentation(code: expandTemplate('int get x => 3;'))]); + return AugmentResponse() + ..typeAugmentations![request.target.name] = [ + Augmentation(code: expandTemplate('int get x => 3;')) + ]; } } diff --git a/pkgs/_test_macros/lib/json_codable.dart b/pkgs/_test_macros/lib/json_codable.dart index bfb4adad..6f32732c 100644 --- a/pkgs/_test_macros/lib/json_codable.dart +++ b/pkgs/_test_macros/lib/json_codable.dart @@ -30,13 +30,14 @@ class JsonCodableImplementation Future buildDeclarationsForClass( Host host, AugmentRequest request) async { final target = request.target; - return AugmentResponse(augmentations: [ - Augmentation(code: expandTemplate(''' + return AugmentResponse() + ..typeAugmentations![request.target.name] = [ + Augmentation(code: expandTemplate(''' // TODO(davidmorgan): see https://github.com/dart-lang/macros/issues/80. // external ${target.name}.fromJson($_jsonMapType json); // external $_jsonMapType toJson(); ''')) - ]); + ]; } @override @@ -49,10 +50,11 @@ class JsonCodableImplementation // TODO(davidmorgan): put `extends` information directly in `Interface`. final superclassName = MacroScope.current.typeSystem.supertypeOf(target); - return AugmentResponse(augmentations: [ - await _generateFromJson(host, model, target, superclassName, clazz), - await _generateToJson(host, model, target, superclassName, clazz) - ]); + return AugmentResponse() + ..typeAugmentations![request.target.name] = [ + await _generateFromJson(host, model, target, superclassName, clazz), + await _generateToJson(host, model, target, superclassName, clazz) + ]; } Future _generateFromJson( diff --git a/pkgs/_test_macros/lib/query_class.dart b/pkgs/_test_macros/lib/query_class.dart index a34c7acf..3a41797b 100644 --- a/pkgs/_test_macros/lib/query_class.dart +++ b/pkgs/_test_macros/lib/query_class.dart @@ -30,8 +30,9 @@ class QueryClassImplementation implements ClassDefinitionsMacro { final model = await host.query(Query( target: request.target, )); - return AugmentResponse(augmentations: [ - Augmentation(code: expandTemplate('// ${json.encode(model)}')) - ]); + return AugmentResponse() + ..typeAugmentations![request.target.name] = [ + Augmentation(code: expandTemplate('// ${json.encode(model)}')) + ]; } } diff --git a/pkgs/macro_service/lib/src/macro_service.g.dart b/pkgs/macro_service/lib/src/macro_service.g.dart index 7364aada..ddd7d381 100644 --- a/pkgs/macro_service/lib/src/macro_service.g.dart +++ b/pkgs/macro_service/lib/src/macro_service.g.dart @@ -32,14 +32,50 @@ extension type AugmentRequest.fromJson(Map node) extension type AugmentResponse.fromJson(Map node) implements Object { AugmentResponse({ - List? augmentations, + List? libraryAugmentations, + List? newTypeNames, }) : this.fromJson({ - if (augmentations != null) 'augmentations': augmentations, + 'enumValueAugmentations': {}, + 'extendsTypeAugmentations': {}, + 'interfaceAugmentations': {}, + if (libraryAugmentations != null) + 'libraryAugmentations': libraryAugmentations, + 'mixinAugmentations': {}, + if (newTypeNames != null) 'newTypeNames': newTypeNames, + 'typeAugmentations': {}, }); - /// The augmentations. - List get augmentations => - (node['augmentations'] as List).cast(); + /// Any augmentations to enum values that should be applied to an enum as a result of executing a macro, indexed by the name of the enum. + Map>? get enumValueAugmentations => + (node['enumValueAugmentations'] as Map?) + ?.deepCast>((v) => (v as List).cast()); + + /// Any extends clauses that should be added to types as a result of executing a macro, indexed by the name of the augmented type declaration. + Map>? get extendsTypeAugmentations => + (node['extendsTypeAugmentations'] as Map?) + ?.deepCast>((v) => (v as List).cast()); + + /// Any interfaces that should be added to types as a result of executing a macro, indexed by the name of the augmented type declaration. + Map>? get interfaceAugmentations => + (node['interfaceAugmentations'] as Map?) + ?.deepCast>((v) => (v as List).cast()); + + /// Any augmentations that should be applied to the library as a result of executing a macro. + List? get libraryAugmentations => + (node['libraryAugmentations'] as List?)?.cast(); + + /// Any mixins that should be added to types as a result of executing a macro, indexed by the name of the augmented type declaration. + Map>? get mixinAugmentations => + (node['mixinAugmentations'] as Map?) + ?.deepCast>((v) => (v as List).cast()); + + /// The names of any new types declared in [libraryAugmentations]. + List? get newTypeNames => (node['newTypeNames'] as List?)?.cast(); + + /// Any augmentations that should be applied to a class as a result of executing a macro, indexed by the name of the class. + Map>? get typeAugmentations => + (node['typeAugmentations'] as Map?) + ?.deepCast>((v) => (v as List).cast()); } /// Request could not be handled. diff --git a/schemas/macro_service.schema.json b/schemas/macro_service.schema.json index fdc68b9a..4567aa49 100644 --- a/schemas/macro_service.schema.json +++ b/schemas/macro_service.schema.json @@ -30,12 +30,69 @@ "type": "object", "description": "Macro's response to an [AugmentRequest]: the resulting augmentations.", "properties": { - "augmentations": { + "enumValueAugmentations": { + "type": "object", + "description": "Any augmentations to enum values that should be applied to an enum as a result of executing a macro, indexed by the name of the enum.", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "file:dart_model.schema.json#/$defs/Augmentation" + } + } + }, + "extendsTypeAugmentations": { + "type": "object", + "description": "Any extends clauses that should be added to types as a result of executing a macro, indexed by the name of the augmented type declaration.", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "file:dart_model.schema.json#/$defs/Augmentation" + } + } + }, + "interfaceAugmentations": { + "type": "object", + "description": "Any interfaces that should be added to types as a result of executing a macro, indexed by the name of the augmented type declaration.", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "file:dart_model.schema.json#/$defs/Augmentation" + } + } + }, + "libraryAugmentations": { "type": "array", - "description": "The augmentations.", + "description": "Any augmentations that should be applied to the library as a result of executing a macro.", "items": { "$ref": "file:dart_model.schema.json#/$defs/Augmentation" } + }, + "mixinAugmentations": { + "type": "object", + "description": "Any mixins that should be added to types as a result of executing a macro, indexed by the name of the augmented type declaration.", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "file:dart_model.schema.json#/$defs/Augmentation" + } + } + }, + "newTypeNames": { + "type": "array", + "description": "The names of any new types declared in [libraryAugmentations].", + "items": { + "type": "string" + } + }, + "typeAugmentations": { + "type": "object", + "description": "Any augmentations that should be applied to a class as a result of executing a macro, indexed by the name of the class.", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "file:dart_model.schema.json#/$defs/Augmentation" + } + } } } }, diff --git a/tool/dart_model_generator/lib/definitions.dart b/tool/dart_model_generator/lib/definitions.dart index fb0a4639..e6bf21ad 100644 --- a/tool/dart_model_generator/lib/definitions.dart +++ b/tool/dart_model_generator/lib/definitions.dart @@ -360,9 +360,52 @@ static QualifiedName parse(String string) { "Macro's response to an [AugmentRequest]: the resulting " 'augmentations.', properties: [ - Property('augmentations', + Property('enumValueAugmentations', + type: 'Map>', + description: + 'Any augmentations to enum values that should be applied ' + 'to an enum as a result of executing a macro, indexed by ' + 'the name of the enum.', + nullable: true), + Property('extendsTypeAugmentations', + type: 'Map>', + description: + 'Any extends clauses that should be added to types as a ' + 'result of executing a macro, indexed by the name ' + 'of the augmented type declaration.', + nullable: true), + Property('interfaceAugmentations', + type: 'Map>', + description: + 'Any interfaces that should be added to types as a ' + 'result of executing a macro, indexed by the name ' + 'of the augmented type declaration.', + nullable: true), + Property('libraryAugmentations', type: 'List', - description: 'The augmentations.'), + description: + 'Any augmentations that should be applied to the library ' + 'as a result of executing a macro.', + nullable: true), + Property('mixinAugmentations', + type: 'Map>', + description: + 'Any mixins that should be added to types as a result of ' + 'executing a macro, indexed by the name of the ' + 'augmented type declaration.', + nullable: true), + Property('newTypeNames', + type: 'List', + description: 'The names of any new types declared in ' + '[libraryAugmentations].', + nullable: true), + Property('typeAugmentations', + type: 'Map>', + description: + 'Any augmentations that should be applied to a class as ' + 'a result of executing a macro, indexed by the ' + 'name of the class.', + nullable: true), ]), Definition.clazz('ErrorResponse', description: 'Request could not be handled.', diff --git a/tool/dart_model_generator/lib/generate_dart_model.dart b/tool/dart_model_generator/lib/generate_dart_model.dart index 42c6b5ee..e1e46cee 100644 --- a/tool/dart_model_generator/lib/generate_dart_model.dart +++ b/tool/dart_model_generator/lib/generate_dart_model.dart @@ -353,7 +353,7 @@ class TypeReference { return '($rawCast)$q.deepCast(' '(v) => ${elementType!.castExpression('v')})'; } else if (isList) { - if (elementType!.elementType == null) return '($rawCast).cast()'; + if (elementType!.elementType == null) return '($rawCast)$q.cast()'; throw UnsupportedError('Deep casting for lists isn\'t yet supported.'); } else { return rawCast; @@ -493,6 +493,7 @@ class ClassTypeDefinition implements Definition { result.writeln(' $name() : '); } else { result.writeln(' $name({'); + // TODO: Why are we excluding Map properties? for (final property in propertiesExceptMap) { result.writeln(property.parameterCode); } @@ -514,7 +515,7 @@ class ClassTypeDefinition implements Definition { result.writeln('{'); for (final property in properties) { if (property.type.isMap) { - result.writeln("'${property.name}': {},"); + result.writeln("'${property.name}': {},"); } else { result.writeln(property.namedArgumentCode); }