/** * Returns the array type contained in the given type, or null if no array * type is available. * * @param PhpType|null $type * * @return ArrayType|null */ private function getContainedArrayType(PhpType $type = null) { if (null === $type) { return null; } if ($type->isArrayType()) { return $type; } if (!$type->isUnionType()) { return null; } foreach ($type->getAlternates() as $alt) { if ($alt->isArrayType()) { return $alt; } } return null; }
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; }
/** * Updates the context of returned ThisType types. * * ```php * class A { * public function returnsThis() { * return $this; // this<A> * } * } * * class B extends A { * public function returnsSomething() { * $rs = $this->returnsThis(); * * return $rs; // this<B> * } * * public function returnsSomethingElse() * { * $rs = parent::returnsThis(); * * return $rs; // this<B> * } * } * * class C extends B { } * * $c = new C(); * $c->returnsThis(); // object<C> * $c->returnsSomething(); // object<C> * ``` * * We have two basic cases: * * 1. The called node refers to the current scope ($this->, self::, * parent::, static::, or SuperTypeName::). * 2. The node was called from the "outside" $foo->...(). * * In the first case, we need to preserve the wrapping with ThisType with * an updated creation context. In the second case, we have to unwrap the * ThisType. * * @param \PHPParser_Node $calledNode * @param PhpType $thisType Type of the current context * @param PhpType $calledType Type of the $calledNode * @param PhpType $returnType * * @return PhpType */ private function updateThisReference(\PHPParser_Node $calledNode, PhpType $thisType = null, PhpType $calledType, PhpType $returnType = null) { // Delays execution until necessary. $needsWrapping = function () use($calledNode, $thisType, $calledType) { if (null === $thisType) { return false; } switch (true) { case $calledNode instanceof \PHPParser_Node_Expr_Variable: return 'this' === $calledNode->name; case $calledNode instanceof \PHPParser_Node_Name: if (in_array(strtolower(implode("\\", $calledNode->parts)), array('self', 'static', 'parent'), true)) { return true; } // This handles the following case: // // class A { public function foo() { return $this; } } // class B extends A { public function bar() { return A::foo(); } } // $x = (new B())->bar(); // // $x is resolved to object<B>. if (null !== $thisType && $thisType->isSubtypeOf($calledType)) { return true; } return false; default: return false; } }; switch (true) { case $returnType instanceof ThisType: return $needsWrapping() ? $this->typeRegistry->getThisType($thisType) : $calledType; case $returnType instanceof UnionType: $types = array(); foreach ($returnType->getAlternates() as $alt) { if ($alt instanceof ThisType) { $types[] = $needsWrapping() ? $this->typeRegistry->getThisType($thisType) : $calledType; continue; } $types[] = $alt; } return $this->typeRegistry->createUnionType($types); default: return $returnType; } }
private function isNullableObjectType(PhpType $type) { if (!$type->isUnionType()) { return false; } foreach ($type->getAlternates() as $alt) { if (null === $alt->toMaybeObjectType() && !$alt->isNullType()) { return false; } } return true; }
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))); }