Skip to content

Commit

Permalink
Fix literal string keys int not handled as int as PHP does
Browse files Browse the repository at this point in the history
Fix #8680
See also #9295
  • Loading branch information
kkmuffme committed Dec 12, 2023
1 parent 7b28fa4 commit 77767aa
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/Psalm/Internal/Type/TypeParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
use Psalm\Type\Atomic\TLiteralClassString;
use Psalm\Type\Atomic\TLiteralFloat;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralString;
use Psalm\Type\Atomic\TMixed;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TNever;
Expand Down Expand Up @@ -99,6 +100,8 @@
use function strtr;
use function substr;

use const FILTER_VALIDATE_INT;

/**
* @psalm-suppress InaccessibleProperty Allowed during construction
* @internal
Expand Down Expand Up @@ -646,6 +649,17 @@ private static function getTypeFromGenericTree(
}

foreach ($generic_params[0]->getAtomicTypes() as $key => $atomic_type) {
// PHP 8 values with whitespace after number are counted as numeric and filter_var treats them as such too
if ($atomic_type instanceof TLiteralString
&& trim($atomic_type->value) === $atomic_type->value
&& ($string_to_int = filter_var($atomic_type->value, FILTER_VALIDATE_INT)) !== false
) {
$builder = $generic_params[0]->getBuilder();
$builder->removeType($key);
$generic_params[0] = $builder->addType(new TLiteralInt($string_to_int, $from_docblock))->freeze();
continue;
}

if ($atomic_type instanceof TInt
|| $atomic_type instanceof TString
|| $atomic_type instanceof TArrayKey
Expand Down Expand Up @@ -702,6 +716,17 @@ private static function getTypeFromGenericTree(
}

foreach ($generic_params[0]->getAtomicTypes() as $key => $atomic_type) {
// PHP 8 values with whitespace after number are counted as numeric and filter_var treats them as such too
if ($atomic_type instanceof TLiteralString
&& trim($atomic_type->value) === $atomic_type->value
&& ($string_to_int = filter_var($atomic_type->value, FILTER_VALIDATE_INT)) !== false
) {
$builder = $generic_params[0]->getBuilder();
$builder->removeType($key);
$generic_params[0] = $builder->addType(new TLiteralInt($string_to_int, $from_docblock))->freeze();
continue;
}

if ($atomic_type instanceof TInt
|| $atomic_type instanceof TString
|| $atomic_type instanceof TArrayKey
Expand Down
23 changes: 23 additions & 0 deletions tests/ArrayAssignmentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2115,6 +2115,29 @@ function getQueryParams(): array
return $queryParams;
}',
],
'stringIntKeys' => [
'code' => '<?php
/**
* @param array<15|"17"|"hello", string> $arg
* @return bool
*/
function foo($arg) {
foreach ($arg as $k => $v) {
if ( $k === 15 ) {
return true;
}
if ( $k === 17 ) {
return false;
}
}
return true;
}
$x = ["15" => "a", 17 => "b"];
foo($x);',
],
];
}

Expand Down
55 changes: 55 additions & 0 deletions tests/ArrayKeysTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,33 @@ function getKey() {
}
',
],
'literalStringAsIntArrayKey' => [
'code' => '<?php
class a {
private const REDIRECTS = [
"a" => [
"from" => "79268724911",
"to" => "74950235931",
],
"b" => [
"from" => "79313044964",
"to" => "78124169167",
],
];
private const SIP_FORMAT = "sip:%s@voip.test.com:9090";
/** @return array<int, string> */
public function test(): array {
$redirects = [];
foreach (self::REDIRECTS as $redirect) {
$redirects[$redirect["from"]] = sprintf(self::SIP_FORMAT, $redirect["to"]);
}
return $redirects;
}
}',
],
];
}

Expand Down Expand Up @@ -126,6 +153,34 @@ function getKeys() {
',
'error_message' => 'InvalidReturnStatement',
],
'literalStringAsIntArrayKey' => [
'code' => '<?php
class a {
private const REDIRECTS = [
"a" => [
"from" => "79268724911",
"to" => "74950235931",
],
"b" => [
"from" => "79313044964",
"to" => "78124169167",
],
];
private const SIP_FORMAT = "sip:%s@voip.test.com:9090";
/** @return array<string, string> */
public function test(): array {
$redirects = [];
foreach (self::REDIRECTS as $redirect) {
$redirects[$redirect["from"]] = sprintf(self::SIP_FORMAT, $redirect["to"]);
}
return $redirects;
}
}',
'error_message' => 'InvalidReturnStatement',
],
];
}
}

0 comments on commit 77767aa

Please sign in to comment.