Skip to content

Commit

Permalink
Move templating to the client side.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmorgan committed Sep 30, 2024
1 parent 20c1bf1 commit 9420577
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 49 deletions.
42 changes: 12 additions & 30 deletions pkgs/_analyzer_macros/lib/macro_implementation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:analyzer/src/summary2/macro_declarations.dart' as analyzer;
import 'package:analyzer/src/summary2/macro_injected_impl.dart' as injected;
import 'package:dart_model/dart_model.dart';
import 'package:macro_service/macro_service.dart';
import 'package:macros/macros.dart';
import 'package:macros/macros.dart' hide Code;
// ignore: implementation_imports
import 'package:macros/src/executor.dart' as injected;

Expand Down Expand Up @@ -138,7 +138,7 @@ class AnalyzerMacroExecutionResult implements injected.MacroExecutionResult {
final declarations = <DeclarationCode>[];
for (final augmentation in augmentResponse.augmentations) {
declarations.add(
DeclarationCode.fromParts(await _expandTemplates(augmentation.code)));
DeclarationCode.fromParts(await _resolveNames(augmentation.code)));
}
return AnalyzerMacroExecutionResult(target, declarations);
}
Expand Down Expand Up @@ -182,36 +182,18 @@ extension MacroTargetExtension on MacroTarget {
}
}

/// 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 {
/// Converts [codes] to a list of `String` and `Identifier`.
Future<List<Object>> _resolveNames(List<Code> codes) async {
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');
for (final code in codes) {
if (code.type == CodeType.resolvedCode) {
result.add(code.asResolvedCode.code);
} else if (code.type == CodeType.qualifiedName) {
final qualifiedName = code.asQualifiedName;
result.add(await (introspector as TypePhaseIntrospector)
// ignore: deprecated_member_use
.resolveIdentifier(Uri.parse(qualifiedName.uri), qualifiedName.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;
}
3 changes: 2 additions & 1 deletion pkgs/_test_macros/lib/declare_x_macro.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.

import 'package:dart_model/dart_model.dart';
import 'package:dart_model/templating.dart';
import 'package:macro/macro.dart';
import 'package:macro_service/macro_service.dart';

Expand All @@ -27,6 +28,6 @@ class DeclareXImplementation implements Macro {

// TODO(davidmorgan): still need to pass through the augment target.
return AugmentResponse(
augmentations: [Augmentation(code: 'int get x => 3;')]);
augmentations: [Augmentation(code: expandTemplate('int get x => 3;'))]);
}
}
13 changes: 7 additions & 6 deletions pkgs/_test_macros/lib/json_codable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.

import 'package:dart_model/dart_model.dart';
import 'package:dart_model/templating.dart';
import 'package:macro/macro.dart';
import 'package:macro_service/macro_service.dart';

Expand Down Expand Up @@ -36,10 +37,10 @@ class JsonCodableImplementation implements Macro {
Future<AugmentResponse> phase1(Host host, AugmentRequest request) async {
final target = request.target;
return AugmentResponse(augmentations: [
Augmentation(code: '''
Augmentation(code: expandTemplate('''
external ${target.code}.fromJson($_jsonMapType json);
external $_jsonMapType toJson();
'''),
''')),
]);
}

Expand All @@ -61,10 +62,10 @@ external $_jsonMapType toJson();

// 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: '''
result.add(Augmentation(code: expandTemplate('''
augment ${target.code}.fromJson($_jsonMapType json) :
${initializers.join(',\n')};
'''));
''')));

final serializers = <String>[];
for (final field
Expand All @@ -76,13 +77,13 @@ ${initializers.join(',\n')};

// 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: '''
result.add(Augmentation(code: expandTemplate('''
$_jsonMapType toJson() {
final json = $_jsonMapType{};
${serializers.map((s) => '$s;\n').join('')}
return json;
};
'''));
''')));

return AugmentResponse(augmentations: result);
}
Expand Down
6 changes: 4 additions & 2 deletions pkgs/_test_macros/lib/query_class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'dart:convert';

import 'package:dart_model/dart_model.dart';
import 'package:dart_model/templating.dart';
import 'package:macro/macro.dart';
import 'package:macro_service/macro_service.dart';

Expand All @@ -31,7 +32,8 @@ class QueryClassImplementation implements Macro {
final model = await host.query(Query(
target: request.target,
));
return AugmentResponse(
augmentations: [Augmentation(code: '// ${json.encode(model)}')]);
return AugmentResponse(augmentations: [
Augmentation(code: expandTemplate('// ${json.encode(model)}'))
]);
}
}
73 changes: 69 additions & 4 deletions pkgs/dart_model/lib/src/dart_model.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,73 @@ import 'package:dart_model/src/json_buffer/json_buffer_builder.dart';
// ignore: implementation_imports,unused_import,prefer_relative_imports
import 'package:dart_model/src/scopes.dart';

/// An augmentation to Dart code. TODO(davidmorgan): this is a placeholder.
/// An augmentation to Dart code.
extension type Augmentation.fromJson(Map<String, Object?> node)
implements Object {
static final TypedMapSchema _schema = TypedMapSchema({
'code': Type.stringPointer,
'code': Type.closedListPointer,
});
Augmentation({
String? code,
List<Code>? code,
}) : this.fromJson(Scope.createMap(
_schema,
code,
));

/// Augmentation code.
String get code => node['code'] as String;
List<Code> get code => (node['code'] as List).cast();
}

enum CodeType {
// Private so switches must have a default. See `isKnown`.
_unknown,
qualifiedName,
resolvedCode;

bool get isKnown => this != _unknown;
}

extension type Code.fromJson(Map<String, Object?> node) implements Object {
static final TypedMapSchema _schema = TypedMapSchema({
'type': Type.stringPointer,
'value': Type.anyPointer,
});
static Code qualifiedName(QualifiedName qualifiedName) =>
Code.fromJson(Scope.createMap(
_schema,
'QualifiedName',
qualifiedName,
));
static Code resolvedCode(ResolvedCode resolvedCode) =>
Code.fromJson(Scope.createMap(
_schema,
'ResolvedCode',
resolvedCode,
));
CodeType get type {
switch (node['type'] as String) {
case 'QualifiedName':
return CodeType.qualifiedName;
case 'ResolvedCode':
return CodeType.resolvedCode;
default:
return CodeType._unknown;
}
}

QualifiedName get asQualifiedName {
if (node['type'] != 'QualifiedName') {
throw StateError('Not a QualifiedName.');
}
return QualifiedName.fromJson(node['value'] as Map<String, Object?>);
}

ResolvedCode get asResolvedCode {
if (node['type'] != 'ResolvedCode') {
throw StateError('Not a ResolvedCode.');
}
return ResolvedCode.fromJson(node['value'] as Map<String, Object?>);
}
}

/// The type-hierarchy representation of the type `dynamic`.
Expand Down Expand Up @@ -369,6 +421,19 @@ extension type RecordTypeDesc.fromJson(Map<String, Object?> node)
List<NamedRecordField> get named => (node['named'] as List).cast();
}

/// Code that is part of an augmentation applied to Dart code.
extension type ResolvedCode.fromJson(Map<String, Object?> node)
implements Object {
ResolvedCode({
String? code,
}) : this.fromJson({
if (code != null) 'code': code,
});

/// The code.
String get code => node['code'] as String;
}

enum StaticTypeDescType {
// Private so switches must have a default. See `isKnown`.
_unknown,
Expand Down
37 changes: 37 additions & 0 deletions pkgs/dart_model/lib/templating.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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 'src/dart_model.dart';

/// Converts [template] to a mix of `Identifier` and `String`.
///
/// References of the form `{{uri#name}}` become [QualifiedName] wrapped in
/// [Code.qualifiedName], everything else becomes [ResolvedCode].
List<Code> expandTemplate(String template) {
final result = <Code>[];
var index = 0;
while (index < template.length) {
final start = template.indexOf('{{', index);
if (start == -1) {
result.add(
Code.resolvedCode(ResolvedCode(code: template.substring(index))));
break;
}
result.add(Code.resolvedCode(
ResolvedCode(code: template.substring(index, start))));
final end = template.indexOf('}}', start);
if (end == -1) {
throw ArgumentError('Unmatched opening brace: $template');
}
final name = template.substring(start + 2, end);
final parts = name.split('#');
if (parts.length != 2) {
throw ArgumentError('Expected "uri#name" in: $name');
}
result
.add(Code.qualifiedName(QualifiedName(uri: parts[0], name: parts[1])));
index = end + 2;
}
return result;
}
42 changes: 39 additions & 3 deletions schemas/dart_model.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,40 @@
"$defs": {
"Augmentation": {
"type": "object",
"description": "An augmentation to Dart code. TODO(davidmorgan): this is a placeholder.",
"description": "An augmentation to Dart code.",
"properties": {
"code": {
"type": "string",
"description": "Augmentation code."
"type": "array",
"description": "Augmentation code.",
"items": {
"$ref": "#/$defs/Code"
}
}
}
},
"Code": {
"type": "object",
"description": "Code that is part of augmentations to Dart code.",
"properties": {
"type": {
"type": "string"
},
"value": {
"oneOf": [
{
"$ref": "#/$defs/QualifiedName"
},
{
"$ref": "#/$defs/ResolvedCode"
}
]
},
"required": [
"type",
"value"
]
}
},
"DynamicTypeDesc": {
"type": "null",
"description": "The type-hierarchy representation of the type `dynamic`."
Expand Down Expand Up @@ -265,6 +291,16 @@
}
}
},
"ResolvedCode": {
"type": "object",
"description": "Code that is part of an augmentation applied to Dart code.",
"properties": {
"code": {
"type": "string",
"description": "The code."
}
}
},
"StaticTypeDesc": {
"type": "object",
"description": "A partially-resolved description of a type as it appears in Dart's type hierarchy.",
Expand Down
21 changes: 18 additions & 3 deletions tool/dart_model_generator/lib/definitions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,19 @@ static Protocol handshakeProtocol = Protocol(
Definition.clazz(
'Augmentation',
createInBuffer: true,
description: 'An augmentation to Dart code. '
'TODO(davidmorgan): this is a placeholder.',
description: 'An augmentation to Dart code.',
properties: [
Property('code', type: 'String', description: 'Augmentation code.'),
Property('code',
type: 'List<Code>', description: 'Augmentation code.'),
],
),
Definition.union(
'Code',
createInBuffer: true,
description: 'Code that is part of augmentations to Dart code.',
types: ['QualifiedName', 'ResolvedCode'],
properties: [],
),
Definition.nullTypedef('DynamicTypeDesc',
description:
'The type-hierarchy representation of the type `dynamic`.'),
Expand Down Expand Up @@ -244,6 +251,14 @@ static QualifiedName parse(String string) {
Property('positional', type: 'List<StaticTypeDesc>'),
Property('named', type: 'List<NamedRecordField>')
]),
Definition.clazz(
'ResolvedCode',
description:
'Code that is part of an augmentation applied to Dart code.',
properties: [
Property('code', type: 'String', description: 'The code.'),
],
),
Definition.union('StaticTypeDesc',
description:
'A partially-resolved description of a type as it appears in '
Expand Down

0 comments on commit 9420577

Please sign in to comment.