mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Add checking of object properties
This commit is contained in:
parent
9f20175b14
commit
a1fb6294dc
@ -108,7 +108,12 @@ class ClassChecker implements StatementsSource
|
||||
} else {
|
||||
if ($stmt instanceof PhpParser\Node\Stmt\Property) {
|
||||
foreach ($stmt->props as $property) {
|
||||
$this->_class_properties[] = $property->name;
|
||||
$comment = $stmt->getDocComment();
|
||||
$type_in_comment = null;
|
||||
if ($comment) {
|
||||
$type_in_comment = CommentChecker::getTypeFromComment($comment, null, $this);
|
||||
}
|
||||
$this->_class_properties[$property->name] = $type_in_comment ? Type::parseString($type_in_comment) : Type::getMixed();
|
||||
}
|
||||
}
|
||||
$leftover_stmts[] = $stmt;
|
||||
@ -310,7 +315,7 @@ class ClassChecker implements StatementsSource
|
||||
return $this->_has_custom_get;
|
||||
}
|
||||
|
||||
public function getPropertyNames()
|
||||
public function getProperties()
|
||||
{
|
||||
return $this->_class_properties;
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ class ClassMethodChecker extends FunctionChecker
|
||||
protected static $_existing_methods = [];
|
||||
protected static $_have_reflected = [];
|
||||
protected static $_have_registered = [];
|
||||
protected static $_method_custom_calls = [];
|
||||
protected static $_inherited_methods = [];
|
||||
protected static $_declaring_class = [];
|
||||
protected static $_method_visibility = [];
|
||||
@ -29,8 +28,6 @@ class ClassMethodChecker extends FunctionChecker
|
||||
const VISIBILITY_PROTECTED = 2;
|
||||
const VISIBILITY_PRIVATE = 3;
|
||||
|
||||
const TYPE_REGEX = '(\\\?[A-Za-z0-9\<\>\[\]|\\\]+[A-Za-z0-9\<\>\[\]]|\$[a-zA-Z_0-9\<\>\|\[\]]+)';
|
||||
|
||||
public function __construct(PhpParser\Node\FunctionLike $function, StatementsSource $source, array $this_vars = [])
|
||||
{
|
||||
parent::__construct($function, $source);
|
||||
@ -238,36 +235,16 @@ class ClassMethodChecker extends FunctionChecker
|
||||
|
||||
$return_types = null;
|
||||
|
||||
$comments = StatementsChecker::parseDocComment($method->getDocComment() ?: '');
|
||||
$docblock_info = CommentChecker::extractDocblockInfo($method->getDocComment());
|
||||
|
||||
if ($comments) {
|
||||
if (isset($comments['specials']['return'])) {
|
||||
$return_blocks = explode(' ', $comments['specials']['return'][0]);
|
||||
foreach ($return_blocks as $block) {
|
||||
if ($block && preg_match('/^' . self::TYPE_REGEX . '$/', $block) && !preg_match('/\[[^\]]+\]/', $block)) {
|
||||
$return_types = $block;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($comments['specials']['call'])) {
|
||||
self::$_method_custom_calls[$method_id] = [];
|
||||
|
||||
$call_blocks = $comments['specials']['call'];
|
||||
foreach ($comments['specials']['call'] as $block) {
|
||||
if ($block) {
|
||||
self::$_method_custom_calls[$method_id][] = trim($block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($return_types) {
|
||||
$return_types = self::_fixUpReturnType($return_types, $method_id);
|
||||
}
|
||||
if ($docblock_info['return_type']) {
|
||||
self::$_method_return_types[$method_id] = Type::parseString(
|
||||
self::_fixUpReturnType($docblock_info['return_type'], $method_id)
|
||||
);
|
||||
}
|
||||
else {
|
||||
self::$_method_return_types[$method_id] = null;
|
||||
}
|
||||
|
||||
self::$_method_return_types[$method_id] = $return_types ? Type::parseString($return_types) : null;
|
||||
}
|
||||
|
||||
protected static function _copyToChildMethod($method_id, $child_method_id)
|
||||
@ -322,39 +299,19 @@ class ClassMethodChecker extends FunctionChecker
|
||||
self::VISIBILITY_PRIVATE :
|
||||
($method->isProtected() ? self::VISIBILITY_PROTECTED : self::VISIBILITY_PUBLIC);
|
||||
|
||||
$comments = StatementsChecker::parseDocComment($method->getDocComment());
|
||||
|
||||
$return_types = null;
|
||||
|
||||
if (isset($comments['specials']['return'])) {
|
||||
$return_blocks = explode(' ', $comments['specials']['return'][0]);
|
||||
foreach ($return_blocks as $block) {
|
||||
if ($block) {
|
||||
if ($block && preg_match('/^' . self::TYPE_REGEX . '$/', $block) && !preg_match('/\[[^\]]+\]/', $block)) {
|
||||
$return_types = $block;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$docblock_info = CommentChecker::extractDocblockInfo($method->getDocComment());
|
||||
|
||||
if ($docblock_info['return_type']) {
|
||||
self::$_method_return_types[$method_id] = Type::parseString(
|
||||
$this->_fixUpLocalReturnType($docblock_info['return_type'], $method_id, $this->_namespace, $this->_aliased_classes)
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($comments['specials']['call'])) {
|
||||
self::$_method_custom_calls[$method_id] = [];
|
||||
|
||||
$call_blocks = $comments['specials']['call'];
|
||||
foreach ($comments['specials']['call'] as $block) {
|
||||
if ($block) {
|
||||
self::$_method_custom_calls[$method_id][] = trim($block);
|
||||
}
|
||||
}
|
||||
else {
|
||||
self::$_method_return_types[$method_id] = null;
|
||||
}
|
||||
|
||||
if ($return_types) {
|
||||
$return_types = $this->_fixUpLocalReturnType($return_types, $method_id, $this->_namespace, $this->_aliased_classes);
|
||||
}
|
||||
|
||||
self::$_method_return_types[$method_id] = $return_types ? Type::parseString($return_types) : null;
|
||||
|
||||
self::$_method_params[$method_id] = [];
|
||||
|
||||
foreach ($method->getParams() as $param) {
|
||||
@ -603,7 +560,6 @@ class ClassMethodChecker extends FunctionChecker
|
||||
self::$_existing_methods = [];
|
||||
self::$_have_reflected = [];
|
||||
self::$_have_registered = [];
|
||||
self::$_method_custom_calls = [];
|
||||
self::$_inherited_methods = [];
|
||||
self::$_declaring_class = [];
|
||||
self::$_method_visibility = [];
|
||||
|
90
src/CodeInspector/CommentChecker.php
Normal file
90
src/CodeInspector/CommentChecker.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace CodeInspector;
|
||||
|
||||
class CommentChecker
|
||||
{
|
||||
const TYPE_REGEX = '(\\\?[A-Za-z0-9\<\>\[\]|\\\]+[A-Za-z0-9\<\>\[\]]|\$[a-zA-Z_0-9\<\>\|\[\]]+)';
|
||||
|
||||
/**
|
||||
* @param string $comment
|
||||
* @param Context $context
|
||||
* @param StatementsSource $source
|
||||
* @param string $var_id
|
||||
* @return Type\Union|null
|
||||
*/
|
||||
public static function getTypeFromComment($comment, Context $context = null, StatementsSource $source, $var_id = null)
|
||||
{
|
||||
$type_in_comments_var_id = null;
|
||||
|
||||
$type_in_comments = null;
|
||||
|
||||
$comments = StatementsChecker::parseDocComment($comment);
|
||||
|
||||
if ($comments && isset($comments['specials']['var'][0])) {
|
||||
$var_parts = array_filter(preg_split('/[\s\t]+/', $comments['specials']['var'][0]));
|
||||
|
||||
if ($var_parts) {
|
||||
$type_in_comments = $var_parts[0];
|
||||
|
||||
if ($type_in_comments[0] === strtoupper($type_in_comments[0])) {
|
||||
$type_in_comments = ClassChecker::getAbsoluteClassFromString($type_in_comments, $source->getNamespace(), $source->getAliasedClasses());
|
||||
}
|
||||
|
||||
// support PHPStorm-style docblocks like
|
||||
// @var Type $variable
|
||||
if (count($var_parts) > 1 && $var_parts[1][0] === '$') {
|
||||
$type_in_comments_var_id = substr($var_parts[1], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$type_in_comments) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$defined_type = Type::parseString($type_in_comments);
|
||||
|
||||
if ($context && $type_in_comments_var_id && $type_in_comments_var_id !== $var_id) {
|
||||
if (isset($context->vars_in_scope[$type_in_comments_var_id])) {
|
||||
$context->vars_in_scope[$type_in_comments_var_id] = $defined_type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $defined_type;
|
||||
}
|
||||
|
||||
public static function extractDocblockInfo($comment)
|
||||
{
|
||||
$comments = StatementsChecker::parseDocComment($comment);
|
||||
|
||||
$info = ['return_type' => null, 'params' => null];
|
||||
|
||||
if (isset($comments['specials']['return'])) {
|
||||
$return_blocks = preg_split('/[\s]+/', $comments['specials']['return'][0]);
|
||||
|
||||
if (preg_match('/^' . self::TYPE_REGEX . '$/', $return_blocks[0]) && !preg_match('/\[[^\]]+\]/', $return_blocks[0])) {
|
||||
$info['return_type'] = $return_blocks[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($comments['specials']['param'])) {
|
||||
foreach ($comments['specials']['param'] as $param) {
|
||||
$param_blocks = preg_split('/[\s]+/', $param);
|
||||
|
||||
if (count($param_blocks) > 1 &&
|
||||
preg_match('/^' . self::TYPE_REGEX . '$/', $param_blocks[0]) &&
|
||||
!preg_match('/\[[^\]]+\]/', $param_blocks[0]) &&
|
||||
preg_match('/^\$[A-Za-z0-9_]+$/', $param_blocks[1])
|
||||
) {
|
||||
$info['params'] = ['name' => $param_blocks[1], 'type' => $param_blocks[0]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
|
||||
}
|
||||
}
|
@ -15,9 +15,13 @@ class FileFilter
|
||||
/** @var array<string> */
|
||||
protected $include_files = [];
|
||||
|
||||
protected $include_files_lowercase = [];
|
||||
|
||||
/** @var array<string> */
|
||||
protected $exclude_files = [];
|
||||
|
||||
protected $exclude_files_lowercase = [];
|
||||
|
||||
/** @var array<string> */
|
||||
protected $include_patterns = [];
|
||||
|
||||
@ -47,6 +51,7 @@ class FileFilter
|
||||
if ($e->file) {
|
||||
foreach ($e->file as $file) {
|
||||
$filter->include_files[] = $file['name'];
|
||||
$filter->include_files_lowercase[] = strtolower($file['name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -60,6 +65,7 @@ class FileFilter
|
||||
if ($e->file) {
|
||||
foreach ($e->file as $file) {
|
||||
$filter->exclude_files[] = $file['name'];
|
||||
$filter->exclude_files_lowercase[] = strtolower($file['name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,17 +78,32 @@ class FileFilter
|
||||
return preg_replace('/\/?$/', '/', $str);
|
||||
}
|
||||
|
||||
public function allows($file_name)
|
||||
public function allows($file_name, $case_sensitive = false)
|
||||
{
|
||||
if ($this->inclusive) {
|
||||
foreach ($this->include_dirs as $include_dir) {
|
||||
if (strpos($file_name, $include_dir) === 0) {
|
||||
if ($case_sensitive) {
|
||||
if (strpos($file_name, $include_dir) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (stripos($file_name, $include_dir) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($case_sensitive) {
|
||||
if (in_array($file_name, $this->include_files)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($file_name, $this->include_files)) {
|
||||
return true;
|
||||
else {
|
||||
if (in_array(strtolower($file_name), $this->include_files_lowercase)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -90,13 +111,27 @@ class FileFilter
|
||||
|
||||
// exclusive
|
||||
foreach ($this->exclude_dirs as $exclude_dir) {
|
||||
if (strpos($file_name, $exclude_dir) === 0) {
|
||||
return false;
|
||||
if ($case_sensitive) {
|
||||
if (strpos($file_name, $exclude_dir) === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (stripos($file_name, $exclude_dir) === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($file_name, $this->exclude_files)) {
|
||||
return false;
|
||||
if ($case_sensitive) {
|
||||
if (in_array($file_name, $this->exclude_files)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (in_array(strtolower($file_name), $this->exclude_files_lowercase)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
7
src/CodeInspector/Issue/InvalidPropertyAssignment.php
Normal file
7
src/CodeInspector/Issue/InvalidPropertyAssignment.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace CodeInspector\Issue;
|
||||
|
||||
class InvalidPropertyAssignment extends CodeError
|
||||
{
|
||||
}
|
@ -14,6 +14,7 @@ use CodeInspector\Issue\ParentNotFound;
|
||||
use CodeInspector\Issue\PossiblyUndefinedVariable;
|
||||
use CodeInspector\Issue\InvalidArrayAssignment;
|
||||
use CodeInspector\Issue\InvalidArrayAccess;
|
||||
use CodeInspector\Issue\InvalidPropertyAssignment;
|
||||
use CodeInspector\Issue\InvalidScope;
|
||||
use CodeInspector\Issue\InvalidStaticInvocation;
|
||||
use CodeInspector\Issue\InvalidStaticVariable;
|
||||
@ -194,6 +195,12 @@ class StatementsChecker
|
||||
foreach ($stmt->props as $prop) {
|
||||
if ($prop->default) {
|
||||
$this->_checkExpression($prop->default, $context);
|
||||
|
||||
if (isset($prop->default->inferredType)) {
|
||||
if ($this->_checkThisPropertyAssignment($prop->name, $prop->default->inferredType, $stmt->getLine()) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::$_existing_static_vars[$this->_absolute_class . '::$' . $prop->name] = 1;
|
||||
@ -1057,15 +1064,19 @@ class StatementsChecker
|
||||
}
|
||||
}
|
||||
|
||||
$var_name = is_string($stmt->name) ? $stmt->name : null;
|
||||
$var_id = self::getVarId($stmt);
|
||||
$property_names = $class_checker->getPropertyNames();
|
||||
$defined_properties = $class_checker->getProperties();
|
||||
$this_class = $context->vars_in_scope['this'];
|
||||
|
||||
if (isset($context->vars_in_scope[$var_id])) {
|
||||
$stmt->inferredType = $context->vars_in_scope[$var_id];
|
||||
}
|
||||
elseif ($var_name && isset($defined_properties[$var_name])) {
|
||||
$stmt->inferredType = $defined_properties[$var_name];
|
||||
}
|
||||
|
||||
if (!in_array($stmt->name, $property_names)) {
|
||||
if (!$var_name || !isset($defined_properties[$var_name])) {
|
||||
$property_id = $this_class . '::' . $stmt->name;
|
||||
|
||||
$var_defined = isset($context->vars_in_scope[$var_id]) || isset($context->vars_possibly_in_scope[$var_id]);
|
||||
@ -1091,6 +1102,39 @@ class StatementsChecker
|
||||
}
|
||||
}
|
||||
|
||||
protected function _checkThisPropertyAssignment($prop_name, Type\Union $assignment_type, $line_number)
|
||||
{
|
||||
if ($assignment_type->isMixed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$class_checker = $this->_source->getClassChecker();
|
||||
|
||||
if ($class_checker) {
|
||||
$properties = $class_checker->getProperties();
|
||||
|
||||
if (isset($properties[$prop_name])) {
|
||||
$property_type = $properties[$prop_name];
|
||||
|
||||
if ($property_type->isMixed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$assignment_type->isIn($property_type)) {
|
||||
if (IssueHandler::accepts(
|
||||
new InvalidPropertyAssignment(
|
||||
'$this->' . $prop_name . ' with declared type \'' . $property_type . '\' cannot be assigned type \'' . $assignment_type . '\'',
|
||||
$this->_file_name,
|
||||
$line_number
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function _checkNew(PhpParser\Node\Expr\New_ $stmt, Context $context)
|
||||
{
|
||||
$absolute_class = null;
|
||||
@ -1485,39 +1529,7 @@ class StatementsChecker
|
||||
return false;
|
||||
}
|
||||
|
||||
$type_in_comments = null;
|
||||
$type_in_comments_var_id = null;
|
||||
$doc_comment = $stmt->getDocComment();
|
||||
|
||||
if ($doc_comment) {
|
||||
$comments = self::parseDocComment($doc_comment);
|
||||
|
||||
if ($comments && isset($comments['specials']['var'][0])) {
|
||||
$var_parts = array_filter(preg_split('/[\s\t]+/', $comments['specials']['var'][0]));
|
||||
|
||||
if ($var_parts) {
|
||||
$type_in_comments = $var_parts[0];
|
||||
|
||||
if ($type_in_comments[0] === strtoupper($type_in_comments[0])) {
|
||||
$type_in_comments = ClassChecker::getAbsoluteClassFromString($type_in_comments, $this->_namespace, $this->_aliased_classes);
|
||||
}
|
||||
|
||||
// support PHPStorm-style docblocks like
|
||||
// @var Type $variable
|
||||
if (count($var_parts) > 1 && $var_parts[1][0] === '$') {
|
||||
$type_in_comments_var_id = substr($var_parts[1], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($type_in_comments_var_id && $type_in_comments_var_id !== $var_id) {
|
||||
if (isset($context->vars_in_scope[$type_in_comments_var_id])) {
|
||||
$context->vars_in_scope[$type_in_comments_var_id] = Type::parseString($type_in_comments);
|
||||
}
|
||||
|
||||
$type_in_comments = null;
|
||||
}
|
||||
$type_in_comments = CommentChecker::getTypeFromComment($stmt->getDocComment(), $context, $this->_source, $var_id);
|
||||
|
||||
if ($type_in_comments) {
|
||||
$return_type = Type::parseString($type_in_comments);
|
||||
@ -1558,6 +1570,8 @@ class StatementsChecker
|
||||
|
||||
$method_id = $this->_source->getMethodId();
|
||||
|
||||
$this->_checkThisPropertyAssignment($stmt->var->name, $return_type, $stmt->getLine());
|
||||
|
||||
if (!isset(self::$_this_assignments[$method_id])) {
|
||||
self::$_this_assignments[$method_id] = [];
|
||||
}
|
||||
@ -2263,39 +2277,7 @@ class StatementsChecker
|
||||
|
||||
protected function _checkReturn(PhpParser\Node\Stmt\Return_ $stmt, Context $context)
|
||||
{
|
||||
$type_in_comments = null;
|
||||
$type_in_comments_var_id = null;
|
||||
$doc_comment = $stmt->getDocComment();
|
||||
|
||||
if ($doc_comment) {
|
||||
$comments = self::parseDocComment($doc_comment);
|
||||
|
||||
if ($comments && isset($comments['specials']['var'][0])) {
|
||||
$var_parts = array_filter(preg_split('/[\s\t]+/', $comments['specials']['var'][0]));
|
||||
|
||||
if ($var_parts) {
|
||||
$type_in_comments = $var_parts[0];
|
||||
|
||||
if ($type_in_comments[0] === strtoupper($type_in_comments[0])) {
|
||||
$type_in_comments = ClassChecker::getAbsoluteClassFromString($type_in_comments, $this->_namespace, $this->_aliased_classes);
|
||||
}
|
||||
|
||||
// support PHPStorm-style docblocks like
|
||||
// @var Type $variable
|
||||
if (count($var_parts) > 1 && $var_parts[1][0] === '$') {
|
||||
$type_in_comments_var_id = substr($var_parts[1], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($type_in_comments_var_id) {
|
||||
if (isset($context->vars_in_scope[$type_in_comments_var_id])) {
|
||||
$context->vars_in_scope[$type_in_comments_var_id] = Type::parseString($type_in_comments);
|
||||
}
|
||||
|
||||
$type_in_comments = null;
|
||||
}
|
||||
$type_in_comments = CommentChecker::getTypeFromComment($stmt->getDocComment(), $context, $this->_source);
|
||||
|
||||
if ($stmt->expr) {
|
||||
if ($this->_checkExpression($stmt->expr, $context) === false) {
|
||||
@ -2347,22 +2329,6 @@ class StatementsChecker
|
||||
if ($this->_checkExpression($stmt->if, $t_if_context) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$if_return_type = isset($stmt->if->inferredType) ? $stmt->if->inferredType : Type::getMixed();
|
||||
}
|
||||
else {
|
||||
if (isset($stmt->cond->inferredType)) {
|
||||
$if_return_type_reconciled = TypeChecker::reconcileTypes('!empty', $stmt->cond->inferredType, $this->_file_name, $stmt->getLine());
|
||||
|
||||
if ($if_return_type_reconciled === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$if_return_type = $if_return_type_reconciled;
|
||||
}
|
||||
else {
|
||||
$if_return_type = Type::getMixed();
|
||||
}
|
||||
}
|
||||
|
||||
$t_else_context = clone $context;
|
||||
@ -2390,7 +2356,13 @@ class StatementsChecker
|
||||
}
|
||||
elseif ($stmt->cond) {
|
||||
if (isset($stmt->cond->inferredType)) {
|
||||
$lhs_type = $stmt->cond->inferredType;
|
||||
$if_return_type_reconciled = TypeChecker::reconcileTypes('!empty', $stmt->cond->inferredType, $this->_file_name, $stmt->getLine());
|
||||
|
||||
if ($if_return_type_reconciled === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lhs_type = $if_return_type_reconciled;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,13 @@ abstract class Type
|
||||
$type_tokens = TypeChecker::tokenize($type_string);
|
||||
|
||||
if (count($type_tokens) === 1) {
|
||||
if ($type_tokens[0] === 'boolean') {
|
||||
$type_tokens[0] = 'bool';
|
||||
}
|
||||
elseif ($type_tokens[0] === 'integer') {
|
||||
$type_tokens[0] = 'int';
|
||||
}
|
||||
|
||||
$parsed_type = new Atomic($type_tokens[0]);
|
||||
|
||||
if ($enclose_with_union) {
|
||||
@ -93,6 +100,13 @@ abstract class Type
|
||||
break;
|
||||
|
||||
default:
|
||||
if ($type_token === 'boolean') {
|
||||
$type_token = 'bool';
|
||||
}
|
||||
elseif ($type_token === 'integer') {
|
||||
$type_token = 'int';
|
||||
}
|
||||
|
||||
if ($current_leaf->value === null) {
|
||||
$current_leaf->value = $type_token;
|
||||
continue;
|
||||
|
@ -75,4 +75,26 @@ class Union extends Type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function isIn(Union $parent)
|
||||
{
|
||||
foreach ($this->types as $type) {
|
||||
if (strtoupper($type->value[0]) === $type->value[0] && $parent->hasType('object')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type->value === 'false' && $parent->hasType('bool')) {
|
||||
// this is fine
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$parent->hasType($type->value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// @todo add is_subclass_of
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user