Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: sdk/lib/_internal/compiler/js_lib/js_helper.dart

Issue 1212513002: sdk files reorganization to make dart2js a proper package (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2013, 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 library _js_helper;
6
7 import 'dart:_async_await_error_codes' as async_error_codes;
8
9 import 'dart:_js_embedded_names' show
10 DEFERRED_LIBRARY_URIS,
11 DEFERRED_LIBRARY_HASHES,
12 GET_TYPE_FROM_NAME,
13 GET_ISOLATE_TAG,
14 INITIALIZE_LOADED_HUNK,
15 INTERCEPTED_NAMES,
16 INTERCEPTORS_BY_TAG,
17 IS_HUNK_LOADED,
18 IS_HUNK_INITIALIZED,
19 JsBuiltin,
20 JsGetName,
21 LEAF_TAGS,
22 NATIVE_SUPERCLASS_TAG_NAME;
23
24 import 'dart:collection';
25
26 import 'dart:_isolate_helper' show
27 IsolateNatives,
28 enterJsAsync,
29 isWorker,
30 leaveJsAsync;
31
32 import 'dart:async' show
33 Completer,
34 DeferredLoadException,
35 Future,
36 StreamController,
37 Stream,
38 StreamSubscription,
39 scheduleMicrotask;
40
41 import 'dart:_foreign_helper' show
42 DART_CLOSURE_TO_JS,
43 JS,
44 JS_BUILTIN,
45 JS_CALL_IN_ISOLATE,
46 JS_CONST,
47 JS_CURRENT_ISOLATE,
48 JS_CURRENT_ISOLATE_CONTEXT,
49 JS_EFFECT,
50 JS_EMBEDDED_GLOBAL,
51 JS_GET_FLAG,
52 JS_GET_NAME,
53 JS_HAS_EQUALS,
54 JS_STRING_CONCAT,
55 RAW_DART_FUNCTION_REF;
56
57 import 'dart:_interceptors';
58 import 'dart:_internal' as _symbol_dev;
59 import 'dart:_internal' show EfficientLength, MappedIterable;
60
61 import 'dart:_native_typed_data';
62
63 import 'dart:_js_names' show
64 extractKeys,
65 mangledNames,
66 unmangleGlobalNameIfPreservedAnyways,
67 unmangleAllIdentifiersIfPreservedAnyways;
68
69 part 'annotations.dart';
70 part 'constant_map.dart';
71 part 'native_helper.dart';
72 part 'regexp_helper.dart';
73 part 'string_helper.dart';
74 part 'js_rti.dart';
75 part 'linked_hash_map.dart';
76
77 /// Marks the internal map in dart2js, so that internal libraries can is-check
78 /// them.
79 abstract class InternalMap {
80 }
81
82 /// Extracts the JavaScript-constructor name from the given isCheckProperty.
83 // TODO(floitsch): move this to foreign_helper.dart or similar.
84 @ForceInline()
85 String isCheckPropertyToJsConstructorName(String isCheckProperty) {
86 return JS_BUILTIN('returns:String;depends:none;effects:none',
87 JsBuiltin.isCheckPropertyToJsConstructorName,
88 isCheckProperty);
89 }
90
91 /// Returns true if the given [type] is a function type object.
92 // TODO(floitsch): move this to foreign_helper.dart or similar.
93 @ForceInline()
94 bool isDartFunctionType(Object type) {
95 return JS_BUILTIN('returns:bool;effects:none;depends:none',
96 JsBuiltin.isFunctionType, type);
97 }
98
99
100 /// Creates a function type object.
101 // TODO(floitsch): move this to foreign_helper.dart or similar.
102 @ForceInline()
103 createDartFunctionTypeRti() {
104 return JS_BUILTIN('returns:=Object;effects:none;depends:none',
105 JsBuiltin.createFunctionTypeRti);
106 }
107
108 /// Retrieves the class name from type information stored on the constructor of
109 /// [type].
110 // TODO(floitsch): move this to foreign_helper.dart or similar.
111 @ForceInline()
112 String rawRtiToJsConstructorName(Object rti) {
113 return JS_BUILTIN('String', JsBuiltin.rawRtiToJsConstructorName, rti);
114 }
115
116 /// Returns the rti from the given [constructorName].
117 // TODO(floitsch): make this a builtin.
118 jsConstructorNameToRti(String constructorName) {
119 var getTypeFromName = JS_EMBEDDED_GLOBAL('', GET_TYPE_FROM_NAME);
120 return JS('', '#(#)', getTypeFromName, constructorName);
121 }
122
123 /// Returns the raw runtime type of the given object [o].
124 ///
125 /// The argument [o] must be the interceptor for primitive types. If
126 /// necessary run it through [getInterceptor] first.
127 // TODO(floitsch): move this to foreign_helper.dart or similar.
128 // TODO(floitsch): we should call getInterceptor ourselves, but currently
129 // getInterceptor is not GVNed.
130 @ForceInline()
131 Object getRawRuntimeType(Object o) {
132 return JS_BUILTIN('', JsBuiltin.rawRuntimeType, o);
133 }
134
135 /// Returns whether the given [type] is a subtype of [other].
136 ///
137 /// The argument [other] is the name of the other type, as computed by
138 /// [runtimeTypeToString].
139 @ForceInline()
140 bool builtinIsSubtype(type, String other) {
141 return JS_BUILTIN('returns:bool;effects:none;depends:none',
142 JsBuiltin.isSubtype, other, type);
143 }
144
145 /// Returns true if the given [type] is _the_ `Function` type.
146 // TODO(floitsch): move this to foreign_helper.dart or similar.
147 @ForceInline()
148 bool isDartFunctionTypeRti(Object type) {
149 return JS_BUILTIN('returns:bool;effects:none;depends:none',
150 JsBuiltin.isFunctionTypeRti, type);
151 }
152
153 /// Returns whether the given type is _the_ Dart Object type.
154 // TODO(floitsch): move this to foreign_helper.dart or similar.
155 @ForceInline()
156 bool isDartObjectTypeRti(type) {
157 return JS_BUILTIN('returns:bool;effects:none;depends:none',
158 JsBuiltin.isDartObjectTypeRti, type);
159 }
160
161 /// Returns whether the given type is _the_ null type.
162 // TODO(floitsch): move this to foreign_helper.dart or similar.
163 @ForceInline()
164 bool isNullTypeRti(type) {
165 return JS_BUILTIN('returns:bool;effects:none;depends:none',
166 JsBuiltin.isNullTypeRti, type);
167 }
168
169 /// Returns the metadata of the given [index].
170 // TODO(floitsch): move this to foreign_helper.dart or similar.
171 @ForceInline()
172 getMetadata(int index) {
173 return JS_BUILTIN('returns:var;effects:none;depends:none',
174 JsBuiltin.getMetadata, index);
175 }
176
177 /// Returns the type of the given [index].
178 // TODO(floitsch): move this to foreign_helper.dart or similar.
179 @ForceInline()
180 getType(int index) {
181 return JS_BUILTIN('returns:var;effects:none;depends:none',
182 JsBuiltin.getType, index);
183 }
184
185 /// No-op method that is called to inform the compiler that preambles might
186 /// be needed when executing the resulting JS file in a command-line
187 /// JS engine.
188 requiresPreamble() {}
189
190 bool isJsIndexable(var object, var record) {
191 if (record != null) {
192 var result = dispatchRecordIndexability(record);
193 if (result != null) return result;
194 }
195 return object is JavaScriptIndexingBehavior;
196 }
197
198 String S(value) {
199 if (value is String) return value;
200 if (value is num) {
201 if (value != 0) {
202 // ""+x is faster than String(x) for integers on most browsers.
203 return JS('String', r'"" + (#)', value);
204 }
205 } else if (true == value) {
206 return 'true';
207 } else if (false == value) {
208 return 'false';
209 } else if (value == null) {
210 return 'null';
211 }
212 var res = value.toString();
213 if (res is !String) throw argumentErrorValue(value);
214 return res;
215 }
216
217 createInvocationMirror(String name, internalName, kind, arguments,
218 argumentNames) {
219 return new JSInvocationMirror(name,
220 internalName,
221 kind,
222 arguments,
223 argumentNames);
224 }
225
226 createUnmangledInvocationMirror(Symbol symbol, internalName, kind, arguments,
227 argumentNames) {
228 return new JSInvocationMirror(symbol,
229 internalName,
230 kind,
231 arguments,
232 argumentNames);
233 }
234
235 void throwInvalidReflectionError(String memberName) {
236 throw new UnsupportedError("Can't use '$memberName' in reflection "
237 "because it is not included in a @MirrorsUsed annotation.");
238 }
239
240 /// Helper to print the given method information to the console the first
241 /// time it is called with it.
242 @NoInline()
243 void traceHelper(String method) {
244 if (JS('bool', '!this.cache')) {
245 JS('', 'this.cache = Object.create(null)');
246 }
247 if (JS('bool', '!this.cache[#]', method)) {
248 JS('', 'console.log(#)', method);
249 JS('', 'this.cache[#] = true', method);
250 }
251 }
252
253 class JSInvocationMirror implements Invocation {
254 static const METHOD = 0;
255 static const GETTER = 1;
256 static const SETTER = 2;
257
258 /// When [_memberName] is a String, it holds the mangled name of this
259 /// invocation. When it is a Symbol, it holds the unmangled name.
260 var /* String or Symbol */ _memberName;
261 final String _internalName;
262 final int _kind;
263 final List _arguments;
264 final List _namedArgumentNames;
265 /** Map from argument name to index in _arguments. */
266 Map<String, dynamic> _namedIndices = null;
267
268 JSInvocationMirror(this._memberName,
269 this._internalName,
270 this._kind,
271 this._arguments,
272 this._namedArgumentNames);
273
274 Symbol get memberName {
275 if (_memberName is Symbol) return _memberName;
276 String name = _memberName;
277 String unmangledName = mangledNames[name];
278 if (unmangledName != null) {
279 name = unmangledName.split(':')[0];
280 } else {
281 if (mangledNames[_internalName] == null) {
282 print("Warning: '$name' is used reflectively but not in MirrorsUsed. "
283 "This will break minified code.");
284 }
285 }
286 _memberName = new _symbol_dev.Symbol.unvalidated(name);
287 return _memberName;
288 }
289
290 bool get isMethod => _kind == METHOD;
291 bool get isGetter => _kind == GETTER;
292 bool get isSetter => _kind == SETTER;
293 bool get isAccessor => _kind != METHOD;
294
295 List get positionalArguments {
296 if (isGetter) return const [];
297 var argumentCount = _arguments.length - _namedArgumentNames.length;
298 if (argumentCount == 0) return const [];
299 var list = [];
300 for (var index = 0 ; index < argumentCount ; index++) {
301 list.add(_arguments[index]);
302 }
303 return JSArray.markUnmodifiableList(list);
304 }
305
306 Map<Symbol, dynamic> get namedArguments {
307 if (isAccessor) return const <Symbol, dynamic>{};
308 int namedArgumentCount = _namedArgumentNames.length;
309 int namedArgumentsStartIndex = _arguments.length - namedArgumentCount;
310 if (namedArgumentCount == 0) return const <Symbol, dynamic>{};
311 var map = new Map<Symbol, dynamic>();
312 for (int i = 0; i < namedArgumentCount; i++) {
313 map[new _symbol_dev.Symbol.unvalidated(_namedArgumentNames[i])] =
314 _arguments[namedArgumentsStartIndex + i];
315 }
316 return new ConstantMapView<Symbol, dynamic>(map);
317 }
318
319 _getCachedInvocation(Object object) {
320 var interceptor = getInterceptor(object);
321 var receiver = object;
322 var name = _internalName;
323 var arguments = _arguments;
324 var interceptedNames = JS_EMBEDDED_GLOBAL('', INTERCEPTED_NAMES);
325 bool isIntercepted =
326 JS("bool", 'Object.prototype.hasOwnProperty.call(#, #)',
327 interceptedNames, name);
328 if (isIntercepted) {
329 receiver = interceptor;
330 if (JS('bool', '# === #', object, interceptor)) {
331 interceptor = null;
332 }
333 } else {
334 interceptor = null;
335 }
336 bool isCatchAll = false;
337 var method = JS('var', '#[#]', receiver, name);
338 if (JS('bool', 'typeof # != "function"', method) ) {
339 String baseName = _symbol_dev.Symbol.getName(memberName);
340 method = JS('', '#[# + "*"]', receiver, baseName);
341 if (method == null) {
342 interceptor = getInterceptor(object);
343 method = JS('', '#[# + "*"]', interceptor, baseName);
344 if (method != null) {
345 isIntercepted = true;
346 receiver = interceptor;
347 } else {
348 interceptor = null;
349 }
350 }
351 isCatchAll = true;
352 }
353 if (JS('bool', 'typeof # == "function"', method)) {
354 if (isCatchAll) {
355 return new CachedCatchAllInvocation(
356 name, method, isIntercepted, interceptor);
357 } else {
358 return new CachedInvocation(name, method, isIntercepted, interceptor);
359 }
360 } else {
361 // In this case, receiver doesn't implement name. So we should
362 // invoke noSuchMethod instead (which will often throw a
363 // NoSuchMethodError).
364 return new CachedNoSuchMethodInvocation(interceptor);
365 }
366 }
367
368 /// This method is called by [InstanceMirror.delegate].
369 static invokeFromMirror(JSInvocationMirror invocation, Object victim) {
370 var cached = invocation._getCachedInvocation(victim);
371 if (cached.isNoSuchMethod) {
372 return cached.invokeOn(victim, invocation);
373 } else {
374 return cached.invokeOn(victim, invocation._arguments);
375 }
376 }
377
378 static getCachedInvocation(JSInvocationMirror invocation, Object victim) {
379 return invocation._getCachedInvocation(victim);
380 }
381 }
382
383 class CachedInvocation {
384 // The mangled name of this invocation.
385 String mangledName;
386
387 /// The JS function to call.
388 var jsFunction;
389
390 /// True if this is an intercepted call.
391 bool isIntercepted;
392
393 /// Non-null interceptor if this is an intercepted call through an
394 /// [Interceptor].
395 Interceptor cachedInterceptor;
396
397 CachedInvocation(this.mangledName,
398 this.jsFunction,
399 this.isIntercepted,
400 this.cachedInterceptor);
401
402 bool get isNoSuchMethod => false;
403 bool get isGetterStub => JS("bool", "!!#.\$getterStub", jsFunction);
404
405 /// Applies [jsFunction] to [victim] with [arguments].
406 /// Users of this class must take care to check the arguments first.
407 invokeOn(Object victim, List arguments) {
408 var receiver = victim;
409 if (!isIntercepted) {
410 if (arguments is! JSArray) arguments = new List.from(arguments);
411 } else {
412 arguments = [victim]..addAll(arguments);
413 if (cachedInterceptor != null) receiver = cachedInterceptor;
414 }
415 return JS("var", "#.apply(#, #)", jsFunction, receiver, arguments);
416 }
417 }
418
419 class CachedCatchAllInvocation extends CachedInvocation {
420 final ReflectionInfo info;
421
422 CachedCatchAllInvocation(String name,
423 jsFunction,
424 bool isIntercepted,
425 Interceptor cachedInterceptor)
426 : info = new ReflectionInfo(jsFunction),
427 super(name, jsFunction, isIntercepted, cachedInterceptor);
428
429 bool get isGetterStub => false;
430
431 invokeOn(Object victim, List arguments) {
432 var receiver = victim;
433 int providedArgumentCount;
434 int fullParameterCount =
435 info.requiredParameterCount + info.optionalParameterCount;
436 if (!isIntercepted) {
437 if (arguments is JSArray) {
438 providedArgumentCount = arguments.length;
439 // If we need to add extra arguments before calling, we have
440 // to copy the arguments array.
441 if (providedArgumentCount < fullParameterCount) {
442 arguments = new List.from(arguments);
443 }
444 } else {
445 arguments = new List.from(arguments);
446 providedArgumentCount = arguments.length;
447 }
448 } else {
449 arguments = [victim]..addAll(arguments);
450 if (cachedInterceptor != null) receiver = cachedInterceptor;
451 providedArgumentCount = arguments.length - 1;
452 }
453 if (info.areOptionalParametersNamed &&
454 (providedArgumentCount > info.requiredParameterCount)) {
455 throw new UnimplementedNoSuchMethodError(
456 "Invocation of unstubbed method '${info.reflectionName}'"
457 " with ${arguments.length} arguments.");
458 } else if (providedArgumentCount < info.requiredParameterCount) {
459 throw new UnimplementedNoSuchMethodError(
460 "Invocation of unstubbed method '${info.reflectionName}'"
461 " with $providedArgumentCount arguments (too few).");
462 } else if (providedArgumentCount > fullParameterCount) {
463 throw new UnimplementedNoSuchMethodError(
464 "Invocation of unstubbed method '${info.reflectionName}'"
465 " with $providedArgumentCount arguments (too many).");
466 }
467 for (int i = providedArgumentCount; i < fullParameterCount; i++) {
468 arguments.add(getMetadata(info.defaultValue(i)));
469 }
470 return JS("var", "#.apply(#, #)", jsFunction, receiver, arguments);
471 }
472 }
473
474 class CachedNoSuchMethodInvocation {
475 /// Non-null interceptor if this is an intercepted call through an
476 /// [Interceptor].
477 var interceptor;
478
479 CachedNoSuchMethodInvocation(this.interceptor);
480
481 bool get isNoSuchMethod => true;
482 bool get isGetterStub => false;
483
484 invokeOn(Object victim, Invocation invocation) {
485 var receiver = (interceptor == null) ? victim : interceptor;
486 return receiver.noSuchMethod(invocation);
487 }
488 }
489
490 class ReflectionInfo {
491 static const int REQUIRED_PARAMETERS_INFO = 0;
492 static const int OPTIONAL_PARAMETERS_INFO = 1;
493 static const int FUNCTION_TYPE_INDEX = 2;
494 static const int FIRST_DEFAULT_ARGUMENT = 3;
495
496 /// A JavaScript function object.
497 final jsFunction;
498
499 /// Raw reflection information.
500 final List data;
501
502 /// Is this a getter or a setter.
503 final bool isAccessor;
504
505 /// Number of required parameters.
506 final int requiredParameterCount;
507
508 /// Number of optional parameters.
509 final int optionalParameterCount;
510
511 /// Are optional parameters named.
512 final bool areOptionalParametersNamed;
513
514 /// Either an index to the function type in the embedded `metadata` global or
515 /// a JavaScript function object which can compute such a type (presumably
516 /// due to free type variables).
517 final functionType;
518
519 List cachedSortedIndices;
520
521 ReflectionInfo.internal(this.jsFunction,
522 this.data,
523 this.isAccessor,
524 this.requiredParameterCount,
525 this.optionalParameterCount,
526 this.areOptionalParametersNamed,
527 this.functionType);
528
529 factory ReflectionInfo(jsFunction) {
530 List data = JS('JSExtendableArray|Null', r'#.$reflectionInfo', jsFunction);
531 if (data == null) return null;
532 data = JSArray.markFixedList(data);
533
534 int requiredParametersInfo =
535 JS('int', '#[#]', data, REQUIRED_PARAMETERS_INFO);
536 int requiredParameterCount = JS('int', '# >> 1', requiredParametersInfo);
537 bool isAccessor = (requiredParametersInfo & 1) == 1;
538
539 int optionalParametersInfo =
540 JS('int', '#[#]', data, OPTIONAL_PARAMETERS_INFO);
541 int optionalParameterCount = JS('int', '# >> 1', optionalParametersInfo);
542 bool areOptionalParametersNamed = (optionalParametersInfo & 1) == 1;
543
544 var functionType = JS('', '#[#]', data, FUNCTION_TYPE_INDEX);
545 return new ReflectionInfo.internal(
546 jsFunction, data, isAccessor, requiredParameterCount,
547 optionalParameterCount, areOptionalParametersNamed, functionType);
548 }
549
550 String parameterName(int parameter) {
551 int metadataIndex;
552 if (JS_GET_FLAG('MUST_RETAIN_METADATA')) {
553 metadataIndex = JS('int', '#[2 * # + # + #]', data,
554 parameter, optionalParameterCount, FIRST_DEFAULT_ARGUMENT);
555 } else {
556 metadataIndex = JS('int', '#[# + # + #]', data,
557 parameter, optionalParameterCount, FIRST_DEFAULT_ARGUMENT);
558 }
559 var name = getMetadata(metadataIndex);
560 return JS('String', '#', name);
561 }
562
563 List<int> parameterMetadataAnnotations(int parameter) {
564 if (!JS_GET_FLAG('MUST_RETAIN_METADATA')) {
565 throw new StateError('metadata has not been preserved');
566 } else {
567 return JS('', '#[2 * # + # + # + 1]', data, parameter,
568 optionalParameterCount, FIRST_DEFAULT_ARGUMENT);
569 }
570 }
571
572 int defaultValue(int parameter) {
573 if (parameter < requiredParameterCount) return null;
574 return JS('int', '#[# + # - #]', data,
575 FIRST_DEFAULT_ARGUMENT, parameter, requiredParameterCount);
576 }
577
578 /// Returns the default value of the [parameter]th entry of the list of
579 /// parameters sorted by name.
580 int defaultValueInOrder(int parameter) {
581 if (parameter < requiredParameterCount) return null;
582
583 if (!areOptionalParametersNamed || optionalParameterCount == 1) {
584 return defaultValue(parameter);
585 }
586
587 int index = sortedIndex(parameter - requiredParameterCount);
588 return defaultValue(index);
589 }
590
591 /// Returns the default value of the [parameter]th entry of the list of
592 /// parameters sorted by name.
593 String parameterNameInOrder(int parameter) {
594 if (parameter < requiredParameterCount) return null;
595
596 if (!areOptionalParametersNamed ||
597 optionalParameterCount == 1) {
598 return parameterName(parameter);
599 }
600
601 int index = sortedIndex(parameter - requiredParameterCount);
602 return parameterName(index);
603 }
604
605 /// Computes the index of the parameter in the list of named parameters sorted
606 /// by their name.
607 int sortedIndex(int unsortedIndex) {
608 if (cachedSortedIndices == null) {
609 // TODO(karlklose): cache this between [ReflectionInfo] instances or cache
610 // [ReflectionInfo] instances by [jsFunction].
611 cachedSortedIndices = new List(optionalParameterCount);
612 Map<String, int> positions = <String, int>{};
613 for (int i = 0; i < optionalParameterCount; i++) {
614 int index = requiredParameterCount + i;
615 positions[parameterName(index)] = index;
616 }
617 int index = 0;
618 (positions.keys.toList()..sort()).forEach((String name) {
619 cachedSortedIndices[index++] = positions[name];
620 });
621 }
622 return cachedSortedIndices[unsortedIndex];
623 }
624
625 @NoInline()
626 computeFunctionRti(jsConstructor) {
627 if (JS('bool', 'typeof # == "number"', functionType)) {
628 return getType(functionType);
629 } else if (JS('bool', 'typeof # == "function"', functionType)) {
630 var fakeInstance = JS('', 'new #()', jsConstructor);
631 setRuntimeTypeInfo(
632 fakeInstance, JS('JSExtendableArray', '#["<>"]', fakeInstance));
633 return JS('=Object|Null', r'#.apply({$receiver:#})',
634 functionType, fakeInstance);
635 } else {
636 throw new RuntimeError('Unexpected function type');
637 }
638 }
639
640 String get reflectionName => JS('String', r'#.$reflectionName', jsFunction);
641 }
642
643 class Primitives {
644 /// Isolate-unique ID for caching [JsClosureMirror.function].
645 /// Note the initial value is used by the first isolate (or if there are no
646 /// isolates), new isolates will update this value to avoid conflicts by
647 /// calling [initializeStatics].
648 static String mirrorFunctionCacheName = '\$cachedFunction';
649
650 /// Isolate-unique ID for caching [JsInstanceMirror._invoke].
651 static String mirrorInvokeCacheName = '\$cachedInvocation';
652
653 /// Called when creating a new isolate (see _IsolateContext constructor in
654 /// isolate_helper.dart).
655 /// Please don't add complicated code to this method, as it will impact
656 /// start-up performance.
657 static void initializeStatics(int id) {
658 // Benchmarking shows significant performance improvements if this is a
659 // fixed value.
660 mirrorFunctionCacheName += '_$id';
661 mirrorInvokeCacheName += '_$id';
662 }
663
664 static int objectHashCode(object) {
665 int hash = JS('int|Null', r'#.$identityHash', object);
666 if (hash == null) {
667 hash = JS('int', '(Math.random() * 0x3fffffff) | 0');
668 JS('void', r'#.$identityHash = #', object, hash);
669 }
670 return JS('int', '#', hash);
671 }
672
673 @NoInline()
674 static int _parseIntError(String source, int handleError(String source)) {
675 if (handleError == null) throw new FormatException(source);
676 return handleError(source);
677 }
678
679 static int parseInt(String source,
680 int radix,
681 int handleError(String source)) {
682 checkString(source);
683 var re = JS('', r'/^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i');
684 var match = JS('JSExtendableArray|Null', '#.exec(#)', re, source);
685 int digitsIndex = 1;
686 int hexIndex = 2;
687 int decimalIndex = 3;
688 int nonDecimalHexIndex = 4;
689 if (match == null) {
690 // TODO(sra): It might be that the match failed due to unrecognized U+0085
691 // spaces. We could replace them with U+0020 spaces and try matching
692 // again.
693 return _parseIntError(source, handleError);
694 }
695 String decimalMatch = match[decimalIndex];
696 if (radix == null) {
697 if (decimalMatch != null) {
698 // Cannot fail because we know that the digits are all decimal.
699 return JS('int', r'parseInt(#, 10)', source);
700 }
701 if (match[hexIndex] != null) {
702 // Cannot fail because we know that the digits are all hex.
703 return JS('int', r'parseInt(#, 16)', source);
704 }
705 return _parseIntError(source, handleError);
706 }
707
708 if (radix is! int) {
709 throw new ArgumentError.value(radix, 'radix', 'is not an integer');
710 }
711 if (radix < 2 || radix > 36) {
712 throw new RangeError.range(radix, 2, 36, 'radix');
713 }
714 if (radix == 10 && decimalMatch != null) {
715 // Cannot fail because we know that the digits are all decimal.
716 return JS('int', r'parseInt(#, 10)', source);
717 }
718 // If radix >= 10 and we have only decimal digits the string is safe.
719 // Otherwise we need to check the digits.
720 if (radix < 10 || decimalMatch == null) {
721 // We know that the characters must be ASCII as otherwise the
722 // regexp wouldn't have matched. Lowercasing by doing `| 0x20` is thus
723 // guaranteed to be a safe operation, since it preserves digits
724 // and lower-cases ASCII letters.
725 int maxCharCode;
726 if (radix <= 10) {
727 // Allow all digits less than the radix. For example 0, 1, 2 for
728 // radix 3.
729 // "0".codeUnitAt(0) + radix - 1;
730 maxCharCode = (0x30 - 1) + radix;
731 } else {
732 // Letters are located after the digits in ASCII. Therefore we
733 // only check for the character code. The regexp above made already
734 // sure that the string does not contain anything but digits or
735 // letters.
736 // "a".codeUnitAt(0) + (radix - 10) - 1;
737 maxCharCode = (0x61 - 10 - 1) + radix;
738 }
739 assert(match[digitsIndex] is String);
740 String digitsPart = JS('String', '#[#]', match, digitsIndex);
741 for (int i = 0; i < digitsPart.length; i++) {
742 int characterCode = digitsPart.codeUnitAt(i) | 0x20;
743 if (characterCode > maxCharCode) {
744 return _parseIntError(source, handleError);
745 }
746 }
747 }
748 // The above matching and checks ensures the source has at least one digits
749 // and all digits are suitable for the radix, so parseInt cannot return NaN.
750 return JS('int', r'parseInt(#, #)', source, radix);
751 }
752
753 @NoInline()
754 static double _parseDoubleError(String source,
755 double handleError(String source)) {
756 if (handleError == null) {
757 throw new FormatException('Invalid double', source);
758 }
759 return handleError(source);
760 }
761
762 static double parseDouble(String source, double handleError(String source)) {
763 checkString(source);
764 // Notice that JS parseFloat accepts garbage at the end of the string.
765 // Accept only:
766 // - [+/-]NaN
767 // - [+/-]Infinity
768 // - a Dart double literal
769 // We do allow leading or trailing whitespace.
770 if (!JS('bool',
771 r'/^\s*[+-]?(?:Infinity|NaN|'
772 r'(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(#)',
773 source)) {
774 return _parseDoubleError(source, handleError);
775 }
776 var result = JS('num', r'parseFloat(#)', source);
777 if (result.isNaN) {
778 var trimmed = source.trim();
779 if (trimmed == 'NaN' || trimmed == '+NaN' || trimmed == '-NaN') {
780 return result;
781 }
782 return _parseDoubleError(source, handleError);
783 }
784 return result;
785 }
786
787 /** [: r"$".codeUnitAt(0) :] */
788 static const int DOLLAR_CHAR_VALUE = 36;
789
790 /// Creates a string containing the complete type for the class [className]
791 /// with the given type arguments.
792 ///
793 /// In minified mode, uses the unminified names if available.
794 ///
795 /// The given [className] string generally contains the name of the JavaScript
796 /// constructor of the given class.
797 static String formatType(String className, List typeArguments) {
798 return unmangleAllIdentifiersIfPreservedAnyways
799 ('$className${joinArguments(typeArguments, 0)}');
800 }
801
802 /// Returns the type of [object] as a string (including type arguments).
803 ///
804 /// In minified mode, uses the unminified names if available.
805 static String objectTypeName(Object object) {
806 String name = constructorNameFallback(getInterceptor(object));
807 if (name == 'Object') {
808 // Try to decompile the constructor by turning it into a string and get
809 // the name out of that. If the decompiled name is a string containing an
810 // identifier, we use that instead of the very generic 'Object'.
811 var decompiled =
812 JS('var', r'#.match(/^\s*function\s*([\w$]*)\s*\(/)[1]',
813 JS('var', r'String(#.constructor)', object));
814 if (decompiled is String)
815 if (JS('bool', r'/^\w+$/.test(#)', decompiled))
816 name = decompiled;
817 }
818 // TODO(kasperl): If the namer gave us a fresh global name, we may
819 // want to remove the numeric suffix that makes it unique too.
820 if (name.length > 1 && identical(name.codeUnitAt(0), DOLLAR_CHAR_VALUE)) {
821 name = name.substring(1);
822 }
823 return formatType(name, getRuntimeTypeInfo(object));
824 }
825
826 /// In minified mode, uses the unminified names if available.
827 static String objectToHumanReadableString(Object object) {
828 String name = objectTypeName(object);
829 return "Instance of '$name'";
830 }
831
832 static num dateNow() => JS('int', r'Date.now()');
833
834 static void initTicker() {
835 if (timerFrequency != null) return;
836 // Start with low-resolution. We overwrite the fields if we find better.
837 timerFrequency = 1000;
838 timerTicks = dateNow;
839 if (JS('bool', 'typeof window == "undefined"')) return;
840 var window = JS('var', 'window');
841 if (window == null) return;
842 var performance = JS('var', '#.performance', window);
843 if (performance == null) return;
844 if (JS('bool', 'typeof #.now != "function"', performance)) return;
845 timerFrequency = 1000000;
846 timerTicks = () => (1000 * JS('num', '#.now()', performance)).floor();
847 }
848
849 static int timerFrequency;
850 static Function timerTicks;
851
852 static String currentUri() {
853 requiresPreamble();
854 // In a browser return self.location.href.
855 if (JS('bool', '!!self.location')) {
856 return JS('String', 'self.location.href');
857 }
858
859 return null;
860 }
861
862 // This is to avoid stack overflows due to very large argument arrays in
863 // apply(). It fixes http://dartbug.com/6919
864 static String _fromCharCodeApply(List<int> array) {
865 const kMaxApply = 500;
866 int end = array.length;
867 if (end <= kMaxApply) {
868 return JS('String', r'String.fromCharCode.apply(null, #)', array);
869 }
870 String result = '';
871 for (int i = 0; i < end; i += kMaxApply) {
872 int chunkEnd = (i + kMaxApply < end) ? i + kMaxApply : end;
873 result = JS('String',
874 r'# + String.fromCharCode.apply(null, #.slice(#, #))',
875 result, array, i, chunkEnd);
876 }
877 return result;
878 }
879
880 static String stringFromCodePoints(codePoints) {
881 List<int> a = <int>[];
882 for (var i in codePoints) {
883 if (i is !int) throw argumentErrorValue(i);
884 if (i <= 0xffff) {
885 a.add(i);
886 } else if (i <= 0x10ffff) {
887 a.add(0xd800 + ((((i - 0x10000) >> 10) & 0x3ff)));
888 a.add(0xdc00 + (i & 0x3ff));
889 } else {
890 throw argumentErrorValue(i);
891 }
892 }
893 return _fromCharCodeApply(a);
894 }
895
896 static String stringFromCharCodes(charCodes) {
897 for (var i in charCodes) {
898 if (i is !int) throw argumentErrorValue(i);
899 if (i < 0) throw argumentErrorValue(i);
900 if (i > 0xffff) return stringFromCodePoints(charCodes);
901 }
902 return _fromCharCodeApply(charCodes);
903 }
904
905 // [start] and [end] are validated.
906 static String stringFromNativeUint8List(
907 NativeUint8List charCodes, int start, int end) {
908 const kMaxApply = 500;
909 if (end <= kMaxApply && start == 0 && end == charCodes.length) {
910 return JS('String', r'String.fromCharCode.apply(null, #)', charCodes);
911 }
912 String result = '';
913 for (int i = start; i < end; i += kMaxApply) {
914 int chunkEnd = (i + kMaxApply < end) ? i + kMaxApply : end;
915 result = JS('String',
916 r'# + String.fromCharCode.apply(null, #.subarray(#, #))',
917 result, charCodes, i, chunkEnd);
918 }
919 return result;
920 }
921
922
923 static String stringFromCharCode(charCode) {
924 if (0 <= charCode) {
925 if (charCode <= 0xffff) {
926 return JS('String', 'String.fromCharCode(#)', charCode);
927 }
928 if (charCode <= 0x10ffff) {
929 var bits = charCode - 0x10000;
930 var low = 0xDC00 | (bits & 0x3ff);
931 var high = 0xD800 | (bits >> 10);
932 return JS('String', 'String.fromCharCode(#, #)', high, low);
933 }
934 }
935 throw new RangeError.range(charCode, 0, 0x10ffff);
936 }
937
938 static String stringConcatUnchecked(String string1, String string2) {
939 return JS_STRING_CONCAT(string1, string2);
940 }
941
942 static String flattenString(String str) {
943 return JS('String', "#.charCodeAt(0) == 0 ? # : #", str, str, str);
944 }
945
946 static String getTimeZoneName(receiver) {
947 // Firefox and Chrome emit the timezone in parenthesis.
948 // Example: "Wed May 16 2012 21:13:00 GMT+0200 (CEST)".
949 // We extract this name using a regexp.
950 var d = lazyAsJsDate(receiver);
951 List match = JS('JSArray|Null', r'/\((.*)\)/.exec(#.toString())', d);
952 if (match != null) return match[1];
953
954 // Internet Explorer 10+ emits the zone name without parenthesis:
955 // Example: Thu Oct 31 14:07:44 PDT 2013
956 match = JS('JSArray|Null',
957 // Thu followed by a space.
958 r'/^[A-Z,a-z]{3}\s'
959 // Oct 31 followed by space.
960 r'[A-Z,a-z]{3}\s\d+\s'
961 // Time followed by a space.
962 r'\d{2}:\d{2}:\d{2}\s'
963 // The time zone name followed by a space.
964 r'([A-Z]{3,5})\s'
965 // The year.
966 r'\d{4}$/'
967 '.exec(#.toString())',
968 d);
969 if (match != null) return match[1];
970
971 // IE 9 and Opera don't provide the zone name. We fall back to emitting the
972 // UTC/GMT offset.
973 // Example (IE9): Wed Nov 20 09:51:00 UTC+0100 2013
974 // (Opera): Wed Nov 20 2013 11:03:38 GMT+0100
975 match = JS('JSArray|Null', r'/(?:GMT|UTC)[+-]\d{4}/.exec(#.toString())', d);
976 if (match != null) return match[0];
977 return "";
978 }
979
980 static int getTimeZoneOffsetInMinutes(receiver) {
981 // Note that JS and Dart disagree on the sign of the offset.
982 return -JS('int', r'#.getTimezoneOffset()', lazyAsJsDate(receiver));
983 }
984
985 static valueFromDecomposedDate(years, month, day, hours, minutes, seconds,
986 milliseconds, isUtc) {
987 final int MAX_MILLISECONDS_SINCE_EPOCH = 8640000000000000;
988 checkInt(years);
989 checkInt(month);
990 checkInt(day);
991 checkInt(hours);
992 checkInt(minutes);
993 checkInt(seconds);
994 checkInt(milliseconds);
995 checkBool(isUtc);
996 var jsMonth = month - 1;
997 var value;
998 if (isUtc) {
999 value = JS('num', r'Date.UTC(#, #, #, #, #, #, #)',
1000 years, jsMonth, day, hours, minutes, seconds, milliseconds);
1001 } else {
1002 value = JS('num', r'new Date(#, #, #, #, #, #, #).valueOf()',
1003 years, jsMonth, day, hours, minutes, seconds, milliseconds);
1004 }
1005 if (value.isNaN ||
1006 value < -MAX_MILLISECONDS_SINCE_EPOCH ||
1007 value > MAX_MILLISECONDS_SINCE_EPOCH) {
1008 return null;
1009 }
1010 if (years <= 0 || years < 100) return patchUpY2K(value, years, isUtc);
1011 return value;
1012 }
1013
1014 static patchUpY2K(value, years, isUtc) {
1015 var date = JS('', r'new Date(#)', value);
1016 if (isUtc) {
1017 JS('num', r'#.setUTCFullYear(#)', date, years);
1018 } else {
1019 JS('num', r'#.setFullYear(#)', date, years);
1020 }
1021 return JS('num', r'#.valueOf()', date);
1022 }
1023
1024 // Lazily keep a JS Date stored in the JS object.
1025 static lazyAsJsDate(receiver) {
1026 if (JS('bool', r'#.date === (void 0)', receiver)) {
1027 JS('void', r'#.date = new Date(#)', receiver,
1028 receiver.millisecondsSinceEpoch);
1029 }
1030 return JS('var', r'#.date', receiver);
1031 }
1032
1033 // The getters for date and time parts below add a positive integer to ensure
1034 // that the result is really an integer, because the JavaScript implementation
1035 // may return -0.0 instead of 0.
1036
1037 static getYear(receiver) {
1038 return (receiver.isUtc)
1039 ? JS('int', r'(#.getUTCFullYear() + 0)', lazyAsJsDate(receiver))
1040 : JS('int', r'(#.getFullYear() + 0)', lazyAsJsDate(receiver));
1041 }
1042
1043 static getMonth(receiver) {
1044 return (receiver.isUtc)
1045 ? JS('JSUInt31', r'#.getUTCMonth() + 1', lazyAsJsDate(receiver))
1046 : JS('JSUInt31', r'#.getMonth() + 1', lazyAsJsDate(receiver));
1047 }
1048
1049 static getDay(receiver) {
1050 return (receiver.isUtc)
1051 ? JS('JSUInt31', r'(#.getUTCDate() + 0)', lazyAsJsDate(receiver))
1052 : JS('JSUInt31', r'(#.getDate() + 0)', lazyAsJsDate(receiver));
1053 }
1054
1055 static getHours(receiver) {
1056 return (receiver.isUtc)
1057 ? JS('JSUInt31', r'(#.getUTCHours() + 0)', lazyAsJsDate(receiver))
1058 : JS('JSUInt31', r'(#.getHours() + 0)', lazyAsJsDate(receiver));
1059 }
1060
1061 static getMinutes(receiver) {
1062 return (receiver.isUtc)
1063 ? JS('JSUInt31', r'(#.getUTCMinutes() + 0)', lazyAsJsDate(receiver))
1064 : JS('JSUInt31', r'(#.getMinutes() + 0)', lazyAsJsDate(receiver));
1065 }
1066
1067 static getSeconds(receiver) {
1068 return (receiver.isUtc)
1069 ? JS('JSUInt31', r'(#.getUTCSeconds() + 0)', lazyAsJsDate(receiver))
1070 : JS('JSUInt31', r'(#.getSeconds() + 0)', lazyAsJsDate(receiver));
1071 }
1072
1073 static getMilliseconds(receiver) {
1074 return (receiver.isUtc)
1075 ? JS('JSUInt31', r'(#.getUTCMilliseconds() + 0)', lazyAsJsDate(receiver))
1076 : JS('JSUInt31', r'(#.getMilliseconds() + 0)', lazyAsJsDate(receiver));
1077 }
1078
1079 static getWeekday(receiver) {
1080 int weekday = (receiver.isUtc)
1081 ? JS('int', r'#.getUTCDay() + 0', lazyAsJsDate(receiver))
1082 : JS('int', r'#.getDay() + 0', lazyAsJsDate(receiver));
1083 // Adjust by one because JS weeks start on Sunday.
1084 return (weekday + 6) % 7 + 1;
1085 }
1086
1087 static valueFromDateString(str) {
1088 if (str is !String) throw argumentErrorValue(str);
1089 var value = JS('num', r'Date.parse(#)', str);
1090 if (value.isNaN) throw argumentErrorValue(str);
1091 return value;
1092 }
1093
1094 static getProperty(object, key) {
1095 if (object == null || object is bool || object is num || object is String) {
1096 throw argumentErrorValue(object);
1097 }
1098 return JS('var', '#[#]', object, key);
1099 }
1100
1101 static void setProperty(object, key, value) {
1102 if (object == null || object is bool || object is num || object is String) {
1103 throw argumentErrorValue(object);
1104 }
1105 JS('void', '#[#] = #', object, key, value);
1106 }
1107
1108 static functionNoSuchMethod(function,
1109 List positionalArguments,
1110 Map<String, dynamic> namedArguments) {
1111 int argumentCount = 0;
1112 List arguments = [];
1113 List namedArgumentList = [];
1114
1115 if (positionalArguments != null) {
1116 argumentCount += positionalArguments.length;
1117 arguments.addAll(positionalArguments);
1118 }
1119
1120 String names = '';
1121 if (namedArguments != null && !namedArguments.isEmpty) {
1122 namedArguments.forEach((String name, argument) {
1123 names = '$names\$$name';
1124 namedArgumentList.add(name);
1125 arguments.add(argument);
1126 argumentCount++;
1127 });
1128 }
1129
1130 String selectorName =
1131 '${JS_GET_NAME(JsGetName.CALL_PREFIX)}\$$argumentCount$names';
1132
1133 return function.noSuchMethod(
1134 createUnmangledInvocationMirror(
1135 #call,
1136 selectorName,
1137 JSInvocationMirror.METHOD,
1138 arguments,
1139 namedArgumentList));
1140 }
1141
1142 static applyFunctionNewEmitter(Function function,
1143 List positionalArguments,
1144 Map<String, dynamic> namedArguments) {
1145 if (namedArguments == null) {
1146 int requiredParameterCount = JS('int', r'#[#]', function,
1147 JS_GET_NAME(JsGetName.REQUIRED_PARAMETER_PROPERTY));
1148 int argumentCount = positionalArguments.length;
1149 if (argumentCount < requiredParameterCount) {
1150 return functionNoSuchMethod(function, positionalArguments, null);
1151 }
1152 String selectorName =
1153 '${JS_GET_NAME(JsGetName.CALL_PREFIX)}\$$argumentCount';
1154 var jsStub = JS('var', r'#[#]', function, selectorName);
1155 if (jsStub == null) {
1156 // Do a dynamic call.
1157 var interceptor = getInterceptor(function);
1158 var jsFunction = JS('', '#[#]', interceptor,
1159 JS_GET_NAME(JsGetName.CALL_CATCH_ALL));
1160 var defaultValues = JS('var', r'#[#]', function,
1161 JS_GET_NAME(JsGetName.DEFAULT_VALUES_PROPERTY));
1162 if (!JS('bool', '# instanceof Array', defaultValues)) {
1163 // The function expects named arguments!
1164 return functionNoSuchMethod(function, positionalArguments, null);
1165 }
1166 int defaultsLength = JS('int', "#.length", defaultValues);
1167 int maxArguments = requiredParameterCount + defaultsLength;
1168 if (argumentCount > maxArguments) {
1169 // The function expects less arguments!
1170 return functionNoSuchMethod(function, positionalArguments, null);
1171 }
1172 List arguments = new List.from(positionalArguments);
1173 List missingDefaults = JS('JSArray', '#.slice(#)', defaultValues,
1174 argumentCount - requiredParameterCount);
1175 arguments.addAll(missingDefaults);
1176 return JS('var', '#.apply(#, #)', jsFunction, function, arguments);
1177 }
1178 return JS('var', '#.apply(#, #)', jsStub, function, positionalArguments);
1179 } else {
1180 var interceptor = getInterceptor(function);
1181 var jsFunction = JS('', '#[#]', interceptor,
1182 JS_GET_NAME(JsGetName.CALL_CATCH_ALL));
1183 var defaultValues = JS('JSArray', r'#[#]', function,
1184 JS_GET_NAME(JsGetName.DEFAULT_VALUES_PROPERTY));
1185 List keys = JS('JSArray', r'Object.keys(#)', defaultValues);
1186 List arguments = new List.from(positionalArguments);
1187 int used = 0;
1188 for (String key in keys) {
1189 var value = namedArguments[key];
1190 if (value != null) {
1191 used++;
1192 arguments.add(value);
1193 } else {
1194 arguments.add(JS('var', r'#[#]', defaultValues, key));
1195 }
1196 }
1197 if (used != namedArguments.length) {
1198 return functionNoSuchMethod(function, positionalArguments,
1199 namedArguments);
1200 }
1201 return JS('var', r'#.apply(#, #)', jsFunction, function, arguments);
1202 }
1203 }
1204
1205 static applyFunction(Function function,
1206 List positionalArguments,
1207 Map<String, dynamic> namedArguments) {
1208 // Dispatch on presence of named arguments to improve tree-shaking.
1209 //
1210 // This dispatch is as simple as possible to help the compiler detect the
1211 // common case of `null` namedArguments, either via inlining or
1212 // specialization.
1213 return namedArguments == null
1214 ? applyFunctionWithPositionalArguments(
1215 function, positionalArguments)
1216 : applyFunctionWithNamedArguments(
1217 function, positionalArguments, namedArguments);
1218 }
1219
1220 static applyFunctionWithPositionalArguments(Function function,
1221 List positionalArguments) {
1222 List arguments;
1223
1224 if (positionalArguments != null) {
1225 if (JS('bool', '# instanceof Array', positionalArguments)) {
1226 arguments = JS('JSArray', '#', positionalArguments);
1227 } else {
1228 arguments = new List.from(positionalArguments);
1229 }
1230 } else {
1231 arguments = [];
1232 }
1233
1234 if (arguments.length == 0) {
1235 String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX0);
1236 if (JS('bool', '!!#[#]', function, selectorName)) {
1237 return JS('', '#[#]()', function, selectorName);
1238 }
1239 } else if (arguments.length == 1) {
1240 String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX1);
1241 if (JS('bool', '!!#[#]', function, selectorName)) {
1242 return JS('', '#[#](#[0])', function, selectorName, arguments);
1243 }
1244 } else if (arguments.length == 2) {
1245 String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX2);
1246 if (JS('bool', '!!#[#]', function, selectorName)) {
1247 return JS('', '#[#](#[0],#[1])', function, selectorName,
1248 arguments, arguments);
1249 }
1250 } else if (arguments.length == 3) {
1251 String selectorName = JS_GET_NAME(JsGetName.CALL_PREFIX3);
1252 if (JS('bool', '!!#[#]', function, selectorName)) {
1253 return JS('', '#[#](#[0],#[1],#[2])', function, selectorName,
1254 arguments, arguments, arguments);
1255 }
1256 }
1257 return _genericApplyFunctionWithPositionalArguments(function, arguments);
1258 }
1259
1260 static _genericApplyFunctionWithPositionalArguments(Function function,
1261 List arguments) {
1262 int argumentCount = arguments.length;
1263 String selectorName =
1264 '${JS_GET_NAME(JsGetName.CALL_PREFIX)}\$$argumentCount';
1265 var jsFunction = JS('var', '#[#]', function, selectorName);
1266 if (jsFunction == null) {
1267 var interceptor = getInterceptor(function);
1268 jsFunction = JS('', '#["call*"]', interceptor);
1269
1270 if (jsFunction == null) {
1271 return functionNoSuchMethod(function, arguments, null);
1272 }
1273 ReflectionInfo info = new ReflectionInfo(jsFunction);
1274 int requiredArgumentCount = info.requiredParameterCount;
1275 int maxArgumentCount = requiredArgumentCount +
1276 info.optionalParameterCount;
1277 if (info.areOptionalParametersNamed ||
1278 requiredArgumentCount > argumentCount ||
1279 maxArgumentCount < argumentCount) {
1280 return functionNoSuchMethod(function, arguments, null);
1281 }
1282 arguments = new List.from(arguments);
1283 for (int pos = argumentCount; pos < maxArgumentCount; pos++) {
1284 arguments.add(getMetadata(info.defaultValue(pos)));
1285 }
1286 }
1287 // We bound 'this' to [function] because of how we compile
1288 // closures: escaped local variables are stored and accessed through
1289 // [function].
1290 return JS('var', '#.apply(#, #)', jsFunction, function, arguments);
1291 }
1292
1293 static applyFunctionWithNamedArguments(Function function,
1294 List positionalArguments,
1295 Map<String, dynamic> namedArguments) {
1296 if (namedArguments.isEmpty) {
1297 return applyFunctionWithPositionalArguments(
1298 function, positionalArguments);
1299 }
1300 // TODO(ahe): The following code can be shared with
1301 // JsInstanceMirror.invoke.
1302 var interceptor = getInterceptor(function);
1303 var jsFunction = JS('', '#[#]', interceptor,
1304 JS_GET_NAME(JsGetName.CALL_CATCH_ALL));
1305
1306 if (jsFunction == null) {
1307 return functionNoSuchMethod(
1308 function, positionalArguments, namedArguments);
1309 }
1310 ReflectionInfo info = new ReflectionInfo(jsFunction);
1311 if (info == null || !info.areOptionalParametersNamed) {
1312 return functionNoSuchMethod(
1313 function, positionalArguments, namedArguments);
1314 }
1315
1316 if (positionalArguments != null) {
1317 positionalArguments = new List.from(positionalArguments);
1318 } else {
1319 positionalArguments = [];
1320 }
1321 // Check the number of positional arguments is valid.
1322 if (info.requiredParameterCount != positionalArguments.length) {
1323 return functionNoSuchMethod(
1324 function, positionalArguments, namedArguments);
1325 }
1326 var defaultArguments = new Map();
1327 for (int i = 0; i < info.optionalParameterCount; i++) {
1328 int index = i + info.requiredParameterCount;
1329 var parameterName = info.parameterNameInOrder(index);
1330 var value = info.defaultValueInOrder(index);
1331 var defaultValue = getMetadata(value);
1332 defaultArguments[parameterName] = defaultValue;
1333 }
1334 bool bad = false;
1335 namedArguments.forEach((String parameter, value) {
1336 if (defaultArguments.containsKey(parameter)) {
1337 defaultArguments[parameter] = value;
1338 } else {
1339 // Extraneous named argument.
1340 bad = true;
1341 }
1342 });
1343 if (bad) {
1344 return functionNoSuchMethod(
1345 function, positionalArguments, namedArguments);
1346 }
1347 positionalArguments.addAll(defaultArguments.values);
1348 return JS('', '#.apply(#, #)', jsFunction, function, positionalArguments);
1349 }
1350
1351 static bool identicalImplementation(a, b) {
1352 return JS('bool', '# == null', a)
1353 ? JS('bool', '# == null', b)
1354 : JS('bool', '# === #', a, b);
1355 }
1356
1357 static StackTrace extractStackTrace(Error error) {
1358 return getTraceFromException(JS('', r'#.$thrownJsError', error));
1359 }
1360 }
1361
1362 /// Helper class for allocating and using JS object literals as caches.
1363 class JsCache {
1364 /// Returns a JavaScript object suitable for use as a cache.
1365 static allocate() {
1366 var result = JS('=Object', 'Object.create(null)');
1367 // Deleting a property makes V8 assume that it shouldn't create a hidden
1368 // class for [result] and map transitions. Although these map transitions
1369 // pay off if there are many cache hits for the same keys, it becomes
1370 // really slow when there aren't many repeated hits.
1371 JS('void', '#.x=0', result);
1372 JS('void', 'delete #.x', result);
1373 return result;
1374 }
1375
1376 static fetch(cache, String key) {
1377 return JS('', '#[#]', cache, key);
1378 }
1379
1380 static void update(cache, String key, value) {
1381 JS('void', '#[#] = #', cache, key, value);
1382 }
1383 }
1384
1385 /**
1386 * Called by generated code to throw an illegal-argument exception,
1387 * for example, if a non-integer index is given to an optimized
1388 * indexed access.
1389 */
1390 @NoInline()
1391 iae(argument) {
1392 throw argumentErrorValue(argument);
1393 }
1394
1395 /**
1396 * Called by generated code to throw an index-out-of-range exception, for
1397 * example, if a bounds check fails in an optimized indexed access. This may
1398 * also be called when the index is not an integer, in which case it throws an
1399 * illegal-argument exception instead, like [iae], or when the receiver is null.
1400 */
1401 @NoInline()
1402 ioore(receiver, index) {
1403 if (receiver == null) receiver.length; // Force a NoSuchMethodError.
1404 throw diagnoseIndexError(receiver, index);
1405 }
1406
1407 /**
1408 * Diagnoses an indexing error. Returns the ArgumentError or RangeError that
1409 * describes the problem.
1410 */
1411 @NoInline()
1412 Error diagnoseIndexError(indexable, index) {
1413 if (index is !int) return new ArgumentError.value(index, 'index');
1414 int length = indexable.length;
1415 // The following returns the same error that would be thrown by calling
1416 // [RangeError.checkValidIndex] with no optional parameters provided.
1417 if (index < 0 || index >= length) {
1418 return new RangeError.index(index, indexable, 'index', null, length);
1419 }
1420 // The above should always match, but if it does not, use the following.
1421 return new RangeError.value(index, 'index');
1422 }
1423
1424
1425 stringLastIndexOfUnchecked(receiver, element, start)
1426 => JS('int', r'#.lastIndexOf(#, #)', receiver, element, start);
1427
1428
1429 /// 'factory' for constructing ArgumentError.value to keep the call sites small.
1430 @NoInline()
1431 ArgumentError argumentErrorValue(object) {
1432 return new ArgumentError.value(object);
1433 }
1434
1435 checkNull(object) {
1436 if (object == null) throw argumentErrorValue(object);
1437 return object;
1438 }
1439
1440 checkNum(value) {
1441 if (value is !num) throw argumentErrorValue(value);
1442 return value;
1443 }
1444
1445 checkInt(value) {
1446 if (value is !int) throw argumentErrorValue(value);
1447 return value;
1448 }
1449
1450 checkBool(value) {
1451 if (value is !bool) throw argumentErrorValue(value);
1452 return value;
1453 }
1454
1455 checkString(value) {
1456 if (value is !String) throw argumentErrorValue(value);
1457 return value;
1458 }
1459
1460 /**
1461 * Wrap the given Dart object and record a stack trace.
1462 *
1463 * The code in [unwrapException] deals with getting the original Dart
1464 * object out of the wrapper again.
1465 */
1466 @NoInline()
1467 wrapException(ex) {
1468 if (ex == null) ex = new NullThrownError();
1469 var wrapper = JS('', 'new Error()');
1470 // [unwrapException] looks for the property 'dartException'.
1471 JS('void', '#.dartException = #', wrapper, ex);
1472
1473 if (JS('bool', '"defineProperty" in Object')) {
1474 // Define a JavaScript getter for 'message'. This is to work around V8 bug
1475 // (https://code.google.com/p/v8/issues/detail?id=2519). The default
1476 // toString on Error returns the value of 'message' if 'name' is
1477 // empty. Setting toString directly doesn't work, see the bug.
1478 JS('void', 'Object.defineProperty(#, "message", { get: # })',
1479 wrapper, DART_CLOSURE_TO_JS(toStringWrapper));
1480 JS('void', '#.name = ""', wrapper);
1481 } else {
1482 // In the unlikely event the browser doesn't support Object.defineProperty,
1483 // hope that it just calls toString.
1484 JS('void', '#.toString = #', wrapper, DART_CLOSURE_TO_JS(toStringWrapper));
1485 }
1486
1487 return wrapper;
1488 }
1489
1490 /// Do not call directly.
1491 toStringWrapper() {
1492 // This method gets installed as toString on a JavaScript object. Due to the
1493 // weird scope rules of JavaScript, JS 'this' will refer to that object.
1494 return JS('', r'this.dartException').toString();
1495 }
1496
1497 /**
1498 * This wraps the exception and does the throw. It is possible to call this in
1499 * a JS expression context, where the throw statement is not allowed. Helpers
1500 * are never inlined, so we don't risk inlining the throw statement into an
1501 * expression context.
1502 */
1503 throwExpression(ex) {
1504 JS('void', 'throw #', wrapException(ex));
1505 }
1506
1507 throwRuntimeError(message) {
1508 throw new RuntimeError(message);
1509 }
1510
1511 throwAbstractClassInstantiationError(className) {
1512 throw new AbstractClassInstantiationError(className);
1513 }
1514
1515 // This is used in open coded for-in loops on arrays.
1516 //
1517 // checkConcurrentModificationError(a.length == startLength, a)
1518 //
1519 // is replaced in codegen by:
1520 //
1521 // a.length == startLength || throwConcurrentModificationError(a)
1522 //
1523 // TODO(sra): We would like to annotate this as @NoSideEffects() so that loops
1524 // with no other effects can recognize that the array length does not
1525 // change. However, in the usual case where the loop does have other effects,
1526 // that causes the length in the loop condition to be phi(startLength,a.length),
1527 // which causes confusion in range analysis and the insertion of a bounds check.
1528 @NoInline()
1529 checkConcurrentModificationError(sameLength, collection) {
1530 if (true != sameLength) {
1531 throwConcurrentModificationError(collection);
1532 }
1533 }
1534
1535 @NoInline()
1536 throwConcurrentModificationError(collection) {
1537 throw new ConcurrentModificationError(collection);
1538 }
1539
1540 /**
1541 * Helper class for building patterns recognizing native type errors.
1542 */
1543 class TypeErrorDecoder {
1544 // Field names are private to help tree-shaking.
1545
1546 /// A regular expression which matches is matched against an error message.
1547 final String _pattern;
1548
1549 /// The group index of "arguments" in [_pattern], or -1 if _pattern has no
1550 /// match for "arguments".
1551 final int _arguments;
1552
1553 /// The group index of "argumentsExpr" in [_pattern], or -1 if _pattern has
1554 /// no match for "argumentsExpr".
1555 final int _argumentsExpr;
1556
1557 /// The group index of "expr" in [_pattern], or -1 if _pattern has no match
1558 /// for "expr".
1559 final int _expr;
1560
1561 /// The group index of "method" in [_pattern], or -1 if _pattern has no match
1562 /// for "method".
1563 final int _method;
1564
1565 /// The group index of "receiver" in [_pattern], or -1 if _pattern has no
1566 /// match for "receiver".
1567 final int _receiver;
1568
1569 /// Pattern used to recognize a NoSuchMethodError error (and
1570 /// possibly extract the method name).
1571 static final TypeErrorDecoder noSuchMethodPattern =
1572 extractPattern(provokeCallErrorOn(buildJavaScriptObject()));
1573
1574 /// Pattern used to recognize an "object not a closure" error (and
1575 /// possibly extract the method name).
1576 static final TypeErrorDecoder notClosurePattern =
1577 extractPattern(provokeCallErrorOn(buildJavaScriptObjectWithNonClosure()));
1578
1579 /// Pattern used to recognize a NoSuchMethodError on JavaScript null
1580 /// call.
1581 static final TypeErrorDecoder nullCallPattern =
1582 extractPattern(provokeCallErrorOn(JS('', 'null')));
1583
1584 /// Pattern used to recognize a NoSuchMethodError on JavaScript literal null
1585 /// call.
1586 static final TypeErrorDecoder nullLiteralCallPattern =
1587 extractPattern(provokeCallErrorOnNull());
1588
1589 /// Pattern used to recognize a NoSuchMethodError on JavaScript
1590 /// undefined call.
1591 static final TypeErrorDecoder undefinedCallPattern =
1592 extractPattern(provokeCallErrorOn(JS('', 'void 0')));
1593
1594 /// Pattern used to recognize a NoSuchMethodError on JavaScript literal
1595 /// undefined call.
1596 static final TypeErrorDecoder undefinedLiteralCallPattern =
1597 extractPattern(provokeCallErrorOnUndefined());
1598
1599 /// Pattern used to recognize a NoSuchMethodError on JavaScript null
1600 /// property access.
1601 static final TypeErrorDecoder nullPropertyPattern =
1602 extractPattern(provokePropertyErrorOn(JS('', 'null')));
1603
1604 /// Pattern used to recognize a NoSuchMethodError on JavaScript literal null
1605 /// property access.
1606 static final TypeErrorDecoder nullLiteralPropertyPattern =
1607 extractPattern(provokePropertyErrorOnNull());
1608
1609 /// Pattern used to recognize a NoSuchMethodError on JavaScript
1610 /// undefined property access.
1611 static final TypeErrorDecoder undefinedPropertyPattern =
1612 extractPattern(provokePropertyErrorOn(JS('', 'void 0')));
1613
1614 /// Pattern used to recognize a NoSuchMethodError on JavaScript literal
1615 /// undefined property access.
1616 static final TypeErrorDecoder undefinedLiteralPropertyPattern =
1617 extractPattern(provokePropertyErrorOnUndefined());
1618
1619 TypeErrorDecoder(this._arguments,
1620 this._argumentsExpr,
1621 this._expr,
1622 this._method,
1623 this._receiver,
1624 this._pattern);
1625
1626 /// Returns a JavaScript object literal (map) with at most the
1627 /// following keys:
1628 ///
1629 /// * arguments: The arguments as formatted by the JavaScript
1630 /// engine. No browsers are known to provide this information.
1631 ///
1632 /// * argumentsExpr: The syntax of the arguments (JavaScript source
1633 /// code). No browsers are known to provide this information.
1634 ///
1635 /// * expr: The syntax of the receiver expression (JavaScript source
1636 /// code). Firefox provides this information, for example: "$expr$.$method$
1637 /// is not a function".
1638 ///
1639 /// * method: The name of the called method (mangled name). At least Firefox
1640 /// and Chrome/V8 provides this information, for example, "Object [object
1641 /// Object] has no method '$method$'".
1642 ///
1643 /// * receiver: The string representation of the receiver. Chrome/V8
1644 /// used to provide this information (by calling user-defined
1645 /// JavaScript toString on receiver), but it has degenerated into
1646 /// "[object Object]" in recent versions.
1647 matchTypeError(message) {
1648 var match = JS('JSExtendableArray|Null',
1649 'new RegExp(#).exec(#)', _pattern, message);
1650 if (match == null) return null;
1651 var result = JS('', 'Object.create(null)');
1652 if (_arguments != -1) {
1653 JS('', '#.arguments = #[# + 1]', result, match, _arguments);
1654 }
1655 if (_argumentsExpr != -1) {
1656 JS('', '#.argumentsExpr = #[# + 1]', result, match, _argumentsExpr);
1657 }
1658 if (_expr != -1) {
1659 JS('', '#.expr = #[# + 1]', result, match, _expr);
1660 }
1661 if (_method != -1) {
1662 JS('', '#.method = #[# + 1]', result, match, _method);
1663 }
1664 if (_receiver != -1) {
1665 JS('', '#.receiver = #[# + 1]', result, match, _receiver);
1666 }
1667
1668 return result;
1669 }
1670
1671 /// Builds a JavaScript Object with a toString method saying
1672 /// r"$receiver$".
1673 static buildJavaScriptObject() {
1674 return JS('', r'{ toString: function() { return "$receiver$"; } }');
1675 }
1676
1677 /// Builds a JavaScript Object with a toString method saying
1678 /// r"$receiver$". The property "$method" is defined, but is not a function.
1679 static buildJavaScriptObjectWithNonClosure() {
1680 return JS('', r'{ $method$: null, '
1681 r'toString: function() { return "$receiver$"; } }');
1682 }
1683
1684 /// Extract a pattern from a JavaScript TypeError message.
1685 ///
1686 /// The patterns are extracted by forcing TypeErrors on known
1687 /// objects thus forcing known strings into the error message. The
1688 /// known strings are then replaced with wildcards which in theory
1689 /// makes it possible to recognize the desired information even if
1690 /// the error messages are reworded or translated.
1691 static extractPattern(String message) {
1692 // Some JavaScript implementations (V8 at least) include a
1693 // representation of the receiver in the error message, however,
1694 // this representation is not always [: receiver.toString() :],
1695 // sometimes it is [: Object.prototype.toString(receiver) :], and
1696 // sometimes it is an implementation specific method (but that
1697 // doesn't seem to happen for object literals). So sometimes we
1698 // get the text "[object Object]". The shortest way to get that
1699 // string is using "String({})".
1700 // See: http://code.google.com/p/v8/issues/detail?id=2519.
1701 message = JS('String', r"#.replace(String({}), '$receiver$')", message);
1702
1703 // Since we want to create a new regular expression from an unknown string,
1704 // we must escape all regular expression syntax.
1705 message = JS('String', r"#.replace(new RegExp(#, 'g'), '\\$&')",
1706 message, ESCAPE_REGEXP);
1707
1708 // Look for the special pattern \$camelCase\$ (all the $ symbols
1709 // have been escaped already), as we will soon be inserting
1710 // regular expression syntax that we want interpreted by RegExp.
1711 List<String> match =
1712 JS('JSExtendableArray|Null', r"#.match(/\\\$[a-zA-Z]+\\\$/g)", message);
1713 if (match == null) match = [];
1714
1715 // Find the positions within the substring matches of the error message
1716 // components. This will help us extract information later, such as the
1717 // method name.
1718 int arguments = JS('int', '#.indexOf(#)', match, r'\$arguments\$');
1719 int argumentsExpr = JS('int', '#.indexOf(#)', match, r'\$argumentsExpr\$');
1720 int expr = JS('int', '#.indexOf(#)', match, r'\$expr\$');
1721 int method = JS('int', '#.indexOf(#)', match, r'\$method\$');
1722 int receiver = JS('int', '#.indexOf(#)', match, r'\$receiver\$');
1723
1724 // Replace the patterns with a regular expression wildcard.
1725 // Note: in a perfect world, one would use "(.*)", but not in
1726 // JavaScript, "." does not match newlines.
1727 String pattern = JS('String',
1728 r"#.replace('\\$arguments\\$', '((?:x|[^x])*)')"
1729 r".replace('\\$argumentsExpr\\$', '((?:x|[^x])*)')"
1730 r".replace('\\$expr\\$', '((?:x|[^x])*)')"
1731 r".replace('\\$method\\$', '((?:x|[^x])*)')"
1732 r".replace('\\$receiver\\$', '((?:x|[^x])*)')",
1733 message);
1734
1735 return new TypeErrorDecoder(arguments,
1736 argumentsExpr,
1737 expr,
1738 method,
1739 receiver,
1740 pattern);
1741 }
1742
1743 /// Provokes a TypeError and returns its message.
1744 ///
1745 /// The error is provoked so all known variable content can be recognized and
1746 /// a pattern can be inferred.
1747 static String provokeCallErrorOn(expression) {
1748 // This function is carefully created to maximize the possibility
1749 // of decoding the TypeError message and turning it into a general
1750 // pattern.
1751 //
1752 // The idea is to inject something known into something unknown. The
1753 // unknown entity is the error message that the browser provides with a
1754 // TypeError. It is a human readable message, possibly localized in a
1755 // language no dart2js engineer understand. We assume that $name$ would
1756 // never naturally occur in a human readable error message, yet it is easy
1757 // to decode.
1758 //
1759 // For example, evaluate this in V8 version 3.13.7.6:
1760 //
1761 // var $expr$ = null; $expr$.$method$()
1762 //
1763 // The VM throws an instance of TypeError whose message property contains
1764 // "Cannot call method '$method$' of null". We can then reasonably assume
1765 // that if the string contains $method$, that's where the method name will
1766 // be in general. Call this automatically reverse engineering the error
1767 // format string in V8.
1768 //
1769 // So the error message from V8 is turned into this regular expression:
1770 //
1771 // "Cannot call method '(.*)' of null"
1772 //
1773 // Similarly, if we evaluate:
1774 //
1775 // var $expr$ = {toString: function() { return '$receiver$'; }};
1776 // $expr$.$method$()
1777 //
1778 // We get this message: "Object $receiver$ has no method '$method$'"
1779 //
1780 // Which is turned into this regular expression:
1781 //
1782 // "Object (.*) has no method '(.*)'"
1783 //
1784 // Firefox/jsshell is slightly different, it tries to include the source
1785 // code that caused the exception, so we get this message: "$expr$.$method$
1786 // is not a function" which is turned into this regular expression:
1787 //
1788 // "(.*)\\.(.*) is not a function"
1789
1790 var function = JS('', r"""function($expr$) {
1791 var $argumentsExpr$ = '$arguments$';
1792 try {
1793 $expr$.$method$($argumentsExpr$);
1794 } catch (e) {
1795 return e.message;
1796 }
1797 }""");
1798 return JS('String', '(#)(#)', function, expression);
1799 }
1800
1801 /// Similar to [provokeCallErrorOn], but provokes an error directly on
1802 /// literal "null" expression.
1803 static String provokeCallErrorOnNull() {
1804 // See [provokeCallErrorOn] for a detailed explanation.
1805 var function = JS('', r"""function() {
1806 var $argumentsExpr$ = '$arguments$';
1807 try {
1808 null.$method$($argumentsExpr$);
1809 } catch (e) {
1810 return e.message;
1811 }
1812 }""");
1813 return JS('String', '(#)()', function);
1814 }
1815
1816 /// Similar to [provokeCallErrorOnNull], but provokes an error directly on
1817 /// (void 0), that is, "undefined".
1818 static String provokeCallErrorOnUndefined() {
1819 // See [provokeCallErrorOn] for a detailed explanation.
1820 var function = JS('', r"""function() {
1821 var $argumentsExpr$ = '$arguments$';
1822 try {
1823 (void 0).$method$($argumentsExpr$);
1824 } catch (e) {
1825 return e.message;
1826 }
1827 }""");
1828 return JS('String', '(#)()', function);
1829 }
1830
1831 /// Similar to [provokeCallErrorOn], but provokes a property access
1832 /// error.
1833 static String provokePropertyErrorOn(expression) {
1834 // See [provokeCallErrorOn] for a detailed explanation.
1835 var function = JS('', r"""function($expr$) {
1836 try {
1837 $expr$.$method$;
1838 } catch (e) {
1839 return e.message;
1840 }
1841 }""");
1842 return JS('String', '(#)(#)', function, expression);
1843 }
1844
1845 /// Similar to [provokePropertyErrorOn], but provokes an property access
1846 /// error directly on literal "null" expression.
1847 static String provokePropertyErrorOnNull() {
1848 // See [provokeCallErrorOn] for a detailed explanation.
1849 var function = JS('', r"""function() {
1850 try {
1851 null.$method$;
1852 } catch (e) {
1853 return e.message;
1854 }
1855 }""");
1856 return JS('String', '(#)()', function);
1857 }
1858
1859 /// Similar to [provokePropertyErrorOnNull], but provokes an property access
1860 /// error directly on (void 0), that is, "undefined".
1861 static String provokePropertyErrorOnUndefined() {
1862 // See [provokeCallErrorOn] for a detailed explanation.
1863 var function = JS('', r"""function() {
1864 try {
1865 (void 0).$method$;
1866 } catch (e) {
1867 return e.message;
1868 }
1869 }""");
1870 return JS('String', '(#)()', function);
1871 }
1872 }
1873
1874 class NullError extends Error implements NoSuchMethodError {
1875 final String _message;
1876 final String _method;
1877
1878 NullError(this._message, match)
1879 : _method = match == null ? null : JS('', '#.method', match);
1880
1881 String toString() {
1882 if (_method == null) return 'NullError: $_message';
1883 return "NullError: method not found: '$_method' on null";
1884 }
1885 }
1886
1887 class JsNoSuchMethodError extends Error implements NoSuchMethodError {
1888 final String _message;
1889 final String _method;
1890 final String _receiver;
1891
1892 JsNoSuchMethodError(this._message, match)
1893 : _method = match == null ? null : JS('String|Null', '#.method', match),
1894 _receiver =
1895 match == null ? null : JS('String|Null', '#.receiver', match);
1896
1897 String toString() {
1898 if (_method == null) return 'NoSuchMethodError: $_message';
1899 if (_receiver == null) {
1900 return "NoSuchMethodError: method not found: '$_method' ($_message)";
1901 }
1902 return "NoSuchMethodError: "
1903 "method not found: '$_method' on '$_receiver' ($_message)";
1904 }
1905 }
1906
1907 class UnknownJsTypeError extends Error {
1908 final String _message;
1909
1910 UnknownJsTypeError(this._message);
1911
1912 String toString() => _message.isEmpty ? 'Error' : 'Error: $_message';
1913 }
1914
1915 /**
1916 * Called from catch blocks in generated code to extract the Dart
1917 * exception from the thrown value. The thrown value may have been
1918 * created by [wrapException] or it may be a 'native' JS exception.
1919 *
1920 * Some native exceptions are mapped to new Dart instances, others are
1921 * returned unmodified.
1922 */
1923 unwrapException(ex) {
1924 /// If error implements Error, save [ex] in [error.$thrownJsError].
1925 /// Otherwise, do nothing. Later, the stack trace can then be extraced from
1926 /// [ex].
1927 saveStackTrace(error) {
1928 if (error is Error) {
1929 var thrownStackTrace = JS('', r'#.$thrownJsError', error);
1930 if (thrownStackTrace == null) {
1931 JS('void', r'#.$thrownJsError = #', error, ex);
1932 }
1933 }
1934 return error;
1935 }
1936
1937 // Note that we are checking if the object has the property. If it
1938 // has, it could be set to null if the thrown value is null.
1939 if (ex == null) return null;
1940 if (ex is ExceptionAndStackTrace) {
1941 return saveStackTrace(ex.dartException);
1942 }
1943 if (JS('bool', 'typeof # !== "object"', ex)) return ex;
1944
1945 if (JS('bool', r'"dartException" in #', ex)) {
1946 return saveStackTrace(JS('', r'#.dartException', ex));
1947 } else if (!JS('bool', r'"message" in #', ex)) {
1948 return ex;
1949 }
1950
1951 // Grab hold of the exception message. This field is available on
1952 // all supported browsers.
1953 var message = JS('var', r'#.message', ex);
1954
1955 // Internet Explorer has an error number. This is the most reliable way to
1956 // detect specific errors, so check for this first.
1957 if (JS('bool', '"number" in #', ex)
1958 && JS('bool', 'typeof #.number == "number"', ex)) {
1959 int number = JS('int', '#.number', ex);
1960
1961 // From http://msdn.microsoft.com/en-us/library/ie/hc53e755(v=vs.94).aspx
1962 // "number" is a 32-bit word. The error code is the low 16 bits, and the
1963 // facility code is the upper 16 bits.
1964 var ieErrorCode = number & 0xffff;
1965 var ieFacilityNumber = (number >> 16) & 0x1fff;
1966
1967 // http://msdn.microsoft.com/en-us/library/aa264975(v=vs.60).aspx
1968 // http://msdn.microsoft.com/en-us/library/ie/1dk3k160(v=vs.94).aspx
1969 if (ieFacilityNumber == 10) {
1970 switch (ieErrorCode) {
1971 case 438:
1972 return saveStackTrace(
1973 new JsNoSuchMethodError('$message (Error $ieErrorCode)', null));
1974 case 445:
1975 case 5007:
1976 return saveStackTrace(
1977 new NullError('$message (Error $ieErrorCode)', null));
1978 }
1979 }
1980 }
1981
1982 if (JS('bool', r'# instanceof TypeError', ex)) {
1983 var match;
1984 // Using JS to give type hints to the compiler to help tree-shaking.
1985 // TODO(ahe): That should be unnecessary due to type inference.
1986 var nsme = TypeErrorDecoder.noSuchMethodPattern;
1987 var notClosure = TypeErrorDecoder.notClosurePattern;
1988 var nullCall = TypeErrorDecoder.nullCallPattern;
1989 var nullLiteralCall = TypeErrorDecoder.nullLiteralCallPattern;
1990 var undefCall = TypeErrorDecoder.undefinedCallPattern;
1991 var undefLiteralCall = TypeErrorDecoder.undefinedLiteralCallPattern;
1992 var nullProperty = TypeErrorDecoder.nullPropertyPattern;
1993 var nullLiteralProperty = TypeErrorDecoder.nullLiteralPropertyPattern;
1994 var undefProperty = TypeErrorDecoder.undefinedPropertyPattern;
1995 var undefLiteralProperty =
1996 TypeErrorDecoder.undefinedLiteralPropertyPattern;
1997 if ((match = nsme.matchTypeError(message)) != null) {
1998 return saveStackTrace(new JsNoSuchMethodError(message, match));
1999 } else if ((match = notClosure.matchTypeError(message)) != null) {
2000 // notClosure may match "({c:null}).c()" or "({c:1}).c()", so we
2001 // cannot tell if this an attempt to invoke call on null or a
2002 // non-function object.
2003 // But we do know the method name is "call".
2004 JS('', '#.method = "call"', match);
2005 return saveStackTrace(new JsNoSuchMethodError(message, match));
2006 } else if ((match = nullCall.matchTypeError(message)) != null ||
2007 (match = nullLiteralCall.matchTypeError(message)) != null ||
2008 (match = undefCall.matchTypeError(message)) != null ||
2009 (match = undefLiteralCall.matchTypeError(message)) != null ||
2010 (match = nullProperty.matchTypeError(message)) != null ||
2011 (match = nullLiteralCall.matchTypeError(message)) != null ||
2012 (match = undefProperty.matchTypeError(message)) != null ||
2013 (match = undefLiteralProperty.matchTypeError(message)) != null) {
2014 return saveStackTrace(new NullError(message, match));
2015 }
2016
2017 // If we cannot determine what kind of error this is, we fall back
2018 // to reporting this as a generic error. It's probably better than
2019 // nothing.
2020 return saveStackTrace(
2021 new UnknownJsTypeError(message is String ? message : ''));
2022 }
2023
2024 if (JS('bool', r'# instanceof RangeError', ex)) {
2025 if (message is String && contains(message, 'call stack')) {
2026 return new StackOverflowError();
2027 }
2028
2029 // In general, a RangeError is thrown when trying to pass a number as an
2030 // argument to a function that does not allow a range that includes that
2031 // number. Translate to a Dart ArgumentError with the same message.
2032 // TODO(sra): Translate to RangeError.
2033 String message = tryStringifyException(ex);
2034 if (message is String) {
2035 message = JS('String', r'#.replace(/^RangeError:\s*/, "")', message);
2036 }
2037 return saveStackTrace(new ArgumentError(message));
2038 }
2039
2040 // Check for the Firefox specific stack overflow signal.
2041 if (JS('bool',
2042 r'typeof InternalError == "function" && # instanceof InternalError',
2043 ex)) {
2044 if (message is String && message == 'too much recursion') {
2045 return new StackOverflowError();
2046 }
2047 }
2048
2049 // Just return the exception. We should not wrap it because in case
2050 // the exception comes from the DOM, it is a JavaScript
2051 // object backed by a native Dart class.
2052 return ex;
2053 }
2054
2055 String tryStringifyException(ex) {
2056 // Since this function is called from [unwrapException] which is called from
2057 // code injected into a catch-clause, use JavaScript try-catch to avoid a
2058 // potential loop if stringifying crashes.
2059 return JS('String|Null', r'''
2060 (function(ex) {
2061 try {
2062 return String(ex);
2063 } catch (e) {}
2064 return null;
2065 })(#)
2066 ''', ex);
2067 }
2068
2069 /**
2070 * Called by generated code to fetch the stack trace from an
2071 * exception. Should never return null.
2072 */
2073 StackTrace getTraceFromException(exception) {
2074 if (exception is ExceptionAndStackTrace) {
2075 return exception.stackTrace;
2076 }
2077 if (exception == null) return new _StackTrace(exception);
2078 _StackTrace trace = JS('_StackTrace|Null', r'#.$cachedTrace', exception);
2079 if (trace != null) return trace;
2080 trace = new _StackTrace(exception);
2081 return JS('_StackTrace', r'#.$cachedTrace = #', exception, trace);
2082 }
2083
2084 class _StackTrace implements StackTrace {
2085 var _exception;
2086 String _trace;
2087 _StackTrace(this._exception);
2088
2089 String toString() {
2090 if (_trace != null) return JS('String', '#', _trace);
2091
2092 String trace;
2093 if (JS('bool', '# !== null', _exception) &&
2094 JS('bool', 'typeof # === "object"', _exception)) {
2095 trace = JS("String|Null", r"#.stack", _exception);
2096 }
2097 return _trace = (trace == null) ? '' : trace;
2098 }
2099 }
2100
2101 int objectHashCode(var object) {
2102 if (object == null || JS('bool', "typeof # != 'object'", object)) {
2103 return object.hashCode;
2104 } else {
2105 return Primitives.objectHashCode(object);
2106 }
2107 }
2108
2109 /**
2110 * Called by generated code to build a map literal. [keyValuePairs] is
2111 * a list of key, value, key, value, ..., etc.
2112 */
2113 fillLiteralMap(keyValuePairs, Map result) {
2114 // TODO(johnniwinther): Use JSArray to optimize this code instead of calling
2115 // [getLength] and [getIndex].
2116 int index = 0;
2117 int length = getLength(keyValuePairs);
2118 while (index < length) {
2119 var key = getIndex(keyValuePairs, index++);
2120 var value = getIndex(keyValuePairs, index++);
2121 result[key] = value;
2122 }
2123 return result;
2124 }
2125
2126 invokeClosure(Function closure,
2127 var isolate,
2128 int numberOfArguments,
2129 var arg1,
2130 var arg2,
2131 var arg3,
2132 var arg4) {
2133 if (numberOfArguments == 0) {
2134 return JS_CALL_IN_ISOLATE(isolate, () => closure());
2135 } else if (numberOfArguments == 1) {
2136 return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1));
2137 } else if (numberOfArguments == 2) {
2138 return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2));
2139 } else if (numberOfArguments == 3) {
2140 return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2, arg3));
2141 } else if (numberOfArguments == 4) {
2142 return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2, arg3, arg4));
2143 } else {
2144 throw new Exception(
2145 'Unsupported number of arguments for wrapped closure');
2146 }
2147 }
2148
2149 /**
2150 * Called by generated code to convert a Dart closure to a JS
2151 * closure when the Dart closure is passed to the DOM.
2152 */
2153 convertDartClosureToJS(closure, int arity) {
2154 if (closure == null) return null;
2155 var function = JS('var', r'#.$identity', closure);
2156 if (JS('bool', r'!!#', function)) return function;
2157
2158 // We use $0 and $1 to not clash with variable names used by the
2159 // compiler and/or minifier.
2160 function = JS('var',
2161 '(function(closure, arity, context, invoke) {'
2162 ' return function(a1, a2, a3, a4) {'
2163 ' return invoke(closure, context, arity, a1, a2, a3, a4);'
2164 ' };'
2165 '})(#,#,#,#)',
2166 closure,
2167 arity,
2168 // Capture the current isolate now. Remember that "#"
2169 // in JS is simply textual substitution of compiled
2170 // expressions.
2171 JS_CURRENT_ISOLATE_CONTEXT(),
2172 DART_CLOSURE_TO_JS(invokeClosure));
2173
2174 JS('void', r'#.$identity = #', closure, function);
2175 return function;
2176 }
2177
2178 /**
2179 * Super class for Dart closures.
2180 */
2181 abstract class Closure implements Function {
2182 // TODO(ahe): These constants must be in sync with
2183 // reflection_data_parser.dart.
2184 static const FUNCTION_INDEX = 0;
2185 static const NAME_INDEX = 1;
2186 static const CALL_NAME_INDEX = 2;
2187 static const REQUIRED_PARAMETER_INDEX = 3;
2188 static const OPTIONAL_PARAMETER_INDEX = 4;
2189 static const DEFAULT_ARGUMENTS_INDEX = 5;
2190
2191 /**
2192 * Global counter to prevent reusing function code objects.
2193 *
2194 * V8 will share the underlying function code objects when the same string is
2195 * passed to "new Function". Shared function code objects can lead to
2196 * sub-optimal performance due to polymorhism, and can be prevented by
2197 * ensuring the strings are different.
2198 */
2199 static int functionCounter = 0;
2200
2201 Closure();
2202
2203 /**
2204 * Creates a new closure class for use by implicit getters associated with a
2205 * method.
2206 *
2207 * In other words, creates a tear-off closure.
2208 *
2209 * Called from [closureFromTearOff] as well as from reflection when tearing
2210 * of a method via [:getField:].
2211 *
2212 * This method assumes that [functions] was created by the JavaScript function
2213 * `addStubs` in `reflection_data_parser.dart`. That is, a list of JavaScript
2214 * function objects with properties `$stubName` and `$callName`.
2215 *
2216 * Further assumes that [reflectionInfo] is the end of the array created by
2217 * [dart2js.js_emitter.ContainerBuilder.addMemberMethod] starting with
2218 * required parameter count or, in case of the new emitter, the runtime
2219 * representation of the function's type.
2220 *
2221 * Caution: this function may be called when building constants.
2222 * TODO(ahe): Don't call this function when building constants.
2223 */
2224 static fromTearOff(receiver,
2225 List functions,
2226 var reflectionInfo,
2227 bool isStatic,
2228 jsArguments,
2229 String propertyName) {
2230 JS_EFFECT(() {
2231 BoundClosure.receiverOf(JS('BoundClosure', 'void 0'));
2232 BoundClosure.selfOf(JS('BoundClosure', 'void 0'));
2233 });
2234 // TODO(ahe): All the place below using \$ should be rewritten to go
2235 // through the namer.
2236 var function = JS('', '#[#]', functions, 0);
2237 String name = JS('String|Null', '#.\$stubName', function);
2238 String callName = JS('String|Null', '#[#]', function,
2239 JS_GET_NAME(JsGetName.CALL_NAME_PROPERTY));
2240
2241 // This variable holds either an index into the types-table, or a function
2242 // that can compute a function-rti. (The latter is necessary if the type
2243 // is dependent on generic arguments).
2244 var functionType;
2245 if (reflectionInfo is List) {
2246 JS('', '#.\$reflectionInfo = #', function, reflectionInfo);
2247 ReflectionInfo info = new ReflectionInfo(function);
2248 functionType = info.functionType;
2249 } else {
2250 functionType = reflectionInfo;
2251 }
2252
2253
2254 // function tmp() {};
2255 // tmp.prototype = BC.prototype;
2256 // var proto = new tmp;
2257 // for each computed prototype property:
2258 // proto[property] = ...;
2259 // proto._init = BC;
2260 // var dynClosureConstructor =
2261 // new Function('self', 'target', 'receiver', 'name',
2262 // 'this._init(self, target, receiver, name)');
2263 // proto.constructor = dynClosureConstructor;
2264 // dynClosureConstructor.prototype = proto;
2265 // return dynClosureConstructor;
2266
2267 // We need to create a new subclass of TearOffClosure, one of StaticClosure
2268 // or BoundClosure. For this, we need to create an object whose prototype
2269 // is the prototype is either StaticClosure.prototype or
2270 // BoundClosure.prototype, respectively in pseudo JavaScript code. The
2271 // simplest way to access the JavaScript construction function of a Dart
2272 // class is to create an instance and access its constructor property.
2273 // Creating an instance ensures that any lazy class initialization has taken
2274 // place. The newly created instance could in theory be used directly as the
2275 // prototype, but it might include additional fields that we don't need. So
2276 // we only use the new instance to access the constructor property and use
2277 // Object.create to create the desired prototype.
2278 //
2279 // TODO(sra): Perhaps cache the prototype to avoid the allocation.
2280 var prototype = isStatic
2281 ? JS('StaticClosure', 'Object.create(#.constructor.prototype)',
2282 new StaticClosure())
2283 : JS('BoundClosure', 'Object.create(#.constructor.prototype)',
2284 new BoundClosure(null, null, null, null));
2285
2286 JS('', '#.\$initialize = #', prototype, JS('', '#.constructor', prototype));
2287 var constructor = isStatic
2288 ? JS('', 'function(){this.\$initialize()}')
2289 : isCsp
2290 ? JS('', 'function(a,b,c,d) {this.\$initialize(a,b,c,d)}')
2291 : JS('',
2292 'new Function("a,b,c,d", "this.\$initialize(a,b,c,d);" + #)',
2293 functionCounter++);
2294
2295 // It is necessary to set the constructor property, otherwise it will be
2296 // "Object".
2297 JS('', '#.constructor = #', prototype, constructor);
2298
2299 JS('', '#.prototype = #', constructor, prototype);
2300
2301 // Create a closure and "monkey" patch it with call stubs.
2302 var trampoline = function;
2303 var isIntercepted = false;
2304 if (!isStatic) {
2305 if (JS('bool', '#.length == 1', jsArguments)) {
2306 // Intercepted call.
2307 isIntercepted = true;
2308 }
2309 trampoline = forwardCallTo(receiver, function, isIntercepted);
2310 JS('', '#.\$reflectionInfo = #', trampoline, reflectionInfo);
2311 } else {
2312 JS('', '#.\$name = #', prototype, propertyName);
2313 }
2314
2315 var signatureFunction;
2316 if (JS('bool', 'typeof # == "number"', functionType)) {
2317 // We cannot call [getType] here, since the types-metadata might not be
2318 // set yet. This is, because fromTearOff might be called for constants
2319 // when the program isn't completely set up yet.
2320 //
2321 // Note that we cannot just textually inline the call
2322 // `getType(functionType)` since we cannot guarantee that the (then)
2323 // captured variable `functionType` isn't reused.
2324 signatureFunction =
2325 JS('',
2326 '''(function(t) {
2327 return function(){ return #(t); };
2328 })(#)''',
2329 RAW_DART_FUNCTION_REF(getType),
2330 functionType);
2331 } else if (!isStatic
2332 && JS('bool', 'typeof # == "function"', functionType)) {
2333 var getReceiver = isIntercepted
2334 ? RAW_DART_FUNCTION_REF(BoundClosure.receiverOf)
2335 : RAW_DART_FUNCTION_REF(BoundClosure.selfOf);
2336 signatureFunction = JS(
2337 '',
2338 'function(f,r){'
2339 'return function(){'
2340 'return f.apply({\$receiver:r(this)},arguments)'
2341 '}'
2342 '}(#,#)', functionType, getReceiver);
2343 } else {
2344 throw 'Error in reflectionInfo.';
2345 }
2346
2347 JS('', '#[#] = #', prototype, JS_GET_NAME(JsGetName.SIGNATURE_NAME),
2348 signatureFunction);
2349
2350 JS('', '#[#] = #', prototype, callName, trampoline);
2351 for (int i = 1; i < functions.length; i++) {
2352 var stub = functions[i];
2353 var stubCallName = JS('String|Null', '#[#]', stub,
2354 JS_GET_NAME(JsGetName.CALL_NAME_PROPERTY));
2355 if (stubCallName != null) {
2356 JS('', '#[#] = #', prototype, stubCallName,
2357 isStatic ? stub : forwardCallTo(receiver, stub, isIntercepted));
2358 }
2359 }
2360
2361 JS('', '#[#] = #', prototype, JS_GET_NAME(JsGetName.CALL_CATCH_ALL),
2362 trampoline);
2363 String reqArgProperty = JS_GET_NAME(JsGetName.REQUIRED_PARAMETER_PROPERTY);
2364 String defValProperty = JS_GET_NAME(JsGetName.DEFAULT_VALUES_PROPERTY);
2365 JS('', '#.# = #.#', prototype, reqArgProperty, function, reqArgProperty);
2366 JS('', '#.# = #.#', prototype, defValProperty, function, defValProperty);
2367
2368 return constructor;
2369 }
2370
2371 static cspForwardCall(int arity, bool isSuperCall, String stubName,
2372 function) {
2373 var getSelf = RAW_DART_FUNCTION_REF(BoundClosure.selfOf);
2374 // Handle intercepted stub-names with the default slow case.
2375 if (isSuperCall) arity = -1;
2376 switch (arity) {
2377 case 0:
2378 return JS(
2379 '',
2380 'function(n,S){'
2381 'return function(){'
2382 'return S(this)[n]()'
2383 '}'
2384 '}(#,#)', stubName, getSelf);
2385 case 1:
2386 return JS(
2387 '',
2388 'function(n,S){'
2389 'return function(a){'
2390 'return S(this)[n](a)'
2391 '}'
2392 '}(#,#)', stubName, getSelf);
2393 case 2:
2394 return JS(
2395 '',
2396 'function(n,S){'
2397 'return function(a,b){'
2398 'return S(this)[n](a,b)'
2399 '}'
2400 '}(#,#)', stubName, getSelf);
2401 case 3:
2402 return JS(
2403 '',
2404 'function(n,S){'
2405 'return function(a,b,c){'
2406 'return S(this)[n](a,b,c)'
2407 '}'
2408 '}(#,#)', stubName, getSelf);
2409 case 4:
2410 return JS(
2411 '',
2412 'function(n,S){'
2413 'return function(a,b,c,d){'
2414 'return S(this)[n](a,b,c,d)'
2415 '}'
2416 '}(#,#)', stubName, getSelf);
2417 case 5:
2418 return JS(
2419 '',
2420 'function(n,S){'
2421 'return function(a,b,c,d,e){'
2422 'return S(this)[n](a,b,c,d,e)'
2423 '}'
2424 '}(#,#)', stubName, getSelf);
2425 default:
2426 return JS(
2427 '',
2428 'function(f,s){'
2429 'return function(){'
2430 'return f.apply(s(this),arguments)'
2431 '}'
2432 '}(#,#)', function, getSelf);
2433 }
2434 }
2435
2436 static bool get isCsp => JS_GET_FLAG("USE_CONTENT_SECURITY_POLICY");
2437
2438 static forwardCallTo(receiver, function, bool isIntercepted) {
2439 if (isIntercepted) return forwardInterceptedCallTo(receiver, function);
2440 String stubName = JS('String|Null', '#.\$stubName', function);
2441 int arity = JS('int', '#.length', function);
2442 var lookedUpFunction = JS("", "#[#]", receiver, stubName);
2443 // The receiver[stubName] may not be equal to the function if we try to
2444 // forward to a super-method. Especially when we create a bound closure
2445 // of a super-call we need to make sure that we don't forward back to the
2446 // dynamically looked up function.
2447 bool isSuperCall = !identical(function, lookedUpFunction);
2448
2449 if (isCsp || isSuperCall || arity >= 27) {
2450 return cspForwardCall(arity, isSuperCall, stubName, function);
2451 }
2452
2453 if (arity == 0) {
2454 return JS(
2455 '',
2456 '(new Function(#))()',
2457 'return function(){'
2458 'return this.${BoundClosure.selfFieldName()}.$stubName();'
2459 '${functionCounter++}'
2460 '}');
2461 }
2462 assert (1 <= arity && arity < 27);
2463 String arguments = JS(
2464 'String',
2465 '"abcdefghijklmnopqrstuvwxyz".split("").splice(0,#).join(",")',
2466 arity);
2467 return JS(
2468 '',
2469 '(new Function(#))()',
2470 'return function($arguments){'
2471 'return this.${BoundClosure.selfFieldName()}.$stubName($arguments);'
2472 '${functionCounter++}'
2473 '}');
2474 }
2475
2476 static cspForwardInterceptedCall(int arity, bool isSuperCall,
2477 String name, function) {
2478 var getSelf = RAW_DART_FUNCTION_REF(BoundClosure.selfOf);
2479 var getReceiver = RAW_DART_FUNCTION_REF(BoundClosure.receiverOf);
2480 // Handle intercepted stub-names with the default slow case.
2481 if (isSuperCall) arity = -1;
2482 switch (arity) {
2483 case 0:
2484 // Intercepted functions always takes at least one argument (the
2485 // receiver).
2486 throw new RuntimeError('Intercepted function with no arguments.');
2487 case 1:
2488 return JS(
2489 '',
2490 'function(n,s,r){'
2491 'return function(){'
2492 'return s(this)[n](r(this))'
2493 '}'
2494 '}(#,#,#)', name, getSelf, getReceiver);
2495 case 2:
2496 return JS(
2497 '',
2498 'function(n,s,r){'
2499 'return function(a){'
2500 'return s(this)[n](r(this),a)'
2501 '}'
2502 '}(#,#,#)', name, getSelf, getReceiver);
2503 case 3:
2504 return JS(
2505 '',
2506 'function(n,s,r){'
2507 'return function(a,b){'
2508 'return s(this)[n](r(this),a,b)'
2509 '}'
2510 '}(#,#,#)', name, getSelf, getReceiver);
2511 case 4:
2512 return JS(
2513 '',
2514 'function(n,s,r){'
2515 'return function(a,b,c){'
2516 'return s(this)[n](r(this),a,b,c)'
2517 '}'
2518 '}(#,#,#)', name, getSelf, getReceiver);
2519 case 5:
2520 return JS(
2521 '',
2522 'function(n,s,r){'
2523 'return function(a,b,c,d){'
2524 'return s(this)[n](r(this),a,b,c,d)'
2525 '}'
2526 '}(#,#,#)', name, getSelf, getReceiver);
2527 case 6:
2528 return JS(
2529 '',
2530 'function(n,s,r){'
2531 'return function(a,b,c,d,e){'
2532 'return s(this)[n](r(this),a,b,c,d,e)'
2533 '}'
2534 '}(#,#,#)', name, getSelf, getReceiver);
2535 default:
2536 return JS(
2537 '',
2538 'function(f,s,r,a){'
2539 'return function(){'
2540 'a=[r(this)];'
2541 'Array.prototype.push.apply(a,arguments);'
2542 'return f.apply(s(this),a)'
2543 '}'
2544 '}(#,#,#)', function, getSelf, getReceiver);
2545 }
2546 }
2547
2548 static forwardInterceptedCallTo(receiver, function) {
2549 String selfField = BoundClosure.selfFieldName();
2550 String receiverField = BoundClosure.receiverFieldName();
2551 String stubName = JS('String|Null', '#.\$stubName', function);
2552 int arity = JS('int', '#.length', function);
2553 bool isCsp = JS_GET_FLAG("USE_CONTENT_SECURITY_POLICY");
2554 var lookedUpFunction = JS("", "#[#]", receiver, stubName);
2555 // The receiver[stubName] may not be equal to the function if we try to
2556 // forward to a super-method. Especially when we create a bound closure
2557 // of a super-call we need to make sure that we don't forward back to the
2558 // dynamically looked up function.
2559 bool isSuperCall = !identical(function, lookedUpFunction);
2560
2561 if (isCsp || isSuperCall || arity >= 28) {
2562 return cspForwardInterceptedCall(arity, isSuperCall, stubName,
2563 function);
2564 }
2565 if (arity == 1) {
2566 return JS(
2567 '',
2568 '(new Function(#))()',
2569 'return function(){'
2570 'return this.$selfField.$stubName(this.$receiverField);'
2571 '${functionCounter++}'
2572 '}');
2573 }
2574 assert(1 < arity && arity < 28);
2575 String arguments = JS(
2576 'String',
2577 '"abcdefghijklmnopqrstuvwxyz".split("").splice(0,#).join(",")',
2578 arity - 1);
2579 return JS(
2580 '',
2581 '(new Function(#))()',
2582 'return function($arguments){'
2583 'return this.$selfField.$stubName(this.$receiverField, $arguments);'
2584 '${functionCounter++}'
2585 '}');
2586 }
2587
2588 // The backend adds a special getter of the form
2589 //
2590 // Closure get call => this;
2591 //
2592 // to allow tearing off a closure from itself. We do this magically in the
2593 // backend rather than simply adding it here, as we do not want this getter
2594 // to be visible to resolution and the generation of extra stubs.
2595
2596 String toString() {
2597 String name = Primitives.objectTypeName(this);
2598 return "Closure '$name'";
2599 }
2600 }
2601
2602 /// Called from implicit method getter (aka tear-off).
2603 closureFromTearOff(receiver,
2604 functions,
2605 reflectionInfo,
2606 isStatic,
2607 jsArguments,
2608 name) {
2609 return Closure.fromTearOff(
2610 receiver,
2611 JSArray.markFixedList(functions),
2612 reflectionInfo is List ? JSArray.markFixedList(reflectionInfo)
2613 : reflectionInfo,
2614 JS('bool', '!!#', isStatic),
2615 jsArguments,
2616 JS('String', '#', name));
2617 }
2618
2619 /// Represents an implicit closure of a function.
2620 abstract class TearOffClosure extends Closure {
2621 }
2622
2623 class StaticClosure extends TearOffClosure {
2624 String toString() {
2625 String name = JS('String|Null', '#.\$name', this);
2626 if (name == null) return "Closure of unknown static method";
2627 return "Closure '$name'";
2628 }
2629 }
2630
2631 /// Represents a 'tear-off' or property extraction closure of an instance
2632 /// method, that is an instance method bound to a specific receiver (instance).
2633 class BoundClosure extends TearOffClosure {
2634 /// The receiver or interceptor.
2635 // TODO(ahe): This could just be the interceptor, we always know if
2636 // we need the interceptor when generating the call method.
2637 final _self;
2638
2639 /// The method.
2640 final _target;
2641
2642 /// The receiver. Null if [_self] is not an interceptor.
2643 final _receiver;
2644
2645 /// The name of the function. Only used by the mirror system.
2646 final String _name;
2647
2648 BoundClosure(this._self, this._target, this._receiver, this._name);
2649
2650 bool operator==(other) {
2651 if (identical(this, other)) return true;
2652 if (other is! BoundClosure) return false;
2653 return JS('bool', '# === # && # === # && # === #',
2654 _self, other._self,
2655 _target, other._target,
2656 _receiver, other._receiver);
2657 }
2658
2659 int get hashCode {
2660 int receiverHashCode;
2661 if (_receiver == null) {
2662 // A bound closure on a regular Dart object, just use the
2663 // identity hash code.
2664 receiverHashCode = Primitives.objectHashCode(_self);
2665 } else if (JS('String', 'typeof #', _receiver) != 'object') {
2666 // A bound closure on a primitive JavaScript type. We
2667 // use the hashCode method we define for those primitive types.
2668 receiverHashCode = _receiver.hashCode;
2669 } else {
2670 // A bound closure on an intercepted native class, just use the
2671 // identity hash code.
2672 receiverHashCode = Primitives.objectHashCode(_receiver);
2673 }
2674 return receiverHashCode ^ Primitives.objectHashCode(_target);
2675 }
2676
2677 toString() {
2678 var receiver = _receiver == null ? _self : _receiver;
2679 return "Closure '$_name' of ${Primitives.objectToHumanReadableString(receive r)}";
2680 }
2681
2682 @NoInline()
2683 static selfOf(BoundClosure closure) => closure._self;
2684
2685 static targetOf(BoundClosure closure) => closure._target;
2686
2687 @NoInline()
2688 static receiverOf(BoundClosure closure) => closure._receiver;
2689
2690 static nameOf(BoundClosure closure) => closure._name;
2691
2692 static String selfFieldNameCache;
2693
2694 static String selfFieldName() {
2695 if (selfFieldNameCache == null) {
2696 selfFieldNameCache = computeFieldNamed('self');
2697 }
2698 return selfFieldNameCache;
2699 }
2700
2701 static String receiverFieldNameCache;
2702
2703 static String receiverFieldName() {
2704 if (receiverFieldNameCache == null) {
2705 receiverFieldNameCache = computeFieldNamed('receiver');
2706 }
2707 return receiverFieldNameCache;
2708 }
2709
2710 @NoInline() @NoSideEffects()
2711 static String computeFieldNamed(String fieldName) {
2712 var template = new BoundClosure('self', 'target', 'receiver', 'name');
2713 var names = JSArray.markFixedList(
2714 JS('', 'Object.getOwnPropertyNames(#)', template));
2715 for (int i = 0; i < names.length; i++) {
2716 var name = names[i];
2717 if (JS('bool', '#[#] === #', template, name, fieldName)) {
2718 return JS('String', '#', name);
2719 }
2720 }
2721 }
2722 }
2723
2724 bool jsHasOwnProperty(var jsObject, String property) {
2725 return JS('bool', r'#.hasOwnProperty(#)', jsObject, property);
2726 }
2727
2728 jsPropertyAccess(var jsObject, String property) {
2729 return JS('var', r'#[#]', jsObject, property);
2730 }
2731
2732 /**
2733 * Called at the end of unaborted switch cases to get the singleton
2734 * FallThroughError exception that will be thrown.
2735 */
2736 getFallThroughError() => new FallThroughErrorImplementation();
2737
2738 /**
2739 * A metadata annotation describing the types instantiated by a native element.
2740 *
2741 * The annotation is valid on a native method and a field of a native class.
2742 *
2743 * By default, a field of a native class is seen as an instantiation point for
2744 * all native classes that are a subtype of the field's type, and a native
2745 * method is seen as an instantiation point fo all native classes that are a
2746 * subtype of the method's return type, or the argument types of the declared
2747 * type of the method's callback parameter.
2748 *
2749 * An @[Creates] annotation overrides the default set of instantiated types. If
2750 * one or more @[Creates] annotations are present, the type of the native
2751 * element is ignored, and the union of @[Creates] annotations is used instead.
2752 * The names in the strings are resolved and the program will fail to compile
2753 * with dart2js if they do not name types.
2754 *
2755 * The argument to [Creates] is a string. The string is parsed as the names of
2756 * one or more types, separated by vertical bars `|`. There are some special
2757 * names:
2758 *
2759 * * `=Object`. This means 'exactly Object', which is a plain JavaScript object
2760 * with properties and none of the subtypes of Object.
2761 *
2762 * Example: we may know that a method always returns a specific implementation:
2763 *
2764 * @Creates('_NodeList')
2765 * List<Node> getElementsByTagName(String tag) native;
2766 *
2767 * Useful trick: A method can be marked as not instantiating any native classes
2768 * with the annotation `@Creates('Null')`. This is useful for fields on native
2769 * classes that are used only in Dart code.
2770 *
2771 * @Creates('Null')
2772 * var _cachedFoo;
2773 */
2774 class Creates {
2775 final String types;
2776 const Creates(this.types);
2777 }
2778
2779 /**
2780 * A metadata annotation describing the types returned or yielded by a native
2781 * element.
2782 *
2783 * The annotation is valid on a native method and a field of a native class.
2784 *
2785 * By default, a native method or field is seen as returning or yielding all
2786 * subtypes if the method return type or field type. This annotation allows a
2787 * more precise set of types to be specified.
2788 *
2789 * See [Creates] for the syntax of the argument.
2790 *
2791 * Example: IndexedDB keys are numbers, strings and JavaScript Arrays of keys.
2792 *
2793 * @Returns('String|num|JSExtendableArray')
2794 * dynamic key;
2795 *
2796 * // Equivalent:
2797 * @Returns('String') @Returns('num') @Returns('JSExtendableArray')
2798 * dynamic key;
2799 */
2800 class Returns {
2801 final String types;
2802 const Returns(this.types);
2803 }
2804
2805 /**
2806 * A metadata annotation placed on native methods and fields of native classes
2807 * to specify the JavaScript name.
2808 *
2809 * This example declares a Dart field + getter + setter called `$dom_title` that
2810 * corresponds to the JavaScript property `title`.
2811 *
2812 * class Docmument native "*Foo" {
2813 * @JSName('title')
2814 * String $dom_title;
2815 * }
2816 */
2817 class JSName {
2818 final String name;
2819 const JSName(this.name);
2820 }
2821
2822 /**
2823 * The following methods are called by the runtime to implement
2824 * checked mode and casts. We specialize each primitive type (eg int, bool), and
2825 * use the compiler's convention to do is-checks on regular objects.
2826 */
2827 boolConversionCheck(value) {
2828 if (value is bool) return value;
2829 // One of the following checks will always fail.
2830 boolTypeCheck(value);
2831 assert(value != null);
2832 return false;
2833 }
2834
2835 stringTypeCheck(value) {
2836 if (value == null) return value;
2837 if (value is String) return value;
2838 throw new TypeErrorImplementation(value, 'String');
2839 }
2840
2841 stringTypeCast(value) {
2842 if (value is String || value == null) return value;
2843 // TODO(lrn): When reified types are available, pass value.class and String.
2844 throw new CastErrorImplementation(
2845 Primitives.objectTypeName(value), 'String');
2846 }
2847
2848 doubleTypeCheck(value) {
2849 if (value == null) return value;
2850 if (value is double) return value;
2851 throw new TypeErrorImplementation(value, 'double');
2852 }
2853
2854 doubleTypeCast(value) {
2855 if (value is double || value == null) return value;
2856 throw new CastErrorImplementation(
2857 Primitives.objectTypeName(value), 'double');
2858 }
2859
2860 numTypeCheck(value) {
2861 if (value == null) return value;
2862 if (value is num) return value;
2863 throw new TypeErrorImplementation(value, 'num');
2864 }
2865
2866 numTypeCast(value) {
2867 if (value is num || value == null) return value;
2868 throw new CastErrorImplementation(
2869 Primitives.objectTypeName(value), 'num');
2870 }
2871
2872 boolTypeCheck(value) {
2873 if (value == null) return value;
2874 if (value is bool) return value;
2875 throw new TypeErrorImplementation(value, 'bool');
2876 }
2877
2878 boolTypeCast(value) {
2879 if (value is bool || value == null) return value;
2880 throw new CastErrorImplementation(
2881 Primitives.objectTypeName(value), 'bool');
2882 }
2883
2884 intTypeCheck(value) {
2885 if (value == null) return value;
2886 if (value is int) return value;
2887 throw new TypeErrorImplementation(value, 'int');
2888 }
2889
2890 intTypeCast(value) {
2891 if (value is int || value == null) return value;
2892 throw new CastErrorImplementation(
2893 Primitives.objectTypeName(value), 'int');
2894 }
2895
2896 void propertyTypeError(value, property) {
2897 String name = isCheckPropertyToJsConstructorName(property);
2898 throw new TypeErrorImplementation(value, name);
2899 }
2900
2901 void propertyTypeCastError(value, property) {
2902 // Cuts the property name to the class name.
2903 String actualType = Primitives.objectTypeName(value);
2904 String expectedType = property.substring(3, property.length);
2905 throw new CastErrorImplementation(actualType, expectedType);
2906 }
2907
2908 /**
2909 * For types that are not supertypes of native (eg DOM) types,
2910 * we emit a simple property check to check that an object implements
2911 * that type.
2912 */
2913 propertyTypeCheck(value, property) {
2914 if (value == null) return value;
2915 if (JS('bool', '!!#[#]', value, property)) return value;
2916 propertyTypeError(value, property);
2917 }
2918
2919 /**
2920 * For types that are not supertypes of native (eg DOM) types,
2921 * we emit a simple property check to check that an object implements
2922 * that type.
2923 */
2924 propertyTypeCast(value, property) {
2925 if (value == null || JS('bool', '!!#[#]', value, property)) return value;
2926 propertyTypeCastError(value, property);
2927 }
2928
2929 /**
2930 * For types that are supertypes of native (eg DOM) types, we use the
2931 * interceptor for the class because we cannot add a JS property to the
2932 * prototype at load time.
2933 */
2934 interceptedTypeCheck(value, property) {
2935 if (value == null) return value;
2936 if ((identical(JS('String', 'typeof #', value), 'object'))
2937 && JS('bool', '#[#]', getInterceptor(value), property)) {
2938 return value;
2939 }
2940 propertyTypeError(value, property);
2941 }
2942
2943 /**
2944 * For types that are supertypes of native (eg DOM) types, we use the
2945 * interceptor for the class because we cannot add a JS property to the
2946 * prototype at load time.
2947 */
2948 interceptedTypeCast(value, property) {
2949 if (value == null
2950 || ((JS('bool', 'typeof # === "object"', value))
2951 && JS('bool', '#[#]', getInterceptor(value), property))) {
2952 return value;
2953 }
2954 propertyTypeCastError(value, property);
2955 }
2956
2957 /**
2958 * Specialization of the type check for num and String and their
2959 * supertype since [value] can be a JS primitive.
2960 */
2961 numberOrStringSuperTypeCheck(value, property) {
2962 if (value == null) return value;
2963 if (value is String) return value;
2964 if (value is num) return value;
2965 if (JS('bool', '!!#[#]', value, property)) return value;
2966 propertyTypeError(value, property);
2967 }
2968
2969 numberOrStringSuperTypeCast(value, property) {
2970 if (value is String) return value;
2971 if (value is num) return value;
2972 return propertyTypeCast(value, property);
2973 }
2974
2975 numberOrStringSuperNativeTypeCheck(value, property) {
2976 if (value == null) return value;
2977 if (value is String) return value;
2978 if (value is num) return value;
2979 if (JS('bool', '#[#]', getInterceptor(value), property)) return value;
2980 propertyTypeError(value, property);
2981 }
2982
2983 numberOrStringSuperNativeTypeCast(value, property) {
2984 if (value == null) return value;
2985 if (value is String) return value;
2986 if (value is num) return value;
2987 if (JS('bool', '#[#]', getInterceptor(value), property)) return value;
2988 propertyTypeCastError(value, property);
2989 }
2990
2991 /**
2992 * Specialization of the type check for String and its supertype
2993 * since [value] can be a JS primitive.
2994 */
2995 stringSuperTypeCheck(value, property) {
2996 if (value == null) return value;
2997 if (value is String) return value;
2998 if (JS('bool', '!!#[#]', value, property)) return value;
2999 propertyTypeError(value, property);
3000 }
3001
3002 stringSuperTypeCast(value, property) {
3003 if (value is String) return value;
3004 return propertyTypeCast(value, property);
3005 }
3006
3007 stringSuperNativeTypeCheck(value, property) {
3008 if (value == null) return value;
3009 if (value is String) return value;
3010 if (JS('bool', '#[#]', getInterceptor(value), property)) return value;
3011 propertyTypeError(value, property);
3012 }
3013
3014 stringSuperNativeTypeCast(value, property) {
3015 if (value is String || value == null) return value;
3016 if (JS('bool', '#[#]', getInterceptor(value), property)) return value;
3017 propertyTypeCastError(value, property);
3018 }
3019
3020 /**
3021 * Specialization of the type check for List and its supertypes,
3022 * since [value] can be a JS array.
3023 */
3024 listTypeCheck(value) {
3025 if (value == null) return value;
3026 if (value is List) return value;
3027 throw new TypeErrorImplementation(value, 'List');
3028 }
3029
3030 listTypeCast(value) {
3031 if (value is List || value == null) return value;
3032 throw new CastErrorImplementation(
3033 Primitives.objectTypeName(value), 'List');
3034 }
3035
3036 listSuperTypeCheck(value, property) {
3037 if (value == null) return value;
3038 if (value is List) return value;
3039 if (JS('bool', '!!#[#]', value, property)) return value;
3040 propertyTypeError(value, property);
3041 }
3042
3043 listSuperTypeCast(value, property) {
3044 if (value is List) return value;
3045 return propertyTypeCast(value, property);
3046 }
3047
3048 listSuperNativeTypeCheck(value, property) {
3049 if (value == null) return value;
3050 if (value is List) return value;
3051 if (JS('bool', '#[#]', getInterceptor(value), property)) return value;
3052 propertyTypeError(value, property);
3053 }
3054
3055 listSuperNativeTypeCast(value, property) {
3056 if (value is List || value == null) return value;
3057 if (JS('bool', '#[#]', getInterceptor(value), property)) return value;
3058 propertyTypeCastError(value, property);
3059 }
3060
3061 voidTypeCheck(value) {
3062 if (value == null) return value;
3063 throw new TypeErrorImplementation(value, 'void');
3064 }
3065
3066 checkMalformedType(value, message) {
3067 if (value == null) return value;
3068 throw new TypeErrorImplementation.fromMessage(message);
3069 }
3070
3071 @NoInline()
3072 void checkDeferredIsLoaded(String loadId, String uri) {
3073 if (!_loadedLibraries.contains(loadId)) {
3074 throw new DeferredNotLoadedError(uri);
3075 }
3076 }
3077
3078 /**
3079 * Special interface recognized by the compiler and implemented by DOM
3080 * objects that support integer indexing. This interface is not
3081 * visible to anyone, and is only injected into special libraries.
3082 */
3083 abstract class JavaScriptIndexingBehavior extends JSMutableIndexable {
3084 }
3085
3086 // TODO(lrn): These exceptions should be implemented in core.
3087 // When they are, remove the 'Implementation' here.
3088
3089 /** Thrown by type assertions that fail. */
3090 class TypeErrorImplementation extends Error implements TypeError {
3091 final String message;
3092
3093 /**
3094 * Normal type error caused by a failed subtype test.
3095 */
3096 TypeErrorImplementation(Object value, String type)
3097 : message = "type '${Primitives.objectTypeName(value)}' is not a subtype "
3098 "of type '$type'";
3099
3100 TypeErrorImplementation.fromMessage(String this.message);
3101
3102 String toString() => message;
3103 }
3104
3105 /** Thrown by the 'as' operator if the cast isn't valid. */
3106 class CastErrorImplementation extends Error implements CastError {
3107 // TODO(lrn): Rename to CastError (and move implementation into core).
3108 final String message;
3109
3110 /**
3111 * Normal cast error caused by a failed type cast.
3112 */
3113 CastErrorImplementation(Object actualType, Object expectedType)
3114 : message = "CastError: Casting value of type $actualType to"
3115 " incompatible type $expectedType";
3116
3117 String toString() => message;
3118 }
3119
3120 class FallThroughErrorImplementation extends FallThroughError {
3121 FallThroughErrorImplementation();
3122 String toString() => "Switch case fall-through.";
3123 }
3124
3125 /**
3126 * Helper function for implementing asserts. The compiler treats this specially.
3127 */
3128 void assertHelper(condition) {
3129 // Do a bool check first because it is common and faster than 'is Function'.
3130 if (condition is !bool) {
3131 if (condition is Function) condition = condition();
3132 if (condition is !bool) {
3133 throw new TypeErrorImplementation(condition, 'bool');
3134 }
3135 }
3136 // Compare to true to avoid boolean conversion check in checked
3137 // mode.
3138 if (true != condition) throw new AssertionError();
3139 }
3140
3141 /**
3142 * Called by generated code when a method that must be statically
3143 * resolved cannot be found.
3144 */
3145 void throwNoSuchMethod(obj, name, arguments, expectedArgumentNames) {
3146 Symbol memberName = new _symbol_dev.Symbol.unvalidated(name);
3147 throw new NoSuchMethodError(obj, memberName, arguments,
3148 new Map<Symbol, dynamic>(),
3149 expectedArgumentNames);
3150 }
3151
3152 /**
3153 * Called by generated code when a static field's initializer references the
3154 * field that is currently being initialized.
3155 */
3156 void throwCyclicInit(String staticName) {
3157 throw new CyclicInitializationError(
3158 "Cyclic initialization for static $staticName");
3159 }
3160
3161 /**
3162 * Error thrown when a runtime error occurs.
3163 */
3164 class RuntimeError extends Error {
3165 final message;
3166 RuntimeError(this.message);
3167 String toString() => "RuntimeError: $message";
3168 }
3169
3170 class DeferredNotLoadedError extends Error implements NoSuchMethodError {
3171 String libraryName;
3172
3173 DeferredNotLoadedError(this.libraryName);
3174
3175 String toString() {
3176 return "Deferred library $libraryName was not loaded.";
3177 }
3178 }
3179
3180 abstract class RuntimeType {
3181 const RuntimeType();
3182
3183 toRti();
3184 }
3185
3186 class RuntimeFunctionType extends RuntimeType {
3187 final RuntimeType returnType;
3188 final List<RuntimeType> parameterTypes;
3189 final List<RuntimeType> optionalParameterTypes;
3190 final namedParameters;
3191
3192 static var /* bool */ inAssert = false;
3193
3194 RuntimeFunctionType(this.returnType,
3195 this.parameterTypes,
3196 this.optionalParameterTypes,
3197 this.namedParameters);
3198
3199 bool get isVoid => returnType is VoidRuntimeType;
3200
3201 /// Called from generated code. [expression] is a Dart object and this method
3202 /// returns true if [this] is a supertype of [expression].
3203 @NoInline() @NoSideEffects()
3204 bool _isTest(expression) {
3205 var functionTypeObject = _extractFunctionTypeObjectFrom(expression);
3206 return functionTypeObject == null
3207 ? false
3208 : isFunctionSubtype(functionTypeObject, toRti());
3209 }
3210
3211 @NoInline() @NoSideEffects()
3212 _asCheck(expression) {
3213 // Type inferrer doesn't think this is called with dynamic arguments.
3214 return _check(JS('', '#', expression), true);
3215 }
3216
3217 @NoInline() @NoSideEffects()
3218 _assertCheck(expression) {
3219 if (inAssert) return null;
3220 inAssert = true; // Don't try to check this library itself.
3221 try {
3222 // Type inferrer don't think this is called with dynamic arguments.
3223 return _check(JS('', '#', expression), false);
3224 } finally {
3225 inAssert = false;
3226 }
3227 }
3228
3229 _check(expression, bool isCast) {
3230 if (expression == null) return null;
3231 if (_isTest(expression)) return expression;
3232
3233 var self = new FunctionTypeInfoDecoderRing(toRti()).toString();
3234 if (isCast) {
3235 var functionTypeObject = _extractFunctionTypeObjectFrom(expression);
3236 var pretty;
3237 if (functionTypeObject != null) {
3238 pretty = new FunctionTypeInfoDecoderRing(functionTypeObject).toString();
3239 } else {
3240 pretty = Primitives.objectTypeName(expression);
3241 }
3242 throw new CastErrorImplementation(pretty, self);
3243 } else {
3244 // TODO(ahe): Pass "pretty" function-type to TypeErrorImplementation?
3245 throw new TypeErrorImplementation(expression, self);
3246 }
3247 }
3248
3249 _extractFunctionTypeObjectFrom(o) {
3250 var interceptor = getInterceptor(o);
3251 var signatureName = JS_GET_NAME(JsGetName.SIGNATURE_NAME);
3252 return JS('bool', '# in #', signatureName, interceptor)
3253 ? JS('', '#[#]()', interceptor, JS_GET_NAME(JsGetName.SIGNATURE_NAME))
3254 : null;
3255 }
3256
3257 toRti() {
3258 var result = createDartFunctionTypeRti();
3259 if (isVoid) {
3260 JS('', '#[#] = true', result,
3261 JS_GET_NAME(JsGetName.FUNCTION_TYPE_VOID_RETURN_TAG));
3262 } else {
3263 if (returnType is! DynamicRuntimeType) {
3264 JS('', '#[#] = #', result,
3265 JS_GET_NAME(JsGetName.FUNCTION_TYPE_RETURN_TYPE_TAG),
3266 returnType.toRti());
3267 }
3268 }
3269 if (parameterTypes != null && !parameterTypes.isEmpty) {
3270 JS('', '#[#] = #', result,
3271 JS_GET_NAME(JsGetName.FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG),
3272 listToRti(parameterTypes));
3273 }
3274
3275 if (optionalParameterTypes != null && !optionalParameterTypes.isEmpty) {
3276 JS('', '#[#] = #', result,
3277 JS_GET_NAME(JsGetName.FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG),
3278 listToRti(optionalParameterTypes));
3279 }
3280
3281 if (namedParameters != null) {
3282 var namedRti = JS('=Object', 'Object.create(null)');
3283 var keys = extractKeys(namedParameters);
3284 for (var i = 0; i < keys.length; i++) {
3285 var name = keys[i];
3286 var rti = JS('', '#[#]', namedParameters, name).toRti();
3287 JS('', '#[#] = #', namedRti, name, rti);
3288 }
3289 JS('', '#[#] = #', result,
3290 JS_GET_NAME(JsGetName.FUNCTION_TYPE_NAMED_PARAMETERS_TAG),
3291 namedRti);
3292 }
3293
3294 return result;
3295 }
3296
3297 static listToRti(list) {
3298 list = JS('JSFixedArray', '#', list);
3299 var result = JS('JSExtendableArray', '[]');
3300 for (var i = 0; i < list.length; i++) {
3301 JS('', '#.push(#)', result, list[i].toRti());
3302 }
3303 return result;
3304 }
3305
3306 String toString() {
3307 String result = '(';
3308 bool needsComma = false;
3309 if (parameterTypes != null) {
3310 for (var i = 0; i < parameterTypes.length; i++) {
3311 RuntimeType type = parameterTypes[i];
3312 if (needsComma) result += ', ';
3313 result += '$type';
3314 needsComma = true;
3315 }
3316 }
3317 if (optionalParameterTypes != null && !optionalParameterTypes.isEmpty) {
3318 if (needsComma) result += ', ';
3319 needsComma = false;
3320 result += '[';
3321 for (var i = 0; i < optionalParameterTypes.length; i++) {
3322 RuntimeType type = optionalParameterTypes[i];
3323 if (needsComma) result += ', ';
3324 result += '$type';
3325 needsComma = true;
3326 }
3327 result += ']';
3328 } else if (namedParameters != null) {
3329 if (needsComma) result += ', ';
3330 needsComma = false;
3331 result += '{';
3332 var keys = extractKeys(namedParameters);
3333 for (var i = 0; i < keys.length; i++) {
3334 var name = keys[i];
3335 if (needsComma) result += ', ';
3336 var rti = JS('', '#[#]', namedParameters, name).toRti();
3337 result += '$rti ${JS("String", "#", name)}';
3338 needsComma = true;
3339 }
3340 result += '}';
3341 }
3342
3343 result += ') -> $returnType';
3344 return result;
3345 }
3346 }
3347
3348 RuntimeFunctionType buildFunctionType(returnType,
3349 parameterTypes,
3350 optionalParameterTypes) {
3351 return new RuntimeFunctionType(
3352 returnType,
3353 parameterTypes,
3354 optionalParameterTypes,
3355 null);
3356 }
3357
3358 RuntimeFunctionType buildNamedFunctionType(returnType,
3359 parameterTypes,
3360 namedParameters) {
3361 return new RuntimeFunctionType(
3362 returnType,
3363 parameterTypes,
3364 null,
3365 namedParameters);
3366 }
3367
3368 RuntimeType buildInterfaceType(rti, typeArguments) {
3369 String jsConstructorName = rawRtiToJsConstructorName(rti);
3370 if (typeArguments == null || typeArguments.isEmpty) {
3371 return new RuntimeTypePlain(jsConstructorName);
3372 }
3373 return new RuntimeTypeGeneric(jsConstructorName, typeArguments, null);
3374 }
3375
3376 class DynamicRuntimeType extends RuntimeType {
3377 const DynamicRuntimeType();
3378
3379 String toString() => 'dynamic';
3380
3381 toRti() => null;
3382 }
3383
3384 RuntimeType getDynamicRuntimeType() => const DynamicRuntimeType();
3385
3386 class VoidRuntimeType extends RuntimeType {
3387 const VoidRuntimeType();
3388
3389 String toString() => 'void';
3390
3391 toRti() => throw 'internal error';
3392 }
3393
3394 RuntimeType getVoidRuntimeType() => const VoidRuntimeType();
3395
3396 /**
3397 * Meta helper for function type tests.
3398 *
3399 * A "meta helper" is a helper function that is never called but simulates how
3400 * generated code behaves as far as resolution and type inference is concerned.
3401 */
3402 functionTypeTestMetaHelper() {
3403 var dyn = JS('', 'x');
3404 var dyn2 = JS('', 'x');
3405 List fixedListOrNull = JS('JSFixedArray|Null', 'x');
3406 List fixedListOrNull2 = JS('JSFixedArray|Null', 'x');
3407 List fixedList = JS('JSFixedArray', 'x');
3408 // TODO(ahe): Can we use [UnknownJavaScriptObject] below?
3409 var /* UnknownJavaScriptObject */ jsObject = JS('=Object', 'x');
3410
3411 buildFunctionType(dyn, fixedListOrNull, fixedListOrNull2);
3412 buildNamedFunctionType(dyn, fixedList, jsObject);
3413 buildInterfaceType(dyn, fixedListOrNull);
3414 getDynamicRuntimeType();
3415 getVoidRuntimeType();
3416 convertRtiToRuntimeType(dyn);
3417 dyn._isTest(dyn2);
3418 dyn._asCheck(dyn2);
3419 dyn._assertCheck(dyn2);
3420 }
3421
3422 RuntimeType convertRtiToRuntimeType(rti) {
3423 if (rti == null) {
3424 return getDynamicRuntimeType();
3425 } else if (JS('bool', 'typeof # == "function"', rti)) {
3426 return new RuntimeTypePlain(JS('String', r'#.name', rti));
3427 } else if (JS('bool', '#.constructor == Array', rti)) {
3428 List list = JS('JSFixedArray', '#', rti);
3429 String name = JS('String', r'#.name', list[0]);
3430 List arguments = [];
3431 for (int i = 1; i < list.length; i++) {
3432 arguments.add(convertRtiToRuntimeType(list[i]));
3433 }
3434 return new RuntimeTypeGeneric(name, arguments, rti);
3435 } else if (JS('bool', '"func" in #', rti)) {
3436 return new FunctionTypeInfoDecoderRing(rti).toRuntimeType();
3437 } else {
3438 throw new RuntimeError(
3439 "Cannot convert "
3440 "'${JS('String', 'JSON.stringify(#)', rti)}' to RuntimeType.");
3441 }
3442 }
3443
3444 class RuntimeTypePlain extends RuntimeType {
3445 /// The constructor name of this raw type.
3446 final String _jsConstructorName;
3447
3448 RuntimeTypePlain(this._jsConstructorName);
3449
3450 toRti() {
3451 var rti = jsConstructorNameToRti(_jsConstructorName);
3452 if (rti == null) throw "no type for '$_jsConstructorName'";
3453 return rti;
3454 }
3455
3456 String toString() => _jsConstructorName;
3457 }
3458
3459 class RuntimeTypeGeneric extends RuntimeType {
3460 /// The constructor name of the raw type for this generic type.
3461 final String _jsConstructorName;
3462 final List<RuntimeType> arguments;
3463 var rti;
3464
3465 RuntimeTypeGeneric(this._jsConstructorName, this.arguments, this.rti);
3466
3467 toRti() {
3468 if (rti != null) return rti;
3469 var result = [jsConstructorNameToRti(_jsConstructorName)];
3470 if (result[0] == null) {
3471 throw "no type for '$_jsConstructorName<...>'";
3472 }
3473 for (RuntimeType argument in arguments) {
3474 result.add(argument.toRti());
3475 }
3476 return rti = result;
3477 }
3478
3479 String toString() => '$_jsConstructorName<${arguments.join(", ")}>';
3480 }
3481
3482 class FunctionTypeInfoDecoderRing {
3483 final _typeData;
3484 String _cachedToString;
3485
3486 FunctionTypeInfoDecoderRing(this._typeData);
3487
3488 bool get _hasReturnType => JS('bool', '"ret" in #', _typeData);
3489 get _returnType => JS('', '#.ret', _typeData);
3490
3491 bool get _isVoid => JS('bool', '!!#.void', _typeData);
3492
3493 bool get _hasArguments => JS('bool', '"args" in #', _typeData);
3494 List get _arguments => JS('JSExtendableArray', '#.args', _typeData);
3495
3496 bool get _hasOptionalArguments => JS('bool', '"opt" in #', _typeData);
3497 List get _optionalArguments => JS('JSExtendableArray', '#.opt', _typeData);
3498
3499 bool get _hasNamedArguments => JS('bool', '"named" in #', _typeData);
3500 get _namedArguments => JS('=Object', '#.named', _typeData);
3501
3502 RuntimeType toRuntimeType() {
3503 // TODO(ahe): Implement this (and update return type).
3504 return const DynamicRuntimeType();
3505 }
3506
3507 String _convert(type) {
3508 String result = runtimeTypeToString(type);
3509 if (result != null) return result;
3510 // Currently the [runtimeTypeToString] method doesn't handle function rtis.
3511 if (JS('bool', '"func" in #', type)) {
3512 return new FunctionTypeInfoDecoderRing(type).toString();
3513 } else {
3514 throw 'bad type';
3515 }
3516 }
3517
3518 String toString() {
3519 if (_cachedToString != null) return _cachedToString;
3520 var s = "(";
3521 var sep = '';
3522 if (_hasArguments) {
3523 for (var argument in _arguments) {
3524 s += sep;
3525 s += _convert(argument);
3526 sep = ', ';
3527 }
3528 }
3529 if (_hasOptionalArguments) {
3530 s += '$sep[';
3531 sep = '';
3532 for (var argument in _optionalArguments) {
3533 s += sep;
3534 s += _convert(argument);
3535 sep = ', ';
3536 }
3537 s += ']';
3538 }
3539 if (_hasNamedArguments) {
3540 s += '$sep{';
3541 sep = '';
3542 for (var name in extractKeys(_namedArguments)) {
3543 s += sep;
3544 s += '$name: ';
3545 s += _convert(JS('', '#[#]', _namedArguments, name));
3546 sep = ', ';
3547 }
3548 s += '}';
3549 }
3550 s += ') -> ';
3551 if (_isVoid) {
3552 s += 'void';
3553 } else if (_hasReturnType) {
3554 s += _convert(_returnType);
3555 } else {
3556 s += 'dynamic';
3557 }
3558 return _cachedToString = "$s";
3559 }
3560 }
3561
3562 // TODO(ahe): Remove this class and call noSuchMethod instead.
3563 class UnimplementedNoSuchMethodError extends Error
3564 implements NoSuchMethodError {
3565 final String _message;
3566
3567 UnimplementedNoSuchMethodError(this._message);
3568
3569 String toString() => "Unsupported operation: $_message";
3570 }
3571
3572 /**
3573 * Creates a random number with 64 bits of randomness.
3574 *
3575 * This will be truncated to the 53 bits available in a double.
3576 */
3577 int random64() {
3578 // TODO(lrn): Use a secure random source.
3579 int int32a = JS("int", "(Math.random() * 0x100000000) >>> 0");
3580 int int32b = JS("int", "(Math.random() * 0x100000000) >>> 0");
3581 return int32a + int32b * 0x100000000;
3582 }
3583
3584 String jsonEncodeNative(String string) {
3585 return JS("String", "JSON.stringify(#)", string);
3586 }
3587
3588 /**
3589 * Returns a property name for placing data on JavaScript objects shared between
3590 * DOM isolates. This happens when multiple programs are loaded in the same
3591 * JavaScript context (i.e. page). The name is based on [name] but with an
3592 * additional part that is unique for each isolate.
3593 *
3594 * The form of the name is '___dart_$name_$id'.
3595 */
3596 String getIsolateAffinityTag(String name) {
3597 var isolateTagGetter =
3598 JS_EMBEDDED_GLOBAL('', GET_ISOLATE_TAG);
3599 return JS('String', '#(#)', isolateTagGetter, name);
3600 }
3601
3602 typedef Future<Null> LoadLibraryFunctionType();
3603
3604 LoadLibraryFunctionType _loadLibraryWrapper(String loadId) {
3605 return () => loadDeferredLibrary(loadId);
3606 }
3607
3608 final Map<String, Future<Null>> _loadingLibraries = <String, Future<Null>>{};
3609 final Set<String> _loadedLibraries = new Set<String>();
3610
3611 typedef void DeferredLoadCallback();
3612
3613 // Function that will be called every time a new deferred import is loaded.
3614 DeferredLoadCallback deferredLoadHook;
3615
3616 Future<Null> loadDeferredLibrary(String loadId) {
3617 // For each loadId there is a list of hunk-uris to load, and a corresponding
3618 // list of hashes. These are stored in the app-global scope.
3619 var urisMap = JS_EMBEDDED_GLOBAL('', DEFERRED_LIBRARY_URIS);
3620 List<String> uris = JS('JSExtendableArray|Null', '#[#]', urisMap, loadId);
3621 var hashesMap = JS_EMBEDDED_GLOBAL('', DEFERRED_LIBRARY_HASHES);
3622 List<String> hashes = JS('JSExtendableArray|Null', '#[#]', hashesMap, loadId);
3623 if (uris == null) return new Future.value(null);
3624 // The indices into `uris` and `hashes` that we want to load.
3625 List<int> indices = new List.generate(uris.length, (i) => i);
3626 var isHunkLoaded = JS_EMBEDDED_GLOBAL('', IS_HUNK_LOADED);
3627 var isHunkInitialized = JS_EMBEDDED_GLOBAL('', IS_HUNK_INITIALIZED);
3628 // Filter away indices for hunks that have already been loaded.
3629 List<int> indicesToLoad = indices
3630 .where((int i) => !JS('bool','#(#)', isHunkLoaded, hashes[i]))
3631 .toList();
3632 return Future.wait(indicesToLoad
3633 .map((int i) => _loadHunk(uris[i]))).then((_) {
3634 // Now all hunks have been loaded, we run the needed initializers.
3635 List<int> indicesToInitialize = indices
3636 .where((int i) => !JS('bool','#(#)', isHunkInitialized, hashes[i]))
3637 .toList(); // Load the needed hunks.
3638 for (int i in indicesToInitialize) {
3639 var initializer = JS_EMBEDDED_GLOBAL('', INITIALIZE_LOADED_HUNK);
3640 JS('void', '#(#)', initializer, hashes[i]);
3641 }
3642 bool updated = _loadedLibraries.add(loadId);
3643 if (updated && deferredLoadHook != null) {
3644 deferredLoadHook();
3645 }
3646 });
3647 }
3648
3649 Future<Null> _loadHunk(String hunkName) {
3650 Future<Null> future = _loadingLibraries[hunkName];
3651 if (future != null) {
3652 return future.then((_) => null);
3653 }
3654
3655 String uri = IsolateNatives.thisScript;
3656
3657 int index = uri.lastIndexOf('/');
3658 uri = '${uri.substring(0, index + 1)}$hunkName';
3659
3660 var deferredLibraryLoader = JS('', 'self.dartDeferredLibraryLoader');
3661 Completer<Null> completer = new Completer<Null>();
3662
3663 void success() {
3664 completer.complete(null);
3665 }
3666
3667 void failure([error, StackTrace stackTrace]) {
3668 _loadingLibraries[hunkName] = null;
3669 completer.completeError(
3670 new DeferredLoadException("Loading $uri failed: $error"),
3671 stackTrace);
3672 }
3673
3674 var jsSuccess = convertDartClosureToJS(success, 0);
3675 var jsFailure = convertDartClosureToJS((error) {
3676 failure(unwrapException(error), getTraceFromException(error));
3677 }, 1);
3678
3679 if (JS('bool', 'typeof # === "function"', deferredLibraryLoader)) {
3680 try {
3681 JS('void', '#(#, #, #)', deferredLibraryLoader, uri,
3682 jsSuccess, jsFailure);
3683 } catch (error, stackTrace) {
3684 failure(error, stackTrace);
3685 }
3686 } else if (isWorker()) {
3687 // We are in a web worker. Load the code with an XMLHttpRequest.
3688 enterJsAsync();
3689 Future<Null> leavingFuture = completer.future.whenComplete(() {
3690 leaveJsAsync();
3691 });
3692
3693 int index = uri.lastIndexOf('/');
3694 uri = '${uri.substring(0, index + 1)}$hunkName';
3695 var xhr = JS('var', 'new XMLHttpRequest()');
3696 JS('void', '#.open("GET", #)', xhr, uri);
3697 JS('void', '#.addEventListener("load", #, false)',
3698 xhr, convertDartClosureToJS((event) {
3699 if (JS('int', '#.status', xhr) != 200) {
3700 failure("");
3701 }
3702 String code = JS('String', '#.responseText', xhr);
3703 try {
3704 // Create a new function to avoid getting access to current function
3705 // context.
3706 JS('void', '(new Function(#))()', code);
3707 success();
3708 } catch (error, stackTrace) {
3709 failure(error, stackTrace);
3710 }
3711 }, 1));
3712
3713 JS('void', '#.addEventListener("error", #, false)', xhr, failure);
3714 JS('void', '#.addEventListener("abort", #, false)', xhr, failure);
3715 JS('void', '#.send()', xhr);
3716 } else {
3717 // We are in a dom-context.
3718 // Inject a script tag.
3719 var script = JS('', 'document.createElement("script")');
3720 JS('', '#.type = "text/javascript"', script);
3721 JS('', '#.src = #', script, uri);
3722 JS('', '#.addEventListener("load", #, false)', script, jsSuccess);
3723 JS('', '#.addEventListener("error", #, false)', script, jsFailure);
3724 JS('', 'document.body.appendChild(#)', script);
3725 }
3726 _loadingLibraries[hunkName] = completer.future;
3727 return completer.future;
3728 }
3729
3730 class MainError extends Error implements NoSuchMethodError {
3731 final String _message;
3732
3733 MainError(this._message);
3734
3735 String toString() => 'NoSuchMethodError: $_message';
3736 }
3737
3738 void missingMain() {
3739 throw new MainError("No top-level function named 'main'.");
3740 }
3741
3742 void badMain() {
3743 throw new MainError("'main' is not a function.");
3744 }
3745
3746 void mainHasTooManyParameters() {
3747 throw new MainError("'main' expects too many parameters.");
3748 }
3749
3750 /// A wrapper around an exception, much like the one created by [wrapException]
3751 /// but with a pre-given stack-trace.
3752 class ExceptionAndStackTrace {
3753 dynamic dartException;
3754 StackTrace stackTrace;
3755
3756 ExceptionAndStackTrace(this.dartException, this.stackTrace);
3757 }
3758
3759 /// Runtime support for async-await transformation.
3760 ///
3761 /// This function is called by a transformed function on each await and return
3762 /// in the untransformed function, and before starting.
3763 ///
3764 /// If [object] is not a future it will be wrapped in a `new Future.value`.
3765 ///
3766 /// If [asyncBody] is [async_error_codes.SUCCESS]/[async_error_codes.ERROR] it
3767 /// indicates a return or throw from the async function, and
3768 /// complete/completeError is called on [completer] with [object].
3769 ///
3770 /// Otherwise [asyncBody] is set up to be called when the future is completed
3771 /// with a code [async_error_codes.SUCCESS]/[async_error_codes.ERROR] depending
3772 /// on the success of the future.
3773 ///
3774 /// Returns the future of the completer for convenience of the first call.
3775 dynamic asyncHelper(dynamic object,
3776 dynamic /* js function */ bodyFunctionOrErrorCode,
3777 Completer completer) {
3778 if (identical(bodyFunctionOrErrorCode, async_error_codes.SUCCESS)) {
3779 completer.complete(object);
3780 return;
3781 } else if (identical(bodyFunctionOrErrorCode, async_error_codes.ERROR)) {
3782 // The error is a js-error.
3783 completer.completeError(unwrapException(object),
3784 getTraceFromException(object));
3785 return;
3786 }
3787 Future future = object is Future ? object : new Future.value(object);
3788 future.then(_wrapJsFunctionForAsync(bodyFunctionOrErrorCode,
3789 async_error_codes.SUCCESS),
3790 onError: (dynamic error, StackTrace stackTrace) {
3791 ExceptionAndStackTrace wrappedException =
3792 new ExceptionAndStackTrace(error, stackTrace);
3793 Function wrapped =_wrapJsFunctionForAsync(bodyFunctionOrErrorCode,
3794 async_error_codes.ERROR);
3795 wrapped(wrappedException);
3796 });
3797 return completer.future;
3798 }
3799
3800 Function _wrapJsFunctionForAsync(dynamic /* js function */ function,
3801 int errorCode) {
3802 var protected = JS('', """
3803 // Invokes [function] with [errorCode] and [result].
3804 //
3805 // If (and as long as) the invocation throws, calls [function] again,
3806 // with an error-code.
3807 function(errorCode, result) {
3808 while (true) {
3809 try {
3810 #(errorCode, result);
3811 break;
3812 } catch (error) {
3813 result = error;
3814 errorCode = #;
3815 }
3816 }
3817 }""", function, async_error_codes.ERROR);
3818 return (result) {
3819 JS('', '#(#, #)', protected, errorCode, result);
3820 };
3821 }
3822
3823 /// Implements the runtime support for async* functions.
3824 ///
3825 /// Called by the transformed function for each original return, await, yield,
3826 /// yield* and before starting the function.
3827 ///
3828 /// When the async* function wants to return it calls this function with
3829 /// [asyncBody] == [async_error_codes.SUCCESS], the asyncStarHelper takes this
3830 /// as signal to close the stream.
3831 ///
3832 /// When the async* function wants to signal that an uncaught error was thrown,
3833 /// it calls this function with [asyncBody] == [async_error_codes.ERROR],
3834 /// the streamHelper takes this as signal to addError [object] to the
3835 /// [controller] and close it.
3836 ///
3837 /// If the async* function wants to do a yield or yield*, it calls this function
3838 /// with [object] being an [IterationMarker].
3839 ///
3840 /// In the case of a yield or yield*, if the stream subscription has been
3841 /// canceled, schedules [asyncBody] to be called with
3842 /// [async_error_codes.STREAM_WAS_CANCELED].
3843 ///
3844 /// If [object] is a single-yield [IterationMarker], adds the value of the
3845 /// [IterationMarker] to the stream. If the stream subscription has been
3846 /// paused, return early. Otherwise schedule the helper function to be
3847 /// executed again.
3848 ///
3849 /// If [object] is a yield-star [IterationMarker], starts listening to the
3850 /// yielded stream, and adds all events and errors to our own controller (taking
3851 /// care if the subscription has been paused or canceled) - when the sub-stream
3852 /// is done, schedules [asyncBody] again.
3853 ///
3854 /// If the async* function wants to do an await it calls this function with
3855 /// [object] not and [IterationMarker].
3856 ///
3857 /// If [object] is not a [Future], it is wrapped in a `Future.value`.
3858 /// The [asyncBody] is called on completion of the future (see [asyncHelper].
3859 void asyncStarHelper(dynamic object,
3860 dynamic /* int | js function */ bodyFunctionOrErrorCode,
3861 AsyncStarStreamController controller) {
3862 if (identical(bodyFunctionOrErrorCode, async_error_codes.SUCCESS)) {
3863 // This happens on return from the async* function.
3864 if (controller.isCanceled) {
3865 controller.cancelationCompleter.complete();
3866 } else {
3867 controller.close();
3868 }
3869 return;
3870 } else if (identical(bodyFunctionOrErrorCode, async_error_codes.ERROR)) {
3871 // The error is a js-error.
3872 if (controller.isCanceled) {
3873 controller.cancelationCompleter.completeError(
3874 unwrapException(object),
3875 getTraceFromException(object));
3876 } else {
3877 controller.addError(unwrapException(object),
3878 getTraceFromException(object));
3879 controller.close();
3880 }
3881 return;
3882 }
3883
3884 if (object is IterationMarker) {
3885 if (controller.isCanceled) {
3886 Function wrapped = _wrapJsFunctionForAsync(bodyFunctionOrErrorCode,
3887 async_error_codes.STREAM_WAS_CANCELED);
3888 wrapped(null);
3889 return;
3890 }
3891 if (object.state == IterationMarker.YIELD_SINGLE) {
3892 controller.add(object.value);
3893
3894 scheduleMicrotask(() {
3895 if (controller.isPaused) {
3896 // We only suspend the thread inside the microtask in order to allow
3897 // listeners on the output stream to pause in response to the just
3898 // output value, and have the stream immediately stop producing.
3899 controller.isSuspended = true;
3900 return;
3901 }
3902 Function wrapped = _wrapJsFunctionForAsync(bodyFunctionOrErrorCode,
3903 async_error_codes.SUCCESS);
3904 wrapped(null);
3905 });
3906 return;
3907 } else if (object.state == IterationMarker.YIELD_STAR) {
3908 Stream stream = object.value;
3909 // Errors of [stream] are passed though to the main stream. (see
3910 // [AsyncStreamController.addStream]).
3911 // TODO(sigurdm): The spec is not very clear here. Clarify with Gilad.
3912 controller.addStream(stream).then((_) {
3913 // No check for isPaused here because the spec 17.16.2 only
3914 // demands checks *before* each element in [stream] not after the last
3915 // one. On the other hand we check for isCanceled, as that check happens
3916 // after insertion of each element.
3917 int errorCode = controller.isCanceled
3918 ? async_error_codes.STREAM_WAS_CANCELED
3919 : async_error_codes.SUCCESS;
3920 Function wrapped = _wrapJsFunctionForAsync(bodyFunctionOrErrorCode,
3921 errorCode);
3922 wrapped(null);
3923 });
3924 return;
3925 }
3926 }
3927
3928 Future future = object is Future ? object : new Future.value(object);
3929 future.then(_wrapJsFunctionForAsync(bodyFunctionOrErrorCode,
3930 async_error_codes.SUCCESS),
3931 onError: (error, StackTrace stackTrace) {
3932 ExceptionAndStackTrace wrappedException =
3933 new ExceptionAndStackTrace(error, stackTrace);
3934 Function wrapped = _wrapJsFunctionForAsync(
3935 bodyFunctionOrErrorCode, async_error_codes.ERROR);
3936 return wrapped(wrappedException);
3937 });
3938 }
3939
3940 Stream streamOfController(AsyncStarStreamController controller) {
3941 return controller.stream;
3942 }
3943
3944 /// A wrapper around a [StreamController] that keeps track of the state of
3945 /// the execution of an async* function.
3946 /// It can be in 1 of 3 states:
3947 ///
3948 /// - running/scheduled
3949 /// - suspended
3950 /// - canceled
3951 ///
3952 /// If yielding while the subscription is paused it will become suspended. And
3953 /// only resume after the subscription is resumed or canceled.
3954 class AsyncStarStreamController {
3955 StreamController controller;
3956 Stream get stream => controller.stream;
3957
3958 /// True when the async* function has yielded while being paused.
3959 /// When true execution will only resume after a `onResume` or `onCancel`
3960 /// event.
3961 bool isSuspended = false;
3962
3963 bool get isPaused => controller.isPaused;
3964
3965 Completer cancelationCompleter = null;
3966
3967 /// True after the StreamSubscription has been cancelled.
3968 /// When this is true, errors thrown from the async* body should go to the
3969 /// [cancelationCompleter] instead of adding them to [controller], and
3970 /// returning from the async function should complete [cancelationCompleter].
3971 bool get isCanceled => cancelationCompleter != null;
3972
3973 add(event) => controller.add(event);
3974
3975 addStream(Stream stream) {
3976 return controller.addStream(stream, cancelOnError: false);
3977 }
3978
3979 addError(error, stackTrace) => controller.addError(error, stackTrace);
3980
3981 close() => controller.close();
3982
3983 AsyncStarStreamController(body) {
3984
3985 _resumeBody() {
3986 scheduleMicrotask(() {
3987 Function wrapped =
3988 _wrapJsFunctionForAsync(body, async_error_codes.SUCCESS);
3989 wrapped(null);
3990 });
3991 }
3992
3993 controller = new StreamController(
3994 onListen: () {
3995 _resumeBody();
3996 }, onResume: () {
3997 // Only schedule again if the async* function actually is suspended.
3998 // Resume directly instead of scheduling, so that the sequence
3999 // `pause-resume-pause` will result in one extra event produced.
4000 if (isSuspended) {
4001 isSuspended = false;
4002 _resumeBody();
4003 }
4004 }, onCancel: () {
4005 // If the async* is finished we ignore cancel events.
4006 if (!controller.isClosed) {
4007 cancelationCompleter = new Completer();
4008 if (isSuspended) {
4009 // Resume the suspended async* function to run finalizers.
4010 isSuspended = false;
4011 scheduleMicrotask(() {
4012 Function wrapped =_wrapJsFunctionForAsync(body,
4013 async_error_codes.STREAM_WAS_CANCELED);
4014 wrapped(null);
4015 });
4016 }
4017 return cancelationCompleter.future;
4018 }
4019 });
4020 }
4021 }
4022
4023 makeAsyncStarController(body) {
4024 return new AsyncStarStreamController(body);
4025 }
4026
4027 class IterationMarker {
4028 static const YIELD_SINGLE = 0;
4029 static const YIELD_STAR = 1;
4030 static const ITERATION_ENDED = 2;
4031 static const UNCAUGHT_ERROR = 3;
4032
4033 final value;
4034 final int state;
4035
4036 IterationMarker._(this.state, this.value);
4037
4038 static yieldStar(dynamic /* Iterable or Stream */ values) {
4039 return new IterationMarker._(YIELD_STAR, values);
4040 }
4041
4042 static endOfIteration() {
4043 return new IterationMarker._(ITERATION_ENDED, null);
4044 }
4045
4046 static yieldSingle(dynamic value) {
4047 return new IterationMarker._(YIELD_SINGLE, value);
4048 }
4049
4050 static uncaughtError(dynamic error) {
4051 return new IterationMarker._(UNCAUGHT_ERROR, error);
4052 }
4053
4054 toString() => "IterationMarker($state, $value)";
4055 }
4056
4057 class SyncStarIterator implements Iterator {
4058 final dynamic _body;
4059
4060 // If [runningNested] this is the nested iterator, otherwise it is the
4061 // current value.
4062 dynamic _current = null;
4063 bool _runningNested = false;
4064
4065 get current => _runningNested ? _current.current : _current;
4066
4067 SyncStarIterator(this._body);
4068
4069 _runBody() {
4070 return JS('', '''
4071 // Invokes [body] with [errorCode] and [result].
4072 //
4073 // If (and as long as) the invocation throws, calls [function] again,
4074 // with an error-code.
4075 (function(body) {
4076 var errorValue, errorCode = #;
4077 while (true) {
4078 try {
4079 return body(errorCode, errorValue);
4080 } catch (error) {
4081 errorValue = error;
4082 errorCode = #
4083 }
4084 }
4085 })(#)''', async_error_codes.SUCCESS, async_error_codes.ERROR, _body);
4086 }
4087
4088
4089 bool moveNext() {
4090 if (_runningNested) {
4091 if (_current.moveNext()) {
4092 return true;
4093 } else {
4094 _runningNested = false;
4095 }
4096 }
4097 _current = _runBody();
4098 if (_current is IterationMarker) {
4099 if (_current.state == IterationMarker.ITERATION_ENDED) {
4100 _current = null;
4101 // Rely on [_body] to repeatedly return `ITERATION_ENDED`.
4102 return false;
4103 } else if (_current.state == IterationMarker.UNCAUGHT_ERROR) {
4104 // Rely on [_body] to repeatedly return `UNCAUGHT_ERROR`.
4105 // This is a wrapped exception, so we use JavaScript throw to throw it.
4106 JS('', 'throw #', _current.value);
4107 } else {
4108 assert(_current.state == IterationMarker.YIELD_STAR);
4109 _current = _current.value.iterator;
4110 _runningNested = true;
4111 return moveNext();
4112 }
4113 }
4114 return true;
4115 }
4116 }
4117
4118 /// An Iterable corresponding to a sync* method.
4119 ///
4120 /// Each invocation of a sync* method will return a new instance of this class.
4121 class SyncStarIterable extends IterableBase {
4122 // This is a function that will return a helper function that does the
4123 // iteration of the sync*.
4124 //
4125 // Each invocation should give a body with fresh state.
4126 final dynamic /* js function */ _outerHelper;
4127
4128 SyncStarIterable(this._outerHelper);
4129
4130 Iterator get iterator => new SyncStarIterator(JS('', '#()', _outerHelper));
4131 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698