Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 8a1f098

Browse files
schlndhondrejmirtes
authored andcommitted
Bleeding edge - check mixed in unary operator
1 parent e35eae4 commit 8a1f098

File tree

6 files changed

+284
-20
lines changed

6 files changed

+284
-20
lines changed

‎conf/config.level2.neon‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ rules:
3030
- PHPStan\Rules\Generics\UsedTraitsRule
3131
- PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule
3232
- PHPStan\Rules\Methods\IncompatibleDefaultParameterTypeRule
33-
- PHPStan\Rules\Operators\InvalidUnaryOperationRule
3433
- PHPStan\Rules\Operators\InvalidComparisonOperationRule
3534
- PHPStan\Rules\PhpDoc\FunctionConditionalReturnTypeRule
3635
- PHPStan\Rules\PhpDoc\MethodConditionalReturnTypeRule
@@ -143,3 +142,9 @@ services:
143142
bleedingEdge: %featureToggles.bleedingEdge%
144143
tags:
145144
- phpstan.rules.rule
145+
-
146+
class: PHPStan\Rules\Operators\InvalidUnaryOperationRule
147+
arguments:
148+
bleedingEdge: %featureToggles.bleedingEdge%
149+
tags:
150+
- phpstan.rules.rule

‎src/Rules/Operators/InvalidUnaryOperationRule.php‎

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
namespace PHPStan\Rules\Operators;
44

55
use PhpParser\Node;
6+
use PHPStan\Analyser\MutatingScope;
67
use PHPStan\Analyser\Scope;
78
use PHPStan\Rules\Rule;
89
use PHPStan\Rules\RuleErrorBuilder;
10+
use PHPStan\Rules\RuleLevelHelper;
11+
use PHPStan\ShouldNotHappenException;
912
use PHPStan\Type\ErrorType;
13+
use PHPStan\Type\Type;
1014
use PHPStan\Type\VerbosityLevel;
1115
use function sprintf;
1216

@@ -16,6 +20,13 @@
1620
class InvalidUnaryOperationRule implements Rule
1721
{
1822

23+
public function __construct(
24+
private RuleLevelHelper $ruleLevelHelper,
25+
private bool $bleedingEdge,
26+
)
27+
{
28+
}
29+
1930
public function getNodeType(): string
2031
{
2132
return Node\Expr::class;
@@ -31,28 +42,58 @@ public function processNode(Node $node, Scope $scope): array
3142
return [];
3243
}
3344

34-
if ($scope->getType($node) instanceof ErrorType) {
45+
if ($this->bleedingEdge) {
46+
$varName = '__PHPSTAN__LEFT__';
47+
$variable = new Node\Expr\Variable($varName);
48+
$newNode = clone $node;
49+
$newNode->setAttribute('phpstan_cache_printer', null);
50+
$newNode->expr = $variable;
3551

36-
if ($node instanceof Node\Expr\UnaryPlus) {
37-
$operator = '+';
38-
} elseif ($node instanceof Node\Expr\UnaryMinus) {
39-
$operator = '-';
52+
if ($node instanceof Node\Expr\BitwiseNot) {
53+
$callback = static fn (Type $type): bool => $type->isString()->yes() || $type->isInteger()->yes() || $type->isFloat()->yes();
4054
} else {
41-
$operator = '~';
55+
$callback = staticfn (Type$type): bool => !$type->toNumber() instanceof ErrorType;
4256
}
43-
return [
44-
RuleErrorBuilder::message(sprintf(
45-
'Unary operation "%s" on %s results in an error.',
46-
$operator,
47-
$scope->getType($node->expr)->describe(VerbosityLevel::value()),
48-
))
49-
->line($node->expr->getStartLine())
50-
->identifier('unaryOp.invalid')
51-
->build(),
52-
];
57+
58+
$exprType = $this->ruleLevelHelper->findTypeToCheck(
59+
$scope,
60+
$node->expr,
61+
'',
62+
$callback,
63+
)->getType();
64+
if ($exprType instanceof ErrorType) {
65+
return [];
66+
}
67+
68+
if (!$scope instanceof MutatingScope) {
69+
throw new ShouldNotHappenException();
70+
}
71+
72+
$scope = $scope->assignVariable($varName, $exprType, $exprType);
73+
if (!$scope->getType($newNode) instanceof ErrorType) {
74+
return [];
75+
}
76+
} elseif (!$scope->getType($node) instanceof ErrorType) {
77+
return [];
5378
}
5479

55-
return [];
80+
if ($node instanceof Node\Expr\UnaryPlus) {
81+
$operator = '+';
82+
} elseif ($node instanceof Node\Expr\UnaryMinus) {
83+
$operator = '-';
84+
} else {
85+
$operator = '~';
86+
}
87+
return [
88+
RuleErrorBuilder::message(sprintf(
89+
'Unary operation "%s" on %s results in an error.',
90+
$operator,
91+
$scope->getType($node->expr)->describe(VerbosityLevel::value()),
92+
))
93+
->line($node->expr->getStartLine())
94+
->identifier('unaryOp.invalid')
95+
->build(),
96+
];
5697
}
5798

5899
}

‎tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php‎

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Rules\Operators;
44

55
use PHPStan\Rules\Rule;
6+
use PHPStan\Rules\RuleLevelHelper;
67
use PHPStan\Testing\RuleTestCase;
78

89
/**
@@ -11,9 +12,16 @@
1112
class InvalidUnaryOperationRuleTest extends RuleTestCase
1213
{
1314

15+
private bool $checkExplicitMixed = false;
16+
17+
private bool $checkImplicitMixed = false;
18+
1419
protected function getRule(): Rule
1520
{
16-
return new InvalidUnaryOperationRule();
21+
return new InvalidUnaryOperationRule(
22+
new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false),
23+
true,
24+
);
1725
}
1826

1927
public function testRule(): void
@@ -39,6 +47,124 @@ public function testRule(): void
3947
'Unary operation "~" on array{} results in an error.',
4048
24,
4149
],
50+
[
51+
'Unary operation "~" on bool results in an error.',
52+
36,
53+
],
54+
[
55+
'Unary operation "+" on array results in an error.',
56+
38,
57+
],
58+
[
59+
'Unary operation "-" on array results in an error.',
60+
39,
61+
],
62+
[
63+
'Unary operation "~" on array results in an error.',
64+
40,
65+
],
66+
[
67+
'Unary operation "+" on object results in an error.',
68+
42,
69+
],
70+
[
71+
'Unary operation "-" on object results in an error.',
72+
43,
73+
],
74+
[
75+
'Unary operation "~" on object results in an error.',
76+
44,
77+
],
78+
[
79+
'Unary operation "+" on resource results in an error.',
80+
50,
81+
],
82+
[
83+
'Unary operation "-" on resource results in an error.',
84+
51,
85+
],
86+
[
87+
'Unary operation "~" on resource results in an error.',
88+
52,
89+
],
90+
[
91+
'Unary operation "~" on null results in an error.',
92+
61,
93+
],
94+
]);
95+
}
96+
97+
public function testMixed(): void
98+
{
99+
$this->checkImplicitMixed = true;
100+
$this->checkExplicitMixed = true;
101+
$this->analyse([__DIR__ . '/data/invalid-unary-mixed.php'], [
102+
[
103+
'Unary operation "+" on T results in an error.',
104+
11,
105+
],
106+
[
107+
'Unary operation "-" on T results in an error.',
108+
12,
109+
],
110+
[
111+
'Unary operation "~" on T results in an error.',
112+
13,
113+
],
114+
[
115+
'Unary operation "+" on mixed results in an error.',
116+
18,
117+
],
118+
[
119+
'Unary operation "-" on mixed results in an error.',
120+
19,
121+
],
122+
[
123+
'Unary operation "~" on mixed results in an error.',
124+
20,
125+
],
126+
[
127+
'Unary operation "+" on mixed results in an error.',
128+
25,
129+
],
130+
[
131+
'Unary operation "-" on mixed results in an error.',
132+
26,
133+
],
134+
[
135+
'Unary operation "~" on mixed results in an error.',
136+
27,
137+
],
138+
]);
139+
}
140+
141+
public function testUnion(): void
142+
{
143+
$this->analyse([__DIR__ . '/data/unary-union.php'], [
144+
[
145+
'Unary operation "+" on array|bool|float|int|object|string|null results in an error.',
146+
21,
147+
],
148+
[
149+
'Unary operation "-" on array|bool|float|int|object|string|null results in an error.',
150+
22,
151+
],
152+
[
153+
'Unary operation "~" on array|bool|float|int|object|string|null results in an error.',
154+
23,
155+
],
156+
[
157+
'Unary operation "+" on (array|object) results in an error.',
158+
25,
159+
],
160+
[
161+
'Unary operation "-" on (array|object) results in an error.',
162+
26,
163+
],
164+
[
165+
'Unary operation "~" on (array|object) results in an error.',
166+
27,
167+
],
42168
]);
43169
}
44170

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace InvalidUnaryMixed;
4+
5+
/**
6+
* @template T
7+
* @param T $a
8+
*/
9+
function genericMixed(mixed $a): void
10+
{
11+
var_dump(+$a);
12+
var_dump(-$a);
13+
var_dump(~$a);
14+
}
15+
16+
function explicitMixed(mixed $a): void
17+
{
18+
var_dump(+$a);
19+
var_dump(-$a);
20+
var_dump(~$a);
21+
}
22+
23+
function implicitMixed($a): void
24+
{
25+
var_dump(+$a);
26+
var_dump(-$a);
27+
var_dump(~$a);
28+
}

‎tests/PHPStan/Rules/Operators/data/invalid-unary.php‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?php
2-
2+
namespaceInvalidUnary;
33
function (
44
int $i,
55
string $str
@@ -24,3 +24,39 @@ function (
2424
~$array;
2525
~1.1;
2626
};
27+
28+
/**
29+
* @param resource $r
30+
* @param numeric-string $ns
31+
*/
32+
function foo(bool $b, array $a, object $o, float $f, $r, string $ns): void
33+
{
34+
+$b;
35+
-$b;
36+
~$b;
37+
38+
+$a;
39+
-$a;
40+
~$a;
41+
42+
+$o;
43+
-$o;
44+
~$o;
45+
46+
+$f;
47+
-$f;
48+
~$f;
49+
50+
+$r;
51+
-$r;
52+
~$r;
53+
54+
+$ns;
55+
-$ns;
56+
~$ns;
57+
58+
$null = null;
59+
+$null;
60+
-$null;
61+
~$null;
62+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace UnaryBenevolentUnion;
4+
5+
/**
6+
* @param __benevolent<scalar|null|array|object> $benevolentUnion
7+
* @param numeric-string|int|float $okUnion
8+
* @param scalar|null|array|object $union
9+
* @param __benevolent<array|object> $badBenevolentUnion
10+
*/
11+
function foo($benevolentUnion, $okUnion, $union, $badBenevolentUnion): void
12+
{
13+
+$benevolentUnion;
14+
-$benevolentUnion;
15+
~$benevolentUnion;
16+
17+
+$okUnion;
18+
-$okUnion;
19+
~$okUnion;
20+
21+
+$union;
22+
-$union;
23+
~$union;
24+
25+
+$badBenevolentUnion;
26+
-$badBenevolentUnion;
27+
~$badBenevolentUnion;
28+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /