Skip to content

Commit

Permalink
Add very basic implementation for class-string-map
Browse files Browse the repository at this point in the history
Fixes #1969
  • Loading branch information
muglug committed Dec 27, 2019
1 parent b78f273 commit 068afa0
Show file tree
Hide file tree
Showing 14 changed files with 818 additions and 37 deletions.
Expand Up @@ -295,6 +295,12 @@ public static function updateArrayType(
} }


if (!$child_stmts) { 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; $child_stmt_type = $assignment_type;
$statements_analyzer->node_data->setType($child_stmt, $assignment_type); $statements_analyzer->node_data->setType($child_stmt, $assignment_type);
} }
Expand Down Expand Up @@ -535,11 +541,61 @@ public static function updateArrayType(
&& $child_stmt && $child_stmt
&& $parent_var_id && $parent_var_id
&& ($parent_type = $context->vars_in_scope[$parent_var_id] ?? null) && ($parent_type = $context->vars_in_scope[$parent_var_id] ?? null)
&& $parent_type->hasList()
) { ) {
$array_atomic_type = new TNonEmptyList( if ($parent_type->hasList()) {
$current_type $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 { } else {
$array_atomic_type = new TNonEmptyArray([ $array_atomic_type = new TNonEmptyArray([
$array_atomic_key_type, $array_atomic_key_type,
Expand All @@ -558,7 +614,12 @@ public static function updateArrayType(
$atomic_root_types = $root_type->getTypes(); $atomic_root_types = $root_type->getTypes();


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


/** /**
* @internal * @internal
Expand Down Expand Up @@ -481,7 +483,11 @@ public static function getArrayAccessTypeGivenOffset(
continue; 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; $has_array_access = true;


if ($in_assignment if ($in_assignment
Expand Down Expand Up @@ -719,6 +725,102 @@ public static function getArrayAccessTypeGivenOffset(
$type->type_param $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 { } else {
$generic_key_type = $type->getGenericKeyType(); $generic_key_type = $type->getGenericKeyType();


Expand Down
30 changes: 26 additions & 4 deletions src/Psalm/Internal/Analyzer/TypeAnalyzer.php
Expand Up @@ -12,6 +12,7 @@
use Psalm\Type\Atomic\TArrayKey; use Psalm\Type\Atomic\TArrayKey;
use Psalm\Type\Atomic\TBool; use Psalm\Type\Atomic\TBool;
use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TClassString;
use Psalm\Type\Atomic\TClassStringMap;
use Psalm\Type\Atomic\TCallable; use Psalm\Type\Atomic\TCallable;
use Psalm\Type\Atomic\TCallableString; use Psalm\Type\Atomic\TCallableString;
use Psalm\Type\Atomic\TEmptyMixed; use Psalm\Type\Atomic\TEmptyMixed;
Expand Down Expand Up @@ -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 if ($container_type_part instanceof TList
&& $input_type_part instanceof TArray && $input_type_part instanceof TArray
&& $input_type_part->type_params[1]->isEmpty() && $input_type_part->type_params[1]->isEmpty()
Expand Down Expand Up @@ -2107,10 +2114,12 @@ private static function isMatchingTypeContainedBy(


if (($input_type_part instanceof TArray if (($input_type_part instanceof TArray
|| $input_type_part instanceof ObjectLike || $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 TArray
|| $container_type_part instanceof ObjectLike || $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) { if ($container_type_part instanceof ObjectLike) {
$generic_container_type_part = $container_type_part->getGenericArrayType(); $generic_container_type_part = $container_type_part->getGenericArrayType();
Expand All @@ -2128,8 +2137,7 @@ function ($carry, Type\Union $item) {
false false
); );


if (!$input_type_part instanceof ObjectLike if ($input_type_part instanceof TArray
&& !$input_type_part instanceof TList
&& !$input_type_part->type_params[0]->hasMixed() && !$input_type_part->type_params[0]->hasMixed()
&& !($input_type_part->type_params[1]->isEmpty() && !($input_type_part->type_params[1]->isEmpty()
&& $container_params_can_be_undefined) && $container_params_can_be_undefined)
Expand All @@ -2145,6 +2153,20 @@ function ($carry, Type\Union $item) {
$input_type_part = $input_type_part->getGenericArrayType(); $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) { if ($container_type_part instanceof TList) {
$all_types_contain = false; $all_types_contain = false;
$atomic_comparison_result->type_coerced = true; $atomic_comparison_result->type_coerced = true;
Expand Down
23 changes: 23 additions & 0 deletions src/Psalm/Internal/Type/ParseTree.php
Expand Up @@ -472,6 +472,29 @@ public static function createFromTokens(array $type_tokens)


break; 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: default:
$new_parent = !$current_leaf instanceof ParseTree\Root ? $current_leaf : null; $new_parent = !$current_leaf instanceof ParseTree\Root ? $current_leaf : null;


Expand Down
25 changes: 25 additions & 0 deletions src/Psalm/Internal/Type/ParseTree/TemplateAsTree.php
@@ -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.