| // 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 'dart_template_buffer.dart'; |
| import 'key.dart'; |
| import 'path.dart'; |
| import 'static_type.dart'; |
| |
| /// Describes a pattern that matches the value or a field accessed from it. |
| /// |
| /// Used only to generate the witness description. |
| class Predicate { |
| /// The path of getters that led from the original matched value to the value |
| /// tested by this predicate. |
| final Path path; |
| |
| /// The static type of the context. [valueType] is a subtype of this type. |
| final StaticType staticType; |
| |
| /// The type this predicate tests. |
| // TODO(johnniwinther): In order to model exhaustiveness on enum types, |
| // bool values, and maybe integers at some point, we may later want a separate |
| // kind of predicate that means "this value was equal to this constant". |
| final StaticType valueType; |
| |
| Predicate(this.path, this.staticType, this.valueType); |
| |
| @override |
| String toString() => 'Predicate(path=$path,type=$valueType)'; |
| } |
| |
| /// Witness that show an unmatched case. |
| /// |
| /// This is used to builds a human-friendly pattern-like string for the witness |
| /// matched by [_predicates]. |
| /// |
| /// For example, given: |
| /// |
| /// [] is U |
| /// ['w'] is T |
| /// ['w', 'x'] is B |
| /// ['w', 'y'] is B |
| /// ['z'] is T |
| /// ['z', 'x'] is C |
| /// ['z', 'y'] is B |
| /// |
| /// the [toString] produces: |
| /// |
| /// 'U(w: T(x: B, y: B), z: T(x: C, y: B))' |
| class Witness { |
| final List<Predicate> _predicates; |
| late final PropertyWitness _witness = _buildWitness(); |
| |
| Witness(this._predicates); |
| |
| PropertyWitness _buildWitness() { |
| PropertyWitness witness = new PropertyWitness(); |
| |
| for (Predicate predicate in _predicates) { |
| PropertyWitness here = witness; |
| for (Key field in predicate.path.toList()) { |
| here = here.properties.putIfAbsent(field, () => new PropertyWitness()); |
| } |
| here.staticType = predicate.staticType; |
| here.valueType = predicate.valueType; |
| } |
| return witness; |
| } |
| |
| String get asWitness => _witness.asWitness; |
| |
| String get asCorrection => _witness.asCorrection; |
| |
| /// Writes a representation of this witness to the given [buffer]. |
| void toDart(DartTemplateBuffer buffer, {required bool forCorrection}) { |
| _witness.witnessToDart(buffer, forCorrection: forCorrection); |
| } |
| |
| @override |
| String toString() => _witness.toString(); |
| } |
| |
| /// Helper class used to turn a list of [Predicate]s into a string. |
| class PropertyWitness { |
| StaticType staticType = StaticType.nullableObject; |
| StaticType valueType = StaticType.nullableObject; |
| final Map<Key, PropertyWitness> properties = {}; |
| |
| void witnessToDart(DartTemplateBuffer buffer, {required bool forCorrection}) { |
| if (properties.isNotEmpty) { |
| Map<StaticType, Map<Key, PropertyWitness>> witnessFieldsByType = {}; |
| for (MapEntry<Key, PropertyWitness> entry in properties.entries) { |
| Key key = entry.key; |
| PropertyWitness witness = entry.value; |
| if (forCorrection && witness.isTrivial) { |
| continue; |
| } |
| if (key is ExtensionKey) { |
| (witnessFieldsByType[key.receiverType] ??= {})[key] = witness; |
| } else { |
| (witnessFieldsByType[valueType] ??= {})[key] = witness; |
| } |
| } |
| if (witnessFieldsByType.isNotEmpty) { |
| String and = ''; |
| for (MapEntry<StaticType, Map<Key, PropertyWitness>> entry |
| in witnessFieldsByType.entries) { |
| StaticType type = entry.key; |
| Map<Key, PropertyWitness> witnessFields = entry.value; |
| buffer.write(and); |
| and = ' && '; |
| type.witnessToDart( |
| buffer, |
| this, |
| witnessFields, |
| forCorrection: forCorrection, |
| ); |
| } |
| } else { |
| valueType.witnessToDart( |
| buffer, |
| this, |
| const {}, |
| forCorrection: forCorrection, |
| ); |
| } |
| } else { |
| valueType.witnessToDart( |
| buffer, |
| this, |
| const {}, |
| forCorrection: forCorrection, |
| ); |
| } |
| } |
| |
| bool get isTrivial { |
| if (!staticType.isSubtypeOf(valueType)) return false; |
| for (PropertyWitness property in properties.values) { |
| if (!property.isTrivial) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /// Returns the witness as pattern syntax including all subproperties. |
| String get asWitness { |
| DartTemplateBuffer buffer = new SimpleDartBuffer(); |
| witnessToDart(buffer, forCorrection: false); |
| return buffer.toString(); |
| } |
| |
| /// Return the witness as pattern syntax without subproperties that fully |
| /// match the static type. |
| String get asCorrection { |
| DartTemplateBuffer buffer = new SimpleDartBuffer(); |
| witnessToDart(buffer, forCorrection: true); |
| return buffer.toString(); |
| } |
| |
| @override |
| String toString() => asWitness; |
| } |