Skip to content

Commit 1f89d3b

Browse files
committed
property cannot get null, even in strict-types
1 parent d5a10bc commit 1f89d3b

File tree

4 files changed

+201
-71
lines changed

4 files changed

+201
-71
lines changed

remember-non-nullable-property.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php declare(strict_types=1); // lint >= 8.1
2+
3+
namespace RememberNonNullablePropertyWhenStrictTypesDisabled;
4+
5+
use function PHPStan\Testing\assertNativeType;
6+
use function PHPStan\Testing\assertType;
7+
8+
class NarrowsNativeUnion {
9+
private readonly int|float $i;
10+
11+
public function __construct()
12+
{
13+
$this->i = getInt();
14+
}
15+
16+
public function doFoo(): void {
17+
assertType('int', $this->i);
18+
assertNativeType('int', $this->i);
19+
}
20+
}
21+
22+
class KeepsPropertyNonNullable {
23+
private readonly int $i;
24+
25+
public function __construct()
26+
{
27+
$this->i = getIntOrNull();
28+
}
29+
30+
public function doFoo(): void {
31+
assertType('int', $this->i);
32+
assertNativeType('int', $this->i);
33+
}
34+
}
35+
36+
class DontCoercePhpdocType {
37+
/** @var int */
38+
private $i;
39+
40+
public function __construct()
41+
{
42+
$this->i = getIntOrNull();
43+
}
44+
45+
public function doFoo(): void {
46+
assertType('int', $this->i);
47+
assertNativeType('mixed', $this->i);
48+
}
49+
}
50+
51+
function getIntOrNull(): ?int {
52+
if (rand(0, 1) === 0) {
53+
return null;
54+
}
55+
return 1;
56+
}
57+
58+
59+
class KeepsPropertyNonNullable2 {
60+
private int|float $i;
61+
62+
public function __construct()
63+
{
64+
$this->i = getIntOrFloatOrNull();
65+
}
66+
67+
public function doFoo(): void {
68+
assertType('float|int', $this->i);
69+
assertNativeType('float|int', $this->i);
70+
}
71+
}
72+
73+
function getIntOrFloatOrNull(): null|int|float {
74+
if (rand(0, 1) === 0) {
75+
return null;
76+
}
77+
78+
if (rand(0, 10) === 0) {
79+
return 1.0;
80+
}
81+
return 1;
82+
}
83+
84+
class NarrowsNativeUnion {
85+
private readonly int|float $i;
86+
87+
public function __construct()
88+
{
89+
$this->i = getInt();
90+
}
91+
92+
public function doFoo(): void {
93+
assertType('int', $this->i);
94+
assertNativeType('int', $this->i);
95+
}
96+
}
97+
98+
function getInt(): int {
99+
return 1;
100+
}

src/Analyser/NodeScopeResolver.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5628,10 +5628,13 @@ static function (): void {
56285628
$assignedNativeType = $scope->getNativeType($assignedExpr);
56295629
$propertyNativeType = $propertyReflection->getNativeType();
56305630

5631+
if ($propertyReflection->hasNativeType() && $propertyNativeType->isNull()->no()) {
5632+
$assignedExprType = TypeCombinator::removeNull($assignedExprType);
5633+
$assignedNativeType = TypeCombinator::removeNull($assignedNativeType);
5634+
}
5635+
56315636
if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) {
56325637
$scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType));
5633-
} elseif ($propertyReflection->hasNativeType() && $propertyNativeType->isNull()->no()) {
5634-
$scope = $scope->assignExpression($var, TypeCombinator::removeNull($assignedExprType), TypeCombinator::removeNull($assignedNativeType));
56355638
} else {
56365639
$scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType);
56375640
}
@@ -5702,10 +5705,13 @@ static function (): void {
57025705
$assignedNativeType = $scope->getNativeType($assignedExpr);
57035706
$propertyNativeType = $propertyReflection->getNativeType();
57045707

5708+
if ($propertyReflection->hasNativeType() && $propertyNativeType->isNull()->no()) {
5709+
$assignedExprType = TypeCombinator::removeNull($assignedExprType);
5710+
$assignedNativeType = TypeCombinator::removeNull($assignedNativeType);
5711+
}
5712+
57055713
if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) {
57065714
$scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType));
5707-
} elseif ($propertyReflection->hasNativeType() && $propertyNativeType->isNull()->no()) {
5708-
$scope = $scope->assignExpression($var, TypeCombinator::removeNull($assignedExprType), TypeCombinator::removeNull($assignedNativeType));
57095715
} else {
57105716
$scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType);
57115717
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php declare(strict_types = 0); // lint >= 8.1
2+
3+
namespace RememberNonNullablePropertyWhenStrictTypesDisabled;
4+
5+
use function PHPStan\Testing\assertNativeType;
6+
use function PHPStan\Testing\assertType;
7+
8+
class KeepsPropertyNonNullable {
9+
private readonly int $i;
10+
11+
public function __construct()
12+
{
13+
$this->i = getIntOrNull();
14+
}
15+
16+
public function doFoo(): void {
17+
assertType('int', $this->i);
18+
assertNativeType('int', $this->i);
19+
}
20+
}
21+
22+
class DontCoercePhpdocType {
23+
/** @var int */
24+
private $i;
25+
26+
public function __construct()
27+
{
28+
$this->i = getIntOrNull();
29+
}
30+
31+
public function doFoo(): void {
32+
assertType('int', $this->i);
33+
assertNativeType('mixed', $this->i);
34+
}
35+
}
36+
37+
function getIntOrNull(): ?int {
38+
if (rand(0, 1) === 0) {
39+
return null;
40+
}
41+
return 1;
42+
}
43+
44+
45+
class KeepsPropertyNonNullable2 {
46+
private int|float $i;
47+
48+
public function __construct()
49+
{
50+
$this->i = getIntOrFloatOrNull();
51+
}
52+
53+
public function doFoo(): void {
54+
assertType('float|int', $this->i);
55+
assertNativeType('float|int', $this->i);
56+
}
57+
}
58+
59+
function getIntOrFloatOrNull(): null|int|float {
60+
if (rand(0, 1) === 0) {
61+
return null;
62+
}
63+
64+
if (rand(0, 10) === 0) {
65+
return 1.0;
66+
}
67+
return 1;
68+
}
69+
70+
class NarrowsNativeUnion {
71+
private readonly int|float $i;
72+
73+
public function __construct()
74+
{
75+
$this->i = getInt();
76+
}
77+
78+
public function doFoo(): void {
79+
assertType('int', $this->i);
80+
assertNativeType('int', $this->i);
81+
}
82+
}
83+
84+
function getInt(): int {
85+
return 1;
86+
}

tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php

Lines changed: 5 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,11 @@
1-
<?php // lint >= 8.1
1+
<?php declare(strict_types = 1); // lint >= 8.1
22

3-
namespace RememberNonNullablePropertyWhenStrictTypesDisabled;
3+
namespace RememberNonNullableProperty;
44

55
use function PHPStan\Testing\assertNativeType;
66
use function PHPStan\Testing\assertType;
77

8-
class KeepsPropertyNonNullable {
9-
private readonly int $i;
10-
11-
public function __construct()
12-
{
13-
$this->i = getIntOrNull();
14-
}
15-
16-
public function doFoo(): void {
17-
assertType('int', $this->i);
18-
assertNativeType('int', $this->i);
19-
}
20-
}
21-
22-
class DontCoercePhpdocType {
23-
/** @var int */
24-
private $i;
25-
26-
public function __construct()
27-
{
28-
$this->i = getIntOrNull();
29-
}
30-
31-
public function doFoo(): void {
32-
assertType('int', $this->i);
33-
assertNativeType('mixed', $this->i);
34-
}
35-
}
36-
37-
function getIntOrNull(): ?int {
38-
if (rand(0, 1) === 0) {
39-
return null;
40-
}
41-
return 1;
42-
}
43-
44-
45-
class KeepsPropertyNonNullable2 {
46-
private int|float $i;
47-
48-
public function __construct()
49-
{
50-
$this->i = getIntOrFloatOrNull();
51-
}
52-
53-
public function doFoo(): void {
54-
assertType('float|int', $this->i);
55-
assertNativeType('float|int', $this->i);
56-
}
57-
}
58-
59-
function getIntOrFloatOrNull(): null|int|float {
60-
if (rand(0, 1) === 0) {
61-
return null;
62-
}
63-
64-
if (rand(0, 10) === 0) {
65-
return 1.0;
66-
}
67-
return 1;
68-
}
69-
70-
class NarrowsNativeUnion {
8+
class DoesntNarrowNativeUnion {
719
private readonly int|float $i;
7210

7311
public function __construct()
@@ -76,8 +14,8 @@ public function __construct()
7614
}
7715

7816
public function doFoo(): void {
79-
assertType('int', $this->i);
80-
assertNativeType('int', $this->i);
17+
assertType('float|int', $this->i);
18+
assertNativeType('float|int', $this->i);
8119
}
8220
}
8321

0 commit comments

Comments
 (0)