From 1b5038850738a5f8c85bcb0dff132e559d131898 Mon Sep 17 00:00:00 2001 From: David Morgan Date: Fri, 4 Oct 2024 12:03:48 +0200 Subject: [PATCH] Add Model.pathToMember, add fast way to find the path to JsonBufferBuilder. --- pkgs/dart_model/lib/src/dart_model.dart | 63 ++++++++++++++++++ .../lib/src/json_buffer/closed_map.dart | 47 ++++++++++---- .../lib/src/json_buffer/growable_map.dart | 65 ++++++++++++------- .../lib/src/json_buffer/iterables.dart | 10 +++ .../src/json_buffer/json_buffer_builder.dart | 14 ++-- pkgs/dart_model/lib/src/json_buffer/type.dart | 4 +- .../lib/src/json_buffer/typed_map.dart | 44 ++++++++----- .../test/json_buffer/closed_map_test.dart | 18 +++++ .../test/json_buffer/growable_map_test.dart | 16 +++++ .../test/json_buffer/typed_map_test.dart | 17 +++++ pkgs/dart_model/test/model_test.dart | 40 ++++++++++++ 11 files changed, 276 insertions(+), 62 deletions(-) diff --git a/pkgs/dart_model/lib/src/dart_model.dart b/pkgs/dart_model/lib/src/dart_model.dart index b9e78636..80bfbe59 100644 --- a/pkgs/dart_model/lib/src/dart_model.dart +++ b/pkgs/dart_model/lib/src/dart_model.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart_model.g.dart'; +import 'json_buffer/json_buffer_builder.dart'; export 'dart_model.g.dart'; @@ -11,3 +12,65 @@ extension QualifiedNameExtension on QualifiedName { bool equals(QualifiedName other) => other.uri == uri && other.name == name; } + +extension ModelExtension on Model { + /// Returns the path in the model to [member], or `null` if [member] is not in this `Model`. + /// + /// TODO(davidmorgan): this works for any node, but it's not clear yet which types of + /// node we want this functionality exposed for. + /// TODO(davidmorgan): a list of path segments is probably more useful than `String`. + String? pathToMember(Member member) => _pathTo(member.node); + + /// Returns the path in the model to [node], or `null` if [node] is not in this `Model`. + String? _pathTo(Map node) { + if (node == this.node) return ''; + final parent = _getParent(node); + if (parent == null) return null; + for (final entry in parent.entries) { + if (entry.value == node) { + final parentPath = _pathTo(parent); + return parentPath == null ? null : '${_pathTo(parent)}/${entry.key}'; + } + } + return null; + } + + /// Gets the `Map` that contains [node], or `null` if there isn't one. + Map? _getParent(Map node) { + // If both maps are in the same `JsonBufferBuilder` then the parent is + // immediately available. + if (this is MapInBuffer && node is MapInBuffer) { + final thisMapInBuffer = this as MapInBuffer; + final thatMapInBuffer = node as MapInBuffer; + if (thisMapInBuffer.buffer == thatMapInBuffer.buffer) { + return thatMapInBuffer.parent; + } + } + // Otherwise, build a `Map` of references to parents and use that. + return _parentsMap[node]; + } + + /// Gets a `Map` from values to parent `Map`s. + Map, Map> get _parentsMap { + var result = _parentsMaps[this]; + if (result == null) { + result = + _parentsMaps[this] = , Map>{}; + _buildParentsMap(node, result); + } + return result; + } + + /// Builds a `Map` from values to parent `Map`s. + static void _buildParentsMap(Map parent, + Map, Map> result) { + for (final child in parent.values.whereType>()) { + result[child] = parent; + _buildParentsMap(child, result); + } + } +} + +/// Expando storing a `Map` from values to parent `Map`s. +final Expando, Map>> _parentsMaps = + Expando(); diff --git a/pkgs/dart_model/lib/src/json_buffer/closed_map.dart b/pkgs/dart_model/lib/src/json_buffer/closed_map.dart index 35bb7dbb..e7dfa6ec 100644 --- a/pkgs/dart_model/lib/src/json_buffer/closed_map.dart +++ b/pkgs/dart_model/lib/src/json_buffer/closed_map.dart @@ -44,20 +44,25 @@ extension ClosedMaps on JsonBufferBuilder { } /// Returns the [_ClosedMap] at [pointer]. - Map _readClosedMap(_Pointer pointer) { - return _ClosedMap(this, pointer); + Map _readClosedMap( + _Pointer pointer, Map? parent) { + return _ClosedMap(this, pointer, parent); } } class _ClosedMap - with MapMixin, _EntryMapMixin { - final JsonBufferBuilder _buffer; + with MapMixin, _EntryMapMixin + implements MapInBuffer { + @override + final JsonBufferBuilder buffer; final _Pointer _pointer; @override + final Map? parent; + @override final int length; - _ClosedMap(this._buffer, this._pointer) - : length = _buffer._readLength(_pointer); + _ClosedMap(this.buffer, this._pointer, this.parent) + : length = buffer._readLength(_pointer); @override Object? operator [](Object? key) { @@ -70,18 +75,18 @@ class _ClosedMap @override late final Iterable keys = _IteratorFunctionIterable( - () => _ClosedMapKeyIterator(_buffer, _pointer, length), + () => _ClosedMapKeyIterator(buffer, this, _pointer, length), length: length); @override late final Iterable values = _IteratorFunctionIterable( - () => _ClosedMapValueIterator(_buffer, _pointer, length), + () => _ClosedMapValueIterator(buffer, this, _pointer, length), length: length); @override late final Iterable> entries = _IteratorFunctionIterable( - () => _ClosedMapEntryIterator(_buffer, _pointer, length), + () => _ClosedMapEntryIterator(buffer, this, _pointer, length), length: length); @override @@ -101,16 +106,26 @@ class _ClosedMap throw UnsupportedError( 'This JsonBufferBuilder map is read-only, see "createGrowableMap".'); } + + @override + bool operator ==(Object other) => + other is _ClosedMap && + other.buffer == buffer && + other._pointer == _pointer; + + @override + int get hashCode => buffer.hashCode ^ _pointer.hashCode; } /// `Iterator` that reads a "closed map" in a [JsonBufferBuilder]. abstract class _ClosedMapIterator implements Iterator { final JsonBufferBuilder _buffer; + final _ClosedMap _parent; final _Pointer _last; _Pointer _pointer; - _ClosedMapIterator(this._buffer, _Pointer pointer, int length) + _ClosedMapIterator(this._buffer, this._parent, _Pointer pointer, int length) : _last = pointer + _lengthSize + length * ClosedMaps._entrySize, // Subtract because `moveNext` is called before reading. _pointer = pointer + _lengthSize - ClosedMaps._entrySize; @@ -119,7 +134,8 @@ abstract class _ClosedMapIterator implements Iterator { T get current; String get _currentKey => _buffer._readString(_buffer._readPointer(_pointer)); - Object? get _currentValue => _buffer._readAny(_pointer + _pointerSize); + Object? get _currentValue => + _buffer._readAny(_pointer + _pointerSize, parent: _parent); @override bool moveNext() { @@ -131,14 +147,16 @@ abstract class _ClosedMapIterator implements Iterator { } class _ClosedMapKeyIterator extends _ClosedMapIterator { - _ClosedMapKeyIterator(super._buffer, super.pointer, super.length); + _ClosedMapKeyIterator( + super._buffer, super._porent, super.pointer, super.length); @override String get current => _currentKey; } class _ClosedMapValueIterator extends _ClosedMapIterator { - _ClosedMapValueIterator(super._buffer, super.pointer, super.length); + _ClosedMapValueIterator( + super._buffer, super._porent, super.pointer, super.length); @override Object? get current => _currentValue; @@ -146,7 +164,8 @@ class _ClosedMapValueIterator extends _ClosedMapIterator { class _ClosedMapEntryIterator extends _ClosedMapIterator> { - _ClosedMapEntryIterator(super._buffer, super.pointer, super.length); + _ClosedMapEntryIterator( + super._buffer, super._porent, super.pointer, super.length); @override MapEntry get current => MapEntry(_currentKey, _currentValue); diff --git a/pkgs/dart_model/lib/src/json_buffer/growable_map.dart b/pkgs/dart_model/lib/src/json_buffer/growable_map.dart index 0b4dd58f..2a07f8a2 100644 --- a/pkgs/dart_model/lib/src/json_buffer/growable_map.dart +++ b/pkgs/dart_model/lib/src/json_buffer/growable_map.dart @@ -43,7 +43,7 @@ extension GrowableMaps on JsonBufferBuilder { // Initially a "growable map" is just a null pointer and zero size, so // there is nothing to write. _explanations?.pop(); - return _readGrowableMap(pointer); + return _readGrowableMap(pointer, null); } /// Returns the [_Pointer] to [map]. @@ -57,26 +57,32 @@ extension GrowableMaps on JsonBufferBuilder { /// Throws if [map is backed by a different buffer to `this`. void _checkGrowableMapOwnership(_GrowableMap map) { - if (map._buffer != this) { + if (map.buffer != this) { throw UnsupportedError('Maps created with `createGrowableMap` can only ' 'be added to the JsonBufferBuilder instance that created them.'); } } /// Returns the [_GrowableMap] at [pointer]. - Map _readGrowableMap(_Pointer pointer) { - return _GrowableMap(this, pointer); + Map _readGrowableMap( + _Pointer pointer, Map? parent) { + return _GrowableMap(this, pointer, parent); } } -class _GrowableMap with MapMixin, _EntryMapMixin { - final JsonBufferBuilder _buffer; +class _GrowableMap + with MapMixin, _EntryMapMixin + implements MapInBuffer { + @override + final JsonBufferBuilder buffer; final _Pointer _pointer; + @override + final Map? parent; int _length; _Pointer? _lastPointer; - _GrowableMap(this._buffer, this._pointer) - : _length = _buffer._readLength(_pointer + _pointerSize); + _GrowableMap(this.buffer, this._pointer, this.parent) + : _length = buffer._readLength(_pointer + _pointerSize); @override int get length => _length; @@ -94,17 +100,17 @@ class _GrowableMap with MapMixin, _EntryMapMixin { @override late final Iterable keys = _IteratorFunctionIterable( - () => _GrowableMapKeyIterator(_buffer, _pointer), + () => _GrowableMapKeyIterator(buffer, this, _pointer), length: length); @override late final Iterable values = _IteratorFunctionIterable( - () => _GrowableMapValueIterator(_buffer, _pointer), + () => _GrowableMapValueIterator(buffer, this, _pointer), length: length); @override late final Iterable> entries = _IteratorFunctionIterable( - () => _GrowableMapEntryIterator(_buffer, _pointer), + () => _GrowableMapEntryIterator(buffer, this, _pointer), length: length); /// Add [value] to the map with key [key]. @@ -115,11 +121,11 @@ class _GrowableMap with MapMixin, _EntryMapMixin { /// iterable. @override void operator []=(String key, V value) { - _buffer._explanations?.push('GrowableMap[]= $key $value'); + buffer._explanations?.push('GrowableMap[]= $key $value'); // If `_lastPointer` is not set yet, walk the map to find the end of it. if (_lastPointer == null) { - final iterator = _GrowableMapEntryIterator(_buffer, _pointer); + final iterator = _GrowableMapEntryIterator(buffer, this, _pointer); _lastPointer = _pointer; while (iterator.moveNext()) { _lastPointer = iterator._pointer; @@ -127,20 +133,20 @@ class _GrowableMap with MapMixin, _EntryMapMixin { } // Reserve and write the new node. - final pointer = _buffer._reserve(GrowableMaps._entrySize); + final pointer = buffer._reserve(GrowableMaps._entrySize); final entryPointer = pointer + _pointerSize; - _buffer._writePointer(entryPointer, _buffer._pointerToString(key)); - _buffer._writeAny(entryPointer + _pointerSize, value); + buffer._writePointer(entryPointer, buffer._pointerToString(key)); + buffer._writeAny(entryPointer + _pointerSize, value); // Point to the new node in the previous node. - _buffer._writePointer(_lastPointer!, pointer); + buffer._writePointer(_lastPointer!, pointer); // Update `_lastPointer` to the new node. _lastPointer = pointer; // Update length. ++_length; - _buffer._writeLength(_pointer + _pointerSize, length, allowOverwrite: true); - _buffer._explanations?.pop(); + buffer._writeLength(_pointer + _pointerSize, length, allowOverwrite: true); + buffer._explanations?.pop(); } @override @@ -152,14 +158,24 @@ class _GrowableMap with MapMixin, _EntryMapMixin { void clear() { throw UnsupportedError('JsonBufferBuilder growable maps are append only.'); } + + @override + bool operator ==(Object other) => + other is _GrowableMap && + other.buffer == buffer && + other._pointer == _pointer; + + @override + int get hashCode => buffer.hashCode ^ _pointer.hashCode; } /// `Iterator` that reads a "growable map" in a [JsonBufferBuilder]. abstract class _GrowableMapIterator implements Iterator { final JsonBufferBuilder _buffer; + final _GrowableMap _parent; _Pointer _pointer; - _GrowableMapIterator(this._buffer, this._pointer); + _GrowableMapIterator(this._buffer, this._parent, this._pointer); @override T get current; @@ -167,7 +183,8 @@ abstract class _GrowableMapIterator implements Iterator { String get _currentKey => _buffer._readString(_buffer._readPointer(_pointer + _pointerSize)); Object? get _currentValue => - _buffer._readAny(_pointer + _pointerSize + GrowableMaps._keySize); + _buffer._readAny(_pointer + _pointerSize + GrowableMaps._keySize, + parent: _parent); @override bool moveNext() { @@ -177,14 +194,14 @@ abstract class _GrowableMapIterator implements Iterator { } class _GrowableMapKeyIterator extends _GrowableMapIterator { - _GrowableMapKeyIterator(super._buffer, super._pointer); + _GrowableMapKeyIterator(super._buffer, super._parent, super._pointer); @override String get current => _currentKey; } class _GrowableMapValueIterator extends _GrowableMapIterator { - _GrowableMapValueIterator(super._buffer, super._pointer); + _GrowableMapValueIterator(super._buffer, super._parent, super._pointer); @override V get current => _currentValue as V; @@ -192,7 +209,7 @@ class _GrowableMapValueIterator extends _GrowableMapIterator { class _GrowableMapEntryIterator extends _GrowableMapIterator> { - _GrowableMapEntryIterator(super._buffer, super._pointer); + _GrowableMapEntryIterator(super._buffer, super._parent, super._pointer); @override MapEntry get current => MapEntry(_currentKey, _currentValue as V); diff --git a/pkgs/dart_model/lib/src/json_buffer/iterables.dart b/pkgs/dart_model/lib/src/json_buffer/iterables.dart index 21ef60ac..d08aedee 100644 --- a/pkgs/dart_model/lib/src/json_buffer/iterables.dart +++ b/pkgs/dart_model/lib/src/json_buffer/iterables.dart @@ -39,3 +39,13 @@ class _IteratorFunctionIterable extends Iterable { @override Iterator get iterator => _function(); } + +/// A `Map` in a `JsonBufferBuilder`. +abstract interface class MapInBuffer { + /// The buffer backing this `Map`. + JsonBufferBuilder get buffer; + + /// The `Map` that contains this value, or `null` if this value has not been + /// added to a `Map` or is itself the root `Map`. + Map? get parent; +} diff --git a/pkgs/dart_model/lib/src/json_buffer/json_buffer_builder.dart b/pkgs/dart_model/lib/src/json_buffer/json_buffer_builder.dart index 26ce71c3..762d7481 100644 --- a/pkgs/dart_model/lib/src/json_buffer/json_buffer_builder.dart +++ b/pkgs/dart_model/lib/src/json_buffer/json_buffer_builder.dart @@ -42,7 +42,7 @@ class JsonBufferBuilder { JsonBufferBuilder.deserialize(this._buffer) : _allowWrites = false, _nextFree = _buffer.length { - map = _readGrowableMap(0); + map = _readGrowableMap(0, null); } JsonBufferBuilder() @@ -63,13 +63,13 @@ class JsonBufferBuilder { /// Reads the value at [_Pointer], which must have been written with /// [_writeAny]. - Object? _readAny(_Pointer pointer) { + Object? _readAny(_Pointer pointer, {Map? parent}) { final type = _readType(pointer); - return _read(type, pointer + _typeSize); + return _read(type, pointer + _typeSize, parent: parent); } /// Reads the value of type [Type] at [_Pointer]. - Object? _read(Type type, _Pointer pointer) { + Object? _read(Type type, _Pointer pointer, {Map? parent}) { switch (type) { case Type.nil: return null; @@ -88,11 +88,11 @@ class JsonBufferBuilder { case Type.closedListPointer: return _readClosedList(_readPointer(pointer)); case Type.closedMapPointer: - return _readClosedMap(_readPointer(pointer)); + return _readClosedMap(_readPointer(pointer), parent); case Type.growableMapPointer: - return _readGrowableMap(_readPointer(pointer)); + return _readGrowableMap(_readPointer(pointer), parent); case Type.typedMapPointer: - return _readTypedMap(_readPointer(pointer)); + return _readTypedMap(_readPointer(pointer), parent); } } diff --git a/pkgs/dart_model/lib/src/json_buffer/type.dart b/pkgs/dart_model/lib/src/json_buffer/type.dart index 776fa57c..75506394 100644 --- a/pkgs/dart_model/lib/src/json_buffer/type.dart +++ b/pkgs/dart_model/lib/src/json_buffer/type.dart @@ -54,11 +54,11 @@ enum Type { case List(): return Type.closedListPointer; case _TypedMap(): - return builder == value._buffer + return builder == value.buffer ? Type.typedMapPointer : Type.closedMapPointer; case _GrowableMap(): - return builder == value._buffer + return builder == value.buffer ? Type.growableMapPointer : Type.closedMapPointer; case Map(): diff --git a/pkgs/dart_model/lib/src/json_buffer/typed_map.dart b/pkgs/dart_model/lib/src/json_buffer/typed_map.dart index ebf81cc4..40324858 100644 --- a/pkgs/dart_model/lib/src/json_buffer/typed_map.dart +++ b/pkgs/dart_model/lib/src/json_buffer/typed_map.dart @@ -224,7 +224,7 @@ extension TypedMaps on JsonBufferBuilder { } _explanations?.pop(); - return _TypedMap(this, pointer); + return _TypedMap(this, pointer, null); } /// Returns the [_Pointer] to [map]. @@ -237,23 +237,27 @@ extension TypedMaps on JsonBufferBuilder { /// Throws if [map is backed by a different buffer to `this`. void _checkTypedMapOwnership(_TypedMap map) { - if (map._buffer != this) { + if (map.buffer != this) { throw UnsupportedError('Maps created with `createTypedMap` can only ' 'be added to the JsonBufferBuilder instance that created them.'); } } /// Returns the [_TypedMap] at [pointer]. - Map _readTypedMap(_Pointer pointer) { - return _TypedMap(this, pointer); + Map _readTypedMap( + _Pointer pointer, Map? parent) { + return _TypedMap(this, pointer, parent); } } class _TypedMap with MapMixin, _EntryMapMixin - implements Map { - final JsonBufferBuilder _buffer; + implements Map, MapInBuffer { + @override + final JsonBufferBuilder buffer; final _Pointer _pointer; + @override + final Map? parent; // If a `TypedMap` is created then immediately added to another `Map` then // these values are never needed, just the `_pointer`. Use `late` so they are @@ -261,17 +265,17 @@ class _TypedMap late final _Pointer _schemaPointer = // The high byte of the schema pointer indicates "filled", omit it. - _buffer._readPointer(_pointer) & 0x7fffffff; + buffer._readPointer(_pointer) & 0x7fffffff; /// The schema of this "typed map" giving its field names and types. late final TypedMapSchema _schema = - _buffer._schemasByPointer[_schemaPointer] ??= - TypedMapSchema(_buffer._readClosedMap(_schemaPointer).cast()); + buffer._schemasByPointer[_schemaPointer] ??= + TypedMapSchema(buffer._readClosedMap(_schemaPointer, null).cast()); /// Whether all fields are present, meaning no explicit field set was written. - late final bool filled = (_buffer._readPointer(_pointer) & 0x80000000) != 0; + late final bool filled = (buffer._readPointer(_pointer) & 0x80000000) != 0; - _TypedMap(this._buffer, this._pointer); + _TypedMap(this.buffer, this._pointer, this.parent); /// Whether the field at [index] is present. bool _hasField(int index) { @@ -282,7 +286,7 @@ class _TypedMap if (filled) return true; final byte = index ~/ 8; final bit = index % 8; - return _buffer._readBit(_pointer + _pointerSize + byte, bit); + return buffer._readBit(_pointer + _pointerSize + byte, bit); } @override @@ -347,6 +351,15 @@ class _TypedMap throw UnsupportedError( 'This JsonBufferBuilder map is read-only, see "createGrowableMap".'); } + + @override + bool operator ==(Object other) => + other is _TypedMap && + other.buffer == buffer && + other._pointer == _pointer; + + @override + int get hashCode => buffer.hashCode ^ _pointer.hashCode; } /// `Iterator` that reads a "typed map" in a [JsonBufferBuilder]. @@ -363,7 +376,7 @@ abstract class _PartialTypedMapIterator implements Iterator { int _offset = -1; _PartialTypedMapIterator(this._map) - : _buffer = _map._buffer, + : _buffer = _map.buffer, _schema = _map._schema, _valuesPointer = _map._pointer + _pointerSize + @@ -374,7 +387,8 @@ abstract class _PartialTypedMapIterator implements Iterator { String get _currentKey => _schema._keys[_index]; Object? get _currentValue => - _buffer._read(_schema._valueTypes[_index], _valuesPointer + _offset); + _buffer._read(_schema._valueTypes[_index], _valuesPointer + _offset, + parent: _map); @override bool moveNext() { @@ -436,7 +450,7 @@ abstract class _AllBoolsTypedMapIterator implements Iterator { int _bitOffset = 7; _AllBoolsTypedMapIterator(this._map) - : _buffer = _map._buffer, + : _buffer = _map.buffer, _schema = _map._schema, _valuesPointer = _map._pointer + _pointerSize + diff --git a/pkgs/dart_model/test/json_buffer/closed_map_test.dart b/pkgs/dart_model/test/json_buffer/closed_map_test.dart index c564c2cd..58e8a295 100644 --- a/pkgs/dart_model/test/json_buffer/closed_map_test.dart +++ b/pkgs/dart_model/test/json_buffer/closed_map_test.dart @@ -20,6 +20,24 @@ void main() { printOnFailure(builder.toString()); }); + test('equality and hashing is by identity', () { + builder.map['a'] = {}; + final closedMap1 = builder.map['a'] as Map; + builder.map['b'] = {}; + final closedMap2 = builder.map['b'] as Map; + + // Different maps with the same contents are not equal, have different hash codes. + // Can't use default `expect` equality check as it special-cases `Map`. + expect(closedMap1 == closedMap2, false); + expect(closedMap1.hashCode, isNot(closedMap2.hashCode)); + + // Map is equal to a reference to itself, has same hash code. + builder.map['a'] = closedMap1; + final closedMap1Reference = builder.map['a'] as Map; + expect(closedMap1 == closedMap1Reference, true); + expect(closedMap1.hashCode, closedMap1Reference.hashCode); + }); + test('simple write and read', () { final value = {'a': 1, 'b': 2}; builder.map['value'] = value; diff --git a/pkgs/dart_model/test/json_buffer/growable_map_test.dart b/pkgs/dart_model/test/json_buffer/growable_map_test.dart index 83a0f218..1b69e854 100644 --- a/pkgs/dart_model/test/json_buffer/growable_map_test.dart +++ b/pkgs/dart_model/test/json_buffer/growable_map_test.dart @@ -20,6 +20,22 @@ void main() { printOnFailure(builder.toString()); }); + test('equality and hashing is by identity', () { + final growableMap1 = builder.createGrowableMap(); + final growableMap2 = builder.createGrowableMap(); + + // Different maps with the same contents are not equal, have different hash codes. + // Can't use default `expect` equality check as it special-cases `Map`. + expect(growableMap1 == growableMap2, false); + expect(growableMap1.hashCode, isNot(growableMap2.hashCode)); + + // Map is equal to a reference to itself, has same hash code. + builder.map['a'] = growableMap1; + final growableMap1Reference = builder.map['a'] as Map; + expect(growableMap1 == growableMap1Reference, true); + expect(growableMap1.hashCode, growableMap1Reference.hashCode); + }); + test('can be written and read if empty', () { final value = builder.createGrowableMap(); builder.map['value'] = value; diff --git a/pkgs/dart_model/test/json_buffer/typed_map_test.dart b/pkgs/dart_model/test/json_buffer/typed_map_test.dart index 64b69d61..48ce11d5 100644 --- a/pkgs/dart_model/test/json_buffer/typed_map_test.dart +++ b/pkgs/dart_model/test/json_buffer/typed_map_test.dart @@ -20,6 +20,23 @@ void main() { printOnFailure(builder.toString()); }); + test('equality and hashing is by identity', () { + final schema = TypedMapSchema({}); + final typedMap1 = builder.createTypedMap(schema); + final typedMap2 = builder.createTypedMap(schema); + + // Different maps with the same contents are not equal, have different hash codes. + // Can't use default `expect` equality check as it special-cases `Map`. + expect(typedMap1 == typedMap2, false); + expect(typedMap1.hashCode, isNot(typedMap2.hashCode)); + + // Map is equal to a reference to itself, has same hash code. + builder.map['a'] = typedMap1; + final typedMap1Reference = builder.map['a'] as Map; + expect(typedMap1 == typedMap1Reference, true); + expect(typedMap1.hashCode, typedMap1Reference.hashCode); + }); + test('with some values missing can be written and read', () { final schema = TypedMapSchema({ 'a': Type.stringPointer, diff --git a/pkgs/dart_model/test/model_test.dart b/pkgs/dart_model/test/model_test.dart index 08732d4b..c2d71e24 100644 --- a/pkgs/dart_model/test/model_test.dart +++ b/pkgs/dart_model/test/model_test.dart @@ -107,6 +107,34 @@ void main() { expected['uris']!['package:dart_model/dart_model.dart']!['scopes']![ 'JsonData']!['members']); }); + + test('can give the path to Members in buffer backed maps', () { + final member = model.uris['package:dart_model/dart_model.dart']! + .scopes['JsonData']!.members['_root']!; + expect(model.pathToMember(member), + '/uris/package:dart_model/dart_model.dart/scopes/JsonData/members/_root'); + }); + + test('can give the path to Members in SDK maps', () { + final copiedModel = Model.fromJson(_copyMap(model.node)); + final member = copiedModel.uris['package:dart_model/dart_model.dart']! + .scopes['JsonData']!.members['_root']!; + expect(copiedModel.pathToMember(member), + '/uris/package:dart_model/dart_model.dart/scopes/JsonData/members/_root'); + }); + + test('path to Member returns null for Member in wrong Map', () { + final copiedModel = Model.fromJson(_copyMap(model.node)); + final member = model.uris['package:dart_model/dart_model.dart']! + .scopes['JsonData']!.members['_root']!; + final copiedMember = Member.fromJson(_copyMap(model + .uris['package:dart_model/dart_model.dart']! + .scopes['JsonData']! + .members['_root']! + .node)); + expect(copiedModel.pathToMember(member), null); + expect(model.pathToMember(copiedMember), null); + }); }); group(QualifiedName, () { @@ -122,3 +150,15 @@ void main() { }); }); } + +Map _copyMap(Map map) { + final result = {}; + for (final entry in map.entries) { + if (entry.value is Map) { + result[entry.key] = _copyMap(entry.value as Map); + } else { + result[entry.key] = entry.value; + } + } + return result; +}