From 38c452ae584467e939d55377aaf83b5a26f19dd1 Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Mon, 24 May 2021 00:09:51 -0400 Subject: [PATCH] Add example given in ticket and ensure that works too --- .../Expression/Call/ArgumentAnalyzer.php | 17 +++-- .../Comparator/CallableTypeComparator.php | 8 ++ src/Psalm/Internal/Type/TypeExpander.php | 76 +++++++++++++------ tests/Template/ClassTemplateTest.php | 13 ++++ 4 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index dc588981515..3dc6fe7475d 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -832,17 +832,22 @@ public static function verifyType( $param_type->possibly_undefined = true; } - if ($param_type->hasCallableType() - && $param_type->isSingle() - && $input_type->isSingleStringLiteral() - && !\Psalm\Internal\Codebase\InternalCallMapHandler::inCallMap($input_type->getSingleStringLiteral()->value) - ) { + if ($param_type->hasCallableType() && $param_type->isSingle()) { + // we do this replacement early because later we don't have access to the + // $statements_analyzer, which is necessary to understand string function names foreach ($input_type->getAtomicTypes() as $key => $atomic_type) { + if (!$atomic_type instanceof Type\Atomic\TLiteralString + || \Psalm\Internal\Codebase\InternalCallMapHandler::inCallMap($atomic_type->value) + ) { + continue; + } + $candidate_callable = CallableTypeComparator::getCallableFromAtomic( $codebase, $atomic_type, null, - $statements_analyzer + $statements_analyzer, + true ); if ($candidate_callable) { diff --git a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php index 2817efc262e..0c403490630 100644 --- a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php @@ -252,9 +252,14 @@ public static function getCallableFromAtomic( null, null, true, + true, + false, + false, true ); } + + $params[] = $param; } $return_type = null; @@ -267,6 +272,9 @@ public static function getCallableFromAtomic( null, null, true, + true, + false, + false, true ); } diff --git a/src/Psalm/Internal/Type/TypeExpander.php b/src/Psalm/Internal/Type/TypeExpander.php index f6b145d5004..3101b994058 100644 --- a/src/Psalm/Internal/Type/TypeExpander.php +++ b/src/Psalm/Internal/Type/TypeExpander.php @@ -32,7 +32,8 @@ public static function expandUnion( bool $evaluate_class_constants = true, bool $evaluate_conditional_types = false, bool $final = false, - bool $expand_generic = false + bool $expand_generic = false, + bool $expand_templates = false ): Type\Union { $return_type = clone $return_type; @@ -50,7 +51,8 @@ public static function expandUnion( $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); if (is_array($parts)) { @@ -99,7 +101,8 @@ public static function expandAtomic( bool $evaluate_class_constants = true, bool $evaluate_conditional_types = false, bool $final = false, - bool $expand_generic = false + bool $expand_generic = false, + bool $expand_templates = false ) { if ($return_type instanceof TNamedObject || $return_type instanceof TTemplateParam @@ -116,7 +119,8 @@ public static function expandAtomic( $parent_class, $evaluate_class_constants, $evaluate_conditional_types, - $expand_generic + $expand_generic, + $expand_templates ); if ($extra_type instanceof TNamedObject && $extra_type->extra_types) { @@ -160,7 +164,8 @@ public static function expandAtomic( $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); if ($new_as_type instanceof TNamedObject) { @@ -177,9 +182,14 @@ public static function expandAtomic( $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); + if ($expand_templates) { + return array_values($new_as_type->getAtomicTypes()); + } + $return_type->as = $new_as_type; } @@ -286,7 +296,8 @@ function ($constant_name) use ($const_name_part): bool { $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); if (is_array($recursively_fleshed_out_type)) { @@ -364,7 +375,8 @@ function ($constant_name) use ($const_name_part): bool { $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); if (\is_array($new_value_type)) { @@ -397,7 +409,8 @@ function ($constant_name) use ($const_name_part): bool { $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); if (!is_array($new_value_types)) { @@ -432,7 +445,8 @@ function ($constant_name) use ($const_name_part): bool { $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); } } elseif ($return_type instanceof Type\Atomic\TKeyedArray) { @@ -446,7 +460,8 @@ function ($constant_name) use ($const_name_part): bool { $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); } } elseif ($return_type instanceof Type\Atomic\TList) { @@ -459,7 +474,8 @@ function ($constant_name) use ($const_name_part): bool { $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); } @@ -474,7 +490,8 @@ function ($constant_name) use ($const_name_part): bool { $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); } } @@ -494,7 +511,8 @@ function ($constant_name) use ($const_name_part): bool { $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); } } @@ -509,7 +527,8 @@ function ($constant_name) use ($const_name_part): bool { $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); } } @@ -524,7 +543,8 @@ function ($constant_name) use ($const_name_part): bool { $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); } @@ -643,7 +663,8 @@ private static function expandConditional( bool $evaluate_class_constants = true, bool $evaluate_conditional_types = false, bool $final = false, - bool &$expand_generic = false + bool &$expand_generic = false, + bool $expand_templates = false ) { $new_as_type = self::expandUnion( $codebase, @@ -654,7 +675,8 @@ private static function expandConditional( $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); $return_type->as_type = $new_as_type; @@ -673,7 +695,8 @@ private static function expandConditional( $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); if (!is_array($candidate)) { @@ -694,7 +717,8 @@ private static function expandConditional( $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); $candidate_types = is_array($candidate) ? $candidate : [$candidate]; @@ -717,7 +741,8 @@ private static function expandConditional( $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); $candidate_types = is_array($candidate) ? $candidate : [$candidate]; @@ -793,7 +818,8 @@ private static function expandConditional( $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); $return_type->if_type = self::expandUnion( @@ -805,7 +831,8 @@ private static function expandConditional( $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); $return_type->else_type = self::expandUnion( @@ -817,7 +844,8 @@ private static function expandConditional( $evaluate_class_constants, $evaluate_conditional_types, $final, - $expand_generic + $expand_generic, + $expand_templates ); return $return_type; diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index a640f2f9216..adcb11ef5d6 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -2293,8 +2293,21 @@ public function map(callable $callback) { /** @param ArrayCollection $ints */ function takesInts(ArrayCollection $ints) :void {} + /** @param ArrayCollection $ints */ + function takesIntsOrStrings(ArrayCollection $ints) :void {} + takesInts((new ArrayCollection([ "a", "bc" ]))->map("strlen")); + /** @return ($s is "string" ? string : int) */ + function foo(string $s) { + if ($s === "string") { + return "hello"; + } + return 5; + } + + takesIntsOrStrings((new ArrayCollection([ "a", "bc" ]))->map("foo")); + /** * @template T * @extends ArrayCollection