Skip to content

Commit c83196b

Browse files
Introduce lowercase-string
Co-authored-by: Ondrej Mirtes <[email protected]>
1 parent fd304ca commit c83196b

File tree

71 files changed

+1115
-80
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+1115
-80
lines changed

phpstan-baseline.neon

+5
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,11 @@ parameters:
691691
count: 1
692692
path: src/Type/Accessory/AccessoryLiteralStringType.php
693693

694+
-
695+
message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#"
696+
count: 1
697+
path: src/Type/Accessory/AccessoryLowercaseStringType.php
698+
694699
-
695700
message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#"
696701
count: 1

resources/functionMap.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -6361,7 +6361,7 @@
63616361
'mb_strripos' => ['0|positive-int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'],
63626362
'mb_strrpos' => ['0|positive-int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'],
63636363
'mb_strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool', 'encoding='=>'string'],
6364-
'mb_strtolower' => ['string', 'str'=>'string', 'encoding='=>'string'],
6364+
'mb_strtolower' => ['lowercase-string', 'str'=>'string', 'encoding='=>'string'],
63656365
'mb_strtoupper' => ['string', 'str'=>'string', 'encoding='=>'string'],
63666366
'mb_strwidth' => ['0|positive-int', 'str'=>'string', 'encoding='=>'string'],
63676367
'mb_substitute_character' => ['mixed', 'substchar='=>'mixed'],
@@ -12085,7 +12085,7 @@
1208512085
'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'mixed', 'before_needle='=>'bool'],
1208612086
'strtok' => ['non-empty-string|false', 'str'=>'string', 'token'=>'string'],
1208712087
'strtok\'1' => ['non-empty-string|false', 'token'=>'string'],
12088-
'strtolower' => ['string', 'str'=>'string'],
12088+
'strtolower' => ['lowercase-string', 'str'=>'string'],
1208912089
'strtotime' => ['int|false', 'time'=>'string', 'now='=>'int'],
1209012090
'strtoupper' => ['string', 'str'=>'string'],
1209112091
'strtr' => ['string', 'str'=>'string', 'from'=>'string', 'to'=>'string'],

src/Php/PhpVersion.php

+5
Original file line numberDiff line numberDiff line change
@@ -348,4 +348,9 @@ public function deprecatesImplicitlyNullableParameterTypes(): bool
348348
return $this->versionId >= 80400;
349349
}
350350

351+
public function substrReturnFalseInsteadOfEmptyString(): bool
352+
{
353+
return $this->versionId < 80000;
354+
}
355+
351356
}

src/PhpDoc/TypeNodeResolver.php

+10-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
use PHPStan\TrinaryLogic;
4747
use PHPStan\Type\Accessory\AccessoryArrayListType;
4848
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
49+
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
4950
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
5051
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
5152
use PHPStan\Type\Accessory\AccessoryNumericStringType;
@@ -216,9 +217,11 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
216217
]);
217218

218219
case 'string':
219-
case 'lowercase-string':
220220
return new StringType();
221221

222+
case 'lowercase-string':
223+
return new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]);
224+
222225
case 'literal-string':
223226
return new IntersectionType([new StringType(), new AccessoryLiteralStringType()]);
224227

@@ -287,10 +290,16 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
287290
]);
288291

289292
case 'non-empty-string':
293+
return new IntersectionType([
294+
new StringType(),
295+
new AccessoryNonEmptyStringType(),
296+
]);
297+
290298
case 'non-empty-lowercase-string':
291299
return new IntersectionType([
292300
new StringType(),
293301
new AccessoryNonEmptyStringType(),
302+
new AccessoryLowercaseStringType(),
294303
]);
295304

296305
case 'truthy-string':

src/Reflection/InitializerExprTypeResolver.php

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use PHPStan\ShouldNotHappenException;
2828
use PHPStan\Type\Accessory\AccessoryArrayListType;
2929
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
30+
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
3031
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
3132
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
3233
use PHPStan\Type\Accessory\AccessoryNumericStringType;
@@ -478,6 +479,9 @@ public function resolveConcatType(Type $left, Type $right): Type
478479
if ($leftStringType->isLiteralString()->and($rightStringType->isLiteralString())->yes()) {
479480
$accessoryTypes[] = new AccessoryLiteralStringType();
480481
}
482+
if ($leftStringType->isLowercaseString()->and($rightStringType->isLowercaseString())->yes()) {
483+
$accessoryTypes[] = new AccessoryLowercaseStringType();
484+
}
481485

482486
$leftNumericStringNonEmpty = TypeCombinator::remove($leftStringType, new ConstantStringType(''));
483487
if ($leftNumericStringNonEmpty->isNumericString()->yes()) {

src/Rules/Api/ApiInstanceofTypeRule.php

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PHPStan\Rules\RuleErrorBuilder;
1212
use PHPStan\Type\Accessory\AccessoryArrayListType;
1313
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
14+
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
1415
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
1516
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
1617
use PHPStan\Type\Accessory\AccessoryNumericStringType;
@@ -84,6 +85,7 @@ final class ApiInstanceofTypeRule implements Rule
8485
AccessoryArrayListType::class => 'Type::isList()',
8586
AccessoryNumericStringType::class => 'Type::isNumericString()',
8687
AccessoryLiteralStringType::class => 'Type::isLiteralString()',
88+
AccessoryLowercaseStringType::class => 'Type::isLowercaseString()',
8789
AccessoryNonEmptyStringType::class => 'Type::isNonEmptyString()',
8890
AccessoryNonFalsyStringType::class => 'Type::isNonFalsyString()',
8991
HasMethodType::class => 'Type::hasMethod()',

src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php

+24-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPStan\Parser\LastConditionVisitor;
88
use PHPStan\Rules\Rule;
99
use PHPStan\Rules\RuleErrorBuilder;
10+
use PHPStan\TrinaryLogic;
1011
use PHPStan\Type\Constant\ConstantBooleanType;
1112
use PHPStan\Type\VerbosityLevel;
1213
use function sprintf;
@@ -61,13 +62,32 @@ public function processNode(Node $node, Scope $scope): array
6162
return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip();
6263
};
6364

65+
$verbosity = VerbosityLevel::value();
66+
if (
67+
(
68+
$leftType->isConstantScalarValue()->yes()
69+
&& $leftType->isString()->yes()
70+
&& $rightType->isConstantScalarValue()->no()
71+
&& $rightType->isString()->yes()
72+
&& TrinaryLogic::extremeIdentity($leftType->isLowercaseString(), $rightType->isLowercaseString())->maybe()
73+
) || (
74+
$rightType->isConstantScalarValue()->yes()
75+
&& $rightType->isString()->yes()
76+
&& $leftType->isConstantScalarValue()->no()
77+
&& $leftType->isString()->yes()
78+
&& TrinaryLogic::extremeIdentity($leftType->isLowercaseString(), $rightType->isLowercaseString())->maybe()
79+
)
80+
) {
81+
$verbosity = VerbosityLevel::precise();
82+
}
83+
6484
if (!$nodeType->getValue()) {
6585
return [
6686
$addTip(RuleErrorBuilder::message(sprintf(
6787
'Strict comparison using %s between %s and %s will always evaluate to false.',
6888
$node->getOperatorSigil(),
69-
$leftType->describe(VerbosityLevel::value()),
70-
$rightType->describe(VerbosityLevel::value()),
89+
$leftType->describe($verbosity),
90+
$rightType->describe($verbosity),
7191
)))->identifier(sprintf('%s.alwaysFalse', $node instanceof Node\Expr\BinaryOp\Identical ? 'identical' : 'notIdentical'))->build(),
7292
];
7393
} elseif ($this->checkAlwaysTrueStrictComparison) {
@@ -79,8 +99,8 @@ public function processNode(Node $node, Scope $scope): array
7999
$errorBuilder = $addTip(RuleErrorBuilder::message(sprintf(
80100
'Strict comparison using %s between %s and %s will always evaluate to true.',
81101
$node->getOperatorSigil(),
82-
$leftType->describe(VerbosityLevel::value()),
83-
$rightType->describe(VerbosityLevel::value()),
102+
$leftType->describe($verbosity),
103+
$rightType->describe($verbosity),
84104
)));
85105
if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) {
86106
$errorBuilder->addTip('Remove remaining cases below this one and this error will disappear too.');

src/Type/Accessory/AccessoryArrayListType.php

+5
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,11 @@ public function isLiteralString(): TrinaryLogic
387387
return TrinaryLogic::createNo();
388388
}
389389

390+
public function isLowercaseString(): TrinaryLogic
391+
{
392+
return TrinaryLogic::createNo();
393+
}
394+
390395
public function isClassStringType(): TrinaryLogic
391396
{
392397
return TrinaryLogic::createNo();

src/Type/Accessory/AccessoryLiteralStringType.php

+5
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,11 @@ public function isLiteralString(): TrinaryLogic
297297
return TrinaryLogic::createYes();
298298
}
299299

300+
public function isLowercaseString(): TrinaryLogic
301+
{
302+
return TrinaryLogic::createMaybe();
303+
}
304+
300305
public function isClassStringType(): TrinaryLogic
301306
{
302307
return TrinaryLogic::createMaybe();

0 commit comments

Comments
 (0)