Skip to content
18 changes: 14 additions & 4 deletions src/Analyser/ExprHandler/NewHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$parametersAcceptor = null;
$constructorReflection = null;
$classReflection = null;
$isDynamic = false;
$hasYield = false;
$throwPoints = [];
$impurePoints = [];
Expand All @@ -92,7 +93,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
if ($expr->class instanceof Name) {
$className = $scope->resolveName($expr->class);

[$constructorReflection, $classReflection, $parametersAcceptor, $constructorImpurePoints] = $this->processConstructorReflection($className, $expr, $scope);
[$constructorReflection, $classReflection, $parametersAcceptor, $constructorImpurePoints] = $this->processConstructorReflection($className, $expr, $scope, false);
$impurePoints = array_merge($impurePoints, $constructorImpurePoints);

if ($parametersAcceptor !== null) {
Expand Down Expand Up @@ -153,6 +154,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$nodeScopeResolver->processStmtNode($expr->class, $scope, $storage, $nodeCallback, StatementContext::createTopLevel());
}
} else {
$isDynamic = true;
$objectClasses = $scope->getType($expr)->getObjectClassNames();
if (count($objectClasses) === 1) {
$objectExprResult = $nodeScopeResolver->processExprNode($stmt, new New_(new Name($objectClasses[0])), $scope, $storage, new NoopNodeCallback(), $context->enterDeep());
Expand All @@ -172,7 +174,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$throwPoints = array_merge($throwPoints, $additionalThrowPoints);

if ($className !== null) {
[$constructorReflection, $classReflection, $parametersAcceptor, $constructorImpurePoints] = $this->processConstructorReflection($className, $expr, $scope);
[$constructorReflection, $classReflection, $parametersAcceptor, $constructorImpurePoints] = $this->processConstructorReflection($className, $expr, $scope, true);
$impurePoints = array_merge($impurePoints, $constructorImpurePoints);
} else {
$impurePoints[] = new ImpurePoint(
Expand Down Expand Up @@ -202,7 +204,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
if ($constructorThrowPoint !== null) {
$throwPoints[] = $constructorThrowPoint;
}
} elseif ($classReflection === null) {
} elseif ($classReflection === null || ($isDynamic && $constructorReflection === null && !$classReflection->isFinal())) {
$throwPoints[] = InternalThrowPoint::createImplicit($scope, $expr);
}

Expand All @@ -218,7 +220,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
/**
* @return array{?MethodReflection, ?ClassReflection, ?ParametersAcceptor, ImpurePoint[]}
*/
private function processConstructorReflection(string $className, New_ $expr, MutatingScope $scope): array
private function processConstructorReflection(string $className, New_ $expr, MutatingScope $scope, bool $isDynamic): array
{
$constructorReflection = null;
$parametersAcceptor = null;
Expand Down Expand Up @@ -257,6 +259,14 @@ private function processConstructorReflection(string $className, New_ $expr, Mut
'instantiation of unknown class',
false,
);
} elseif ($isDynamic && !$classReflection->isFinal()) {
$impurePoints[] = new ImpurePoint(
$scope,
$expr,
'new',
sprintf('instantiation of class %s', $classReflection->getDisplayName()),
false,
);
}

return [$constructorReflection, $classReflection, $parametersAcceptor, $impurePoints];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -798,4 +798,22 @@ public function testBug14569(): void
$this->analyse([__DIR__ . '/data/bug-14569.php'], []);
}

public function testBug6574(): void
{
$this->analyse([__DIR__ . '/data/bug-6574.php'], [
[
'Dead catch - Exception is never thrown in the try block.',
97,
],
[
'Dead catch - Exception is never thrown in the try block.',
106,
],
[
'Dead catch - Exception is never thrown in the try block.',
115,
],
]);
}

}
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Exceptions/data/bug-4806.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final public function __construct()
}
}

class HasNoConstructor
final class HasNoConstructor
{

}
Expand Down
117 changes: 117 additions & 0 deletions tests/PHPStan/Rules/Exceptions/data/bug-6574.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php declare(strict_types = 1);

namespace Bug6574;

interface FooInterface
{
}

interface BarInterface
Comment thread
staabm marked this conversation as resolved.
{
public function __construct();
}

abstract class AbstractBaz
{
}

abstract class AbstractQux
{
public function __construct()
{
}
}

class NonFinalClass
{
}

interface ThrowsVoidInterface
{
/** @throws void */
public function __construct();
}

abstract class AbstractThrowsVoid
{
/** @throws void */
public function __construct()
{
}
}

final class FinalClass
{
}

/** @param class-string<FooInterface> $class */
function interfaceWithoutConstructor(string $class): void
{
try {
new $class();
} catch (\Exception $e) {
}
}

/** @param class-string<BarInterface> $class */
function interfaceWithConstructor(string $class): void
{
try {
new $class();
} catch (\Exception $e) {
}
}

/** @param class-string<AbstractBaz> $class */
function abstractClassWithoutConstructor(string $class): void
{
try {
new $class();
} catch (\Exception $e) {
}
}

/** @param class-string<AbstractQux> $class */
function abstractClassWithConstructor(string $class): void
{
try {
new $class();
} catch (\Exception $e) {
}
}

/** @param class-string<NonFinalClass> $class */
function nonFinalClassWithoutConstructor(string $class): void
{
try {
new $class();
} catch (\Exception $e) {
}
}

/** @param class-string<ThrowsVoidInterface> $class */
function interfaceWithThrowsVoidConstructor(string $class): void
{
try {
new $class();
} catch (\Exception $e) { // dead catch - constructor is @throws void
}
}

/** @param class-string<AbstractThrowsVoid> $class */
function abstractClassWithThrowsVoidConstructor(string $class): void
{
try {
new $class();
} catch (\Exception $e) { // dead catch - constructor is @throws void
}
}

/** @param class-string<FinalClass> $class */
function finalClassWithoutConstructor(string $class): void
{
try {
new $class();
} catch (\Exception $e) { // dead catch - final class with no constructor
}
}
5 changes: 5 additions & 0 deletions tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,9 @@ public function testBug14557(): void
$this->analyse([__DIR__ . '/data/bug-14557-function.php'], []);
}

public function testBug6574(): void
{
$this->analyse([__DIR__ . '/data/bug-6574.php'], []);
}

}
23 changes: 23 additions & 0 deletions tests/PHPStan/Rules/Pure/data/bug-6574.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php declare(strict_types = 1);

namespace Bug6574Pure;

interface FooInterface
{
}

abstract class AbstractBar
{
}

/** @param class-string<FooInterface> $class */
function interfaceWithoutConstructor(string $class): void
{
new $class();
}

/** @param class-string<AbstractBar> $class */
function abstractClassWithoutConstructor(string $class): void
{
new $class();
}
Loading