Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Reflection/ClassReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,7 @@ public function getBackedEnumType(): ?Type
return null;
}

return TypehintHelper::decideTypeFromReflection($this->reflection->getBackingType());
return TypehintHelper::decideTypeFromReflection($this->reflection->getBackingType(), isBuiltin: $this->isBuiltin());
}

public function hasEnumCase(string $name): bool
Expand Down Expand Up @@ -1305,7 +1305,7 @@ public function getConstant(string $name): ClassConstantReflection

$nativeType = null;
if ($reflectionConstant->getType() !== null) {
$nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), selfClass: $declaringClass);
$nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), selfClass: $declaringClass, isBuiltin: $declaringClass->isBuiltin());
} elseif ($this->signatureMapProvider->hasClassConstantMetadata($declaringClass->getName(), $name)) {
$nativeType = $this->signatureMapProvider->getClassConstantMetadata($declaringClass->getName(), $name)['nativeType'];
}
Expand Down Expand Up @@ -1366,7 +1366,7 @@ public function getConstantPhpDocType(string $name): ?Type

$nativeType = null;
if ($reflectionConstant->getType() !== null) {
$nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType());
$nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), isBuiltin: $reflectionConstant->getDeclaringClass()->isInternal());
}

$declaringClassName = $reflectionConstant->getDeclaringClass()->getName();
Expand Down
2 changes: 1 addition & 1 deletion src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -2659,7 +2659,7 @@ public function getClassConstFetchTypeByReflection(Name|Expr $class, string $con
$constantType = $this->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($reflectionConstantDeclaringClass->getName(), $reflectionConstantDeclaringClass->getFileName() ?: null));
$nativeType = null;
if ($reflectionConstant->getType() !== null) {
$nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), selfClass: $constantClassReflection);
$nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), selfClass: $constantClassReflection, isBuiltin: $reflectionConstantDeclaringClass->isInternal());
}
$resolvedType = $this->constantResolver->resolveClassConstantType(
$constantClassReflection->getName(),
Expand Down
2 changes: 1 addition & 1 deletion src/Reflection/Native/NativeMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function getPrototype(): ClassMemberReflection

$tentativeReturnType = null;
if ($prototypeMethod->getTentativeReturnType() !== null) {
$tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType(), selfClass: $prototypeDeclaringClass);
$tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType(), selfClass: $prototypeDeclaringClass, isBuiltin: $prototypeDeclaringClass->isBuiltin());
}

return new MethodPrototypeReflection(
Expand Down
3 changes: 2 additions & 1 deletion src/Reflection/Php/PhpClassReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ private function createProperty(
);
}

$nativeType = TypehintHelper::decideTypeFromReflection($propertyReflection->getType(), selfClass: $declaringClassReflection);
$nativeType = TypehintHelper::decideTypeFromReflection($propertyReflection->getType(), selfClass: $declaringClassReflection, isBuiltin: $declaringClassReflection->isBuiltin());

$declaringTrait = null;
$reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider();
Expand Down Expand Up @@ -881,6 +881,7 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla
$nativeReturnType = TypehintHelper::decideTypeFromReflection(
$methodReflection->getReturnType(),
selfClass: $actualDeclaringClass,
isBuiltin: $actualDeclaringClass->isBuiltin(),
);

$isPure = null;
Expand Down
4 changes: 3 additions & 1 deletion src/Reflection/Php/PhpFunctionReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ private function getParameters(): array
$this->attributeReflectionFactory->fromNativeReflection($reflection->getAttributes(), InitializerExprContext::fromReflectionParameter($reflection)),
$this->allowedConstantsMapProvider->getForFunctionParameter(strtolower($this->reflection->getName()), $reflection->getName()),
TrinaryLogic::createFromBoolean($this->phpDocParameterPureUnlessCallableIsImpure[$reflection->getName()] ?? false),
$this->isBuiltin(),
);
}, $this->reflection->getParameters());
}
Expand All @@ -147,6 +148,7 @@ private function getReturnType(): Type
return TypehintHelper::decideTypeFromReflection(
$this->reflection->getReturnType(),
$this->phpDocReturnType,
isBuiltin: $this->isBuiltin(),
);
}

Expand All @@ -161,7 +163,7 @@ private function getPhpDocReturnType(): Type

private function getNativeReturnType(): Type
{
return TypehintHelper::decideTypeFromReflection($this->reflection->getReturnType());
return TypehintHelper::decideTypeFromReflection($this->reflection->getReturnType(), isBuiltin: $this->isBuiltin());
}

public function getDeprecatedDescription(): ?string
Expand Down
5 changes: 4 additions & 1 deletion src/Reflection/Php/PhpMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public function getPrototype(): ClassMemberReflection

$tentativeReturnType = null;
if ($prototypeMethod->getTentativeReturnType() !== null) {
$tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType(), selfClass: $prototypeDeclaringClass);
$tentativeReturnType = TypehintHelper::decideTypeFromReflection($prototypeMethod->getTentativeReturnType(), selfClass: $prototypeDeclaringClass, isBuiltin: $prototypeDeclaringClass->isBuiltin());
}

return new MethodPrototypeReflection(
Expand Down Expand Up @@ -232,6 +232,7 @@ private function getParameters(): array
$this->attributeReflectionFactory->fromNativeReflection($reflection->getAttributes(), InitializerExprContext::fromReflectionParameter($reflection)),
$this->allowedConstantsMapProvider->getForMethodParameter($this->declaringClass->getName(), $this->reflection->getName(), $reflection->getName()),
TrinaryLogic::createFromBoolean($this->pureUnlessCallableIsImpureParameters[$reflection->getName()] ?? false),
$this->reflection->isInternal(),
), $this->reflection->getParameters());
}

Expand Down Expand Up @@ -277,6 +278,7 @@ private function getReturnType(): Type
$returnType,
$this->phpDocReturnType,
$this->declaringClass,
isBuiltin: $this->reflection->isInternal(),
);
}

Expand All @@ -297,6 +299,7 @@ private function getNativeReturnType(): Type
return $this->nativeReturnType ??= TypehintHelper::decideTypeFromReflection(
$this->reflection->getReturnType(),
selfClass: $this->declaringClass,
isBuiltin: $this->reflection->isInternal(),
);
}

Expand Down
3 changes: 3 additions & 0 deletions src/Reflection/Php/PhpParameterReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public function __construct(
private array $attributes,
private ?ParameterAllowedConstants $allowedConstants,
private TrinaryLogic $pureUnlessCallableIsImpureParameter,
private bool $isBuiltin = false,
)
{
}
Expand Down Expand Up @@ -74,6 +75,7 @@ public function getType(): Type
$phpDocType,
$this->declaringClass,
$this->isVariadic(),
$this->isBuiltin,
);
}

Expand Down Expand Up @@ -112,6 +114,7 @@ public function getNativeType(): Type
$this->reflection->getType(),
selfClass: $this->declaringClass,
isVariadic: $this->isVariadic(),
isBuiltin: $this->isBuiltin,
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/Reflection/SignatureMap/FunctionSignatureMapProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private function createSignature(string $functionName, ?string $className, ?Refl
$parameter->getName(),
$parameter->isOptional(),
$parameter->getType(),
TypehintHelper::decideTypeFromReflection($nativeParameters[$i]->getType()),
TypehintHelper::decideTypeFromReflection($nativeParameters[$i]->getType(), isBuiltin: true),
$parameter->passedByReference(),
$parameter->isVariadic(),
$nativeParameters[$i]->isDefaultValueAvailable() ? $this->initializerExprTypeResolver->getType(
Expand All @@ -163,7 +163,7 @@ private function createSignature(string $functionName, ?string $className, ?Refl
if ($reflectionFunction === null) {
$nativeReturnType = new MixedType();
} else {
$nativeReturnType = TypehintHelper::decideTypeFromReflection($reflectionFunction->getReturnType());
$nativeReturnType = TypehintHelper::decideTypeFromReflection($reflectionFunction->getReturnType(), isBuiltin: true);
}

return new FunctionSignature(
Expand Down
6 changes: 3 additions & 3 deletions src/Reflection/SignatureMap/Php8SignatureMapProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ private function findClassStubs(string $className): void
continue;
}

$this->constantTypes[$lowerClassName][$const->name->toLowerString()] = ParserNodeTypeToPHPStanType::resolve($stmt->type, null);
$this->constantTypes[$lowerClassName][$const->name->toLowerString()] = ParserNodeTypeToPHPStanType::resolve($stmt->type, null, true);
}
}
}
Expand Down Expand Up @@ -442,7 +442,7 @@ private function getSignature(
if (!$name instanceof Variable || !is_string($name->name)) {
throw new ShouldNotHappenException();
}
$parameterType = ParserNodeTypeToPHPStanType::resolve($param->type, $classReflection);
$parameterType = ParserNodeTypeToPHPStanType::resolve($param->type, $classReflection, true);
$phpDocParameterType = $phpDocParameterTypes[$name->name] ?? null;

if ($param->default instanceof ConstFetch) {
Expand Down Expand Up @@ -473,7 +473,7 @@ private function getSignature(
$variadic = $variadic || $param->variadic;
}

$returnType = ParserNodeTypeToPHPStanType::resolve($function->getReturnType(), $classReflection);
$returnType = ParserNodeTypeToPHPStanType::resolve($function->getReturnType(), $classReflection, true);

return new FunctionSignature(
$parameters,
Expand Down
14 changes: 10 additions & 4 deletions src/Type/ParserNodeTypeToPHPStanType.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@ final class ParserNodeTypeToPHPStanType

/**
* @param Node\Name|Node\Identifier|Node\ComplexType|null $type
* @param bool $isBuiltin Whether the type declaration comes from a PHP internal symbol.
* Stubs of internal functions declare the resource pseudo-type
* as `resource`, which PHP itself would read as a class name.
*/
public static function resolve($type, ?ClassReflection $classReflection): Type
public static function resolve($type, ?ClassReflection $classReflection, bool $isBuiltin = false): Type
{
if ($type === null) {
return new MixedType();
} elseif ($type instanceof Name) {
$typeClassName = (string) $type;
$lowercasedClassName = strtolower($typeClassName);
if ($isBuiltin && $lowercasedClassName === 'resource') {
return new ResourceType();
}
if ($classReflection !== null && in_array($lowercasedClassName, ['self', 'static'], true)) {
if ($lowercasedClassName === 'static') {
return new StaticType($classReflection);
Expand All @@ -41,18 +47,18 @@ public static function resolve($type, ?ClassReflection $classReflection): Type

return new ObjectType($typeClassName);
} elseif ($type instanceof NullableType) {
return TypeCombinator::addNull(self::resolve($type->type, $classReflection));
return TypeCombinator::addNull(self::resolve($type->type, $classReflection, $isBuiltin));
} elseif ($type instanceof Node\UnionType) {
$types = [];
foreach ($type->types as $unionTypeType) {
$types[] = self::resolve($unionTypeType, $classReflection);
$types[] = self::resolve($unionTypeType, $classReflection, $isBuiltin);
}

return TypeCombinator::union(...$types);
} elseif ($type instanceof Node\IntersectionType) {
$types = [];
foreach ($type->types as $intersectionTypeType) {
$innerType = self::resolve($intersectionTypeType, $classReflection);
$innerType = self::resolve($intersectionTypeType, $classReflection, $isBuiltin);
if (!$innerType->isObject()->yes()) {
return new NeverType();
}
Expand Down
14 changes: 10 additions & 4 deletions src/Type/TypehintHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@
final class TypehintHelper
{

/** @api */
/**
* @api
* @param bool $isBuiltin Whether the type declaration comes from a PHP internal symbol.
* Stubs of internal functions declare the resource pseudo-type
* as `resource`, which PHP itself would read as a class name.
*/
public static function decideTypeFromReflection(
?ReflectionType $reflectionType,
?Type $phpDocType = null,
ClassReflection|null $selfClass = null,
bool $isVariadic = false,
bool $isBuiltin = false,
): Type
{
if ($reflectionType === null) {
Expand All @@ -36,15 +42,15 @@ public static function decideTypeFromReflection(
}

if ($reflectionType instanceof ReflectionUnionType) {
$type = TypeCombinator::union(...array_map(static fn (ReflectionType $type): Type => self::decideTypeFromReflection($type, selfClass: $selfClass), $reflectionType->getTypes()));
$type = TypeCombinator::union(...array_map(static fn (ReflectionType $type): Type => self::decideTypeFromReflection($type, selfClass: $selfClass, isBuiltin: $isBuiltin), $reflectionType->getTypes()));

return self::decideType($type, $phpDocType);
}

if ($reflectionType instanceof ReflectionIntersectionType) {
$types = [];
foreach ($reflectionType->getTypes() as $innerReflectionType) {
$innerType = self::decideTypeFromReflection($innerReflectionType, selfClass: $selfClass);
$innerType = self::decideTypeFromReflection($innerReflectionType, selfClass: $selfClass, isBuiltin: $isBuiltin);
if (!$innerType->isObject()->yes()) {
return new NeverType();
}
Expand All @@ -65,7 +71,7 @@ public static function decideTypeFromReflection(
$typeNode = new FullyQualified($reflectionType->getName());
}

$type = ParserNodeTypeToPHPStanType::resolve($typeNode, $selfClass);
$type = ParserNodeTypeToPHPStanType::resolve($typeNode, $selfClass, $isBuiltin);
if ($reflectionType->allowsNull()) {
$type = TypeCombinator::addNull($type);
}
Expand Down
36 changes: 36 additions & 0 deletions tests/PHPStan/Analyser/Bug15185Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\Testing\TypeInferenceTestCase;
use PHPUnit\Framework\Attributes\DataProvider;

class Bug15185Test extends TypeInferenceTestCase
{

public static function dataFileAsserts(): iterable
{
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-15185.php');
}

/**
* @param mixed ...$args
*/
#[DataProvider('dataFileAsserts')]
public function testFileAsserts(
string $assertType,
string $file,
...$args,
): void
{
$this->assertFileAsserts($assertType, $file, ...$args);
}

public static function getAdditionalConfigFiles(): array
{
return [
__DIR__ . '/nodeScopeResolverPhp7.neon',
];
}

}
31 changes: 31 additions & 0 deletions tests/PHPStan/Analyser/data/bug-15185.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php declare(strict_types = 1);

namespace Bug15185;

use function PHPStan\Testing\assertType;

function doFoo(string $connectionString, string $query): void
{
$connection = pg_connect($connectionString);
if ($connection === false) {
return;
}

$result = pg_exec($connection, $query);
assertType('resource|false', $result);
if (is_resource($result)) {
assertType('resource', $result);
}
}

function doBar(): void
{
$handle = curl_init();
assertType('(resource|false)', $handle);
if (is_resource($handle)) {
assertType('resource', $handle);
}

$info = curl_getinfo($handle, CURLINFO_HTTP_CODE);
assertType('int', $info);
}
Loading
Loading