diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index d2e2524286523..8324d4f1a0396 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -11521,12 +11521,12 @@ private BoundConditionalAccess BindConditionalAccessExpression(ConditionalAccess return GenerateBadConditionalAccessNodeError(node, receiver, access, diagnostics); } - // The resulting type must be either a reference type T or Nullable + // The resulting type must be either a reference type T, Nullable, or a pointer type. // Therefore we must reject cases resulting in types that are not reference types and cannot be lifted into nullable. // - access cannot have unconstrained generic type - // - access cannot be a pointer // - access cannot be a restricted type - if ((!accessType.IsReferenceType && !accessType.IsValueType) || accessType.IsPointerOrFunctionPointer() || accessType.IsRestrictedType()) + // Note: Pointers (including function pointers) are allowed because they can represent null (as the zero value). + if ((!accessType.IsReferenceType && !accessType.IsValueType) || accessType.IsRestrictedType()) { // Result type of the access is void when result value cannot be made nullable. // For improved diagnostics we detect the cases where the value will be used and produce a @@ -11540,10 +11540,13 @@ private BoundConditionalAccess BindConditionalAccessExpression(ConditionalAccess accessType = GetSpecialType(SpecialType.System_Void, diagnostics, node); } - // if access has value type, the type of the conditional access is nullable of that + // if access has value type (but not a pointer), the type of the conditional access is nullable of that // https://github.com/dotnet/roslyn/issues/35075: The test `accessType.IsValueType && !accessType.IsNullableType()` // should probably be `accessType.IsNonNullableValueType()` - if (accessType.IsValueType && !accessType.IsNullableType() && !accessType.IsVoidType()) + // Note: As far as the language is concerned, pointers (including function pointers) are not value types. + // However, due to a historical quirk in the compiler implementation, we do treat them as value types. + // Since we're checking for value types here, we exclude pointers to avoid wrapping them in Nullable<>. + if (accessType.IsValueType && !accessType.IsNullableType() && !accessType.IsVoidType() && !accessType.IsPointerOrFunctionPointer()) { accessType = GetSpecialType(SpecialType.System_Nullable_T, diagnostics, node).Construct(accessType); } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenShortCircuitOperatorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenShortCircuitOperatorTests.cs index 0c33fb356fbee..a50befa28b367 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenShortCircuitOperatorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenShortCircuitOperatorTests.cs @@ -5616,16 +5616,18 @@ static public void F1(C c) var compilation = CreateCompilation(source, options: TestOptions.DebugExe.WithAllowUnsafe(true)); compilation.VerifyDiagnostics( - // (16,41): error CS8977: 'void*' cannot be made nullable. + // (16,39): error CS0029: Cannot implicitly convert type 'void*' to 'object' // Func a = o => c?.M(); - Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".M()").WithArguments("void*").WithLocation(16, 41), - // (19,39): error CS8977: 'void*' cannot be made nullable. + Diagnostic(ErrorCode.ERR_NoImplicitConv, "c?.M()").WithArguments("void*", "object").WithLocation(16, 39), + // (16,39): error CS1662: Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type + // Func a = o => c?.M(); + Diagnostic(ErrorCode.ERR_CantConvAnonMethReturns, "c?.M()").WithArguments("lambda expression").WithLocation(16, 39), + // (19,37): error CS0029: Cannot implicitly convert type 'void*' to 'object' // static public object F2(C c) => c?.M(); - Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".M()").WithArguments("void*").WithLocation(19, 39), - // (21,42): error CS8977: 'void*' cannot be made nullable. + Diagnostic(ErrorCode.ERR_NoImplicitConv, "c?.M()").WithArguments("void*", "object").WithLocation(19, 37), + // (21,32): error CS0029: Cannot implicitly convert type 'void*' to 'object' // static public object P1 => (new C())?.M(); - Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".M()").WithArguments("void*").WithLocation(21, 42) - ); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "(new C())?.M()").WithArguments("void*", "object").WithLocation(21, 32)); } [WorkItem(1109164, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1109164")] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/FunctionPointerTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/FunctionPointerTests.cs index f6e980d11d3e9..16958e6e3d892 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/FunctionPointerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/FunctionPointerTests.cs @@ -3163,14 +3163,7 @@ void M(delegate* ptr, C c) comp.VerifyDiagnostics( // (7,12): error CS0023: Operator '?' cannot be applied to operand of type 'delegate*' // ptr?.ToString(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "delegate*").WithLocation(7, 12), - // (8,17): error CS8977: 'delegate*' cannot be made nullable. - // ptr = c?.GetPtr(); - Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".GetPtr()").WithArguments("delegate*").WithLocation(8, 17), - // (9,12): error CS8977: 'delegate*' cannot be made nullable. - // (c?.GetPtr())(); - Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".GetPtr()").WithArguments("delegate*").WithLocation(9, 12) - ); + Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "delegate*").WithLocation(7, 12)); var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); @@ -3200,44 +3193,39 @@ void M(delegate* ptr, C c) FunctionPointerUtilities.VerifyFunctionPointerSemanticInfo(model, invocations[1], expectedSyntax: "c?.GetPtr()", - expectedType: "?", + expectedType: "delegate*", expectedConvertedType: "delegate*", expectedSymbol: null, expectedSymbolCandidates: null); VerifyOperationTreeForNode(comp, model, invocations[1], expectedOperationTree: @" -IConditionalAccessOperation (OperationKind.ConditionalAccess, Type: ?, IsInvalid) (Syntax: 'c?.GetPtr()') +IConditionalAccessOperation (OperationKind.ConditionalAccess, Type: delegate*) (Syntax: 'c?.GetPtr()') Operation: - IInvalidOperation (OperationKind.Invalid, Type: ?, IsImplicit) (Syntax: 'c') - Children(1): - IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') WhenNotNull: - IInvocationOperation ( delegate* C.GetPtr()) (OperationKind.Invocation, Type: delegate*, IsInvalid) (Syntax: '.GetPtr()') + IInvocationOperation ( delegate* C.GetPtr()) (OperationKind.Invocation, Type: delegate*) (Syntax: '.GetPtr()') Instance Receiver: IConditionalAccessInstanceOperation (OperationKind.ConditionalAccessInstance, Type: C, IsImplicit) (Syntax: 'c') - Arguments(0) -"); + Arguments(0)"); FunctionPointerUtilities.VerifyFunctionPointerSemanticInfo(model, invocations[2].Parent!.Parent!, expectedSyntax: "(c?.GetPtr())()", - expectedType: "?", - expectedSymbol: null, + expectedType: "System.Void", + expectedSymbol: "delegate*", expectedSymbolCandidates: null); VerifyOperationTreeForNode(comp, model, invocations[2].Parent!.Parent!, expectedOperationTree: @" -IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: '(c?.GetPtr())()') - Children(1): - IConditionalAccessOperation (OperationKind.ConditionalAccess, Type: ?, IsInvalid) (Syntax: 'c?.GetPtr()') - Operation: - IInvalidOperation (OperationKind.Invalid, Type: ?, IsImplicit) (Syntax: 'c') - Children(1): - IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') - WhenNotNull: - IInvocationOperation ( delegate* C.GetPtr()) (OperationKind.Invocation, Type: delegate*, IsInvalid) (Syntax: '.GetPtr()') - Instance Receiver: - IConditionalAccessInstanceOperation (OperationKind.ConditionalAccessInstance, Type: C, IsImplicit) (Syntax: 'c') - Arguments(0) -"); +IFunctionPointerInvocationOperation (OperationKind.FunctionPointerInvocation, Type: System.Void) (Syntax: '(c?.GetPtr())()') + Target: + IConditionalAccessOperation (OperationKind.ConditionalAccess, Type: delegate*) (Syntax: 'c?.GetPtr()') + Operation: + IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c') + WhenNotNull: + IInvocationOperation ( delegate* C.GetPtr()) (OperationKind.Invocation, Type: delegate*) (Syntax: '.GetPtr()') + Instance Receiver: + IConditionalAccessInstanceOperation (OperationKind.ConditionalAccessInstance, Type: C, IsImplicit) (Syntax: 'c') + Arguments(0) + Arguments(0)"); } @@ -3299,37 +3287,35 @@ .maxstack 2 FunctionPointerUtilities.VerifyFunctionPointerSemanticInfo(model, invocations[0], expectedSyntax: "c?.GetPtr()", - expectedType: "System.Void", + expectedType: "delegate*", expectedSymbol: null, expectedSymbolCandidates: null); VerifyOperationTreeForNode(comp, model, invocations[0], expectedOperationTree: @" -IConditionalAccessOperation (OperationKind.ConditionalAccess, Type: System.Void) (Syntax: 'c?.GetPtr()') - Operation: +IConditionalAccessOperation (OperationKind.ConditionalAccess, Type: delegate*) (Syntax: 'c?.GetPtr()') + Operation: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') - WhenNotNull: + WhenNotNull: IInvocationOperation ( delegate* C.GetPtr()) (OperationKind.Invocation, Type: delegate*) (Syntax: '.GetPtr()') - Instance Receiver: + Instance Receiver: IConditionalAccessInstanceOperation (OperationKind.ConditionalAccessInstance, Type: C, IsImplicit) (Syntax: 'c') - Arguments(0) -"); + Arguments(0)"); FunctionPointerUtilities.VerifyFunctionPointerSemanticInfo(model, invocations[1], expectedSyntax: "c?.GetPtr()", - expectedType: "System.Void", + expectedType: "delegate*", expectedSymbol: null, expectedSymbolCandidates: null); VerifyOperationTreeForNode(comp, model, invocations[1], expectedOperationTree: @" -IConditionalAccessOperation (OperationKind.ConditionalAccess, Type: System.Void) (Syntax: 'c?.GetPtr()') - Operation: +IConditionalAccessOperation (OperationKind.ConditionalAccess, Type: delegate*) (Syntax: 'c?.GetPtr()') + Operation: ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') - WhenNotNull: + WhenNotNull: IInvocationOperation ( delegate* C.GetPtr()) (OperationKind.Invocation, Type: delegate*) (Syntax: '.GetPtr()') - Instance Receiver: + Instance Receiver: IConditionalAccessInstanceOperation (OperationKind.ConditionalAccessInstance, Type: C, IsImplicit) (Syntax: 'c') - Arguments(0) -"); + Arguments(0)"); } [Fact] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullConditionalAssignmentTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullConditionalAssignmentTests.cs index 8d81cd1f83687..6bc99f7f1f1ba 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullConditionalAssignmentTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullConditionalAssignmentTests.cs @@ -2892,5 +2892,654 @@ static void M(bool b, C? c1, C? c2) """, graph, symbol); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerReturnType_Simple() + { + var source = """ + public class A + { + public unsafe byte* Ptr = null; + } + + class Test + { + unsafe static void M1(A a) + { + byte* ptr = a?.Ptr; + } + + unsafe static void M2(A a) + { + var result = a?.Ptr; + } + } + """; + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerReturnType_ClassReceiver() + { + var source = """ + using System; + + public class A + { + public unsafe byte* Ptr = null; + + unsafe byte* M1(bool b) + { + return b ? this?.Ptr : ((A)null)?.Ptr; + } + + static unsafe void Main() + { + byte x = 42; + A a = new () { Ptr = &x }; + var v = a.M1(true); + Console.Write(v == null ? " null " : *v); + v = a.M1(false); + Console.Write(v == null ? " null " : *v); + } + } + """; + var comp = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, expectedOutput: "42 null ", verify: Verification.Fails).VerifyDiagnostics(); + comp.VerifyIL("A.M1", """ + { + // Code size 26 (0x1a) + .maxstack 1 + .locals init (byte* V_0) + IL_0000: nop + IL_0001: ldarg.1 + IL_0002: brtrue.s IL_0008 + IL_0004: ldc.i4.0 + IL_0005: conv.u + IL_0006: br.s IL_0015 + IL_0008: ldarg.0 + IL_0009: brtrue.s IL_000f + IL_000b: ldc.i4.0 + IL_000c: conv.u + IL_000d: br.s IL_0015 + IL_000f: ldarg.0 + IL_0010: ldfld "byte* A.Ptr" + IL_0015: stloc.0 + IL_0016: br.s IL_0018 + IL_0018: ldloc.0 + IL_0019: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerReturnType_Parameter_ClassReceiver() + { + var source = """ + using System; + + public class A + { + public unsafe byte* Ptr = null; + } + + class Test + { + unsafe static byte* M1(A a) + { + return a?.Ptr; + } + + static unsafe void Main() + { + byte x = 42; + A a = new () { Ptr = &x }; + var v = Test.M1(a); + Console.Write(v == null ? " null " : *v); + v = Test.M1(null); + Console.Write(v == null ? " null " : *v); + } + } + """; + var comp = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, expectedOutput: "42 null ", verify: Verification.Fails).VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 19 (0x13) + .maxstack 1 + .locals init (byte* V_0) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: brtrue.s IL_0008 + IL_0004: ldc.i4.0 + IL_0005: conv.u + IL_0006: br.s IL_000e + IL_0008: ldarg.0 + IL_0009: ldfld "byte* A.Ptr" + IL_000e: stloc.0 + IL_000f: br.s IL_0011 + IL_0011: ldloc.0 + IL_0012: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerReturnType_NullableValueTypeReceiver() + { + var source = """ + using System; + + public struct A + { + public unsafe byte* Ptr; + } + + class Test + { + unsafe static byte* M1(A? a) + { + return a?.Ptr; + } + + static unsafe void Main() + { + byte x = 42; + A a = new () { Ptr = &x }; + var v = Test.M1(a); + Console.Write(v == null ? " null " : *v); + v = Test.M1(null); + Console.Write(v == null ? " null " : *v); + } + } + """; + var comp = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, expectedOutput: "42 null ", verify: Verification.Fails).VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 31 (0x1f) + .maxstack 1 + .locals init (byte* V_0) + IL_0000: nop + IL_0001: ldarga.s V_0 + IL_0003: call "bool A?.HasValue.get" + IL_0008: brtrue.s IL_000e + IL_000a: ldc.i4.0 + IL_000b: conv.u + IL_000c: br.s IL_001a + IL_000e: ldarga.s V_0 + IL_0010: call "A A?.GetValueOrDefault()" + IL_0015: ldfld "byte* A.Ptr" + IL_001a: stloc.0 + IL_001b: br.s IL_001d + IL_001d: ldloc.0 + IL_001e: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerReturnType_MethodCallReceiver() + { + var source = """ + using System; + + public class A + { + public unsafe byte* Ptr; + } + + class Test + { + static A GetA(A a) => a; + + static unsafe byte* M1(A a) + { + return GetA(a)?.Ptr; + } + + static unsafe void Main() + { + byte x = 42; + A a = new () { Ptr = &x }; + var v = M1(a); + Console.Write(v == null ? " null " : *v); + v = M1(null); + Console.Write(v == null ? " null " : *v); + } + } + """; + var comp = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, expectedOutput: "42 null ", verify: Verification.Fails).VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 25 (0x19) + .maxstack 2 + .locals init (byte* V_0) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: call "A Test.GetA(A)" + IL_0007: dup + IL_0008: brtrue.s IL_000f + IL_000a: pop + IL_000b: ldc.i4.0 + IL_000c: conv.u + IL_000d: br.s IL_0014 + IL_000f: ldfld "byte* A.Ptr" + IL_0014: stloc.0 + IL_0015: br.s IL_0017 + IL_0017: ldloc.0 + IL_0018: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerReturnType_NullableValueTypeMethodCallReceiver() + { + var source = """ + using System; + + public struct A + { + public unsafe byte* Ptr; + } + + class Test + { + static A? GetA(A? a) => a; + + static unsafe byte* M1(A? a) + { + return GetA(a)?.Ptr; + } + + static unsafe void Main() + { + byte x = 42; + A a = new () { Ptr = &x }; + var v = M1(a); + Console.Write(v == null ? " null " : *v); + v = M1(null); + Console.Write(v == null ? " null " : *v); + } + } + """; + var comp = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, expectedOutput: "42 null ", verify: Verification.Fails).VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 38 (0x26) + .maxstack 2 + .locals init (A? V_0, + byte* V_1) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: call "A? Test.GetA(A?)" + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: dup + IL_000b: call "bool A?.HasValue.get" + IL_0010: brtrue.s IL_0017 + IL_0012: pop + IL_0013: ldc.i4.0 + IL_0014: conv.u + IL_0015: br.s IL_0021 + IL_0017: call "A A?.GetValueOrDefault()" + IL_001c: ldfld "byte* A.Ptr" + IL_0021: stloc.1 + IL_0022: br.s IL_0024 + IL_0024: ldloc.1 + IL_0025: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerReturnType_WithUsage() + { + var source = """ + using System; + + public class A + { + public unsafe byte* Ptr; + } + + class Test + { + static unsafe void Main() + { + byte b = 42; + var a = new A { Ptr = &b }; + byte* ptr1 = a?.Ptr; + Console.Write(ptr1 == null ? " null-ptr1 " : *ptr1); + + a?.Ptr = null; + byte* ptr2 = a?.Ptr; + Console.Write(ptr2 == null ? " null-ptr2 " : *ptr2); + + a = null; + byte* ptr3 = a?.Ptr; + Console.Write(ptr3 == null ? " null-ptr3 " : *ptr3); + } + } + """; + var verifier = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, verify: Verification.Skipped, expectedOutput: "42 null-ptr2 null-ptr3"); + verifier.VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerReturnType_IntPointer() + { + var source = """ + class Test + { + public unsafe int* Value = null; + + static unsafe void M(Test t) + { + int* p = t?.Value; + var v = t?.Value; + } + } + """; + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var conditionalAccess = tree.GetRoot().DescendantNodes().OfType().First(); + var typeInfo = model.GetTypeInfo(conditionalAccess); + Assert.Equal("System.Int32*", typeInfo.Type.ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerReturnType_VoidPointer() + { + var source = """ + class Test + { + public unsafe void* Data = null; + + static unsafe void M(Test t) + { + void* p = t?.Data; + var d = t?.Data; + } + } + """; + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerReturnType_Chained() + { + var source = """ + struct Node + { + public unsafe Node* Next; + public int Value; + + public unsafe Node(int v) { Next = null; Value = v; } + } + + class Test + { + public unsafe Node* Head = null; + + static unsafe void M(Test t) + { + Node* n1 = t?.Head; + } + } + """; + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerReturnType_StatementContext() + { + var source = """ + public class A + { + public unsafe byte* DoSomething() => null; + } + + class Test + { + static unsafe void M(A a) + { + a?.DoSomething(); + } + } + """; + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void FunctionPointerReturnType_Allowed() + { + var source = """ + class Test + { + public unsafe delegate* FPtr = null; + + static unsafe void M(Test t) + { + var f = t?.FPtr; + } + } + """; + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void FunctionPointerReturnType_LocalAssignment() + { + var source = """ + class Test + { + public unsafe delegate* FPtr = null; + + static unsafe void M(Test t) + { + delegate* f = t?.FPtr; + var g = t?.FPtr; + } + } + """; + var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void FunctionPointerReturnType_Execution() + { + var source = """ + using System; + + class Test + { + public unsafe delegate* FPtr; + + static int Double(int x) => x * 2; + + static unsafe void Main() + { + var t = new Test { FPtr = &Double }; + delegate* f1 = t?.FPtr; + Console.Write(f1 != null ? f1(21) : -1); + + t = null; + delegate* f2 = t?.FPtr; + Console.Write(f2 == null ? " null" : " not null"); + } + } + """; + var verifier = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, verify: Verification.Skipped, expectedOutput: "42 null"); + verifier.VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerConditionalAssignment() + { + var source = """ + using System; + + public class A + { + public unsafe byte* Ptr; + } + + class Test + { + static unsafe void Main() + { + byte b1 = 10; + byte b2 = 20; + var a = new A { Ptr = &b1 }; + + // Assignment when receiver is not null + a?.Ptr = &b2; + Console.Write(*a.Ptr); // Should be 20 + + // Assignment when receiver is null + a = null; + a?.Ptr = &b1; + Console.Write(a == null ? " null" : " not null"); + } + } + """; + var verifier = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, verify: Verification.Skipped, expectedOutput: "20 null"); + verifier.VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void PointerConditionalAssignment_IL() + { + var source = """ + public class A + { + public unsafe byte* Ptr; + } + + class Test + { + static unsafe void Main() + { + byte b1 = 10; + byte b2 = 20; + var a = new A { Ptr = &b1 }; + + a?.Ptr = &b2; + } + } + """; + var verifier = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.Main", """ + { + // Code size 37 (0x25) + .maxstack 3 + .locals init (byte V_0, //b1 + byte V_1, //b2 + A V_2) //a + IL_0000: nop + IL_0001: ldc.i4.s 10 + IL_0003: stloc.0 + IL_0004: ldc.i4.s 20 + IL_0006: stloc.1 + IL_0007: newobj "A..ctor()" + IL_000c: dup + IL_000d: ldloca.s V_0 + IL_000f: conv.u + IL_0010: stfld "byte* A.Ptr" + IL_0015: stloc.2 + IL_0016: ldloc.2 + IL_0017: brtrue.s IL_001b + IL_0019: br.s IL_0024 + IL_001b: ldloc.2 + IL_001c: ldloca.s V_1 + IL_001e: conv.u + IL_001f: stfld "byte* A.Ptr" + IL_0024: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void FunctionPointerConditionalAssignment() + { + var source = """ + using System; + + class Test + { + public unsafe delegate* FPtr; + + static int Double(int x) => x * 2; + static int Triple(int x) => x * 3; + + static unsafe void Main() + { + var t = new Test { FPtr = &Double }; + + // Assignment when receiver is not null + t?.FPtr = &Triple; + Console.Write(t.FPtr != null ? t.FPtr(7) : -1); // Should be 21 (7*3) + + // Assignment when receiver is null + t = null; + t?.FPtr = &Double; + Console.Write(t == null ? " null" : " not null"); + } + } + """; + var verifier = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, verify: Verification.Skipped, expectedOutput: "21 null"); + verifier.VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/7502")] + public void FunctionPointerConditionalAssignment_IL() + { + var source = """ + class Test + { + public unsafe delegate* FPtr; + + static int Double(int x) => x * 2; + static int Triple(int x) => x * 3; + + static unsafe void Main() + { + var t = new Test { FPtr = &Double }; + t?.FPtr = &Triple; + } + } + """; + var verifier = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.Main", """ + { + // Code size 37 (0x25) + .maxstack 3 + .locals init (Test V_0) //t + IL_0000: nop + IL_0001: newobj "Test..ctor()" + IL_0006: dup + IL_0007: ldftn "int Test.Double(int)" + IL_000d: stfld "delegate* Test.FPtr" + IL_0012: stloc.0 + IL_0013: ldloc.0 + IL_0014: brtrue.s IL_0018 + IL_0016: br.s IL_0024 + IL_0018: ldloc.0 + IL_0019: ldftn "int Test.Triple(int)" + IL_001f: stfld "delegate* Test.FPtr" + IL_0024: ret + } + """); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs index d5da3ff399b4e..f3a4b64e7b4d5 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs @@ -24356,11 +24356,28 @@ unsafe static void Main() } "; - CreateCompilationWithMscorlib461(text, options: TestOptions.UnsafeReleaseDll).VerifyDiagnostics( - // (9,24): error CS8977: 'void*' cannot be made nullable. - // var p = intPtr?.ToPointer(); - Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".ToPointer()").WithArguments("void*").WithLocation(9, 24) - ); + CompileAndVerify(text, options: TestOptions.UnsafeReleaseDll) + .VerifyDiagnostics() + .VerifyIL("Program.Main", """ + { + // Code size 34 (0x22) + .maxstack 1 + .locals init (System.IntPtr? V_0, //intPtr + System.IntPtr V_1) + IL_0000: ldloca.s V_0 + IL_0002: initobj "System.IntPtr?" + IL_0008: ldloca.s V_0 + IL_000a: call "bool System.IntPtr?.HasValue.get" + IL_000f: brfalse.s IL_0021 + IL_0011: ldloca.s V_0 + IL_0013: call "System.IntPtr System.IntPtr?.GetValueOrDefault()" + IL_0018: stloc.1 + IL_0019: ldloca.s V_1 + IL_001b: call "void* System.IntPtr.ToPointer()" + IL_0020: pop + IL_0021: ret + } + """); } [Fact]