mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Fix #2019 - allow union in @throws
This commit is contained in:
parent
d6e084ec9c
commit
2b2988b072
@ -523,14 +523,18 @@ class CommentAnalyzer
|
||||
}
|
||||
|
||||
if (isset($parsed_docblock['specials']['throws'])) {
|
||||
foreach ($parsed_docblock['specials']['throws'] as $throws_entry) {
|
||||
foreach ($parsed_docblock['specials']['throws'] as $offset => $throws_entry) {
|
||||
$throws_class = preg_split('/[\s]+/', $throws_entry)[0];
|
||||
|
||||
if (!$throws_class) {
|
||||
throw new IncorrectDocblockException('Unexpectedly empty @throws');
|
||||
}
|
||||
|
||||
$info->throws[] = $throws_class;
|
||||
$info->throws[] = [
|
||||
$throws_class,
|
||||
$offset + $comment->getFilePos(),
|
||||
$comment->getLine() + substr_count($comment->getText(), "\n", 0, $offset)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1020,32 +1020,32 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
}
|
||||
|
||||
foreach ($storage->throws as $expected_exception => $_) {
|
||||
if ($storage->location
|
||||
&& ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
||||
if (isset($storage->throw_locations[$expected_exception])) {
|
||||
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
||||
$statements_analyzer,
|
||||
$expected_exception,
|
||||
$storage->location,
|
||||
$storage->throw_locations[$expected_exception],
|
||||
$statements_analyzer->getSuppressedIssues(),
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
)
|
||||
) {
|
||||
$input_type = new Type\Union([new TNamedObject($expected_exception)]);
|
||||
$container_type = new Type\Union([new TNamedObject('Exception'), new TNamedObject('Throwable')]);
|
||||
)) {
|
||||
$input_type = new Type\Union([new TNamedObject($expected_exception)]);
|
||||
$container_type = new Type\Union([new TNamedObject('Exception'), new TNamedObject('Throwable')]);
|
||||
|
||||
if (!TypeAnalyzer::isContainedBy($codebase, $input_type, $container_type)) {
|
||||
if (IssueBuffer::accepts(
|
||||
new \Psalm\Issue\InvalidThrow(
|
||||
'Class supplied for @throws ' . $expected_exception
|
||||
. ' does not implement Throwable',
|
||||
$storage->location,
|
||||
$expected_exception
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
if (!TypeAnalyzer::isContainedBy($codebase, $input_type, $container_type)) {
|
||||
if (IssueBuffer::accepts(
|
||||
new \Psalm\Issue\InvalidThrow(
|
||||
'Class supplied for @throws ' . $expected_exception
|
||||
. ' does not implement Throwable',
|
||||
$storage->throw_locations[$expected_exception],
|
||||
$expected_exception
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ class FunctionDocblockComment
|
||||
public $suppress = [];
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
* @var array<int, array{0: string, 1: int, 2: int}>
|
||||
*/
|
||||
public $throws = [];
|
||||
|
||||
|
@ -2034,16 +2034,25 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
|
||||
$storage->suppressed_issues = $docblock_info->suppress;
|
||||
|
||||
foreach ($docblock_info->throws as $throw_class) {
|
||||
$exception_fqcln = Type::getFQCLNFromString(
|
||||
$throw_class,
|
||||
$this->aliases
|
||||
foreach ($docblock_info->throws as [$throw, $offset, $line]) {
|
||||
$throw_location = new CodeLocation\DocblockTypeLocation(
|
||||
$this->file_scanner,
|
||||
$offset,
|
||||
$offset + \strlen($throw),
|
||||
$line
|
||||
);
|
||||
|
||||
$this->codebase->scanner->queueClassLikeForScanning($exception_fqcln, $this->file_path);
|
||||
$this->file_storage->referenced_classlikes[strtolower($exception_fqcln)] = $exception_fqcln;
|
||||
foreach (\array_map('trim', explode('|', $throw)) as $throw_class) {
|
||||
$exception_fqcln = Type::getFQCLNFromString(
|
||||
$throw_class,
|
||||
$this->aliases
|
||||
);
|
||||
|
||||
$storage->throws[$exception_fqcln] = true;
|
||||
$this->codebase->scanner->queueClassLikeForScanning($exception_fqcln, $this->file_path);
|
||||
$this->file_storage->referenced_classlikes[strtolower($exception_fqcln)] = $exception_fqcln;
|
||||
$storage->throws[$exception_fqcln] = true;
|
||||
$storage->throw_locations[$exception_fqcln] = $throw_location;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->config->use_docblock_types) {
|
||||
|
@ -151,6 +151,11 @@ class FunctionLikeStorage
|
||||
*/
|
||||
public $throws = [];
|
||||
|
||||
/**
|
||||
* @var array<string, CodeLocation>
|
||||
*/
|
||||
public $throw_locations = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
@ -8,7 +8,7 @@ class ThrowsAnnotationTest extends TestCase
|
||||
{
|
||||
public function testUndefinedClassAsThrows() : void
|
||||
{
|
||||
$this->expectExceptionMessage('UndefinedDocblockClass');
|
||||
$this->expectExceptionMessage('UndefinedDocblockClass - somefile.php:3:28');
|
||||
$this->expectException(\Psalm\Exception\CodeException::class);
|
||||
|
||||
$this->addFile(
|
||||
@ -46,6 +46,26 @@ class ThrowsAnnotationTest extends TestCase
|
||||
$this->analyzeFile('somefile.php', $context);
|
||||
}
|
||||
|
||||
public function testInheritedThrowableClassAsThrows() : void
|
||||
{
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
class MyException extends Exception {}
|
||||
|
||||
class Foo {
|
||||
/**
|
||||
* @throws MyException|Throwable
|
||||
*/
|
||||
public function bar() : void {}
|
||||
}'
|
||||
);
|
||||
|
||||
$context = new Context();
|
||||
|
||||
$this->analyzeFile('somefile.php', $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user