Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow pattern matching null against pointer types when the pointer types contain nested type parameters #49915

Merged
merged 3 commits into from
Dec 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,8 @@ internal BoundExpression ConvertPatternExpression(
HashSet<DiagnosticInfo>? useSiteDiagnostics = null;
if (expression.ConstantValue == ConstantValue.Null)
{
if (inputType.IsNonNullableValueType())
// Pointers are value types, but they can be assigned null, so they can be matched against null.
if (inputType.IsNonNullableValueType() && !inputType.IsPointerOrFunctionPointer())
{
// We do not permit matching null against a struct type.
diagnostics.Add(ErrorCode.ERR_ValueCantBeNull, expression.Syntax.Location, inputType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11176,6 +11176,49 @@ public void InvalidReturnInCompoundAssignment()
);
}

[Fact, WorkItem(49639, "https://github.com/dotnet/roslyn/issues/49639")]
public void CompareToNullWithNestedUnconstrainedTypeParameter()
333fred marked this conversation as resolved.
Show resolved Hide resolved
{
var verifier = CompileAndVerifyFunctionPointers(@"
using System;
unsafe
{
test<int>(null);
333fred marked this conversation as resolved.
Show resolved Hide resolved
test<int>(&intTest);

static void test<T>(delegate*<T, void> f)
333fred marked this conversation as resolved.
Show resolved Hide resolved
{
Console.WriteLine(f == null);
Console.WriteLine(f is null);
}

static void intTest(int i) {}
}
", expectedOutput: @"
True
True
False
False");

verifier.VerifyIL("<Program>$.<<Main>$>g__test|0_0<T>(delegate*<T, void>)", @"
{
// Code size 21 (0x15)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: conv.u
IL_0003: ceq
IL_0005: call ""void System.Console.WriteLine(bool)""
IL_000a: ldarg.0
IL_000b: ldc.i4.0
IL_000c: conv.u
IL_000d: ceq
IL_000f: call ""void System.Console.WriteLine(bool)""
IL_0014: ret
}
");
}

private static readonly Guid s_guid = new Guid("97F4DBD4-F6D1-4FAD-91B3-1001F92068E5");
private static readonly BlobContentId s_contentId = new BlobContentId(s_guid, 0x04030201);

Expand Down
110 changes: 110 additions & 0 deletions src/Compilers/CSharp/Test/Emit/CodeGen/UnsafeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9539,6 +9539,116 @@ .locals init (S* V_0, //p
");
}

[Fact, WorkItem(49639, "https://github.com/dotnet/roslyn/issues/49639")]
public void CompareToNullWithNestedUnconstrainedTypeParameter()
333fred marked this conversation as resolved.
Show resolved Hide resolved
{
var verifier = CompileAndVerify(@"
using System;
unsafe
{
test<int>(null);
S<int> s = default;
test<int>(&s);

static void test<T>(S<T>* s)
{
Console.WriteLine(s == null);
Console.WriteLine(s is null);
}
}

struct S<T> {}
", options: TestOptions.UnsafeReleaseExe, expectedOutput: @"
True
True
False
False", verify: Verification.Skipped);

verifier.VerifyIL("<Program>$.<<Main>$>g__test|0_0<T>(S<T>*)", @"
{
// Code size 21 (0x15)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: conv.u
IL_0003: ceq
IL_0005: call ""void System.Console.WriteLine(bool)""
IL_000a: ldarg.0
IL_000b: ldc.i4.0
IL_000c: conv.u
IL_000d: ceq
IL_000f: call ""void System.Console.WriteLine(bool)""
IL_0014: ret
}
");
}

[Fact, WorkItem(49639, "https://github.com/dotnet/roslyn/issues/49639")]
public void CompareToNullWithPointerToUnmanagedTypeParameter()
{
var verifier = CompileAndVerify(@"
using System;
unsafe
{
test<int>(null);
int i = 0;
test<int>(&i);

static void test<T>(T* t) where T : unmanaged
{
Console.WriteLine(t == null);
Console.WriteLine(t is null);
}
}
", options: TestOptions.UnsafeReleaseExe, expectedOutput: @"
True
True
False
False", verify: Verification.Skipped);

verifier.VerifyIL("<Program>$.<<Main>$>g__test|0_0<T>(T*)", @"
{
// Code size 21 (0x15)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldc.i4.0
IL_0002: conv.u
IL_0003: ceq
IL_0005: call ""void System.Console.WriteLine(bool)""
IL_000a: ldarg.0
IL_000b: ldc.i4.0
IL_000c: conv.u
IL_000d: ceq
IL_000f: call ""void System.Console.WriteLine(bool)""
IL_0014: ret
}
");
}
333fred marked this conversation as resolved.
Show resolved Hide resolved

[Theory]
[InlineData("int*")]
[InlineData("delegate*<void>")]
[InlineData("T*")]
[InlineData("delegate*<T>")]
public void CompareToNullInPatternOutsideUnsafe(string pointerType)
{
var comp = CreateCompilation($@"
var c = default(S<int>);
_ = c.Field is null;
unsafe struct S<T> where T : unmanaged
{{
#pragma warning disable CS0649 // Field is unassigned
public {pointerType} Field;
}}
", options: TestOptions.UnsafeReleaseExe);

comp.VerifyDiagnostics(
// (3,5): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context
// _ = c.Field is null;
Diagnostic(ErrorCode.ERR_UnsafeNeeded, "c.Field").WithLocation(3, 5)
);
}

#endregion Pointer comparison tests

#region stackalloc tests
Expand Down