Add test for #47852
Bug: #47852
Change-Id: If17635dfcf9794edc2cf23ad5219ed87fda41fcb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/393182
Reviewed-by: Lasse Nielsen <lrn@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/tests/lib/collection/regress_47852_test.dart b/tests/lib/collection/regress_47852_test.dart
new file mode 100644
index 0000000..4345878
--- /dev/null
+++ b/tests/lib/collection/regress_47852_test.dart
@@ -0,0 +1,99 @@
+// Copyright (c) 2024, 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:collection';
+
+import 'package:expect/expect.dart';
+
+/// Bug in dev-compiler's [putIfAbsent](https://dartbug.com/47852).
+void originalError() {
+ final map = {};
+ final key = DateTime.now(); // Overrides Object.==/hashCode
+ bool wasAbsent = false;
+ map.putIfAbsent(key, () {
+ wasAbsent = true;
+ Expect.isFalse(
+ map.containsKey(key), 'containsKey should be false in putIfAbsent');
+ return key;
+ });
+ Expect.isTrue(wasAbsent);
+}
+
+enum AnEnum { element1, element2 }
+
+class Dumb {
+ final Object field;
+ Dumb(this.field);
+
+ // Dumb hashCode to stress same-bucket paths.
+ int get hashCode => 0;
+ bool operator ==(Object other) => other is Dumb && this.field == other.field;
+
+ String toString() => 'Dumb($field)';
+}
+
+// Test keys. These instances of classes that do and don't override `==` and
+// `hashCode`, and a variety of primitive values since we generally don't know
+// whether they have overrides.
+final keys = [
+ 123,
+ 3.14,
+ 10.0, // int or double depending on platform.
+ 'aString',
+ #someSymbol,
+ true,
+ false,
+ null,
+ AnEnum.element1,
+ AnEnum.element2,
+ DateTime.now(),
+ Dumb(1),
+ Dumb(2),
+ Dumb(3),
+ Object(),
+ const [1],
+ const {1},
+ const {'x'},
+ // const maps have different implementation types depending on keys.
+ const {'x': 1},
+ const {123: 1},
+ const {AnEnum.element1: 1, #foo: 2},
+];
+
+void testMap(Map<Object?, Object?> map) {
+ for (final key in keys) {
+ bool wasAbsent = false;
+ map.putIfAbsent(key, () {
+ wasAbsent = true;
+ Expect.isFalse(map.containsKey(key),
+ 'containsKey should be false in putIfAbsent. key = $key');
+ return key;
+ });
+ Expect.isTrue(wasAbsent);
+ Expect.isTrue(map.containsKey(key), 'Key was not added. key = $key');
+ }
+}
+
+void main() {
+ originalError();
+
+ testMap({});
+ testMap(Map()); // Should be same as `{}`.
+ testMap(HashMap());
+ testMap(LinkedHashMap()); // Should be same as `{}`.
+
+ testMap(Map.identity());
+ testMap(HashMap.identity());
+ testMap(LinkedHashMap.identity()); // Should be same as `Map.identity()`.
+
+ // Custom maps:
+ testMap(HashMap(
+ equals: (k1, k2) => k2 == k1,
+ hashCode: (key) => key.hashCode + 1,
+ isValidKey: (_) => true));
+ testMap(LinkedHashMap(
+ equals: (k1, k2) => k2 == k1,
+ hashCode: (key) => key.hashCode + 1,
+ isValidKey: (_) => true));
+}