03/10/2025 –, Amphithéâtre Jean-Baptiste Say
Langue: English
Static analysis improves software quality by identifying problematic patterns in code. Julia provides static analysis tooling through packages such as JET.jl and StaticLint.jl. This presentation introduces Argus.jl, a JuliaSyntax
-based package that proposes a new approach to static analysis in Julia. It draws inspiration from the Racket libraries syntax/parse
and resyntax
.
Argus implements a framework for writing static analysis rules on top of a syntax matching mechanism. It is structured around several core concepts:
- Syntax patterns
- Pattern variables
- Syntax classes
- Rules
Syntax patterns form the basis for syntax matching and closely resemble Julia code. For example, @pattern x = 2
matches an assignment where the left-hand side is a variable named x
and the right-hand side is the literal 2
. On the other hand, @pattern _x = 2
matches any assignment or short-form function definition where the right-hand side is the literal 2
; the expression on the left-hand side is bound to the pattern variable _x
.
A pattern variable is one of several special forms permitted within patterns. It can be seen as a "hole" that is filled by matching syntax. For example, when matching @pattern _x = 2
against the expression f(a) = 2
, _x
is bound to f(a)
. The result of a pattern match is either a set of bindings corresponding to the syntax matched by each pattern variable, or an error explaining why the matching failed.
A pattern variable can be constrained by a syntax class. In the example above, @pattern _x = 2
is equivalent to @pattern _x:::expr = 2
, where expr
is the syntax class that matches any expression. Syntax classes are defined through patterns and can reference other syntax classes. For example, a syntax class matching any assignment may be defined as such:
assign = @syntax_class "assignment" begin
@pattern _lhs:::identifier = _rhs
end
Argus provides a set of pre-defined syntax classes, including expr
, identifier
and assign
.
A rule contains a description and a pattern. In the case of rules, matching recursively traverses a given unit of source code (e.g. a file) and collects the sub-expressions that match the rule's pattern. Pattern variables bound by these identifications are returned in corresponding binding sets.
Rules may be organised into rule groups. For example, it may be useful to group all rules related to Julia usage in a lang
group:
lang_rules = RuleGroup("lang")
@define_rule_in_group lang_rules "compare-nothing" begin
description = """
Comparisons to `nothing` should use `===`, `!==` or `isnothing`.
"""
pattern = @pattern begin
~or(
nothing == _,
_ == nothing,
nothing != _,
_ != nothing
)
end
end
This presentation is an opportunity to share Argus with the community and exchange feedback and ideas for further improvement.