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
5 changes: 5 additions & 0 deletions src/Analyser/ExprHandler/AssignHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -3096,6 +3096,11 @@ private static function redundant(ExpressionResult $rhs, Expr $target, Expressio
if (count($values) !== 1 || !$values[0]->equals($rhs->getType())) {
return 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
if (in_array(0.0, $values[0]->getConstantScalarValues(), true)) {
return null;
}
$nativeValues = $nativeType->getFiniteTypes();
return count($nativeValues) === 1 && $nativeValues[0]->equals($rhs->getNativeType()) ? $rhs->getType() : null;
}
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 @@ -206,6 +206,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.', 50],
]);
}

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,80 @@
<?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 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 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
}
6 changes: 6 additions & 0 deletions turbo-ext/src/AssignHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4810,6 +4810,12 @@ class AssignHandler
bool equal;
AH_OK(ahEquals(zend_hash_index_find(Z_ARRVAL_P(values.raw()), 0), rhsType.raw(), equal));
if (!equal) return ahNull();
// in_array(0.0, ..., true): matches -0.0 too, never NAN; the sign of a zero is observable
AH_VAL(scalarValues, ahTypeOp(zend_hash_index_find(Z_ARRVAL_P(values.raw()), 0), PT_OP_GET_CONSTANT_SCALAR_VALUES, 0, NULL));
for (auto entry : zv::TableRef(Z_ARRVAL_P(scalarValues.raw()))) {
zval *value = entry.value().deref().raw();
if (Z_TYPE_P(value) == IS_DOUBLE && Z_DVAL_P(value) == 0.0) 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 Down
Loading