2121use PHPStan \Analyser \Scope ;
2222use PHPStan \Analyser \StatementContext ;
2323use PHPStan \Analyser \ThrowPoint ;
24+ use PHPStan \Analyser \Traverser \ConstructorClassTemplateTraverser ;
25+ use PHPStan \Analyser \Traverser \GenericTypeTemplateTraverser ;
2426use PHPStan \DependencyInjection \AutowiredParameter ;
2527use PHPStan \DependencyInjection \AutowiredService ;
28+ use PHPStan \DependencyInjection \Type \DynamicReturnTypeExtensionRegistryProvider ;
2629use PHPStan \DependencyInjection \Type \DynamicThrowTypeExtensionProvider ;
2730use PHPStan \Node \MethodReturnStatementsNode ;
31+ use PHPStan \Parser \NewAssignedToPropertyVisitor ;
2832use PHPStan \Reflection \ClassReflection ;
33+ use PHPStan \Reflection \Dummy \DummyConstructorReflection ;
2934use PHPStan \Reflection \MethodReflection ;
3035use PHPStan \Reflection \ParametersAcceptor ;
3136use PHPStan \Reflection \ParametersAcceptorSelector ;
3237use PHPStan \Reflection \ReflectionProvider ;
38+ use PHPStan \ShouldNotHappenException ;
39+ use PHPStan \Type \ErrorType ;
40+ use PHPStan \Type \Generic \GenericObjectType ;
41+ use PHPStan \Type \Generic \GenericStaticType ;
42+ use PHPStan \Type \Generic \TemplateType ;
43+ use PHPStan \Type \Generic \TemplateTypeMap ;
44+ use PHPStan \Type \NeverType ;
45+ use PHPStan \Type \NonexistentParentClassType ;
46+ use PHPStan \Type \ObjectType ;
47+ use PHPStan \Type \StaticType ;
48+ use PHPStan \Type \Type ;
49+ use PHPStan \Type \TypeCombinator ;
50+ use PHPStan \Type \TypeTraverser ;
3351use Throwable ;
52+ use function array_key_exists ;
3453use function array_map ;
3554use function array_merge ;
3655use function count ;
@@ -46,6 +65,7 @@ final class NewHandler implements ExprHandler
4665 public function __construct (
4766 private ReflectionProvider $ reflectionProvider ,
4867 private DynamicThrowTypeExtensionProvider $ dynamicThrowTypeExtensionProvider ,
68+ private DynamicReturnTypeExtensionRegistryProvider $ dynamicReturnTypeExtensionRegistryProvider ,
4969 #[AutowiredParameter(ref: '%exceptions.implicitThrows% ' )]
5070 private bool $ implicitThrows ,
5171 )
@@ -251,4 +271,301 @@ private function getConstructorThrowPoint(MethodReflection $constructorReflectio
251271 return null ;
252272 }
253273
274+ /**
275+ * @param New_ $expr
276+ */
277+ public function resolveType (MutatingScope $ scope , Expr $ expr ): Type
278+ {
279+ if ($ expr ->class instanceof Name) {
280+ return $ this ->exactInstantiation ($ scope , $ expr , $ expr ->class );
281+ }
282+ if ($ expr ->class instanceof Node \Stmt \Class_) {
283+ $ anonymousClassReflection = $ this ->reflectionProvider ->getAnonymousClassReflection ($ expr ->class , $ scope );
284+
285+ return new ObjectType ($ anonymousClassReflection ->getName ());
286+ }
287+
288+ $ exprType = $ scope ->getType ($ expr ->class );
289+ return $ exprType ->getObjectTypeOrClassStringObjectType ();
290+ }
291+
292+ private function exactInstantiation (MutatingScope $ scope , New_ $ node , Name $ className ): Type
293+ {
294+ $ resolvedClassName = $ scope ->resolveName ($ className );
295+ $ isStatic = false ;
296+ $ lowercasedClassName = $ className ->toLowerString ();
297+ if ($ lowercasedClassName === 'static ' ) {
298+ $ isStatic = true ;
299+ }
300+
301+ if (!$ this ->reflectionProvider ->hasClass ($ resolvedClassName )) {
302+ if ($ lowercasedClassName === 'static ' ) {
303+ if (!$ scope ->isInClass ()) {
304+ return new ErrorType ();
305+ }
306+
307+ return new StaticType ($ scope ->getClassReflection ());
308+ }
309+ if ($ lowercasedClassName === 'parent ' ) {
310+ return new NonexistentParentClassType ();
311+ }
312+
313+ return new ObjectType ($ resolvedClassName );
314+ }
315+
316+ $ classReflection = $ this ->reflectionProvider ->getClass ($ resolvedClassName );
317+ $ nonFinalClassReflection = $ classReflection ;
318+ if (!$ isStatic ) {
319+ $ classReflection = $ classReflection ->asFinal ();
320+ }
321+ if ($ classReflection ->hasConstructor ()) {
322+ $ constructorMethod = $ classReflection ->getConstructor ();
323+ } else {
324+ $ constructorMethod = new DummyConstructorReflection ($ classReflection );
325+ }
326+
327+ if ($ constructorMethod ->getName () === '' ) {
328+ throw new ShouldNotHappenException ();
329+ }
330+
331+ $ resolvedTypes = [];
332+ $ methodCall = new Expr \StaticCall (
333+ new Name ($ resolvedClassName ),
334+ new Node \Identifier ($ constructorMethod ->getName ()),
335+ $ node ->getArgs (),
336+ );
337+
338+ $ parametersAcceptor = ParametersAcceptorSelector::selectFromArgs (
339+ $ scope ,
340+ $ methodCall ->getArgs (),
341+ $ constructorMethod ->getVariants (),
342+ $ constructorMethod ->getNamedArgumentsVariants (),
343+ );
344+ $ normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments ($ parametersAcceptor , $ methodCall );
345+
346+ if ($ normalizedMethodCall !== null ) {
347+ foreach ($ this ->dynamicReturnTypeExtensionRegistryProvider ->getRegistry ()->getDynamicStaticMethodReturnTypeExtensionsForClass ($ classReflection ->getName ()) as $ dynamicStaticMethodReturnTypeExtension ) {
348+ if (!$ dynamicStaticMethodReturnTypeExtension ->isStaticMethodSupported ($ constructorMethod )) {
349+ continue ;
350+ }
351+
352+ $ resolvedType = $ dynamicStaticMethodReturnTypeExtension ->getTypeFromStaticMethodCall (
353+ $ constructorMethod ,
354+ $ normalizedMethodCall ,
355+ $ scope ,
356+ );
357+ if ($ resolvedType === null ) {
358+ continue ;
359+ }
360+
361+ $ resolvedTypes [] = $ resolvedType ;
362+ }
363+ }
364+
365+ if (count ($ resolvedTypes ) > 0 ) {
366+ return TypeCombinator::union (...$ resolvedTypes );
367+ }
368+
369+ $ methodResult = $ scope ->getType ($ methodCall );
370+ if ($ methodResult instanceof NeverType && $ methodResult ->isExplicit ()) {
371+ return $ methodResult ;
372+ }
373+
374+ $ objectType = $ isStatic ? new StaticType ($ classReflection ) : new ObjectType ($ resolvedClassName , classReflection: $ classReflection );
375+ if (!$ classReflection ->isGeneric ()) {
376+ return $ objectType ;
377+ }
378+
379+ $ assignedToProperty = $ node ->getAttribute (NewAssignedToPropertyVisitor::ATTRIBUTE_NAME );
380+ if ($ assignedToProperty !== null ) {
381+ $ constructorVariants = $ constructorMethod ->getVariants ();
382+ if (count ($ constructorVariants ) === 1 ) {
383+ $ constructorVariant = $ constructorVariants [0 ];
384+ $ classTemplateTypes = $ classReflection ->getTemplateTypeMap ()->getTypes ();
385+ $ originalClassTemplateTypes = $ classTemplateTypes ;
386+
387+ $ traverser = new ConstructorClassTemplateTraverser ($ classTemplateTypes );
388+ foreach ($ constructorVariant ->getParameters () as $ parameter ) {
389+ if (!$ parameter ->getType ()->hasTemplateOrLateResolvableType ()) {
390+ continue ;
391+ }
392+ TypeTraverser::map ($ parameter ->getType (), $ traverser );
393+ }
394+ $ classTemplateTypes = $ traverser ->getClassTemplateTypes ();
395+
396+ if (count ($ classTemplateTypes ) === count ($ originalClassTemplateTypes )) {
397+ $ propertyType = TypeCombinator::removeNull ($ scope ->getType ($ assignedToProperty ));
398+ $ nonFinalObjectType = $ isStatic ? new StaticType ($ nonFinalClassReflection ) : new ObjectType ($ resolvedClassName , classReflection: $ nonFinalClassReflection );
399+ if ($ nonFinalObjectType ->isSuperTypeOf ($ propertyType )->yes ()) {
400+ return $ propertyType ;
401+ }
402+ }
403+ }
404+ }
405+
406+ if ($ constructorMethod instanceof DummyConstructorReflection) {
407+ if ($ isStatic ) {
408+ return new GenericStaticType (
409+ $ classReflection ,
410+ $ classReflection ->typeMapToList ($ classReflection ->getTemplateTypeMap ()->resolveToBounds ()),
411+ null ,
412+ [],
413+ );
414+ }
415+
416+ $ types = $ classReflection ->typeMapToList ($ classReflection ->getTemplateTypeMap ()->resolveToBounds ());
417+ return new GenericObjectType (
418+ $ resolvedClassName ,
419+ $ types ,
420+ classReflection: $ classReflection ->withTypes ($ types )->asFinal (),
421+ );
422+ }
423+
424+ if ($ constructorMethod ->getDeclaringClass ()->getName () !== $ classReflection ->getName ()) {
425+ if (!$ constructorMethod ->getDeclaringClass ()->isGeneric ()) {
426+ if ($ isStatic ) {
427+ return new GenericStaticType (
428+ $ classReflection ,
429+ $ classReflection ->typeMapToList ($ classReflection ->getTemplateTypeMap ()->resolveToBounds ()),
430+ null ,
431+ [],
432+ );
433+ }
434+
435+ $ types = $ classReflection ->typeMapToList ($ classReflection ->getTemplateTypeMap ()->resolveToBounds ());
436+ return new GenericObjectType (
437+ $ resolvedClassName ,
438+ $ types ,
439+ classReflection: $ classReflection ->withTypes ($ types )->asFinal (),
440+ );
441+ }
442+ $ newType = new GenericObjectType ($ resolvedClassName , $ classReflection ->typeMapToList ($ classReflection ->getTemplateTypeMap ()));
443+ $ ancestorType = $ newType ->getAncestorWithClassName ($ constructorMethod ->getDeclaringClass ()->getName ());
444+ if ($ ancestorType === null ) {
445+ if ($ isStatic ) {
446+ return new GenericStaticType (
447+ $ classReflection ,
448+ $ classReflection ->typeMapToList ($ classReflection ->getTemplateTypeMap ()->resolveToBounds ()),
449+ null ,
450+ [],
451+ );
452+ }
453+
454+ $ types = $ classReflection ->typeMapToList ($ classReflection ->getTemplateTypeMap ()->resolveToBounds ());
455+ return new GenericObjectType (
456+ $ resolvedClassName ,
457+ $ types ,
458+ classReflection: $ classReflection ->withTypes ($ types )->asFinal (),
459+ );
460+ }
461+ $ ancestorClassReflections = $ ancestorType ->getObjectClassReflections ();
462+ if (count ($ ancestorClassReflections ) !== 1 ) {
463+ if ($ isStatic ) {
464+ return new GenericStaticType (
465+ $ classReflection ,
466+ $ classReflection ->typeMapToList ($ classReflection ->getTemplateTypeMap ()->resolveToBounds ()),
467+ null ,
468+ [],
469+ );
470+ }
471+
472+ $ types = $ classReflection ->typeMapToList ($ classReflection ->getTemplateTypeMap ()->resolveToBounds ());
473+ return new GenericObjectType (
474+ $ resolvedClassName ,
475+ $ types ,
476+ classReflection: $ classReflection ->withTypes ($ types )->asFinal (),
477+ );
478+ }
479+
480+ $ newParentNode = new New_ (new Name ($ constructorMethod ->getDeclaringClass ()->getName ()), $ node ->args );
481+ $ newParentType = $ scope ->getType ($ newParentNode );
482+ $ newParentTypeClassReflections = $ newParentType ->getObjectClassReflections ();
483+ if (count ($ newParentTypeClassReflections ) !== 1 ) {
484+ if ($ isStatic ) {
485+ return new GenericStaticType (
486+ $ classReflection ,
487+ $ classReflection ->typeMapToList ($ classReflection ->getTemplateTypeMap ()->resolveToBounds ()),
488+ null ,
489+ [],
490+ );
491+ }
492+
493+ $ types = $ classReflection ->typeMapToList ($ classReflection ->getTemplateTypeMap ()->resolveToBounds ());
494+ return new GenericObjectType (
495+ $ resolvedClassName ,
496+ $ types ,
497+ classReflection: $ classReflection ->withTypes ($ types )->asFinal (),
498+ );
499+ }
500+ $ newParentTypeClassReflection = $ newParentTypeClassReflections [0 ];
501+
502+ $ ancestorClassReflection = $ ancestorClassReflections [0 ];
503+ $ ancestorMapping = [];
504+ foreach ($ ancestorClassReflection ->getActiveTemplateTypeMap ()->getTypes () as $ typeName => $ templateType ) {
505+ if (!$ templateType instanceof TemplateType) {
506+ continue ;
507+ }
508+
509+ $ ancestorMapping [$ typeName ] = $ templateType ;
510+ }
511+
512+ $ resolvedTypeMap = [];
513+ foreach ($ newParentTypeClassReflection ->getActiveTemplateTypeMap ()->getTypes () as $ typeName => $ type ) {
514+ if (!array_key_exists ($ typeName , $ ancestorMapping )) {
515+ continue ;
516+ }
517+
518+ $ ancestorType = $ ancestorMapping [$ typeName ];
519+ if (!$ ancestorType ->getBound ()->isSuperTypeOf ($ type )->yes ()) {
520+ continue ;
521+ }
522+
523+ if (!array_key_exists ($ ancestorType ->getName (), $ resolvedTypeMap )) {
524+ $ resolvedTypeMap [$ ancestorType ->getName ()] = $ type ;
525+ continue ;
526+ }
527+
528+ $ resolvedTypeMap [$ ancestorType ->getName ()] = TypeCombinator::union ($ resolvedTypeMap [$ ancestorType ->getName ()], $ type );
529+ }
530+
531+ if ($ isStatic ) {
532+ return new GenericStaticType (
533+ $ classReflection ,
534+ $ classReflection ->typeMapToList (new TemplateTypeMap ($ resolvedTypeMap )),
535+ null ,
536+ [],
537+ );
538+ }
539+
540+ $ types = $ classReflection ->typeMapToList (new TemplateTypeMap ($ resolvedTypeMap ));
541+ return new GenericObjectType (
542+ $ resolvedClassName ,
543+ $ types ,
544+ classReflection: $ classReflection ->withTypes ($ types )->asFinal (),
545+ );
546+ }
547+
548+ $ resolvedTemplateTypeMap = $ parametersAcceptor ->getResolvedTemplateTypeMap ();
549+ $ types = $ classReflection ->typeMapToList ($ classReflection ->getTemplateTypeMap ());
550+ $ newGenericType = new GenericObjectType (
551+ $ resolvedClassName ,
552+ $ types ,
553+ classReflection: $ classReflection ->withTypes ($ types )->asFinal (),
554+ );
555+ if ($ isStatic ) {
556+ $ newGenericType = new GenericStaticType (
557+ $ classReflection ,
558+ $ types ,
559+ null ,
560+ [],
561+ );
562+ }
563+
564+ if (!$ newGenericType ->hasTemplateOrLateResolvableType ()) {
565+ return $ newGenericType ;
566+ }
567+
568+ return TypeTraverser::map ($ newGenericType , new GenericTypeTemplateTraverser ($ resolvedTemplateTypeMap ));
569+ }
570+
254571}
0 commit comments