Repository navigation
fix(autoload): give autoloaded files distinct span identities - #2038
Merged
Merged
Conversation
Autoloaded class files were lexed with the entry file's source identity 0, and the resolver of their includes rewound the identity counter, so a statement at the same line and column of two files was one key in every span-keyed checker map. A readonly write guard of one class fired on a plain write of another (#1445), and same-position retypes in two class files were refused as ambiguous. Each autoloaded file now takes a fresh identity, and its includes resolve through a continuing resolver entry; only the root resolution rewinds the counter. The approach comes from #1921 by @JGamboa.
…iagnostics Checker and lowering diagnostics carry only a span, so an error inside an autoloaded class file printed a bare error[line:col] that read as a position in the entry file. Each loader now registers the path behind the source identity it hands out (Span::register_source_path), and the error and warning printers fall back to that path when the diagnostic names no file. Entry-file diagnostics keep the error[line:col] form.
nahime0
marked this pull request as ready for review
October 24, 2026 07:29
Contributor
[Medium impact] The PR appears safe to merge; the previous filename issue is fixed and no new actionable issues were found. SummaryThis PR gives included and autoloaded files distinct span identities and records their paths for diagnostics.
Reviews (2) · Last reviewed commit: "fix(diagnostics): let the span's source ..." · Reviewed by Greptile |
The pipeline tags every lowering, export and --emit-ir refusal with the entry filename, so a refusal inside an included file printed that file's line and column under main.php. The printer now asks the span's registered source first and keeps the tag only for spans with no registered source (every entry-file span), so those diagnostics are unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Autoloaded class files were lexed with the entry file's source identity 0, and the resolver of their own includes rewound the identity counter (
Span::reset_source_ids). Two physically different files therefore produced equal spans for statements at the same line and column, and every span-keyed checker result leaked between them:throw_access_sites,builtin_call_types,loop_storage_types, the call-type memo, the local binding decisions.Concrete miscompile (the Ivory repro, PSR-4
App\->src/):src/Fixed.lfc:9:9initializes a readonly property,src/Counter.lfc:9:9is a plain$this->total = ...write.Counter::add()gotFixed's readonly guard and the program died withinstead of printing
2. Ivory hit this as soon as it had a second class with a readonly property assigned in a constructor (6 unrelated tests broke). The same collision also made two autoloaded files with a retype at the same position fail withCannot re-bind $x here, and checker errors inside an autoloaded file printed a bareerror[7:16]that read as a position in the entry file.Fix
The span identity approach comes from #1921 by @JGamboa; this PR ports that part as a focused change.
fix(autoload):autoload::load_autoloaded_filelexes each file withSpan::fresh_source_id()and resolves its includes through a newresolver::resolve_collecting_includes_continuing, which does not rewind the counter.Span::reset_source_idsmoved from the shared resolver body to the root entryresolve_collecting_includes_with_defines, so it runs once per compile. Every file a compile loads now has its own identity, so equal coordinates in two files are distinct map keys.fix(diagnostics): each loader registers the path behind the identity it hands out (Span::register_source_path, per thread like the counter), andprint_error/print_warningnameSpan::source_path()first. The span wins over a file tag because the pipeline tags every lowering, export and--emit-irrefusal with the entry filename (found by Greptile); the tag is kept only for spans with no registered source, which is every entry-file span. A checker or lowering error inside an included or autoloaded file now printserror[<path>:line:col].Difference from #1921
#1921 also registers the entry file, so every positioned diagnostic becomes
error[file:line:col]. That is a CLI-wide format change beyond #1445, so I left it out: the entry file is never registered and its diagnostics keeperror[line:col]. It stays compatible with landing that change later. The other parts of #1921 (autoload prelude probe, monotonic method variadics, checker cost indexes, schema error spans) are untouched here and remain #1921's.Tests
tests/codegen/spl/autoload_span_identity.rs(new):2(fails on main with the readonly error above);0s/1t; main:Cannot re-bind $x here, the autoload part of Give Span a file identity so cross-file same-position collisions stop being compile errors #778);--checkon an autoloaded file with an undefined class namessrc/Broken.lfc:7:16(main:error[7:16]).tests/codegen/include_paths.rs: a checker error inside arequired file nameslib.php:3:12(main:error[3:12]), and a lowering refusal inside arequired file (--emit-ir) nameslib.php:6:5(main:error[main.php:6:5]).span::tests::registered_source_paths_name_the_file_of_their_spans,errors::report::tests::diagnostic_file_falls_back_to_the_span_source.I reproduced the failing behaviour of the integration fixtures on main with an existing build; the new code itself is validated by CI only (no local cargo runs on this shared machine).
Not addressed
Throwable::getFile(), thein <file>:<line>suffix of the uncaught report) is still the per-module entry script path for code that came from an included or autoloaded file (lower_throwable_get_file). With this fix the bogus readonly error no longer happens, but a genuine error raised in a class file still namesmain.lfc. That needs a per-throwable file slot in the runtime and is left for a follow-up.Cannot re-bindmessage and itscli-reference.mdsection still say a position carries no file; after this PR neither of the causes they list (two files, a file required twice) can trigger it. Left for a follow-up so the message and docs change together.Fixes #1445. Overlaps #1921.