@@ -23,6 +23,11 @@ Base.@kwdef struct PerUsageInfo
23
23
analysis_code:: AnalysisCode
24
24
end
25
25
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
+
26
31
function Base. NamedTuple (r:: PerUsageInfo )
27
32
names = fieldnames (typeof (r))
28
33
return NamedTuple {names} (map (x -> getfield (r, x), names))
53
58
54
59
# returns `nothing` for no qualifying module, otherwise a symbol
55
60
function 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
+
56
65
# 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"
58
68
# 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"
60
71
# 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)))
62
73
path = Symbol[]
63
74
retrieve_module_path! (path, node)
64
75
return path
@@ -139,8 +150,8 @@ function is_anonymous_do_function_definition_arg(leaf)
139
150
if ! has_parent (leaf, 2 )
140
151
return false
141
152
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
144
155
elseif kind (parent (leaf)) in (K " tuple" , K " parameters" )
145
156
# Ok, let's just step up one level and see again
146
157
return is_anonymous_do_function_definition_arg (parent (leaf))
@@ -231,10 +242,6 @@ function call_is_func_def(node)
231
242
# note: macros only support full-form function definitions
232
243
# (not inline)
233
244
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
238
245
return false
239
246
end
240
247
@@ -268,11 +275,18 @@ end
268
275
# https://github.com/JuliaLang/JuliaSyntax.jl/issues/432
269
276
function in_for_argument_position (node)
270
277
# We must be on the LHS of a `for` `equal`.
271
- if ! has_parent (node, 2 )
278
+ if ! has_parent (node, 3 )
272
279
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`
276
290
return child_index (node) == 1
277
291
elseif kind (parent (node)) in (K " tuple" , K " parameters" )
278
292
return in_for_argument_position (get_parent (node))
@@ -293,13 +307,11 @@ end
293
307
294
308
function in_generator_arg_position (node)
295
309
# 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 )
298
312
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" ))
303
315
return child_index (node) == 1
304
316
elseif kind (parent (node)) in (K " tuple" , K " parameters" )
305
317
return in_generator_arg_position (get_parent (node))
@@ -356,16 +368,13 @@ function analyze_name(leaf; debug=false)
356
368
# update our state
357
369
val = get_val (node)
358
370
k = kind (node)
359
- args = nodevalue (node). node. raw. args
371
+ args = nodevalue (node). node. raw. children
360
372
361
373
debug && println (val, " : " , k)
362
374
# Constructs that start a new local scope. Note `let` & `macro` *arguments* are not explicitly supported/tested yet,
363
375
# but we can at least keep track of scope properly.
364
376
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" ) ||
369
378
# any child of `try` gets it's own individual scope (I think)
370
379
(parents_match (node, (K " try" ,)))
371
380
push! (scope_path, nodevalue (node). node)
@@ -506,7 +515,7 @@ function is_name_internal_in_higher_local_scope(name, scope_path, seen)
506
515
end
507
516
# Ok, now pop off the first scope and check.
508
517
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 )
510
519
if ret === nothing
511
520
# Not introduced here yet, trying recursing further
512
521
continue
@@ -519,6 +528,25 @@ function is_name_internal_in_higher_local_scope(name, scope_path, seen)
519
528
return false
520
529
end
521
530
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
+
522
550
function analyze_per_usage_info (per_usage_info)
523
551
# For each scope, we want to understand if there are any global usages of the name in that scope
524
552
# First, throw away all qualified usages, they are irrelevant
@@ -527,9 +555,9 @@ function analyze_per_usage_info(per_usage_info)
527
555
# Otherwise, we are in local scope:
528
556
# 1. Next, if the name is a function arg, then this is not a global name (essentially first usage is assignment)
529
557
# 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} ()
531
559
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)
533
561
return PerUsageInfo (; nt... , first_usage_in_scope= false ,
534
562
external_global_name= missing ,
535
563
analysis_code= IgnoredNonFirst)
@@ -561,7 +589,8 @@ function analyze_per_usage_info(per_usage_info)
561
589
(nt. is_assignment, InternalAssignment))
562
590
if is_local
563
591
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)
565
594
return PerUsageInfo (; nt... , first_usage_in_scope= true ,
566
595
external_global_name,
567
596
analysis_code= reason)
@@ -572,13 +601,15 @@ function analyze_per_usage_info(per_usage_info)
572
601
nt. scope_path,
573
602
seen)
574
603
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)
576
606
return PerUsageInfo (; nt... , first_usage_in_scope= true , external_global_name,
577
607
analysis_code= InternalHigherScope)
578
608
end
579
609
580
610
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)
582
613
return PerUsageInfo (; nt... , first_usage_in_scope= true , external_global_name,
583
614
analysis_code= External)
584
615
end
0 commit comments