Skip to content

Overload resolution incorrectly assumes reference types for unresolved implicit user-defined conversion operator return types #81049

@tgjones

Description

@tgjones

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 Bar method, Roslyn compiles the call to Foo.
  • 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 - because null can't be converted to MyStruct2.
  • However... there's an implicit conversion operator from MyStruct1 to MyStruct2.
  • This shouldn't change anything - because MyStruct1 is also a struct, so null can't be converted to MyStruct1.
  • ... 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 that MyStruct1 is a reference type. Which means Foo(MyStruct2) is a valid choice, because null can be converted to MyStruct1, which can be converted to MyStruct2.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-CompilersConcept-Diagnostic ClarityThe issues deals with the ease of understanding of errors and warnings.help wantedThe issue is "up for grabs" - add a comment if you are interested in working on it

    Type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions