/** * Emulates a loose comparison between two types, and returns the result. * * The result can be true, false, or unknown: * * - true: loose comparison of these types is always true * - false: loose comparison of these types is always false * - unknown: outcome depends on the actual values of these types * * @see http://php.net/manual/en/types.comparisons.php (table with loose comparison ==) * * @param PhpType $thisType * @param PhpType $thatType */ public function testForEquality(PhpType $that) { if ($that->isAllType() || $that->isUnknownType() || $that->isNoResolvedType() || $this->isAllType() || $this->isUnknownType() || $this->isNoResolvedType()) { return TernaryValue::get('unknown'); } if ($this->isNoType() || $that->isNoType()) { if ($this->isNoType() && $that->isNoType()) { return TernaryValue::get('true'); } return TernaryValue::get('unknown'); } $isThisNumeric = $this->isIntegerType() || $this->isDoubleType(); $isThatNumeric = $that->isIntegerType() || $that->isDoubleType(); if (($isThisNumeric || $this->isStringType()) && $that->isArrayType() || ($isThatNumeric || $that->isStringType()) && $this->isArrayType()) { return TernaryValue::get('false'); } if ($this->isObjectType() ^ $that->isObjectType()) { return TernaryValue::get('false'); } if ($that->isUnionType()) { return $that->testForEquality($this); } if ($this->isArrayType() && $that->isArrayType()) { // TODO: Maybe make this more sophisticated by looking at the key, // and element types. return TernaryValue::get('unknown'); } // If this is reached, then this base type does not have enough information to // make an informed decision, but the method should be overridden by a subtype // as this method eventually is never allowed to return null. return null; }
private function getStringRepr(PhpType $type) { switch (true) { case $type instanceof AllType: return TypeRegistry::NATIVE_ALL; // This handles the generic array type specially. // This handles the generic array type specially. case $type === self::$typeRegistry->getNativeType('array'): return 'array'; case $type instanceof ArrayType: $itemTypes = $type->getItemTypes(); if (empty($itemTypes)) { return TypeRegistry::NATIVE_ARRAY . '<' . $this->getStringRepr($type->getKeyType()) . ',' . $this->getStringRepr($type->getElementType()) . '>'; } return sprintf('array<%s,%s,%s>', $this->getStringRepr($type->getKeyType()), $this->getStringRepr($type->getElementType()), $this->dumpJsonLike($itemTypes, true)); case $type instanceof FalseType: return TypeRegistry::NATIVE_BOOLEAN_FALSE; case $type instanceof BooleanType: return TypeRegistry::NATIVE_BOOLEAN; case $type instanceof CallableType: return TypeRegistry::NATIVE_CALLABLE; case $type instanceof ResourceType: return TypeRegistry::NATIVE_RESOURCE; case $type instanceof DoubleType: return TypeRegistry::NATIVE_DOUBLE; case $type instanceof IntegerType: return TypeRegistry::NATIVE_INTEGER; case $type instanceof ThisType: return 'this<' . $type->getReferenceName() . '>'; case $type instanceof NamedType: // If this type has been resolved, we can get the representation // of the resolved type instead of using the reference name. if (!$type->isNoResolvedType()) { return $this->getStringRepr($type->getReferencedType()); } return 'object<' . $type->getReferenceName() . '>'; case $type instanceof NoObjectType: return TypeRegistry::NATIVE_OBJECT; case $type instanceof NoType: return TypeRegistry::NATIVE_NONE; case $type instanceof NullType: return TypeRegistry::NATIVE_NULL; case $type instanceof ObjectType: return 'object<' . $type->getName() . '>'; case $type instanceof StringType: return TypeRegistry::NATIVE_STRING; case $type instanceof UnionType: $alt = array(); foreach ($type->getAlternates() as $t) { $alt[] = $this->getStringRepr($t); } return implode('|', $alt); case $type instanceof UnknownType: return $type->isChecked() ? TypeRegistry::NATIVE_UNKNOWN_CHECKED : TypeRegistry::NATIVE_UNKNOWN; } throw new \InvalidArgumentException(sprintf('The SWITCH statement is exhaustive, but got "%s".', get_class($type))); }