Skip to content

Skip bindings for assignments in @. #320

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions src/StaticLint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ Meta() = Meta(nothing, nothing, nothing, nothing)

function Base.show(io::IO, m::Meta)
m.binding !== nothing && show(io, m.binding)
m.ref !== nothing && printstyled(io, " * ", color=:red)
m.scope !== nothing && printstyled(io, " new scope", color=:green)
m.error !== nothing && printstyled(io, " lint ", color=:red)
m.ref !== nothing && printstyled(io, " * ", color = :red)
m.scope !== nothing && printstyled(io, " new scope", color = :green)
m.error !== nothing && printstyled(io, " lint ", color = :red)
end
hasmeta(x::EXPR) = x.meta isa Meta
hasbinding(m::Meta) = m.binding isa Binding
Expand Down Expand Up @@ -61,8 +61,12 @@ mutable struct Toplevel{T} <: State
resolveonly::Vector{EXPR}
env::ExternalEnv
server
flags::Int
end

Toplevel(file, included_files, scope, in_modified_expr, modified_exprs, delayed, resolveonly, env, server) =
Toplevel(file, included_files, scope, in_modified_expr, modified_exprs, delayed, resolveonly, env, server, 0)

function (state::Toplevel)(x::EXPR)
resolve_import(x, state)
mark_bindings!(x, state)
Expand All @@ -84,7 +88,9 @@ function (state::Toplevel)(x::EXPR)
push!(state.resolveonly, x)
end
else
old = flag!(state, x)
traverse(x, state)
state.flags = old
end

state.in_modified_expr = old_in_modified_expr
Expand All @@ -96,8 +102,11 @@ mutable struct Delayed <: State
scope::Scope
env::ExternalEnv
server
flags::Int
end

Delayed(scope, env, server) = Delayed(scope, env, server, 0)

function (state::Delayed)(x::EXPR)
mark_bindings!(x, state)
add_binding(x, state)
Expand All @@ -106,7 +115,9 @@ function (state::Delayed)(x::EXPR)
s0 = scopes(x, state)
resolve_ref(x, state)

old = flag!(state, x)
traverse(x, state)
state.flags = old
if state.scope != s0
for b in values(state.scope.names)
infer_type_by_use(b, state.env)
Expand Down Expand Up @@ -139,13 +150,23 @@ function (state::ResolveOnly)(x::EXPR)
return state.scope
end

# feature flags that can disable or enable functionality further down in the CST
const NO_NEW_BINDINGS = 0x1

function flag!(state, x::EXPR)
old = state.flags
if CSTParser.ismacrocall(x) && (valof(x.args[1]) == "@." || valof(x.args[1]) == "@__dot__")
state.flags |= NO_NEW_BINDINGS
end
return old
end

"""
semantic_pass(file, modified_expr=nothing)

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.
"""
function semantic_pass(file, modified_expr=nothing)
function semantic_pass(file, modified_expr = nothing)
server = file.server
env = getenv(file, server)
setscope!(getcst(file), Scope(nothing, getcst(file), Dict(), Dict{Symbol,Any}(:Base => env.symbols[:Base], :Core => env.symbols[:Core]), nothing))
Expand Down Expand Up @@ -200,7 +221,7 @@ function traverse(x::EXPR, state)
end
state(x.args[2])
elseif x.args !== nothing && length(x.args) > 0
@inbounds for i in 1:length(x.args)
@inbounds for i = 1:length(x.args)
state(x.args[i])
end
end
Expand Down
2 changes: 1 addition & 1 deletion src/bindings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function mark_bindings!(x::EXPR, state)
mark_sig_args!(x.args[1])
elseif CSTParser.iscurly(x.args[1])
mark_typealias_bindings!(x)
elseif !is_getfield(x.args[1])
elseif !is_getfield(x.args[1]) && state.flags & NO_NEW_BINDINGS == 0
mark_binding!(x.args[1], x)
end
elseif CSTParser.defines_anon_function(x)
Expand Down
4 changes: 2 additions & 2 deletions src/linting/checks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,8 @@ function check_farg_unused_(arg, arg_names)
isempty(b.refs) ||
# only self ref:
(length(b.refs) == 1 && first(b.refs) == b.name) ||
# first usage is assignment:
(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])
# first usage has binding:
(length(b.refs) > 1 && b.refs[2] isa EXPR && hasbinding(b.refs[2]))
seterror!(arg, UnusedFunctionArgument)
end

Expand Down
12 changes: 12 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,18 @@ f(arg) = arg
StaticLint.check_farg_unused(cst[1])
@test cst[1].args[1].args[2].meta.error === nothing
end
let cst = parse_and_pass("""
function f(x,y,z)
@. begin
x = z
y = z
end
end
""")
StaticLint.check_farg_unused(cst[1])
@test StaticLint.errorof(CSTParser.get_sig(cst[1])[3]) === nothing
@test StaticLint.errorof(CSTParser.get_sig(cst[1])[5]) === nothing
end
end

@testset "check redefinition of const" begin
Expand Down