Skip to content

Commit 45860e0

Browse files
committed
Allow parens in import paths like import A.(:+)
Some packages use this syntax, even though it's apparently pointless!
1 parent 7a5de3a commit 45860e0

File tree

5 files changed

+62
-32
lines changed

5 files changed

+62
-32
lines changed

src/expr.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,21 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true,
320320
pushfirst!(args, :*)
321321
elseif headsym === :struct
322322
pushfirst!(args, has_flags(node, MUTABLE_FLAG))
323+
elseif headsym === :import || headsym == :using
324+
# Permit nonsense additional quoting such as
325+
# import A.(:b).:c
326+
if !isempty(args) && Meta.isexpr(args[1], Symbol(":"))
327+
imports = args[1].args
328+
else
329+
imports = args
330+
end
331+
for imp in imports
332+
for i = 1:length(imp.args)
333+
if imp.args[i] isa QuoteNode
334+
imp.args[i] = imp.args[i].value
335+
end
336+
end
337+
end
323338
end
324339
return Expr(headsym, args...)
325340
end

src/parser.jl

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,21 +1444,6 @@ function parse_unary_prefix(ps::ParseState)
14441444
end
14451445
end
14461446

1447-
# Parse a symbol or interpolation syntax
1448-
function parse_identifier_or_interpolate(ps::ParseState)
1449-
mark = position(ps)
1450-
parse_unary_prefix(ps)
1451-
b = peek_behind(ps)
1452-
# export (x::T) ==> (export (error (parens (::-i x T))))
1453-
# export outer ==> (export outer)
1454-
# export ($f) ==> (export ($ f))
1455-
ok = (b.is_leaf && (b.kind == K"Identifier" || is_operator(b.kind))) ||
1456-
(!b.is_leaf && b.kind in KSet"$ var")
1457-
if !ok
1458-
emit(ps, mark, K"error", error="Expected identifier")
1459-
end
1460-
end
1461-
14621447
# Parses a chain of sufficies at function call precedence, leftmost binding
14631448
# tightest. This handles
14641449
# * Bracketed calls like a() b[] c{}
@@ -2008,7 +1993,7 @@ function parse_resword(ps::ParseState)
20081993
# export \n a ==> (export a)
20091994
# export \$a, \$(a*b) ==> (export (\$ a) (\$ (parens (call-i a * b))))
20101995
bump(ps, TRIVIA_FLAG)
2011-
parse_comma_separated(ps, parse_atsym)
1996+
parse_comma_separated(ps, x->parse_atsym(x, false))
20121997
emit(ps, mark, K"export")
20131998
elseif word in KSet"import using"
20141999
parse_imports(ps)
@@ -2098,7 +2083,7 @@ function parse_function_signature(ps::ParseState, is_function::Bool)
20982083
mark = position(ps)
20992084
if !is_function
21002085
# Parse macro name
2101-
parse_identifier_or_interpolate(ps)
2086+
parse_unary_prefix(ps)
21022087
kb = peek_behind(ps).orig_kind
21032088
if is_initial_reserved_word(ps, kb)
21042089
# macro while(ex) end ==> (macro (call (error while) ex) (block))
@@ -2111,7 +2096,9 @@ function parse_function_signature(ps::ParseState, is_function::Bool)
21112096
# macro ($f)() end ==> (macro (call (parens ($ f))) (block))
21122097
end
21132098
else
2114-
if peek(ps) == K"("
2099+
if peek(ps) != K"("
2100+
parse_unary_prefix(ps)
2101+
else
21152102
# When an initial parenthesis is present, we might either have
21162103
# * the function name in parens, followed by (args...)
21172104
# * an anonymous function argument list in parens
@@ -2151,14 +2138,12 @@ function parse_function_signature(ps::ParseState, is_function::Bool)
21512138
# function (::T)() end ==> (function (call (parens (::-pre T))) (block))
21522139
emit(ps, mark, K"parens", PARENS_FLAG)
21532140
end
2154-
else
2155-
parse_unary_prefix(ps)
21562141
end
21572142
if !is_anon_func
21582143
kb = peek_behind(ps).orig_kind
21592144
if is_reserved_word(kb)
21602145
# function begin() end ==> (function (call (error begin)) (block))
2161-
emit(ps, mark, K"error", error="Invalid function name")
2146+
emit(ps, mark, K"error", error="invalid function name")
21622147
else
21632148
# function f() end ==> (function (call f) (block))
21642149
# function type() end ==> (function (call type) (block))
@@ -2379,7 +2364,7 @@ end
23792364
# Parse an identifier, interpolation or @-prefixed symbol
23802365
#
23812366
# flisp: parse-atsym
2382-
function parse_atsym(ps::ParseState)
2367+
function parse_atsym(ps::ParseState, allow_quotes=true)
23832368
bump_trivia(ps)
23842369
if peek(ps) == K"@"
23852370
# export @a ==> (export @a)
@@ -2392,7 +2377,30 @@ function parse_atsym(ps::ParseState)
23922377
# export a ==> (export a)
23932378
# export \n a ==> (export a)
23942379
# export $a, $(a*b) ==> (export ($ a) (parens ($ (call * a b))))
2395-
parse_identifier_or_interpolate(ps)
2380+
# export (x::T) ==> (export (error (parens (::-i x T))))
2381+
# export outer ==> (export outer)
2382+
# export ($f) ==> (export ($ f))
2383+
mark = position(ps)
2384+
if allow_quotes && peek(ps) == K":"
2385+
# import A.:+ ==> (import (. A (quote +)))
2386+
emit_diagnostic(ps, warning="quoting with `:` is not required here")
2387+
end
2388+
parse_unary_prefix(ps)
2389+
pos = position(ps)
2390+
while peek_behind(ps, pos).kind == K"parens"
2391+
# import A.(:+) ==> (import (. A (parens (quote +))))
2392+
pos = first_child_position(ps, pos)
2393+
emit_diagnostic(ps, mark, warning="parentheses are not required here")
2394+
end
2395+
if allow_quotes && peek_behind(ps, pos).kind == K"quote"
2396+
pos = first_child_position(ps, pos)
2397+
end
2398+
b = peek_behind(ps, pos)
2399+
ok = (b.is_leaf && (b.kind == K"Identifier" || is_operator(b.kind))) ||
2400+
(!b.is_leaf && b.kind in KSet"$ var")
2401+
if !ok
2402+
emit(ps, mark, K"error", error="expected identifier")
2403+
end
23962404
end
23972405
end
23982406

@@ -2524,12 +2532,6 @@ function parse_import_path(ps::ParseState)
25242532
# import A.B.C ==> (import (. A B C))
25252533
bump_disallowed_space(ps)
25262534
bump(ps, TRIVIA_FLAG)
2527-
if peek(ps) == K":"
2528-
# import A.:+ ==> (import (. A +))
2529-
bump_disallowed_space(ps)
2530-
emit_diagnostic(ps, warning="quoting with `:` in import is unnecessary")
2531-
bump(ps, TRIVIA_FLAG)
2532-
end
25332535
parse_atsym(ps)
25342536
elseif is_dotted(t)
25352537
# Resolve tokenization ambiguity: In imports, dots are part of the

test/diagnostics.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ end
3636
@test diagnostic("import A .==") ==
3737
Diagnostic(9, 9, :warning, "space between dots in import path")
3838
@test diagnostic("import A.:+") ==
39-
Diagnostic(10, 10, :warning, "quoting with `:` in import is unnecessary")
39+
Diagnostic(10, 10, :warning, "quoting with `:` is not required here")
40+
@test diagnostic("import A.(:+)") ==
41+
Diagnostic(10, 13, :warning, "parentheses are not required here")
42+
@test diagnostic("export (x)") ==
43+
Diagnostic(8, 10, :warning, "parentheses are not required here")
44+
@test diagnostic("export :x") ==
45+
Diagnostic(8, 9, :error, "expected identifier")
4046
end
4147

4248
@testset "diagnostics for literal parsing" begin

test/expr.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,11 +327,17 @@
327327
Expr(:module, false, :A, Expr(:block, LineNumberNode(1), LineNumberNode(1)))
328328
end
329329

330+
330331
@testset "errors" begin
331332
@test parse(Expr, "--", ignore_errors=true) ==
332333
Expr(:error, "invalid operator: `--`")
333334
@test parseall(Expr, "a b", ignore_errors=true) ==
334335
Expr(:toplevel, LineNumberNode(1), :a,
335336
LineNumberNode(1), Expr(:error, :b))
336337
end
338+
339+
@testset "import" begin
340+
@test parse(Expr, "import A.(:b).:c: x.:z", ignore_warnings=true) ==
341+
Expr(:import, Expr(Symbol(":"), Expr(:., :A, :b, :c), Expr(:., :x, :z)))
342+
end
337343
end

test/parser.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,8 @@ tests = [
649649
"import \$A.@x" => "(import (. (\$ A) @x))"
650650
"import A.B" => "(import (. A B))"
651651
"import A.B.C" => "(import (. A B C))"
652-
"import A.:+" => "(import (. A +))"
652+
"import A.:+" => "(import (. A (quote +)))"
653+
"import A.(:+)"=> "(import (. A (parens (quote +))))"
653654
"import A.==" => "(import (. A ==))"
654655
"import A.⋆.f" => "(import (. A ⋆ f))"
655656
"import A..." => "(import (. A ..))"
@@ -942,7 +943,7 @@ parseall_test_specs = [
942943

943944
# The following may not be ideal error recovery! But at least the parser
944945
# shouldn't crash
945-
"@(x y)" => "(toplevel (macrocall (error x (error-t y))))"
946+
"@(x y)" => "(toplevel (macrocall (parens @x (error-t y))))"
946947
"|(&\nfunction" => "(toplevel (call | (& (function (error (error)) (block (error)) (error-t))) (error-t)))"
947948
]
948949

0 commit comments

Comments
 (0)