diff --git a/Makefile b/Makefile index 046f18492bc3e..eb6e54ae70b34 100644 --- a/Makefile +++ b/Makefile @@ -365,6 +365,10 @@ endif # Remove various files which should not be installed -rm -f $(DESTDIR)$(datarootdir)/julia/base/version_git.sh -rm -f $(DESTDIR)$(datarootdir)/julia/test/Makefile + -rm -f $(DESTDIR)$(datarootdir)/julia/base/*/source-extracted + -rm -f $(DESTDIR)$(datarootdir)/julia/base/*/build-configured + -rm -f $(DESTDIR)$(datarootdir)/julia/base/*/build-compiled + -rm -f $(DESTDIR)$(datarootdir)/julia/base/*/build-checked -rm -f $(DESTDIR)$(datarootdir)/julia/stdlib/$(VERSDIR)/*/source-extracted -rm -f $(DESTDIR)$(datarootdir)/julia/stdlib/$(VERSDIR)/*/build-configured -rm -f $(DESTDIR)$(datarootdir)/julia/stdlib/$(VERSDIR)/*/build-compiled diff --git a/NEWS.md b/NEWS.md index 6c60b56b7a028..d73373d95d26e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,9 @@ Julia v1.10 Release Notes New language features --------------------- +* JuliaSyntax.jl is now used as the default parser, providing better diagnostics and faster + parsing. Set environment variable `JULIA_USE_NEW_PARSER` to `0` to switch back to the old + parser if necessary (and if you find this necessary, please file an issue) ([#46372]). * `⥺` (U+297A, `\leftarrowsubset`) and `⥷` (U+2977, `\leftarrowless`) may now be used as binary operators with arrow precedence. ([#45962]) diff --git a/base/.gitignore b/base/.gitignore index e572b8ea229d0..0fab5b41fda08 100644 --- a/base/.gitignore +++ b/base/.gitignore @@ -8,3 +8,4 @@ /version_git.jl /version_git.jl.phony /userimg.jl +/JuliaSyntax diff --git a/base/Base.jl b/base/Base.jl index 1a677bf508977..1fc20293aa384 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -489,6 +489,10 @@ a_method_to_overwrite_in_test() = inferencebarrier(1) include(mod::Module, _path::AbstractString) = _include(identity, mod, _path) include(mapexpr::Function, mod::Module, _path::AbstractString) = _include(mapexpr, mod, _path) +# External libraries vendored into Base +Core.println("JuliaSyntax/src/JuliaSyntax.jl") +include(@__MODULE__, "JuliaSyntax/src/JuliaSyntax.jl") + end_base_include = time_ns() const _sysimage_modules = PkgId[] @@ -600,6 +604,9 @@ function __init__() _require_world_age[] = get_world_counter() # Prevent spawned Julia process from getting stuck waiting on Tracy to connect. delete!(ENV, "JULIA_WAIT_FOR_TRACY") + if get_bool_env("JULIA_USE_NEW_PARSER", true) === true + JuliaSyntax.enable_in_core!() + end nothing end diff --git a/base/boot.jl b/base/boot.jl index ec25fa2bc0b6d..6698d4360cc7d 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -825,9 +825,11 @@ Integer(x::Union{Float16, Float32, Float64}) = Int(x) # `_parse` must return an `svec` containing an `Expr` and the new offset as an # `Int`. # -# The internal jl_parse which will call into Core._parse if not `nothing`. +# The internal jl_parse will call into Core._parse if not `nothing`. _parse = nothing +_setparser!(parser) = setglobal!(Core, :_parse, parser) + # support for deprecated uses of internal _apply function _apply(x...) = Core._apply_iterate(Main.Base.iterate, x...) diff --git a/base/client.jl b/base/client.jl index dd529dad5281e..6e30c9991e45e 100644 --- a/base/client.jl +++ b/base/client.jl @@ -202,10 +202,7 @@ parse_input_line(s::AbstractString) = parse_input_line(String(s)) # detect the reason which caused an :incomplete expression # from the error message # NOTE: the error messages are defined in src/julia-parser.scm -incomplete_tag(ex) = :none -function incomplete_tag(ex::Expr) - Meta.isexpr(ex, :incomplete) || return :none - msg = ex.args[1] +function fl_incomplete_tag(msg::AbstractString) occursin("string", msg) && return :string occursin("comment", msg) && return :comment occursin("requires end", msg) && return :block @@ -214,6 +211,20 @@ function incomplete_tag(ex::Expr) return :other end +incomplete_tag(ex) = :none +function incomplete_tag(ex::Expr) + if ex.head !== :incomplete + return :none + elseif isempty(ex.args) + return :other + elseif ex.args[1] isa String + return fl_incomplete_tag(ex.args[1]) + else + return incomplete_tag(ex.args[1]) + end +end +incomplete_tag(exc::Meta.ParseError) = incomplete_tag(exc.detail) + function exec_options(opts) quiet = (opts.quiet != 0) startup = (opts.startupfile != 2) diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index 58f77078ddb5e..04b0791d9a79e 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -171,7 +171,7 @@ include("compiler/bootstrap.jl") ccall(:jl_set_typeinf_func, Cvoid, (Any,), typeinf_ext_toplevel) include("compiler/parsing.jl") -Core.eval(Core, :(_parse = Compiler.fl_parse)) +Core._setparser!(fl_parse) end # baremodule Compiler )) diff --git a/base/errorshow.jl b/base/errorshow.jl index 24bd3a37d5298..81f4c9c2ee9e0 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -35,6 +35,13 @@ show_index(io::IO, x::LogicalIndex) = summary(io, x.mask) show_index(io::IO, x::OneTo) = print(io, "1:", x.stop) show_index(io::IO, x::Colon) = print(io, ':') +function showerror(io::IO, ex::Meta.ParseError) + if isnothing(ex.detail) + print(io, "ParseError(", repr(ex.msg), ")") + else + showerror(io, ex.detail) + end +end function showerror(io::IO, ex::BoundsError) print(io, "BoundsError") diff --git a/base/meta.jl b/base/meta.jl index b0e0dc371b26c..ba2a5eeb6858b 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -187,8 +187,11 @@ expression. """ struct ParseError <: Exception msg::String + detail::Any end +ParseError(msg::AbstractString) = ParseError(msg, nothing) + function _parse_string(text::AbstractString, filename::AbstractString, lineno::Integer, index::Integer, options) if index < 1 || index > ncodeunits(text) + 1 @@ -233,7 +236,11 @@ function parse(str::AbstractString, pos::Integer; greedy::Bool=true, raise::Bool depwarn::Bool=true) ex, pos = _parse_string(str, "none", 1, pos, greedy ? :statement : :atom) if raise && isa(ex,Expr) && ex.head === :error - throw(ParseError(ex.args[1])) + err = ex.args[1] + if err isa String + err = ParseError(err) # For flisp parser + end + throw(err) end return ex, pos end @@ -247,20 +254,22 @@ syntax errors will raise an error; otherwise, `parse` will return an expression raise an error upon evaluation. If `depwarn` is `false`, deprecation warnings will be suppressed. -```jldoctest +```jldoctest; filter=r"(?<=Expr\\(:error).*|(?<=Expr\\(:incomplete).*" julia> Meta.parse("x = 3") :(x = 3) -julia> Meta.parse("x = ") -:($(Expr(:incomplete, "incomplete: premature end of input"))) - julia> Meta.parse("1.0.2") -ERROR: Base.Meta.ParseError("invalid numeric constant \\\"1.0.\\\"") -Stacktrace: +ERROR: ParseError: +# Error @ none:1:1 +1.0.2 +└──┘ ── invalid numeric constant [...] julia> Meta.parse("1.0.2"; raise = false) -:($(Expr(:error, "invalid numeric constant \"1.0.\""))) +:(\$(Expr(:error, "invalid numeric constant \"1.0.\""))) + +julia> Meta.parse("x = ") +:(\$(Expr(:incomplete, "incomplete: premature end of input"))) ``` """ function parse(str::AbstractString; raise::Bool=true, depwarn::Bool=true) diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 8fa40e4920eea..7312726fe2eaa 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -153,7 +153,6 @@ if Artifacts !== nothing """ end - Pkg = get(Base.loaded_modules, Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg"), nothing) diff --git a/deps/JuliaSyntax.mk b/deps/JuliaSyntax.mk new file mode 100644 index 0000000000000..e9cc0c942dbe0 --- /dev/null +++ b/deps/JuliaSyntax.mk @@ -0,0 +1,16 @@ +$(eval $(call git-external,JuliaSyntax,JULIASYNTAX,,,$(BUILDDIR))) + +$(BUILDDIR)/$(JULIASYNTAX_SRC_DIR)/build-compiled: $(BUILDDIR)/$(JULIASYNTAX_SRC_DIR)/source-extracted + @# no build steps + echo 1 > $@ + +$(eval $(call symlink_install,JuliaSyntax,$$(JULIASYNTAX_SRC_DIR),$$(JULIAHOME)/base)) + +clean-JuliaSyntax: + -rm -f $(BUILDDIR)/$(JULIASYNTAX_SRC_DIR)/build-compiled +get-JuliaSyntax: $(JULIASYNTAX_SRC_FILE) +extract-JuliaSyntax: $(BUILDDIR)/$(JULIASYNTAX_SRC_DIR)/source-extracted +configure-JuliaSyntax: extract-JuliaSyntax +compile-JuliaSyntax: $(BUILDDIR)/$(JULIASYNTAX_SRC_DIR)/build-compiled +fastcheck-JuliSyntax: check-JuliSyntax +check-JuliSyntax: compile-JuliSyntax diff --git a/deps/JuliaSyntax.version b/deps/JuliaSyntax.version new file mode 100644 index 0000000000000..2bd765e6f4535 --- /dev/null +++ b/deps/JuliaSyntax.version @@ -0,0 +1,4 @@ +JULIASYNTAX_BRANCH = main +JULIASYNTAX_SHA1 = ec51994833d78f8c5525bc1647f448dfadc370c1 +JULIASYNTAX_GIT_URL := https://github.com/JuliaLang/JuliaSyntax.jl.git +JULIASYNTAX_TAR_URL = https://api.github.com/repos/JuliaLang/JuliaSyntax.jl/tarball/$1 diff --git a/deps/Makefile b/deps/Makefile index 62bb85e72c492..ac899b634a3fa 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -36,7 +36,7 @@ BUILDDIR := $(BUILDDIR)$(MAYBE_HOST) # prevent installing libs into usr/lib64 on opensuse unexport CONFIG_SITE -DEP_LIBS := +DEP_LIBS := JuliaSyntax ifeq ($(USE_SYSTEM_LIBBLASTRAMPOLINE), 0) DEP_LIBS += blastrampoline @@ -188,7 +188,7 @@ DEP_LIBS_STAGED := $(DEP_LIBS) DEP_LIBS_STAGED_ALL := llvm llvm-tools clang llvmunwind unwind libuv pcre \ openlibm dsfmt blastrampoline openblas lapack gmp mpfr patchelf utf8proc \ objconv mbedtls libssh2 nghttp2 curl libgit2 libwhich zlib p7zip csl \ - libsuitesparse lld libtracyclient ittapi + libsuitesparse lld libtracyclient ittapi JuliaSyntax DEP_LIBS_ALL := $(DEP_LIBS_STAGED_ALL) ifneq ($(USE_BINARYBUILDER_OPENBLAS),0) @@ -248,4 +248,7 @@ include $(SRCDIR)/libwhich.mk include $(SRCDIR)/p7zip.mk include $(SRCDIR)/libtracyclient.mk +# vendored Julia libs +include $(SRCDIR)/JuliaSyntax.mk + include $(SRCDIR)/tools/uninstallers.mk diff --git a/deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/md5 b/deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/md5 new file mode 100644 index 0000000000000..e1f51dd3d711a --- /dev/null +++ b/deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/md5 @@ -0,0 +1 @@ +b1d1ccb00e422eb8b70b2120d7083bf3 diff --git a/deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/sha512 b/deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/sha512 new file mode 100644 index 0000000000000..2ac2b9ed7c903 --- /dev/null +++ b/deps/checksums/JuliaSyntax-ec51994833d78f8c5525bc1647f448dfadc370c1.tar.gz/sha512 @@ -0,0 +1 @@ +e6df6dc2b5d2a5618da0d553eed793e1192147175d84d51f725c0ea8f7b6be92fbeb37de9abee2b2f548b0f0736f836ec7e3e20e93c12f77e1a2b2058bbfd6db diff --git a/src/ast.c b/src/ast.c index bd1ffee5b76b1..06727b453d6a3 100644 --- a/src/ast.c +++ b/src/ast.c @@ -1348,8 +1348,8 @@ jl_value_t *jl_parse(const char *text, size_t text_len, jl_value_t *filename, jl_svecset(args[1], 0, jl_box_uint8pointer((uint8_t*)text)); jl_svecset(args[1], 1, jl_box_long(text_len)); args[2] = filename; - args[3] = jl_box_ulong(lineno); - args[4] = jl_box_ulong(offset); + args[3] = jl_box_long(lineno); + args[4] = jl_box_long(offset); args[5] = options; jl_task_t *ct = jl_current_task; size_t last_age = ct->world_age; diff --git a/src/toplevel.c b/src/toplevel.c index cf0104879a7b0..51ff93488426f 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -656,19 +656,28 @@ static void check_macro_rename(jl_sym_t *from, jl_sym_t *to, const char *keyword jl_errorf("cannot rename non-macro \"%s\" to macro \"%s\" in \"%s\"", n1, n2, keyword); } -// Format msg and eval `throw(ErrorException(msg)))` in module `m`. -// Used in `jl_toplevel_eval_flex` instead of `jl_errorf` so that the error +// Eval `throw(ErrorException(msg)))` in module `m`. +// Used in `jl_toplevel_eval_flex` instead of `jl_throw` so that the error // location in julia code gets into the backtrace. -static void jl_eval_errorf(jl_module_t *m, const char* fmt, ...) +static void jl_eval_throw(jl_module_t *m, jl_value_t *exc) { jl_value_t *throw_ex = (jl_value_t*)jl_exprn(jl_call_sym, 2); JL_GC_PUSH1(&throw_ex); jl_exprargset(throw_ex, 0, jl_builtin_throw); + jl_exprargset(throw_ex, 1, exc); + jl_toplevel_eval_flex(m, throw_ex, 0, 0); + JL_GC_POP(); +} + +// Format error message and call jl_eval +static void jl_eval_errorf(jl_module_t *m, const char* fmt, ...) +{ va_list args; va_start(args, fmt); - jl_exprargset(throw_ex, 1, jl_vexceptionf(jl_errorexception_type, fmt, args)); + jl_value_t *exc = jl_vexceptionf(jl_errorexception_type, fmt, args); va_end(args); - jl_toplevel_eval_flex(m, throw_ex, 0, 0); + JL_GC_PUSH1(&exc); + jl_eval_throw(m, exc); JL_GC_POP(); } @@ -875,7 +884,7 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int jl_eval_errorf(m, "malformed \"%s\" expression", jl_symbol_name(head)); if (jl_is_string(jl_exprarg(ex, 0))) jl_eval_errorf(m, "syntax: %s", jl_string_data(jl_exprarg(ex, 0))); - jl_throw(jl_exprarg(ex, 0)); + jl_eval_throw(m, jl_exprarg(ex, 0)); } else if (jl_is_symbol(ex)) { JL_GC_POP(); diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 6ec7074f105fd..20d26953eb22b 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -232,7 +232,10 @@ function complete_keyword(s::Union{String,SubString{String}}) Completion[KeywordCompletion(kw) for kw in sorted_keywords[r]] end -function complete_path(path::AbstractString, pos::Int; use_envpath=false, shell_escape=false) +function complete_path(path::AbstractString, pos::Int; + use_envpath=false, shell_escape=false, + string_escape=false) + @assert !(shell_escape && string_escape) if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path) # if the path is just "~", don't consider the expanded username as a prefix if path == "~" @@ -259,9 +262,9 @@ function complete_path(path::AbstractString, pos::Int; use_envpath=false, shell_ matches = Set{String}() for file in files if startswith(file, prefix) - id = try isdir(joinpath(dir, file)) catch; false end - # joinpath is not used because windows needs to complete with double-backslash - push!(matches, id ? file * (@static Sys.iswindows() ? "\\\\" : "/") : file) + p = joinpath(dir, file) + is_dir = try isdir(p) catch; false end + push!(matches, is_dir ? joinpath(file, "") : file) end end @@ -307,8 +310,14 @@ function complete_path(path::AbstractString, pos::Int; use_envpath=false, shell_ end end - matchList = Completion[PathCompletion(shell_escape ? replace(s, r"\s" => s"\\\0") : s) for s in matches] - startpos = pos - lastindex(prefix) + 1 - count(isequal(' '), prefix) + function do_escape(s) + return shell_escape ? replace(s, r"(\s|\\)" => s"\\\0") : + string_escape ? escape_string(s, ('\"','$')) : + s + end + + matchList = Completion[PathCompletion(do_escape(s)) for s in matches] + startpos = pos - lastindex(do_escape(prefix)) + 1 # The pos - lastindex(prefix) + 1 is correct due to `lastindex(prefix)-lastindex(prefix)==0`, # hence we need to add one to get the first index. This is also correct when considering # pos, because pos is the `lastindex` a larger string which `endswith(path)==true`. @@ -767,7 +776,7 @@ end function close_path_completion(str, startpos, r, paths, pos) length(paths) == 1 || return false # Only close if there's a single choice... _path = str[startpos:prevind(str, first(r))] * (paths[1]::PathCompletion).path - path = expanduser(replace(_path, r"\\ " => " ")) + path = expanduser(unescape_string(replace(_path, "\\\$"=>"\$", "\\\""=>"\""))) # ...except if it's a directory... try isdir(path) @@ -1039,23 +1048,44 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif dotpos = something(findprev(isequal('.'), string, first(varrange)-1), 0) return complete_identifiers!(Completion[], ffunc, context_module, string, string[startpos:pos], pos, dotpos, startpos) - # otherwise... - elseif inc_tag in [:cmd, :string] + elseif inc_tag === :cmd m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) startpos = nextind(partial, reverseind(partial, m.offset)) r = startpos:pos + # This expansion with "\\ "=>' ' replacement and shell_escape=true + # assumes the path isn't further quoted within the cmd backticks. expanded = complete_expanduser(replace(string[r], r"\\ " => " "), r) expanded[3] && return expanded # If user expansion available, return it - paths, r, success = complete_path(replace(string[r], r"\\ " => " "), pos) + paths, r, success = complete_path(replace(string[r], r"\\ " => " "), pos, + shell_escape=true) + + return sort!(paths, by=p->p.path), r, success + elseif inc_tag === :string + # Find first non-escaped quote + m = match(r"\"(?!\\)", reverse(partial)) + startpos = nextind(partial, reverseind(partial, m.offset)) + r = startpos:pos + + expanded = complete_expanduser(string[r], r) + expanded[3] && return expanded # If user expansion available, return it - if inc_tag === :string && close_path_completion(string, startpos, r, paths, pos) - paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"") + path_prefix = try + unescape_string(replace(string[r], "\\\$"=>"\$", "\\\""=>"\"")) + catch + nothing end + if !isnothing(path_prefix) + paths, r, success = complete_path(path_prefix, pos, string_escape=true) - #Latex symbols can be completed for strings - (success || inc_tag === :cmd) && return sort!(paths, by=p->p.path), r, success + if close_path_completion(string, startpos, r, paths, pos) + paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"") + end + + # Fallthrough allowed so that Latex symbols can be completed in strings + success && return sort!(paths, by=p->p.path), r, success + end end ok, ret = bslash_completions(string, pos) diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index b0d1ff4b5237a..b2199e10bef55 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -1177,7 +1177,7 @@ let current_dir, forbidden catch e e isa Base.IOError && occursin("ELOOP", e.msg) end - c, r = test_complete("\"$(joinpath(path, "selfsym"))") + c, r = test_complete("\""*escape_string(joinpath(path, "selfsym"))) @test c == ["selfsymlink"] end end @@ -1207,26 +1207,62 @@ end mktempdir() do path space_folder = randstring() * " α" dir = joinpath(path, space_folder) - dir_space = replace(space_folder, " " => "\\ ") - mkdir(dir) cd(path) do - open(joinpath(space_folder, "space .file"),"w") do f - s = Sys.iswindows() ? "rm $dir_space\\\\space" : "cd $dir_space/space" - c, r = test_scomplete(s) - @test r == lastindex(s)-4:lastindex(s) - @test "space\\ .file" in c + touch(joinpath(space_folder, "space .file")) + + dir_space = replace(space_folder, " " => "\\ ") + s = Sys.iswindows() ? "cd $dir_space\\\\space" : "cd $dir_space/space" + c, r = test_scomplete(s) + @test s[r] == "space" + @test "space\\ .file" in c + # Also use shell escape rules within cmd backticks + s = "`$s" + c, r = test_scomplete(s) + @test s[r] == "space" + @test "space\\ .file" in c + + # escape string according to Julia escaping rules + julia_esc(str) = escape_string(str, ('\"','$')) + + # For normal strings the string should be properly escaped according to + # the usual rules for Julia strings. + s = "cd(\"" * julia_esc(joinpath(path, space_folder, "space")) + c, r = test_complete(s) + @test s[r] == "space" + @test "space .file\"" in c + + # '$' is the only character which can appear in a windows filename and + # which needs to be escaped in Julia strings (on unix we could do this + # test with all sorts of special chars) + touch(joinpath(space_folder, "needs_escape\$.file")) + escpath = julia_esc(joinpath(path, space_folder, "needs_escape\$")) + s = "cd(\"$escpath" + c, r = test_complete(s) + @test s[r] == "needs_escape\\\$" + @test "needs_escape\\\$.file\"" in c - s = Sys.iswindows() ? "cd(\"β $dir_space\\\\space" : "cd(\"β $dir_space/space" + if !Sys.iswindows() + touch(joinpath(space_folder, "needs_escape2\n\".file")) + escpath = julia_esc(joinpath(path, space_folder, "needs_escape2\n\"")) + s = "cd(\"$escpath" c, r = test_complete(s) - @test r == lastindex(s)-4:lastindex(s) - @test "space .file\"" in c + @test s[r] == "needs_escape2\\n\\\"" + @test "needs_escape2\\n\\\".file\"" in c + + touch(joinpath(space_folder, "needs_escape3\\.file")) + escpath = julia_esc(joinpath(path, space_folder, "needs_escape3\\")) + s = "cd(\"$escpath" + c, r = test_complete(s) + @test s[r] == "needs_escape3\\\\" + @test "needs_escape3\\\\.file\"" in c end + # Test for issue #10324 - s = "cd(\"$dir_space" + s = "cd(\"$space_folder" c, r = test_complete(s) - @test r == 5:15 - @test s[r] == dir_space + @test r == 5:14 + @test s[r] == space_folder #Test for #18479 for c in "'`@\$;&" @@ -1240,8 +1276,9 @@ mktempdir() do path @test c[1] == test_dir*(Sys.iswindows() ? "\\\\" : "/") @test res end - c, r, res = test_complete("\""*test_dir) - @test c[1] == test_dir*(Sys.iswindows() ? "\\\\" : "/") + escdir = julia_esc(test_dir) + c, r, res = test_complete("\""*escdir) + @test c[1] == escdir*(Sys.iswindows() ? "\\\\" : "/") @test res finally rm(joinpath(path, test_dir), recursive=true) @@ -1285,7 +1322,7 @@ if Sys.iswindows() @test r == length(s)-1:length(s) @test file in c - s = "cd(\"..\\" + s = "cd(\"..\\\\" c,r = test_complete(s) @test r == length(s)+1:length(s) @test temp_name * "\\\\" in c diff --git a/test/backtrace.jl b/test/backtrace.jl index 38019880da35d..50a50100488c4 100644 --- a/test/backtrace.jl +++ b/test/backtrace.jl @@ -195,6 +195,13 @@ let bt, found = false end # Syntax error locations appear in backtraces +let trace = try + eval(Expr(:error, 1)) + catch + stacktrace(catch_backtrace()) + end + @test trace[1].func === Symbol("top-level scope") +end let trace = try include_string(@__MODULE__, """ @@ -221,7 +228,7 @@ let trace = try end @test trace[1].func === Symbol("top-level scope") @test trace[1].file === :a_filename - @test trace[1].line == 2 + @test trace[1].line == 3 end # issue #45171 diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index 9c8c0ac553c24..13a68be2927de 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -924,7 +924,7 @@ let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` close(in) close(err.in) txt = readline(err) - @test startswith(txt, "ERROR: syntax: incomplete") + @test startswith(txt, r"ERROR: (syntax: incomplete|ParseError:)") end # Issue #29855 diff --git a/test/show.jl b/test/show.jl index f2c553b3ff49a..25c5a49372054 100644 --- a/test/show.jl +++ b/test/show.jl @@ -633,7 +633,7 @@ end @test_repr "::@m(x, y) + z" @test_repr "[@m(x) y z]" @test_repr "[@m(x) y; z]" -@test_repr "let @m(x), y=z; end" +test_repr("let @m(x), y=z; end", true) @test repr(:(@m x y)) == ":(#= $(@__FILE__):$(@__LINE__) =# @m x y)" @test string(:(@m x y)) == "#= $(@__FILE__):$(@__LINE__) =# @m x y" diff --git a/test/strings/basic.jl b/test/strings/basic.jl index 7151a4d4fd60a..13f2f5197187a 100644 --- a/test/strings/basic.jl +++ b/test/strings/basic.jl @@ -250,8 +250,6 @@ end @test string(sym) == string(Char(0xdcdb)) @test String(sym) == string(Char(0xdcdb)) @test Meta.lower(Main, sym) === sym - @test Meta.parse(string(Char(0xe0080)," = 1"), 1, raise=false)[1] == - Expr(:error, "invalid character \"\Ue0080\" near column 1") end @testset "Symbol and gensym" begin @@ -761,11 +759,6 @@ function getData(dic) end @test getData(Dict()) == ",,,,,,,,,,,,,,,,,," -@testset "unrecognized escapes in string/char literals" begin - @test_throws Meta.ParseError Meta.parse("\"\\.\"") - @test_throws Meta.ParseError Meta.parse("\'\\.\'") -end - @testset "thisind" begin let strs = Any["∀α>β:α+1>β", s"∀α>β:α+1>β", SubString("123∀α>β:α+1>β123", 4, 18), diff --git a/test/syntax.jl b/test/syntax.jl index 119f6d427a15a..4d1b167693adb 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -5,22 +5,29 @@ using Random using Base: remove_linenums! -import Base.Meta.ParseError - -function parseall(str) - pos = firstindex(str) - exs = [] - while pos <= lastindex(str) - ex, pos = Meta.parse(str, pos) - push!(exs, ex) - end - if length(exs) == 0 - throw(ParseError("end of input")) - elseif length(exs) == 1 - return exs[1] +using_JuliaSyntax = parentmodule(Core._parse) != Core.Compiler + +macro test_parseerror(str, msg) + if using_JuliaSyntax + # Diagnostics are tested separately in JuliaSyntax + ex = :(@test_throws Meta.ParseError Meta.parse($(esc(str)))) else - return Expr(:block, exs...) + ex = :(@test_throws Meta.ParseError($(esc(msg))) Meta.parse($(esc(str)))) end + ex.args[2] = __source__ + return ex +end + +macro test_parseerror(str) + ex = :(@test_throws Meta.ParseError Meta.parse($(esc(str)))) + ex.args[2] = __source__ + return ex +end + +function parseall_nolines(str) + ex = Meta.parseall(str) + filter!(e->!(e isa LineNumberNode), ex.args) + return ex end # issue #9684 @@ -60,19 +67,19 @@ macro test999_str(args...); args; end @test test999"foo"123 == ("foo", 123) # issue #5997 -@test_throws ParseError Meta.parse(": x") -@test_throws ParseError Meta.parse("""begin +@test_parseerror ": x" +@test_parseerror """begin : - x""") -@test_throws ParseError Meta.parse("d[: 2]") + x""" +@test_parseerror "d[: 2]" # issue #6770 -@test_throws ParseError Meta.parse("x.3") +@test_parseerror "x.3" # issue #8763 -@test_throws ParseError Meta.parse("sqrt(16)2") -@test_throws ParseError Meta.parse("x' y") -@test_throws ParseError Meta.parse("x 'y") +@test_parseerror "sqrt(16)2" +@test_parseerror "x' y" +@test_parseerror "x 'y" @test Meta.parse("x'y") == Expr(:call, :*, Expr(Symbol("'"), :x), :y) # issue #18851 @@ -84,22 +91,22 @@ macro test999_str(args...); args; end @test Meta.parse("-2(m)") == Expr(:call, :*, -2, :m) # issue #8301 -@test_throws ParseError Meta.parse("&*s") +@test_parseerror "&*s" # issue #10677 -@test_throws ParseError Meta.parse("/1") -@test_throws ParseError Meta.parse("/pi") +@test_parseerror "/1" +@test_parseerror "/pi" @test Meta.parse("- = 2") == Expr(:(=), :(-), 2) @test Meta.parse("/ = 2") == Expr(:(=), :(/), 2) -@test_throws ParseError Meta.parse("< : 2") -@test_throws ParseError Meta.parse("+ : 2") -@test_throws ParseError Meta.parse("< :2") +@test_parseerror "< : 2" +@test_parseerror "+ : 2" +@test_parseerror "< :2" @test Meta.parse("+ :2") == Expr(:call, :(+), QuoteNode(2)) # issue #10900 -@test_throws ParseError Meta.parse("+=") -@test_throws ParseError Meta.parse(".") -@test_throws ParseError Meta.parse("...") +@test_parseerror "+=" +@test_parseerror "." +@test_parseerror "..." # issue #10901 @test Meta.parse("/([1], 1)[1]") == :(([1] / 1)[1]) @@ -152,35 +159,35 @@ macro test999_str(args...); args; end Expr(:., Expr(:$, :c), Expr(:$, :d)))) # fix pr #11338 and test for #11497 -@test parseall("using \$\na") == Expr(:block, Expr(:using, Expr(:., :$)), :a) -@test parseall("using \$,\na") == Expr(:using, Expr(:., :$), Expr(:., :a)) -@test parseall("using &\na") == Expr(:block, Expr(:using, Expr(:., :&)), :a) +@test parseall_nolines("using \$\na") == Expr(:toplevel, Expr(:using, Expr(:., :$)), :a) +@test parseall_nolines("using \$,\na") == Expr(:toplevel, Expr(:using, Expr(:., :$), Expr(:., :a))) +@test parseall_nolines("using &\na") == Expr(:toplevel, Expr(:using, Expr(:., :&)), :a) -@test parseall("a = &\nb") == Expr(:block, Expr(:(=), :a, :&), :b) -@test parseall("a = \$\nb") == Expr(:block, Expr(:(=), :a, :$), :b) -@test parseall(":(a = &\nb)") == Expr(:quote, Expr(:(=), :a, Expr(:&, :b))) -@test parseall(":(a = \$\nb)") == Expr(:quote, Expr(:(=), :a, Expr(:$, :b))) +@test parseall_nolines("a = &\nb") == Expr(:toplevel, Expr(:(=), :a, :&), :b) +@test parseall_nolines("a = \$\nb") == Expr(:toplevel, Expr(:(=), :a, :$), :b) +@test parseall_nolines(":(a = &\nb)") == Expr(:toplevel, Expr(:quote, Expr(:(=), :a, Expr(:&, :b)))) +@test parseall_nolines(":(a = \$\nb)") == Expr(:toplevel, Expr(:quote, Expr(:(=), :a, Expr(:$, :b)))) # issue 12027 - short macro name parsing vs _str suffix -@test parseall(""" - macro f(args...) end; @f "macro argument" +@test parseall_nolines(""" + macro f(args...) end\n@f "macro argument" """) == Expr(:toplevel, Expr(:macro, Expr(:call, :f, Expr(:..., :args)), Expr(:block, LineNumberNode(1, :none), LineNumberNode(1, :none))), - Expr(:macrocall, Symbol("@f"), LineNumberNode(1, :none), "macro argument")) + Expr(:macrocall, Symbol("@f"), LineNumberNode(2, :none), "macro argument")) # blocks vs. tuples @test Meta.parse("()") == Expr(:tuple) @test Meta.parse("(;)") == Expr(:tuple, Expr(:parameters)) @test Meta.parse("(;;)") == Expr(:block) @test Meta.parse("(;;;;)") == Expr(:block) -@test_throws ParseError Meta.parse("(,)") -@test_throws ParseError Meta.parse("(;,)") -@test_throws ParseError Meta.parse("(,;)") +@test_parseerror "(,)" +@test_parseerror "(;,)" +@test_parseerror "(,;)" # TODO: would be nice to make these errors, but needed to parse e.g. `(x;y,)->x` -#@test_throws ParseError Meta.parse("(1;2,)") -#@test_throws ParseError Meta.parse("(1;2,;)") -#@test_throws ParseError Meta.parse("(1;2,;3)") +#@test_parseerror "(1;2,)" +#@test_parseerror "(1;2,;)" +#@test_parseerror "(1;2,;3)" @test Meta.parse("(x;)") == Expr(:block, :x) @test Meta.parse("(;x)") == Expr(:tuple, Expr(:parameters, :x)) @test Meta.parse("(;x,)") == Expr(:tuple, Expr(:parameters, :x)) @@ -197,7 +204,7 @@ macro test999_str(args...); args; end @test Meta.parse("(x,a;y=1)") == Expr(:tuple, Expr(:parameters, Expr(:kw, :y, 1)), :x, :a) @test Meta.parse("(x,a;y=1,z=2)") == Expr(:tuple, Expr(:parameters, Expr(:kw,:y,1), Expr(:kw,:z,2)), :x, :a) @test Meta.parse("(a=1, b=2)") == Expr(:tuple, Expr(:(=), :a, 1), Expr(:(=), :b, 2)) -@test_throws ParseError Meta.parse("(1 2)") # issue #15248 +@test_parseerror "(1 2)" # issue #15248 @test Meta.parse("f(x;)") == Expr(:call, :f, Expr(:parameters), :x) @@ -268,13 +275,16 @@ end @test_throws BoundsError Meta.parse("x = 1", 7) # issue #14683 -@test_throws ParseError Meta.parse("'\\A\"'") +@test_parseerror "'\\A\"'" @test Meta.parse("'\"'") == Meta.parse("'\\\"'") == '"' == "\""[1] == '\42' # issue #24558 @test '\u2200' == "\u2200"[1] -@test_throws ParseError Meta.parse("f(2x for x=1:10, y") +if !using_JuliaSyntax + # This should be Expr(:incomplete) + @test_parseerror "f(2x for x=1:10, y" +end # issue #15223 call0(f) = f() @@ -310,11 +320,6 @@ let p = 15 @test 2p+1 == 31 # not a hex float literal end -macro test_parseerror(str, msg) - ex = :(@test_throws ParseError($(esc(msg))) Meta.parse($(esc(str)))) - ex.args[2] = __source__ - return ex -end @test_parseerror("0x", "invalid numeric constant \"0x\"") @test_parseerror("0b", "invalid numeric constant \"0b\"") @test_parseerror("0o", "invalid numeric constant \"0o\"") @@ -322,9 +327,8 @@ end @test_parseerror("0x1.0p", "invalid numeric constant \"0x1.0\"") # issue #15798 -@test Meta.lower(Main, Base.parse_input_line(""" - try = "No" - """)) == Expr(:error, "unexpected \"=\"") +# lowering preserves Expr(:error) +@test Meta.lower(Main, Expr(:error, "no")) == Expr(:error, "no") # issue #19861 make sure macro-expansion happens in the newest world for top-level expression @test eval(Base.parse_input_line(""" @@ -368,9 +372,9 @@ add_method_to_glob_fn!() @test f15844(Int64(1)) == 3 # issue #15661 -@test_throws ParseError Meta.parse("function catch() end") -@test_throws ParseError Meta.parse("function end() end") -@test_throws ParseError Meta.parse("function finally() end") +@test_parseerror "function catch() end" +@test_parseerror "function end() end" +@test_parseerror "function finally() end" # PR #16170 @test Meta.lower(Main, Meta.parse("true(x) = x")) == Expr(:error, "invalid function name \"true\"") @@ -421,18 +425,18 @@ end :y)) # test that pre 0.5 deprecated syntax is a parse error -@test_throws ParseError Meta.parse("Int [1,2,3]") -@test_throws ParseError Meta.parse("Int [x for x in 1:10]") -@test_throws ParseError Meta.parse("foo (x) = x") -@test_throws ParseError Meta.parse("foo {T<:Int}(x::T) = x") +@test_parseerror "Int [1,2,3]" +@test_parseerror "Int [x for x in 1:10]" +@test_parseerror "foo (x) = x" +@test_parseerror "foo {T<:Int}(x::T) = x" -@test_throws ParseError Meta.parse("Foo .bar") +@test_parseerror "Foo .bar" -@test_throws ParseError Meta.parse("import x .y") -@test_throws ParseError Meta.parse("using x .y") +@test_parseerror "import x .y" +@test_parseerror "using x .y" -@test_throws ParseError Meta.parse("--x") -@test_throws ParseError Meta.parse("stagedfunction foo(x); end") +@test_parseerror "--x" +@test_parseerror "stagedfunction foo(x); end" @test Meta.parse("A=>B") == Expr(:call, :(=>), :A, :B) @@ -448,7 +452,7 @@ end @test Meta.parse("[a,;c]") == Expr(:vect, Expr(:parameters, :c), :a) @test Meta.parse("a[b,c;d]") == Expr(:ref, :a, Expr(:parameters, :d), :b, :c) @test Meta.parse("a[b,;d]") == Expr(:ref, :a, Expr(:parameters, :d), :b) -@test_throws ParseError Meta.parse("[a,;,b]") +@test_parseerror "[a,;,b]" @test Meta.parse("{a,b;c}") == Expr(:braces, Expr(:parameters, :c), :a, :b) @test Meta.parse("{a,;c}") == Expr(:braces, Expr(:parameters, :c), :a) @test Meta.parse("a{b,c;d}") == Expr(:curly, :a, Expr(:parameters, :d), :b, :c) @@ -534,10 +538,13 @@ for (str, tag) in Dict("" => :none, "\"" => :string, "#=" => :comment, "'" => :c "let;" => :block, "for i=1;" => :block, "function f();" => :block, "f() do x;" => :block, "module X;" => :block, "mutable struct X;" => :block, "struct X;" => :block, "(" => :other, "[" => :other, - "begin" => :other, "quote" => :other, - "let" => :other, "for" => :other, "function" => :other, + "for" => :other, "function" => :other, "f() do" => :other, "module" => :other, "mutable struct" => :other, - "struct" => :other) + "struct" => :other, + "quote" => using_JuliaSyntax ? :block : :other, + "let" => using_JuliaSyntax ? :block : :other, + "begin" => using_JuliaSyntax ? :block : :other, + ) @test Base.incomplete_tag(Meta.parse(str, raise=false)) == tag end @@ -622,7 +629,7 @@ end # issue 10046 for op in ["+", "-", "\$", "|", ".+", ".-", "*", ".*"] - @test_throws ParseError Meta.parse("$op in [+, -]") + @test_parseerror "$op in [+, -]" end # issue #17701 @@ -634,7 +641,7 @@ end # PR #15592 let str = "[1] [2]" - @test_throws ParseError Meta.parse(str) + @test_parseerror str end # issue 15896 and PR 15913 @@ -997,14 +1004,14 @@ end @test Test21604.X(1.0) === Test21604.X(1.0) # issue #20575 -@test_throws ParseError Meta.parse("\"a\"x") -@test_throws ParseError Meta.parse("\"a\"begin end") -@test_throws ParseError Meta.parse("\"a\"begin end\"b\"") +@test_parseerror "\"a\"x" +@test_parseerror "\"a\"begin end" +@test_parseerror "\"a\"begin end\"b\"" # issue #16427 -@test_throws ParseError Meta.parse("for i=1:1 end(3)") -@test_throws ParseError Meta.parse("begin end(3)") -@test_throws ParseError Meta.parse("while false end(3)") +@test_parseerror "for i=1:1 end(3)" +@test_parseerror "begin end(3)" +@test_parseerror "while false end(3)" # comment 298107224 on pull #21607 module Test21607 @@ -1065,7 +1072,7 @@ end === (3, String) @test Meta.parse("3 +⁽¹⁾ 4") == Expr(:call, :+⁽¹⁾, 3, 4) @test Meta.parse("3 +₍₀₎ 4") == Expr(:call, :+₍₀₎, 3, 4) for bad in ('=', '$', ':', "||", "&&", "->", "<:") - @test_throws ParseError Meta.parse("3 $(bad)⁽¹⁾ 4") + @test_parseerror "3 $(bad)⁽¹⁾ 4" end @test Base.operator_precedence(:+̂) == Base.operator_precedence(:+) @@ -1080,20 +1087,20 @@ end Expr(:tuple, :x, :y), Expr(:tuple, 1, 2))) -@test_throws ParseError Meta.parse("[2for i=1:10]") -@test_throws ParseError Meta.parse("[1 for i in 1:2for j in 2]") -@test_throws ParseError Meta.parse("(1 for i in 1:2for j in 2)") +@test_parseerror "[2for i=1:10]" +@test_parseerror "[1 for i in 1:2for j in 2]" +@test_parseerror "(1 for i in 1:2for j in 2)" # issue #20441 -@test_throws ParseError Meta.parse("[x.2]") -@test_throws ParseError Meta.parse("x.2") +@test_parseerror "[x.2]" +@test_parseerror "x.2" @test Meta.parse("[x;.2]") == Expr(:vcat, :x, 0.2) # issue #22840 @test Meta.parse("[:a :b]") == Expr(:hcat, QuoteNode(:a), QuoteNode(:b)) # issue #22868 -@test_throws ParseError Meta.parse("x@time 2") -@test_throws ParseError Meta.parse("@ time") +@test_parseerror "x@time 2" +@test_parseerror "@ time" # issue #7479 @test Meta.lower(Main, Meta.parse("(true &&& false)")) == Expr(:error, "invalid syntax &false") @@ -1102,9 +1109,9 @@ end @test Meta.lower(Main, :(&(1, 2))) == Expr(:error, "invalid syntax &(1, 2)") # if an indexing expression becomes a cat expression, `end` is not special -@test_throws ParseError Meta.parse("a[end end]") -@test_throws ParseError Meta.parse("a[end;end]") -#@test_throws ParseError Meta.parse("a[end;]") # this is difficult to fix +@test_parseerror "a[end end]" +@test_parseerror "a[end;end]" +#@test_parseerror "a[end;]" # this is difficult to fix let a = rand(8), i = 3 @test a[[1:i-1; i+1:end]] == a[[1,2,4,5,6,7,8]] end @@ -1115,12 +1122,12 @@ end end for i = 1:5] == fill(nothing, 5) # issue #18912 -@test_throws ParseError Meta.parse("(::)") +@test_parseerror "(::)" @test Meta.parse(":(::)") == QuoteNode(Symbol("::")) -@test_throws ParseError Meta.parse("f(::) = ::") +@test_parseerror "f(::) = ::" @test Meta.parse("(::A)") == Expr(Symbol("::"), :A) -@test_throws ParseError Meta.parse("(::, 1)") -@test_throws ParseError Meta.parse("(1, ::)") +@test_parseerror "(::, 1)" +@test_parseerror "(1, ::)" # issue #18650 let ex = Meta.parse("maximum(@elapsed sleep(1) for k = 1:10)") @@ -1192,10 +1199,10 @@ M24289.@m24289 # parsing numbers with _ and . @test Meta.parse("1_2.3_4") == 12.34 -@test_throws ParseError Meta.parse("1._") -@test_throws ParseError Meta.parse("1._5") -@test_throws ParseError Meta.parse("1e.3") -@test_throws ParseError Meta.parse("1e3.") +@test_parseerror "1._" +@test_parseerror "1._5" +@test_parseerror "1e.3" +@test_parseerror "1e3." @test Meta.parse("2e_1") == Expr(:call, :*, 2, :e_1) # issue #17705 @test Meta.parse("2e3_") == Expr(:call, :*, 2e3, :_) @@ -1261,8 +1268,10 @@ end @test raw"x \\\ y" == "x \\\\\\ y" end -@test_throws ParseError("expected \"}\" or separator in arguments to \"{ }\"; got \"V)\"") Meta.parse("f(x::V) where {V) = x") -@test_throws ParseError("expected \"]\" or separator in arguments to \"[ ]\"; got \"1)\"") Meta.parse("[1)") +@test_parseerror("f(x::V) where {V) = x", + "expected \"}\" or separator in arguments to \"{ }\"; got \"V)\"") +@test_parseerror("[1)", + "expected \"]\" or separator in arguments to \"[ ]\"; got \"1)\"") # issue #9972 @test Meta.lower(@__MODULE__, :(f(;3))) == Expr(:error, "invalid keyword argument syntax \"3\"") @@ -1310,7 +1319,7 @@ let getindex = 0, setindex! = 1, colon = 2, vcat = 3, hcat = 4, hvcat = 5 end # issue #25020 -@test_throws ParseError Meta.parse("using Colors()") +@test_parseerror "using Colors()" let ex = Meta.parse("md\"x\" f(x) = x", 1)[1] # custom string literal is not a docstring @@ -1364,18 +1373,18 @@ end @test Meta.parse("-(x;;;)^2") == Expr(:call, :-, Expr(:call, :^, Expr(:block, :x), 2)) @test Meta.parse("+((1,2))") == Expr(:call, :+, Expr(:tuple, 1, 2)) -@test_throws ParseError("space before \"(\" not allowed in \"+ (\" at none:1") Meta.parse("1 -+ (a=1, b=2)") +@test_parseerror "1 -+ (a=1, b=2)" "space before \"(\" not allowed in \"+ (\" at none:1" # issue #29781 -@test_throws ParseError("space before \"(\" not allowed in \"sin. (\" at none:1") Meta.parse("sin. (1)") +@test_parseerror "sin. (1)" "space before \"(\" not allowed in \"sin. (\" at none:1" # Parser errors for disallowed space contain line numbers -@test_throws ParseError("space before \"[\" not allowed in \"f() [\" at none:2") Meta.parse("\nf() [i]") -@test_throws ParseError("space before \"(\" not allowed in \"f() (\" at none:2") Meta.parse("\nf() (i)") -@test_throws ParseError("space before \".\" not allowed in \"f() .\" at none:2") Meta.parse("\nf() .i") -@test_throws ParseError("space before \"{\" not allowed in \"f() {\" at none:2") Meta.parse("\nf() {i}") -@test_throws ParseError("space before \"m\" not allowed in \"@ m\" at none:2") Meta.parse("\n@ m") -@test_throws ParseError("space before \".\" not allowed in \"a .\" at none:2") Meta.parse("\nusing a .b") -@test_throws ParseError("space before \".\" not allowed in \"a .\" at none:2") Meta.parse("\nusing a .b") -@test_throws ParseError("space before \"(\" not allowed in \"+ (\" at none:2") Meta.parse("\n+ (x, y)") +@test_parseerror "\nf() [i]" "space before \"[\" not allowed in \"f() [\" at none:2" +@test_parseerror "\nf() (i)" "space before \"(\" not allowed in \"f() (\" at none:2" +@test_parseerror "\nf() .i" "space before \".\" not allowed in \"f() .\" at none:2" +@test_parseerror "\nf() {i}" "space before \"{\" not allowed in \"f() {\" at none:2" +@test_parseerror "\n@ m" "space before \"m\" not allowed in \"@ m\" at none:2" +@test_parseerror "\nusing a .b" "space before \".\" not allowed in \"a .\" at none:2" +@test_parseerror "\nusing a .b" "space before \".\" not allowed in \"a .\" at none:2" +@test_parseerror "\n+ (x, y)" "space before \"(\" not allowed in \"+ (\" at none:2" @test Meta.parse("1 -+(a=1, b=2)") == Expr(:call, :-, 1, Expr(:call, :+, Expr(:kw, :a, 1), Expr(:kw, :b, 2))) @@ -1397,7 +1406,7 @@ end @test Meta.parse("-√2") == Expr(:call, :-, Expr(:call, :√, 2)) @test Meta.parse("√3x^2") == Expr(:call, :*, Expr(:call, :√, 3), Expr(:call, :^, :x, 2)) @test Meta.parse("-3x^2") == Expr(:call, :*, -3, Expr(:call, :^, :x, 2)) -@test_throws ParseError Meta.parse("2!3") +@test_parseerror "2!3" # issue #27914 @test Meta.parse("2f(x)") == Expr(:call, :*, 2, Expr(:call, :f, :x)) @@ -1407,7 +1416,7 @@ end @test Meta.parse("2(x)") == Expr(:call, :*, 2, :x) @test Meta.parse("2(x)y") == Expr(:call, :*, 2, :x, :y) -@test_throws ParseError Meta.parse("a.: b") +@test_parseerror "a.: b" @test Meta.parse("a.:end") == Expr(:., :a, QuoteNode(:end)) @test Meta.parse("a.:catch") == Expr(:., :a, QuoteNode(:catch)) @test Meta.parse("a.end") == Expr(:., :a, QuoteNode(:end)) @@ -1423,7 +1432,7 @@ let len = 10 end # Module name cannot be a reserved word. -@test_throws ParseError Meta.parse("module module end") +@test_parseerror "module module end" @test Meta.lower(@__MODULE__, :(global true)) == Expr(:error, "invalid syntax in \"global\" declaration") @test Meta.lower(@__MODULE__, :(let ccall end)) == Expr(:error, "invalid identifier name \"ccall\"") @@ -1440,7 +1449,7 @@ end # issue #27690 # previously, this was allowed since it thought `end` was being used for indexing. # however the quote should disable that context. -@test_throws ParseError Meta.parse("Any[:(end)]") +@test_parseerror "Any[:(end)]" # issue #17781 let ex = Meta.lower(@__MODULE__, Meta.parse(" @@ -1671,20 +1680,20 @@ let x = @macroexpand @foo28244(var"let") end # #16356 -@test_throws ParseError Meta.parse("0xapi") +@test_parseerror "0xapi" # #22523 #22712 -@test_throws ParseError Meta.parse("a?b:c") -@test_throws ParseError Meta.parse("a ?b:c") -@test_throws ParseError Meta.parse("a ? b:c") -@test_throws ParseError Meta.parse("a ? b :c") -@test_throws ParseError Meta.parse("?") +@test_parseerror "a?b:c" +@test_parseerror "a ?b:c" +@test_parseerror "a ? b:c" +@test_parseerror "a ? b :c" +@test_parseerror "?" # #13079 @test Meta.parse("1<<2*3") == :((1<<2)*3) # #19987 -@test_throws ParseError Meta.parse("try ; catch f() ; end") +@test_parseerror "try ; catch f() ; end" # #23076 @test :([1,2;]) == Expr(:vect, Expr(:parameters), 1, 2) @@ -1721,8 +1730,8 @@ end @test Meta.lower(@__MODULE__, :(f(x) = (y = x + 1; ccall((:a, y), Cvoid, ())))) == Expr(:error, "ccall function name and library expression cannot reference local variables") -@test_throws ParseError Meta.parse("x.'") -@test_throws ParseError Meta.parse("0.+1") +@test_parseerror "x.'" +@test_parseerror "0.+1" # #24221 @test Meta.isexpr(Meta.lower(@__MODULE__, :(a=_)), :error) @@ -1816,7 +1825,7 @@ end @test Meta.parse("1⁝2") == Expr(:call, :⁝, 1, 2) @test Meta.parse("1..2") == Expr(:call, :.., 1, 2) # we don't parse chains of these since the associativity and meaning aren't clear -@test_throws ParseError Meta.parse("1..2..3") +@test_parseerror "1..2..3" # issue #30048 @test Meta.isexpr(Meta.lower(@__MODULE__, :(for a in b @@ -1990,9 +1999,9 @@ end @test Meta.parse("var\"#\"") === Symbol("#") @test Meta.parse("var\"true\"") === Symbol("true") @test Meta.parse("var\"false\"") === Symbol("false") -@test_throws ParseError Meta.parse("var\"#\"x") # Reject string macro-like suffix -@test_throws ParseError Meta.parse("var \"#\"") -@test_throws ParseError Meta.parse("var\"for\" i = 1:10; end") +@test_parseerror "var\"#\"x" # Reject string macro-like suffix +@test_parseerror "var \"#\"" +@test_parseerror "var\"for\" i = 1:10; end" # A few cases which would be ugly to deal with if var"#" were a string macro: @test Meta.parse("var\"#\".var\"a-b\"") == Expr(:., Symbol("#"), QuoteNode(Symbol("a-b"))) @test Meta.parse("export var\"#\"") == Expr(:export, Symbol("#")) @@ -2217,7 +2226,7 @@ end end # line break in : expression disallowed -@test_throws Meta.ParseError Meta.parse("[1 :\n2] == [1:2]") +@test_parseerror "[1 :\n2] == [1:2]" # added ⟂ to operator precedence (#24404) @test Meta.parse("a ⟂ b ⟂ c") == Expr(:comparison, :a, :⟂, :b, :⟂, :c) @@ -2238,7 +2247,8 @@ end end # only allow certain characters after interpolated vars (#25231) -@test Meta.parse("\"\$x෴ \"",raise=false) == Expr(:error, "interpolated variable \$x ends with invalid character \"෴\"; use \"\$(x)\" instead.") +@test_parseerror("\"\$x෴ \"", + "interpolated variable \$x ends with invalid character \"෴\"; use \"\$(x)\" instead.") @test Base.incomplete_tag(Meta.parse("\"\$foo", raise=false)) === :string @testset "issue #30341" begin @@ -2277,14 +2287,11 @@ end err = Expr( :error, - "\":\" in \"$imprt\" syntax can only be used when importing a single module. " * - "Split imports into multiple lines." ) - ex = Meta.parse("$imprt A, B: x, y", raise=false) - @test ex == err - - ex = Meta.parse("$imprt A: x, B: y", raise=false) - @test ex == err + @test_parseerror("$imprt A, B: x, y", + "\":\" in \"$imprt\" syntax can only be used when importing a single module. Split imports into multiple lines.") + @test_parseerror("$imprt A: x, B: y", + "\":\" in \"$imprt\" syntax can only be used when importing a single module. Split imports into multiple lines.") end end @@ -2304,24 +2311,31 @@ let exc = try eval(:(f(x,x)=1)) catch e ; e ; end @test !occursin("incorrect_file", exc.msg) end -# issue #34967 -@test_throws LoadError("string", 2, ErrorException("syntax: invalid UTF-8 sequence")) include_string(@__MODULE__, - "x34967 = 1\n# Halloa\xf5b\nx34967 = 2") -@test x34967 == 1 -@test_throws LoadError("string", 1, ErrorException("syntax: invalid UTF-8 sequence")) include_string(@__MODULE__, - "x\xf5 = 3\n# Halloa\xf5b\nx34967 = 4") -@test_throws LoadError("string", 3, ErrorException("syntax: invalid UTF-8 sequence")) include_string(@__MODULE__, - """ - # line 1 - # line 2 - # Hello\xf5b - x34967 = 6 - """) - -@test Meta.parse("aa\u200b_", raise=false) == - Expr(:error, "invisible character \\u200b near column 3") -@test Meta.parse("aa\UE0080", raise=false) == - Expr(:error, "invalid character \"\Ue0080\" near column 3") +@testset "issue #34967" begin + @test_parseerror "#\xf5b\nx" "invalid UTF-8 sequence" + + # Test line UTF-8 errors with line numbers + let ex = Meta.parseall("x\n#\xf5b\ny") + @test Meta.isexpr(ex, :toplevel, 4) && Meta.isexpr(last(ex.args), :error) + @test ex.args[3] == LineNumberNode(2,:none) + end + let ex = Meta.parseall("x\xf5\n#\xf5b\ny") + @test Meta.isexpr(ex, :toplevel, 2) && Meta.isexpr(last(ex.args), :error) + @test ex.args[1] == LineNumberNode(1,:none) + end + let ex = Meta.parseall("#line1\n#line2\n#\xf5b\ny") + @test Meta.isexpr(ex, :toplevel, 2) && Meta.isexpr(last(ex.args), :error) + @test ex.args[1] == LineNumberNode(3,:none) + end +end + +@test_parseerror "aa\u200b_" "invisible character \\u200b near column 3" +@test_parseerror "aa\UE0080" "invalid character \"\Ue0080\" near column 3" + +@testset "unrecognized escapes in string/char literals" begin + @test_parseerror "\"\\.\"" + @test_parseerror "\'\\.\'" +end # issue #31238 a31238, b31238 = let x @@ -2390,8 +2404,8 @@ end @test x == 6 # issue #36196 -@test_throws ParseError("\"for\" at none:1 expected \"end\", got \")\"") Meta.parse("(for i=1; println())") -@test_throws ParseError("\"try\" at none:1 expected \"end\", got \")\"") Meta.parse("(try i=1; println())") +@test_parseerror "(for i=1; println())" "\"for\" at none:1 expected \"end\", got \")\"" +@test_parseerror "(try i=1; println())" "\"try\" at none:1 expected \"end\", got \")\"" # issue #36272 macro m36272() @@ -2438,10 +2452,10 @@ end let (-->) = (+) @test (40 --> 2) == 42 end -@test_throws ParseError("invalid operator \"<---\"") Meta.parse("1<---2") -@test_throws ParseError("invalid operator \".<---\"") Meta.parse("1 .<--- 2") -@test_throws ParseError("invalid operator \"--\"") Meta.parse("a---b") -@test_throws ParseError("invalid operator \".--\"") Meta.parse("a.---b") +@test_parseerror("1<---2", "invalid operator \"<---\"") +@test_parseerror("1 .<--- 2", "invalid operator \".<---\"") +@test_parseerror("a---b", "invalid operator \"--\"") +@test_parseerror("a.---b", "invalid operator \".--\"") # issue #37228 # NOTE: the `if` needs to be at the top level @@ -2476,15 +2490,14 @@ end @test :(if true 'a' else 1 end) == Expr(:if, true, quote 'a' end, quote 1 end) # issue #37664 -@test_throws ParseError("extra token \"b\" after end of expression") Meta.parse("a b") -@test_throws ParseError("extra token \"b\" after end of expression") Meta.parse("a#==#b") -@test_throws ParseError("extra token \"b\" after end of expression") Meta.parse("a #==#b") -@test_throws ParseError("extra token \"b\" after end of expression") Meta.parse("a#==# b") - -@test_throws ParseError("extra token \"2\" after end of expression") Meta.parse("1 2") -@test_throws ParseError("extra token \"2\" after end of expression") Meta.parse("1#==#2") -@test_throws ParseError("extra token \"2\" after end of expression") Meta.parse("1 #==#2") -@test_throws ParseError("extra token \"2\" after end of expression") Meta.parse("1#==# 2") +@test_parseerror("a b", "extra token \"b\" after end of expression") +@test_parseerror("a#==#b", "extra token \"b\" after end of expression") +@test_parseerror("a #==#b", "extra token \"b\" after end of expression") +@test_parseerror("a#==# b", "extra token \"b\" after end of expression") +@test_parseerror("1 2", "extra token \"2\" after end of expression") +@test_parseerror("1#==#2", "extra token \"2\" after end of expression") +@test_parseerror("1 #==#2", "extra token \"2\" after end of expression") +@test_parseerror("1#==# 2", "extra token \"2\" after end of expression") @test size([1#==#2#==#3]) == size([1 2 3]) @test size([1#==#2#==#3]) == size([1 2 3]) # tabs @@ -2507,9 +2520,7 @@ end Meta.parse("if#==#x0#==#y+1#==#else#==#z#==#end") @test Meta.parse("function(x) x end") == Meta.parse("function(x)#==#x#==#end") @test Meta.parse("a ? b : c") == Meta.parse("a#==#?#==#b#==#:#==#c") -@test_throws ParseError("space before \"(\" not allowed in \"f (\" at none:1") begin - Meta.parse("f#==#(x)=x") -end +@test_parseerror("f#==#(x)=x", "space before \"(\" not allowed in \"f (\" at none:1") @test Meta.parse("try f() catch e g() finally h() end") == Meta.parse("try#==#f()#==#catch#==#e#==#g()#==#finally#==#h()#==#end") @test Meta.parse("@m a b") == Meta.parse("@m#==#a#==#b") @@ -2541,11 +2552,11 @@ end @test B37890(1.0, 2.0f0) isa B37890{Int, Int8} # import ... as -@test_throws ParseError("invalid syntax \"using A as ...\"") Meta.parse("using A as B") -@test_throws ParseError("invalid syntax \"using A.b as ...\"") Meta.parse("using A.b as B") -@test_throws ParseError("invalid syntax \"using A.b as ...\"") Meta.parse("using X, A.b as B") -@test_throws ParseError("invalid syntax \"import A as B:\"") Meta.parse("import A as B: c") -@test_throws ParseError("invalid syntax \"import A.b as B:\"") Meta.parse("import A.b as B: c") +@test_parseerror("using A as B", "invalid syntax \"using A as ...\"") +@test_parseerror("using A.b as B", "invalid syntax \"using A.b as ...\"") +@test_parseerror("using X, A.b as B", "invalid syntax \"using A.b as ...\"") +@test_parseerror("import A as B: c", "invalid syntax \"import A as B:\"") +@test_parseerror("import A.b as B: c", "invalid syntax \"import A.b as B:\"") module TestImportAs using Test @@ -2584,7 +2595,9 @@ import .Mod2.y as y2 @test y2 == 2 @test !@isdefined(y) -@test_throws ErrorException eval(:(import .Mod.x as (a.b))) +# Test that eval rejects the invalid syntax `import .Mod.x as (a.b)` +@test_throws ErrorException eval( + Expr(:import, Expr(:as, Expr(:., :., :Mod, :x), Expr(:., :a, QuoteNode(:b))))) import .Mod.maybe_undef as mu @test_throws UndefVarError mu @@ -2662,10 +2675,10 @@ end @test Meta.isexpr(Meta.parse(""" f(i for i in 1:3)""").args[2], :generator) - @test_throws Meta.ParseError Meta.parse(""" + @test_parseerror """ for i in 1:3 - end""") + end""" end # PR #37973 @@ -2820,7 +2833,7 @@ end Expr(:nrow, 1, Expr(:row, 0, 9, 3), Expr(:row, 4, 5, 4))) @test :([1 ; 2 ;; 3 ; 4]) == Expr(:ncat, 2, Expr(:nrow, 1, 1, 2), Expr(:nrow, 1, 3, 4)) - @test_throws ParseError Meta.parse("[1 2 ;; 3 4]") # cannot mix spaces and ;; except as line break + @test_parseerror "[1 2 ;; 3 4]" # cannot mix spaces and ;; except as line break @test :([1 2 ;; 3 4]) == :([1 2 3 4]) @test :([1 2 ;; @@ -2830,8 +2843,8 @@ end @test Meta.parse("[1;\n\n]") == :([1;]) @test Meta.parse("[1\n;]") == :([1;]) # semicolons following a linebreak are fine @test Meta.parse("[1\n;;; 2]") == :([1;;; 2]) - @test_throws ParseError Meta.parse("[1;\n;2]") # semicolons cannot straddle a line break - @test_throws ParseError Meta.parse("[1; ;2]") # semicolons cannot be separated by a space + @test_parseerror "[1;\n;2]" # semicolons cannot straddle a line break + @test_parseerror "[1; ;2]" # semicolons cannot be separated by a space end # issue #25652 @@ -3104,10 +3117,10 @@ end @test fails(error) @test !fails(() -> 1 + 2) - @test_throws ParseError Meta.parse("try foo() else bar() end") - @test_throws ParseError Meta.parse("try foo() else bar() catch; baz() end") - @test_throws ParseError Meta.parse("try foo() catch; baz() finally foobar() else bar() end") - @test_throws ParseError Meta.parse("try foo() finally foobar() else bar() catch; baz() end") + @test_parseerror "try foo() else bar() end" + @test_parseerror "try foo() else bar() catch; baz() end" + @test_parseerror "try foo() catch; baz() finally foobar() else bar() end" + @test_parseerror "try foo() finally foobar() else bar() catch; baz() end" err = try try @@ -3172,23 +3185,23 @@ end @test x == 1 end -@test_throws ParseError Meta.parse(""" +@test_parseerror """ function checkUserAccess(u::User) if u.accessLevel != "user\u202e \u2066# users are not allowed\u2069\u2066" return true end return false end -""") +""" -@test_throws ParseError Meta.parse(""" +@test_parseerror """ function checkUserAccess(u::User) #=\u202e \u2066if (u.isAdmin)\u2069 \u2066 begin admins only =# return true #= end admin only \u202e \u2066end\u2069 \u2066=# return false end -""") +""" @testset "empty nd arrays" begin @test :([]) == Expr(:vect) @@ -3219,9 +3232,9 @@ end ;; ]) == Expr(:ncat, 2) - @test_throws ParseError Meta.parse("[; ;]") - @test_throws ParseError Meta.parse("[;; ;]") - @test_throws ParseError Meta.parse("[;\n;]") + @test_parseerror "[; ;]" + @test_parseerror "[;; ;]" + @test_parseerror "[;\n;]" end @test Meta.parseatom("@foo", 1; filename="foo", lineno=7) == (Expr(:macrocall, :var"@foo", LineNumberNode(7, :foo)), 5) @@ -3415,14 +3428,12 @@ f45162(f) = f(x=1) @test first(methods(f45162)).called != 0 # issue #45024 -@test_throws ParseError("expected assignment after \"const\"") Meta.parse("const x") -@test_throws ParseError("expected assignment after \"const\"") Meta.parse("const x::Int") +@test_parseerror "const x" "expected assignment after \"const\"" +@test_parseerror "const x::Int" "expected assignment after \"const\"" # these cases have always been caught during lowering, since (const (global x)) is not # ambiguous with the lowered form (const x), but that could probably be changed. -@test Meta.lower(@__MODULE__, :(global const x)) == Expr(:error, "expected assignment after \"const\"") -@test Meta.lower(@__MODULE__, :(global const x::Int)) == Expr(:error, "expected assignment after \"const\"") -@test Meta.lower(@__MODULE__, :(const global x)) == Expr(:error, "expected assignment after \"const\"") -@test Meta.lower(@__MODULE__, :(const global x::Int)) == Expr(:error, "expected assignment after \"const\"") +@test Meta.lower(@__MODULE__, Expr(:const, Expr(:global, :x))) == Expr(:error, "expected assignment after \"const\"") +@test Meta.lower(@__MODULE__, Expr(:const, Expr(:global, Expr(:(::), :x, :Int)))) == Expr(:error, "expected assignment after \"const\"") @testset "issue 25072" begin @test '\xc0\x80' == reinterpret(Char, 0xc0800000)