-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
Version Used: Microsoft.CodeAnalysis.CSharp 4.14.0
Steps to Reproduce:
This bug requires multiple assemblies to reproduce, so for simplicity I've created a repro in the form of a single C# program that uses Roslyn APIs.
Here's the csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
</ItemGroup>
</Project>And here's Program.cs:
using System.Collections.Immutable;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
static Compilation CreateCompilation(string code, MetadataReference[] references) =>
CSharpCompilation.Create(
assemblyName: Guid.NewGuid().ToString(),
syntaxTrees: [CSharpSyntaxTree.ParseText(code)],
references: [MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location), .. references],
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
static ImmutableArray<byte> CreateImage(string code, MetadataReference[] references)
{
var compilation = CreateCompilation(code, references);
using var ms = new MemoryStream();
compilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
return ImmutableArray.Create(ms.ToArray());
}
var myStruct1Image = CreateImage(
"public struct MyStruct1 { }",
[]);
var myStruct2Image = CreateImage(
"""
public struct MyStruct2
{
public static implicit operator MyStruct2(MyStruct1 x) => default;
}
""",
[MetadataReference.CreateFromImage(myStruct1Image)]);
var compilation = CreateCompilation(
"""
public static class TestCode
{
public static void Foo(MyClass x) { }
public static void Foo(MyStruct2 x) { }
public static void Bar() => Foo(null);
}
public class MyClass { }
""",
[
//MetadataReference.CreateFromImage(myStruct1Image),
MetadataReference.CreateFromImage(myStruct2Image)
]);
foreach (var diagnostic in compilation.GetDiagnostics())
{
Console.WriteLine(diagnostic);
}If you run it as-is, you'll see this output:
(6,33): error CS0121: The call is ambiguous between the following methods or properties: 'TestCode.Foo(MyClass)' and 'TestCode.Foo(MyStruct2)'
This is an unexpected error, because MyStruct2 is a struct, so it shouldn't be a valid candidate for the call to Foo(null).
However, if you uncomment the commented line (MetadataReference.CreateFromImage(myStruct1Image)), then you'll see no diagnostics, and compilation succeeds.
This is a quite subtle bug. As far as I can tell, here's what's happening:
- In the
Barmethod, Roslyn compiles the call toFoo. - There are two overloads of
Foo, so it does overload resolution to find the right one. Foo(MyClass)is a valid choice.Foo(MyStruct2)isn't itself a valid choice - becausenullcan't be converted toMyStruct2.- However... there's an implicit conversion operator from
MyStruct1toMyStruct2. - This shouldn't change anything - because
MyStruct1is also astruct, sonullcan't be converted toMyStruct1. - ... and when Roslyn is given the assembly reference for the assembly containing
MyStruct1, that's indeed what happens -Foo(MyStruct2)isn't a valid choice.Foo(MyClass)is the only valid choice, and compilation succeeds. - but when Roslyn is not given the assembly reference for the assembly containing
MyStruct1, it appears to assume thatMyStruct1is a reference type. Which meansFoo(MyStruct2)is a valid choice, becausenullcan be converted toMyStruct1, which can be converted toMyStruct2.
That seems wrong to me. Instead of assuming that the return type of the implicit conversion operator is a reference type, I'd have expected Roslyn to instead emit a compiler error saying that it needs the definition of MyStruct1, but it can't resolve it.