Skip to content

Commit

Permalink
support try (#46)
Browse files Browse the repository at this point in the history
ericphanson authored May 17, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 4fecaaf commit 30f6b47
Showing 3 changed files with 88 additions and 15 deletions.
37 changes: 30 additions & 7 deletions src/get_names_used.jl
Original file line number Diff line number Diff line change
@@ -200,6 +200,23 @@ function in_generator_arg_position(node)
end
end

function is_catch_arg(leaf)
kind(leaf) == K"Identifier" || return false
return in_catch_arg_position(leaf)
end

function in_catch_arg_position(node)
# We must be the first argument of a `catch` block
if !has_parent(node)
return false
elseif parents_match(node, (K"catch",))
return child_index(node) == 1
else
# catch doesn't support destructuring, type annotations, etc, so we're done!
return false
end
end

# matches `x` in `x::Y`, but not `Y`, nor `foo(::Y)`
function is_double_colon_LHS(leaf)
parents_match(leaf, (K"::",)) || return false
@@ -219,6 +236,7 @@ function analyze_name(leaf; debug=false)
struct_field_or_type_param = is_struct_type_param(leaf) || is_struct_field_name(leaf)
for_loop_index = is_for_arg(leaf)
generator_index = is_generator_arg(leaf)
catch_arg = is_catch_arg(leaf)
module_path = Symbol[]
scope_path = JuliaSyntax.SyntaxNode[]
is_assignment = false
@@ -233,11 +251,15 @@ function analyze_name(leaf; debug=false)
args = nodevalue(node).node.raw.args

debug && println(val, ": ", k)
# Constructs that start a new local scope:
if k in (K"let", K"for", K"function", K"struct", K"generator", K"while") ||
# Constructs that start a new local scope. Note `let` & `macro` *arguments* are not explicitly supported/tested yet,
# but we can at least keep track of scope properly.
if k in
(K"let", K"for", K"function", K"struct", K"generator", K"while", K"macro") ||
# Or do-block when we are considering a path that did not go through the first-arg
# (which is the function name, and NOT part of the local scope)
(k == K"do" && child_index(prev_node) > 1)
(k == K"do" && child_index(prev_node) > 1) ||
# any child of `try` gets it's own individual scope (I think)
(parents_match(node, (K"try",)))
push!(scope_path, nodevalue(node).node)
# try to detect presence in RHS of inline function definition
elseif idx > 3 && k == K"=" && !isempty(args) &&
@@ -246,7 +268,7 @@ function analyze_name(leaf; debug=false)
end

# track which modules we are in
if k == K"module"
if k == K"module" # baremodules?
ids = filter(children(nodevalue(node))) do arg
return kind(arg.node) == K"Identifier"
end
@@ -273,7 +295,7 @@ function analyze_name(leaf; debug=false)
# finished climbing to the root
node === nothing &&
return (; function_arg, is_assignment, module_path, scope_path,
struct_field_or_type_param, for_loop_index, generator_index)
struct_field_or_type_param, for_loop_index, generator_index, catch_arg)
idx += 1
end
end
@@ -304,7 +326,7 @@ function analyze_all_names(file; debug=false)
module_path::Vector{Symbol},
scope_path::Vector{JuliaSyntax.SyntaxNode},
struct_field_or_type_param::Bool,for_loop_index::Bool,
generator_index::Bool}[]
generator_index::Bool,catch_arg::Bool}[]

# we need to keep track of all names that we see, because we could
# miss entire modules if it is an `include` we cannot follow.
@@ -384,7 +406,7 @@ function is_name_internal_in_higher_local_scope(name, scope_path, seen)
return false
end

@enum AnalysisCode IgnoredNonFirst IgnoredQualified IgnoredImportRHS InternalHigherScope InternalFunctionArg InternalAssignment InternalStruct InternalForLoop InternalGenerator External
@enum AnalysisCode IgnoredNonFirst IgnoredQualified IgnoredImportRHS InternalHigherScope InternalFunctionArg InternalAssignment InternalStruct InternalForLoop InternalGenerator InternalCatchArgument External

function analyze_per_usage_info(per_usage_info)
# For each scope, we want to understand if there are any global usages of the name in that scope
@@ -418,6 +440,7 @@ function analyze_per_usage_info(per_usage_info)
(nt.struct_field_or_type_param, InternalStruct),
(nt.for_loop_index, InternalForLoop),
(nt.generator_index, InternalGenerator),
(nt.catch_arg, InternalCatchArgument),
# We check this last, since it is less specific
# than e.g. `InternalForLoop` but can trigger in
# some of the same cases
25 changes: 25 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -166,6 +166,31 @@ end
@test Hermitian_codes == [ExplicitImports.External, ExplicitImports.IgnoredNonFirst]
end

@testset "try-catch" begin
@test using_statement.(explicit_imports_nonrecursive(TestMod12, "test_mods.jl")) ==
["using LinearAlgebra: LinearAlgebra",
"using LinearAlgebra: I",
"using LinearAlgebra: svd"]

per_usage_info, _ = analyze_all_names("test_mods.jl")
df = DataFrame(analyze_per_usage_info(per_usage_info))
subset!(df, :module_path => ByRow(==([:TestMod12])))

I_codes = subset(df, :name => ByRow(==(:I))).analysis_code
@test I_codes == [ExplicitImports.InternalAssignment,
ExplicitImports.External,
ExplicitImports.External,
ExplicitImports.InternalAssignment,
ExplicitImports.InternalCatchArgument,
ExplicitImports.IgnoredNonFirst,
ExplicitImports.External]
svd_codes = subset(df, :name => ByRow(==(:svd))).analysis_code
@test svd_codes == [ExplicitImports.InternalAssignment,
ExplicitImports.External,
ExplicitImports.InternalAssignment,
ExplicitImports.External]
end

@testset "scripts" begin
str = sprint(print_explicit_imports_script, "script.jl")
@test contains(str, "Script `script.jl`")
41 changes: 33 additions & 8 deletions test/test_mods.jl
Original file line number Diff line number Diff line change
@@ -134,7 +134,6 @@ end

end # TestMod9


module TestMod10

using LinearAlgebra
@@ -150,37 +149,63 @@ end

end # TestMod10


module TestMod11

using LinearAlgebra


function foo(f)
# This `I` is a local variable!
f() do I
I + 1
return I + 1
end

# These are locals too, but in different scopes
f() do I, svd
I + 1
return I + 1
end

# global, despite a local of the same name ocuring in a different scope above
svd

f() do (; I, z)
I + 1
return I + 1
end

# This name is external
Hermitian() do I
I + 1
return I + 1
end

# Non-first invocation of `Hermitian`
Hermitian
return Hermitian
end

end # TestMod11

module TestMod12

using LinearAlgebra

function foo(f)
try
I = 1 # local
catch
svd = 1 # local
I # this one is global!
finally
svd # global
I # this one is global too!
end

try
I = 1 # local
catch I
svd = 1 # local
I # this one is local
finally
svd # global
I # this one is global
end
end

end # TestMod12

0 comments on commit 30f6b47

Please sign in to comment.