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,7 +55,10 @@ public function __construct(
4855 private ReflectionProvider $ reflectionProvider ,
4956 private FunctionCallParametersCheck $ check ,
5057 private ClassNameCheck $ classCheck ,
58+ private RuleLevelHelper $ ruleLevelHelper ,
5159 private ConsistentConstructorHelper $ consistentConstructorHelper ,
60+ #[AutowiredParameter(ref: '%featureToggles.newOnNonObject% ' )]
61+ private bool $ newOnNonObject ,
5262 #[AutowiredParameter(ref: '%tips.discoveringSymbols% ' )]
5363 private bool $ discoveringSymbolsTip ,
5464 )
@@ -62,13 +72,51 @@ public function getNodeType(): string
6272
6373 public function processNode (Node $ node , Scope &NodeCallbackInvoker &CollectedDataEmitter $ scope ): array
6474 {
75+ if ($ this ->newOnNonObject && $ node ->class instanceof Node \Expr) {
76+ $ errors = $ this ->checkClassNameExprType ($ node ->class , $ scope );
77+ if ($ errors !== []) {
78+ return $ errors ;
79+ }
80+ }
81+
6582 $ errors = [];
6683 foreach ($ this ->getClassNames ($ node , $ scope ) as [$ class , $ isName ]) {
6784 $ errors = array_merge ($ errors , $ this ->checkClassName ($ class , $ isName , $ node , $ scope ));
6885 }
6986 return $ errors ;
7087 }
7188
89+ /**
90+ * @return list<IdentifierRuleError>
91+ */
92+ private function checkClassNameExprType (Node \Expr $ class , Scope $ scope ): array
93+ {
94+ $ acceptedType = new UnionType ([new StringType (), new ObjectWithoutClassType ()]);
95+ $ typeResult = $ this ->ruleLevelHelper ->findTypeToCheck (
96+ $ scope ,
97+ $ class ,
98+ '' ,
99+ static fn (Type $ type ): bool => $ acceptedType ->isSuperTypeOf ($ type )->yes (),
100+ );
101+
102+ $ foundType = $ typeResult ->getType ();
103+ if ($ foundType instanceof ErrorType) {
104+ // Unknown classes and mixed are reported elsewhere (e.g. "Instantiated class X not found.").
105+ return [];
106+ }
107+
108+ if ($ acceptedType ->isSuperTypeOf ($ foundType )->yes ()) {
109+ return [];
110+ }
111+
112+ return [
113+ RuleErrorBuilder::message (sprintf (
114+ 'Cannot instantiate class using %s. ' ,
115+ $ foundType ->describe (VerbosityLevel::typeOnly ()),
116+ ))->identifier ('new.nonObject ' )->build (),
117+ ];
118+ }
119+
72120 /**
73121 * @param Node\Expr\New_ $node
74122 * @return list<IdentifierRuleError>
0 commit comments