Skip to content

Commit 8a3f6f6

Browse files
committed
Handle more identity.
1 parent d15f8e0 commit 8a3f6f6

8 files changed

Lines changed: 98 additions & 18 deletions

IDisposableAnalyzers.Test/Helpers/DisposableWalkerTests.Assigns.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,36 @@ private void M(IDisposable arg)
210210
Assert.AreEqual("N.C.Disposable", field.Symbol.ToString());
211211
}
212212

213+
[Test]
214+
public static void PropertyAssignedViaIdentity()
215+
{
216+
var code = @"
217+
namespace N
218+
{
219+
using System;
220+
221+
internal class C
222+
{
223+
internal C(IDisposable disposable)
224+
{
225+
this.Disposable = this.M(disposable);
226+
}
227+
228+
public IDisposable Disposable { get; private set; }
229+
230+
private void M(IDisposable arg) => arg;
231+
}
232+
}";
233+
var syntaxTree = CSharpSyntaxTree.ParseText(code);
234+
var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes());
235+
var semanticModel = compilation.GetSemanticModel(syntaxTree);
236+
var value = syntaxTree.FindParameter("IDisposable disposable");
237+
var symbol = semanticModel.GetDeclaredSymbol(value, CancellationToken.None);
238+
Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter));
239+
Assert.AreEqual(true, DisposableWalker.Assigns(localOrParameter, semanticModel, CancellationToken.None, out var field));
240+
Assert.AreEqual("N.C.Disposable", field.Symbol.ToString());
241+
}
242+
213243
[TestCase("Task.FromResult(File.OpenRead(fileName))")]
214244
[TestCase("Task.FromResult(File.OpenRead(fileName)).ConfigureAwait(true)")]
215245
[TestCase("Task.Run(() => File.OpenRead(fileName))")]
@@ -283,7 +313,7 @@ public void Dispose()
283313
var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes());
284314
var semanticModel = compilation.GetSemanticModel(syntaxTree);
285315
var value = syntaxTree.FindExpression("File.OpenRead(fileName)");
286-
Assert.AreEqual(true, DisposableWalker.Assigns(value, semanticModel, CancellationToken.None, out var fieldOrProperty));
316+
Assert.AreEqual(true, DisposableWalker.Assigns(value, semanticModel, CancellationToken.None, out var fieldOrProperty));
287317
Assert.AreEqual("disposable", fieldOrProperty.Name);
288318
}
289319
}

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Assigns.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ private static bool Assigns(ExpressionSyntax candidate, Recursion recursion, out
4848
return candidate switch
4949
{
5050
{ }
51-
when Identity(candidate, recursion) is { } id
52-
=> Assigns(id, recursion, out fieldOrProperty),
51+
when Identity(candidate, recursion) is { } id &&
52+
Assigns(id, recursion, out fieldOrProperty)
53+
=> true,
5354
{ Parent: AssignmentExpressionSyntax { Left: { } left, Right: { } right } }
5455
=> right.Contains(candidate) &&
5556
recursion.SemanticModel.TryGetSymbol(left, recursion.CancellationToken, out var assignedSymbol) &&

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.DisposedByReturnValue.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,6 @@ when reducedFrom.Parameters.TryFirst(out var parameter):
155155
using var walker = CreateUsagesWalker(target, recursion);
156156
foreach (var usage in walker.usages)
157157
{
158-
switch (usage.Parent.Kind())
159-
{
160-
case SyntaxKind.ReturnStatement:
161-
case SyntaxKind.ArrowExpressionClause:
162-
return true;
163-
}
164-
165158
if (Assigns(usage, recursion, out var fieldOrProperty) &&
166159
DisposableMember.IsDisposed(fieldOrProperty, target.Symbol.ContainingType, recursion.SemanticModel, recursion.CancellationToken).IsEither(Result.Yes, Result.AssumeYes))
167160
{

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Disposes.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,9 @@ private static bool Disposes(ExpressionSyntax candidate, Recursion recursion)
145145
{ Parent: EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax { UsingKeyword: { ValueText: "using" } } } } } }
146146
=> true,
147147
{ }
148-
when Identity(candidate, recursion) is { } id
149-
=> Disposes(id, recursion),
148+
when Identity(candidate, recursion) is { } id &&
149+
Disposes(id, recursion)
150+
=> true,
150151
{ Parent: ConditionalAccessExpressionSyntax { WhenNotNull: InvocationExpressionSyntax invocation } }
151152
=> IsDisposeOrReturnValueDisposed(invocation),
152153
{ Parent: MemberAccessExpressionSyntax { Parent: InvocationExpressionSyntax invocation } }

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Identity.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
namespace IDisposableAnalyzers
22
{
33
using Gu.Roslyn.AnalyzerExtensions;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp;
46
using Microsoft.CodeAnalysis.CSharp.Syntax;
57

68
internal sealed partial class DisposableWalker
@@ -12,6 +14,10 @@ internal sealed partial class DisposableWalker
1214
{ Parent: ArgumentSyntax { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax { Name: IdentifierNameSyntax { Identifier: { ValueText: "FromResult" } } } } invocation } } }
1315
when invocation.IsSymbol(KnownSymbol.Task.FromResult, recursion.SemanticModel, recursion.CancellationToken)
1416
=> Recursive(invocation, recursion),
17+
{ Parent: ArgumentSyntax { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax invocation } } argument }
18+
when recursion.Target(argument) is { } target &&
19+
IsIdentity(target, recursion)
20+
=> Recursive(invocation, recursion),
1521
{ Parent: AwaitExpressionSyntax parent }
1622
=> Recursive(parent, recursion),
1723
{ Parent: BinaryExpressionSyntax { OperatorToken: { ValueText: "as" } } parent }
@@ -37,6 +43,10 @@ when recursion.SemanticModel.TryGetNamedType(expression, recursion.CancellationT
3743
when recursion.SemanticModel.TryGetNamedType(expression, recursion.CancellationToken, out var type) &&
3844
type.IsAssignableTo(KnownSymbol.Task, recursion.SemanticModel.Compilation)
3945
=> Recursive(memberAccess, recursion),
46+
{ Parent: MemberAccessExpressionSyntax { Parent: InvocationExpressionSyntax invocation } }
47+
when recursion.Target(invocation) is { } target &&
48+
IsIdentity(target, recursion)
49+
=> Recursive(invocation, recursion),
4050
{ Parent: ReturnStatementSyntax returnStatement }
4151
when returnStatement.TryFirstAncestor(out LambdaExpressionSyntax? lambda) &&
4252
lambda is { Parent: ArgumentSyntax { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax invocation } } } &&
@@ -49,5 +59,47 @@ when returnStatement.TryFirstAncestor(out LambdaExpressionSyntax? lambda) &&
4959

5060
static ExpressionSyntax Recursive(ExpressionSyntax parent, Recursion recursion) => Identity(parent, recursion) ?? parent;
5161
}
62+
63+
private static bool IsIdentity<TSource, TSymbol, TNode>(Target<TSource, TSymbol, TNode> target, Recursion recursion)
64+
where TSource : SyntaxNode
65+
where TSymbol : ISymbol
66+
where TNode : SyntaxNode
67+
{
68+
switch (target.Symbol)
69+
{
70+
case IMethodSymbol { IsExtensionMethod: true, ReducedFrom: { } reducedFrom }
71+
when reducedFrom.Parameters.TryFirst(out var parameter):
72+
return IsIdentity(Target.Create(target.Source, parameter, target.TargetNode), recursion);
73+
case IFieldSymbol _:
74+
case IPropertySymbol _:
75+
return false;
76+
}
77+
78+
if (target.TargetNode is { })
79+
{
80+
using var walker = CreateUsagesWalker(target, recursion);
81+
foreach (var usage in walker.usages)
82+
{
83+
switch (usage.Parent.Kind())
84+
{
85+
case SyntaxKind.ReturnStatement:
86+
case SyntaxKind.ArrowExpressionClause:
87+
return true;
88+
}
89+
90+
if (Identity(usage, recursion) is { } id)
91+
{
92+
switch (id.Parent.Kind())
93+
{
94+
case SyntaxKind.ReturnStatement:
95+
case SyntaxKind.ArrowExpressionClause:
96+
return true;
97+
}
98+
}
99+
}
100+
}
101+
102+
return false;
103+
}
52104
}
53105
}

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Ignores.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ private static bool Ignores(ExpressionSyntax candidate, Recursion recursion)
3636
{ Parent: StatementSyntax _ }
3737
=> true,
3838
{ }
39-
when Identity(candidate, recursion) is { } id
40-
=> Ignores(id, recursion),
39+
when Identity(candidate, recursion) is { } id &&
40+
!Ignores(id, recursion)
41+
=> false,
4142
{ Parent: ArgumentSyntax { Parent: TupleExpressionSyntax tuple } }
4243
=> Ignores(tuple, recursion),
4344
{ Parent: ArgumentSyntax argument }

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Returns.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ private static bool Returns(ExpressionSyntax candidate, Recursion recursion)
5454
=> recursion.Target(variableDeclarator) is { } target &&
5555
Returns(target, recursion),
5656
{ }
57-
when Identity(candidate, recursion) is { } id
58-
=> Returns(id, recursion),
57+
when Identity(candidate, recursion) is { } id &&
58+
Returns(id, recursion)
59+
=> true,
5960
_ => false,
6061
};
6162
}

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Stores.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ private static bool Stores(ExpressionSyntax candidate, Recursion recursion, [Not
6161
when right.Contains(candidate):
6262
return recursion.SemanticModel.TryGetSymbol(element, recursion.CancellationToken, out container);
6363
case { }
64-
when Identity(candidate, recursion) is { } id:
65-
return Stores(id, recursion, out container);
64+
when Identity(candidate, recursion) is { } id &&
65+
Stores(id, recursion, out container):
66+
return true;
6667
case { Parent: ArgumentSyntax { Parent: ArgumentListSyntax { Parent: ObjectCreationExpressionSyntax _ } } argument }
6768
when recursion.Target(argument) is { } target:
6869
if (DisposedByReturnValue(target, recursion, out var objectCreation) ||

0 commit comments

Comments
 (0)