Skip to content

False positive staticMethod.alreadyNarrowedType for Strings::length() inside numeric comparison #209

Description

@janlanger

Bug report

StringsLengthTypeSpecifiyingExtension narrows the argument of Strings::length($s) to non-empty-string when the call is analyzed in a truthy/true context. That is useful for a direct truthiness check such as:

if (Strings::length($s)) {
}

However, the same narrowing currently also leads to an alreadyNarrowedType diagnostic when the call is used as the numeric operand of a comparison, for example a max-length guard:

if ($s !== '' && Strings::length($s) < 100) {
}

In this expression, the condition being tested is not "is Strings::length($s) truthy?" but "is the numeric length less than 100?". Even when $s is already known to be non-empty-string, the comparison result is not constant; it still depends on the runtime length.

Code snippet that reproduces the problem

declare(strict_types=1);

use Nette\Utils\Strings;

function withoutPriorCheck(string $body): void
{
    if (Strings::length($body) < 100) { // no error
        echo 'a';
    }
}

function withPriorCheck(string $body): void
{
    if ($body !== '' && Strings::length($body) < 100) { // false positive
        echo 'a';
    }
}

Expected output

No error for withPriorCheck(). The prior $body !== '' guard only makes the result of Strings::length($body) non-zero; it does not make Strings::length($body) < 100 always true.

Actual output

Call to static method Nette\Utils\Strings::length() with non-empty-string will always evaluate to true.
staticMethod.alreadyNarrowedType

The diagnostic appears to treat the Strings::length($body) call as if its truthiness were the relevant check, but in this code the call's numeric result is used in a < 100 comparison.

Versions

  • phpstan/phpstan: 2.2.3
  • phpstan/phpstan-nette: 2.0.11
  • PHPStan level: 8

Related pattern: phpstan/phpstan-webmozart-assert#191

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions