diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/debugger.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/debugger.dart index 55b43dfb056..eb97c550b02 100644 --- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/debugger.dart +++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/debugger.dart @@ -304,7 +304,7 @@ Object getObjectMetadata(@notNull Object object) { } else if (object is Function) { _set(result, 'runtimeKind', RuntimeObjectKind.function); } else if (object is RecordImpl) { - var shape = object.shape; + var shape = JS('!', '#[#]', object, shapeProperty); var positionalCount = shape.positionals; var namedCount = shape.named?.length ?? 0; var length = positionalCount + namedCount; @@ -452,10 +452,10 @@ Object getTypeFields(@notNull Type type) { /// ``` @notNull Object getRecordFields(@notNull RecordImpl record) { - var shape = record.shape; + var shape = JS('!', '#[#]', record, shapeProperty); var positionalCount = shape.positionals; var named = shape.named?.toList(); - var values = record.values; + var values = JS('!', '#[#]', record, valuesProperty); return _createJsObject({ 'positionalCount': positionalCount, diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/records.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/records.dart index ced6ac55c8e..f03fa9816cb 100644 --- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/records.dart +++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/records.dart @@ -20,16 +20,15 @@ final class Shape { } } +/// Used to store a [Shape] on an instance of a record object. +final shapeProperty = JS('', 'Symbol("shape")'); + +/// Used to store a [JSArray] on an instance of a record object containing all +/// the elements in the record in order. +final valuesProperty = JS('', 'Symbol("values")'); + /// Internal base class for all concrete records. final class RecordImpl implements Record { - final Shape shape; - - /// Stores the elements of this record. - /// - /// Contains all positional elements followed by all named elements in the - /// order corresponding to names as they appear in [shape]. - final List values; - /// Cache for faster access after the first call of [hashCode]. int? _hashCode; @@ -38,16 +37,17 @@ final class RecordImpl implements Record { /// NOTE: Does not contain the cached result of the "safe" [_toString] call. String? _printed; - RecordImpl(this.shape, this.values) { - var valueCount = JS('!', '#.length', values); + RecordImpl(Shape shape, JSArray values) { // Coerce all undefined values to null because dynamic gets of record // elements rely on the getter returning undefined to signal that the getter // does not exist. - for (int i = 0; i < valueCount; i++) { + for (int i = 0; i < values.length; i++) { if (JS('!', '#[#] === void 0', values, i)) { JS('', '#[#] = null', values, i); } } + JS('!', '#[#] = #', this, shapeProperty, shape); + JS('!', '#[#] = #', this, valuesProperty, values); } @override @@ -55,12 +55,16 @@ final class RecordImpl implements Record { if (!(other is RecordImpl)) return false; // Shapes are canonicalized and stored in a map so there will only ever be // one instance of the same shape. - if (JS('!', '# !== #', shape, other.shape)) return false; + if (JS( + '!', '#[#] !== #[#]', this, shapeProperty, other, shapeProperty)) + return false; // If the shapes are identical then the two records have the same number of // positional elements and the same named elements. // This implies: `values.length == other.values.length`. + var values = JS('!', '#[#]', this, valuesProperty); + var otherValues = JS('!', '#[#]', other, valuesProperty); for (var i = 0; i < values.length; i++) { - if (values[i] != other.values[i]) { + if (values[i] != otherValues[i]) { return false; } } @@ -71,6 +75,8 @@ final class RecordImpl implements Record { int get hashCode { final cachedValue = _hashCode; if (cachedValue != null) return cachedValue; + var shape = JS('!', '#[#]', this, shapeProperty); + var values = JS('!', '#[#]', this, valuesProperty); return _hashCode = Object.hashAll([shape, ...values]); } @@ -85,6 +91,8 @@ final class RecordImpl implements Record { final cachedValue = _printed; if (!safe && cachedValue != null) return cachedValue; var buffer = StringBuffer(); + var shape = JS('!', '#[#]', this, shapeProperty); + var values = JS('!', '#[#]', this, valuesProperty); var posCount = shape.positionals; var count = values.length; @@ -173,8 +181,8 @@ Object registerRecord( JS('!', '#.prototype = #.prototype', newRecord, recordClass); var recordPrototype = JS('', '#.prototype', recordClass); - _recordGet(@notNull int index) => - JS('!', 'function recordGet() {return this.values[#];}', index); + _recordGet(@notNull int index) => JS( + '!', 'function recordGet() {return this[#][#];}', valuesProperty, index); // Add convenience getters for accessing the record's field values. var count = 0; @@ -185,7 +193,12 @@ Object registerRecord( count++; } if (named != null) { - for (final name in named) { + for (var name in named) { + if (name == 'constructor' || name == 'prototype') { + // This renaming is directly coupled to the renaming logic at compile + // time in js_names.dart `.memberNameForDartMember()`. + name = '_$name'; + } defineAccessor(recordPrototype, name, get: _recordGet(count), enumerable: true); count++; diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart index d289c1678aa..59036fb4826 100644 --- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart +++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart @@ -96,7 +96,7 @@ getFunctionType(obj) { RecordType getRecordType(RecordImpl obj) { var type = JS('', '#[#]', obj, _runtimeType); if (type == null) { - var shape = obj.shape; + var shape = JS('!', '#[#]', obj, shapeProperty); var named = shape.named; var positionals = shape.positionals; var types = []; diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart index d5a028c47dd..2ef0fd35f92 100644 --- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart +++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart @@ -2376,12 +2376,13 @@ class RecordType extends DartType { @JSExportName('is') bool is_T(obj) { if (!(obj is RecordImpl)) return false; - if (shape != obj.shape) return false; - if (types.length != obj.values.length) { + if (shape != JS('!', '#[#]', obj, shapeProperty)) return false; + var values = JS('!', '#[#]', obj, valuesProperty); + if (types.length != values.length) { return false; } for (var i = 0; i < types.length; i++) { - if (!JS('!', '#.is(#)', types[i], obj.values[i])) { + if (!JS('!', '#.is(#)', types[i], values[i])) { return false; } } diff --git a/sdk/lib/_internal/js_dev_runtime/private/js_helper.dart b/sdk/lib/_internal/js_dev_runtime/private/js_helper.dart index 29bdda0ff13..99cc31352f5 100644 --- a/sdk/lib/_internal/js_dev_runtime/private/js_helper.dart +++ b/sdk/lib/_internal/js_dev_runtime/private/js_helper.dart @@ -882,9 +882,10 @@ Object? createRecordTypePredicate(String partialShapeTag, JSArray fieldRtis) { return (obj) { return JS( '!', '# instanceof #', obj, JS_CLASS_REF(dart.RecordImpl)) && - JS('!', '#', obj).shape == + JS('!', '#[#]', obj, dart.shapeProperty) == JS('', '#.get(#)', dart.shapes, shapeKey) && - rti.pairwiseIsTest(fieldRtis, JS('!', '#.values', obj)); + rti.pairwiseIsTest( + fieldRtis, JS('!', '#[#]', obj, dart.valuesProperty)); }; } else { dart.throwUnimplementedInCurrentRti(); @@ -902,14 +903,16 @@ rti.Rti getRtiForRecord(Object? record) { if (JS_GET_FLAG('NEW_RUNTIME_TYPES')) { var recordObj = JS('!', '#', record); var recipeBuffer = StringBuffer('+'); - var named = recordObj.shape.named; + var shape = JS('!', '#[#]', recordObj, dart.shapeProperty); + var values = JS('!', '#[#]', recordObj, dart.valuesProperty); + var named = shape.named; if (named != null) recipeBuffer.writeAll(named, ','); recipeBuffer.write('('); - var elementCount = recordObj.values.length; + var elementCount = values.length; recipeBuffer.writeAll([for (var i = 1; i <= elementCount; i++) i], ','); recipeBuffer.write(')'); - return rti.evaluateRtiForRecord(recipeBuffer.toString(), recordObj.values); + return rti.evaluateRtiForRecord(recipeBuffer.toString(), values); } else { dart.throwUnimplementedInCurrentRti(); } diff --git a/tests/language/records/regress54375_test.dart b/tests/language/records/regress54375_test.dart new file mode 100644 index 00000000000..defea07c194 --- /dev/null +++ b/tests/language/records/regress54375_test.dart @@ -0,0 +1,38 @@ +// 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. + +/// Regression test for https://github.com/dart-lang/sdk/issues/54375. +/// +/// Records should support named elements with the names 'shape', 'values', +/// 'constructor', and 'prototype'. + +import 'package:expect/expect.dart'; + +@pragma('dart2js:noInline') +@pragma('dart2js:assumeDynamic') +confuse(x) { + return x; +} + +void main() { + var r = (shape: 123); + Expect.equals(123, r.shape); + var d = confuse(r); + Expect.equals(123, d.shape); + + var r2 = (values: 'hello'); + Expect.equals('hello', r2.values); + d = confuse(r2); + Expect.equals('hello', d.values); + + var r3 = (constructor: Duration.zero); + Expect.equals(Duration.zero, r3.constructor); + d = confuse(r3); + Expect.equals(Duration.zero, d.constructor); + + var r4 = (prototype: null); + Expect.equals(null, r4.prototype); + d = confuse(r4); + Expect.equals(null, d.prototype); +}