Skip to content

Commit 2f3dce8

Browse files
schlndhondrejmirtes
authored andcommitted
WIP: add option to prohibit coercing bool to string
1 parent d3f3b18 commit 2f3dce8

6 files changed

Lines changed: 81 additions & 2 deletions

File tree

‎conf/bleedingEdge.neon‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ parameters:
3434
unusedVariable: true
3535
unusedParameters: true
3636
reuseCollectedDataRuleErrors: true
37+
checkTypeCoercions: true

‎conf/config.neon‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ parameters:
6666
unusedVariable: false
6767
unusedParameters: false
6868
reuseCollectedDataRuleErrors: false
69+
checkTypeCoercions: false
6970
fileExtensions:
7071
- php
7172
checkAdvancedIsset: false
@@ -267,6 +268,8 @@ parameters:
267268
- [parameters, tmpDir]
268269
- [parameters, pro]
269270
- parametersSchema
271+
allowedTypeCoercions:
272+
boolToString: true
270273

271274
autowiredAttributeServices:
272275
level: null

‎conf/parametersSchema.neon‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ parametersSchema:
6363
unusedVariable: bool()
6464
unusedParameters: bool()
6565
reuseCollectedDataRuleErrors: bool()
66+
checkTypeCoercions: bool()
6667
])
6768
fileExtensions: listOf(string())
6869
checkAdvancedIsset: bool()
@@ -199,6 +200,9 @@ parametersSchema:
199200
string(),
200201
listOf(string()),
201202
)))
203+
allowedTypeCoercions: structure([
204+
boolToString: bool()
205+
])
202206

203207
# playground mode
204208
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,6 +16,8 @@
1516
class InvalidPartOfEncapsedStringRuleTest extends RuleTestCase
1617
{
1718

19+
private ?TypeCoercionRuleHelper $typeCoercionRuleHelper = null;
20+
1821
protected function getRule(): Rule
1922
{
2023
return new InvalidPartOfEncapsedStringRule(
@@ -29,6 +32,7 @@ protected function getRule(): Rule
2932
checkBenevolentUnionTypes: false,
3033
discoveringSymbolsTip: true,
3134
),
35+
$this->typeCoercionRuleHelper ?? new TypeCoercionRuleHelper(true, true),
3236
);
3337
}
3438

@@ -54,6 +58,37 @@ public function testRule(): void
5458
]);
5559
}
5660

61+
public function testRuleWithStrictCoercions(): void
62+
{
63+
$this->typeCoercionRuleHelper = new TypeCoercionRuleHelper(true, false);
64+
$this->analyse([__DIR__ . '/data/invalid-encapsed-part.php'], [
65+
[
66+
'Part $std (stdClass) of encapsed string cannot be cast to string.',
67+
26,
68+
],
69+
[
70+
'Part $bool (bool) of encapsed string cannot be cast to string.',
71+
27,
72+
],
73+
[
74+
'Part $array (array) of encapsed string cannot be cast to string.',
75+
30,
76+
],
77+
[
78+
'Part $std (stdClass|string) of encapsed string cannot be cast to string.',
79+
56,
80+
],
81+
[
82+
'Part $bool (bool|string) of encapsed string cannot be cast to string.',
83+
57,
84+
],
85+
[
86+
'Part $array (array|string) of encapsed string cannot be cast to string.',
87+
60,
88+
],
89+
]);
90+
}
91+
5792
#[RequiresPhp('>= 8.0.0')]
5893
public function testRuleWithNullsafeVariant(): void
5994
{

0 commit comments

Comments
 (0)