mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Fix #1219 - allow generation of intersections inside class-string
This commit is contained in:
parent
681391f970
commit
6fe9e525df
@ -30,7 +30,7 @@ class TClassString extends TString
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'class-string' . ($this->as === 'object' ? '' : '<' . $this->as . '>');
|
||||
return 'class-string' . ($this->as === 'object' ? '' : '<' . $this->as_type . '>');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,6 +23,7 @@ use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TArrayKey;
|
||||
use Psalm\Type\Atomic\TBool;
|
||||
use Psalm\Type\Atomic\TCallable;
|
||||
use Psalm\Type\Atomic\TClassString;
|
||||
use Psalm\Type\Atomic\TEmpty;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TGenericParam;
|
||||
@ -561,7 +562,7 @@ class Reconciler
|
||||
|| $type instanceof TObject
|
||||
|| $type instanceof TResource
|
||||
|| $type instanceof TCallable
|
||||
|| $type instanceof Type\Atomic\TClassString
|
||||
|| $type instanceof TClassString
|
||||
) {
|
||||
$did_remove_type = true;
|
||||
|
||||
@ -895,7 +896,82 @@ class Reconciler
|
||||
|
||||
$new_type = Type::getMixed();
|
||||
} else {
|
||||
$new_type_has_interface_string = $codebase->interfaceExists($new_var_type);
|
||||
|
||||
$old_type_has_interface_string = false;
|
||||
|
||||
foreach ($existing_var_type->getTypes() as $existing_type_part) {
|
||||
if ($existing_type_part instanceof TClassString
|
||||
&& $existing_type_part->as_type
|
||||
&& $codebase->interfaceExists($existing_type_part->as_type->value)
|
||||
) {
|
||||
$old_type_has_interface_string = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$new_type = Type::getClassString($new_var_type);
|
||||
|
||||
if (($new_type_has_interface_string
|
||||
&& !TypeAnalyzer::isContainedBy(
|
||||
$codebase,
|
||||
$existing_var_type,
|
||||
$new_type
|
||||
)
|
||||
)
|
||||
|| ($old_type_has_interface_string
|
||||
&& !TypeAnalyzer::isContainedBy(
|
||||
$codebase,
|
||||
$new_type,
|
||||
$existing_var_type
|
||||
)
|
||||
)
|
||||
) {
|
||||
$new_type_part = Atomic::create($new_var_type);
|
||||
|
||||
$acceptable_atomic_types = [];
|
||||
|
||||
foreach ($existing_var_type->getTypes() as $existing_var_type_part) {
|
||||
if (!$new_type_part instanceof TNamedObject
|
||||
|| !$existing_var_type_part instanceof TClassString
|
||||
) {
|
||||
$acceptable_atomic_types = [];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$existing_var_type_part->as_type instanceof TNamedObject) {
|
||||
$acceptable_atomic_types = [];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$existing_var_type_part = $existing_var_type_part->as_type;
|
||||
|
||||
if (TypeAnalyzer::isAtomicContainedBy(
|
||||
$codebase,
|
||||
$existing_var_type_part,
|
||||
$new_type_part
|
||||
)) {
|
||||
$acceptable_atomic_types[] = clone $existing_var_type_part;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($codebase->classExists($existing_var_type_part->value)
|
||||
|| $codebase->interfaceExists($existing_var_type_part->value)
|
||||
) {
|
||||
$existing_var_type_part = clone $existing_var_type_part;
|
||||
$existing_var_type_part->addIntersectionType($new_type_part);
|
||||
$acceptable_atomic_types[] = $existing_var_type_part;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($acceptable_atomic_types) === 1) {
|
||||
return new Type\Union([
|
||||
new TClassString('object', $acceptable_atomic_types[0])
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$new_type = Type::getMixed();
|
||||
@ -976,13 +1052,7 @@ class Reconciler
|
||||
if (TypeAnalyzer::isAtomicContainedBy(
|
||||
$codebase,
|
||||
$existing_var_type_part,
|
||||
$new_type_part,
|
||||
false,
|
||||
false,
|
||||
$scalar_type_match_found,
|
||||
$type_coerced,
|
||||
$type_coerced_from_mixed,
|
||||
$atomic_to_string_cast
|
||||
$new_type_part
|
||||
)) {
|
||||
$acceptable_atomic_types[] = clone $existing_var_type_part;
|
||||
continue;
|
||||
|
@ -90,6 +90,11 @@ class Union
|
||||
*/
|
||||
private $literal_string_types = [];
|
||||
|
||||
/**
|
||||
* @var array<string, Type\Atomic\TClassString>
|
||||
*/
|
||||
private $typed_class_strings = [];
|
||||
|
||||
/**
|
||||
* @var array<string, TLiteralInt>
|
||||
*/
|
||||
@ -129,6 +134,8 @@ class Union
|
||||
$this->literal_string_types[$key] = $type;
|
||||
} elseif ($type instanceof TLiteralFloat) {
|
||||
$this->literal_float_types[$key] = $type;
|
||||
} elseif ($type instanceof Type\Atomic\TClassString && $type->as_type) {
|
||||
$this->typed_class_strings[$key] = $type;
|
||||
}
|
||||
|
||||
$from_docblock = $from_docblock || $type->from_docblock;
|
||||
@ -163,6 +170,14 @@ class Union
|
||||
unset($this->literal_string_types[$key]);
|
||||
unset($this->types[$key]);
|
||||
}
|
||||
if (!$type instanceof Type\Atomic\TClassString
|
||||
|| !$type->as_type
|
||||
) {
|
||||
foreach ($this->typed_class_strings as $key => $_) {
|
||||
unset($this->typed_class_strings[$key]);
|
||||
unset($this->types[$key]);
|
||||
}
|
||||
}
|
||||
} elseif ($type instanceof TInt && $this->literal_int_types) {
|
||||
foreach ($this->literal_int_types as $key => $_) {
|
||||
unset($this->literal_int_types[$key]);
|
||||
@ -183,6 +198,7 @@ class Union
|
||||
$this->literal_string_types = [];
|
||||
$this->literal_int_types = [];
|
||||
$this->literal_float_types = [];
|
||||
$this->typed_class_strings = [];
|
||||
|
||||
foreach ($this->types as $key => &$type) {
|
||||
$type = clone $type;
|
||||
@ -193,6 +209,8 @@ class Union
|
||||
$this->literal_string_types[$key] = $type;
|
||||
} elseif ($type instanceof TLiteralFloat) {
|
||||
$this->literal_float_types[$key] = $type;
|
||||
} elseif ($type instanceof Type\Atomic\TClassString && $type->as_type) {
|
||||
$this->typed_class_strings[$key] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -535,7 +553,8 @@ class Union
|
||||
|| isset($this->types['class-string'])
|
||||
|| isset($this->types['numeric-string'])
|
||||
|| isset($this->types['array-key'])
|
||||
|| $this->literal_string_types;
|
||||
|| $this->literal_string_types
|
||||
|| $this->typed_class_strings;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -604,6 +623,7 @@ class Union
|
||||
return isset($this->types['int'])
|
||||
|| isset($this->types['float'])
|
||||
|| isset($this->types['string'])
|
||||
|| isset($this->types['class-string'])
|
||||
|| isset($this->types['bool'])
|
||||
|| isset($this->types['false'])
|
||||
|| isset($this->types['true'])
|
||||
@ -611,7 +631,8 @@ class Union
|
||||
|| isset($this->types['numeric-string'])
|
||||
|| $this->literal_int_types
|
||||
|| $this->literal_float_types
|
||||
|| $this->literal_string_types;
|
||||
|| $this->literal_string_types
|
||||
|| $this->typed_class_strings;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -452,6 +452,28 @@ class ClassStringTest extends TestCase
|
||||
$className::two();
|
||||
}'
|
||||
],
|
||||
'implicitIntersectionClassString' => [
|
||||
'<?php
|
||||
interface Foo {
|
||||
public static function one() : bool;
|
||||
};
|
||||
|
||||
interface Bar {
|
||||
public static function two() : bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<Bar> $className
|
||||
*/
|
||||
function foo($className) : void {
|
||||
$className::two();
|
||||
|
||||
if (is_subclass_of($className, Foo::class, true)) {
|
||||
$className::one();
|
||||
$className::two();
|
||||
}
|
||||
}'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user