Skip to content

Commit 9eb4e6d

Browse files
staabmondrejmirtes
authored andcommitted
Implement BinaryModHandler
1 parent b0d1fa4 commit 9eb4e6d

File tree

4 files changed

+101
-1
lines changed

4 files changed

+101
-1
lines changed

src/Analyser/Generator/ExprHandler/BinaryDivHandler.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Analyser\Generator\ExprHandler;
44

5+
use DivisionByZeroError;
56
use Generator;
67
use PhpParser\Node\Expr;
78
use PhpParser\Node\Stmt;
@@ -10,9 +11,12 @@
1011
use PHPStan\Analyser\Generator\ExprAnalysisResult;
1112
use PHPStan\Analyser\Generator\ExprHandler;
1213
use PHPStan\Analyser\Generator\GeneratorScope;
14+
use PHPStan\Analyser\Generator\InternalThrowPoint;
1315
use PHPStan\Analyser\SpecifiedTypes;
1416
use PHPStan\DependencyInjection\AutowiredService;
1517
use PHPStan\Reflection\InitializerExprTypeResolver;
18+
use PHPStan\Type\Constant\ConstantIntegerType;
19+
use PHPStan\Type\ObjectType;
1620
use function array_merge;
1721

1822
/**
@@ -36,13 +40,20 @@ public function analyseExpr(Stmt $stmt, Expr $expr, GeneratorScope $scope, Expre
3640
$leftResult = yield new ExprAnalysisRequest($stmt, $expr->left, $scope, $context, $alternativeNodeCallback);
3741
$rightResult = yield new ExprAnalysisRequest($stmt, $expr->right, $leftResult->scope, $context, $alternativeNodeCallback);
3842

43+
$throwPoints = array_merge($leftResult->throwPoints, $rightResult->throwPoints);
44+
if (
45+
!$rightResult->type->toNumber()->isSuperTypeOf(new ConstantIntegerType(0))->no()
46+
) {
47+
$throwPoints[] = InternalThrowPoint::createExplicit($scope, new ObjectType(DivisionByZeroError::class), $expr, false);
48+
}
49+
3950
return new ExprAnalysisResult(
4051
$this->initializerExprTypeResolver->getDivTypeFromTypes($expr->left, $expr->right, $leftResult->type, $rightResult->type),
4152
$this->initializerExprTypeResolver->getDivTypeFromTypes($expr->left, $expr->right, $leftResult->nativeType, $rightResult->nativeType),
4253
$rightResult->scope,
4354
hasYield: $leftResult->hasYield || $rightResult->hasYield,
4455
isAlwaysTerminating: $leftResult->isAlwaysTerminating || $rightResult->isAlwaysTerminating,
45-
throwPoints: array_merge($leftResult->throwPoints, $rightResult->throwPoints),
56+
throwPoints: $throwPoints,
4657
impurePoints: array_merge($leftResult->impurePoints, $rightResult->impurePoints),
4758
specifiedTruthyTypes: new SpecifiedTypes(),
4859
specifiedFalseyTypes: new SpecifiedTypes(),
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Generator\ExprHandler;
4+
5+
use DivisionByZeroError;
6+
use Generator;
7+
use PhpParser\Node\Expr;
8+
use PhpParser\Node\Stmt;
9+
use PHPStan\Analyser\ExpressionContext;
10+
use PHPStan\Analyser\Generator\ExprAnalysisRequest;
11+
use PHPStan\Analyser\Generator\ExprAnalysisResult;
12+
use PHPStan\Analyser\Generator\ExprHandler;
13+
use PHPStan\Analyser\Generator\GeneratorScope;
14+
use PHPStan\Analyser\Generator\InternalThrowPoint;
15+
use PHPStan\Analyser\SpecifiedTypes;
16+
use PHPStan\DependencyInjection\AutowiredService;
17+
use PHPStan\Reflection\InitializerExprTypeResolver;
18+
use PHPStan\Type\Constant\ConstantIntegerType;
19+
use PHPStan\Type\ObjectType;
20+
use function array_merge;
21+
22+
/**
23+
* @implements ExprHandler<Expr\BinaryOp\Mod>
24+
*/
25+
#[AutowiredService]
26+
final class BinaryModHandler implements ExprHandler
27+
{
28+
29+
public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver)
30+
{
31+
}
32+
33+
public function supports(Expr $expr): bool
34+
{
35+
return $expr instanceof Expr\BinaryOp\Mod;
36+
}
37+
38+
public function analyseExpr(Stmt $stmt, Expr $expr, GeneratorScope $scope, ExpressionContext $context, ?callable $alternativeNodeCallback): Generator
39+
{
40+
$leftResult = yield new ExprAnalysisRequest($stmt, $expr->left, $scope, $context, $alternativeNodeCallback);
41+
$rightResult = yield new ExprAnalysisRequest($stmt, $expr->right, $leftResult->scope, $context, $alternativeNodeCallback);
42+
43+
$throwPoints = array_merge($leftResult->throwPoints, $rightResult->throwPoints);
44+
if (
45+
!$rightResult->type->toNumber()->isSuperTypeOf(new ConstantIntegerType(0))->no()
46+
) {
47+
$throwPoints[] = InternalThrowPoint::createExplicit($scope, new ObjectType(DivisionByZeroError::class), $expr, false);
48+
}
49+
50+
return new ExprAnalysisResult(
51+
$this->initializerExprTypeResolver->getModTypeFromTypes($expr->left, $expr->right, $leftResult->type, $rightResult->type),
52+
$this->initializerExprTypeResolver->getModTypeFromTypes($expr->left, $expr->right, $leftResult->nativeType, $rightResult->nativeType),
53+
$rightResult->scope,
54+
hasYield: $leftResult->hasYield || $rightResult->hasYield,
55+
isAlwaysTerminating: $leftResult->isAlwaysTerminating || $rightResult->isAlwaysTerminating,
56+
throwPoints: $throwPoints,
57+
impurePoints: array_merge($leftResult->impurePoints, $rightResult->impurePoints),
58+
specifiedTruthyTypes: new SpecifiedTypes(),
59+
specifiedFalseyTypes: new SpecifiedTypes(),
60+
specifiedNullTypes: new SpecifiedTypes(),
61+
);
62+
}
63+
64+
}

src/Reflection/InitializerExprTypeResolver.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,11 @@ public function getModType(Expr $left, Expr $right, callable $getTypeCallback):
12381238
$leftType = $getTypeCallback($left);
12391239
$rightType = $getTypeCallback($right);
12401240

1241+
return $this->getModTypeFromTypes($left, $right, $leftType, $rightType);
1242+
}
1243+
1244+
public function getModTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type
1245+
{
12411246
if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
12421247
return $this->getNeverType($leftType, $rightType);
12431248
}

tests/PHPStan/Analyser/Generator/data/gnsr.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace GeneratorNodeScopeResolverTest;
66

77
use Closure;
8+
use DivisionByZeroError;
89
use function PHPStan\Testing\assertNativeType;
910
use function PHPStan\Testing\assertType;
1011

@@ -50,6 +51,25 @@ public function doDiv($a, $b, int $c, int $d): void
5051
assertNativeType('1', 1 / 1);
5152
assertType('(float|int)', $c / $d);
5253
assertNativeType('(float|int)', $c / $d);
54+
55+
assertType('*ERROR*', $c / 0); // DivisionByZeroError
56+
}
57+
58+
/**
59+
* @param int $a
60+
* @param int $b
61+
* @return void
62+
*/
63+
public function doMod($a, $b, int $c, int $d): void
64+
{
65+
assertType('int', $a % $b);
66+
assertNativeType('int', $a % $b);
67+
assertType('0', 1 % 1);
68+
assertNativeType('0', 1 % 1);
69+
assertType('int', $c % $d);
70+
assertNativeType('int', $c % $d);
71+
72+
assertType('*ERROR*', $c % 0); // DivisionByZeroError
5373
}
5474

5575
/**

0 commit comments

Comments
 (0)