Skip to content

Commit c5f3ce4

Browse files
committed
resolveType in NewHandler
1 parent 8191828 commit c5f3ce4

File tree

2 files changed

+317
-300
lines changed

2 files changed

+317
-300
lines changed

src/Analyser/ExprHandler/NewHandler.php

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,35 @@
2121
use PHPStan\Analyser\Scope;
2222
use PHPStan\Analyser\StatementContext;
2323
use PHPStan\Analyser\ThrowPoint;
24+
use PHPStan\Analyser\Traverser\ConstructorClassTemplateTraverser;
25+
use PHPStan\Analyser\Traverser\GenericTypeTemplateTraverser;
2426
use PHPStan\DependencyInjection\AutowiredParameter;
2527
use PHPStan\DependencyInjection\AutowiredService;
28+
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
2629
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
2730
use PHPStan\Node\MethodReturnStatementsNode;
31+
use PHPStan\Parser\NewAssignedToPropertyVisitor;
2832
use PHPStan\Reflection\ClassReflection;
33+
use PHPStan\Reflection\Dummy\DummyConstructorReflection;
2934
use PHPStan\Reflection\MethodReflection;
3035
use PHPStan\Reflection\ParametersAcceptor;
3136
use PHPStan\Reflection\ParametersAcceptorSelector;
3237
use 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;
3351
use Throwable;
52+
use function array_key_exists;
3453
use function array_map;
3554
use function array_merge;
3655
use 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

Comments
 (0)