Skip to content
Open
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 @@ -19,12 +19,15 @@
<location>Test0.cs: (10,52-58)</location>
</diagnostic>
<diagnostic>
<location>Test0.cs: (12,40-43)</location>
<location>Test0.cs: (11,53-59)</location>
</diagnostic>
<diagnostic>
<location>Test0.cs: (12,56-59)</location>
<location>Test0.cs: (13,40-43)</location>
</diagnostic>
<diagnostic>
<location>Test0.cs: (12,72-76)</location>
<location>Test0.cs: (13,56-59)</location>
</diagnostic>
<diagnostic>
<location>Test0.cs: (13,72-76)</location>
</diagnostic>
</diagnostics>
3 changes: 2 additions & 1 deletion src/WTG.Analyzers.Test/TestData/VarAnalyzer/Out/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class Bob
public void V3() => Generic(1, out var value);
public void V4() => Generic(TryGet("key", out var value1), out var value2);
public void V5() => Lookup.TryGetValue("key", out var value);
public void V6() => Lookup?.TryGetValue("key", out var value);

public void S4() => SemiAmbiguous(out var value1, out int value2, out var value3); // value1 and value2 cannot both be var as that would make it ambiguous.

Expand All @@ -30,5 +31,5 @@ public void N()
void Generic<T>(out T value) where T : struct => value = default(T);
void Generic<T>(T inValue, out T outValue) => outValue = inValue;
bool TryGet(string key, out string value) => (value = key) != null;
Dictionary<string, string> Lookup { get; } = new Dictionary<string, string>();
Dictionary<string, string>? Lookup { get; } = new Dictionary<string, string>();
}
3 changes: 2 additions & 1 deletion src/WTG.Analyzers.Test/TestData/VarAnalyzer/Out/Source.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class Bob
public void V3() => Generic(1, out int value);
public void V4() => Generic(TryGet("key", out string value1), out bool value2);
public void V5() => Lookup.TryGetValue("key", out string value);
public void V6() => Lookup?.TryGetValue("key", out string value);

public void S4() => SemiAmbiguous(out int value1, out int value2, out bool value3); // value1 and value2 cannot both be var as that would make it ambiguous.

Expand All @@ -30,5 +31,5 @@ public void N()
void Generic<T>(out T value) where T : struct => value = default(T);
void Generic<T>(T inValue, out T outValue) => outValue = inValue;
bool TryGet(string key, out string value) => (value = key) != null;
Dictionary<string, string> Lookup { get; } = new Dictionary<string, string>();
Dictionary<string, string>? Lookup { get; } = new Dictionary<string, string>();
}
27 changes: 25 additions & 2 deletions src/WTG.Analyzers/Analyzers/Var/VarAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,31 @@ static void AnalyzeInvoke(SyntaxNodeAnalysisContext context, FileDetailCache cac
}
}

var proposedSyntax = invoke.ReplaceNode(type, SyntaxFactory.IdentifierName("var").WithTriviaFrom(type));
var symbol = context.SemanticModel.GetSpeculativeSymbolInfo(invoke.SpanStart, proposedSyntax, SpeculativeBindingOption.BindAsExpression).Symbol;
var proposedInvoke = invoke.ReplaceNode(type, SyntaxFactory.IdentifierName("var").WithTriviaFrom(type));

ExpressionSyntax speculativeExpression;
int speculativePosition;

if (invoke.Expression is MemberBindingExpressionSyntax memberBinding
&& invoke.Parent is ConditionalAccessExpressionSyntax conditionalAccess)
{
// For conditional access (e.g. obj?.Method(out Type x)), the invocation node alone
// does not have enough context for speculative binding (NRE). Rewrite as a regular
// member access expression using the conditional access target.
var memberAccess = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
conditionalAccess.Expression,
memberBinding.Name);
speculativeExpression = proposedInvoke.WithExpression(memberAccess);
speculativePosition = conditionalAccess.SpanStart;
}
else
{
speculativeExpression = proposedInvoke;
speculativePosition = invoke.SpanStart;
}

var symbol = context.SemanticModel.GetSpeculativeSymbolInfo(speculativePosition, speculativeExpression, SpeculativeBindingOption.BindAsExpression).Symbol;

if (SymbolEqualityComparer.Default.Equals(knownMethod, symbol))
{
Expand Down
22 changes: 21 additions & 1 deletion src/WTG.Analyzers/Analyzers/Var/VarFixAllProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,27 @@ static InvocationExpressionSyntax ReplaceTypes(SemanticModel model, InvocationEx
{
index = GetIndex(originalArguments, types[i]);
var proposedInvoke = VarifyOutTypeAtIndex(targetInvoke, index);
var actualSymbol = (IMethodSymbol?)model.GetSpeculativeSymbolInfo(originalInvoke.SpanStart, proposedInvoke, SpeculativeBindingOption.BindAsExpression).Symbol;

ExpressionSyntax speculativeExpression;
int speculativePosition;

if (originalInvoke.Expression is MemberBindingExpressionSyntax memberBinding
&& originalInvoke.Parent is ConditionalAccessExpressionSyntax conditionalAccess)
{
var memberAccess = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
conditionalAccess.Expression,
memberBinding.Name);
speculativeExpression = proposedInvoke.WithExpression(memberAccess);
speculativePosition = conditionalAccess.SpanStart;
}
else
{
speculativeExpression = proposedInvoke;
speculativePosition = originalInvoke.SpanStart;
}

var actualSymbol = (IMethodSymbol?)model.GetSpeculativeSymbolInfo(speculativePosition, speculativeExpression, SpeculativeBindingOption.BindAsExpression).Symbol;

if (SymbolEqualityComparer.Default.Equals(requiredSymbol, actualSymbol))
{
Expand Down
Loading