blob: 08c36af0ff47b2b0f253bbc3fcb02f36909188fb [file]
// Copyright (c) 2023, 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:_fe_analyzer_shared/src/exhaustiveness/key.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/shared.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/types.dart';
class TestEnvironment implements ObjectPropertyLookup {
late final _TypeOperations _typeOperations = new _TypeOperations(this);
late final _EnumOperations _enumOperations = new _EnumOperations();
late final _SealedClassOperations _sealedClassOperations =
new _SealedClassOperations(this);
late final _ExhaustivenessCache _exhaustivenessCache =
new _ExhaustivenessCache(
_typeOperations, _enumOperations, _sealedClassOperations);
TestEnvironment() {
_addClass(_Class.Object);
_addClass(_Class.Never);
_addClass(_Class.Bool);
}
Map<String, _Class> _classes = {};
Map<_Class, Map<Key, _Type>> _fields = {};
Map<_Class, Set<_InterfaceType>> _supertypes = {};
Map<_Class, Set<_InterfaceType>> _subtypes = {};
void _addClass(_Class cls) {
assert(!_classes.containsKey(cls.name), "Duplicate class '${cls.name}'");
_classes[cls.name] = cls;
}
void _addSupertype(_InterfaceType type, _InterfaceType supertype) {
(_supertypes[type.cls] ??= {}).add(supertype);
(_subtypes[supertype.cls] ??= {}).add(type);
}
_Type _typeFromStaticType(StaticType type) {
if (type is TypeBasedStaticType<_Type>) {
return type.typeForTesting;
} else if (type is NullableStaticType) {
return new _NullableType(_typeFromStaticType(type.underlying));
} else if (type == StaticType.nullType) {
return _Type.Null;
} else if (type == StaticType.nonNullableObject) {
return _Type.Object;
} else if (type == StaticType.nullableObject) {
return _Type.NullableObject;
} else if (type == StaticType.neverType) {
return _Type.Never;
}
throw new UnsupportedError(
"Unexpected StaticType $type (${type.runtimeType}).");
}
Set<_InterfaceType> _getSupertypes(_Class cls) {
return _supertypes[cls] ?? const {};
}
Set<_InterfaceType> _getSubtypes(_Class cls) {
return _subtypes[cls] ?? const {};
}
Map<Key, _Type> _getFields(_Class cls) {
return _fields[cls] ?? const {};
}
StaticType createClass(String name,
{bool isSealed = false,
List<StaticType> inherits = const [],
Map<String, StaticType> fields = const {}}) {
_Class cls = new _Class(name, isSealed: isSealed);
_addClass(cls);
_InterfaceType type = new _InterfaceType(cls);
_addSupertype(type, _Type.Object);
for (StaticType inherit in inherits) {
_Type supertype = _typeFromStaticType(inherit);
if (supertype is _InterfaceType) {
_addSupertype(type, supertype);
} else {
throw new UnsupportedError(
"Unexpected supertype $supertype (${supertype.runtimeType}).");
}
}
if (fields.isNotEmpty) {
Map<Key, _Type> fieldMap = _fields[cls] ??= {};
for (MapEntry<String, StaticType> entry in fields.entries) {
// TODO(srawlins): Look into fixing this code. Right now we get:
// "The argument type 'String' isn't related to 'Key'."
// ignore: collection_methods_unrelated_type
assert(!fieldMap.containsKey(entry.key),
"Duplicate field '${entry.key}' in $cls.");
fieldMap[new NameKey(entry.key)] = _typeFromStaticType(entry.value);
}
}
int sealed = 0;
for (_InterfaceType supertype in _getSupertypes(cls)) {
if (supertype.cls.isSealed) sealed++;
}
// We don't allow a sealed type's subtypes to be shared with some other
// sibling supertype, as in D here:
//
// (A) (B)
// / \ / \
// C D E
//
// We could remove this restriction but doing so will require
// expandTypes() to be more complex. In the example here, if we subtract
// E from A, the result should be C|D. That requires knowing that B should
// be expanded, which expandTypes() doesn't currently handle.
if (sealed > 1) {
throw new ArgumentError('Can only have one sealed supertype.');
}
return _exhaustivenessCache.getStaticType(type);
}
StaticType createRecordType(Map<String, StaticType> named) {
Map<Key, _Type> namedTypes = {};
for (MapEntry<String, StaticType> entry in named.entries) {
namedTypes[new RecordNameKey(entry.key)] =
_typeFromStaticType(entry.value);
}
_Type type = new _RecordType([], namedTypes);
return _exhaustivenessCache.getStaticType(type);
}
@override
StaticType? getObjectFieldType(Key key) {
return _exhaustivenessCache.getObjectFieldType(key);
}
}
class _Type {
static const _InterfaceType Object = _InterfaceType(_Class.Object);
static const _Type NullableObject = _NullableType(_Type.Object);
static const _InterfaceType Never = _InterfaceType(_Class.Never);
static const _InterfaceType Bool = _InterfaceType(_Class.Bool);
static const _Type Null = _NullableType(_Type.Never);
}
class _Class {
final String name;
final bool isSealed;
const _Class(this.name, {this.isSealed = false});
@override
String toString() => name;
static const _Class Object = const _Class('Object');
static const _Class Bool = const _Class('bool');
static const _Class Never = const _Class('Never');
}
class _EnumClass extends _Class {
_EnumClass(super.name, {required super.isSealed});
}
// TODO(johnniwinther): Support testing of enums elements.
typedef _EnumElement = Object;
// TODO(johnniwinther): Support testing of enums element values.
typedef _EnumElementValue = Object;
class _InterfaceType implements _Type {
final _Class cls;
const _InterfaceType(this.cls);
@override
int get hashCode => cls.hashCode;
@override
bool operator ==(other) {
if (identical(this, other)) return true;
return other is _InterfaceType && cls == other.cls;
}
@override
String toString() => cls.name;
}
class _NullableType implements _Type {
final _Type type;
const _NullableType(this.type);
@override
int get hashCode => type.hashCode;
@override
bool operator ==(other) {
if (identical(this, other)) return true;
return other is _NullableType && type == other.type;
}
@override
String toString() => identical(this, _Type.Null) ? 'Null' : '$type?';
}
class _RecordType implements _Type {
final List<_Type> positional;
final Map<Key, _Type> named;
_RecordType(this.positional, this.named);
@override
int get hashCode => Object.hash(
Object.hashAll(positional),
Object.hashAllUnordered(named.keys),
Object.hashAllUnordered(named.values));
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! _RecordType) return false;
if (positional.length != other.positional.length) return false;
if (named.length != other.named.length) return false;
for (int i = 0; i < positional.length; i++) {
if (positional[i] != other.positional[i]) {
return false;
}
}
for (MapEntry<Key, _Type> entry in named.entries) {
if (entry.value != other.named[entry.key]) return false;
}
return true;
}
@override
String toString() {
StringBuffer sb = new StringBuffer();
sb.write('(');
String comma = '';
for (_Type type in positional) {
sb.write(comma);
sb.write(type);
comma = ', ';
}
if (named.isNotEmpty) {
sb.write(comma);
sb.write('{');
comma = '';
for (MapEntry<Key, _Type> entry in named.entries) {
sb.write(comma);
sb.write(entry.key.name);
sb.write(': ');
sb.write(entry.value);
comma = ', ';
}
sb.write('}');
}
sb.write(')');
return sb.toString();
}
}
class _TypeOperations implements TypeOperations<_Type> {
final TestEnvironment env;
_TypeOperations(this.env);
@override
_Type get boolType => _Type.Bool;
@override
Map<Key, _Type> getFieldTypes(_Type type) {
if (type is _InterfaceType) {
Map<Key, _Type> fields = {};
for (_InterfaceType supertype in env._getSupertypes(type.cls)) {
fields.addAll(getFieldTypes(supertype));
}
fields.addAll(env._getFields(type.cls));
return fields;
} else if (type is _RecordType) {
Map<Key, _Type> fields = {};
fields.addAll(getFieldTypes(_Type.Object));
for (int i = 0; i < type.positional.length; i++) {
fields[new RecordIndexKey(i)] = type.positional[i];
}
fields.addAll(type.named.map(
(key, value) => new MapEntry(new RecordNameKey(key.name), value)));
return fields;
} else {
return getFieldTypes(_Type.Object);
}
}
@override
_Type getNonNullable(_Type type) {
if (type is _NullableType) {
return type.type;
}
return type;
}
@override
bool isBoolType(_Type type) {
return type == _Type.Bool;
}
@override
bool isNeverType(_Type type) {
return type == _Type.Never;
}
@override
bool isNonNullableObject(_Type type) {
return type == _Type.Object;
}
@override
bool isDynamic(_Type type) {
return type == _Type.NullableObject;
}
@override
bool isNullType(_Type type) {
return type == _Type.Null;
}
@override
bool isNullable(_Type type) {
return type is _NullableType;
}
@override
bool isNullableObject(_Type type) {
return type == _Type.NullableObject;
}
@override
bool isSubtypeOf(_Type s, _Type t) {
if (s == t) return true;
if (t == _Type.NullableObject) return true;
if (s == _Type.Never) return true;
if (s == _Type.Null && t is _NullableType) return true;
if (s is _NullableType) {
if (t is _NullableType) {
return isSubtypeOf(s.type, t.type);
}
return false;
} else {
if (t is _NullableType) {
return isSubtypeOf(s, t.type);
} else {
if (s is _InterfaceType && t is _InterfaceType) {
if (t.cls == _Class.Object) return true;
for (_InterfaceType supertype in env._getSupertypes(s.cls)) {
if (isSubtypeOf(supertype, t)) {
return true;
}
}
}
return false;
}
}
}
@override
_Type get nonNullableObjectType => _Type.Object;
@override
_Type get nullableObjectType => _Type.NullableObject;
@override
String typeToString(_Type type) {
return type.toString();
}
@override
bool isRecordType(_Type type) {
return type is _RecordType;
}
@override
_Type overapproximate(_Type type) {
// TODO(johnniwinther): Support generic types in testing.
return type;
}
@override
bool isGeneric(_Type type) {
// TODO(johnniwinther): Support generic types in testing.
return false;
}
@override
_Type instantiateFuture(_Type type) {
throw new UnimplementedError('_TypeOperations.getFutureOrFutureType');
}
@override
_Type? getFutureOrTypeArgument(_Type type) {
// TODO(johnniwinther): Support future or types in testing.
return null;
}
@override
_Type? getListElementType(_Type type) {
// TODO(johnniwinther): Support list types in testing.
return null;
}
@override
_Type? getListType(_Type type) {
// TODO(johnniwinther): Support list types in testing.
return null;
}
@override
_Type? getMapValueType(_Type type) {
// TODO(johnniwinther): Support map types in testing.
return null;
}
@override
bool hasSimpleName(_Type type) {
return type is _InterfaceType;
}
@override
_Type? getTypeVariableBound(_Type type) {
// TODO(johnniwinther): Support type variable bounds in testing.
return null;
}
@override
_Type getExtensionTypeErasure(_Type type) {
// TODO(johnniwinther): Support extension types in testing.
return type;
}
}
class _EnumOperations
implements
EnumOperations<_Type, _EnumClass, _EnumElement, _EnumElementValue> {
@override
_EnumClass? getEnumClass(_Type type) {
if (type is _InterfaceType) {
_Class cls = type.cls;
if (cls is _EnumClass) {
return cls;
}
}
return null;
}
@override
String getEnumElementName(_EnumElement enumElement) {
// TODO(johnniwinther): Support testing of enums.
throw new UnimplementedError('_EnumOperations.getEnumElementName');
}
@override
_Type getEnumElementType(_EnumElement enumElement) {
// TODO(johnniwinther): Support testing of enums.
throw new UnimplementedError('_EnumOperations.getEnumElementType');
}
@override
_EnumElementValue getEnumElementValue(_EnumElement enumElement) {
// TODO(johnniwinther): Support testing of enums.
throw new UnimplementedError('_EnumOperations.getEnumElementValue');
}
@override
Iterable<_EnumElement> getEnumElements(_EnumClass enumClass) {
// TODO(johnniwinther): Support testing of enums.
throw new UnimplementedError('_EnumOperations.getEnumElements');
}
}
class _SealedClassOperations implements SealedClassOperations<_Type, _Class> {
final TestEnvironment env;
_SealedClassOperations(this.env);
@override
List<_Class> getDirectSubclasses(_Class sealedClass) {
List<_Class> classes = [];
for (_InterfaceType subtype in env._getSubtypes(sealedClass)) {
classes.add(subtype.cls);
}
return classes;
}
@override
_Class? getSealedClass(_Type type) {
if (type is _InterfaceType) {
_Class cls = type.cls;
if (cls.isSealed) {
return cls;
}
}
return null;
}
@override
_Type? getSubclassAsInstanceOf(_Class subClass, _Type sealedClassType) {
return new _InterfaceType(subClass);
}
}
class _ExhaustivenessCache extends ExhaustivenessCache<_Type, _Class,
_EnumClass, _EnumElement, _EnumElementValue> {
_ExhaustivenessCache(
super.typeOperations, super.enumOperations, super.sealedClassOperations);
}