function __cast_and_compare()

in hphp/hsl/src/legacy_fixme/coercions.php [213:430]


function __cast_and_compare(mixed $l, mixed $r, COMPARISON_TYPE $ctype)[]: int {
  if ($r is bool && !(\HH\is_fun($l) || \HH\is_class_meth($l))) {
    if (!($l is AnyArray<_, _>)) {
      $l = (bool)$l;
    } else if ($ctype === COMPARISON_TYPE::EQ) {
      $l = !C\is_empty($l);
    }
  } else if ($r is null) {
    if ($l is string) {
      $r = '';
    } else if (\is_object($l)) {
      $l = true;
      $r = false;
    } else {
      return __cast_and_compare($l, false, $ctype);
    }
  } else if (
    \HH\is_fun($r) ||
    \HH\is_fun($l) ||
    \HH\is_class_meth($r) ||
    \HH\is_class_meth($l)
  ) {
    // no-op.
  } else if ($r is num) {
    if ($l is null) {
      $l = false;
      $r = (bool)$r;
    } else if ($l is bool) {
      $r = (bool)$r;
    } else if ($l is string) {
      $l = \HH\str_to_numeric($l) ?? 0;
    } else if (
      $l is resource || (\is_object($l) && !($l is \ConstCollection<_>))
    ) {
      $l = $r is int ? (int)$l : (float)$l;
    }
    // if we're ==/!= an int and a float, convert both to float
    if (
      $ctype === COMPARISON_TYPE::EQ &&
      $r is num &&
      $l is num &&
      $r is int !== $l is int
    ) {
      $l = (float)$l;
      $r = (float)$r;
    }
  } else if ($r is string) {
    if ($l is null) {
      $l = '';
    } else if ($l is bool) {
      $r = (bool)$r;
    } else if ($l is num) {
      $r = \HH\str_to_numeric($r) ?? 0;
      return __cast_and_compare($l, $r, $ctype);
    } else if (\is_object($l)) {
      if ($l is \StringishObject && !($l is \ConstCollection<_>)) {
        $l = (string)$l;
      } else if (!($l is \ConstCollection<_>)) {
        $l = true;
        $r = false;
      }
    } else if ($l is resource) {
      $l = (float)$l;
      $r = (float)$r;
    } else if (
      $l is string &&
      $ctype === COMPARISON_TYPE::EQ &&
      \is_numeric($l) &&
      \is_numeric($r)
    ) {
      $l = \HH\str_to_numeric($l);
      $r = \HH\str_to_numeric($r);
      return __cast_and_compare($l, $r, $ctype);
    }
  } else if ($r is resource) {
    if ($l is null) {
      $l = false;
      $r = true;
    } else if ($l is bool) {
      // @lint-ignore CAST_NON_PRIMITIVE 2fax
      $r = (bool)$r;
    } else if ($l is num) {
      // @lint-ignore CAST_NON_PRIMITIVE 2fax
      $r = $l is int ? (int)$r : (float)$r;
    } else if ($l is string) {
      $l = (float)$l;
      // @lint-ignore CAST_NON_PRIMITIVE 2fax
      $r = (float)$r;
    } else if (\is_object($l)) {
      $l = true;
      $r = false;
    }
  } else if ($r is AnyArray<_, _> && ($l is null || $l is bool)) {
    if ($l is null) {
      $l = false;
    }
    if ($ctype === COMPARISON_TYPE::EQ) {
      $r = !C\is_empty($r);
    }
  } else if (
    ($r is vec<_> && $l is vec<_>) ||
    (
      $ctype === COMPARISON_TYPE::EQ &&
      $r is \ConstVector<_> &&
      $l is \ConstVector<_>
    )
  ) {
    if (C\count($l) !== C\count($r)) {
      return C\count($l) > C\count($r) ? 1 : -1;
    }
    foreach ($l as $i => $li) {
      $ri = $r[$i];
      $res = __cast_and_compare($li, $ri, $ctype);
      if ($res !== 0) {
        if (
          $ctype === COMPARISON_TYPE::GT &&
          \is_object($ri) &&
          \is_object($li) &&
          (\get_class($li) !== \get_class($ri) || $li is \Closure) &&
          !($li is \DateTimeInterface && $ri is \DateTimeInterface)
        ) {
          // flip the result :p
          return $res === -1 ? 1 : -1;
        }
        return $res;
      }
    }
    return 0;
  } else if (
    $ctype === COMPARISON_TYPE::EQ &&
    (
      ($r is dict<_, _> && $l is dict<_, _>) ||
      ($r is \ConstMap<_, _> && $l is \ConstMap<_, _>) ||
      ($r is \ConstSet<_> && $l is \ConstSet<_>)
    )
  ) {
    if (C\count($l) !== C\count($r)) return 1;
    foreach ($l as $i => $li) {
      if (
        /* HH_FIXME[4324] I've just confirmed this is safe */
        /* HH_FIXME[4005] Set is KeyedContainer... */
        !C\contains_key($r, $i) || __cast_and_compare($li, $r[$i], $ctype) !== 0
      ) {
        return 1;
      }
    }
    return 0;
  } else if (\is_object($r)) {
    if (
      $l is string && $r is \StringishObject && !($r is \ConstCollection<_>)
    ) {
      $r = (string)$r;
    } else if (
      $l is null ||
      $l is resource ||
      ($l is string && !($r is \ConstCollection<_>))
    ) {
      $l = false;
      $r = true;
    } else if ($l is num && !($r is \ConstCollection<_>)) {
      // this probably throws, but sometimes it doesn't!
      $r = $l is int ? (int)$r : (float)$r;
    } else if ($l is bool) {
      $r = (bool)$r;
    } else if (
      \is_object($l) &&
      !($l is \ConstCollection<_> || $r is \ConstCollection<_>) &&
      $l !== $r &&
      !($l is \DateTimeInterface && $r is \DateTimeInterface)
    ) {
      if (\get_class($l) !== \get_class($r) || $l is \Closure) {
        return $ctype === COMPARISON_TYPE::GT ? -1 : 1;
      } else if (!($l is \SimpleXMLElement)) {
        $l = \HH\object_prop_array($l);
        $r = \HH\object_prop_array($r);
        if (C\count($l) !== C\count($r)) {
          return C\count($l) > C\count($r) ? 1 : -1;
        }

        $loop_var = $ctype === COMPARISON_TYPE::GT ? $r : $l;
        $other_var = $ctype === COMPARISON_TYPE::GT ? $l : $r;
        foreach ($loop_var as $i => $_) {
          if (!C\contains_key($other_var, $i)) {
            // dyn prop in a but not b
            return $ctype === COMPARISON_TYPE::GT ? -1 : 1;
          }
          $li = $l[$i];
          $ri = $r[$i];
          $res = __cast_and_compare($li, $ri, $ctype);
          if ($res !== 0) {
            if (
              ($li is float && Math\is_nan($li)) ||
              ($ri is float && Math\is_nan($ri))
            ) {
              // in the case of NAN && GT, straight up flip the result
              return $ctype === COMPARISON_TYPE::GT && $res === -1 ? 1 : -1;
            }
            return $res;
          }
        }
        return 0;
      }
    }
  }

  if (($l is float && Math\is_nan($l)) || ($r is float && Math\is_nan($r))) {
    // trigger exception if necessary
    $_r = $ctype === COMPARISON_TYPE::EQ
      ? ($l as dynamic) == ($r as dynamic)
      : ($l as dynamic) <=> ($r as dynamic);
    return $ctype === COMPARISON_TYPE::LT ? 1 : -1;
  }

  if ($ctype === COMPARISON_TYPE::EQ) {
    return (int)($l != $r);
  }
  return ($l as dynamic) <=> ($r as dynamic);
}