Skip to content

Commit e0342c0

Browse files
authored
Make Expr(:incomplete) detection more robust to whitespace (#538)
Rework incomplete expression detection so that trailing whitespace is always ignored, regardless of how the parser itself decides to attach it to other nodes of the tree. To do this, we walk back from the end of the parse stream and look for the byte offset of the last non-whitespace token. We then use that to determine whether the error node is "at the end of the parse". Improve testing by * Extracting the incomplete expressions which are part of the REPL stdlib tests and ensuring these match the incomplete tag generation of the flisp parser. Fix some divergences for `var""` syntax and invalid escape sequences in strings. * Ensuring that we test both `:statement` and `:all` level parsing - the REPL uses `:all` to allow parsing of multiple top level statements, so we need to test this. Also fix a minor bug where `enable_in_core!(false)` would result in the flisp parser being used, regardless of whether `VERSION` ships with JuliaSyntax enabled by default. Fixes #519. See also #518.
1 parent 86bc433 commit e0342c0

File tree

3 files changed

+339
-14
lines changed

3 files changed

+339
-14
lines changed

src/hooks.jl

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,17 @@ end
3535
function _incomplete_tag(n::SyntaxNode, codelen)
3636
i,c = _first_error(n)
3737
if isnothing(c) || last_byte(c) < codelen || codelen == 0
38-
return :none
39-
elseif first_byte(c) <= codelen
40-
if kind(c) == K"ErrorEofMultiComment" && last_byte(c) == codelen
38+
if kind(c) == K"ErrorEofMultiComment"
4139
# This is the one weird case where the token itself is an
4240
# incomplete error
4341
return :comment
4442
else
4543
return :none
4644
end
45+
elseif first_byte(c) <= codelen && kind(c) != K"ErrorInvalidEscapeSequence"
46+
# "ErrorInvalidEscapeSequence" may be incomplete, so we don't include it
47+
# here as a hard error.
48+
return :none
4749
end
4850
if kind(c) == K"error" && numchildren(c) > 0
4951
for cc in children(c)
@@ -56,7 +58,7 @@ function _incomplete_tag(n::SyntaxNode, codelen)
5658
return :other
5759
end
5860
kp = kind(c.parent)
59-
if kp == K"string"
61+
if kp == K"string" || kp == K"var"
6062
return :string
6163
elseif kp == K"cmdstring"
6264
return :cmd
@@ -170,7 +172,6 @@ function core_parser_hook(code, filename::String, lineno::Int, offset::Int, opti
170172
end
171173
end
172174
parse!(stream; rule=options)
173-
pos_before_trivia = last_byte(stream)
174175
if options === :statement
175176
bump_trivia(stream; skip_newlines=false)
176177
if peek(stream) == K"NewlineWs"
@@ -179,8 +180,9 @@ function core_parser_hook(code, filename::String, lineno::Int, offset::Int, opti
179180
end
180181

181182
if any_error(stream)
183+
pos_before_comments = last_non_whitespace_byte(stream)
182184
tree = build_tree(SyntaxNode, stream, first_line=lineno, filename=filename)
183-
tag = _incomplete_tag(tree, pos_before_trivia)
185+
tag = _incomplete_tag(tree, pos_before_comments)
184186
if _has_v1_10_hooks
185187
exc = ParseError(stream, filename=filename, first_line=lineno,
186188
incomplete_tag=tag)
@@ -245,6 +247,7 @@ function core_parser_hook(code, filename::String, lineno::Int, offset::Int, opti
245247
# EXIT last_offset=$last_offset
246248
#-#-#-
247249
""")
250+
flush(_debug_log[])
248251
end
249252

250253
# Rewrap result in an svec for use by the C code
@@ -257,6 +260,7 @@ function core_parser_hook(code, filename::String, lineno::Int, offset::Int, opti
257260
# $exc
258261
#-#-#-
259262
""")
263+
flush(_debug_log[])
260264
end
261265
@error("""JuliaSyntax parser failed — falling back to flisp!
262266
This is not your fault. Please submit a bug report to https://github.com/JuliaLang/JuliaSyntax.jl/issues""",
@@ -284,6 +288,8 @@ else
284288
Base.Meta.ParseError(e::JuliaSyntax.ParseError) = e
285289
end
286290

291+
_default_system_parser = _has_v1_6_hooks ? Core._parse : nothing
292+
287293
"""
288294
enable_in_core!([enable=true; freeze_world_age=true, debug_filename=nothing])
289295
@@ -313,7 +319,8 @@ function enable_in_core!(enable=true; freeze_world_age = true,
313319
world_age = freeze_world_age ? Base.get_world_counter() : typemax(UInt)
314320
_set_core_parse_hook(fix_world_age(core_parser_hook, world_age))
315321
else
316-
_set_core_parse_hook(Core.Compiler.fl_parse)
322+
@assert !isnothing(_default_system_parser)
323+
_set_core_parse_hook(_default_system_parser)
317324
end
318325
nothing
319326
end

src/parse_stream.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,17 @@ first_byte(stream::ParseStream) = first(stream.tokens).next_byte # Use sentinel
12571257
last_byte(stream::ParseStream) = _next_byte(stream)-1
12581258
any_error(stream::ParseStream) = any_error(stream.diagnostics)
12591259

1260+
# Return last non-whitespace byte which was parsed
1261+
function last_non_whitespace_byte(stream::ParseStream)
1262+
for i = length(stream.tokens):-1:1
1263+
tok = stream.tokens[i]
1264+
if !(kind(tok) in KSet"Comment Whitespace NewlineWs ErrorEofMultiComment")
1265+
return tok.next_byte - 1
1266+
end
1267+
end
1268+
return first_byte(stream) - 1
1269+
end
1270+
12601271
function Base.empty!(stream::ParseStream)
12611272
t = last(stream.tokens)
12621273
empty!(stream.tokens)

0 commit comments

Comments
 (0)