Skip to content
Closed
36 changes: 28 additions & 8 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -5625,12 +5625,22 @@ static function (): void {
$assignedExprType = $scope->getType($assignedExpr);
$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope);
if ($propertyReflection->canChangeTypeAfterAssignment()) {
if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) {
$propertyNativeType = $propertyReflection->getNativeType();
$assignedNativeType = $scope->getNativeType($assignedExpr);
$propertyNativeType = $propertyReflection->getNativeType();

$scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($scope->getNativeType($assignedExpr)->toCoercedArgumentType(true), $propertyNativeType));
if ($propertyReflection->hasNativeType()) {
if ($scope->isDeclareStrictTypes()) {
$scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType));
} elseif ($propertyNativeType->isNull()->no()) {
$assignedExprType = TypeCombinator::removeNull($assignedExprType);
$assignedNativeType = TypeCombinator::removeNull($assignedNativeType);

$scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType);
} else {
$scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType);
}
} else {
$scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr));
$scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType);
}
}
$declaringClass = $propertyReflection->getDeclaringClass();
Expand Down Expand Up @@ -5696,12 +5706,22 @@ static function (): void {
$assignedExprType = $scope->getType($assignedExpr);
$nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope);
if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) {
if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) {
$propertyNativeType = $propertyReflection->getNativeType();
$assignedNativeType = $scope->getNativeType($assignedExpr);
$propertyNativeType = $propertyReflection->getNativeType();

$scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($scope->getNativeType($assignedExpr)->toCoercedArgumentType(true), $propertyNativeType));
if ($propertyReflection->hasNativeType()) {
if ($scope->isDeclareStrictTypes()) {
$scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType));
} elseif ($propertyNativeType->isNull()->no()) {
$assignedExprType = TypeCombinator::removeNull($assignedExprType);
$assignedNativeType = TypeCombinator::removeNull($assignedNativeType);

$scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType);
} else {
$scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType);
}
} else {
$scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr));
$scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType);
}
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/Testing/TypeInferenceTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ private static function isFileLintSkipped(string $file): bool

@fclose($f);

if (preg_match('~<?php\\s*\\/\\/\s*lint\s*([^\d\s]+)\s*([^\s]+)\s*~i', $firstLine, $m) === 1) {
if (preg_match('~<?php\\s*(?:declare\\s*\([^)]+\)\\s*;\\s*)?\\/\\/\s*lint\s*([^\d\s]+)\s*([^\s]+)\s*~i', $firstLine, $m) === 1) {
Copy link
Contributor Author

@staabm staabm Apr 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added support for <?php declare(…); // lint >= 8.1-format in gatherAssertTypesFromDirectory

return version_compare(PHP_VERSION, $m[2], $m[1]) === false;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php declare(strict_types = 0); // lint >= 8.1

namespace RememberNonNullablePropertyWhenStrictTypesDisabled;

use function PHPStan\Testing\assertNativeType;
use function PHPStan\Testing\assertType;

class KeepsPropertyNonNullable {
private readonly int $i;

public function __construct()
{
$this->i = getIntOrNull();
}

public function doFoo(): void {
assertType('int', $this->i);
assertNativeType('int', $this->i);
}
}

class DontCoercePhpdocType {
/** @var int */
private $i;

public function __construct()
{
$this->i = getIntOrNull();
}

public function doFoo(): void {
assertType('int', $this->i);
assertNativeType('mixed', $this->i);
}
}

function getIntOrNull(): ?int {
if (rand(0, 1) === 0) {
return null;
}
return 1;
}


class KeepsPropertyNonNullable2 {
private int|float $i;

public function __construct()
{
$this->i = getIntOrFloatOrNull();
}

public function doFoo(): void {
assertType('float|int', $this->i);
assertNativeType('float|int', $this->i);
}
}

function getIntOrFloatOrNull(): null|int|float {
if (rand(0, 1) === 0) {
return null;
}

if (rand(0, 10) === 0) {
return 1.0;
}
return 1;
}

class NarrowsNativeUnion {
private readonly int|float $i;

public function __construct()
{
$this->i = getInt();
}

public function doFoo(): void {
assertType('int', $this->i);
assertNativeType('int', $this->i);
}
}

function getInt(): int {
return 1;
}
Loading