1
0
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:
Matthew Brown 2019-01-20 10:39:08 -05:00
parent 681391f970
commit 6fe9e525df
4 changed files with 124 additions and 11 deletions

View File

@ -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 . '>');
}
/**

View File

@ -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;

View File

@ -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;
}
/**

View File

@ -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();
}
}'
],
];
}