addFile(
'somefile.php',
'analyzeFile('somefile.php', new Context());
}
public function testMethodCallMemoize(): void
{
$this->project_analyzer->getConfig()->memoize_method_calls = true;
$this->addFile(
'somefile.php',
'getFoo()) {
if ($a->getFoo()->getBar()) {
$a->getFoo()->getBar()->bat();
}
}',
);
$this->analyzeFile('somefile.php', new Context());
}
public function testPropertyMethodCallMemoize(): void
{
$this->project_analyzer->getConfig()->memoize_method_calls = true;
$this->addFile(
'somefile.php',
'bar = $bar;
}
public function getBar(): ?string {
return $this->bar;
}
}
function doSomething(Foo $foo): string {
if ($foo->getBar() !== null){
return $foo->getBar();
}
return "hello";
}',
);
$this->analyzeFile('somefile.php', new Context());
}
public function testPropertyMethodCallMutationFreeMemoize(): void
{
$this->project_analyzer->getConfig()->memoize_method_calls = true;
$this->addFile(
'somefile.php',
'bar = $bar;
}
/**
* @psalm-mutation-free
*/
public function getBar(): ?string {
return $this->bar;
}
}
function doSomething(Foo $foo): string {
if ($foo->getBar() !== null){
return $foo->getBar();
}
return "hello";
}',
);
$this->analyzeFile('somefile.php', new Context());
}
public function testUnchainedMethodCallMemoize(): void
{
$this->project_analyzer->getConfig()->memoize_method_calls = true;
$this->addFile(
'somefile.php',
'int = 1;
}
final public function getInt(): ?int {
return $this->int;
}
}
function printInt(int $int): void {
echo $int;
}
$obj = new SomeClass();
if ($obj->getInt()) {
printInt($obj->getInt());
}',
);
$this->analyzeFile('somefile.php', new Context());
}
public function testUnchainedMutationFreeMethodCallMemoize(): void
{
$this->project_analyzer->getConfig()->memoize_method_calls = true;
$this->addFile(
'somefile.php',
'int = 1;
}
/**
* @psalm-mutation-free
*/
public function getInt(): ?int {
return $this->int;
}
}
function printInt(int $int): void {
echo $int;
}
$obj = new SomeClass();
if ($obj->getInt()) {
printInt($obj->getInt());
}',
);
$this->analyzeFile('somefile.php', new Context());
}
public function providerValidCodeParse(): iterable
{
return [
'notInCallMapTest' => [
'code' => ' [
'code' => ' [
'code' => 'barBar();',
],
'staticInvocation' => [
'code' => ' [
'code' => ' [
'code' => ' [
'code' => 'sub(new DateInterval("P1D"));
$b = (new DateTimeImmutable())->modify("+3 hours");',
'assertions' => [
'$yesterday' => 'MyDate',
'$b' => 'DateTimeImmutable',
],
],
'magicCall' => [
'code' => 'bar();',
'assertions' => [
'$s' => 'string',
],
],
'canBeCalledOnMagic' => [
'code' => 'maybeUndefinedMethod();',
'assertions' => [],
'ignored_issues' => ['PossiblyUndefinedMethod'],
],
'canBeCalledOnMagicWithMethod' => [
'code' => 'bar();',
'assertions' => [],
],
'invokeCorrectType' => [
'code' => ' [
'code' => 'createElement("foo");
if ($node instanceof DOMElement) {
$newnode = $doc->appendChild($node);
$newnode->setAttribute("bar", "baz");
}',
],
'nonStaticSelfCall' => [
'code' => 'call());',
],
'simpleXml' => [
'code' => '");
$a = $xml->asXML();
$b = $xml->asXML("foo.xml");',
'assertions' => [
'$a' => 'false|string',
'$b' => 'bool',
],
],
'datetimeformatNotFalse' => [
'code' => 'format($format);
if (false !== $formatted) {}
function takesString(string $s) : void {}
takesString($formatted);',
'assertions' => [],
'ignored_issues' => [],
'php_version' => '7.4',
],
'domElement' => [
'code' => 'getElementsByTagName("bar");
$b = $a->item(0);
if (!$b) {
return null;
}
return $b->getAttribute("bat");
}',
],
'domElementIteratorOrEmptyArray' => [
'code' => 'loadXML($XML);
$elements = rand(0, 1) ? $dom->getElementsByTagName("bar") : [];
foreach ($elements as $element) {
$element->getElementsByTagName("bat");
}
}',
],
'reflectionParameter' => [
'code' => 'getType();
if ($type === null) {
return "mixed";
}
if ($type instanceof ReflectionUnionType) {
return "union";
}
if ($type instanceof ReflectionNamedType) {
return $type->getName();
}
throw new RuntimeException("unexpected type");
}',
'assertions' => [],
'ignored_issues' => [],
'php_version' => '8.0',
],
'PDOMethod' => [
'code' => 'sqliteCreateFunction("md5rev", "md5_and_reverse", 1);',
],
'dontConvertedMaybeMixedAfterCall' => [
'code' => ' $b
*/
function foo(array $a, array $b) : void {
$c = array_merge($b, $a);
foreach ($c as $d) {
$d->foo();
if ($d instanceof B) {}
}
}',
'assertions' => [],
'ignored_issues' => ['MixedAssignment', 'MixedMethodCall'],
],
'methodResolution' => [
'code' => 'getId());
(is_object($a) && method_exists($a, "getS")) ? (string)$a->GETS() : "";
return $user->getId();
}',
],
'defineVariableCreatedInArgToMixed' => [
'code' => 'foo($b = (int) "5")) {
echo $b;
}
}',
'assertions' => [],
'ignored_issues' => ['MixedMethodCall', 'MissingParamType'],
],
'staticCallAfterMethodExists' => [
'code' => ' [
'code' => 'bar();',
],
'pdoStatementSetFetchMode' => [
'code' => 'setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$stmt = $db->prepare("select \"a\" as a");
$stmt->setFetchMode(PDO::FETCH_CLASS, A::class);
$stmt2 = $db->prepare("select \"a\" as a");
$stmt2->setFetchMode(PDO::FETCH_ASSOC);
$stmt3 = $db->prepare("select \"a\" as a");
$stmt3->setFetchMode(PDO::ATTR_DEFAULT_FETCH_MODE);
$stmt->execute();
$stmt2->execute();
/** @psalm-suppress MixedAssignment */
$a = $stmt->fetch();
$b = $stmt->fetchAll();
$c = $stmt->fetch(PDO::FETCH_CLASS);
$d = $stmt->fetchAll(PDO::FETCH_CLASS);
$e = $stmt->fetchAll(PDO::FETCH_CLASS, B::class);
$f = $stmt->fetch(PDO::FETCH_ASSOC);
$g = $stmt->fetchAll(PDO::FETCH_ASSOC);
/** @psalm-suppress MixedAssignment */
$h = $stmt2->fetch();
$i = $stmt2->fetchAll();
$j = $stmt2->fetch(PDO::FETCH_BOTH);
$k = $stmt2->fetchAll(PDO::FETCH_BOTH);
/** @psalm-suppress MixedAssignment */
$l = $stmt3->fetch();',
'assertions' => [
'$a' => 'mixed',
'$b' => 'array|false',
'$c' => 'false|object',
'$d' => 'list