Skip to content

Model scoped and UnscopedRef lifetimes in the type system#3887

Draft
sailro wants to merge 2 commits into
icsharpcode:masterfrom
sailro:scoped-ref-type-system
Draft

Model scoped and UnscopedRef lifetimes in the type system#3887
sailro wants to merge 2 commits into
icsharpcode:masterfrom
sailro:scoped-ref-type-system

Conversation

@sailro

@sailro sailro commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Related to #3853. This is the type-system prerequisite requested during review of #3854.

tl;dr: ILSpy currently treats ScopedRefAttribute as a single boolean and has no semantic
model for UnscopedRefAttribute, implicit scopes, or the module's ref-safety rules. This PR
aligns the type system with Roslyn without changing decompiler output. A separate follow-up
will use this model to recover erased scoped local modifiers.

Problem

LifetimeAnnotation.ScopedRef only reports whether a parameter has
ScopedRefAttribute. That is sufficient to print the explicit scoped keyword, but not to
reason about effective lifetimes:

  • ScopedRefAttribute represents ScopedRef on a by-ref parameter and ScopedValue on a
    by-value ref-like parameter.
  • out is implicitly ScopedRef only for modules using
    [module: RefSafetyRules(11)].
  • ref-like params collections are implicitly ScopedValue, while their compiler-emitted
    ScopedRefAttribute is redundant and must not be printed.
  • UnscopedRefAttribute can remove an implicit scope.
  • a struct receiver is implicitly ScopedRef; UnscopedRefAttribute may be placed on the
    method, property, or accessor.

Without these distinctions, a local-lifetime analysis cannot tell whether an argument may be
captured by a call.

Solution

  • Add ScopedKind with None, ScopedRef, and ScopedValue.
  • Extend LifetimeAnnotation with the explicit attribute-encoded scope and
    HasUnscopedRefAttribute, while preserving the current ScopedRef API.
    ScopedKind replaces the obsolete preview-era RefScoped and ValueScoped fields.
  • Recognize System.Diagnostics.CodeAnalysis.UnscopedRefAttribute and
    System.Runtime.CompilerServices.RefSafetyRulesAttribute.
  • Decode ScopedRefAttribute according to parameter ref-ness and ref-like type semantics.
    Conflicting or malformed annotations degrade to no explicit scope.
  • Read exactly RefSafetyRules(11) from the module, matching Roslyn's old/new escape-rule
    selection.
  • Add internal helpers for effective parameter scope and implicit struct-receiver scope,
    including method/property/accessor UnscopedRefAttribute.

The display path continues to use only the explicit scope. Implicit out, this, and
ref-like params scopes therefore do not introduce redundant scoped keywords, and
UnscopedRefAttribute remains visible as a normal attribute.

Tests

  • explicit scoped ref and scoped ref-struct parameters
  • implicit new-rules out
  • [UnscopedRef] out
  • legacy out from a module without RefSafetyRules(11)
  • struct, class, static, method, property, and accessor receiver scopes
  • ref-like params effective scope
  • ScopedRef option disabled, including preservation of the raw attribute
  • existing RefFields and ParamsCollections Pretty matrices remain byte-identical

Validation:

  • Debug and Release solution builds: 0 warnings, 0 errors
  • full Pretty suite: 1906 passed, 5 existing skips
  • full solution test suite: 4887 passed, 15 existing skips

Review focus

The main review points are the public LifetimeAnnotation shape and the exact mapping of
Roslyn's declared versus effective scope rules.

  • At least one test covering the code changed

ScopedRefAttribute only records explicit syntax. Effective lifetime also
depends on UnscopedRefAttribute, params collections, out parameters, and
the defining module's RefSafetyRules version. Model those distinctions in
the type system without changing decompiler output.

Assisted-by: Copilot:gpt-5.6-sol:GitHub Copilot CLI
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 86d2918e-5a24-48b4-9a86-41d331ec3720
@sailro
sailro force-pushed the scoped-ref-type-system branch from 284a68f to 0df949c Compare July 19, 2026 03:28
@siegfriedpammer
siegfriedpammer marked this pull request as draft July 19, 2026 04:01
@siegfriedpammer

Copy link
Copy Markdown
Member

@dgrunwald this needs a design/review session

Comment thread ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs Outdated
Comment thread ICSharpCode.Decompiler/TypeSystem/IParameter.cs Outdated
ScopedKind is now the authoritative lifetime representation, so retaining
the preview-era boolean fields would duplicate state. Keep the current
ScopedRef compatibility property and group the new metadata attributes
with the other C# 11 attributes.

Assisted-by: Copilot:gpt-5.6-sol:GitHub Copilot CLI
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 86d2918e-5a24-48b4-9a86-41d331ec3720
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