Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
73d5925
Add support for PHP 8.5 `#[\NoDiscard]` on functions
DanielEScherzer Aug 21, 2025
6fd2138
Trinary logic migration
DanielEScherzer Aug 28, 2025
477b4b6
Void casts, methods and static methods
DanielEScherzer Aug 29, 2025
806b61d
fixes
DanielEScherzer Aug 29, 2025
b9e0c62
parse errors
DanielEScherzer Aug 29, 2025
4379a75
missing use
DanielEScherzer Aug 29, 2025
dfbb5a6
another
DanielEScherzer Aug 29, 2025
065712d
New VoidCastRule
DanielEScherzer Aug 30, 2025
62dd5b7
NoDiscard on hooks
DanielEScherzer Aug 30, 2025
d87ffc7
Lint exclusions, unify implementations
DanielEScherzer Aug 30, 2025
76a00d4
fixes
DanielEScherzer Aug 30, 2025
59646cc
Fixes
DanielEScherzer Sep 15, 2025
53ff416
try validating void
DanielEScherzer Sep 15, 2025
0bcf3b9
lint exclusions
DanielEScherzer Sep 15, 2025
3dacefe
,
DanielEScherzer Sep 15, 2025
097dee7
s/s/, ->yes
DanielEScherzer Sep 15, 2025
df77b1c
->name
DanielEScherzer Sep 15, 2025
e9c974c
identifier
DanielEScherzer Sep 15, 2025
79a5f1f
namespace
DanielEScherzer Sep 15, 2025
fb0e6ae
misplaced
DanielEScherzer Sep 15, 2025
07ec1f7
impossible
DanielEScherzer Sep 15, 2025
e2fdc31
Remove the visitor, not sure what it is for
ondrejmirtes Sep 18, 2025
35918cc
Remove changes to collision detector config
ondrejmirtes Sep 18, 2025
6604dc6
Run Collision Detector on PHP 8.5
ondrejmirtes Sep 18, 2025
374fc6f
Rework property hook check
ondrejmirtes Sep 18, 2025
d12b87d
Fix build
ondrejmirtes Sep 18, 2025
0a3b734
Test works on older PHP versions
ondrejmirtes Sep 18, 2025
6c947cb
This file does not exist
ondrejmirtes Sep 18, 2025
560895e
Rename method
ondrejmirtes Sep 18, 2025
257076b
Shift rules to level 0
ondrejmirtes Sep 18, 2025
9bf8476
Check callables too
ondrejmirtes Sep 18, 2025
96e3a0d
Do not report first class callables
ondrejmirtes Sep 18, 2025
1d2157d
Report NoDiscard above never return type + implicit void methods repo…
ondrejmirtes Sep 18, 2025
5db8f0d
Fix tests
ondrejmirtes Sep 18, 2025
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: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.4"
php-version: "8.5"

- name: "Install dependencies"
run: "composer install --no-interaction --no-progress"
Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ 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 \
--exclude tests/PHPStan/Rules/Functions/data/arrow-function-typehints-nodiscard.php \
--exclude tests/PHPStan/Rules/Functions/data/closure-typehints-nodiscard.php \
--exclude tests/PHPStan/Rules/Functions/data/typehints-nodiscard.php \
--exclude tests/PHPStan/Rules/Methods/data/typehints-nodiscard.php \
src tests

install-paratest:
Expand Down
3 changes: 2 additions & 1 deletion build/collision-detector.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"../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/Cast/data/void-cast.php"
]
}
26 changes: 26 additions & 0 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,16 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
if (array_key_exists($cacheKey, $cachedTypes)) {
$cachedClosureData = $cachedTypes[$cacheKey];

$mustUseReturnValue = TrinaryLogic::createNo();
foreach ($node->attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attr) {
if ($attr->name->toLowerString() === 'nodiscard') {
$mustUseReturnValue = TrinaryLogic::createYes();
break;
}
}
}

return new ClosureType(
$parameters,
$cachedClosureData['returnType'],
Expand All @@ -1454,6 +1464,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
invalidateExpressions: $cachedClosureData['invalidateExpressions'],
usedVariables: $cachedClosureData['usedVariables'],
acceptsNamedArguments: TrinaryLogic::createYes(),
mustUseReturnValue: $mustUseReturnValue,
);
}
if (self::$resolveClosureTypeDepth >= 2) {
Expand Down Expand Up @@ -1656,6 +1667,16 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
];
$node->setAttribute('phpstanCachedTypes', $cachedTypes);

$mustUseReturnValue = TrinaryLogic::createNo();
foreach ($node->attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attr) {
if ($attr->name->toLowerString() === 'nodiscard') {
$mustUseReturnValue = TrinaryLogic::createYes();
break;
}
}
}

return new ClosureType(
$parameters,
$returnType,
Expand All @@ -1668,6 +1689,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
invalidateExpressions: $invalidateExpressions,
usedVariables: $usedVariables,
acceptsNamedArguments: TrinaryLogic::createYes(),
mustUseReturnValue: $mustUseReturnValue,
);
} elseif ($node instanceof New_) {
if ($node->class instanceof Name) {
Expand Down Expand Up @@ -2716,10 +2738,12 @@ private function createFirstClassCallable(
$throwPoints = [];
$impurePoints = [];
$acceptsNamedArguments = TrinaryLogic::createYes();
$mustUseReturnValue = TrinaryLogic::createMaybe();
if ($variant instanceof CallableParametersAcceptor) {
$throwPoints = $variant->getThrowPoints();
$impurePoints = $variant->getImpurePoints();
$acceptsNamedArguments = $variant->acceptsNamedArguments();
$mustUseReturnValue = $variant->mustUseReturnValue();
} elseif ($function !== null) {
$returnTypeForThrow = $variant->getReturnType();
$throwType = $function->getThrowType();
Expand All @@ -2745,6 +2769,7 @@ private function createFirstClassCallable(
}

$acceptsNamedArguments = $function->acceptsNamedArguments();
$mustUseReturnValue = $function->mustUseReturnValue();
}

$parameters = $variant->getParameters();
Expand All @@ -2759,6 +2784,7 @@ private function createFirstClassCallable(
$throwPoints,
$impurePoints,
acceptsNamedArguments: $acceptsNamedArguments,
mustUseReturnValue: $mustUseReturnValue,
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/PhpDoc/TypeNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1032,7 +1032,7 @@ function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadi
),
]);
} elseif ($mainType instanceof ClosureType) {
$closure = new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, templateTags: $templateTags, impurePoints: $mainType->getImpurePoints(), invalidateExpressions: $mainType->getInvalidateExpressions(), usedVariables: $mainType->getUsedVariables(), acceptsNamedArguments: $mainType->acceptsNamedArguments());
$closure = new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, templateTags: $templateTags, impurePoints: $mainType->getImpurePoints(), invalidateExpressions: $mainType->getInvalidateExpressions(), usedVariables: $mainType->getUsedVariables(), acceptsNamedArguments: $mainType->acceptsNamedArguments(), mustUseReturnValue: $mainType->mustUseReturnValue());
if ($closure->isPure()->yes() && $returnType->isVoid()->yes()) {
return new ErrorType();
}
Expand Down
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 mustUseReturnValue(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
7 changes: 7 additions & 0 deletions src/Reflection/Callables/CallableParametersAcceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,11 @@ public function getInvalidateExpressions(): array;
*/
public function getUsedVariables(): 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 mustUseReturnValue(): TrinaryLogic;

}
5 changes: 5 additions & 0 deletions src/Reflection/Callables/FunctionCallableVariant.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,9 @@ public function acceptsNamedArguments(): TrinaryLogic
return $this->function->acceptsNamedArguments();
}

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

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

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

}
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 mustUseReturnValue(): 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 mustUseReturnValue(): TrinaryLogic
{
// Align with the getAttributes() returning empty
return TrinaryLogic::createNo();
}

}
6 changes: 6 additions & 0 deletions src/Reflection/ExtendedCallableFunctionVariant.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function __construct(
private array $invalidateExpressions,
private array $usedVariables,
private TrinaryLogic $acceptsNamedArguments,
private TrinaryLogic $mustUseReturnValue,
)
{
parent::__construct(
Expand Down Expand Up @@ -80,4 +81,9 @@ public function acceptsNamedArguments(): TrinaryLogic
return $this->acceptsNamedArguments;
}

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

}
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 mustUseReturnValue(): 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 mustUseReturnValue(): TrinaryLogic;

}
1 change: 1 addition & 0 deletions src/Reflection/GenericParametersAcceptorResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc
$originalParametersAcceptor->getInvalidateExpressions(),
$originalParametersAcceptor->getUsedVariables(),
$originalParametersAcceptor->acceptsNamedArguments(),
$originalParametersAcceptor->mustUseReturnValue(),
);
}

Expand Down
5 changes: 5 additions & 0 deletions src/Reflection/InaccessibleMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,9 @@ public function acceptsNamedArguments(): TrinaryLogic
return $this->methodReflection->acceptsNamedArguments();
}

public function mustUseReturnValue(): TrinaryLogic
{
return TrinaryLogic::createMaybe();
}

}
11 changes: 11 additions & 0 deletions src/Reflection/Native/NativeFunctionReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use PHPStan\TrinaryLogic;
use PHPStan\Type\Type;
use function count;
use function strtolower;

final class NativeFunctionReflection implements FunctionReflection
{
Expand Down Expand Up @@ -150,4 +151,14 @@ public function getAttributes(): array
return $this->attributes;
}

public function mustUseReturnValue(): TrinaryLogic
{
foreach ($this->attributes as $attrib) {
if (strtolower($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 mustUseReturnValue(): TrinaryLogic
{
foreach ($this->attributes as $attrib) {
if (strtolower($attrib->getName()) === 'nodiscard') {
return TrinaryLogic::createYes();
}
}
return TrinaryLogic::createNo();
}

}
4 changes: 4 additions & 0 deletions src/Reflection/ParametersAcceptorSelector.php
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc
$invalidateExpressions = [];
$usedVariables = [];
$acceptsNamedArguments = TrinaryLogic::createNo();
$mustUseReturnValue = TrinaryLogic::createMaybe();

foreach ($acceptors as $acceptor) {
$returnTypes[] = $acceptor->getReturnType();
Expand All @@ -655,6 +656,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc
$invalidateExpressions = array_merge($invalidateExpressions, $acceptor->getInvalidateExpressions());
$usedVariables = array_merge($usedVariables, $acceptor->getUsedVariables());
$acceptsNamedArguments = $acceptsNamedArguments->or($acceptor->acceptsNamedArguments());
$mustUseReturnValue = $mustUseReturnValue->or($acceptor->mustUseReturnValue());
}
$isVariadic = $isVariadic || $acceptor->isVariadic();

Expand Down Expand Up @@ -761,6 +763,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc
$invalidateExpressions,
$usedVariables,
$acceptsNamedArguments,
$mustUseReturnValue,
);
}

Expand Down Expand Up @@ -797,6 +800,7 @@ private static function wrapAcceptor(ParametersAcceptor $acceptor): ExtendedPara
$acceptor->getInvalidateExpressions(),
$acceptor->getUsedVariables(),
$acceptor->acceptsNamedArguments(),
$acceptor->mustUseReturnValue(),
);
}

Expand Down
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 mustUseReturnValue(): TrinaryLogic
{
return $this->nativeMethodReflection->mustUseReturnValue();
}

}
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 mustUseReturnValue(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
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 mustUseReturnValue(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
11 changes: 11 additions & 0 deletions src/Reflection/Php/PhpFunctionFromParserNodeReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use function array_reverse;
use function is_array;
use function is_string;
use function strtolower;

/**
* @api
Expand Down Expand Up @@ -338,4 +339,14 @@ public function getAttributes(): array
return $this->attributes;
}

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

}
11 changes: 11 additions & 0 deletions src/Reflection/Php/PhpFunctionReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use function count;
use function is_array;
use function is_file;
use function strtolower;

#[GenerateFactory(interface: FunctionReflectionFactory::class)]
final class PhpFunctionReflection implements FunctionReflection
Expand Down Expand Up @@ -275,4 +276,14 @@ public function getAttributes(): array
return $this->attributes;
}

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

}
Loading
Loading