Skip to content

Commit 325a850

Browse files
committed
Fixes for SourceFile byte offsets
This fixes a crash formatting error messages when core_parse_hook is used to parse a piece of broken code with a nontrivial byte offset. * `SourceFile` held by `SyntaxNode` preserves the indexing of the original string passed to the `parse*()` functions. * Fix `source_line_range` and `thisind` accordingly
1 parent fd9075d commit 325a850

File tree

9 files changed

+64
-25
lines changed

9 files changed

+64
-25
lines changed

src/expr.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,7 @@ end
457457

458458
function build_tree(::Type{Expr}, stream::ParseStream;
459459
filename=nothing, first_line=1, kws...)
460-
source = SourceFile(sourcetext(stream), first_index=first_byte(stream),
461-
filename=filename, first_line=first_line)
460+
source = SourceFile(stream, filename=filename, first_line=first_line)
462461
txtbuf = unsafe_textbuf(stream)
463462
args = Any[]
464463
childranges = UnitRange{Int}[]

src/parse_stream.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,10 @@ function sourcetext(stream::ParseStream; steal_textbuf=false)
11081108
SubString(str, first_byte(stream), thisind(str, last_byte(stream)))
11091109
end
11101110

1111+
function SourceFile(stream::ParseStream; kws...)
1112+
return SourceFile(sourcetext(stream); first_index=first_byte(stream), kws...)
1113+
end
1114+
11111115
"""
11121116
unsafe_textbuf(stream)
11131117

src/parser_api.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct ParseError <: Exception
1111
end
1212

1313
function ParseError(stream::ParseStream; incomplete_tag=:none, kws...)
14-
source = SourceFile(sourcetext(stream); kws...)
14+
source = SourceFile(stream; kws...)
1515
ParseError(source, stream.diagnostics, incomplete_tag)
1616
end
1717

src/source_files.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ function source_line_range(source::SourceFile, byte_index;
7676
lineidx = _source_line_index(source, byte_index)
7777
fbyte = source.line_starts[max(lineidx-context_lines_before, 1)]
7878
lbyte = source.line_starts[min(lineidx+1+context_lines_after, end)] - 1
79-
fbyte,lbyte
79+
return (fbyte + source.byte_offset,
80+
lbyte + source.byte_offset)
8081
end
8182

8283
function source_location(::Type{LineNumberNode}, source::SourceFile, byte_index)
@@ -120,7 +121,7 @@ function Base.getindex(source::SourceFile, i::Int)
120121
end
121122

122123
function Base.thisind(source::SourceFile, i::Int)
123-
thisind(source.code, i - source.byte_offset)
124+
thisind(source.code, i - source.byte_offset) + source.byte_offset
124125
end
125126

126127
Base.firstindex(source::SourceFile) = firstindex(source.code) + source.byte_offset

src/syntax_tree.jl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,11 @@ Base.show(io::IO, ::ErrorVal) = printstyled(io, "✘", color=:light_red)
6161

6262
function SyntaxNode(source::SourceFile, raw::GreenNode{SyntaxHead};
6363
keep_parens=false, position::Integer=1)
64-
offset, txtbuf = _unsafe_wrap_substring(sourcetext(source))
65-
_to_SyntaxNode(source, txtbuf, offset, raw, convert(Int, position), keep_parens)
64+
GC.@preserve source begin
65+
raw_offset, txtbuf = _unsafe_wrap_substring(source.code)
66+
offset = raw_offset - source.byte_offset
67+
_to_SyntaxNode(source, txtbuf, offset, raw, convert(Int, position), keep_parens)
68+
end
6669
end
6770

6871
function _to_SyntaxNode(source::SourceFile, txtbuf::Vector{UInt8}, offset::Int,
@@ -222,8 +225,8 @@ Base.copy(data::SyntaxData) = SyntaxData(data.source, data.raw, data.position, d
222225
function build_tree(::Type{SyntaxNode}, stream::ParseStream;
223226
filename=nothing, first_line=1, keep_parens=false, kws...)
224227
green_tree = build_tree(GreenNode, stream; kws...)
225-
source = SourceFile(sourcetext(stream), filename=filename, first_line=first_line)
226-
SyntaxNode(source, green_tree, position=1, keep_parens=keep_parens)
228+
source = SourceFile(stream, filename=filename, first_line=first_line)
229+
SyntaxNode(source, green_tree, position=first_byte(stream), keep_parens=keep_parens)
227230
end
228231

229232
#-------------------------------------------------------------------------------

test/hooks.jl

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
function _unwrap_parse_error(core_hook_result)
2+
@test Meta.isexpr(core_hook_result[1], :error, 1)
3+
err = core_hook_result[1].args[1]
4+
if JuliaSyntax._has_v1_10_hooks
5+
@test err isa Meta.ParseError
6+
return err.detail
7+
else
8+
@test err isa JuliaSyntax.ParseError
9+
return err
10+
end
11+
end
12+
113
@testset "Hooks for Core integration" begin
214
@testset "whitespace parsing" begin
315
@test JuliaSyntax.core_parser_hook("", "somefile", 1, 0, :statement) == Core.svec(nothing, 0)
@@ -19,26 +31,28 @@
1931
@test ex.args[2] == LineNumberNode(2, "otherfile")
2032

2133
# Errors also propagate file & lineno
22-
err = JuliaSyntax.core_parser_hook("[x)", "f1", 1, 0, :statement)[1].args[1]
23-
if JuliaSyntax._has_v1_10_hooks
24-
@test err isa Meta.ParseError
25-
err = err.detail
26-
else
27-
@test err isa JuliaSyntax.ParseError
28-
end
34+
err = _unwrap_parse_error(
35+
JuliaSyntax.core_parser_hook("[x)", "f1", 1, 0, :statement)
36+
)
2937
@test err isa JuliaSyntax.ParseError
3038
@test err.source.filename == "f1"
3139
@test err.source.first_line == 1
32-
err = JuliaSyntax.core_parser_hook("[x)", "f2", 2, 0, :statement)[1].args[1]
33-
if JuliaSyntax._has_v1_10_hooks
34-
@test err isa Meta.ParseError
35-
err = err.detail
36-
else
37-
@test err isa JuliaSyntax.ParseError
38-
end
40+
err = _unwrap_parse_error(
41+
JuliaSyntax.core_parser_hook("[x)", "f2", 2, 0, :statement)
42+
)
3943
@test err isa JuliaSyntax.ParseError
4044
@test err.source.filename == "f2"
4145
@test err.source.first_line == 2
46+
47+
# Errors including nontrivial offset indices
48+
err = _unwrap_parse_error(
49+
JuliaSyntax.core_parser_hook("a\nh{x)\nb", "test.jl", 1, 2, :statement)
50+
)
51+
@test err isa JuliaSyntax.ParseError
52+
@test err.source.first_line == 1
53+
@test err.diagnostics[1].first_byte == 6
54+
@test err.diagnostics[1].last_byte == 5
55+
@test err.diagnostics[1].message == "Expected `}`"
4256
end
4357

4458
@testset "toplevel errors" begin

test/source_files.jl

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,14 @@
2828
end
2929

3030
# byte offset
31-
@test source_location(SourceFile("a\nbb\nccc\ndddd", first_index=10), 13) == (2,2)
32-
@test source_line(SourceFile("a\nbb\nccc\ndddd", first_index=10), 15) == 3
31+
sf = SourceFile("a\nbb\nccc\ndddd", first_index=10)
32+
@test source_location(sf, 13) == (2,2)
33+
@test source_line(sf, 15) == 3
34+
@test source_line_range(sf, 10) == (10,11)
35+
@test source_line_range(sf, 11) == (10,11)
36+
@test source_line_range(sf, 12) == (12,14)
37+
@test source_line_range(sf, 14) == (12,14)
38+
@test source_line_range(sf, 15) == (15,18)
3339

3440
# source_line convenience function
3541
@test source_line(SourceFile("a\nb\n"), 2) == 1
@@ -52,6 +58,11 @@ end
5258
@test sf[10:11] == "ab"
5359
@test view(sf, 10:11) == "ab"
5460

61+
@test thisind(SourceFile("xαx", first_index=10), 10) == 10
62+
@test thisind(SourceFile("xαx", first_index=10), 11) == 11
63+
@test thisind(SourceFile("xαx", first_index=10), 12) == 11
64+
@test thisind(SourceFile("xαx", first_index=10), 13) == 13
65+
5566
if Base.VERSION >= v"1.4"
5667
# Protect the `[begin` from being viewed by the parser on older Julia versions
5768
@test eval(Meta.parse("SourceFile(\"a\nb\n\")[begin:end]")) == "a\nb\n"

test/syntax_tree.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@
4949
@test length(children(node)) == 2
5050
node[2] = parsestmt(SyntaxNode, "y")
5151
@test sourcetext(child(node, 2)) == "y"
52+
53+
# SyntaxNode with offsets
54+
t,_ = parsestmt(SyntaxNode, "begin a end\nbegin b end", 13)
55+
@test t.position == 13
56+
@test child(t,1).position == 19
57+
@test child(t,1).val == :b
5258
end
5359

5460
@testset "SyntaxNode pretty printing" begin

test/test_utils.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ using .JuliaSyntax:
99
SourceFile,
1010
source_location,
1111
source_line,
12+
source_line_range,
1213
parse!,
1314
parsestmt,
1415
parseall,

0 commit comments

Comments
 (0)