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
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ lint:
--exclude tests/PHPStan/Rules/Classes/data/enum-cannot-be-attribute.php \
--exclude tests/PHPStan/Rules/Classes/data/class-attributes.php \
--exclude tests/PHPStan/Rules/Classes/data/enum-attributes.php \
--exclude tests/PHPStan/Rules/Cast/data/void-cast.php \
--exclude tests/PHPStan/Rules/Properties/data/property-hook-attributes-nodiscard.php \
src tests

install-paratest:
Expand Down
7 changes: 6 additions & 1 deletion build/collision-detector.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
"../tests/PHPStan/Rules/Functions/data/define-bug-3349.php",
"../tests/PHPStan/Levels/data/stubs/function.php",
"../tests/PHPStan/Rules/Properties/data/abstract-final-property-hook-parse-error.php",
"../tests/PHPStan/Rules/Properties/data/final-property-hooks.php"
"../tests/PHPStan/Rules/Properties/data/final-property-hooks.php",
"../tests/PHPStan/Rules/Functions/data/function-call-statement-result-discarded.php",
"../tests/PHPStan/Rules/Methods/data/method-call-statement-result-discarded.php",
"../tests/PHPStan/Rules/Methods/data/static-method-call-statement-result-discarded.php",
"../tests/PHPStan/Rules/Properties/data/property-hook-attributes-nodiscard.php",
"../tests/PHPStan/Rules/Cast/data/void-cast.php"
]
}
31 changes: 31 additions & 0 deletions src/Parser/VoidCastVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php declare(strict_types = 1);

namespace PHPStan\Parser;

use Override;
use PhpParser\Node;
use PhpParser\Node\Expr\Cast\Void_;
use PhpParser\NodeVisitorAbstract;
use PHPStan\DependencyInjection\AutowiredService;

#[AutowiredService]
final class VoidCastVisitor extends NodeVisitorAbstract
{

private bool $pendingVoidCast = false;

public const ATTRIBUTE_NAME = 'voidCastExpr';

#[Override]
public function enterNode(Node $node): ?Node
{
if ($node instanceof Void_) {
$this->pendingVoidCast = true;
} elseif ($this->pendingVoidCast) {
$node->setAttribute(self::ATTRIBUTE_NAME, true);
$this->pendingVoidCast = false;
}
return null;
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Annotations/AnnotationMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,9 @@ public function getAttributes(): array
return [];
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Dummy/ChangedTypeMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,9 @@ public function getAttributes(): array
return $this->reflection->getAttributes();
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
return $this->reflection->hasNoDiscardAttribute();
}

}
6 changes: 6 additions & 0 deletions src/Reflection/Dummy/DummyConstructorReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,10 @@ public function getAttributes(): array
return [];
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
// Align with the getAttributes() returning empty
return TrinaryLogic::createNo();
}

}
6 changes: 6 additions & 0 deletions src/Reflection/Dummy/DummyMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,10 @@ public function getAttributes(): array
return [];
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
// Align with the getAttributes() returning empty
return TrinaryLogic::createNo();
}

}
7 changes: 7 additions & 0 deletions src/Reflection/ExtendedMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,11 @@ public function isPure(): TrinaryLogic;
*/
public function getAttributes(): array;

/**
* Has the #[\NoDiscard] attribute - on PHP 8.5+ if the function's return
* value is unused at runtime a warning is emitted, phpstan will emit the
* warning during analysis and on older PHP versions too
*/
public function hasNoDiscardAttribute(): TrinaryLogic;

}
7 changes: 7 additions & 0 deletions src/Reflection/FunctionReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,11 @@ public function isPure(): TrinaryLogic;
*/
public function getAttributes(): array;

/**
* Has the #[\NoDiscard] attribute - on PHP 8.5+ if the function's return
* value is unused at runtime a warning is emitted, phpstan will emit the
* warning during analysis and on older PHP versions too
*/
public function hasNoDiscardAttribute(): TrinaryLogic;

}
10 changes: 10 additions & 0 deletions src/Reflection/Native/NativeFunctionReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,14 @@ public function getAttributes(): array
return $this->attributes;
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
foreach ($this->attributes as $attrib) {
if ($attrib->getName() === 'NoDiscard') {
return TrinaryLogic::createYes();
}
}
return TrinaryLogic::createNo();
}

}
10 changes: 10 additions & 0 deletions src/Reflection/Native/NativeMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,14 @@ public function getAttributes(): array
return $this->attributes;
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
foreach ($this->attributes as $attrib) {
if ($attrib->getName() === 'NoDiscard') {
return TrinaryLogic::createYes();
}
}
return TrinaryLogic::createNo();
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Php/ClosureCallMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,9 @@ public function getAttributes(): array
return $this->nativeMethodReflection->getAttributes();
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
return $this->nativeMethodReflection->hasNoDiscardAttribute();
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Php/EnumCasesMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,9 @@ public function getAttributes(): array
return [];
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
return TrinaryLogic::createYes();
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Php/ExitFunctionReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,9 @@ public function getAttributes(): array
return [];
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
10 changes: 10 additions & 0 deletions src/Reflection/Php/PhpFunctionFromParserNodeReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -330,4 +330,14 @@ public function getAttributes(): array
return $this->attributes;
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
foreach ($this->attributes as $attrib) {
if ($attrib->getName() === 'NoDiscard') {
return TrinaryLogic::createYes();
}
}
return TrinaryLogic::createNo();
}

}
10 changes: 10 additions & 0 deletions src/Reflection/Php/PhpFunctionReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,14 @@ public function getAttributes(): array
return $this->attributes;
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
foreach ($this->attributes as $attrib) {
if ($attrib->getName() === 'NoDiscard') {
return TrinaryLogic::createYes();
}
}
return TrinaryLogic::createNo();
}

}
10 changes: 10 additions & 0 deletions src/Reflection/Php/PhpMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -520,4 +520,14 @@ public function getAttributes(): array
return $this->attributes;
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
foreach ($this->attributes as $attrib) {
if ($attrib->getName() === 'NoDiscard') {
return TrinaryLogic::createYes();
}
}
return TrinaryLogic::createNo();
}

}
5 changes: 5 additions & 0 deletions src/Reflection/ResolvedMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,9 @@ public function getAttributes(): array
return $this->reflection->getAttributes();
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
return $this->reflection->hasNoDiscardAttribute();
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Type/IntersectionTypeMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,9 @@ public function getAttributes(): array
return $this->methods[0]->getAttributes();
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
return TrinaryLogic::lazyMaxMin($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => $method->hasNoDiscardAttribute());
}

}
5 changes: 5 additions & 0 deletions src/Reflection/Type/UnionTypeMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,9 @@ public function getAttributes(): array
return $this->methods[0]->getAttributes();
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
return TrinaryLogic::lazyExtremeIdentity($this->methods, static fn (ExtendedMethodReflection $method): TrinaryLogic => $method->hasNoDiscardAttribute());
}

}
6 changes: 6 additions & 0 deletions src/Reflection/WrappedExtendedMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,10 @@ public function getAttributes(): array
return [];
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
// Align with the getAttributes() returning empty
return TrinaryLogic::createNo();
}

}
7 changes: 7 additions & 0 deletions src/Rules/AttributesCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public function check(
array $attrGroups,
int $requiredTarget,
string $targetName,
bool $isPropertyHook = false,
): array
{
$errors = [];
Expand Down Expand Up @@ -81,6 +82,12 @@ public function check(
->identifier('attribute.target')
->line($attribute->getStartLine())
->build();
} elseif ($isPropertyHook && strtolower($name) === 'nodiscard') {
// #[\NoDiscard] cannot be used on property hooks
$errors[] = RuleErrorBuilder::message(sprintf('Attribute class %s cannot be used on property hooks.', $name))
->identifier('attribute.target')
->line($attribute->getStartLine())
->build();
}

if (($flags & Attribute::IS_REPEATABLE) === 0) {
Expand Down
41 changes: 41 additions & 0 deletions src/Rules/Cast/VoidCastRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Cast;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;

/**
* @implements Rule<Node\Expr\Cast\Void_>
*/
#[RegisteredRule(level: 0)]
final class VoidCastRule implements Rule
{

public function __construct()
{
}

public function getNodeType(): string
{
return Node\Expr\Cast\Void_::class;
}

public function processNode(Node $node, Scope $scope): array
{
if ($scope->isInFirstLevelStatement()) {
return [];
}

return [
RuleErrorBuilder::message('The (void) cast cannot be used within an expression.')
->identifier('cast.void')
->nonIgnorable()
->build(),
];
}

}
58 changes: 58 additions & 0 deletions src/Rules/Functions/CallToFunctionStatementWithNoDiscardRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Functions;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function sprintf;

/**
* @implements Rule<Node\Stmt\Expression>
*/
#[RegisteredRule(level: 4)]
final class CallToFunctionStatementWithNoDiscardRule implements Rule
{

public function __construct(private ReflectionProvider $reflectionProvider)
{
}

public function getNodeType(): string
{
return Node\Stmt\Expression::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (!$node->expr instanceof Node\Expr\FuncCall) {
return [];
}

$funcCall = $node->expr;
if (!($funcCall->name instanceof Node\Name)) {
return [];
}

if (!$this->reflectionProvider->hasFunction($funcCall->name, $scope)) {
return [];
}

$function = $this->reflectionProvider->getFunction($funcCall->name, $scope);

if (!$function->hasNoDiscardAttribute()->yes()) {
return [];
}

return [
RuleErrorBuilder::message(sprintf(
'Call to function %s() on a separate line discards return value.',
$function->getName(),
))->identifier('function.resultDiscarded')->build(),
];
}

}
Loading
Loading