mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 05:41:20 +01:00
Fix #2275 - allow extended templated types to bypass mixed type coercion rules
This commit is contained in:
parent
f40fe86321
commit
8d7cdeb8ec
@ -436,16 +436,18 @@ class ReturnTypeAnalyzer
|
|||||||
// is the declared return type more specific than the inferred one?
|
// is the declared return type more specific than the inferred one?
|
||||||
if ($union_comparison_results->type_coerced) {
|
if ($union_comparison_results->type_coerced) {
|
||||||
if ($union_comparison_results->type_coerced_from_mixed) {
|
if ($union_comparison_results->type_coerced_from_mixed) {
|
||||||
if (IssueBuffer::accepts(
|
if (!$union_comparison_results->type_coerced_from_as_mixed) {
|
||||||
new MixedReturnTypeCoercion(
|
if (IssueBuffer::accepts(
|
||||||
'The declared return type \'' . $declared_return_type->getId() . '\' for '
|
new MixedReturnTypeCoercion(
|
||||||
. $cased_method_id . ' is more specific than the inferred return type '
|
'The declared return type \'' . $declared_return_type->getId() . '\' for '
|
||||||
. '\'' . $inferred_return_type->getId() . '\'',
|
. $cased_method_id . ' is more specific than the inferred return type '
|
||||||
$return_type_location
|
. '\'' . $inferred_return_type->getId() . '\'',
|
||||||
),
|
$return_type_location
|
||||||
$suppressed_issues
|
),
|
||||||
)) {
|
$suppressed_issues
|
||||||
return false;
|
)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (IssueBuffer::accepts(
|
if (IssueBuffer::accepts(
|
||||||
|
@ -285,16 +285,18 @@ class ReturnAnalyzer
|
|||||||
// is the declared return type more specific than the inferred one?
|
// is the declared return type more specific than the inferred one?
|
||||||
if ($union_comparison_results->type_coerced) {
|
if ($union_comparison_results->type_coerced) {
|
||||||
if ($union_comparison_results->type_coerced_from_mixed) {
|
if ($union_comparison_results->type_coerced_from_mixed) {
|
||||||
if (IssueBuffer::accepts(
|
if (!$union_comparison_results->type_coerced_from_as_mixed) {
|
||||||
new MixedReturnTypeCoercion(
|
if (IssueBuffer::accepts(
|
||||||
'The type \'' . $stmt->inferredType->getId() . '\' is more general than the'
|
new MixedReturnTypeCoercion(
|
||||||
. ' declared return type \'' . $local_return_type->getId() . '\''
|
'The type \'' . $stmt->inferredType->getId() . '\' is more general than the'
|
||||||
. ' for ' . $cased_method_id,
|
. ' declared return type \'' . $local_return_type->getId() . '\''
|
||||||
new CodeLocation($source, $stmt->expr)
|
. ' for ' . $cased_method_id,
|
||||||
),
|
new CodeLocation($source, $stmt->expr)
|
||||||
$statements_analyzer->getSuppressedIssues()
|
),
|
||||||
)) {
|
$statements_analyzer->getSuppressedIssues()
|
||||||
return false;
|
)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (IssueBuffer::accepts(
|
if (IssueBuffer::accepts(
|
||||||
|
@ -56,6 +56,7 @@ use function array_keys;
|
|||||||
use function array_reduce;
|
use function array_reduce;
|
||||||
use function end;
|
use function end;
|
||||||
use function array_unique;
|
use function array_unique;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -101,6 +102,7 @@ class TypeAnalyzer
|
|||||||
|
|
||||||
$all_type_coerced = null;
|
$all_type_coerced = null;
|
||||||
$all_type_coerced_from_mixed = null;
|
$all_type_coerced_from_mixed = null;
|
||||||
|
$all_type_coerced_from_as_mixed = null;
|
||||||
|
|
||||||
$some_type_coerced = false;
|
$some_type_coerced = false;
|
||||||
$some_type_coerced_from_mixed = false;
|
$some_type_coerced_from_mixed = false;
|
||||||
@ -144,6 +146,14 @@ class TypeAnalyzer
|
|||||||
$atomic_comparison_result
|
$atomic_comparison_result
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($input_type_part instanceof TMixed
|
||||||
|
&& $input_type->from_template_default
|
||||||
|
&& $atomic_comparison_result
|
||||||
|
&& $atomic_comparison_result->type_coerced_from_mixed
|
||||||
|
) {
|
||||||
|
$atomic_comparison_result->type_coerced_from_as_mixed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if ($atomic_comparison_result) {
|
if ($atomic_comparison_result) {
|
||||||
if ($atomic_comparison_result->scalar_type_match_found !== null) {
|
if ($atomic_comparison_result->scalar_type_match_found !== null) {
|
||||||
$scalar_type_match_found = $atomic_comparison_result->scalar_type_match_found;
|
$scalar_type_match_found = $atomic_comparison_result->scalar_type_match_found;
|
||||||
@ -203,6 +213,14 @@ class TypeAnalyzer
|
|||||||
} else {
|
} else {
|
||||||
$all_type_coerced_from_mixed = true;
|
$all_type_coerced_from_mixed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($atomic_comparison_result->type_coerced_from_as_mixed !== true
|
||||||
|
|| $all_type_coerced_from_as_mixed === false
|
||||||
|
) {
|
||||||
|
$all_type_coerced_from_as_mixed = false;
|
||||||
|
} else {
|
||||||
|
$all_type_coerced_from_as_mixed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($is_atomic_contained_by) {
|
if ($is_atomic_contained_by) {
|
||||||
@ -215,6 +233,7 @@ class TypeAnalyzer
|
|||||||
}
|
}
|
||||||
|
|
||||||
$all_type_coerced_from_mixed = false;
|
$all_type_coerced_from_mixed = false;
|
||||||
|
$all_type_coerced_from_as_mixed = false;
|
||||||
$all_type_coerced = false;
|
$all_type_coerced = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,6 +252,10 @@ class TypeAnalyzer
|
|||||||
|
|
||||||
if ($all_type_coerced_from_mixed) {
|
if ($all_type_coerced_from_mixed) {
|
||||||
$union_comparison_result->type_coerced_from_mixed = true;
|
$union_comparison_result->type_coerced_from_mixed = true;
|
||||||
|
|
||||||
|
if ($input_type->from_template_default || $all_type_coerced_from_as_mixed) {
|
||||||
|
$union_comparison_result->type_coerced_from_as_mixed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,6 +267,10 @@ class TypeAnalyzer
|
|||||||
|
|
||||||
if ($some_type_coerced_from_mixed) {
|
if ($some_type_coerced_from_mixed) {
|
||||||
$union_comparison_result->type_coerced_from_mixed = true;
|
$union_comparison_result->type_coerced_from_mixed = true;
|
||||||
|
|
||||||
|
if ($input_type->from_template_default || $all_type_coerced_from_as_mixed) {
|
||||||
|
$union_comparison_result->type_coerced_from_as_mixed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$scalar_type_match_found) {
|
if (!$scalar_type_match_found) {
|
||||||
@ -1808,6 +1835,8 @@ class TypeAnalyzer
|
|||||||
$candidate_param_type = new Type\Union([$et]);
|
$candidate_param_type = new Type\Union([$et]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$candidate_param_type->from_template_default = true;
|
||||||
|
|
||||||
if (!$new_input_param) {
|
if (!$new_input_param) {
|
||||||
$new_input_param = $candidate_param_type;
|
$new_input_param = $candidate_param_type;
|
||||||
} else {
|
} else {
|
||||||
|
@ -13,6 +13,9 @@ class TypeComparisonResult
|
|||||||
/** @var ?bool */
|
/** @var ?bool */
|
||||||
public $type_coerced_from_mixed = null;
|
public $type_coerced_from_mixed = null;
|
||||||
|
|
||||||
|
/** @var ?bool */
|
||||||
|
public $type_coerced_from_as_mixed = null;
|
||||||
|
|
||||||
/** @var ?bool */
|
/** @var ?bool */
|
||||||
public $to_string_cast = null;
|
public $to_string_cast = null;
|
||||||
|
|
||||||
|
@ -130,6 +130,13 @@ class Union
|
|||||||
*/
|
*/
|
||||||
public $had_template = false;
|
public $had_template = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not this union comes from a template "as" default
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $from_template_default = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<string, TLiteralString>
|
* @var array<string, TLiteralString>
|
||||||
*/
|
*/
|
||||||
|
@ -2316,6 +2316,34 @@ class ClassTemplateExtendsTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
class Test extends C {}'
|
class Test extends C {}'
|
||||||
],
|
],
|
||||||
|
'eitherType' => [
|
||||||
|
'<?php
|
||||||
|
/**
|
||||||
|
* @template L
|
||||||
|
* @template R
|
||||||
|
*/
|
||||||
|
interface Either{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template R
|
||||||
|
* @template-implements Either<mixed,R>
|
||||||
|
*/
|
||||||
|
final class Right implements Either {
|
||||||
|
/**
|
||||||
|
* @param R $value
|
||||||
|
*/
|
||||||
|
public function __construct($value) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Either<B,B>
|
||||||
|
*/
|
||||||
|
function result(bool $a = true): Either {
|
||||||
|
return new Right(new B());
|
||||||
|
}'
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user