Skip to content
Permalink
Browse files

Add very basic implementation for class-string-map

Fixes #1969
  • Loading branch information
muglug committed Dec 27, 2019
1 parent b78f273 commit 068afa09d36fc95cedff3638e72229b63b930f27
@@ -295,6 +295,12 @@ public static function updateArrayType(
}

if (!$child_stmts) {
// we need this slight hack as the type we're putting it has to be
// different from the type we're getting out
if ($array_type->isSingle() && $array_type->hasClassStringMap()) {
$assignment_type = $child_stmt_type;
}

$child_stmt_type = $assignment_type;
$statements_analyzer->node_data->setType($child_stmt, $assignment_type);
}
@@ -535,11 +541,61 @@ public static function updateArrayType(
&& $child_stmt
&& $parent_var_id
&& ($parent_type = $context->vars_in_scope[$parent_var_id] ?? null)
&& $parent_type->hasList()

) {
$array_atomic_type = new TNonEmptyList(
$current_type
);
if ($parent_type->hasList()) {
$array_atomic_type = new TNonEmptyList(
$current_type
);
} elseif ($parent_type->hasClassStringMap()
&& $current_dim_type
&& $current_dim_type->isTemplatedClassString()
) {
/**
* @var Type\Atomic\TClassStringMap
* @psalm-suppress PossiblyUndefinedStringArrayOffset
*/
$class_string_map = $parent_type->getTypes()['array'];
/**
* @var Type\Atomic\TTemplateParamClass
*/
$offset_type_part = \array_values($current_dim_type->getTypes())[0];

$template_result = new \Psalm\Internal\Type\TemplateResult(
[],
[
$offset_type_part->param_name => [
($offset_type_part->defining_class ?? '') => [
new Type\Union([
new Type\Atomic\TTemplateParam(
$class_string_map->param_name,
$offset_type_part->as_type
? new Type\Union([$offset_type_part->as_type])
: Type::getObject(),
'class-string-map'
)
])
]
]
]
);

$current_type->replaceTemplateTypesWithArgTypes(
$template_result->generic_params,
$codebase
);

$array_atomic_type = new Type\Atomic\TClassStringMap(
$class_string_map->param_name,
$class_string_map->as_type,
$current_type
);
} else {
$array_atomic_type = new TNonEmptyArray([
$array_atomic_key_type,
$current_type,
]);
}
} else {
$array_atomic_type = new TNonEmptyArray([
$array_atomic_key_type,
@@ -558,7 +614,12 @@ public static function updateArrayType(
$atomic_root_types = $root_type->getTypes();

if (isset($atomic_root_types['array'])) {
if ($atomic_root_types['array'] instanceof TNonEmptyArray
if ($array_atomic_type instanceof Type\Atomic\TClassStringMap) {
$array_atomic_type = new TNonEmptyArray([
$array_atomic_type->getStandinKeyParam(),
$array_atomic_type->value_param
]);
} elseif ($atomic_root_types['array'] instanceof TNonEmptyArray
|| $atomic_root_types['array'] instanceof TNonEmptyList
) {
$array_atomic_type->count = $atomic_root_types['array']->count;
@@ -1289,8 +1289,9 @@ public static function analyzeStatic(
if (TypeAnalyzer::canBeContainedBy($codebase, $assignment_value_type, $class_property_type)) {
if (IssueBuffer::accepts(
new PossiblyInvalidPropertyAssignmentValue(
$var_id . ' with declared type \'' . $class_property_type . '\' cannot be assigned type \'' .
$assignment_value_type . '\'',
$var_id . ' with declared type \''
. $class_property_type->getId() . '\' cannot be assigned type \''
. $assignment_value_type->getId() . '\'',
new CodeLocation(
$statements_analyzer->getSource(),
$assignment_value ?: $stmt
@@ -1304,8 +1305,9 @@ public static function analyzeStatic(
} else {
if (IssueBuffer::accepts(
new InvalidPropertyAssignmentValue(
$var_id . ' with declared type \'' . $class_property_type . '\' cannot be assigned type \'' .
$assignment_value_type . '\'',
$var_id . ' with declared type \'' . $class_property_type->getId()
. '\' cannot be assigned type \''
. $assignment_value_type->getId() . '\'',
new CodeLocation(
$statements_analyzer->getSource(),
$assignment_value ?: $stmt
@@ -32,6 +32,7 @@
use Psalm\Type\Atomic\ObjectLike;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TArrayKey;
use Psalm\Type\Atomic\TClassStringMap;
use Psalm\Type\Atomic\TEmpty;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralString;
@@ -56,6 +57,7 @@
use function is_int;
use function preg_match;
use Psalm\Internal\Taint\Source;
use Psalm\Internal\Type\TemplateResult;

/**
* @internal
@@ -481,7 +483,11 @@ public static function getArrayAccessTypeGivenOffset(
continue;
}

if ($type instanceof TArray || $type instanceof ObjectLike || $type instanceof TList) {
if ($type instanceof TArray
|| $type instanceof ObjectLike
|| $type instanceof TList
|| $type instanceof TClassStringMap
) {
$has_array_access = true;

if ($in_assignment
@@ -719,6 +725,102 @@ public static function getArrayAccessTypeGivenOffset(
$type->type_param
);
}
} elseif ($type instanceof TClassStringMap) {
$offset_type_parts = array_values($offset_type->getTypes());

foreach ($offset_type_parts as $offset_type_part) {
if ($offset_type_part instanceof Type\Atomic\TClassString) {
if ($offset_type_part instanceof Type\Atomic\TTemplateParamClass) {
$template_result_get = new TemplateResult(
[],
[
$type->param_name => [
'class-string-map' => [
new Type\Union([
new TTemplateParam(
$offset_type_part->param_name,
$offset_type_part->as_type
? new Type\Union([$offset_type_part->as_type])
: Type::getObject(),
$offset_type_part->defining_class
)
])
]
]
]
);

$template_result_set = new TemplateResult(
[],
[
$offset_type_part->param_name => [
($offset_type_part->defining_class ?: '') => [
new Type\Union([
new TTemplateParam(
$type->param_name,
$type->as_type
? new Type\Union([$type->as_type])
: Type::getObject(),
'class-string-map'
)
])
]
]
]
);
} else {
$template_result_get = new TemplateResult(
[],
[
$type->param_name => [
'class-string-map' => [
new Type\Union([
$offset_type_part->as_type
?: new Type\Atomic\TObject()
])
]
]
]
);
$template_result_set = new TemplateResult(
[],
[]
);
}

$expected_value_param_get = clone $type->value_param;

$expected_value_param_get->replaceTemplateTypesWithArgTypes(
$template_result_get->generic_params,
$codebase
);

if ($replacement_type) {
$expected_value_param_set = clone $type->value_param;

$replacement_type->replaceTemplateTypesWithArgTypes(
$template_result_set->generic_params,
$codebase
);

$type->value_param = Type::combineUnionTypes(
$replacement_type,
$expected_value_param_set,
$codebase
);
}

if (!$array_access_type) {
$array_access_type = $expected_value_param_get;
} else {
$array_access_type = Type::combineUnionTypes(
$array_access_type,
$expected_value_param_get,
$codebase
);
}
}
}
} else {
$generic_key_type = $type->getGenericKeyType();

@@ -12,6 +12,7 @@
use Psalm\Type\Atomic\TArrayKey;
use Psalm\Type\Atomic\TBool;
use Psalm\Type\Atomic\TClassString;
use Psalm\Type\Atomic\TClassStringMap;
use Psalm\Type\Atomic\TCallable;
use Psalm\Type\Atomic\TCallableString;
use Psalm\Type\Atomic\TEmptyMixed;
@@ -2079,6 +2080,12 @@ private static function isMatchingTypeContainedBy(
}
}

if ($container_type_part instanceof TList
&& $input_type_part instanceof TClassStringMap
) {
return false;
}

if ($container_type_part instanceof TList
&& $input_type_part instanceof TArray
&& $input_type_part->type_params[1]->isEmpty()
@@ -2107,10 +2114,12 @@ private static function isMatchingTypeContainedBy(

if (($input_type_part instanceof TArray
|| $input_type_part instanceof ObjectLike
|| $input_type_part instanceof TList)
|| $input_type_part instanceof TList
|| $input_type_part instanceof TClassStringMap)
&& ($container_type_part instanceof TArray
|| $container_type_part instanceof ObjectLike
|| $container_type_part instanceof TList)
|| $container_type_part instanceof TList
|| $container_type_part instanceof TClassStringMap)
) {
if ($container_type_part instanceof ObjectLike) {
$generic_container_type_part = $container_type_part->getGenericArrayType();
@@ -2128,8 +2137,7 @@ function ($carry, Type\Union $item) {
false
);

if (!$input_type_part instanceof ObjectLike
&& !$input_type_part instanceof TList
if ($input_type_part instanceof TArray
&& !$input_type_part->type_params[0]->hasMixed()
&& !($input_type_part->type_params[1]->isEmpty()
&& $container_params_can_be_undefined)
@@ -2145,6 +2153,20 @@ function ($carry, Type\Union $item) {
$input_type_part = $input_type_part->getGenericArrayType();
}

if ($input_type_part instanceof TClassStringMap) {
$input_type_part = new TArray([
$input_type_part->getStandinKeyParam(),
clone $input_type_part->value_param
]);
}

if ($container_type_part instanceof TClassStringMap) {
$container_type_part = new TArray([
$container_type_part->getStandinKeyParam(),
clone $container_type_part->value_param
]);
}

if ($container_type_part instanceof TList) {
$all_types_contain = false;
$atomic_comparison_result->type_coerced = true;
@@ -472,6 +472,29 @@ public static function createFromTokens(array $type_tokens)

break;

case 'as':
$current_parent = $current_leaf->parent;

if (!$current_leaf instanceof ParseTree\Value
|| !$current_parent instanceof ParseTree\GenericTree
|| !$next_token
) {
throw new TypeParseTreeException('Unexpected token ' . $type_token[0]);
}

array_pop($current_parent->children);

$current_leaf = new ParseTree\TemplateAsTree(
$current_leaf->value,
$next_token[0],
$current_parent
);

$current_parent->children[] = $current_leaf;
++$i;

break;

default:
$new_parent = !$current_leaf instanceof ParseTree\Root ? $current_leaf : null;

@@ -0,0 +1,25 @@
<?php
namespace Psalm\Internal\Type\ParseTree;

/**
* @internal
*/
class TemplateAsTree extends \Psalm\Internal\Type\ParseTree
{
/**
* @var string
*/
public $param_name;

/**
* @var string
*/
public $as;

public function __construct(string $param_name, string $as, ?\Psalm\Internal\Type\ParseTree $parent = null)
{
$this->param_name = $param_name;
$this->as = $as;
$this->parent = $parent;
}
}

0 comments on commit 068afa0

Please sign in to comment.
You can’t perform that action at this time.