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) {}