提交 1b61db95 编写于 作者: C Cyrus Najmabadi

Do not convert to 'var' if it would change tuple names

上级 153fec7d
......@@ -2664,5 +2664,71 @@ void M()
}
}", options: ImplicitTypeEverywhere());
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExplicitType)]
[WorkItem(32088, "https://github.com/dotnet/roslyn/issues/32088")]
public async Task DoNotSuggestVarOnDeclarationExpressionWithInferredTupleNames()
{
await TestMissingAsync(
@"
using System.Collections.Generic;
using System.Linq;
static class Program
{
static void Main(string[] args)
{
if (!_data.TryGetValue(0, [|out List<(int X, int Y)>|] value))
return;
var x = value.FirstOrDefault().X;
}
private static Dictionary<int, List<(int, int)>> _data =
new Dictionary<int, List<(int, int)>>();
}", parameters: new TestParameters(options: ImplicitTypeEverywhere()));
}
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExplicitType)]
[WorkItem(32088, "https://github.com/dotnet/roslyn/issues/32088")]
public async Task DoSuggestVarOnDeclarationExpressionWithMatchingTupleNames()
{
await TestInRegularAndScriptAsync(
@"
using System.Collections.Generic;
using System.Linq;
static class Program
{
static void Main(string[] args)
{
if (!_data.TryGetValue(0, [|out List<(int X, int Y)>|] value))
return;
var x = value.FirstOrDefault().X;
}
private static Dictionary<int, List<(int X, int Y)>> _data =
new Dictionary<int, List<(int, int)>>();
}",
@"
using System.Collections.Generic;
using System.Linq;
static class Program
{
static void Main(string[] args)
{
if (!_data.TryGetValue(0, out var value))
return;
var x = value.FirstOrDefault().X;
}
private static Dictionary<int, List<(int X, int Y)>> _data =
new Dictionary<int, List<(int, int)>>();
}",
options: ImplicitTypeEverywhere());
}
}
}
......@@ -11,6 +11,9 @@
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Simplification;
using System;
using Microsoft.CodeAnalysis.Operations;
using Roslyn.Utilities;
#if CODE_STYLE
using OptionSet = Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions;
......@@ -160,7 +163,7 @@ protected override bool IsStylePreferred(in State state)
}
}
else if (typeName.Parent is DeclarationExpressionSyntax declarationExpression &&
TryAnalyzeDeclarationExpression(declarationExpression, semanticModel, optionSet, cancellationToken))
TryAnalyzeDeclarationExpression(declarationExpression, semanticModel, cancellationToken))
{
return true;
}
......@@ -171,7 +174,6 @@ protected override bool IsStylePreferred(in State state)
private bool TryAnalyzeDeclarationExpression(
DeclarationExpressionSyntax declarationExpression,
SemanticModel semanticModel,
OptionSet optionSet,
CancellationToken cancellationToken)
{
// It's not always safe to convert a decl expression like "Method(out int i)" to
......@@ -180,21 +182,9 @@ protected override bool IsStylePreferred(in State state)
// Note: this is fairly expensive, so we try to avoid this if we can by seeing if
// there are multiple candidates with the original call. If not, then we don't
// have to do anything.
if (declarationExpression.Parent is ArgumentSyntax argument &&
argument.Parent is ArgumentListSyntax argumentList &&
argumentList.Parent is InvocationExpressionSyntax invocationExpression)
if (IsMatchingArgToSimpleMethod(declarationExpression, semanticModel, cancellationToken))
{
// If there was only one member in the group, and it was non-generic itself,
// then this change is safe to make without doing any complex analysis.
// Multiple methods mean that switching to 'var' might remove information
// that affects overload resolution. And if the method is generic, then
// switching to 'var' may mean that inference might not work properly.
var memberGroup = semanticModel.GetMemberGroup(invocationExpression.Expression, cancellationToken);
if (memberGroup.Length == 1 &&
memberGroup[0].GetTypeParameters().IsEmpty)
{
return true;
}
return true;
}
if (!semanticModel.SyntaxTree.HasCompilationUnitRoot)
......@@ -228,6 +218,46 @@ protected override bool IsStylePreferred(in State state)
return SymbolEquivalenceComparer.Instance.Equals(declarationType, newDeclarationType);
}
private bool IsMatchingArgToSimpleMethod(DeclarationExpressionSyntax declarationExpression, SemanticModel semanticModel, CancellationToken cancellationToken)
{
if (!(declarationExpression.Parent is ArgumentSyntax argument) ||
!(argument.Parent is ArgumentListSyntax argumentList) ||
!(argumentList.Parent is InvocationExpressionSyntax invocationExpression))
return false;
// If there was only one member in the group, and it was non-generic itself,
// then this change is safe to make without doing any complex analysis.
// Multiple methods mean that switching to 'var' might remove information
// that affects overload resolution. And if the method is generic, then
// switching to 'var' may mean that inference might not work properly.
var memberGroup = semanticModel.GetMemberGroup(invocationExpression.Expression, cancellationToken);
if (memberGroup.Length != 1)
return false;
var method = memberGroup[0] as IMethodSymbol;
if (method == null)
return false;
if (!method.GetTypeParameters().IsEmpty)
return false;
var invocationOp = semanticModel.GetOperation(invocationExpression) as IInvocationOperation;
if (invocationOp == null)
return false;
var argumentOp = invocationOp.Arguments.FirstOrDefault(a => a.Syntax == argument);
if (argumentOp == null)
return false;
if (argumentOp.Value?.Type == null)
return false;
if (argumentOp.Parameter?.Type == null)
return false;
return argumentOp.Value.Type.Equals(argumentOp.Parameter.Type);
}
/// <summary>
/// Analyzes the assignment expression and rejects a given declaration if it is unsuitable for implicit typing.
/// </summary>
......
......@@ -24,7 +24,8 @@ private class EquivalenceVisitor
public EquivalenceVisitor(
SymbolEquivalenceComparer symbolEquivalenceComparer,
bool compareMethodTypeParametersByIndex,
bool objectAndDynamicCompareEqually)
bool objectAndDynamicCompareEqually,
bool tupleNamesMustMatch)
{
_symbolEquivalenceComparer = symbolEquivalenceComparer;
_compareMethodTypeParametersByIndex = compareMethodTypeParametersByIndex;
......
......@@ -39,39 +39,48 @@ namespace Microsoft.CodeAnalysis.Shared.Utilities
internal partial class SymbolEquivalenceComparer :
IEqualityComparer<ISymbol>
{
private readonly ImmutableArray<EquivalenceVisitor> _equivalenceVisitors;
private readonly ImmutableArray<GetHashCodeVisitor> _getHashCodeVisitors;
private readonly EquivalenceVisitor[] _equivalenceVisitors;
private readonly GetHashCodeVisitor[] _getHashCodeVisitors;
public static readonly SymbolEquivalenceComparer Instance = new SymbolEquivalenceComparer(SimpleNameAssemblyComparer.Instance, distinguishRefFromOut: false);
public static readonly SymbolEquivalenceComparer IgnoreAssembliesInstance = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: false);
public static readonly SymbolEquivalenceComparer Instance = new SymbolEquivalenceComparer(SimpleNameAssemblyComparer.Instance, distinguishRefFromOut: false, tupleNamesMustMatch: false);
public static readonly SymbolEquivalenceComparer TupleNamesMustMatchInstance = new SymbolEquivalenceComparer(SimpleNameAssemblyComparer.Instance, distinguishRefFromOut: false, tupleNamesMustMatch: true);
public static readonly SymbolEquivalenceComparer IgnoreAssembliesInstance = new SymbolEquivalenceComparer(assemblyComparerOpt: null, distinguishRefFromOut: false, tupleNamesMustMatch: false);
private readonly IEqualityComparer<IAssemblySymbol> _assemblyComparerOpt;
public ParameterSymbolEqualityComparer ParameterEquivalenceComparer { get; }
public SignatureTypeSymbolEquivalenceComparer SignatureTypeEquivalenceComparer { get; }
internal SymbolEquivalenceComparer(IEqualityComparer<IAssemblySymbol> assemblyComparerOpt, bool distinguishRefFromOut)
internal SymbolEquivalenceComparer(
IEqualityComparer<IAssemblySymbol> assemblyComparerOpt,
bool distinguishRefFromOut,
bool tupleNamesMustMatch)
{
_assemblyComparerOpt = assemblyComparerOpt;
_tupleNamesMustMatch =
this.ParameterEquivalenceComparer = new ParameterSymbolEqualityComparer(this, distinguishRefFromOut);
this.SignatureTypeEquivalenceComparer = new SignatureTypeSymbolEquivalenceComparer(this);
// There are only so many EquivalenceVisitors and GetHashCodeVisitors we can have.
// Create them all up front.
var equivalenceVisitorsBuilder = ImmutableArray.CreateBuilder<EquivalenceVisitor>();
equivalenceVisitorsBuilder.Add(new EquivalenceVisitor(this, compareMethodTypeParametersByIndex: true, objectAndDynamicCompareEqually: true));
equivalenceVisitorsBuilder.Add(new EquivalenceVisitor(this, compareMethodTypeParametersByIndex: true, objectAndDynamicCompareEqually: false));
equivalenceVisitorsBuilder.Add(new EquivalenceVisitor(this, compareMethodTypeParametersByIndex: false, objectAndDynamicCompareEqually: true));
equivalenceVisitorsBuilder.Add(new EquivalenceVisitor(this, compareMethodTypeParametersByIndex: false, objectAndDynamicCompareEqually: false));
_equivalenceVisitors = equivalenceVisitorsBuilder.ToImmutable();
var getHashCodeVisitorsBuilder = ImmutableArray.CreateBuilder<GetHashCodeVisitor>();
getHashCodeVisitorsBuilder.Add(new GetHashCodeVisitor(this, compareMethodTypeParametersByIndex: true, objectAndDynamicCompareEqually: true));
getHashCodeVisitorsBuilder.Add(new GetHashCodeVisitor(this, compareMethodTypeParametersByIndex: true, objectAndDynamicCompareEqually: false));
getHashCodeVisitorsBuilder.Add(new GetHashCodeVisitor(this, compareMethodTypeParametersByIndex: false, objectAndDynamicCompareEqually: true));
getHashCodeVisitorsBuilder.Add(new GetHashCodeVisitor(this, compareMethodTypeParametersByIndex: false, objectAndDynamicCompareEqually: false));
_getHashCodeVisitors = getHashCodeVisitorsBuilder.ToImmutable();
GetEquivalenceVisitor(false, false, false);
GetEquivalenceVisitor(false, false, true);
GetEquivalenceVisitor(false, true, false);
GetEquivalenceVisitor(false, true, true);
GetEquivalenceVisitor(true, false, false);
GetEquivalenceVisitor(true, false, true);
GetEquivalenceVisitor(true, true, false);
GetEquivalenceVisitor(true, true, true);
GetGetHashCodeVisitor(false, false, false);
GetGetHashCodeVisitor(false, false, true);
GetGetHashCodeVisitor(false, true, false);
GetGetHashCodeVisitor(false, true, true);
GetGetHashCodeVisitor(true, false, false);
GetGetHashCodeVisitor(true, false, true);
GetGetHashCodeVisitor(true, true, false);
GetGetHashCodeVisitor(true, true, true);
}
// Very subtle logic here. When checking if two parameters are the same, we can end up with
......@@ -82,44 +91,33 @@ internal SymbolEquivalenceComparer(IEqualityComparer<IAssemblySymbol> assemblyCo
// here. So, instead, when asking if parameters are equal, we pass an appropriate flag so
// that method type parameters are just compared by index and nothing else.
private EquivalenceVisitor GetEquivalenceVisitor(
bool compareMethodTypeParametersByIndex = false, bool objectAndDynamicCompareEqually = false)
bool compareMethodTypeParametersByIndex = false,
bool objectAndDynamicCompareEqually = false,
bool tupleNamesMustMatch = false)
{
var visitorIndex = GetVisitorIndex(compareMethodTypeParametersByIndex, objectAndDynamicCompareEqually);
var visitorIndex = GetVisitorIndex(
compareMethodTypeParametersByIndex, objectAndDynamicCompareEqually, tupleNamesMustMatch);
return _equivalenceVisitors[visitorIndex];
}
private GetHashCodeVisitor GetGetHashCodeVisitor(
bool compareMethodTypeParametersByIndex, bool objectAndDynamicCompareEqually)
bool compareMethodTypeParametersByIndex = false,
bool objectAndDynamicCompareEqually = false,
bool tupleNamesMustMatch = false)
{
var visitorIndex = GetVisitorIndex(compareMethodTypeParametersByIndex, objectAndDynamicCompareEqually);
var visitorIndex = GetVisitorIndex(
compareMethodTypeParametersByIndex, objectAndDynamicCompareEqually, tupleNamesMustMatch);
return _getHashCodeVisitors[visitorIndex];
}
private static int GetVisitorIndex(
bool compareMethodTypeParametersByIndex, bool objectAndDynamicCompareEqually)
bool compareMethodTypeParametersByIndex,
bool objectAndDynamicCompareEqually,
bool tupleNamesMustMatch)
{
if (compareMethodTypeParametersByIndex)
{
if (objectAndDynamicCompareEqually)
{
return 0;
}
else
{
return 1;
}
}
else
{
if (objectAndDynamicCompareEqually)
{
return 2;
}
else
{
return 3;
}
}
return ((compareMethodTypeParametersByIndex ? 1 : 0) << 2) |
((objectAndDynamicCompareEqually ? 1 : 0) << 1) |
((tupleNamesMustMatch ? 1 : 0) << 0);
}
public bool ReturnTypeEquals(IMethodSymbol x, IMethodSymbol y, Dictionary<INamedTypeSymbol, INamedTypeSymbol> equivalentTypesWithDifferingAssemblies = null)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册