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:
parent
602d8a5ade
commit
87052537cf
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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__':
|
||||
|
@ -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) {
|
||||
|
@ -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]);
|
||||
|
@ -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(
|
||||
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user