Skip to content

Commit

Permalink
Merge branch refs/heads/1.11.x into 1.12.x
Browse files Browse the repository at this point in the history
  • Loading branch information
phpstan-bot authored Jul 19, 2024
2 parents 516f915 + 8a1f098 commit 1bce168
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 20 deletions.
7 changes: 6 additions & 1 deletion conf/config.level2.neon
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ rules:
- PHPStan\Rules\Generics\UsedTraitsRule
- PHPStan\Rules\Methods\CallPrivateMethodThroughStaticRule
- PHPStan\Rules\Methods\IncompatibleDefaultParameterTypeRule
- PHPStan\Rules\Operators\InvalidUnaryOperationRule
- PHPStan\Rules\Operators\InvalidComparisonOperationRule
- PHPStan\Rules\PhpDoc\FunctionConditionalReturnTypeRule
- PHPStan\Rules\PhpDoc\MethodConditionalReturnTypeRule
Expand Down Expand Up @@ -143,3 +142,9 @@ services:
bleedingEdge: %featureToggles.bleedingEdge%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Operators\InvalidUnaryOperationRule
arguments:
bleedingEdge: %featureToggles.bleedingEdge%
tags:
- phpstan.rules.rule
75 changes: 58 additions & 17 deletions src/Rules/Operators/InvalidUnaryOperationRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
namespace PHPStan\Rules\Operators;

use PhpParser\Node;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;
use function sprintf;

Expand All @@ -16,6 +20,13 @@
class InvalidUnaryOperationRule implements Rule
{

public function __construct(
private RuleLevelHelper $ruleLevelHelper,
private bool $bleedingEdge,
)
{
}

public function getNodeType(): string
{
return Node\Expr::class;
Expand All @@ -31,28 +42,58 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

if ($scope->getType($node) instanceof ErrorType) {
if ($this->bleedingEdge) {
$varName = '__PHPSTAN__LEFT__';
$variable = new Node\Expr\Variable($varName);
$newNode = clone $node;
$newNode->setAttribute('phpstan_cache_printer', null);
$newNode->expr = $variable;

if ($node instanceof Node\Expr\UnaryPlus) {
$operator = '+';
} elseif ($node instanceof Node\Expr\UnaryMinus) {
$operator = '-';
if ($node instanceof Node\Expr\BitwiseNot) {
$callback = static fn (Type $type): bool => $type->isString()->yes() || $type->isInteger()->yes() || $type->isFloat()->yes();
} else {
$operator = '~';
$callback = static fn (Type $type): bool => !$type->toNumber() instanceof ErrorType;
}
return [
RuleErrorBuilder::message(sprintf(
'Unary operation "%s" on %s results in an error.',
$operator,
$scope->getType($node->expr)->describe(VerbosityLevel::value()),
))
->line($node->expr->getStartLine())
->identifier('unaryOp.invalid')
->build(),
];

$exprType = $this->ruleLevelHelper->findTypeToCheck(
$scope,
$node->expr,
'',
$callback,
)->getType();
if ($exprType instanceof ErrorType) {
return [];
}

if (!$scope instanceof MutatingScope) {
throw new ShouldNotHappenException();
}

$scope = $scope->assignVariable($varName, $exprType, $exprType);
if (!$scope->getType($newNode) instanceof ErrorType) {
return [];
}
} elseif (!$scope->getType($node) instanceof ErrorType) {
return [];
}

return [];
if ($node instanceof Node\Expr\UnaryPlus) {
$operator = '+';
} elseif ($node instanceof Node\Expr\UnaryMinus) {
$operator = '-';
} else {
$operator = '~';
}
return [
RuleErrorBuilder::message(sprintf(
'Unary operation "%s" on %s results in an error.',
$operator,
$scope->getType($node->expr)->describe(VerbosityLevel::value()),
))
->line($node->expr->getStartLine())
->identifier('unaryOp.invalid')
->build(),
];
}

}
128 changes: 127 additions & 1 deletion tests/PHPStan/Rules/Operators/InvalidUnaryOperationRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Rules\Operators;

use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;

/**
Expand All @@ -11,9 +12,16 @@
class InvalidUnaryOperationRuleTest extends RuleTestCase
{

private bool $checkExplicitMixed = false;

private bool $checkImplicitMixed = false;

protected function getRule(): Rule
{
return new InvalidUnaryOperationRule();
return new InvalidUnaryOperationRule(
new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, $this->checkImplicitMixed, true, false),
true,
);
}

public function testRule(): void
Expand All @@ -39,6 +47,124 @@ public function testRule(): void
'Unary operation "~" on array{} results in an error.',
24,
],
[
'Unary operation "~" on bool results in an error.',
36,
],
[
'Unary operation "+" on array results in an error.',
38,
],
[
'Unary operation "-" on array results in an error.',
39,
],
[
'Unary operation "~" on array results in an error.',
40,
],
[
'Unary operation "+" on object results in an error.',
42,
],
[
'Unary operation "-" on object results in an error.',
43,
],
[
'Unary operation "~" on object results in an error.',
44,
],
[
'Unary operation "+" on resource results in an error.',
50,
],
[
'Unary operation "-" on resource results in an error.',
51,
],
[
'Unary operation "~" on resource results in an error.',
52,
],
[
'Unary operation "~" on null results in an error.',
61,
],
]);
}

public function testMixed(): void
{
$this->checkImplicitMixed = true;
$this->checkExplicitMixed = true;
$this->analyse([__DIR__ . '/data/invalid-unary-mixed.php'], [
[
'Unary operation "+" on T results in an error.',
11,
],
[
'Unary operation "-" on T results in an error.',
12,
],
[
'Unary operation "~" on T results in an error.',
13,
],
[
'Unary operation "+" on mixed results in an error.',
18,
],
[
'Unary operation "-" on mixed results in an error.',
19,
],
[
'Unary operation "~" on mixed results in an error.',
20,
],
[
'Unary operation "+" on mixed results in an error.',
25,
],
[
'Unary operation "-" on mixed results in an error.',
26,
],
[
'Unary operation "~" on mixed results in an error.',
27,
],
]);
}

public function testUnion(): void
{
$this->analyse([__DIR__ . '/data/unary-union.php'], [
[
'Unary operation "+" on array|bool|float|int|object|string|null results in an error.',
21,
],
[
'Unary operation "-" on array|bool|float|int|object|string|null results in an error.',
22,
],
[
'Unary operation "~" on array|bool|float|int|object|string|null results in an error.',
23,
],
[
'Unary operation "+" on (array|object) results in an error.',
25,
],
[
'Unary operation "-" on (array|object) results in an error.',
26,
],
[
'Unary operation "~" on (array|object) results in an error.',
27,
],
]);
}

Expand Down
28 changes: 28 additions & 0 deletions tests/PHPStan/Rules/Operators/data/invalid-unary-mixed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);

namespace InvalidUnaryMixed;

/**
* @template T
* @param T $a
*/
function genericMixed(mixed $a): void
{
var_dump(+$a);
var_dump(-$a);
var_dump(~$a);
}

function explicitMixed(mixed $a): void
{
var_dump(+$a);
var_dump(-$a);
var_dump(~$a);
}

function implicitMixed($a): void
{
var_dump(+$a);
var_dump(-$a);
var_dump(~$a);
}
38 changes: 37 additions & 1 deletion tests/PHPStan/Rules/Operators/data/invalid-unary.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?php

namespace InvalidUnary;
function (
int $i,
string $str
Expand All @@ -24,3 +24,39 @@ function (
~$array;
~1.1;
};

/**
* @param resource $r
* @param numeric-string $ns
*/
function foo(bool $b, array $a, object $o, float $f, $r, string $ns): void
{
+$b;
-$b;
~$b;

+$a;
-$a;
~$a;

+$o;
-$o;
~$o;

+$f;
-$f;
~$f;

+$r;
-$r;
~$r;

+$ns;
-$ns;
~$ns;

$null = null;
+$null;
-$null;
~$null;
}
28 changes: 28 additions & 0 deletions tests/PHPStan/Rules/Operators/data/unary-union.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);

namespace UnaryBenevolentUnion;

/**
* @param __benevolent<scalar|null|array|object> $benevolentUnion
* @param numeric-string|int|float $okUnion
* @param scalar|null|array|object $union
* @param __benevolent<array|object> $badBenevolentUnion
*/
function foo($benevolentUnion, $okUnion, $union, $badBenevolentUnion): void
{
+$benevolentUnion;
-$benevolentUnion;
~$benevolentUnion;

+$okUnion;
-$okUnion;
~$okUnion;

+$union;
-$union;
~$union;

+$badBenevolentUnion;
-$badBenevolentUnion;
~$badBenevolentUnion;
}

0 comments on commit 1bce168

Please sign in to comment.