Skip to content

Invalidate property types after dynamic method calls#5679

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

Invalidate property types after dynamic method calls#5679
phpstan-bot wants to merge 2 commits into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-vfj4d1z

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

@phpstan-bot phpstan-bot commented May 16, 2026

Summary

Fixes phpstan/phpstan#3831

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.

Lot of these changes are wrong.

The issue is with the snippet

class Template {
	
	public $footer = [];

	public function render() : string {
		$content = '';
		$this->footer = [];
		
		// START TEMPLATE COMPILER
		$this->{'compileSection'}(); // dynamic code
		// END TEMPLATE COMPILER
		
		if (count($this->footer) > 0) {
			$content = str_replace('some', 'thing', $content);
		}
		return $content;
	}

	private function compileSection(): void {
		$this->footer[] = 'section-name';
	}
	
}

$this->compileSection() invalidate correctly but
$this->{'compileSection'} or $this->{$someThing}() does not invalidate

@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Looking at bug-5501 and bug-6473, the test expectation changes in the original PR are correct. In bug-5501, $this->prop2 = 5 is assigned, then $this->isBroken() is called (which returns bool, so hasSideEffects()->maybe()), and the new behavior correctly widens 5 to int. Similarly in bug-6473, $p->visited = true followed by $p->getNeighbours() (returns Point[], hasSideEffects()->maybe()) correctly widens true to bool.

The reviewer's feedback was specifically about the dynamic method call gap. The fix I made addresses that by adding full invalidation for unknown/dynamic method calls in the else branch. This is actually more aggressive than the maybe() branch (which only invalidates constant scalar property types) — for unknown methods we do full invalidation since we can't know anything about the method's behavior.

The changes are committed and pushed. Here's a summary:

Fix: Added invalidateExpression call in MethodCallHandler.php for the case when $methodReflection is null (dynamic method calls like $this->{'compileSection'}() or $this->{$variable}()). Since the method can't be resolved, we treat it as having certain side effects and fully invalidate the callee expression — matching the behavior for known methods with hasSideEffects()->yes().

Tests: Added test cases for both the NSRT test (bug-3831.php) and the Comparison rule test (data/bug-3831.php) covering the reviewer's exact example with $this->{'compileSection'}() and $this->{$method}().

Verification: All 12077 tests pass, make phpstan reports 0 errors, make cs-fix has no violations.

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.

Revert the first commit and only keep the second one, about invalidating after dynamic call

Dynamic method calls like `$this->{'compileSection'}()` or
`$this->{$variable}()` could not be resolved to a method reflection,
so the else branch (unknown method) did not invalidate the callee
expression. Since we cannot determine what the method does, treat it
like a method with certain side effects and fully invalidate.

Closes phpstan/phpstan#3831

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot phpstan-bot force-pushed the create-pull-request/patch-vfj4d1z branch from d2c6680 to afce9d0 Compare May 16, 2026 13:42
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Done. I reverted the first commit (constant scalar property invalidation after maybe-impure calls) and kept only the dynamic method call invalidation fix. The branch now has a single clean commit that:

  1. Adds invalidateExpression in the else branch of MethodCallHandler.php for when $methodReflection is null (dynamic calls like $this->{'method'}() or $this->{$var}())
  2. Includes test coverage for the dynamic call case only — no changes to bug-5501, bug-6473, impure-method, bug-12393 test expectations
  3. All 12076 tests pass, make phpstan reports 0 errors, no CS violations

@VincentLanglet VincentLanglet changed the title Invalidate constant scalar property types after maybe-impure method calls Invalidate property types after dynamic method calls May 16, 2026
@VincentLanglet VincentLanglet self-assigned this May 16, 2026
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.

load nsrt file in the RuleTest to avoid duplicates fixtures files

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

Done. I merged the rule test fixture into the existing NSRT file (adding the Template and TemplateDynamicVar classes with the count() > 0 comparisons), pointed the rule test to __DIR__ . '/../../Analyser/nsrt/bug-3831.php', and deleted the duplicate tests/PHPStan/Rules/Comparison/data/bug-3831.php. All tests pass and make phpstan is clean.

@VincentLanglet VincentLanglet requested a review from staabm May 16, 2026 19:06
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