Repository navigation
Resolve embedded assemblies by name and version, not by simple name - #74
Merged
ByronMayne merged 1 commit intoOct 1, 2026
Merged
Conversation
The hoist's resolver keyed loaded assemblies on their simple name. One process (a compiler server, an IDE) hosts several generators and several builds of one, so the first assembly loaded under a name shadowed every later one: - an older build of a generator without embedded assemblies took the name, and the newer build never unpacked its own: FileNotFoundException, CS8784; - two generators embedding different versions of a dependency (Contracts 2.0.14 and 2.0.16) got whichever loaded first: TypeLoadException, ByronMayne#73. The comparer now matches name and version. A request without an exact match gets the newest loaded version that is at least the requested one, as the runtime binds a reference to a newer version; never an older one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Owner
|
Hey Jesse, I appreciate the effort to track down the issue and contribute a fix back to the project. Due to the nature of this project it can be rather difficult to debug due to all the different environments that generators can run in. I am just going to take a quick look but at it's face value it looks good to me. |
Owner
|
I created a PR to address the other issues you called out in your review #76 |
This was referenced Oct 2, 2026
This was referenced Oct 5, 2026
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
The hoist's resolver keys loaded assemblies on their simple name (
AssemblyNameComparerdrops everything after the first comma). A single process — a compiler server (VBCSCompiler), an IDE — hosts the analyzers of every project it builds, so the first assembly loaded under a name shadows every later one. Two ways this breaks a generator:An older build without embedded assemblies loads first.
AddAssemblysees the name already registered and returns before it looks at the newer build'sSGF.Assembly::resources, so nothing is unpacked. The embeddedSourceGenerator.Foundations.Contractsnever loads:Hit in the wild with Facet: 6.6.9+ is built on SGF, 6.6.8 and older are not. A dev machine that builds one repo on Facet 6.6.8 and another on 6.6.13 gets this warning in the second one until the compiler server restarts. Order-dependent: 6.6.13 first, then 6.6.8, is clean. CI never sees it (fresh process per build).
Two generators embed different versions of the same dependency. The second gets the first one's copy:
TypeLoadException/MissingMethodException. That is TypeLoadException across generators: embedded SourceGenerator.Foundations.Contracts resolves to an older loaded version (simple-name collision) #73.Fix
AssemblyNameComparercompares name and version, so a second build or a second version gets its own entry, andAddAssemblyunpacks its resources.ResolveMissingAssembly: with no exact match, return the newest loaded version that is at least the requested one — the runtime binds a reference to a newer version the same way. Never an older one: it can lack what the caller was built against. A request without a version takes the newest loaded.The rest of
ResolveMissingAssemblyis unchanged. Side note, not touched here:s_assembliesWithResourcesis never added to, so the resource-scan loop after the lookup never runs; resolution relies entirely onAddAssemblyhaving unpacked everything up front.Tests
AssemblyResolverTestsin the Sandbox test project (it can see the generatedConsoleAppSourceGeneratorHoist). Each test emits small assemblies with Roslyn, some carryingSGF.Assembly::resources, and loads each into its ownAssemblyLoadContext, the way Roslyn loads analyzers:masterEmbedded_Assembly_Resolves_When_An_Assembly_With_The_Same_Name_Was_Loaded_First(case 1)FileNotFoundExceptionEmbedded_Assembly_Resolves_In_The_Requested_Version(case 2, #73)Request_For_An_Older_Version_Resolves_To_The_Newer_One_LoadedEmbedded_Assembly_Resolves_For_A_Request_Without_A_VersionRequest_For_A_Newer_Version_Does_Not_Resolve_To_An_Older_OneThe two tests that pass on
masterguard the fallback; with the fallback disabled they fail.Also checked end to end: packed this branch as
2.0.17-local.1, built Facet against it, and ran the failing order in one compiler server (Facet 6.6.8 project, then the locally built Facet). Stock 6.6.13: CS8784. Facet on this branch: 0 warnings; stock 6.6.13 afterwards in that same server was clean too, because the fixed resolver was attached first.Locally (.NET SDK 10.0.300): Sandbox tests 8/8,
SourceGenerator.Foundations.Tests3/3 (net6.0, rolled forward to 8.0 because no 6.0 runtime is installed), solution build 0 warnings.Relates to #73.
🤖 Generated with Claude Code