Skip to content

Commit

Permalink
Add LiteralParams macro. (#128)
Browse files Browse the repository at this point in the history
* Add LiteralParams macro.

Fix converter for lists of union types.

* Address review comments.
  • Loading branch information
davidmorgan authored Oct 31, 2024
1 parent 65bf11d commit 178c59f
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 13 deletions.
14 changes: 14 additions & 0 deletions goldens/foo/lib/literal_params.analyzer.augmentations
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
part of 'package:foo/literal_params.dart';

augment class Foo {
// anInt: 7, int
// aNum: 8.0, double
// aDouble: 9.0, double
// aString: 10, String
// anObject: {type: {reference: {type: ClassReference, value: {}}, typeArguments: []}, constructor: {type: ConstructorReference, value: {}}, arguments: [{type: NamedArgument, value: {name: a, expression: {type: BooleanLiteral, value: {text: true}}}}, {type: NamedArgument, value: {name: b, expression: {type: BooleanLiteral, value: {text: false}}}}]}, String
// ints: [11, 12], List<Object>
// nums: [13.0, 14], List<Object>
// doubles: [15.0, 16], List<Object>
// strings: [17, eighteen], List<Object>
// objects: [19, {type: {reference: {type: ClassReference, value: {}}, typeArguments: []}, constructor: {type: ConstructorReference, value: {}}, arguments: [{type: NamedArgument, value: {name: a, expression: {type: BooleanLiteral, value: {text: true}}}}, {type: NamedArgument, value: {name: b, expression: {type: BooleanLiteral, value: {text: false}}}}]}], List<Object>
}
31 changes: 31 additions & 0 deletions goldens/foo/lib/literal_params.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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/literal_params.dart';

@LiteralParams(
anInt: 7,
aNum: 8.0,
aDouble: 9.0,
aString: '10',
anObject: Bar(a: true, b: false),
ints: [11, 12],
nums: [13.0, 14],
doubles: [15.0, 16],
strings: ['17', 'eighteen'],
objects: [
19,
Bar(a: true, b: false),
],
)
class Foo {}

class Bar {
final bool? a;
final bool? b;

const Bar({this.a, this.b});
}

void main() {}
26 changes: 16 additions & 10 deletions goldens/foo/lib/metadata.analyzer.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,26 @@
},
"arguments": [
{
"name": "aBool",
"expression": {
"type": "BooleanLiteral",
"value": {
"text": "true"
"type": "NamedArgument",
"value": {
"name": "aBool",
"expression": {
"type": "BooleanLiteral",
"value": {
"text": "true"
}
}
}
},
{
"name": "anInt",
"expression": {
"type": "IntegerLiteral",
"value": {
"text": "23"
"type": "NamedArgument",
"value": {
"name": "anInt",
"expression": {
"type": "IntegerLiteral",
"value": {
"text": "23"
}
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions pkgs/_analyzer_cfe_macros/lib/metadata_converter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,19 @@ T? convert<T>(Object? object) => switch (object) {
int o => o as T,
bool o => o as T,
double o => o as T,
// Manually added converters for lists of union types.
List<front_end.Argument> o =>
o.map((i) => convertToArgument(i)!).toList() as T,
List<front_end.Element> o =>
o.map((i) => convertToElement(i)!).toList() as T,
List<front_end.Expression> o =>
o.map((i) => convertToExpression(i)!).toList() as T,
List<front_end.RecordField> o =>
o.map((i) => convertToRecordField(i)!).toList() as T,
List<front_end.StringLiteralPart> o =>
o.map((i) => convertToStringLiteralPart(i)!).toList() as T,
List<front_end.TypeAnnotation> o =>
o.map((i) => convertToTypeAnnotation(i)!).toList() as T,
List o => o.map((i) => convert<Map<String, Object?>>(i)!).toList() as T,
null => null,
_ => throw ArgumentError(object),
Expand Down
9 changes: 6 additions & 3 deletions pkgs/_analyzer_cfe_macros/test/metadata_converter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,12 @@ void main() {
'typeArguments': [],
'arguments': [
{
'expression': {
'type': 'IntegerLiteral',
'value': {'text': '4'}
'type': 'PositionalArgument',
'value': {
'expression': {
'type': 'IntegerLiteral',
'value': {'text': '4'}
}
}
}
]
Expand Down
121 changes: 121 additions & 0 deletions pkgs/_test_macros/lib/literal_params.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// 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';
// ignore: implementation_imports
import 'package:dart_model/src/macro_metadata.g.dart';
import 'package:macro/macro.dart';
import 'package:macro_service/macro_service.dart';

import 'templating.dart';

/// Covers macro metadata cases where the params will always be written as
/// literals in the annotation.
///
/// Outputs comments with evaluation results.
///
/// Throws if the annotation has something other than supported literals.
/// TODO(davidmorgan): support diagnostics, make failures diagnostics.
class LiteralParams {
final int? anInt;
final num? aNum;
final double? aDouble;
final String? aString;
final Object? anObject;
final List<int>? ints;
final List<num>? nums;
final List<double>? doubles;
final List<String>? strings;
final List<Object>? objects;

const LiteralParams(
{required this.anInt,
this.aNum,
this.aDouble,
this.aString,
this.anObject,
this.ints,
this.nums,
this.doubles,
this.strings,
this.objects});
}

class LiteralParamsImplementation implements ClassDeclarationsMacro {
// TODO(davidmorgan): this should be injected by the bootstrap script.
@override
MacroDescription get description => MacroDescription(
annotation: QualifiedName(
uri: 'package:_test_macros/literal_params.dart',
name: 'LiteralParams'),
runsInPhases: [2]);

@override
Future<void> buildDeclarationsForClass(
ClassDeclarationsBuilder builder) async {
// TODO(davidmorgan): need a way to find the correct annotation, this just
// uses the first.
final annotation = builder
.target.metadataAnnotations.first.expression.asConstructorInvocation;

final namedArguments = {
for (final argument in annotation.arguments)
if (argument.type == ArgumentType.namedArgument)
argument.asNamedArgument.name:
argument.asNamedArgument.expression.evaluate
};

builder.declareInType(Augmentation(
code: expandTemplate([
for (final entry in namedArguments.entries)
' // ${entry.key}: ${entry.value}, ${entry.value.runtimeType}',
].join('\n'))));
}
}

// TODO(davidmorgan): common code for this in `dart_model` so macros don't
// all have to write expression evaluation code.
extension ExpressionExtension on Expression {
Object get evaluate => switch (type) {
ExpressionType.integerLiteral => int.parse(asIntegerLiteral.text),
ExpressionType.doubleLiteral => double.parse(asDoubleLiteral.text),
ExpressionType.stringLiteral => asStringLiteral.evaluate,
ExpressionType.booleanLiteral => bool.parse(asBooleanLiteral.text),
ExpressionType.listLiteral =>
asListLiteral.elements.map((e) => e.evaluate).toList(),
// TODO(davidmorgan): need the type name to do something useful here,
// for now just return the JSON.
ExpressionType.constructorInvocation =>
asConstructorInvocation.toString(),
// TODO(davidmorgan): need to follow references to do something useful
// here, for now just return the JSON.
ExpressionType.staticGet => asStaticGet.toString(),
_ => throw UnsupportedError(
'Not supported in @LiteralParams annotation: $this'),
};
}

extension ElementExtension on Element {
Object get evaluate => switch (type) {
ElementType.expressionElement =>
asExpressionElement.expression.evaluate,
_ => throw UnsupportedError(
'Not supported in @LiteralParams annotation: $this'),
};
}

extension StringLiteralExtension on StringLiteral {
Object get evaluate {
if (parts.length != 1) {
throw UnsupportedError(
'Not supported in @LiteralParams annotation: $this');
}
final part = parts.single;
return switch (part.type) {
StringLiteralPartType.stringPart => part.asStringPart.text,
_ => throw UnsupportedError(
'Not supported in @LiteralParams annotation: $this'),
};
}
}
1 change: 1 addition & 0 deletions pkgs/_test_macros/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ dev_dependencies:
# 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
# macro lib/literal_params.dart#LiteralParams package:_test_macros/literal_params.dart#LiteralParamsImplementation

0 comments on commit 178c59f

Please sign in to comment.