text:
package com.google.javascript.jscomp.newtypes;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.NodeUtil;
import com.google.javascript.jscomp.parsing.parser.util.format.SimpleFormat;
import com.google.javascript.rhino.FunctionTypeI;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.ObjectTypeI;
import com.google.javascript.rhino.TypeI;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

/**
 *
 * @author blickly@google.com (Ben Lickly)
 * @author dimvar@google.com (Dimitris Vardoulakis)
 */
public abstract class JSType implements TypeI, FunctionTypeI, ObjectTypeI {
  private static final int BOTTOM_MASK = 0x0;
  private static final int TYPEVAR_MASK = 0x1;
  private static final int NON_SCALAR_MASK = 0x2;
  private static final int ENUM_MASK = 0x4;
  // The less important use case for TRUE_MASK and FALSE_MASK is to type the
  // values true and false precisely. But people don't write: if (true) {...}
  // More importantly, these masks come up as the negation of TRUTHY_MASK and
  // FALSY_MASK when the ! operator is used.
  private static final int TRUE_MASK = 0x8;  // These two print out
  private static final int FALSE_MASK = 0x10; // as 'boolean'
  private static final int NULL_MASK = 0x20;
  private static final int NUMBER_MASK = 0x40;
  private static final int STRING_MASK = 0x80;
  private static final int UNDEFINED_MASK = 0x100;
  private static final int END_MASK = UNDEFINED_MASK * 2;
  // When either of the next two bits is set, the rest of the type isn't
  // guaranteed to be in a consistent state.
  private static final int TRUTHY_MASK = 0x200;
  private static final int FALSY_MASK = 0x400;
  // Room to grow.
  private static final int UNKNOWN_MASK = 0x7fffffff; // @type {?}
  private static final int TOP_MASK = 0xffffffff; // @type {*}

  private static final int BOOLEAN_MASK = TRUE_MASK | FALSE_MASK;
  private static final int TOP_SCALAR_MASK =
      NUMBER_MASK | STRING_MASK | BOOLEAN_MASK | NULL_MASK | UNDEFINED_MASK;

  //Masks for common types:
  private static final int NUMBER_OR_STRING_MASK = NUMBER_MASK | STRING_MASK;
  // union of undefined and stuff
  private static final int UNDEFINED_OR_BOOLEAN_MASK = UNDEFINED_MASK | TRUE_MASK | FALSE_MASK;
  private static final int UNDEFINED_OR_NUMBER_MASK = UNDEFINED_MASK | NUMBER_MASK;
  private static final int UNDEFINED_OR_STRING_MASK = UNDEFINED_MASK | STRING_MASK;
  private static final int UNDEFINED_OR_NULL_MASK = UNDEFINED_MASK | NULL_MASK;
  // union of null and stuff
  private static final int NULL_OR_BOOLEAN_MASK = NULL_MASK | TRUE_MASK | FALSE_MASK;
  private static final int NULL_OR_NUMBER_MASK = NULL_MASK | NUMBER_MASK;
  private static final int NULL_OR_STRING_MASK = NULL_MASK | STRING_MASK;

  private static final ImmutableSet<ObjectType> NO_OBJS = ImmutableSet.<ObjectType>of();
  private static final ImmutableSet<EnumType> NO_ENUMS = ImmutableSet.<EnumType>of();

  private final JSTypes commonTypes;

  // Used only for development, to test performance of the code without the cost
  // of printing the error messages.
  public static boolean mockToString = false;

  private JSType(JSTypes commonTypes) {
    Preconditions.checkNotNull(commonTypes);
    this.commonTypes = commonTypes;
  }

  private static JSType makeType(JSTypes commonTypes,
      int mask, ImmutableSet<ObjectType> objs,
      String typeVar, ImmutableSet<EnumType> enums) {
    // Fix up the mask for objects and enums
    if (Preconditions.checkNotNull(enums).isEmpty()) {
      mask &= ~ENUM_MASK;
    } else {
      mask |= ENUM_MASK;
    }

    if (Preconditions.checkNotNull(objs).isEmpty()) {
      mask &= ~NON_SCALAR_MASK;
    } else {
      mask |= NON_SCALAR_MASK;
    }

    if (objs.isEmpty() && enums.isEmpty()
        && typeVar == null && (mask & TYPEVAR_MASK) == 0) {
      return makeMaskType(commonTypes, mask);
    }
    if (!JSType.isInhabitable(objs)) {
      return commonTypes.BOTTOM;
    }
    if (mask == NON_SCALAR_MASK) {
      return new ObjsType(commonTypes, objs);
    }
    if (mask == (NON_SCALAR_MASK | NULL_MASK)) {
      return new NullableObjsType(commonTypes, objs);
    }
    return new UnionType(commonTypes, mask, objs, typeVar, enums);
  }

  private static JSType makeType(JSTypes commonTypes, int mask) {
    return makeType(commonTypes, mask, NO_OBJS, null, NO_ENUMS);
  }

  static JSType makeMaskType(JSTypes commonTypes, int mask) {
    switch (mask) {
      case BOTTOM_MASK:
        return commonTypes.BOTTOM;
      case TRUE_MASK:
        return commonTypes.TRUE_TYPE;
      case FALSE_MASK:
        return commonTypes.FALSE_TYPE;
      case NULL_MASK:
        return commonTypes.NULL;
      case NUMBER_MASK:
        return commonTypes.NUMBER;
      case STRING_MASK:
        return commonTypes.STRING;
      case UNDEFINED_MASK:
        return commonTypes.UNDEFINED;
      case TRUTHY_MASK:
        return commonTypes.TRUTHY;
      case FALSY_MASK:
        return commonTypes.FALSY;
      case UNKNOWN_MASK:
        return commonTypes.UNKNOWN;
      case TOP_MASK:
        return commonTypes.TOP;
      case BOOLEAN_MASK:
        return commonTypes.BOOLEAN;
      case NUMBER_OR_STRING_MASK:
        return commonTypes.NUMBER_OR_STRING;
      case UNDEFINED_OR_BOOLEAN_MASK:
        return commonTypes.UNDEFINED_OR_BOOLEAN;
      case UNDEFINED_OR_NUMBER_MASK:
        return commonTypes.UNDEFINED_OR_NUMBER;
      case UNDEFINED_OR_STRING_MASK:
        return commonTypes.UNDEFINED_OR_STRING;
      case UNDEFINED_OR_NULL_MASK:
        return commonTypes.NULL_OR_UNDEFINED;
      case NULL_OR_BOOLEAN_MASK:
        return commonTypes.NULL_OR_BOOLEAN;
      case NULL_OR_NUMBER_MASK:
        return commonTypes.NULL_OR_NUMBER;
      case NULL_OR_STRING_MASK:
        return commonTypes.NULL_OR_STRING;
      default:
        return new MaskType(commonTypes, mask);
    }
  }

  protected abstract int getMask();

  abstract ImmutableSet<ObjectType> getObjs();

  protected abstract String getTypeVar();

  protected abstract ImmutableSet<EnumType> getEnums();

  // Factory method for wrapping a function in a JSType
  static JSType fromFunctionType(FunctionType fn, NominalType fnNominal) {
    return makeType(
        fn.getCommonTypes(),
        NON_SCALAR_MASK,
        ImmutableSet.of(ObjectType.fromFunction(fn, fnNominal)),
        null, NO_ENUMS);
  }

  public static JSType fromObjectType(ObjectType obj) {
    return makeType(obj.getCommonTypes(), NON_SCALAR_MASK, ImmutableSet.of(obj), null, NO_ENUMS);
  }

  public static JSType fromTypeVar(JSTypes commonTypes, String typevarName) {
    return makeType(commonTypes, TYPEVAR_MASK, NO_OBJS, typevarName, NO_ENUMS);
  }

  static JSType fromEnum(EnumType e) {
    return makeType(e.getCommonTypes(), ENUM_MASK, NO_OBJS, null, ImmutableSet.of(e));
  }

  final boolean isValidType() {
    if (isUnknown() || isTop()) {
      return true;
    }
    if ((getMask() & NON_SCALAR_MASK) != 0
        && getObjs().isEmpty()) {
      return false;
    }
    if ((getMask() & NON_SCALAR_MASK) == 0 && !getObjs().isEmpty()) {
      return false;
    }
    if ((getMask() & ENUM_MASK) != 0
        && getEnums().isEmpty()) {
      return false;
    }
    if ((getMask() & ENUM_MASK) == 0 && !getEnums().isEmpty()) {
      return false;
    }
    return ((getMask() & TYPEVAR_MASK) != 0) == (getTypeVar() != null);
  }

  static Map<String, JSType> createScalars(JSTypes commonTypes) {
    LinkedHashMap<String, JSType> types = new LinkedHashMap<>();

    types.put("BOOLEAN", new MaskType(commonTypes, TRUE_MASK | FALSE_MASK));
    types.put("BOTTOM", new MaskType(commonTypes, BOTTOM_MASK));
    types.put("FALSE_TYPE", new MaskType(commonTypes, FALSE_MASK));
    types.put("FALSY", new MaskType(commonTypes, FALSY_MASK));
    types.put("NULL", new MaskType(commonTypes, NULL_MASK));
    types.put("NUMBER", new MaskType(commonTypes, NUMBER_MASK));
    types.put("STRING", new MaskType(commonTypes, STRING_MASK));
    types.put("TOP", new MaskType(commonTypes, TOP_MASK));
    types.put("TOP_SCALAR", new MaskType(commonTypes, TOP_SCALAR_MASK));
    types.put("TRUE_TYPE", new MaskType(commonTypes, TRUE_MASK));
    types.put("TRUTHY", new MaskType(commonTypes, TRUTHY_MASK));
    types.put("UNDEFINED", new MaskType(commonTypes, UNDEFINED_MASK));
    types.put("UNKNOWN", new MaskType(commonTypes, UNKNOWN_MASK));

    types.put("UNDEFINED_OR_BOOLEAN", new MaskType(commonTypes, UNDEFINED_OR_BOOLEAN_MASK));
    types.put("UNDEFINED_OR_NUMBER", new MaskType(commonTypes, UNDEFINED_OR_NUMBER_MASK));
    types.put("UNDEFINED_OR_STRING", new MaskType(commonTypes, UNDEFINED_OR_STRING_MASK));
    types.put("NULL_OR_BOOLEAN", new MaskType(commonTypes, NULL_OR_BOOLEAN_MASK));
    types.put("NULL_OR_NUMBER", new MaskType(commonTypes, NULL_OR_NUMBER_MASK));
    types.put("NULL_OR_STRING", new MaskType(commonTypes, NULL_OR_STRING_MASK));
    types.put("NULL_OR_UNDEFINED", new MaskType(commonTypes, NULL_MASK | UNDEFINED_MASK));
    types.put("NUMBER_OR_STRING", new MaskType(commonTypes, NUMBER_MASK | STRING_MASK));

    return types;
  }

  @Override
  public final boolean isTop() {
    return TOP_MASK == getMask();
  }

  @Override
  public final boolean isBottom() {
    return BOTTOM_MASK == getMask();
  }

  public final boolean isUndefined() {
    return UNDEFINED_MASK == getMask();
  }

  public final boolean isUnknown() {
    return UNKNOWN_MASK == getMask();
  }

  public final boolean isTrueOrTruthy() {
    return TRUTHY_MASK == getMask() || TRUE_MASK == getMask();
  }

  private boolean isTheTruthyType() {
    return TRUTHY_MASK == getMask();
  }

  private boolean isTheTrueType() {
    return TRUE_MASK == getMask();
  }

  public final boolean isFalseOrFalsy() {
    return FALSY_MASK == getMask() || FALSE_MASK == getMask();
  }

  // Ignoring enums for simplicity
  public final boolean isAnyTruthyType() {
    int mask = getMask();
    int truthyMask = TRUTHY_MASK | TRUE_MASK | NON_SCALAR_MASK;
    return mask != BOTTOM_MASK && (mask | truthyMask) == truthyMask;
  }

  // Ignoring enums for simplicity
  public final boolean isAnyFalsyType() {
    int mask = getMask();
    int falsyMask = FALSY_MASK | FALSE_MASK | NULL_MASK | UNDEFINED_MASK;
    return mask != BOTTOM_MASK && (mask | falsyMask) == falsyMask;
  }

  private boolean isTheFalsyType() {
    return FALSY_MASK == getMask();
  }

  private boolean isTheFalseType() {
    return FALSE_MASK == getMask();
  }

  public final boolean isBoolean() {
    return (getMask() & ~BOOLEAN_MASK) == 0 && (getMask() & BOOLEAN_MASK) != 0;
  }

  @Override
  public final boolean isBooleanValueType() {
    return isBoolean();
  }

  public final boolean isString() {
    return STRING_MASK == getMask();
  }

  @Override
  public final boolean isStringValueType() {
    return isString();
  }

  public final boolean isNumber() {
    return NUMBER_MASK == getMask();
  }

  @Override
  public final boolean isNumberValueType() {
    return isNumber();
  }

  public final boolean isNullOrUndef() {
    int nullUndefMask = NULL_MASK | UNDEFINED_MASK;
    return getMask() != 0 && (getMask() | nullUndefMask) == nullUndefMask;
  }

  public final boolean isScalar() {
    return getMask() == NUMBER_MASK
        || getMask() == STRING_MASK
        || getMask() == NULL_MASK
        || getMask() == UNDEFINED_MASK
        || isBoolean();
  }

  // True iff there exists a value that can have this type
  private static boolean isInhabitable(Set<ObjectType> objs) {
    for (ObjectType obj : objs) {
      if (!obj.isInhabitable()) {
        return false;
      }
    }
    return true;
  }

  final JSTypes getCommonTypes() {
    return this.commonTypes;
  }

  final boolean hasScalar() {
    return (getMask() & TOP_SCALAR_MASK) != 0 || EnumType.hasScalar(getEnums());
  }

  public final boolean hasNonScalar() {
    return !getObjs().isEmpty() || EnumType.hasNonScalar(getEnums());
  }

  @Override
  public final boolean isNullable() {
    return !isTop() && (getMask() & NULL_MASK) != 0;
  }

  @Override
  public final boolean isTypeVariable() {
    return getMask() == TYPEVAR_MASK;
  }

  public final boolean hasTypeVariable() {
    return (getMask() & TYPEVAR_MASK) != 0;
  }

  public final boolean isStruct() {
    if (isUnknown()) {
      return false;
    }
    Preconditions.checkState(!getObjs().isEmpty(),
        "Expected object type but found %s", this);
    for (ObjectType objType : getObjs()) {
      if (!objType.isStruct()) {
        return false;
      }
    }
    return true;
  }

  public final boolean mayBeStruct() {
    for (ObjectType objType : getObjs()) {
      if (objType.isStruct()) {
        return true;
      }
    }
    return false;
  }

  public final boolean isStructWithoutProp(QualifiedName pname) {
    for (ObjectType obj : getObjs()) {
      if (obj.isStruct() && !obj.mayHaveProp(pname)) {
        return true;
      }
    }
    return false;
  }

  public final boolean isLoose() {
    ImmutableSet<ObjectType> objs = getObjs();
    return objs.size() == 1 && Iterables.getOnlyElement(objs).isLoose();
  }

  public final boolean isDict() {
    if (isUnknown()) {
      return false;
    }
    Preconditions.checkState(!getObjs().isEmpty());
    for (ObjectType objType : getObjs()) {
      if (!objType.isDict()) {
        return false;
      }
    }
    return true;
  }

  // Returns null if this type doesn't inherit from IObject
  public final JSType getIndexType() {
    if (getMask() != NON_SCALAR_MASK) {
      return null;
    }
    // This (union) type is a supertype of all indexed types in the union.
    // Different from NominalType#getIndexType, which uses join.
    JSType result = this.commonTypes.TOP;
    // We need this because the index type may explicitly be TOP.
    boolean foundIObject = false;
    for (ObjectType objType : getObjs()) {
      JSType tmp = objType.getNominalType().getIndexType();
      if (tmp == null) {
        return null;
      }
      foundIObject = true;
      result = meet(result, tmp);
    }
    return foundIObject ? result : null;
  }

  // May be called for types that include non-objects, and we ignore the
  // non-object parts in those cases.
  public final JSType getIndexedType() {
    if ((getMask() & NON_SCALAR_MASK) == 0) {
      return null;
    }
    JSType result = this.commonTypes.BOTTOM;
    for (ObjectType objType : getObjs()) {
      JSType tmp = objType.getNominalType().getIndexedType();
      if (tmp == null) {
        return null;
      }
      result = join(result, tmp);
    }
    return result.isBottom() ? null : result;
  }

  public final boolean mayBeDict() {
    for (ObjectType objType : getObjs()) {
      if (objType.isDict()) {
        return true;
      }
    }
    return false;
  }

  @Override
  public final boolean isEnumElement() {
    return getMask() == ENUM_MASK && getEnums().size() == 1;
  }

  @Override
  public final boolean isEnumObject() {
    ObjectType obj = getObjTypeIfSingletonObj();
    return obj != null && obj.isEnumObject();
  }

  public final boolean isUnion() {
    if (isBottom() || isTop() || isUnknown()
        || isScalar() || isTypeVariable() || isEnumElement()
        || isTheTruthyType() || isTheFalsyType()) {
      return false;
    }
    return !(getMask() == NON_SCALAR_MASK && getObjs().size() == 1);
  }

  public final boolean isFunctionWithProperties() {
    ObjectType obj = getObjTypeIfSingletonObj();
    return obj != null && obj.isFunctionWithProperties();
  }

  public final boolean isNamespace() {
    ObjectType obj = getObjTypeIfSingletonObj();
    return obj != null && obj.isNamespace();
  }

  // Only makes sense for a JSType that represents a single enum
  @Override
  public final JSType getEnumeratedTypeOfEnumElement() {
    return isEnumElement() ?
        Iterables.getOnlyElement(getEnums()).getEnumeratedType() : null;
  }

  @Override
  public final JSType autobox() {
    if (isTop() || isUnknown()) {
      return this;
    }
    int mask = getMask();
    if ((mask & (NUMBER_MASK | STRING_MASK | BOOLEAN_MASK | ENUM_MASK)) == BOTTOM_MASK) {
      return this;
    }
    switch (mask) {
      case NUMBER_MASK:
        return this.commonTypes.getNumberInstance();
      case BOOLEAN_MASK:
      case TRUE_MASK:
      case FALSE_MASK:
        return this.commonTypes.getBooleanInstance();
      case STRING_MASK:
        return this.commonTypes.getStringInstance();
      default:
        // Not a scalar type: handled below.
    }
    // For each set bit, add the corresponding obj to the new objs
    // construct and return the new type.
    // Don't bother autoboxing enums.
    ImmutableSet.Builder<ObjectType> builder = ImmutableSet.builder();
    builder.addAll(getObjs());
    if ((mask & NUMBER_MASK) != 0) {
      builder.add(this.commonTypes.getNumberInstanceObjType());
    }
    if ((mask & STRING_MASK) != 0) {
      builder.add(this.commonTypes.getStringInstanceObjType());
    }
    if ((mask & BOOLEAN_MASK) != 0) { // may have truthy or falsy
      builder.add(this.commonTypes.getBooleanInstanceObjType());
    }
    JSType result = makeType(
        this.commonTypes,
        mask & ~(NUMBER_MASK | STRING_MASK | BOOLEAN_MASK),
        builder.build(), getTypeVar(), NO_ENUMS);
    for (EnumType e : getEnums()) {
      result = join(result, e.getEnumeratedType().autobox());
    }
    return result;
  }

  static JSType nullAcceptingJoin(JSType t1, JSType t2) {
    if (t1 == null) {
      return t2;
    } else if (t2 == null) {
      return t1;
    }
    return JSType.join(t1, t2);
  }

  static JSType joinManyTypes(JSTypes commonTypes, Iterable<JSType> types) {
    JSType result = commonTypes.BOTTOM;
    for (JSType t : types) {
      result = join(result, t);
    }
    return result;
  }

  // When joining w/ TOP or UNKNOWN, avoid setting more fields on them, eg, obj.
  public static JSType join(JSType lhs, JSType rhs) {
    Preconditions.checkNotNull(lhs);
    Preconditions.checkNotNull(rhs);
    JSTypes commonTypes = lhs.commonTypes;
    if (lhs.isTop() || rhs.isTop()) {
      return commonTypes.TOP;
    }
    if (lhs.isUnknown() || rhs.isUnknown()) {
      return commonTypes.UNKNOWN;
    }
    if (lhs.isBottom()) {
      return rhs;
    }
    if (rhs.isBottom()) {
      return lhs;
    }
    if (lhs.isTheTruthyType() || lhs.isTheFalsyType()
        || rhs.isTheTruthyType() || rhs.isTheFalsyType()) {
      return commonTypes.UNKNOWN;
    }
    if (lhs.getTypeVar() != null && rhs.getTypeVar() != null
        && !lhs.getTypeVar().equals(rhs.getTypeVar())) {
      // For now return ? when joining two type vars. This is probably uncommon.
      return commonTypes.UNKNOWN;
    }

    int newMask = lhs.getMask() | rhs.getMask();
    ImmutableSet<ObjectType> newObjs =
        ObjectType.joinSets(lhs.getObjs(), rhs.getObjs());
    String newTypevar =
        lhs.getTypeVar() != null ? lhs.getTypeVar() : rhs.getTypeVar();
    ImmutableSet<EnumType> newEnums =
        EnumType.union(lhs.getEnums(), rhs.getEnums());
    if (newEnums.isEmpty()) {
      return makeType(commonTypes, newMask, newObjs, newTypevar, NO_ENUMS);
    }
    JSType tmpJoin =
        makeType(commonTypes, newMask & ~ENUM_MASK, newObjs, newTypevar, NO_ENUMS);
    return makeType(commonTypes, newMask, newObjs,
        newTypevar, EnumType.normalizeForJoin(newEnums, tmpJoin));
  }

  public final JSType substituteGenerics(Map<String, JSType> concreteTypes) {
    if (isTop()
        || isUnknown()
        || (getObjs().isEmpty() && getTypeVar() == null)
        || concreteTypes.isEmpty()) {
      return this;
    }
    // TODO(dimvar): By adding prints, I found that the majority of the time, when
    // we substitute generics in obj, it has no effect; the result is equal to the type
    // before substitution. I did some timing tests to estimate what the improvement
    // would be if we did better here, and they were inconclusive because of large variance
    // in the test runs. Intuitively though, it seems that we can save time here,
    // so revisit this in the future when I have time to dig in deeper.
    ImmutableSet.Builder<ObjectType> builder = ImmutableSet.builder();
    for (ObjectType obj : getObjs()) {
      builder.add(obj.substituteGenerics(concreteTypes));
    }
    JSType current = makeType(
        this.commonTypes, getMask() & ~TYPEVAR_MASK, builder.build(), null, getEnums());
    if (hasTypeVariable()) {
      current = JSType.join(current, concreteTypes.containsKey(getTypeVar()) ?
          concreteTypes.get(getTypeVar()) : fromTypeVar(this.commonTypes, getTypeVar()));
    }
    return current;
  }

  public final JSType substituteGenericsWithUnknown() {
    return substituteGenerics(this.commonTypes.MAP_TO_UNKNOWN);
  }

  private static void updateTypemap(
      Multimap<String, JSType> typeMultimap,
      String typeParam, JSType type) {
    Preconditions.checkNotNull(type);
    Set<JSType> typesToRemove = new LinkedHashSet<>();
    for (JSType other : typeMultimap.get(typeParam)) {
      if (type.isUnknown()) {
        typesToRemove.add(other);
        continue;
      }
      if (other.isUnknown()) {
        type = null;
        break;
      }
      // The only way to instantiate with a loose type is if there are no
      // concrete types available. We may miss some warnings this way but we
      // also avoid false positives.
      if (type.isLoose()) {
        type = null;
        break;
      } else if (other.isLoose()) {
        typesToRemove.add(other);
        continue;
      }
      JSType unified = unifyUnknowns(type, other);
      if (unified != null) {
        // Can't remove elms while iterating over the collection, so do it later
        typesToRemove.add(other);
        type = unified;
      } else if (other.isSubtypeOf(type, SubtypeCache.create())) {
        typesToRemove.add(other);
      } else if (type.isSubtypeOf(other, SubtypeCache.create())) {
        type = null;
        break;
      }
    }
    for (JSType typeToRemove : typesToRemove) {
      typeMultimap.remove(typeParam, typeToRemove);
    }
    if (type != null) {
      typeMultimap.put(typeParam, type);
    }
  }

  private final JSType promoteBoolean() {
    return isTheTrueType() || isTheFalseType() ? this.commonTypes.BOOLEAN : this;
  }

  private static int promoteBooleanMask(int mask) {
    if ((mask & (TRUE_MASK | FALSE_MASK)) != 0) {
      return mask | TRUE_MASK | FALSE_MASK;
    }
    return mask;
  }

  /**
   * Unify the two types symmetrically, given that we have already instantiated
   * the type variables of interest in {@code t1} and {@code t2}, treating
   * JSType.UNKNOWN as a "hole" to be filled.
   * @return The unified type, or null if unification fails */
  static JSType unifyUnknowns(JSType t1, JSType t2) {
    Preconditions.checkNotNull(t1);
    Preconditions.checkNotNull(t2);
    if (t1.isUnknown() || t1.isLoose()) {
      return t2;
    } else if (t2.isUnknown() || t2.isLoose()) {
      return t1;
    } else if (t1.isTop() && t2.isTop()) {
      return t1.commonTypes.TOP;
    } else if (t1.isTop() || t2.isTop()) {
      return null;
    }

    if (!t1.getEnums().equals(t2.getEnums())) {
      return null;
    }
    ImmutableSet<EnumType> newEnums = t1.getEnums();

    int t1Mask = promoteBooleanMask(t1.getMask());
    int t2Mask = promoteBooleanMask(t2.getMask());
    if (t1Mask != t2Mask || !Objects.equals(t1.getTypeVar(), t2.getTypeVar())) {
      return null;
    }
    // All scalar types are equal
    if ((t1Mask & NON_SCALAR_MASK) == 0) {
      return t1;
    }
    if (t1.getObjs().size() != t2.getObjs().size()) {
      return null;
    }

    Set<ObjectType> ununified = new LinkedHashSet<>(t2.getObjs());
    Set<ObjectType> unifiedObjs = new LinkedHashSet<>();
    for (ObjectType objType1 : t1.getObjs()) {
      ObjectType unified = objType1;
      boolean hasUnified = false;
      for (ObjectType objType2 : t2.getObjs()) {
        ObjectType tmp = ObjectType.unifyUnknowns(unified, objType2);
        if (tmp != null) {
          hasUnified = true;
          ununified.remove(objType2);
          unified = tmp;
        }
      }
      if (!hasUnified) {
        return null;
      }
      unifiedObjs.add(unified);
    }
    if (!ununified.isEmpty()) {
      return null;
    }
    return makeType(t1.commonTypes, t1Mask,
        ImmutableSet.copyOf(unifiedObjs), t1.getTypeVar(), newEnums);
  }

  /**
   * Unify {@code this}, which may contain free type variables,
   * with {@code other}, a concrete subtype, modifying the supplied
   * {@code typeMultimap} to add any new template variable type bindings.
   *
   * This method should only be called outside the newtypes package;
   * classes inside the package should use unifyWithSubtype.
   *
   * Unification algorithm.
   *
   * Say that {@code this} is a potentially generic type G and {@code other} is a concrete type C.
   * 1. If C is not a union:
   *    (A) If C is a subtype of G, then unification succeeds and we don't update the type map.
   *        E.g., (T|string) unifies with string and we learn nothing about T.
   *    (B) If some member of G unifies with C, unification succeeds and we update the type map.
   *    (C) If 2+ members of G unify with C, we use each of them to update the type map. This is
   *        simpler than depending on the iteration order of the members of G to decide which one
   *        to unify with C.
   *        E.g., if Foo extends Bar and implements Baz,
   *        (Bar(T)|Baz(T)) unifies with Foo(number) and T is number.
   *        (Bar(number, T)|Baz(T, string)) doesn't unify with Foo(number,string), but each member
   *        unifies with C, and we get a "not unique instantiation" warning.
   *
   * 2. If C is a union:
   *    We throw away C's members that are subtypes of G, and for each remaining
   *    member we try to individually unify G with it. A single member of G may unify with
   *    2+ members of C.
   *    Let SubC be a type that contains all members of C that are left over: they are not subtypes
   *    of G and G doesn't unify with them. If G is of the form (T|...), then T is mapped to SubC.
   *    E.g., (T|Foo|Bar(U)) unifies with (number|string|Foo|Bar(Baz))
   *    SubC is (number|string), T is mapped to (number|string), and U is mapped to Baz.
   */
  public final void unifyWith(JSType other, List<String> typeParameters,
      Multimap<String, JSType> typeMultimap) {
    unifyWithSubtype(other, typeParameters, typeMultimap, SubtypeCache.create());
  }

  /**
   * Unify this, which is a potentially generic type G, with other, a concrete type C.
   * Unifying means finding concrete types to substitute for the type variables in G and get a
   * type G', such that C is a subtype of G'.
   *
   * Doing unification of function types correctly also requires a unifyWithSupertype method.
   * See {@link FunctionType#unifyWithSubtype} for details. For now, we think that's an overkill.
   *
   * @param subSuperMap needed to avoid infinite recursion when unifying structural interfaces.
   * @return whether unification succeeded
   */
  final boolean unifyWithSubtype(JSType other, List<String> typeParameters,
      Multimap<String, JSType> typeMultimap, SubtypeCache subSuperMap) {
    if (other.isUnknown()) {
      String thisTypevar = getTypeVar();
      if (thisTypevar != null && typeParameters.contains(thisTypevar)) {
        updateTypemap(typeMultimap, thisTypevar, other);
      }
      return true;
    }
    HashSet<JSType> leftovers = new HashSet<>();
    for (JSType otherMember : other.getUnionMembers()) {
      if (otherMember.isSubtypeOf(this)) {
        continue;
      }
      otherMember = otherMember.promoteBoolean();
      if (!unifyWithSingleType(otherMember, typeParameters, typeMultimap, subSuperMap)) {
        leftovers.add(otherMember);
      }
    }
    if (leftovers.isEmpty()) {
      return true;
    }
    String thisTypevar = getTypeVar();
    if (thisTypevar != null && typeParameters.contains(thisTypevar)) {
      updateTypemap(typeMultimap, thisTypevar, joinManyTypes(this.commonTypes, leftovers));
      return true;
    }
    return false;
  }

  /**
   * Unify other, which is NOT a union type, with this, which may be a union.
   * Note that if other is a primitive type then this method will always return false:
   * primitives may only be unified with a typevar as "leftovers".
   */
  final boolean unifyWithSingleType(JSType other, List<String> typeParameters,
      Multimap<String, JSType> typeMultimap, SubtypeCache subSuperMap) {
    Preconditions.checkArgument(!other.isUnion(), "Expected non-union type but found: %s", other);
    if (other.isEnumElement()) {
      JSType enumType = other.getEnumeratedTypeOfEnumElement();
      return unifyWithSingleType(enumType, typeParameters, typeMultimap, subSuperMap);
    }
    if (other.isSingletonObj()) {
      ObjectType otherObj = other.getObjTypeIfSingletonObj();
      boolean result = false;
      for (ObjectType thisObj : getObjs()) {
        // Note that we want to execute thisObj.unifyWithSubtype(otherObj, ...) even if result
        // is already true, because several members of the generic type may unify with otherObj.
        if (thisObj.unifyWithSubtype(otherObj, typeParameters, typeMultimap, subSuperMap)) {
          result = true;
        }
      }
      return result;
    }
    return false;
  }

  public final JSType specialize(JSType other) {
    JSType t = specializeHelper(other);
    if (t.isBottom() && (isLoose() || other.isLoose())) {
      t = autobox().specializeHelper(other.autobox());
      // If the autoboxed specialization is not null, this means that one of
      // the two types contains scalars that when autoboxed are compatible with
      // the loose object in the other type. In this case, don't return bottom,
      // just leave the type unspecialized.
      if (!t.isBottom()) {
        return this;
      }
    }
    return t.isLoose() ? ObjectType.mayTurnLooseObjectToScalar(t, this.commonTypes) : t;
  }

  @SuppressWarnings("ReferenceEquality")
  private JSType specializeHelper(JSType other) {
    if (other.isTop() || other.isUnknown() || this == other) {
      return this;
    }
    if (other.isTheTruthyType()) {
      return makeTruthy();
    }
    if (isTheTruthyType()) {
      // If the only thing we know about this type is that it's truthy, that's very
      // little information, so we loosen the other type to avoid spurious warnings.
      JSType otherTruthy = other.makeTruthy();
      return otherTruthy.hasNonScalar()
          ? otherTruthy.withLoose()
          : otherTruthy;
    }
    if (other.isTheFalsyType()) {
      return makeFalsy();
    }
    if (isTheFalsyType()) {
      return other.makeFalsy();
    }
    if (this.isTop()) {
      return other;
    }
    if (this.isUnknown()) {
      NominalType otherNt = other.getNominalTypeIfSingletonObj();
      return otherNt != null && otherNt.isBuiltinObject() ? other.withLoose() : other;
    }
    int newMask = getMask() & other.getMask();
    String newTypevar;
    if (Objects.equals(getTypeVar(), other.getTypeVar())) {
      newTypevar = getTypeVar();
    } else if (getTypeVar() != null && other.getTypeVar() == null) {
      // Consider, e.g., function f(/** T|number */ x) { if (typeof x === 'string') { ... } }
      // We want to specialize the T to string, rather than going to bottom.
      return other;
    } else {
      newTypevar = null;
      newMask &= ~TYPEVAR_MASK;
    }
    return meetEnums(
        this.commonTypes, newMask, getMask() | other.getMask(),
        ObjectType.specializeSet(getObjs(), other.getObjs()),
        newTypevar, getObjs(), other.getObjs(), getEnums(), other.getEnums());
  }

  public static JSType meet(JSType lhs, JSType rhs) {
    JSType t = meetHelper(lhs, rhs);
    if (t.isBottom() && (lhs.isLoose() || rhs.isLoose())) {
      t = meetHelper(lhs.autobox(), rhs.autobox());
      // If the autoboxed meet is not null, this means that one of
      // the two types contains scalars that when autoboxed are compatible with
      // the loose object in the other type. In this case, don't return bottom,
      // use some heuristic to return a result.
      if (!t.isBottom()) {
        if (!lhs.isLoose()) {
          return lhs;
        } else {
          // If this fails, find repro case, add a test, and return ? here.
          Preconditions.checkState(!rhs.isLoose(), "Two loose types %s and %s"
              + " that meet to bottom, meet to non-bottom after autoboxing.",
              lhs.toString(), rhs.toString());
          return rhs;
        }
      }
    }
    return t;
  }

  private static JSType meetHelper(JSType lhs, JSType rhs) {
    if (lhs.isTop()) {
      return rhs;
    }
    if (rhs.isTop()) {
      return lhs;
    }
    if (lhs.isUnknown()) {
      return rhs;
    }
    if (rhs.isUnknown()) {
      return lhs;
    }
    if (lhs.isBottom() || rhs.isBottom()) {
      return lhs.commonTypes.BOTTOM;
    }
    if (lhs.isTheTruthyType()) {
      return rhs.makeTruthy();
    }
    if (lhs.isTheFalsyType()) {
      return rhs.makeFalsy();
    }
    if (rhs.isTheTruthyType()) {
      return lhs.makeTruthy();
    }
    if (rhs.isTheFalsyType()) {
      return lhs.makeFalsy();
    }
    int newMask = lhs.getMask() & rhs.getMask();
    String newTypevar;
    if (Objects.equals(lhs.getTypeVar(), rhs.getTypeVar())) {
      newTypevar = lhs.getTypeVar();
    } else {
      newTypevar = null;
      newMask = newMask & ~TYPEVAR_MASK;
    }
    return meetEnums(
        lhs.commonTypes, newMask, lhs.getMask() | rhs.getMask(),
        ObjectType.meetSets(lhs.getObjs(), rhs.getObjs()),
        newTypevar, lhs.getObjs(), rhs.getObjs(),
        lhs.getEnums(), rhs.getEnums());
  }

  /**
   * Both {@code meet} and {@code specialize} do the same computation for enums.
   * They don't just compute the set of enums; they may modify mask and objs.
   * So, both methods finish off by calling this one.
   */
  private static JSType meetEnums(JSTypes commonTypes, int newMask, int unionMask,
      ImmutableSet<ObjectType> newObjs, String newTypevar,
      ImmutableSet<ObjectType> objs1, ImmutableSet<ObjectType> objs2,
      ImmutableSet<EnumType> enums1, ImmutableSet<EnumType> enums2) {
    if (Objects.equals(enums1, enums2)) {
      return makeType(commonTypes, newMask, newObjs, newTypevar, enums1);
    }
    ImmutableSet.Builder<EnumType> enumBuilder = ImmutableSet.builder();
    ImmutableSet<EnumType> allEnums = EnumType.union(enums1, enums2);
    for (EnumType e : allEnums) {
      // An enum in the intersection will always be in the result
      if (enums1 != null && enums1.contains(e)
          && enums2 != null && enums2.contains(e)) {
        enumBuilder.add(e);
        continue;
      }
      // An enum {?} in the union will always be in the result
      JSType enumeratedType = e.getEnumeratedType();
      if (enumeratedType.isUnknown()) {
        enumBuilder.add(e);
        continue;
      }
      // An enum {TypeA} meets with any supertype of TypeA. When this happens,
      // we put the enum in the result and remove the supertype.
      // The following would be much more complex if we allowed the type of
      // an enum to be a union.
      if (enumeratedType.getMask() != NON_SCALAR_MASK) {
        if ((enumeratedType.getMask() & unionMask) != 0) {
          enumBuilder.add(e);
          newMask &= ~enumeratedType.getMask();
        }
      } else if (!objs1.isEmpty() || !objs2.isEmpty()) {
        Set<ObjectType> objsToRemove = new LinkedHashSet<>();
        ObjectType enumObj = Iterables.getOnlyElement(enumeratedType.getObjs());
        for (ObjectType obj1 : objs1) {
          if (enumObj.isSubtypeOf(obj1, SubtypeCache.create())) {
            enumBuilder.add(e);
            objsToRemove.add(obj1);
          }
        }
        for (ObjectType obj2 : objs2) {
          if (enumObj.isSubtypeOf(obj2, SubtypeCache.create())) {
            enumBuilder.add(e);
            objsToRemove.add(obj2);
          }
        }
        if (!objsToRemove.isEmpty()) {
          newObjs = Sets.difference(newObjs, objsToRemove).immutableCopy();
        }
      }
    }
    return makeType(commonTypes, newMask, newObjs, newTypevar, enumBuilder.build());
  }

  public static boolean haveCommonSubtype(JSType lhs, JSType rhs) {
    return lhs.isBottom() || rhs.isBottom() || !meet(lhs, rhs).isBottom();
  }

  private JSType makeTruthy() {
    if (this.isTop()) {
      return this;
    }
    if (this.isUnknown()) {
      return this.commonTypes.TRUTHY;
    }
    return makeType(this.commonTypes,
        getMask() & ~NULL_MASK & ~FALSE_MASK & ~UNDEFINED_MASK,
        getObjs(), getTypeVar(), getEnums());
  }

  private JSType makeFalsy() {
    if (this.isTop()) {
      return this;
    }
    if (this.isUnknown()) {
      return this.commonTypes.FALSY;
    }
    return makeType(this.commonTypes,
        getMask() & ~TRUE_MASK & ~NON_SCALAR_MASK, NO_OBJS, getTypeVar(), getEnums());
  }

  public static JSType plus(JSType lhs, JSType rhs) {
    JSTypes commonTypes = lhs.commonTypes;
    if ((!lhs.isUnknown() && !lhs.isBottom() && lhs.isSubtypeOf(commonTypes.STRING))
        || (!rhs.isUnknown() && !rhs.isBottom() && rhs.isSubtypeOf(commonTypes.STRING))) {
      return commonTypes.STRING;
    }
    if (lhs.isUnknown() || lhs.isTop() || rhs.isUnknown() || rhs.isTop()) {
      return commonTypes.UNKNOWN;
    }
    // If either has string, string in the result.
    int newtype = (lhs.getMask() | rhs.getMask()) & STRING_MASK;
    // If both have non-string (including boolean, null, undefined),
    // number in the result.
    if ((lhs.getMask() & ~STRING_MASK) != 0
        && (rhs.getMask() & ~STRING_MASK) != 0) {
      newtype |= NUMBER_MASK;
    }
    return makeType(lhs.commonTypes, newtype);
  }

  public final JSType negate() {
    if (isTop() || isUnknown()) {
      return this;
    }
    if (isTrueOrTruthy()) {
      return this.commonTypes.FALSY;
    } else if (isFalseOrFalsy()) {
      return this.commonTypes.TRUTHY;
    }
    return this.commonTypes.UNKNOWN;
  }

  public final JSType toBoolean() {
    if (isTrueOrTruthy()) {
      return this.commonTypes.TRUE_TYPE;
    } else if (isFalseOrFalsy()) {
      return this.commonTypes.FALSE_TYPE;
    }
    return this.commonTypes.BOOLEAN;
  }

  public final boolean isNonLooseSubtypeOf(JSType other) {
    return isSubtypeOfHelper(false, other, SubtypeCache.create(), null);
  }

  @Override
  public final boolean isSubtypeOf(TypeI other) {
    return isSubtypeOf(other, SubtypeCache.create());
  }

  public static MismatchInfo whyNotSubtypeOf(JSType found, JSType expected) {
    if (found.isSingletonObj() && expected.isSingletonObj()) {
      MismatchInfo[] boxedInfo = new MismatchInfo[1];
      ObjectType.whyNotSubtypeOf(
          found.getObjTypeIfSingletonObj(),
          expected.getObjTypeIfSingletonObj(),
          boxedInfo);
      return boxedInfo[0];
    }
    if (found.isUnion()) {
      MismatchInfo[] boxedInfo = new MismatchInfo[1];
      boolean areSubtypes =
          found.isSubtypeOfHelper(true, expected, SubtypeCache.create(), boxedInfo);
      Preconditions.checkState(!areSubtypes);
      return boxedInfo[0];
    }
    return null;
  }

  final boolean isSubtypeOf(TypeI other, SubtypeCache subSuperMap) {
    if (this == other) {
      return true;
    }
    JSType type2 = (JSType) other;
    if (isLoose() || type2.isLoose()) {
      return this.commonTypes.looseSubtypingForLooseObjects
          ? haveCommonSubtype(autobox(), type2.autobox())
          : autobox().isSubtypeOfHelper(true, type2.autobox(), subSuperMap, null);
    } else {
      return isSubtypeOfHelper(true, type2, subSuperMap, null);
    }
  }

  private boolean isSubtypeOfHelper(
      boolean keepLoosenessOfThis, JSType other, SubtypeCache subSuperMap,
      MismatchInfo[] boxedInfo) {
    if (isUnknown() || other.isUnknown() || other.isTop()) {
      return true;
    }
    if (isTheTruthyType()) {
      return !other.makeTruthy().isBottom();
    }
    if (isTheFalsyType()) {
      return !other.makeFalsy().isBottom();
    }
    if (!EnumType.areSubtypes(this, other, subSuperMap)) {
      return false;
    }
    int mask = getMask() & ~ENUM_MASK;
    if ((mask | other.getMask()) != other.getMask()) {
      if (boxedInfo != null && isUnion()) {
        whyNotUnionSubtypes(this, other, boxedInfo);
      }
      return false;
    }
    if (getTypeVar() != null && !getTypeVar().equals(other.getTypeVar())) {
      return false;
    }
    if (getObjs().isEmpty()) {
      return true;
    }
    // Because of optional properties,
    //   x \le y \iff x \join y = y does not hold.
    boolean result = ObjectType.isUnionSubtype(
        keepLoosenessOfThis, getObjs(), other.getObjs(), subSuperMap);
    if (boxedInfo != null) {
      ObjectType.whyNotUnionSubtypes(
          keepLoosenessOfThis, getObjs(), other.getObjs(),
          subSuperMap, boxedInfo);
    }
    return result;
  }

  private static void whyNotUnionSubtypes(
      JSType found, JSType expected, MismatchInfo[] boxedInfo) {
    JSTypes commonTypes = found.commonTypes;
    if (commonTypes.NUMBER.isSubtypeOf(found) && !commonTypes.NUMBER.isSubtypeOf(expected)) {
      boxedInfo[0] = MismatchInfo.makeUnionTypeMismatch(commonTypes.NUMBER);
    } else if (commonTypes.STRING.isSubtypeOf(found)
        && !commonTypes.STRING.isSubtypeOf(expected)) {
      boxedInfo[0] = MismatchInfo.makeUnionTypeMismatch(commonTypes.STRING);
    } else if (commonTypes.BOOLEAN.isSubtypeOf(found)
        && !commonTypes.BOOLEAN.isSubtypeOf(expected)) {
      boxedInfo[0] = MismatchInfo.makeUnionTypeMismatch(commonTypes.BOOLEAN);
    } else if (commonTypes.NULL.isSubtypeOf(found)
        && !commonTypes.NULL.isSubtypeOf(expected)) {
      boxedInfo[0] = MismatchInfo.makeUnionTypeMismatch(commonTypes.NULL);
    } else if (commonTypes.UNDEFINED.isSubtypeOf(found)
        && !commonTypes.UNDEFINED.isSubtypeOf(expected)) {
      boxedInfo[0] = MismatchInfo.makeUnionTypeMismatch(commonTypes.UNDEFINED);
    } else if (found.hasTypeVariable() && !expected.hasTypeVariable()) {
      boxedInfo[0] = MismatchInfo.makeUnionTypeMismatch(
          fromTypeVar(found.commonTypes, found.getTypeVar()));
    } else if ((found.getMask() & NON_SCALAR_MASK) != 0
        && (expected.getMask() & NON_SCALAR_MASK) == 0) {
      boxedInfo[0] = MismatchInfo.makeUnionTypeMismatch(makeType(
          found.commonTypes, NON_SCALAR_MASK, found.getObjs(), null, NO_ENUMS));
    }
  }

  public final JSType removeType(JSType other) {
    int otherMask = other.getMask();
    Preconditions.checkState(
        !other.isTop() && !other.isUnknown()
        && (otherMask & TYPEVAR_MASK) == 0 && (otherMask & ENUM_MASK) == 0,
        "Requested invalid type to remove: %s", other);
    if (isUnknown()) {
      return this;
    }
    if (isTop()) {
      JSType almostTop = makeType(
          commonTypes,
          TRUE_MASK | FALSE_MASK | NUMBER_MASK | STRING_MASK
          | NULL_MASK | UNDEFINED_MASK | NON_SCALAR_MASK,
          ImmutableSet.of(this.commonTypes.getTopObjectType()), null, NO_ENUMS);
      return almostTop.removeType(other);
    }
    int newMask = getMask() & ~otherMask;
    if ((otherMask & NON_SCALAR_MASK) == 0) {
      return newMask == getMask()
          ? this : makeType(this.commonTypes, newMask, getObjs(), getTypeVar(), getEnums());
    }
    // TODO(dimvar): If objs and enums stay unchanged, reuse, don't recreate.
    Preconditions.checkState(other.getObjs().size() == 1,
        "Invalid type to remove: %s", other);
    ObjectType otherObj = Iterables.getOnlyElement(other.getObjs());
    ImmutableSet.Builder<ObjectType> objsBuilder = ImmutableSet.builder();
    for (ObjectType obj : getObjs()) {
      if (obj.isLoose() || !obj.isSubtypeOf(otherObj, SubtypeCache.create())) {
        objsBuilder.add(obj);
      }
    }
    ImmutableSet.Builder<EnumType> enumBuilder = ImmutableSet.builder();
    for (EnumType e : getEnums()) {
      if (!e.getEnumeratedType().isSubtypeOf(other, SubtypeCache.create())) {
        enumBuilder.add(e);
      }
    }
    return makeType(
       this.commonTypes, newMask, objsBuilder.build(), getTypeVar(), enumBuilder.build());
  }

  // Adds ft to this type, replacing the current function, if any.
  public final JSType withFunction(FunctionType ft, NominalType fnNominal) {
    // This method is used for a very narrow purpose, hence these checks.
    Preconditions.checkNotNull(ft);
    Preconditions.checkState(this.isNamespace());
    return fromObjectType(
        getObjTypeIfSingletonObj().withFunction(ft, fnNominal));
  }

  public static String createGetterPropName(String originalPropName) {
    return "%getter_fun" + originalPropName;
  }

  public static String createSetterPropName(String originalPropName) {
    return "%setter_fun" + originalPropName;
  }

  public final boolean isSingletonObj() {
    return getMask() == NON_SCALAR_MASK && getObjs().size() == 1;
  }

  final boolean isSingletonObjWithNull() {
    return getMask() == (NON_SCALAR_MASK | NULL_MASK) && getObjs().size() == 1;
  }

  final ObjectType getObjTypeIfSingletonObj() {
    return isSingletonObj() ? Iterables.getOnlyElement(getObjs()) : null;
  }

  public final FunctionType getFunTypeIfSingletonObj() {
    ObjectType obj = getObjTypeIfSingletonObj();
    return obj == null ? null : obj.getFunType();
  }

  public final FunctionType getFunType() {
    for (ObjectType obj : getObjs()) {
      FunctionType ft = obj.getFunType();
      if (ft != null) {
        return ft;
      }
    }
    return null;
  }

  public final NominalType getNominalTypeIfSingletonObj() {
    return isSingletonObj()
        ? Iterables.getOnlyElement(getObjs()).getNominalType() : null;
  }

  public final boolean isInterfaceInstance() {
    NominalType nt = getNominalTypeIfSingletonObj();
    return nt != null && nt.isInterface();
  }

  // True for functions and instances of Object (including object literals).
  public final boolean isNonClassyObject() {
    NominalType nt = getNominalTypeIfSingletonObj();
    return nt != null && !nt.isClassy();
  }

  public final boolean isIObject() {
    NominalType nt = getNominalTypeIfSingletonObj();
    return nt != null && nt.isIObject();
  }

  public final boolean isInterfaceDefinition() {
    FunctionType ft = getFunTypeIfSingletonObj();
    return ft != null && ft.isInterfaceDefinition();
  }

  /** Turns the class-less object of this type (if any) into a loose object */
  public final JSType withLoose() {
    if (getObjs().isEmpty()) {
      Preconditions.checkState(!getEnums().isEmpty());
      return this;
    }
    return makeType(this.commonTypes, getMask(),
        ObjectType.withLooseObjects(getObjs()), getTypeVar(), getEnums());
  }

  public final JSType getProp(QualifiedName qname) {
    if (isBottom() || isUnknown() || isTheTruthyType()) {
      return this.commonTypes.UNKNOWN;
    }
    Preconditions.checkState(!getObjs().isEmpty() || !getEnums().isEmpty(),
        "Can't getProp %s of type %s", qname, this);
    return nullAcceptingJoin(
        TypeWithPropertiesStatics.getProp(getObjs(), qname),
        TypeWithPropertiesStatics.getProp(getEnums(), qname));
  }

  public final JSType getDeclaredProp(QualifiedName qname) {
    if (isUnknown()) {
      return this.commonTypes.UNKNOWN;
    }
    Preconditions.checkState(!getObjs().isEmpty() || !getEnums().isEmpty());
    return nullAcceptingJoin(
        TypeWithPropertiesStatics.getDeclaredProp(getObjs(), qname),
        TypeWithPropertiesStatics.getDeclaredProp(getEnums(), qname));
  }

  public final boolean mayHaveProp(QualifiedName qname) {
    return TypeWithPropertiesStatics.mayHaveProp(getObjs(), qname) ||
        TypeWithPropertiesStatics.mayHaveProp(getEnums(), qname);
  }

  public final boolean hasProp(QualifiedName qname) {
    if (!getObjs().isEmpty()
        && !TypeWithPropertiesStatics.hasProp(getObjs(), qname)) {
      return false;
    }
    if (!getEnums().isEmpty()
        && !TypeWithPropertiesStatics.hasProp(getEnums(), qname)) {
      return false;
    }
    return !getEnums().isEmpty() || !getObjs().isEmpty();
  }

  public final boolean hasConstantProp(QualifiedName pname) {
    Preconditions.checkArgument(pname.isIdentifier());
    return TypeWithPropertiesStatics.hasConstantProp(getObjs(), pname) ||
        TypeWithPropertiesStatics.hasConstantProp(getEnums(), pname);
  }

  @Override
  public final boolean containsArray() {
    ObjectType arrayType = this.commonTypes.getArrayInstance().getObjTypeIfSingletonObj();
    Preconditions.checkNotNull(arrayType);
    for (ObjectType objType : this.getObjs()) {
      if (objType.isSubtypeOf(arrayType, SubtypeCache.create())) {
        return true;
      }
    }
    return false;
  }

  public final JSType withoutProperty(QualifiedName qname) {
    return getObjs().isEmpty() ?
        this :
        makeType(this.commonTypes, getMask(),
            ObjectType.withoutProperty(getObjs(), qname), getTypeVar(), getEnums());
  }

  public final JSType withProperty(QualifiedName qname, JSType type) {
    Preconditions.checkArgument(type != null);
    if (isUnknown() || isBottom() || getObjs().isEmpty()) {
      return this;
    }
    return makeType(this.commonTypes, getMask(),
        ObjectType.withProperty(getObjs(), qname, type), getTypeVar(), getEnums());
  }

  public final JSType withDeclaredProperty(
      QualifiedName qname, JSType type, boolean isConstant) {
    Preconditions.checkState(!getObjs().isEmpty());
    if (type == null && isConstant) {
      type = this.commonTypes.UNKNOWN;
    }
    return makeType(this.commonTypes,
        getMask(),
        ObjectType.withDeclaredProperty(getObjs(), qname, type, isConstant), getTypeVar(), getEnums());
  }

  public final JSType withPropertyRequired(String pname) {
    return (isUnknown() || getObjs().isEmpty()) ?
        this :
        makeType(this.commonTypes, getMask(),
            ObjectType.withPropertyRequired(getObjs(), pname), getTypeVar(), getEnums());
  }

  // For a type A, this method tries to return the greatest subtype of A that
  // has a property called pname. If it can't safely find a subtype, it
  // returns bottom.
  public final JSType findSubtypeWithProp(QualifiedName pname) {
    Preconditions.checkArgument(pname.isIdentifier());
    // common cases first
    if (isTop() || isUnknown() || (getMask() & NON_SCALAR_MASK) == 0) {
      return this.commonTypes.BOTTOM;
    }
    // For simplicity, if this type has scalars with pname, return bottom.
    // If it has enums, return bottom.
    if ((this.commonTypes.NUMBER.isSubtypeOf(this)
            && this.commonTypes.getNumberInstance().mayHaveProp(pname))
        || (this.commonTypes.STRING.isSubtypeOf(this)
            && this.commonTypes.getNumberInstance().mayHaveProp(pname))
        || (this.commonTypes.BOOLEAN.isSubtypeOf(this)
            && this.commonTypes.getBooleanInstance().mayHaveProp(pname))) {
      return this.commonTypes.BOTTOM;
    }
    if ((getMask() & ENUM_MASK) != 0) {
      return this.commonTypes.BOTTOM;
    }
    // NOTE(dimvar): Nothing for type variables for now, but we will have to
    // handle them here when we implement bounded generics.
    if (getObjs().size() == 1) {
      ObjectType obj = Iterables.getOnlyElement(getObjs());
      return obj.mayHaveProp(pname) ? this : this.commonTypes.BOTTOM;
    }
    ImmutableSet.Builder<ObjectType> builder = ImmutableSet.builder();
    boolean foundObjWithProp = false;
    for (ObjectType o : getObjs()) {
      if (o.mayHaveProp(pname)) {
        foundObjWithProp = true;
        builder.add(o);
      }
    }
    return foundObjWithProp
        ? makeType(this.commonTypes, NON_SCALAR_MASK, builder.build(), null, NO_ENUMS)
        : this.commonTypes.BOTTOM;
  }

  public final boolean isPropDefinedOnSubtype(QualifiedName pname) {
    Preconditions.checkArgument(pname.isIdentifier());
    for (ObjectType obj : getObjs()) {
      if (obj.isPropDefinedOnSubtype(pname)) {
        return true;
      }
    }
    return false;
  }

  @Override
  public final String toString() {
    if (mockToString) {
      return "";
    }
    return appendTo(new StringBuilder()).toString();
  }

  public final StringBuilder appendTo(StringBuilder builder) {
    return typeToString(builder);
  }

  /** For use in {@link #typeToString} */
  private static final Joiner PIPE_JOINER = Joiner.on("|");

  private StringBuilder typeToString(StringBuilder builder) {
    switch (getMask()) {
      case BOTTOM_MASK:
        return builder.append("bottom");
      case TOP_MASK:
        return builder.append("*");
      case UNKNOWN_MASK:
        return builder.append("?");
      default:
        int tags = getMask();
        boolean firstIteration = true;
        for (int tag = 1; tag != END_MASK; tag <<= 1) {
          if ((tags & tag) != 0) {
            if (!firstIteration) {
              builder.append('|');
            }
            firstIteration = false;
            switch (tag) {
              case TRUE_MASK:
              case FALSE_MASK:
                builder.append(
                    (tags & BOOLEAN_MASK) == BOOLEAN_MASK
                        ? "boolean"
                        : tag == TRUE_MASK
                        ? "true"
                        : "false");
                tags &= ~BOOLEAN_MASK;
                continue;
              case NULL_MASK:
                builder.append("null");
                tags &= ~NULL_MASK;
                continue;
              case NUMBER_MASK:
                builder.append("number");
                tags &= ~NUMBER_MASK;
                continue;
              case STRING_MASK:
                builder.append("string");
                tags &= ~STRING_MASK;
                continue;
              case UNDEFINED_MASK:
                builder.append("undefined");
                tags &= ~UNDEFINED_MASK;
                continue;
              case TYPEVAR_MASK:
                builder.append(UniqueNameGenerator.getOriginalName(getTypeVar()));
                tags &= ~TYPEVAR_MASK;
                continue;
              case NON_SCALAR_MASK: {
                if (getObjs().size() == 1) {
                  Iterables.getOnlyElement(getObjs()).appendTo(builder);
                } else {
                  Set<String> strReps = new TreeSet<>();
                  for (ObjectType obj : getObjs()) {
                    strReps.add(obj.toString());
                  }
                  PIPE_JOINER.appendTo(builder, strReps);
                }
                tags &= ~NON_SCALAR_MASK;
                continue;
              }
              case ENUM_MASK: {
                if (getEnums().size() == 1) {
                  builder.append(Iterables.getOnlyElement(getEnums()));
                } else {
                  Set<String> strReps = new TreeSet<>();
                  for (EnumType e : getEnums()) {
                    strReps.add(e.toString());
                  }
                  PIPE_JOINER.appendTo(builder, strReps);
                }
                tags &= ~ENUM_MASK;
                continue;
              }
              default:
                throw new AssertionError("Impossible: " + tag);
            }
          }
        }
        if (tags == 0) { // Found all types in the union
          return builder;
        } else if (tags == TRUTHY_MASK) {
          return builder.append("truthy");
        } else if (tags == FALSY_MASK) {
          return builder.append("falsy");
        } else {
          return builder.append("Unrecognized type: ").append(tags);
        }
    }
  }

  @Override
  public final boolean isConstructor() {
    FunctionType ft = getFunTypeIfSingletonObj();
    return ft != null && ft.isUniqueConstructor();
  }

  @Override
  public final boolean isEquivalentTo(TypeI type) {
    return equals(type);
  }

  @Override
  public final boolean isFunctionType() {
    return getFunTypeIfSingletonObj() != null;
  }

  @Override
  public final boolean isInterface() {
    return isInterfaceDefinition();
  }

  @Override
  public final boolean isUnknownType() {
    return isUnknown();
  }

  @Override
  public final boolean isSomeUnknownType() {
    FunctionType ft = this.getFunTypeIfSingletonObj();
    return isUnknown()
        || (isUnknownObject() && isLoose())
        || (ft != null && ft.isTopFunction());
  }

  @Override
  public final boolean isUnresolved() {
    // TODO(aravindpg): This is purely a stub to ensure we never get into a codepath that
    // depends on us being an unresolved type. We currently do not mark unresolved types as such
    // in NTI since the main use-case (warning for unfulfilled forward declares) can be
    // handled differently (by warning after GTI), so we don't want to change the type system.
    return false;
  }

  @Override
  public final boolean isUnresolvedOrResolvedUnknown() {
    return isUnknown();
  }

  @Override
  public final boolean isUnionType() {
    return isUnion();
  }

  @Override
  public final boolean isVoidable() {
    return !isTop() && (getMask() & UNDEFINED_MASK) != 0;
  }

  @Override
  public final boolean isNullType() {
    return equals(this.commonTypes.NULL);
  }

  @Override
  public final boolean isVoidType() {
    return equals(this.commonTypes.UNDEFINED);
  }

  @Override
  public final TypeI restrictByNotNullOrUndefined() {
    return this.removeType(this.commonTypes.NULL_OR_UNDEFINED);
  }

  @Override
  public final FunctionTypeI toMaybeFunctionType() {
    return isFunctionType() ? this : null;
  }

  @Override
  public final ObjectTypeI toMaybeObjectType() {
    return isSingletonObj() ? this : null;
  }

  @Override
  public final ObjectTypeI autoboxAndGetObject() {
    return this.autobox().restrictByNotNullOrUndefined().toMaybeObjectType();
  }

  @Override
  public final TypeI meetWith(TypeI other) {
    return meet(this, (JSType) other);
  }

  @Override
  public final boolean equals(Object o) {
    if (o == null) {
      return false;
    }
    if (this == o) {
      return true;
    }
    Preconditions.checkArgument(o instanceof JSType,
        "Expected newtypes.JSType but found %s", o);
    JSType t2 = (JSType) o;
    return getMask() == t2.getMask() && Objects.equals(getObjs(), t2.getObjs())
        && Objects.equals(getEnums(), t2.getEnums())
        && Objects.equals(getTypeVar(), t2.getTypeVar());
  }

  @Override
  public final int hashCode() {
    return Objects.hash(getMask(), getObjs(), getEnums(), getTypeVar());
  }

  @Override
  public final String getDisplayName() {
    // TODO(aravindpg): could implement in a more sophisticated way.
    // One particular pain point is that we currently return the object literal representation of
    // prototype objects instead of something more readable such as "Foo.prototype". But this is
    // difficult to fix since we don't represent prototype objects in any special way.
    NominalType nt = getNominalTypeIfSingletonObj();
    // Prefer just the class name to the name bundled with all its properties.
    if (nt != null && nt.isClassy()) {
      return nt.toString();
    }
    return toString();
  }

  @Override
  public final TypeI convertMethodToFunction() {
    Preconditions.checkState(this.isFunctionType());
    FunctionType devirtualized = getFunTypeIfSingletonObj().devirtualize();
    return commonTypes.fromFunctionType(devirtualized);
  }

  @Override
  public final boolean hasInstanceType() {
    Preconditions.checkState(this.isFunctionType());
    return getFunTypeIfSingletonObj().getInstanceTypeOfCtor() != null;
  }

  @Override
  public final ObjectTypeI getInstanceType() {
    Preconditions.checkState(this.isFunctionType());
    JSType instanceType = getFunTypeIfSingletonObj().getInstanceTypeOfCtor();
    return instanceType == null ? null : instanceType.toMaybeObjectType();
  }

  @Override
  public final String getReferenceName() {
    return isConstructor() ? getInstanceType().toString() : null;
  }

  @Override
  public final Node getSource() {
    if (isConstructor()) {
      JSType instance = getFunTypeIfSingletonObj().getInstanceTypeOfCtor();
      return instance.getNominalTypeIfSingletonObj().getDefSite();
    }
    return this.isSingletonObj()
        ? getNominalTypeIfSingletonObj().getDefSite()
        : null;
  }

  @Override
  public final List<? extends FunctionTypeI> getSubTypes() {
    throw new UnsupportedOperationException("getSubTypes not implemented yet");
  }

  @Override
  public final TypeI getTypeOfThis() {
    Preconditions.checkState(this.isFunctionType());
    return getFunTypeIfSingletonObj().getThisType();
  }

  @Override
  public final boolean acceptsArguments(List<? extends TypeI> argumentTypes) {
    Preconditions.checkState(this.isFunctionType());

    int numArgs = argumentTypes.size();
    FunctionType fnType = this.getFunTypeIfSingletonObj();

    if (numArgs < fnType.getMinArity() || numArgs > fnType.getMaxArity()) {
      return false;
    }

    for (int i = 0; i < numArgs; i++) {
      TypeI ithArgType = argumentTypes.get(i);
      JSType ithParamType = fnType.getFormalType(i);
      if (!ithArgType.isSubtypeOf(ithParamType)) {
        return false;
      }
    }

    return true;
  }

  @Override
  public final boolean hasProperties() {
    throw new UnsupportedOperationException("hasProperties not implemented yet");
  }

  @Override
  public final void setSource(Node n) {
    throw new UnsupportedOperationException("setSource not implemented yet");
  }

  @Override
  public final TypeI getReturnType() {
    Preconditions.checkState(this.isFunctionType());
    return getFunTypeIfSingletonObj().getReturnType();
  }

  @Override
  public final FunctionTypeI getConstructor() {
    Preconditions.checkState(this.isSingletonObj());
    FunctionType ctorType = this.getNominalTypeIfSingletonObj().getConstructorFunction();
    return this.commonTypes.fromFunctionType(ctorType);
  }

  @Override
  public final FunctionTypeI getSuperClassConstructor() {
    if (equals(this.commonTypes.getTopObject())) {
      return null;
    }
    ObjectTypeI proto = getPrototypeObject();
    return proto == null ? null : proto.getConstructor();
  }

  @Override
  public final JSType getPrototypeObject() {
    Preconditions.checkState(this.isSingletonObj());
    JSType proto = getNominalTypeIfSingletonObj().getPrototypePropertyOfCtor();
    if (this.equals(proto)) {
      // In JS's dynamic semantics, the only object without a __proto__ is
      // Object.prototype, but it's not representable in NTI.
      // Object.prototype is the only case where we are equal to our own prototype.
      // In this case, we should return null.
      Preconditions.checkState(
          isBuiltinObjectPrototype(),
          "Failed to reach Object.prototype in prototype chain, unexpected self-link found at %s",
          this);
      return null;
    }
    return proto;
  }

  @Override
  public final JSDocInfo getJSDocInfo() {
    return getSource() == null ? null : NodeUtil.getBestJSDocInfo(getSource());
  }

  @Override
  public final JSDocInfo getOwnPropertyJSDocInfo(String propertyName) {
    Node defsite = this.getOwnPropertyDefSite(propertyName);
    return defsite == null ? null : NodeUtil.getBestJSDocInfo(defsite);
  }

  @Override
  public final JSDocInfo getPropertyJSDocInfo(String propertyName) {
    Node defsite = this.getPropertyDefSite(propertyName);
    return defsite == null ? null : NodeUtil.getBestJSDocInfo(defsite);
  }

  @Override
  public final Node getOwnPropertyDefSite(String propertyName) {
    Preconditions.checkState(this.isSingletonObj());
    return this.getObjTypeIfSingletonObj().getOwnPropertyDefSite(propertyName);
  }

  @Override
  public final Node getPropertyDefSite(String propertyName) {
    Preconditions.checkState(this.isSingletonObj());
    return this.getObjTypeIfSingletonObj().getPropertyDefSite(propertyName);
  }

  /** Returns the names of all the properties directly on this type. */
  @Override
  public final Iterable<String> getOwnPropertyNames() {
    Preconditions.checkState(this.isSingletonObj());
    // TODO(aravindpg): this might need to also include the extra properties as stored in the
    // ObjectType::props. If so, demonstrate a test case that needs it and fix this.
    Set<String> props = getNominalTypeIfSingletonObj().getAllOwnClassProps();
    return props;
  }

  @Override
  public final boolean isPrototypeObject() {
    return isSingletonObj() && getObjTypeIfSingletonObj().isPrototypeObject();
  }

  @Override
  public final boolean isUnknownObject() {
    if (isSingletonObj()) {
      ObjectType obj = getObjTypeIfSingletonObj();
      NominalType nt = getNominalTypeIfSingletonObj();
      return (nt.isBuiltinObject() || nt.isLiteralObject())
          && !obj.isEnumObject() && !obj.isPrototypeObject();
    }
    return false;
  }

  final boolean isBuiltinObjectPrototype() {
    ObjectType obj = getObjTypeIfSingletonObj();
    return obj != null && obj.getNominalType().isBuiltinObject() && obj.isPrototypeObject();
  }

  @Override
  public final boolean isLiteralObject() {
    return isSingletonObj() && getNominalTypeIfSingletonObj().isLiteralObject();
  }

  @Override
  public final boolean isInstanceofObject() {
    if (isSingletonObj()) {
      NominalType nt = getNominalTypeIfSingletonObj();
      return nt.isLiteralObject() || nt.isBuiltinObject();
    }
    return false;
  }

  public final boolean mayContainUnknownObject() {
    for (ObjectType obj : this.getObjs()) {
      if (obj.getNominalType().isBuiltinObject()) {
        return true;
      }
    }
    return false;
  }

  @Override
  public final boolean isInstanceType() {
    Preconditions.checkState(this.isSingletonObj());
    return this.getNominalTypeIfSingletonObj().isClassy();
  }

  @Override
  public final boolean hasProperty(String propertyName) {
    Preconditions.checkState(this.isSingletonObj());
    Preconditions.checkArgument(!propertyName.contains("."));
    return hasProp(new QualifiedName(propertyName));
  }

  @Override
  public final Iterable<JSType> getUnionMembers() {
    if (!isUnion()) {
      return ImmutableList.of(this);
    }
    ImmutableSet.Builder<JSType> builder = ImmutableSet.builder();
    JSType[] primitiveTypes = {
        this.commonTypes.BOOLEAN,
        this.commonTypes.NUMBER,
        this.commonTypes.STRING,
        this.commonTypes.UNDEFINED,
        this.commonTypes.NULL };
    for (JSType primitiveType : primitiveTypes) {
      if ((this.getMask() & primitiveType.getMask()) != 0) {
        builder.add(primitiveType);
      }
    }
    for (ObjectType obj : this.getObjs()) {
      builder.add(JSType.fromObjectType(obj));
    }
    for (EnumType e : this.getEnums()) {
      builder.add(JSType.fromEnum(e));
    }
    if (this.getTypeVar() != null) {
      builder.add(JSType.fromTypeVar(this.commonTypes, getTypeVar()));
    }
    return builder.build();
  }

  @Override
  public final ObjectTypeI normalizeObjectForCheckAccessControls() {
    if (isSingletonObj()) {
      FunctionTypeI ctor = getConstructor();
      if (ctor != null) {
        return ctor.getInstanceType();
      }
    }
    return this;
  }

  @Override
  public final boolean isBoxableScalar() {
    return isNumber()
        || isString()
        || isBoolean()
        || (isEnumElement() && getEnumeratedTypeOfEnumElement().isBoxableScalar());
  }

  @Override
  public final boolean isObjectType() {
    return !isBottom() && !isUnknown() && isSubtypeOf(this.commonTypes.getTopObject());
  }

  @Override
  public final boolean isGenericObjectType() {
    NominalType nt = getNominalTypeIfSingletonObj();
    return nt != null && nt.isGeneric();
  }

  @Override
  public final Collection<ObjectTypeI> getAncestorInterfaces() {
    FunctionType funType = getFunTypeIfSingletonObj();
    if (!funType.isUniqueConstructor() && !funType.isInterfaceDefinition()) {
      return ImmutableSet.of();
    }
    NominalType nt = funType.getInstanceTypeOfCtor().getNominalTypeIfSingletonObj();
    Set<ObjectTypeI> interfaces = new HashSet<>();
    for (NominalType i : nt.getInstantiatedInterfaces()) {
      if (!i.isBuiltinObject()) { // interfaces inherit from Object, remove it here?
        interfaces.add(i.getInstanceAsJSType());
      }
    }
    return interfaces;
  }

  @Override
  public final boolean isStructuralInterface() {
    FunctionType ft = getFunTypeIfSingletonObj();
    if (ft != null && ft.isSomeConstructorOrInterface()) {
      NominalType nt = ft.getThisType().getNominalTypeIfSingletonObj();
      return nt != null && nt.isStructuralInterface();
    }
    return false;
  }

  @Override
  public final boolean hasOwnProperty(String propertyName) {
    ObjectType obj = getObjTypeIfSingletonObj();
    return obj != null && obj.hasOwnProperty(new QualifiedName(propertyName));
  }

  @Override
  public final ObjectTypeI getRawType() {
    NominalType nt = getNominalTypeIfSingletonObj();
    return nt.isGeneric()
        ? nt.getRawNominalTypeAfterTypeChecking().getInstanceAsJSType() : null;
  }

  @Override
  public final ObjectTypeI instantiateGenericsWithUnknown() {
    NominalType nt = getNominalTypeIfSingletonObj();
    if (nt != null && nt.isGeneric()) {
      return nt.instantiateGenericsWithUnknown().getInstanceAsJSType();
    }
    return this;
  }

  @Override
  public final boolean isLegacyNamedType() {
    return false;
  }

  @Override
  public final TypeI getLegacyResolvedType() {
    throw new UnsupportedOperationException(
        "NTI does not have NamedType. This method should never be called on NTI types.");
  }

  final Collection<JSType> getSubtypesWithProperty(QualifiedName qname) {
    Collection<JSType> typesWithProp =
        TypeWithPropertiesStatics.getSubtypesWithProperty(getEnums(), qname);
    typesWithProp.addAll(TypeWithPropertiesStatics.getSubtypesWithProperty(getObjs(), qname));
    return typesWithProp;
  }

  @Override
  public final TypeI getGreatestSubtypeWithProperty(String pname) {
    return joinManyTypes(this.commonTypes, getSubtypesWithProperty(new QualifiedName(pname)));
  }

  @Override
  public final Collection<JSType> getDirectImplementors() {
    Set<JSType> result = new HashSet<>();
    NominalType nt = getNominalTypeIfSingletonObj();
    if (nt != null) {
      for (RawNominalType rawType : nt.getSubtypes()) {
        result.add(this.commonTypes.fromFunctionType(rawType.getConstructorFunction()));
      }
    }
    return result;
  }

  @Override
  public final ObjectTypeI getPrototypeProperty() {
    return getProp(new QualifiedName("prototype"));
  }

  @Override
  public final JSType getTopDefiningInterface(String pname) {
    NominalType nt = getNominalTypeIfSingletonObj();
    if (nt != null && nt.isInterface()) {
      nt = nt.getTopDefiningInterface(pname);
      return nt == null ? null : nt.getInstanceAsJSType();
    }
    return null;
  }

  @Override
  public final FunctionTypeI getOwnerFunction() {
    if (isPrototypeObject()) {
      return this.commonTypes.fromFunctionType(getObjTypeIfSingletonObj().getOwnerFunction());
    }
    return null;
  }

  @Override
  public final boolean isSubtypeWithoutStructuralTyping(TypeI other) {
    if (!isSubtypeOf(other)) {
      return false;
    }
    NominalType thisNt = getNominalTypeIfSingletonObj();
    NominalType otherNt = ((JSType) other).getNominalTypeIfSingletonObj();
    return thisNt == null || otherNt == null || thisNt.isNominalSubtypeOf(otherNt);
  }

  @Override
  public final Iterable<TypeI> getParameterTypes() {
    return Preconditions.checkNotNull(getFunType()).getParameterTypes();
  }

  @Override
  public TypeI getPropertyType(String propName) {
    return isObjectType() ? getProp(new QualifiedName(propName)) : null;
  }

  @Override
  public boolean isRecordType() {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean hasUninstantiatedTypeVariables() {
    throw new UnsupportedOperationException();
  }

  @Override
  public ImmutableList<? extends TypeI> getTemplateTypes() {
    NominalType nt = getNominalTypeIfSingletonObj();
    if (nt.isGeneric()) {
      ImmutableList.Builder<JSType> builder = ImmutableList.builder();
      return builder.addAll(nt.getTypeMap().values()).build();
    }
    return null;
  }

  // Note: concrete subclasses follow below.  The above code in JSType
  // should not depend on any of these specific implementations, other
  // than instantiating them when appropriate (in the makeType methods).
  // They exist as optimizations.  All subclass fields should be
  // treated as private to the specific subclass.

  private static final class UnionType extends JSType {
    final int mask;
    // objs is empty for scalar types
    final ImmutableSet<ObjectType> objs;
    // typeVar is null for non-generic types
    final String typeVar;
    // enums is empty for types that don't have enums
    final ImmutableSet<EnumType> enums;

    UnionType(JSTypes commonTypes, int mask, ImmutableSet<ObjectType> objs,
        String typeVar, ImmutableSet<EnumType> enums) {
      super(commonTypes);
      this.enums = Preconditions.checkNotNull(enums);
      this.objs = Preconditions.checkNotNull(objs);

      if (typeVar != null) {
        mask |= TYPEVAR_MASK;
      }

      this.typeVar = typeVar;
      this.mask = mask;

      if (!isValidType()) {
        throw new IllegalStateException(SimpleFormat.format(
            "Cannot create type with bits <<<%x>>>, "
            + "objs <<<%s>>>, typeVar <<<%s>>>, enums <<<%s>>>",
            mask, objs, typeVar, enums));
      }
    }

    @Override
    protected int getMask() {
      return mask;
    }

    @Override
    protected ImmutableSet<ObjectType> getObjs() {
      return Preconditions.checkNotNull(objs);
    }

    @Override
    protected String getTypeVar() {
      return typeVar;
    }

    @Override
    protected ImmutableSet<EnumType> getEnums() {
      return Preconditions.checkNotNull(enums);
    }
  }

  private static final class MaskType extends JSType {
    final int mask;

    MaskType(JSTypes commonTypes, int mask) {
      super(commonTypes);
      this.mask = mask;
    }

    @Override
    protected int getMask() {
      return mask;
    }

    @Override
    protected ImmutableSet<ObjectType> getObjs() {
      return ImmutableSet.of();
    }

    @Override
    protected String getTypeVar() {
      return null;
    }

    @Override
    protected ImmutableSet<EnumType> getEnums() {
      return ImmutableSet.of();
    }
  }

  private static final class ObjsType extends JSType {
    final ImmutableSet<ObjectType> objs;

    ObjsType(JSTypes commonTypes, ImmutableSet<ObjectType> objs) {
      super(commonTypes);
      this.objs = Preconditions.checkNotNull(objs);
    }

    @Override
    protected int getMask() {
      return NON_SCALAR_MASK;
    }

    @Override
    protected ImmutableSet<ObjectType> getObjs() {
      return objs;
    }

    @Override
    protected String getTypeVar() {
      return null;
    }

    @Override
    protected ImmutableSet<EnumType> getEnums() {
      return ImmutableSet.of();
    }
  }

  private static final class NullableObjsType extends JSType {
    final ImmutableSet<ObjectType> objs;

    NullableObjsType(JSTypes commonTypes, ImmutableSet<ObjectType> objs) {
      super(commonTypes);
      this.objs = Preconditions.checkNotNull(objs);
    }

    @Override
    protected int getMask() {
      return NON_SCALAR_MASK | NULL_MASK;
    }

    @Override
    protected ImmutableSet<ObjectType> getObjs() {
      return objs;
    }

    @Override
    protected String getTypeVar() {
      return null;
    }

    @Override
    protected ImmutableSet<EnumType> getEnums() {
      return ImmutableSet.of();
    }
  }
}


For those who think clowns are all seltzer bottles and slide whistles, The Offspring's new video for "Coming For You" may change your opinion.
Directed by Josh Forbes, "Coming For You" turns an abandoned warehouse into a big-top of brutality, as rival gangs of clowns trade in their balloon animals for beat-downs.
The video was a labour of love for Forbes, a self-described "clown nerd" who taught himself pratfalls and juggling as a kid. After pitching clown-themed video concepts to a number of bands with no success, Forbes found a taker for his violent vision in The Offspring's Dexter Holland.
The Los Angeles-based directed has been on quite a roll lately, directing Walk The Moon's video for "Shut Up And Dance", and recently wrapping shooting on his first full-length feature "Contracted: Phase II" (the sequel to IFC's "Contracted").
Josh was kind enough to answer a few questions about "Coming For You", and share some insights on working with Juggalos, the genius of Jackie Chan, and why people love watching clowns get slapped around.
What inspired the concept for "Coming For You"?
A couple of factors went into the idea. First, the song feels very anthemic. Almost sports-like, like something that would play in a stadium. And it conjured up images of competition.
Second, the phrase "they won't stop coming for you" brought to mind martial arts films where one guy has to fight a ton of people.
Then I landed on clowns.
Naturally! Were there any specific films or directors that influenced your treatment?
The initial pitch was basically "Hey! Three words: CLOWN FIGHT CLUB!" That said, I tried to avoid actually referencing Fight Club.
For this video, I watched a ton of Jackie Chan films. He is a god, in my opinion. There is no greater entertainer alive than Jackie Chan. Drunken Master 2 was a real inspiration.
Another huge influence was the Riki Oh: The Story of Ricky. That movie is one of my absolute favorites. It's essentially a vehicle for a series of super-ambitious, completely ridiculous gore gags.
Also, "The Warriors", "Escape from New York", "Mad Max 2", "Dead Alive", "Evil Dead", "Scott Pilgrim vs. The World" and of course The Three Stooges.
What did the band think of the concept when you brought it to them? Were they on-board pretty much right away, or did you need to sell them on it?
I had a pretty specific treatment, but I got on the phone early on with Dexter to talk out what aspects of it he dug and what he didn't.
I think he was kind of wary about it being too goofy. He wanted to make sure we took it in a dark direction. Which I hope we did.
I don't think there's any doubt about that – it's definitely one of the darkest things the band has ever done. Coming back to the clowns, how many did you have on set? Did you hold auditions, or was it more of a "have red nose, will work for food"-type thing?
We didn't have as many as I wanted, I'll tell you that. In my perfect version there would be hundreds. Like a Lord of the Rings-style battle.
I think we had around 20 or so. Clowns are expensive and time-consuming. Our makeup team (headed up by Mayera Abeita) was amazing, but imagine doing 20 totally unique clowns' makeup. It's mind-boggling. Our wardrobe stylist Kimmy Erin also did an amazing job finding costumes for everyone.
Luckily we also had a bunch of self-made clowns come in. They were amazing! One guy, Roger Fojas, I worked with years ago on a video for Nicole Atkins. He's part of a clown/performance troupe called Lucent Dossier. He came with him own perfectly made character. He had a rubber duck hanging from his hat. There's no way I could have thought of that.
You also feature a number of Juggalos in the video – how did them and the "legit" clowns get along during the filming?
Are you saying juggalos aren't legit!? I wouldn't say that to their face. They were really unruly. They were cooking crank in their clown car. We started with a dozen Juggalos but nine of them OD'd on Faygo and onion rings.
Who choreographed the fight scenes? Was there anyone that particularly blew you away with their stage-fighting skills?
Chase Rivera at Blue8 put the team together, and they were just perfect. Not just as fighters, but actors and characters. They're all great, but some had parkour moves that killed me.
Who was the VFX lead on the video?
What do you mean? That was 100% real. All those clowns are dead now. This was actually a documentary.
I clown, I clown. The VFX were done by Norton. He's a great director in his own right. We're both at the same company, More Media. He did an amazing job and is also very handsome.
Were any of the effects particularly difficult to film?
Not really. The practical effects are pretty time-consuming. People in the horror community like to complain about digital effects, which I get. Obviously practical effects are always going to look better, but they can take forever.
When you're shooting a video like this, you get one day to do everything. I'd rather have 20 digital blood hits than use up all our time trying to clean up blood and reset a blood hose. It's a give and take. This one was so cartoony, I was cool with going digital on a lot of gags.
Speaking of cartoony, was there anything specific that inspired the jack-in-the-box decapitation scene? Perhaps a desire to re-purpose the spring-loaded-fist from the "Shut Up And Dance" video in a darker fashion?
Haha. I was hoping nobody would notice. I'm running out of tricks.
That scene just came out of brainstorming how a clown would try to kill another clown. I'm sure the Joker does stuff like that all the time.
The bloody stump was a nod to Kill Bill, but also movies like The Master of the Flying Guillotine.
We so much going on during the shoot, you must have had some challenges in post.
We did. Like I said, we had a ton of stuff to cram into one day. I had a whole other fight scene that got cut. We shot a really good scene with Cirque du Soleil-style clowns that got cut because they felt tonally wrong. We also were missing a lot of the more intense gore gags because they got cut for time.
After the first day the cut was looking really rad, but it was missing that next level violence we were hoping for. But Dexter did the most amazing thing: he pushed for a second day of shooting!
This made all the difference. We were able to regroup and team up with an additional gore FX artist, Julia Hapney, and really do something special.
I've never had a record label do that. It was really amazing to work with a collaborative team who were willing to put in the extra time and money to do things right.
Well it was definitely worth it – the fight scenes and gore look amazing! Any tips for aspiring film makers shooting their first fight scenes?
Get a good stunt coordinator, and good stunt people. That's huge.
It sounds expensive (and it can be), but there are always people looking for rad stuff on their reel. The Blue8 team really worked with us because I pitched it to them as basically a showpiece for their team.
Having great fighters is 90% of the battle. For this project, it was all about stringing together funny moves. I basically made a big list of things that I wanted and gave them to Chase. I knew I wanted things like the Three Stooges eye gouge, and the electric hand buzzer, but I didn't really have the order fully worked out. Having a great stunt guy really helps tie everything together.
As far as the technical aspect of doing it, having two cameras is great. We didn't on this one, but it would definitely have helped. Also, we ended up re-editing the video to let the fights play out longer. Our first edit was insanely fast-paced and really cool, but we found that letting the fights speak for themselves ended up being the way to go.
He talks about how Jackie lets the action play out in the wide. It's more in line with how Chaplin or Keaton shoot. We didn't completely adhere to that, but it was useful in deciding to let the stunts speak for themselves.
With all the buzz this video has received, we had to ask: clowns, creepy or cool?
Both. A lot has been written about this video sparking people's fear of clowns, which wasn't the intention. For me, in this video, the clowns represent a sort of tribalism. It's funny to me that there are distinct groups of clowns all with their own looks. They remind me of tribal warriors in the jungle. Or literally warriors from the movie "The Warriors".
I'm really interested in how violent clowns are in general. They're always hitting each other and getting knocked around. Think of things like Punch and Judy. They're incredibly violent. With all this violence I had never seen it portrayed in a realistic (or semi-realistic) way.
And lastly: any final thoughts on what makes it so enjoyable to see a clown get slapped around?
I think the point of clowns is that they can't be hurt. The red nose creates an objective distance. Or so I've read.
I'm just so excited that I was allowed to do a video that I wanted to do, the way I wanted to do it, with the level of violence and comedy that I wanted. It's a rare rare treat. I'm so glad people are digging it!
A huge thanks to Josh Forbes for his contributions to this post. A talented director and very funny guy, be sure to look out for "Contracted: Phase II" in theatres and on VOD this Spring.


text:
Jorge Pacheco has been an artist/cartoonist since age 2, but a professional for the past 26 years. He`has worked on characters, such as Bugs Bunny, Power Puff Girls, Johnny Quest, Space Ghost, Garfield, Tom and Jerry and has even had his own syndicated comic strip "CEO DAD", as well as illustrated six children's books. He has also illustrated 8 pages for the graphic novel "1000 Ways to Die" by Zenescope Entertianment and most recently finished illustrating the "Hero Envy" graphic novel.
Hero Envy
4 Geeks, 1 Adventure, 0 Lives! The "Hero Envy" gang answers the call to action and comedy. These losers are enlisted by an inter dimensional being to go on a mission to stop the universe's biggest bully. The laughs hit as powerful as the punches in their first graphic novel.
Laurel & Hardy are back with brand new nostalgic adventures based on their fan-favorite comedy! The magic of Stan Laurel and Oliver Hardy was recently reintroduced through the Stan & Ollie film starring John C. Reilly and Steve Coogan!
The Pink Panther
The cool cat is back! Generations of kids grew up loving the Pink Panther's unique style and wit on Saturday morning cartoons. And now today's best writers and artists are delivering original comic book stories that bring the trademark Pink Panther flair for confounding The Inspector and The Little Man to legions of new fans! Each issue is packed full-a pink with several new stories, original shor
Pink Panther Classic
The Pink Panther has been entertaining fans of all ages for over 50 years and his antics are back for good! This issue includes four classic tales - The Pink Past, Mountain Pink, Pink Belt, and The Inspector in The Puddle Clue! Enjoy these classic chuckles from the Pink Panther!
Reckless Chronicles
A new anthology series from RSP Productions! The first installment features three tales of genius inventors, Saturday morning cartoon heroes and self-absorbed geeks! They will take you on adventures from the streets of Victorian London to super-secret laboratories, and all the way into the vast reaches of space! A variety of comic book characters that will transport you to the wildest reaches of
Rocky & Bullwinkle Show
Hey Rocky, watch me pull a rabbit out of my hat! That staple of Saturday morning cartoon shenanigans, The Rocky & Bullwinkle Show, is back in all its satirical slapsticky joy! The entire misfit cast of Jay Ward's wonderful series are here - Rocket J. Squirrel, Bullwinkle the Moose, Mr. Peabody & Sherman, Dudley Do-Right, and everyone's favorite Russian hooligans, Boris and Natasha! Americ
Scooby-Doo (1997-2010)
Shaggy, Scooby, Fred, Daphne, and Velma are back, ready to solve strange cases, unmask dastardly villains, and, in Shaggy and Scooby's case, find as many reasons as possible to chow down on some food and get a taste of some Scooby Snacks! Hop in the Mystery Machine and get ready for adventure because the gang is back in business!
There's no need to fear, UNDERDOG is here! American Mythology is proud to bring back another nostalgic kids classic to a brand new audience. Underdog is an incredible animated series that chronicled the adventures of Shoeshine, who when danger was afoot would turn into a powerful superhero! With an incredible rogues' gallery of villains like Simon Bar Sinister, Cad Lackey, Riff Raff, Battyman,

redpajama_set_name: RedPajamaCommonCrawl


text:
Archive for the 'Cindy Michaels' Category
numerology for Cindy Michaels
Posted in 34 (Eight of Wands), Cindy Michaels, tagged Bangor, Bangor Maine, Cindy Michaels, Michaels, Sarah Palin, Senior management, Tony Consiglio, WFVX-LP on November 21, 2012| Leave a Comment »
10:07 EST 21 November 2012
Two news anchors at a Fox affiliate station in Bangor, Maine have sensationally quit live on-air, citing a longstanding battle with senior management over journalistic practices.
Co-anchors Cindy Michaels, 46, and Tony Consiglio, 28, shocked both staff and viewers by announcing their resignations at the end of Tuesday's 6 p.m. broadcast.
Michaels, who was also the station's news director, said she had grown frustrated with senior management's influence and desire for 'somewhat unbalanced news,' particularly in relation to politics.
'There was a constant disrespecting and belittling of staff and we both felt there was a lack of knowledge from ownership and upper management in running a newsroom to the extent that I was not allowed to structure and direct them professionally,' Michaels told BangorDailyNews.com.
Michaels, who joined the station in 2006, said a balanced journalistic approach was often frowned upon, although she wasn't specific about what political leaning senior management had expected the reporters to adopt.
'I couldn't do everything I wanted to as a news director. There was a regular undoing of decisions,' she said.
Both Michaels and Consiglio said the idea to resign was something they had talked about for some time and so felt was right to make as a joint decision.
'I just wanted to know that I was doing the best job I could and was being honest and ethical as a journalist, and I thought there were times when I wasn't able to do that,' said Consiglio, who joined the station in 2006 as a sports reporter before becoming a news anchor the next year.
Neither presenter told anyone of their decision prior to Tuesday's broadcast for fear they wouldn't be given the opportunity to say goodbye to viewers.
We quit: Consiglio, second left, and Michaels, third left, claim bosses at Fox Bangor News forced them to report 'unbalanced' political news items
Cindy Michaels gained notoriety during the 2008 presidential election due to her striking resemblance to Sarah Palin, left
WFVX-LP is the Fox affiliate for Central and Eastern Maine and is licensed to serve Bangor.
The station's general manager Mike Palmer described the on air resignations as 'unfortunate, but not unexpected' and denied that senior management gets involved in the daily production of news.
He claimed the station is bucking the industry trend toward downsizing and has added 10 full-time jobs over the past 18 months.
Michaels gained notoriety during the 2008 presidential election due to her striking resemblance to Sarah Palin, then the Republican nominee for Vice President.
Fox News ran a news item about how Michaels received hate mail and nasty phone calls from viewers, she described being compared to Palin as 'an honor.'
Both reporters said they don't have anything lined up in terms of new jobs in the media, although Michaels has started freelance writing.
story from and video at: http://www.dailymail.co.uk/news/article-2236355/Two-news-anchors-stun-viewers-quitting-local-Fox-affiliate-station-live-air-clashes-senior-management-unbalanced-political-news-items.html#ixzz2Ct3IawwA
Cindy Michaels
her primary challenge = CM = 34 = Generating a buzz. Ratings spike. Things happen really quickly. A real firecracker. Lashing out in anger. Getting people all fired up. Burnt out.
numerology for Friday December 21st, 2012 (the "end of the Mayan calendar") at:
http://2012numerology.com/


text:
Oregon search for Susan Powell's remains ends
By Brett Cihon
MARION COUNTY, Ore -- A search for the remains of Susan Powell at an expansive Oregon property ended with nothing found Thursday morning.
Police searched a heavily wooded farming area Wednesday near Salem for the possible remains of Powell, who vanished in 2009. Her husband, Josh Powell, was named a person of interest in her disappearance.
Investigators returned to the property Thursday morning, but they ended their search shortly after 9 a.m.
Josh Powell killed his and Susan's two young sons, Charlie and Braden, and himself in a house in Graham, Wash., in February 2012.


Cross Stitch Fabric Walmart Handy 22 I Riolis Counted Cross Stitch Kit 6"x6" Black Cat 10 Count Walmart Uploaded by William Howell on Saturday, October 6th, 2018 in category Cross Stitch.
You may also like Cross Stitch Fabric Walmart Favorite 71 G The Tourist from Cross Stitch Topic.
You can get ideas from Cross Stitch Fabric Walmart Catching 47 Y Bucilla Stamped Cross Stitch Baby Quilt Top 34 By 43 Inch featured under Cross Stitch Fabric Walmart Handy 22 I Riolis Counted Cross Stitch Kit 6"x6" Black Cat 10 Count Walmart.


text:
Sam Allardyce calls for a break as positive coronavirus tests rise in top flight
PA Sport Staff
Sam Allardyce has called for a "circuit break" after the Premier League returned its highest number of positive coronavirus tests this season.
Responding to reports in the Telegraph that representatives of some top-flight clubs have proposed a two-week 'cooling off' period, the West Brom boss insisted it was the "right thing" to cope with the outbreak.
Between December 21 and 27, the League tested 1,479 players and club staff and 18 tested positive, eclipsing the previous highest number of cases, 16 during the week of November 9-15.
Sam Allardyce has revealed his fears over covid (Clive Brunskill/PA)
Speaking after his side's 5-0 home defeat to Leeds, former England boss Allardyce said: "Everyone's safety is more important than anything else.
"When I listen to the news the variant virus transmits quicker than the original virus we can only do the right thing which is have a circuit break.
"I am 66 years old and the last thing I want to do it catch Covid. I'm very concerned for myself and football in general. If that's what needs to be done we need to do it.
"We had one positive this week and it seems to be creeping around no matter how hard we try."
Sheffield United can confirm the club recorded a number of positive coronavirus tests after the latest round of testing.
The individuals who have tested positive have now entered into a period of self-isolation in accordance with Premier League and Government guidelines.
— Sheffield United (@SheffieldUnited) December 29, 2020
However, Manchester United boss Ole Gunnar Solskjaer questioned the need for a break after his side's late 1-0 win over Wolves.
Solskjaer insisted his club are successfully following the required protocols and added: "I can't see the benefit in having a circuit breaker or whatever it is going to be called because when are we going to play the games?
"We all know it's so difficult this year but if you just stop more games I don't think that's going to make a big, big change."
The PA news agency understands that the Premier League is unaware of any such talks taking place. However, pressure is likely to grow after both Sheffield United and Fulham were affected by new outbreaks of the virus.
Blades boss Chris Wilder revealed ahead of their match at Burnley that the club had seen "a couple" of players and "four or five backroom staff" test positive. Wilder's starting XI was unaffected, with just one change from the previous match, Oliver Burke dropping to the bench, but he was only able to name seven of a possible nine substitutes.
Wilder said neither he nor the club were looking to have the game delayed.
Ralph Hasenhüttl will be absent from the dugout tonight, following a positive Covid-19 test in his household.
The #SaintsFC boss will manage the game remotely and be in touch with his coaching staff throughout:
"It's not an ideal situation but I don't think we're a club of one. It (the virus) is picking up and it's running high at football clubs," he told SUTV.
"We just notified the relevant authorities that we've had a situation at the football club. But I want to play football. That's how it is, we're not looking to get anything canned. We did the right thing as far as our responsibility to the Premier League and notified them regarding the players that are ill."
The PA news agency understands that Fulham have also recorded a number of positive tests and Wednesday's London derby at Tottenham is dependent on the results of the players and staff at Craven Cottage who are yet to receive their results.
Manchester City began retesting their squad on Tuesday in the hope they have acted quickly enough to prevent a widespread outbreak of the disease after their Monday game at Everton was called off at short notice due to a number of positive cases, while Arsenal confirmed on Monday that Brazilian defender Gabriel had tested positive for the virus.
NEWS | This evening's game at Everton has been postponed.
🔷 #ManCity | https://t.co/axa0klD5re https://t.co/ZLnMhyIloa
— Manchester City (@ManCity) December 28, 2020
Southampton boss Ralph Hasenhuttl did not attend his side's match with West Ham following a positive test in his household.
A Premier League statement read: "The Premier League can today confirm that between Monday 21 December and Sunday 27 December, 1,479 players and Club staff were tested for COVID-19. Of these, there were 18 new positive tests.
"Players or club staff who have tested positive will self-isolate for a period of 10 days.
"The Premier League is providing this aggregated information for the purposes of competition integrity and transparency. No specific details as to clubs or individuals will be provided by the League and results will be made public after each round of testing."
Sheffield United said in a statement: "Sheffield United can confirm the club recorded a number of positive coronavirus tests after the latest round of testing.
"Due to medical confidentiality the club will not be revealing the names of those who have been affected."
Our next two @SkyBetLeagueOne fixtures have been suspended ❌
This is due to players and first-team staff self-isolating in line with EFL and Government guidance
⁰We would like to apologise to supporters for the delay in communicating this news #RAFC
— Rochdale AFC (@officiallydale) December 28, 2020
Nine games across the Sky Bet Championship, League One and League Two on Tuesday were postponed because of the virus, while Rotherham boss Paul Warne said his side are only playing their match with Barnsley to avoid a possible points deduction by the EFL.
Rochdale are one of the clubs in League One whose next two games have been postponed and their club doctor Wes Tensel called for the EFL to see the "bigger picture" and halt the current campaign.
Tensel, who is also a practising GP, told the PA news agency: "This is my opinion as a doctor. Some people will disagree, some won't, this is just my opinion based on what I have seen working in the NHS and at a football club."