Skip to content

Commit 60a4b38

Browse files
authored
More features for ConstantReader (#180)
* Add a Constant helper class. * Add List and Map. * Add missing field handling. * Oops. * Fix tests.
1 parent 696bff3 commit 60a4b38

File tree

3 files changed

+123
-18
lines changed

3 files changed

+123
-18
lines changed

lib/src/constants.dart

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,25 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:analyzer/dart/constant/value.dart';
6+
import 'package:analyzer/dart/element/element.dart';
7+
8+
/// Throws an exception if [root] or its super(s) does not contain [name].
9+
void _assertHasField(ClassElement root, String name) {
10+
var element = root;
11+
while (element != null) {
12+
final field = element.getField(name);
13+
if (field != null) {
14+
return;
15+
}
16+
element = element.supertype?.element;
17+
}
18+
final allFields = root.fields.toSet();
19+
root.allSupertypes.forEach((t) => allFields.addAll(t.element.fields));
20+
throw new FormatException(
21+
'Class ${root.name} does not have field "$name".',
22+
'Fields: $allFields',
23+
);
24+
}
625

726
/// Returns whether or not [object] is or represents a `null` value.
827
bool _isNull(DartObject object) => object?.isNull != false;
@@ -43,6 +62,26 @@ abstract class ConstantReader {
4362
/// Throws [FormatException] if [isInt] is `false`.
4463
int get intValue;
4564

65+
/// Returns whether this constant represents a `List` literal.
66+
///
67+
/// If `true`, [listValue] will return a `List` (not throw).
68+
bool get isList;
69+
70+
/// Returns this constant as a `List` value.
71+
///
72+
/// Throws [FormatException] if [isList] is `false`.
73+
List<DartObject> get listValue;
74+
75+
/// Returns whether this constant represents a `Map` literal.
76+
///
77+
/// If `true`, [listValue] will return a `Map` (not throw).
78+
bool get isMap;
79+
80+
/// Returns this constant as a `Map` value.
81+
///
82+
/// Throws [FormatException] if [isMap] is `false`.
83+
Map<DartObject, DartObject> get mapValue;
84+
4685
/// Returns whether this constant represents a `String` literal.
4786
///
4887
/// If `true`, [stringValue] will return a `String` (not throw).
@@ -60,33 +99,49 @@ abstract class ConstantReader {
6099
ConstantReader read(String field);
61100
}
62101

102+
dynamic _throw(String expected, [dynamic object]) {
103+
throw new FormatException('Not a $expected', '$object');
104+
}
105+
63106
/// Implements a [ConstantReader] representing a `null` value.
64107
class _NullConstant implements ConstantReader {
65108
const _NullConstant();
66109

67110
@override
68-
bool get boolValue => throw new FormatException('Not a bool', 'null');
111+
bool get boolValue => _throw('bool');
112+
113+
@override
114+
int get intValue => _throw('int');
115+
116+
@override
117+
String get stringValue => _throw('String');
69118

70119
@override
71-
int get intValue => throw new FormatException('Not an int', 'null');
120+
List<DartObject> get listValue => _throw('List');
72121

73122
@override
74-
String get stringValue => throw new FormatException('Not a String', 'null');
123+
Map<DartObject, DartObject> get mapValue => _throw('Map');
75124

76125
@override
77126
bool get isBool => false;
78127

79128
@override
80129
bool get isInt => false;
81130

131+
@override
132+
bool get isList => false;
133+
134+
@override
135+
bool get isMap => false;
136+
82137
@override
83138
bool get isNull => true;
84139

85140
@override
86141
bool get isString => false;
87142

88143
@override
89-
ConstantReader read(_) => this;
144+
ConstantReader read(_) => throw new UnsupportedError('Null');
90145
}
91146

92147
/// Default implementation of [ConstantReader].
@@ -96,33 +151,51 @@ class _Constant implements ConstantReader {
96151
const _Constant(this._object);
97152

98153
@override
99-
bool get boolValue => isBool
100-
? _object.toBoolValue()
101-
: throw new FormatException('Not a bool', _object);
154+
bool get boolValue =>
155+
isBool ? _object.toBoolValue() : _throw('bool', _object);
156+
157+
@override
158+
int get intValue => isInt ? _object.toIntValue() : _throw('int', _object);
159+
160+
@override
161+
String get stringValue =>
162+
isString ? _object.toStringValue() : _throw('String', _object);
102163

103164
@override
104-
int get intValue => isInt
105-
? _object.toIntValue()
106-
: throw new FormatException('Not an int', _object);
165+
List<DartObject> get listValue =>
166+
isList ? _object.toListValue() : _throw('List', _object);
107167

108168
@override
109-
String get stringValue => isString
110-
? _object.toStringValue()
111-
: throw new FormatException('Not a String', _object);
169+
Map<DartObject, DartObject> get mapValue =>
170+
isMap ? _object.toMapValue() : _throw('Map', _object);
112171

113172
@override
114173
bool get isBool => _object.toBoolValue() != null;
115174

116175
@override
117176
bool get isInt => _object.toIntValue() != null;
118177

178+
@override
179+
bool get isList => _object?.toListValue() != null;
180+
119181
@override
120182
bool get isNull => _isNull(_object);
121183

184+
@override
185+
bool get isMap => _object?.toMapValue() != null;
186+
122187
@override
123188
bool get isString => _object.toStringValue() != null;
124189

125190
@override
126-
ConstantReader read(String field) =>
127-
new ConstantReader(_getFieldRecursive(_object, field));
191+
ConstantReader read(String field) {
192+
final constant = new ConstantReader(_getFieldRecursive(_object, field));
193+
if (constant.isNull) {
194+
_assertHasField(_object?.type?.element, field);
195+
}
196+
return constant;
197+
}
198+
199+
@override
200+
String toString() => 'ConstantReader ${_object}';
128201
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ environment:
88
dependencies:
99
analyzer: '>=0.29.2 <0.31.0'
1010
build: ^0.9.0
11+
collection: ^1.1.2
1112
dart_style: '>=0.1.7 <2.0.0'
1213
path: ^1.3.2
1314
dev_dependencies:
1415
build_runner: ^0.3.2
1516
build_test: ^0.6.0
1617
cli_util: '>=0.0.1 <0.2.0'
17-
collection: ^1.1.2
1818
mockito: '>=0.11.0 <3.0.0'
1919
test: ^0.12.3

test/constants_test.dart

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:analyzer/dart/constant/value.dart';
56
import 'package:build_test/build_test.dart';
7+
import 'package:collection/collection.dart';
68
import 'package:source_gen/source_gen.dart';
79
import 'package:test/test.dart';
810

@@ -18,6 +20,8 @@ void main() {
1820
const aInt = 1234;
1921
const aBool = true;
2022
const aNull = null;
23+
const aList = const [1, 2, 3];
24+
const aMap = const {1: 'A', 2: 'B'};
2125
2226
@aString // [0]
2327
@aInt // [1]
@@ -28,9 +32,11 @@ void main() {
2832
aInt: aInt,
2933
aBool: aBool,
3034
aNull: aNull,
31-
nested: const Exampe(),
35+
nested: const Example(),
3236
)
3337
@Super() // [5]
38+
@aList // [6]
39+
@aMap // [7]
3440
class Example {
3541
final String aString;
3642
final int aInt;
@@ -76,9 +82,9 @@ void main() {
7682
expect(constant.read('aString').stringValue, 'Hello');
7783
expect(constant.read('aInt').intValue, 1234);
7884
expect(constant.read('aBool').boolValue, true);
79-
expect(constant.read('aNull').isNull, isTrue);
8085

8186
final nested = constant.read('nested');
87+
expect(nested.isNull, isFalse, reason: '$nested');
8288
expect(nested.read('aString').isNull, isTrue);
8389
expect(nested.read('aInt').isNull, isTrue);
8490
expect(nested.read('aBool').isNull, isTrue);
@@ -88,5 +94,31 @@ void main() {
8894
final constant = constants[5];
8995
expect(constant.read('aString').stringValue, 'Super Hello');
9096
});
97+
98+
test('should read a list', () {
99+
expect(constants[6].isList, isTrue, reason: '${constants[6]}');
100+
expect(constants[6].listValue.map((c) => new ConstantReader(c).intValue),
101+
[1, 2, 3]);
102+
});
103+
104+
test('should read a map', () {
105+
expect(constants[7].isMap, isTrue, reason: '${constants[7]}');
106+
expect(
107+
mapMap<DartObject, DartObject, int, String>(constants[7].mapValue,
108+
key: (k, _) => new ConstantReader(k).intValue,
109+
value: (_, v) => new ConstantReader(v).stringValue),
110+
{1: 'A', 2: 'B'});
111+
});
112+
113+
test('should fail reading from `null`', () {
114+
final $null = constants[3];
115+
expect($null.isNull, isTrue, reason: '${$null}');
116+
expect(() => $null.read('foo'), throwsUnsupportedError);
117+
});
118+
119+
test('should fail reading a missing field', () {
120+
final $super = constants[5];
121+
expect(() => $super.read('foo'), throwsFormatException);
122+
});
91123
});
92124
}

0 commit comments

Comments
 (0)