Skip to content

Fix global constant discovery after class name tokens - #6756

Merged
staabm merged 6 commits into
phpstan:2.3.xfrom
LubuSeb:codex/fix-class-token-symbol-scan
Oct 27, 2026
Merged

staabm merged 6 commits into
phpstan:2.3.xfrom
LubuSeb:codex/fix-class-token-symbol-scan

Conversation

@LubuSeb

@LubuSeb LubuSeb commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

When a global constant follows stdClass::class inside a function, the directory symbol scan can mistake the class name token for a declaration and lose the constant. The same can happen with identifiers beginning with class. This change checks the keyword boundary before entering type scope in both the PHP cleaner and the Turbo scanner.

The regression covers ::class, className(), and a real anonymous class. The native differential corpora also include the trigger. Thanks to @Victor-emil for identifying the cause and suggesting the boundary check in phpstan/phpstan#15482.

Validation: the focused PHPUnit test passes (3 tests); the source-locator suite passes (78 tests, 159 assertions, 1 skip); and the issue's two-file example analyzes without errors using this branch. PHP syntax and git diff --check pass. I could not run the native differential scripts locally because the Turbo extension is not installed.

Fixes phpstan/phpstan#15482

@LubuSeb

LubuSeb commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

CI note: the result-cache-parse-error E2E check is failing on this PR, but the same check failed on the untouched 2.3.x base commit before this PR. Its final assertion expects a circular-reference error and gets exit 0. I have not changed that E2E case or the relevant result-cache code.

The PHP 7.4 call syntax and Turbo version-pin failures from my first push have been corrected in the current head.

@LubuSeb
LubuSeb force-pushed the codex/fix-class-token-symbol-scan branch 3 times, most recently from 4e6c817 to 1abd49e Compare October 23, 2026 19:30
@staabm
staabm force-pushed the codex/fix-class-token-symbol-scan branch 2 times, most recently from dd7bc45 to 083179b Compare October 24, 2026 07:05
@staabm

staabm commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

//cc @SanderMuller

@staabm

staabm commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

the issue phpstan/phpstan#15482 talks about unexpected rule errors. we should have a test which asserts these unexpected rule errors no longer appear

@SanderMuller

Copy link
Copy Markdown
Contributor

I ran the reporter's three files from phpstan/phpstan#15482 as a project. 2.3.x reports Constant BAR not found., and this branch reports no errors. The branch also fixes the same loss after enum_exists(), is_subclass_of() and $o?->class.

Two shapes still lose the constant on this branch:

function foo(): string
{
    return bar(class: 'x');
}

const BAR = 'bar';

and return stdClass:: class; (with a space). The second idea in the issue fixes both: when $typeLevel drops to 0 or below, set it back to 0 and leave the type.

if ($typeLevel <= 0) {
	$typeLevel = 0;
	$inType = false;
}

With that change on top of this branch, all 8 shapes I tried find BAR. On 17,955 PHP files from this repository and its vendor, it finds the same symbols as this branch. It does not help at the top level of a file, outside any braces, because no closing brace follows there. I did not run the full gate on it, and SymbolScan.h would need the same change.

About a test for the rule errors: a ConstantRule test cannot show this bug. With both files analysed, it gives the same result on 2.3.x and on this branch. An e2e project with the reporter's three files fails on 2.3.x and passes here. So e2e/bug-15482 with a step that runs ../../bin/phpstan analyze would cover it.

What I checked:

  • The new SymbolFinderInFilesTest case fails without the PhpFileCleaner change.
  • On those 17,955 files, the symbols found change for one file only: the new fixture.
  • With the extension built from this branch, php-file-cleaner-corpus.php and symbol-finder-corpus.php print ALL OK. make tests passes with and without the extension (22,639 tests). Self-analysis passes, and phpcs passes on the changed files that make cs checks.
  • Performance: the PHP cleaner over the 17,955 files was 0.5% to 2% slower than on 2.3.x, in two sets of 3 runs each. That is about the noise level. I did not measure the native cleaner.
  • The red tempest and phpstan-laravel integration jobs are also red on Do not delete a container that another process has not journaled yet #6797 from the same day, so they do not come from this change.

@LubuSeb

LubuSeb commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for checking this. I added an E2E case using the reporter's three-file layout and covered the named class argument and spaced class constant variants in the scanner fixture. Both the PHP and native scanners now reset the type state when its depth reaches zero. The E2E analysis and targeted symbol-finder tests pass locally. The update is in 5988ead.

@SanderMuller SanderMuller left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I checked the update on d2a30e4, with and without the Turbo extension.

  • With the base scanner, the reporter's files from phpstan/phpstan#15482, bar(class: 'x') and stdClass:: class all report Constant BAR not found. This branch reports no errors for them. The PHP cleaner and a native build give the same result.
  • SymbolFinderInFilesTest fails with the base PhpFileCleaner and with a native build that has the base SymbolScan.h. It passes with the branch on both paths.
  • Over-reach: a class, interface, enum, function and constant declared after Foo:: class, $o?->class and new class {} are still found. I also ran the base and branch finders over every PHP file in the repository and vendor/ (18369 files). Only class-name-before-global-constant.php and e2e/bug-15482/src/z_functions.php get different symbols.
  • The native corpora (php-file-cleaner-corpus, symbol-finder-corpus) and smoke.php print ALL OK with the branch build.
  • make tests (22639 tests), make phpstan, phpcs on the changed files and e2e/bug-15482 pass.
  • Performance: no slowdown. I timed one scan of the same 18369 files, 7 interleaved rounds. The PHP finder takes a median 3.26 s with the branch and 3.46 s with base. The native finder takes 2.70 s in both.

CI has not run on d2a30e4 yet. On 5988ead, Lint (7.4) failed on the new fixture, which d2a30e4 excludes. The calebdw/phpstan-laravel integration failed with five No error with identifier phpstanApi.trait is reported. That comes from 486821c in the base, which marks LateResolvableTypeTrait as @api and makes the trait rule respect it. calebdw/phpstan-laravel removed those five ignores on 2026-10-25. The branch finds the same symbols as base in all 232 files of that project's src.

@staabm

staabm commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

(needs rebase)

Comment on lines +100 to +101
if ($typeLevel <= 0) {
$typeLevel = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is this just noise or a required change?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It is required. I ran the PR's fixture with the PR's PhpFileCleaner and with the same file minus the clamp. Without it, AFTER_NAMED_CLASS_ARGUMENT and AFTER_SPACED_CLASS_CONSTANT are lost again. In symbolFinderNamedArgument(class: 'named argument') and \stdClass:: class, the byte before class is ( or a space, so the boundary check still enters a type. No { follows, so the function's closing } takes $typeLevel to -1, and with === 0 the cleaner never leaves the type.

@staabm
staabm force-pushed the codex/fix-class-token-symbol-scan branch from d2a30e4 to c654fb3 Compare October 27, 2026 06:00
Comment on lines +267 to +269
- script: |
cd e2e/bug-15482
../../bin/phpstan analyse

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

on my macOS machine the added reproducer does not emit the errors of the origin reproducer, without the fix beeing applied

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

for some reason I can reproduce the initial errors now, after I renamed the tests

@staabm
staabm force-pushed the codex/fix-class-token-symbol-scan branch from 013b685 to 29f4de3 Compare October 27, 2026 06:19
@staabm
staabm merged commit b43bdcb into phpstan:2.3.x Oct 27, 2026
1029 of 1030 checks passed
@staabm

staabm commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

thank you!

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.

Global constant "not found" when an earlier function body in its file uses ::class

3 participants