Skip to content

[NET 10] NULL_PTR generates IntPtr.Zero, causing CS0034 ambiguous operator errors when compared to PTR #2069

Description

@hpetriffer

The X# native type PTR compiles to a real unsafe pointer (void*). However, the NULL_PTR literal does not compile to a void* value — it compiles to System.IntPtr.Zero. Since IntPtr/nint and void* both have applicable (but different) equality operators, comparing a PTR variable to NULL_PTR now produces:

Operator '!=' is ambiguous on operands of type 'ptr' and 'nint'

(and the equivalent for ==).

This appears to be a long-standing inconsistency in the compiler that only surfaces as a hard error under newer .NET / Roslyn versions where pointer-vs-nint operator resolution is stricter.

Repro

LOCAL h AS PTR
h := NULL_PTR
IF h == NULL_PTR
    ? "null"
ENDIF

Compiling under .NET 10 gives:

Operator '==' is ambiguous on operands of type 'ptr' and 'nint'

Root cause

  • PTR as a declared type maps to a genuine C# pointer type (void*):
    XSharpTreeTransformationCore.cs:10282

    case XP.PTR:
        context.Put(_syntaxFactory.PointerType(VoidType, SyntaxFactory.MakeToken(SyntaxKind.AsteriskToken)));
        break;
  • The internal helper PtrType, used when generating the NULL_PTR literal, is defined as System.IntPtr, not void*:
    XSharpTreeTransformationCore.cs:190

    protected TypeSyntax PtrType => GenerateQualifiedName(SystemQualifiedNames.IntPtr);
  • NULL_PTR is generated as IntPtr.Zero using that helper:
    XSharpTreeTransformationRT.cs:4189

    case XP.NULL_PTR:
        expr = MakeSimpleMemberAccess(PtrType, GenerateSimpleName("Zero"));
        break;

    (same pattern repeated at XSharpTreeTransformationRT.cs:4796)

Since nint is just the C# built-in alias for System.IntPtr, the emitted comparison is effectively void* == nint. Under Roslyn's pointer/native-int operator resolution rules this is ambiguous (CS0034), because both the pointer equality operator and the nint numeric equality operator are equally applicable via implicit conversion.

PtrType (IntPtr) is correctly used elsewhere for USUAL-boxing and PSZ/PCALL marshalling, where a real IntPtr is actually required — those usages are not affected and should not change:

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions