mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Emit ImplicitToStringCast in more places (#5344)
* Emit ImplicitToStringCast in more places Fixes vimeo/psalm#5320 `to_string_cast` is set on successful comparison, thus it needs to always bubble up (it will be ignored in UnionTypeComparator if some part does not match). * Fix implicit casts * Fix handling of string method references in self-out context
This commit is contained in:
parent
7138678c63
commit
71a0457284
@ -1850,16 +1850,24 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
$interface_return_type,
|
||||
$interface_class,
|
||||
$interface_return_type_location,
|
||||
[$analyzed_method_id],
|
||||
[$analyzed_method_id->__toString()],
|
||||
$did_explicitly_return
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$overridden_method_ids = array_map(
|
||||
function ($method_id) {
|
||||
return $method_id->__toString();
|
||||
},
|
||||
$overridden_method_ids
|
||||
);
|
||||
|
||||
if ($actual_method_storage->overridden_downstream) {
|
||||
$overridden_method_ids['overridden::downstream'] = 'overridden::downstream';
|
||||
}
|
||||
|
||||
|
||||
FunctionLike\ReturnTypeAnalyzer::verifyReturnType(
|
||||
$stmt,
|
||||
$stmt->getStmts() ?: [],
|
||||
|
@ -64,7 +64,7 @@ class ExistingAtomicMethodCallAnalyzer extends CallAnalyzer
|
||||
|
||||
$cased_method_id = $fq_class_name . '::' . $stmt_name->name;
|
||||
|
||||
$result->existent_method_ids[] = $method_id;
|
||||
$result->existent_method_ids[] = $method_id->__toString();
|
||||
|
||||
if ($context->collect_initializations && $context->calling_method_id) {
|
||||
[$calling_method_class] = explode('::', $context->calling_method_id);
|
||||
|
@ -77,7 +77,7 @@ class MissingMethodCallHandler
|
||||
|
||||
if (isset($class_storage->pseudo_methods[$method_name_lc])) {
|
||||
$result->has_valid_method_call_type = true;
|
||||
$result->existent_method_ids[] = $method_id;
|
||||
$result->existent_method_ids[] = $method_id->__toString();
|
||||
|
||||
$pseudo_method_storage = $class_storage->pseudo_methods[$method_name_lc];
|
||||
|
||||
@ -144,14 +144,14 @@ class MissingMethodCallHandler
|
||||
);
|
||||
|
||||
if ($class_storage->sealed_methods || $config->seal_all_methods) {
|
||||
$result->non_existent_magic_method_ids[] = $method_id;
|
||||
$result->non_existent_magic_method_ids[] = $method_id->__toString();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$result->has_valid_method_call_type = true;
|
||||
$result->existent_method_ids[] = $method_id;
|
||||
$result->existent_method_ids[] = $method_id->__toString();
|
||||
|
||||
$array_values = array_map(
|
||||
/**
|
||||
@ -220,7 +220,7 @@ class MissingMethodCallHandler
|
||||
&& isset($class_storage->pseudo_methods[$method_name_lc])
|
||||
) {
|
||||
$result->has_valid_method_call_type = true;
|
||||
$result->existent_method_ids[] = $method_id;
|
||||
$result->existent_method_ids[] = $method_id->__toString();
|
||||
|
||||
$pseudo_method_storage = $class_storage->pseudo_methods[$method_name_lc];
|
||||
|
||||
|
@ -434,19 +434,14 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
|
||||
}
|
||||
|
||||
if ($lhs_var_id) {
|
||||
// TODO: Always defined? Always correct?
|
||||
$method_id = $result->existent_method_ids[0];
|
||||
if ($method_id instanceof MethodIdentifier) {
|
||||
// TODO: When should a method have a storage?
|
||||
if ($codebase->methods->hasStorage($method_id)) {
|
||||
$storage = $codebase->methods->getStorage($method_id);
|
||||
if ($storage->self_out_type) {
|
||||
$self_out_type = $storage->self_out_type;
|
||||
$context->vars_in_scope[$lhs_var_id] = $self_out_type;
|
||||
}
|
||||
$method_id = MethodIdentifier::wrap($result->existent_method_ids[0]);
|
||||
// TODO: When should a method have a storage?
|
||||
if ($codebase->methods->hasStorage($method_id)) {
|
||||
$storage = $codebase->methods->getStorage($method_id);
|
||||
if ($storage->self_out_type) {
|
||||
$self_out_type = $storage->self_out_type;
|
||||
$context->vars_in_scope[$lhs_var_id] = $self_out_type;
|
||||
}
|
||||
} else {
|
||||
// TODO: When is method_id a string?
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,8 +194,8 @@ class ArrayTypeComparator
|
||||
|
||||
$param_comparison_result = new TypeComparisonResult();
|
||||
|
||||
if (!$input_param->isEmpty() &&
|
||||
!UnionTypeComparator::isContainedBy(
|
||||
if (!$input_param->isEmpty()) {
|
||||
if (!UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
$input_param,
|
||||
$container_param,
|
||||
@ -203,36 +203,38 @@ class ArrayTypeComparator
|
||||
$input_param->ignore_falsable_issues,
|
||||
$param_comparison_result,
|
||||
$allow_interface_equality
|
||||
)
|
||||
) {
|
||||
if ($atomic_comparison_result) {
|
||||
$atomic_comparison_result->type_coerced
|
||||
= $param_comparison_result->type_coerced === true
|
||||
&& $atomic_comparison_result->type_coerced !== false;
|
||||
)) {
|
||||
if ($atomic_comparison_result) {
|
||||
$atomic_comparison_result->type_coerced
|
||||
= $param_comparison_result->type_coerced === true
|
||||
&& $atomic_comparison_result->type_coerced !== false;
|
||||
|
||||
$atomic_comparison_result->type_coerced_from_mixed
|
||||
= $param_comparison_result->type_coerced_from_mixed === true
|
||||
&& $atomic_comparison_result->type_coerced_from_mixed !== false;
|
||||
$atomic_comparison_result->type_coerced_from_mixed
|
||||
= $param_comparison_result->type_coerced_from_mixed === true
|
||||
&& $atomic_comparison_result->type_coerced_from_mixed !== false;
|
||||
|
||||
$atomic_comparison_result->type_coerced_from_as_mixed
|
||||
= $param_comparison_result->type_coerced_from_as_mixed === true
|
||||
&& $atomic_comparison_result->type_coerced_from_as_mixed !== false;
|
||||
$atomic_comparison_result->type_coerced_from_as_mixed
|
||||
= $param_comparison_result->type_coerced_from_as_mixed === true
|
||||
&& $atomic_comparison_result->type_coerced_from_as_mixed !== false;
|
||||
|
||||
$atomic_comparison_result->to_string_cast
|
||||
= $param_comparison_result->to_string_cast === true
|
||||
&& $atomic_comparison_result->to_string_cast !== false;
|
||||
$atomic_comparison_result->type_coerced_from_scalar
|
||||
= $param_comparison_result->type_coerced_from_scalar === true
|
||||
&& $atomic_comparison_result->type_coerced_from_scalar !== false;
|
||||
|
||||
$atomic_comparison_result->type_coerced_from_scalar
|
||||
= $param_comparison_result->type_coerced_from_scalar === true
|
||||
&& $atomic_comparison_result->type_coerced_from_scalar !== false;
|
||||
$atomic_comparison_result->scalar_type_match_found
|
||||
= $param_comparison_result->scalar_type_match_found === true
|
||||
&& $atomic_comparison_result->scalar_type_match_found !== false;
|
||||
}
|
||||
|
||||
$atomic_comparison_result->scalar_type_match_found
|
||||
= $param_comparison_result->scalar_type_match_found === true
|
||||
&& $atomic_comparison_result->scalar_type_match_found !== false;
|
||||
}
|
||||
|
||||
if (!$param_comparison_result->type_coerced_from_as_mixed) {
|
||||
$all_types_contain = false;
|
||||
if (!$param_comparison_result->type_coerced_from_as_mixed) {
|
||||
$all_types_contain = false;
|
||||
}
|
||||
} else {
|
||||
if ($atomic_comparison_result) {
|
||||
$atomic_comparison_result->to_string_cast
|
||||
= $atomic_comparison_result->to_string_cast === true
|
||||
|| $param_comparison_result->to_string_cast === true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -247,14 +249,6 @@ class ArrayTypeComparator
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($all_types_contain) {
|
||||
if ($atomic_comparison_result) {
|
||||
$atomic_comparison_result->to_string_cast = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return $all_types_contain;
|
||||
}
|
||||
}
|
||||
|
@ -457,8 +457,8 @@ class AtomicTypeComparator
|
||||
|
||||
$array_comparison_result = new TypeComparisonResult();
|
||||
|
||||
if (!$input_param->isEmpty()
|
||||
&& !UnionTypeComparator::isContainedBy(
|
||||
if (!$input_param->isEmpty()) {
|
||||
if (!UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
$input_param,
|
||||
$container_param,
|
||||
@ -467,24 +467,22 @@ class AtomicTypeComparator
|
||||
$array_comparison_result,
|
||||
$allow_interface_equality
|
||||
)
|
||||
&& !$array_comparison_result->type_coerced_from_scalar
|
||||
) {
|
||||
if ($atomic_comparison_result && $array_comparison_result->type_coerced_from_mixed) {
|
||||
$atomic_comparison_result->type_coerced_from_mixed = true;
|
||||
&& !$array_comparison_result->type_coerced_from_scalar
|
||||
) {
|
||||
if ($atomic_comparison_result && $array_comparison_result->type_coerced_from_mixed) {
|
||||
$atomic_comparison_result->type_coerced_from_mixed = true;
|
||||
}
|
||||
$all_types_contain = false;
|
||||
} else {
|
||||
if ($atomic_comparison_result) {
|
||||
$atomic_comparison_result->to_string_cast
|
||||
= $atomic_comparison_result->to_string_cast === true
|
||||
|| $array_comparison_result->to_string_cast === true;
|
||||
}
|
||||
}
|
||||
$all_types_contain = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($all_types_contain) {
|
||||
if ($atomic_comparison_result) {
|
||||
$atomic_comparison_result->to_string_cast = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return $all_types_contain;
|
||||
}
|
||||
|
||||
if ($input_type_part->hasTraversableInterface($codebase)) {
|
||||
|
@ -40,8 +40,8 @@ class KeyedArrayComparator
|
||||
|
||||
$property_type_comparison = new TypeComparisonResult();
|
||||
|
||||
if (!$input_property_type->isEmpty()
|
||||
&& !UnionTypeComparator::isContainedBy(
|
||||
if (!$input_property_type->isEmpty()) {
|
||||
if (!UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
$input_property_type,
|
||||
$container_property_type,
|
||||
@ -50,39 +50,37 @@ class KeyedArrayComparator
|
||||
$property_type_comparison,
|
||||
$allow_interface_equality
|
||||
)
|
||||
&& !$property_type_comparison->type_coerced_from_scalar
|
||||
) {
|
||||
$inverse_property_type_comparison = new TypeComparisonResult();
|
||||
&& !$property_type_comparison->type_coerced_from_scalar
|
||||
) {
|
||||
$inverse_property_type_comparison = new TypeComparisonResult();
|
||||
|
||||
if ($atomic_comparison_result) {
|
||||
if (UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
$container_property_type,
|
||||
$input_property_type,
|
||||
false,
|
||||
false,
|
||||
$inverse_property_type_comparison,
|
||||
$allow_interface_equality
|
||||
)
|
||||
|| $inverse_property_type_comparison->type_coerced_from_scalar
|
||||
) {
|
||||
$atomic_comparison_result->type_coerced = true;
|
||||
if ($atomic_comparison_result) {
|
||||
if (UnionTypeComparator::isContainedBy(
|
||||
$codebase,
|
||||
$container_property_type,
|
||||
$input_property_type,
|
||||
false,
|
||||
false,
|
||||
$inverse_property_type_comparison,
|
||||
$allow_interface_equality
|
||||
)
|
||||
|| $inverse_property_type_comparison->type_coerced_from_scalar
|
||||
) {
|
||||
$atomic_comparison_result->type_coerced = true;
|
||||
}
|
||||
}
|
||||
|
||||
$all_types_contain = false;
|
||||
} else {
|
||||
if ($atomic_comparison_result) {
|
||||
$atomic_comparison_result->to_string_cast
|
||||
= $atomic_comparison_result->to_string_cast === true
|
||||
|| $property_type_comparison->to_string_cast === true;
|
||||
}
|
||||
}
|
||||
|
||||
$all_types_contain = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($all_types_contain) {
|
||||
if ($atomic_comparison_result) {
|
||||
$atomic_comparison_result->to_string_cast = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return $all_types_contain;
|
||||
}
|
||||
|
||||
public static function isContainedByObjectWithProperties(
|
||||
|
@ -408,6 +408,66 @@ class ToStringTest extends TestCase
|
||||
false,
|
||||
'7.4',
|
||||
],
|
||||
'implicitCastInArray' => [
|
||||
'<?php
|
||||
interface S {
|
||||
public function __toString(): string;
|
||||
}
|
||||
/** @return array<array-key, string> */
|
||||
function f(S $s): array {
|
||||
return [$s];
|
||||
}
|
||||
',
|
||||
'error_message' => 'ImplicitToStringCast'
|
||||
],
|
||||
'implicitCastInList' => [
|
||||
'<?php
|
||||
interface S {
|
||||
public function __toString(): string;
|
||||
}
|
||||
/** @return list<string> */
|
||||
function f(S $s): array {
|
||||
return [$s];
|
||||
}
|
||||
',
|
||||
'error_message' => 'ImplicitToStringCast'
|
||||
],
|
||||
'implicitCastInTuple' => [
|
||||
'<?php
|
||||
interface S {
|
||||
public function __toString(): string;
|
||||
}
|
||||
/** @return array{string} */
|
||||
function f(S $s): array {
|
||||
return [$s];
|
||||
}
|
||||
',
|
||||
'error_message' => 'ImplicitToStringCast'
|
||||
],
|
||||
'implicitCastInShape' => [
|
||||
'<?php
|
||||
interface S {
|
||||
public function __toString(): string;
|
||||
}
|
||||
/** @return array{0:string} */
|
||||
function f(S $s): array {
|
||||
return [$s];
|
||||
}
|
||||
',
|
||||
'error_message' => 'ImplicitToStringCast'
|
||||
],
|
||||
'implicitCastInIterable' => [
|
||||
'<?php
|
||||
interface S {
|
||||
public function __toString(): string;
|
||||
}
|
||||
/** @return iterable<int, string> */
|
||||
function f(S $s) {
|
||||
return [$s];
|
||||
}
|
||||
',
|
||||
'error_message' => 'ImplicitToStringCast'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user