public function addAlternate(PhpType $alternate) { // build() returns the bottom type by default, so we can // just bail out early here. if ($alternate->isNoType()) { return $this; } $this->isAllType = $this->isAllType || $alternate->isAllType(); $isAlternateUnknown = $alternate instanceof UnknownType; // instanceof is desired here $this->isNativeUnknownType = $this->isNativeUnknownType || $isAlternateUnknown; if ($isAlternateUnknown) { $this->areAllUnknownsChecked = $this->areAllUnknownsChecked && $alternate->isChecked(); } if (!$this->isAllType && !$this->isNativeUnknownType) { if ($alternate->isUnionType()) { $union = $alternate->toMaybeUnionType(); foreach ($union->getAlternates() as $unionAlt) { $this->addAlternate($unionAlt); } } else { // Look through the alternates we've got so far, // and check if any of them are duplicates of // one another. foreach ($this->alternates as $index => $current) { // The Unknown type is special in that we cannot use our // subtype based check, but need to check for equality to // avoid duplicates, and not remove all other alternates. if ($alternate->isUnknownType()) { if ($alternate->equals($current)) { return $this; } continue; } // Check if we already have a more general type in the union. // Then, we do not add this alternate. if ($alternate->isSubTypeOf($current)) { return $this; } // Check if we have a subtype of the passed alternate. Then, // we remove that alternate in favor of the newly passed one. if ($current->isSubTypeOf($alternate)) { unset($this->alternates[$index]); } } $this->alternates[] = $alternate; } } return $this; }
private function restrictByCallable(PhpType $type = null, $outcome) { if (null === $type) { return $outcome ? $this->typeRegistry->getNativeType('callable') : null; } if ($outcome) { if ($type->isUnknownType() || $type->isAllType()) { return $this->typeRegistry->getNativeType('callable'); } if ($type->isUnionType()) { $types = array(); foreach ($type->getAlternates() as $altType) { if ($altType->canBeCalled()) { $types[] = $altType; } } return $this->typeRegistry->createUnionType($types); } return $type->canBeCalled() ? $type : $this->typeRegistry->getNativeType('none'); } if ($type->isCallableType()) { return $this->typeRegistry->getNativeType('none'); } if ($type->isUnionType()) { $types = array(); foreach ($type->getAlternates() as $altType) { if ($altType->isCallableType()) { continue; } $types[] = $altType; } return $this->typeRegistry->createUnionType($types); } return $type; }
/** * @param string $type * * @return null|PhpType */ private function tryGettingMoreSpecificType(PhpType $docType = null, PhpType $actualType, AbstractFunction $function, array $importedNamespaces, MethodContainer $container = null, $type) { if (!$docType) { if (!$actualType->isUnknownType() && !$actualType->isAllType()) { return null; } return $this->{'infer' . $type . 'TypeForFunction'}($function, $container); } // If the type defined by the comment is an object (and not the NoObjectType), // and a super type of the actual type, we keep it. if (null !== $docType->toMaybeObjectType() && $actualType->isSubtypeOf($docType)) { return $docType; } // If the type defined by the comment is a nullable object type (excluding the // NoObject type), we keep the comment that is currently in place. if ($this->isNullableObjectType($docType) && $actualType->isSubtypeOf($docType)) { return $docType; } if ($docType->getDocType($importedNamespaces) !== $actualType->getDocType($importedNamespaces)) { return $actualType; } if ($actualType === $this->registry->getNativeType('array')) { $inferredType = $this->{'infer' . $type . 'TypeForFunction'}($function, $container); if ($inferredType && $inferredType->isArrayType()) { return $inferredType; } } return null; }
/** * 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 verifyParamType(PhpType $type, \PHPParser_Node $node, $paramName) { if (null === ($commentType = $this->parser->getTypeFromParamAnnotation($node, $paramName))) { if ($this->getSetting('ask_for_param_type_annotation')) { if ($type->isAllType() || $type->isUnknownType()) { $this->phpFile->addComment($node->getLine(), Comment::warning('php_doc.param_type_all_type_non_commented', 'Please add a ``@param`` annotation for parameter ``$%parameter%`` which defines a more specific range of types; something like ``string|array``, or ``null|MyObject``.', array('parameter' => $paramName))->varyIn(array())); } else { if ($this->typeRegistry->getNativeType('array')->isSubtypeOf($type)) { $this->phpFile->addComment($node->getLine(), Comment::warning('php_doc.param_type_array_type_not_inferrable', 'Please add a ``@param`` annotation for parameter ``$%parameter%`` which defines the array type; using ``array<SomeType>``, or ``SomeType[]``.', array('parameter' => $paramName))->varyIn(array())); } } } return; } if (!$this->getSetting('parameters')) { return; } // If the type is not a subtype of the annotated type, then there is an // error somewhere (could also be in the type inference engine). if (!$type->isSubtypeOf($commentType)) { if ($this->getSetting('suggest_more_specific_types') && $this->typeRegistry->getNativeType('array')->isSubtypeOf($type)) { $this->phpFile->addComment($this->getLineOfParam($node, $paramName), Comment::warning('php_doc.param_type_mismatch_with_generic_array', 'Should the type for parameter ``$%parameter%`` not be ``%expected_type%``? Also, consider making the array more specific, something like ``array<String>``, or ``String[]``.', array('parameter' => $paramName, 'expected_type' => $type->getDocType($this->importedNamespaces)))); } else { $this->phpFile->addComment($this->getLineOfParam($node, $paramName), Comment::warning('php_doc.param_type_mismatch', 'Should the type for parameter ``$%parameter%`` not be ``%expected_type%``?', array('parameter' => $paramName, 'expected_type' => $type->getDocType($this->importedNamespaces)))); } return; } if (!$this->getSetting('suggest_more_specific_types')) { return; } if (!$this->isMoreSpecificType($type, $commentType)) { if ($type->isAllType()) { $this->phpFile->addComment($this->getLineOfParam($node, $paramName), Comment::warning('php_doc.param_type_all_type_more_specific', 'Please define a more specific type for parameter ``$%parameter%``; consider using a union like ``null|Object``, or ``string|array``.', array('parameter' => $paramName))->varyIn(array())); } else { if ($this->typeRegistry->getNativeType('array')->isSubtypeOf($type)) { $this->phpFile->addComment($this->getLineOfParam($node, $paramName), Comment::warning('php_doc.param_type_array_element_type', 'Please define the element type for the array of parameter ``$%parameter%`` (using ``array<SomeType>``, or ``SomeType[]``).', array('parameter' => $paramName))->varyIn(array())); } } return; } $this->phpFile->addComment($this->getLineOfParam($node, $paramName), Comment::warning('php_doc.param_type_more_specific', 'Consider making the type for parameter ``$%parameter%`` a bit more specific; maybe use ``%actual_type%``.', array('parameter' => $paramName, $type->getDocType($this->importedNamespaces)))); }