Skip to content

Commit 30902d0

Browse files
authored
Merge pull request #320 from BenPH/skip-bindings-for-assignments-in-@__dot__
Skip bindings for assignments in @.
2 parents 3351bc9 + ea81849 commit 30902d0

File tree

4 files changed

+41
-8
lines changed

4 files changed

+41
-8
lines changed

src/StaticLint.jl

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ Meta() = Meta(nothing, nothing, nothing, nothing)
2727

2828
function Base.show(io::IO, m::Meta)
2929
m.binding !== nothing && show(io, m.binding)
30-
m.ref !== nothing && printstyled(io, " * ", color=:red)
31-
m.scope !== nothing && printstyled(io, " new scope", color=:green)
32-
m.error !== nothing && printstyled(io, " lint ", color=:red)
30+
m.ref !== nothing && printstyled(io, " * ", color = :red)
31+
m.scope !== nothing && printstyled(io, " new scope", color = :green)
32+
m.error !== nothing && printstyled(io, " lint ", color = :red)
3333
end
3434
hasmeta(x::EXPR) = x.meta isa Meta
3535
hasbinding(m::Meta) = m.binding isa Binding
@@ -61,8 +61,12 @@ mutable struct Toplevel{T} <: State
6161
resolveonly::Vector{EXPR}
6262
env::ExternalEnv
6363
server
64+
flags::Int
6465
end
6566

67+
Toplevel(file, included_files, scope, in_modified_expr, modified_exprs, delayed, resolveonly, env, server) =
68+
Toplevel(file, included_files, scope, in_modified_expr, modified_exprs, delayed, resolveonly, env, server, 0)
69+
6670
function (state::Toplevel)(x::EXPR)
6771
resolve_import(x, state)
6872
mark_bindings!(x, state)
@@ -84,7 +88,9 @@ function (state::Toplevel)(x::EXPR)
8488
push!(state.resolveonly, x)
8589
end
8690
else
91+
old = flag!(state, x)
8792
traverse(x, state)
93+
state.flags = old
8894
end
8995

9096
state.in_modified_expr = old_in_modified_expr
@@ -96,8 +102,11 @@ mutable struct Delayed <: State
96102
scope::Scope
97103
env::ExternalEnv
98104
server
105+
flags::Int
99106
end
100107

108+
Delayed(scope, env, server) = Delayed(scope, env, server, 0)
109+
101110
function (state::Delayed)(x::EXPR)
102111
mark_bindings!(x, state)
103112
add_binding(x, state)
@@ -106,7 +115,9 @@ function (state::Delayed)(x::EXPR)
106115
s0 = scopes(x, state)
107116
resolve_ref(x, state)
108117

118+
old = flag!(state, x)
109119
traverse(x, state)
120+
state.flags = old
110121
if state.scope != s0
111122
for b in values(state.scope.names)
112123
infer_type_by_use(b, state.env)
@@ -139,13 +150,23 @@ function (state::ResolveOnly)(x::EXPR)
139150
return state.scope
140151
end
141152

153+
# feature flags that can disable or enable functionality further down in the CST
154+
const NO_NEW_BINDINGS = 0x1
155+
156+
function flag!(state, x::EXPR)
157+
old = state.flags
158+
if CSTParser.ismacrocall(x) && (valof(x.args[1]) == "@." || valof(x.args[1]) == "@__dot__")
159+
state.flags |= NO_NEW_BINDINGS
160+
end
161+
return old
162+
end
142163

143164
"""
144165
semantic_pass(file, modified_expr=nothing)
145166
146167
Performs a semantic pass across a project from the entry point `file`. A first pass traverses the top-level scope after which secondary passes handle delayed scopes (e.g. functions). These secondary passes can be, optionally, very light and only seek to resovle references (e.g. link symbols to bindings). This can be done by supplying a list of expressions on which the full secondary pass should be made (`modified_expr`), all others will receive the light-touch version.
147168
"""
148-
function semantic_pass(file, modified_expr=nothing)
169+
function semantic_pass(file, modified_expr = nothing)
149170
server = file.server
150171
env = getenv(file, server)
151172
setscope!(getcst(file), Scope(nothing, getcst(file), Dict(), Dict{Symbol,Any}(:Base => env.symbols[:Base], :Core => env.symbols[:Core]), nothing))
@@ -200,7 +221,7 @@ function traverse(x::EXPR, state)
200221
end
201222
state(x.args[2])
202223
elseif x.args !== nothing && length(x.args) > 0
203-
@inbounds for i in 1:length(x.args)
224+
@inbounds for i = 1:length(x.args)
204225
state(x.args[i])
205226
end
206227
end

src/bindings.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function mark_bindings!(x::EXPR, state)
5959
mark_sig_args!(x.args[1])
6060
elseif CSTParser.iscurly(x.args[1])
6161
mark_typealias_bindings!(x)
62-
elseif !is_getfield(x.args[1])
62+
elseif !is_getfield(x.args[1]) && state.flags & NO_NEW_BINDINGS == 0
6363
mark_binding!(x.args[1], x)
6464
end
6565
elseif CSTParser.defines_anon_function(x)

src/linting/checks.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,8 +507,8 @@ function check_farg_unused_(arg, arg_names)
507507
isempty(b.refs) ||
508508
# only self ref:
509509
(length(b.refs) == 1 && first(b.refs) == b.name) ||
510-
# first usage is assignment:
511-
(length(b.refs) > 1 && b.refs[2] isa EXPR && CSTParser.hasparent(b.refs[2]) && isassignment(parentof(b.refs[2])) && parentof(b.refs[2]).args[1] == b.refs[2])
510+
# first usage has binding:
511+
(length(b.refs) > 1 && b.refs[2] isa EXPR && hasbinding(b.refs[2]))
512512
seterror!(arg, UnusedFunctionArgument)
513513
end
514514

test/runtests.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,18 @@ f(arg) = arg
789789
StaticLint.check_farg_unused(cst[1])
790790
@test cst[1].args[1].args[2].meta.error === nothing
791791
end
792+
let cst = parse_and_pass("""
793+
function f(x,y,z)
794+
@. begin
795+
x = z
796+
y = z
797+
end
798+
end
799+
""")
800+
StaticLint.check_farg_unused(cst[1])
801+
@test StaticLint.errorof(CSTParser.get_sig(cst[1])[3]) === nothing
802+
@test StaticLint.errorof(CSTParser.get_sig(cst[1])[5]) === nothing
803+
end
792804
end
793805

794806
@testset "check redefinition of const" begin

0 commit comments

Comments
 (0)