public function getLeastSuperType(PhpType $that) { if ($that->isArrayType()) { if ($this->isSubtypeOf($that)) { // We have a special case, that is if we compare array<unknown> to array<all>. Although, both types are // subtypes of each other, we always must have a predictable outcome of this method regardless of the // order of types, i.e. $a->getLeastSuperType($b) === $b->getLeastSuperType($a). if ($this->elementType->isUnknownType() && $that->elementType->isAllType()) { return $this; } if ($that->elementType->isUnknownType() && $this->elementType->isAllType()) { return $that; } return parent::getLeastSuperType($that); } $genericArrayType = $this->registry->getNativeType('array'); if ($this === $genericArrayType) { return $this; } if ($that === $genericArrayType) { return $that; } // If both arrays have different item types defined, we keep them as // separate arrays in a union type. It can be used as an indication // that a key is not always defined. if (count($this->itemTypes) !== count($that->itemTypes) || (bool) array_diff_key($this->itemTypes, $that->itemTypes)) { return parent::getLeastSuperType($that); } $keyType = $this->registry->createUnionType(array($this->keyType, $that->keyType)); $elementType = $this->registry->createUnionType(array($this->elementType, $that->elementType)); $itemTypes = array(); foreach ($this->itemTypes as $name => $itemType) { $itemTypes[$name] = $this->registry->createUnionType(array($itemType, $that->itemTypes[$name])); } return $this->registry->getArrayType($elementType, $keyType, $itemTypes); } return parent::getLeastSuperType($that); }
/** * Returns whether the given type may be passed for the given expected type. * * @param PhpType $expectedType * @param PhpType $actualType * * @return boolean */ public function mayBePassed(PhpType $expectedType, PhpType $actualType) { // This effectively disables all type checks for mock objects. // TODO: Remove this once we have support for parameterized types, and // anonymous classes. if (null !== ($objType = $actualType->toMaybeObjectType()) && $objType->getName() === 'PHPUnit_Framework_MockObject_MockObject') { return true; } if ($actualType instanceof ProxyObjectType && $actualType->getReferenceName() === 'PHPUnit_Framework_MockObject_MockObject') { return true; } // If the actual type is not yet resolved, then we need to let it go through // in favor of avoiding false positives. This indicates a non-existent class, // but that is handled in a different pass. if ($actualType->isNoResolvedType()) { return true; } if ($expectedType->isCallableType()) { return $actualType->canBeCalled(); } // Allow an object that is implementing __toString to be passed where a // string is expected. This should work in most cases unless users perform // some sort of is_??? check, but then they probably also expect non strings. if ($expectedType->isStringType() && null !== ($objType = $actualType->toMaybeObjectType()) && $objType->hasMethod('__toString')) { return true; } if ($actualType->isSubtypeOf($expectedType)) { return true; } // If we are in strict mode, it's already over here. if (self::LEVEL_LENIENT !== $this->level) { return false; } // If the actual type is an all-type, we will not make any fuzz in lenient mode. Simply, because there are a lot // of cases where "all" should really rather mean "unknown" type. if ($actualType->isAllType()) { return true; } switch (true) { case $expectedType->isArrayType(): // If the generic array type is passed in places where a more specific // array type is required, we will let this go through in lenient mode. if ($actualType === $this->typeRegistry->getNativeType('array')) { return true; } if (!$actualType->isArrayType()) { return false; } return $this->mayBePassed($expectedType->getElementType(), $actualType->getElementType()); case $expectedType->isDoubleType(): case $expectedType->isStringType(): case $expectedType->isIntegerType(): $actualType = $actualType->restrictByNotNull(); return $actualType->isSubTypeOf($this->typeRegistry->createUnionType(array('string', 'integer', 'double'))); // For unions we let the check pass if the actual type may be passed for any // of the union's alternates. // For unions we let the check pass if the actual type may be passed for any // of the union's alternates. case $expectedType->isUnionType(): assert($expectedType instanceof UnionType); foreach ($expectedType->getAlternates() as $alt) { if ($this->mayBePassed($alt, $actualType)) { return true; } } return false; default: return false; } }