Skip to content
This repository has been archived by the owner on Feb 4, 2025. It is now read-only.

Add a first JSON macro based on package:json/json.dart. #68

Merged
merged 4 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 39 additions & 19 deletions goldens/foo/lib/foo.analyzer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@
"isField": true,
"isMethod": false,
"isStatic": false
},
"returnType": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This model for properties I think is going to fall apart here. We really don't want to be modeling all the possible properties for all the possible things with a single type?

For instance, returnType is really not what the type of a field is, there is a return type on the getter sure, but not the field (it just has a type).

When you are using this Properties class, you are also going to get terrible auto complete, with all possible things that might exist on all possible declaration kinds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, definitely: as we discussed we probably want to end up with something flat but probably still giving basic "available properties" at each point.

Added a TODO in definitions.dart

// TODO(davidmorgan): make Member a union.

"type": "NamedTypeDesc",
"value": {
"name": {
"uri": "dart:core",
"name": "int"
},
"instantiation": []
}
}
}
}
Expand All @@ -24,6 +34,16 @@
"isField": true,
"isMethod": false,
"isStatic": false
},
"returnType": {
"type": "NamedTypeDesc",
"value": {
"name": {
"uri": "dart:core",
"name": "int"
},
"instantiation": []
}
}
}
}
Expand All @@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down
65 changes: 65 additions & 0 deletions goldens/foo/lib/json_codable.analyzer.augmentations
Original file line number Diff line number Diff line change
@@ -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<prefix1.String, prefix1.Object?> json);
external prefix1.Map<prefix1.String, prefix1.Object?> toJson();

augment prefix0.A.fromJson(prefix1.Map<prefix1.String, prefix1.Object?> 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<prefix1.Object?>) item == null ? null : prefix0.C.fromJson(item as prefix1.Map<prefix1.String, prefix1.Object?>],
nullableListOfSerializableField = [for (final item in json[r'nullableListOfSerializableField'] as prefix1.List<prefix1.Object?>) item == null ? null : prefix0.C.fromJson(item as prefix1.Map<prefix1.String, prefix1.Object?>],
setOfSerializableField = json[r'setOfSerializableField'] == null ? null : {for (final item in json[r'setOfSerializableField'] as prefix1.Set<prefix1.Object?>) item == null ? null : prefix0.C.fromJson(item as prefix1.Map<prefix1.String, prefix1.Object?>},
nullableSetOfSerializableField = {for (final item in json[r'nullableSetOfSerializableField'] as prefix1.Set<prefix1.Object?>) item == null ? null : prefix0.C.fromJson(item as prefix1.Map<prefix1.String, prefix1.Object?>},
mapOfSerializableField = json[r'mapOfSerializableField'] == null ? null : {for (final (:key, :value) in json[r'mapOfSerializableField'] as prefix1.Map<prefix1.String, prefix1.Object?>) key: value == null ? null : prefix0.C.fromJson(value as prefix1.Map<prefix1.String, prefix1.Object?>},
nullableMapOfSerializableField = {for (final (:key, :value) in json[r'nullableMapOfSerializableField'] as prefix1.Map<prefix1.String, prefix1.Object?>) key: value == null ? null : prefix0.C.fromJson(value as prefix1.Map<prefix1.String, prefix1.Object?>};

prefix1.Map<prefix1.String, prefix1.Object?> toJson() {
final json = prefix1.Map<prefix1.String, prefix1.Object?>{};
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<prefix1.String, prefix1.Object?> json);
external prefix1.Map<prefix1.String, prefix1.Object?> toJson();

augment prefix0.C.fromJson(prefix1.Map<prefix1.String, prefix1.Object?> json) :
boolField = json[r'boolField'] as prefix1.bool;

prefix1.Map<prefix1.String, prefix1.Object?> toJson() {
final json = prefix1.Map<prefix1.String, prefix1.Object?>{};
json[r'boolField'] = boolField;

return json;
};

}
45 changes: 45 additions & 0 deletions goldens/foo/lib/json_codable.dart
Original file line number Diff line number Diff line change
@@ -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<C> listOfSerializableField;

final List<C>? nullableListOfSerializableField;

final Set<C> setOfSerializableField;

final Set<C>? nullableSetOfSerializableField;

final Map<String, C> mapOfSerializableField;

final Map<String, C>? nullableMapOfSerializableField;
}

@JsonCodable()
class C {
final bool boolField;
}
72 changes: 56 additions & 16 deletions pkgs/_analyzer_macros/lib/macro_implementation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand All @@ -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)));
Expand All @@ -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)));
Expand All @@ -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<Identifier, Iterable<DeclarationCode>> typeAugmentations;

AnalyzerMacroExecutionResult(
this.target, Iterable<DeclarationCode> 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<AnalyzerMacroExecutionResult> expandTemplates(
MacroTarget target, AugmentResponse augmentResponse) async {
final declarations = <DeclarationCode>[];
for (final augmentation in augmentResponse.augmentations) {
declarations.add(
DeclarationCode.fromParts(await _expandTemplates(augmentation.code)));
}
return AnalyzerMacroExecutionResult(target, declarations);
}

@override
List<Diagnostic> get diagnostics => [];
Expand Down Expand Up @@ -154,16 +170,6 @@ class AnalyzerMacroExecutionResult implements injected.MacroExecutionResult {

@override
void serialize(Object serializer) => throw UnimplementedError();

@override
Map<Identifier, Iterable<DeclarationCode>> 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 {
Expand All @@ -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<List<Object>> _expandTemplates(String code) async {
jakemac53 marked this conversation as resolved.
Show resolved Hide resolved
final result = <Object>[];
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;
}
Loading