From 117a4d4d4001938bb2f11df51f2ada1b99933cc6 Mon Sep 17 00:00:00 2001 From: Brown Date: Thu, 30 Apr 2020 23:35:13 -0400 Subject: [PATCH] Require callable() have a return type when in conditional Fixes #3260 --- src/Psalm/Internal/Type/ParseTree.php | 4 ++++ src/Psalm/Type.php | 4 ++++ tests/TypeParseTest.php | 34 ++++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/ParseTree.php b/src/Psalm/Internal/Type/ParseTree.php index ebd9a9b99..f285335d0 100644 --- a/src/Psalm/Internal/Type/ParseTree.php +++ b/src/Psalm/Internal/Type/ParseTree.php @@ -340,6 +340,10 @@ class ParseTree } if ($current_parent && $current_parent instanceof ParseTree\ConditionalTree) { + if (count($current_parent->children) > 1) { + throw new TypeParseTreeException('Cannot process colon in conditional twice'); + } + $current_leaf = $current_parent; $current_parent = $current_parent->parent; break; diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index ec2d47579..e9972d1f9 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -859,6 +859,10 @@ abstract class Type throw new TypeParseTreeException('Unrecognized template \'' . $template_param_name . '\''); } + if (count($parse_tree->children) !== 2) { + throw new TypeParseTreeException('Invalid conditional'); + } + $first_class = array_keys($template_type_map[$template_param_name])[0]; $conditional_type = self::getTypeFromTree( diff --git a/tests/TypeParseTest.php b/tests/TypeParseTest.php index 72db8eb94..82764d5ed 100644 --- a/tests/TypeParseTest.php +++ b/tests/TypeParseTest.php @@ -714,6 +714,26 @@ class TypeParseTest extends TestCase ); } + /** + * @return void + */ + public function testConditionalTypeWithCallableElseBool() + { + $this->expectException(\Psalm\Exception\TypeParseTreeException::class); + Type::parseString('(T is string ? callable() : bool)', null, ['T' => ['' => [Type::getArray()]]]); + } + + /** + * @return void + */ + public function testConditionalTypeWithCallableReturningBoolElseBool() + { + $this->assertSame( + '(T is string ? callable():bool : bool)', + (string) Type::parseString('(T is string ? (callable() : bool) : bool)', null, ['T' => ['' => [Type::getArray()]]]) + ); + } + public function testConditionalTypeWithGenerics() : void { $this->assertSame( @@ -726,7 +746,19 @@ class TypeParseTest extends TestCase ); } - public function testConditionalTypeWithCallable() : void + public function testConditionalTypeWithCallableBracketed() : void + { + $this->assertSame( + '(T is string ? callable(string, string):string : callable(mixed...):mixed)', + (string) Type::parseString( + '(T is string ? (callable(string, string):string) : (callable(mixed...):mixed))', + null, + ['T' => ['' => [Type::getArray()]]] + ) + ); + } + + public function testConditionalTypeWithCallableNotBracketed() : void { $this->assertSame( '(T is string ? callable(string, string):string : callable(mixed...):mixed)',