Skip to content

Fix phpstan/phpstan#12597: 'Variable might not be undefined' in if-statement after in_array check.#5384

Open
phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-il08wg5
Open

Fix phpstan/phpstan#12597: 'Variable might not be undefined' in if-statement after in_array check.#5384
phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-il08wg5

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

Fixes a false positive "Variable $message might not be defined" when a variable is assigned inside an in_array() check and later used inside a more specific condition (e.g., $type === self::TYPE_1) that implies the in_array() check was true.

Changes

  • Added a second pass for conditional expression matching in src/Analyser/MutatingScope.php (~line 3232) that uses isSuperTypeOf() instead of equals() for type comparison
  • The second pass is restricted to: targets that are Variables, have Maybe certainty (not-yet-definitely-defined), and are not already matched by the exact-match first pass
  • Added regression test data in tests/PHPStan/Rules/Variables/data/bug-12597.php
  • Added test method testBug12597() in tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Root cause

During scope merging after an if-without-else, PHPStan correctly creates conditional expressions like "if $type is 1|2, then $message is defined with Yes certainty." However, when later processing $type === 1, the specified type for $type is 1, which is a strict subtype of 1|2. The existing condition matching used ExpressionTypeHolder::equals() which requires exact type equality, so the conditional expression never matched. The fix adds a second pass after exact matching that uses isSuperTypeOf() for subtype checking, but only for variable targets with Maybe certainty to avoid interfering with type narrowing of already-defined expressions.

Test

The regression test reproduces the exact scenario from the issue: a variable assigned inside in_array($type, [TYPE_1, TYPE_2], true) and used inside $type === TYPE_1. The test expects no errors.

Fixes phpstan/phpstan#12597

…ay check

- Added subtype matching for conditional expressions in MutatingScope::processSpecifiedTypes
- After exact-match conditional resolution, a second pass checks if specified types
  are subtypes of guard types for maybe-defined variables
- Limited to Variable targets with Maybe certainty to avoid interfering with type narrowing
- New regression test in tests/PHPStan/Rules/Variables/data/bug-12597.php
Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add non regression test

class HelloWorld
{
	public function test(mixed $type): void
	{
		if (is_int($type) || is_string($type)) {
			$message = 'Hello!';
		}

		if (is_int($type)) {
			$this->message($message);
		}
	}

	public function message(string $message): void {}
}

class Foo {}
class Bar {}

class HelloWorld2
{
	public function test(mixed $type): void
	{
		if (is_int($type) || is_object($type)) {
			$message = 'Hello!';
		}

		if (is_int($type)) {
			$this->message($message);
		}
	}

	public function test2(mixed $type): void
	{
		if ($type instanceof Foo || $type instanceof Bar) {
			$message = 'Hello!';
		}

		if ($type instanceof Foo) {
			$this->message($message);
		}
	}

	public function message(string $message): void
	{
	}
}

in DefinedVariableRuleTest.php‎

And update the implementation if necessary

…t/instanceof

Add additional test scenarios per review feedback to ensure the
conditional variable definition fix works with type-narrowing functions
(is_int, is_string, is_object) and instanceof checks, not just in_array.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Both background tasks have completed successfully. Everything was already committed and pushed — the review feedback has been fully addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants