docs: function isRelatedTo(source, target, reportErrors, headMessage) {
                  var result;
                  // both types are the same - covers 'they are the same primitive type or both are Any' or the same type parameter cases
                  if (source === target)
                      return -1 /* True */;
                  if (relation === identityRelation) {
                      return isIdenticalTo(source, target);
                  }
                  if (isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined))
                      return -1 /* True */;
                  if (source.flags & 16777216 /* FreshObjectLiteral */) {
                      if (hasExcessProperties(source, target, reportErrors)) {
                          if (reportErrors) {
                              reportRelationError(headMessage, source, target);
                          }
                          return 0 /* False */;
                      }
                      // Above we check for excess properties with respect to the entire target type. When union
                      // and intersection types are further deconstructed on the target side, we don't want to
                      // make the check again (as it might fail for a partial target type). Therefore we obtain
                      // the regular source type and proceed with that.
                      if (target.flags & 1572864 /* UnionOrIntersection */) {
                          source = getRegularTypeOfObjectLiteral(source);
                      }
                  }
                  var saveErrorInfo = errorInfo;
                  // Note that these checks are specifically ordered to produce correct results.
                  if (source.flags & 524288 /* Union */) {
                      if (relation === comparableRelation) {
                          result = someTypeRelatedToType(source, target, reportErrors && !(source.flags & 8190 /* Primitive */));
                      }
                      else {
                          result = eachTypeRelatedToType(source, target, reportErrors && !(source.flags & 8190 /* Primitive */));
                      }
                      if (result) {
                          return result;
                      }
                  }
                  else if (target.flags & 1048576 /* Intersection */) {
                      result = typeRelatedToEachType(source, target, reportErrors);
                      if (result) {
                          return result;
                      }
                  }
                  else {
                      // It is necessary to try these "some" checks on both sides because there may be nested "each" checks
                      // on either side that need to be prioritized. For example, A | B = (A | B) & (C | D) or
                      // A & B = (A & B) | (C & D).
                      if (source.flags & 1048576 /* Intersection */) {
                          // Check to see if any constituents of the intersection are immediately related to the target.
                          //
                          // Don't report errors though. Checking whether a constituent is related to the source is not actually
                          // useful and leads to some confusing error messages. Instead it is better to let the below checks
                          // take care of this, or to not elaborate at all. For instance,
                          //
                          //    - For an object type (such as 'C = A & B'), users are usually more interested in structural errors.
                          //
                          //    - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection
                          //          than to report that 'D' is not assignable to 'A' or 'B'.
                          //
                          //    - For a primitive type or type parameter (such as 'number = A & B') there is no point in
                          //          breaking the intersection apart.
                          if (result = someTypeRelatedToType(source, target, /*reportErrors*/ false)) {
                              return result;
                          }
                      }
                      if (target.flags & 524288 /* Union */) {
                          if (result = typeRelatedToSomeType(source, target, reportErrors && !(source.flags & 8190 /* Primitive */) && !(target.flags & 8190 /* Primitive */))) {
                              return result;
                          }
                      }
                  }
                  if (source.flags & 16384 /* TypeParameter */) {
                      var constraint = getConstraintOfTypeParameter(source);
                      if (!constraint || constraint.flags & 1 /* Any */) {
                          constraint = emptyObjectType;
                      }
                      // The constraint may need to be further instantiated with its 'this' type.
                      constraint = getTypeWithThisArgument(constraint, source);
                      // Report constraint errors only if the constraint is not the empty object type
                      var reportConstraintErrors = reportErrors && constraint !== emptyObjectType;
                      if (result = isRelatedTo(constraint, target, reportConstraintErrors)) {
                          errorInfo = saveErrorInfo;
                          return result;
                      }
                  }
                  else {
                      if (source.flags & 131072 /* Reference */ && target.flags & 131072 /* Reference */ && source.target === target.target) {
                          // We have type references to same target type, see if relationship holds for all type arguments
                          if (result = typeArgumentsRelatedTo(source, target, reportErrors)) {
                              return result;
                          }
                      }
                      // Even if relationship doesn't hold for unions, intersections, or generic type references,
                      // it may hold in a structural comparison.
                      var apparentSource = getApparentType(source);
                      // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
                      // to X. Failing both of those we want to check if the aggregation of A and B's members structurally
                      // relates to X. Thus, we include intersection types on the source side here.
                      if (apparentSource.flags & (2588672 /* ObjectType */ | 1048576 /* Intersection */) && target.flags & 2588672 /* ObjectType */) {
                          // Report structural errors only if we haven't reported any errors yet
                          var reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !(source.flags & 8190 /* Primitive */);
                          if (result = objectTypeRelatedTo(apparentSource, source, target, reportStructuralErrors)) {
                              errorInfo = saveErrorInfo;
                              return result;
                          }
                      }
                  }
                  if (reportErrors) {
                      if (source.flags & 2588672 /* ObjectType */ && target.flags & 8190 /* Primitive */) {
                          tryElaborateErrorsForPrimitivesAndObjects(source, target);
                      }
                      reportRelationError(headMessage, source, target);
                  }
                  return 0 /* False */;
              }