Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.UseDeconstruction;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
Expand Down Expand Up @@ -481,7 +482,7 @@ void M()
(string name, int age) [|person|] = default;
Console.WriteLine(person.name + "" "" + person.age);
}
}");
}", new TestParameters(parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_1)));
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDeconstruction)]
Expand Down Expand Up @@ -610,6 +611,28 @@ void M()
}");
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDeconstruction)]
public async Task TestWithTupleLiteralConversion()
{
await TestInRegularAndScriptAsync(
@"class C
{
void M()
{
(object name, double age) [|person|] = (null, 0);
Console.WriteLine(person.name + "" "" + person.age);
}
}",
@"class C
{
void M()
{
(object name, double age) = (null, 0);
Console.WriteLine(name + "" "" + age);
}
}");
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseDeconstruction)]
public async Task TestWithImplicitTupleConversion()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@ public static bool TryAnalyzeVariableDeclaration(
}

var local = (ILocalSymbol)semanticModel.GetDeclaredSymbol(declarator, cancellationToken);
var expressionType = semanticModel.GetTypeInfo(declarator.Initializer.Value, cancellationToken).Type;
var initializerConversion = semanticModel.GetConversion(declarator.Initializer.Value, cancellationToken);

return TryAnalyze(
semanticModel, local, variableDeclaration.Type, declarator.Identifier, expressionType,
semanticModel, local, variableDeclaration.Type, declarator.Identifier, initializerConversion,
variableDeclaration.Parent.Parent, out tupleType, out memberAccessExpressions, cancellationToken);
}

Expand All @@ -140,10 +140,10 @@ public static bool TryAnalyzeForEachStatement(
CancellationToken cancellationToken)
{
var local = semanticModel.GetDeclaredSymbol(forEachStatement, cancellationToken);
var elementType = semanticModel.GetForEachStatementInfo(forEachStatement).ElementType;
var elementConversion = semanticModel.GetForEachStatementInfo(forEachStatement).ElementConversion;

return TryAnalyze(
semanticModel, local, forEachStatement.Type, forEachStatement.Identifier, elementType,
semanticModel, local, forEachStatement.Type, forEachStatement.Identifier, elementConversion,
forEachStatement, out tupleType, out memberAccessExpressions, cancellationToken);
}

Expand All @@ -152,7 +152,7 @@ private static bool TryAnalyze(
ILocalSymbol local,
TypeSyntax typeNode,
SyntaxToken identifier,
ITypeSymbol initializerType,
Conversion conversion,
SyntaxNode searchScope,
out INamedTypeSymbol tupleType,
out ImmutableArray<MemberAccessExpressionSyntax> memberAccessExpressions,
Expand All @@ -171,9 +171,21 @@ private static bool TryAnalyze(
return false;
}

if (conversion.Exists &&
!conversion.IsIdentity &&
!conversion.IsTupleConversion &&
!conversion.IsTupleLiteralConversion)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably would benefit from a comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment added

{
// If there is any other conversion, we bail out because the source type might not be a tuple
// or it is a tuple but only thanks to target type inference, which won't occur in a deconstruction.
// Interesting case that illustrates this is initialization with a default literal:
// (int a, int b) t = default;
// This is classified as conversion.IsNullLiteral.
return false;
}

var type = semanticModel.GetTypeInfo(typeNode, cancellationToken).Type;
if (type == null || !type.IsTupleType ||
initializerType == null || !initializerType.IsTupleType)
if (type == null || !type.IsTupleType)
{
return false;
}
Expand Down