mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Move string-stored types into named classes
This commit is contained in:
parent
dc592f7a6e
commit
03141e41c2
@ -9,4 +9,4 @@
|
||||
|
||||
<!-- This is a vendor file that we don't want to bother linting. -->
|
||||
<exclude-pattern>src/Psalm/CallMap.php</exclude-pattern>
|
||||
</ruleset>
|
||||
</ruleset>
|
||||
|
@ -21,6 +21,7 @@ use Psalm\Storage\ClassLikeStorage;
|
||||
use Psalm\Storage\MethodStorage;
|
||||
use Psalm\Storage\PropertyStorage;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use ReflectionProperty;
|
||||
@ -176,7 +177,7 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
$class_context->vars_possibly_in_scope = $global_context->vars_possibly_in_scope;
|
||||
}
|
||||
|
||||
$class_context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic($this->fq_class_name)]);
|
||||
$class_context->vars_in_scope['$this'] = new Type\Union([new TNamedObject($this->fq_class_name)]);
|
||||
}
|
||||
|
||||
// set all constants first
|
||||
|
@ -214,6 +214,7 @@ class CommentChecker
|
||||
* @param string $docblock
|
||||
* @param int $line_number
|
||||
* @return array Array of the main comment and specials
|
||||
* @psalm-return array{description:string, specials:array<string, array<mixed, string>>}
|
||||
*/
|
||||
public static function parseDocComment($docblock, $line_number = null)
|
||||
{
|
||||
|
@ -341,11 +341,11 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
if (isset($first_arg->inferredType)) {
|
||||
if ($first_arg->inferredType->hasArray()) {
|
||||
$array_type = $first_arg->inferredType->types['array'];
|
||||
if ($array_type instanceof Type\ObjectLike) {
|
||||
if ($array_type instanceof Type\Atomic\ObjectLike) {
|
||||
return $array_type->getGenericTypeParam();
|
||||
}
|
||||
|
||||
if ($array_type instanceof Type\Generic) {
|
||||
if ($array_type instanceof Type\Atomic\TArray) {
|
||||
return clone $array_type->type_params[1];
|
||||
}
|
||||
} elseif ($first_arg->inferredType->hasScalarType() &&
|
||||
@ -389,14 +389,14 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
$first_arg_array_generic = $first_arg
|
||||
&& isset($first_arg->inferredType)
|
||||
&& isset($first_arg->inferredType->types['array'])
|
||||
&& $first_arg->inferredType->types['array'] instanceof Type\Generic
|
||||
&& $first_arg->inferredType->types['array'] instanceof Type\Atomic\TArray
|
||||
? $first_arg->inferredType->types['array']
|
||||
: null;
|
||||
|
||||
$first_arg_array_objectlike = $first_arg
|
||||
&& isset($first_arg->inferredType)
|
||||
&& isset($first_arg->inferredType->types['array'])
|
||||
&& $first_arg->inferredType->types['array'] instanceof Type\ObjectLike
|
||||
&& $first_arg->inferredType->types['array'] instanceof Type\Atomic\ObjectLike
|
||||
? $first_arg->inferredType->types['array']
|
||||
: null;
|
||||
|
||||
@ -405,11 +405,16 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
if ($first_arg_array_generic) {
|
||||
$inner_type = clone $first_arg_array_generic->type_params[1];
|
||||
} else {
|
||||
/** @var Type\ObjectLike $first_arg_array_objectlike */
|
||||
/** @var Type\Atomic\ObjectLike $first_arg_array_objectlike */
|
||||
$inner_type = $first_arg_array_objectlike->getGenericTypeParam();
|
||||
}
|
||||
|
||||
return new Type\Union([new Type\Generic('array', [Type::getInt(), $inner_type])]);
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TArray([
|
||||
Type::getInt(),
|
||||
$inner_type
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -421,7 +426,12 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
$inner_type = Type::getString();
|
||||
}
|
||||
|
||||
return new Type\Union([new Type\Generic('array', [Type::getInt(), $inner_type])]);
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TArray([
|
||||
Type::getInt(),
|
||||
$inner_type
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -435,7 +445,7 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
}
|
||||
|
||||
foreach ($call_arg->value->inferredType->types as $type_part) {
|
||||
if (!$type_part instanceof Type\Generic) {
|
||||
if (!$type_part instanceof Type\Atomic\TArray) {
|
||||
return Type::getArray();
|
||||
}
|
||||
|
||||
@ -452,13 +462,10 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
|
||||
if ($inner_value_types) {
|
||||
return new Type\Union([
|
||||
new Type\Generic(
|
||||
'array',
|
||||
[
|
||||
Type::combineTypes($inner_key_types),
|
||||
Type::combineTypes($inner_value_types)
|
||||
]
|
||||
)
|
||||
new Type\Atomic\TArray([
|
||||
Type::combineTypes($inner_key_types),
|
||||
Type::combineTypes($inner_value_types)
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -472,13 +479,10 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
}
|
||||
|
||||
return new Type\Union([
|
||||
new Type\Generic(
|
||||
'array',
|
||||
[
|
||||
Type::getInt(),
|
||||
clone $first_arg_array_generic->type_params[1]
|
||||
]
|
||||
)
|
||||
new Type\Atomic\TArray([
|
||||
Type::getInt(),
|
||||
clone $first_arg_array_generic->type_params[1]
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
@ -497,13 +501,10 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
}
|
||||
|
||||
return new Type\Union([
|
||||
new Type\Generic(
|
||||
'array',
|
||||
[
|
||||
clone $first_arg_array_generic->type_params[0],
|
||||
$inner_type
|
||||
]
|
||||
)
|
||||
new Type\Atomic\TArray([
|
||||
clone $first_arg_array_generic->type_params[0],
|
||||
$inner_type
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
@ -547,7 +548,7 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
$array_arg_type = $array_arg
|
||||
&& isset($array_arg->inferredType)
|
||||
&& isset($array_arg->inferredType->types['array'])
|
||||
&& $array_arg->inferredType->types['array'] instanceof Type\Generic
|
||||
&& $array_arg->inferredType->types['array'] instanceof Type\Atomic\TArray
|
||||
? $array_arg->inferredType->types['array']
|
||||
: null;
|
||||
|
||||
@ -556,7 +557,7 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
|
||||
if ($function_call_arg->value instanceof PhpParser\Node\Expr\Closure &&
|
||||
isset($function_call_arg->value->inferredType) &&
|
||||
$function_call_arg->value->inferredType->types['Closure'] instanceof Type\Fn
|
||||
$function_call_arg->value->inferredType->types['Closure'] instanceof Type\Atomic\Fn
|
||||
) {
|
||||
$closure_return_type = $function_call_arg->value->inferredType->types['Closure']->return_type;
|
||||
|
||||
@ -576,12 +577,22 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
|
||||
if ($call_map_key === 'array_map') {
|
||||
$inner_type = clone $closure_return_type;
|
||||
return new Type\Union([new Type\Generic('array', [$key_type, $inner_type])]);
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TArray([
|
||||
$key_type,
|
||||
$inner_type
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
if ($array_arg_type) {
|
||||
$inner_type = clone $array_arg_type->type_params[1];
|
||||
return new Type\Union([new Type\Generic('array', [$key_type, $inner_type])]);
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TArray([
|
||||
$key_type,
|
||||
$inner_type
|
||||
])
|
||||
]);
|
||||
}
|
||||
} elseif ($function_call_arg->value instanceof PhpParser\Node\Scalar\String_) {
|
||||
$mapped_function_id = strtolower($function_call_arg->value->value);
|
||||
@ -591,7 +602,12 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
if (isset($call_map[$mapped_function_id][0])) {
|
||||
if ($call_map[$mapped_function_id][0]) {
|
||||
$mapped_function_return = Type::parseString($call_map[$mapped_function_id][0]);
|
||||
return new Type\Union([new Type\Generic('array', [Type::getInt(), $mapped_function_return])]);
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TArray([
|
||||
Type::getInt(),
|
||||
$mapped_function_return
|
||||
])
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
// @todo handle array_map('some_custom_function', $arr)
|
||||
|
@ -28,6 +28,7 @@ use Psalm\StatementsSource;
|
||||
use Psalm\Storage\FunctionLikeStorage;
|
||||
use Psalm\Storage\MethodStorage;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
|
||||
abstract class FunctionLikeChecker extends SourceChecker implements StatementsSource
|
||||
{
|
||||
@ -121,7 +122,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
return null;
|
||||
}
|
||||
} elseif ($context->self) {
|
||||
$context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic($context->self)]);
|
||||
$context->vars_in_scope['$this'] = new Type\Union([new TNamedObject($context->self)]);
|
||||
}
|
||||
|
||||
$declaring_method_id = (string)MethodChecker::getDeclaringMethodId($method_id);
|
||||
@ -239,7 +240,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
|
||||
/** @var PhpParser\Node\Expr\Closure $this->function */
|
||||
$this->function->inferredType = new Type\Union([
|
||||
new Type\Fn(
|
||||
new Type\Atomic\Fn(
|
||||
'Closure',
|
||||
$storage->params,
|
||||
$storage->return_type ?: Type::getMixed()
|
||||
@ -324,7 +325,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
);
|
||||
|
||||
if ($closure_return_types && $this->function->inferredType) {
|
||||
/** @var Type\Fn */
|
||||
/** @var Type\Atomic\Fn */
|
||||
$closure_atomic = $this->function->inferredType->types['Closure'];
|
||||
$closure_atomic->return_type = new Type\Union($closure_return_types);
|
||||
}
|
||||
@ -1006,7 +1007,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
$existing_param_type_nullable = $function_signature_param->is_nullable;
|
||||
|
||||
if ($existing_param_type_nullable && !$new_param_type->isNullable()) {
|
||||
$new_param_type->types['null'] = new Type\Atomic('null');
|
||||
$new_param_type->types['null'] = new Type\Atomic\TNull();
|
||||
}
|
||||
|
||||
$function_signature_param->signature_type = $function_signature_param->type;
|
||||
@ -1083,13 +1084,10 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
|
||||
if ($param->variadic) {
|
||||
$param_type = new Type\Union([
|
||||
new Type\GenericArray(
|
||||
'array',
|
||||
[
|
||||
Type::getInt(),
|
||||
$param_type
|
||||
]
|
||||
)
|
||||
new Type\Atomic\TArray([
|
||||
Type::getInt(),
|
||||
$param_type
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ class ForeachChecker
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($return_type instanceof Type\Generic) {
|
||||
if ($return_type instanceof Type\Atomic\TArray || $return_type instanceof Type\Atomic\TGenericObject) {
|
||||
$value_index = count($return_type->type_params) - 1;
|
||||
$value_type_part = $return_type->type_params[$value_index];
|
||||
|
||||
@ -86,88 +86,66 @@ class ForeachChecker
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($return_type->value) {
|
||||
case 'mixed':
|
||||
case 'empty':
|
||||
case 'Generator':
|
||||
$value_type = Type::getMixed();
|
||||
break;
|
||||
if ($return_type instanceof Type\Atomic\Scalar ||
|
||||
$return_type instanceof Type\Atomic\TNull ||
|
||||
$return_type instanceof Type\Atomic\TVoid
|
||||
) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidIterator(
|
||||
'Cannot iterate over ' . $return_type->getKey(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->expr)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
case 'array':
|
||||
case 'object':
|
||||
$value_type = Type::getMixed();
|
||||
break;
|
||||
|
||||
case 'null':
|
||||
if (IssueBuffer::accepts(
|
||||
new NullReference(
|
||||
'Cannot iterate over ' . $return_type->value,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->expr)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value_type = Type::getMixed();
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
case 'void':
|
||||
case 'int':
|
||||
case 'bool':
|
||||
case 'false':
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidIterator(
|
||||
'Cannot iterate over ' . $return_type->value,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->expr)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value_type = Type::getMixed();
|
||||
break;
|
||||
|
||||
default:
|
||||
if ($return_type->value !== 'Traversable' &&
|
||||
$return_type->value !== $statements_checker->getClassName()
|
||||
) {
|
||||
if (ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$return_type->value,
|
||||
$statements_checker->getFileChecker(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->expr),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ClassChecker::classImplements(
|
||||
$value_type = Type::getMixed();
|
||||
} elseif ($return_type instanceof Type\Atomic\TArray ||
|
||||
$return_type instanceof Type\Atomic\TObject ||
|
||||
$return_type instanceof Type\Atomic\TMixed ||
|
||||
$return_type instanceof Type\Atomic\TEmpty ||
|
||||
($return_type instanceof Type\Atomic\TNamedObject && $return_type->value === 'Generator')
|
||||
) {
|
||||
$value_type = Type::getMixed();
|
||||
} elseif ($return_type instanceof Type\Atomic\TNamedObject) {
|
||||
if ($return_type->value !== 'Traversable' &&
|
||||
$return_type->value !== $statements_checker->getClassName()
|
||||
) {
|
||||
if (ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$return_type->value,
|
||||
'Iterator'
|
||||
)) {
|
||||
$iterator_method = $return_type->value . '::current';
|
||||
$iterator_class_type = MethodChecker::getMethodReturnType($iterator_method);
|
||||
|
||||
if ($iterator_class_type) {
|
||||
$value_type_part = ExpressionChecker::fleshOutTypes(
|
||||
$iterator_class_type,
|
||||
[],
|
||||
$return_type->value,
|
||||
$iterator_method
|
||||
);
|
||||
|
||||
if (!$value_type) {
|
||||
$value_type = $value_type_part;
|
||||
} else {
|
||||
$value_type = Type::combineUnionTypes($value_type, $value_type_part);
|
||||
}
|
||||
} else {
|
||||
$value_type = Type::getMixed();
|
||||
}
|
||||
$statements_checker->getFileChecker(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->expr),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ClassChecker::classImplements(
|
||||
$return_type->value,
|
||||
'Iterator'
|
||||
)) {
|
||||
$iterator_method = $return_type->value . '::current';
|
||||
$iterator_class_type = MethodChecker::getMethodReturnType($iterator_method);
|
||||
|
||||
if ($iterator_class_type) {
|
||||
$value_type_part = ExpressionChecker::fleshOutTypes(
|
||||
$iterator_class_type,
|
||||
[],
|
||||
$return_type->value,
|
||||
$iterator_method
|
||||
);
|
||||
|
||||
if (!$value_type) {
|
||||
$value_type = $value_type_part;
|
||||
} else {
|
||||
$value_type = Type::combineUnionTypes($value_type, $value_type_part);
|
||||
}
|
||||
} else {
|
||||
$value_type = Type::getMixed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,8 +32,8 @@ class SwitchChecker
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($stmt->cond->inferredType) && array_values($stmt->cond->inferredType->types)[0] instanceof Type\T) {
|
||||
/** @var Type\T */
|
||||
if (isset($stmt->cond->inferredType) && array_values($stmt->cond->inferredType->types)[0] instanceof Type\Atomic\T) {
|
||||
/** @var Type\Atomic\T */
|
||||
$type_type = array_values($stmt->cond->inferredType->types)[0];
|
||||
$type_candidate_var = $type_type->typeof;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use Psalm\Checker\ScopeChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
use Psalm\Context;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
|
||||
class TryChecker
|
||||
{
|
||||
@ -62,7 +63,7 @@ class TryChecker
|
||||
* @return Type\Atomic
|
||||
*/
|
||||
function ($fq_catch_class) {
|
||||
return new Type\Atomic($fq_catch_class);
|
||||
return new TNamedObject($fq_catch_class);
|
||||
},
|
||||
$fq_catch_classes
|
||||
)
|
||||
|
@ -31,6 +31,26 @@ use Psalm\Issue\UndefinedPropertyAssignment;
|
||||
use Psalm\Issue\UndefinedThisPropertyAssignment;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\Generic;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\Scalar;
|
||||
use Psalm\Type\Atomic\TNumeric;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TVoid;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TBool;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TEmpty;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
use Psalm\Type\Atomic\TResource;
|
||||
use Psalm\Type\Atomic\TCallable;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TGenericObject;
|
||||
use Psalm\Type\Atomic\TNumericString;
|
||||
|
||||
class AssignmentChecker
|
||||
{
|
||||
@ -156,11 +176,11 @@ class AssignmentChecker
|
||||
$statements_checker->registerVariable($list_var_id, $var->getLine());
|
||||
|
||||
if (isset($assign_value_type->types['array'])) {
|
||||
if ($assign_value_type->types['array'] instanceof Type\Generic) {
|
||||
if ($assign_value_type->types['array'] instanceof Type\Atomic\TArray) {
|
||||
$context->vars_in_scope[$list_var_id] = clone $assign_value_type->types['array']->type_params[1];
|
||||
|
||||
continue;
|
||||
} elseif ($assign_value_type->types['array'] instanceof Type\ObjectLike) {
|
||||
} elseif ($assign_value_type->types['array'] instanceof Type\Atomic\ObjectLike) {
|
||||
if ($assign_var_item->key
|
||||
&& $assign_var_item->key instanceof PhpParser\Node\Scalar\String_
|
||||
&& isset($assign_value_type->types['array']->properties[$assign_var_item->key->value])
|
||||
@ -412,14 +432,15 @@ class AssignmentChecker
|
||||
$has_regular_setter = false;
|
||||
|
||||
foreach ($lhs_type->types as $lhs_type_part) {
|
||||
if ($lhs_type_part->isNull()) {
|
||||
if ($lhs_type_part instanceof TNull) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$lhs_type_part->isObjectType()) {
|
||||
if (!$lhs_type_part instanceof TObject && !$lhs_type_part instanceof TNamedObject) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidPropertyAssignment(
|
||||
$lhs_var_id . ' with possible non-object type \'' . $lhs_type_part . '\' cannot treated as an object',
|
||||
$lhs_var_id . ' with possible non-object type \'' . $lhs_type_part .
|
||||
'\' cannot treated as an object',
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->var)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
@ -433,13 +454,15 @@ class AssignmentChecker
|
||||
// stdClass and SimpleXMLElement are special cases where we cannot infer the return types
|
||||
// but we don't want to throw an error
|
||||
// Hack has a similar issue: https://github.com/facebook/hhvm/issues/5164
|
||||
if ($lhs_type_part->isObject() ||
|
||||
in_array(
|
||||
strtolower($lhs_type_part->value),
|
||||
['stdclass', 'simplexmlelement', 'dateinterval', 'domdocument', 'domnode']
|
||||
if ($lhs_type_part instanceof TObject ||
|
||||
($lhs_type_part instanceof TNamedObject &&
|
||||
in_array(
|
||||
strtolower($lhs_type_part->value),
|
||||
['stdclass', 'simplexmlelement', 'dateinterval', 'domdocument', 'domnode']
|
||||
)
|
||||
)
|
||||
) {
|
||||
if (strtolower($lhs_type_part->value) === 'stdclass') {
|
||||
if ($lhs_type_part instanceof TNamedObject && strtolower($lhs_type_part->value) === 'stdclass') {
|
||||
$context->vars_in_scope[$var_id] = $assignment_value_type;
|
||||
} else {
|
||||
$context->vars_in_scope[$var_id] = Type::getMixed();
|
||||
@ -448,17 +471,13 @@ class AssignmentChecker
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$lhs_type_part->isObject() && MethodChecker::methodExists($lhs_type_part . '::__set')) {
|
||||
if (MethodChecker::methodExists($lhs_type_part . '::__set')) {
|
||||
$context->vars_in_scope[$var_id] = Type::getMixed();
|
||||
continue;
|
||||
}
|
||||
|
||||
$has_regular_setter = true;
|
||||
|
||||
if ($lhs_type_part->isObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ExpressionChecker::isMock($lhs_type_part->value)) {
|
||||
$context->vars_in_scope[$var_id] = Type::getMixed();
|
||||
|
||||
@ -862,8 +881,8 @@ class AssignmentChecker
|
||||
// do nothing
|
||||
} elseif ($is_string) {
|
||||
foreach ($assignment_value_type->types as $value_type) {
|
||||
if (!$value_type->isString()) {
|
||||
if ($value_type->isMixed()) {
|
||||
if (!$value_type instanceof TString) {
|
||||
if ($value_type instanceof TMixed) {
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedStringOffsetAssignment(
|
||||
'Cannot assign a mixed variable to a string offset for ' . $var_id,
|
||||
@ -929,9 +948,9 @@ class AssignmentChecker
|
||||
}
|
||||
|
||||
if (!$nesting) {
|
||||
/** @var Type\Generic|null */
|
||||
/** @var Type\Atomic\TArray|null */
|
||||
$array_type = isset($context->vars_in_scope[$var_id]->types['array'])
|
||||
&& $context->vars_in_scope[$var_id]->types['array'] instanceof Type\Generic
|
||||
&& $context->vars_in_scope[$var_id]->types['array'] instanceof Type\Atomic\TArray
|
||||
? $context->vars_in_scope[$var_id]->types['array']
|
||||
: null;
|
||||
|
||||
@ -942,22 +961,16 @@ class AssignmentChecker
|
||||
|| ($array_type && $array_type->type_params[0]->isEmpty()))
|
||||
) {
|
||||
$assignment_value_type = new Type\Union([
|
||||
new Type\ObjectLike(
|
||||
'array',
|
||||
[
|
||||
$assignment_key_value => $assignment_value_type
|
||||
]
|
||||
)
|
||||
new Type\Atomic\ObjectLike([
|
||||
$assignment_key_value => $assignment_value_type
|
||||
])
|
||||
]);
|
||||
} else {
|
||||
$assignment_value_type = new Type\Union([
|
||||
new Type\Generic(
|
||||
'array',
|
||||
[
|
||||
$assignment_key_type,
|
||||
$assignment_value_type
|
||||
]
|
||||
)
|
||||
new Type\Atomic\TArray([
|
||||
$assignment_key_type,
|
||||
$assignment_value_type
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,26 @@ use Psalm\Issue\TypeCoercion;
|
||||
use Psalm\Issue\UndefinedFunction;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\Generic;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\Scalar;
|
||||
use Psalm\Type\Atomic\TNumeric;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TVoid;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TBool;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TEmpty;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
use Psalm\Type\Atomic\TResource;
|
||||
use Psalm\Type\Atomic\TCallable;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TGenericObject;
|
||||
use Psalm\Type\Atomic\TNumericString;
|
||||
|
||||
class CallChecker
|
||||
{
|
||||
@ -110,7 +130,7 @@ class CallChecker
|
||||
|
||||
if (isset($stmt->name->inferredType)) {
|
||||
foreach ($stmt->name->inferredType->types as $var_type_part) {
|
||||
if ($var_type_part instanceof Type\Fn) {
|
||||
if ($var_type_part instanceof Type\Atomic\Fn) {
|
||||
$function_params = $var_type_part->params;
|
||||
|
||||
if ($var_type_part->return_type) {
|
||||
@ -123,9 +143,9 @@ class CallChecker
|
||||
$stmt->inferredType = $var_type_part->return_type;
|
||||
}
|
||||
}
|
||||
} elseif (!$var_type_part->isMixed() &&
|
||||
$var_type_part->value !== 'Closure' &&
|
||||
!$var_type_part->isCallable()
|
||||
} elseif (!$var_type_part instanceof TMixed &&
|
||||
(!$var_type_part instanceof TNamedObject || $var_type_part->value !== 'Closure') &&
|
||||
!$var_type_part instanceof TCallable
|
||||
) {
|
||||
$var_id = ExpressionChecker::getVarId(
|
||||
$stmt->name,
|
||||
@ -236,7 +256,7 @@ class CallChecker
|
||||
$var = $stmt->args[0]->value;
|
||||
|
||||
if ($var instanceof PhpParser\Node\Expr\Variable && is_string($var->name)) {
|
||||
$stmt->inferredType = new Type\Union([new Type\T('$' . $var->name)]);
|
||||
$stmt->inferredType = new Type\Union([new Type\Atomic\T('$' . $var->name)]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,7 +328,7 @@ class CallChecker
|
||||
}
|
||||
|
||||
if ($fq_class_name) {
|
||||
$stmt->inferredType = new Type\Union([new Type\Atomic($fq_class_name)]);
|
||||
$stmt->inferredType = new Type\Union([new TNamedObject($fq_class_name)]);
|
||||
|
||||
if (strtolower($fq_class_name) !== 'stdclass' &&
|
||||
($class_checked || ClassChecker::classExists($fq_class_name, $file_checker)) &&
|
||||
@ -353,7 +373,7 @@ class CallChecker
|
||||
$value_type = null;
|
||||
|
||||
foreach ($first_arg_type->types as $type) {
|
||||
if ($type instanceof Type\Generic) {
|
||||
if ($type instanceof Type\Atomic\TArray) {
|
||||
$first_type_param = count($type->type_params) ? $type->type_params[0] : null;
|
||||
$last_type_param = $type->type_params[count($type->type_params) - 1];
|
||||
|
||||
@ -380,7 +400,7 @@ class CallChecker
|
||||
}
|
||||
|
||||
$stmt->inferredType = new Type\Union([
|
||||
new Type\Generic(
|
||||
new Type\Atomic\TGenericObject(
|
||||
$fq_class_name,
|
||||
[
|
||||
$key_type,
|
||||
@ -489,136 +509,140 @@ class CallChecker
|
||||
$return_type = null;
|
||||
|
||||
foreach ($class_type->types as $type) {
|
||||
if (!$type instanceof TNamedObject) {
|
||||
switch (get_class($type)) {
|
||||
case 'Psalm\\Type\\Atomic\\TNull':
|
||||
if (IssueBuffer::accepts(
|
||||
new NullReference(
|
||||
'Cannot call method ' . $stmt->name . ' on possibly null variable ' . $var_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Psalm\\Type\\Atomic\\TInt':
|
||||
case 'Psalm\\Type\\Atomic\\TBool':
|
||||
case 'Psalm\\Type\\Atomic\\TFalse':
|
||||
case 'Psalm\\Type\\Atomic\\TArray':
|
||||
case 'Psalm\\Type\\Atomic\\TString':
|
||||
case 'Psalm\\Type\\Atomic\\TNumericString':
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidArgument(
|
||||
'Cannot call method ' . $stmt->name . ' on ' . $class_type . ' variable ' . $var_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Psalm\\Type\\Atomic\\TMixed':
|
||||
case 'Psalm\\Type\\Atomic\\TObject':
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedMethodCall(
|
||||
'Cannot call method ' . $stmt->name . ' on a mixed variable ' . $var_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$fq_class_name = $type->value;
|
||||
|
||||
$is_mock = ExpressionChecker::isMock($fq_class_name);
|
||||
|
||||
$has_mock = $has_mock || $is_mock;
|
||||
|
||||
switch ($fq_class_name) {
|
||||
case 'null':
|
||||
if (IssueBuffer::accepts(
|
||||
new NullReference(
|
||||
'Cannot call method ' . $stmt->name . ' on possibly null variable ' . $var_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
if ($fq_class_name === 'static') {
|
||||
$fq_class_name = (string) $context->self;
|
||||
}
|
||||
|
||||
case 'int':
|
||||
case 'bool':
|
||||
case 'false':
|
||||
case 'array':
|
||||
case 'string':
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidArgument(
|
||||
'Cannot call method ' . $stmt->name . ' on ' . $class_type . ' variable ' . $var_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
if ($is_mock ||
|
||||
$context->isPhantomClass($fq_class_name)
|
||||
) {
|
||||
$return_type = Type::getMixed();
|
||||
continue;
|
||||
}
|
||||
|
||||
case 'mixed':
|
||||
case 'object':
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedMethodCall(
|
||||
'Cannot call method ' . $stmt->name . ' on a mixed variable ' . $var_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
$does_class_exist = ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$fq_class_name,
|
||||
$statements_checker->getFileChecker(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->var),
|
||||
$statements_checker->getSuppressedIssues(),
|
||||
true
|
||||
);
|
||||
|
||||
case 'static':
|
||||
$fq_class_name = (string) $context->self;
|
||||
// fall through to default
|
||||
if (!$does_class_exist) {
|
||||
return $does_class_exist;
|
||||
}
|
||||
|
||||
default:
|
||||
if ($is_mock ||
|
||||
$context->isPhantomClass($fq_class_name)
|
||||
) {
|
||||
$return_type = Type::getMixed();
|
||||
continue;
|
||||
}
|
||||
if (MethodChecker::methodExists($fq_class_name . '::__call')) {
|
||||
$return_type = Type::getMixed();
|
||||
continue;
|
||||
}
|
||||
|
||||
$does_class_exist = ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$fq_class_name,
|
||||
$statements_checker->getFileChecker(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->var),
|
||||
$statements_checker->getSuppressedIssues(),
|
||||
true
|
||||
);
|
||||
$method_id = $fq_class_name . '::' . strtolower($stmt->name);
|
||||
$cased_method_id = $fq_class_name . '::' . $stmt->name;
|
||||
|
||||
if (!$does_class_exist) {
|
||||
return $does_class_exist;
|
||||
}
|
||||
$does_method_exist = MethodChecker::checkMethodExists(
|
||||
$cased_method_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
if (MethodChecker::methodExists($fq_class_name . '::__call')) {
|
||||
$return_type = Type::getMixed();
|
||||
continue;
|
||||
}
|
||||
if (!$does_method_exist) {
|
||||
return $does_method_exist;
|
||||
}
|
||||
|
||||
$method_id = $fq_class_name . '::' . strtolower($stmt->name);
|
||||
$cased_method_id = $fq_class_name . '::' . $stmt->name;
|
||||
if (FunctionChecker::inCallMap($cased_method_id)) {
|
||||
$return_type_candidate = FunctionChecker::getReturnTypeFromCallMap($method_id);
|
||||
} else {
|
||||
if (MethodChecker::checkMethodVisibility(
|
||||
$method_id,
|
||||
$context->self,
|
||||
$statements_checker->getSource(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$does_method_exist = MethodChecker::checkMethodExists(
|
||||
$cased_method_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
if (MethodChecker::checkMethodNotDeprecated(
|
||||
$method_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$does_method_exist) {
|
||||
return $does_method_exist;
|
||||
}
|
||||
$return_type_candidate = MethodChecker::getMethodReturnType($method_id);
|
||||
}
|
||||
|
||||
if (FunctionChecker::inCallMap($cased_method_id)) {
|
||||
$return_type_candidate = FunctionChecker::getReturnTypeFromCallMap($method_id);
|
||||
} else {
|
||||
if (MethodChecker::checkMethodVisibility(
|
||||
$method_id,
|
||||
$context->self,
|
||||
$statements_checker->getSource(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
if ($return_type_candidate) {
|
||||
$return_type_candidate = ExpressionChecker::fleshOutTypes(
|
||||
$return_type_candidate,
|
||||
$stmt->args,
|
||||
$fq_class_name,
|
||||
$method_id
|
||||
);
|
||||
|
||||
if (MethodChecker::checkMethodNotDeprecated(
|
||||
$method_id,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$return_type_candidate = MethodChecker::getMethodReturnType($method_id);
|
||||
}
|
||||
|
||||
if ($return_type_candidate) {
|
||||
$return_type_candidate = ExpressionChecker::fleshOutTypes(
|
||||
$return_type_candidate,
|
||||
$stmt->args,
|
||||
$fq_class_name,
|
||||
$method_id
|
||||
);
|
||||
|
||||
if (!$return_type) {
|
||||
$return_type = $return_type_candidate;
|
||||
} else {
|
||||
$return_type = Type::combineUnionTypes($return_type_candidate, $return_type);
|
||||
}
|
||||
} else {
|
||||
$return_type = Type::getMixed();
|
||||
}
|
||||
if (!$return_type) {
|
||||
$return_type = $return_type_candidate;
|
||||
} else {
|
||||
$return_type = Type::combineUnionTypes($return_type_candidate, $return_type);
|
||||
}
|
||||
} else {
|
||||
$return_type = Type::getMixed();
|
||||
}
|
||||
}
|
||||
|
||||
@ -749,7 +773,7 @@ class CallChecker
|
||||
}
|
||||
|
||||
if ($fq_class_name) {
|
||||
$lhs_type = new Type\Union([new Type\Atomic($fq_class_name)]);
|
||||
$lhs_type = new Type\Union([new TNamedObject($fq_class_name)]);
|
||||
}
|
||||
} else {
|
||||
ExpressionChecker::analyze($statements_checker, $stmt->class, $context);
|
||||
@ -765,6 +789,11 @@ class CallChecker
|
||||
$has_mock = false;
|
||||
|
||||
foreach ($lhs_type->types as $lhs_type_part) {
|
||||
if (!$lhs_type_part instanceof TNamedObject) {
|
||||
// @todo deal with it
|
||||
continue;
|
||||
}
|
||||
|
||||
$fq_class_name = $lhs_type_part->value;
|
||||
|
||||
$is_mock = ExpressionChecker::isMock($fq_class_name);
|
||||
@ -1134,7 +1163,7 @@ class CallChecker
|
||||
$array_arg_types[] = $array_arg
|
||||
&& isset($array_arg->inferredType)
|
||||
&& isset($array_arg->inferredType->types['array'])
|
||||
&& $array_arg->inferredType->types['array'] instanceof Type\Generic
|
||||
&& $array_arg->inferredType->types['array'] instanceof Type\Atomic\TArray
|
||||
? $array_arg->inferredType->types['array']
|
||||
: null;
|
||||
}
|
||||
@ -1151,7 +1180,7 @@ class CallChecker
|
||||
$expected_closure_param_count = $method_id === 'array_filter' ? 1 : count($array_arg_types);
|
||||
|
||||
foreach ($closure_arg_type->types as $closure_type) {
|
||||
if (!$closure_type instanceof Type\Fn) {
|
||||
if (!$closure_type instanceof Type\Atomic\Fn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1189,7 +1218,7 @@ class CallChecker
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var Type\Generic */
|
||||
/** @var Type\Atomic\TArray */
|
||||
$array_arg_type = $array_arg_types[$i];
|
||||
|
||||
$input_type = $array_arg_type->type_params[1];
|
||||
|
@ -31,6 +31,26 @@ use Psalm\Issue\UndefinedPropertyFetch;
|
||||
use Psalm\Issue\UndefinedThisPropertyFetch;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\Generic;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\Scalar;
|
||||
use Psalm\Type\Atomic\TNumeric;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TVoid;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TBool;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TEmpty;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
use Psalm\Type\Atomic\TResource;
|
||||
use Psalm\Type\Atomic\TCallable;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TGenericObject;
|
||||
use Psalm\Type\Atomic\TNumericString;
|
||||
|
||||
class FetchChecker
|
||||
{
|
||||
@ -153,7 +173,7 @@ class FetchChecker
|
||||
}
|
||||
|
||||
foreach ($stmt_var_type->types as $lhs_type_part) {
|
||||
if ($lhs_type_part->isNull()) {
|
||||
if ($lhs_type_part instanceof TNull) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -180,8 +200,10 @@ class FetchChecker
|
||||
// stdClass and SimpleXMLElement are special cases where we cannot infer the return types
|
||||
// but we don't want to throw an error
|
||||
// Hack has a similar issue: https://github.com/facebook/hhvm/issues/5164
|
||||
if ($lhs_type_part->isObject() ||
|
||||
in_array(strtolower($lhs_type_part->value), ['stdclass', 'simplexmlelement'])
|
||||
if ($lhs_type_part instanceof TObject ||
|
||||
($lhs_type_part instanceof TNamedObject &&
|
||||
in_array(strtolower($lhs_type_part->value), ['stdclass', 'simplexmlelement'])
|
||||
)
|
||||
) {
|
||||
$stmt->inferredType = Type::getMixed();
|
||||
continue;
|
||||
@ -189,6 +211,11 @@ class FetchChecker
|
||||
|
||||
$file_checker = $statements_checker->getFileChecker();
|
||||
|
||||
if (!$lhs_type_part instanceof TNamedObject) {
|
||||
// @todo deal with this
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ClassChecker::classExists($lhs_type_part->value, $file_checker)) {
|
||||
if (InterfaceChecker::interfaceExists($lhs_type_part->value, $file_checker)) {
|
||||
if (IssueBuffer::accepts(
|
||||
@ -544,7 +571,7 @@ class FetchChecker
|
||||
}
|
||||
}
|
||||
|
||||
$stmt->class->inferredType = $fq_class_name ? new Type\Union([new Type\Atomic($fq_class_name)]) : null;
|
||||
$stmt->class->inferredType = $fq_class_name ? new Type\Union([new TNamedObject($fq_class_name)]) : null;
|
||||
}
|
||||
|
||||
if ($fq_class_name &&
|
||||
@ -685,26 +712,23 @@ class FetchChecker
|
||||
if (!$keyed_assignment_type || $keyed_assignment_type->isEmpty()) {
|
||||
if (!$assignment_key_type->isMixed() && !$assignment_key_type->hasInt() && $assignment_key_value) {
|
||||
$keyed_assignment_type = new Type\Union([
|
||||
new Type\ObjectLike(
|
||||
'array',
|
||||
[
|
||||
$assignment_key_value => $assignment_value_type
|
||||
]
|
||||
)
|
||||
new Type\Atomic\ObjectLike([
|
||||
$assignment_key_value => $assignment_value_type
|
||||
])
|
||||
]);
|
||||
} else {
|
||||
$keyed_assignment_type = Type::getEmptyArray();
|
||||
/** @var Type\Generic */
|
||||
/** @var Type\Atomic\TArray */
|
||||
$keyed_assignment_type_array = $keyed_assignment_type->types['array'];
|
||||
$keyed_assignment_type_array->type_params[0] = $assignment_key_type;
|
||||
$keyed_assignment_type_array->type_params[1] = $assignment_value_type;
|
||||
}
|
||||
} else {
|
||||
foreach ($keyed_assignment_type->types as &$type) {
|
||||
if ($type->isScalarType() && !$type->isString()) {
|
||||
if ($type instanceof TInt || $type instanceof TFloat || $type instanceof TBool) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidArrayAssignment(
|
||||
'Cannot assign value on variable ' . $var_id . ' of scalar type ' . $type->value,
|
||||
'Cannot assign value on variable ' . $var_id . ' of scalar type ' . $type->getKey(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
@ -715,7 +739,7 @@ class FetchChecker
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type instanceof Type\Generic) {
|
||||
if ($type instanceof TString || $type instanceof Type\Atomic\TArray) {
|
||||
$refined_type = self::refineArrayType(
|
||||
$statements_checker,
|
||||
$type,
|
||||
@ -734,7 +758,7 @@ class FetchChecker
|
||||
}
|
||||
|
||||
$type = $refined_type;
|
||||
} elseif ($type instanceof Type\ObjectLike && $assignment_key_value) {
|
||||
} elseif ($type instanceof Type\Atomic\ObjectLike && $assignment_key_value) {
|
||||
if (isset($type->properties[$assignment_key_value])) {
|
||||
$type->properties[$assignment_key_value] = Type::combineUnionTypes(
|
||||
$type->properties[$assignment_key_value],
|
||||
@ -768,10 +792,10 @@ class FetchChecker
|
||||
$var_type = $stmt->var->inferredType;
|
||||
|
||||
foreach ($var_type->types as &$type) {
|
||||
if ($type instanceof Type\Generic || $type instanceof Type\ObjectLike) {
|
||||
if ($type instanceof Type\Atomic\TArray || $type instanceof Type\Atomic\ObjectLike) {
|
||||
$value_index = null;
|
||||
|
||||
if ($type instanceof Type\Generic) {
|
||||
if ($type instanceof Type\Atomic\TArray) {
|
||||
// create a union type to pass back to the statement
|
||||
$value_index = count($type->type_params) - 1;
|
||||
|
||||
@ -821,16 +845,17 @@ class FetchChecker
|
||||
}
|
||||
|
||||
if ($array_var_id === $var_id) {
|
||||
if ($type instanceof Type\ObjectLike ||
|
||||
($type->isGenericArray() && !$key_type->hasInt() && $type->type_params[1]->isEmpty())
|
||||
if ($type instanceof Type\Atomic\ObjectLike ||
|
||||
(
|
||||
$type instanceof TArray &&
|
||||
!$key_type->hasInt() &&
|
||||
$type->type_params[1]->isEmpty()
|
||||
)
|
||||
) {
|
||||
$properties = $key_value ? [$key_value => $keyed_assignment_type] : [];
|
||||
|
||||
$assignment_type = new Type\Union([
|
||||
new Type\ObjectLike(
|
||||
'array',
|
||||
$properties
|
||||
)
|
||||
new Type\Atomic\ObjectLike($properties)
|
||||
]);
|
||||
} else {
|
||||
if (!$keyed_assignment_type) {
|
||||
@ -838,13 +863,10 @@ class FetchChecker
|
||||
}
|
||||
|
||||
$assignment_type = new Type\Union([
|
||||
new Type\Generic(
|
||||
'array',
|
||||
[
|
||||
$key_type,
|
||||
$keyed_assignment_type
|
||||
]
|
||||
)
|
||||
new Type\Atomic\TArray([
|
||||
$key_type,
|
||||
$keyed_assignment_type
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
@ -858,7 +880,9 @@ class FetchChecker
|
||||
}
|
||||
}
|
||||
|
||||
if ($type instanceof Type\Generic && $type->type_params[$value_index]->isEmpty()) {
|
||||
if ($type instanceof Type\Atomic\TArray &&
|
||||
$type->type_params[$value_index]->isEmpty()
|
||||
) {
|
||||
$empty_type = Type::getEmptyArray();
|
||||
|
||||
if (!isset($stmt->inferredType)) {
|
||||
@ -873,14 +897,14 @@ class FetchChecker
|
||||
|
||||
for ($i = 0; $i < $nesting + 1; $i++) {
|
||||
if (isset($array_type->types['array']) &&
|
||||
$array_type->types['array'] instanceof Type\Generic
|
||||
$array_type->types['array'] instanceof Type\Atomic\TArray
|
||||
) {
|
||||
$atomic_array = $array_type->types['array'];
|
||||
|
||||
if ($i < $nesting) {
|
||||
if ($atomic_array->type_params[1]->isEmpty()) {
|
||||
$new_empty = clone $empty_type;
|
||||
/** @var Type\Generic */
|
||||
/** @var Type\Atomic\TArray */
|
||||
$new_atomic_empty = $new_empty->types['array'];
|
||||
$new_atomic_empty->type_params[0] = $key_type;
|
||||
|
||||
@ -901,9 +925,9 @@ class FetchChecker
|
||||
|
||||
$context->vars_in_scope[$var_id] = $context_type;
|
||||
}
|
||||
} elseif ($type instanceof Type\Generic && $value_index !== null) {
|
||||
} elseif ($type instanceof Type\Atomic\TArray && $value_index !== null) {
|
||||
$stmt->inferredType = $type->type_params[$value_index];
|
||||
} elseif ($type instanceof Type\ObjectLike) {
|
||||
} elseif ($type instanceof Type\Atomic\ObjectLike) {
|
||||
if ($key_value && isset($type->properties[$key_value])) {
|
||||
$stmt->inferredType = clone $type->properties[$key_value];
|
||||
} elseif ($key_type->hasInt()) {
|
||||
@ -932,7 +956,7 @@ class FetchChecker
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($type->isString()) {
|
||||
} elseif ($type instanceof TString) {
|
||||
if ($key_type) {
|
||||
$key_type = Type::combineUnionTypes($key_type, Type::getInt());
|
||||
} else {
|
||||
@ -946,7 +970,7 @@ class FetchChecker
|
||||
}
|
||||
|
||||
$stmt->inferredType = Type::getString();
|
||||
} elseif ($type->isNull()) {
|
||||
} elseif ($type instanceof TNull) {
|
||||
if (IssueBuffer::accepts(
|
||||
new NullArrayAccess(
|
||||
'Cannot access array value on possibly null variable ' . $array_var_id . ' of type ' . $var_type,
|
||||
@ -961,7 +985,7 @@ class FetchChecker
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} elseif ($type->isMixed() || $type->isEmpty()) {
|
||||
} elseif ($type instanceof TMixed || $type instanceof TEmpty) {
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedArrayAccess(
|
||||
'Cannot access array value on mixed variable ' . $array_var_id,
|
||||
@ -972,12 +996,15 @@ class FetchChecker
|
||||
$stmt->inferredType = Type::getMixed();
|
||||
break;
|
||||
}
|
||||
} elseif (strtolower($type->value) !== 'simplexmlelement' &&
|
||||
!ClassChecker::classImplements($type->value, 'ArrayAccess')
|
||||
} elseif (!$type instanceof TNamedObject ||
|
||||
(strtolower($type->value) !== 'simplexmlelement' &&
|
||||
!ClassChecker::classImplements($type->value, 'ArrayAccess')
|
||||
)
|
||||
) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidArrayAccess(
|
||||
'Cannot access array value on non-array variable ' . $array_var_id . ' of type ' . $var_type,
|
||||
'Cannot access array value on non-array variable ' .
|
||||
$array_var_id . ' of type ' . $var_type,
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
@ -999,15 +1026,15 @@ class FetchChecker
|
||||
|
||||
if (!$key_type) {
|
||||
$key_type = new Type\Union([
|
||||
new Type\Atomic('int'),
|
||||
new Type\Atomic('string')
|
||||
new TInt,
|
||||
new TString
|
||||
]);
|
||||
}
|
||||
|
||||
if ($stmt->dim) {
|
||||
if (isset($stmt->dim->inferredType) && $key_type && !$key_type->isEmpty()) {
|
||||
foreach ($stmt->dim->inferredType->types as $at) {
|
||||
if (($at->isMixed() || $at->isEmpty()) &&
|
||||
if (($at instanceof TMixed || $at instanceof TEmpty) &&
|
||||
$inferred_key_type &&
|
||||
!$inferred_key_type->isMixed() &&
|
||||
!$inferred_key_type->isEmpty()
|
||||
@ -1058,7 +1085,7 @@ class FetchChecker
|
||||
$var_id,
|
||||
CodeLocation $code_location
|
||||
) {
|
||||
if ($type->value === 'null') {
|
||||
if ($type instanceof TNull) {
|
||||
if (IssueBuffer::accepts(
|
||||
new NullReference(
|
||||
'Cannot assign value on possibly null array' . ($var_id ? ' ' . $var_id : ''),
|
||||
@ -1072,16 +1099,16 @@ class FetchChecker
|
||||
return $type;
|
||||
}
|
||||
|
||||
if ($type->value === 'string' && $assignment_value_type->hasString() && !$assignment_key_type->hasString()) {
|
||||
if ($type instanceof TString && $assignment_value_type->hasString() && !$assignment_key_type->hasString()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$type->isArray() &&
|
||||
!ClassChecker::classImplements($type->value, 'ArrayAccess')
|
||||
if (!$type instanceof TArray &&
|
||||
(!$type instanceof TNamedObject || !ClassChecker::classImplements($type->value, 'ArrayAccess'))
|
||||
) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidArrayAssignment(
|
||||
'Cannot assign value on variable' . ($var_id ? ' ' . $var_id : '') . ' of type ' . $type->value . ' that does not ' .
|
||||
'Cannot assign value on variable' . ($var_id ? ' ' . $var_id : '') . ' of type ' . $type . ' that does not ' .
|
||||
'implement ArrayAccess',
|
||||
$code_location
|
||||
),
|
||||
@ -1093,7 +1120,7 @@ class FetchChecker
|
||||
return $type;
|
||||
}
|
||||
|
||||
if ($type instanceof Type\Generic && $type->value === 'array') {
|
||||
if ($type instanceof Type\Atomic\Generic && $type instanceof TArray) {
|
||||
if ($type->type_params[1]->isEmpty()) {
|
||||
$type->type_params[0] = $assignment_key_type;
|
||||
$type->type_params[1] = $assignment_value_type;
|
||||
|
@ -28,6 +28,26 @@ use Psalm\Issue\UnrecognizedExpression;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\StatementsSource;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\Generic;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\Scalar;
|
||||
use Psalm\Type\Atomic\TNumeric;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TVoid;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TBool;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TEmpty;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
use Psalm\Type\Atomic\TResource;
|
||||
use Psalm\Type\Atomic\TCallable;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TGenericObject;
|
||||
use Psalm\Type\Atomic\TNumericString;
|
||||
|
||||
class ExpressionChecker
|
||||
{
|
||||
@ -205,7 +225,7 @@ class ExpressionChecker
|
||||
) {
|
||||
$use_context->vars_in_scope['$this'] = clone $context->vars_in_scope['$this'];
|
||||
} elseif ($context->self) {
|
||||
$use_context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic($context->self)]);
|
||||
$use_context->vars_in_scope['$this'] = new Type\Union([new TNamedObject($context->self)]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -637,18 +657,15 @@ class ExpressionChecker
|
||||
|
||||
// if this array looks like an object-like array, let's return that instead
|
||||
if ($item_value_type && $item_key_type && $item_key_type->hasString() && !$item_key_type->hasInt()) {
|
||||
$stmt->inferredType = new Type\Union([new Type\ObjectLike('array', $property_types)]);
|
||||
$stmt->inferredType = new Type\Union([new Type\Atomic\ObjectLike($property_types)]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt->inferredType = new Type\Union([
|
||||
new Type\Generic(
|
||||
'array',
|
||||
[
|
||||
$item_key_type ?: new Type\Union([new Type\Atomic('int'), new Type\Atomic('string')]),
|
||||
$item_value_type ?: Type::getMixed()
|
||||
]
|
||||
)
|
||||
new Type\Atomic\TArray([
|
||||
$item_key_type ?: new Type\Union([new TInt, new TString]),
|
||||
$item_value_type ?: Type::getMixed()
|
||||
])
|
||||
]);
|
||||
|
||||
return null;
|
||||
@ -887,8 +904,8 @@ class ExpressionChecker
|
||||
if ($left_type && $right_type) {
|
||||
foreach ($left_type->types as $left_type_part) {
|
||||
foreach ($right_type->types as $right_type_part) {
|
||||
if ($left_type_part->isMixed() || $right_type_part->isMixed()) {
|
||||
if ($left_type_part->isMixed()) {
|
||||
if ($left_type_part instanceof TMixed || $right_type_part instanceof TMixed) {
|
||||
if ($left_type_part instanceof TMixed) {
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedOperand(
|
||||
'Left operand cannot be mixed',
|
||||
@ -914,9 +931,9 @@ class ExpressionChecker
|
||||
return;
|
||||
}
|
||||
|
||||
if ($left_type_part->isArray() || $right_type_part->isArray()) {
|
||||
if (!$right_type_part->isArray() || !$left_type_part->isArray()) {
|
||||
if (!$left_type_part->isArray()) {
|
||||
if ($left_type_part instanceof TArray || $right_type_part instanceof TArray) {
|
||||
if (!$right_type_part instanceof TArray || !$left_type_part instanceof TArray) {
|
||||
if (!$left_type_part instanceof TArray) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidOperand(
|
||||
'Cannot add an array to a non-array',
|
||||
@ -926,7 +943,7 @@ class ExpressionChecker
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
} elseif (!$right_type_part->isArray()) {
|
||||
} elseif (!$right_type_part instanceof TArray) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidOperand(
|
||||
'Cannot add an array to a non-array',
|
||||
@ -954,9 +971,7 @@ class ExpressionChecker
|
||||
}
|
||||
|
||||
if ($left_type_part->isNumericType() || $right_type_part->isNumericType()) {
|
||||
if ($left_type_part->isInt() &&
|
||||
$right_type_part->isInt()
|
||||
) {
|
||||
if ($left_type_part instanceof TInt && $right_type_part instanceof TInt) {
|
||||
if (!$result_type) {
|
||||
$result_type = Type::getInt();
|
||||
} else {
|
||||
@ -966,9 +981,7 @@ class ExpressionChecker
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($left_type_part->isFloat() &&
|
||||
$right_type_part->isFloat()
|
||||
) {
|
||||
if ($left_type_part instanceof TFloat && $right_type_part instanceof TFloat) {
|
||||
if (!$result_type) {
|
||||
$result_type = Type::getFloat();
|
||||
} else {
|
||||
@ -978,8 +991,8 @@ class ExpressionChecker
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($left_type_part->isFloat() && $right_type_part->isInt()) ||
|
||||
($left_type_part->isInt() && $right_type_part->isFloat())
|
||||
if (($left_type_part instanceof TFloat && $right_type_part instanceof TInt) ||
|
||||
($left_type_part instanceof TInt && $right_type_part instanceof TFloat)
|
||||
) {
|
||||
if ($config->strict_binary_operands) {
|
||||
if (IssueBuffer::accepts(
|
||||
@ -1269,48 +1282,53 @@ class ExpressionChecker
|
||||
*/
|
||||
protected static function fleshOutAtomicType(Type\Atomic $return_type, array $args, $calling_class, $method_id)
|
||||
{
|
||||
if ($return_type->value === '$this' || $return_type->value === 'static' || $return_type->value === 'self') {
|
||||
if (!$calling_class) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Cannot handle ' . $return_type->value . ' when $calling_class is empty'
|
||||
);
|
||||
}
|
||||
if ($return_type instanceof TNamedObject) {
|
||||
if ($return_type->value === '$this' ||
|
||||
$return_type->value === 'static' ||
|
||||
$return_type->value === 'self'
|
||||
) {
|
||||
if (!$calling_class) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Cannot handle ' . $return_type->value . ' when $calling_class is empty'
|
||||
);
|
||||
}
|
||||
|
||||
if ($return_type->value === 'static' || !$method_id) {
|
||||
$return_type->value = $calling_class;
|
||||
} else {
|
||||
list(, $method_name) = explode('::', $method_id);
|
||||
if ($return_type->value === 'static' || !$method_id) {
|
||||
$return_type->value = $calling_class;
|
||||
} else {
|
||||
list(, $method_name) = explode('::', $method_id);
|
||||
|
||||
$appearing_method_id = MethodChecker::getAppearingMethodId($calling_class . '::' . $method_name);
|
||||
$appearing_method_id = MethodChecker::getAppearingMethodId($calling_class . '::' . $method_name);
|
||||
|
||||
$return_type->value = explode('::', (string)$appearing_method_id)[0];
|
||||
}
|
||||
} elseif ($return_type->value[0] === '$' && $method_id) {
|
||||
$method_params = MethodChecker::getMethodParams($method_id);
|
||||
$return_type->value = explode('::', (string)$appearing_method_id)[0];
|
||||
}
|
||||
} elseif ($return_type->value[0] === '$' && $method_id) {
|
||||
$method_params = MethodChecker::getMethodParams($method_id);
|
||||
|
||||
if (!$method_params) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Cannot get method params of ' . $method_id
|
||||
);
|
||||
}
|
||||
if (!$method_params) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Cannot get method params of ' . $method_id
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($args as $i => $arg) {
|
||||
$method_param = $method_params[$i];
|
||||
foreach ($args as $i => $arg) {
|
||||
$method_param = $method_params[$i];
|
||||
|
||||
if ($return_type->value === '$' . $method_param->name) {
|
||||
$arg_value = $arg->value;
|
||||
if ($arg_value instanceof PhpParser\Node\Scalar\String_) {
|
||||
$return_type->value = preg_replace('/^\\\/', '', $arg_value->value);
|
||||
if ($return_type->value === '$' . $method_param->name) {
|
||||
$arg_value = $arg->value;
|
||||
if ($arg_value instanceof PhpParser\Node\Scalar\String_) {
|
||||
$return_type->value = preg_replace('/^\\\/', '', $arg_value->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($return_type->value[0] === '$') {
|
||||
$return_type = new Type\Atomic('mixed');
|
||||
if ($return_type->value[0] === '$') {
|
||||
$return_type = new TMixed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($return_type instanceof Type\Generic) {
|
||||
if ($return_type instanceof Type\Atomic\TArray || $return_type instanceof Type\Atomic\TGenericObject) {
|
||||
foreach ($return_type->type_params as &$type_param) {
|
||||
$type_param = self::fleshOutTypes($type_param, $args, $calling_class, $method_id);
|
||||
}
|
||||
|
@ -552,7 +552,7 @@ class StatementsChecker extends SourceChecker implements StatementsSource
|
||||
if (isset($dim->inferredType)) {
|
||||
return $dim->inferredType;
|
||||
} else {
|
||||
return new Type\Union([Type::getInt()->types['int'], Type::getString()->types['string']]);
|
||||
return new Type\Union([new Type\Atomic\TInt(), new Type\Atomic\TString()]);
|
||||
}
|
||||
} else {
|
||||
return Type::getInt();
|
||||
|
@ -11,6 +11,27 @@ use Psalm\Issue\TypeDoesNotContainType;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\StatementsSource;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\Generic;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\Scalar;
|
||||
use Psalm\Type\Atomic\TScalar;
|
||||
use Psalm\Type\Atomic\TNumeric;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TVoid;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TBool;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TEmpty;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
use Psalm\Type\Atomic\TResource;
|
||||
use Psalm\Type\Atomic\TCallable;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TGenericObject;
|
||||
use Psalm\Type\Atomic\TNumericString;
|
||||
|
||||
class TypeChecker
|
||||
{
|
||||
@ -597,7 +618,7 @@ class TypeChecker
|
||||
if ($new_var_type === 'empty') {
|
||||
if ($existing_var_type->hasType('bool')) {
|
||||
$existing_var_type->removeType('bool');
|
||||
$existing_var_type->types['false'] = new Type\Atomic('false');
|
||||
$existing_var_type->types['false'] = new TFalse;
|
||||
}
|
||||
|
||||
$existing_var_type->removeObjects();
|
||||
@ -625,7 +646,7 @@ class TypeChecker
|
||||
|
||||
if ($new_var_type === 'numeric' && $existing_var_type->hasString()) {
|
||||
$existing_var_type->removeType('string');
|
||||
$existing_var_type->types['numeric-string'] = new Type\Atomic('numeric-string');
|
||||
$existing_var_type->types['numeric-string'] = new TNumericString;
|
||||
|
||||
return $existing_var_type;
|
||||
}
|
||||
@ -701,7 +722,7 @@ class TypeChecker
|
||||
$has_type_mismatch = false;
|
||||
|
||||
foreach ($input_type->types as $input_type_part) {
|
||||
if ($input_type_part->isNull() && $ignore_null) {
|
||||
if ($input_type_part instanceof TNull && $ignore_null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -709,18 +730,19 @@ class TypeChecker
|
||||
$scalar_type_match_found = false;
|
||||
|
||||
foreach ($container_type->types as $container_type_part) {
|
||||
if ($container_type_part->isNull() && $ignore_null) {
|
||||
if ($container_type_part instanceof TNull && $ignore_null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$input_is_object = $input_type_part->isObjectType();
|
||||
$container_is_object = $container_type_part->isObjectType();
|
||||
|
||||
if (strtolower($input_type_part->value) === strtolower($container_type_part->value) ||
|
||||
if ($input_type_part->shallowEquals($container_type_part) ||
|
||||
(
|
||||
$input_is_object &&
|
||||
$container_is_object &&
|
||||
!$input_type_part->isObject() &&
|
||||
$input_type_part instanceof TNamedObject &&
|
||||
$container_type_part instanceof TNamedObject &&
|
||||
ClassChecker::classExists($input_type_part->value, $file_checker) &&
|
||||
(
|
||||
ClassChecker::classExtendsOrImplements(
|
||||
@ -733,7 +755,7 @@ class TypeChecker
|
||||
) {
|
||||
$all_types_contain = true;
|
||||
|
||||
if ($input_type_part instanceof Type\Generic && $container_type_part instanceof Type\Generic) {
|
||||
if ($input_type_part instanceof TArray && $container_type_part instanceof TArray) {
|
||||
foreach ($input_type_part->type_params as $i => $input_param) {
|
||||
$container_param = $container_type_part->type_params[$i];
|
||||
|
||||
@ -763,45 +785,50 @@ class TypeChecker
|
||||
break;
|
||||
}
|
||||
|
||||
if ($input_type_part->value === 'false' && $container_type_part->value === 'bool') {
|
||||
if ($input_type_part instanceof TFalse && $container_type_part instanceof TBool) {
|
||||
$type_match_found = true;
|
||||
}
|
||||
|
||||
if ($input_type_part->value === 'int' && $container_type_part->value === 'float') {
|
||||
if ($input_type_part instanceof TInt && $container_type_part instanceof TFloat) {
|
||||
$type_match_found = true;
|
||||
}
|
||||
|
||||
if ($input_type_part->value === 'Closure' && $container_type_part->value === 'callable') {
|
||||
if ($input_type_part instanceof TNamedObject &&
|
||||
$input_type_part->value === 'Closure' &&
|
||||
$container_type_part instanceof TCallable
|
||||
) {
|
||||
$type_match_found = true;
|
||||
}
|
||||
|
||||
if ($container_type_part->isNumeric() && $input_type_part->isNumericType()) {
|
||||
if ($container_type_part instanceof TNumeric && $input_type_part->isNumericType()) {
|
||||
$type_match_found = true;
|
||||
}
|
||||
|
||||
if ($container_type_part->isGenericArray() && $input_type_part->isObjectLike()) {
|
||||
if ($container_type_part instanceof TArray && $input_type_part instanceof ObjectLike) {
|
||||
$type_match_found = true;
|
||||
}
|
||||
|
||||
if ($container_type_part->isIterable() &&
|
||||
if ($container_type_part instanceof TNamedObject &&
|
||||
strtolower($container_type_part->value) === 'iterable' &&
|
||||
(
|
||||
$input_type_part->isArray() ||
|
||||
ClassChecker::classExtendsOrImplements(
|
||||
$input_type_part->value,
|
||||
'Traversable'
|
||||
$input_type_part instanceof TArray ||
|
||||
($input_type_part instanceof TNamedObject &&
|
||||
ClassChecker::classExtendsOrImplements(
|
||||
$input_type_part->value,
|
||||
'Traversable'
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
$type_match_found = true;
|
||||
}
|
||||
|
||||
if ($container_type_part->isScalar() && $input_type_part->isScalarType()) {
|
||||
if ($container_type_part instanceof TScalar && $input_type_part instanceof Scalar) {
|
||||
$type_match_found = true;
|
||||
}
|
||||
|
||||
if ($container_type_part->isString() &&
|
||||
$input_type_part->isObjectType() &&
|
||||
!$input_type_part->isObject()
|
||||
if ($container_type_part instanceof TString &&
|
||||
$input_type_part instanceof TNamedObject
|
||||
) {
|
||||
// check whether the object has a __toString method
|
||||
if (ClassChecker::classExists($input_type_part->value, $file_checker) &&
|
||||
@ -812,30 +839,30 @@ class TypeChecker
|
||||
}
|
||||
}
|
||||
|
||||
if ($container_type_part->isCallable() &&
|
||||
($input_type_part->value === 'string' || $input_type_part->value === 'array')
|
||||
if ($container_type_part instanceof TCallable &&
|
||||
($input_type_part instanceof TString || $input_type_part instanceof TArray)
|
||||
) {
|
||||
// @todo add value checks if possible here
|
||||
$type_match_found = true;
|
||||
}
|
||||
|
||||
if ($input_type_part->isNumeric()) {
|
||||
if ($input_type_part instanceof TNumeric) {
|
||||
if ($container_type_part->isNumericType()) {
|
||||
$scalar_type_match_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($input_type_part->isScalarType() || $input_type_part->isScalar()) {
|
||||
if ($container_type_part->isScalarType()) {
|
||||
if ($input_type_part instanceof Scalar || $input_type_part instanceof TScalar) {
|
||||
if ($container_type_part instanceof Scalar) {
|
||||
$scalar_type_match_found = true;
|
||||
}
|
||||
} elseif ($container_type_part->isObject() &&
|
||||
!$input_type_part->isArray() &&
|
||||
!$input_type_part->isResource()
|
||||
} elseif ($container_type_part instanceof TObject &&
|
||||
!$input_type_part instanceof TArray &&
|
||||
!$input_type_part instanceof TResource
|
||||
) {
|
||||
$type_match_found = true;
|
||||
} elseif ($input_is_object &&
|
||||
$container_is_object &&
|
||||
} elseif ($container_type_part instanceof TNamedObject &&
|
||||
$input_type_part instanceof TNamedObject &&
|
||||
ClassChecker::classExists($container_type_part->value, $file_checker) &&
|
||||
ClassChecker::classOrInterfaceExists($input_type_part->value, $file_checker) &&
|
||||
ClassChecker::classExtendsOrImplements(
|
||||
@ -888,14 +915,15 @@ class TypeChecker
|
||||
$new_base_type = null;
|
||||
|
||||
foreach ($existing_keys[$base_key]->types as $existing_key_type_part) {
|
||||
if ($existing_key_type_part->isNull()) {
|
||||
if ($existing_key_type_part instanceof TNull) {
|
||||
$class_property_type = Type::getNull();
|
||||
} elseif ($existing_key_type_part->isMixed() ||
|
||||
$existing_key_type_part->isObject() ||
|
||||
strtolower($existing_key_type_part->value) === 'stdclass'
|
||||
} elseif ($existing_key_type_part instanceof TMixed ||
|
||||
$existing_key_type_part instanceof TObject ||
|
||||
($existing_key_type_part instanceof TNamedObject &&
|
||||
strtolower($existing_key_type_part->value) === 'stdclass')
|
||||
) {
|
||||
$class_property_type = Type::getMixed();
|
||||
} else {
|
||||
} elseif ($existing_key_type_part instanceof TNamedObject) {
|
||||
$property_id = $existing_key_type_part->value . '::$' . $key_parts[$i];
|
||||
|
||||
if (!ClassLikeChecker::propertyExists($property_id)) {
|
||||
@ -909,6 +937,9 @@ class TypeChecker
|
||||
$class_property_type = $class_storage->properties[$key_parts[$i]]->type;
|
||||
|
||||
$class_property_type = $class_property_type ? clone $class_property_type : Type::getMixed();
|
||||
} else {
|
||||
// @todo handle this
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($new_base_type instanceof Type\Union) {
|
||||
@ -959,9 +990,9 @@ class TypeChecker
|
||||
$new_base_type = null;
|
||||
|
||||
foreach ($existing_keys[$base_key]->types as $existing_key_type_part) {
|
||||
if ($existing_key_type_part instanceof Type\Generic) {
|
||||
if ($existing_key_type_part instanceof Type\Atomic\TArray) {
|
||||
$new_base_type_candidate = clone $existing_key_type_part->type_params[1];
|
||||
} elseif (!$existing_key_type_part instanceof Type\ObjectLike) {
|
||||
} elseif (!$existing_key_type_part instanceof Type\Atomic\ObjectLike) {
|
||||
return null;
|
||||
} else {
|
||||
$array_properties = $existing_key_type_part->properties;
|
||||
@ -1252,11 +1283,15 @@ class TypeChecker
|
||||
|
||||
$inferred_atomic_type = $inferred_type->types[$key];
|
||||
|
||||
if (!($declared_atomic_type instanceof Type\Generic)) {
|
||||
if (!$declared_atomic_type instanceof Type\Atomic\TArray &&
|
||||
!$declared_atomic_type instanceof Type\Atomic\TGenericObject
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!($inferred_atomic_type instanceof Type\Generic)) {
|
||||
if (!$inferred_atomic_type instanceof Type\Atomic\TArray &&
|
||||
!$inferred_atomic_type instanceof Type\Atomic\TGenericObject
|
||||
) {
|
||||
// @todo handle this better
|
||||
continue;
|
||||
}
|
||||
@ -1279,11 +1314,11 @@ class TypeChecker
|
||||
|
||||
$inferred_atomic_type = $inferred_type->types[$key];
|
||||
|
||||
if (!($declared_atomic_type instanceof Type\ObjectLike)) {
|
||||
if (!($declared_atomic_type instanceof Type\Atomic\ObjectLike)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!($inferred_atomic_type instanceof Type\ObjectLike)) {
|
||||
if (!($inferred_atomic_type instanceof Type\Atomic\ObjectLike)) {
|
||||
// @todo handle this better
|
||||
continue;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace Psalm;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Type\Atomic;
|
||||
|
||||
/**
|
||||
* A class for analysing a given method call's effects in relation to $this/self and also looking at return types
|
||||
@ -37,7 +38,7 @@ class EffectsAnalyser
|
||||
if (isset($stmt->inferredType)) {
|
||||
$return_types = array_merge(array_values($stmt->inferredType->types), $return_types);
|
||||
} else {
|
||||
$return_types[] = new Type\Atomic('mixed');
|
||||
$return_types[] = new Atomic\TMixed();
|
||||
}
|
||||
}
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\Yield_ || $stmt instanceof PhpParser\Node\Expr\YieldFrom) {
|
||||
@ -88,7 +89,7 @@ class EffectsAnalyser
|
||||
$value_type = null;
|
||||
|
||||
foreach ($yield_types as $type) {
|
||||
if ($type instanceof Type\Generic) {
|
||||
if ($type instanceof Type\Atomic\TArray || $type instanceof Type\Atomic\TGenericObject) {
|
||||
$first_type_param = count($type->type_params) ? $type->type_params[0] : null;
|
||||
$last_type_param = $type->type_params[count($type->type_params) - 1];
|
||||
|
||||
@ -107,7 +108,7 @@ class EffectsAnalyser
|
||||
}
|
||||
|
||||
$yield_types = [
|
||||
new Type\Generic(
|
||||
new Atomic\TGenericObject(
|
||||
'Generator',
|
||||
[
|
||||
$key_type ?: Type::getMixed(),
|
||||
@ -124,8 +125,8 @@ class EffectsAnalyser
|
||||
) {
|
||||
// only add null if we have a return statement elsewhere and it wasn't void
|
||||
foreach ($return_types as $return_type) {
|
||||
if (!$return_type->isVoid()) {
|
||||
$return_types[] = new Type\Atomic('null');
|
||||
if (!$return_type instanceof Atomic\TVoid) {
|
||||
$return_types[] = new Atomic\TNull();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -149,7 +150,7 @@ class EffectsAnalyser
|
||||
}
|
||||
|
||||
if (isset($stmt->inferredType)) {
|
||||
$generator_type = new Type\Generic(
|
||||
$generator_type = new Atomic\TGenericObject(
|
||||
'Generator',
|
||||
[
|
||||
$key_type ?: Type::getInt(),
|
||||
@ -159,7 +160,7 @@ class EffectsAnalyser
|
||||
|
||||
return [$generator_type];
|
||||
} else {
|
||||
return [new Type\Atomic('mixed')];
|
||||
return [new Atomic\TMixed()];
|
||||
}
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\YieldFrom) {
|
||||
$key_type = null;
|
||||
@ -167,7 +168,7 @@ class EffectsAnalyser
|
||||
if (isset($stmt->inferredType)) {
|
||||
return [$stmt->inferredType->types];
|
||||
} else {
|
||||
return [new Type\Atomic('mixed')];
|
||||
return [new Atomic\TMixed()];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,26 @@ namespace Psalm;
|
||||
|
||||
use Psalm\Exception\TypeParseTreeException;
|
||||
use Psalm\Type\Atomic;
|
||||
use Psalm\Type\Generic;
|
||||
use Psalm\Type\ObjectLike;
|
||||
use Psalm\Type\Atomic\Generic;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\Scalar;
|
||||
use Psalm\Type\Atomic\TNumeric;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TVoid;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TBool;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TEmpty;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
use Psalm\Type\Atomic\TResource;
|
||||
use Psalm\Type\Atomic\TCallable;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TGenericObject;
|
||||
use Psalm\Type\Atomic\TNumericString;
|
||||
use Psalm\Type\ParseTree;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
@ -32,17 +50,12 @@ abstract class Type
|
||||
if (count($type_tokens) === 1) {
|
||||
$type_tokens[0] = self::fixScalarTerms($type_tokens[0]);
|
||||
|
||||
if ($type_tokens[0] === 'array') {
|
||||
return Type::getArray();
|
||||
}
|
||||
|
||||
return new Union([new Atomic($type_tokens[0])]);
|
||||
return new Union([Atomic::create($type_tokens[0])]);
|
||||
}
|
||||
|
||||
try {
|
||||
$parse_tree = ParseTree::createFromTokens($type_tokens);
|
||||
}
|
||||
catch (TypeParseTreeException $e) {
|
||||
} catch (TypeParseTreeException $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@ -66,6 +79,7 @@ abstract class Type
|
||||
[
|
||||
'numeric',
|
||||
'int',
|
||||
'void',
|
||||
'float',
|
||||
'string',
|
||||
'bool',
|
||||
@ -94,7 +108,7 @@ abstract class Type
|
||||
|
||||
/**
|
||||
* @param ParseTree $parse_tree
|
||||
* @return Atomic|Generic|ObjectLike|Union
|
||||
* @return Atomic|TArray|TGenericObject|ObjectLike|Union
|
||||
*/
|
||||
private static function getTypeFromTree(ParseTree $parse_tree)
|
||||
{
|
||||
@ -125,7 +139,7 @@ abstract class Type
|
||||
if (($generic_type_value === 'array' || $generic_type_value === 'Generator') &&
|
||||
count($generic_params) === 1
|
||||
) {
|
||||
array_unshift($generic_params, Type::getMixed());
|
||||
array_unshift($generic_params, new Union([new TMixed]));
|
||||
}
|
||||
|
||||
if (!$generic_params) {
|
||||
@ -133,10 +147,10 @@ abstract class Type
|
||||
}
|
||||
|
||||
if ($generic_type_value === 'array') {
|
||||
return new Type\GenericArray($generic_type_value, $generic_params);
|
||||
return new TArray($generic_params);
|
||||
}
|
||||
|
||||
return new Generic($generic_type_value, $generic_params);
|
||||
return new TGenericObject($generic_type_value, $generic_params);
|
||||
}
|
||||
|
||||
if ($parse_tree->value === ParseTree::UNION) {
|
||||
@ -174,20 +188,16 @@ abstract class Type
|
||||
$properties[(string)($property_branch->children[0]->value)] = $property_type;
|
||||
}
|
||||
|
||||
if (!$type->value) {
|
||||
throw new \InvalidArgumentException('Object-like type must have a value');
|
||||
if ($type->value !== 'array') {
|
||||
throw new \InvalidArgumentException('Object-like type must be array');
|
||||
}
|
||||
|
||||
return new ObjectLike($type->value, $properties);
|
||||
return new ObjectLike($properties);
|
||||
}
|
||||
|
||||
$atomic_type = self::fixScalarTerms($parse_tree->value);
|
||||
|
||||
if ($atomic_type === 'array') {
|
||||
return self::getArray()->types['array'];
|
||||
}
|
||||
|
||||
return new Atomic($atomic_type);
|
||||
return Atomic::create($atomic_type);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -262,7 +272,7 @@ abstract class Type
|
||||
*/
|
||||
public static function getInt()
|
||||
{
|
||||
$type = new Atomic('int');
|
||||
$type = new TInt;
|
||||
|
||||
return new Union([$type]);
|
||||
}
|
||||
@ -272,7 +282,7 @@ abstract class Type
|
||||
*/
|
||||
public static function getString()
|
||||
{
|
||||
$type = new Atomic('string');
|
||||
$type = new TString;
|
||||
|
||||
return new Union([$type]);
|
||||
}
|
||||
@ -282,7 +292,7 @@ abstract class Type
|
||||
*/
|
||||
public static function getNull()
|
||||
{
|
||||
$type = new Atomic('null');
|
||||
$type = new TNull;
|
||||
|
||||
return new Union([$type]);
|
||||
}
|
||||
@ -292,7 +302,7 @@ abstract class Type
|
||||
*/
|
||||
public static function getMixed()
|
||||
{
|
||||
$type = new Atomic('mixed');
|
||||
$type = new TMixed;
|
||||
|
||||
return new Union([$type]);
|
||||
}
|
||||
@ -302,7 +312,7 @@ abstract class Type
|
||||
*/
|
||||
public static function getBool()
|
||||
{
|
||||
$type = new Atomic('bool');
|
||||
$type = new TBool;
|
||||
|
||||
return new Union([$type]);
|
||||
}
|
||||
@ -312,7 +322,7 @@ abstract class Type
|
||||
*/
|
||||
public static function getFloat()
|
||||
{
|
||||
$type = new Atomic('float');
|
||||
$type = new TFloat;
|
||||
|
||||
return new Union([$type]);
|
||||
}
|
||||
@ -322,7 +332,7 @@ abstract class Type
|
||||
*/
|
||||
public static function getObject()
|
||||
{
|
||||
$type = new Atomic('object');
|
||||
$type = new TObject;
|
||||
|
||||
return new Union([$type]);
|
||||
}
|
||||
@ -332,7 +342,7 @@ abstract class Type
|
||||
*/
|
||||
public static function getClosure()
|
||||
{
|
||||
$type = new Atomic('Closure');
|
||||
$type = new TNamedObject('Closure');
|
||||
|
||||
return new Union([$type]);
|
||||
}
|
||||
@ -342,11 +352,10 @@ abstract class Type
|
||||
*/
|
||||
public static function getArray()
|
||||
{
|
||||
$type = new Type\GenericArray(
|
||||
'array',
|
||||
$type = new TArray(
|
||||
[
|
||||
Type::getMixed(),
|
||||
Type::getMixed()
|
||||
new Type\Union([new TMixed]),
|
||||
new Type\Union([new TMixed])
|
||||
]
|
||||
);
|
||||
|
||||
@ -359,11 +368,10 @@ abstract class Type
|
||||
public static function getEmptyArray()
|
||||
{
|
||||
return new Type\Union([
|
||||
new Type\GenericArray(
|
||||
'array',
|
||||
new TArray(
|
||||
[
|
||||
new Type\Union([new Type\Atomic('empty')]),
|
||||
new Type\Union([new Type\Atomic('empty')])
|
||||
new Type\Union([new TEmpty]),
|
||||
new Type\Union([new TEmpty])
|
||||
]
|
||||
)
|
||||
]);
|
||||
@ -374,7 +382,7 @@ abstract class Type
|
||||
*/
|
||||
public static function getVoid()
|
||||
{
|
||||
$type = new Atomic('void');
|
||||
$type = new TVoid;
|
||||
|
||||
return new Union([$type]);
|
||||
}
|
||||
@ -384,7 +392,7 @@ abstract class Type
|
||||
*/
|
||||
public static function getFalse()
|
||||
{
|
||||
$type = new Atomic('false');
|
||||
$type = new TFalse;
|
||||
|
||||
return new Union([$type]);
|
||||
}
|
||||
@ -399,8 +407,8 @@ abstract class Type
|
||||
foreach ($redefined_vars as $var_name => $redefined_union_type) {
|
||||
foreach ($redefined_union_type->types as $redefined_atomic_type) {
|
||||
foreach ($context->vars_in_scope[$var_name]->types as $context_type) {
|
||||
if ($context_type instanceof Type\Generic &&
|
||||
$redefined_atomic_type instanceof Type\Generic &&
|
||||
if ($context_type instanceof Type\Atomic\TArray &&
|
||||
$redefined_atomic_type instanceof Type\Atomic\TArray &&
|
||||
$context_type->value === $redefined_atomic_type->value
|
||||
) {
|
||||
// index of last param
|
||||
@ -463,8 +471,8 @@ abstract class Type
|
||||
}
|
||||
|
||||
if (count($types) === 1) {
|
||||
if ($types[0]->value === 'false') {
|
||||
$types[0]->value = 'bool';
|
||||
if ($types[0] instanceof TFalse) {
|
||||
$types[0] = new TBool;
|
||||
}
|
||||
|
||||
return new Union([$types[0]]);
|
||||
@ -492,7 +500,7 @@ abstract class Type
|
||||
|
||||
if (count($value_types) === 1) {
|
||||
if (isset($value_types['false'])) {
|
||||
return self::getBool();
|
||||
return Type::getBool();
|
||||
}
|
||||
} elseif (isset($value_types['void'])) {
|
||||
unset($value_types['void']);
|
||||
@ -508,7 +516,7 @@ abstract class Type
|
||||
// special case for ObjectLike where $value_type is actually an array of properties
|
||||
if ($generic_type === 'object-like') {
|
||||
if (!isset($value_types['array']) || isset($value_types['array']['empty'])) {
|
||||
$new_types[] = new ObjectLike('array', $value_type);
|
||||
$new_types[] = new ObjectLike($value_type);
|
||||
}
|
||||
|
||||
continue;
|
||||
@ -542,9 +550,16 @@ abstract class Type
|
||||
array_unshift($generic_type_params, self::combineTypes($expanded_key_types));
|
||||
}
|
||||
|
||||
$new_types[] = $value_type_param
|
||||
? new Generic($generic_type, $generic_type_params)
|
||||
: new Atomic($generic_type);
|
||||
if ($value_type_param) {
|
||||
if ($generic_type === 'array') {
|
||||
$new_types[] = new TArray($generic_type_params);
|
||||
} else {
|
||||
$new_types[] = new TGenericObject($generic_type, $generic_type_params);
|
||||
}
|
||||
} else {
|
||||
$new_types[] = Atomic::create($generic_type);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -560,7 +575,7 @@ abstract class Type
|
||||
array_values($expandable_value_type->types)
|
||||
);
|
||||
} else {
|
||||
$expanded_value_types = [Type::getMixed()->types['mixed']];
|
||||
$expanded_value_types = [new TMixed];
|
||||
}
|
||||
}
|
||||
|
||||
@ -570,8 +585,11 @@ abstract class Type
|
||||
array_unshift($generic_type_params, self::combineTypes($expanded_key_types));
|
||||
}
|
||||
|
||||
// we have a generic type with
|
||||
$new_types[] = new Generic($generic_type, $generic_type_params);
|
||||
if ($generic_type === 'array') {
|
||||
$new_types[] = new TArray($generic_type_params);
|
||||
} else {
|
||||
$new_types[] = new TGenericObject($generic_type, $generic_type_params);
|
||||
}
|
||||
}
|
||||
|
||||
$new_types = array_values($new_types);
|
||||
@ -588,32 +606,35 @@ abstract class Type
|
||||
public static function scrapeTypeProperties(Atomic $type, array &$key_types, array &$value_types)
|
||||
{
|
||||
// if we see the magic empty value and there's more than one type, ignore it
|
||||
if ($type->value === 'empty') {
|
||||
if ($type instanceof TEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($type->value === 'mixed') {
|
||||
if ($type instanceof TMixed) {
|
||||
return Type::getMixed();
|
||||
}
|
||||
|
||||
// deal with false|bool => bool
|
||||
if ($type->value === 'false' && isset($value_types['bool'])) {
|
||||
if ($type instanceof TFalse && isset($value_types['bool'])) {
|
||||
return null;
|
||||
} elseif ($type->value === 'bool' && isset($value_types['false'])) {
|
||||
} elseif ($type instanceof TBool && isset($value_types['false'])) {
|
||||
unset($value_types['false']);
|
||||
}
|
||||
|
||||
if ($type instanceof Generic) {
|
||||
if (!isset($value_types[$type->value])) {
|
||||
$value_types[$type->value] = [];
|
||||
$type_key = $type->getKey();
|
||||
|
||||
if ($type instanceof TArray || $type instanceof TGenericObject) {
|
||||
if (!isset($value_types[$type_key])) {
|
||||
$value_types[$type_key] = [];
|
||||
}
|
||||
|
||||
$value_type_param_index = count($type->type_params) - 1;
|
||||
$value_types[$type->value][(string) $type->type_params[$value_type_param_index]] =
|
||||
|
||||
$value_types[$type_key][(string) $type->type_params[$value_type_param_index]] =
|
||||
$type->type_params[$value_type_param_index];
|
||||
|
||||
if ($value_type_param_index) {
|
||||
$key_types[$type->value][(string) $type->type_params[0]] = $type->type_params[0];
|
||||
$key_types[$type_key][(string) $type->type_params[0]] = $type->type_params[0];
|
||||
}
|
||||
} elseif ($type instanceof ObjectLike) {
|
||||
foreach ($type->properties as $candidate_property_name => $candidate_property_type) {
|
||||
@ -631,15 +652,15 @@ abstract class Type
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!isset($value_types[$type->value])) {
|
||||
$value_types[$type->value] = [];
|
||||
if (!isset($value_types[$type_key])) {
|
||||
$value_types[$type_key] = [];
|
||||
}
|
||||
|
||||
if ($type->value === 'array') {
|
||||
if ($type instanceof TArray) {
|
||||
throw new \InvalidArgumentException('Cannot have a non-generic array');
|
||||
}
|
||||
|
||||
$value_types[$type->value][(string) $type] = null;
|
||||
$value_types[$type_key][(string) $type] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,67 +2,88 @@
|
||||
namespace Psalm\Type;
|
||||
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\Scalar;
|
||||
use Psalm\Type\Atomic\TNumeric;
|
||||
use Psalm\Type\Atomic\TInt;
|
||||
use Psalm\Type\Atomic\TVoid;
|
||||
use Psalm\Type\Atomic\TFloat;
|
||||
use Psalm\Type\Atomic\TString;
|
||||
use Psalm\Type\Atomic\TBool;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TEmpty;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TMixed;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
use Psalm\Type\Atomic\TResource;
|
||||
use Psalm\Type\Atomic\TCallable;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNumericString;
|
||||
use Psalm\Type\Union;
|
||||
use Psalm\Checker\ClassChecker;
|
||||
use Psalm\Checker\ClassLikeChecker;
|
||||
use Psalm\Checker\FileChecker;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\StatementsSource;
|
||||
|
||||
class Atomic extends Type
|
||||
abstract class Atomic extends Type
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $value;
|
||||
const KEY = 'atomic';
|
||||
|
||||
/**
|
||||
* Constructs an Atomic instance
|
||||
*
|
||||
* @param string $value
|
||||
* @param string $value
|
||||
* @return Atomic
|
||||
*/
|
||||
public function __construct($value)
|
||||
public static function create($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
switch ($value) {
|
||||
case 'numeric':
|
||||
return new TNumeric();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
if ($this->isNumericString()) {
|
||||
return 'string';
|
||||
case 'int':
|
||||
return new TInt();
|
||||
|
||||
case 'void':
|
||||
return new TVoid();
|
||||
|
||||
case 'float':
|
||||
return new TFloat();
|
||||
|
||||
case 'string':
|
||||
return new TString();
|
||||
|
||||
case 'bool':
|
||||
case 'true':
|
||||
return new TBool();
|
||||
|
||||
case 'false':
|
||||
return new TFalse();
|
||||
|
||||
case 'empty':
|
||||
return new TEmpty();
|
||||
|
||||
case 'null':
|
||||
return new TNull();
|
||||
|
||||
case 'array':
|
||||
return new TArray([new Union([new TMixed]), new Union([new TMixed])]);
|
||||
|
||||
case 'object':
|
||||
return new TObject();
|
||||
|
||||
case 'mixed':
|
||||
return new TMixed();
|
||||
|
||||
case 'resource':
|
||||
return new TResource();
|
||||
|
||||
case 'callable':
|
||||
return new TCallable();
|
||||
|
||||
default:
|
||||
return new TNamedObject($value);
|
||||
}
|
||||
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $aliased_classes
|
||||
* @param string|null $this_class
|
||||
* @param bool $use_phpdoc_format
|
||||
* @return string
|
||||
*/
|
||||
public function toNamespacedString(array $aliased_classes, $this_class, $use_phpdoc_format)
|
||||
{
|
||||
if (!$this->isObjectType()) {
|
||||
if ($this->isNumericString()) {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
if ($this->value === $this_class) {
|
||||
$class_parts = explode('\\', $this_class);
|
||||
return array_pop($class_parts);
|
||||
}
|
||||
|
||||
if (isset($aliased_classes[strtolower($this->value)])) {
|
||||
return $aliased_classes[strtolower($this->value)];
|
||||
}
|
||||
|
||||
return '\\' . $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,7 +97,10 @@ class Atomic extends Type
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($parent->hasType('object') && ClassLikeChecker::classOrInterfaceExists($this->value, $file_checker)) {
|
||||
if ($parent->hasType('object') &&
|
||||
$this instanceof TNamedObject &&
|
||||
ClassLikeChecker::classOrInterfaceExists($this->value, $file_checker)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -84,25 +108,27 @@ class Atomic extends Type
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($parent->hasType('array') && $this->isObjectLike()) {
|
||||
if ($parent->hasType('array') && $this instanceof ObjectLike) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->value === 'false' && $parent->hasType('bool')) {
|
||||
if ($this instanceof TFalse && $parent->hasType('bool')) {
|
||||
// this is fine
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($parent->hasType($this->value)) {
|
||||
if ($parent->hasType($this->getKey())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// last check to see if class is subclass
|
||||
if (ClassChecker::classExists($this->value, $file_checker)) {
|
||||
if ($this instanceof TNamedObject && ClassChecker::classExists($this->value, $file_checker)) {
|
||||
$this_is_subclass = false;
|
||||
|
||||
foreach ($parent->types as $parent_type) {
|
||||
if (ClassChecker::classExtendsOrImplements($this->value, $parent_type->value)) {
|
||||
if ($parent_type instanceof TNamedObject &&
|
||||
ClassChecker::classExtendsOrImplements($this->value, $parent_type->value)
|
||||
) {
|
||||
$this_is_subclass = true;
|
||||
break;
|
||||
}
|
||||
@ -117,57 +143,16 @@ class Atomic extends Type
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @return string
|
||||
*/
|
||||
public function isArray()
|
||||
{
|
||||
return $this->value === 'array';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isGenericArray()
|
||||
{
|
||||
return $this->value === 'array' && $this instanceof Generic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isObjectLike()
|
||||
{
|
||||
return $this instanceof ObjectLike;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isObject()
|
||||
{
|
||||
return $this->value === 'object';
|
||||
}
|
||||
abstract public function getKey();
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isNumericType()
|
||||
{
|
||||
return $this->value === 'int' || $this->value === 'float' || $this->value === 'numeric-string';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isScalarType()
|
||||
{
|
||||
return $this->value === 'int' ||
|
||||
$this->value === 'string' ||
|
||||
$this->value === 'float' ||
|
||||
$this->value === 'bool' ||
|
||||
$this->value === 'false' ||
|
||||
$this->value === 'numeric' ||
|
||||
$this->value === 'numeric-string';
|
||||
return $this instanceof TInt || $this instanceof TFloat || $this instanceof TNumericString;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -175,131 +160,7 @@ class Atomic extends Type
|
||||
*/
|
||||
public function isObjectType()
|
||||
{
|
||||
return $this->isObject()
|
||||
|| (
|
||||
!$this->isScalarType()
|
||||
&& !$this->isCallable()
|
||||
&& !$this->isArray()
|
||||
&& !$this->isMixed()
|
||||
&& !$this->isNull()
|
||||
&& !$this->isVoid()
|
||||
&& !$this->isEmpty()
|
||||
&& !$this->isResource()
|
||||
&& !$this->isIterable()
|
||||
&& !$this->isScalar()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isString()
|
||||
{
|
||||
return $this->value === 'string' || $this->value === 'numeric-string';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isInt()
|
||||
{
|
||||
return $this->value === 'int';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isFloat()
|
||||
{
|
||||
return $this->value === 'float';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isNumeric()
|
||||
{
|
||||
return $this->value === 'numeric';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isNumericString()
|
||||
{
|
||||
return $this->value === 'numeric-string';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isScalar()
|
||||
{
|
||||
return $this->value === 'scalar';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isResource()
|
||||
{
|
||||
return $this->value === 'resource';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isCallable()
|
||||
{
|
||||
return $this->value === 'callable';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isGenerator()
|
||||
{
|
||||
return $this->value === 'Generator';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isIterable()
|
||||
{
|
||||
return $this->value === 'iterable';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isMixed()
|
||||
{
|
||||
return $this->value === 'mixed';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isNull()
|
||||
{
|
||||
return $this->value === 'null';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isVoid()
|
||||
{
|
||||
return $this->value === 'void';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return $this->value === 'empty';
|
||||
return $this instanceof TObject || $this instanceof TNamedObject;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -310,8 +171,7 @@ class Atomic extends Type
|
||||
*/
|
||||
public function check(StatementsSource $source, CodeLocation $code_location, array $suppressed_issues)
|
||||
{
|
||||
if ($this->isObjectType()
|
||||
&& !$this->isObject()
|
||||
if ($this instanceof TNamedObject
|
||||
&& ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$this->value,
|
||||
$source->getFileChecker(),
|
||||
@ -322,10 +182,38 @@ class Atomic extends Type
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this instanceof Type\Generic) {
|
||||
if ($this instanceof Type\Atomic\TArray || $this instanceof Type\Atomic\TGenericObject) {
|
||||
foreach ($this->type_params as $type_param) {
|
||||
$type_param->check($source, $code_location, $suppressed_issues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Atomic $other
|
||||
* @return bool
|
||||
*/
|
||||
public function shallowEquals(Atomic $other)
|
||||
{
|
||||
return $this->getKey() === $other->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $aliased_classes
|
||||
* @param string|null $this_class
|
||||
* @param bool $use_phpdoc_format
|
||||
* @return string
|
||||
*/
|
||||
public function toNamespacedString(array $aliased_classes, $this_class, $use_phpdoc_format)
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
<?php
|
||||
namespace Psalm\Type;
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
use Psalm\FunctionLikeParameter;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
class Fn extends Atomic
|
||||
class Fn extends TNamedObject
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
@ -32,4 +33,12 @@ class Fn extends Atomic
|
||||
$this->params = $params;
|
||||
$this->return_type = $return_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'Closure';
|
||||
}
|
||||
}
|
6
src/Psalm/Type/Atomic/Generic.php
Normal file
6
src/Psalm/Type/Atomic/Generic.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
interface Generic
|
||||
{
|
||||
}
|
@ -1,25 +1,15 @@
|
||||
<?php
|
||||
namespace Psalm\Type;
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class Generic extends Atomic
|
||||
use Psalm\Type\Union;
|
||||
|
||||
trait GenericTrait
|
||||
{
|
||||
/**
|
||||
* @var array<Union>
|
||||
*/
|
||||
public $type_params;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of a generic type
|
||||
*
|
||||
* @param string $value
|
||||
* @param array<int,Union> $type_params
|
||||
*/
|
||||
public function __construct($value, array $type_params)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->type_params = $type_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
@ -1,15 +1,11 @@
|
||||
<?php
|
||||
namespace Psalm\Type;
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
class ObjectLike extends Atomic
|
||||
class ObjectLike extends \Psalm\Type\Atomic
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $value = 'array';
|
||||
|
||||
/**
|
||||
* @var array<string,Union>
|
||||
*/
|
||||
@ -18,10 +14,9 @@ class ObjectLike extends Atomic
|
||||
/**
|
||||
* Constructs a new instance of a generic type
|
||||
*
|
||||
* @param string $value
|
||||
* @param array<string,Union> $properties
|
||||
*/
|
||||
public function __construct($value, array $properties)
|
||||
public function __construct(array $properties)
|
||||
{
|
||||
$this->properties = $properties;
|
||||
}
|
||||
@ -31,8 +26,7 @@ class ObjectLike extends Atomic
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->value .
|
||||
'{' .
|
||||
return 'array{' .
|
||||
implode(
|
||||
', ',
|
||||
array_map(
|
||||
@ -60,11 +54,10 @@ class ObjectLike extends Atomic
|
||||
public function toNamespacedString(array $aliased_classes, $this_class, $use_phpdoc_format)
|
||||
{
|
||||
if ($use_phpdoc_format) {
|
||||
return $this->value;
|
||||
return 'array';
|
||||
}
|
||||
|
||||
return $this->value .
|
||||
'{' .
|
||||
return 'array{' .
|
||||
implode(
|
||||
', ',
|
||||
array_map(
|
||||
@ -103,4 +96,12 @@ class ObjectLike extends Atomic
|
||||
$property = clone $property;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'array';
|
||||
}
|
||||
}
|
6
src/Psalm/Type/Atomic/Scalar.php
Normal file
6
src/Psalm/Type/Atomic/Scalar.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
abstract class Scalar extends \Psalm\Type\Atomic
|
||||
{
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace Psalm\Type;
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class T extends Atomic
|
||||
class T extends TString
|
||||
{
|
||||
/**
|
||||
* Used to hold information as to what this refers to
|
||||
@ -14,8 +14,6 @@ class T extends Atomic
|
||||
*/
|
||||
public function __construct($typeof)
|
||||
{
|
||||
$this->value = 'string';
|
||||
|
||||
$this->typeof = $typeof;
|
||||
}
|
||||
}
|
30
src/Psalm/Type/Atomic/TArray.php
Normal file
30
src/Psalm/Type/Atomic/TArray.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TArray extends \Psalm\Type\Atomic implements Generic
|
||||
{
|
||||
use GenericTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $value = 'array';
|
||||
|
||||
/**
|
||||
* Constructs a new instance of a generic type
|
||||
*
|
||||
* @param array<int, \Psalm\Type\Union> $type_params
|
||||
*/
|
||||
public function __construct(array $type_params)
|
||||
{
|
||||
$this->type_params = $type_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'array';
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TBool.php
Normal file
18
src/Psalm/Type/Atomic/TBool.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TBool extends Scalar
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'bool';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'bool';
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TCallable.php
Normal file
18
src/Psalm/Type/Atomic/TCallable.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TCallable extends \Psalm\Type\Atomic
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'callable';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'callable';
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TEmpty.php
Normal file
18
src/Psalm/Type/Atomic/TEmpty.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TEmpty extends Scalar
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'empty';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'empty';
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TFalse.php
Normal file
18
src/Psalm/Type/Atomic/TFalse.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TFalse extends TBool
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'false';
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TFloat.php
Normal file
18
src/Psalm/Type/Atomic/TFloat.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TFloat extends Scalar
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'float';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'float';
|
||||
}
|
||||
}
|
17
src/Psalm/Type/Atomic/TGenericObject.php
Normal file
17
src/Psalm/Type/Atomic/TGenericObject.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TGenericObject extends TNamedObject implements Generic
|
||||
{
|
||||
use GenericTrait;
|
||||
|
||||
/**
|
||||
* @param string $value the name of the object
|
||||
* @param array<int, \Psalm\Type\Union> $type_params
|
||||
*/
|
||||
public function __construct($value, array $type_params)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->type_params = $type_params;
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TInt.php
Normal file
18
src/Psalm/Type/Atomic/TInt.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TInt extends Scalar
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'int';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'int';
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TMixed.php
Normal file
18
src/Psalm/Type/Atomic/TMixed.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TMixed extends \Psalm\Type\Atomic
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'mixed';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'mixed';
|
||||
}
|
||||
}
|
51
src/Psalm/Type/Atomic/TNamedObject.php
Normal file
51
src/Psalm/Type/Atomic/TNamedObject.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TNamedObject extends \Psalm\Type\Atomic
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* @param string $value the name of the object
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $aliased_classes
|
||||
* @param string|null $this_class
|
||||
* @param bool $use_phpdoc_format
|
||||
* @return string
|
||||
*/
|
||||
public function toNamespacedString(array $aliased_classes, $this_class, $use_phpdoc_format)
|
||||
{
|
||||
if ($this->value === $this_class) {
|
||||
$class_parts = explode('\\', $this_class);
|
||||
return array_pop($class_parts);
|
||||
}
|
||||
|
||||
if (isset($aliased_classes[strtolower($this->value)])) {
|
||||
return $aliased_classes[strtolower($this->value)];
|
||||
}
|
||||
|
||||
return '\\' . $this->value;
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TNull.php
Normal file
18
src/Psalm/Type/Atomic/TNull.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TNull extends \Psalm\Type\Atomic
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'null';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'null';
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TNumeric.php
Normal file
18
src/Psalm/Type/Atomic/TNumeric.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TNumeric extends Scalar
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'numeric';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'numeric';
|
||||
}
|
||||
}
|
13
src/Psalm/Type/Atomic/TNumericString.php
Normal file
13
src/Psalm/Type/Atomic/TNumericString.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TNumericString extends TString
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'numeric-string';
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TObject.php
Normal file
18
src/Psalm/Type/Atomic/TObject.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TObject extends \Psalm\Type\Atomic
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'object';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'object';
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TResource.php
Normal file
18
src/Psalm/Type/Atomic/TResource.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TResource extends \Psalm\Type\Atomic
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'resource';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'resource';
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TScalar.php
Normal file
18
src/Psalm/Type/Atomic/TScalar.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TScalar extends Scalar
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'scalar';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'scalar';
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TString.php
Normal file
18
src/Psalm/Type/Atomic/TString.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TString extends Scalar
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'string';
|
||||
}
|
||||
}
|
18
src/Psalm/Type/Atomic/TVoid.php
Normal file
18
src/Psalm/Type/Atomic/TVoid.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Psalm\Type\Atomic;
|
||||
|
||||
class TVoid extends \Psalm\Type\Atomic
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'void';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return 'void';
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?php
|
||||
namespace Psalm\Type;
|
||||
|
||||
class GenericArray extends Generic
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $value = 'array';
|
||||
|
||||
/**
|
||||
* Constructs a new instance of a generic type
|
||||
*
|
||||
* @param string $value
|
||||
* @param array<int,Union> $type_params
|
||||
*/
|
||||
public function __construct($value, array $type_params)
|
||||
{
|
||||
$this->type_params = $type_params;
|
||||
}
|
||||
}
|
@ -8,18 +8,18 @@ use Psalm\Type;
|
||||
class Union extends Type
|
||||
{
|
||||
/**
|
||||
* @var array<string,Atomic>
|
||||
* @var array<string, Atomic>
|
||||
*/
|
||||
public $types = [];
|
||||
|
||||
/**
|
||||
* Constructs an Union instance
|
||||
* @param array<int,Atomic> $types
|
||||
* @param array<int, Atomic> $types
|
||||
*/
|
||||
public function __construct(array $types)
|
||||
{
|
||||
foreach ($types as $type) {
|
||||
$this->types[$type->value] = $type;
|
||||
$this->types[$type->getKey()] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ class Union extends Type
|
||||
* @return string
|
||||
*/
|
||||
function ($type) {
|
||||
return $type;
|
||||
return (string)$type;
|
||||
},
|
||||
$this->types
|
||||
)
|
||||
@ -93,7 +93,7 @@ class Union extends Type
|
||||
public function hasGeneric()
|
||||
{
|
||||
foreach ($this->types as $type) {
|
||||
if ($type instanceof Generic) {
|
||||
if ($type instanceof Atomic\Generic) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -122,7 +122,7 @@ class Union extends Type
|
||||
*/
|
||||
public function hasObjectLike()
|
||||
{
|
||||
return isset($this->types['array']) && $this->types['array'] instanceof ObjectLike;
|
||||
return isset($this->types['array']) && $this->types['array'] instanceof Atomic\ObjectLike;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -297,7 +297,7 @@ class Union extends Type
|
||||
}
|
||||
|
||||
foreach ($old_type->types as $old_type_part) {
|
||||
$this->removeType($old_type_part->value);
|
||||
$this->removeType($old_type_part->getKey());
|
||||
}
|
||||
|
||||
if ($new_type) {
|
||||
@ -318,7 +318,7 @@ class Union extends Type
|
||||
|
||||
$type = array_values($this->types)[0];
|
||||
|
||||
if (!$type instanceof Generic) {
|
||||
if (!$type instanceof Atomic\TArray && !$type instanceof Atomic\TGenericObject) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -357,7 +357,7 @@ class Php71Test extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
$stmts = self::$parser->parse('<?php
|
||||
/**
|
||||
* @param iterable<string> $iter
|
||||
* @param iterable<int> $iter
|
||||
*/
|
||||
function iterator(iterable $iter) : void
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user