Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions conf/bleedingEdge.neon
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ parameters:
reportMethodPurityOverride: true
checkDynamicConstantNameValues: true
unusedLabel: true
newOnNonObject: true
1 change: 1 addition & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ parameters:
reportMethodPurityOverride: false
checkDynamicConstantNameValues: false
unusedLabel: false
newOnNonObject: false
fileExtensions:
- php
checkAdvancedIsset: false
Expand Down
1 change: 1 addition & 0 deletions conf/parametersSchema.neon
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ parametersSchema:
reportMethodPurityOverride: bool()
checkDynamicConstantNameValues: bool()
unusedLabel: bool()
newOnNonObject: bool()
])
fileExtensions: listOf(string())
checkAdvancedIsset: bool()
Expand Down
48 changes: 48 additions & 0 deletions src/Rules/Classes/InstantiationRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@
use PHPStan\Rules\RestrictedUsage\RewrittenDeclaringClassMethodReflection;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use PHPStan\Type\VerbosityLevel;
use function array_filter;
use function array_map;
use function array_merge;
Expand All @@ -48,7 +55,10 @@
private ReflectionProvider $reflectionProvider,
private FunctionCallParametersCheck $check,
private ClassNameCheck $classCheck,
private RuleLevelHelper $ruleLevelHelper,
private ConsistentConstructorHelper $consistentConstructorHelper,
#[AutowiredParameter(ref: '%featureToggles.newOnNonObject%')]
private bool $newOnNonObject,
#[AutowiredParameter(ref: '%tips.discoveringSymbols%')]
private bool $discoveringSymbolsTip,
)
Expand All @@ -62,13 +72,51 @@

public function processNode(Node $node, Scope&NodeCallbackInvoker&CollectedDataEmitter $scope): array
{
if ($this->newOnNonObject && $node->class instanceof Node\Expr) {
$errors = $this->checkClassNameExprType($node->class, $scope);
if ($errors !== []) {
return $errors;
}
}

$errors = [];
foreach ($this->getClassNames($node, $scope) as [$class, $isName]) {
$errors = array_merge($errors, $this->checkClassName($class, $isName, $node, $scope));
}
return $errors;
}

/**
* @return list<IdentifierRuleError>
*/
private function checkClassNameExprType(Node\Expr $class, Scope $scope): array
{
$acceptedType = new UnionType([new StringType(), new ObjectWithoutClassType()]);
$typeResult = $this->ruleLevelHelper->findTypeToCheck(
$scope,
$class,
'',
static fn (Type $type): bool => $acceptedType->isSuperTypeOf($type)->yes(),

Check warning on line 99 in src/Rules/Classes/InstantiationRule.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ $scope, $class, '', - static fn (Type $type): bool => $acceptedType->isSuperTypeOf($type)->yes(), + static fn (Type $type): bool => $type->isSuperTypeOf($acceptedType)->yes(), ); $foundType = $typeResult->getType();

Check warning on line 99 in src/Rules/Classes/InstantiationRule.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $scope, $class, '', - static fn (Type $type): bool => $acceptedType->isSuperTypeOf($type)->yes(), + static fn (Type $type): bool => !$acceptedType->isSuperTypeOf($type)->no(), ); $foundType = $typeResult->getType();

Check warning on line 99 in src/Rules/Classes/InstantiationRule.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ $scope, $class, '', - static fn (Type $type): bool => $acceptedType->isSuperTypeOf($type)->yes(), + static fn (Type $type): bool => $type->isSuperTypeOf($acceptedType)->yes(), ); $foundType = $typeResult->getType();
);

$foundType = $typeResult->getType();
if ($foundType instanceof ErrorType) {
// Unknown classes and mixed are reported elsewhere (e.g. "Instantiated class X not found.").
return [];
}

if ($acceptedType->isSuperTypeOf($foundType)->yes()) {

Check warning on line 108 in src/Rules/Classes/InstantiationRule.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ return []; } - if ($acceptedType->isSuperTypeOf($foundType)->yes()) { + if (!$acceptedType->isSuperTypeOf($foundType)->no()) { return []; }

Check warning on line 108 in src/Rules/Classes/InstantiationRule.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ return []; } - if ($acceptedType->isSuperTypeOf($foundType)->yes()) { + if (!$acceptedType->isSuperTypeOf($foundType)->no()) { return []; }

Check warning on line 108 in src/Rules/Classes/InstantiationRule.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ return []; } - if ($acceptedType->isSuperTypeOf($foundType)->yes()) { + if ($foundType->isSuperTypeOf($acceptedType)->yes()) { return []; }
return [];
}

return [
RuleErrorBuilder::message(sprintf(
'Cannot instantiate class using %s.',
$foundType->describe(VerbosityLevel::typeOnly()),
))->identifier('new.nonObject')->build(),
];
}

/**
* @param Node\Expr\New_ $node
* @return list<IdentifierRuleError>
Expand Down
23 changes: 13 additions & 10 deletions tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,21 @@ protected function getRule(): Rule
{
$reflectionProvider = self::createReflectionProvider();
$container = self::getContainer();
$ruleLevelHelper = new RuleLevelHelper(
$reflectionProvider,
checkNullables: true,
checkThisOnly: false,
checkUnionTypes: true,
checkExplicitMixed: false,
checkImplicitMixed: false,
checkBenevolentUnionTypes: false,
discoveringSymbolsTip: true,
);
return new InstantiationRule(
$container,
$reflectionProvider,
new FunctionCallParametersCheck(
new RuleLevelHelper(
$reflectionProvider,
checkNullables: true,
checkThisOnly: false,
checkUnionTypes: true,
checkExplicitMixed: false,
checkImplicitMixed: false,
checkBenevolentUnionTypes: false,
discoveringSymbolsTip: true,
),
$ruleLevelHelper,
new NullsafeCheck(),
new UnresolvableTypeHelper(),
new PropertyReflectionFinder(),
Expand All @@ -53,7 +54,9 @@ protected function getRule(): Rule
$reflectionProvider,
$container,
),
$ruleLevelHelper,
new ConsistentConstructorHelper(),
newOnNonObject: true,
discoveringSymbolsTip: true,
);
}
Expand Down
61 changes: 51 additions & 10 deletions tests/PHPStan/Rules/Classes/InstantiationRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,21 @@ protected function getRule(): Rule
{
$reflectionProvider = self::createReflectionProvider();
$container = self::getContainer();
$ruleLevelHelper = new RuleLevelHelper(
$reflectionProvider,
checkNullables: true,
checkThisOnly: false,
checkUnionTypes: true,
checkExplicitMixed: $this->checkExplicitMixed,
checkImplicitMixed: false,
checkBenevolentUnionTypes: false,
discoveringSymbolsTip: true,
);
return new InstantiationRule(
$container,
$reflectionProvider,
new FunctionCallParametersCheck(
new RuleLevelHelper(
$reflectionProvider,
checkNullables: true,
checkThisOnly: false,
checkUnionTypes: true,
checkExplicitMixed: $this->checkExplicitMixed,
checkImplicitMixed: false,
checkBenevolentUnionTypes: false,
discoveringSymbolsTip: true,
),
$ruleLevelHelper,
new NullsafeCheck(),
new UnresolvableTypeHelper(),
new PropertyReflectionFinder(),
Expand All @@ -55,7 +56,9 @@ protected function getRule(): Rule
$reflectionProvider,
$container,
),
$ruleLevelHelper,
new ConsistentConstructorHelper(),
newOnNonObject: true,
discoveringSymbolsTip: true,
);
}
Expand Down Expand Up @@ -682,4 +685,42 @@ public function testBug14499(): void
$this->analyse([__DIR__ . '/data/bug-14499.php'], []);
}

public function testInstantiationWithNonObjectType(): void
{
$this->analyse([__DIR__ . '/data/instantiation-non-object.php'], [
[
'Cannot instantiate class using int.',
35,
],
[
'Cannot instantiate class using int.',
39,
],
[
'Cannot instantiate class using float.',
40,
],
[
'Cannot instantiate class using bool.',
41,
],
[
'Cannot instantiate class using int|string.',
42,
],
[
'Cannot instantiate class using float|int.',
43,
],
[
'Cannot instantiate class using string|null.',
44,
],
[
'Cannot instantiate class using array<int, string>.',
50,
],
]);
}

}
51 changes: 51 additions & 0 deletions tests/PHPStan/Rules/Classes/data/instantiation-non-object.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php declare(strict_types = 1);

namespace InstantiationNonObject;

function get_class_name(): int
{
return 123;
}

class Foo
{
}

/**
* @param int|string $intOrString
* @param int|float $intOrFloat
* @param class-string $classString
* @param class-string<Foo> $classStringOfFoo
*/
function doFoo(
string $string,
object $object,
int $int,
float $float,
bool $bool,
$intOrString,
$intOrFloat,
?string $nullableString,
string $classString,
Comment thread
VincentLanglet marked this conversation as resolved.
string $classStringOfFoo,
Foo $foo
): void
{
$class = get_class_name();
new $class;

new $string;
new $object;
new $int;
new $float;
new $bool;
new $intOrString;
new $intOrFloat;
new $nullableString;
new $classString;
new $classStringOfFoo;
new $foo;

$array = ['a'];
new $array;
}
Loading