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

Fix #1167 - don’t worry about AbstractInstantiation when it could be a descendant

This commit is contained in:
Matthew Brown 2019-01-04 12:28:00 -05:00
parent 602d8a5ade
commit 87052537cf
7 changed files with 109 additions and 11 deletions

View File

@ -43,7 +43,7 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
$codebase = $statements_analyzer->getCodebase();
$config = $codebase->config;
$late_static = false;
$can_extend = false;
if ($stmt->class instanceof PhpParser\Node\Name) {
if (!in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)) {
@ -77,7 +77,7 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
case 'static':
// @todo maybe we can do better here
$fq_class_name = $context->self;
$late_static = true;
$can_extend = true;
break;
}
}
@ -146,6 +146,10 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
? $lhs_type_part->extends
: $lhs_type_part->value;
if ($lhs_type_part instanceof Type\Atomic\TClassString) {
$can_extend = true;
}
if ($class_name === 'object') {
if (IssueBuffer::accepts(
new MixedMethodCall(
@ -278,7 +282,7 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
$storage = $codebase->classlike_storage_provider->get($fq_class_name);
// if we're not calling this constructor via new static()
if ($storage->abstract && !$late_static) {
if ($storage->abstract && !$can_extend) {
if (IssueBuffer::accepts(
new AbstractInstantiation(
'Unable to instantiate a abstract class ' . $fq_class_name,

View File

@ -140,7 +140,7 @@ class ConstFetchAnalyzer
}
if ($stmt->name instanceof PhpParser\Node\Identifier && $stmt->name->name === 'class') {
$stmt->inferredType = Type::getClassString($fq_class_name);
$stmt->inferredType = Type::getLiteralClassString($fq_class_name);
return null;
}

View File

@ -35,6 +35,7 @@ use Psalm\Issue\InvalidCast;
use Psalm\Issue\InvalidClone;
use Psalm\Issue\InvalidDocblock;
use Psalm\Issue\PossiblyUndefinedVariable;
use Psalm\Issue\UndefinedConstant;
use Psalm\Issue\UndefinedVariable;
use Psalm\Issue\UnrecognizedExpression;
use Psalm\IssueBuffer;
@ -128,7 +129,22 @@ class ExpressionAnalyzer
break;
case '__class__':
$stmt->inferredType = Type::getClassString($context->self);
if (!$context->self) {
if (IssueBuffer::accepts(
new UndefinedConstant(
'Cannot get __class__ outside a class',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
$stmt->inferredType = Type::getClassString();
break;
}
$stmt->inferredType = Type::getLiteralClassString($context->self);
break;
case '__file__':

View File

@ -1022,7 +1022,7 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
}
if (strtolower($stmt->name->name) === 'class') {
return Type::getClassString($const_fq_class_name);
return Type::getLiteralClassString($const_fq_class_name);
}
if ($existing_class_constants === null) {

View File

@ -800,17 +800,23 @@ abstract class Type
return new Union([$type]);
}
/**
* @param string $extends
*
* @return Type\Union
*/
public static function getClassString($extends = 'object')
{
return new Union([new TClassString($extends)]);
}
/**
* @param string $class_type
*
* @return Type\Union
*/
public static function getClassString($class_type = null)
public static function getLiteralClassString($class_type)
{
if (!$class_type) {
return new Union([new TClassString()]);
}
$type = new TLiteralClassString($class_type);
return new Union([$type]);

View File

@ -22,6 +22,7 @@ use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TGenericParam;
use Psalm\Type\Atomic\THtmlEscapedString;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TLiteralClassString;
use Psalm\Type\Atomic\TMixed;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TNever;
@ -270,6 +271,32 @@ abstract class Atomic
}
}
if ($this instanceof TLiteralClassString) {
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$source,
$this->value,
$code_location,
$suppressed_issues,
$inferred
) === false
) {
return false;
}
}
if ($this instanceof TClassString && $this->extends !== 'object') {
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$source,
$this->extends,
$code_location,
$suppressed_issues,
$inferred
) === false
) {
return false;
}
}
if ($this instanceof TScalarClassConstant) {
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$source,
@ -342,6 +369,30 @@ abstract class Atomic
}
}
if ($this instanceof TClassString && $this->extends !== 'object') {
$codebase->scanner->queueClassLikeForScanning(
$this->extends,
$file_storage ? $file_storage->file_path : null,
false,
!$this->from_docblock
);
if ($file_storage) {
$file_storage->referenced_classlikes[] = $this->extends;
}
}
if ($this instanceof TLiteralClassString) {
$codebase->scanner->queueClassLikeForScanning(
$this->value,
$file_storage ? $file_storage->file_path : null,
false,
!$this->from_docblock
);
if ($file_storage) {
$file_storage->referenced_classlikes[] = $this->value;
}
}
if ($this instanceof Type\Atomic\TArray || $this instanceof Type\Atomic\TGenericObject) {
foreach ($this->type_params as $type_param) {
$type_param->queueClassLikesForScanning(

View File

@ -320,6 +320,16 @@ class ClassTest extends TestCase
'UndefinedClass'
],
],
'allowAbstractInstantiationOnPossibleChild' => [
'<?php
abstract class A {}
function foo(string $a_class) : void {
if (is_a($a_class, A::class, true)) {
new $a_class();
}
}',
],
];
}
@ -508,6 +518,17 @@ class ClassTest extends TestCase
class A extends A {}',
'error_message' => 'CircularReference',
],
'preventAbstractInstantiationDefiniteClasss' => [
'<?php
abstract class A {}
function foo(string $a_class) : void {
if ($a_class === A::class) {
new $a_class();
}
}',
'error_message' => 'AbstractInstantiation',
],
];
}
}