markTestSkipped('Cannot run test, base class "SoapClient" does not exist!'); return; } $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new \Psalm\Context()); } /** * @return void */ public function testMethodCallMemoize() { $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 \Psalm\Context()); } /** * @return void */ public function testPropertyMethodCallMemoize() { $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 \Psalm\Context()); } /** * @return void */ public function testUnchainedMethodCallMemoize() { $this->project_analyzer->getConfig()->memoize_method_calls = true; $this->addFile( 'somefile.php', 'int = 1; } 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 \Psalm\Context()); } /** * @return iterable,error_levels?:string[]}> */ public function providerValidCodeParse() { return [ 'notInCallMapTest' => [ ' [ ' [ 'barBar();', ], 'staticInvocation' => [ ' [ ' [ ' [ 'sub(new DateInterval("P1D")); $b = (new DateTimeImmutable())->modify("+3 hours");', 'assertions' => [ '$yesterday' => 'MyDate|false', '$b' => 'DateTimeImmutable', ], ], 'magicCall' => [ 'bar();', [ '$s' => 'string', ] ], 'canBeCalledOnMagic' => [ 'maybeUndefinedMethod();', 'assertions' => [], 'error_levels' => ['PossiblyUndefinedMethod'], ], 'canBeCalledOnMagicWithMethod' => [ 'bar();', 'assertions' => [], ], 'invokeCorrectType' => [ ' [ 'createElement("foo"); if ($node instanceof DOMElement) { $newnode = $doc->appendChild($node); $newnode->setAttribute("bar", "baz"); }', ], 'nonStaticSelfCall' => [ 'call());', ], 'simpleXml' => [ '"); $a = $xml->asXML(); $b = $xml->asXML("foo.xml");', 'assertions' => [ '$a' => 'false|string', '$b' => 'bool', ], ], 'datetimeformatNotFalse' => [ 'format($format); if (false !== $formatted) {} function takesString(string $s) : void {} takesString($formatted);', ], 'domElement' => [ 'getElementsByTagName("bar"); $b = $a->item(0); if (!$b) { return null; } return $b->getAttribute("bat"); }', ], 'domElementIteratorOrEmptyArray' => [ 'loadXML($XML); $elements = rand(0, 1) ? $dom->getElementsByTagName("bar") : []; foreach ($elements as $element) { $element->getElementsByTagName("bat"); } }', ], 'reflectionParameter' => [ 'getType(); if ($type === null) { return "mixed"; } return $type->getName(); }', ], 'PDOMethod' => [ 'sqliteCreateFunction("md5rev", "md5_and_reverse", 1);', ], 'dontConvertedMaybeMixedAfterCall' => [ ' $b */ function foo(array $a, array $b) : void { $c = array_merge($b, $a); foreach ($c as $d) { $d->foo(); if ($d instanceof B) {} } }', [], 'error_levels' => ['MixedAssignment', 'MixedMethodCall'], ], 'methodResolution' => [ 'getId()); (is_object($a) && method_exists($a, "getS")) ? (string)$a->getS() : ""; return $user->getId(); }', ], 'defineVariableCreatedInArgToMixed' => [ 'foo($b = (int) 5)) { echo $b; } }', [], 'error_levels' => ['MixedMethodCall', 'MissingParamType'], ], 'staticCallAfterMethodExists' => [ ' [ 'bar();', ], 'pdoStatementSetFetchMode' => [ 'setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $stmt = $db->prepare("select \"a\" as a"); $stmt->setFetchMode(PDO::FETCH_CLASS, A::class); $stmt->execute(); /** @psalm-suppress MixedAssignment */ $a = $stmt->fetch();', ], 'datePeriodConstructor' => [ ' [ 'bar(); } }' ], 'callMethodAfterCheckingExistenceInClosure' => [ 'bar(); })(); } }' ], 'callManyMethodsAfterCheckingExistence' => [ 'foo(); $object->bar(); }' ], 'callManyMethodsAfterCheckingExistenceChained' => [ 'foo(); $object->bar(); } }' ], 'callManyMethodsOnKnownObjectAfterCheckingExistenceChained' => [ 'foo(); $object->bar(); } }' ], 'preserveMethodExistsType' => [ ' [ ' $foo */ function foo(string $foo): string { if (!method_exists($foo, "something")) { return ""; } return $foo; }' ], 'pdoStatementFetchAssoc' => [ '|false */ function fetch_assoc() { $p = new PDO("sqlite::memory:"); $sth = $p->prepare("SELECT 1"); $sth->execute(); return $sth->fetch(PDO::FETCH_ASSOC); }' ], 'pdoStatementFetchBoth' => [ '|false */ function fetch_both() { $p = new PDO("sqlite::memory:"); $sth = $p->prepare("SELECT 1"); $sth->execute(); return $sth->fetch(PDO::FETCH_BOTH); }' ], 'pdoStatementFetchBound' => [ 'prepare("SELECT 1"); $sth->execute(); return $sth->fetch(PDO::FETCH_BOUND); }' ], 'pdoStatementFetchClass' => [ 'prepare("SELECT 1"); $sth->execute(); return $sth->fetch(PDO::FETCH_CLASS); }' ], 'pdoStatementFetchLazy' => [ 'prepare("SELECT 1"); $sth->execute(); return $sth->fetch(PDO::FETCH_LAZY); }' ], 'pdoStatementFetchNamed' => [ '>|false */ function fetch_named() { $p = new PDO("sqlite::memory:"); $sth = $p->prepare("SELECT 1"); $sth->execute(); return $sth->fetch(PDO::FETCH_NAMED); }' ], 'pdoStatementFetchNum' => [ '|false */ function fetch_named() { $p = new PDO("sqlite::memory:"); $sth = $p->prepare("SELECT 1"); $sth->execute(); return $sth->fetch(PDO::FETCH_NUM); }' ], 'pdoStatementFetchObj' => [ 'prepare("SELECT 1"); $sth->execute(); return $sth->fetch(PDO::FETCH_OBJ); }' ], 'dateTimeSecondArg' => [ 'format("Y-m-d H:i:sP") . "\n";' ], 'noCrashOnGetClassMethodCallWithNull' => [ ' [ 'passedByRef($this->b); } }', ], 'maybeNotTooManyArgumentsToInstance' => [ 'fooFoo(5, "dfd");', ], 'interfaceMethodCallCheck' => [ 'foo(""); } } takesWithoutArguments(new C);' ], 'getterTypeInferring' => [ 'a; } function takesNullOrA(?A $a) : void {} } $a = new A(); $a->a = 1; echo $a->getA() + 2; $a->a = "string"; echo strlen($a->getA()); $a->a = null; $a->takesNullOrA($a->getA()); ' ], 'getterAutomagicAssertion' => [ 'a; } } $a = new A(); if ($a->getA()) { echo strlen($a->getA()); }' ], 'ignorePossiblyNull' => [ 'getType(); } /** * @psalm-ignore-nullable-return */ public function getType() : ?string { return $this->type; } }' ], 'abstractMethodExistsOnChild' => [ 'createFoo(); } }', [], [], '7.4' ], ]; } /** * @return iterable */ public function providerInvalidCodeParse() { return [ 'staticInvocation' => [ ' 'InvalidStaticInvocation', ], 'parentStaticCall' => [ ' 'InvalidStaticInvocation', ], 'mixedMethodCall' => [ 'barBar();', 'error_message' => 'MixedMethodCall', 'error_levels' => [ 'MissingPropertyType', 'MixedAssignment', ], ], 'invalidMethodCall' => [ 'someMethod();', 'error_message' => 'InvalidMethodCall', ], 'possiblyInvalidMethodCall' => [ 'methodOfA(); } }', 'error_message' => 'PossiblyInvalidMethodCall', ], 'selfNonStaticInvocation' => [ ' 'NonStaticSelfCall', ], 'noParent' => [ ' 'ParentNotFound', ], 'coercedClass' => [ ' 'LessSpecificReturnStatement', 'error_levels' => ['MixedInferredReturnType', 'MixedReturnStatement', 'TypeCoercion', 'MixedMethodCall'], ], 'undefinedVariableStaticCall' => [ ' 'UndefinedGlobalVariable', ], 'staticCallOnString' => [ ' 'MixedAssignment', ], 'possiblyNullFunctionCall' => [ 'foo();', 'error_message' => 'InvalidScope', ], 'possiblyFalseReference' => [ 'bar();', 'error_message' => 'PossiblyFalseReference', ], 'undefinedParentClass' => [ ' 'MissingDependency - src' . DIRECTORY_SEPARATOR . 'somefile.php:7', ], 'variableMethodCallOnArray' => [ '$b();', 'error_message' => 'InvalidMethodCall', ], 'intVarStaticCall' => [ ' 'UndefinedClass', ], 'intVarNewCall' => [ ' 'UndefinedClass', ], 'invokeTypeMismatch' => [ ' 'InvalidScalarArgument', ], 'explicitInvokeTypeMismatch' => [ '__invoke(1);', 'error_message' => 'InvalidScalarArgument', ], 'undefinedMethodPassedAsArg' => [ 'foo(bar());', 'error_message' => 'UndefinedFunction', ], 'noIntersectionMethod' => [ 'zugzug(); }', 'error_message' => 'UndefinedInterfaceMethod - src' . DIRECTORY_SEPARATOR . 'somefile.php:7:29 - Method (B&A)::zugzug does not exist', ], 'noInstanceCallAsStatic' => [ ' 'InvalidStaticInvocation', ], 'noExceptionOnMissingClass' => [ ' */ protected $bar; public function foo(string $s): void { $bar = $this->bar; $bar::baz(); } }', 'error_message' => 'UndefinedClass', ], 'checkMixedMethodCallStaticMethodCallArg' => [ 'bar(B::bat()); }', 'error_message' => 'UndefinedMethod', ], 'complainAboutUndefinedPropertyOnMixedCall' => [ 'bar($this->d); } }', 'error_message' => 'UndefinedThisPropertyFetch', ], 'complainAboutUndefinedPropertyOnMixedCallConcatOp' => [ 'bar("bat" . $this->baz); } }', 'error_message' => 'UndefinedThisPropertyFetch', ], 'alreadyHasmethod' => [ 'foo(); } }', 'error_message' => 'RedundantCondition', ], 'possiblyNullOrMixedArg' => [ 'foo); }', 'error_message' => 'PossiblyNullArgument', ], 'callOnVoid' => [ 'foo()->bar();', 'error_message' => 'NullReference' ], 'dateTimeNullFirstArg' => [ ' 'NullArgument' ], 'noCrashOnGetClassMethodCall' => [ ' 'InvalidStringClass', ], 'preventAbstractMethodCall' => [ ' 'AbstractMethodCall', ], 'tooManyArgumentsToStatic' => [ ' 'TooManyArguments', ], 'tooFewArgumentsToStatic' => [ ' 'TooFewArguments', ], 'tooManyArgumentsToInstance' => [ 'fooFoo(5, "dfd");', 'error_message' => 'TooManyArguments', ], 'tooFewArgumentsToInstance' => [ 'fooFoo();', 'error_message' => 'TooFewArguments', ], 'getterAutomagicOverridden' => [ 'a; } } class AChild extends A { function getA() { return rand(0, 1) ? $this->a : null; } } function foo(A $a) : void { if ($a->getA()) { echo strlen($a->getA()); } } foo(new AChild());', 'error_message' => 'PossiblyNullArgument' ], 'getterAutomagicOverriddenWithAssertion' => [ 'a */ function hasA() { return is_string($this->a); } /** @return string|null */ function getA() { return $this->a; } } class AChild extends A { function getA() { return rand(0, 1) ? $this->a : null; } } function foo(A $a) : void { if ($a->hasA()) { echo strlen($a->getA()); } } foo(new AChild());', 'error_message' => 'PossiblyNullArgument' ], 'checkVariableInUnknownClassConstructor' => [ ' 'PossiblyUndefinedVariable', ], ]; } }