dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
18.72k stars 3.99k forks source link

Cannot create nuint to UInt32 conversion in custom CoreLib #66442

Open kant2002 opened 1 year ago

kant2002 commented 1 year ago

Version Used:

Steps to Reproduce:

  1. Create Class1.cs with following content

    namespace System
    {
    public class Object
    {
    
    }
    
    public readonly struct UIntPtr
    {
        // error CS0556: User-defined conversion must convert to or from the enclosing type
        public static explicit operator nuint(uint value) => checked((nuint)value);
    }
    
    public class Attribute { }
    public class String { }
    public abstract class ValueType { }
    public struct Void { }
    public struct UInt32 { }
    
    namespace Runtime.Versioning
    {
        public class TargetFrameworkAttribute : Attribute
        {
            public TargetFrameworkAttribute(string name)
            {
            }
    
            public string FrameworkDisplayName { get; set; }
        }
    }
    
    namespace Reflection
    {
        class Dummmy { }
    }
    }
  2. Create nuintissue.csproj with following content

    
    <Project Sdk="Microsoft.NET.Sdk">
    
    <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>disable</ImplicitUsings>
    
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
    <NoStdLib>true</NoStdLib>
    <NoConfig>true</NoConfig>
    <IsCoreAssembly>true</IsCoreAssembly>
    <RuntimeMetadataVersion>v4.0.30319</RuntimeMetadataVersion>
    <DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
    </PropertyGroup>

3. Run `dotnet build`

**Expected Behavior**:

No compilation error

**Actual Behavior**:

error CS0556: User-defined conversion must convert to or from the enclosing type

jcouv commented 1 year ago

I think this is by-design. In frameworks that don't have the RuntimeFeature.NumericIntPtr feature flag, UIntPtr and nuint are not considered identical. You have two possible solutions:

  1. change the definition of the conversion operator to use UIntPtr and UInt32 instead of corresponding built-in type syntax (nuint and uint): public static explicit operator UIntPtr(UInt32 value)
  2. add the RuntimeFeature.NumericIntPtr feature flag (after that release we do consider the two types to be strictly equivalent)

The test below shows that both of those solutions work. Tagging @cston (who worked on native integers) in case anything to add.

        [Fact]
        public void TODO2()
        {
            var src = """
namespace System
{
    public class Object
    {

    }

    public readonly struct UIntPtr
    {
        // error CS0556: User-defined conversion must convert to or from the enclosing type
        public static explicit operator nuint(uint value) => checked((nuint)value);
    }

    public class Attribute { }
    public class String { }
    public abstract class ValueType { }
    public struct Void { }
    public struct UInt32 { }

    namespace Runtime.Versioning
    {
        public class TargetFrameworkAttribute : Attribute
        {
            public TargetFrameworkAttribute(string name)
            {
            }

            public string FrameworkDisplayName { get; set; }
        }
    }

    namespace Reflection
    {
        class Dummmy { }
    }
}
""";
            var comp = CreateEmptyCompilation(src);
            comp.VerifyDiagnostics(
                // (11,41): error CS0556: User-defined conversion must convert to or from the enclosing type
                //         public static explicit operator nuint(uint value) => checked((nuint)value);
                Diagnostic(ErrorCode.ERR_ConversionNotInvolvingContainedType, "nuint").WithLocation(11, 41)
                );

            var runtimeFeatures = """
namespace System.Runtime.CompilerServices
{
    public static class RuntimeFeature
    {
        public const string NumericIntPtr = "NumericIntPtr";
    }
}
""";
            comp = CreateEmptyCompilation(src + runtimeFeatures);
            comp.VerifyDiagnostics();

            var src2 = """
namespace System
{
    public class Object
    {

    }

    public readonly struct UIntPtr
    {
        // error CS0556: User-defined conversion must convert to or from the enclosing type
        public static explicit operator UIntPtr(UInt32 value) => checked((nuint)value);
    }

    public class Attribute { }
    public class String { }
    public abstract class ValueType { }
    public struct Void { }
    public struct UInt32 { }

    namespace Runtime.Versioning
    {
        public class TargetFrameworkAttribute : Attribute
        {
            public TargetFrameworkAttribute(string name)
            {
            }

            public string FrameworkDisplayName { get; set; }
        }
    }

    namespace Reflection
    {
        class Dummmy { }
    }
}
""";
            comp = CreateEmptyCompilation(src2);
            comp.VerifyDiagnostics(
                );

        }
kant2002 commented 1 year ago

Thanks a lot, that unblocks me and I really should be looking on RuntimeFeature class more in the future.

kant2002 commented 1 year ago

@jcouv same setup with RuntimeFeature.NumericIntPtr added.

If I add this line

public static bool operator ==(nint value1, nint value2) => value1 == value2;

I receive error CS0563: One of the parameters of a binary operator must be the containing type which at this point also strange

jcouv commented 1 year ago

You'll need to provide the complete snippet to get any useful advice. The operator you provided can't work in the code snippet in OP (which doesn't define IntPtr).