1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Tighten up rules around instantiation a bit more

This commit is contained in:
Brown 2020-08-06 10:18:55 -04:00
parent ccd4eaa8e7
commit afce2dc66f
13 changed files with 142 additions and 3 deletions

View File

@ -18,6 +18,7 @@ use function count;
/**
* @internal
* @psalm-consistent-constructor
*/
class FileAnalyzer extends SourceAnalyzer implements StatementsSource
{

View File

@ -134,10 +134,22 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
$lhs_type_part->param_name,
$lhs_type_part->as_type
? new Type\Union([$lhs_type_part->as_type])
: Type::parseString($lhs_type_part->as),
: Type::getObject(),
$lhs_type_part->defining_class
);
if (!$lhs_type_part->as_type) {
if (IssueBuffer::accepts(
new MixedMethodCall(
'Cannot call constructor on an unknown class',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
if ($new_type) {
$new_type = Type::combineUnionTypes(
$new_type,
@ -146,6 +158,28 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
} else {
$new_type = new Type\Union([$new_type_part]);
}
if ($lhs_type_part->as_type
&& $codebase->classlikes->classExists($lhs_type_part->as_type->value)
) {
$as_storage = $codebase->classlike_storage_provider->get(
$lhs_type_part->as_type->value
);
if (!$as_storage->preserve_constructor_signature) {
if (IssueBuffer::accepts(
new UnsafeInstantiation(
'Cannot safely instantiate class ' . $lhs_type_part->as_type->value
. ' with "new $class_name" as '
. ' its constructor might change in child classes',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
}
}
if ($lhs_type_part->as_type) {
@ -174,6 +208,28 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
$generated_type = $lhs_type_part->as_type
? clone $lhs_type_part->as_type
: new Type\Atomic\TObject();
if ($lhs_type_part->as_type
&& $codebase->classlikes->classExists($lhs_type_part->as_type->value)
) {
$as_storage = $codebase->classlike_storage_provider->get(
$lhs_type_part->as_type->value
);
if (!$as_storage->preserve_constructor_signature) {
if (IssueBuffer::accepts(
new UnsafeInstantiation(
'Cannot safely instantiate class ' . $lhs_type_part->as_type->value
. ' with "new $class_name" as '
. ' its constructor might change in child classes',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
}
} elseif ($lhs_type_part instanceof Type\Atomic\GetClassT) {
$generated_type = new Type\Atomic\TObject();

View File

@ -12,6 +12,7 @@ use Psalm\Internal\PhpVisitor\ReflectorVisitor;
/**
* @internal
* @psalm-consistent-constructor
*/
class FileScanner implements FileSource
{

View File

@ -620,6 +620,9 @@ class ClassStringTest extends TestCase
],
'createNewObjectFromGetClass' => [
'<?php
/**
* @psalm-consistent-constructor
*/
class Example {
static function staticMethod(): string {
return "";
@ -706,6 +709,9 @@ class ClassStringTest extends TestCase
}
}
/**
* @psalm-consistent-constructor
*/
class A
{
use Factory;
@ -717,6 +723,9 @@ class ClassStringTest extends TestCase
}
}
/**
* @psalm-consistent-constructor
*/
class B
{
use Factory;
@ -730,6 +739,9 @@ class ClassStringTest extends TestCase
],
'staticClassReturn' => [
'<?php
/**
* @psalm-consistent-constructor
*/
class A {
/** @return static */
public static function getInstance() {
@ -740,6 +752,9 @@ class ClassStringTest extends TestCase
],
'getCalledClassIsStaticClass' => [
'<?php
/**
* @psalm-consistent-constructor
*/
class A {
/** @return static */
public function getStatic() {

View File

@ -155,6 +155,9 @@ class ClassTest extends TestCase
],
'instantiateClassAndIsA' => [
'<?php
/**
* @psalm-consistent-constructor
*/
class Foo {
public function bar() : void{}
}
@ -364,6 +367,9 @@ class ClassTest extends TestCase
],
'allowAbstractInstantiationOnPossibleChild' => [
'<?php
/**
* @psalm-consistent-constructor
*/
abstract class A {}
function foo(string $a_class) : void {

View File

@ -20,7 +20,11 @@ class ClassStringMapTest extends TestCase
'<?php
namespace Bar;
/**
* @psalm-consistent-constructor
*/
class Foo {}
class A {
/** @var class-string-map<T as Foo, T> */
public static array $map = [];
@ -44,7 +48,11 @@ class ClassStringMapTest extends TestCase
'<?php
namespace Bar;
/**
* @psalm-consistent-constructor
*/
class Foo {}
class A {
/** @var class-string-map<T as Foo, T> */
public static array $map = [];
@ -77,7 +85,6 @@ class ClassStringMapTest extends TestCase
'<?php
namespace Bar;
class Foo {}
class A {
/** @var class-string-map<T, T> */
public static array $map = [];
@ -96,7 +103,6 @@ class ClassStringMapTest extends TestCase
'<?php
namespace Bar;
class Foo {}
class A {
/** @var class-string-map<T, T> */
public static array $map = [];

View File

@ -2201,6 +2201,7 @@ class ClassTemplateExtendsTest extends TestCase
* @template T as object
* @param class-string<T> $t
* @return I<T>
* @psalm-suppress MixedMethodCall
*/
function f(string $t) {
return new C(new $t);
@ -2959,6 +2960,7 @@ class ClassTemplateExtendsTest extends TestCase
* @template T2
* @param class-string<T2> $t
* @return ?T2
* @psalm-suppress MixedMethodCall
*/
public function get($t) {
return new $t;
@ -2970,6 +2972,7 @@ class ClassTemplateExtendsTest extends TestCase
* @template T3
* @param class-string<T3> $t
* @return ?T3
* @psalm-suppress MixedMethodCall
*/
public function get($t) {
return new $t;

View File

@ -40,6 +40,7 @@ class ClassTemplateTest extends TestCase
/**
* @return T
* @psalm-suppress MixedMethodCall
*/
public function bar() {
$t = $this->T;
@ -98,6 +99,7 @@ class ClassTemplateTest extends TestCase
/**
* @return T
* @psalm-suppress MixedMethodCall
*/
public function bar() {
$t = $this->T;
@ -169,6 +171,7 @@ class ClassTemplateTest extends TestCase
/**
* @return T
* @psalm-suppress MixedMethodCall
*/
public function bar() {
$t = $this->T;
@ -1239,6 +1242,8 @@ class ClassTemplateTest extends TestCase
* @param class-string<Q> $obj2
*
* @return array<I, V|Q>
*
* @psalm-suppress MixedMethodCall
*/
private function appender(string $obj2): array
{

View File

@ -278,6 +278,9 @@ class ConditionalReturnTypeTest extends TestCase
return new Application();
}
/**
* @psalm-suppress MixedMethodCall
*/
return new $className();
}

View File

@ -92,6 +92,7 @@ class FunctionClassStringTemplateTest extends TestCase
* @template T as Exception
* @param T::class $type
* @return T
* @psalm-suppress UnsafeInstantiation
*/
function a(string $type): Exception {
return new $type;
@ -114,6 +115,8 @@ class FunctionClassStringTemplateTest extends TestCase
* @param class-string<T> $foo
*
* @return T
*
* @psalm-suppress MixedMethodCall
*/
function Foo(string $foo) : object {
return new $foo;
@ -123,6 +126,9 @@ class FunctionClassStringTemplateTest extends TestCase
],
'templatedClassStringParamAsClass' => [
'<?php
/**
* @psalm-consistent-constructor
*/
abstract class C {
public function foo() : void{}
}
@ -160,6 +166,9 @@ class FunctionClassStringTemplateTest extends TestCase
],
'templatedClassStringParamAsObject' => [
'<?php
/**
* @psalm-consistent-constructor
*/
abstract class C {
public function foo() : void{}
}
@ -170,6 +179,7 @@ class FunctionClassStringTemplateTest extends TestCase
* @param class-string<T> $c_class
*
* @psalm-return T
* @psalm-suppress MixedMethodCall
*/
public static function get(string $c_class) {
return new $c_class;
@ -186,6 +196,9 @@ class FunctionClassStringTemplateTest extends TestCase
],
'templatedClassStringParamMoreSpecific' => [
'<?php
/**
* @psalm-consistent-constructor
*/
abstract class C {
public function foo() : void{}
}
@ -281,6 +294,7 @@ class FunctionClassStringTemplateTest extends TestCase
* @psalm-template T of object
* @psalm-param T|class-string<T> $someType
* @psalm-return T
* @psalm-suppress MixedMethodCall
*/
function getObject($someType) {
if (is_object($someType)) {
@ -304,6 +318,7 @@ class FunctionClassStringTemplateTest extends TestCase
* @psalm-template T of object
* @psalm-param T|class-string<T> $someType
* @psalm-return T
* @psalm-suppress MixedMethodCall
*/
function getObject($someType) {
if (is_object($someType)) {
@ -333,6 +348,7 @@ class FunctionClassStringTemplateTest extends TestCase
* @param-out array<T> $map
* @param int $id
* @return T
* @psalm-suppress MixedMethodCall
*/
function get(string $className, array &$map, int $id) {
if(!array_key_exists($id, $map)) {
@ -361,6 +377,7 @@ class FunctionClassStringTemplateTest extends TestCase
* @template T as object
* @param T|class-string<T> $s
* @return T
* @psalm-suppress MixedMethodCall
*/
function bar($s) {
if (is_object($s)) {
@ -520,7 +537,14 @@ class FunctionClassStringTemplateTest extends TestCase
],
'templateAsUnionClassStringPassingValidClass' => [
'<?php
/**
* @psalm-consistent-constructor
*/
class A {}
/**
* @psalm-consistent-constructor
*/
class B {}
/**
@ -618,6 +642,9 @@ class FunctionClassStringTemplateTest extends TestCase
],
'templateFromDifferentClassStrings' => [
'<?php
/**
* @psalm-consistent-constructor
*/
class A {}
class B extends A {}
@ -739,8 +766,16 @@ class FunctionClassStringTemplateTest extends TestCase
],
'templateAsUnionClassStringPassingInvalidClass' => [
'<?php
/**
* @psalm-consistent-constructor
*/
class A {}
/**
* @psalm-consistent-constructor
*/
class B {}
class C {}
/**

View File

@ -1222,6 +1222,7 @@ class FunctionTemplateTest extends TestCase
string $className,
Closure $outmaker
) : object {
/** @psalm-suppress MixedMethodCall */
$t = new $className();
$outmaker($t);
return $t;
@ -1247,6 +1248,7 @@ class FunctionTemplateTest extends TestCase
string $className,
callable $outmaker
) : object {
/** @psalm-suppress MixedMethodCall */
$t = new $className();
$outmaker($t);
return $t;
@ -1827,6 +1829,7 @@ class FunctionTemplateTest extends TestCase
string $className,
Closure $outmaker
) : object {
/** @psalm-suppress MixedMethodCall */
$t = new $className();
$outmaker($t);
return $t;
@ -1855,6 +1858,7 @@ class FunctionTemplateTest extends TestCase
string $className,
callable $outmaker
) : object {
/** @psalm-suppress MixedMethodCall */
$t = new $className();
$outmaker($t);
return $t;

View File

@ -682,6 +682,9 @@ class UnusedCodeTest extends TestCase
],
'usedThroughNewClassStringOfBase' => [
'<?php
/**
* @psalm-consistent-constructor
*/
abstract class FooBase {
public final function __construct() {}

View File

@ -881,6 +881,7 @@ class UnusedVariableTest extends TestCase
function bar(string $type) : ArrayObject {
$data = [["foo"], ["bar"]];
/** @psalm-suppress UnsafeInstantiation */
return new $type($data[0]);
}',
],