@@ -23,6 +23,11 @@ Base.@kwdef struct PerUsageInfo
2323 analysis_code:: AnalysisCode
2424end
2525
26+ function Base. show (io:: IO , r:: PerUsageInfo )
27+ return print (io,
28+ " PerUsageInfo (`$(r. name) ` @ $(r. location) , `qualified_by`=$(r. qualified_by) )" )
29+ end
30+
2631function Base. NamedTuple (r:: PerUsageInfo )
2732 names = fieldnames (typeof (r))
2833 return NamedTuple {names} (map (x -> getfield (r, x), names))
5358
5459# returns `nothing` for no qualifying module, otherwise a symbol
5560function qualifying_module (leaf)
61+ @debug " [qualifying_module] leaf: $(js_node (leaf)) start"
62+ # introspect leaf and its tree of parents
63+ @debug " [qualifying_module] leaf: $(js_node (leaf)) parents: $(parent_kinds (leaf)) "
64+
5665 # is this name being used in a qualified context, like `X.y`?
57- parents_match (leaf, (K " quote" , K " ." )) || return nothing
66+ parents_match (leaf, (K " ." ,)) || return nothing
67+ @debug " [qualifying_module] leaf: $(js_node (leaf)) passed dot"
5868 # Are we on the right-hand side?
59- child_index (parent (leaf)) == 2 || return nothing
69+ child_index (leaf) == 2 || return nothing
70+ @debug " [qualifying_module] leaf: $(js_node (leaf)) passed right-hand side"
6071 # Ok, now try to retrieve the child on the left-side
61- node = first (AbstractTrees. children (get_parent (leaf, 2 )))
72+ node = first (AbstractTrees. children (parent (leaf)))
6273 path = Symbol[]
6374 retrieve_module_path! (path, node)
6475 return path
@@ -139,8 +150,8 @@ function is_anonymous_do_function_definition_arg(leaf)
139150 if ! has_parent (leaf, 2 )
140151 return false
141152 elseif parents_match (leaf, (K " tuple" , K " do" ))
142- # second argument of `do`-block
143- return child_index (parent (leaf)) == 2
153+ # first argument of `do`-block (args then function body since JuliaSyntax 1.0)
154+ return child_index (parent (leaf)) == 1
144155 elseif kind (parent (leaf)) in (K " tuple" , K " parameters" )
145156 # Ok, let's just step up one level and see again
146157 return is_anonymous_do_function_definition_arg (parent (leaf))
@@ -231,10 +242,6 @@ function call_is_func_def(node)
231242 # note: macros only support full-form function definitions
232243 # (not inline)
233244 kind (p) in (K " function" , K " macro" ) && return true
234- if kind (p) == K " ="
235- # call should be the first arg in an inline function def
236- return child_index (node) == 1
237- end
238245 return false
239246end
240247
@@ -268,11 +275,18 @@ end
268275# https://github.com/JuliaLang/JuliaSyntax.jl/issues/432
269276function in_for_argument_position (node)
270277 # We must be on the LHS of a `for` `equal`.
271- if ! has_parent (node, 2 )
278+ if ! has_parent (node, 3 )
272279 return false
273- elseif parents_match (node, (K " =" , K " for" ))
274- return child_index (node) == 1
275- elseif parents_match (node, (K " =" , K " cartesian_iterator" , K " for" ))
280+ elseif parents_match (node, (K " in" , K " iteration" , K " for" ))
281+ @debug """
282+ [in_for_argument_position] node: $(js_node (node))
283+ parents: $(parent_kinds (node))
284+ child_index=$(child_index (node))
285+ parent_child_index=$(child_index (get_parent (node, 1 )))
286+ parent_child_index2=$(child_index (get_parent (node, 2 )))
287+ """
288+
289+ # child_index(node) == 1 means we are the first argument of the `in`, like `yi in y`
276290 return child_index (node) == 1
277291 elseif kind (parent (node)) in (K " tuple" , K " parameters" )
278292 return in_for_argument_position (get_parent (node))
@@ -293,13 +307,11 @@ end
293307
294308function in_generator_arg_position (node)
295309 # We must be on the LHS of a `=` inside a generator
296- # (possibly inside a filter, possibly inside a `cartesian_iterator `)
297- if ! has_parent (node, 2 )
310+ # (possibly inside a filter, possibly inside a `iteration `)
311+ if ! has_parent (node, 3 )
298312 return false
299- elseif parents_match (node, (K " =" , K " generator" )) ||
300- parents_match (node, (K " =" , K " cartesian_iterator" , K " generator" )) ||
301- parents_match (node, (K " =" , K " filter" )) ||
302- parents_match (node, (K " =" , K " cartesian_iterator" , K " filter" ))
313+ elseif parents_match (node, (K " in" , K " iteration" , K " generator" )) ||
314+ parents_match (node, (K " in" , K " iteration" , K " filter" ))
303315 return child_index (node) == 1
304316 elseif kind (parent (node)) in (K " tuple" , K " parameters" )
305317 return in_generator_arg_position (get_parent (node))
@@ -356,16 +368,13 @@ function analyze_name(leaf; debug=false)
356368 # update our state
357369 val = get_val (node)
358370 k = kind (node)
359- args = nodevalue (node). node. raw. args
371+ args = nodevalue (node). node. raw. children
360372
361373 debug && println (val, " : " , k)
362374 # Constructs that start a new local scope. Note `let` & `macro` *arguments* are not explicitly supported/tested yet,
363375 # but we can at least keep track of scope properly.
364376 if k in
365- (K " let" , K " for" , K " function" , K " struct" , K " generator" , K " while" , K " macro" ) ||
366- # Or do-block when we are considering a path that did not go through the first-arg
367- # (which is the function name, and NOT part of the local scope)
368- (k == K " do" && child_index (prev_node) > 1 ) ||
377+ (K " let" , K " for" , K " function" , K " struct" , K " generator" , K " while" , K " macro" , K " do" ) ||
369378 # any child of `try` gets it's own individual scope (I think)
370379 (parents_match (node, (K " try" ,)))
371380 push! (scope_path, nodevalue (node). node)
@@ -506,7 +515,7 @@ function is_name_internal_in_higher_local_scope(name, scope_path, seen)
506515 end
507516 # Ok, now pop off the first scope and check.
508517 scope_path = scope_path[2 : end ]
509- ret = get (seen, (; name, scope_path), nothing )
518+ ret = get (seen, (; name, scope_path= SyntaxNodeList (scope_path) ), nothing )
510519 if ret === nothing
511520 # Not introduced here yet, trying recursing further
512521 continue
@@ -519,6 +528,25 @@ function is_name_internal_in_higher_local_scope(name, scope_path, seen)
519528 return false
520529end
521530
531+ # We implement a workaround for https://github.com/JuliaLang/JuliaSyntax.jl/issues/558
532+ # Hashing and equality for SyntaxNodes were changed from object identity to a recursive comparison
533+ # in JuliaSyntax 1.0. This is very slow and also not quite the semantics we want anyway.
534+ # Here, we wrap our nodes in a custom type that only compares object identity.
535+ struct SyntaxNodeList
536+ nodes:: Vector{JuliaSyntax.SyntaxNode}
537+ end
538+
539+ function Base.:(== )(a:: SyntaxNodeList , b:: SyntaxNodeList )
540+ return map (objectid, a. nodes) == map (objectid, b. nodes)
541+ end
542+ function Base. isequal (a:: SyntaxNodeList , b:: SyntaxNodeList )
543+ return isequal (map (objectid, a. nodes), map (objectid, b. nodes))
544+ end
545+
546+ function Base. hash (a:: SyntaxNodeList , h:: UInt )
547+ return hash (map (objectid, a. nodes), h)
548+ end
549+
522550function analyze_per_usage_info (per_usage_info)
523551 # For each scope, we want to understand if there are any global usages of the name in that scope
524552 # First, throw away all qualified usages, they are irrelevant
@@ -527,9 +555,9 @@ function analyze_per_usage_info(per_usage_info)
527555 # Otherwise, we are in local scope:
528556 # 1. Next, if the name is a function arg, then this is not a global name (essentially first usage is assignment)
529557 # 2. Otherwise, if first usage is assignment, then it is local, otherwise it is global
530- seen = Dict {@NamedTuple{name::Symbol,scope_path::Vector{JuliaSyntax.SyntaxNode} },Bool} ()
558+ seen = Dict {@NamedTuple{name::Symbol,scope_path::SyntaxNodeList },Bool} ()
531559 return map (per_usage_info) do nt
532- @compat if (; nt. name, nt. scope_path) in keys (seen)
560+ @compat if (; nt. name, scope_path = SyntaxNodeList ( nt. scope_path) ) in keys (seen)
533561 return PerUsageInfo (; nt... , first_usage_in_scope= false ,
534562 external_global_name= missing ,
535563 analysis_code= IgnoredNonFirst)
@@ -561,7 +589,8 @@ function analyze_per_usage_info(per_usage_info)
561589 (nt. is_assignment, InternalAssignment))
562590 if is_local
563591 external_global_name = false
564- push! (seen, (; nt. name, nt. scope_path) => external_global_name)
592+ push! (seen,
593+ (; nt. name, scope_path= SyntaxNodeList (nt. scope_path)) => external_global_name)
565594 return PerUsageInfo (; nt... , first_usage_in_scope= true ,
566595 external_global_name,
567596 analysis_code= reason)
@@ -572,13 +601,15 @@ function analyze_per_usage_info(per_usage_info)
572601 nt. scope_path,
573602 seen)
574603 external_global_name = false
575- push! (seen, (; nt. name, nt. scope_path) => external_global_name)
604+ push! (seen,
605+ (; nt. name, scope_path= SyntaxNodeList (nt. scope_path)) => external_global_name)
576606 return PerUsageInfo (; nt... , first_usage_in_scope= true , external_global_name,
577607 analysis_code= InternalHigherScope)
578608 end
579609
580610 external_global_name = true
581- push! (seen, (; nt. name, nt. scope_path) => external_global_name)
611+ push! (seen,
612+ (; nt. name, scope_path= SyntaxNodeList (nt. scope_path)) => external_global_name)
582613 return PerUsageInfo (; nt... , first_usage_in_scope= true , external_global_name,
583614 analysis_code= External)
584615 end
0 commit comments