Skip to content

Decompiling a ReadOnlySpan<T> built from an array literal references the undeclared <PrivateImplementationDetails> cache (does not recompile) #3850

Description

@sailro

Summary

Decompiling a ReadOnlySpan<T> that is created from an array literal on a target framework without RuntimeHelpers.CreateSpan (e.g. .NET Framework, or netstandard2.0 + the System.Memory package) produces code that does not recompile: it references the compiler-synthesized <PrivateImplementationDetails> cache field, which is never declared and whose angle-bracket name is not expressible in C#.

Input code

Compiled in Release targeting .NET Framework 4.6.2 with the System.Memory package:

using System;

public static class SpanCache
{
    public static ReadOnlySpan<char> NewLine => new char[] { '\r', '\n' };
}

On such a framework Roslyn caches the backing char[] in a lazy <PrivateImplementationDetails> static field (there is no RuntimeHelpers.CreateSpan to blit the data at run time).

Erroneous output

public static ReadOnlySpan<char> NewLine
{
    get
    {
        object obj = <PrivateImplementationDetails>.B7F5...7075_A1;
        if (obj == null)
        {
            obj = new char[2] { '\r', '\n' };
            <PrivateImplementationDetails>.B7F5...7075_A1 = (char[])obj;
        }
        return new ReadOnlySpan<char>((char[])obj);
    }
}

The synthesized <PrivateImplementationDetails> type is referenced but never declared, and its name is not expressible in C#. In whole-project output the name is escaped to _003CPrivateImplementationDetails_003E, which then fails to compile with CS0400 (the type is undeclared).

Expected output

The array literal should be recovered, so the <PrivateImplementationDetails> reference disappears — the same result the RuntimeHelpers.CreateSpan path (net7+) already produces:

public static ReadOnlySpan<char> NewLine => new ReadOnlySpan<char>(new char[2] { '\r', '\n' });

Details

  • Product in use: ICSharpCode.Decompiler
  • Version in use: master fe50c00f (11.0.0.9086); also reproduces on 10.1 (10.1.0.8386).
  • Real-world occurrence: Markdig CodeInlineParser (net462).
  • Root cause: ILSpy recovers the modern RuntimeHelpers.CreateSpan form (TransformArrayInitializers.TransformRuntimeHelpersCreateSpanInitialization) but not the legacy lazy-char[]-cache form, so the raw <PrivateImplementationDetails> field reference is left behind. This lazy cache has the same shape as the anonymous-method delegate cache already collapsed by CachedDelegateInitialization.
  • I'm happy to contribute a fix.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions