diff --git a/config.xsd b/config.xsd index 2dc25540f..522f74a71 100644 --- a/config.xsd +++ b/config.xsd @@ -143,6 +143,7 @@ + diff --git a/docs/issues.md b/docs/issues.md index 632e655a9..d62b3967f 100644 --- a/docs/issues.md +++ b/docs/issues.md @@ -180,6 +180,16 @@ class A {} class A {} ``` +### DuplicateFunction + +Emitted when a function is defined twice + +```php +function foo() : void {} +function bar() : void {} +function foo() : void {} +``` + ### DuplicateMethod Emitted when a method is defined twice diff --git a/src/Psalm/Internal/Visitor/ReflectorVisitor.php b/src/Psalm/Internal/Visitor/ReflectorVisitor.php index 6adbbfa1c..1a5bfaa8e 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\DuplicateFunction; use Psalm\Issue\DuplicateMethod; use Psalm\Issue\DuplicateParam; use Psalm\Issue\InvalidDocblock; @@ -851,15 +852,46 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse ($this->aliases->namespace ? $this->aliases->namespace . '\\' : '') . $stmt->name->name; $function_id = strtolower($cased_function_id); - if (isset($this->file_storage->functions[$function_id])) { - if ($this->codebase->register_stub_files || $this->codebase->register_autoload_files) { + if ($this->codebase->register_stub_files || $this->codebase->register_autoload_files) { + if (isset($this->file_storage->functions[$function_id])) { $this->codebase->functions->addGlobalFunction( $function_id, $this->file_storage->functions[$function_id] ); - } - return $this->file_storage->functions[$function_id]; + return $this->file_storage->functions[$function_id]; + } + } else { + if (isset($this->file_storage->functions[$function_id])) { + $duplicate_function_storage = $this->file_storage->functions[$function_id]; + + if (IssueBuffer::accepts( + new DuplicateFunction( + 'Method ' . $function_id . ' has already been defined' + . ($duplicate_function_storage->location + ? ' in ' . $duplicate_function_storage->location->file_path + : ''), + new CodeLocation($this->file_scanner, $stmt, null, true) + ) + )) { + // fall through + } + + $this->file_storage->has_visitor_issues = true; + + $duplicate_function_storage->has_visitor_issues = true; + + return $this->file_storage->functions[$function_id]; + } elseif (isset($this->config->getPredefinedFunctions()[$function_id])) { + if (IssueBuffer::accepts( + new DuplicateFunction( + 'Method ' . $function_id . ' has already been defined as a core function', + new CodeLocation($this->file_scanner, $stmt, null, true) + ) + )) { + // fall through + } + } } $storage = new FunctionLikeStorage(); diff --git a/src/Psalm/Issue/DuplicateFunction.php b/src/Psalm/Issue/DuplicateFunction.php new file mode 100644 index 000000000..b9543980e --- /dev/null +++ b/src/Psalm/Issue/DuplicateFunction.php @@ -0,0 +1,6 @@ + [ + ' 'InvalidScalarArgument', ], + 'duplicateFunction' => [ + ' 'DuplicateFunction', + ], + 'duplicateCoreFunction' => [ + ' 'DuplicateFunction', + ], ]; } } diff --git a/tests/LanguageServer/SymbolLookupTest.php b/tests/LanguageServer/SymbolLookupTest.php index d04741f85..9ff610473 100644 --- a/tests/LanguageServer/SymbolLookupTest.php +++ b/tests/LanguageServer/SymbolLookupTest.php @@ -77,7 +77,6 @@ class SymbolLookupTest extends \Psalm\Tests\TestCase $codebase = $this->project_analyzer->getCodebase(); - $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); $this->assertSame('getSymbolInformation('somefile.php', 'B\A::foo()')); @@ -114,7 +113,6 @@ class SymbolLookupTest extends \Psalm\Tests\TestCase $codebase = $this->project_analyzer->getCodebase(); - $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); diff --git a/tests/Php56Test.php b/tests/Php56Test.php index 8be124c7c..2287c4f73 100644 --- a/tests/Php56Test.php +++ b/tests/Php56Test.php @@ -236,7 +236,7 @@ class Php56Test extends TestCase return intval(...$args); } - function foo(ArrayIterator $args): int { + function bar(ArrayIterator $args): int { return intval(...$args); }', ], diff --git a/tests/TypeReconciliationTest.php b/tests/TypeReconciliationTest.php index ea48dd7e3..94f54d848 100644 --- a/tests/TypeReconciliationTest.php +++ b/tests/TypeReconciliationTest.php @@ -872,7 +872,7 @@ class TypeReconciliationTest extends TestCase if ($i == 0.0) {} if (0.0 == $i) {} } - function foo(float $i) : void { + function bar(float $i) : void { $i = $i / 100.0; if ($i == "5") {} if ("5" == $i) {} @@ -883,7 +883,7 @@ class TypeReconciliationTest extends TestCase if ($i == 0) {} if (0 == $i) {} } - function foo(string $i) : void { + function bat(string $i) : void { if ($i == 5) {} if (5 == $i) {} if ($i == 5.0) {}