| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of _interceptors; | |
| 6 | |
| 7 /** | |
| 8 * The interceptor class for [List]. The compiler recognizes this | |
| 9 * class as an interceptor, and changes references to [:this:] to | |
| 10 * actually use the receiver of the method, which is generated as an extra | |
| 11 * argument added to each member. | |
| 12 */ | |
| 13 class JSArray<E> extends Interceptor implements List<E>, JSIndexable { | |
| 14 | |
| 15 const JSArray(); | |
| 16 | |
| 17 /** | |
| 18 * Returns a fresh JavaScript Array, marked as fixed-length. | |
| 19 * | |
| 20 * [length] must be a non-negative integer. | |
| 21 */ | |
| 22 factory JSArray.fixed(int length) { | |
| 23 // Explicit type test is necessary to guard against JavaScript conversions | |
| 24 // in unchecked mode. | |
| 25 if ((length is !int) || (length < 0)) { | |
| 26 throw new ArgumentError("Length must be a non-negative integer: $length"); | |
| 27 } | |
| 28 return new JSArray<E>.markFixed(JS('', 'new Array(#)', length)); | |
| 29 } | |
| 30 | |
| 31 /** | |
| 32 * Returns a fresh growable JavaScript Array of zero length length. | |
| 33 */ | |
| 34 factory JSArray.emptyGrowable() => new JSArray<E>.markGrowable(JS('', '[]')); | |
| 35 | |
| 36 /** | |
| 37 * Returns a fresh growable JavaScript Array with initial length. | |
| 38 * | |
| 39 * [validatedLength] must be a non-negative integer. | |
| 40 */ | |
| 41 factory JSArray.growable(int length) { | |
| 42 // Explicit type test is necessary to guard against JavaScript conversions | |
| 43 // in unchecked mode. | |
| 44 if ((length is !int) || (length < 0)) { | |
| 45 throw new ArgumentError("Length must be a non-negative integer: $length"); | |
| 46 } | |
| 47 return new JSArray<E>.markGrowable(JS('', 'new Array(#)', length)); | |
| 48 } | |
| 49 | |
| 50 /** | |
| 51 * Constructor for adding type parameters to an existing JavaScript Array. | |
| 52 * The compiler specially recognizes this constructor. | |
| 53 * | |
| 54 * var a = new JSArray<int>.typed(JS('JSExtendableArray', '[]')); | |
| 55 * a is List<int> --> true | |
| 56 * a is List<String> --> false | |
| 57 * | |
| 58 * Usually either the [JSArray.markFixed] or [JSArray.markGrowable] | |
| 59 * constructors is used instead. | |
| 60 * | |
| 61 * The input must be a JavaScript Array. The JS form is just a re-assertion | |
| 62 * to help type analysis when the input type is sloppy. | |
| 63 */ | |
| 64 factory JSArray.typed(allocation) => JS('JSArray', '#', allocation); | |
| 65 | |
| 66 factory JSArray.markFixed(allocation) => | |
| 67 JS('JSFixedArray', '#', markFixedList(new JSArray<E>.typed(allocation))); | |
| 68 | |
| 69 factory JSArray.markGrowable(allocation) => | |
| 70 JS('JSExtendableArray', '#', new JSArray<E>.typed(allocation)); | |
| 71 | |
| 72 static List markFixedList(List list) { | |
| 73 // Functions are stored in the hidden class and not as properties in | |
| 74 // the object. We never actually look at the value, but only want | |
| 75 // to know if the property exists. | |
| 76 JS('void', r'#.fixed$length = Array', list); | |
| 77 return JS('JSFixedArray', '#', list); | |
| 78 } | |
| 79 | |
| 80 static List markUnmodifiableList(List list) { | |
| 81 // Functions are stored in the hidden class and not as properties in | |
| 82 // the object. We never actually look at the value, but only want | |
| 83 // to know if the property exists. | |
| 84 JS('void', r'#.fixed$length = Array', list); | |
| 85 JS('void', r'#.immutable$list = Array', list); | |
| 86 return JS('JSUnmodifiableArray', '#', list); | |
| 87 } | |
| 88 | |
| 89 checkMutable(reason) { | |
| 90 if (this is !JSMutableArray) { | |
| 91 throw new UnsupportedError(reason); | |
| 92 } | |
| 93 } | |
| 94 | |
| 95 checkGrowable(reason) { | |
| 96 if (this is !JSExtendableArray) { | |
| 97 throw new UnsupportedError(reason); | |
| 98 } | |
| 99 } | |
| 100 | |
| 101 void add(E value) { | |
| 102 checkGrowable('add'); | |
| 103 JS('void', r'#.push(#)', this, value); | |
| 104 } | |
| 105 | |
| 106 E removeAt(int index) { | |
| 107 checkGrowable('removeAt'); | |
| 108 if (index is !int) throw argumentErrorValue(index); | |
| 109 if (index < 0 || index >= length) { | |
| 110 throw new RangeError.value(index); | |
| 111 } | |
| 112 return JS('var', r'#.splice(#, 1)[0]', this, index); | |
| 113 } | |
| 114 | |
| 115 void insert(int index, E value) { | |
| 116 checkGrowable('insert'); | |
| 117 if (index is !int) throw argumentErrorValue(index); | |
| 118 if (index < 0 || index > length) { | |
| 119 throw new RangeError.value(index); | |
| 120 } | |
| 121 JS('void', r'#.splice(#, 0, #)', this, index, value); | |
| 122 } | |
| 123 | |
| 124 void insertAll(int index, Iterable<E> iterable) { | |
| 125 checkGrowable('insertAll'); | |
| 126 RangeError.checkValueInInterval(index, 0, this.length, "index"); | |
| 127 if (iterable is! EfficientLength) { | |
| 128 iterable = iterable.toList(); | |
| 129 } | |
| 130 int insertionLength = iterable.length; | |
| 131 this.length += insertionLength; | |
| 132 int end = index + insertionLength; | |
| 133 this.setRange(end, this.length, this, index); | |
| 134 this.setRange(index, end, iterable); | |
| 135 } | |
| 136 | |
| 137 void setAll(int index, Iterable<E> iterable) { | |
| 138 checkMutable('setAll'); | |
| 139 RangeError.checkValueInInterval(index, 0, this.length, "index"); | |
| 140 for (var element in iterable) { | |
| 141 this[index++] = element; | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 E removeLast() { | |
| 146 checkGrowable('removeLast'); | |
| 147 if (length == 0) throw new RangeError.value(-1); | |
| 148 return JS('var', r'#.pop()', this); | |
| 149 } | |
| 150 | |
| 151 bool remove(Object element) { | |
| 152 checkGrowable('remove'); | |
| 153 for (int i = 0; i < this.length; i++) { | |
| 154 if (this[i] == element) { | |
| 155 JS('var', r'#.splice(#, 1)', this, i); | |
| 156 return true; | |
| 157 } | |
| 158 } | |
| 159 return false; | |
| 160 } | |
| 161 | |
| 162 /** | |
| 163 * Removes elements matching [test] from [this] List. | |
| 164 */ | |
| 165 void removeWhere(bool test(E element)) { | |
| 166 checkGrowable('removeWhere'); | |
| 167 _removeWhere(test, true); | |
| 168 } | |
| 169 | |
| 170 void retainWhere(bool test(E element)) { | |
| 171 checkGrowable('retainWhere'); | |
| 172 _removeWhere(test, false); | |
| 173 } | |
| 174 | |
| 175 void _removeWhere(bool test(E element), bool removeMatching) { | |
| 176 // Performed in two steps, to avoid exposing an inconsistent state | |
| 177 // to the [test] function. First the elements to retain are found, and then | |
| 178 // the original list is updated to contain those elements. | |
| 179 | |
| 180 // TODO(sra): Replace this algorthim with one that retains a list of ranges | |
| 181 // to be removed. Most real uses remove 0, 1 or a few clustered elements. | |
| 182 | |
| 183 List retained = []; | |
| 184 int end = this.length; | |
| 185 for (int i = 0; i < end; i++) { | |
| 186 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
| 187 // be replaced by indexing. | |
| 188 var element = JS('', '#[#]', this, i); | |
| 189 // !test() ensures bool conversion in checked mode. | |
| 190 if (!test(element) == removeMatching) { | |
| 191 retained.add(element); | |
| 192 } | |
| 193 if (this.length != end) throw new ConcurrentModificationError(this); | |
| 194 } | |
| 195 if (retained.length == end) return; | |
| 196 this.length = retained.length; | |
| 197 for (int i = 0; i < retained.length; i++) { | |
| 198 this[i] = retained[i]; | |
| 199 } | |
| 200 } | |
| 201 | |
| 202 Iterable<E> where(bool f(E element)) { | |
| 203 return new WhereIterable<E>(this, f); | |
| 204 } | |
| 205 | |
| 206 Iterable expand(Iterable f(E element)) { | |
| 207 return new ExpandIterable<E, dynamic>(this, f); | |
| 208 } | |
| 209 | |
| 210 void addAll(Iterable<E> collection) { | |
| 211 checkGrowable('addAll'); | |
| 212 for (E e in collection) { | |
| 213 JS('void', r'#.push(#)', this, e); | |
| 214 } | |
| 215 } | |
| 216 | |
| 217 void clear() { | |
| 218 length = 0; | |
| 219 } | |
| 220 | |
| 221 void forEach(void f(E element)) { | |
| 222 int end = this.length; | |
| 223 for (int i = 0; i < end; i++) { | |
| 224 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
| 225 // be replaced by indexing. | |
| 226 var element = JS('', '#[#]', this, i); | |
| 227 f(element); | |
| 228 if (this.length != end) throw new ConcurrentModificationError(this); | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 Iterable map(f(E element)) { | |
| 233 return new MappedListIterable(this, f); | |
| 234 } | |
| 235 | |
| 236 String join([String separator = ""]) { | |
| 237 var list = new List(this.length); | |
| 238 for (int i = 0; i < this.length; i++) { | |
| 239 list[i] = "${this[i]}"; | |
| 240 } | |
| 241 return JS('String', "#.join(#)", list, separator); | |
| 242 } | |
| 243 | |
| 244 Iterable<E> take(int n) { | |
| 245 return new SubListIterable<E>(this, 0, n); | |
| 246 } | |
| 247 | |
| 248 Iterable<E> takeWhile(bool test(E value)) { | |
| 249 return new TakeWhileIterable<E>(this, test); | |
| 250 } | |
| 251 | |
| 252 Iterable<E> skip(int n) { | |
| 253 return new SubListIterable<E>(this, n, null); | |
| 254 } | |
| 255 | |
| 256 Iterable<E> skipWhile(bool test(E value)) { | |
| 257 return new SkipWhileIterable<E>(this, test); | |
| 258 } | |
| 259 | |
| 260 E reduce(E combine(E previousValue, E element)) { | |
| 261 int length = this.length; | |
| 262 if (length == 0) throw IterableElementError.noElement(); | |
| 263 E value = this[0]; | |
| 264 for (int i = 1; i < length; i++) { | |
| 265 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
| 266 // be replaced by indexing. | |
| 267 var element = JS('', '#[#]', this, i); | |
| 268 value = combine(value, element); | |
| 269 if (length != this.length) throw new ConcurrentModificationError(this); | |
| 270 } | |
| 271 return value; | |
| 272 } | |
| 273 | |
| 274 fold(var initialValue, combine(var previousValue, E element)) { | |
| 275 var value = initialValue; | |
| 276 int length = this.length; | |
| 277 for (int i = 0; i < length; i++) { | |
| 278 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
| 279 // be replaced by indexing. | |
| 280 var element = JS('', '#[#]', this, i); | |
| 281 value = combine(value, element); | |
| 282 if (this.length != length) throw new ConcurrentModificationError(this); | |
| 283 } | |
| 284 return value; | |
| 285 } | |
| 286 | |
| 287 E firstWhere(bool test(E value), {E orElse()}) { | |
| 288 var end = this.length; | |
| 289 for (int i = 0; i < end; ++i) { | |
| 290 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
| 291 // be replaced by indexing. | |
| 292 var element = JS('', '#[#]', this, i); | |
| 293 if (test(element)) return element; | |
| 294 if (this.length != end) throw new ConcurrentModificationError(this); | |
| 295 } | |
| 296 if (orElse != null) return orElse(); | |
| 297 throw IterableElementError.noElement(); | |
| 298 } | |
| 299 | |
| 300 E lastWhere(bool test(E element), { E orElse() }) { | |
| 301 int length = this.length; | |
| 302 for (int i = length - 1; i >= 0; i--) { | |
| 303 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
| 304 // be replaced by indexing. | |
| 305 var element = JS('', '#[#]', this, i); | |
| 306 if (test(element)) return element; | |
| 307 if (length != this.length) { | |
| 308 throw new ConcurrentModificationError(this); | |
| 309 } | |
| 310 } | |
| 311 if (orElse != null) return orElse(); | |
| 312 throw IterableElementError.noElement(); | |
| 313 } | |
| 314 | |
| 315 E singleWhere(bool test(E element)) { | |
| 316 int length = this.length; | |
| 317 E match = null; | |
| 318 bool matchFound = false; | |
| 319 for (int i = 0; i < length; i++) { | |
| 320 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
| 321 // be replaced by indexing. | |
| 322 var element = JS('', '#[#]', this, i); | |
| 323 if (test(element)) { | |
| 324 if (matchFound) { | |
| 325 throw IterableElementError.tooMany(); | |
| 326 } | |
| 327 matchFound = true; | |
| 328 match = element; | |
| 329 } | |
| 330 if (length != this.length) { | |
| 331 throw new ConcurrentModificationError(this); | |
| 332 } | |
| 333 } | |
| 334 if (matchFound) return match; | |
| 335 throw IterableElementError.noElement(); | |
| 336 } | |
| 337 | |
| 338 E elementAt(int index) { | |
| 339 return this[index]; | |
| 340 } | |
| 341 | |
| 342 List<E> sublist(int start, [int end]) { | |
| 343 checkNull(start); // TODO(ahe): This is not specified but co19 tests it. | |
| 344 if (start is !int) throw argumentErrorValue(start); | |
| 345 if (start < 0 || start > length) { | |
| 346 throw new RangeError.range(start, 0, length); | |
| 347 } | |
| 348 if (end == null) { | |
| 349 end = length; | |
| 350 } else { | |
| 351 if (end is !int) throw argumentErrorValue(end); | |
| 352 if (end < start || end > length) { | |
| 353 throw new RangeError.range(end, start, length); | |
| 354 } | |
| 355 } | |
| 356 if (start == end) return <E>[]; | |
| 357 return new JSArray<E>.markGrowable( | |
| 358 JS('', r'#.slice(#, #)', this, start, end)); | |
| 359 } | |
| 360 | |
| 361 | |
| 362 Iterable<E> getRange(int start, int end) { | |
| 363 RangeError.checkValidRange(start, end, this.length); | |
| 364 return new SubListIterable<E>(this, start, end); | |
| 365 } | |
| 366 | |
| 367 E get first { | |
| 368 if (length > 0) return this[0]; | |
| 369 throw IterableElementError.noElement(); | |
| 370 } | |
| 371 | |
| 372 E get last { | |
| 373 if (length > 0) return this[length - 1]; | |
| 374 throw IterableElementError.noElement(); | |
| 375 } | |
| 376 | |
| 377 E get single { | |
| 378 if (length == 1) return this[0]; | |
| 379 if (length == 0) throw IterableElementError.noElement(); | |
| 380 throw IterableElementError.tooMany(); | |
| 381 } | |
| 382 | |
| 383 void removeRange(int start, int end) { | |
| 384 checkGrowable('removeRange'); | |
| 385 RangeError.checkValidRange(start, end, this.length); | |
| 386 int deleteCount = end - start; | |
| 387 JS('', '#.splice(#, #)', this, start, deleteCount); | |
| 388 } | |
| 389 | |
| 390 void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) { | |
| 391 checkMutable('set range'); | |
| 392 | |
| 393 RangeError.checkValidRange(start, end, this.length); | |
| 394 int length = end - start; | |
| 395 if (length == 0) return; | |
| 396 RangeError.checkNotNegative(skipCount, "skipCount"); | |
| 397 | |
| 398 List otherList; | |
| 399 int otherStart; | |
| 400 // TODO(floitsch): Make this accept more. | |
| 401 if (iterable is List) { | |
| 402 otherList = iterable; | |
| 403 otherStart = skipCount; | |
| 404 } else { | |
| 405 otherList = iterable.skip(skipCount).toList(growable: false); | |
| 406 otherStart = 0; | |
| 407 } | |
| 408 if (otherStart + length > otherList.length) { | |
| 409 throw IterableElementError.tooFew(); | |
| 410 } | |
| 411 if (otherStart < start) { | |
| 412 // Copy backwards to ensure correct copy if [from] is this. | |
| 413 // TODO(sra): If [from] is the same Array as [this], we can copy without | |
| 414 // type annotation checks on the stores. | |
| 415 for (int i = length - 1; i >= 0; i--) { | |
| 416 // Use JS to avoid bounds check (the bounds check elimination | |
| 417 // optimzation is too weak). The 'E' type annotation is a store type | |
| 418 // check - we can't rely on iterable, it could be List<dynamic>. | |
| 419 E element = otherList[otherStart + i]; | |
| 420 JS('', '#[#] = #', this, start + i, element); | |
| 421 } | |
| 422 } else { | |
| 423 for (int i = 0; i < length; i++) { | |
| 424 E element = otherList[otherStart + i]; | |
| 425 JS('', '#[#] = #', this, start + i, element); | |
| 426 } | |
| 427 } | |
| 428 } | |
| 429 | |
| 430 void fillRange(int start, int end, [E fillValue]) { | |
| 431 checkMutable('fill range'); | |
| 432 RangeError.checkValidRange(start, end, this.length); | |
| 433 for (int i = start; i < end; i++) { | |
| 434 // Store is safe since [fillValue] type has been checked as parameter. | |
| 435 JS('', '#[#] = #', this, i, fillValue); | |
| 436 } | |
| 437 } | |
| 438 | |
| 439 void replaceRange(int start, int end, Iterable<E> replacement) { | |
| 440 checkGrowable('replace range'); | |
| 441 RangeError.checkValidRange(start, end, this.length); | |
| 442 if (replacement is! EfficientLength) { | |
| 443 replacement = replacement.toList(); | |
| 444 } | |
| 445 int removeLength = end - start; | |
| 446 int insertLength = replacement.length; | |
| 447 if (removeLength >= insertLength) { | |
| 448 int delta = removeLength - insertLength; | |
| 449 int insertEnd = start + insertLength; | |
| 450 int newLength = this.length - delta; | |
| 451 this.setRange(start, insertEnd, replacement); | |
| 452 if (delta != 0) { | |
| 453 this.setRange(insertEnd, newLength, this, end); | |
| 454 this.length = newLength; | |
| 455 } | |
| 456 } else { | |
| 457 int delta = insertLength - removeLength; | |
| 458 int newLength = this.length + delta; | |
| 459 int insertEnd = start + insertLength; // aka. end + delta. | |
| 460 this.length = newLength; | |
| 461 this.setRange(insertEnd, newLength, this, end); | |
| 462 this.setRange(start, insertEnd, replacement); | |
| 463 } | |
| 464 } | |
| 465 | |
| 466 bool any(bool test(E element)) { | |
| 467 int end = this.length; | |
| 468 for (int i = 0; i < end; i++) { | |
| 469 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
| 470 // be replaced by indexing. | |
| 471 var element = JS('', '#[#]', this, i); | |
| 472 if (test(element)) return true; | |
| 473 if (this.length != end) throw new ConcurrentModificationError(this); | |
| 474 } | |
| 475 return false; | |
| 476 } | |
| 477 | |
| 478 bool every(bool test(E element)) { | |
| 479 int end = this.length; | |
| 480 for (int i = 0; i < end; i++) { | |
| 481 // TODO(22407): Improve bounds check elimination to allow this JS code to | |
| 482 // be replaced by indexing. | |
| 483 var element = JS('', '#[#]', this, i); | |
| 484 if (!test(element)) return false; | |
| 485 if (this.length != end) throw new ConcurrentModificationError(this); | |
| 486 } | |
| 487 return true; | |
| 488 } | |
| 489 | |
| 490 Iterable<E> get reversed => new ReversedListIterable<E>(this); | |
| 491 | |
| 492 void sort([int compare(E a, E b)]) { | |
| 493 checkMutable('sort'); | |
| 494 Sort.sort(this, compare == null ? Comparable.compare : compare); | |
| 495 } | |
| 496 | |
| 497 void shuffle([Random random]) { | |
| 498 checkMutable('shuffle'); | |
| 499 if (random == null) random = new Random(); | |
| 500 int length = this.length; | |
| 501 while (length > 1) { | |
| 502 int pos = random.nextInt(length); | |
| 503 length -= 1; | |
| 504 var tmp = this[length]; | |
| 505 this[length] = this[pos]; | |
| 506 this[pos] = tmp; | |
| 507 } | |
| 508 } | |
| 509 | |
| 510 int indexOf(Object element, [int start = 0]) { | |
| 511 if (start >= this.length) { | |
| 512 return -1; | |
| 513 } | |
| 514 if (start < 0) { | |
| 515 start = 0; | |
| 516 } | |
| 517 for (int i = start; i < this.length; i++) { | |
| 518 if (this[i] == element) { | |
| 519 return i; | |
| 520 } | |
| 521 } | |
| 522 return -1; | |
| 523 } | |
| 524 | |
| 525 int lastIndexOf(Object element, [int startIndex]) { | |
| 526 if (startIndex == null) { | |
| 527 startIndex = this.length - 1; | |
| 528 } else { | |
| 529 if (startIndex < 0) { | |
| 530 return -1; | |
| 531 } | |
| 532 if (startIndex >= this.length) { | |
| 533 startIndex = this.length - 1; | |
| 534 } | |
| 535 } | |
| 536 for (int i = startIndex; i >= 0; i--) { | |
| 537 if (this[i] == element) { | |
| 538 return i; | |
| 539 } | |
| 540 } | |
| 541 return -1; | |
| 542 } | |
| 543 | |
| 544 bool contains(Object other) { | |
| 545 for (int i = 0; i < length; i++) { | |
| 546 if (this[i] == other) return true; | |
| 547 } | |
| 548 return false; | |
| 549 } | |
| 550 | |
| 551 bool get isEmpty => length == 0; | |
| 552 | |
| 553 bool get isNotEmpty => !isEmpty; | |
| 554 | |
| 555 String toString() => ListBase.listToString(this); | |
| 556 | |
| 557 List<E> toList({ bool growable: true }) => | |
| 558 growable | |
| 559 ? _toListGrowable() | |
| 560 : _toListFixed(); | |
| 561 | |
| 562 List<E> _toListGrowable() => | |
| 563 new JSArray<E>.markGrowable(JS('', '#.slice()', this)); | |
| 564 | |
| 565 List<E> _toListFixed() => | |
| 566 new JSArray<E>.markFixed(JS('', '#.slice()', this)); | |
| 567 | |
| 568 Set<E> toSet() => new Set<E>.from(this); | |
| 569 | |
| 570 Iterator<E> get iterator => new ArrayIterator<E>(this); | |
| 571 | |
| 572 int get hashCode => Primitives.objectHashCode(this); | |
| 573 | |
| 574 int get length => JS('JSUInt32', r'#.length', this); | |
| 575 | |
| 576 void set length(int newLength) { | |
| 577 checkGrowable('set length'); | |
| 578 if (newLength is !int) { | |
| 579 throw new ArgumentError.value(newLength, 'newLength'); | |
| 580 } | |
| 581 // TODO(sra): Remove this test and let JavaScript throw an error. | |
| 582 if (newLength < 0) { | |
| 583 throw new RangeError.range(newLength, 0, null, 'newLength'); | |
| 584 } | |
| 585 // JavaScript with throw a RangeError for numbers that are too big. The | |
| 586 // message does not contain the value. | |
| 587 JS('void', r'#.length = #', this, newLength); | |
| 588 } | |
| 589 | |
| 590 E operator [](int index) { | |
| 591 if (index is !int) throw diagnoseIndexError(this, index); | |
| 592 if (index >= length || index < 0) throw diagnoseIndexError(this, index); | |
| 593 return JS('var', '#[#]', this, index); | |
| 594 } | |
| 595 | |
| 596 void operator []=(int index, E value) { | |
| 597 checkMutable('indexed set'); | |
| 598 if (index is !int) throw diagnoseIndexError(this, index); | |
| 599 if (index >= length || index < 0) throw diagnoseIndexError(this, index); | |
| 600 JS('void', r'#[#] = #', this, index, value); | |
| 601 } | |
| 602 | |
| 603 Map<int, E> asMap() { | |
| 604 return new ListMapView<E>(this); | |
| 605 } | |
| 606 } | |
| 607 | |
| 608 /** | |
| 609 * Dummy subclasses that allow the backend to track more precise | |
| 610 * information about arrays through their type. The CPA type inference | |
| 611 * relies on the fact that these classes do not override [] nor []=. | |
| 612 * | |
| 613 * These classes are really a fiction, and can have no methods, since | |
| 614 * getInterceptor always returns JSArray. We should consider pushing the | |
| 615 * 'isGrowable' and 'isMutable' checks into the getInterceptor implementation so | |
| 616 * these classes can have specialized implementations. Doing so will challenge | |
| 617 * many assuptions in the JS backend. | |
| 618 */ | |
| 619 class JSMutableArray<E> extends JSArray<E> implements JSMutableIndexable {} | |
| 620 class JSFixedArray<E> extends JSMutableArray<E> {} | |
| 621 class JSExtendableArray<E> extends JSMutableArray<E> {} | |
| 622 class JSUnmodifiableArray<E> extends JSArray<E> {} // Already is JSIndexable. | |
| 623 | |
| 624 | |
| 625 /// An [Iterator] that iterates a JSArray. | |
| 626 /// | |
| 627 class ArrayIterator<E> implements Iterator<E> { | |
| 628 final JSArray<E> _iterable; | |
| 629 final int _length; | |
| 630 int _index; | |
| 631 E _current; | |
| 632 | |
| 633 ArrayIterator(JSArray<E> iterable) | |
| 634 : _iterable = iterable, _length = iterable.length, _index = 0; | |
| 635 | |
| 636 E get current => _current; | |
| 637 | |
| 638 bool moveNext() { | |
| 639 int length = _iterable.length; | |
| 640 | |
| 641 // We have to do the length check even on fixed length Arrays. If we can | |
| 642 // inline moveNext() we might be able to GVN the length and eliminate this | |
| 643 // check on known fixed length JSArray. | |
| 644 if (_length != length) { | |
| 645 throw new ConcurrentModificationError(_iterable); | |
| 646 } | |
| 647 | |
| 648 if (_index >= length) { | |
| 649 _current = null; | |
| 650 return false; | |
| 651 } | |
| 652 _current = _iterable[_index]; | |
| 653 _index++; | |
| 654 return true; | |
| 655 } | |
| 656 } | |
| OLD | NEW |