content: // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
  // for details. All rights reserved. Use of this source code is governed by a
  // BSD-style license that can be found in the LICENSE file.
  
  /**
   * This part contains helpers for supporting runtime type information.
   *
   * The helper use a mixture of Dart and JavaScript objects. To indicate which is
   * used where we adopt the scheme of using explicit type annotation for Dart
   * objects and 'var' or omitted return type for JavaScript objects.
   *
   * Since bool, int, and String values are represented by the same JavaScript
   * primitives, type annotations are used for these types in all cases.
   *
   * Several methods use a common JavaScript encoding of runtime type information.
   * This encoding is referred to as the type representation which is one of
   * these:
   *  1) a JavaScript constructor for a class C: the represented type is the raw
   *     type C.
   *  2) a Dart object: this is the interceptor instance for a native type.
   *  3) a JavaScript object: this represents a class for which there is no
   *     JavaScript constructor, because it is only used in type arguments or it
   *     is native. The represented type is the raw type of this class.
   *  4) a JavaScript array: the first entry is of type 1, 2 or 3 and contains the
   *     subtyping flags and the substitution of the type and the rest of the
   *     array are the type arguments.
   *  5) `null`: the dynamic type.
   *
   *
   * To check subtype relations between generic classes we use a JavaScript
   * expression that describes the necessary substitution for type arguments.
   * Such a substitution expresssion can be:
   *  1) `null`, if no substituted check is necessary, because the
   *     type variables are the same or there are no type variables in the class
   *     that is checked for.
   *  2) A list expression describing the type arguments to be used in the
   *     subtype check, if the type arguments to be used in the check do not
   *     depend on the type arguments of the object.
   *  3) A function mapping the type variables of the object to be checked to
   *     a list expression.
   */
  
  part of _js_helper;
  
  Type createRuntimeType(String name) => new TypeImpl(name);
  
  class TypeImpl implements Type {
    final String _typeName;
    String _unmangledName;
  
    TypeImpl(this._typeName);
  
    String toString() {
      if (_unmangledName != null) return _unmangledName;
      String unmangledName = unmangleGlobalNameIfPreservedAnyways(_typeName);
      // TODO(ahe): Handle type arguments.
      if (unmangledName == null) unmangledName = _typeName;
      return _unmangledName = unmangledName;
    }
  
    // TODO(ahe): This is a poor hashCode as it collides with its name.
    int get hashCode => _typeName.hashCode;
  
    bool operator ==(other) {
      return  (other is TypeImpl) && _typeName == other._typeName;
    }
  }
  
  /**
   * Represents a type variable.
   *
   * This class holds the information needed when reflecting on generic classes
   * and their members.
   */
  class TypeVariable {
    final Type owner;
    final String name;
    final int bound;
  
    const TypeVariable(this.owner, this.name, this.bound);
  }
  
  getMangledTypeName(TypeImpl type) => type._typeName;
  
  /**
   * Sets the runtime type information on [target]. [typeInfo] is a type
   * representation of type 4 or 5, that is, either a JavaScript array or
   * [:null:].
   */
  Object setRuntimeTypeInfo(Object target, var typeInfo) {
    assert(isNull(typeInfo) || isJsArray(typeInfo));
    // We have to check for null because factories may return null.
    if (target != null) JS('var', r'#.$builtinTypeInfo = #', target, typeInfo);
    return target;
  }
  
  /**
   * Returns the runtime type information of [target]. The returned value is a
   * list of type representations for the type arguments.
   */
  getRuntimeTypeInfo(Object target) {
    if (target == null) return null;
    return JS('var', r'#.$builtinTypeInfo', target);
  }
  
  /**
   * Returns the type arguments of [target] as an instance of [substitutionName].
   */
  getRuntimeTypeArguments(target, substitutionName) {
    var substitution =
        getField(target, '${JS_OPERATOR_AS_PREFIX()}$substitutionName');
    return substitute(substitution, getRuntimeTypeInfo(target));
  }
  
  /**
   * Returns the [index]th type argument of [target] as an instance of
   * [substitutionName].
   */
  getRuntimeTypeArgument(Object target, String substitutionName, int index) {
    var arguments = getRuntimeTypeArguments(target, substitutionName);
    return isNull(arguments) ? null : getIndex(arguments, index);
  }
  
  /**
   * Retrieves the class name from type information stored on the constructor
   * of [object].
   */
  String getClassName(var object) {
    return JS('String', r'#.constructor.builtin$cls', getInterceptor(object));
  }
  
  /**
   * Creates the string representation for the type representation [runtimeType]
   * of type 4, the JavaScript array, where the first element represents the class
   * and the remaining elements represent the type arguments.
   */
  String getRuntimeTypeAsString(var runtimeType, {String onTypeVariable(int i)}) {
    assert(isJsArray(runtimeType));
    String className = getConstructorName(getIndex(runtimeType, 0));
    return '$className'
           '${joinArguments(runtimeType, 1, onTypeVariable: onTypeVariable)}';
  }
  
  /**
   * Retrieves the class name from type information stored on the constructor
   * [type].
   */
  String getConstructorName(var type) => JS('String', r'#.builtin$cls', type);
  
  /**
   * Returns a human-readable representation of the type representation [type].
   */
  String runtimeTypeToString(var type , {String onTypeVariable(int i)}) {
    if (isNull(type)) {
      return 'dynamic';
    } else if (isJsArray(type)) {
      // A list representing a type with arguments.
      return getRuntimeTypeAsString(type, onTypeVariable: onTypeVariable);
    } else if (isJsFunction(type)) {
      // A reference to the constructor.
      return getConstructorName(type);
    } else if (type is int) {
      if (onTypeVariable == null) {
        return type.toString();
      } else {
        return onTypeVariable(type);
      }
    } else {
      return null;
    }
  }
  
  /**
   * Creates a comma-separated string of human-readable representations of the
   * type representations in the JavaScript array [types] starting at index
   * [startIndex].
   */
  String joinArguments(var types, int startIndex,
                       {String onTypeVariable(int i)}) {
    if (isNull(types)) return '';
    assert(isJsArray(types));
    bool firstArgument = true;
    bool allDynamic = true;
    StringBuffer buffer = new StringBuffer();
    for (int index = startIndex; index < getLength(types); index++) {
      if (firstArgument) {
        firstArgument = false;
      } else {
        buffer.write(', ');
      }
      var argument = getIndex(types, index);
      if (argument != null) {
        allDynamic = false;
      }
      buffer.write(runtimeTypeToString(argument, onTypeVariable: onTypeVariable));
    }
    return allDynamic ? '' : '<$buffer>';
  }
  
  /**
   * Returns a human-readable representation of the type of [object].
   */
  String getRuntimeTypeString(var object) {
    String className = isJsArray(object) ? 'List' : getClassName(object);
    var typeInfo = JS('var', r'#.$builtinTypeInfo', object);
    return "$className${joinArguments(typeInfo, 0)}";
  }
  
  Type getRuntimeType(var object) {
    String type = getRuntimeTypeString(object);
    return new TypeImpl(type);
  }
  
  /**
   * Applies the [substitution] on the [arguments].
   *
   * See the comment in the beginning of this file for a description of the
   * possible values for [substitution].
   */
  substitute(var substitution, var arguments) {
    assert(isNull(substitution) ||
           isJsArray(substitution) ||
           isJsFunction(substitution));
    assert(isNull(arguments) || isJsArray(arguments));
    if (isJsArray(substitution)) {
      arguments = substitution;
    } else if (isJsFunction(substitution)) {
      substitution = invoke(substitution, arguments);
      if (isJsArray(substitution)) {
        arguments = substitution;
      } else if (isJsFunction(substitution)) {
        // TODO(johnniwinther): Check if this is still needed.
        arguments = invoke(substitution, arguments);
      }
    }
    return arguments;
  }
  
  /**
   * Perform a type check with arguments on the Dart object [object].
   *
   * Parameters:
   * - [isField]: the name of the flag/function to check if the object
   *   is of the correct class.
   * - [checks]: the (JavaScript) list of type representations for the
   *   arguments to check against.
   * - [asField]: the name of the function that transforms the type
   *   arguments of [objects] to an instance of the class that we check
   *   against.
   */
  bool checkSubtype(Object object, String isField, List checks, String asField) {
    if (object == null) return false;
    var arguments = getRuntimeTypeInfo(object);
    // Interceptor is needed for JSArray and native classes.
    // TODO(sra): It could be a more specialized interceptor since [object] is not
    // `null` or a primitive.
    // TODO(9586): Move type info for static functions onto an interceptor.
    var interceptor = getInterceptor(object);
    var isSubclass = getField(interceptor, isField);
    // When we read the field and it is not there, [isSubclass] will be [:null:].
    if (isNull(isSubclass)) return false;
    // Should the asField function be passed the receiver?
    var substitution = getField(interceptor, asField);
    return checkArguments(substitution, arguments, checks);
  }
  
  String computeTypeName(String isField, List arguments) {
    // Shorten the field name to the class name and append the textual
    // representation of the type arguments.
    int prefixLength = JS_OPERATOR_IS_PREFIX().length;
    return Primitives.formatType(isField.substring(prefixLength, isField.length),
                                 arguments);
  }
  
  Object subtypeCast(Object object, String isField, List checks, String asField) {
    if (object != null && !checkSubtype(object, isField, checks, asField)) {
      String actualType = Primitives.objectTypeName(object);
      String typeName = computeTypeName(isField, checks);
      // TODO(johnniwinther): Move type lookup to [CastErrorImplementation] to
      // align with [TypeErrorImplementation].
      throw new CastErrorImplementation(actualType, typeName);
    }
    return object;
  }
  
  Object assertSubtype(Object object, String isField, List checks,
                       String asField) {
    if (object != null && !checkSubtype(object, isField, checks, asField)) {
      String typeName = computeTypeName(isField, checks);
      throw new TypeErrorImplementation(object, typeName);
    }
    return object;
  }
  
  /// Checks that the type represented by [subtype] is a subtype of [supertype].
  /// If not a type error with [message] is thrown.
  assertIsSubtype(var subtype, var supertype, String message) {
    if (!isSubtype(subtype, supertype)) {
      throwTypeError(message);
    }
  }
  
  throwTypeError(message) {
    throw new TypeErrorImplementation.fromMessage(message);
  }
  
  /**
   * Check that the types in the list [arguments] are subtypes of the types in
   * list [checks] (at the respective positions), possibly applying [substitution]
   * to the arguments before the check.
   *
   * See the comment in the beginning of this file for a description of the
   * possible values for [substitution].
   */
  bool checkArguments(var substitution, var arguments, var checks) {
    return areSubtypes(substitute(substitution, arguments), checks);
  }
  
  /**
   * Checks whether the types of [s] are all subtypes of the types of [t].
   *
   * [s] and [t] are either [:null:] or JavaScript arrays of type representations,
   * A [:null:] argument is interpreted as the arguments of a raw type, that is a
   * list of [:dynamic:]. If [s] and [t] are JavaScript arrays they must be of the
   * same length.
   *
   * See the comment in the beginning of this file for a description of type
   * representations.
   */
  bool areSubtypes(var s, var t) {
    // [:null:] means a raw type.
    if (isNull(s) || isNull(t)) return true;
  
    assert(isJsArray(s));
    assert(isJsArray(t));
    assert(getLength(s) == getLength(t));
  
    int len = getLength(s);
    for (int i = 0; i < len; i++) {
      if (!isSubtype(getIndex(s, i), getIndex(t, i))) {
        return false;
      }
    }
    return true;
  }
  
  Object functionSubtypeCast(Object object, String signatureName,
                             String contextName, var context,
                             var typeArguments) {
    if (!checkFunctionSubtype(object, signatureName,
                              contextName, context, typeArguments)) {
      String actualType = Primitives.objectTypeName(object);
      // TODO(johnniwinther): Provide better function type naming.
      String typeName = signatureName;
      throw new CastErrorImplementation(actualType, typeName);
    }
    return object;
  }
  
  Object assertFunctionSubtype(Object object, String signatureName,
                               String contextName, var context,
                               var typeArguments) {
    if (!checkFunctionSubtype(object, signatureName,
        contextName, context, typeArguments)) {
      // TODO(johnniwinther): Provide better function type naming.
      String typeName = signatureName;
      throw new TypeErrorImplementation(object, typeName);
    }
    return object;
  }
  
  /**
   * Checks that the type of [target] is a subtype of the function type denoted by
   * [signatureName]. If the type contains type variables, [contextName] holds the
   * name of the class where these were declared and either [context] holds the
   * object in which the runtime values of these can be found or [typeArguments]
   * contains these values as a list of runtime type information.
   */
  bool checkFunctionSubtype(var target, String signatureName,
                            String contextName, var context,
                            var typeArguments) {
    if (isNull(target)) return true;
    var interceptor = getInterceptor(target);
    if (hasField(interceptor, '${JS_OPERATOR_IS_PREFIX()}_$signatureName')) {
      return true;
    }
    var signatureLocation = JS_CURRENT_ISOLATE();
    if (isNotNull(contextName)) {
      signatureLocation = getField(JS('=Object', 'init.allClasses'), contextName);
    }
    var typeSignature =
        getField(signatureLocation, '${JS_SIGNATURE_NAME()}_$signatureName');
    if (isNull(typeSignature)) {
      // All checks can be determined statically so the type signature has not
      // been computed.
      return false;
    }
    var targetSignatureFunction = getField(interceptor, '${JS_SIGNATURE_NAME()}');
    if (isNull(targetSignatureFunction)) return false;
    var targetSignature = invokeOn(targetSignatureFunction, interceptor, null);
    if (isJsFunction(typeSignature)) {
      if (isNotNull(typeArguments)) {
        typeSignature = invoke(typeSignature, typeArguments);
      } else if (isNotNull(context)) {
        typeSignature =
            invoke(typeSignature, getRuntimeTypeArguments(context, contextName));
      } else {
        typeSignature = invoke(typeSignature, null);
      }
    }
    return isFunctionSubtype(targetSignature, typeSignature);
  }
  
  /**
   * Computes the signature by applying the type arguments of [context] as an
   * instance of [contextName] to the signature function [signature].
   */
  computeSignature(var signature, var context, var contextName) {
    var typeArguments = getRuntimeTypeArguments(context, contextName);
    return invokeOn(signature, context, typeArguments);
  }
  
  /**
   * Returns [:true:] if the runtime type representation [type] is a supertype of
   * [:Null:].
   */
  bool isSupertypeOfNull(var type) {
    // `null` means `dynamic`.
    return isNull(type) || getConstructorName(type) == JS_OBJECT_CLASS_NAME()
                        || getConstructorName(type) == JS_NULL_CLASS_NAME();
  }
  
  /**
   * Tests whether the Dart object [o] is a subtype of the runtime type
   * representation [t].
   *
   * See the comment in the beginning of this file for a description of type
   * representations.
   */
  bool checkSubtypeOfRuntimeType(Object o, var t) {
    if (isNull(o)) return isSupertypeOfNull(t);
    if (isNull(t)) return true;
    // Get the runtime type information from the object here, because we may
    // overwrite o with the interceptor below.
    var rti = getRuntimeTypeInfo(o);
    o = getInterceptor(o);
    // We can use the object as its own type representation because we install
    // the subtype flags and the substitution on the prototype, so they are
    // properties of the object in JS.
    var type;
    if (isNotNull(rti)) {
      // If the type has type variables (that is, [:rti != null:]), make a copy of
      // the type arguments and insert [o] in the first position to create a
      // compound type representation.
      type = JS('JSExtendableArray', '#.slice()', rti);
      JS('', '#.splice(0, 0, #)', type, o);
    } else {
      // Use the object as representation of the raw type.
      type = o;
    }
    return isSubtype(type, t);
  }
  
  Object subtypeOfRuntimeTypeCast(Object object, var type) {
    if (object != null && !checkSubtypeOfRuntimeType(object, type)) {
      String actualType = Primitives.objectTypeName(object);
      throw new CastErrorImplementation(actualType, runtimeTypeToString(type));
    }
    return object;
  }
  
  Object assertSubtypeOfRuntimeType(Object object, var type) {
    if (object != null && !checkSubtypeOfRuntimeType(object, type)) {
      throw new TypeErrorImplementation(object, runtimeTypeToString(type));
    }
    return object;
  }
  
  /**
   * Extracts the type arguments from a type representation. The result is a
   * JavaScript array or [:null:].
   */
  getArguments(var type) {
    return isJsArray(type) ? JS('var', r'#.slice(1)', type) : null;
  }
  
  /**
   * Checks whether the type represented by the type representation [s] is a
   * subtype of the type represented by the type representation [t].
   *
   * See the comment in the beginning of this file for a description of type
   * representations.
   */
  bool isSubtype(var s, var t) {
    // Subtyping is reflexive.
    if (isIdentical(s, t)) return true;
    // If either type is dynamic, [s] is a subtype of [t].
    if (isNull(s) || isNull(t)) return true;
    if (hasField(t, '${JS_FUNCTION_TYPE_TAG()}')) {
      if (hasNoField(s, '${JS_FUNCTION_TYPE_TAG()}')) {
        var signatureName =
            '${JS_OPERATOR_IS_PREFIX()}_${getField(t, JS_FUNCTION_TYPE_TAG())}';
        if (hasField(s, signatureName)) return true;
        var targetSignatureFunction = getField(s, '${JS_SIGNATURE_NAME()}');
        if (isNull(targetSignatureFunction)) return false;
        s = invokeOn(targetSignatureFunction, s, null);
      }
      return isFunctionSubtype(s, t);
    }
    // Check function types against the Function class.
    if (getConstructorName(t) == JS_FUNCTION_CLASS_NAME() &&
        hasField(s, '${JS_FUNCTION_TYPE_TAG()}')) {
      return true;
    }
    // Get the object describing the class and check for the subtyping flag
    // constructed from the type of [t].
    var typeOfS = isJsArray(s) ? getIndex(s, 0) : s;
    var typeOfT = isJsArray(t) ? getIndex(t, 0) : t;
    // Check for a subtyping flag.
    var name = runtimeTypeToString(typeOfT);
    var test = '${JS_OPERATOR_IS_PREFIX()}${name}';
    if (hasNoField(typeOfS, test)) return false;
    // Get the necessary substitution of the type arguments, if there is one.
    var substitution;
    if (isNotIdentical(typeOfT, typeOfS)) {
      var field = '${JS_OPERATOR_AS_PREFIX()}${runtimeTypeToString(typeOfT)}';
      substitution = getField(typeOfS, field);
    }
    // The class of [s] is a subclass of the class of [t].  If [s] has no type
    // arguments and no substitution, it is used as raw type.  If [t] has no
    // type arguments, it used as a raw type.  In both cases, [s] is a subtype
    // of [t].
    if ((!isJsArray(s) && isNull(substitution)) || !isJsArray(t)) {
      return true;
    }
    // Recursively check the type arguments.
    return checkArguments(substitution, getArguments(s), getArguments(t));
  }
  
  bool isAssignable(var s, var t) {
    return isSubtype(s, t) || isSubtype(t, s);
  }
  
  /**
   * If [allowShorter] is [:true:], [t] is allowed to be shorter than [s].
   */
  bool areAssignable(List s, List t, bool allowShorter) {
    // Both lists are empty and thus equal.
    if (isNull(t) && isNull(s)) return true;
    // [t] is empty (and [s] is not) => only OK if [allowShorter].
    if (isNull(t)) return allowShorter;
    // [s] is empty (and [t] is not) => [s] is not longer or equal to [t].
    if (isNull(s)) return false;
  
    assert(isJsArray(s));
    assert(isJsArray(t));
  
    int sLength = getLength(s);
    int tLength = getLength(t);
    if (allowShorter) {
      if (sLength < tLength) return false;
    } else {
      if (sLength != tLength) return false;
    }
  
    for (int i = 0; i < tLength; i++) {
      if (!isAssignable(getIndex(s, i), getIndex(t, i))) {
        return false;
      }
    }
    return true;
  }
  
  bool areAssignableMaps(var s, var t) {
    if (isNull(t)) return true;
    if (isNull(s)) return false;
  
    assert(isJsObject(s));
    assert(isJsObject(t));
  
    return JS('bool', r'''
       function (t, s, isAssignable) {
         for (var $name in t) {
           if (!s.hasOwnProperty($name)) {
             return false;
           }
           var tType = t[$name];
           var sType = s[$name];
           if (!isAssignable.call$2(sType, tType)) {
            return false;
           }
         }
         return true;
       }(#, #, #)
    ''', t, s, RAW_DART_FUNCTION_REF(isAssignable));
  }
  
  bool isFunctionSubtype(var s, var t) {
    assert(hasField(t, '${JS_FUNCTION_TYPE_TAG()}'));
    if (hasNoField(s, '${JS_FUNCTION_TYPE_TAG()}')) return false;
    if (hasField(s, '${JS_FUNCTION_TYPE_VOID_RETURN_TAG()}')) {
      if (hasNoField(t, '${JS_FUNCTION_TYPE_VOID_RETURN_TAG()}') &&
          hasField(t, '${JS_FUNCTION_TYPE_RETURN_TYPE_TAG()}')) {
        return false;
      }
    } else if (hasNoField(t, '${JS_FUNCTION_TYPE_VOID_RETURN_TAG()}')) {
      var sReturnType = getField(s, '${JS_FUNCTION_TYPE_RETURN_TYPE_TAG()}');
      var tReturnType = getField(t, '${JS_FUNCTION_TYPE_RETURN_TYPE_TAG()}');
      if (!isAssignable(sReturnType, tReturnType)) return false;
    }
    var sParameterTypes =
        getField(s, '${JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG()}');
    var tParameterTypes =
        getField(t, '${JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG()}');
  
    var sOptionalParameterTypes =
        getField(s, '${JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG()}');
    var tOptionalParameterTypes =
        getField(t, '${JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG()}');
  
    int sParametersLen =
        isNotNull(sParameterTypes) ? getLength(sParameterTypes) : 0;
    int tParametersLen =
        isNotNull(tParameterTypes) ? getLength(tParameterTypes) : 0;
  
    int sOptionalParametersLen = isNotNull(sOptionalParameterTypes)
        ? getLength(sOptionalParameterTypes) : 0;
    int tOptionalParametersLen = isNotNull(tOptionalParameterTypes)
        ? getLength(tOptionalParameterTypes) : 0;
  
    if (sParametersLen > tParametersLen) {
      // Too many required parameters in [s].
      return false;
    }
    if (sParametersLen + sOptionalParametersLen <
        tParametersLen + tOptionalParametersLen) {
      // Too few required and optional parameters in [s].
      return false;
    }
    if (sParametersLen == tParametersLen) {
      // Simple case: Same number of required parameters.
      if (!areAssignable(sParameterTypes, tParameterTypes, false)) return false;
      if (!areAssignable(sOptionalParameterTypes,
                         tOptionalParameterTypes, true)) {
        return false;
      }
    } else {
      // Complex case: Optional parameters of [s] for required parameters of [t].
      int pos = 0;
      // Check all required parameters of [s].
      for (; pos < sParametersLen; pos++) {
        if (!isAssignable(getIndex(sParameterTypes, pos),
                          getIndex(tParameterTypes, pos))) {
          return false;
        }
      }
      int sPos = 0;
      int tPos = pos;
      // Check the remaining parameters of [t] with the first optional parameters
      // of [s].
      for (; tPos < tParametersLen ; sPos++, tPos++) {
        if (!isAssignable(getIndex(sOptionalParameterTypes, sPos),
                          getIndex(tParameterTypes, tPos))) {
          return false;
        }
      }
      sPos = 0;
      // Check the optional parameters of [t] with the remaing optional parameters
      // of [s]:
      for (; tPos < tOptionalParametersLen ; sPos++, tPos++) {
        if (!isAssignable(getIndex(tOptionalParameterTypes, sPos),
                          getIndex(tOptionalParameterTypes, tPos))) {
          return false;
        }
      }
    }
  
    var sNamedParameters =
        getField(s, '${JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG()}');
    var tNamedParameters =
        getField(t, '${JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG()}');
    return areAssignableMaps(sNamedParameters, tNamedParameters);
  }
  
  /**
   * Calls the JavaScript [function] with the [arguments] with the global scope
   * as the [:this:] context.
   */
  invoke(var function, var arguments) => invokeOn(function, null, arguments);
  
  /**
   * Calls the JavaScript [function] with the [arguments] with [receiver] as the
   * [:this:] context.
   */
  Object invokeOn(function, receiver, arguments) {
    assert(isJsFunction(function));
    assert(isNull(arguments) || isJsArray(arguments));
    return JS('var', r'#.apply(#, #)', function, receiver, arguments);
  }
  
  /// Calls the property [name] on the JavaScript [object].
  call(var object, String name) => JS('var', r'#[#]()', object, name);
  
  /// Returns the property [name] of the JavaScript object [object].
  getField(var object, String name) => JS('var', r'#[#]', object, name);
  
  /// Returns the property [index] of the JavaScript array [array].
  getIndex(var array, int index) {
    assert(isJsArray(array));
    return JS('var', r'#[#]', array, index);
  }
  
  /// Returns the length of the JavaScript array [array].
  int getLength(var array) {
    assert(isJsArray(array));
    return JS('int', r'#.length', array);
  }
  
  /// Returns whether [value] is a JavaScript array.
  bool isJsArray(var value) {
    return value is JSArray;
  }
  
  hasField(var object, var name) => JS('bool', r'# in #', name, object);
  
  hasNoField(var object, var name) => !hasField(object, name);
  
  /// Returns [:true:] if [o] is a JavaScript function.
  bool isJsFunction(var o) => JS('bool', r'typeof # == "function"', o);
  
  /// Returns [:true:] if [o] is a JavaScript object.
  bool isJsObject(var o) => JS('bool', r"typeof # == 'object'", o);
  
  /**
   * Returns [:true:] if [o] is equal to [:null:], that is either [:null:] or
   * [:undefined:]. We use this helper to avoid generating code under the invalid
   * assumption that [o] is a Dart value.
   */
  bool isNull(var o) => JS('bool', '# == null', o);
  
  /**
   * Returns [:true:] if [o] is not equal to [:null:], that is neither [:null:]
   * nor [:undefined:].  We use this helper to avoid generating code under the
   * invalid assumption that [o] is a Dart value.
   */
  bool isNotNull(var o) => JS('bool', '# != null', o);
  
  /**
   * Returns [:true:] if the JavaScript values [s] and [t] are identical. We use
   * this helper to avoid generating code under the invalid assumption that [s]
   * and [t] are Dart values.
   */
  bool isIdentical(var s, var t) => JS('bool', '# === #', s, t);
  
  /**
   * Returns [:true:] if the JavaScript values [s] and [t] are not identical. We
   * use this helper to avoid generating code under the invalid assumption that
   * [s] and [t] are Dart values.
   */
  bool isNotIdentical(var s, var t) => JS('bool', '# !== #', s, t);
  