Skip to content

Commit 86d7f90

Browse files
authored
Fix const struct field errors + cleanup global const AST (#130)
Here I've replicated the fix from JuliaLang/julia#45024 so that `const x` (ie, without an assignment) is only valid within a `struct` and is otherwise an error. Also avoid lowering the syntax `global const` into `const global` within the parser; do this in `Expr` conversion instead. This more closely reflects the structure of the source, allowing trivia attachment to be more natural.
1 parent fd9e981 commit 86d7f90

File tree

3 files changed

+99
-86
lines changed

3 files changed

+99
-86
lines changed

src/expr.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true,
253253
args[1] = a
254254
end
255255
end
256+
elseif headsym == :local || headsym == :global
257+
if length(args) == 1 && Meta.isexpr(args[1], :const)
258+
# Normalize `local const` to `const local`
259+
args[1] = Expr(headsym, args[1].args...)
260+
headsym = :const
261+
end
256262
end
257263
return Expr(headsym, args...)
258264
end

src/parser.jl

Lines changed: 71 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,6 +1647,23 @@ function parse_subtype_spec(ps::ParseState)
16471647
parse_comparison(ps, true)
16481648
end
16491649

1650+
# flisp: parse-struct-field
1651+
function parse_struct_field(ps::ParseState)
1652+
mark = position(ps)
1653+
const_field = peek(ps) == K"const"
1654+
if const_field
1655+
bump(ps, TRIVIA_FLAG)
1656+
end
1657+
parse_eq(ps)
1658+
if const_field
1659+
# Const fields https://github.com/JuliaLang/julia/pull/43305
1660+
#v1.8: struct A const a end ==> (struct false A (block (const x)))
1661+
#v1.7: struct A const a end ==> (struct false A (block (error (const x))))
1662+
emit(ps, mark, K"const")
1663+
min_supported_version(v"1.8", ps, mark, "`const` struct field")
1664+
end
1665+
end
1666+
16501667
# parse expressions or blocks introduced by syntactic reserved words.
16511668
#
16521669
# The caller should use peek_initial_reserved_words to determine whether
@@ -1727,8 +1744,47 @@ function parse_resword(ps::ParseState)
17271744
emit(ps, mark, K"let")
17281745
elseif word == K"if"
17291746
parse_if_elseif(ps)
1730-
elseif word in KSet"const global local"
1731-
parse_const_local_global(ps)
1747+
elseif word in KSet"global local"
1748+
# global x ==> (global x)
1749+
# local x ==> (local x)
1750+
# global x,y ==> (global x y)
1751+
bump(ps, TRIVIA_FLAG)
1752+
const_mark = nothing
1753+
if peek(ps) == K"const"
1754+
const_mark = position(ps)
1755+
bump(ps, TRIVIA_FLAG)
1756+
end
1757+
had_assignment = parse_global_local_const_vars(ps)
1758+
if !isnothing(const_mark)
1759+
# global const x = 1 ==> (global (const (= x 1)))
1760+
# local const x = 1 ==> (local (const (= x 1)))
1761+
emit(ps, const_mark, K"const")
1762+
if !had_assignment
1763+
# global const x ==> (global (error (const x)))
1764+
emit(ps, mark, K"error", error="expected assignment after `const`")
1765+
end
1766+
end
1767+
emit(ps, mark, word)
1768+
elseif word == K"const"
1769+
# const x = 1 ==> (const (= x 1))
1770+
bump(ps, TRIVIA_FLAG)
1771+
scope_mark = nothing
1772+
scope_k = peek(ps)
1773+
if scope_k in KSet"local global"
1774+
scope_mark = position(ps)
1775+
bump(ps, TRIVIA_FLAG)
1776+
end
1777+
had_assignment = parse_global_local_const_vars(ps)
1778+
if !isnothing(scope_mark)
1779+
# const global x = 1 ==> (const (global (= x 1)))
1780+
# const local x = 1 ==> (const (local (= x 1)))
1781+
emit(ps, scope_mark, scope_k)
1782+
end
1783+
emit(ps, mark, K"const")
1784+
if !had_assignment
1785+
# const x .= 1 ==> (error (const (.= x 1)))
1786+
emit(ps, mark, K"error", error="expected assignment after `const`")
1787+
end
17321788
elseif word in KSet"function macro"
17331789
parse_function(ps)
17341790
elseif word == K"abstract"
@@ -1749,6 +1805,9 @@ function parse_resword(ps::ParseState)
17491805
emit(ps, mark, K"abstract")
17501806
elseif word in KSet"struct mutable"
17511807
# struct A <: B \n a::X \n end ==> (struct false (<: A B) (block (:: a X)))
1808+
# struct A \n a \n b \n end ==> (struct false A (block a b))
1809+
#v1.7: struct A const a end ==> (struct false A (block (error (const a))))
1810+
#v1.8: struct A const a end ==> (struct false A (block (const a)))
17521811
if word == K"mutable"
17531812
# mutable struct A end ==> (struct true A (block))
17541813
bump(ps, TRIVIA_FLAG)
@@ -1760,7 +1819,7 @@ function parse_resword(ps::ParseState)
17601819
@check peek(ps) == K"struct"
17611820
bump(ps, TRIVIA_FLAG)
17621821
parse_subtype_spec(ps)
1763-
parse_block(ps)
1822+
parse_block(ps, parse_struct_field)
17641823
bump_closing_token(ps, K"end")
17651824
emit(ps, mark, K"struct")
17661825
elseif word == K"primitive"
@@ -1888,75 +1947,24 @@ function parse_if_elseif(ps, is_elseif=false, is_elseif_whitespace_err=false)
18881947
emit(ps, mark, word)
18891948
end
18901949

1891-
function parse_const_local_global(ps)
1950+
# Like parse_assignment, but specialized so that we can omit the
1951+
# tuple when there's commas but no assignment.
1952+
function parse_global_local_const_vars(ps)
18921953
mark = position(ps)
1893-
scope_mark = mark
1894-
has_const = false
1895-
scope_k = K"None"
1896-
k = peek(ps)
1897-
if k in KSet"global local"
1898-
# global x ==> (global x)
1899-
# local x ==> (local x)
1900-
scope_k = k
1901-
bump(ps, TRIVIA_FLAG)
1902-
if peek(ps) == K"const"
1903-
# global const x = 1 ==> (const (global (= x 1)))
1904-
# local const x = 1 ==> (const (local (= x 1)))
1905-
has_const = true
1906-
bump(ps, TRIVIA_FLAG)
1907-
end
1908-
else
1909-
has_const = true
1910-
# const x = 1 ==> (const (= x 1))
1911-
bump(ps, TRIVIA_FLAG)
1912-
k = peek(ps)
1913-
if k in KSet"global local"
1914-
# const global x = 1 ==> (const (global (= x 1)))
1915-
# const local x = 1 ==> (const (local (= x 1)))
1916-
scope_k = k
1917-
scope_mark = position(ps)
1918-
bump(ps, TRIVIA_FLAG)
1919-
end
1920-
end
1921-
# Like parse_assignment, but specialized so that we can omit the
1922-
# tuple when there's commas but no assignment.
1923-
beforevar_mark = position(ps)
19241954
n_commas = parse_comma(ps, false)
19251955
t = peek_token(ps)
1926-
has_assignment = is_prec_assignment(t)
1927-
if n_commas >= 1 && (has_assignment || has_const)
1956+
assign_prec = is_prec_assignment(t)
1957+
if n_commas >= 1 && assign_prec
19281958
# const x,y = 1,2 ==> (const (= (tuple x y) (tuple 1 2)))
1929-
# Maybe nonsensical? But this is what the flisp parser does.
1930-
#v1.8: const x,y ==> (const (tuple x y))
1931-
emit(ps, beforevar_mark, K"tuple")
1959+
emit(ps, mark, K"tuple")
19321960
end
1933-
if has_assignment
1961+
if assign_prec
19341962
# const x = 1 ==> (const (= x 1))
19351963
# global x ~ 1 ==> (global (call-i x ~ 1))
19361964
# global x += 1 ==> (global (+= x 1))
1937-
parse_assignment_with_initial_ex(ps, beforevar_mark, parse_comma)
1938-
else
1939-
# global x ==> (global x)
1940-
# local x ==> (local x)
1941-
# global x,y ==> (global x y)
1942-
end
1943-
if has_const && (!has_assignment || is_dotted(t))
1944-
# Const fields https://github.com/JuliaLang/julia/pull/43305
1945-
#v1.8: const x ==> (const x)
1946-
#v1.8: const x::T ==> (const (:: x T))
1947-
# Disallowed const forms on <= 1.7
1948-
#v1.7: const x ==> (const (error x))
1949-
#v1.7: const x .= 1 ==> (const (error (.= x 1)))
1950-
min_supported_version(v"1.8", ps, beforevar_mark,
1951-
"`const` struct field without assignment")
1952-
end
1953-
if scope_k != K"None"
1954-
emit(ps, scope_mark, scope_k)
1955-
end
1956-
if has_const
1957-
# TODO: Normalize `global const` during Expr conversion rather than here?
1958-
emit(ps, mark, K"const")
1965+
parse_assignment_with_initial_ex(ps, mark, parse_comma)
19591966
end
1967+
return kind(t) == K"=" && !is_dotted(t)
19601968
end
19611969

19621970
# Parse function and macro definitions

test/parser.jl

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -379,9 +379,12 @@ tests = [
379379
"primitive type A \$N end" => "(primitive A (\$ N))"
380380
"primitive type A <: B \n 8 \n end" => "(primitive (<: A B) 8)"
381381
# struct
382-
"struct A <: B \n a::X \n end" => "(struct false (<: A B) (block (:: a X)))"
383-
"mutable struct A end" => "(struct true A (block))"
384-
"struct A end" => "(struct false A (block))"
382+
"struct A <: B \n a::X \n end" => "(struct false (<: A B) (block (:: a X)))" => Expr(:struct, false, Expr(:<:, :A, :B), Expr(:block, Expr(:(::), :a, :X)))
383+
"struct A \n a \n b \n end" => "(struct false A (block a b))" => Expr(:struct, false, :A, Expr(:block, :a, :b))
384+
"mutable struct A end" => "(struct true A (block))"
385+
((v=v"1.8",), "struct A const a end") => "(struct false A (block (const a)))" => Expr(:struct, false, :A, Expr(:block, Expr(:const, :a)))
386+
((v=v"1.7",), "struct A const a end") => "(struct false A (block (error (const a))))"
387+
"struct A end" => "(struct false A (block))" => Expr(:struct, false, :A, Expr(:block))
385388
"struct try end" => "(struct false (error (try)) (block))"
386389
# return
387390
"return\nx" => "(return nothing)"
@@ -424,26 +427,22 @@ tests = [
424427
"if true; x ? true\nend" => "(if true (block (if x true (error-t) (error-t))))"
425428
"if true; x ? true : elseif true end" => "(if true (block (if x true (error-t))) (elseif true (block)))"
426429
],
427-
JuliaSyntax.parse_const_local_global => [
428-
"global x" => "(global x)"
429-
"local x" => "(local x)"
430-
"global const x = 1" => "(const (global (= x 1)))"
431-
"local const x = 1" => "(const (local (= x 1)))"
432-
"const x = 1" => "(const (= x 1))"
433-
"const global x = 1" => "(const (global (= x 1)))"
434-
"const local x = 1" => "(const (local (= x 1)))"
435-
"const x,y = 1,2" => "(const (= (tuple x y) (tuple 1 2)))"
436-
((v=v"1.8",), "const x,y") => "(const (tuple x y))"
437-
"const x = 1" => "(const (= x 1))"
438-
"global x ~ 1" => "(global (call-i x ~ 1))"
439-
"global x += 1" => "(global (+= x 1))"
440-
"global x" => "(global x)"
441-
"local x" => "(local x)"
442-
"global x,y" => "(global x y)"
443-
((v=v"1.8",), "const x") => "(const x)"
444-
((v=v"1.8",), "const x::T") => "(const (:: x T))"
445-
((v=v"1.7",), "const x") => "(const (error x))"
446-
((v=v"1.7",), "const x .= 1") => "(const (error (.= x 1)))"
430+
JuliaSyntax.parse_resword => [
431+
"global x" => "(global x)" => Expr(:global, :x)
432+
"local x" => "(local x)" => Expr(:local, :x)
433+
"global x,y" => "(global x y)" => Expr(:global, :x, :y)
434+
"global const x = 1" => "(global (const (= x 1)))" => Expr(:const, Expr(:global, Expr(:(=), :x, 1)))
435+
"local const x = 1" => "(local (const (= x 1)))" => Expr(:const, Expr(:local, Expr(:(=), :x, 1)))
436+
"const global x = 1" => "(const (global (= x 1)))" => Expr(:const, Expr(:global, Expr(:(=), :x, 1)))
437+
"const local x = 1" => "(const (local (= x 1)))" => Expr(:const, Expr(:local, Expr(:(=), :x, 1)))
438+
"const x,y = 1,2" => "(const (= (tuple x y) (tuple 1 2)))" => Expr(:const, Expr(:(=), Expr(:tuple, :x, :y), Expr(:tuple, 1, 2)))
439+
"const x = 1" => "(const (= x 1))" => Expr(:const, Expr(:(=), :x, 1))
440+
"const x .= 1" => "(error (const (.= x 1)))"
441+
"global x ~ 1" => "(global (call-i x ~ 1))" => Expr(:global, Expr(:call, :~, :x, 1))
442+
"global x += 1" => "(global (+= x 1))" => Expr(:global, Expr(:+=, :x, 1))
443+
"const x" => "(error (const x))"
444+
"global const x" => "(global (error (const x)))"
445+
"const global x" => "(error (const (global x)))"
447446
],
448447
JuliaSyntax.parse_function => [
449448
"macro while(ex) end" => "(macro (call (error while) ex) (block))"

0 commit comments

Comments
 (0)