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', ], ]; }