Skip to content

fix: name generated helper types per assembly to avoid InternalsVisibleTo conflicts#2257

Merged
glennawatson merged 4 commits into
mainfrom
fix/generated-type-ivt-collision
Jul 18, 2026
Merged

fix: name generated helper types per assembly to avoid InternalsVisibleTo conflicts#2257
glennawatson merged 4 commits into
mainfrom
fix/generated-type-ivt-collision

Conversation

@glennawatson

@glennawatson glennawatson commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Bug fix (source generator + runtime).

What is the new behavior?

  • The generator folds the (identifier-sanitized, Pascal-cased) assembly name into the generated implementation container type and the internal PreserveAttribute namespace, so each assembly emits distinctly named copies (e.g. Refit.Implementation.GeneratedMyApp).
  • Two assemblies linked by [InternalsVisibleTo] that both use Refit now compile cleanly.
  • UniqueName.ForType reconstructs the identical container name at runtime (its own sanitizer, mirroring the generator), so reflection-based resolution and the derived IHttpClientFactory client key stay in sync with the emitted code.

What is the current behavior?

Every assembly emitted the same Refit.Implementation.Generated container and RefitInternalGenerated.PreserveAttribute. Under [InternalsVisibleTo] the second compilation imported those alongside the ones it generated, producing CS0436 duplicate-type warnings that fail a TreatWarningsAsErrors build.

Closes #2254

What might this PR break?

  • UniqueName.ForType<T>() (and the IHttpClientFactory client key derived from it) returns a different string - it now includes the interface's assembly name. The value is computed at runtime, never hardcoded, so no consumer code change is needed. v14 is still beta.

Checklist

  • I have read the Contribute guide
  • Tests have been added or updated (for bug fixes / features)
  • Docs have been added or updated (for bug fixes / features)
  • Changes target the main branch
  • PR title follows Conventional Commits

Additional information

The scope segment maps every non-identifier character to _ and upper-cases the first character; a null/blank assembly name yields no segment (unnamed compilations keep the historic Generated name). The generator builds it and the runtime UniqueName reconstructs it independently - a mirrored pair guarded by a parity test that resolves a generic interface against the real emitted assembly. dotnet test -c Release from ./src: 7065 passed, 0 failed (net8-net11); UniqueName.cs is 100 percent line and branch.

…leTo conflicts

- Fold the sanitized, Pascal-cased assembly name into the generated
  implementation container type and the internal PreserveAttribute
  namespace, so each assembly emits distinctly named copies instead of
  the shared Refit.Implementation.Generated and RefitInternalGenerated
  names that collide under [InternalsVisibleTo] (CS0436)
- Reconstruct the identical container name in UniqueName.ForType with a
  runtime sanitizer that mirrors the generator, keeping reflection
  resolution and the derived IHttpClientFactory client key in sync
- Wrap the generated factory registrations and DynamicDependency
  attribute so emitted lines stay within the line-length limit for long
  assembly names
- Add a two-assembly InternalsVisibleTo generator test (no duplicate-type
  diagnostic), a reflection-resolution parity test, and sanitizer tests

Closes #2254
@glennawatson
glennawatson force-pushed the fix/generated-type-ivt-collision branch from 1bb964f to e3d6992 Compare July 18, 2026 04:32
… instead

- remove the upper-casing transform from the generator and UniqueName sanitizers; real assembly names are already PascalCase, and the transform only existed to prettify the lowercase test default
- name the generator test compilation `Compilation` (via nameof) so the scoped output stays PascalCase with no production transform; generated output is byte-identical (snapshots unchanged)
- update the sanitizer expectations to the preserved-case form
- UnnamedCompilationEmitsUnscopedContainerName joined generated sources
  with \n but each source keeps CRLF on Windows runners, so the
  "...RefitInternalGenerated\n" needle never matched there
- Replace CRLF with LF on the joined output so the assertion holds on
  both Windows and Linux; test-only, generator emission unchanged
@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (136d002) to head (dbaba6a).

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #2257   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          183       184    +1     
  Lines         9566      9600   +34     
  Branches      1803      1810    +7     
=========================================
+ Hits          9566      9600   +34     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sonarqubecloud

Copy link
Copy Markdown

@glennawatson
glennawatson merged commit 17c24d8 into main Jul 18, 2026
17 checks passed
@glennawatson
glennawatson deleted the fix/generated-type-ivt-collision branch July 18, 2026 08:31
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.

[Bug]: "PreserveAttribute" and "Generated" type conflict warnings when using InternalsVisibleTo

2 participants