diff --git a/goldens/foo/lib/foo.analyzer.json b/goldens/foo/lib/foo.analyzer.json index 922c4fcc..4f908f41 100644 --- a/goldens/foo/lib/foo.analyzer.json +++ b/goldens/foo/lib/foo.analyzer.json @@ -11,6 +11,16 @@ "isField": true, "isMethod": false, "isStatic": false + }, + "returnType": { + "type": "NamedTypeDesc", + "value": { + "name": { + "uri": "dart:core", + "name": "int" + }, + "instantiation": [] + } } } } @@ -24,6 +34,16 @@ "isField": true, "isMethod": false, "isStatic": false + }, + "returnType": { + "type": "NamedTypeDesc", + "value": { + "name": { + "uri": "dart:core", + "name": "int" + }, + "instantiation": [] + } } } } @@ -33,25 +53,6 @@ }, "types": { "named": { - "package:foo/foo.dart#Foo": { - "typeParameters": [], - "self": { - "name": { - "uri": "package:foo/foo.dart", - "name": "Foo" - }, - "instantiation": [] - }, - "supertypes": [ - { - "name": { - "uri": "dart:core", - "name": "Object" - }, - "instantiation": [] - } - ] - }, "dart:core#Object": { "typeParameters": [], "self": { @@ -112,6 +113,25 @@ } ] }, + "package:foo/foo.dart#Foo": { + "typeParameters": [], + "self": { + "name": { + "uri": "package:foo/foo.dart", + "name": "Foo" + }, + "instantiation": [] + }, + "supertypes": [ + { + "name": { + "uri": "dart:core", + "name": "Object" + }, + "instantiation": [] + } + ] + }, "package:foo/foo.dart#Bar": { "typeParameters": [], "self": { diff --git a/goldens/foo/lib/json_codable.analyzer.augmentations b/goldens/foo/lib/json_codable.analyzer.augmentations new file mode 100644 index 00000000..8ce63b9c --- /dev/null +++ b/goldens/foo/lib/json_codable.analyzer.augmentations @@ -0,0 +1,65 @@ +part of 'package:foo/json_codable.dart'; + +import 'package:foo/json_codable.dart' as prefix0; +import 'dart:core' as prefix1; + +augment class A { +external prefix0.A.fromJson(prefix1.Map json); +external prefix1.Map toJson(); + +augment prefix0.A.fromJson(prefix1.Map json) : +boolField = json[r'boolField'] as prefix1.bool, +nullableBoolField = json[r'nullableBoolField'] as prefix1.bool?, +stringField = json[r'stringField'] as prefix1.String, +nullableStringField = json[r'nullableStringField'] as prefix1.String?, +intField = json[r'intField'] as prefix1.int, +nullableIntField = json[r'nullableIntField'] as prefix1.int?, +doubleField = json[r'doubleField'] as prefix1.double, +nullableDoubleField = json[r'nullableDoubleField'] as prefix1.double?, +numField = json[r'numField'] as prefix1.num, +nullableNumField = json[r'nullableNumField'] as prefix1.num?, +listOfSerializableField = json[r'listOfSerializableField'] == null ? null : [for (final item in json[r'listOfSerializableField'] as prefix1.List) item == null ? null : prefix0.C.fromJson(item as prefix1.Map], +nullableListOfSerializableField = [for (final item in json[r'nullableListOfSerializableField'] as prefix1.List) item == null ? null : prefix0.C.fromJson(item as prefix1.Map], +setOfSerializableField = json[r'setOfSerializableField'] == null ? null : {for (final item in json[r'setOfSerializableField'] as prefix1.Set) item == null ? null : prefix0.C.fromJson(item as prefix1.Map}, +nullableSetOfSerializableField = {for (final item in json[r'nullableSetOfSerializableField'] as prefix1.Set) item == null ? null : prefix0.C.fromJson(item as prefix1.Map}, +mapOfSerializableField = json[r'mapOfSerializableField'] == null ? null : {for (final (:key, :value) in json[r'mapOfSerializableField'] as prefix1.Map) key: value == null ? null : prefix0.C.fromJson(value as prefix1.Map}, +nullableMapOfSerializableField = {for (final (:key, :value) in json[r'nullableMapOfSerializableField'] as prefix1.Map) key: value == null ? null : prefix0.C.fromJson(value as prefix1.Map}; + +prefix1.Map toJson() { + final json = prefix1.Map{}; +json[r'boolField'] = boolField; +json[r'nullableBoolField'] = nullableBoolField; +json[r'stringField'] = stringField; +json[r'nullableStringField'] = nullableStringField; +json[r'intField'] = intField; +json[r'nullableIntField'] = nullableIntField; +json[r'doubleField'] = doubleField; +json[r'nullableDoubleField'] = nullableDoubleField; +json[r'numField'] = numField; +json[r'nullableNumField'] = nullableNumField; +json[r'listOfSerializableField'] = listOfSerializableField == null ? null : [for (final item in listOfSerializableField) item == null ? null : item.toJson()]; +json[r'nullableListOfSerializableField'] = [for (final item in nullableListOfSerializableField) item == null ? null : item.toJson()]; +json[r'setOfSerializableField'] = setOfSerializableField == null ? null : [for (final item in setOfSerializableField) item == null ? null : item.toJson()]; +json[r'nullableSetOfSerializableField'] = [for (final item in nullableSetOfSerializableField) item == null ? null : item.toJson()]; +json[r'mapOfSerializableField'] = mapOfSerializableField == null ? null : {for (final (:key, :value) in mapOfSerializableField.entries) key: value == null ? null : value.toJson()}; +json[r'nullableMapOfSerializableField'] = {for (final (:key, :value) in nullableMapOfSerializableField.entries) key: value == null ? null : value.toJson()}; + + return json; +}; + +} +augment class C { +external prefix0.C.fromJson(prefix1.Map json); +external prefix1.Map toJson(); + +augment prefix0.C.fromJson(prefix1.Map json) : +boolField = json[r'boolField'] as prefix1.bool; + +prefix1.Map toJson() { + final json = prefix1.Map{}; +json[r'boolField'] = boolField; + + return json; +}; + +} diff --git a/goldens/foo/lib/json_codable.dart b/goldens/foo/lib/json_codable.dart new file mode 100644 index 00000000..6ea32858 --- /dev/null +++ b/goldens/foo/lib/json_codable.dart @@ -0,0 +1,45 @@ +// Copyright (c) 2024, 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:_test_macros/json_codable.dart'; + +@JsonCodable() +class A { + final bool boolField; + + final bool? nullableBoolField; + + final String stringField; + + final String? nullableStringField; + + final int intField; + + final int? nullableIntField; + + final double doubleField; + + final double? nullableDoubleField; + + final num numField; + + final num? nullableNumField; + + final List listOfSerializableField; + + final List? nullableListOfSerializableField; + + final Set setOfSerializableField; + + final Set? nullableSetOfSerializableField; + + final Map mapOfSerializableField; + + final Map? nullableMapOfSerializableField; +} + +@JsonCodable() +class C { + final bool boolField; +} diff --git a/pkgs/_analyzer_macros/lib/macro_implementation.dart b/pkgs/_analyzer_macros/lib/macro_implementation.dart index 7d5808bb..0594c47b 100644 --- a/pkgs/_analyzer_macros/lib/macro_implementation.dart +++ b/pkgs/_analyzer_macros/lib/macro_implementation.dart @@ -87,7 +87,7 @@ class AnalyzerRunningMacro implements injected.RunningMacro { DeclarationPhaseIntrospector declarationsPhaseIntrospector) async { // TODO(davidmorgan): this is a hack to access analyzer internals; remove. introspector = declarationsPhaseIntrospector; - return AnalyzerMacroExecutionResult( + return await AnalyzerMacroExecutionResult.expandTemplates( target, await _impl._host.augment( name, AugmentRequest(phase: 2, target: target.qualifiedName))); @@ -99,7 +99,7 @@ class AnalyzerRunningMacro implements injected.RunningMacro { DefinitionPhaseIntrospector definitionPhaseIntrospector) async { // TODO(davidmorgan): this is a hack to access analyzer internals; remove. introspector = definitionPhaseIntrospector; - return AnalyzerMacroExecutionResult( + return await AnalyzerMacroExecutionResult.expandTemplates( target, await _impl._host.augment( name, AugmentRequest(phase: 3, target: target.qualifiedName))); @@ -110,7 +110,7 @@ class AnalyzerRunningMacro implements injected.RunningMacro { MacroTarget target, TypePhaseIntrospector typePhaseIntrospector) async { // TODO(davidmorgan): this is a hack to access analyzer internals; remove. introspector = typePhaseIntrospector; - return AnalyzerMacroExecutionResult( + return await AnalyzerMacroExecutionResult.expandTemplates( target, await _impl._host.augment( name, AugmentRequest(phase: 1, target: target.qualifiedName))); @@ -123,9 +123,25 @@ class AnalyzerRunningMacro implements injected.RunningMacro { /// functionality of `MacroExecutionResult`. class AnalyzerMacroExecutionResult implements injected.MacroExecutionResult { final MacroTarget target; - final AugmentResponse augmentResponse; - - AnalyzerMacroExecutionResult(this.target, this.augmentResponse); + @override + final Map> typeAugmentations; + + AnalyzerMacroExecutionResult( + this.target, Iterable declarations) + // TODO(davidmorgan): this assumes augmentations are for the macro + // application target. Instead, it should be explicit in + // `AugmentResponse`. + : typeAugmentations = {(target as Declaration).identifier: declarations}; + + static Future expandTemplates( + MacroTarget target, AugmentResponse augmentResponse) async { + final declarations = []; + for (final augmentation in augmentResponse.augmentations) { + declarations.add( + DeclarationCode.fromParts(await _expandTemplates(augmentation.code))); + } + return AnalyzerMacroExecutionResult(target, declarations); + } @override List get diagnostics => []; @@ -154,16 +170,6 @@ class AnalyzerMacroExecutionResult implements injected.MacroExecutionResult { @override void serialize(Object serializer) => throw UnimplementedError(); - - @override - Map> get typeAugmentations => { - // TODO(davidmorgan): this assumes augmentations are for the macro - // application target. Instead, it should be explicit in - // `AugmentResponse`. - (target as Declaration).identifier: augmentResponse.augmentations - .map((a) => DeclarationCode.fromParts([a.code])) - .toList(), - }; } extension MacroTargetExtension on MacroTarget { @@ -175,3 +181,37 @@ extension MacroTargetExtension on MacroTarget { name: element.displayName); } } + +/// Converts [code] to a mix of `Identifier` and `String`. +/// +/// Looks up references of the form `{{uri#name}}` using `resolveIdentifier`. +/// +/// TODO(davidmorgan): move to the client side. +Future> _expandTemplates(String code) async { + final result = []; + var index = 0; + while (index < code.length) { + final start = code.indexOf('{{', index); + if (start == -1) { + result.add(code.substring(index)); + break; + } + result.add(code.substring(index, start)); + final end = code.indexOf('}}', start); + if (end == -1) { + throw ArgumentError('Unmatched opening brace: $code'); + } + final name = code.substring(start + 2, end); + final parts = name.split('#'); + if (parts.length != 2) { + throw ArgumentError('Expected "uri#name" in: $name'); + } + final uri = Uri.parse(parts[0]); + final identifier = await (introspector as TypePhaseIntrospector) + // ignore: deprecated_member_use + .resolveIdentifier(uri, parts[1]); + result.add(identifier); + index = end + 2; + } + return result; +} diff --git a/pkgs/_analyzer_macros/lib/query_service.dart b/pkgs/_analyzer_macros/lib/query_service.dart index ad0bcca2..47fa28f7 100644 --- a/pkgs/_analyzer_macros/lib/query_service.dart +++ b/pkgs/_analyzer_macros/lib/query_service.dart @@ -5,6 +5,7 @@ import 'package:_macro_host/macro_host.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; +import 'package:analyzer/dart/element/type_provider.dart'; // ignore: implementation_imports import 'package:analyzer/src/summary2/linked_element_factory.dart' as analyzer; import 'package:dart_model/dart_model.dart' hide InterfaceType; @@ -31,64 +32,76 @@ class AnalyzerQueryService implements QueryService { final uri = target.uri; final library = _elementFactory.libraryOfUri2(Uri.parse(uri)); final clazz = library.getClass(target.name)!; + final types = AnalyzerTypeHierarchy(library.typeProvider) + ..addInterfaceElement(clazz); + final interface = Interface(); for (final field in clazz.fields) { interface.members[field.name] = Member( properties: Properties( - isAbstract: field.isAbstract, - isGetter: false, - isField: true, - isMethod: false, - isStatic: field.isStatic, - )); + isAbstract: field.isAbstract, + isGetter: false, + isField: true, + isMethod: false, + isStatic: field.isStatic, + ), + returnType: types.addDartType(field.type)); } - return Model(types: _buildTypeHierarchy(library, {clazz})) + return Model(types: types.typeHierarchy) ..uris[uri] = (Library()..scopes[clazz.name] = interface); } +} - TypeHierarchy _buildTypeHierarchy( - LibraryElement library, Set classes) { - // These classes need to be present in every type hierarchy due to their - // special role. Other types are only relevant if reachable from [classes]. - final typeProvider = library.typeProvider; - classes.add(typeProvider.objectElement); - classes.add(typeProvider.nullElement); - classes.add(typeProvider.futureElement); +/// Converts between analyzer types and `dart_model` types. +class AnalyzerTypeHierarchy { + final TypeProvider typeProvider; + final AnalyzerTypesToMacros translator = const AnalyzerTypesToMacros(); + final TypeHierarchy typeHierarchy = TypeHierarchy(); + final TypeTranslationContext context = TypeTranslationContext(); - // Make sure we include all supertypes of involved classes to close the - // type hierarchy. - for (final included in classes.toList()) { - for (final superType in included.allSupertypes) { - classes.add(superType.element); - } - } + AnalyzerTypeHierarchy(this.typeProvider) { + // Types that are always needed. + addInterfaceElement(typeProvider.objectElement); + addInterfaceElement(typeProvider.nullElement); + addInterfaceElement(typeProvider.futureElement); + } - final context = TypeTranslationContext(); - const translator = AnalyzerTypesToMacros(); - final result = TypeHierarchy(); - for (final element in classes) { - final asNamedType = element.thisType - .acceptWithArgument(translator, context) - .asNamedTypeDesc; + /// Adds [type] to the hierarchy and returns its [StaticTypeDesc]. + StaticTypeDesc addDartType(DartType type) => + type.acceptWithArgument(translator, context); - final superTypes = [ - if (element.supertype case final supertype?) supertype, - ...element.interfaces, - ...element.mixins, - ]; + /// Adds [element] and any supertypes to the hierarchy, if not already + /// present. + void addInterfaceElement(InterfaceElement element) { + final asNamedType = element.thisType + .acceptWithArgument(translator, context) + .asNamedTypeDesc; - result.named[asNamedType.name.asString] = TypeHierarchyEntry( - self: asNamedType, - typeParameters: [ - for (final typeParameter in element.typeParameters) - translator.translateTypeParameter(typeParameter, context), - ], - supertypes: [ - for (final superType in superTypes) - superType.acceptWithArgument(translator, context).asNamedTypeDesc - ], - ); + final maybeEntry = typeHierarchy.named[asNamedType.name.asString]; + if (maybeEntry != null) { + return; } - return result; + + final superTypes = [ + if (element.supertype case final supertype?) supertype, + ...element.interfaces, + ...element.mixins, + ]; + + for (final type in superTypes) { + addInterfaceElement(type.element); + } + + typeHierarchy.named[asNamedType.name.asString] = TypeHierarchyEntry( + self: asNamedType, + typeParameters: [ + for (final typeParameter in element.typeParameters) + translator.translateTypeParameter(typeParameter, context), + ], + supertypes: [ + for (final superType in superTypes) + superType.acceptWithArgument(translator, context).asNamedTypeDesc + ], + ); } } diff --git a/pkgs/_analyzer_macros/test/golden_test.dart b/pkgs/_analyzer_macros/test/golden_test.dart index 83ebb1c8..96c20a79 100644 --- a/pkgs/_analyzer_macros/test/golden_test.dart +++ b/pkgs/_analyzer_macros/test/golden_test.dart @@ -69,10 +69,6 @@ void main() { } else { applicationGoldenFile = null; } - - if (introspectionGoldenFile == null && applicationGoldenFile == null) { - throw StateError('Setup failed, no goldens for $path'); - } }); if (updateGoldens) { @@ -99,6 +95,11 @@ void main() { } test(relativePath, () async { + if (introspectionGoldenFile == null && applicationGoldenFile == null) { + // Nothing to check. + return; + } + final resolvedLibrary = (await analysisContext.currentSession .getResolvedLibrary(path)) as ResolvedLibraryResult; final augmentationUnit = diff --git a/pkgs/_cfe_macros/test/golden_test.dart b/pkgs/_cfe_macros/test/golden_test.dart index da9ca51e..aa810979 100644 --- a/pkgs/_cfe_macros/test/golden_test.dart +++ b/pkgs/_cfe_macros/test/golden_test.dart @@ -78,10 +78,6 @@ void main() { } else { applicationGoldenFile = null; } - - if (introspectionGoldenFile == null && applicationGoldenFile == null) { - throw StateError('Setup failed, no goldens for $path'); - } }); if (updateGoldens) { tearDown(() { @@ -107,6 +103,11 @@ void main() { } test(relativePath, () async { + if (introspectionGoldenFile == null && applicationGoldenFile == null) { + // Nothing to check. + return; + } + final packagesUri = Isolate.packageConfigSync; final outputFile = File('${tempDir.path}/$relativePath.dill'); diff --git a/pkgs/_test_macros/lib/json_codable.dart b/pkgs/_test_macros/lib/json_codable.dart new file mode 100644 index 00000000..24d1ec07 --- /dev/null +++ b/pkgs/_test_macros/lib/json_codable.dart @@ -0,0 +1,183 @@ +// Copyright (c) 2024, 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:dart_model/dart_model.dart'; +import 'package:macro/macro.dart'; +import 'package:macro_service/macro_service.dart'; + +/// A macro which adds a `fromJson(Map json)` JSON decoding +/// constructor to a class. +class JsonCodable { + const JsonCodable(); +} + +final _jsonMapType = + '{{dart:core#Map}}<{{dart:core#String}}, {{dart:core#Object}}?>'; + +class JsonCodableImplementation implements Macro { + @override + MacroDescription get description => MacroDescription( + annotation: QualifiedName( + uri: 'package:_test_macros/json_codable.dart', name: 'JsonCodable'), + runsInPhases: [2, 3]); + + @override + Future augment(Host host, AugmentRequest request) async { + return switch (request.phase) { + // TODO(davidmorgan): these should be phases 2 and 3, but that doesn't + // work right now, it gives no output for phase 3. Investigate and fix. + 1 => phase1(host, request), + 2 => phase2(host, request), + _ => AugmentResponse(augmentations: []), + }; + } + + Future phase1(Host host, AugmentRequest request) async { + final target = request.target; + return AugmentResponse(augmentations: [ + Augmentation(code: ''' +external ${target.code}.fromJson($_jsonMapType json); +external $_jsonMapType toJson(); + '''), + ]); + } + + Future phase2(Host host, AugmentRequest request) async { + final result = []; + + final target = request.target; + final model = await host.query(Query(target: target)); + final clazz = model.uris[target.uri]!.scopes[target.name]!; + // TODO(davidmorgan): check for super `fromJson`. + final initializers = []; + for (final field + in clazz.members.entries.where((m) => m.value.properties.isField)) { + final name = field.key; + final type = field.value.returnType; + initializers + .add('$name = ${_convertTypeFromJson("json[r'$name']", type)}'); + } + + // TODO(davidmorgan): helper for augmenting initializers. + // See: https://github.com/dart-lang/sdk/blob/main/pkg/_macros/lib/src/executor/builder_impls.dart#L500 + result.add(Augmentation(code: ''' +augment ${target.code}.fromJson($_jsonMapType json) : +${initializers.join(',\n')}; +''')); + + final serializers = []; + for (final field + in clazz.members.entries.where((m) => m.value.properties.isField)) { + final name = field.key; + final type = field.value.returnType; + serializers.add("json[r'$name'] = ${_convertTypeToJson(name, type)}"); + } + + // TODO(davidmorgan): helper for augmenting methods. + // See: https://github.com/dart-lang/sdk/blob/main/pkg/_macros/lib/src/executor/builder_impls.dart#L500 + result.add(Augmentation(code: ''' +$_jsonMapType toJson() { + final json = $_jsonMapType{}; +${serializers.map((s) => '$s;\n').join('')} + return json; +}; +''')); + + return AugmentResponse(augmentations: result); + } + + String _convertTypeFromJson(String reference, StaticTypeDesc type) { + // TODO(davidmorgan): _checkNamedType equivalent. + // TODO(davidmorgan): should this code use `StaticType` and related classes + // instead of using the extension types `StaticTypeDesc` directly? + // TODO(davidmorgan): check for and handle missing type argument(s). + final nullable = type.type == StaticTypeDescType.nullableTypeDesc; + final orNull = nullable ? '?' : ''; + final nullCheck = nullable ? '' : '$reference == null ? null : '; + final underlyingType = type.type == StaticTypeDescType.nullableTypeDesc + ? type.asNullableTypeDesc.inner + : type; + + if (underlyingType.type == StaticTypeDescType.namedTypeDesc) { + final namedType = underlyingType.asNamedTypeDesc; + if (namedType.name.uri == 'dart:core') { + switch (namedType.name.name) { + case 'bool': + case 'String': + case 'int': + case 'double': + case 'num': + return '$reference as ${namedType.name.code}$orNull'; + case 'List': + final type = namedType.instantiation.single; + return '$nullCheck [for (final item in $reference ' + 'as {{dart:core#List}}<{{dart:core#Object}}?>) ' + '${_convertTypeFromJson('item', type)}' + ']'; + case 'Set': + final type = namedType.instantiation.single; + return '$nullCheck {for (final item in $reference ' + 'as {{dart:core#Set}}<{{dart:core#Object}}?>) ' + '${_convertTypeFromJson('item', type)}' + '}'; + case 'Map': + // TODO(davidmorgan): check for and handle wrong key type. + return '$nullCheck {for (final (:key, :value) in $reference ' + 'as $_jsonMapType) key: ' + '${_convertTypeFromJson('value', namedType.instantiation.last)}' + '}'; + } + } + // TODO(davidmorgan): check for fromJson constructor. + return '$nullCheck ${namedType.name.code}.fromJson($reference as ' + '$_jsonMapType'; + } + + // TODO(davidmorgan): error reporting. + throw UnsupportedError('$type'); + } + + String _convertTypeToJson(String reference, StaticTypeDesc type) { + // TODO(davidmorgan): add _checkNamedType equivalent. + final nullable = type.type == StaticTypeDescType.nullableTypeDesc; + final nullCheck = nullable ? '' : '$reference == null ? null : '; + final underlyingType = type.type == StaticTypeDescType.nullableTypeDesc + ? type.asNullableTypeDesc.inner + : type; + + if (underlyingType.type == StaticTypeDescType.namedTypeDesc) { + final namedType = underlyingType.asNamedTypeDesc; + if (namedType.name.uri == 'dart:core') { + switch (namedType.name.name) { + case 'bool': + case 'String': + case 'int': + case 'double': + case 'num': + return reference; + case 'List': + case 'Set': + return '$nullCheck [for (final item in $reference) ' + '${_convertTypeToJson('item', namedType.instantiation.first)}' + ']'; + case 'Map': + return '$nullCheck {for (final (:key, :value) in ' + '$reference.entries) key: ' + '${_convertTypeToJson('value', namedType.instantiation.last)}' + '}'; + } + } + // TODO(davidmorgan): check for toJson method. + return '$nullCheck $reference.toJson()'; + } + + // TODO(davidmorgan): error reporting. + throw UnsupportedError('$type'); + } +} + +// TODO(davidmorgan): figure out where this should go. +extension TemplatingExtension on QualifiedName { + String get code => '{{$uri#$name}}'; +} diff --git a/pkgs/_test_macros/pubspec.yaml b/pkgs/_test_macros/pubspec.yaml index 3067cdcf..e96e0f3d 100644 --- a/pkgs/_test_macros/pubspec.yaml +++ b/pkgs/_test_macros/pubspec.yaml @@ -19,4 +19,5 @@ dev_dependencies: # TODO(language/3728): use the real feature when there is one. # macro lib/declare_x_macro.dart#DeclareX package:_test_macros/declare_x_macro.dart#DeclareXImplementation +# macro lib/json_codable.dart#JsonCodable package:_test_macros/json_codable.dart#JsonCodableImplementation # macro lib/query_class.dart#QueryClass package:_test_macros/query_class.dart#QueryClassImplementation diff --git a/pkgs/dart_model/lib/src/dart_model.g.dart b/pkgs/dart_model/lib/src/dart_model.g.dart index bc20f541..8a7b4f30 100644 --- a/pkgs/dart_model/lib/src/dart_model.g.dart +++ b/pkgs/dart_model/lib/src/dart_model.g.dart @@ -137,16 +137,22 @@ extension type Library.fromJson(Map node) implements Object { extension type Member.fromJson(Map node) implements Object { static final TypedMapSchema _schema = TypedMapSchema({ 'properties': Type.typedMapPointer, + 'returnType': Type.typedMapPointer, }); Member({ Properties? properties, + StaticTypeDesc? returnType, }) : this.fromJson(Scope.createMap( _schema, properties, + returnType, )); /// The properties of this member. Properties get properties => node['properties'] as Properties; + + /// The return type of this member, if it has one. + StaticTypeDesc get returnType => node['returnType'] as StaticTypeDesc; } /// Partial model of a corpus of Dart source code. diff --git a/schemas/dart_model.schema.json b/schemas/dart_model.schema.json index 7cac2ebe..18e1e6fb 100644 --- a/schemas/dart_model.schema.json +++ b/schemas/dart_model.schema.json @@ -113,6 +113,10 @@ "properties": { "$comment": "The properties of this member.", "$ref": "#/$defs/Properties" + }, + "returnType": { + "$comment": "The return type of this member, if it has one.", + "$ref": "#/$defs/StaticTypeDesc" } } }, diff --git a/tool/dart_model_generator/lib/definitions.dart b/tool/dart_model_generator/lib/definitions.dart index 9348a946..90055fcd 100644 --- a/tool/dart_model_generator/lib/definitions.dart +++ b/tool/dart_model_generator/lib/definitions.dart @@ -130,6 +130,7 @@ static Protocol handshakeProtocol = Protocol( Property('scopes', type: 'Map', description: 'Scopes by name.'), ]), + // TODO(davidmorgan): make `Member` a union. Definition.clazz('Member', description: 'Member of a scope.', createInBuffer: true, @@ -137,6 +138,11 @@ static Protocol handshakeProtocol = Protocol( Property('properties', type: 'Properties', description: 'The properties of this member.'), + Property( + 'returnType', + type: 'StaticTypeDesc', + description: 'The return type of this member, if it has one.', + ), ]), Definition.clazz('Model', description: 'Partial model of a corpus of Dart source code.',