Roslyn: Surprising System.ValueTuple requirement for deconstruction

Created on 12 Apr 2017  路  15Comments  路  Source: dotnet/roslyn

Version Used: 2.1.0.61520

Steps to Reproduce:

Try to compile

using System;

static class Test
{
    static void Deconstruct(this DateTime date,
                            out int year, out int month, out int day)
    {
        year = date.Year;
        month = date.Month;
        day = date.Day;
    }

    static void Main()
    {
        (int year, int month, int day) = DateTime.UtcNow;
        Console.WriteLine(year);
        Console.WriteLine(month);
        Console.WriteLine(day);
    }
}

without a reference to System.ValueTuple.dll

Expected Behavior:

Compilation with no errors. No tuple literals or tuple types are involved.

Actual Behavior:

Error:

Test.cs(16,9): error CS8179: Predefined type 'System.ValueTuple`3' is not defined or imported

When compiling with a reference to System.ValueTuple.dll, the resulting IL has no reference to System.ValueTuple.dll, so why is it required at compile-time? This is very surprising.

There mention of this in #11299:

I assume deconstruction-assignment should work even if System.ValueTuple is not present. (answer: that is no longer the case, ValueTuple is required)

I haven't found any mention of that in the LDM meetings.

  • Is this likely to be permanent?
  • Any chance of an explanation of a seemingly-arbitrary requirement?
4 - In Review Area-Compilers Feature Request New Language Feature - Tuples Resolution-Fixed

Most helpful comment

Fixed by #20295 (in C# 7.2 and Visual Studio 2017 version 15.5).

All 15 comments

CC @jcouv

(Do let me know if filing this sort of strange picky issue isn't useful, btw. When writing, I tend to explore all kinds of corner cases. This one probably isn't as corner-case-like as most, admittedly - but some really will be. If it's useful to see what surprises more investigative users, that's great - but I certainly don't want to cause churn to no-one's benefit.)

@jskeet No problem with filing issues that may be questions. Please tag me on tuples and deconstruction questions for a faster triage.

The compiler doesn't emit any ValueTuple unless the return value of the deconstruction is used (for example, ((x, y) = (1, 2)).Item1).
Still, you can "see" tuples in the deconstruction. Technically, the left-hand-side in your example, (int year, int month, int day), __is a tuple__ of three declaration expressions, used in an assignment. This changed just before RTM (it used to be a deconstruction-declaration statement syntax, but we felt that declaration expressions would be more general for the future).
So we always require ValueTuple, because we don't know at the time of checking if it will be used. It's probably possible to relax this, but I don't plan to.

The requirement of ValueTuple even when the returned tuple is not used/emitted was discussed with LDM. I'm not sure if it was explicitly recorded. I did record it in the feature notes (which may serve you for inspiration/quirks): https://github.com/dotnet/roslyn/issues/11299

I assume deconstruction-assignment should work even if System.ValueTuple is not present. (answer: that is no longer the case, ValueTuple is required)

If this ever does come up for discussion again, I'd certainly like to urge the team to consider it as a useful change. It probably doesn't matter too much for application authors, but I spend all my time writing libraries - I try to keep a wide range of targets (e.g. down to netstandard 1.3) and I avoid adding dependencies as far as possible. At the moment, I can add Deconstruct methods for clients to use if they want, but the language feature will be unavailable to me :(

Re-opening the issue to track the request.
I think this is not a trivial change, as the binding for deconstruction prepares as tuple symbol (which checks the existence of ValueTuple types to use as underlying types).

I hit this too. I was using deconstruction in my Roslyn analyzer library (Microsoft/vs-threading) which still supports VS2015. I was disappointed to see that I have to use System.ValueTuple, which isn't supported on VS2015's version of Roslyn. So I changed all my ValueTuple use to Tuple and wrote my own Deconstruct extension methods, and the compilation fails because of the missing reference. Yet when I give it the reference, the resulting DLL doesn't retain that reference. So it's obviously not really needed, yet the compiler requiring it as input means that my project is super-fragile in that if anyone accidentally takes a real dependency on ValueTuple, the compiler will be happy, but VS2015 scenarios will break.

System.ValueTuple, which isn't supported on VS2015's version of Roslyn

@AArnott I didn't understand.
Roslyn 1.x (corresponding to Visual Studio 2015 and its updates) supports neither tuple, nor deconstruction.
System.ValueTuple is just a type and it can be used in C# 6 and older targets.

@jcouv: Roslyn 1.x does not guarantee that the System.ValueTuple assembly is around for an analyzer to load. Therefore, one cannot write an analyzer that both works with Roslyn 1.x and uses ValueTuple as an implementation detail of the analyzer. The unfortunate thing is that ValueTuple has nothing to do with deconstruction itself, and I should be able to write my own deconstructible types or deconstructing extension methods without a dependency on the System.ValueTuple assembly. But the compiler seems to have an extraneous dependency during build on it even if I'm not using it.

I build my analyzer with Roslyn 2.x, so I'm allowed to use deconstruction within it. And in fact my current workaround is to go ahead and reference System.ValueTuple during compilation of my analyzer, and then I have a unit test to ensure that the assembly reference is not retained in the compiled assembly so that it still works in Roslyn 1.x environments.

@AArnott Thanks for the clarification. Makes sense.

:memo: From discussion with @VSadov, I'm exploring how to bind the deconstruction to have a void type (instead of a tuple type) when the return value of the deconstruction expression isn't used. The binding for conditional access does something of that sort (inspecting parent syntax to decide if the result is used).

@jcouv would the semantic model still follow the language spec and give a tuple type?

Fix is in-progress (#20295). With that change, the returned tuple value is recognized as unused in an expression-statement and some positions of a for-statement.

@gafter Yes, the semantic model will still return the proper type in all cases. The only caveat is that the tuple type for the expression may have an error type as its underlying type instead of a proper ValueTuple (when it was missing).

Fixed by #20295 (in C# 7.2 and Visual Studio 2017 version 15.5).

Now I get to moan and say this has given me extra work to write about it in C# in Depth ;)

Seriously though - many thanks!

Nice!!

Now we should be able to deconstruct into spans.
Need to add a test for that when the change makes into the feature branch.

Specifically, spans are stack-only types and as such cannot be used to construct arrays/generics/tuples, etc...

The following should work:

```C#
Span s;
(s, s) = SomethingDeconstructableIntoTwoSpans();

The following should not work though:

```C#
Span<int> s;
var wouldHaveForbiddenType = (s, s) = SomethingDeconstructableIntoTwoSpans();
Was this page helpful?
0 / 5 - 0 ratings