1
0
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:
Bruce Weirdan 2021-03-11 07:07:39 +02:00 committed by GitHub
parent 7138678c63
commit 71a0457284
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 154 additions and 101 deletions

View File

@ -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() ?: [],

View File

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

View File

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

View File

@ -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?
}
}

View File

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

View File

@ -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)) {

View File

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

View File

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