diff --git a/config.xsd b/config.xsd
index 65fa59df7..6257eea10 100644
--- a/config.xsd
+++ b/config.xsd
@@ -137,6 +137,7 @@
+
diff --git a/docs/issues.md b/docs/issues.md
index 5013446b5..7f2946b9a 100644
--- a/docs/issues.md
+++ b/docs/issues.md
@@ -340,6 +340,16 @@ class Foo {}
(new foo());
```
+### InvalidStringClass
+
+Emitted when you have `allowStringToStandInForClass="false"` in your config and you’re passing a string instead of calling a class directly
+
+```php
+class Foo {}
+$a = "Foo";
+new $a();
+```
+
### InvalidClone
Emitted when trying to clone a value that's not cloneable
diff --git a/src/Psalm/Checker/Statements/Expression/Call/NewChecker.php b/src/Psalm/Checker/Statements/Expression/Call/NewChecker.php
index 1313f8318..b9169cc6d 100644
--- a/src/Psalm/Checker/Statements/Expression/Call/NewChecker.php
+++ b/src/Psalm/Checker/Statements/Expression/Call/NewChecker.php
@@ -12,8 +12,9 @@ use Psalm\Context;
use Psalm\Issue\AbstractInstantiation;
use Psalm\Issue\DeprecatedClass;
use Psalm\Issue\InterfaceInstantiation;
-use Psalm\Issue\InvalidClass;
+use Psalm\Issue\InvalidStringClass;
use Psalm\Issue\TooManyArguments;
+use Psalm\Issue\UndefinedClass;
use Psalm\IssueBuffer;
use Psalm\Type;
use Psalm\Type\Atomic\TNamedObject;
@@ -125,12 +126,26 @@ class NewChecker extends \Psalm\Checker\Statements\Expression\CallChecker
) {
continue;
}
- } elseif ($lhs_type_part instanceof Type\Atomic\TMixed) {
+
+ if (IssueBuffer::accepts(
+ new InvalidStringClass(
+ 'String cannot be used as a class',
+ new CodeLocation($statements_checker->getSource(), $stmt)
+ ),
+ $statements_checker->getSuppressedIssues()
+ )) {
+ // fall through
+ }
+
+ continue;
+ }
+
+ if ($lhs_type_part instanceof Type\Atomic\TMixed) {
continue;
}
if (IssueBuffer::accepts(
- new InvalidClass(
+ new UndefinedClass(
'Type ' . $lhs_type_part . ' cannot be called as a class',
new CodeLocation($statements_checker->getSource(), $stmt)
),
diff --git a/src/Psalm/Checker/Statements/Expression/Call/StaticCallChecker.php b/src/Psalm/Checker/Statements/Expression/Call/StaticCallChecker.php
index 2a21cfa95..ada9485d9 100644
--- a/src/Psalm/Checker/Statements/Expression/Call/StaticCallChecker.php
+++ b/src/Psalm/Checker/Statements/Expression/Call/StaticCallChecker.php
@@ -11,8 +11,9 @@ use Psalm\Config;
use Psalm\Context;
use Psalm\FileManipulation\FileManipulationBuffer;
use Psalm\Issue\DeprecatedClass;
-use Psalm\Issue\InvalidClass;
+use Psalm\Issue\InvalidStringClass;
use Psalm\Issue\ParentNotFound;
+use Psalm\Issue\UndefinedClass;
use Psalm\IssueBuffer;
use Psalm\Type;
use Psalm\Type\Atomic\TNamedObject;
@@ -205,12 +206,26 @@ class StaticCallChecker extends \Psalm\Checker\Statements\Expression\CallChecker
) {
continue;
}
- } elseif ($lhs_type_part instanceof Type\Atomic\TMixed) {
+
+ if (IssueBuffer::accepts(
+ new InvalidStringClass(
+ 'String cannot be used as a class',
+ new CodeLocation($statements_checker->getSource(), $stmt)
+ ),
+ $statements_checker->getSuppressedIssues()
+ )) {
+ // fall through
+ }
+
+ continue;
+ }
+
+ if ($lhs_type_part instanceof Type\Atomic\TMixed) {
continue;
}
if (IssueBuffer::accepts(
- new InvalidClass(
+ new UndefinedClass(
'Type ' . $lhs_type_part . ' cannot be called as a class',
new CodeLocation($statements_checker->getSource(), $stmt)
),
diff --git a/src/Psalm/Issue/InvalidStringClass.php b/src/Psalm/Issue/InvalidStringClass.php
new file mode 100644
index 000000000..2e2a1fb4c
--- /dev/null
+++ b/src/Psalm/Issue/InvalidStringClass.php
@@ -0,0 +1,6 @@
+ $blocks) {
switch ($issue_name) {
+ case 'InvalidStringClass':
+ continue 2;
+
case 'InvalidFalsableReturnType':
$ignored_issues = ['FalsableReturnStatement'];
break;
diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php
index 56c724434..bbf7bcc00 100644
--- a/tests/MethodCallTest.php
+++ b/tests/MethodCallTest.php
@@ -271,13 +271,13 @@ class MethodCallTest extends TestCase
' 'InvalidClass',
+ 'error_message' => 'UndefinedClass',
],
'intVarNewCall' => [
' 'InvalidClass',
+ 'error_message' => 'UndefinedClass',
],
];
}