diff --git a/config.xsd b/config.xsd
index 2f3c6f059..373bb2094 100644
--- a/config.xsd
+++ b/config.xsd
@@ -144,6 +144,7 @@
+
diff --git a/docs/issues.md b/docs/issues.md
index 1f85d1807..e36a4f2a9 100644
--- a/docs/issues.md
+++ b/docs/issues.md
@@ -180,6 +180,17 @@ class A {}
class A {}
```
+### DuplicateMethod
+
+Emitted when a method is defined twice
+
+```php
+class A {
+ public function foo() {}
+ public function foo() {}
+}
+```
+
### DuplicateParam
Emitted when a function has a param defined twice
diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php
index f5ff851cf..5c7c37503 100644
--- a/src/Psalm/Config.php
+++ b/src/Psalm/Config.php
@@ -497,7 +497,13 @@ class Config
}
if (isset($config_xml['autoloader'])) {
- $config->autoloader = (string) $config_xml['autoloader'];
+ $autoloader_path = $config->base_dir . DIRECTORY_SEPARATOR . $config_xml['autoloader'];
+
+ if (!file_exists($autoloader_path)) {
+ throw new ConfigException('Cannot locate config schema');
+ }
+
+ $config->autoloader = realpath($autoloader_path);
}
if (isset($config_xml['cacheDirectory'])) {
@@ -1331,7 +1337,7 @@ class Config
if ($this->autoloader) {
// do this in a separate method so scope does not leak
- $this->requireAutoloader($this->base_dir . DIRECTORY_SEPARATOR . $this->autoloader);
+ $this->requireAutoloader($this->autoloader);
$this->collectPredefinedConstants();
$this->collectPredefinedFunctions();
diff --git a/src/Psalm/Internal/Visitor/ReflectorVisitor.php b/src/Psalm/Internal/Visitor/ReflectorVisitor.php
index 754d99a63..68b0df840 100644
--- a/src/Psalm/Internal/Visitor/ReflectorVisitor.php
+++ b/src/Psalm/Internal/Visitor/ReflectorVisitor.php
@@ -21,6 +21,7 @@ use Psalm\Exception\IncorrectDocblockException;
use Psalm\Exception\TypeParseTreeException;
use Psalm\FileSource;
use Psalm\Issue\DuplicateClass;
+use Psalm\Issue\DuplicateMethod;
use Psalm\Issue\DuplicateParam;
use Psalm\Issue\InvalidDocblock;
use Psalm\Issue\MisplacedRequiredParam;
@@ -835,7 +836,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
* @param PhpParser\Node\FunctionLike $stmt
* @param bool $fake_method in the case of @method annotations we do something a little strange
*
- * @return FunctionLikeStorage
+ * @return FunctionLikeStorage|false
*/
private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, $fake_method = false)
{
@@ -889,7 +890,25 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
if (isset($class_storage->methods[strtolower($stmt->name->name)])) {
if (!$this->codebase->register_stub_files) {
- throw new \InvalidArgumentException('Cannot re-register ' . $function_id);
+ $duplicate_method_storage = $class_storage->methods[strtolower($stmt->name->name)];
+
+ if (IssueBuffer::accepts(
+ new DuplicateMethod(
+ 'Method ' . $function_id . ' has already been defined'
+ . ($duplicate_method_storage->location
+ ? ' in ' . $duplicate_method_storage->location->file_path
+ : ''),
+ new CodeLocation($this->file_scanner, $stmt, null, true)
+ )
+ )) {
+ // fall through
+ }
+
+ $this->file_storage->has_visitor_issues = true;
+
+ $duplicate_method_storage->has_visitor_issues = true;
+
+ return false;
}
$storage = $class_storage->methods[strtolower($stmt->name->name)];
diff --git a/src/Psalm/Issue/DuplicateMethod.php b/src/Psalm/Issue/DuplicateMethod.php
new file mode 100644
index 000000000..8ce64ef50
--- /dev/null
+++ b/src/Psalm/Issue/DuplicateMethod.php
@@ -0,0 +1,6 @@
+reportIssueInFile($issue_type, $e->getFilePath())) {
+ if (!$e instanceof DuplicateClass
+ && !$e instanceof DuplicateMethod
+ && !$config->reportIssueInFile($issue_type, $e->getFilePath())
+ ) {
return false;
}
diff --git a/tests/ClassStringTest.php b/tests/ClassStringTest.php
index 805ff3bd1..9b781cb14 100644
--- a/tests/ClassStringTest.php
+++ b/tests/ClassStringTest.php
@@ -288,6 +288,17 @@ class ClassStringTest extends TestCase
use T;
}'
],
+ 'refineStringToClassString' => [
+ 'file_provider->registerFile($file_path, $contents);
}
+ $this->expectException('\Psalm\Exception\CodeException');
+ $this->expectExceptionMessageRegexp('/\b' . preg_quote($error_message, '/') . '\b/');
+
$codebase->reloadFiles($this->project_analyzer, array_keys($end_files));
foreach ($end_files as $file_path => $_) {
$codebase->addFilesToAnalyze([$file_path => $file_path]);
}
- $codebase->scanFiles();
-
- $this->expectException('\Psalm\Exception\CodeException');
- $this->expectExceptionMessageRegexp('/\b' . preg_quote($error_message, '/') . '\b/');
-
$codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false);
}
@@ -556,6 +554,46 @@ class ErrorAfterUpdateTest extends \Psalm\Tests\TestCase
],
'error_message' => 'PropertyNotSetInConstructor'
],
+ 'duplicateClass' => [
+ 'file_stages' => [
+ [
+ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' 'DuplicateClass'
+ ],
+ 'duplicateMethod' => [
+ 'file_stages' => [
+ [
+ getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' 'DuplicateMethod'
+ ],
];
}
}
diff --git a/tests/performance/diff_parse_large_file.php b/tests/performance/diff_parse_large_file.php
index 83ee6d6fc..5539670fa 100644
--- a/tests/performance/diff_parse_large_file.php
+++ b/tests/performance/diff_parse_large_file.php
@@ -2,7 +2,7 @@
ini_set('display_startup_errors', '1');
ini_set('html_errors', '1');
-ini_set('memory_limit', '-1');
+ini_set('memory_limit', '4G');
error_reporting(E_ALL);
gc_disable();
@@ -44,3 +44,7 @@ Psalm\Internal\Provider\StatementsProvider::parseStatements($b);
$diff_2 = microtime(true) - $time;
echo 'Full parsing: ' . number_format($diff_2, 4) . "\n";
+
+echo strlen($a);
+
+Psalm\Internal\Diff\FileDiffer::getDiff($a, '');