From 1037485a603b71cb4cc31c01e32a3af27fa35a34 Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Tue, 28 Aug 2018 12:37:25 -0400 Subject: [PATCH] Convert InvalidScalarArgument to InvalidArgument when declare(strict_types=1) is set --- src/Psalm/Checker/TypeChecker.php | 1 + src/Psalm/Type.php | 22 +++++--- src/Psalm/Type/Atomic.php | 53 ++++++++++++++----- src/Psalm/Type/Atomic/Scalar.php | 3 ++ src/Psalm/Visitor/DependencyFinderVisitor.php | 20 +++++-- tests/FunctionCallTest.php | 6 +++ 6 files changed, 83 insertions(+), 22 deletions(-) diff --git a/src/Psalm/Checker/TypeChecker.php b/src/Psalm/Checker/TypeChecker.php index df7d0d5e5..86858b68c 100644 --- a/src/Psalm/Checker/TypeChecker.php +++ b/src/Psalm/Checker/TypeChecker.php @@ -733,6 +733,7 @@ class TypeChecker if ($input_type_part instanceof Scalar) { if ($container_type_part instanceof Scalar + && !$container_type_part->strict && !$container_type_part instanceof TLiteralInt && !$container_type_part instanceof TLiteralString && !$container_type_part instanceof TLiteralFloat diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index 578f42dd4..e89d41571 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -76,12 +76,17 @@ abstract class Type * @param string $type_string * @param bool $php_compatible * @param array $template_type_names + * @param bool $strict_types * * @return Union */ - public static function parseString($type_string, $php_compatible = false, array $template_type_names = []) - { - return self::parseTokens(self::tokenize($type_string), $php_compatible, $template_type_names); + public static function parseString( + $type_string, + $php_compatible = false, + array $template_type_names = [], + $strict_types = false + ) { + return self::parseTokens(self::tokenize($type_string), $php_compatible, $template_type_names, $strict_types); } /** @@ -90,11 +95,16 @@ abstract class Type * @param array $type_tokens * @param bool $php_compatible * @param array $template_type_names + * @param bool $strict_types * * @return Union */ - public static function parseTokens(array $type_tokens, $php_compatible = false, array $template_type_names = []) - { + public static function parseTokens( + array $type_tokens, + $php_compatible = false, + array $template_type_names = [], + $strict_types = false + ) { if (count($type_tokens) === 1) { $only_token = $type_tokens[0]; @@ -105,7 +115,7 @@ abstract class Type $only_token = self::fixScalarTerms($only_token, $php_compatible); - return new Union([Atomic::create($only_token, $php_compatible, $template_type_names)]); + return new Union([Atomic::create($only_token, $php_compatible, $template_type_names, $strict_types)]); } try { diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index ea7e84f77..d1f7f68ca 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -54,27 +54,56 @@ abstract class Atomic * @param string $value * @param bool $php_compatible * @param array $template_type_names + * @param bool $strict_types * * @return Atomic */ - public static function create($value, $php_compatible = false, array $template_type_names = []) - { + public static function create( + $value, + $php_compatible = false, + array $template_type_names = [], + $strict_types = false + ) { switch ($value) { case 'int': - return new TInt(); + $t = new TInt(); + + if ($strict_types) { + $t->strict = true; + } + + return $t; + + case 'float': + $t = new TFloat(); + + if ($strict_types) { + $t->strict = true; + } + + return $t; + + case 'string': + $t = new TString(); + + if ($strict_types) { + $t->strict = true; + } + + return $t; + + case 'bool': + $t = new TBool(); + + if ($strict_types) { + $t->strict = true; + } + + return $t; case 'void': return new TVoid(); - case 'float': - return new TFloat(); - - case 'string': - return new TString(); - - case 'bool': - return new TBool(); - case 'object': return new TObject(); diff --git a/src/Psalm/Type/Atomic/Scalar.php b/src/Psalm/Type/Atomic/Scalar.php index 704d7d5e7..d551dccad 100644 --- a/src/Psalm/Type/Atomic/Scalar.php +++ b/src/Psalm/Type/Atomic/Scalar.php @@ -3,6 +3,9 @@ namespace Psalm\Type\Atomic; abstract class Scalar extends \Psalm\Type\Atomic { + /** @var true|null */ + public $strict; + /** * @param string|null $namespace * @param array $aliased_classes diff --git a/src/Psalm/Visitor/DependencyFinderVisitor.php b/src/Psalm/Visitor/DependencyFinderVisitor.php index 21e368676..d76eb6f7e 100644 --- a/src/Psalm/Visitor/DependencyFinderVisitor.php +++ b/src/Psalm/Visitor/DependencyFinderVisitor.php @@ -83,6 +83,9 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P /** @var string[] */ private $after_classlike_check_plugins; + /** @var bool */ + private $strict_types = false; + /** * @var array> */ @@ -450,6 +453,15 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P if ($function_like_storage) { $function_like_storage->has_yield = true; } + } elseif ($node instanceof PhpParser\Node\Stmt\Declare_) { + foreach ($node->declares as $declaration) { + if ((string) $declaration->key === 'strict_types' + && $declaration->value instanceof PhpParser\Node\Scalar\LNumber + && $declaration->value->value === 1 + ) { + $this->strict_types = true; + } + } } } @@ -1418,11 +1430,11 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P } if ($param_type_string) { - if ($is_nullable) { - $param_type_string .= '|null'; - } + $param_type = Type::parseString($param_type_string, true, [], $this->strict_types); - $param_type = Type::parseString($param_type_string, true); + if ($is_nullable) { + $param_type->addType(new Type\Atomic\TNull); + } if ($param->variadic) { $param_type = new Type\Union([ diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index 4dd6e219f..042969481 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -956,6 +956,12 @@ class FunctionCallTest extends TestCase fooFoo("string");', 'error_message' => 'InvalidScalarArgument', ], + 'invalidArgumentWithDeclareStrictTypes' => [ + ' 'InvalidArgument', + ], 'mixedArgument' => [ '