Skip to content

Commit ac74e96

Browse files
authored
ast: Preserve @nospecialize/@specialize in remove_macrocalls (#347)
The `remove_macrocalls` function converts macrocall nodes to block nodes to enable LSP features to work with local variables inside macrocalls. However, this transformation caused `@nospecialize` and `@specialize` macros in function argument lists to become invalid block expressions, which prevented JuliaLowering from generating correct lowered trees. This commit adds special handling for these macros: instead of transforming them, we keep them intact. JuliaLowering.jl provides new macro style definitions for `@nospecialize` and `@specialize`, so they don't need to be removed in the first place. This fix enables document highlight and rename to work correctly for function parameters annotated with these macros.
1 parent 9c2e4d7 commit ac74e96

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
3636
opened simultaneously. Added global lock to `activate_do` to serialize
3737
environment switching operations. This fixes spurious "Failed to identify
3838
package environment" warnings.
39+
- Fixed document highlight and rename not working for function parameters
40+
annotated with `@nospecialize` or `@specialize`.
3941

4042
### Internal
4143

src/utils/ast.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,30 @@ function without_kinds(st::JL.SyntaxTree, kinds::Tuple{Vararg{JS.Kind}})
3131
_without_kinds(st, kinds)[1])::JL.SyntaxTree
3232
end
3333

34+
function is_nospecialize_or_specialize_macrocall(st::JL.SyntaxTree)
35+
JS.kind(st) === JS.K"macrocall" || return false
36+
JS.numchildren(st) >= 1 || return false
37+
macro_name_node = st[1]
38+
JS.kind(macro_name_node) === JS.K"macro_name" || return false
39+
JS.numchildren(macro_name_node) >= 1 || return false
40+
macro_name = macro_name_node[1]
41+
JS.kind(macro_name) === JS.K"Identifier" || return false
42+
hasproperty(macro_name, :name_val) || return false
43+
return macro_name.name_val == "nospecialize" || macro_name.name_val == "specialize"
44+
end
45+
3446
function _remove_macrocalls(st::JL.SyntaxTree)
3547
if JS.kind(st) === JS.K"macrocall"
48+
if is_nospecialize_or_specialize_macrocall(st)
49+
# Special case `@nospecialize`/`@specialize`:
50+
# These macros are sometimes used in method definition argument lists, but
51+
# if we apply the `_remove_macrocalls` transformation directly, it would
52+
# result in a `:block` expression being inserted into the argument list,
53+
# preventing generation of a correct lowered tree.
54+
# Furthermore, JuliaLowering.jl provides new macro style definitions for
55+
# these macros, so there's no need to remove them in the first place.
56+
return st, false
57+
end
3658
new_children = JL.SyntaxList(JL.syntax_graph(st))
3759
for i = 2:JS.numchildren(st)
3860
push!(new_children, _remove_macrocalls(st[i])[1])

test/test_document_highlight.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,33 @@ end
119119
end
120120
end
121121

122+
@testset "highlight with @nospecialize" begin
123+
code = """
124+
function func(@nospecialize(│xxx│), yyy)
125+
zzz = │xxx│, yyy
126+
zzz, yyy
127+
end
128+
"""
129+
clean_code, positions = JETLS.get_text_and_positions(code)
130+
@test length(positions) == 4
131+
fi = JETLS.FileInfo(#=version=#0, clean_code, @__FILE__)
132+
@test issorted(positions; by = x -> JETLS.xy_to_offset(fi, x))
133+
for pos in positions
134+
highlights = JETLS.document_highlights(fi, pos)
135+
@test length(highlights) == 2
136+
@test any(highlights) do highlight
137+
highlight.range.start == positions[1] &&
138+
highlight.range.var"end" == positions[2] &&
139+
highlight.kind == DocumentHighlightKind.Write
140+
end
141+
@test any(highlights) do highlight
142+
highlight.range.start == positions[3] &&
143+
highlight.range.var"end" == positions[4] &&
144+
highlight.kind == DocumentHighlightKind.Read
145+
end
146+
end
147+
end
148+
122149
let code = """
123150
let │xxx│, │yyy│ = :yyy
124151
│xxx│ = :xxx

0 commit comments

Comments
 (0)