Skip to content

Commit

Permalink
Introduce lowercase-string
Browse files Browse the repository at this point in the history
Co-authored-by: Ondrej Mirtes <ondrej@mirtes.cz>
  • Loading branch information
VincentLanglet and ondrejmirtes authored Sep 26, 2024
1 parent fd304ca commit c83196b
Show file tree
Hide file tree
Showing 71 changed files with 1,115 additions and 80 deletions.
5 changes: 5 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,11 @@ parameters:
count: 1
path: src/Type/Accessory/AccessoryLiteralStringType.php

-
message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#"
count: 1
path: src/Type/Accessory/AccessoryLowercaseStringType.php

-
message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#"
count: 1
Expand Down
4 changes: 2 additions & 2 deletions resources/functionMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -6361,7 +6361,7 @@
'mb_strripos' => ['0|positive-int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'],
'mb_strrpos' => ['0|positive-int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'],
'mb_strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'part='=>'bool', 'encoding='=>'string'],
'mb_strtolower' => ['string', 'str'=>'string', 'encoding='=>'string'],
'mb_strtolower' => ['lowercase-string', 'str'=>'string', 'encoding='=>'string'],
'mb_strtoupper' => ['string', 'str'=>'string', 'encoding='=>'string'],
'mb_strwidth' => ['0|positive-int', 'str'=>'string', 'encoding='=>'string'],
'mb_substitute_character' => ['mixed', 'substchar='=>'mixed'],
Expand Down Expand Up @@ -12085,7 +12085,7 @@
'strstr' => ['string|false', 'haystack'=>'string', 'needle'=>'mixed', 'before_needle='=>'bool'],
'strtok' => ['non-empty-string|false', 'str'=>'string', 'token'=>'string'],
'strtok\'1' => ['non-empty-string|false', 'token'=>'string'],
'strtolower' => ['string', 'str'=>'string'],
'strtolower' => ['lowercase-string', 'str'=>'string'],
'strtotime' => ['int|false', 'time'=>'string', 'now='=>'int'],
'strtoupper' => ['string', 'str'=>'string'],
'strtr' => ['string', 'str'=>'string', 'from'=>'string', 'to'=>'string'],
Expand Down
5 changes: 5 additions & 0 deletions src/Php/PhpVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,9 @@ public function deprecatesImplicitlyNullableParameterTypes(): bool
return $this->versionId >= 80400;
}

public function substrReturnFalseInsteadOfEmptyString(): bool
{
return $this->versionId < 80000;
}

}
11 changes: 10 additions & 1 deletion src/PhpDoc/TypeNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
use PHPStan\TrinaryLogic;
use PHPStan\Type\Accessory\AccessoryArrayListType;
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
use PHPStan\Type\Accessory\AccessoryNumericStringType;
Expand Down Expand Up @@ -216,9 +217,11 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
]);

case 'string':
case 'lowercase-string':
return new StringType();

case 'lowercase-string':
return new IntersectionType([new StringType(), new AccessoryLowercaseStringType()]);

case 'literal-string':
return new IntersectionType([new StringType(), new AccessoryLiteralStringType()]);

Expand Down Expand Up @@ -287,10 +290,16 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
]);

case 'non-empty-string':
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);

case 'non-empty-lowercase-string':
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
new AccessoryLowercaseStringType(),
]);

case 'truthy-string':
Expand Down
4 changes: 4 additions & 0 deletions src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Accessory\AccessoryArrayListType;
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
use PHPStan\Type\Accessory\AccessoryNumericStringType;
Expand Down Expand Up @@ -478,6 +479,9 @@ public function resolveConcatType(Type $left, Type $right): Type
if ($leftStringType->isLiteralString()->and($rightStringType->isLiteralString())->yes()) {
$accessoryTypes[] = new AccessoryLiteralStringType();
}
if ($leftStringType->isLowercaseString()->and($rightStringType->isLowercaseString())->yes()) {
$accessoryTypes[] = new AccessoryLowercaseStringType();
}

$leftNumericStringNonEmpty = TypeCombinator::remove($leftStringType, new ConstantStringType(''));
if ($leftNumericStringNonEmpty->isNumericString()->yes()) {
Expand Down
2 changes: 2 additions & 0 deletions src/Rules/Api/ApiInstanceofTypeRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Accessory\AccessoryArrayListType;
use PHPStan\Type\Accessory\AccessoryLiteralStringType;
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
use PHPStan\Type\Accessory\AccessoryNumericStringType;
Expand Down Expand Up @@ -84,6 +85,7 @@ final class ApiInstanceofTypeRule implements Rule
AccessoryArrayListType::class => 'Type::isList()',
AccessoryNumericStringType::class => 'Type::isNumericString()',
AccessoryLiteralStringType::class => 'Type::isLiteralString()',
AccessoryLowercaseStringType::class => 'Type::isLowercaseString()',
AccessoryNonEmptyStringType::class => 'Type::isNonEmptyString()',
AccessoryNonFalsyStringType::class => 'Type::isNonFalsyString()',
HasMethodType::class => 'Type::hasMethod()',
Expand Down
28 changes: 24 additions & 4 deletions src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Parser\LastConditionVisitor;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\VerbosityLevel;
use function sprintf;
Expand Down Expand Up @@ -61,13 +62,32 @@ public function processNode(Node $node, Scope $scope): array
return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip();
};

$verbosity = VerbosityLevel::value();
if (
(
$leftType->isConstantScalarValue()->yes()
&& $leftType->isString()->yes()
&& $rightType->isConstantScalarValue()->no()
&& $rightType->isString()->yes()
&& TrinaryLogic::extremeIdentity($leftType->isLowercaseString(), $rightType->isLowercaseString())->maybe()
) || (
$rightType->isConstantScalarValue()->yes()
&& $rightType->isString()->yes()
&& $leftType->isConstantScalarValue()->no()
&& $leftType->isString()->yes()
&& TrinaryLogic::extremeIdentity($leftType->isLowercaseString(), $rightType->isLowercaseString())->maybe()
)
) {
$verbosity = VerbosityLevel::precise();
}

if (!$nodeType->getValue()) {
return [
$addTip(RuleErrorBuilder::message(sprintf(
'Strict comparison using %s between %s and %s will always evaluate to false.',
$node->getOperatorSigil(),
$leftType->describe(VerbosityLevel::value()),
$rightType->describe(VerbosityLevel::value()),
$leftType->describe($verbosity),
$rightType->describe($verbosity),
)))->identifier(sprintf('%s.alwaysFalse', $node instanceof Node\Expr\BinaryOp\Identical ? 'identical' : 'notIdentical'))->build(),
];
} elseif ($this->checkAlwaysTrueStrictComparison) {
Expand All @@ -79,8 +99,8 @@ public function processNode(Node $node, Scope $scope): array
$errorBuilder = $addTip(RuleErrorBuilder::message(sprintf(
'Strict comparison using %s between %s and %s will always evaluate to true.',
$node->getOperatorSigil(),
$leftType->describe(VerbosityLevel::value()),
$rightType->describe(VerbosityLevel::value()),
$leftType->describe($verbosity),
$rightType->describe($verbosity),
)));
if ($isLast === false && !$this->reportAlwaysTrueInLastCondition) {
$errorBuilder->addTip('Remove remaining cases below this one and this error will disappear too.');
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Accessory/AccessoryArrayListType.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,11 @@ public function isLiteralString(): TrinaryLogic
return TrinaryLogic::createNo();
}

public function isLowercaseString(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

public function isClassStringType(): TrinaryLogic
{
return TrinaryLogic::createNo();
Expand Down
5 changes: 5 additions & 0 deletions src/Type/Accessory/AccessoryLiteralStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@ public function isLiteralString(): TrinaryLogic
return TrinaryLogic::createYes();
}

public function isLowercaseString(): TrinaryLogic
{
return TrinaryLogic::createMaybe();
}

public function isClassStringType(): TrinaryLogic
{
return TrinaryLogic::createMaybe();
Expand Down
Loading

0 comments on commit c83196b

Please sign in to comment.