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

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

Powered by Google App Engine
This is Rietveld 408576698