Skip to content

Commit e454058

Browse files
committed
WIP: add option to prohibit coercing bool to string
1 parent 51f9e83 commit e454058

File tree

6 files changed

+81
-2
lines changed

6 files changed

+81
-2
lines changed

conf/bleedingEdge.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ parameters:
1414
rawMessageInBaseline: true
1515
reportNestedTooWideType: false
1616
assignToByRefForeachExpr: true
17+
checkTypeCoercions: true

conf/config.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ parameters:
3838
rawMessageInBaseline: false
3939
reportNestedTooWideType: false
4040
assignToByRefForeachExpr: false
41+
checkTypeCoercions: false
4142
fileExtensions:
4243
- php
4344
checkAdvancedIsset: false
@@ -221,6 +222,8 @@ parameters:
221222
- [parameters, memoryLimitFile]
222223
- [parameters, pro]
223224
- parametersSchema
225+
allowedTypeCoercions:
226+
boolToString: true
224227

225228
extensions:
226229
rules: PHPStan\DependencyInjection\RulesExtension

conf/parametersSchema.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ parametersSchema:
4141
rawMessageInBaseline: bool()
4242
reportNestedTooWideType: bool()
4343
assignToByRefForeachExpr: bool()
44+
checkTypeCoercions: bool()
4445
])
4546
fileExtensions: listOf(string())
4647
checkAdvancedIsset: bool()
@@ -163,6 +164,9 @@ parametersSchema:
163164
string(),
164165
listOf(string()),
165166
)))
167+
allowedTypeCoercions: structure([
168+
boolToString: bool()
169+
])
166170

167171
# playground mode
168172
sourceLocatorPlaygroundMode: bool()

src/Rules/Cast/InvalidPartOfEncapsedStringRule.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPStan\Rules\Rule;
1010
use PHPStan\Rules\RuleErrorBuilder;
1111
use PHPStan\Rules\RuleLevelHelper;
12+
use PHPStan\Rules\TypeCoercionRuleHelper;
1213
use PHPStan\Type\ErrorType;
1314
use PHPStan\Type\Type;
1415
use PHPStan\Type\VerbosityLevel;
@@ -24,6 +25,7 @@ final class InvalidPartOfEncapsedStringRule implements Rule
2425
public function __construct(
2526
private ExprPrinter $exprPrinter,
2627
private RuleLevelHelper $ruleLevelHelper,
28+
private TypeCoercionRuleHelper $typeCoercionRuleHelper,
2729
)
2830
{
2931
}
@@ -45,14 +47,14 @@ public function processNode(Node $node, Scope $scope): array
4547
$scope,
4648
$part,
4749
'',
48-
static fn (Type $type): bool => !$type->toString() instanceof ErrorType,
50+
fn (Type $type): bool => !$this->typeCoercionRuleHelper->coerceToString($type) instanceof ErrorType,
4951
);
5052
$partType = $typeResult->getType();
5153
if ($partType instanceof ErrorType) {
5254
continue;
5355
}
5456

55-
$stringPartType = $partType->toString();
57+
$stringPartType = $this->typeCoercionRuleHelper->coerceToString($partType);
5658
if (!$stringPartType instanceof ErrorType) {
5759
continue;
5860
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules;
4+
5+
use PHPStan\DependencyInjection\AutowiredParameter;
6+
use PHPStan\DependencyInjection\AutowiredService;
7+
use PHPStan\Type\ErrorType;
8+
use PHPStan\Type\Type;
9+
10+
#[AutowiredService]
11+
final class TypeCoercionRuleHelper
12+
{
13+
14+
public function __construct(
15+
#[AutowiredParameter(ref: '%featureToggles.checkTypeCoercions%')]
16+
private readonly bool $checkTypeCoercions,
17+
#[AutowiredParameter(ref: '%allowedTypeCoercions.boolToString%')]
18+
private readonly bool $allowBoolToString,
19+
)
20+
{
21+
}
22+
23+
public function coerceToString(Type $type): Type
24+
{
25+
if (!$this->checkTypeCoercions) {
26+
return $type->toString();
27+
}
28+
if (!$this->allowBoolToString && !$type->isBoolean()->no()) {
29+
return new ErrorType();
30+
}
31+
return $type->toString();
32+
}
33+
34+
}

tests/PHPStan/Rules/Cast/InvalidPartOfEncapsedStringRuleTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\Node\Printer\Printer;
77
use PHPStan\Rules\Rule;
88
use PHPStan\Rules\RuleLevelHelper;
9+
use PHPStan\Rules\TypeCoercionRuleHelper;
910
use PHPStan\Testing\RuleTestCase;
1011
use PHPUnit\Framework\Attributes\RequiresPhp;
1112

@@ -15,11 +16,14 @@
1516
class InvalidPartOfEncapsedStringRuleTest extends RuleTestCase
1617
{
1718

19+
private ?TypeCoercionRuleHelper $typeCoercionRuleHelper = null;
20+
1821
protected function getRule(): Rule
1922
{
2023
return new InvalidPartOfEncapsedStringRule(
2124
new ExprPrinter(new Printer()),
2225
new RuleLevelHelper(self::createReflectionProvider(), true, false, true, false, false, false, true),
26+
$this->typeCoercionRuleHelper ?? new TypeCoercionRuleHelper(true, true),
2327
);
2428
}
2529

@@ -45,6 +49,37 @@ public function testRule(): void
4549
]);
4650
}
4751

52+
public function testRuleWithStrictCoercions(): void
53+
{
54+
$this->typeCoercionRuleHelper = new TypeCoercionRuleHelper(true, false);
55+
$this->analyse([__DIR__ . '/data/invalid-encapsed-part.php'], [
56+
[
57+
'Part $std (stdClass) of encapsed string cannot be cast to string.',
58+
26,
59+
],
60+
[
61+
'Part $bool (bool) of encapsed string cannot be cast to string.',
62+
27,
63+
],
64+
[
65+
'Part $array (array) of encapsed string cannot be cast to string.',
66+
30,
67+
],
68+
[
69+
'Part $std (stdClass|string) of encapsed string cannot be cast to string.',
70+
56,
71+
],
72+
[
73+
'Part $bool (bool|string) of encapsed string cannot be cast to string.',
74+
57,
75+
],
76+
[
77+
'Part $array (array|string) of encapsed string cannot be cast to string.',
78+
60,
79+
],
80+
]);
81+
}
82+
4883
#[RequiresPhp('>= 8.0')]
4984
public function testRuleWithNullsafeVariant(): void
5085
{

0 commit comments

Comments
 (0)