Skip to content

Do not export what is declared inside a function or method body - #6699

Merged
ondrejmirtes merged 1 commit into
phpstan:2.3.xfrom
SanderMuller:export-define-in-function-bodies
Oct 7, 2026
Merged

ondrejmirtes merged 1 commit into
phpstan:2.3.xfrom
SanderMuller:export-define-in-function-bodies

Conversation

@SanderMuller

Copy link
Copy Markdown
Contributor

DependencyResolver exported a define() or a class declared inside a function or method body. The result cache restore reads the exported nodes again with ExportedNodeVisitor, which does not look inside function bodies. So after any edit of such a file, the restore saw a symbol disappear and re-analysed every file with errors.

f30b81c added this guard for a function declared inside another function. This change extends it to everything inside a function or method body. None of it is a symbol that other files can use. On 2.3.x, other files get constant.notFound for a constant defined with define() inside a function or a method, also inside an if (!defined()) check. They get class.notFound for a class declared inside a function.

I found this on wordpress-develop, which calls define() inside many functions, for example GETID3_TEMP_DIR in wp-admin/includes/media.php. At level 9 without a baseline, 2,333 files there have errors, so each such edit re-analysed about 2,290 files.

Verification:

  • New e2e test result-cache-define-in-function, modelled on result-cache-inner-function. One file calls define() inside a function and inside a method, and another file has an error. After a body-only edit of the first file, result-cache-info reports 1 file to analyse. The step fails on 2.3.x, where it reports 2. It also fails when the guard covers functions but not methods.

  • On wordpress-develop, I appended a newline to every analysed file except one file with errors. That makes the restore compare the exported nodes of all 3,215 changed files. With this change, none of them looked like a symbol appeared or disappeared. On 2.3.x, class-wp-filesystem-ftpext.php already did.

  • I replayed the last 300 trunk commits of wordpress-develop, each run with the cache of the commit before, on 2.3.0 and with this change. The files re-analysed in total went from 78,462 to 59,182, and the runs that re-analysed more than 1,000 files went from 34 to 26. Ten commits changed, and each of them edits a file with a define() inside a function. After the last commit, the incremental result matches a cold run, apart from 4 files with __() errors that also differ without this change.

  • make phpstan reports no errors, phpcs passes on DependencyResolver.php, and make tests passes (22,503 tests, 74 skipped).

Performance on wordpress-develop at level 9, from source without Turbo, on an M4 Pro. Base is e840006d7, the parent of this change. The cold and hot runs are at r63669. Each value is the median of 3 alternating rounds, with the range in brackets.

Base This PR
Cold 33.05 s (33.00 to 35.79), 303.0 s CPU 32.69 s (32.57 to 33.72), 304.1 s CPU
Hot 1.64 s (1.55 to 1.78) 1.60 s (1.54 to 1.90)
One-line body edit of media.php (r63670, with the cache from r63669) 24.17 s (23.73 to 25.60), 266.1 s CPU, 2,290 files 3.25 s (3.24 to 3.25), 2.7 s CPU, 1 file
resultCache.php 144.21 MB 144.15 MB

🤖 Generated with Claude Code

A define() or a class declared inside a function or method body is
not found from other files, and ExportedNodeVisitor does not look
inside function bodies. Exporting it during the analysis made every
edit of its file look like a symbol disappeared, so all files with
errors were re-analysed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ondrejmirtes
ondrejmirtes merged commit e187945 into phpstan:2.3.x Oct 7, 2026
911 of 923 checks passed
@ondrejmirtes

Copy link
Copy Markdown
Member

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.

2 participants