Skip to content

Commit

Permalink
Incremented numeric-string should change to int/float
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm committed Apr 5, 2024
1 parent 23fa0f8 commit 59c3eaa
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 12 deletions.
4 changes: 2 additions & 2 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1528,8 +1528,8 @@ parameters:
path: src/Type/Php/SscanfFunctionDynamicReturnTypeExtension.php

-
message: "#^Casting to string something that's already string\\.$#"
count: 1
message: "#^Cannot access offset int\\<0, max\\> on \\(float\\|int\\)\\.$#"
count: 2
path: src/Type/Php/StrIncrementDecrementFunctionReturnTypeExtension.php

-
Expand Down
4 changes: 2 additions & 2 deletions resources/functionMap_php80delta.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
'str_ends_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'],
'str_starts_with' => ['bool', 'haystack'=>'string', 'needle'=>'string'],
'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'],
'stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'],
'stripos' => ['0|positive-int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'],
'stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'string', 'before_needle='=>'bool'],
'strpos' => ['positive-int|0|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int'],
'strrchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string'],
Expand Down Expand Up @@ -241,7 +241,7 @@
'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => ['?string|?false', 'confidential_message'=>'string', 'public_message'=>'string', 'nonce'=>'string', 'key'=>'string'],
'SplFileObject::fgetss' => ['string|false', 'allowable_tags='=>'string'],
'strchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'],
'stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'],
'stripos' => ['0|positive-int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'],
'stristr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int', 'before_needle='=>'bool'],
'strpos' => ['int|false', 'haystack'=>'string', 'needle'=>'string|int', 'offset='=>'int'],
'strrchr' => ['string|false', 'haystack'=>'string', 'needle'=>'string|int'],
Expand Down
22 changes: 19 additions & 3 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
use PHPStan\Type\DynamicReturnTypeExtensionRegistry;
use PHPStan\Type\ErrorType;
use PHPStan\Type\ExpressionTypeResolverExtensionRegistry;
use PHPStan\Type\FloatType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\Generic\GenericObjectType;
Expand Down Expand Up @@ -1461,7 +1462,7 @@ private function resolveType(string $exprString, Expr $node): Type
} elseif ($node instanceof Expr\PreInc || $node instanceof Expr\PreDec) {
$varType = $this->getType($node->var);
$varScalars = $varType->getConstantScalarValues();
$stringType = new StringType();

if (count($varScalars) > 0) {
$newTypes = [];

Expand All @@ -1477,9 +1478,24 @@ private function resolveType(string $exprString, Expr $node): Type
return TypeCombinator::union(...$newTypes);
} elseif ($varType->isString()->yes()) {
if ($varType->isLiteralString()->yes()) {
return new IntersectionType([$stringType, new AccessoryLiteralStringType()]);
return new IntersectionType([
new StringType(),
new AccessoryLiteralStringType(),
]);
}

if ($varType->isNumericString()->yes()) {
return new BenevolentUnionType([
new IntegerType(),
new FloatType(),
]);
}
return $stringType;

return new BenevolentUnionType([
new StringType(),
new IntegerType(),
new FloatType(),
]);
}

if ($node instanceof Expr\PreInc) {
Expand Down
8 changes: 4 additions & 4 deletions tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2956,19 +2956,19 @@ public function dataBinaryOperations(): array
'$string--',
],
[
'string',
'(float|int|string)',
'++$string',
],
[
'string',
'(float|int|string)',
'--$string',
],
[
'string',
'(float|int|string)',
'$incrementedString',
],
[
'string',
'(float|int|string)',
'$decrementedString',
],
[
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/sort.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3312.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5961.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10122.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10189.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10317.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10302-interface-extends.php');
Expand All @@ -1459,6 +1460,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/set-type-type-specifying.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10468.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6613.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10187.php');
}

/**
Expand Down
54 changes: 54 additions & 0 deletions tests/PHPStan/Analyser/data/bug-10122.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Bug10122;

use function PHPStan\Testing\assertType;

function doFoo():void
{
function(string $s) {
assertType('(float|int|string)', ++$s);
};
function(string $s) {
assertType('(float|int|string)', --$s);
};
function(string $s) {
assertType('string', $s++);
assertType('(float|int|string)', $s);
};
function(string $s) {
assertType('string', $s--);
assertType('(float|int|string)', $s);
};

function(float $f) {
assertType('float', ++$f);
};
function(float $f) {
assertType('float', --$f);
};
function(float $f) {
assertType('float', $f++);
};
function(float $f) {
assertType('float', $f--);
};
}

/** @param numeric-string $ns */
function doNumericString(string $ns) {
function() use ($ns) {
assertType('(float|int)', ++$ns);
};
function() use ($ns) {
assertType('(float|int)', --$ns);
};
function() use ($ns) {
assertType('numeric-string', $ns++);
assertType('(float|int)', $ns);
};
function() use ($ns) {
assertType('numeric-string', $ns--);
assertType('(float|int)', $ns);
};
}
14 changes: 14 additions & 0 deletions tests/PHPStan/Analyser/data/bug-10187.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types = 1);

namespace Bug10187;

use function PHPStan\Testing\assertType;

function inc(string $n): string
{
$before = $n;
$after = ++$n;
assertType('array{n: (float|int|string), before: string, after: (float|int|string)}', compact('n', 'before', 'after'));

return (string)$after; // No warnings expected here
}
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/literal-string.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function increment($literalString, string $string)
assertType('literal-string', $literalString);

$string++;
assertType('string', $string);
assertType('(float|int|string)', $string);
}

}

0 comments on commit 59c3eaa

Please sign in to comment.