Repository navigation
Convert New ValueTuple(Of ...)(args) to a tuple literal - #7
Draft
GrahamTheCoder wants to merge 9 commits into
Draft
GrahamTheCoder wants to merge 9 commits into
GrahamTheCoder wants to merge 9 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
C# reports CS8181 for `new (int, int)(...)`, so an object creation of a tuple type now uses `new ValueTuple<int, int>(...)`. This makes every ValueTuple construction compile; simpler forms follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
Fixes icsharpcode#1027: `New ValueTuple(Of Double, Double)(1, 2)` now becomes `(1d, 2d)`. A tuple literal only takes its element types from a target type when it has one (not when assigned to var), so it's used only when every converted argument already has its element's type. Otherwise (boxing, int to int?, lambdas), and for 1-tuples, named arguments, initializers and expression trees (CS8143), the creation stays `new ValueTuple<...>(...)`. A TRest created inline is flattened, as C# lays out longer literals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
A struct's parameterless creation is its default value, and `default((int, string))` names the tuple type without constructing it, so it also simplifies to `default` where the type is inferred. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
`New ValueTuple(Of T1..T7, ValueTuple(Of T8))(..., New ValueTuple(Of T8)())` flattened to a 7-element literal, a different type. It now keeps the constructor, with the empty rest as `default`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
A VB tuple type always has a TupleUnderlyingType, and a literal-suffix conversion never has identical source and converted types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes icsharpcode#1027
Root cause
VisitObjectCreationExpressionconverted the VB type syntaxValueTuple(Of Double, Double)through the symbol-based type conversion, and Roslyn'sSyntaxGenerator.TypeExpressionwrites a tuple type with tuple syntax(double, double). C# forbids tuple syntax afternew(CS8181), sonew (double, double)(1d, 2d)didn't compile. Only object creation is affected:new (int, int)[3],typeof((int, int)),default((int, int))andList<(int, int)>are all valid C#.Design (simplest correct form first, uglier fallbacks only where needed)
ValueTupleCreationConverter(new) picks the form. If it declines,VisitObjectCreationExpressionnames the tuple type asValueTuple<...>(CommonConversions.GetValueTupleTypeSyntax), so every remaining case compiles.Return New ValueTuple(Of Double, Double)(1, 2)return (1d, 2d);Dim t = New ValueTuple(Of Long, Short)(x, s)(x Integer)var t = ((long)x, s);New ValueTuple(Of Double, Long, Byte)(1, 2, 3)(1d, 2L, (byte)3)New ValueTuple(Of Short, Integer)(s * s, s * s)((short)(s * s), s * s)New ValueTuple(Of String, Integer)(Nothing, Nothing)((string)null, default(int))New ValueTuple(Of Integer, String)(o, o)(Option Strict Off, o Object)(Conversions.ToInteger(o), Conversions.ToString(o))New ValueTuple(Of Colour, Integer)(c, c)/(Of Colour, Colour)(0, c)(c, (int)c)/((Colour)0, c)New ValueTuple(Of Object, Object)(s, 1)new ValueTuple<object, object>(s, 1)New ValueTuple(Of Integer?, Integer)(x, 1)new ValueTuple<int?, int>(x, 1)New ValueTuple(Of Func(Of Integer), Integer)(Function() x, 2)new ValueTuple<Func<int>, int>(() => x, 2)New ValueTuple(Of Integer, String)(item2:=s, item1:=x)new ValueTuple<int, string>(item2: s, item1: x)New ValueTuple(Of Integer, Integer) With {.Item1 = x}new ValueTuple<int, int>() { Item1 = x }New ValueTuple(Of Integer)(x)new ValueTuple<int>(x)New ValueTuple(Of Integer, String)()/ no parenthesesdefault((int, string))(defaultwhere target-typed)New ValueTuple(Of Integer)()default(ValueTuple<int>)New ValueTuple(Of 7×Integer, ValueTuple(Of Integer))(1, …, 7, New ValueTuple(Of Integer)(8))(1, 2, 3, 4, 5, 6, 7, 8)ValueTuple(Of Integer, Long)rest(8, 9)(1, …, 7, 8, 9L)ValueTuple(Of Integer)intoValueTuple(Of Long))new ValueTuple<…, ValueTuple<…>>(…)new ValueTuple<int, int>(x, 2)(tuple literals aren't allowed: CS8143)ValueTuple.Create(1, 2),New Tuple(Of Integer, Integer)(1, 2), arrays,GetType,List(Of (Integer, Integer))Literal rule. A tuple literal's element types come from its target type if it has one. With no target type (e.g.
var), they come from the elements. So a literal is used only when every converted argument already has its element's type. That'sTypeConversionAnalyzer.KeepsConvertedTypeWithoutTargetType: either the source type is the element type, or the conversion the analyzer adds is explicit (cast,Conversions.ToX, or an exactly typed literal such as1d). Implicit C# conversions (boxing,inttoint?, lambda to delegate) would give the literal a different type undervar, so those keep the constructor. When the literal is target-typed, the simplifier removes casts it doesn't need.Decisions (accepted)
new ValueTuple<object, object>(s, 1)rather than((object)s, (object)1). Target-typed contexts aren't detected, so they also keep the constructor.default((T1, T2))rather thannew ValueTuple<T1, T2>().Review
The blind review converged after 1 round: it found only nitpicks. Its quality points (a shared plain-tuple-creation check, and a plain
HasNoConversionbody) and its test gaps (Option Strict Off and enum elements) were addressed in separate commits.Out of scope (pre-existing, for separate issues)
New ValueTuple(Of Integer, Integer)(item2:=A(), item1:=B())runsBthenAin VB, and the converted C# runsAthenB. This affects every call with reordered named arguments. The self-verifying test here only asserts values for named arguments.(int)(short)1from existing literal conversions.Tests
ExpressionTests.cs:ValueTupleConstructorBecomesTupleLiteral,…BecomesTupleLiteralWithElementsOfTheTypeArguments,…WithConvertedElementsBecomesTupleLiteral,…WithoutArgumentsBecomesDefault,…WithInlineRestBecomesFlatTupleLiteral,…KeepsConstructorWhenTupleLiteralWouldChangeMeaning,…InExpressionTreeKeepsConstructor.Tests/TestData/SelfVerifyingTests/VBToCS/ValueTupleCreationTests.vb(10 facts). They check runtimeGetType()ofvar-inferred results, so a wrongly typed literal fails. They also check values, converted and enum elements, evaluation order, long tuples withRest, defaults, and an expression tree that is compiled and invoked.Coverage of changed lines
Measured with
--collect:"XPlat Code Coverage"on the tuple and self-verifying tests. Every changed line is executed. Partially covered branches:vbConvertedType is nullguard inKeepsConvertedTypeWithoutTargetType(error recovery only; it stopsEquals(null, null)from returning true);GetTupleElementsOrNull: a non-tuple type, and named arguments or no parentheses.Two branches that coverage showed to be unreachable were removed in their own commit. Mutation testing was not run.
🤖 Generated with Claude Code
https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg