1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-11 08:49:52 +01:00

Unify names with intersection creation

This commit is contained in:
Matthew Brown 2022-01-05 00:38:40 +00:00
parent 0a78b320da
commit 26de4faa51

View File

@ -506,7 +506,7 @@ class AssertionReconciler extends Reconciler
); );
} }
$new_type = self::filterTypeWithAnother( $intersection_type = self::filterTypeWithAnother(
$codebase, $codebase,
$existing_var_type, $existing_var_type,
$new_type, $new_type,
@ -570,6 +570,10 @@ class AssertionReconciler extends Reconciler
$failed_reconciliation = Reconciler::RECONCILIATION_EMPTY; $failed_reconciliation = Reconciler::RECONCILIATION_EMPTY;
} }
if ($intersection_type) {
$new_type = $intersection_type;
}
} }
return $new_type; return $new_type;
@ -588,7 +592,7 @@ class AssertionReconciler extends Reconciler
array $template_type_map, array $template_type_map,
bool &$has_match = false, bool &$has_match = false,
bool &$any_scalar_type_match_found = false bool &$any_scalar_type_match_found = false
): Union { ): ?Union {
$matching_atomic_types = []; $matching_atomic_types = [];
$new_type = clone $new_type; $new_type = clone $new_type;
@ -622,38 +626,38 @@ class AssertionReconciler extends Reconciler
return new Union($matching_atomic_types); return new Union($matching_atomic_types);
} }
return $new_type; return null;
} }
/** /**
* @param array<string, array<string, Union>> $template_type_map * @param array<string, array<string, Union>> $template_type_map
*/ */
private static function filterAtomicWithAnother( private static function filterAtomicWithAnother(
Atomic $existing_type_part, Atomic $type_1_atomic,
Atomic $new_type_part, Atomic $type_2_atomic,
Codebase $codebase, Codebase $codebase,
array $template_type_map, array $template_type_map,
bool &$has_local_match, bool &$has_local_match,
bool &$any_scalar_type_match_found bool &$any_scalar_type_match_found
): ?Atomic { ): ?Atomic {
if ($existing_type_part instanceof TFloat if ($type_1_atomic instanceof TFloat
&& $new_type_part instanceof TInt && $type_2_atomic instanceof TInt
) { ) {
$any_scalar_type_match_found = true; $any_scalar_type_match_found = true;
return $new_type_part; return $type_2_atomic;
}
if ($type_1_atomic instanceof TNamedObject) {
$type_1_atomic->was_static = false;
} }
$atomic_comparison_results = new TypeComparisonResult(); $atomic_comparison_results = new TypeComparisonResult();
if ($existing_type_part instanceof TNamedObject) {
$existing_type_part->was_static = false;
}
$atomic_contained_by = AtomicTypeComparator::isContainedBy( $atomic_contained_by = AtomicTypeComparator::isContainedBy(
$codebase, $codebase,
$new_type_part, $type_2_atomic,
$existing_type_part, $type_1_atomic,
true, true, // this probably should be false, but a few tests currently fail if it's not true
false, false,
$atomic_comparison_results $atomic_comparison_results
); );
@ -661,81 +665,108 @@ class AssertionReconciler extends Reconciler
if ($atomic_contained_by) { if ($atomic_contained_by) {
$has_local_match = true; $has_local_match = true;
if ($atomic_comparison_results->type_coerced return self::refineContainedAtomicWithAnother(
&& get_class($new_type_part) === TNamedObject::class $type_1_atomic,
&& $existing_type_part instanceof TGenericObject $type_2_atomic,
) { $codebase,
// this is a hack - it's not actually rigorous, as the params may be different $template_type_map,
return new TGenericObject( $atomic_comparison_results->type_coerced ?? false
$new_type_part->value, );
$existing_type_part->type_params
);
} elseif ($new_type_part instanceof TNamedObject
&& $existing_type_part instanceof 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 Union([$new_type_part]),
$template_type_map
);
return $existing_type_part;
} else {
return clone $new_type_part;
}
} }
if (AtomicTypeComparator::isContainedBy( $atomic_comparison_results = new TypeComparisonResult();
$atomic_contained_by = AtomicTypeComparator::isContainedBy(
$codebase, $codebase,
$existing_type_part, $type_1_atomic,
$new_type_part, $type_2_atomic,
false, false,
false, false,
null $atomic_comparison_results
)) { );
if ($atomic_contained_by) {
$has_local_match = true; $has_local_match = true;
return $existing_type_part; return self::refineContainedAtomicWithAnother(
$type_2_atomic,
$type_1_atomic,
$codebase,
$template_type_map,
$atomic_comparison_results->type_coerced ?? false
);
} }
$matching_atomic_type = null; $matching_atomic_type = null;
if ($existing_type_part instanceof TNamedObject if ($type_1_atomic instanceof TNamedObject
&& $new_type_part instanceof TNamedObject && $type_2_atomic instanceof TNamedObject
&& ($codebase->interfaceExists($existing_type_part->value) && ($codebase->interfaceExists($type_1_atomic->value)
|| $codebase->interfaceExists($new_type_part->value)) || $codebase->interfaceExists($type_2_atomic->value))
) { ) {
$matching_atomic_type = clone $new_type_part; $matching_atomic_type = clone $type_2_atomic;
$matching_atomic_type->extra_types[$existing_type_part->getKey()] = $existing_type_part; $matching_atomic_type->extra_types[$type_1_atomic->getKey()] = $type_1_atomic;
$has_local_match = true; $has_local_match = true;
return $matching_atomic_type; return $matching_atomic_type;
} }
if ($new_type_part instanceof TKeyedArray if ($type_2_atomic instanceof TKeyedArray
&& $existing_type_part instanceof TList && $type_1_atomic instanceof TList
) { ) {
$new_type_key = $new_type_part->getGenericKeyType(); $type_2_key = $type_2_atomic->getGenericKeyType();
$new_type_value = $new_type_part->getGenericValueType(); $type_2_value = $type_2_atomic->getGenericValueType();
if (!$new_type_key->hasString()) { if (!$type_2_key->hasString()) {
$has_param_match = false; $has_param_match = false;
$new_type_value = self::filterTypeWithAnother( $type_2_value = self::filterTypeWithAnother(
$codebase, $codebase,
$existing_type_part->type_param, $type_1_atomic->type_param,
$new_type_value, $type_2_value,
$template_type_map, $template_type_map,
$has_param_match, $has_param_match,
$any_scalar_type_match_found $any_scalar_type_match_found
); );
$hybrid_type_part = new TKeyedArray($new_type_part->properties); if ($type_2_value === null) {
return null;
}
$hybrid_type_part = new TKeyedArray($type_2_atomic->properties);
$hybrid_type_part->previous_key_type = Type::getInt(); $hybrid_type_part->previous_key_type = Type::getInt();
$hybrid_type_part->previous_value_type = $new_type_value; $hybrid_type_part->previous_value_type = $type_2_value;
$hybrid_type_part->is_list = true;
$has_local_match = true;
return $hybrid_type_part;
}
} elseif ($type_1_atomic instanceof TKeyedArray
&& $type_2_atomic instanceof TList
) {
$type_1_key = $type_1_atomic->getGenericKeyType();
$type_1_value = $type_1_atomic->getGenericValueType();
if (!$type_1_key->hasString()) {
$has_param_match = false;
$type_1_value = self::filterTypeWithAnother(
$codebase,
$type_2_atomic->type_param,
$type_1_value,
$template_type_map,
$has_param_match,
$any_scalar_type_match_found
);
if ($type_1_value === null) {
return null;
}
$hybrid_type_part = new TKeyedArray($type_1_atomic->properties);
$hybrid_type_part->previous_key_type = Type::getInt();
$hybrid_type_part->previous_value_type = $type_1_value;
$hybrid_type_part->is_list = true; $hybrid_type_part->is_list = true;
$has_local_match = true; $has_local_match = true;
@ -744,60 +775,64 @@ class AssertionReconciler extends Reconciler
} }
} }
if ($new_type_part instanceof TTemplateParam if ($type_2_atomic instanceof TTemplateParam
&& $existing_type_part instanceof TTemplateParam && $type_1_atomic instanceof TTemplateParam
&& $new_type_part->param_name !== $existing_type_part->param_name && $type_2_atomic->param_name !== $type_1_atomic->param_name
&& $new_type_part->as->hasObject() && $type_2_atomic->as->hasObject()
&& $existing_type_part->as->hasObject() && $type_1_atomic->as->hasObject()
) { ) {
$matching_atomic_type = clone $new_type_part; $matching_atomic_type = clone $type_2_atomic;
$matching_atomic_type->extra_types[$existing_type_part->getKey()] = $existing_type_part; $matching_atomic_type->extra_types[$type_1_atomic->getKey()] = $type_1_atomic;
$has_local_match = true; $has_local_match = true;
return $matching_atomic_type; return $matching_atomic_type;
} }
//we filter both types of standard iterables //we filter both types of standard iterables
if (($new_type_part instanceof TGenericObject if (($type_2_atomic instanceof TGenericObject
|| $new_type_part instanceof TArray || $type_2_atomic instanceof TArray
|| $new_type_part instanceof TIterable) || $type_2_atomic instanceof TIterable)
&& ($existing_type_part instanceof TGenericObject && ($type_1_atomic instanceof TGenericObject
|| $existing_type_part instanceof TArray || $type_1_atomic instanceof TArray
|| $existing_type_part instanceof TIterable) || $type_1_atomic instanceof TIterable)
&& count($new_type_part->type_params) === count($existing_type_part->type_params) && count($type_2_atomic->type_params) === count($type_1_atomic->type_params)
) { ) {
$has_any_param_match = false; $has_any_param_match = false;
foreach ($new_type_part->type_params as $i => $new_param) { foreach ($type_2_atomic->type_params as $i => $type_2_param) {
$existing_param = $existing_type_part->type_params[$i]; $type_1_param = $type_1_atomic->type_params[$i];
$has_param_match = true; $has_param_match = true;
$new_param_id = $new_param->getId(); $type_2_param_id = $type_2_param->getId();
$new_param = self::filterTypeWithAnother( $type_2_param = self::filterTypeWithAnother(
$codebase, $codebase,
$existing_param, $type_1_param,
$new_param, $type_2_param,
$template_type_map, $template_type_map,
$has_param_match, $has_param_match,
$any_scalar_type_match_found $any_scalar_type_match_found
); );
if ($type_2_param === null) {
return null;
}
if ($template_type_map) { if ($template_type_map) {
TemplateInferredTypeReplacer::replace( TemplateInferredTypeReplacer::replace(
$new_param, $type_2_param,
new TemplateResult([], $template_type_map), new TemplateResult([], $template_type_map),
$codebase $codebase
); );
} }
if ($has_param_match if ($has_param_match
&& $existing_type_part->type_params[$i]->getId() !== $new_param_id && $type_1_atomic->type_params[$i]->getId() !== $type_2_param_id
) { ) {
/** @psalm-suppress PropertyTypeCoercion */ /** @psalm-suppress PropertyTypeCoercion */
$existing_type_part->type_params[$i] = $new_param; $type_1_atomic->type_params[$i] = $type_2_param;
if (!$has_local_match) { if (!$has_local_match) {
$has_any_param_match = true; $has_any_param_match = true;
@ -807,44 +842,48 @@ class AssertionReconciler extends Reconciler
if ($has_any_param_match) { if ($has_any_param_match) {
$has_local_match = true; $has_local_match = true;
$matching_atomic_type = $existing_type_part; $matching_atomic_type = $type_1_atomic;
$atomic_comparison_results->type_coerced = true; $atomic_comparison_results->type_coerced = true;
} }
} }
//we filter the second part of a list with the second part of standard iterables //we filter the second part of a list with the second part of standard iterables
if (($new_type_part instanceof TArray if (($type_2_atomic instanceof TArray
|| $new_type_part instanceof TIterable) || $type_2_atomic instanceof TIterable)
&& $existing_type_part instanceof TList && $type_1_atomic instanceof TList
) { ) {
$has_any_param_match = false; $has_any_param_match = false;
$new_param = $new_type_part->type_params[1]; $type_2_param = $type_2_atomic->type_params[1];
$existing_param = $existing_type_part->type_param; $type_1_param = $type_1_atomic->type_param;
$has_param_match = true; $has_param_match = true;
$new_param = self::filterTypeWithAnother( $type_2_param = self::filterTypeWithAnother(
$codebase, $codebase,
$existing_param, $type_1_param,
$new_param, $type_2_param,
$template_type_map, $template_type_map,
$has_param_match, $has_param_match,
$any_scalar_type_match_found $any_scalar_type_match_found
); );
if ($type_2_param === null) {
return null;
}
if ($template_type_map) { if ($template_type_map) {
TemplateInferredTypeReplacer::replace( TemplateInferredTypeReplacer::replace(
$new_param, $type_2_param,
new TemplateResult([], $template_type_map), new TemplateResult([], $template_type_map),
$codebase $codebase
); );
} }
if ($has_param_match if ($has_param_match
&& $existing_type_part->type_param->getId() !== $new_param->getId() && $type_1_atomic->type_param->getId() !== $type_2_param->getId()
) { ) {
$existing_type_part->type_param = $new_param; $type_1_atomic->type_param = $type_2_param;
if (!$has_local_match) { if (!$has_local_match) {
$has_any_param_match = true; $has_any_param_match = true;
@ -853,43 +892,47 @@ class AssertionReconciler extends Reconciler
if ($has_any_param_match) { if ($has_any_param_match) {
$has_local_match = true; $has_local_match = true;
$matching_atomic_type = $existing_type_part; $matching_atomic_type = $type_1_atomic;
$atomic_comparison_results->type_coerced = true; $atomic_comparison_results->type_coerced = true;
} }
} }
//we filter each property of a Keyed Array with the second part of standard iterables //we filter each property of a Keyed Array with the second part of standard iterables
if (($new_type_part instanceof TArray if (($type_2_atomic instanceof TArray
|| $new_type_part instanceof TIterable) || $type_2_atomic instanceof TIterable)
&& $existing_type_part instanceof TKeyedArray && $type_1_atomic instanceof TKeyedArray
) { ) {
$has_any_param_match = false; $has_any_param_match = false;
$new_param = $new_type_part->type_params[1]; $type_2_param = $type_2_atomic->type_params[1];
foreach ($existing_type_part->properties as $property_key => $existing_param) { foreach ($type_1_atomic->properties as $property_key => $type_1_param) {
$has_param_match = true; $has_param_match = true;
$new_param = self::filterTypeWithAnother( $type_2_param = self::filterTypeWithAnother(
$codebase, $codebase,
$existing_param, $type_1_param,
$new_param, $type_2_param,
$template_type_map, $template_type_map,
$has_param_match, $has_param_match,
$any_scalar_type_match_found $any_scalar_type_match_found
); );
if ($type_2_param === null) {
return null;
}
if ($template_type_map) { if ($template_type_map) {
TemplateInferredTypeReplacer::replace( TemplateInferredTypeReplacer::replace(
$new_param, $type_2_param,
new TemplateResult([], $template_type_map), new TemplateResult([], $template_type_map),
$codebase $codebase
); );
} }
if ($has_param_match if ($has_param_match
&& $existing_type_part->properties[$property_key]->getId() !== $new_param->getId() && $type_1_atomic->properties[$property_key]->getId() !== $type_2_param->getId()
) { ) {
$existing_type_part->properties[$property_key] = $new_param; $type_1_atomic->properties[$property_key] = $type_2_param;
if (!$has_local_match) { if (!$has_local_match) {
$has_any_param_match = true; $has_any_param_match = true;
@ -899,31 +942,31 @@ class AssertionReconciler extends Reconciler
if ($has_any_param_match) { if ($has_any_param_match) {
$has_local_match = true; $has_local_match = true;
$matching_atomic_type = $existing_type_part; $matching_atomic_type = $type_1_atomic;
$atomic_comparison_results->type_coerced = true; $atomic_comparison_results->type_coerced = true;
} }
} }
//These partial match wouldn't have been handled by AtomicTypeComparator //These partial match wouldn't have been handled by AtomicTypeComparator
$new_range = null; $new_range = null;
if ($new_type_part instanceof TIntRange && $existing_type_part instanceof TPositiveInt) { if ($type_2_atomic instanceof TIntRange && $type_1_atomic instanceof TPositiveInt) {
$new_range = TIntRange::intersectIntRanges( $new_range = TIntRange::intersectIntRanges(
TIntRange::convertToIntRange($existing_type_part), TIntRange::convertToIntRange($type_1_atomic),
$new_type_part $type_2_atomic
); );
} elseif ($existing_type_part instanceof TIntRange } elseif ($type_1_atomic instanceof TIntRange
&& $new_type_part instanceof TPositiveInt && $type_2_atomic instanceof TPositiveInt
) { ) {
$new_range = TIntRange::intersectIntRanges( $new_range = TIntRange::intersectIntRanges(
$existing_type_part, $type_1_atomic,
TIntRange::convertToIntRange($new_type_part) TIntRange::convertToIntRange($type_2_atomic)
); );
} elseif ($new_type_part instanceof TIntRange } elseif ($type_2_atomic instanceof TIntRange
&& $existing_type_part instanceof TIntRange && $type_1_atomic instanceof TIntRange
) { ) {
$new_range = TIntRange::intersectIntRanges( $new_range = TIntRange::intersectIntRanges(
$existing_type_part, $type_1_atomic,
$new_type_part $type_2_atomic
); );
} }
@ -939,6 +982,49 @@ class AssertionReconciler extends Reconciler
return $matching_atomic_type; return $matching_atomic_type;
} }
/**
* @param array<string, array<string, Union>> $template_type_map
*/
private static function refineContainedAtomicWithAnother(
Atomic $type_1_atomic,
Atomic $type_2_atomic,
Codebase $codebase,
array $template_type_map,
bool $type_coerced
): ?Atomic {
if ($type_coerced
&& get_class($type_2_atomic) === TNamedObject::class
&& $type_1_atomic instanceof TGenericObject
) {
// this is a hack - it's not actually rigorous, as the params may be different
return new TGenericObject(
$type_2_atomic->value,
$type_1_atomic->type_params
);
} elseif ($type_2_atomic instanceof TNamedObject
&& $type_1_atomic instanceof TTemplateParam
&& $type_1_atomic->as->hasObjectType()
) {
$type_1_atomic = clone $type_1_atomic;
$type_1_as = self::filterTypeWithAnother(
$codebase,
$type_1_atomic->as,
new Union([$type_2_atomic]),
$template_type_map
);
if ($type_1_as === null) {
return null;
}
$type_1_atomic->as = $type_1_as;
return $type_1_atomic;
} else {
return clone $type_2_atomic;
}
}
/** /**
* @param string[] $suppressed_issues * @param string[] $suppressed_issues
*/ */