diff --git a/config.xsd b/config.xsd index 4c1f462d0..f764fe3a6 100644 --- a/config.xsd +++ b/config.xsd @@ -150,7 +150,7 @@ - + @@ -173,7 +173,7 @@ - + diff --git a/src/Psalm/Checker/Statements/Block/TryChecker.php b/src/Psalm/Checker/Statements/Block/TryChecker.php index 7b612e587..6d062f95c 100644 --- a/src/Psalm/Checker/Statements/Block/TryChecker.php +++ b/src/Psalm/Checker/Statements/Block/TryChecker.php @@ -176,7 +176,8 @@ class TryChecker if (IssueBuffer::accepts( new InvalidCatch( 'Class/interface ' . $fq_catch_class . ' cannot be caught', - new CodeLocation($statements_checker->getSource(), $stmt) + new CodeLocation($statements_checker->getSource(), $stmt), + $fq_catch_class ), $statements_checker->getSuppressedIssues() )) { diff --git a/src/Psalm/Checker/Statements/ThrowChecker.php b/src/Psalm/Checker/Statements/ThrowChecker.php index 4c09e7150..67f7aaea0 100644 --- a/src/Psalm/Checker/Statements/ThrowChecker.php +++ b/src/Psalm/Checker/Statements/ThrowChecker.php @@ -33,20 +33,26 @@ class ThrowChecker $file_checker = $statements_checker->getFileChecker(); $project_checker = $file_checker->project_checker; - if (!TypeChecker::isContainedBy($project_checker->codebase, $throw_type, $exception_type)) { - if (IssueBuffer::accepts( - new InvalidThrow( - 'Cannot throw ' . $throw_type . ' as it does not extend Exception or implement Throwable', - new CodeLocation($file_checker, $stmt) - ), - $statements_checker->getSuppressedIssues() - )) { - return false; - } - } elseif ($context->collect_exceptions) { - foreach ($throw_type->getTypes() as $throw_atomic_type) { - if ($throw_atomic_type instanceof TNamedObject) { - $context->possibly_thrown_exceptions[$throw_atomic_type->value] = true; + foreach ($throw_type->getTypes() as $throw_type_part) { + $throw_type_candidate = new Union([$throw_type_part]); + + if (!TypeChecker::isContainedBy($project_checker->codebase, $throw_type_candidate, $exception_type)) { + if (IssueBuffer::accepts( + new InvalidThrow( + 'Cannot throw ' . $throw_type_part + . ' as it does not extend Exception or implement Throwable', + new CodeLocation($file_checker, $stmt), + (string) $throw_type_part + ), + $statements_checker->getSuppressedIssues() + )) { + return false; + } + } elseif ($context->collect_exceptions) { + foreach ($throw_type->getTypes() as $throw_atomic_type) { + if ($throw_atomic_type instanceof TNamedObject) { + $context->possibly_thrown_exceptions[$throw_atomic_type->value] = true; + } } } } diff --git a/src/Psalm/Issue/InvalidCatch.php b/src/Psalm/Issue/InvalidCatch.php index 45611d740..ba28f75ff 100644 --- a/src/Psalm/Issue/InvalidCatch.php +++ b/src/Psalm/Issue/InvalidCatch.php @@ -1,6 +1,6 @@ analyzeFile($file_path, new Context()); } + /** + * @expectedException \Psalm\Exception\CodeException + * @expectedExceptionMessage InvalidCatch + * @return void + */ + public function testValidThrowInvalidCatch() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } + + /** + * @expectedException \Psalm\Exception\CodeException + * @expectedExceptionMessage InvalidThrow + * @return void + */ + public function testInvalidThrowValidCatch() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } + + /** + * @return void + */ + public function testValidThrowValidCatch() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } + /** * @return void */