Skip to content

Unified: Add unused-variable query - #22657

Open
asgerf wants to merge 8 commits into
github:mainfrom
asgerf:unified/unused-variable
Open

asgerf wants to merge 8 commits into
github:mainfrom
asgerf:unified/unused-variable

Conversation

@asgerf

@asgerf asgerf commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

This query has a lot of FPs currently but is great at pointing out bugs in our analysis. The PR also fixes one of these bugs, there are many more to find.

@asgerf asgerf added the no-change-note-required This PR does not need a change note label Sep 23, 2026

from LocalName local
where isUnusedLocal(local)
select local, "Unused " + getKind(local) + " '" + local.getName() + "'"
The special-cased labelExpr rules were problematic.
They are now coveered by a combination of more general rules.
@asgerf
asgerf force-pushed the unified/unused-variable branch from f9e102a to 84fe0cc Compare September 23, 2026 08:29
@asgerf
asgerf marked this pull request as ready for review September 23, 2026 12:31
@asgerf
asgerf requested review from a team as code owners September 23, 2026 12:31
Copilot AI balanced review requested due to automatic review settings September 23, 2026 12:31

Copilot AI left a comment

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.

Copilot review overview

🟡 Changes recommended

The query has misleading precision metadata and incorrectly treats plain assignment targets as reads.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Adds a Unified unused-variable query and fixes Swift AST extraction for patterns and nested trailing closures.

Changes:

  • Adds unused-variable query logic and regression tests.
  • Improves Swift pattern and argument translation.
  • Updates extractor and dataflow expectations.
File Description
unified/​ql/​test/​query-tests/​unusedentities/​UnusedVariable.qlref Registers the query test.
unified/​ql/​test/​query-tests/​unusedentities/​UnusedVariable.expected Records expected query results.
unified/​ql/​test/​query-tests/​unusedentities/​test.swift Provides query regression cases.
unified/​ql/​test/​library-tests/​dataflow/​test.expected Updates generated dataflow expectations.
unified/​ql/​src/​queries/​unusedentities/​UnusedVariable.ql Implements the unused-variable query.
unified/​extractor/​tests/​corpus/​swift/​control-flow/​nested-enum-case-pattern.output Updates generated pattern output.
unified/​extractor/​tests/​corpus/​swift/​closures/​nested-trailing-closure.swift Adds nested trailing-closure coverage.
unified/​extractor/​tests/​corpus/​swift/​closures/​nested-trailing-closure.output Records generated AST output.
unified/​extractor/​src/​languages/​swift/​swift.rs Fixes Swift pattern and argument translation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

* @kind problem
* @problem.severity recommendation
* @id unified/unused-variable
* @precision high

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.

I tend to agree - and past experience suggests it takes quite a lot of effort to get the false positive rate on an unused variable query to an acceptable level for high precision.

Suggested change
* @precision high
* @precision medium

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.

I think this was perhaps to ensure that the query is included in the default DCA suite?

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.

What @hvitved said. In prod this would not be high and in its current state not even medium. But right now we just need to make sure the alerts are seen.

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.

OK.

callback()
return "df"
}
// Note: This currently fails because the trailing closure is not extracted correctly
private predicate isUnusedLocal(LocalName local) {
local.getABinding().fromSource() and // ignore unused implicit locals, and ignore locals in built-ins
not ignoreDeclaration(local.getABinding().getDeclaration()) and // ignore fields, method, etc
not local.getName().regexpMatch("_.*") and

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.

not local.getName().charAt(0) = "_".

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.

Isn't regexpMatch more efficient though? I seem to recall we did a mass replacement of string matching expressions to regexpMatch and I've just defaulted to using it since.


private import unified

private predicate isUnusedLocal(LocalName local) {

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.

Why not use LocalVariable instead of LocalName? Then it should not be necessary to filter away fields and methods.

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.

Mainly because it's a debugging query and we don't have the corresponding UnusedType.ql. It would also mean we don't catch things like unused local type aliases, or if there's a bug affecting the LocalVariable charpred it might not be revealed by this query.

access = local.getAnAccess() and
not access instanceof NameBinding
) and
not local = any(UnqualifiedMemberAccess access).getImplicitQualifierVariable()

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.

With #22666 this can instead be a call to isImplicitReceiverParameter; that should also eliminate FPs when a function doesn't have any implicit self receivers.

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.

Implicit variables are always ignored so I'm not sure which FPs you mean?

Anyway, with this formulation we can detect FPs from guard let self or capture-declarations [self] without a subsequent use of self which is actually a nice stress test of the machinery around self.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-change-note-required This PR does not need a change note

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants