Skip to content

Commit

Permalink
allow keyed array to contain class-strings
Browse files Browse the repository at this point in the history
  • Loading branch information
orklah committed Dec 14, 2021
1 parent 2cd7b19 commit 0c1564f
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 18 deletions.
7 changes: 5 additions & 2 deletions src/Psalm/Internal/Type/ParseTreeCreator.php
Expand Up @@ -792,9 +792,12 @@ private function handleValue(array $type_token): void
case '::':
$nexter_token = $this->t + 2 < $this->type_token_count ? $this->type_tokens[$this->t + 2] : null;

if ($this->current_leaf instanceof KeyedArrayTree) {
if ($this->current_leaf instanceof ParseTree\KeyedArrayTree
&& $nexter_token
&& strtolower($nexter_token[0]) !== 'class'
) {
throw new TypeParseTreeException(
'Unexpected :: in array key'
':: in array key is only allowed for ::class'
);
}

Expand Down
20 changes: 18 additions & 2 deletions src/Psalm/Internal/Type/TypeParser.php
Expand Up @@ -1242,12 +1242,15 @@ private static function getTypeFromKeyedArrayTree(
array $type_aliases
) {
$properties = [];
$class_strings = [];

$type = $parse_tree->value;

$is_tuple = true;

foreach ($parse_tree->children as $i => $property_branch) {
$class_string = false;

if (!$property_branch instanceof KeyedArrayPropertyTree) {
$property_type = self::getTypeFromTree(
$property_branch,
Expand All @@ -1267,7 +1270,17 @@ private static function getTypeFromKeyedArrayTree(
$type_aliases
);
$property_maybe_undefined = $property_branch->possibly_undefined;
$property_key = $property_branch->value;
if (strpos($property_branch->value, '::')) {
[$fq_classlike_name, $const_name] = explode('::', $property_branch->value);
if ($const_name === 'class') {
$property_key = $fq_classlike_name;
$class_string = true;
} else {
$property_key = $property_branch->value;
}
} else {
$property_key = $property_branch->value;
}
$is_tuple = false;
} else {
throw new TypeParseTreeException(
Expand All @@ -1288,6 +1301,9 @@ private static function getTypeFromKeyedArrayTree(
}

$properties[$property_key] = $property_type;
if ($class_string) {
$class_strings[$property_key] = true;
}
}

if ($type !== 'array' && $type !== 'object' && $type !== 'callable-array') {
Expand All @@ -1306,7 +1322,7 @@ private static function getTypeFromKeyedArrayTree(
return new TCallableKeyedArray($properties);
}

$object_like = new TKeyedArray($properties);
$object_like = new TKeyedArray($properties, $class_strings);

if ($is_tuple) {
$object_like->sealed = true;
Expand Down
51 changes: 37 additions & 14 deletions src/Psalm/Type/Atomic/TKeyedArray.php
Expand Up @@ -92,11 +92,14 @@ function ($name, Union $type): string {
return (string) $type;
}

if (is_string($name) && preg_match('/[ "\'\\\\.\n:]/', $name)) {
$name = '\'' . str_replace("\n", '\n', addslashes($name)) . '\'';
$class_string_suffix = '';
if (isset($this->class_strings[$name])) {
$class_string_suffix = '::class';
}

return $name . ($type->possibly_undefined ? '?' : '') . ': ' . $type;
$name = $this->escapeAndQuote($name);

return $name . $class_string_suffix . ($type->possibly_undefined ? '?' : '') . ': ' . $type;
},
array_keys($this->properties),
$this->properties
Expand All @@ -118,11 +121,14 @@ function ($name, Union $type): string {
return $type->getId();
}

if (is_string($name) && preg_match('/[ "\'\\\\.\n:]/', $name)) {
$name = '\'' . str_replace("\n", '\n', addslashes($name)) . '\'';
$class_string_suffix = '';
if (isset($this->class_strings[$name])) {
$class_string_suffix = '::class';
}

return $name . ($type->possibly_undefined ? '?' : '') . ': ' . $type->getId();
$name = $this->escapeAndQuote($name);

return $name . $class_string_suffix . ($type->possibly_undefined ? '?' : '') . ': ' . $type->getId();
},
array_keys($this->properties),
$this->properties
Expand Down Expand Up @@ -177,16 +183,20 @@ function (
$this_class,
$use_phpdoc_format
): string {
if (is_string($name) && preg_match('/[ "\'\\\\.\n:]/', $name)) {
$name = '\'' . str_replace("\n", '\n', addslashes($name)) . '\'';
$class_string_suffix = '';
if (isset($this->class_strings[$name])) {
$class_string_suffix = '::class';
}

return $name . ($type->possibly_undefined ? '?' : '') . ': ' . $type->toNamespacedString(
$namespace,
$aliased_classes,
$this_class,
$use_phpdoc_format
);
$name = $this->escapeAndQuote($name);

return $name . $class_string_suffix . ($type->possibly_undefined ? '?' : '') . ': ' .
$type->toNamespacedString(
$namespace,
$aliased_classes,
$this_class,
$use_phpdoc_format
);
},
array_keys($this->properties),
$this->properties
Expand Down Expand Up @@ -412,4 +422,17 @@ public function getList(): TNonEmptyList

return new TNonEmptyList($this->getGenericValueType());
}

/**
* @param string|int $name
* @return string|int
*/
private function escapeAndQuote($name)
{
if (is_string($name) && preg_match('/[ "\'\\\\.\n:]/', $name)) {
$name = '\'' . str_replace("\n", '\n', addslashes($name)) . '\'';
}

return $name;
}
}

0 comments on commit 0c1564f

Please sign in to comment.