[js_runtime] Use custom hashCode for GeneralConstantMap

Fixes #46580

Change-Id: Ida2b7df75415881973085f9afeacd9ee384fd910
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/207160
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Mayank Patke <fishythefish@google.com>
This commit is contained in:
Stephen Adams 2021-07-16 22:58:02 +00:00 committed by commit-bot@chromium.org
parent a8f2262c10
commit cfc8ad4e7f
3 changed files with 98 additions and 1 deletions

View file

@ -173,13 +173,35 @@ class GeneralConstantMap<K, V> extends ConstantMap<K, V> {
Map<K, V> _getMap() {
LinkedHashMap<K, V>? backingMap = JS('LinkedHashMap|Null', r'#.$map', this);
if (backingMap == null) {
backingMap = JsLinkedHashMap<K, V>();
backingMap = LinkedHashMap<K, V>(
hashCode: _constantMapHashCode,
// In legacy mode (--no-sound-null-safety), `null` keys are
// permitted. In sound mode, `null` keys are permitted only if [K] is
// nullable.
isValidKey: JS_GET_FLAG('LEGACY') ? _typeTest<K?>() : _typeTest<K>());
fillLiteralMap(_jsData, backingMap);
JS('', r'#.$map = #', this, backingMap);
}
return backingMap;
}
static int _constantMapHashCode(Object? key) {
// Types are tested here one-by-one so that each call to get:hashCode can be
// resolved differently.
// Some common primitives in a GeneralConstantMap.
if (key is num) return key.hashCode; // One method on JSNumber.
// Specially handled known types.
if (key is Symbol) return key.hashCode;
if (key is Type) return key.hashCode;
// Everything else, including less common primitives.
return identityHashCode(key);
}
static bool Function(Object?) _typeTest<T>() => (Object? o) => o is T;
bool containsValue(Object? needle) {
return _getMap().containsValue(needle);
}

View file

@ -0,0 +1,21 @@
// Copyright (c) 2021, 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.
// Test the use of `null` keys in const maps.
library map_literal15_test;
import "package:expect/expect.dart";
void main() {
var m1 = const <String, int>{null: 10, 'null': 20};
// ^^^^
// [analyzer] COMPILE_TIME_ERROR.MAP_KEY_TYPE_NOT_ASSIGNABLE
// [cfe] The value 'null' can't be assigned to a variable of type 'String' because 'String' is not nullable.
var m2 = const <Comparable, int>{null: 10, 'null': 20};
// ^^^^
// [analyzer] COMPILE_TIME_ERROR.MAP_KEY_TYPE_NOT_ASSIGNABLE
// [cfe] The value 'null' can't be assigned to a variable of type 'Comparable<dynamic>' because 'Comparable<dynamic>' is not nullable.
}

View file

@ -0,0 +1,54 @@
// Copyright (c) 2021, 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.
// @dart = 2.9
// Test the use of `null` keys in const maps. In versions before 2.12, when
// nullable types were introduced, types were nullable so it was legal to have
// `null` keys in maps.
library map_literal15_test;
import "package:expect/expect.dart";
void test1() {
var m1 = const <String, int>{null: 10, 'null': 20};
Expect.isTrue(m1.containsKey(null));
Expect.isTrue(m1.containsKey(undefined()));
Expect.equals(10, m1[null]);
Expect.equals(10, m1[undefined()]);
Expect.isTrue(m1.containsKey('null'));
Expect.equals(20, m1['null']);
// The '.keys' carry the 'String' type
Expect.type<Iterable<String>>(m1.keys);
Expect.type<Iterable<Comparable>>(m1.keys);
Expect.notType<Iterable<int>>(m1.keys);
}
void test2() {
var m2 = const <Comparable, int>{null: 10, 'null': 20};
Expect.isTrue(m2.containsKey(null));
Expect.isTrue(m2.containsKey(undefined()));
Expect.equals(10, m2[null]);
Expect.equals(10, m2[undefined()]);
Expect.isTrue(m2.containsKey('null'));
Expect.equals(20, m2['null']);
// The '.keys' carry the 'Comparable' type
Expect.notType<Iterable<String>>(m2.keys);
Expect.type<Iterable<Comparable>>(m2.keys);
Expect.notType<Iterable<int>>(m2.keys);
}
main() {
test1();
test2();
}
// Calling `undefined()` gives us a `null` that is implemented as JavaScript
// `undefined` on dart2js.
@pragma('dart2js:noInline')
dynamic get undefined => _undefined;
@pragma('dart2js:noInline')
void _undefined() {}