blob: e15db865e8aa9dd08e625494ddd55892c05f5906 [file] [log] [blame]
// 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.
part of '../types.dart';
/// [StaticType] for a map pattern type using a [MapTypeIdentity] for its
/// uniqueness.
class MapPatternStaticType<Type extends Object>
extends RestrictedStaticType<Type, MapTypeIdentity<Type>> {
MapPatternStaticType(super.typeOperations, super.fieldLookup, super.type,
super.restriction, super.name);
@override
String spaceToText(
Map<Key, Space> spaceFields, Map<Key, Space> additionalSpaceFields) {
StringBuffer buffer = new StringBuffer();
buffer.write(restriction.typeArgumentsText);
buffer.write('{');
bool first = true;
additionalSpaceFields.forEach((Key key, Space space) {
if (!first) buffer.write(', ');
buffer.write('$key: $space');
first = false;
});
if (restriction.hasRest) {
if (!first) buffer.write(', ');
buffer.write('...');
}
buffer.write('}');
return buffer.toString();
}
@override
void witnessToText(StringBuffer buffer, FieldWitness witness,
Map<Key, FieldWitness> witnessFields) {
buffer.write('{');
String comma = '';
for (MapKey key in restriction.keys) {
buffer.write(comma);
buffer.write(key.valueAsText);
buffer.write(': ');
FieldWitness? witness = witnessFields[key];
if (witness != null) {
witness.witnessToText(buffer);
} else {
buffer.write('_');
}
comma = ', ';
}
if (restriction.hasRest) {
buffer.write(comma);
buffer.write('...');
}
buffer.write('}');
// If we have restrictions on the record type we create an and pattern.
String additionalStart = ' && Object(';
String additionalEnd = '';
comma = '';
for (MapEntry<Key, FieldWitness> entry in witnessFields.entries) {
Key key = entry.key;
if (key is! MapKey) {
buffer.write(additionalStart);
additionalStart = '';
additionalEnd = ')';
buffer.write(comma);
comma = ', ';
buffer.write(key.name);
buffer.write(': ');
FieldWitness field = entry.value;
field.witnessToText(buffer);
}
}
buffer.write(additionalEnd);
}
}
/// Identity object used for creating a unique [MapPatternStaticType] for a
/// map pattern.
///
/// The uniqueness is defined by the key and value types, the key values of
/// the map pattern, and whether the map pattern has a rest element.
///
/// This identity ensures that we can detect overlap between map patterns with
/// the same set of keys.
class MapTypeIdentity<Type extends Object> implements Restriction<Type> {
final Type keyType;
final Type valueType;
final Set<MapKey> keys;
final bool hasRest;
final String typeArgumentsText;
MapTypeIdentity(
this.keyType, this.valueType, this.keys, this.typeArgumentsText,
{required this.hasRest});
@override
late final int hashCode =
Object.hash(keyType, valueType, Object.hashAllUnordered(keys), hasRest);
@override
bool get isUnrestricted {
// The map pattern containing only a rest pattern covers the whole type.
return hasRest && keys.isEmpty;
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! MapTypeIdentity<Type>) return false;
if (keyType != other.keyType ||
valueType != other.valueType ||
hasRest != other.hasRest) {
return false;
}
if (keys.length != other.keys.length) return false;
return keys.containsAll(other.keys);
}
@override
bool isSubtypeOf(TypeOperations<Type> typeOperations, Restriction other) {
if (other.isUnrestricted) return true;
if (other is! MapTypeIdentity<Type>) return false;
if (!typeOperations.isSubtypeOf(keyType, other.keyType)) return false;
if (!typeOperations.isSubtypeOf(valueType, other.valueType)) return false;
if (other.hasRest) {
return keys.containsAll(other.keys);
} else if (hasRest) {
return false;
} else {
return keys.length == other.keys.length && keys.containsAll(other.keys);
}
}
@override
String toString() {
StringBuffer sb = new StringBuffer();
sb.write(typeArgumentsText);
sb.write('{');
String comma = '';
for (MapKey key in keys) {
sb.write(comma);
sb.write(key);
sb.write(': ()');
comma = ', ';
}
if (hasRest) {
sb.write(comma);
sb.write('...');
comma = ', ';
}
sb.write('}');
return sb.toString();
}
}