From 6a9bcea9019fc18c1bcc53377a539f744a687252 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Mon, 12 Sep 2016 11:32:44 -0400 Subject: [PATCH] Improve typing of properties --- src/Psalm/Checker/ClassLikeChecker.php | 28 ++++++-- src/Psalm/Checker/StatementsChecker.php | 92 ++++++++++++++++++++----- 2 files changed, 98 insertions(+), 22 deletions(-) diff --git a/src/Psalm/Checker/ClassLikeChecker.php b/src/Psalm/Checker/ClassLikeChecker.php index 1a5fd061e..8bee73d28 100644 --- a/src/Psalm/Checker/ClassLikeChecker.php +++ b/src/Psalm/Checker/ClassLikeChecker.php @@ -342,9 +342,21 @@ abstract class ClassLikeChecker implements StatementsSource $type_in_comment = CommentChecker::getTypeFromComment((string) $comment, null, $this); } - $property_type = $type_in_comment ? $type_in_comment : Type::getMixed(); + $property_group_type = $type_in_comment ? $type_in_comment : null; foreach ($stmt->props as $property) { + if (!$property_group_type) { + if (!$property->default) { + $property_type = Type::getMixed(); + } + else { + $property_type = StatementsChecker::getSimpleType($property->default) ?: Type::getMixed(); + } + } + else { + $property_type = $property_group_type; + } + if ($stmt->isStatic()) { if ($stmt->isPublic()) { self::$public_static_class_properties[$class_context->self][$property->name] = $property_type; @@ -367,10 +379,6 @@ abstract class ClassLikeChecker implements StatementsSource self::$private_class_properties[$class_context->self][$property->name] = $property_type; } } - - if (!$stmt->isStatic()) { - $class_context->vars_in_scope['this->' . $property->name] = $property_type; - } } } elseif ($stmt instanceof PhpParser\Node\Stmt\ClassConst) { @@ -400,6 +408,16 @@ abstract class ClassLikeChecker implements StatementsSource (new StatementsChecker($this))->check($leftover_stmts, $class_context); } + $all_instance_properties = array_merge( + self::$public_class_properties[$this->absolute_class], + self::$protected_class_properties[$this->absolute_class], + self::$private_class_properties[$this->absolute_class] + ); + + foreach ($all_instance_properties as $property_name => $property_type) { + $class_context->vars_in_scope['this->' . $property_name] = $property_type; + } + $config = Config::getInstance(); if ($check_methods) { diff --git a/src/Psalm/Checker/StatementsChecker.php b/src/Psalm/Checker/StatementsChecker.php index a6df5c4ec..eed81520c 100644 --- a/src/Psalm/Checker/StatementsChecker.php +++ b/src/Psalm/Checker/StatementsChecker.php @@ -1129,6 +1129,50 @@ class StatementsChecker } } + public static function getSimpleType(PhpParser\Node\Expr $stmt) + { + if ($stmt instanceof PhpParser\Node\Expr\ConstFetch) { + // @todo support this + } + elseif ($stmt instanceof PhpParser\Node\Expr\ClassConstFetch) { + // @todo support this as well + } + elseif ($stmt instanceof PhpParser\Node\Scalar\String_) { + return Type::getString(); + } + elseif ($stmt instanceof PhpParser\Node\Scalar\LNumber) { + return Type::getInt(); + } + elseif ($stmt instanceof PhpParser\Node\Scalar\DNumber) { + return Type::getFloat(); + } + elseif ($stmt instanceof PhpParser\Node\Expr\Array_) { + return Type::getArray(); + } + elseif ($stmt instanceof PhpParser\Node\Expr\Cast\Int_) { + return Type::getInt(); + } + elseif ($stmt instanceof PhpParser\Node\Expr\Cast\Double) { + return Type::getFloat(); + } + elseif ($stmt instanceof PhpParser\Node\Expr\Cast\Bool_) { + return Type::getBool(); + } + elseif ($stmt instanceof PhpParser\Node\Expr\Cast\String_) { + return Type::getString(); + } + elseif ($stmt instanceof PhpParser\Node\Expr\Cast\Object_) { + return Type::getObject(); + } + elseif ($stmt instanceof PhpParser\Node\Expr\Cast\Array_) { + return Type::getArray(); + } + else { + var_dump('Unrecognised default property type in ' . $this->checked_file_name); + var_dump($stmt); + } + } + /** * @param PhpParser\Node\Expr\Variable|PhpParser\Node\Expr\PropertyFetch $stmt * @param string $method_id @@ -2232,29 +2276,36 @@ class StatementsChecker } } - if ($nesting) { - $context_type = clone $context->vars_in_scope[$var_id]; + if ($return_type) { + if ($nesting && $var_id) { + $context_type = clone $context->vars_in_scope[$var_id]; - $array_type = $context_type; + $array_type = $context_type; - for ($i = 0; $i < $nesting + 1; $i++) { - if ($i < $nesting) { - if ($array_type->types['array']->type_params[1]->isEmpty()) { - $array_type->types['array']->type_params[1] = $return_type; - break; + for ($i = 0; $i < $nesting + 1; $i++) { + if ($array_type->isArray()) { + if ($i < $nesting) { + if ($array_type->types['array']->type_params[1]->isEmpty()) { + $array_type->types['array']->type_params[1] = $return_type; + break; + } + + $array_type = $array_type->types['array']->type_params[1]; + } + else { + $array_type->types['array']->type_params[1] = $return_type->types['array']->type_params[1]; + } } + } - $array_type = $array_type->types['array']->type_params[1]; - } - else { - $array_type->types['array']->type_params[1] = $return_type->types['array']->type_params[1]; - } + $context->vars_in_scope[$var_id] = $context_type; + } + else { + $context->vars_in_scope[$var_id] = $return_type; } - - $context->vars_in_scope[$var_id] = $context_type; } else { - $context->vars_in_scope[$var_id] = $return_type; + $context->vars_in_scope[$var_id] = Type::getMixed(); } } } @@ -2289,10 +2340,15 @@ class StatementsChecker } } + if ($type->isMixed()) { + // @todo emit issue + return; + } + if ($type->value !== 'array' && !ClassChecker::classImplements($type->value, 'ArrayAccess')) { if (IssueBuffer::accepts( new InvalidArrayAssignment( - 'Cannot assign value on variable ' . $var_id . ' that does not implement ArrayAccess', + 'Cannot assign value on variable $' . $var_id . ' of type ' . $type->value . ' that does not implement ArrayAccess', $this->checked_file_name, $line_number ), @@ -3621,6 +3677,8 @@ class StatementsChecker return false; } } + + $stmt->inferredType = Type::getString(); } public function registerVariable($var_name, $line_number)