diff --git a/config.xsd b/config.xsd
index eb5f11e2c21..3d5deb61e90 100644
--- a/config.xsd
+++ b/config.xsd
@@ -350,6 +350,7 @@
+
diff --git a/docs/contributing/adding_issues.md b/docs/contributing/adding_issues.md
index b609d872837..4cc2fabb4b9 100644
--- a/docs/contributing/adding_issues.md
+++ b/docs/contributing/adding_issues.md
@@ -17,8 +17,8 @@ namespace Psalm\Issue;
final class MyNewIssue extends CodeIssue
{
- public const SHORTCODE = 123;
public const ERROR_LEVEL = 2;
+ public const SHORTCODE = 123;
}
```
@@ -26,7 +26,7 @@ For `SHORTCODE` value use `$max_shortcode + 1`. To choose appropriate error leve
There a number of abstract classes you can extend:
-* `CodeIssue` - non specific, default issue. It's a base class for all issues.
+* `CodeIssue` - non-specific, default issue. It's a base class for all issues.
* `ClassIssue` - issue related to a specific class (also interface, trait, enum). These issues can be suppressed for specific classes in `psalm.xml` by using `referencedClass` attribute
* `PropertyIssue` - issue related to a specific property. Can be targeted by using `referencedProperty` in `psalm.xml`
* `FunctionIssue` - issue related to a specific function. Can be suppressed with `referencedFunction` attribute.
diff --git a/docs/running_psalm/error_levels.md b/docs/running_psalm/error_levels.md
index 90b5d5351b3..ca2ba58e4e4 100644
--- a/docs/running_psalm/error_levels.md
+++ b/docs/running_psalm/error_levels.md
@@ -100,6 +100,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even
- [ContinueOutsideLoop](issues/ContinueOutsideLoop.md)
- [InvalidTypeImport](issues/InvalidTypeImport.md)
- [MethodSignatureMismatch](issues/MethodSignatureMismatch.md)
+- [NonVariableReferenceReturn](issues/NonVariableReferenceReturn.md)
- [OverriddenMethodAccess](issues/OverriddenMethodAccess.md)
- [ParamNameMismatch](issues/ParamNameMismatch.md)
- [ReservedWord](issues/ReservedWord.md)
diff --git a/docs/running_psalm/issues.md b/docs/running_psalm/issues.md
index 592225002e7..d05e070a4e7 100644
--- a/docs/running_psalm/issues.md
+++ b/docs/running_psalm/issues.md
@@ -151,6 +151,7 @@
- [NonInvariantDocblockPropertyType](issues/NonInvariantDocblockPropertyType.md)
- [NonInvariantPropertyType](issues/NonInvariantPropertyType.md)
- [NonStaticSelfCall](issues/NonStaticSelfCall.md)
+ - [NonVariableReferenceReturn](issues/NonVariableReferenceReturn.md)
- [NoValue](issues/NoValue.md)
- [NullableReturnStatement](issues/NullableReturnStatement.md)
- [NullArgument](issues/NullArgument.md)
diff --git a/docs/running_psalm/issues/NonVariableReferenceReturn.md b/docs/running_psalm/issues/NonVariableReferenceReturn.md
new file mode 100644
index 00000000000..5dcc759898d
--- /dev/null
+++ b/docs/running_psalm/issues/NonVariableReferenceReturn.md
@@ -0,0 +1,11 @@
+# NonVariableReferenceReturn
+
+Emitted when a function returns by reference expression that is not a variable
+
+```php
+getFunctionLikeStorage($statements_analyzer);
+ if ($storage->signature_return_type
+ && $storage->signature_return_type->by_ref
+ && $stmt->expr !== null
+ && !($stmt->expr instanceof PhpParser\Node\Expr\Variable
+ || $stmt->expr instanceof PhpParser\Node\Expr\PropertyFetch
+ || $stmt->expr instanceof PhpParser\Node\Expr\StaticPropertyFetch
+ )
+ ) {
+ IssueBuffer::maybeAdd(
+ new NonVariableReferenceReturn(
+ 'Only variable references should be returned by reference',
+ new CodeLocation($source, $stmt->expr),
+ ),
+ $statements_analyzer->getSuppressedIssues(),
+ );
+ }
+
$cased_method_id = $source->getCorrectlyCasedMethodId();
if ($stmt->expr && $storage->location) {
diff --git a/src/Psalm/Issue/NonVariableReferenceReturn.php b/src/Psalm/Issue/NonVariableReferenceReturn.php
new file mode 100644
index 00000000000..83d26049550
--- /dev/null
+++ b/src/Psalm/Issue/NonVariableReferenceReturn.php
@@ -0,0 +1,11 @@
+ [
+ 'code' => ' [
+ 'code' => ' $x;
+ ',
+ ],
];
}
@@ -1428,6 +1442,20 @@ public function f(): int {
'ignored_issues' => [],
'php_version' => '8.1',
],
+ 'returnByReferenceNonVariableInClosure' => [
+ 'code' => ' 'NonVariableReferenceReturn',
+ ],
+ 'returnByReferenceNonVariableInShortClosure' => [
+ 'code' => ' 45;
+ ',
+ 'error_message' => 'NonVariableReferenceReturn',
+ ],
];
}
}
diff --git a/tests/ReturnTypeTest.php b/tests/ReturnTypeTest.php
index 2557fdf8abe..275f093c08e 100644
--- a/tests/ReturnTypeTest.php
+++ b/tests/ReturnTypeTest.php
@@ -1279,6 +1279,40 @@ function aggregate($type) {
return $t;
}',
],
+ 'returnByReferenceVariableInStaticMethod' => [
+ 'code' => <<<'PHP'
+ [
+ 'code' => <<<'PHP'
+ foo;
+ }
+ }
+ PHP,
+ ],
+ 'returnByReferenceVariableInFunction' => [
+ 'code' => <<<'PHP'
+ [],
'php_version' => '8.0',
],
+ 'returnByReferenceNonVariableInStaticMethod' => [
+ 'code' => <<<'PHP'
+ 'NonVariableReferenceReturn',
+ ],
+ 'returnByReferenceNonVariableInInstanceMethod' => [
+ 'code' => <<<'PHP'
+ 'NonVariableReferenceReturn',
+ ],
+ 'returnByReferenceNonVariableInFunction' => [
+ 'code' => <<<'PHP'
+ 'NonVariableReferenceReturn',
+ ],
];
}
}