Skip to content
Permalink
Browse files

Fix #1897 - add support for unions in @psalm-assert annotations

  • Loading branch information...
muglug committed Jul 4, 2019
1 parent df3d7e1 commit efe096c7abfb1fb37568d738c2cf216a36235cf7
@@ -24,8 +24,7 @@ public function __construct(array $file_names)
/**
* @param mixed $package
* @psalm-assert-if-true array{type:'psalm-plugin',name:string,extra:array{psalm:array{pluginClass:string}}}
* $package
* @psalm-assert-if-true array $package
*/
public function isPlugin($package): bool
{
@@ -79,6 +78,7 @@ private function getAllPluginPackages(): array
/** @psalm-suppress MixedAssignment */
foreach ($packages as $package) {
if ($this->isPlugin($package)) {
/** @var array{type:'psalm-plugin',name:string,extra:array{psalm:array{pluginClass:string}}} */
$ret[] = $package;
}
}
@@ -2080,108 +2080,79 @@ private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, $fake_m
$storage->assertions = [];
foreach ($docblock_info->assertions as $assertion) {
$assertion_type = $assertion['type'];
if (strpos($assertion_type, '|') !== false) {
if (IssueBuffer::accepts(
new InvalidDocblock(
'Docblock assertions cannot contain | characters',
new CodeLocation($this->file_scanner, $stmt, null, true)
)
)) {
}
continue;
}
if (strpos($assertion_type, '\'') !== false || strpos($assertion_type, '"') !== false) {
if (IssueBuffer::accepts(
new InvalidDocblock(
'Docblock assertions cannot contain quotes',
new CodeLocation($this->file_scanner, $stmt, null, true)
)
)) {
}
$assertion_type_parts = $this->getAssertionParts($assertion['type'], $stmt, $template_types);
if (!$assertion_type_parts) {
continue;
}
$prefix = '';
if ($assertion_type[0] === '!') {
$prefix = '!';
$assertion_type = substr($assertion_type, 1);
}
if ($assertion_type[0] === '~') {
$prefix .= '~';
$assertion_type = substr($assertion_type, 1);
}
if ($assertion_type[0] === '=') {
$prefix .= '=';
$assertion_type = substr($assertion_type, 1);
}
if ($assertion_type !== 'falsy'
&& !isset($template_types[$assertion_type])
&& !isset(Type::PSALM_RESERVED_WORDS[$assertion_type])
) {
$assertion_type = Type::getFQCLNFromString($assertion_type, $this->aliases);
}
foreach ($storage->params as $i => $param) {
if ($param->name === $assertion['param_name']) {
$storage->assertions[] = new \Psalm\Storage\Assertion(
$i,
[[$prefix . $assertion_type]]
[$assertion_type_parts]
);
continue 2;
}
}
$storage->assertions[] = new \Psalm\Storage\Assertion(
'$' . $assertion['param_name'],
[[$prefix . $assertion_type]]
[$assertion_type_parts]
);
}
}
if ($docblock_info->if_true_assertions) {
$storage->assertions = [];
$storage->if_true_assertions = [];
foreach ($docblock_info->if_true_assertions as $assertion) {
$assertion_type_parts = $this->getAssertionParts($assertion['type'], $stmt, $template_types);
if (!$assertion_type_parts) {
continue;
}
foreach ($storage->params as $i => $param) {
if ($param->name === $assertion['param_name']) {
$storage->if_true_assertions[] = new \Psalm\Storage\Assertion(
$i,
[[$assertion['type']]]
[$assertion_type_parts]
);
continue 2;
}
}
$storage->if_true_assertions[] = new \Psalm\Storage\Assertion(
'$' . $assertion['param_name'],
[[$assertion['type']]]
[$assertion_type_parts]
);
}
}
if ($docblock_info->if_false_assertions) {
$storage->assertions = [];
$storage->if_false_assertions = [];
foreach ($docblock_info->if_false_assertions as $assertion) {
$assertion_type_parts = $this->getAssertionParts($assertion['type'], $stmt, $template_types);
if (!$assertion_type_parts) {
continue;
}
foreach ($storage->params as $i => $param) {
if ($param->name === $assertion['param_name']) {
$storage->if_false_assertions[] = new \Psalm\Storage\Assertion(
$i,
[[$assertion['type']]]
[$assertion_type_parts]
);
continue 2;
}
}
$storage->if_false_assertions[] = new \Psalm\Storage\Assertion(
'$' . $assertion['param_name'],
[[$assertion['type']]]
[$assertion_type_parts]
);
}
}
@@ -2410,6 +2381,74 @@ function (FunctionLikeParameter $p) {
return $storage;
}
/**
* @return ?array<int, string>
*/
private function getAssertionParts(
string $assertion_type,
PhpParser\Node\FunctionLike $stmt,
?array $template_types
) : ?array {
$is_union = false;
if (strpos($assertion_type, '|') !== false) {
$is_union = true;
}
if (strpos($assertion_type, '\'') !== false || strpos($assertion_type, '"') !== false) {
if (IssueBuffer::accepts(
new InvalidDocblock(
'Docblock assertions cannot contain quotes',
new CodeLocation($this->file_scanner, $stmt, null, true)
)
)) {
}
return null;
}
$prefix = '';
if ($assertion_type[0] === '!') {
$prefix = '!';
$assertion_type = substr($assertion_type, 1);
}
if ($assertion_type[0] === '~') {
$prefix .= '~';
$assertion_type = substr($assertion_type, 1);
}
if ($assertion_type[0] === '=') {
$prefix .= '=';
$assertion_type = substr($assertion_type, 1);
}
if ($prefix && $is_union) {
if (IssueBuffer::accepts(
new InvalidDocblock(
'Docblock assertions cannot contain | characters together with ' . $prefix,
new CodeLocation($this->file_scanner, $stmt, null, true)
)
)) {
}
return null;
}
$assertion_type_parts = explode('|', $assertion_type);
foreach ($assertion_type_parts as $i => $assertion_type_part) {
if ($assertion_type_part !== 'falsy'
&& !isset($template_types[$assertion_type_part])
&& !isset(Type::PSALM_RESERVED_WORDS[$assertion_type_part])
) {
$assertion_type_parts[$i] = $prefix . Type::getFQCLNFromString($assertion_type_part, $this->aliases);
} else {
$assertion_type_parts[$i] = $prefix . $assertion_type_part;
}
}
return $assertion_type_parts;
}
/**
* @param PhpParser\Node\Param $param
*
@@ -21,7 +21,7 @@ class Assertion
* @param string|int $var_id
* @param array<int, array<int, string>> $rule
*/
public function __construct($var_id, $rule)
public function __construct($var_id, array $rule)
{
$this->rule = $rule;
$this->var_id = $var_id;
@@ -885,6 +885,29 @@ function test(?string $in) : void {
}
}',
],
'assertUnion' => [
'<?php
class Foo{
public function bar() : void {}
}
/**
* @param mixed $b
* @psalm-assert int|Foo $b
*/
function assertIntOrFoo($b) : void {
if (!is_int($b) && !(is_object($b) && $b instanceof Foo)) {
throw new \Exception("bad");
}
}
/** @psalm-suppress MixedAssignment */
$a = $_GET["a"];
assertIntOrFoo($a);
if (!is_int($a)) $a->bar();',
],
];
}

0 comments on commit efe096c

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