Skip to content

False report of unused use #9478

@mimmi20

Description

@mimmi20

Bug report

I have this Unittest:

<?php
/**
 * This file is part of the mimmi20/laminasviewrenderer-flash-message package.
 *
 * Copyright (c) 2023, Thomas Mueller <mimmi20@live.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types = 1);

namespace Mimmi20\LaminasView\FlashMessage\View\Helper;

use ArrayAccess;
use Laminas\Mvc\Plugin\FlashMessenger\FlashMessenger as LaminasFlashMessenger;
use Laminas\View\Exception\RuntimeException;
use Laminas\View\Model\ModelInterface;
use Laminas\View\Model\ViewModel;
use Laminas\View\Renderer\RendererInterface;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\TestCase;

use function str_repeat;

final class FlashMessenger2Test extends TestCase
{
    /**
     * @throws Exception
     * @throws RuntimeException
     *
     * @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
     * @phpcs:disable SlevomatCodingStandard.Functions.FunctionLength.FunctionLength
     */
    public function testRender(): void
    {
        $flashMessenger = $this->createMock(LaminasFlashMessenger::class);
        $flashMessenger->expects(self::once())
            ->method('getErrorMessages')
            ->willReturn(['error-message']);
        $flashMessenger->expects(self::once())
            ->method('getCurrentErrorMessages')
            ->willReturn(['error-message', 'current-error-message']);
        $flashMessenger->expects(self::once())
            ->method('getSuccessMessages')
            ->willReturn(['success-message']);
        $flashMessenger->expects(self::once())
            ->method('getCurrentSuccessMessages')
            ->willReturn(['success-message', 'current-success-message']);
        $flashMessenger->expects(self::once())
            ->method('getWarningMessages')
            ->willReturn(['warning-message']);
        $flashMessenger->expects(self::once())
            ->method('getCurrentWarningMessages')
            ->willReturn(['warning-message', 'current-warning-message']);
        $flashMessenger->expects(self::once())
            ->method('getInfoMessages')
            ->willReturn(['info-message']);
        $flashMessenger->expects(self::once())
            ->method('getCurrentInfoMessages')
            ->willReturn(['info-message', 'current-info-message']);
        $flashMessenger->expects(self::once())
            ->method('getMessages')
            ->willReturn(['default-message']);
        $flashMessenger->expects(self::once())
            ->method('getCurrentMessages')
            ->willReturn(['default-message', 'current-default-message']);
        $flashMessenger->expects(self::once())
            ->method('clearMessagesFromContainer')
            ->willReturn(true);
        $flashMessenger->expects(self::once())
            ->method('clearCurrentMessagesFromContainer')
            ->willReturn(true);

        $object = new FlashMessenger($flashMessenger);

        $view    = $this->createMock(RendererInterface::class);
        $matcher = self::exactly(10);
        $view->expects($matcher)
            ->method('render')
            ->willReturnCallback(
                static function (ModelInterface | string $nameOrModel, array | ArrayAccess | null $values = null) use ($matcher): string {
                    self::assertInstanceOf(ViewModel::class, $nameOrModel);

                    match ($matcher->numberOfInvocations()) {
                        1, 2 => self::assertSame('danger', $nameOrModel->getVariable('alertLevel')),
                        3, 4 => self::assertSame('warning', $nameOrModel->getVariable('alertLevel')),
                        5, 6 => self::assertSame('info', $nameOrModel->getVariable('alertLevel')),
                        7, 8 => self::assertSame('success', $nameOrModel->getVariable('alertLevel')),
                        9, 10 => self::assertSame('primary', $nameOrModel->getVariable('alertLevel')),
                        default => self::assertSame('', $nameOrModel->getVariable('alertLevel')),
                    };

                    match ($matcher->numberOfInvocations()) {
                        1 => self::assertSame(
                            'error-message',
                            $nameOrModel->getVariable('alertMessage'),
                        ),
                        2 => self::assertSame(
                            'current-error-message',
                            $nameOrModel->getVariable('alertMessage'),
                        ),
                        3 => self::assertSame(
                            'warning-message',
                            $nameOrModel->getVariable('alertMessage'),
                        ),
                        4 => self::assertSame(
                            'current-warning-message',
                            $nameOrModel->getVariable('alertMessage'),
                        ),
                        5 => self::assertSame(
                            'info-message',
                            $nameOrModel->getVariable('alertMessage'),
                        ),
                        6 => self::assertSame(
                            'current-info-message',
                            $nameOrModel->getVariable('alertMessage'),
                        ),
                        7 => self::assertSame(
                            'success-message',
                            $nameOrModel->getVariable('alertMessage'),
                        ),
                        8 => self::assertSame(
                            'current-success-message',
                            $nameOrModel->getVariable('alertMessage'),
                        ),
                        9 => self::assertSame(
                            'default-message',
                            $nameOrModel->getVariable('alertMessage'),
                        ),
                        10 => self::assertSame(
                            'current-default-message',
                            $nameOrModel->getVariable('alertMessage'),
                        ),
                        default => self::assertSame('', $nameOrModel->getVariable('alertMessage')),
                    };

                    self::assertSame('widget/bootstrap-alert', $nameOrModel->getTemplate());
                    self::assertNull($values);

                    return 'test-render';
                },
            );

        $object->setView($view);

        self::assertSame(str_repeat('test-render', 10), $object->render());
    }
}

PHPStan 1.10.20 reports this:

 ------ ------------------------------------------------
  Line   tests\View\Helper\FlashMessenger2Test.php
 ------ ------------------------------------------------
  82     Anonymous function has an unused use $matcher.
 ------ ------------------------------------------------

But the variable $matcher is used on lines 85 and 94.

This is my phpstan.neon file:

parameters:
  level: max

  parallel:
    maximumNumberOfProcesses: 1
    processTimeout: 200.0

  paths:
    - src
    - tests

  scanFiles:
    - %currentWorkingDirectory%/vendor/autoload.php
    - %currentWorkingDirectory%/vendor/squizlabs/php_codesniffer/autoload.php
    - %currentWorkingDirectory%/vendor/squizlabs/php_codesniffer/src/Util/Tokens.php

  # reports occurrences of type-checking functions always evaluated to true
  checkAlwaysTrueCheckTypeFunctionCall: false

  # reports instanceof occurrences always evaluated to true
  checkAlwaysTrueInstanceof: true

  # reports === and !== occurrences always evaluated to true
  checkAlwaysTrueStrictComparison: true

  # enable stricter analysis of benevolent union types
  checkBenevolentUnionTypes: true

  # reports use of dynamic properties as undefined
  checkDynamicProperties: true

  # reports code paths with missing return statement in functions and methods with @return mixed PHPDoc
  checkExplicitMixedMissingReturn: true

  # reports function and method calls with incorrect name case
  checkFunctionNameCase: true

  # it requires type variables always be specified in typehints
  checkGenericClassInNonGenericObjectType: true

  # be strict about values with an unspecified (implicit mixed) type
  checkImplicitMixed: true

  # reports references to built-in classes with incorrect name case
  checkInternalClassCaseSensitivity: true

  # require that callable signatures are specified
  checkMissingCallableSignature: true

  # checks for missing typehints in iterables
  checkMissingIterableValueType: true

  # reports return typehints that could be narrowed down because some of the listed types are never returned
  checkTooWideReturnTypesInProtectedAndPublicMethods: true

  # reports properties with native types that weren’t initialized in the class constructor
  checkUninitializedProperties: true

  # doesn’t require typehints for properties if the types can be inferred from constructor injection
  inferPrivatePropertyTypeFromConstructor: false

  # prevents reading key and value variables set in foreach when iterating over a non-empty array
  polluteScopeWithAlwaysIterableForeach: false

  # prevents reading variables set in for loop initial statement and while loop condition after the loop
  polluteScopeWithLoopInitialAssignments: false

  # report always true last condition in a series of elseif branches and match expression arms
  reportAlwaysTrueInLastCondition: true

  # reports violations of parameter type contravariance and return type covariance
  reportMaybesInMethodSignatures: true

  # reports violations of property type invariance
  reportMaybesInPropertyPhpDocTypes: true

  # reports violations of parameter type contravariance and return type covariance in static methods
  reportStaticMethodSignatures: true

  #
  reportWrongPhpDocTypeInVarTag: true

  # differentiate between PHPDoc and native types (if false)
  treatPhpDocTypesAsCertain: false

  tipsOfTheDay: false

  exceptions:
    implicitThrows: false
    checkedExceptionRegexes:
      - '#Exception#'
      - '#Throwable#'
    check:
      missingCheckedExceptionInThrows: true
      tooWideThrowType: true

#  ignoreErrors:
#    - '~MockObject~'

The error is reported with PHPStan 1.10.20, but not with 1.10.19.

Code snippet that reproduces the problem

I was not able to reproduce the error with the playground, maybe because of the many dependencies to PHPUnit and the Laminas Framework.

Expected output

no error

Did PHPStan help you today? Did it make you happy in any way?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions