New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Assignment not used #5128
Assignment not used #5128
Conversation
…merations of IEnumerable, and removes the need to explicitly create arrays at call sites.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we get a few test with an actual expression tree, e.g. nested conditionals and conditionals in loops?
RubberduckTests/Inspections/AssignmentNotUsedInspectionTests.cs
Outdated
Show resolved
Hide resolved
…ion and into DeclarationSymbolsResolver
case VBAParser.ElseBlockContext _: | ||
node = new BranchNode(tree); | ||
lastAssignment = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it really a concern of a code path analysis? This seems to be more specialized than the type suggests. When code path analysis API matures, this might need to be refactored into more specialized type. That said, I'm fine with merging it as-is as long it has a TODO
marker to note that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lastAssignment
is an AssignmentNode
whose Usages
grow with each ReferenceNode
objects as the tree is traversed - that said I need to add more tests, not sure this state reset for Else
blocks is covered yet.
How should we go about walking |
If the code path analysis doesn't handle the |
…ly reset the 'lastAssignment'.
/// <summary> | ||
/// Gets all nodes reading this assignment's value. | ||
/// </summary> | ||
public IEnumerable<INode> Usages => _usages; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be IEnumerable<ReferenceNode>
?
/// </summary> | ||
public IEnumerable<INode> Usages => _usages; | ||
|
||
internal void AddUsage(INode node) => _usages.Add(node); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AddUsage(ReferenceNode node)
?
ValueNode = value; | ||
} | ||
|
||
public AssignmentNode ValueNode { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: not used for AssignmentNotUsedInspection
- remove?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that this is the vanilla code path analysis API, I would definitely want to remove. I think the API itself needs to be made generic, which an inspection can then specialize for their own uses to keep the concerns clean, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDK, maybe I'm misunderstanding what the code path analysis API is supposed to do, but the way I see it, it's very much its job to create things like ReferenceNode
and AssignmentNode
- where AssignmentNode
has its Usages
already tracked, therefore where ReferenceNode
already knows what assignment/value it's referring to, i.e. the client code (e.g. inspections) doesn't need to further process the trees much.
I'll still remove it because YAGNI as far as this PR is concerned, but I feel like the boundaries of this API need to be discussed/clarified.
{ | ||
INode node = default; | ||
VBAParser.BooleanExpressionContext branchCondition; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assigned, but not used - feels confusing & wrong
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. A proper solution would be to have a true expression evaluation engine (which is a open issue ATM as we have 3 engines, each not working 100% to specs) but that's definitely beyond this PR's scope.
Something feels off with the way we're flattening the trees for each
var blocks = node.GetNodes(typeof(BlockNode));
foreach (var block in blocks)
{
var flattened = block.GetFlattenedNodes(typeof(GenericNode), typeof(BlockNode)); And then proceeds to yield the I'm thinking Public Sub DoSomething(ByVal foo As Long)
If foo > 10 Then
foo = 10
Else
foo = 0
End If
Debug.Print foo
End Sub Makes a tree like this (tracking
...and then |
|
||
public interface IExecutableNode : INode | ||
{ | ||
bool HasExecuted { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I'm not sure bool
is what we want here. If we need to measure the reachability, we need to also consider the conditionals. I'm thinking an flag bitmask is what we want:
Unreachable = 0,
Reachable = 1,
Conditional = 2,
Unconditional = 4,
Error = 8,
// Use those for assignment
ConditionallyReachable = Conditional | Reachable,
AlwaysReachable = Unconditional | Reachable,
ConditionalError = Conditional | Error
I also used the word reachable
rather than execute
because we are not literally executing; we are only assessing whether we could execute this node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See analogous class under Parsing: the flag is contextual - I've yet to define what's on IExecutionContext
but the idea is to have each code path carry its own context.
|
||
public class ReturnJumpNode : JumpNode<VBAParser.ReturnStmtContext> | ||
{ | ||
public ReturnJumpNode(VBAParser.ReturnStmtContext tree, JumpNode<VBAParser.GoSubStmtContext> origin) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be the statement immediately following the GoSub
, rather than the GoSub
itself?
var node = new ResumeJumpNode(tree, label?.Context); | ||
CurrentNode = node; | ||
} | ||
// todo: handle Resume Next? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I follow, this is handling the Resume <label>
case only. We can't resolve both Resume
and Resume Next
. Both have similar behavior, the primary difference being that Resume
repeats the same statement that failed whereas Resume Next
picks up from the next statement. However, in both cases, they don't return to same place; they could return to anywhere within the procedure because errors can happen anywhere. Thus, we don't have a defined return point for those 2 statement at the compile time, unlike the Resume <label>
.
I think we want to flag where Resume
and Resume Next
are used and simply presume that they will come from a reachable path without worrying about exactly where they will go back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything under Rubberduck.CodeAnalysis/CodePathAnalysis is going to end up deleted or rewritten - that said I'll be treating OnErrorGoTo
statements as jump nodes going to a label; I intend CPA to traverse them.
@@ -20,7 +20,7 @@ private IEnumerable<IInspectionResult> GetInspectionResults(string code, bool in | |||
using (var state = MockParser.CreateAndParse(vbe.Object)) | |||
{ | |||
|
|||
var inspection = new AssignmentNotUsedInspection(state, new Walker()); | |||
var inspection = new AssignmentNotUsedInspection(state, new ProcedureTreeVisitor()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't new things up - use the arrange pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My next PR will actually introduce a simple base class for inspection tests.
|
||
var inspection = new ExcelMemberMayReturnNothingInspection(state); | ||
var visitor = new ProcedureTreeVisitor(); | ||
var inspection = new ExcelMemberMayReturnNothingInspection(state, visitor); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise, don't new up things here. There are several new
s in the file that should be an arrange.
{ | ||
public partial class VBAParser | ||
{ | ||
public partial class ConstSubStmtContext : IIdentifierContext, IAnnotatedContext |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a thought - I notice that several of the classes have same code repeated for the IIdentifierContext
& IAnnottedContext
. Maybe consider delegating to a privately-backed class (or a privately-backed class for each interface) so that all public members are now public Foo => _backingField.Foo
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed the same thing, and it tickles. I think the problem is that the nodes need to implement the interfaces, since they are being cast to these interfaces in a couple of places.. and implementing CPA traversal will be much easier if nodes can be cast to (or filtered by) the CPA interfaces (e.g. IExecutableNode
) they're implementing.
For example there could easily be an ExecutableNodeImpl
class:
public class ExecutableNodeImpl : IExecutableNode
{
private readonly IDictionary<IExecutionContext, bool> _hasExecuted
= new Dictionary<IExecutionContext, bool>();
public bool HasExecuted(IExecutionContext context)
=> _hasExecuted.TryGetValue(context, out var value) && value;
public void Execute(IExecutionContext context)
=> _hasExecuted[context] = true;
}
And then the GoToStmtContext
extension could do this:
private readonly ExecutableNodeImpl _executableNode = new ExecutableNodeImpl();
But then, it's no longer implementing IJumpNode : IExecutableNode
, so it would need to define the members anyway.
I'm concluding that as long as we want/need tree nodes to implement interfaces, we'll have to put up with stupid-simple, but repetitive code in all the partial extension classes.
… DebugPrintStmt is skipped.
This is going to be WIP for a while; will re-open as a draft PR. |
Fixes known false positives by tracking and ignoring conditional assignments & references, and assignments made inside a loop body.