Skip to content

Commit 6f252d3

Browse files
matanlureykevmoo
authored andcommitted
Constants reviver (#182)
1 parent cf56cfe commit 6f252d3

File tree

6 files changed

+264
-50
lines changed

6 files changed

+264
-50
lines changed

lib/source_gen.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ export 'src/constants.dart' show ConstantReader;
99
export 'src/generator.dart';
1010
export 'src/generator_for_annotation.dart';
1111
export 'src/library.dart' show LibraryReader;
12+
export 'src/revive.dart' show Revivable;
1213
export 'src/type_checker.dart' show TypeChecker;

lib/src/constants.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:analyzer/dart/constant/value.dart';
66
import 'package:analyzer/dart/element/element.dart';
77

8+
import 'revive.dart';
89
import 'type_checker.dart';
910

1011
/// Throws an exception if [root] or its super(s) does not contain [name].
@@ -50,6 +51,9 @@ abstract class ConstantReader {
5051
factory ConstantReader(DartObject object) =>
5152
_isNull(object) ? const _NullConstant() : new _Constant(object);
5253

54+
/// Constant as any supported literal value.
55+
dynamic get anyValue;
56+
5357
/// Returns whether this constant represents a `bool` literal.
5458
bool get isBool;
5559

@@ -102,6 +106,13 @@ abstract class ConstantReader {
102106

103107
/// Reads[ field] from the constant as another constant value.
104108
ConstantReader read(String field);
109+
110+
/// Returns as a revived meta class.
111+
///
112+
/// This is appropriate for cases where the underlying object is not a literal
113+
/// and code generators will want to figure out how to "recreate" a constant
114+
/// at runtime.
115+
Revivable revive();
105116
}
106117

107118
dynamic _throw(String expected, [dynamic object]) {
@@ -112,6 +123,9 @@ dynamic _throw(String expected, [dynamic object]) {
112123
class _NullConstant implements ConstantReader {
113124
const _NullConstant();
114125

126+
@override
127+
dynamic get anyValue => _throw('dynamic');
128+
115129
@override
116130
bool get boolValue => _throw('bool');
117131

@@ -150,6 +164,9 @@ class _NullConstant implements ConstantReader {
150164

151165
@override
152166
ConstantReader read(_) => throw new UnsupportedError('Null');
167+
168+
@override
169+
Revivable revive() => throw new UnsupportedError('Null');
153170
}
154171

155172
/// Default implementation of [ConstantReader].
@@ -158,6 +175,14 @@ class _Constant implements ConstantReader {
158175

159176
const _Constant(this._object);
160177

178+
@override
179+
dynamic get anyValue =>
180+
_object.toBoolValue() ??
181+
_object.toIntValue() ??
182+
_object.toStringValue() ??
183+
_object.toListValue() ??
184+
_object.toMapValue();
185+
161186
@override
162187
bool get boolValue =>
163188
isBool ? _object.toBoolValue() : _throw('bool', _object);
@@ -208,6 +233,9 @@ class _Constant implements ConstantReader {
208233
return constant;
209234
}
210235

236+
@override
237+
Revivable revive() => reviveInstance(_object);
238+
211239
@override
212240
String toString() => 'ConstantReader ${_object}';
213241
}

lib/src/revive.dart

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/dart/constant/value.dart';
6+
import 'package:analyzer/dart/element/element.dart';
7+
import 'package:analyzer/src/dart/constant/value.dart';
8+
9+
import 'utils.dart';
10+
11+
/// Returns a revivable instance of [object].
12+
///
13+
/// Optionally specify the [library] type that contains the reference.
14+
///
15+
/// Returns [null] if not revivable.
16+
Revivable reviveInstance(DartObject object, [LibraryElement library]) {
17+
library ??= object.type.element.library;
18+
var url = Uri.parse(urlOfElement(object.type.element));
19+
final clazz = object?.type?.element as ClassElement;
20+
for (final e in clazz.fields.where(
21+
(f) => f.isPublic && f.isConst && f.computeConstantValue() == object,
22+
)) {
23+
return new Revivable._(source: url, accessor: e.name);
24+
}
25+
final i = (object as DartObjectImpl).getInvocation();
26+
if (i != null &&
27+
i.constructor.isPublic &&
28+
i.constructor.enclosingElement.isPublic) {
29+
url = Uri.parse(urlOfElement(i.constructor.enclosingElement));
30+
return new Revivable._(
31+
source: url,
32+
accessor: i.constructor.name,
33+
namedArguments: i.namedArguments,
34+
positionalArguments: i.positionalArguments,
35+
);
36+
}
37+
if (library == null) {
38+
return null;
39+
}
40+
for (final e in library.definingCompilationUnit.topLevelVariables.where(
41+
(f) => f.isPublic && f.isConst && f.computeConstantValue() == object,
42+
)) {
43+
return new Revivable._(
44+
source: Uri.parse(urlOfElement(library)).replace(fragment: ''),
45+
accessor: e.name,
46+
);
47+
}
48+
return null;
49+
}
50+
51+
/// Decoded "instructions" for re-creating a const [DartObject] at runtime.
52+
class Revivable {
53+
/// A URL pointing to the location and class name.
54+
///
55+
/// For example, `LinkedHashMap` looks like: `dart:collection#LinkedHashMap`.
56+
///
57+
/// An accessor to a top-level do does not have a fragment.
58+
final Uri source;
59+
60+
/// Constructor or getter name used to invoke `const Class(...)`.
61+
///
62+
/// Optional - if empty string (`''`) then this means the default constructor.
63+
final String accessor;
64+
65+
/// Positional arguments used to invoke the constructor.
66+
final List<DartObject> positionalArguments;
67+
68+
/// Named arguments used to invoke the constructor.
69+
final Map<String, DartObject> namedArguments;
70+
71+
const Revivable._({
72+
this.source,
73+
this.accessor: '',
74+
this.positionalArguments: const [],
75+
this.namedArguments: const {},
76+
});
77+
78+
/// Whether this instance is visible outside the same library.
79+
bool get isPrivate => accessor.startsWith('_');
80+
}

lib/src/type_checker.dart

Lines changed: 7 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import 'package:analyzer/dart/constant/value.dart';
88
import 'package:analyzer/dart/element/element.dart';
99
import 'package:analyzer/dart/element/type.dart';
1010

11+
import 'utils.dart';
12+
1113
/// An abstraction around doing static type checking at compile/build time.
1214
abstract class TypeChecker {
1315
const TypeChecker._();
@@ -118,51 +120,6 @@ Iterable<InterfaceType> _getAllSupertypes(Element element) sync* {
118120
}
119121
}
120122

121-
Uri _normalizeUrl(Uri url) {
122-
switch (url.scheme) {
123-
case 'dart':
124-
return _normalizeDartUrl(url);
125-
case 'package':
126-
return _packageToAssetUrl(url);
127-
default:
128-
return url;
129-
}
130-
}
131-
132-
/// Make `dart:`-type URLs look like a user-knowable path.
133-
///
134-
/// Some internal dart: URLs are something like `dart:core/map.dart`.
135-
///
136-
/// This isn't a user-knowable path, so we strip out extra path segments
137-
/// and only expose `dart:core`.
138-
Uri _normalizeDartUrl(Uri url) => url.pathSegments.isNotEmpty
139-
? url.replace(pathSegments: url.pathSegments.take(1))
140-
: url;
141-
142-
/// Returns a `package:` URL into a `asset:` URL.
143-
///
144-
/// This makes internal comparison logic much easier, but still allows users
145-
/// to define assets in terms of `package:`, which is something that makes more
146-
/// sense to most.
147-
///
148-
/// For example this transforms `package:source_gen/source_gen.dart` into:
149-
/// `asset:source_gen/lib/source_gen.dart`.
150-
Uri _packageToAssetUrl(Uri url) => url.scheme == 'package'
151-
? url.replace(
152-
scheme: 'asset',
153-
pathSegments: <String>[]
154-
..add(url.pathSegments.first)
155-
..add('lib')
156-
..addAll(url.pathSegments.skip(1)))
157-
: url;
158-
159-
/// Returns
160-
String _urlOfElement(Element element) => element.kind == ElementKind.DYNAMIC
161-
? 'dart:core#dynmaic'
162-
: _normalizeUrl(element.source.uri)
163-
.replace(fragment: element.name)
164-
.toString();
165-
166123
// Checks a static type against another static type;
167124
class _LibraryTypeChecker extends TypeChecker {
168125
final DartType _type;
@@ -174,13 +131,13 @@ class _LibraryTypeChecker extends TypeChecker {
174131
element is ClassElement && element == _type.element;
175132

176133
@override
177-
String toString() => '${_urlOfElement(_type.element)}';
134+
String toString() => '${urlOfElement(_type.element)}';
178135
}
179136

180137
// Checks a runtime type against a static type.
181138
class _MirrorTypeChecker extends TypeChecker {
182139
static Uri _uriOf(ClassMirror mirror) =>
183-
_normalizeUrl((mirror.owner as LibraryMirror).uri)
140+
normalizeUrl((mirror.owner as LibraryMirror).uri)
184141
.replace(fragment: MirrorSystem.getName(mirror.simpleName));
185142

186143
// Precomputed type checker for types that already have been used.
@@ -218,14 +175,14 @@ class _UriTypeChecker extends TypeChecker {
218175
int get hashCode => _url.hashCode;
219176

220177
/// Url as a [Uri] object, lazily constructed.
221-
Uri get uri => _cache[this] ??= _normalizeUrl(Uri.parse(_url));
178+
Uri get uri => _cache[this] ??= normalizeUrl(Uri.parse(_url));
222179

223180
/// Returns whether this type represents the same as [url].
224181
bool hasSameUrl(dynamic url) =>
225-
uri.toString() == (url is String ? url : _normalizeUrl(url).toString());
182+
uri.toString() == (url is String ? url : normalizeUrl(url).toString());
226183

227184
@override
228-
bool isExactly(Element element) => hasSameUrl(_urlOfElement(element));
185+
bool isExactly(Element element) => hasSameUrl(urlOfElement(element));
229186

230187
@override
231188
String toString() => '${uri}';

lib/src/utils.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,51 @@ String suggestLibraryName(AssetId source) {
7676
return '${source.package}.${parts.join('.')}';
7777
}
7878

79+
/// Returns a URL representing [element].
80+
String urlOfElement(Element element) => element.kind == ElementKind.DYNAMIC
81+
? 'dart:core#dynmaic'
82+
: normalizeUrl(element.source.uri)
83+
.replace(fragment: element.name)
84+
.toString();
85+
86+
Uri normalizeUrl(Uri url) {
87+
switch (url.scheme) {
88+
case 'dart':
89+
return normalizeDartUrl(url);
90+
case 'package':
91+
return packageToAssetUrl(url);
92+
default:
93+
return url;
94+
}
95+
}
96+
97+
/// Make `dart:`-type URLs look like a user-knowable path.
98+
///
99+
/// Some internal dart: URLs are something like `dart:core/map.dart`.
100+
///
101+
/// This isn't a user-knowable path, so we strip out extra path segments
102+
/// and only expose `dart:core`.
103+
Uri normalizeDartUrl(Uri url) => url.pathSegments.isNotEmpty
104+
? url.replace(pathSegments: url.pathSegments.take(1))
105+
: url;
106+
107+
/// Returns a `package:` URL into a `asset:` URL.
108+
///
109+
/// This makes internal comparison logic much easier, but still allows users
110+
/// to define assets in terms of `package:`, which is something that makes more
111+
/// sense to most.
112+
///
113+
/// For example this transforms `package:source_gen/source_gen.dart` into:
114+
/// `asset:source_gen/lib/source_gen.dart`.
115+
Uri packageToAssetUrl(Uri url) => url.scheme == 'package'
116+
? url.replace(
117+
scheme: 'asset',
118+
pathSegments: <String>[]
119+
..add(url.pathSegments.first)
120+
..add('lib')
121+
..addAll(url.pathSegments.skip(1)))
122+
: url;
123+
79124
/// Returns all of the declarations in [unit], including [unit] as the first
80125
/// item.
81126
Iterable<Element> getElementsFromLibraryElement(LibraryElement unit) sync* {

0 commit comments

Comments
 (0)