Skip to content

Commit

Permalink
Fix #5236 - improve reconciliation of interfaces when unioned with class
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Feb 24, 2021
1 parent 7958ef6 commit f8cbb22
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 47 deletions.
87 changes: 40 additions & 47 deletions src/Psalm/Internal/Type/AssertionReconciler.php
Expand Up @@ -634,17 +634,51 @@ private static function filterTypeWithAnother(
$new_type_part->value,
$existing_type_part->type_params
);
} elseif ($new_type_part instanceof Type\Atomic\TNamedObject
&& $existing_type_part instanceof Type\Atomic\TTemplateParam
&& $existing_type_part->as->hasObjectType()
) {
$existing_type_part = clone $existing_type_part;
$existing_type_part->as = self::filterTypeWithAnother(
$codebase,
$existing_type_part->as,
new Type\Union([$new_type_part]),
$template_type_map
);

$matching_atomic_types[] = $existing_type_part;
} else {
$matching_atomic_types[] = clone $new_type_part;
}
} elseif (AtomicTypeComparator::isContainedBy(

continue;
}

if (AtomicTypeComparator::isContainedBy(
$codebase,
$existing_type_part,
$new_type_part,
true,
false,
false,
null
)) {
$has_local_match = true;
$matching_atomic_types[] = $existing_type_part;

continue;
}

if ($existing_type_part instanceof Type\Atomic\TNamedObject
&& $new_type_part instanceof Type\Atomic\TNamedObject
&& ($codebase->interfaceExists($existing_type_part->value)
|| $codebase->interfaceExists($new_type_part->value))
) {
$matching_atomic_type = clone $new_type_part;
$matching_atomic_type->extra_types[$existing_type_part->getKey()] = $existing_type_part;
$matching_atomic_types[] = $matching_atomic_type;
$has_local_match = true;

continue;
}

if ($new_type_part instanceof Type\Atomic\TKeyedArray
Expand Down Expand Up @@ -690,27 +724,10 @@ private static function filterTypeWithAnother(
&& $new_type_part->as->hasObject()
&& $existing_type_part->as->hasObject()
) {
$new_type_part->extra_types[$existing_type_part->getKey()] = $existing_type_part;
$matching_atomic_types[] = $new_type_part;
$has_local_match = true;
$matching_atomic_type = clone $new_type_part;

continue;
}

if ($has_local_match
&& $new_type_part instanceof Type\Atomic\TNamedObject
&& $existing_type_part instanceof Type\Atomic\TTemplateParam
&& $existing_type_part->as->hasObjectType()
) {
$existing_type_part = clone $existing_type_part;
$existing_type_part->as = self::filterTypeWithAnother(
$codebase,
$existing_type_part->as,
new Type\Union([$new_type_part]),
$template_type_map
);

$matching_atomic_types[] = $existing_type_part;
$matching_atomic_type->extra_types[$existing_type_part->getKey()] = $existing_type_part;
$matching_atomic_types[] = $matching_atomic_type;
$has_local_match = true;

continue;
Expand Down Expand Up @@ -816,31 +833,7 @@ private static function filterTypeWithAnother(
}
}

if ($atomic_contained_by || $atomic_comparison_results->type_coerced) {
if ($atomic_contained_by
&& $existing_type_part instanceof TNamedObject
&& $new_type_part instanceof TNamedObject
&& $existing_type_part->extra_types
&& !$codebase->classExists($existing_type_part->value)
&& !$codebase->classExists($new_type_part->value)
&& !array_filter(
$existing_type_part->extra_types,
function ($extra_type) use ($codebase): bool {
return $extra_type instanceof TNamedObject
&& $codebase->classExists($extra_type->value);
}
)
) {
if (!$has_cloned_type) {
$new_type = clone $new_type;
$has_cloned_type = true;
}

$new_type->removeType($key);
$new_type->addType($existing_type_part);
$new_type->from_docblock = $existing_type_part->from_docblock;
}

if ($atomic_comparison_results->type_coerced) {
continue;
}

Expand Down
14 changes: 14 additions & 0 deletions tests/InterfaceTest.php
Expand Up @@ -701,6 +701,20 @@ function foo(I $a) {
return $a;
}'
],
'interfaceAssertionOnClassInterfaceUnion' => [
'<?php
class SomeClass {}
interface SomeInterface {
public function doStuff(): void;
}
function takesAorB(SomeClass|SomeInterface $some): void {
if ($some instanceof SomeInterface) {
$some->doStuff();
}
}'
],
];
}

Expand Down
1 change: 1 addition & 0 deletions tests/TypeReconciliation/ReconcilerTest.php
Expand Up @@ -138,6 +138,7 @@ public function providerTestReconcilation(): array
'iterableAndObject' => ['Traversable<int, string>', 'object', 'iterable<int, string>'],
'iterableAndNotObject' => ['array<int, string>', '!object', 'iterable<int, string>'],
'boolNotEmptyIsTrue' => ['true', '!empty', 'bool'],
'interfaceAssertionOnClassInterfaceUnion' => ['SomeInterface|SomeInterface&SomeClass', 'SomeInterface', 'SomeClass|SomeInterface'],
];
}

Expand Down

0 comments on commit f8cbb22

Please sign in to comment.