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
22 changes: 21 additions & 1 deletion src/Analyser/ExprHandler/AssignHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -3027,11 +3027,31 @@ private static function redundant(ExpressionResult $rhs, Expr $target, Expressio
$nativeType = $nativeType->getOffsetValueType($nativeOffset);
}
$values = $type->getFiniteTypes();
if (count($values) !== 1 || !$values[0]->equals($rhs->getType())) {
if (count($values) !== 1 || !$values[0]->equals($rhs->getType()) || self::containsFloatZero($values[0])) {
return null;
}
$nativeValues = $nativeType->getFiniteTypes();
return count($nativeValues) === 1 && $nativeValues[0]->equals($rhs->getNativeType()) ? $rhs->getType() : null;
}

/**
* 0.0 and -0.0 are identical (`===`) but distinguishable (`(string)`, `fdiv()`),
* and `$f === 0.0` narrows $f to 0.0 while it may still hold -0.0, so a type
* holding a float zero cannot prove that assigning it again changes nothing.
*/
private static function containsFloatZero(Type $type): bool
{
if (in_array(0.0, $type->getConstantScalarValues(), true)) {
return true;
}
foreach ($type->getConstantArrays() as $array) {
foreach ($array->getValueTypes() as $valueType) {
if (self::containsFloatZero($valueType)) {
return true;
}
}
}
return false;
}

}
2 changes: 1 addition & 1 deletion src/Turbo/TurboExtensionEnabler.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
final class TurboExtensionEnabler
{

public const EXPECTED_EXTENSION_VERSION = '63cd54c';
public const EXPECTED_EXTENSION_VERSION = '06be8c5';

private static bool $active = false;

Expand Down
8 changes: 8 additions & 0 deletions tests/PHPStan/Rules/DeadCode/UnusedVariableRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ public function testRedundantAssignment(): void
]);
}

public function testRedundantAssignmentOfSignedZero(): void
{
$this->analyse([__DIR__ . '/data/unused-variable-redundant-signed-zero.php'], [
['Value assigned to variable $x is never read before being overwritten.', 12],
['Variable $x is assigned value 1.5 but it already has that value.', 58],
]);
}

public function testByRefReturn(): void
{
$this->analyse([__DIR__ . '/data/unused-variable-by-ref-return.php'], [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php declare(strict_types = 1);

namespace UnusedVariableRedundantSignedZero;

function f(float $f): float {
if ($f === 0.0) {
$f = 0.0;
}
return $f;
}
function g(): float {
$x = -0.0;
$x = 0.0;
return $x;
}

/** @param mixed $v */
function sink($v): void
{
}

function negativeZeroNarrowing(float $f): void
{
if ($f === -0.0) {
$f = -0.0;
}
sink($f);
}

function looseZeroNarrowing(float $f): void
{
if ($f == 0) {
$f = 0.0;
}
sink($f);
}

function zeroInArray(): void
{
$a = [-0.0];
sink($a);
$a = [0.0];
sink($a);
}

function zeroAtOffset(): void
{
$a = ['k' => -0.0];
sink($a);
$a['k'] = 0.0;
sink($a);
}

function nonZeroFloatIsStillRedundant(): void
{
$x = 1.5;
sink($x);
$x = 1.5;
sink($x);
}

function zeroInNestedArray(): void
{
$a = [[-0.0]];
sink($a);
$a = [[0.0]];
sink($a);
}

function zeroAtNestedOffset(): void
{
$a = ['x' => ['y' => -0.0]];
sink($a);
$a['x']['y'] = 0.0;
sink($a);
}

function repeatedPositiveZeroIsNotReportedEither(): void
{
// the type of $x cannot tell this 0.0 from a narrowed zero of unknown sign
$x = 0.0;
sink($x);
$x = 0.0;
sink($x);
}

// https://3v4l.org/GiSbY
function h(): void {
$f = -0.0;
var_dump($f === 0.0); // bool(true)
echo $f, "\n"; // -0
if ($f === 0.0) {
$f = 0.0; // not redundant: turns -0.0 into 0.0
}
echo $f, "\n"; // 0
}
32 changes: 32 additions & 0 deletions turbo-ext/src/AssignHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4679,6 +4679,9 @@ class AssignHandler
bool equal;
AH_OK(ahEquals(zend_hash_index_find(Z_ARRVAL_P(values.raw()), 0), rhsType.raw(), equal));
if (!equal) return ahNull();
bool floatZero;
AH_OK(containsFloatZero(zend_hash_index_find(Z_ARRVAL_P(values.raw()), 0), floatZero));
if (floatZero) return ahNull();
}
AH_VAL(nativeValues, ahCall(nativeType.raw(), PT_LC("getfinitetypes"), 0, NULL));
if (zend_hash_num_elements(Z_ARRVAL_P(nativeValues.raw())) != 1) return ahNull();
Expand All @@ -4688,6 +4691,35 @@ class AssignHandler
if (!equal) return ahNull();
return pt_expression_result_get_type(rhs);
}

/* (twin 3042); false = pending exception */
static bool containsFloatZero(zval *type, bool &result)
{
result = false;
{
AH_VALB(scalarValues, ahTypeOp(type, PT_OP_GET_CONSTANT_SCALAR_VALUES, 0, NULL));
for (auto entry : zv::TableRef(Z_ARRVAL_P(scalarValues.raw()))) {
zval *value = entry.value().deref().raw();
// in_array(0.0, ..., true): -0.0 is identical to 0.0, NAN to nothing
if (Z_TYPE_P(value) == IS_DOUBLE && Z_DVAL_P(value) == 0.0) {
result = true;
return true;
}
}
}
AH_VALB(constantArrays, ahTypeOp(type, PT_OP_GET_CONSTANT_ARRAYS, 0, NULL));
for (auto arrayEntry : zv::TableRef(Z_ARRVAL_P(constantArrays.raw()))) {
AH_VALB(valueTypes, ahTypeOp(arrayEntry.value().deref().raw(), PT_OP_GET_VALUE_TYPES, 0, NULL));
for (auto entry : zv::TableRef(Z_ARRVAL_P(valueTypes.raw()))) {
zval *valueType = entry.value().deref().raw();
bool ok = true;
pt_engine_with_stack([&]() { ok = containsFloatZero(valueType, result); });
AH_OKB(ok);
if (result) return true;
}
}
return true;
}
};

} // namespace phpstanturbo
Expand Down
7 changes: 6 additions & 1 deletion turbo-ext/src/generated/AssignHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ inline constexpr char strings[] =
"array\0" /* 3789 */
"hasArrayReference\0" /* 3795 */
"rhs\0" /* 3813 */
"redundant"; /* 3817 */
"redundant\0" /* 3817 */
"type\0" /* 3827 */
"containsFloatZero"; /* 3832 */
inline constexpr reg::PackedArg args[] = {
reg::packed(0, 0, 25), /* __construct $templateArgumentObserver */
reg::packed(76, 0, 99), /* __construct $varAnnotationProcessor */
Expand Down Expand Up @@ -473,6 +475,8 @@ inline constexpr reg::PackedArg args[] = {
reg::packed(1975, 0, 1376), /* redundant $target */
reg::packed(1521, 0, 1529), /* redundant $storage */
reg::packed(1396, MAY_BE_NULL, 2038), /* redundant return */
reg::packed(3827, 0, 2038), /* containsFloatZero $type */
reg::packed(1396, MAY_BE_BOOL), /* containsFloatZero return */
};
using Sig = reg::Sig<strings, args>;
} // namespace sigtab
Expand Down Expand Up @@ -519,6 +523,7 @@ inline constexpr sigtab::Sig getLiteralArrayKeyType = { { 3654 /* getLiteralArra
inline constexpr sigtab::Sig getOriginalPropertyType = { { 3765 /* getOriginalPropertyType */, 3, 234, 3, 237, ZEND_ACC_PRIVATE } };
inline constexpr sigtab::Sig hasArrayReference = { { 3795 /* hasArrayReference */, 1, 238, 1, 239, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } };
inline constexpr sigtab::Sig redundant = { { 3817 /* redundant */, 3, 240, 3, 243, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } };
inline constexpr sigtab::Sig containsFloatZero = { { 3832 /* containsFloatZero */, 1, 244, 1, 245, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } };
} // namespace sig

} // namespace ptdecl::AssignHandler
Expand Down
Loading