2424use PHPStan \Rules \RestrictedUsage \RewrittenDeclaringClassMethodReflection ;
2525use PHPStan \Rules \Rule ;
2626use PHPStan \Rules \RuleErrorBuilder ;
27+ use PHPStan \Rules \RuleLevelHelper ;
2728use PHPStan \ShouldNotHappenException ;
2829use PHPStan \Type \Constant \ConstantStringType ;
30+ use PHPStan \Type \ErrorType ;
31+ use PHPStan \Type \ObjectWithoutClassType ;
32+ use PHPStan \Type \StringType ;
33+ use PHPStan \Type \Type ;
34+ use PHPStan \Type \UnionType ;
35+ use PHPStan \Type \VerbosityLevel ;
2936use function array_filter ;
3037use function array_map ;
3138use function array_merge ;
@@ -48,6 +55,7 @@ public function __construct(
4855 private ReflectionProvider $ reflectionProvider ,
4956 private FunctionCallParametersCheck $ check ,
5057 private ClassNameCheck $ classCheck ,
58+ private RuleLevelHelper $ ruleLevelHelper ,
5159 private ConsistentConstructorHelper $ consistentConstructorHelper ,
5260 #[AutowiredParameter(ref: '%tips.discoveringSymbols% ' )]
5361 private bool $ discoveringSymbolsTip ,
@@ -62,13 +70,51 @@ public function getNodeType(): string
6270
6371 public function processNode (Node $ node , Scope &NodeCallbackInvoker &CollectedDataEmitter $ scope ): array
6472 {
73+ if ($ node ->class instanceof Node \Expr) {
74+ $ errors = $ this ->checkClassNameExprType ($ node ->class , $ scope );
75+ if ($ errors !== []) {
76+ return $ errors ;
77+ }
78+ }
79+
6580 $ errors = [];
6681 foreach ($ this ->getClassNames ($ node , $ scope ) as [$ class , $ isName ]) {
6782 $ errors = array_merge ($ errors , $ this ->checkClassName ($ class , $ isName , $ node , $ scope ));
6883 }
6984 return $ errors ;
7085 }
7186
87+ /**
88+ * @return list<IdentifierRuleError>
89+ */
90+ private function checkClassNameExprType (Node \Expr $ class , Scope $ scope ): array
91+ {
92+ $ acceptedType = new UnionType ([new StringType (), new ObjectWithoutClassType ()]);
93+ $ typeResult = $ this ->ruleLevelHelper ->findTypeToCheck (
94+ $ scope ,
95+ $ class ,
96+ '' ,
97+ static fn (Type $ type ): bool => $ acceptedType ->isSuperTypeOf ($ type )->yes (),
98+ );
99+
100+ $ foundType = $ typeResult ->getType ();
101+ if ($ foundType instanceof ErrorType) {
102+ // Unknown classes and mixed are reported elsewhere (e.g. "Instantiated class X not found.").
103+ return [];
104+ }
105+
106+ if ($ acceptedType ->isSuperTypeOf ($ foundType )->yes ()) {
107+ return [];
108+ }
109+
110+ return [
111+ RuleErrorBuilder::message (sprintf (
112+ 'Cannot instantiate class using %s. ' ,
113+ $ foundType ->describe (VerbosityLevel::typeOnly ()),
114+ ))->identifier ('new.nonObject ' )->build (),
115+ ];
116+ }
117+
72118 /**
73119 * @param Node\Expr\New_ $node
74120 * @return list<IdentifierRuleError>
0 commit comments