dotnet / dotnet/csharpstandard

Incorrect behaviour on conditional access to generic field where the generic type is a Nullable<T>

Open
#302 6 comments 0 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
815
Forks
99
Avg merge
1d 14h
Merged PRs (30d)
16

Description

**Version Used**:

Master (27 April 2019)

**Steps to Reproduce**:

```csharp
using System;
public static class Program {
public static void Main() {
MutableStruct? field = new MutableStruct();
MutableStruct? param = new MutableStruct();
var printer = new Printer(field);
printer.Print(ref param);
printer.Print(ref param);
}
}

public class Printer
{
T field;
public Printer(T f) => field = f;
public void Print(ref T param)
{
Console.WriteLine(field?.ToString() + field?.ToString() + " " +
param?.ToString() + param?.ToString());
}
}

public struct MutableStruct
{
int i;
public override string ToString() => (++i).ToString();
}
```

[SharpLab](https://sharplab.io/#v2:EYLgtghgzgLgpgJwDQxAgrgOwD4AEAMABLgIwDcAsAFC4DMxJAbMQEwMDshA3tYX8fVLNcAFkIBZCAEtMACgCU3XvxXj0MCMAA2cAMowMAYxgB+QgDMpcLQBNCAXkKY4AdwnrNO/UZgLKVFVUPbT0DdGMzAAcIBAgwBydXdw0Q73DfeX9A/gA3GMJIhBl4BATnNwAFIswSgB41FK8wiIA+WUtrG0zlbMLixAA6KuLZBDhzApi47oDsguqSoYXR8cnYsBmVAF9qHapqOlZCYZrEWoAVFuoeWb5ziytbLP5Dk5LZe/NFexaHzoTzM8+IdRMdlmMJvdout5D0lLdCABIUgATnajxsJgG5wA9t4ZABzBSEADUf1sWNx+MwRMUZIARIRGSS4SpEdC4pS8QZCcSyRywFzqbTNvw9nsDvRYD5kp5Qj5rnDioQpECBIQcTlEEUbHAGEQqTyacSfoRZCSSVJ5NjudVaf4tkA=)

**Expected Behavior**:

```
12 12
34 34
```

**Actual Behavior**:

```
11 11
11 11
```

**Discussion**:

You can see the correct behaviour if you change all occurrences of `MultableStruct?` to `MutableStruct`. If you remove the `ref`, then `param` shows the correct behaviour.

When there is conditional access of an unconstrained generic field, something similar to the following gets emitted (in C#):

```csharp
ref T reference = ref field;
object result;
if (default(T) == null)
{
T val = reference;
reference = ref val;
if (val == null)
{
result = null;
goto done;
}
}
result = reference.ToString(); // Constrained virtual call
done:
```

This has separate code paths for when `T` is a reference or value type, as value types must not be copied (but cannot be null), but reference types must be checked for null (but the reference can be copied).

Unfortunately `default(Nullable)` boxes to `null`, so when `T` is a `Nullable`, we hit the code path meant for reference types, and copy it before calling `ToString()`.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.