dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.36k stars 4.75k forks source link

Compiling against methods that accept params span with only System.Runtime reference produces compiler error #102513

Closed ericstj closed 3 months ago

ericstj commented 5 months ago

Description

We have API in System.Runtime that accepts params ReadonlySpan. Normally we'd be able to consume any API in System.Runtime with just a reference to System.Runtime - but calling these new APIs demands System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan

I wonder if we should push this type down, perhaps others in System.Memory as well. It's implementation already lives in System.Private.CoreLib

Reproduction Steps

Here's a repro that demonstrates a customer scenario where they do runtime compilation and specify System.Runtime only as a reference. This isn't too farfetched since we recommend that folks use reference assemblies.

compileSR.zip -> derived from https://learn.microsoft.com/en-us/archive/msdn-magazine/2017/may/net-core-cross-platform-code-generation-with-roslyn-and-net-core#executing-code-the-emit-apis sample

using System.Reflection;
using System.Runtime.Loader;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;

const string code = """
        public static class C
        {
            public static string M() => string.Concat("Hello", " ", "World", "!", "!", "!");
        }
        """;
var tree = SyntaxFactory.ParseSyntaxTree(code, new CSharpParseOptions(LanguageVersion.Preview));
string fileName="lib.dll";
// add reference to only System.Runtime
var systemRuntimeRefLocation=Path.Combine(AppContext.BaseDirectory, "refs", "System.Runtime.dll");
var systemRuntimeReference = MetadataReference.CreateFromFile(systemRuntimeRefLocation);
var compilation = CSharpCompilation.Create(fileName)
    .WithOptions(
        new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
    .AddReferences(systemRuntimeReference)
    .AddSyntaxTrees(tree);
string path = Path.Combine(Directory.GetCurrentDirectory(), fileName);

EmitResult compilationResult = compilation.Emit(path);
if(compilationResult.Success)
{
    // Load the assembly
    Assembly asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
    object? result = asm!.GetType("C")!.GetMethod("M")!.Invoke(null, []);
    Console.WriteLine(result);
}
else
{
    foreach (Diagnostic codeIssue in compilationResult.Diagnostics)
    {
        string issue = $"ID: {codeIssue.Id}, Message: {codeIssue.GetMessage()}, Location: {codeIssue.Location.GetLineSpan()}, Severity: {codeIssue.Severity}";
        Console.WriteLine(issue);
    }
}

Expected behavior

Comple successful and output "Hello World!!!"

Actual behavior

ID: CS0656, Message: Missing compiler required member 'System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan', Location: : (0,0)-(0,0), Severity: Error

Regression?

Yes, the addition of the params ReadOnlySpan overloads introduced this break.

Known Workarounds

Add a reference to System.Memory

Configuration

No response

Other information

See https://github.com/dotnet/sdk/pull/41014 for some background. CC @333fred @jaredpar @Jozkee

dotnet-policy-service[bot] commented 5 months ago

Tagging subscribers to this area: @dotnet/area-system-runtime See info in area-owners.md if you want to be subscribed.