Hot reload#1
Closed
NatElkins wants to merge 443 commits into
Closed
Conversation
Complete closure-matrix item dotnet#8 by introducing an SRM shadow writer and parity gate over the existing delta row model. - Added DeltaMetadataSrmWriter and wired compare/output flags in FSharpDeltaMetadataWriter. - Enabled SRM compare mode in check-hotreload-metadata-parity.sh. - Hardened SRM writer to allocate heap handles via MetadataBuilder (no baseline offset reuse). - Updated closure matrix status/evidence and validation snapshot. Validation: - ./.dotnet/dotnet build FSharp.sln -c Debug -v minimal - ./.dotnet/dotnet test tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj -c Debug --no-build --filter FullyQualifiedName~HotReload -v minimal (328 passed) - ./.dotnet/dotnet test tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj -c Debug --no-build --filter FullyQualifiedName~HotReload -v minimal (110 passed) - ./tests/scripts/check-hotreload-metadata-parity.sh (9 + 28 passed) Roslyn reference commit/date: 511a87e0653 (2025-10-29).
Owner
Author
|
Superseded: this described the original |
NatElkins
added a commit
that referenced
this pull request
Jul 1, 2026
…naming The delta emitter's fail-closed guard for synthesized types with no baseline counterpart only recognized the stable '@hotreload' naming scheme. External and flag-off builds name closures with the fresh-fsc '@<line>' scheme (for example 'Pipe #1 stage #3 at line 16@16' from debug pipe desugaring), so an added pipe-stage closure skipped the guard, fell through to a baseline token-map lookup known to be invalid, and crashed EmitDelta with an ilwrite InternalError surfaced as DeltaEmissionFailed. dotnet-watch treats UnsupportedEdit as a graceful rebuild-and-restart but DeltaEmissionFailed as an error, so the crash broke the watch experience for a common edit. Broaden the guard to IsCompilerGeneratedName, covering both naming schemes. Legitimately added lambdas are unaffected: generation-suffixed added closures resolve through the earlier added-TypeDef path, and added user types resolve through addedUserTypeNames before the guard. Verified with a full external-build repro (multi-module app, pipe stage inserted before existing stages): EmitDelta now returns the FSHRDL099 closure-chain rude edit instead of crashing. HotReload component suite 229 passed, service suite 413 passed. A new disk-started flag-off contract test pins the rejection shape for this topology; with a flag-on baseline the same insertion remains supported via CDI-reconstructed occurrence keys.
NatElkins
added a commit
that referenced
this pull request
Jul 2, 2026
Two synthesized-closure families could not be aligned across compiles after a
line-adding edit, so such edits always fell back to restart on closure-dense
real-world files (reproduced on Giraffe's endpoint list, failing identically on
the external and in-process paths):
- debug-pipe closures embed the source line in the basic name itself
('Pipe #1 stage #2 at line 28'), so a line shift changes the name and the
fresh type has no baseline counterpart;
- value-binding list closures use sequential ordinals (endpoints@hotreload-N)
allocated in encounter order, so shifts make one fresh name match several
baseline candidates.
Add a last-resort positional pairing to the delta emitter's synthesized-type
match ladder, consulted only after exact and path-normalized matching fail or
tie. Groups are keyed by enclosing type plus a line-normalized basic name; the
normalizer recognizes only the known generated forms (pipe labels, @hotreload
ordinals, legacy @line ordinals) and refuses everything else, including
occurrence-keyed names, which already match exactly. Pairing requires equal
group counts, distinct ordinals on both sides, an unused baseline token, and a
structural shape match per pair (generic arity, base type, interfaces, field
type multiset, method name/arity set, with self-references normalized), and
produces a bijection; any failure keeps today's fail-closed rude edit, so a
structural change such as adding a pipe stage or list lambda still requires a
rebuild rather than risking a mispair, which would be silent corruption.
Baseline synthesized-type shapes are derived from the module at session start
and chained for delta-added types; no persisted format changes.
This is the recovery layer: it makes line-shift edits on these families apply
in place when the replayed names drift. A follow-up addresses the drift at its
source by keying the replay name map on line-normalized identity so matched
closures keep their generation-0 names, Roslyn's model; exact matching then
succeeds without inference and this pairing remains a guarded fallback.
Component acceptance: in-process and disk-started line-shifted pipe closures
and ordinal-shifted value-list closures each emit with exactly one user-authored
updated method; count-mismatch cases stay UnsupportedEdit. Service suite 423
passed, component 234 passed (2 expected manual-host skips).
NatElkins
added a commit
that referenced
this pull request
Jul 2, 2026
Replay of compiler-generated closure names was keyed by the raw basic name, and
debug-pipe closures embed the source line in the basic name itself ('Pipe #1
stage #2 at line 28'), so any line-adding edit changed the bucket key, missed
the recorded generation-0 allocation, and produced fresh names the baseline
could not match. Value-binding list closures shared a bucket whose sequential
ordinals depended on encounter order and drifted similarly. Both families then
depended on the positional-pairing recovery layer or fell back to restart.
Key replay buckets by the line-normalized basic name and keep the original
generation-0 full names as the values, so a matched closure whose code moves
from line 28 to line 30 is handed back its line-28 birth name. This is Roslyn's
Edit and Continue model: synthesized-member identity is established at first
allocation and replayed exactly, making downstream matching exact rather than
inferred. The normalization grammar is extracted to one shared home in
GeneratedNames and reused by the map, the baseline snapshot collector, the
symbol matcher, and the delta emitter's pairing layer, which now acts purely as
a recovery net behind exact matching.
Snapshot compatibility is handled at load: raw legacy keys are normalized,
duplicate buckets merge, replay buckets canonicalize by ordinal, and gapped
buckets recover the unambiguous raw birth basic name from recorded entries
rather than synthesizing a name that would lose the original line text (a
normalized-key fallback remains for genuinely ambiguous recovery). The
ICompilerGeneratedNameMap surface and the persisted Map<string, string[]>
snapshot shape are unchanged; behavior without an installed map is untouched.
Acceptance: the flag-on in-process session test decodes TypeDef names from the
baseline and fresh assemblies after a line-adding edit and asserts exact name
equality for the unchanged closure families. Service HotReload suite: 426
passed, 0 failed. Component suite: 234 passed, 0 failed, 2 expected skips.
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.
F# Hot Reload (Edit-and-Continue) Implementation
This PR introduces hot reload infrastructure for F# on top of .NET's
MetadataUpdater.ApplyUpdatepipeline, with Roslyn-parity-oriented metadata behavior and multi-generation delta chaining.Status Snapshot (2026-02-07)
NatElkins/fsharp#1hot-reload@c454aebVerified in this branch
./.dotnet/dotnet build FSharp.sln -c Debug -v minimal✅./.dotnet/dotnet test tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj -c Debug --no-build --filter FullyQualifiedName~HotReload -v minimal✅ (258passed)./.dotnet/dotnet test tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj -c Debug --no-build --filter FullyQualifiedName~HotReload -v minimal✅ (101passed)What This PR Adds
FSharpChecker:StartHotReloadSessionEmitHotReloadDeltaEndHotReloadSessionHotReloadSessionActiveHotReloadCapabilitiesFSharpEmitBaseline) and generation chaining (EncId/EncBaseId) across multiple edits.tests/projects/HotReloadDemo/HotReloadDemoApp) and smoke script (tests/scripts/hot-reload-demo-smoke.sh).Recent Architecture/Parity Direction (late 2025)
BinaryConstants/delta token helpers)c454aeb).Supported vs Unsupported (Current)
Supported
Still rejected / rebuild required
UnsupportedEdit/rude edits).Runtime Apply Capability Model
RuntimeApplyis only advertised when the runtime supports it andFSHARP_HOTRELOAD_ENABLE_RUNTIME_APPLY=1is set.HOTRELOAD_SMOKE_RUNTIME_APPLY=1Remaining Follow-ups (Not Claimed Done Here)
dotnet watchintegration path.Key Files
src/Compiler/Service/service.fs,src/Compiler/HotReload/EditAndContinueLanguageService.fssrc/Compiler/CodeGen/IlxDeltaEmitter.fs,src/Compiler/CodeGen/FSharpDeltaMetadataWriter.fs,src/Compiler/CodeGen/DeltaMetadataSerializer.fssrc/Compiler/CodeGen/HotReloadBaseline.fs,src/Compiler/HotReload/HotReloadCapabilities.fstests/FSharp.Compiler.Service.Tests/HotReload/*tests/FSharp.Compiler.ComponentTests/HotReload/*