blob: 494ae57189cc56ed67846d0cfc3b32eda4379126 [file] [log] [blame]
// Copyright (c) 2017, 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:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:meta/meta.dart';
import '../type_checker.dart';
import 'revive.dart';
import 'utils.dart';
/// A wrapper for analyzer's [DartObject] with a predictable high-level API.
///
/// Unlike [DartObject.getField], the [read] method attempts to access super
/// classes for the field value if not found.
abstract class ConstantReader {
factory ConstantReader(DartObject object) =>
isNullLike(object) ? const _NullConstant() : _DartObjectConstant(object);
const ConstantReader._();
/// Whether this constant is a literal value.
@Deprecated('Use `isLiteral`, will be removed in 0.8.0')
bool get isAny => isLiteral;
/// Constant as a literal value.
///
/// Throws [FormatException] if a valid literal value cannot be returned. This
/// is the case if the constant is not a literal or if the literal value
/// is represented at least partially with [DartObject] instances.
@Deprecated('Use `literalValue`, will be removed in 0.8.0')
Object get anyValue => literalValue;
/// Whether this constant is a literal value.
bool get isLiteral => true;
/// Constant as a literal value.
///
/// Throws [FormatException] if a valid literal value cannot be returned. This
/// is the case if the constant is not a literal or if the literal value
/// is represented at least partially with [DartObject] instances.
Object get literalValue => null;
/// Underlying object this instance is reading from.
DartObject get objectValue;
/// Whether the value this constant represents matches [checker].
bool instanceOf(TypeChecker checker) => false;
/// Reads [field] from the constant as another constant value.
///
/// If the field is not present in the [DartObject] crawl up the chain of
/// super classes until it is found. If the field is not present throw a
/// [FormatException].
ConstantReader read(String field);
/// Reads [field] from the constant as another constant value.
///
/// Unlike [read], returns `null` if the field is not found.
ConstantReader peek(String field);
/// Whether this constant is a `null` value.
bool get isNull => true;
/// Whether this constant represents a `bool` value.
bool get isBool => false;
/// Constant as a `bool` value.
bool get boolValue;
/// Whether this constant represents an `int` value.
bool get isInt => false;
/// Constant as a `int` value.
int get intValue;
/// Whether this constant represents a `double` value.
bool get isDouble => false;
/// Constant as a `double` value.
double get doubleValue;
/// Whether this constant represents a `String` value.
bool get isString => false;
/// Constant as a `String` value.
String get stringValue;
/// Whether this constant represents a `Symbol` value.
bool get isSymbol => false;
/// Constant as a `Symbol` value.
Symbol get symbolValue;
/// Whether this constant represents a `Type` value.
bool get isType => false;
/// Constant as a [DartType] representing a `Type` value.
DartType get typeValue;
/// Whether this constant represents a `Map` value.
bool get isMap => false;
/// Constant as a `Map` value.
Map<DartObject, DartObject> get mapValue;
/// Whether this constant represents a `List` value.
bool get isList => false;
/// Constant as a `List` value.
List<DartObject> get listValue;
/// Returns as a revived meta class.
///
/// This is appropriate for cases where the underlying object is not a literal
/// and code generators will want to figure out how to "recreate" a constant
/// at runtime.
Revivable revive();
}
class _NullConstant extends ConstantReader {
@alwaysThrows
static T _throw<T>(String expected) {
throw FormatException('Not an instance of $expected.');
}
const _NullConstant() : super._();
@override
DartObject get objectValue => throw UnsupportedError('Null');
@override
bool get boolValue => _throw('bool');
@override
double get doubleValue => _throw('double');
@override
int get intValue => _throw('int');
@override
List<DartObject> get listValue => _throw('List');
@override
Map<DartObject, DartObject> get mapValue => _throw('Map');
@override
ConstantReader peek(_) => null;
@override
ConstantReader read(_) => throw UnsupportedError('Null');
@override
String get stringValue => _throw('String');
@override
Symbol get symbolValue => _throw('Symbol');
@override
DartType get typeValue => _throw('Type');
@override
Revivable revive() => throw UnsupportedError('Null');
}
class _DartObjectConstant extends ConstantReader {
@override
final DartObject objectValue;
const _DartObjectConstant(this.objectValue) : super._();
T _check<T>(T value, String expected) {
if (value == null) {
throw FormatException('Not an instance of $expected.', objectValue);
}
return value;
}
@override
Object get literalValue =>
objectValue.toBoolValue() ??
objectValue.toStringValue() ??
objectValue.toIntValue() ??
objectValue.toDoubleValue() ??
objectValue.toListValue() ??
objectValue.toMapValue() ??
Symbol(_check(objectValue.toSymbolValue(), 'literal'));
@override
bool get isLiteral =>
isBool ||
isString ||
isInt ||
isDouble ||
isList ||
isMap ||
isSymbol ||
isNull;
@override
bool instanceOf(TypeChecker checker) =>
checker.isAssignableFromType(objectValue.type);
@override
bool get isNull => isNullLike(objectValue);
@override
bool get isBool => objectValue.toBoolValue() != null;
@override
bool get boolValue => _check(objectValue.toBoolValue(), 'bool');
@override
bool get isDouble => objectValue.toDoubleValue() != null;
@override
double get doubleValue => _check(objectValue.toDoubleValue(), 'double');
@override
bool get isInt => objectValue.toIntValue() != null;
@override
int get intValue => _check(objectValue.toIntValue(), 'int');
@override
bool get isList => objectValue.toListValue() != null;
@override
List<DartObject> get listValue => _check(objectValue.toListValue(), 'List');
@override
bool get isMap => objectValue.toMapValue() != null;
@override
Map<DartObject, DartObject> get mapValue =>
_check(objectValue.toMapValue(), 'Map');
@override
bool get isString => objectValue.toStringValue() != null;
@override
String get stringValue => _check(objectValue.toStringValue(), 'String');
@override
bool get isSymbol => objectValue.toSymbolValue() != null;
@override
Symbol get symbolValue =>
Symbol(_check(objectValue.toSymbolValue(), 'Symbol'));
@override
bool get isType => objectValue.toTypeValue() != null;
@override
DartType get typeValue => _check(objectValue.toTypeValue(), 'Type');
@override
ConstantReader peek(String field) {
final constant = ConstantReader(getFieldRecursive(objectValue, field));
return constant.isNull ? null : constant;
}
@override
ConstantReader read(String field) {
final reader = peek(field);
if (reader == null) {
assertHasField(objectValue?.type?.element as ClassElement, field);
return const _NullConstant();
}
return reader;
}
@override
Revivable revive() => reviveInstance(objectValue);
}