diff --git a/NEWS.md b/NEWS.md index 32481ffc54bd1..bc0a7e72ed4b9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -213,15 +213,28 @@ This section lists changes that do not have deprecation warnings. * The `openspecfun` library is no longer built and shipped with Julia, as it is no longer used internally ([#22390]). + * All loaded packges used to have bindings in `Main` (e.g. `Main.Package`). This is no + longer the case; now bindings will only exist for packages brought into scope by + typing `using Package` or `import Package` ([#17997]). + * `slicedim(b::BitVector, 1, x)` now consistently returns the same thing that `b[x]` would, consistent with its documentation. Previously it would return a `BitArray{0}` for scalar `x` ([#20233]). + * The rules for mixed-signedness integer arithmetic (e.g. `Int32(1) + UInt64(1)`) have been + simplified: if the arguments have different sizes (in bits), then the type of the larger + argument is used. If the arguments have the same size, the unsigned type is used ([#9292]). + + * All command line arguments passed via `-e`, `-E`, and `-L` will be executed in the order + given on the command line ([#23665]). + Library improvements -------------------- * The functions `strip`, `lstrip` and `rstrip` now return `SubString` ([#22496]). + * The functions `strwidth` and `charwidth` have been merged into `textwidth`([#20816]). + * The functions `base` and `digits` digits now accept a negative base (like `ndigits` did) ([#21692]). @@ -294,6 +307,11 @@ Deprecated or removed * The keyword `immutable` is fully deprecated to `struct`, and `type` is fully deprecated to `mutable struct` ([#19157], [#20418]). + * Indexing into multidimensional arrays with more than one index but fewer indices than there are + dimensions is no longer permitted when those trailing dimensions have lengths greater than 1. + Instead, reshape the array or add trailing indices so the dimensionality and number of indices + match ([#14770], [#23628]). + * `writecsv(io, a; opts...)` has been deprecated in favor of `writedlm(io, a, ','; opts...)` ([#23529]). diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 08f0beba1158a..5d78168dbd72c 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -429,7 +429,7 @@ function checkbounds_indices(::Type{Bool}, ::Tuple{}, I::Tuple) @_inline_meta checkindex(Bool, OneTo(1), I[1]) & checkbounds_indices(Bool, (), tail(I)) end -checkbounds_indices(::Type{Bool}, ::Tuple, ::Tuple{}) = true +checkbounds_indices(::Type{Bool}, IA::Tuple, ::Tuple{}) = (@_inline_meta; all(x->unsafe_length(x)==1, IA)) checkbounds_indices(::Type{Bool}, ::Tuple{}, ::Tuple{}) = true throw_boundserror(A, I) = (@_noinline_meta; throw(BoundsError(A, I))) diff --git a/base/abstractarraymath.jl b/base/abstractarraymath.jl index 121beb203f3df..90816f08e35ae 100644 --- a/base/abstractarraymath.jl +++ b/base/abstractarraymath.jl @@ -67,7 +67,7 @@ julia> squeeze(a,3) function squeeze(A::AbstractArray, dims::Dims) for i in 1:length(dims) 1 <= dims[i] <= ndims(A) || throw(ArgumentError("squeezed dims must be in range 1:ndims(A)")) - size(A, dims[i]) == 1 || throw(ArgumentError("squeezed dims must all be size 1")) + length(indices(A, dims[i])) == 1 || throw(ArgumentError("squeezed dims must all be size 1")) for j = 1:i-1 dims[j] == dims[i] && throw(ArgumentError("squeezed dims must be unique")) end @@ -75,10 +75,10 @@ function squeeze(A::AbstractArray, dims::Dims) d = () for i = 1:ndims(A) if !in(i, dims) - d = tuple(d..., size(A, i)) + d = tuple(d..., indices(A, i)) end end - reshape(A, d::typeof(_sub(size(A), dims))) + reshape(A, d::typeof(_sub(indices(A), dims))) end squeeze(A::AbstractArray, dim::Integer) = squeeze(A, (Int(dim),)) diff --git a/base/client.jl b/base/client.jl index 4ea0be5581a48..29abb482966a8 100644 --- a/base/client.jl +++ b/base/client.jl @@ -250,7 +250,6 @@ function process_options(opts::JLOptions) idxs = find(x -> x == "--", ARGS) length(idxs) > 0 && deleteat!(ARGS, idxs[1]) end - repl = true quiet = (opts.quiet != 0) startup = (opts.startupfile != 2) history_file = (opts.historyfile != 0) @@ -258,66 +257,75 @@ function process_options(opts::JLOptions) global have_color = (opts.color == 1) global is_interactive = (opts.isinteractive != 0) + # pre-process command line argument list + arg_is_program = !isempty(ARGS) + repl = !arg_is_program + cmds = unsafe_load_commands(opts.commands) + for (cmd, arg) in cmds + if cmd == 'e' + arg_is_program = false + repl = false + elseif cmd == 'E' + arg_is_program = false + repl = false + elseif cmd == 'L' + # nothing + else + warn("unexpected command -$cmd'$arg'") + end + end + # remove filename from ARGS - arg_is_program = opts.eval == C_NULL && opts.print == C_NULL && !isempty(ARGS) global PROGRAM_FILE = arg_is_program ? shift!(ARGS) : "" - while true - # startup worker. - # opts.startupfile, opts.load, etc should should not be processed for workers. - if opts.worker == 1 - # does not return - if opts.cookie != C_NULL - start_worker(unsafe_string(opts.cookie)) - else - start_worker() - end + # startup worker. + # opts.startupfile, opts.load, etc should should not be processed for workers. + if opts.worker == 1 + # does not return + if opts.cookie != C_NULL + start_worker(unsafe_string(opts.cookie)) + else + start_worker() end + end - # add processors - if opts.nprocs > 0 - addprocs(opts.nprocs) - end - # load processes from machine file - if opts.machinefile != C_NULL - addprocs(load_machine_file(unsafe_string(opts.machinefile))) - end + # add processors + if opts.nprocs > 0 + addprocs(opts.nprocs) + end + # load processes from machine file + if opts.machinefile != C_NULL + addprocs(load_machine_file(unsafe_string(opts.machinefile))) + end - # load ~/.juliarc file - startup && load_juliarc() + # load ~/.juliarc file + startup && load_juliarc() - # load file immediately on all processors - if opts.load != C_NULL + # process cmds list + for (cmd, arg) in cmds + if cmd == 'e' + eval(Main, parse_input_line(arg)) + elseif cmd == 'E' + invokelatest(show, eval(Main, parse_input_line(arg))) + println() + elseif cmd == 'L' + # load file immediately on all processors @sync for p in procs() - @async remotecall_fetch(include, p, Main, unsafe_string(opts.load)) + @async remotecall_wait(include, p, Main, arg) end end - # eval expression - if opts.eval != C_NULL - repl = false - eval(Main, parse_input_line(unsafe_string(opts.eval))) - break - end - # eval expression and show result - if opts.print != C_NULL - repl = false - show(eval(Main, parse_input_line(unsafe_string(opts.print)))) - println() - break - end - # load file - if !isempty(PROGRAM_FILE) - # program - repl = false - if !is_interactive - ccall(:jl_exit_on_sigint, Void, (Cint,), 1) - end - include(Main, PROGRAM_FILE) + end + + # load file + if arg_is_program + # program + if !is_interactive + ccall(:jl_exit_on_sigint, Void, (Cint,), 1) end - break + include(Main, PROGRAM_FILE) end repl |= is_interactive - return (quiet,repl,startup,color_set,history_file) + return (quiet, repl, startup, color_set, history_file) end function load_juliarc() diff --git a/base/deprecated.jl b/base/deprecated.jl index 34176d7259858..17578096c5fbd 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -1651,7 +1651,7 @@ end end # PR #23187 -@deprecate cpad(s, n::Integer, p=" ") rpad(lpad(s, div(n+strwidth(s), 2), p), n, p) false +@deprecate cpad(s, n::Integer, p=" ") rpad(lpad(s, div(n+textwidth(s), 2), p), n, p) false # PR #22088 function hex2num(s::AbstractString) @@ -1746,6 +1746,24 @@ function countnz(x) return count(t -> t != 0, x) end +# issue #14470 +# TODO: More deprecations must be removed in src/cgutils.cpp:emit_array_nd_index() +# TODO: Re-enable the disabled tests marked PLI +# On the Julia side, this definition will gracefully supercede the new behavior (already coded) +@inline function checkbounds_indices(::Type{Bool}, IA::Tuple{Any,Vararg{Any}}, ::Tuple{}) + any(x->unsafe_length(x)==0, IA) && return false + any(x->unsafe_length(x)!=1, IA) && return _depwarn_for_trailing_indices(IA) + return true +end +function _depwarn_for_trailing_indices(n::Integer) # Called by the C boundscheck + depwarn("omitting indices for non-singleton trailing dimensions is deprecated. Add `1`s as trailing indices or use `reshape(A, Val($n))` to make the dimensionality of the array match the number of indices.", (:getindex, :setindex!, :view)) + true +end +function _depwarn_for_trailing_indices(t::Tuple) + depwarn("omitting indices for non-singleton trailing dimensions is deprecated. Add `$(join(map(first, t),','))` as trailing indices or use `reshape` to make the dimensionality of the array match the number of indices.", (:getindex, :setindex!, :view)) + true +end + # issue #22791 @deprecate select partialsort @deprecate select! partialsort! @@ -1804,6 +1822,10 @@ import .Iterators.enumerate return p end +# ease transition for return type change of e.g. indmax due to PR #22907 when used in the +# common pattern `ind2sub(size(a), indmax(a))` +@deprecate(ind2sub(dims::NTuple{N,Integer}, idx::CartesianIndex{N}) where N, Tuple(idx)) + @deprecate contains(eq::Function, itr, x) any(y->eq(y,x), itr) # PR #23690 @@ -1855,6 +1877,10 @@ end nothing end +# issue #20816 +@deprecate strwidth textwidth +@deprecate charwidth textwidth + # END 0.7 deprecations # BEGIN 1.0 deprecations diff --git a/base/dict.jl b/base/dict.jl index 24aaa18c9dbae..f4b0642759eca 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -1,7 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license function _truncate_at_width_or_chars(str, width, chars="", truncmark="…") - truncwidth = strwidth(truncmark) + truncwidth = textwidth(truncmark) (width <= 0 || width < truncwidth) && return "" wid = truncidx = lastidx = 0 @@ -9,7 +9,7 @@ function _truncate_at_width_or_chars(str, width, chars="", truncmark="…") while !done(str, idx) lastidx = idx c, idx = next(str, idx) - wid += charwidth(c) + wid += textwidth(c) wid >= width - truncwidth && truncidx == 0 && (truncidx = lastidx) (wid >= width || c in chars) && break end diff --git a/base/distributed/Distributed.jl b/base/distributed/Distributed.jl index 73270b0672b3b..06e2bd9b06a6c 100644 --- a/base/distributed/Distributed.jl +++ b/base/distributed/Distributed.jl @@ -10,7 +10,8 @@ import Base: getindex, wait, put!, take!, fetch, isready, push!, length, using Base: Process, Semaphore, JLOptions, AnyDict, buffer_writes, wait_connected, VERSION_STRING, sync_begin, sync_add, sync_end, async_run_thunk, binding_module, notify_error, atexit, julia_exename, julia_cmd, - AsyncGenerator, display_error, acquire, release, invokelatest + AsyncGenerator, display_error, acquire, release, invokelatest, warn_once, + shell_escape, uv_error # NOTE: clusterserialize.jl imports additional symbols from Base.Serializer for use diff --git a/base/distributed/managers.jl b/base/distributed/managers.jl index 8a09063dceef4..4aa1e975cd676 100644 --- a/base/distributed/managers.jl +++ b/base/distributed/managers.jl @@ -188,7 +188,7 @@ function launch_on_machine(manager::SSHManager, machine, cnt, params, launched, cmd = `cd $dir '&&' $tval $exename $exeflags` # shell login (-l) with string command (-c) to launch julia process - cmd = `sh -l -c $(Base.shell_escape(cmd))` + cmd = `sh -l -c $(shell_escape(cmd))` # remote launch with ssh with given ssh flags / host / port information # -T → disable pseudo-terminal allocation @@ -196,7 +196,7 @@ function launch_on_machine(manager::SSHManager, machine, cnt, params, launched, # -x → disable X11 forwarding # -o ClearAllForwardings → option if forwarding connections and # forwarded connections are causing collisions - cmd = `ssh -T -a -x -o ClearAllForwardings=yes $sshflags $host $(Base.shell_escape(cmd))` + cmd = `ssh -T -a -x -o ClearAllForwardings=yes $sshflags $host $(shell_escape(cmd))` # launch the remote Julia process @@ -381,7 +381,7 @@ connection to worker with id `pid`, specified by `config` and return a pair of ` objects. Messages from `pid` to current process will be read off `instrm`, while messages to be sent to `pid` will be written to `outstrm`. The custom transport implementation must ensure that messages are delivered and received completely and in order. -`Base.connect(manager::ClusterManager.....)` sets up TCP/IP socket connections in-between +`connect(manager::ClusterManager.....)` sets up TCP/IP socket connections in-between workers. """ function connect(manager::ClusterManager, pid::Int, config::WorkerConfig) @@ -485,7 +485,7 @@ end function bind_client_port(s) err = ccall(:jl_tcp_bind, Int32, (Ptr{Void}, UInt16, UInt32, Cuint), s.handle, hton(client_port[]), hton(UInt32(0)), 0) - Base.uv_error("bind() failed", err) + uv_error("bind() failed", err) _addr, port = getsockname(s) client_port[] = port @@ -520,7 +520,7 @@ end Implemented by cluster managers. It is called on the master process, by [`rmprocs`](@ref). It should cause the remote worker specified by `pid` to exit. -`Base.kill(manager::ClusterManager.....)` executes a remote `exit()` +`kill(manager::ClusterManager.....)` executes a remote `exit()` on `pid`. """ function kill(manager::ClusterManager, pid::Int, config::WorkerConfig) diff --git a/base/exports.jl b/base/exports.jl index 5d80d9feddfd6..57e0a174cd491 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -730,7 +730,6 @@ export bin, bits, bytes2hex, - charwidth, chomp, chop, chr2ind, @@ -800,8 +799,8 @@ export sprint, string, strip, - strwidth, summary, + textwidth, titlecase, transcode, ucfirst, diff --git a/base/inference.jl b/base/inference.jl index 8383d53a45eca..ec9ac4b9536d0 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -837,7 +837,7 @@ function limit_type_depth(@nospecialize(t), d::Int, cov::Bool, vars::Vector{Type end ub = Any else - ub = limit_type_depth(v.ub, d - 1, true) + ub = limit_type_depth(v.ub, d - 1, cov, vars) end if v.lb === Bottom || type_depth(v.lb) > d # note: lower bounds need to be widened by making them lower @@ -855,7 +855,8 @@ function limit_type_depth(@nospecialize(t), d::Int, cov::Bool, vars::Vector{Type if d < 0 if isvarargtype(t) # never replace Vararg with non-Vararg - return Vararg{limit_type_depth(P[1], d, cov, vars), P[2]} + # passing depth=0 avoids putting a bare typevar here, for the diagonal rule + return Vararg{limit_type_depth(P[1], 0, cov, vars), P[2]} end widert = t.name.wrapper if !(t <: widert) @@ -870,7 +871,11 @@ function limit_type_depth(@nospecialize(t), d::Int, cov::Bool, vars::Vector{Type return var end stillcov = cov && (t.name === Tuple.name) - Q = map(x -> limit_type_depth(x, d - 1, stillcov, vars), P) + newdepth = d - 1 + if isvarargtype(t) + newdepth = max(newdepth, 0) + end + Q = map(x -> limit_type_depth(x, newdepth, stillcov, vars), P) R = t.name.wrapper{Q...} if cov && !stillcov for var in vars diff --git a/base/int.jl b/base/int.jl index e563dcc066df8..4d3f3c1522aa1 100644 --- a/base/int.jl +++ b/base/int.jl @@ -602,24 +602,21 @@ end ## integer promotions ## -promote_rule(::Type{Int8}, ::Type{Int16}) = Int16 -promote_rule(::Type{UInt8}, ::Type{UInt16}) = UInt16 -promote_rule(::Type{Int32}, ::Type{<:Union{Int8,Int16}}) = Int32 -promote_rule(::Type{UInt32}, ::Type{<:Union{UInt8,UInt16}}) = UInt32 -promote_rule(::Type{Int64}, ::Type{<:Union{Int8,Int16,Int32}}) = Int64 -promote_rule(::Type{UInt64}, ::Type{<:Union{UInt8,UInt16,UInt32}}) = UInt64 -promote_rule(::Type{Int128}, ::Type{<:BitSigned64}) = Int128 -promote_rule(::Type{UInt128}, ::Type{<:BitUnsigned64}) = UInt128 -for T in BitSigned_types - @eval promote_rule(::Type{<:Union{UInt8,UInt16}}, ::Type{$T}) = - $(sizeof(T) < sizeof(Int) ? Int : T) -end -@eval promote_rule(::Type{UInt32}, ::Type{<:Union{Int8,Int16,Int32}}) = - $(Core.sizeof(Int) == 8 ? Int : UInt) -promote_rule(::Type{UInt32}, ::Type{Int64}) = Int64 -promote_rule(::Type{UInt64}, ::Type{<:BitSigned64}) = UInt64 -promote_rule(::Type{<:Union{UInt32, UInt64}}, ::Type{Int128}) = Int128 -promote_rule(::Type{UInt128}, ::Type{<:BitSigned}) = UInt128 +# with different sizes, promote to larger type +promote_rule(::Type{Int16}, ::Union{Type{Int8}, Type{UInt8}}) = Int16 +promote_rule(::Type{Int32}, ::Union{Type{Int16}, Type{Int8}, Type{UInt16}, Type{UInt8}}) = Int32 +promote_rule(::Type{Int64}, ::Union{Type{Int16}, Type{Int32}, Type{Int8}, Type{UInt16}, Type{UInt32}, Type{UInt8}}) = Int64 +promote_rule(::Type{Int128}, ::Union{Type{Int16}, Type{Int32}, Type{Int64}, Type{Int8}, Type{UInt16}, Type{UInt32}, Type{UInt64}, Type{UInt8}}) = Int128 +promote_rule(::Type{UInt16}, ::Union{Type{Int8}, Type{UInt8}}) = UInt16 +promote_rule(::Type{UInt32}, ::Union{Type{Int16}, Type{Int8}, Type{UInt16}, Type{UInt8}}) = UInt32 +promote_rule(::Type{UInt64}, ::Union{Type{Int16}, Type{Int32}, Type{Int8}, Type{UInt16}, Type{UInt32}, Type{UInt8}}) = UInt64 +promote_rule(::Type{UInt128}, ::Union{Type{Int16}, Type{Int32}, Type{Int64}, Type{Int8}, Type{UInt16}, Type{UInt32}, Type{UInt64}, Type{UInt8}}) = UInt128 +# with mixed signedness and same size, Unsigned wins +promote_rule(::Type{UInt8}, ::Type{Int8} ) = UInt8 +promote_rule(::Type{UInt16}, ::Type{Int16} ) = UInt16 +promote_rule(::Type{UInt32}, ::Type{Int32} ) = UInt32 +promote_rule(::Type{UInt64}, ::Type{Int64} ) = UInt64 +promote_rule(::Type{UInt128}, ::Type{Int128}) = UInt128 _default_type(::Type{Unsigned}) = UInt _default_type(::Union{Type{Integer},Type{Signed}}) = Int diff --git a/base/interactiveutil.jl b/base/interactiveutil.jl index 1314023ae7a56..3b3326a122160 100644 --- a/base/interactiveutil.jl +++ b/base/interactiveutil.jl @@ -566,7 +566,7 @@ end Return an array of methods with an argument of type `typ`. The optional second argument restricts the search to a particular module or function -(the default is all modules, starting from Main). +(the default is all top-level modules). If optional `showparents` is `true`, also return arguments with a parent type of `typ`, excluding type `Any`. @@ -588,7 +588,7 @@ function methodswith(t::Type, f::Function, showparents::Bool=false, meths = Meth return meths end -function methodswith(t::Type, m::Module, showparents::Bool=false) +function _methodswith(t::Type, m::Module, showparents::Bool) meths = Method[] for nm in names(m) if isdefined(m, nm) @@ -601,17 +601,12 @@ function methodswith(t::Type, m::Module, showparents::Bool=false) return unique(meths) end +methodswith(t::Type, m::Module, showparents::Bool=false) = _methodswith(t, m, showparents) + function methodswith(t::Type, showparents::Bool=false) meths = Method[] - mainmod = Main - # find modules in Main - for nm in names(mainmod) - if isdefined(mainmod, nm) - mod = getfield(mainmod, nm) - if isa(mod, Module) - append!(meths, methodswith(t, mod, showparents)) - end - end + for mod in loaded_modules_array() + append!(meths, _methodswith(t, mod, showparents)) end return unique(meths) end @@ -678,8 +673,10 @@ download(url, filename) workspace() Replace the top-level module (`Main`) with a new one, providing a clean workspace. The -previous `Main` module is made available as `LastMain`. A previously-loaded package can be -accessed using a statement such as `using LastMain.Package`. +previous `Main` module is made available as `LastMain`. + +If `Package` was previously loaded, `using Package` in the new `Main` will re-use the +loaded copy. Run `reload("Package")` first to load a fresh copy. This function should only be used interactively. """ @@ -700,15 +697,20 @@ end # testing """ - runtests([tests=["all"] [, numcores=ceil(Int, Sys.CPU_CORES / 2) ]]) + Base.runtests(tests=["all"], numcores=ceil(Int, Sys.CPU_CORES / 2); + exit_on_error=false) Run the Julia unit tests listed in `tests`, which can be either a string or an array of -strings, using `numcores` processors. (not exported) +strings, using `numcores` processors. If `exit_on_error` is `false`, when one test +fails, all remaining tests in other files will still be run; they are otherwise discarded, +when `exit_on_error == true`. """ -function runtests(tests = ["all"], numcores = ceil(Int, Sys.CPU_CORES / 2)) +function runtests(tests = ["all"], numcores = ceil(Int, Sys.CPU_CORES / 2); + exit_on_error=false) if isa(tests,AbstractString) tests = split(tests) end + exit_on_error && push!(tests, "--exit-on-error") ENV2 = copy(ENV) ENV2["JULIA_CPU_CORES"] = "$numcores" try diff --git a/base/libgit2/callbacks.jl b/base/libgit2/callbacks.jl index 196d8651fcfbe..9267bbb2982cf 100644 --- a/base/libgit2/callbacks.jl +++ b/base/libgit2/callbacks.jl @@ -42,22 +42,38 @@ end function user_abort() # Note: Potentially it could be better to just throw a Julia error. ccall((:giterr_set_str, :libgit2), Void, - (Cint, Cstring), - Cint(Error.Callback), "Aborting, user cancelled credential request.") - + (Cint, Cstring), Cint(Error.Callback), + "Aborting, user cancelled credential request.") return Cint(Error.EUSER) end +function prompt_limit() + ccall((:giterr_set_str, :libgit2), Void, + (Cint, Cstring), Cint(Error.Callback), + "Aborting, maximum number of prompts reached.") + return Cint(Error.EAUTH) +end + +function exhausted_abort() + ccall((:giterr_set_str, :libgit2), Void, + (Cint, Cstring), Cint(Error.Callback), + "All authentication methods have failed.") + return Cint(Error.EAUTH) +end + function authenticate_ssh(libgit2credptr::Ptr{Ptr{Void}}, p::CredentialPayload, username_ptr) - creds = Base.get(p.credential)::SSHCredentials + cred = Base.get(p.credential)::SSHCredentials + revised = false - # Reset password on sucessive calls - if !p.first_pass - creds.pass = "" + # Use a filled credential as-is on the first pass. Reset password on sucessive calls. + if p.first_pass && isfilled(cred) + revised = true + elseif !p.first_pass + cred.pass = "" end # first try ssh-agent if credentials support its usage - if p.use_ssh_agent && username_ptr != Cstring(C_NULL) + if p.use_ssh_agent && username_ptr != Cstring(C_NULL) && (!revised || !isfilled(cred)) err = ccall((:git_cred_ssh_key_from_agent, :libgit2), Cint, (Ptr{Ptr{Void}}, Cstring), libgit2credptr, username_ptr) @@ -65,154 +81,171 @@ function authenticate_ssh(libgit2credptr::Ptr{Ptr{Void}}, p::CredentialPayload, err == 0 && return Cint(0) end - username = username_ptr != Cstring(C_NULL) ? unsafe_string(username_ptr) : "" + if p.use_env && (!revised || !isfilled(cred)) + if isempty(cred.user) && username_ptr != Cstring(C_NULL) + cred.user = unsafe_string(username_ptr) + end - privatekey = Base.get(ENV, "SSH_KEY_PATH") do - default = joinpath(homedir(), ".ssh", "id_rsa") - if isempty(creds.prvkey) && isfile(default) - default - else - creds.prvkey + cred.prvkey = Base.get(ENV, "SSH_KEY_PATH") do + default = joinpath(homedir(), ".ssh", "id_rsa") + if isempty(cred.prvkey) && isfile(default) + default + else + cred.prvkey + end end - end - publickey = Base.get(ENV, "SSH_PUB_KEY_PATH") do - default = privatekey * ".pub" - if isempty(creds.pubkey) && isfile(default) - default - else - creds.pubkey + cred.pubkey = Base.get(ENV, "SSH_PUB_KEY_PATH") do + default = cred.prvkey * ".pub" + if isempty(cred.pubkey) && isfile(default) + default + else + cred.pubkey + end end - end - passphrase = Base.get(ENV, "SSH_KEY_PASS", creds.pass) + cred.pass = Base.get(ENV, "SSH_KEY_PASS", cred.pass) - if p.allow_prompt - # if username is not provided or empty, then prompt for it - if isempty(username) - prompt_url = git_url(scheme=p.scheme, host=p.host) - response = Base.prompt("Username for '$prompt_url'", default=creds.user) + revised = true + p.use_env = false + end + + if p.remaining_prompts > 0 && (!revised || !isfilled(cred)) + if isempty(cred.user) || username_ptr == Cstring(C_NULL) + url = git_url(scheme=p.scheme, host=p.host) + response = Base.prompt("Username for '$url'", default=cred.user) isnull(response) && return user_abort() - username = unsafe_get(response) + cred.user = unsafe_get(response) end - prompt_url = git_url(scheme=p.scheme, host=p.host, username=username) + url = git_url(scheme=p.scheme, host=p.host, username=cred.user) # For SSH we need a private key location - if !isfile(privatekey) - response = Base.prompt("Private key location for '$prompt_url'", - default=privatekey) + last_private_key = cred.prvkey + if !isfile(cred.prvkey) || !revised || !haskey(ENV, "SSH_KEY_PATH") + response = Base.prompt("Private key location for '$url'", default=cred.prvkey) isnull(response) && return user_abort() - privatekey = unsafe_get(response) + cred.prvkey = expanduser(unsafe_get(response)) # Only update the public key if the private key changed - if privatekey != creds.prvkey - publickey = privatekey * ".pub" + if cred.prvkey != last_private_key + cred.pubkey = cred.prvkey * ".pub" end end # For SSH we need a public key location. Avoid asking about the public key as # typically this will just annoy users. - if !isfile(publickey) && isfile(privatekey) - response = Base.prompt("Public key location for '$prompt_url'", - default=publickey) + stale = !p.first_pass && cred.prvkey == last_private_key && cred.pubkey != cred.prvkey * ".pub" + if isfile(cred.prvkey) && (stale || !isfile(cred.pubkey)) + response = Base.prompt("Public key location for '$url'", default=cred.pubkey) isnull(response) && return user_abort() - publickey = unsafe_get(response) + cred.pubkey = expanduser(unsafe_get(response)) end - if isempty(passphrase) && is_passphrase_required(privatekey) + # Ask for a passphrase when the private key exists and requires a passphrase + if isempty(cred.pass) && is_passphrase_required(cred.prvkey) if Sys.iswindows() response = Base.winprompt( "Your SSH Key requires a password, please enter it now:", - "Passphrase required", privatekey; prompt_username=false) + "Passphrase required", cred.prvkey; prompt_username=false) isnull(response) && return user_abort() - passphrase = unsafe_get(response)[2] + cred.pass = unsafe_get(response)[2] else - response = Base.prompt("Passphrase for $privatekey", password=true) + response = Base.prompt("Passphrase for $(cred.prvkey)", password=true) isnull(response) && return user_abort() - passphrase = unsafe_get(response) - isempty(passphrase) && return user_abort() # Ambiguous if EOF or newline + cred.pass = unsafe_get(response) + isempty(cred.pass) && return user_abort() # Ambiguous if EOF or newline end end - creds.user = username # save credentials - creds.prvkey = privatekey # save credentials - creds.pubkey = publickey # save credentials - creds.pass = passphrase - elseif !p.first_pass - return Cint(Error.EAUTH) + revised = true + + p.remaining_prompts -= 1 + p.remaining_prompts <= 0 && return prompt_limit() + end + + if !revised + return exhausted_abort() end return ccall((:git_cred_ssh_key_new, :libgit2), Cint, (Ptr{Ptr{Void}}, Cstring, Cstring, Cstring, Cstring), - libgit2credptr, creds.user, creds.pubkey, creds.prvkey, creds.pass) + libgit2credptr, cred.user, cred.pubkey, cred.prvkey, cred.pass) end function authenticate_userpass(libgit2credptr::Ptr{Ptr{Void}}, p::CredentialPayload) - creds = Base.get(p.credential)::UserPasswordCredentials + cred = Base.get(p.credential)::UserPasswordCredentials + revised = false - # Reset password on sucessive calls - if !p.first_pass - creds.pass = "" + # Use a filled credential as-is on the first pass. Reset password on sucessive calls. + if p.first_pass && isfilled(cred) + revised = true + elseif !p.first_pass + cred.pass = "" end - if p.allow_prompt - username = creds.user - userpass = creds.pass - if isempty(username) || isempty(userpass) - prompt_url = git_url(scheme=p.scheme, host=p.host) - if Sys.iswindows() - response = Base.winprompt( - "Please enter your credentials for '$prompt_url'", "Credentials required", - isempty(username) ? p.username : username; prompt_username=true) - isnull(response) && return user_abort() - username, userpass = unsafe_get(response) - else - response = Base.prompt("Username for '$prompt_url'", - default=isempty(username) ? p.username : username) - isnull(response) && return user_abort() - username = unsafe_get(response) + if p.remaining_prompts > 0 && (!revised || !isfilled(cred)) + url = git_url(scheme=p.scheme, host=p.host) + username = isempty(cred.user) ? p.username : cred.user + if Sys.iswindows() + response = Base.winprompt( + "Please enter your credentials for '$url'", "Credentials required", + username; prompt_username=true) + isnull(response) && return user_abort() + cred.user, cred.pass = unsafe_get(response) + else + response = Base.prompt("Username for '$url'", default=username) + isnull(response) && return user_abort() + cred.user = unsafe_get(response) - prompt_url = git_url(scheme=p.scheme, host=p.host, username=username) - response = Base.prompt("Password for '$prompt_url'", password=true) - isnull(response) && return user_abort() - userpass = unsafe_get(response) - isempty(userpass) && return user_abort() # Ambiguous if EOF or newline - end + url = git_url(scheme=p.scheme, host=p.host, username=cred.user) + response = Base.prompt("Password for '$url'", password=true) + isnull(response) && return user_abort() + cred.pass = unsafe_get(response) + isempty(cred.pass) && return user_abort() # Ambiguous if EOF or newline end - creds.user = username # save credentials - creds.pass = userpass # save credentials - elseif !p.first_pass - return Cint(Error.EAUTH) + + revised = true + + p.remaining_prompts -= 1 + p.remaining_prompts <= 0 && return prompt_limit() + end + + if !revised + return exhausted_abort() end return ccall((:git_cred_userpass_plaintext_new, :libgit2), Cint, (Ptr{Ptr{Void}}, Cstring, Cstring), - libgit2credptr, creds.user, creds.pass) + libgit2credptr, cred.user, cred.pass) end -"""Credentials callback function - -Function provides different credential acquisition functionality w.r.t. a connection protocol. -If a payload is provided then `payload_ptr` should contain a `LibGit2.CredentialPayload` object. +""" + credential_callback(...) -> Cint -For `LibGit2.Consts.CREDTYPE_USERPASS_PLAINTEXT` type, if the payload contains fields: -`user` & `pass`, they are used to create authentication credentials. +A LibGit2 credential callback function which provides different credential acquisition +functionality w.r.t. a connection protocol. The `payload_ptr` is required to contain a +`LibGit2.CredentialPayload` object which will keep track of state and settings. -For `LibGit2.Consts.CREDTYPE_SSH_KEY` type, if the payload contains fields: -`user`, `prvkey`, `pubkey` & `pass`, they are used to create authentication credentials. +The `allowed_types` contains a bitmask of `LibGit2.Consts.GIT_CREDTYPE` values specifying +which authentication methods should be attempted. -Typing `^D` (control key together with the `d` key) will abort the credential prompt. +Credential authentication is done in the following order (if supported): +- SSH agent +- SSH private/public key pair +- Username/password plain text -Credentials are checked in the following order (if supported): -- ssh key pair (`ssh-agent` if specified in payload's `use_ssh_agent` field) -- plain text +If a user is presented with a credential prompt they can abort the prompt by typing `^D` +(pressing the control key together with the `d` key). **Note**: Due to the specifics of the `libgit2` authentication procedure, when authentication fails, this function is called again without any indication whether authentication was successful or not. To avoid an infinite loop from repeatedly using the same faulty credentials, we will keep track of state using the payload. + +For addition details see the LibGit2 guide on +[authenticating against a server](https://libgit2.github.com/docs/guides/authentication/). """ function credentials_callback(libgit2credptr::Ptr{Ptr{Void}}, url_ptr::Cstring, username_ptr::Cstring, @@ -232,7 +265,6 @@ function credentials_callback(libgit2credptr::Ptr{Ptr{Void}}, url_ptr::Cstring, p.scheme = m[:scheme] === nothing ? "" : m[:scheme] p.username = m[:user] === nothing ? "" : m[:user] p.host = m[:host] - p.path = m[:path] # When an explicit credential is supplied we will make sure to use the given # credential during the first callback by modifying the allowed types. The diff --git a/base/libgit2/types.jl b/base/libgit2/types.jl index 564b0536cb81b..6a8ade8e76cd3 100644 --- a/base/libgit2/types.jl +++ b/base/libgit2/types.jl @@ -1062,6 +1062,13 @@ import Base.securezero! "Abstract credentials payload" abstract type AbstractCredentials end +""" + isfilled(cred::AbstractCredentials) -> Bool + +Verifies that a credential is ready for use in authentication. +""" +isfilled(::AbstractCredentials) + "Credentials that support only `user` and `password` parameters" mutable struct UserPasswordCredentials <: AbstractCredentials user::String @@ -1093,6 +1100,10 @@ function Base.:(==)(a::UserPasswordCredentials, b::UserPasswordCredentials) a.user == b.user && a.pass == b.pass end +function isfilled(cred::UserPasswordCredentials) + !isempty(cred.user) && !isempty(cred.pass) +end + "SSH credentials type" mutable struct SSHCredentials <: AbstractCredentials user::String @@ -1130,6 +1141,11 @@ function Base.:(==)(a::SSHCredentials, b::SSHCredentials) a.user == b.user && a.pass == b.pass && a.prvkey == b.prvkey && a.pubkey == b.pubkey end +function isfilled(cred::SSHCredentials) + !isempty(cred.user) && isfile(cred.prvkey) && isfile(cred.pubkey) && + (!isempty(cred.pass) || !is_passphrase_required(cred.prvkey)) +end + "Credentials that support caching" struct CachedCredentials <: AbstractCredentials cred::Dict{String,AbstractCredentials} @@ -1177,11 +1193,13 @@ mutable struct CredentialPayload <: Payload credential::Nullable{AbstractCredentials} first_pass::Bool use_ssh_agent::Bool + use_env::Bool + remaining_prompts::Int + url::String scheme::String username::String host::String - path::String function CredentialPayload( credential::Nullable{<:AbstractCredentials}=Nullable{AbstractCredentials}(), @@ -1211,15 +1229,22 @@ function reset!(p::CredentialPayload) p.credential = Nullable{AbstractCredentials}() p.first_pass = true p.use_ssh_agent = p.allow_ssh_agent + p.use_env = true + p.remaining_prompts = p.allow_prompt ? 3 : 0 p.url = "" p.scheme = "" p.username = "" p.host = "" - p.path = "" return p end +""" + approve(payload::CredentialPayload) -> Void + +Store the `payload` credential for re-use in a future authentication. Should only be called +when authentication was successful. +""" function approve(p::CredentialPayload) isnull(p.credential) && return # No credentials were used cred = unsafe_get(p.credential) @@ -1229,6 +1254,12 @@ function approve(p::CredentialPayload) end end +""" + reject(payload::CredentialPayload) -> Void + +Discard the `payload` credential from begin re-used in future authentication. Should only be +called when authentication was unsuccessful. +""" function reject(p::CredentialPayload) isnull(p.credential) && return # No credentials were used cred = unsafe_get(p.credential) diff --git a/base/linalg/lapack.jl b/base/linalg/lapack.jl index 4db08f1f57d54..271674a992221 100644 --- a/base/linalg/lapack.jl +++ b/base/linalg/lapack.jl @@ -2559,8 +2559,10 @@ for (orglq, orgqr, orgql, orgrq, ormlq, ormqr, ormql, ormrq, gemqrt, elty) in # DOUBLE PRECISION A( LDA, * ), TAU( * ), WORK( * ) function orgrq!(A::StridedMatrix{$elty}, tau::StridedVector{$elty}, k::Integer = length(tau)) chkstride1(A,tau) - m = size(A, 1) - n = min(m, size(A, 2)) + m, n = size(A) + if n < m + throw(DimensionMismatch("input matrix A has dimensions ($m,$n), but cannot have fewer columns than rows")) + end if k > n throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= n = $n")) end @@ -2580,11 +2582,7 @@ for (orglq, orgqr, orgql, orgrq, ormlq, ormqr, ormql, ormrq, gemqrt, elty) in resize!(work, lwork) end end - if n < size(A,2) - A[:,1:n] - else - A - end + A end # SUBROUTINE DORMLQ( SIDE, TRANS, M, N, K, A, LDA, TAU, C, LDC, @@ -2600,13 +2598,13 @@ for (orglq, orgqr, orgql, orgrq, ormlq, ormqr, ormql, ormrq, gemqrt, elty) in chkside(side) chkstride1(A, C, tau) m,n = ndims(C) == 2 ? size(C) : (size(C, 1), 1) - mA, nA = size(A) + nA = size(A, 2) k = length(tau) if side == 'L' && m != nA throw(DimensionMismatch("for a left-sided multiplication, the first dimension of C, $m, must equal the second dimension of A, $nA")) end - if side == 'R' && n != mA - throw(DimensionMismatch("for a right-sided multiplication, the second dimension of C, $n, must equal the first dimension of A, $mA")) + if side == 'R' && n != nA + throw(DimensionMismatch("for a right-sided multiplication, the second dimension of C, $n, must equal the second dimension of A, $nA")) end if side == 'L' && k > m throw(DimensionMismatch("invalid number of reflectors: k = $k should be <= m = $m")) diff --git a/base/linalg/lq.jl b/base/linalg/lq.jl index 95e7483546668..d4b6f7fa3e986 100644 --- a/base/linalg/lq.jl +++ b/base/linalg/lq.jl @@ -92,17 +92,22 @@ full(Q::LQPackedQ; thin::Bool = true) = size(A::LQ, dim::Integer) = size(A.factors, dim) size(A::LQ) = size(A.factors) -function size(A::LQPackedQ, dim::Integer) - if 0 < dim && dim <= 2 - return size(A.factors, dim) - elseif 0 < dim && dim > 2 - return 1 - else + +# size(Q::LQPackedQ) yields the shape of Q's square form +function size(Q::LQPackedQ) + n = size(Q.factors, 2) + return n, n +end +function size(Q::LQPackedQ, dim::Integer) + if dim < 1 throw(BoundsError()) + elseif dim <= 2 # && 1 <= dim + return size(Q.factors, 2) + else # 2 < dim + return 1 end end -size(A::LQPackedQ) = size(A.factors) ## Multiplication by LQ A_mul_B!(A::LQ{T}, B::StridedVecOrMat{T}) where {T<:BlasFloat} = @@ -159,39 +164,60 @@ for (f1, f2) in ((:A_mul_Bc, :A_mul_B!), end end -### AQ -A_mul_B!(A::StridedMatrix{T}, B::LQPackedQ{T}) where {T<:BlasFloat} = LAPACK.ormlq!('R', 'N', B.factors, B.τ, A) -function *(A::StridedMatrix{TA}, B::LQPackedQ{TB}) where {TA,TB} - TAB = promote_type(TA,TB) - if size(B.factors,2) == size(A,2) - A_mul_B!(copy_oftype(A, TAB),convert(AbstractMatrix{TAB},B)) - elseif size(B.factors,1) == size(A,2) - A_mul_B!( [A zeros(TAB, size(A,1), size(B.factors,2)-size(B.factors,1))], convert(AbstractMatrix{TAB},B)) +# in-place right-application of LQPackedQs +# these methods require that the applied-to matrix's (A's) number of columns +# match the number of columns (nQ) of the LQPackedQ (Q) (necessary for in-place +# operation, and the underlying LAPACK routine (ormlq) treats the implicit Q +# as its (nQ-by-nQ) square form) +A_mul_B!(A::StridedMatrix{T}, B::LQPackedQ{T}) where {T<:BlasFloat} = + LAPACK.ormlq!('R', 'N', B.factors, B.τ, A) +A_mul_Bc!(A::StridedMatrix{T}, B::LQPackedQ{T}) where {T<:BlasReal} = + LAPACK.ormlq!('R', 'T', B.factors, B.τ, A) +A_mul_Bc!(A::StridedMatrix{T}, B::LQPackedQ{T}) where {T<:BlasComplex} = + LAPACK.ormlq!('R', 'C', B.factors, B.τ, A) + +# out-of-place right-application of LQPackedQs +# unlike their in-place equivalents, these methods: (1) check whether the applied-to +# matrix's (A's) appropriate dimension (columns for A_*, rows for Ac_*) matches the +# number of columns (nQ) of the LQPackedQ (Q), as the underlying LAPACK routine (ormlq) +# treats the implicit Q as its (nQ-by-nQ) square form; and (2) if the preceding dimensions +# do not match, these methods check whether the appropriate dimension of A instead matches +# the number of rows of the matrix of which Q is a factor (i.e. size(Q.factors, 1)), +# and if so zero-extends A as necessary for check (1) to pass (if possible). +*(A::StridedVecOrMat, Q::LQPackedQ) = _A_mul_Bq(A_mul_B!, A, Q) +A_mul_Bc(A::StridedVecOrMat, Q::LQPackedQ) = _A_mul_Bq(A_mul_Bc!, A, Q) +function _A_mul_Bq(A_mul_Bop!::FT, A::StridedVecOrMat, Q::LQPackedQ) where FT<:Function + TR = promote_type(eltype(A), eltype(Q)) + if size(A, 2) == size(Q.factors, 2) + C = copy_oftype(A, TR) + elseif size(A, 2) == size(Q.factors, 1) + C = zeros(TR, size(A, 1), size(Q.factors, 2)) + copy!(C, 1, A, 1, length(A)) else - throw(DimensionMismatch("second dimension of A, $(size(A,2)), must equal one of the dimensions of B, $(size(B))")) + _rightappdimmismatch("columns") end -end - -### AQc -A_mul_Bc!(A::StridedMatrix{T}, B::LQPackedQ{T}) where {T<:BlasReal} = LAPACK.ormlq!('R','T',B.factors,B.τ,A) -A_mul_Bc!(A::StridedMatrix{T}, B::LQPackedQ{T}) where {T<:BlasComplex} = LAPACK.ormlq!('R','C',B.factors,B.τ,A) -function A_mul_Bc(A::StridedVecOrMat{TA}, B::LQPackedQ{TB}) where {TA<:Number,TB<:Number} - TAB = promote_type(TA,TB) - A_mul_Bc!(copy_oftype(A, TAB), convert(AbstractMatrix{TAB},(B))) -end - -### AcQ/AcQc -for (f1, f2) in ((:Ac_mul_B, :A_mul_B!), - (:Ac_mul_Bc, :A_mul_Bc!)) - @eval begin - function ($f1)(A::StridedMatrix, B::LQPackedQ) - TAB = promote_type(eltype(A), eltype(B)) - AA = similar(A, TAB, (size(A, 2), size(A, 1))) - adjoint!(AA, A) - return ($f2)(AA, B) - end + return A_mul_Bop!(C, convert(AbstractMatrix{TR}, Q)) +end +Ac_mul_B(A::StridedMatrix, Q::LQPackedQ) = _Ac_mul_Bq(A_mul_B!, A, Q) +Ac_mul_Bc(A::StridedMatrix, Q::LQPackedQ) = _Ac_mul_Bq(A_mul_Bc!, A, Q) +function _Ac_mul_Bq(A_mul_Bop!::FT, A::StridedMatrix, Q::LQPackedQ) where FT<:Function + TR = promote_type(eltype(A), eltype(Q)) + if size(A, 1) == size(Q.factors, 2) + C = adjoint!(similar(A, TR, reverse(size(A))), A) + elseif size(A, 1) == size(Q.factors, 1) + C = zeros(TR, size(A, 2), size(Q.factors, 2)) + adjoint!(view(C, :, 1:size(A, 1)), A) + else + _rightappdimmismatch("rows") end + return A_mul_Bop!(C, convert(AbstractMatrix{TR}, Q)) end +_rightappdimmismatch(rowsorcols) = + throw(DimensionMismatch(string("the number of $(rowsorcols) of the matrix on the left ", + "must match either (1) the number of columns of the (LQPackedQ) matrix on the right ", + "or (2) the number of rows of that (LQPackedQ) matrix's internal representation ", + "(the factorization's originating matrix's number of rows)"))) + function (\)(A::LQ{TA}, b::StridedVector{Tb}) where {TA,Tb} S = promote_type(TA,Tb) diff --git a/base/linalg/lu.jl b/base/linalg/lu.jl index beaaf3f09c9ca..086314a0c8eee 100644 --- a/base/linalg/lu.jl +++ b/base/linalg/lu.jl @@ -88,7 +88,7 @@ lufact(A::Union{AbstractMatrix{T}, AbstractMatrix{Complex{T}}}, # for all other types we must promote to a type which is stable under division """ - lufact(A [,pivot=Val(true)]) -> F::LU + lufact(A, pivot=Val(true)) -> F::LU Compute the LU factorization of `A`. diff --git a/base/linalg/qr.jl b/base/linalg/qr.jl index c7e266f98b2d9..8c14a58d99c9b 100644 --- a/base/linalg/qr.jl +++ b/base/linalg/qr.jl @@ -207,6 +207,34 @@ qrfact!(A::StridedMatrix{<:BlasFloat}) = qrfact!(A, Val(false)) `StridedMatrix`, but saves space by overwriting the input `A`, instead of creating a copy. An [`InexactError`](@ref) exception is thrown if the factorization produces a number not representable by the element type of `A`, e.g. for integer types. + +# Examples +```jldoctest +julia> a = [1. 2.; 3. 4.] +2×2 Array{Float64,2}: + 1.0 2.0 + 3.0 4.0 + +julia> qrfact!(a) +Base.LinAlg.QRCompactWY{Float64,Array{Float64,2}} with factors Q and R: +[-0.316228 -0.948683; -0.948683 0.316228] +[-3.16228 -4.42719; 0.0 -0.632456] + +julia> a = [1 2; 3 4] +2×2 Array{Int64,2}: + 1 2 + 3 4 + +julia> qrfact!(a) +ERROR: InexactError: convert(Int64, -3.1622776601683795) +Stacktrace: + [1] convert at ./float.jl:703 [inlined] + [2] setindex! at ./array.jl:806 [inlined] + [3] setindex! at ./subarray.jl:245 [inlined] + [4] reflector! at ./linalg/generic.jl:1196 [inlined] + [5] qrfactUnblocked!(::Array{Int64,2}) at ./linalg/qr.jl:141 + [6] qrfact!(::Array{Int64,2}) at ./linalg/qr.jl:213 +``` """ qrfact!(A::StridedMatrix, ::Val{false}) = qrfactUnblocked!(A) qrfact!(A::StridedMatrix, ::Val{true}) = qrfactPivotedUnblocked!(A) @@ -345,6 +373,20 @@ and `r`, the norm of `v`. See also [`normalize`](@ref), [`normalize!`](@ref), and [`qr`](@ref). + +# Examples +```jldoctest +julia> v = [1.; 2.] +2-element Array{Float64,1}: + 1.0 + 2.0 + +julia> w, r = Base.LinAlg.qr!(v) +([0.447214, 0.894427], 2.23606797749979) + +julia> w === v +true +``` """ function qr!(v::AbstractVector) nrm = norm(v) @@ -471,6 +513,39 @@ Optionally takes a `thin` Boolean argument, which if `true` omits the columns th rows of `R` in the QR factorization that are zero. The resulting matrix is the `Q` in a thin QR factorization (sometimes called the reduced QR factorization). If `false`, returns a `Q` that spans all rows of `R` in its corresponding QR factorization. + +# Examples +```jldoctest +julia> a = [1. 2.; 3. 4.; 5. 6.]; + +julia> qra = qrfact(a, Val(true)); + +julia> full(qra[:Q], thin=true) +3×2 Array{Float64,2}: + -0.267261 0.872872 + -0.534522 0.218218 + -0.801784 -0.436436 + +julia> full(qra[:Q], thin=false) +3×3 Array{Float64,2}: + -0.267261 0.872872 0.408248 + -0.534522 0.218218 -0.816497 + -0.801784 -0.436436 0.408248 + +julia> qra = qrfact(a, Val(false)); + +julia> full(qra[:Q], thin=true) +3×2 Array{Float64,2}: + -0.169031 0.897085 + -0.507093 0.276026 + -0.845154 -0.345033 + +julia> full(qra[:Q], thin=false) +3×3 Array{Float64,2}: + -0.169031 0.897085 0.408248 + -0.507093 0.276026 -0.816497 + -0.845154 -0.345033 0.408248 +``` """ function full(A::AbstractQ{T}; thin::Bool = true) where T if thin diff --git a/base/linalg/schur.jl b/base/linalg/schur.jl index e53741108596b..fe4dce5d408ab 100644 --- a/base/linalg/schur.jl +++ b/base/linalg/schur.jl @@ -109,7 +109,7 @@ schur(A::Symmetric) = schur(full(A)) schur(A::Hermitian) = schur(full(A)) schur(A::UpperTriangular) = schur(full(A)) schur(A::LowerTriangular) = schur(full(A)) -schur(A::Tridiagonal) = schur(full(A)) +schur(A::Tridiagonal) = schur(Matrix(A)) """ diff --git a/base/linalg/special.jl b/base/linalg/special.jl index d41078fe2a238..c5a67beb8637c 100644 --- a/base/linalg/special.jl +++ b/base/linalg/special.jl @@ -65,7 +65,7 @@ function convert(::Type{Tridiagonal}, A::SymTridiagonal) end function convert(::Type{Diagonal}, A::AbstractTriangular) - if full(A) != diagm(diag(A)) + if !isdiag(A) throw(ArgumentError("matrix cannot be represented as Diagonal")) end Diagonal(diag(A)) diff --git a/base/linalg/triangular.jl b/base/linalg/triangular.jl index 69d2be62633dc..f0ab6b86ce9b4 100644 --- a/base/linalg/triangular.jl +++ b/base/linalg/triangular.jl @@ -1434,7 +1434,7 @@ end ## for these cases, but I'm not sure it is worth it. for t in (UpperTriangular, UnitUpperTriangular, LowerTriangular, UnitLowerTriangular) @eval begin - (*)(A::Tridiagonal, B::$t) = A_mul_B!(full(A), B) + (*)(A::Tridiagonal, B::$t) = A_mul_B!(Matrix(A), B) end end diff --git a/base/loading.jl b/base/loading.jl index bdc38471890fe..5b1f748a5c050 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -288,6 +288,7 @@ function reload(name::AbstractString) error("use `include` instead of `reload` to load source files") else # reload("Package") is ok + unreference_module(Symbol(name)) require(Symbol(name)) end end @@ -315,21 +316,78 @@ all platforms, including those with case-insensitive filesystems like macOS and Windows. """ function require(mod::Symbol) - _require(mod) - # After successfully loading, notify downstream consumers - if toplevel_load[] && myid() == 1 && nprocs() > 1 - # broadcast top-level import/using from node 1 (only) - @sync for p in procs() - p == 1 && continue - @async remotecall_wait(p) do - if !isbindingresolved(Main, mod) || !isdefined(Main, mod) - _require(mod) + if !root_module_exists(mod) + _require(mod) + # After successfully loading, notify downstream consumers + if toplevel_load[] && myid() == 1 && nprocs() > 1 + # broadcast top-level import/using from node 1 (only) + @sync for p in procs() + p == 1 && continue + @async remotecall_wait(p) do + require(mod) + nothing end end end + for callback in package_callbacks + invokelatest(callback, mod) + end end - for callback in package_callbacks - invokelatest(callback, mod) + return root_module(mod) +end + +const loaded_modules = ObjectIdDict() +const module_keys = ObjectIdDict() + +function register_root_module(key, m::Module) + if haskey(loaded_modules, key) + oldm = loaded_modules[key] + if oldm !== m + name = module_name(oldm) + warn("replacing module $name.") + end + end + loaded_modules[key] = m + module_keys[m] = key + nothing +end + +register_root_module(:Core, Core) +register_root_module(:Base, Base) +register_root_module(:Main, Main) + +is_root_module(m::Module) = haskey(module_keys, m) + +root_module_key(m::Module) = module_keys[m] + +# This is used as the current module when loading top-level modules. +# It has the special behavior that modules evaluated in it get added +# to the loaded_modules table instead of getting bindings. +baremodule __toplevel__ +using Base +end + +# get a top-level Module from the given key +# for now keys can only be Symbols, but that will change +root_module(key::Symbol) = loaded_modules[key] + +root_module_exists(key::Symbol) = haskey(loaded_modules, key) + +loaded_modules_array() = collect(values(loaded_modules)) + +function unreference_module(key) + if haskey(loaded_modules, key) + m = pop!(loaded_modules, key) + # need to ensure all modules are GC rooted; will still be referenced + # in module_keys + end +end + +function register_all(a) + for m in a + if module_parent(m) === m + register_root_module(module_name(m), m) + end end end @@ -364,7 +422,8 @@ function _require(mod::Symbol) if JLOptions().use_compiled_modules != 0 doneprecompile = _require_search_from_serialized(mod, path) if !isa(doneprecompile, Bool) - return # success + register_all(doneprecompile) + return end end @@ -391,14 +450,16 @@ function _require(mod::Symbol) warn(m, prefix="WARNING: ") # fall-through, TODO: disable __precompile__(true) error so that the normal include will succeed else - return # success + register_all(m) + return end end # just load the file normally via include # for unknown dependencies try - Base.include_relative(Main, path) + Base.include_relative(__toplevel__, path) + return catch ex if doneprecompile === true || JLOptions().use_compiled_modules == 0 || !precompilableerror(ex, true) rethrow() # rethrow non-precompilable=true errors @@ -411,6 +472,7 @@ function _require(mod::Symbol) # TODO: disable __precompile__(true) error and do normal include instead of error error("Module $mod declares __precompile__(true) but require failed to create a usable precompiled cache file.") end + register_all(m) end finally toplevel_load[] = last @@ -532,7 +594,7 @@ function create_expr_cache(input::String, output::String, concrete_deps::Vector{ task_local_storage()[:SOURCE_PATH] = $(source) end) end - serialize(in, :(Base.include(Main, $(abspath(input))))) + serialize(in, :(Base.include(Base.__toplevel__, $(abspath(input))))) if source !== nothing serialize(in, :(delete!(task_local_storage(), :SOURCE_PATH))) end @@ -570,15 +632,9 @@ function compilecache(name::String) cachefile::String = abspath(cachepath, name*".ji") # build up the list of modules that we want the precompile process to preserve concrete_deps = copy(_concrete_dependencies) - for existing in names(Main) - if isdefined(Main, existing) - mod = getfield(Main, existing) - if isa(mod, Module) && !(mod === Main || mod === Core || mod === Base) - mod = mod::Module - if module_parent(mod) === Main && module_name(mod) === existing - push!(concrete_deps, (existing, module_uuid(mod))) - end - end + for (key,mod) in loaded_modules + if !(mod === Main || mod === Core || mod === Base) + push!(concrete_deps, (key, module_uuid(mod))) end end # run the expression and cache the result @@ -675,7 +731,7 @@ function stale_cachefile(modpath::String, cachefile::String) if mod == :Main || mod == :Core || mod == :Base continue # Module is already loaded - elseif isbindingresolved(Main, mod) + elseif root_module_exists(mod) continue end name = string(mod) diff --git a/base/options.jl b/base/options.jl index aec3ae1862237..432ca9e205d32 100644 --- a/base/options.jl +++ b/base/options.jl @@ -6,9 +6,7 @@ struct JLOptions banner::Int8 julia_home::Ptr{UInt8} julia_bin::Ptr{UInt8} - eval::Ptr{UInt8} - print::Ptr{UInt8} - load::Ptr{UInt8} + commands::Ptr{Ptr{UInt8}} # (e)eval, (E)print, (L)load image_file::Ptr{UInt8} cpu_target::Ptr{UInt8} nprocs::Int32 @@ -58,8 +56,24 @@ function show(io::IO, opt::JLOptions) v = getfield(opt, i) if isa(v, Ptr{UInt8}) v = (v != C_NULL) ? unsafe_string(v) : "" + elseif isa(v, Ptr{Ptr{UInt8}}) + v = unsafe_load_commands(v) end print(io, f, " = ", repr(v), i < nfields ? ", " : "") end print(io, ")") end + +function unsafe_load_commands(v::Ptr{Ptr{UInt8}}) + cmds = Pair{Char, String}[] + v == C_NULL && return cmds + i = 1 + while true + s = unsafe_load(v, i) + s == C_NULL && break + e = Char(unsafe_load(s)) + push!(cmds, e => unsafe_string(s + 1)) + i += 1 + end + return cmds +end diff --git a/base/pkg/entry.jl b/base/pkg/entry.jl index ecbedd8e86b7f..ad160ae956b9a 100644 --- a/base/pkg/entry.jl +++ b/base/pkg/entry.jl @@ -539,7 +539,7 @@ function resolve( info("$(up)grading $pkg: v$ver1 => v$ver2") Write.update(pkg, Read.sha1(pkg,ver2)) pkgsym = Symbol(pkg) - if Base.isbindingresolved(Main, pkgsym) && isa(getfield(Main, pkgsym), Module) + if Base.root_module_exists(pkgsym) push!(imported, "- $pkg") end end @@ -570,7 +570,7 @@ end function warnbanner(msg...; label="[ WARNING ]", prefix="") cols = Base.displaysize(STDERR)[2] - str = rpad(lpad(label, div(cols+strwidth(label), 2), "="), cols, "=") + str = rpad(lpad(label, div(cols+textwidth(label), 2), "="), cols, "=") warn(prefix="", str) println(STDERR) warn(prefix=prefix, msg...) diff --git a/base/printf.jl b/base/printf.jl index dff3d28412df7..2f8347f6f8a95 100644 --- a/base/printf.jl +++ b/base/printf.jl @@ -617,11 +617,11 @@ function gen_c(flags::String, width::Int, precision::Int, c::Char) blk = Expr(:block, :($x = Char($x))) if width > 1 && !('-' in flags) p = '0' in flags ? '0' : ' ' - push!(blk.args, pad(width-1, :($width-charwidth($x)), p)) + push!(blk.args, pad(width-1, :($width-textwidth($x)), p)) end push!(blk.args, :(write(out, $x))) if width > 1 && '-' in flags - push!(blk.args, pad(width-1, :($width-charwidth($x)), ' ')) + push!(blk.args, pad(width-1, :($width-textwidth($x)), ' ')) end :(($x)::Integer), blk end @@ -652,11 +652,11 @@ function gen_s(flags::String, width::Int, precision::Int, c::Char) push!(blk.args, :($x = _limit($x, $precision))) end if !('-' in flags) - push!(blk.args, pad(width, :($width-strwidth($x)), ' ')) + push!(blk.args, pad(width, :($width-textwidth($x)), ' ')) end push!(blk.args, :(write(out, $x))) if '-' in flags - push!(blk.args, pad(width, :($width-strwidth($x)), ' ')) + push!(blk.args, pad(width, :($width-textwidth($x)), ' ')) end else if precision!=-1 diff --git a/base/reflection.jl b/base/reflection.jl index c1ab5459de132..826449dc1d006 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -56,13 +56,17 @@ julia> fullname(Main) ``` """ function fullname(m::Module) - m === Main && return () - m === Base && return (:Base,) # issue #10653 mn = module_name(m) + if m === Main || m === Base || m === Core + return (mn,) + end mp = module_parent(m) if mp === m - # not Main, but is its own parent, means a prior Main module - n = () + if mn !== :Main + return (mn,) + end + # top-level module, not Main, called :Main => prior Main module + n = (:Main,) this = Main while this !== m if isdefined(this, :LastMain) @@ -530,15 +534,22 @@ function _subtypes(m::Module, x::Union{DataType,UnionAll}, end return sts end -function subtypes(m::Module, x::Union{DataType,UnionAll}) - if isabstract(x) - sort!(collect(_subtypes(m, x)), by=string) - else + +function _subtypes_in(mods::Array, x::Union{DataType,UnionAll}) + if !isabstract(x) # Fast path - Union{DataType,UnionAll}[] + return Union{DataType,UnionAll}[] + end + sts = Set{Union{DataType,UnionAll}}() + visited = Set{Module}() + for m in mods + _subtypes(m, x, sts, visited) end + return sort!(collect(sts), by=string) end +subtypes(m::Module, x::Union{DataType,UnionAll}) = _subtypes_in([m], x) + """ subtypes(T::DataType) @@ -555,7 +566,7 @@ julia> subtypes(Integer) Unsigned ``` """ -subtypes(x::Union{DataType,UnionAll}) = subtypes(Main, x) +subtypes(x::Union{DataType,UnionAll}) = _subtypes_in(loaded_modules_array(), x) function to_tuple_type(@nospecialize(t)) @_pure_meta diff --git a/base/repl/LineEdit.jl b/base/repl/LineEdit.jl index 7daae45a42c36..36c8ffb726f19 100644 --- a/base/repl/LineEdit.jl +++ b/base/repl/LineEdit.jl @@ -307,8 +307,8 @@ function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf while moreinput l = readline(buf, chomp=false) moreinput = endswith(l, "\n") - # We need to deal with on-screen characters, so use strwidth to compute occupied columns - llength = strwidth(l) + # We need to deal with on-screen characters, so use textwidth to compute occupied columns + llength = textwidth(l) slength = sizeof(l) cur_row += 1 cmove_col(termbuf, lindent + 1) @@ -319,7 +319,7 @@ function refresh_multi_line(termbuf::TerminalBuffer, terminal::UnixTerminal, buf # in this case, we haven't yet written the cursor position line_pos -= slength # '\n' gets an extra pos if line_pos < 0 || !moreinput - num_chars = (line_pos >= 0 ? llength : strwidth(l[1:prevind(l, line_pos + slength + 1)])) + num_chars = (line_pos >= 0 ? llength : textwidth(l[1:prevind(l, line_pos + slength + 1)])) curs_row, curs_pos = divrem(lindent + num_chars - 1, cols) curs_row += cur_row curs_pos += 1 @@ -408,7 +408,7 @@ function edit_move_left(buf::IOBuffer) #move to the next base UTF8 character to the left while true c = char_move_left(buf) - if charwidth(c) != 0 || c == '\n' || position(buf) == 0 + if textwidth(c) != 0 || c == '\n' || position(buf) == 0 break end end @@ -466,7 +466,7 @@ function edit_move_right(buf::IOBuffer) pos = position(buf) nextc = read(buf,Char) seek(buf,pos) - (charwidth(nextc) != 0 || nextc == '\n') && break + (textwidth(nextc) != 0 || nextc == '\n') && break end return true end @@ -637,7 +637,7 @@ function edit_backspace(buf::IOBuffer, align::Bool=false, adjust::Bool=false) newpos = position(buf) if align && c == ' ' # maybe delete multiple spaces beg = beginofline(buf, newpos) - align = strwidth(String(buf.data[1+beg:newpos])) % 4 + align = textwidth(String(buf.data[1+beg:newpos])) % 4 nonspace = findprev(_notspace, buf.data, newpos) if newpos - align >= nonspace newpos -= align @@ -950,7 +950,7 @@ end function write_prompt(terminal, s::Union{AbstractString,Function}) promptstr = prompt_string(s) write(terminal, promptstr) - strwidth(promptstr) + textwidth(promptstr) end ### Keymap Support @@ -1572,7 +1572,8 @@ function setup_search_keymap(hp) # Bracketed paste mode "\e[200~" => (s,data,c)-> begin ps = state(s, mode(s)) - input = readuntil(ps.terminal, "\e[201~")[1:(end-6)] + str = readuntil(ps.terminal, "\e[201~") + input = str[1:prevind(str, end-5)] edit_insert(data.query_buffer, input); update_display_buffer(s, data) end, "*" => (s,data,c)->(edit_insert(data.query_buffer, c); update_display_buffer(s, data)) @@ -1642,7 +1643,8 @@ global tabwidth = 8 function bracketed_paste(s) ps = state(s, mode(s)) - input = readuntil(ps.terminal, "\e[201~")[1:(end-6)] + str = readuntil(ps.terminal, "\e[201~") + input = str[1:prevind(str, end-5)] input = replace(input, '\r', '\n') if position(buffer(s)) == 0 indent = Base.indentation(input; tabwidth=tabwidth)[1] @@ -1695,7 +1697,7 @@ function edit_insert_tab(buf::IOBuffer, jump_spaces=false, delete_trailing=jump_ end end # align to multiples of 4: - align = 4 - strwidth(String(buf.data[1+beginofline(buf, i):i])) % 4 + align = 4 - textwidth(String(buf.data[1+beginofline(buf, i):i])) % 4 edit_insert(buf, ' '^align) return true end diff --git a/base/replutil.jl b/base/replutil.jl index 5fef42c044118..c8b51e6ba7d18 100644 --- a/base/replutil.jl +++ b/base/replutil.jl @@ -158,6 +158,8 @@ function show(io::IO, ::MIME"text/plain", opt::JLOptions) v = getfield(opt, i) if isa(v, Ptr{UInt8}) v = (v != C_NULL) ? unsafe_string(v) : "" + elseif isa(v, Ptr{Ptr{UInt8}}) + v = unsafe_load_commands(v) end println(io, " ", f, " = ", repr(v), i < nfields ? "," : "") end @@ -228,7 +230,7 @@ end function showerror(io::IO, ex::LoadError, bt; backtrace=true) print(io, "LoadError: ") showerror(io, ex.error, bt, backtrace=backtrace) - print(io, "\nwhile loading $(ex.file), in expression starting on line $(ex.line)") + print(io, "\nin expression starting at $(ex.file):$(ex.line)") end showerror(io::IO, ex::LoadError) = showerror(io, ex, []) diff --git a/base/serialize.jl b/base/serialize.jl index 4dea2c3e818bc..62302040be93f 100644 --- a/base/serialize.jl +++ b/base/serialize.jl @@ -347,9 +347,10 @@ function serialize(s::AbstractSerializer, d::Dict) end function serialize_mod_names(s::AbstractSerializer, m::Module) - p = module_parent(m) - if m !== p - serialize_mod_names(s, p) + if Base.is_root_module(m) + serialize(s, Base.root_module_key(m)) + else + serialize_mod_names(s, module_parent(m)) serialize(s, module_name(m)) end end @@ -820,21 +821,25 @@ function deserialize_svec(s::AbstractSerializer) end function deserialize_module(s::AbstractSerializer) - path = deserialize(s) - m = Main - if isa(path,Tuple) && path !== () - # old version - for mname in path - m = getfield(m,mname)::Module + mkey = deserialize(s) + if isa(mkey, Tuple) + # old version, TODO: remove + if mkey === () + return Main + end + m = Base.root_module(mkey[1]) + for i = 2:length(mkey) + m = getfield(m, mkey[i])::Module end else - mname = path + m = Base.root_module(mkey) + mname = deserialize(s) while mname !== () - m = getfield(m,mname)::Module + m = getfield(m, mname)::Module mname = deserialize(s) end end - m + return m end function deserialize(s::AbstractSerializer, ::Type{Method}) diff --git a/base/show.jl b/base/show.jl index 0962f488dd082..1ad119d814e83 100644 --- a/base/show.jl +++ b/base/show.jl @@ -380,8 +380,8 @@ function show(io::IO, p::Pair) end function show(io::IO, m::Module) - if m === Main - print(io, "Main") + if is_root_module(m) + print(io, module_name(m)) else print(io, join(fullname(m),".")) end @@ -572,8 +572,46 @@ function isidentifier(s::AbstractString) end isidentifier(s::Symbol) = isidentifier(string(s)) +""" + isoperator(s::Symbol) + +Return `true` if the symbol can be used as an operator, `false` otherwise. + +# Examples +```jldoctest +julia> Base.isoperator(:+), Base.isoperator(:f) +(true, false) +``` +""" isoperator(s::Symbol) = ccall(:jl_is_operator, Cint, (Cstring,), s) != 0 +""" + isunaryoperator(s::Symbol) + +Return `true` if the symbol can be used as a unary (prefix) operator, `false` otherwise. + +# Examples +```jldoctest +julia> Base.isunaryoperator(:-), Base.isunaryoperator(:√), Base.isunaryoperator(:f) +(true, true, false) +``` +""" +isunaryoperator(s::Symbol) = ccall(:jl_is_unary_operator, Cint, (Cstring,), s) != 0 +is_unary_and_binary_operator(s::Symbol) = ccall(:jl_is_unary_and_binary_operator, Cint, (Cstring,), s) != 0 + +""" + isbinaryoperator(s::Symbol) + +Return `true` if the symbol can be used as a binary (infix) operator, `false` otherwise. + +# Examples +```jldoctest +julia> Base.isbinaryoperator(:-), Base.isbinaryoperator(:√), Base.isbinaryoperator(:f) +(true, false, false) +``` +""" +isbinaryoperator(s::Symbol) = isoperator(s) && (!isunaryoperator(s) || is_unary_and_binary_operator(s)) + """ operator_precedence(s::Symbol) @@ -584,17 +622,46 @@ operators. Return `0` if `s` is not a valid operator. # Examples ```jldoctest julia> Base.operator_precedence(:+), Base.operator_precedence(:*), Base.operator_precedence(:.) -(9,11,15) +(9, 11, 15) -julia> Base.operator_precedence(:+=), Base.operator_precedence(:(=)) # (Note the necessary parens on `:(=)`) -(1,1) +julia> Base.operator_precedence(:sin), Base.operator_precedence(:+=), Base.operator_precedence(:(=)) # (Note the necessary parens on `:(=)`) +(0, 1, 1) ``` """ operator_precedence(s::Symbol) = Int(ccall(:jl_operator_precedence, Cint, (Cstring,), s)) operator_precedence(x::Any) = 0 # fallback for generic expression nodes +const prec_assignment = operator_precedence(:(=)) +const prec_arrow = operator_precedence(:(-->)) +const prec_control_flow = operator_precedence(:(&&)) +const prec_comparison = operator_precedence(:(>)) const prec_power = operator_precedence(:(^)) const prec_decl = operator_precedence(:(::)) +""" + operator_associativity(s::Symbol) + +Return a symbol representing the associativity of operator `s`. Left- and right-associative +operators return `:left` and `:right`, respectively. Return `:none` if `s` is non-associative +or an invalid operator. + +# Examples +```jldoctest +julia> Base.operator_associativity(:-), Base.operator_associativity(:+), Base.operator_associativity(:^) +(:left, :none, :right) + +julia> Base.operator_associativity(:⊗), Base.operator_associativity(:sin), Base.operator_associativity(:→) +(:left, :none, :right) +``` +""" +function operator_associativity(s::Symbol) + if operator_precedence(s) in (prec_arrow, prec_assignment, prec_control_flow, prec_power) || isunaryoperator(s) && !is_unary_and_binary_operator(s) + return :right + elseif operator_precedence(s) in (0, prec_comparison) || s in (:+, :++, :*) + return :none + end + return :left +end + is_expr(ex, head::Symbol) = (isa(ex, Expr) && (ex.head == head)) is_expr(ex, head::Symbol, n::Int) = is_expr(ex, head) && length(ex.args) == n @@ -1615,7 +1682,7 @@ function print_matrix(io::IO, X::AbstractVecOrMat, screenwidth -= length(pre) + length(post) presp = repeat(" ", length(pre)) # indent each row to match pre string postsp = "" - @assert strwidth(hdots) == strwidth(ddots) + @assert textwidth(hdots) == textwidth(ddots) sepsize = length(sep) rowsA, colsA = indices(X,1), indices(X,2) m, n = length(rowsA), length(colsA) diff --git a/base/sparse/sparsevector.jl b/base/sparse/sparsevector.jl index 95c50798f3cdb..aed5f9adc3830 100644 --- a/base/sparse/sparsevector.jl +++ b/base/sparse/sparsevector.jl @@ -1443,13 +1443,9 @@ scale!(x::AbstractSparseVector, a::Complex) = (scale!(nonzeros(x), a); x) scale!(a::Real, x::AbstractSparseVector) = (scale!(nonzeros(x), a); x) scale!(a::Complex, x::AbstractSparseVector) = (scale!(nonzeros(x), a); x) - (*)(x::AbstractSparseVector, a::Number) = SparseVector(length(x), copy(nonzeroinds(x)), nonzeros(x) * a) (*)(a::Number, x::AbstractSparseVector) = SparseVector(length(x), copy(nonzeroinds(x)), a * nonzeros(x)) (/)(x::AbstractSparseVector, a::Number) = SparseVector(length(x), copy(nonzeroinds(x)), nonzeros(x) / a) -broadcast(::typeof(*), x::AbstractSparseVector, a::Number) = x * a -broadcast(::typeof(*), a::Number, x::AbstractSparseVector) = a * x -broadcast(::typeof(/), x::AbstractSparseVector, a::Number) = x / a # dot function dot(x::StridedVector{Tx}, y::SparseVectorUnion{Ty}) where {Tx<:Number,Ty<:Number} diff --git a/base/strings/basic.jl b/base/strings/basic.jl index 71edd09db674a..29b639e5b42b3 100644 --- a/base/strings/basic.jl +++ b/base/strings/basic.jl @@ -389,21 +389,6 @@ next(e::EachStringIndex, state) = (state, nextind(e.s, state)) done(e::EachStringIndex, state) = done(e.s, state) eltype(::Type{EachStringIndex}) = Int -## character column width function ## - -""" - strwidth(s::AbstractString) - -Give the number of columns needed to print a string. - -# Examples -```jldoctest -julia> strwidth("March") -5 -``` -""" -strwidth(s::AbstractString) = (w=0; for c in s; w += charwidth(c); end; w) - """ isascii(c::Union{Char,AbstractString}) -> Bool diff --git a/base/strings/types.jl b/base/strings/types.jl index c6e61f584b1c2..1298258d20860 100644 --- a/base/strings/types.jl +++ b/base/strings/types.jl @@ -124,10 +124,22 @@ end Reverses a string. +Technically, this function reverses the codepoints in a string, and its +main utility is for reversed-order string processing, especially for reversed +regular-expression searches. See also [`reverseind`](@ref) to convert indices +in `s` to indices in `reverse(s)` and vice-versa, and [`graphemes`](@ref) +to operate on user-visible "characters" (graphemes) rather than codepoints. + # Examples ```jldoctest julia> reverse("JuliaLang") "gnaLailuJ" + +julia> reverse("ax̂e") # combining characters can lead to surprising results +"êxa" + +julia> join(reverse(collect(graphemes("ax̂e")))) # reverses graphemes +"ex̂a" ``` """ reverse(s::AbstractString) = RevString(s) @@ -138,9 +150,9 @@ reverse(s::RevString) = s.string """ reverseind(v, i) -Given an index `i` in `reverse(v)`, return the corresponding index in `v` so that -`v[reverseind(v,i)] == reverse(v)[i]`. (This can be nontrivial in the case where `v` is a -Unicode string.) +Given an index `i` in [`reverse(v)`](@ref), return the corresponding index in `v` so that +`v[reverseind(v,i)] == reverse(v)[i]`. (This can be nontrivial in cases where `v` contains +non-ASCII characters.) # Examples ```jldoctest diff --git a/base/strings/utf8proc.jl b/base/strings/utf8proc.jl index 3c82902a1c01b..70e406097303a 100644 --- a/base/strings/utf8proc.jl +++ b/base/strings/utf8proc.jl @@ -8,7 +8,7 @@ import Base: show, ==, hash, string, Symbol, isless, length, eltype, start, next export isgraphemebreak, category_code, category_abbrev, category_string # also exported by Base: -export normalize_string, graphemes, is_assigned_char, charwidth, isvalid, +export normalize_string, graphemes, is_assigned_char, textwidth, isvalid, islower, isupper, isalpha, isdigit, isnumber, isalnum, iscntrl, ispunct, isspace, isprint, isgraph @@ -208,21 +208,35 @@ end ############################################################################ +## character column width function ## """ - charwidth(c) + textwidth(c) -Gives the number of columns needed to print a character. +Give the number of columns needed to print a character. # Examples ```jldoctest -julia> charwidth('α') +julia> textwidth('α') 1 -julia> charwidth('❤') +julia> textwidth('❤') 2 ``` """ -charwidth(c::Char) = Int(ccall(:utf8proc_charwidth, Cint, (UInt32,), c)) +textwidth(c::Char) = Int(ccall(:utf8proc_charwidth, Cint, (UInt32,), c)) + +""" + textwidth(s::AbstractString) + +Give the number of columns needed to print a string. + +# Examples +```jldoctest +julia> textwidth("March") +5 +``` +""" +textwidth(s::AbstractString) = mapreduce(textwidth, +, 0, s) lowercase(c::Char) = isascii(c) ? ('A' <= c <= 'Z' ? c + 0x20 : c) : Char(ccall(:utf8proc_tolower, UInt32, (UInt32,), c)) uppercase(c::Char) = isascii(c) ? ('a' <= c <= 'z' ? c - 0x20 : c) : Char(ccall(:utf8proc_toupper, UInt32, (UInt32,), c)) diff --git a/base/strings/util.jl b/base/strings/util.jl index 7addd0d30c98d..91b0583a083b3 100644 --- a/base/strings/util.jl +++ b/base/strings/util.jl @@ -204,9 +204,9 @@ strip(s::AbstractString, chars::Chars) = lstrip(rstrip(s, chars), chars) ## string padding functions ## function lpad(s::AbstractString, n::Integer, p::AbstractString=" ") - m = n - strwidth(s) + m = n - textwidth(s) (m <= 0) && (return s) - l = strwidth(p) + l = textwidth(p) if l==1 return string(p^m, s) end @@ -217,9 +217,9 @@ function lpad(s::AbstractString, n::Integer, p::AbstractString=" ") end function rpad(s::AbstractString, n::Integer, p::AbstractString=" ") - m = n - strwidth(s) + m = n - textwidth(s) (m <= 0) && (return s) - l = strwidth(p) + l = textwidth(p) if l==1 return string(s, p^m) end diff --git a/base/util.jl b/base/util.jl index da8cc8bb04862..199f332db5839 100644 --- a/base/util.jl +++ b/base/util.jl @@ -490,7 +490,7 @@ function warn(io::IO, msg...; show_backtrace(io, bt) end if filename !== nothing - print(io, "\nwhile loading $filename, in expression starting on line $lineno") + print(io, "\nin expression starting at $filename:$lineno") end println(io) return diff --git a/deps/libgit2.mk b/deps/libgit2.mk index 1196c0f6d0ac8..5d16aee5063fd 100644 --- a/deps/libgit2.mk +++ b/deps/libgit2.mk @@ -85,6 +85,11 @@ $(LIBGIT2_SRC_PATH)/libgit2-mbedtls-fixup.patch-applied: $(LIBGIT2_SRC_PATH)/sou patch -p1 -f < $(SRCDIR)/patches/libgit2-mbedtls-fixup.patch echo 1 > $@ +$(LIBGIT2_SRC_PATH)/libgit2-ssh-loop.patch-applied: $(LIBGIT2_SRC_PATH)/source-extracted | $(LIBGIT2_SRC_PATH)/libgit2-mbedtls-fixup.patch-applied + cd $(LIBGIT2_SRC_PATH) && \ + patch -p1 -f < $(SRCDIR)/patches/libgit2-ssh-loop.patch + echo 1 > $@ + $(build_datarootdir)/julia/cert.pem: $(CERTFILE) mkdir -p $(build_datarootdir)/julia cp -f $(CERTFILE) $@ @@ -94,7 +99,8 @@ $(BUILDDIR)/$(LIBGIT2_SRC_DIR)/build-configured: \ $(LIBGIT2_SRC_PATH)/libgit2-ssh.patch-applied \ $(LIBGIT2_SRC_PATH)/libgit2-agent-nonfatal.patch-applied \ $(LIBGIT2_SRC_PATH)/libgit2-mbedtls-verify.patch-applied \ - $(LIBGIT2_SRC_PATH)/libgit2-mbedtls-fixup.patch-applied + $(LIBGIT2_SRC_PATH)/libgit2-mbedtls-fixup.patch-applied \ + $(LIBGIT2_SRC_PATH)/libgit2-ssh-loop.patch-applied \ ifneq ($(CERTFILE),) $(BUILDDIR)/$(LIBGIT2_SRC_DIR)/build-configured: $(build_datarootdir)/julia/cert.pem diff --git a/deps/patches/libgit2-ssh-loop.patch b/deps/patches/libgit2-ssh-loop.patch new file mode 100644 index 0000000000000..dfc0ac632c95b --- /dev/null +++ b/deps/patches/libgit2-ssh-loop.patch @@ -0,0 +1,24 @@ +commit eac62497aec204568a494743f829d922787d69c5 +Author: Curtis Vogt +Date: Thu Sep 21 15:51:52 2017 -0500 + + Ask for credentials again when passphrase is wrong + + When trying to decode the private key it looks like LibSSH2 returns a + LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED when the passphrase is incorrect. + +diff --git a/src/transports/ssh.c b/src/transports/ssh.c +index 172ef413c..ec3b0b6ff 100644 +--- a/src/transports/ssh.c ++++ b/src/transports/ssh.c +@@ -420,8 +420,8 @@ static int _git_ssh_authenticate_session( + } + } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + +- if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED) +- return GIT_EAUTH; ++ if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED || rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) ++ return GIT_EAUTH; + + if (rc != LIBSSH2_ERROR_NONE) { + if (!giterr_last()) diff --git a/doc/man/julia.1 b/doc/man/julia.1 index 2944eca7f4436..56d8dfb909d68 100644 --- a/doc/man/julia.1 +++ b/doc/man/julia.1 @@ -89,7 +89,7 @@ Evaluate .TP -E, --print -Evaluate and show +Evaluate and display the result .TP -L, --load diff --git a/doc/src/devdocs/libgit2.md b/doc/src/devdocs/libgit2.md index f31eed7e6b396..3b6c49065691f 100644 --- a/doc/src/devdocs/libgit2.md +++ b/doc/src/devdocs/libgit2.md @@ -12,9 +12,7 @@ For more information on some of the objects and methods referenced here, consult [libgit2 API reference](https://libgit2.github.com/libgit2/#v0.25.1). ```@docs -Base.LibGit2.AbstractCredentials Base.LibGit2.Buffer -Base.LibGit2.CachedCredentials Base.LibGit2.CheckoutOptions Base.LibGit2.CloneOptions Base.LibGit2.DescribeOptions @@ -49,13 +47,11 @@ Base.LibGit2.PushOptions Base.LibGit2.RebaseOperation Base.LibGit2.RebaseOptions Base.LibGit2.RemoteCallbacks -Base.LibGit2.SSHCredentials Base.LibGit2.SignatureStruct Base.LibGit2.StatusEntry Base.LibGit2.StatusOptions Base.LibGit2.StrArrayStruct Base.LibGit2.TimeStruct -Base.LibGit2.UserPasswordCredentials Base.LibGit2.add! Base.LibGit2.add_fetch! Base.LibGit2.add_push! @@ -92,6 +88,7 @@ Base.LibGit2.features Base.LibGit2.filename Base.LibGit2.filemode Base.LibGit2.gitdir +Base.LibGit2.git_url Base.LibGit2.@githash_str Base.LibGit2.head Base.LibGit2.head! @@ -153,4 +150,12 @@ Base.LibGit2.with Base.LibGit2.with_warn Base.LibGit2.workdir Base.LibGit2.GitObject(::Base.LibGit2.GitTreeEntry) +Base.LibGit2.AbstractCredentials +Base.LibGit2.UserPasswordCredentials +Base.LibGit2.SSHCredentials +Base.LibGit2.isfilled +Base.LibGit2.CachedCredentials +Base.LibGit2.CredentialPayload +Base.LibGit2.approve +Base.LibGit2.reject ``` diff --git a/doc/src/devdocs/require.md b/doc/src/devdocs/require.md index 4862d9e5802b1..5198a7425ee49 100644 --- a/doc/src/devdocs/require.md +++ b/doc/src/devdocs/require.md @@ -26,15 +26,7 @@ The callback below is an example of how to do that: ```julia # Get the fully-qualified name of a module. function module_fqn(name::Symbol) - fqn = Symbol[name] - mod = getfield(Main, name) - parent = Base.module_parent(mod) - while parent !== Main - push!(fqn, Base.module_name(parent)) - parent = Base.module_parent(parent) - end - fqn = reverse!(fqn) + fqn = fullname(Base.root_module(name)) return join(fqn, '.') end ``` - diff --git a/doc/src/manual/getting-started.md b/doc/src/manual/getting-started.md index acae790309ad7..f71cbb520d41b 100644 --- a/doc/src/manual/getting-started.md +++ b/doc/src/manual/getting-started.md @@ -108,7 +108,7 @@ julia [switches] -- [programfile] [args...] Enable or disable incremental precompilation of modules -e, --eval Evaluate - -E, --print Evaluate and show + -E, --print Evaluate and display the result -L, --load Load immediately on all processors -p, --procs {N|auto} Integer value N launches N additional local worker processes @@ -116,6 +116,7 @@ julia [switches] -- [programfile] [args...] --machinefile Run processes on hosts listed in -i Interactive mode; REPL runs and isinteractive() is true + -q, --quiet Quiet startup: no banner, suppress REPL warnings --banner={yes|no} Enable or disable startup banner --color={yes|no} Enable or disable color text --history-file={yes|no} Load or save history @@ -124,10 +125,10 @@ julia [switches] -- [programfile] [args...] --warn-overwrite={yes|no} Enable or disable method overwrite warnings --compile={yes|no|all|min}Enable or disable JIT compiler, or request exhaustive compilation - -C, --cpu-target Limit usage of cpu features up to - -O, --optimize={0,1,2,3} Set the optimization level (default is 2 if unspecified or 3 if specified as -O) - -g, -g Enable / Set the level of debug info generation (default is 1 if unspecified or 2 if specified as -g) - --inline={yes|no} Control whether inlining is permitted (overrides functions declared as @inline) + -C, --cpu-target Limit usage of cpu features up to ; set to "help" to see the available options + -O, --optimize={0,1,2,3} Set the optimization level (default level is 2 if unspecified or 3 if used without a level) + -g, -g Enable / Set the level of debug info generation (default level is 1 if unspecified or 2 if used without a level) + --inline={yes|no} Control whether inlining is permitted, including overriding @inline declarations --check-bounds={yes|no} Emit bounds checks always or never (ignoring declarations) --math-mode={ieee,fast} Disallow or enable unsafe floating point optimizations (overrides @fastmath declaration) diff --git a/doc/src/manual/mathematical-operations.md b/doc/src/manual/mathematical-operations.md index 1f4fc3a3dddd2..4b7500fcc6e94 100644 --- a/doc/src/manual/mathematical-operations.md +++ b/doc/src/manual/mathematical-operations.md @@ -347,22 +347,29 @@ Moreover, these functions (like any Julia function) can be applied in "vectorize arrays and other collections with the [dot syntax](@ref man-vectorized) `f.(A)`, e.g. `sin.(A)` will compute the sine of each element of an array `A`. -## Operator Precedence - -Julia applies the following order of operations, from highest precedence to lowest: - -| Category | Operators | -|:-------------- |:------------------------------------------------------------------------------------------------- | -| Syntax | `.` followed by `::` | -| Exponentiation | `^` | -| Fractions | `//` | -| Multiplication | `* / % & \` | -| Bitshifts | `<< >> >>>` | -| Addition | `+ - \| ⊻` | -| Syntax | `: ..` followed by `\|>` | -| Comparisons | `> < >= <= == === != !== <:` | -| Control flow | `&&` followed by `\|\|` followed by `?` | -| Assignments | `= += -= *= /= //= \= ^= ÷= %= \|= &= ⊻= <<= >>= >>>=` | +## Operator Precedence and Associativity + +Julia applies the following order and associativity of operations, from highest precedence to lowest: + +| Category | Operators | Associativity | +|:-------------- |:------------------------------------------------------------------------------------------------- |:-------------------------- | +| Syntax | `.` followed by `::` | Left | +| Exponentiation | `^` | Right | +| Unary | `+ - √` | Right[^1] | +| Fractions | `//` | Left | +| Multiplication | `* / % & \` | Left[^2] | +| Bitshifts | `<< >> >>>` | Left | +| Addition | `+ - \| ⊻` | Left[^2] | +| Syntax | `: ..` followed by `\|>` | Left | +| Comparisons | `> < >= <= == === != !== <:` | Non-associative | +| Control flow | `&&` followed by `\|\|` followed by `?` | Right | +| Assignments | `= += -= *= /= //= \= ^= ÷= %= \|= &= ⊻= <<= >>= >>>=` | Right | + +[^1]: + The unary operators `+` and `-` require explicit parentheses around their argument to disambiguate them from the operator `++`, etc. Other compositions of unary operators are parsed with right-associativity, e. g., `√√-a` as `√(√(-a))`. +[^2]: + The operators `+`, `++` and `*` are non-associative. `a + b + c` is parsed as `+(a, b, c)` not `+(+(a, b), + c)`. However, the fallback methods for `+(a, b, c, d...)` and `*(a, b, c, d...)` both default to left-associative evaluation. For a complete list of *every* Julia operator's precedence, see the top of this file: [`src/julia-parser.scm`](https://github.com/JuliaLang/julia/blob/master/src/julia-parser.scm) @@ -373,10 +380,23 @@ You can also find the numerical precedence for any given operator via the built- julia> Base.operator_precedence(:+), Base.operator_precedence(:*), Base.operator_precedence(:.) (9, 11, 15) -julia> Base.operator_precedence(:+=), Base.operator_precedence(:(=)) # (Note the necessary parens on `:(=)`) -(1, 1) +julia> Base.operator_precedence(:sin), Base.operator_precedence(:+=), Base.operator_precedence(:(=)) # (Note the necessary parens on `:(=)`) +(0, 1, 1) ``` +A symbol representing the operator associativity can also be found by calling the built-in function `Base.operator_associativity`: + +```jldoctest +julia> Base.operator_associativity(:-), Base.operator_associativity(:+), Base.operator_associativity(:^) +(:left, :none, :right) + +julia> Base.operator_associativity(:⊗), Base.operator_associativity(:sin), Base.operator_associativity(:→) +(:left, :none, :right) +``` + +Note that symbols such as `:sin` return precedence `0`. This value represents invalid operators and not +operators of lowest precedence. Similarly, such operators are assigned associativity `:none`. + ## Numerical Conversions Julia supports three forms of numerical conversion, which differ in their handling of inexact diff --git a/doc/src/manual/modules.md b/doc/src/manual/modules.md index 0921880fa87e3..5a18c0083e9b3 100644 --- a/doc/src/manual/modules.md +++ b/doc/src/manual/modules.md @@ -158,14 +158,14 @@ end ### Relative and absolute module paths -Given the statement `using Foo`, the system looks for `Foo` within `Main`. If the module does -not exist, the system attempts to `require("Foo")`, which typically results in loading code from -an installed package. - -However, some modules contain submodules, which means you sometimes need to access a module that -is not directly available in `Main`. There are two ways to do this. The first is to use an absolute -path, for example `using Base.Sort`. The second is to use a relative path, which makes it easier -to import submodules of the current module or any of its enclosing modules: +Given the statement `using Foo`, the system consults an internal table of top-level modules +to look for one named `Foo`. If the module does not exist, the system attempts to `require(:Foo)`, +which typically results in loading code from an installed package. + +However, some modules contain submodules, which means you sometimes need to access a non-top-level +module. There are two ways to do this. The first is to use an absolute path, for example +`using Base.Sort`. The second is to use a relative path, which makes it easier to import submodules +of the current module or any of its enclosing modules: ``` module Parent diff --git a/doc/src/manual/types.md b/doc/src/manual/types.md index 893c53a1b54b7..1ccd9b5ef4ef2 100644 --- a/doc/src/manual/types.md +++ b/doc/src/manual/types.md @@ -1267,7 +1267,7 @@ julia> print(:($a^2)) 3.0 * exp(4.0im) ^ 2 ``` -Because the operator `^` has higher precedence than `*` (see [Operator Precedence](@ref)), this +Because the operator `^` has higher precedence than `*` (see [Operator Precedence and Associativity](@ref)), this output does not faithfully represent the expression `a ^ 2` which should be equal to `(3.0 * exp(4.0im)) ^ 2`. To solve this issue, we must make a custom method for `Base.show_unquoted(io::IO, z::Polar, indent::Int, precedence::Int)`, which is called internally by the expression object when diff --git a/doc/src/stdlib/strings.md b/doc/src/stdlib/strings.md index 9be348f2ec9f8..003b9b1b92b6d 100644 --- a/doc/src/stdlib/strings.md +++ b/doc/src/stdlib/strings.md @@ -59,8 +59,7 @@ Base.chr2ind Base.nextind Base.prevind Base.Random.randstring -Base.UTF8proc.charwidth -Base.strwidth +Base.UTF8proc.textwidth Base.UTF8proc.isalnum Base.UTF8proc.isalpha Base.isascii diff --git a/examples/clustermanager/0mq/ZMQCM.jl b/examples/clustermanager/0mq/ZMQCM.jl index d18e7b66d58d0..658c5b9f438d4 100644 --- a/examples/clustermanager/0mq/ZMQCM.jl +++ b/examples/clustermanager/0mq/ZMQCM.jl @@ -1,6 +1,10 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -using ZMQ +# the 0mq clustermanager depends on package ZMQ. For testing purposes, at least +# make sure the code loads without it. +try + using ZMQ +end import Base: launch, manage, connect, kill diff --git a/src/ast.c b/src/ast.c index 1a75ab7a319f1..4f328bad1c979 100644 --- a/src/ast.c +++ b/src/ast.c @@ -950,6 +950,24 @@ JL_DLLEXPORT int jl_is_operator(char *sym) return res; } +JL_DLLEXPORT int jl_is_unary_operator(char *sym) +{ + jl_ast_context_t *ctx = jl_ast_ctx_enter(); + fl_context_t *fl_ctx = &ctx->fl; + int res = fl_applyn(fl_ctx, 1, symbol_value(symbol(fl_ctx, "unary-op?")), symbol(fl_ctx, sym)) == fl_ctx->T; + jl_ast_ctx_leave(ctx); + return res; +} + +JL_DLLEXPORT int jl_is_unary_and_binary_operator(char *sym) +{ + jl_ast_context_t *ctx = jl_ast_ctx_enter(); + fl_context_t *fl_ctx = &ctx->fl; + int res = fl_applyn(fl_ctx, 1, symbol_value(symbol(fl_ctx, "unary-and-binary-op?")), symbol(fl_ctx, sym)) == fl_ctx->T; + jl_ast_ctx_leave(ctx); + return res; +} + JL_DLLEXPORT int jl_operator_precedence(char *sym) { jl_ast_context_t *ctx = jl_ast_ctx_enter(); diff --git a/src/builtins.c b/src/builtins.c index 0a5950e930d25..d77471819df63 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -115,17 +115,63 @@ static int NOINLINE compare_fields(jl_value_t *a, jl_value_t *b, jl_datatype_t * return 1; } +static int egal_types(jl_value_t *a, jl_value_t *b, jl_typeenv_t *env) +{ + if (a == b) + return 1; + jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(a); + if (dt != (jl_datatype_t*)jl_typeof(b)) + return 0; + if (dt == jl_tvar_type) { + jl_typeenv_t *pe = env; + while (pe != NULL) { + if (pe->var == (jl_tvar_t*)a) + return pe->val == b; + pe = pe->prev; + } + return 0; + } + if (dt == jl_uniontype_type) { + return egal_types(((jl_uniontype_t*)a)->a, ((jl_uniontype_t*)b)->a, env) && + egal_types(((jl_uniontype_t*)a)->b, ((jl_uniontype_t*)b)->b, env); + } + if (dt == jl_unionall_type) { + jl_unionall_t *ua = (jl_unionall_t*)a; + jl_unionall_t *ub = (jl_unionall_t*)b; + if (ua->var->name != ub->var->name) + return 0; + if (!(egal_types(ua->var->lb, ub->var->lb, env) && egal_types(ua->var->ub, ub->var->ub, env))) + return 0; + jl_typeenv_t e = { ua->var, (jl_value_t*)ub->var, env }; + return egal_types(ua->body, ub->body, &e); + } + if (dt == jl_datatype_type) { + jl_datatype_t *dta = (jl_datatype_t*)a; + jl_datatype_t *dtb = (jl_datatype_t*)b; + if (dta->name != dtb->name) + return 0; + size_t i, l = jl_nparams(dta); + if (jl_nparams(dtb) != l) + return 0; + for (i = 0; i < l; i++) { + if (!egal_types(jl_tparam(dta, i), jl_tparam(dtb, i), env)) + return 0; + } + return 1; + } + return jl_egal(a, b); +} + JL_DLLEXPORT int jl_egal(jl_value_t *a, jl_value_t *b) { // warning: a,b may NOT have been gc-rooted by the caller if (a == b) return 1; - jl_value_t *ta = (jl_value_t*)jl_typeof(a); - if (ta != (jl_value_t*)jl_typeof(b)) + jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(a); + if (dt != (jl_datatype_t*)jl_typeof(b)) return 0; - if (jl_is_svec(a)) + if (dt == jl_simplevector_type) return compare_svec((jl_svec_t*)a, (jl_svec_t*)b); - jl_datatype_t *dt = (jl_datatype_t*)ta; if (dt == jl_datatype_type) { jl_datatype_t *dta = (jl_datatype_t*)a; jl_datatype_t *dtb = (jl_datatype_t*)b; @@ -145,6 +191,8 @@ JL_DLLEXPORT int jl_egal(jl_value_t *a, jl_value_t *b) size_t nf = jl_datatype_nfields(dt); if (nf == 0) return bits_equal(jl_data_ptr(a), jl_data_ptr(b), sz); + if (dt == jl_unionall_type) + return egal_types(a, b, NULL); return compare_fields(a, b, dt); } @@ -182,6 +230,52 @@ static uintptr_t NOINLINE hash_svec(jl_svec_t *v) return h; } +typedef struct _varidx { + jl_tvar_t *var; + struct _varidx *prev; +} jl_varidx_t; + +static uintptr_t jl_object_id_(jl_value_t *tv, jl_value_t *v); + +static uintptr_t type_object_id_(jl_value_t *v, jl_varidx_t *env) +{ + if (v == NULL) return 0; + jl_datatype_t *tv = (jl_datatype_t*)jl_typeof(v); + if (tv == jl_tvar_type) { + jl_varidx_t *pe = env; + int i = 0; + while (pe != NULL) { + if (pe->var == (jl_tvar_t*)v) + return (i<<8) + 42; + i++; + pe = pe->prev; + } + return inthash((uintptr_t)v); + } + if (tv == jl_uniontype_type) { + return bitmix(bitmix(jl_object_id((jl_value_t*)tv), + type_object_id_(((jl_uniontype_t*)v)->a, env)), + type_object_id_(((jl_uniontype_t*)v)->b, env)); + } + if (tv == jl_unionall_type) { + jl_unionall_t *u = (jl_unionall_t*)v; + uintptr_t h = u->var->name->hash; + h = bitmix(h, type_object_id_(u->var->lb, env)); + h = bitmix(h, type_object_id_(u->var->ub, env)); + jl_varidx_t e = { u->var, env }; + return bitmix(h, type_object_id_(u->body, &e)); + } + if (tv == jl_datatype_type) { + uintptr_t h = ~((jl_datatype_t*)v)->name->hash; + size_t i, l = jl_nparams(v); + for (i = 0; i < l; i++) { + h = bitmix(h, type_object_id_(jl_tparam(v, i), env)); + } + return h; + } + return jl_object_id_((jl_value_t*)tv, v); +} + static uintptr_t jl_object_id_(jl_value_t *tv, jl_value_t *v) { if (tv == (jl_value_t*)jl_sym_type) @@ -222,6 +316,8 @@ static uintptr_t jl_object_id_(jl_value_t *tv, jl_value_t *v) size_t f, nf = jl_datatype_nfields(dt); if (nf == 0) return bits_hash(jl_data_ptr(v), sz) ^ h; + if (dt == jl_unionall_type) + return type_object_id_(v, NULL); for (f = 0; f < nf; f++) { size_t offs = jl_field_offset(dt, f); char *vo = (char*)jl_data_ptr(v) + offs; diff --git a/src/ccall.cpp b/src/ccall.cpp index 350fa016fd788..9834376258e8e 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -2087,21 +2087,26 @@ jl_cgval_t function_sig_t::emit_a_ccall( size_t rtsz = jl_datatype_size(rt); assert(rtsz > 0); Value *strct = emit_allocobj(ctx, rtsz, runtime_bt); + MDNode *tbaa = jl_is_mutable(rt) ? tbaa_mutab : tbaa_immut; int boxalign = jl_datatype_align(rt); -#ifndef JL_NDEBUG + // copy the data from the return value to the new struct #if JL_LLVM_VERSION >= 40000 const DataLayout &DL = jl_data_layout; #else const DataLayout &DL = jl_ExecutionEngine->getDataLayout(); #endif - // ARM and AArch64 can use a LLVM type larger than the julia - // type. However, the LLVM type size should be no larger than - // the GC allocation size. (multiple of `sizeof(void*)`) - assert(DL.getTypeStoreSize(lrt) <= LLT_ALIGN(rtsz, boxalign)); -#endif - // copy the data from the return value to the new struct - MDNode *tbaa = jl_is_mutable(rt) ? tbaa_mutab : tbaa_immut; - init_bits_value(ctx, strct, result, tbaa, boxalign); + auto resultTy = result->getType(); + if (DL.getTypeStoreSize(resultTy) > rtsz) { + // ARM and AArch64 can use a LLVM type larger than the julia type. + // When this happens, cast through memory. + auto slot = emit_static_alloca(ctx, resultTy); + slot->setAlignment(boxalign); + ctx.builder.CreateAlignedStore(result, slot, boxalign); + emit_memcpy(ctx, strct, slot, rtsz, boxalign, tbaa); + } + else { + init_bits_value(ctx, strct, result, tbaa, boxalign); + } return mark_julia_type(ctx, strct, true, rt); } jlretboxed = false; // trigger mark_or_box_ccall_result to build the runtime box diff --git a/src/cgutils.cpp b/src/cgutils.cpp index a81c67c960fac..239068286ef09 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -1758,18 +1758,49 @@ static Value *emit_array_nd_index( if (bc) { // We have already emitted a bounds check for each index except for // the last one which we therefore have to do here. - bool linear_indexing = nd == -1 || nidxs < (size_t)nd; - if (linear_indexing && nidxs == 1) { - // Check against the entire linear span of the array + if (nidxs == 1) { + // Linear indexing: Check against the entire linear span of the array Value *alen = emit_arraylen(ctx, ainfo, ex); ctx.builder.CreateCondBr(ctx.builder.CreateICmpULT(i, alen), endBB, failBB); - } else { - // Compare the last index of the access against the last dimension of - // the accessed array, i.e. `if !(last_index < last_dimension) goto error`. + } else if (nidxs >= (size_t)nd){ + // No dimensions were omitted; just check the last remaining index assert(nd >= 0); Value *last_index = ii; Value *last_dimension = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, nidxs, nd); ctx.builder.CreateCondBr(ctx.builder.CreateICmpULT(last_index, last_dimension), endBB, failBB); + } else { + // There were fewer indices than dimensions; check the last remaining index + BasicBlock *depfailBB = BasicBlock::Create(jl_LLVMContext, "dimsdepfail"); // REMOVE AFTER 0.7 + BasicBlock *depwarnBB = BasicBlock::Create(jl_LLVMContext, "dimsdepwarn"); // REMOVE AFTER 0.7 + BasicBlock *checktrailingdimsBB = BasicBlock::Create(jl_LLVMContext, "dimsib"); + assert(nd >= 0); + Value *last_index = ii; + Value *last_dimension = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, nidxs, nd); + ctx.builder.CreateCondBr(ctx.builder.CreateICmpULT(last_index, last_dimension), checktrailingdimsBB, failBB); + ctx.f->getBasicBlockList().push_back(checktrailingdimsBB); + ctx.builder.SetInsertPoint(checktrailingdimsBB); + // And then also make sure that all dimensions that weren't explicitly + // indexed into have size 1 + for (size_t k = nidxs+1; k < (size_t)nd; k++) { + BasicBlock *dimsokBB = BasicBlock::Create(jl_LLVMContext, "dimsok"); + Value *dim = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, k, nd); + ctx.builder.CreateCondBr(ctx.builder.CreateICmpEQ(dim, ConstantInt::get(T_size, 1)), dimsokBB, depfailBB); // s/depfailBB/failBB/ AFTER 0.7 + ctx.f->getBasicBlockList().push_back(dimsokBB); + ctx.builder.SetInsertPoint(dimsokBB); + } + Value *dim = emit_arraysize_for_unsafe_dim(ctx, ainfo, ex, nd, nd); + ctx.builder.CreateCondBr(ctx.builder.CreateICmpEQ(dim, ConstantInt::get(T_size, 1)), endBB, depfailBB); // s/depfailBB/failBB/ AFTER 0.7 + + // Remove after 0.7: Ensure no dimensions were 0 and depwarn + ctx.f->getBasicBlockList().push_back(depfailBB); + ctx.builder.SetInsertPoint(depfailBB); + Value *total_length = emit_arraylen(ctx, ainfo, ex); + ctx.builder.CreateCondBr(ctx.builder.CreateICmpULT(i, total_length), depwarnBB, failBB); + + ctx.f->getBasicBlockList().push_back(depwarnBB); + ctx.builder.SetInsertPoint(depwarnBB); + ctx.builder.CreateCall(prepare_call(jldepwarnpi_func), ConstantInt::get(T_size, nidxs)); + ctx.builder.CreateBr(endBB); } ctx.f->getBasicBlockList().push_back(failBB); diff --git a/src/codegen.cpp b/src/codegen.cpp index 22e12f955be8a..240f01214cd52 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -341,6 +341,7 @@ static Function *expect_func; static Function *jldlsym_func; static Function *jlnewbits_func; static Function *jltypeassert_func; +static Function *jldepwarnpi_func; //static Function *jlgetnthfield_func; static Function *jlgetnthfieldchecked_func; //static Function *jlsetnthfield_func; @@ -6315,6 +6316,14 @@ static void init_julia_llvm_env(Module *m) jlapply2va_func = jlcall_func_to_llvm("jl_apply_2va", &jl_apply_2va, m); + std::vector argsdepwarnpi(0); + argsdepwarnpi.push_back(T_size); + jldepwarnpi_func = Function::Create(FunctionType::get(T_void, argsdepwarnpi, false), + Function::ExternalLinkage, + "jl_depwarn_partial_indexing", m); + add_named_global(jldepwarnpi_func, &jl_depwarn_partial_indexing); + + std::vector args_1ptr(0); args_1ptr.push_back(T_prjlvalue); queuerootfun = Function::Create(FunctionType::get(T_void, args_1ptr, false), diff --git a/src/dump.c b/src/dump.c index ba2df8034321f..1941730083e88 100644 --- a/src/dump.c +++ b/src/dump.c @@ -110,6 +110,7 @@ typedef struct { jl_array_t *tree_literal_values; jl_module_t *tree_enclosing_module; jl_ptls_t ptls; + jl_array_t *loaded_modules_array; } jl_serializer_state; static jl_value_t *jl_idtable_type = NULL; @@ -369,16 +370,32 @@ static void jl_serialize_module(jl_serializer_state *s, jl_module_t *m) { writetag(s->s, jl_module_type); jl_serialize_value(s, m->name); - int ref_only = 0; - if (!module_in_worklist(m)) - ref_only = 1; - write_int8(s->s, ref_only); - jl_serialize_value(s, m->parent); - if (ref_only) { - assert(m->parent != m); + size_t i; + if (!module_in_worklist(m)) { + if (m == m->parent) { + // top-level module + write_int8(s->s, 2); + int j = 0; + for (i = 0; i < jl_array_len(s->loaded_modules_array); i++) { + jl_module_t *mi = (jl_module_t*)jl_array_ptr_ref(s->loaded_modules_array, i); + if (!module_in_worklist(mi)) { + if (m == mi) { + write_int32(s->s, j); + return; + } + j++; + } + } + assert(0 && "top level module not found in modules array"); + } + else { + write_int8(s->s, 1); + jl_serialize_value(s, m->parent); + } return; } - size_t i; + write_int8(s->s, 0); + jl_serialize_value(s, m->parent); void **table = m->bindings.table; for(i=1; i < m->bindings.size; i+=2) { if (table[i] != HT_NOTFOUND) { @@ -994,28 +1011,19 @@ static void jl_collect_backedges(jl_array_t *s) } } -// serialize information about all of the modules accessible directly from Main -static void write_mod_list(ios_t *s) +// serialize information about all loaded modules +static void write_mod_list(ios_t *s, jl_array_t *a) { - jl_module_t *m = jl_main_module; size_t i; - void **table = m->bindings.table; - for (i = 1; i < m->bindings.size; i += 2) { - if (table[i] != HT_NOTFOUND) { - jl_binding_t *b = (jl_binding_t*)table[i]; - if (b->owner == m && - b->value && b->constp && - jl_is_module(b->value) && - !module_in_worklist((jl_module_t*)b->value)) { - jl_module_t *child = (jl_module_t*)b->value; - if (child->name == b->name) { - // this is the original/primary binding for the submodule - size_t l = strlen(jl_symbol_name(child->name)); - write_int32(s, l); - ios_write(s, jl_symbol_name(child->name), l); - write_uint64(s, child->uuid); - } - } + size_t len = jl_array_len(a); + for (i = 0; i < len; i++) { + jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(a, i); + assert(jl_is_module(m)); + if (!module_in_worklist(m)) { + size_t l = strlen(jl_symbol_name(m->name)); + write_int32(s, l); + ios_write(s, jl_symbol_name(m->name), l); + write_uint64(s, m->uuid); } } write_int32(s, 0); @@ -1045,7 +1053,7 @@ static void write_work_list(ios_t *s) int i, l = jl_array_len(serializer_worklist); for (i = 0; i < l; i++) { jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(serializer_worklist, i); - if (workmod->parent == jl_main_module) { + if (workmod->parent == jl_main_module || workmod->parent == workmod) { size_t l = strlen(jl_symbol_name(workmod->name)); write_int32(s, l); ios_write(s, jl_symbol_name(workmod->name), l); @@ -1514,7 +1522,11 @@ static jl_value_t *jl_deserialize_value_module(jl_serializer_state *s) jl_sym_t *mname = (jl_sym_t*)jl_deserialize_value(s, NULL); int ref_only = read_uint8(s->s); if (ref_only) { - jl_value_t *m_ref = jl_get_global((jl_module_t*)jl_deserialize_value(s, NULL), mname); + jl_value_t *m_ref; + if (ref_only == 1) + m_ref = jl_get_global((jl_module_t*)jl_deserialize_value(s, NULL), mname); + else + m_ref = jl_array_ptr_ref(s->loaded_modules_array, read_int32(s->s)); if (usetable) backref_list.items[pos] = m_ref; return m_ref; @@ -1900,44 +1912,47 @@ static jl_value_t *read_verify_mod_list(ios_t *s, arraylist_t *dependent_worlds) return jl_get_exceptionf(jl_errorexception_type, "Main module uuid state is invalid for module deserialization."); } + jl_array_t *mod_array = jl_alloc_vec_any(0); + JL_GC_PUSH1(&mod_array); while (1) { size_t len = read_int32(s); - if (len == 0) - return NULL; + if (len == 0) { + JL_GC_POP(); + return (jl_value_t*)mod_array; + } char *name = (char*)alloca(len+1); ios_read(s, name, len); name[len] = '\0'; uint64_t uuid = read_uint64(s); jl_sym_t *sym = jl_symbol(name); jl_module_t *m = NULL; - if (jl_binding_resolved_p(jl_main_module, sym)) - m = (jl_module_t*)jl_get_global(jl_main_module, sym); - if (!m) { - static jl_value_t *require_func = NULL; - if (!require_func) - require_func = jl_get_global(jl_base_module, jl_symbol("require")); - jl_value_t *reqargs[2] = {require_func, (jl_value_t*)sym}; - JL_TRY { - jl_apply(reqargs, 2); - } - JL_CATCH { - ios_close(s); - jl_rethrow(); - } - m = (jl_module_t*)jl_get_global(jl_main_module, sym); + static jl_value_t *require_func = NULL; + if (!require_func) + require_func = jl_get_global(jl_base_module, jl_symbol("require")); + jl_value_t *reqargs[2] = {require_func, (jl_value_t*)sym}; + JL_TRY { + m = (jl_module_t*)jl_apply(reqargs, 2); + } + JL_CATCH { + ios_close(s); + jl_rethrow(); } if (!m) { + JL_GC_POP(); return jl_get_exceptionf(jl_errorexception_type, "Requiring \"%s\" did not define a corresponding module.", name); } if (!jl_is_module(m)) { + JL_GC_POP(); return jl_get_exceptionf(jl_errorexception_type, "Invalid module path (%s does not name a module).", name); } if (m->uuid != uuid) { + JL_GC_POP(); return jl_get_exceptionf(jl_errorexception_type, "Module %s uuid did not match cache file.", name); } + jl_array_ptr_1d_push(mod_array, (jl_value_t*)m); if (m->primary_world > jl_main_module->primary_world) arraylist_push(dependent_worlds, (void*)m->primary_world); } @@ -2003,6 +2018,8 @@ static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list) } case 2: { // reinsert module v into parent (const) jl_module_t *mod = (jl_module_t*)v; + if (mod->parent == mod) // top level modules handled by loader + break; jl_binding_t *b = jl_get_binding_wr(mod->parent, mod->name, 1); jl_declare_constant(b); // this can throw if (b->value != NULL) { @@ -2013,8 +2030,7 @@ static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list) if (jl_generating_output() && jl_options.incremental) { jl_errorf("Cannot replace module %s during incremental precompile.", jl_symbol_name(mod->name)); } - jl_printf(JL_STDERR, "WARNING: replacing module %s.\n", - jl_symbol_name(mod->name)); + jl_printf(JL_STDERR, "WARNING: replacing module %s.\n", jl_symbol_name(mod->name)); } b->value = v; jl_gc_wb_binding(b, v); @@ -2100,7 +2116,8 @@ JL_DLLEXPORT jl_array_t *jl_compress_ast(jl_method_t *m, jl_code_info_t *code) jl_serializer_state s = { &dest, MODE_AST, m->roots, m->module, - jl_get_ptls_states() + jl_get_ptls_states(), + NULL }; uint8_t flags = (code->inferred << 3) @@ -2158,7 +2175,8 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ast(jl_method_t *m, jl_array_t *data) jl_serializer_state s = { &src, MODE_AST, m->roots, m->module, - jl_get_ptls_states() + jl_get_ptls_states(), + NULL }; jl_code_info_t *code = @@ -2262,16 +2280,20 @@ JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist) { char *tmpfname = strcat(strcpy((char *) alloca(strlen(fname)+8), fname), ".XXXXXX"); ios_t f; + jl_array_t *mod_array; if (ios_mkstemp(&f, tmpfname) == NULL) { jl_printf(JL_STDERR, "Cannot open cache file \"%s\" for writing.\n", tmpfname); return 1; } + JL_GC_PUSH1(&mod_array); + mod_array = jl_get_loaded_modules(); + serializer_worklist = worklist; write_header(&f); write_work_list(&f); write_dependency_list(&f); - write_mod_list(&f); // this can return errors during deserialize, - // best to keep it early (before any actual initialization) + write_mod_list(&f, mod_array); // this can return errors during deserialize, + // best to keep it early (before any actual initialization) arraylist_new(&reinit_list, 0); htable_new(&edges_map, 0); @@ -2283,13 +2305,23 @@ JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist) int en = jl_gc_enable(0); // edges map is not gc-safe jl_array_t *lambdas = jl_alloc_vec_any(0); jl_array_t *edges = jl_alloc_vec_any(0); - jl_collect_lambdas_from_mod(lambdas, jl_main_module); + + size_t i; + size_t len = jl_array_len(mod_array); + for (i = 0; i < len; i++) { + jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_array, i); + assert(jl_is_module(m)); + jl_collect_lambdas_from_mod(lambdas, m); + } + JL_GC_POP(); + jl_collect_backedges(edges); jl_serializer_state s = { &f, MODE_MODULE, NULL, NULL, - jl_get_ptls_states() + jl_get_ptls_states(), + mod_array }; jl_serialize_value(&s, worklist); jl_serialize_value(&s, lambdas); @@ -2588,12 +2620,13 @@ static jl_value_t *_jl_restore_incremental(ios_t *f) arraylist_new(&dependent_worlds, 0); // verify that the system state is valid - jl_value_t *verify_error = read_verify_mod_list(f, &dependent_worlds); - if (verify_error) { + jl_value_t *verify_result = read_verify_mod_list(f, &dependent_worlds); + if (!jl_is_array(verify_result)) { arraylist_free(&dependent_worlds); ios_close(f); - return verify_error; + return verify_result; } + jl_array_t *mod_array = (jl_array_t*)verify_result; // prepare to deserialize int en = jl_gc_enable(0); @@ -2610,7 +2643,8 @@ static jl_value_t *_jl_restore_incremental(ios_t *f) jl_serializer_state s = { f, MODE_MODULE, NULL, NULL, - ptls + ptls, + mod_array }; jl_array_t *restored = (jl_array_t*)jl_deserialize_value(&s, (jl_value_t**)&restored); serializer_worklist = restored; diff --git a/src/flisp/julia_extensions.c b/src/flisp/julia_extensions.c index 2b75282863b8f..b54c6889ab982 100644 --- a/src/flisp/julia_extensions.c +++ b/src/flisp/julia_extensions.c @@ -203,7 +203,7 @@ value_t fl_julia_strip_op_suffix(fl_context_t *fl_ctx, value_t *args, uint32_t n } if (!op[i]) return args[0]; // no suffix to strip if (!i) lerror(fl_ctx, symbol(fl_ctx, "error"), "invalid operator"); - char *opnew = strncpy(malloc(i+1), op, i); + char *opnew = strncpy((char*)malloc(i+1), op, i); opnew[i] = 0; value_t opnew_symbol = symbol(fl_ctx, opnew); free(opnew); diff --git a/src/gf.c b/src/gf.c index 6e01d6de2669a..bf2ef23109fdf 100644 --- a/src/gf.c +++ b/src/gf.c @@ -435,13 +435,15 @@ static int get_method_unspec_list(jl_typemap_entry_t *def, void *closure) return 1; } -void jl_foreach_mtable_in_module( +static void foreach_mtable_in_module( jl_module_t *m, void (*visit)(jl_methtable_t *mt, void *env), - void *env) + void *env, + jl_array_t *visited) { size_t i; void **table = m->bindings.table; + jl_eqtable_put(visited, m, jl_true); for (i = 1; i < m->bindings.size; i += 2) { if (table[i] != HT_NOTFOUND) { jl_binding_t *b = (jl_binding_t*)table[i]; @@ -458,9 +460,10 @@ void jl_foreach_mtable_in_module( } else if (jl_is_module(v)) { jl_module_t *child = (jl_module_t*)v; - if (child != m && child->parent == m && child->name == b->name) { + if (child != m && child->parent == m && child->name == b->name && + !jl_eqtable_get(visited, v, NULL)) { // this is the original/primary binding for the submodule - jl_foreach_mtable_in_module(child, visit, env); + foreach_mtable_in_module(child, visit, env, visited); } } } @@ -468,6 +471,27 @@ void jl_foreach_mtable_in_module( } } +void jl_foreach_reachable_mtable(void (*visit)(jl_methtable_t *mt, void *env), void *env) +{ + jl_array_t *visited = jl_alloc_vec_any(16); + jl_array_t *mod_array = NULL; + JL_GC_PUSH2(&visited, &mod_array); + mod_array = jl_get_loaded_modules(); + if (mod_array) { + int i; + for (i = 0; i < jl_array_len(mod_array); i++) { + jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_array, i); + assert(jl_is_module(m)); + if (!jl_eqtable_get(visited, (jl_value_t*)m, NULL)) + foreach_mtable_in_module(m, visit, env, visited); + } + } + else { + foreach_mtable_in_module(jl_main_module, visit, env, visited); + } + JL_GC_POP(); +} + static void reset_mt_caches(jl_methtable_t *mt, void *env) { // removes all method caches @@ -489,7 +513,7 @@ JL_DLLEXPORT void jl_set_typeinf_func(jl_value_t *f) // TODO: also reinfer if max_world != ~(size_t)0 jl_array_t *unspec = jl_alloc_vec_any(0); JL_GC_PUSH1(&unspec); - jl_foreach_mtable_in_module(jl_main_module, reset_mt_caches, (void*)unspec); + jl_foreach_reachable_mtable(reset_mt_caches, (void*)unspec); size_t i, l; for (i = 0, l = jl_array_len(unspec); i < l; i++) { jl_method_instance_t *li = (jl_method_instance_t*)jl_array_ptr_ref(unspec, i); diff --git a/src/init.c b/src/init.c index 3556f7d8a90d9..ddbd9e45678a9 100644 --- a/src/init.c +++ b/src/init.c @@ -434,40 +434,58 @@ int isabspath(const char *in) return 0; // relative path } -static char *abspath(const char *in) +static char *abspath(const char *in, int nprefix) { // compute an absolute path location, so that chdir doesn't change the file reference + // ignores (copies directly over) nprefix characters at the start of abspath #ifndef _OS_WINDOWS_ - char *out = realpath(in, NULL); - if (!out) { - if (in[0] == PATHSEPSTRING[0]) { - out = strdup(in); + char *out = realpath(in + nprefix, NULL); + if (out) { + if (nprefix > 0) { + size_t sz = strlen(out) + 1; + char *cpy = (char*)malloc(sz + nprefix); + if (!cpy) + jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno)); + memcpy(cpy, in, nprefix); + memcpy(cpy + nprefix, out, sz); + free(out); + out = cpy; + } + } + else { + size_t sz = strlen(in + nprefix) + 1; + if (in[nprefix] == PATHSEPSTRING[0]) { + out = (char*)malloc(sz + nprefix); + if (!out) + jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno)); + memcpy(out, in, sz + nprefix); } else { size_t path_size = PATH_MAX; - size_t len = strlen(in); char *path = (char*)malloc(PATH_MAX); + if (!path) + jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno)); if (uv_cwd(path, &path_size)) { jl_error("fatal error: unexpected error while retrieving current working directory"); } - if (path_size + len + 2 >= PATH_MAX) { - jl_error("fatal error: current working directory path too long"); - } - path[path_size] = PATHSEPSTRING[0]; - memcpy(path + path_size + 1, in, len+1); - out = strdup(path); + out = (char*)malloc(path_size + 1 + sz + nprefix); + memcpy(out, in, nprefix); + memcpy(out + nprefix, path, path_size); + out[nprefix + path_size] = PATHSEPSTRING[0]; + memcpy(out + nprefix + path_size + 1, in + nprefix, sz); free(path); } } #else - DWORD n = GetFullPathName(in, 0, NULL, NULL); + DWORD n = GetFullPathName(in + nprefix, 0, NULL, NULL); if (n <= 0) { jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed"); } - char *out = (char*)malloc(n); - DWORD m = GetFullPathName(in, n, out, NULL); + char *out = (char*)malloc(n + nprefix); + DWORD m = GetFullPathName(in + nprefix, n, out + nprefix, NULL); if (n != m + 1) { jl_error("fatal error: jl_options.image_file path too long or GetFullPathName failed"); } + memcpy(out, in, nprefix); #endif return out; } @@ -498,7 +516,7 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel) } } if (jl_options.julia_home) - jl_options.julia_home = abspath(jl_options.julia_home); + jl_options.julia_home = abspath(jl_options.julia_home, 0); free(free_path); free_path = NULL; if (jl_options.image_file) { @@ -513,22 +531,30 @@ static void jl_resolve_sysimg_location(JL_IMAGE_SEARCH rel) jl_options.image_file = free_path; } if (jl_options.image_file) - jl_options.image_file = abspath(jl_options.image_file); + jl_options.image_file = abspath(jl_options.image_file, 0); if (free_path) { free(free_path); free_path = NULL; } } if (jl_options.outputo) - jl_options.outputo = abspath(jl_options.outputo); + jl_options.outputo = abspath(jl_options.outputo, 0); if (jl_options.outputji) - jl_options.outputji = abspath(jl_options.outputji); + jl_options.outputji = abspath(jl_options.outputji, 0); if (jl_options.outputbc) - jl_options.outputbc = abspath(jl_options.outputbc); + jl_options.outputbc = abspath(jl_options.outputbc, 0); if (jl_options.machinefile) - jl_options.machinefile = abspath(jl_options.machinefile); - if (jl_options.load) - jl_options.load = abspath(jl_options.load); + jl_options.machinefile = abspath(jl_options.machinefile, 0); + + const char **cmdp = jl_options.cmds; + if (cmdp) { + for (; *cmdp; cmdp++) { + const char *cmd = *cmdp; + if (cmd[0] == 'L') { + *cmdp = abspath(cmd, 1); + } + } + } } static void jl_set_io_wait(int v) diff --git a/src/jloptions.c b/src/jloptions.c index 7fe1adb84913d..7bd9729eaaccc 100644 --- a/src/jloptions.c +++ b/src/jloptions.c @@ -38,9 +38,7 @@ jl_options_t jl_options = { 0, // quiet -1, // banner NULL, // julia_home NULL, // julia_bin - NULL, // eval - NULL, // print - NULL, // load + NULL, // cmds NULL, // image_file (will be filled in below) NULL, // cpu_target ("native", "core2", etc...) 0, // nprocs @@ -96,7 +94,7 @@ static const char opts[] = // actions " -e, --eval Evaluate \n" - " -E, --print Evaluate and show \n" + " -E, --print Evaluate and display the result\n" " -L, --load Load immediately on all processors\n\n" // parallel options @@ -125,7 +123,7 @@ static const char opts[] = #else " (default level is 1 if unspecified or 2 if used without a level)\n" #endif - " --inline={yes|no} Control whether inlining is permitted (overrides functions declared as @inline)\n" + " --inline={yes|no} Control whether inlining is permitted, including overriding @inline declarations\n" " --check-bounds={yes|no} Emit bounds checks always or never (ignoring declarations)\n" #ifdef USE_POLLY " --polly={yes|no} Enable or disable the polyhedral optimizer Polly (overrides @polly declaration)\n" @@ -231,14 +229,17 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) // If CPUID specific binaries are enabled, this varies between runs, so initialize // it here, rather than as part of the static initialization above. jl_options.image_file = jl_get_default_sysimg_path(); + jl_options.cmds = NULL; - int codecov = JL_LOG_NONE; - int malloclog= JL_LOG_NONE; + int ncmds = 0; + const char **cmds = NULL; + int codecov = JL_LOG_NONE; + int malloclog = JL_LOG_NONE; // getopt handles argument parsing up to -- delineator int argc = *argcp; char **argv = *argvp; if (argc > 0) { - for (int i=0; i < argc; i++) { + for (int i = 0; i < argc; i++) { if (!strcmp(argv[i], "--")) { argc = i; break; @@ -309,18 +310,36 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) break; case 'H': // home jl_options.julia_home = strdup(optarg); + if (!jl_options.julia_home) + jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno)); break; case 'e': // eval - jl_options.eval = strdup(optarg); - break; case 'E': // print - jl_options.print = strdup(optarg); - break; case 'L': // load - jl_options.load = strdup(optarg); + { + size_t sz = strlen(optarg) + 1; + char *arg = (char*)malloc(sz + 1); + const char **newcmds; + if (!arg) + jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno)); + arg[0] = c; + memcpy(arg + 1, optarg, sz); + newcmds = (const char**)realloc(cmds, (ncmds + 2) * sizeof(char*)); + if (!newcmds) { + free(cmds); + jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno)); + } + cmds = newcmds; + cmds[ncmds] = arg; + ncmds++; + cmds[ncmds] = 0; + jl_options.cmds = cmds; break; + } case 'J': // sysimage jl_options.image_file = strdup(optarg); + if (!jl_options.image_file) + jl_errorf("fatal error: failed to allocate memory: %s", strerror(errno)); jl_options.image_file_specified = 1; break; case 'q': // quiet @@ -360,6 +379,8 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) break; case 'C': // cpu-target jl_options.cpu_target = strdup(optarg); + if (!jl_options.cpu_target) + jl_error("julia: failed to allocate memory"); break; case 'p': // procs errno = 0; @@ -375,6 +396,8 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) break; case opt_machinefile: jl_options.machinefile = strdup(optarg); + if (!jl_options.machinefile) + jl_error("julia: failed to allocate memory"); break; case opt_color: if (!strcmp(optarg,"yes")) @@ -548,10 +571,16 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) break; case opt_worker: jl_options.worker = 1; - if (optarg != NULL) jl_options.cookie = strdup(optarg); + if (optarg != NULL) { + jl_options.cookie = strdup(optarg); + if (!jl_options.cookie) + jl_error("julia: failed to allocate memory"); + } break; case opt_bind_to: jl_options.bindto = strdup(optarg); + if (!jl_options.bindto) + jl_error("julia: failed to allocate memory"); break; case opt_handle_signals: if (!strcmp(optarg,"yes")) diff --git a/src/jltypes.c b/src/jltypes.c index 95b215913711e..320c12b969936 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -697,8 +697,13 @@ static int typekey_eq(jl_datatype_t *tt, jl_value_t **key, size_t n) } for(j=0; j < n; j++) { jl_value_t *kj = key[j], *tj = jl_svecref(tt->parameters,j); - if (tj != kj && !jl_types_equal(tj, kj)) - return 0; + if (tj != kj) { + // require exact same Type{T}. see e.g. issue #22842 + if (jl_is_type_type(tj) || jl_is_type_type(kj)) + return 0; + if (!jl_types_equal(tj, kj)) + return 0; + } } return 1; } diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 102ac4dbc4cac..ebe7c79eb3fe8 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -104,6 +104,8 @@ ; operators that are both unary and binary (define unary-and-binary-ops '(+ - $ & ~ |.+| |.-|)) +(define unary-and-binary-op? (Set unary-and-binary-ops)) + ; operators that are special forms, not function names (define syntactic-operators (append! (add-dots '(= += -= *= /= //= |\\=| ^= ÷= %= <<= >>= >>>= |\|=| &= ⊻=)) diff --git a/src/julia.h b/src/julia.h index 398d7eeeba9ca..30e2c26548c70 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1426,6 +1426,8 @@ JL_DLLEXPORT uint8_t jl_ast_flag_pure(jl_array_t *data); JL_DLLEXPORT void jl_fill_argnames(jl_array_t *data, jl_array_t *names); JL_DLLEXPORT int jl_is_operator(char *sym); +JL_DLLEXPORT int jl_is_unary_operator(char *sym); +JL_DLLEXPORT int jl_is_unary_and_binary_operator(char *sym); JL_DLLEXPORT int jl_operator_precedence(char *sym); STATIC_INLINE int jl_vinfo_sa(uint8_t vi) @@ -1677,9 +1679,7 @@ typedef struct { int8_t banner; const char *julia_home; const char *julia_bin; - const char *eval; - const char *print; - const char *load; + const char **cmds; const char *image_file; const char *cpu_target; int32_t nprocs; diff --git a/src/julia_internal.h b/src/julia_internal.h index 9af96ffb689a3..4a587616da6cf 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -485,6 +485,7 @@ jl_expr_t *jl_exprn(jl_sym_t *head, size_t n); jl_function_t *jl_new_generic_function(jl_sym_t *name, jl_module_t *module); jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *st, int iskw); int jl_is_submodule(jl_module_t *child, jl_module_t *parent); +jl_array_t *jl_get_loaded_modules(void); jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int expanded); diff --git a/src/llvm-alloc-opt.cpp b/src/llvm-alloc-opt.cpp index ca3189466160d..582dccab62e5d 100644 --- a/src/llvm-alloc-opt.cpp +++ b/src/llvm-alloc-opt.cpp @@ -62,6 +62,8 @@ static bool isBundleOperand(CallInst *call, unsigned idx) * * * load * * `pointer_from_objref` + * * Any real llvm intrinsics + * * gc preserve intrinsics * * `ccall` gcroot array (`jl_roots` operand bundle) * * store (as address) * * addrspacecast, bitcast, getelementptr @@ -88,6 +90,7 @@ struct AllocOpt : public FunctionPass { Function *ptr_from_objref; Function *lifetime_start; Function *lifetime_end; + Function *gc_preserve_begin; Type *T_int8; Type *T_int32; @@ -150,7 +153,8 @@ struct AllocOpt : public FunctionPass { bool checkInst(Instruction *I, CheckInstStack &stack, std::set &uses, bool &ignore_tag); void replaceUsesWith(Instruction *orig_i, Instruction *new_i, ReplaceUsesStack &stack); - void replaceIntrinsicUseWith(IntrinsicInst *call, Instruction *orig_i, Instruction *new_i); + void replaceIntrinsicUseWith(IntrinsicInst *call, Intrinsic::ID ID, Instruction *orig_i, + Instruction *new_i); bool isSafepoint(Instruction *inst); void getAnalysisUsage(AnalysisUsage &AU) const override { @@ -333,6 +337,7 @@ bool AllocOpt::doInitialization(Module &M) return false; ptr_from_objref = M.getFunction("julia.pointer_from_objref"); + gc_preserve_begin = M.getFunction("llvm.julia.gc_preserve_begin"); T_prjlvalue = alloc_obj->getReturnType(); T_pjlvalue = PointerType::get(cast(T_prjlvalue)->getElementType(), 0); @@ -384,9 +389,16 @@ bool AllocOpt::checkInst(Instruction *I, CheckInstStack &stack, std::set(inst)) { // TODO handle `memcmp` // None of the intrinsics should care if the memory is stack or heap allocated. - if (isa(call)) - return true; - if (ptr_from_objref && ptr_from_objref == call->getCalledFunction()) + auto callee = call->getCalledFunction(); + if (auto II = dyn_cast(call)) { + if (II->getIntrinsicID()) { + return true; + } + if (gc_preserve_begin && gc_preserve_begin == callee) { + return true; + } + } + if (ptr_from_objref && ptr_from_objref == callee) return true; auto opno = use->getOperandNo(); // Uses in `jl_roots` operand bundle are not counted as escaping, everything else is. @@ -445,11 +457,9 @@ bool AllocOpt::checkInst(Instruction *I, CheckInstStack &stack, std::setgetIntrinsicID(); - assert(ID); auto nargs = call->getNumArgOperands(); SmallVector args(nargs); SmallVector argTys(nargs); @@ -549,10 +559,12 @@ void AllocOpt::replaceUsesWith(Instruction *orig_inst, Instruction *new_inst, return; } if (auto intrinsic = dyn_cast(call)) { - replaceIntrinsicUseWith(intrinsic, orig_i, new_i); - return; + if (Intrinsic::ID ID = intrinsic->getIntrinsicID()) { + replaceIntrinsicUseWith(intrinsic, ID, orig_i, new_i); + return; + } } - // remove from operand bundle + // remove from operand bundle or arguments for gc_perserve_begin Type *new_t = new_i->getType(); user->replaceUsesOfWith(orig_i, ConstantPointerNull::get(cast(new_t))); } diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index aeaaff0bf24b1..4062562b13c6e 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -764,6 +764,8 @@ State LateLowerGCFrame::LocalScan(Function &F) { std::vector args; for (Use &U : CI->arg_operands()) { Value *V = U; + if (isa(V)) + continue; int Num = Number(S, V); if (Num >= 0) args.push_back(Num); diff --git a/src/precompile.c b/src/precompile.c index db4c2f57792f8..1a200df271f2b 100644 --- a/src/precompile.c +++ b/src/precompile.c @@ -286,10 +286,7 @@ static void compile_all_enq_(jl_methtable_t *mt, void *env) jl_typemap_visitor(mt->defs, compile_all_enq__, env); } -void jl_foreach_mtable_in_module( - jl_module_t *m, - void (*visit)(jl_methtable_t *mt, void *env), - void *env); +void jl_foreach_reachable_mtable(void (*visit)(jl_methtable_t *mt, void *env), void *env); static void jl_compile_all_defs(void) { @@ -298,7 +295,7 @@ static void jl_compile_all_defs(void) jl_array_t *m = jl_alloc_vec_any(0); JL_GC_PUSH1(&m); while (1) { - jl_foreach_mtable_in_module(jl_main_module, compile_all_enq_, m); + jl_foreach_reachable_mtable(compile_all_enq_, m); size_t changes = jl_array_len(m); if (!changes) break; @@ -334,7 +331,7 @@ static void jl_compile_specializations(void) // type signatures that were inferred but haven't been compiled jl_array_t *m = jl_alloc_vec_any(0); JL_GC_PUSH1(&m); - jl_foreach_mtable_in_module(jl_main_module, precompile_enq_all_specializations_, m); + jl_foreach_reachable_mtable(precompile_enq_all_specializations_, m); size_t i, l; for (i = 0, l = jl_array_len(m); i < l; i++) { jl_compile_hint((jl_tupletype_t*)jl_array_ptr_ref(m, i)); diff --git a/src/rtutils.c b/src/rtutils.c index 1d0e5417163b5..f5fa5bb2a6d58 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -558,6 +558,10 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt else if (vt == jl_simplevector_type) { n += jl_show_svec(out, (jl_svec_t*)v, "svec", "(", ")"); } + else if (v == (jl_value_t*)jl_unionall_type) { + // avoid printing `typeof(Type)` for `UnionAll`. + n += jl_printf(out, "UnionAll"); + } else if (vt == jl_datatype_type) { jl_datatype_t *dv = (jl_datatype_t*)v; jl_sym_t *globname = dv->name->mt != NULL ? dv->name->mt->name : NULL; @@ -1023,6 +1027,26 @@ void jl_depwarn(const char *msg, jl_value_t *sym) JL_GC_POP(); } +JL_DLLEXPORT void jl_depwarn_partial_indexing(size_t n) +{ + static jl_value_t *depwarn_func = NULL; + if (!depwarn_func && jl_base_module) { + depwarn_func = jl_get_global(jl_base_module, jl_symbol("_depwarn_for_trailing_indices")); + } + if (!depwarn_func) { + jl_safe_printf("WARNING: omitting indices for non-singleton trailing dimensions is deprecated. Use " + "`reshape(A, Val(%zd))` or add trailing `1` indices to make the dimensionality of the array match " + "the number of indices\n", n); + return; + } + jl_value_t **depwarn_args; + JL_GC_PUSHARGS(depwarn_args, 2); + depwarn_args[0] = depwarn_func; + depwarn_args[1] = jl_box_long(n); + jl_apply(depwarn_args, 2); + JL_GC_POP(); +} + #ifdef __cplusplus } #endif diff --git a/src/signal-handling.c b/src/signal-handling.c index d8d0bf553889d..ebaa18b12db36 100644 --- a/src/signal-handling.c +++ b/src/signal-handling.c @@ -229,7 +229,7 @@ void jl_critical_error(int sig, bt_context_t *context, uintptr_t *bt_data, size_ size_t i, n = *bt_size; if (sig) jl_safe_printf("\nsignal (%d): %s\n", sig, strsignal(sig)); - jl_safe_printf("while loading %s, in expression starting on line %d\n", jl_filename, jl_lineno); + jl_safe_printf("in expression starting at %s:%d\n", jl_filename, jl_lineno); if (context) *bt_size = n = rec_backtrace_ctx(bt_data, JL_MAX_BT_SIZE, context); for (i = 0; i < n; i++) diff --git a/src/sys.c b/src/sys.c index 1e74db72abf7c..128664fc45d5f 100644 --- a/src/sys.c +++ b/src/sys.c @@ -285,6 +285,7 @@ JL_DLLEXPORT jl_value_t *jl_readuntil(ios_t *s, uint8_t delim, uint8_t str, uint } int truncret = ios_trunc(&dest, n); // it should always be possible to truncate dest assert(truncret == 0); + (void)truncret; // ensure the variable is used to avoid warnings } if (dest.buf != a->data) { a = jl_take_buffer(&dest); diff --git a/src/toplevel.c b/src/toplevel.c index 9a56455b7aed8..6da9c0d92391b 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -120,6 +120,27 @@ static void jl_module_load_time_initialize(jl_module_t *m) } } +void jl_register_root_module(jl_value_t *key, jl_module_t *m) +{ + static jl_value_t *register_module_func=NULL; + if (register_module_func == NULL && jl_base_module != NULL) + register_module_func = jl_get_global(jl_base_module, jl_symbol("register_root_module")); + if (register_module_func != NULL) { + jl_value_t *rmargs[3] = {register_module_func, key, (jl_value_t*)m}; + jl_apply(rmargs, 3); + } +} + +jl_array_t *jl_get_loaded_modules(void) +{ + static jl_value_t *loaded_modules_array = NULL; + if (loaded_modules_array == NULL && jl_base_module != NULL) + loaded_modules_array = jl_get_global(jl_base_module, jl_symbol("loaded_modules_array")); + if (loaded_modules_array != NULL) + return (jl_array_t*)jl_call0((jl_function_t*)loaded_modules_array); + return NULL; +} + jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex) { jl_ptls_t ptls = jl_get_ptls_states(); @@ -140,32 +161,34 @@ jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex) if (!jl_is_symbol(name)) { jl_type_error("module", (jl_value_t*)jl_sym_type, (jl_value_t*)name); } - jl_binding_t *b = jl_get_binding_wr(parent_module, name, 1); - jl_declare_constant(b); - if (b->value != NULL) { - if (!jl_is_module(b->value)) { - jl_errorf("invalid redefinition of constant %s", - jl_symbol_name(name)); - } - if (jl_generating_output()) { - jl_errorf("cannot replace module %s during compilation", - jl_symbol_name(name)); + jl_module_t *newm = jl_new_module(name); + if (jl_base_module && + (jl_value_t*)parent_module == jl_get_global(jl_base_module, jl_symbol("__toplevel__"))) { + newm->parent = newm; + // TODO: pass through correct key somehow + jl_register_root_module((jl_value_t*)name, newm); + } + else { + jl_binding_t *b = jl_get_binding_wr(parent_module, name, 1); + jl_declare_constant(b); + if (b->value != NULL) { + if (!jl_is_module(b->value)) { + jl_errorf("invalid redefinition of constant %s", jl_symbol_name(name)); + } + if (jl_generating_output()) { + jl_errorf("cannot replace module %s during compilation", jl_symbol_name(name)); + } + jl_printf(JL_STDERR, "WARNING: replacing module %s.\n", jl_symbol_name(name)); } - jl_printf(JL_STDERR, "WARNING: replacing module %s\n", - jl_symbol_name(name)); + newm->parent = parent_module; + b->value = (jl_value_t*)newm; + jl_gc_wb_binding(b, newm); } - jl_module_t *newm = jl_new_module(name); - newm->parent = parent_module; - b->value = (jl_value_t*)newm; - jl_gc_wb_binding(b, newm); if (parent_module == jl_main_module && name == jl_symbol("Base")) { // pick up Base module during bootstrap jl_base_module = newm; } - // export all modules from Main - if (parent_module == jl_main_module) - jl_module_export(jl_main_module, name); // add standard imports unless baremodule if (std_imports) { @@ -339,99 +362,70 @@ static int jl_eval_expr_with_compiler_p(jl_value_t *e, int compileloops, jl_modu return 0; } -static jl_value_t *require_func=NULL; - -static jl_module_t *eval_import_path_(jl_module_t *from, jl_array_t *args, int retrying) +// either: +// - sets *name and returns the module to import *name from +// - sets *name to NULL and returns a module to import +static jl_module_t *eval_import_path(jl_module_t *from, jl_array_t *args, jl_sym_t **name, const char *keyword) { - // in .A.B.C, first find a binding for A in the chain of module scopes - // following parent links. then evaluate the rest of the path from there. - // in A.B, look for A in Main first. + static jl_value_t *require_func=NULL; jl_sym_t *var = (jl_sym_t*)jl_array_ptr_ref(args, 0); size_t i = 1; + jl_module_t *m = NULL; + *name = NULL; if (!jl_is_symbol(var)) - jl_type_error("import or using", (jl_value_t*)jl_sym_type, (jl_value_t*)var); + jl_type_error(keyword, (jl_value_t*)jl_sym_type, (jl_value_t*)var); - jl_module_t *m; if (var != dot_sym) { - m = jl_main_module; + // `A.B`: call the loader to obtain the root A in the current environment. + if (jl_core_module && var == jl_core_module->name) { + m = jl_core_module; + } + else if (jl_base_module && var == jl_base_module->name) { + m = jl_base_module; + } + else { + if (require_func == NULL && jl_base_module != NULL) + require_func = jl_get_global(jl_base_module, jl_symbol("require")); + if (require_func != NULL) { + jl_value_t *reqargs[2] = {require_func, (jl_value_t*)var}; + m = (jl_module_t*)jl_apply(reqargs, 2); + } + if (m == NULL || !jl_is_module(m)) { + jl_errorf("failed to load module %s", jl_symbol_name(var)); + } + } + if (i == jl_array_len(args)) + return m; } else { + // `.A.B.C`: strip off leading dots by following parent links m = from; while (1) { if (i >= jl_array_len(args)) jl_error("invalid module path"); var = (jl_sym_t*)jl_array_ptr_ref(args, i); - if (!jl_is_symbol(var)) - jl_type_error("import or using", (jl_value_t*)jl_sym_type, (jl_value_t*)var); + if (var != dot_sym) + break; i++; - if (var != dot_sym) { - if (i == jl_array_len(args)) - return m; - else - break; - } m = m->parent; } } while (1) { - if (jl_binding_resolved_p(m, var)) { - jl_binding_t *mb = jl_get_binding(m, var); - jl_module_t *m0 = m; - int isimp = jl_is_imported(m, var); - assert(mb != NULL); - if (mb->owner == m0 || isimp) { - m = (jl_module_t*)mb->value; - if ((mb->owner == m0 && m != NULL && !jl_is_module(m)) || - (isimp && (m == NULL || !jl_is_module(m)))) - jl_errorf("invalid module path (%s does not name a module)", - jl_symbol_name(var)); - // If the binding has been resolved but is (1) undefined, and (2) owned - // by the module we're importing into, then allow the import into the - // undefined variable (by setting m back to m0). - if (m == NULL) - m = m0; - else - break; - } - } - if (m == jl_main_module) { - if (!retrying && i==1) { // (i==1) => no require() for relative imports - if (require_func == NULL && jl_base_module != NULL) - require_func = jl_get_global(jl_base_module, jl_symbol("require")); - if (require_func != NULL) { - jl_value_t *reqargs[2] = {require_func, (jl_value_t*)var}; - jl_apply(reqargs, 2); - return eval_import_path_(from, args, 1); - } - } - } - if (retrying && require_func) { - jl_printf(JL_STDERR, "WARNING: requiring \"%s\" in module \"%s\" did not define a corresponding module.\n", - jl_symbol_name(var), - jl_symbol_name(from->name)); - return NULL; - } - else { - jl_errorf("in module path: %s not defined", jl_symbol_name(var)); - } - } - - for(; i < jl_array_len(args)-1; i++) { - jl_value_t *s = jl_array_ptr_ref(args,i); - assert(jl_is_symbol(s)); - m = (jl_module_t*)jl_eval_global_var(m, (jl_sym_t*)s); + var = (jl_sym_t*)jl_array_ptr_ref(args, i); + if (!jl_is_symbol(var)) + jl_type_error(keyword, (jl_value_t*)jl_sym_type, (jl_value_t*)var); + if (i == jl_array_len(args)-1) + break; + m = (jl_module_t*)jl_eval_global_var(m, var); if (!jl_is_module(m)) - jl_errorf("invalid import statement"); + jl_errorf("invalid %s path: \"%s\" does not name a module", keyword, jl_symbol_name(var)); + i++; } + *name = var; return m; } -static jl_module_t *eval_import_path(jl_module_t *from, jl_array_t *args) -{ - return eval_import_path_(from, args, 0); -} - jl_value_t *jl_toplevel_eval_body(jl_module_t *m, jl_array_t *stmts); int jl_is_toplevel_only_expr(jl_value_t *e) @@ -464,6 +458,19 @@ static jl_method_instance_t *jl_new_thunk(jl_code_info_t *src, jl_module_t *modu return li; } +static void import_module(jl_module_t *m, jl_module_t *import) +{ + jl_sym_t *name = import->name; + if (jl_binding_resolved_p(m, name)) { + jl_binding_t *b = jl_get_binding(m, name); + if (b->owner != m || (b->value && b->value != (jl_value_t*)import)) { + jl_errorf("importing %s into %s conflicts with an existing identifier", + jl_symbol_name(name), jl_symbol_name(m->name)); + } + } + jl_set_const(m, name, (jl_value_t*)import); +} + jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int expanded) { jl_ptls_t ptls = jl_get_ptls_states(); @@ -484,28 +491,29 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int e return jl_eval_module_expr(m, ex); } else if (ex->head == importall_sym) { - jl_module_t *import = eval_import_path(m, ex->args); - if (import == NULL) - return jl_nothing; - jl_sym_t *name = (jl_sym_t*)jl_array_ptr_ref(ex->args, jl_array_len(ex->args) - 1); - if (!jl_is_symbol(name)) - jl_error("syntax: malformed \"importall\" statement"); - import = (jl_module_t*)jl_eval_global_var(import, name); - if (!jl_is_module(import)) - jl_errorf("invalid %s statement: name exists but does not refer to a module", jl_symbol_name(ex->head)); + jl_sym_t *name = NULL; + jl_module_t *import = eval_import_path(m, ex->args, &name, "importall"); + if (name != NULL) { + import = (jl_module_t*)jl_eval_global_var(import, name); + if (!jl_is_module(import)) + jl_errorf("invalid %s statement: name exists but does not refer to a module", jl_symbol_name(ex->head)); + } jl_module_importall(m, import); return jl_nothing; } else if (ex->head == using_sym) { - jl_module_t *import = eval_import_path(m, ex->args); - if (import == NULL) - return jl_nothing; - jl_sym_t *name = (jl_sym_t*)jl_array_ptr_ref(ex->args, jl_array_len(ex->args) - 1); - if (!jl_is_symbol(name)) - jl_error("syntax: malformed \"using\" statement"); - jl_module_t *u = (jl_module_t*)jl_eval_global_var(import, name); + jl_sym_t *name = NULL; + jl_module_t *import = eval_import_path(m, ex->args, &name, "using"); + jl_module_t *u = import; + if (name != NULL) + u = (jl_module_t*)jl_eval_global_var(import, name); if (jl_is_module(u)) { jl_module_using(m, u); + if (m == jl_main_module && name == NULL) { + // TODO: for now, `using A` in Main also creates an explicit binding for `A` + // This will possibly be extended to all modules. + import_module(m, u); + } } else { jl_module_use(m, import, name); @@ -513,13 +521,14 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int e return jl_nothing; } else if (ex->head == import_sym) { - jl_module_t *import = eval_import_path(m, ex->args); - if (import == NULL) - return jl_nothing; - jl_sym_t *name = (jl_sym_t*)jl_array_ptr_ref(ex->args, jl_array_len(ex->args) - 1); - if (!jl_is_symbol(name)) - jl_error("syntax: malformed \"import\" statement"); - jl_module_import(m, import, name); + jl_sym_t *name = NULL; + jl_module_t *import = eval_import_path(m, ex->args, &name, "import"); + if (name == NULL) { + import_module(m, import); + } + else { + jl_module_import(m, import, name); + } return jl_nothing; } else if (ex->head == export_sym) { diff --git a/test/abstractarray.jl b/test/abstractarray.jl index a736681aac040..8ab7b413efc0d 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -15,11 +15,11 @@ A = rand(5,4,3) @test checkbounds(Bool, A, 61) == false @test checkbounds(Bool, A, 2, 2, 2, 1) == true # extra indices @test checkbounds(Bool, A, 2, 2, 2, 2) == false - @test checkbounds(Bool, A, 1, 1) == true # partial linear indexing (PLI) - @test checkbounds(Bool, A, 1, 12) == false - @test checkbounds(Bool, A, 5, 12) == false - @test checkbounds(Bool, A, 1, 13) == false - @test checkbounds(Bool, A, 6, 12) == false + # @test checkbounds(Bool, A, 1, 1) == false # TODO: partial linear indexing (PLI) + # @test checkbounds(Bool, A, 1, 12) == false + # @test checkbounds(Bool, A, 5, 12) == false + # @test checkbounds(Bool, A, 1, 13) == false + # @test checkbounds(Bool, A, 6, 12) == false end @testset "single CartesianIndex" begin @@ -31,16 +31,16 @@ end @test checkbounds(Bool, A, CartesianIndex((6, 4, 3))) == false @test checkbounds(Bool, A, CartesianIndex((5, 5, 3))) == false @test checkbounds(Bool, A, CartesianIndex((5, 4, 4))) == false - @test checkbounds(Bool, A, CartesianIndex((1,))) == true - @test checkbounds(Bool, A, CartesianIndex((60,))) == false - @test checkbounds(Bool, A, CartesianIndex((61,))) == false + # @test checkbounds(Bool, A, CartesianIndex((1,))) == false # TODO: PLI + # @test checkbounds(Bool, A, CartesianIndex((60,))) == false + # @test checkbounds(Bool, A, CartesianIndex((61,))) == false @test checkbounds(Bool, A, CartesianIndex((2, 2, 2, 1,))) == true @test checkbounds(Bool, A, CartesianIndex((2, 2, 2, 2,))) == false - @test checkbounds(Bool, A, CartesianIndex((1, 1,))) == true - @test checkbounds(Bool, A, CartesianIndex((1, 12,))) == false - @test checkbounds(Bool, A, CartesianIndex((5, 12,))) == false - @test checkbounds(Bool, A, CartesianIndex((1, 13,))) == false - @test checkbounds(Bool, A, CartesianIndex((6, 12,))) == false + # @test checkbounds(Bool, A, CartesianIndex((1, 1,))) == false # TODO: PLI + # @test checkbounds(Bool, A, CartesianIndex((1, 12,))) == false + # @test checkbounds(Bool, A, CartesianIndex((5, 12,))) == false + # @test checkbounds(Bool, A, CartesianIndex((1, 13,))) == false + # @test checkbounds(Bool, A, CartesianIndex((6, 12,))) == false end @testset "mix of CartesianIndex and Int" begin @@ -66,10 +66,10 @@ end @test checkbounds(Bool, A, 1:61) == false @test checkbounds(Bool, A, 2, 2, 2, 1:1) == true # extra indices @test checkbounds(Bool, A, 2, 2, 2, 1:2) == false - @test checkbounds(Bool, A, 1:5, 1:4) == true - @test checkbounds(Bool, A, 1:5, 1:12) == false - @test checkbounds(Bool, A, 1:5, 1:13) == false - @test checkbounds(Bool, A, 1:6, 1:12) == false + # @test checkbounds(Bool, A, 1:5, 1:4) == false # TODO: PLI + # @test checkbounds(Bool, A, 1:5, 1:12) == false + # @test checkbounds(Bool, A, 1:5, 1:13) == false + # @test checkbounds(Bool, A, 1:6, 1:12) == false end @testset "logical" begin @@ -81,9 +81,9 @@ end @test checkbounds(Bool, A, trues(61)) == false @test checkbounds(Bool, A, 2, 2, 2, trues(1)) == true # extra indices @test checkbounds(Bool, A, 2, 2, 2, trues(2)) == false - @test checkbounds(Bool, A, trues(5), trues(12)) == false - @test checkbounds(Bool, A, trues(5), trues(13)) == false - @test checkbounds(Bool, A, trues(6), trues(12)) == false + # @test checkbounds(Bool, A, trues(5), trues(12)) == false # TODO: PLI + # @test checkbounds(Bool, A, trues(5), trues(13)) == false + # @test checkbounds(Bool, A, trues(6), trues(12)) == false @test checkbounds(Bool, A, trues(5, 4, 3)) == true @test checkbounds(Bool, A, trues(5, 4, 2)) == false @test checkbounds(Bool, A, trues(5, 12)) == false @@ -258,6 +258,10 @@ function test_scalar_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where B = T(A) @test A == B # Test indexing up to 5 dimensions + trailing5 = CartesianIndex(ntuple(x->1, max(ndims(B)-5, 0))) + trailing4 = CartesianIndex(ntuple(x->1, max(ndims(B)-4, 0))) + trailing3 = CartesianIndex(ntuple(x->1, max(ndims(B)-3, 0))) + trailing2 = CartesianIndex(ntuple(x->1, max(ndims(B)-2, 0))) i=0 for i5 = 1:size(B, 5) for i4 = 1:size(B, 4) @@ -265,9 +269,9 @@ function test_scalar_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where for i2 = 1:size(B, 2) for i1 = 1:size(B, 1) i += 1 - @test A[i1,i2,i3,i4,i5] == B[i1,i2,i3,i4,i5] == i - @test A[i1,i2,i3,i4,i5] == - Base.unsafe_getindex(B, i1, i2, i3, i4, i5) == i + @test A[i1,i2,i3,i4,i5,trailing5] == B[i1,i2,i3,i4,i5,trailing5] == i + @test A[i1,i2,i3,i4,i5,trailing5] == + Base.unsafe_getindex(B, i1, i2, i3, i4, i5, trailing5) == i end end end @@ -283,7 +287,7 @@ function test_scalar_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where for i2 = 1:size(B, 2) for i1 = 1:size(B, 1) i += 1 - @test A[i1,i2] == B[i1,i2] == i + @test A[i1,i2,trailing2] == B[i1,i2,trailing2] == i end end @test A == B @@ -292,7 +296,7 @@ function test_scalar_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where for i2 = 1:size(B, 2) for i1 = 1:size(B, 1) i += 1 - @test A[i1,i2,i3] == B[i1,i2,i3] == i + @test A[i1,i2,i3,trailing3] == B[i1,i2,i3,trailing3] == i end end end @@ -310,20 +314,20 @@ function test_scalar_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where for i2 = 1:size(B, 2) for i1 = 1:size(B, 1) i += 1 - C[i1,i2,i3,i4,i5] = i + C[i1,i2,i3,i4,i5,trailing5] = i # test general unsafe_setindex! - Base.unsafe_setindex!(D1, i, i1,i2,i3,i4,i5) + Base.unsafe_setindex!(D1, i, i1,i2,i3,i4,i5,trailing5) # test for dropping trailing dims - Base.unsafe_setindex!(D2, i, i1,i2,i3,i4,i5, 1, 1, 1) + Base.unsafe_setindex!(D2, i, i1,i2,i3,i4,i5,trailing5, 1, 1, 1) # test for expanding index argument to appropriate dims - Base.unsafe_setindex!(D3, i, i1,i2,i3,i4) + Base.unsafe_setindex!(D3, i, i1,i2,i3,i4,trailing4) end end end end end @test D1 == D2 == C == B == A - @test D3[:, :, :, :, 1] == D2[:, :, :, :, 1] + @test D3[:, :, :, :, 1, trailing5] == D2[:, :, :, :, 1, trailing5] # Test linear indexing and partial linear indexing C = T(Int, shape) fill!(C, 0) @@ -340,7 +344,7 @@ function test_scalar_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where for i2 = 1:size(C2, 2) for i1 = 1:size(C2, 1) i += 1 - C2[i1,i2] = i + C2[i1,i2,trailing2] = i end end @test C == B == A @@ -351,7 +355,7 @@ function test_scalar_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where for i2 = 1:size(C3, 2) for i1 = 1:size(C3, 1) i += 1 - C3[i1,i2,i3] = i + C3[i1,i2,i3,trailing3] = i end end end @@ -367,13 +371,17 @@ function test_vector_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where N = prod(shape) A = reshape(collect(1:N), shape) B = T(A) + trailing5 = CartesianIndex(ntuple(x->1, max(ndims(B)-5, 0))) + trailing4 = CartesianIndex(ntuple(x->1, max(ndims(B)-4, 0))) + trailing3 = CartesianIndex(ntuple(x->1, max(ndims(B)-3, 0))) + trailing2 = CartesianIndex(ntuple(x->1, max(ndims(B)-2, 0))) idxs = rand(1:N, 3, 3, 3) @test B[idxs] == A[idxs] == idxs @test B[vec(idxs)] == A[vec(idxs)] == vec(idxs) @test B[:] == A[:] == collect(1:N) @test B[1:end] == A[1:end] == collect(1:N) - @test B[:,:] == A[:,:] == B[:,:,1] == A[:,:,1] - B[1:end,1:end] == A[1:end,1:end] == B[1:end,1:end,1] == A[1:end,1:end,1] + @test B[:,:,trailing2] == A[:,:,trailing2] == B[:,:,1,trailing3] == A[:,:,1,trailing3] + B[1:end,1:end,trailing2] == A[1:end,1:end,trailing2] == B[1:end,1:end,1,trailing3] == A[1:end,1:end,1,trailing3] @testset "Test with containers that aren't Int[]" begin @test B[[]] == A[[]] == [] @@ -383,14 +391,14 @@ function test_vector_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where idx1 = rand(1:size(A, 1), 3) idx2 = rand(1:size(A, 2), 4, 5) @testset "Test adding dimensions with matrices" begin - @test B[idx1, idx2] == A[idx1, idx2] == reshape(A[idx1, vec(idx2)], 3, 4, 5) == reshape(B[idx1, vec(idx2)], 3, 4, 5) - @test B[1, idx2] == A[1, idx2] == reshape(A[1, vec(idx2)], 4, 5) == reshape(B[1, vec(idx2)], 4, 5) + @test B[idx1, idx2, trailing2] == A[idx1, idx2, trailing2] == reshape(A[idx1, vec(idx2), trailing2], 3, 4, 5) == reshape(B[idx1, vec(idx2), trailing2], 3, 4, 5) + @test B[1, idx2, trailing2] == A[1, idx2, trailing2] == reshape(A[1, vec(idx2), trailing2], 4, 5) == reshape(B[1, vec(idx2), trailing2], 4, 5) end # test removing dimensions with 0-d arrays @testset "test removing dimensions with 0-d arrays" begin idx0 = reshape([rand(1:size(A, 1))]) - @test B[idx0, idx2] == A[idx0, idx2] == reshape(A[idx0[], vec(idx2)], 4, 5) == reshape(B[idx0[], vec(idx2)], 4, 5) - @test B[reshape([end]), reshape([end])] == A[reshape([end]), reshape([end])] == reshape([A[end,end]]) == reshape([B[end,end]]) + @test B[idx0, idx2, trailing2] == A[idx0, idx2, trailing2] == reshape(A[idx0[], vec(idx2), trailing2], 4, 5) == reshape(B[idx0[], vec(idx2), trailing2], 4, 5) + @test B[reshape([end]), reshape([end]), trailing2] == A[reshape([end]), reshape([end]), trailing2] == reshape([A[end,end,trailing2]]) == reshape([B[end,end,trailing2]]) end mask = bitrand(shape) @@ -399,8 +407,8 @@ function test_vector_indexing(::Type{T}, shape, ::Type{TestAbstractArray}) where @test B[vec(mask)] == A[vec(mask)] == find(mask) mask1 = bitrand(size(A, 1)) mask2 = bitrand(size(A, 2)) - @test B[mask1, mask2] == A[mask1, mask2] == B[find(mask1), find(mask2)] - @test B[mask1, 1] == A[mask1, 1] == find(mask1) + @test B[mask1, mask2, trailing2] == A[mask1, mask2, trailing2] == B[find(mask1), find(mask2), trailing2] + @test B[mask1, 1, trailing2] == A[mask1, 1, trailing2] == find(mask1) end end end diff --git a/test/arrayops.jl b/test/arrayops.jl index 52c1f591e3d12..3f8cb8a786888 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -2,7 +2,7 @@ # Array test isdefined(Main, :TestHelpers) || @eval Main include("TestHelpers.jl") -using TestHelpers.OAs +using Main.TestHelpers.OAs @testset "basics" begin @test length([1, 2, 3]) == 3 @@ -2231,3 +2231,9 @@ let a = Vector{Int}[[1]], @test eltype([a;b]) == Vector{Float64} @test eltype([a;c]) == Vector end + +# Issue #23629 +@testset "issue 23629" begin + @test_throws BoundsError zeros(2,3,0)[2,3] + @test_throws BoundsError checkbounds(zeros(2,3,0), 2, 3) +end diff --git a/test/choosetests.jl b/test/choosetests.jl index 56e42ab8a072c..b527dc8d5b216 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -2,7 +2,7 @@ @doc """ -`tests, net_on = choosetests(choices)` selects a set of tests to be +`tests, net_on, exit_on_error = choosetests(choices)` selects a set of tests to be run. `choices` should be a vector of test names; if empty or set to `["all"]`, all tests are selected. @@ -10,8 +10,14 @@ This function also supports "test collections": specifically, "linalg" refers to collections of tests in the correspondingly-named directories. -Upon return, `tests` is a vector of fully-expanded test names, and -`net_on` is true if networking is available (required for some tests). +Upon return, `tests` is a vector of fully-expanded test names, +`net_on` is true if networking is available (required for some tests), +and `exit_on_error` is true if an error in one test should cancel +remaining tests to be run (otherwise, all tests are run unconditionally). + +Two options can be passed to `choosetests` by including a special token +in the `choices` argument: "--skip", which makes all tests coming after +be skipped, and "--exit-on-error" which sets the value of `exit_on_error`. """ -> function choosetests(choices = []) testnames = [ @@ -51,11 +57,14 @@ function choosetests(choices = []) tests = [] skip_tests = [] + exit_on_error = false for (i, t) in enumerate(choices) if t == "--skip" skip_tests = choices[i + 1:end] break + elseif t == "--exit-on-error" + exit_on_error = true else push!(tests, t) end @@ -168,5 +177,5 @@ function choosetests(choices = []) filter!(x -> !(x in skip_tests), tests) - tests, net_on + tests, net_on, exit_on_error end diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index f46299f748864..7d4684d10a344 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -64,11 +64,29 @@ let exename = `$(Base.julia_cmd()) --sysimage-native-code=yes --startup-file=no` # --load let testfile = tempname() try - write(testfile, "testvar = :test\n") - @test split(readchomp(`$exename -i --load=$testfile -e "println(testvar)"`), - '\n')[end] == "test" - @test split(readchomp(`$exename -i -e "println(testvar)" -L $testfile`), - '\n')[end] == "test" + write(testfile, "testvar = :test\nprintln(\"loaded\")\n") + @test read(`$exename -i --load=$testfile -e "println(testvar)"`, String) == "loaded\ntest\n" + @test read(`$exename -i -L $testfile -e "println(testvar)"`, String) == "loaded\ntest\n" + # multiple, combined + @test read(```$exename + -e 'push!(ARGS, "hi")' + -E "1+1" + -E "2+2" + -L $testfile + -E '3+3' + -L $testfile + -E 'pop!(ARGS)' + -e 'show(ARGS); println()' + 9 10 + ```, String) == """ + 2 + 4 + loaded + 6 + loaded + "hi" + ["9", "10"] + """ finally rm(testfile) end diff --git a/test/codegen.jl b/test/codegen.jl index 35ac3b09e365e..b4069e3c77dbf 100644 --- a/test/codegen.jl +++ b/test/codegen.jl @@ -188,6 +188,13 @@ function two_breakpoint(a::Float64) ccall(:jl_breakpoint, Void, (Ref{Float64},), a) end +function load_dummy_ref(x::Int) + r = Ref{Int}(x) + Base.@gc_preserve r begin + unsafe_load(Ptr{Int}(pointer_from_objref(r))) + end +end + if opt_level > 0 breakpoint_f64_ir = get_llvm((a)->ccall(:jl_breakpoint, Void, (Ref{Float64},), a), Tuple{Float64}) @@ -198,6 +205,12 @@ if opt_level > 0 two_breakpoint_ir = get_llvm(two_breakpoint, Tuple{Float64}) @test !contains(two_breakpoint_ir, "jl_gc_pool_alloc") @test contains(two_breakpoint_ir, "llvm.lifetime.end") + + @test load_dummy_ref(1234) === 1234 + load_dummy_ref_ir = get_llvm(load_dummy_ref, Tuple{Int}) + @test !contains(load_dummy_ref_ir, "jl_gc_pool_alloc") + # Hopefully this is reliable enough. LLVM should be able to optimize this to a direct return. + @test contains(load_dummy_ref_ir, "ret $Iptr %0") end # Issue 22770 diff --git a/test/codevalidation.jl b/test/codevalidation.jl index 829df9759c473..b64657a8989b7 100644 --- a/test/codevalidation.jl +++ b/test/codevalidation.jl @@ -20,110 +20,123 @@ c0 = Core.Inference.retrieve_code_info(mi) @test isempty(Core.Inference.validate_code(mi)) @test isempty(Core.Inference.validate_code(c0)) -# INVALID_EXPR_HEAD -c = Core.Inference.copy_code_info(c0) -insert!(c.code, 4, Expr(:(=), SlotNumber(2), Expr(:invalid, 1))) -errors = Core.Inference.validate_code(c) -@test length(errors) == 1 -@test errors[1].kind === Core.Inference.INVALID_EXPR_HEAD - -# INVALID_LVALUE -c = Core.Inference.copy_code_info(c0) -insert!(c.code, 4, Expr(:(=), LabelNode(1), 1)) -insert!(c.code, 2, Expr(:(=), :x, 1)) -insert!(c.code, 10, Expr(:(=), 3, 1)) -errors = Core.Inference.validate_code(c) -@test length(errors) == 3 -@test all(e.kind === Core.Inference.INVALID_LVALUE for e in errors) - -# INVALID_RVALUE -c = Core.Inference.copy_code_info(c0) -insert!(c.code, 2, Expr(:(=), SlotNumber(2), GotoNode(1))) -insert!(c.code, 4, Expr(:(=), SlotNumber(2), LabelNode(2))) -insert!(c.code, 10, Expr(:(=), SlotNumber(2), LineNumberNode(2))) -for h in (:gotoifnot, :line, :const, :meta) - push!(c.code, Expr(:(=), SlotNumber(2), Expr(h))) +@testset "INVALID_EXPR_HEAD" begin + c = Core.Inference.copy_code_info(c0) + insert!(c.code, 4, Expr(:(=), SlotNumber(2), Expr(:invalid, 1))) + errors = Core.Inference.validate_code(c) + @test length(errors) == 1 + @test errors[1].kind === Core.Inference.INVALID_EXPR_HEAD end -errors = Core.Inference.validate_code(c) -@test length(errors) == 10 -@test count(e.kind === Core.Inference.INVALID_RVALUE for e in errors) == 7 -@test count(e.kind === Core.Inference.INVALID_EXPR_NARGS for e in errors) == 3 - -# INVALID_CALL_ARG/INVALID_EXPR_NARGS -c = Core.Inference.copy_code_info(c0) -insert!(c.code, 2, Expr(:(=), SlotNumber(2), Expr(:call, :+, SlotNumber(2), GotoNode(1)))) -insert!(c.code, 4, Expr(:call, :-, Expr(:call, :sin, LabelNode(2)), 3)) -insert!(c.code, 10, Expr(:call, LineNumberNode(2))) -for h in (:gotoifnot, :line, :const, :meta) - push!(c.code, Expr(:call, :f, Expr(h))) + +@testset "INVALID_LVALUE" begin + c = Core.Inference.copy_code_info(c0) + insert!(c.code, 4, Expr(:(=), LabelNode(1), 1)) + insert!(c.code, 2, Expr(:(=), :x, 1)) + insert!(c.code, 10, Expr(:(=), 3, 1)) + errors = Core.Inference.validate_code(c) + @test length(errors) == 3 + @test all(e.kind === Core.Inference.INVALID_LVALUE for e in errors) +end + +@testset "INVALID_RVALUE" begin + c = Core.Inference.copy_code_info(c0) + insert!(c.code, 2, Expr(:(=), SlotNumber(2), GotoNode(1))) + insert!(c.code, 4, Expr(:(=), SlotNumber(2), LabelNode(2))) + insert!(c.code, 10, Expr(:(=), SlotNumber(2), LineNumberNode(2))) + for h in (:gotoifnot, :line, :const, :meta) + push!(c.code, Expr(:(=), SlotNumber(2), Expr(h))) + end + errors = Core.Inference.validate_code(c) + @test length(errors) == 10 + @test count(e.kind === Core.Inference.INVALID_RVALUE for e in errors) == 7 + @test count(e.kind === Core.Inference.INVALID_EXPR_NARGS for e in errors) == 3 +end + +@testset "INVALID_CALL_ARG/INVALID_EXPR_NARGS" begin + c = Core.Inference.copy_code_info(c0) + insert!(c.code, 2, Expr(:(=), SlotNumber(2), Expr(:call, :+, SlotNumber(2), GotoNode(1)))) + insert!(c.code, 4, Expr(:call, :-, Expr(:call, :sin, LabelNode(2)), 3)) + insert!(c.code, 10, Expr(:call, LineNumberNode(2))) + for h in (:gotoifnot, :line, :const, :meta) + push!(c.code, Expr(:call, :f, Expr(h))) + end + errors = Core.Inference.validate_code(c) + @test length(errors) == 10 + @test count(e.kind === Core.Inference.INVALID_CALL_ARG for e in errors) == 7 + @test count(e.kind === Core.Inference.INVALID_EXPR_NARGS for e in errors) == 3 +end + +@testset "EMPTY_SLOTNAMES" begin + c = Core.Inference.copy_code_info(c0) + empty!(c.slotnames) + errors = Core.Inference.validate_code(c) + @test length(errors) == 2 + @test any(e.kind === Core.Inference.EMPTY_SLOTNAMES for e in errors) + @test any(e.kind === Core.Inference.SLOTFLAGS_MISMATCH for e in errors) +end + +@testset "SLOTFLAGS_MISMATCH" begin + c = Core.Inference.copy_code_info(c0) + push!(c.slotnames, :dummy) + errors = Core.Inference.validate_code(c) + @test length(errors) == 1 + @test errors[1].kind === Core.Inference.SLOTFLAGS_MISMATCH +end + +@testset "SLOTTYPES_MISMATCH" begin + c = @code_typed(f22938(1,2,3,4))[1] + pop!(c.slottypes) + errors = Core.Inference.validate_code(c) + @test length(errors) == 1 + @test errors[1].kind === Core.Inference.SLOTTYPES_MISMATCH +end + +@testset "SLOTTYPES_MISMATCH_UNINFERRED" begin + c = Core.Inference.copy_code_info(c0) + c.slottypes = 1 + errors = Core.Inference.validate_code(c) + @test length(errors) == 1 + @test errors[1].kind === Core.Inference.SLOTTYPES_MISMATCH_UNINFERRED +end + +@testset "SSAVALUETYPES_MISMATCH" begin + c = @code_typed(f22938(1,2,3,4))[1] + empty!(c.ssavaluetypes) + errors = Core.Inference.validate_code(c) + @test length(errors) == 1 + @test errors[1].kind === Core.Inference.SSAVALUETYPES_MISMATCH +end + +@testset "SSAVALUETYPES_MISMATCH_UNINFERRED" begin + c = Core.Inference.copy_code_info(c0) + c.ssavaluetypes -= 1 + errors = Core.Inference.validate_code(c) + @test length(errors) == 1 + @test errors[1].kind === Core.Inference.SSAVALUETYPES_MISMATCH_UNINFERRED +end + +@testset "SIGNATURE_NARGS_MISMATCH" begin + old_sig = mi.def.sig + mi.def.sig = Tuple{1,2} + errors = Core.Inference.validate_code(mi) + mi.def.sig = old_sig + @test length(errors) == 1 + @test errors[1].kind === Core.Inference.SIGNATURE_NARGS_MISMATCH +end + +@testset "NON_TOP_LEVEL_METHOD" begin + c = Core.Inference.copy_code_info(c0) + push!(c.code, Expr(:method, :dummy)) + errors = Core.Inference.validate_code(c) + @test length(errors) == 1 + @test errors[1].kind === Core.Inference.NON_TOP_LEVEL_METHOD +end + +@testset "SLOTNAMES_NARGS_MISMATCH" begin + mi.def.nargs += 20 + errors = Core.Inference.validate_code(mi) + mi.def.nargs -= 20 + @test length(errors) == 2 + @test count(e.kind === Core.Inference.SLOTNAMES_NARGS_MISMATCH for e in errors) == 1 + @test count(e.kind === Core.Inference.SIGNATURE_NARGS_MISMATCH for e in errors) == 1 end -errors = Core.Inference.validate_code(c) -@test length(errors) == 10 -@test count(e.kind === Core.Inference.INVALID_CALL_ARG for e in errors) == 7 -@test count(e.kind === Core.Inference.INVALID_EXPR_NARGS for e in errors) == 3 - -# EMPTY_SLOTNAMES -c = Core.Inference.copy_code_info(c0) -empty!(c.slotnames) -errors = Core.Inference.validate_code(c) -@test length(errors) == 2 -@test any(e.kind === Core.Inference.EMPTY_SLOTNAMES for e in errors) -@test any(e.kind === Core.Inference.SLOTFLAGS_MISMATCH for e in errors) - -# SLOTFLAGS_MISMATCH -c = Core.Inference.copy_code_info(c0) -push!(c.slotnames, :dummy) -errors = Core.Inference.validate_code(c) -@test length(errors) == 1 -@test errors[1].kind === Core.Inference.SLOTFLAGS_MISMATCH - -# SLOTTYPES_MISMATCH -c = @code_typed(f22938(1,2,3,4))[1] -pop!(c.slottypes) -errors = Core.Inference.validate_code(c) -@test length(errors) == 1 -@test errors[1].kind === Core.Inference.SLOTTYPES_MISMATCH - -# SLOTTYPES_MISMATCH_UNINFERRED -c = Core.Inference.copy_code_info(c0) -c.slottypes = 1 -errors = Core.Inference.validate_code(c) -@test length(errors) == 1 -@test errors[1].kind === Core.Inference.SLOTTYPES_MISMATCH_UNINFERRED - -# SSAVALUETYPES_MISMATCH -c = @code_typed(f22938(1,2,3,4))[1] -empty!(c.ssavaluetypes) -errors = Core.Inference.validate_code(c) -@test length(errors) == 1 -@test errors[1].kind === Core.Inference.SSAVALUETYPES_MISMATCH - -# SSAVALUETYPES_MISMATCH_UNINFERRED -c = Core.Inference.copy_code_info(c0) -c.ssavaluetypes -= 1 -errors = Core.Inference.validate_code(c) -@test length(errors) == 1 -@test errors[1].kind === Core.Inference.SSAVALUETYPES_MISMATCH_UNINFERRED - -# SIGNATURE_NARGS_MISMATCH -old_sig = mi.def.sig -mi.def.sig = Tuple{1,2} -errors = Core.Inference.validate_code(mi) -mi.def.sig = old_sig -@test length(errors) == 1 -@test errors[1].kind === Core.Inference.SIGNATURE_NARGS_MISMATCH - -# NON_TOP_LEVEL_METHOD -c = Core.Inference.copy_code_info(c0) -push!(c.code, Expr(:method, :dummy)) -errors = Core.Inference.validate_code(c) -@test length(errors) == 1 -@test errors[1].kind === Core.Inference.NON_TOP_LEVEL_METHOD - -# SLOTNAMES_NARGS_MISMATCH -mi.def.nargs += 20 -errors = Core.Inference.validate_code(mi) -mi.def.nargs -= 20 -@test length(errors) == 2 -@test count(e.kind === Core.Inference.SLOTNAMES_NARGS_MISMATCH for e in errors) == 1 -@test count(e.kind === Core.Inference.SIGNATURE_NARGS_MISMATCH for e in errors) == 1 diff --git a/test/compile.jl b/test/compile.jl index 18aef37377dbc..075a33b82aed6 100644 --- a/test/compile.jl +++ b/test/compile.jl @@ -2,6 +2,8 @@ using Base.Test +import Base: root_module + Foo_module = :Foo4b3a94a1a081a8cb Foo2_module = :F2oo4b3a94a1a081a8cb FooBase_module = :FooBase4b3a94a1a081a8cb @@ -149,7 +151,7 @@ try # Issue #21307 Base.require(Foo2_module) @eval let Foo2_module = $(QuoteNode(Foo2_module)), # use @eval to see the results of loading the compile - Foo = getfield(Main, Foo2_module) + Foo = root_module(Foo2_module) Foo.override(::Int) = 'a' Foo.override(::Float32) = 'b' end @@ -157,7 +159,7 @@ try Base.require(Foo_module) @eval let Foo_module = $(QuoteNode(Foo_module)), # use @eval to see the results of loading the compile - Foo = getfield(Main, Foo_module) + Foo = root_module(Foo_module) @test Foo.foo(17) == 18 @test Foo.Bar.bar(17) == 19 @@ -172,16 +174,18 @@ try # use _require_from_serialized to ensure that the test fails if # the module doesn't reload from the image: @test_warn "WARNING: replacing module $Foo_module." begin - @test isa(Base._require_from_serialized(Foo_module, cachefile), Array{Any,1}) + ms = Base._require_from_serialized(Foo_module, cachefile) + @test isa(ms, Array{Any,1}) + Base.register_all(ms) end - let Foo = getfield(Main, Foo_module) + let Foo = root_module(Foo_module) @test_throws MethodError Foo.foo(17) # world shouldn't be visible yet end @eval let Foo_module = $(QuoteNode(Foo_module)), # use @eval to see the results of loading the compile Foo2_module = $(QuoteNode(Foo2_module)), FooBase_module = $(QuoteNode(FooBase_module)), - Foo = getfield(Main, Foo_module), + Foo = root_module(Foo_module), dir = $(QuoteNode(dir)), cachefile = $(QuoteNode(cachefile)), Foo_file = $(QuoteNode(Foo_file)) @@ -291,7 +295,7 @@ try @test !Base.stale_cachefile(relFooBar_file, joinpath(dir, "FooBar.ji")) @eval using FooBar - fb_uuid = Base.module_uuid(Main.FooBar) + fb_uuid = Base.module_uuid(FooBar) sleep(2); touch(FooBar_file) insert!(Base.LOAD_CACHE_PATH, 1, dir2) @test Base.stale_cachefile(FooBar_file, joinpath(dir, "FooBar.ji")) @@ -301,22 +305,22 @@ try @test isfile(joinpath(dir2, "FooBar1.ji")) @test Base.stale_cachefile(FooBar_file, joinpath(dir, "FooBar.ji")) @test !Base.stale_cachefile(FooBar1_file, joinpath(dir2, "FooBar1.ji")) - @test fb_uuid == Base.module_uuid(Main.FooBar) - fb_uuid1 = Base.module_uuid(Main.FooBar1) + @test fb_uuid == Base.module_uuid(FooBar) + fb_uuid1 = Base.module_uuid(FooBar1) @test fb_uuid != fb_uuid1 - @test_warn "WARNING: replacing module FooBar." reload("FooBar") - @test fb_uuid != Base.module_uuid(Main.FooBar) - @test fb_uuid1 == Base.module_uuid(Main.FooBar1) - fb_uuid = Base.module_uuid(Main.FooBar) + reload("FooBar") + @test fb_uuid != Base.module_uuid(root_module(:FooBar)) + @test fb_uuid1 == Base.module_uuid(FooBar1) + fb_uuid = Base.module_uuid(root_module(:FooBar)) @test isfile(joinpath(dir2, "FooBar.ji")) @test Base.stale_cachefile(FooBar_file, joinpath(dir, "FooBar.ji")) @test !Base.stale_cachefile(FooBar1_file, joinpath(dir2, "FooBar1.ji")) @test !Base.stale_cachefile(FooBar_file, joinpath(dir2, "FooBar.ji")) - @test_warn "WARNING: replacing module FooBar1." reload("FooBar1") - @test fb_uuid == Base.module_uuid(Main.FooBar) - @test fb_uuid1 != Base.module_uuid(Main.FooBar1) + reload("FooBar1") + @test fb_uuid == Base.module_uuid(root_module(:FooBar)) + @test fb_uuid1 != Base.module_uuid(root_module(:FooBar1)) @test isfile(joinpath(dir2, "FooBar.ji")) @test isfile(joinpath(dir2, "FooBar1.ji")) @@ -331,15 +335,16 @@ try @test Base.stale_cachefile(FooBar1_file, joinpath(dir2, "FooBar1.ji")) # test behavior of precompile modules that throw errors - write(FooBar_file, + FooBar2_file = joinpath(dir, "FooBar2.jl") + write(FooBar2_file, """ __precompile__(true) - module FooBar + module FooBar2 error("break me") end """) @test_warn "ERROR: LoadError: break me\nStacktrace:\n [1] error" try - Base.require(:FooBar) + Base.require(:FooBar2) error("\"LoadError: break me\" test failed") catch exc isa(exc, ErrorException) || rethrow(exc) @@ -379,7 +384,7 @@ try """) rm(FooBarT_file) @test Base.stale_cachefile(FooBarT2_file, joinpath(dir2, "FooBarT2.ji")) - @test Base.require(:FooBarT2) === nothing + @test Base.require(:FooBarT2) isa Module finally splice!(Base.LOAD_CACHE_PATH, 1:2) splice!(LOAD_PATH, 1) @@ -513,7 +518,6 @@ let dir = mktempdir() Base.compilecache("$(Test2_module)") @test !Base.isbindingresolved(Main, Test2_module) Base.require(Test2_module) - @test Base.isbindingresolved(Main, Test2_module) @test take!(loaded_modules) == Test1_module @test take!(loaded_modules) == Test2_module write(joinpath(dir, "$(Test3_module).jl"), @@ -541,7 +545,7 @@ let module_name = string("a",randstring()) code = """module $(module_name)\nend\n""" write(file_name, code) reload(module_name) - @test isa(getfield(Main, Symbol(module_name)), Module) + @test isa(root_module(Symbol(module_name)), Module) @test shift!(LOAD_PATH) == path rm(file_name) end @@ -586,9 +590,9 @@ let end try @eval using $ModuleB - uuid = Base.module_uuid(getfield(Main, ModuleB)) + uuid = Base.module_uuid(root_module(ModuleB)) for wid in test_workers - @test Base.Distributed.remotecall_eval(Main, wid, :( Base.module_uuid($ModuleB) )) == uuid + @test Base.Distributed.remotecall_eval(Main, wid, :( Base.module_uuid(Base.root_module($(QuoteNode(ModuleB)))) )) == uuid if wid != myid() # avoid world-age errors on the local proc @test remotecall_fetch(g, wid) == wid end diff --git a/test/core.jl b/test/core.jl index 91554772086f7..6848237d8e07e 100644 --- a/test/core.jl +++ b/test/core.jl @@ -911,6 +911,12 @@ let @test_throws MethodError foor(StridedArray) end +# issue #22842 +f22842(x::UnionAll) = UnionAll +f22842(x::DataType) = length(x.parameters) +@test f22842(Tuple{Vararg{Int64,N} where N}) == 1 +@test f22842(Tuple{Vararg{Int64,N}} where N) === UnionAll + # issue #1153 mutable struct SI{m, s, kg} value::AbstractFloat diff --git a/test/dict.jl b/test/dict.jl index 45245f457185b..8c0f22e383592 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -274,7 +274,7 @@ end Base.show(io, MIME("text/plain"), d) out = split(String(take!(s)),'\n') for line in out[2:end] - @test strwidth(line) <= cols + @test textwidth(line) <= cols end @test length(out) <= rows @@ -284,7 +284,7 @@ end Base.show(io, MIME("text/plain"), f(d)) out = split(String(take!(s)),'\n') for line in out[2:end] - @test strwidth(line) <= cols + @test textwidth(line) <= cols end @test length(out) <= rows end diff --git a/test/examples.jl b/test/examples.jl index 2c0a1791db864..ebc6cdcdd0531 100644 --- a/test/examples.jl +++ b/test/examples.jl @@ -75,17 +75,4 @@ put!(dc, "Hello", "World") # At least make sure code loads include(joinpath(dir, "wordcount.jl")) -# the 0mq clustermanager depends on package ZMQ. Just making sure the -# code loads using a stub module definition for ZMQ. -zmq_found = true -try - using ZMQ -catch - global zmq_found = false -end - -if !zmq_found - eval(Main, parse("module ZMQ end")) -end - include(joinpath(dir, "clustermanager/0mq/ZMQCM.jl")) diff --git a/test/hashing.jl b/test/hashing.jl index dd52afbb609f1..e2f277ab5b61c 100644 --- a/test/hashing.jl +++ b/test/hashing.jl @@ -123,3 +123,19 @@ end # issue #20744 @test hash(:c, hash(:b, hash(:a))) != hash(:a, hash(:b, hash(:c))) + +# issue #5849, object_id of types +@test Vector === (Array{T,1} where T) +@test (Pair{A,B} where A where B) !== (Pair{A,B} where B where A) +let vals_expr = :(Any[Vector, (Array{T,1} where T), 1, 2, Union{Int, String}, Union{String, Int}, + (Union{String, T} where T), Ref{Ref{T} where T}, (Ref{Ref{T}} where T), + (Vector{T} where T<:Real), (Vector{T} where T<:Integer), + (Vector{T} where T>:Integer), + (Pair{A,B} where A where B), (Pair{A,B} where B where A)]) + vals_a = eval(vals_expr) + vals_b = eval(vals_expr) + for (i, a) in enumerate(vals_a), (j, b) in enumerate(vals_b) + @test i != j || (a === b) + @test (a === b) == (object_id(a) == object_id(b)) + end +end diff --git a/test/inference.jl b/test/inference.jl index daf205518b37c..6d8e5cf78ac20 100644 --- a/test/inference.jl +++ b/test/inference.jl @@ -1218,3 +1218,10 @@ let c(::Type{T}, x) where {T<:Array} = T, f() = c(Vector{Any[Int][1]}, [1]) @test f() === Vector{Int} end + +# issue #23786 +struct T23786{D<:Tuple{Vararg{Vector{T} where T}}, N} +end +let t = Tuple{Type{T23786{D, N} where N where D<:Tuple{Vararg{Array{T, 1} where T, N} where N}}} + @test Core.Inference.limit_type_depth(t, 4) >: t +end diff --git a/test/int.jl b/test/int.jl index 29ee29c7f00b1..f8e8432c7ac2d 100644 --- a/test/int.jl +++ b/test/int.jl @@ -229,3 +229,25 @@ end @test x[end] == 1180591620717411303424 @test eltype(x) == BigInt end + +# issue #9292 +@testset "mixed signedness arithmetic" begin + for T in Base.BitInteger_types + for S in Base.BitInteger_types + a, b = one(T), one(S) + for c in (a+b, a-b, a*b) + if T === S + @test c isa T + elseif sizeof(T) > sizeof(S) + # larger type wins + @test c isa T + elseif sizeof(S) > sizeof(T) + @test c isa S + else + # otherwise Unsigned wins + @test c isa (T <: Unsigned ? T : S) + end + end + end + end +end diff --git a/test/libgit2-online.jl b/test/libgit2-online.jl index 5424ef31bd9dc..39c31a5b1946d 100644 --- a/test/libgit2-online.jl +++ b/test/libgit2-online.jl @@ -43,13 +43,7 @@ mktempdir() do dir error("unexpected") catch ex @test isa(ex, LibGit2.Error.GitError) - if Sys.iswindows() && LibGit2.version() >= v"0.26.0" - # see #22681 and https://github.com/libgit2/libgit2/pull/4055 - @test_broken ex.code == LibGit2.Error.EAUTH - @test ex.code == LibGit2.Error.ERROR - else - @test ex.code == LibGit2.Error.EAUTH - end + @test ex.code == LibGit2.Error.EAUTH end end end diff --git a/test/libgit2.jl b/test/libgit2.jl index ac290a189d516..c353e2f74f3e4 100644 --- a/test/libgit2.jl +++ b/test/libgit2.jl @@ -1,12 +1,13 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license isdefined(Main, :TestHelpers) || @eval Main include(joinpath(@__DIR__, "TestHelpers.jl")) -import TestHelpers: challenge_prompt +import Main.TestHelpers: challenge_prompt const LIBGIT2_MIN_VER = v"0.23.0" const LIBGIT2_HELPER_PATH = joinpath(@__DIR__, "libgit2-helpers.jl") const KEY_DIR = joinpath(@__DIR__, "libgit2") +const HOME = Sys.iswindows() ? "USERPROFILE" : "HOME" # Environment variable name for home function get_global_dir() buf = Ref(LibGit2.Buffer()) @@ -1643,7 +1644,7 @@ mktempdir() do dir @test !haskey(cache, cred_id) - # Reject a credential which wasn't stored + # Attempt to reject a credential which wasn't stored LibGit2.reject(cache, cred, url) @test !haskey(cache, cred_id) @test cred.user == "julia" @@ -1673,14 +1674,18 @@ mktempdir() do dir LibGit2.Error.Callback, LibGit2.Error.EUSER, "Aborting, user cancelled credential request.") + prompt_limit = LibGit2.GitError( + LibGit2.Error.Callback, LibGit2.Error.EAUTH, + "Aborting, maximum number of prompts reached.") + incompatible_error = LibGit2.GitError( LibGit2.Error.Callback, LibGit2.Error.EAUTH, "The explicitly provided credential is incompatible with the requested " * "authentication methods.") - eauth_error = LibGit2.GitError( - LibGit2.Error.None, LibGit2.Error.EAUTH, - "No errors") + exhausted_error = LibGit2.GitError( + LibGit2.Error.Callback, LibGit2.Error.EAUTH, + "All authentication methods have failed.") @testset "SSH credential prompt" begin url = "git@github.com:test/package.jl" @@ -1712,14 +1717,14 @@ mktempdir() do dir # sure a users will actually have these files. Instead we will use the ENV # variables to set the default values. - # Default credentials are valid + # ENV credentials are valid withenv("SSH_KEY_PATH" => valid_key) do err, auth_attempts = challenge_prompt(ssh_ex, []) @test err == git_ok @test auth_attempts == 1 end - # Default credentials are valid but requires a passphrase + # ENV credentials are valid but requires a passphrase withenv("SSH_KEY_PATH" => valid_p_key) do challenges = [ "Passphrase for $valid_p_key:" => "$passphrase\n", @@ -1734,7 +1739,7 @@ mktempdir() do dir # could also just re-call the credential callback like they do for HTTP. challenges = [ "Passphrase for $valid_p_key:" => "foo\n", - # "Private key location for 'git@github.com' [$valid_p_key]:" => "\n", + "Private key location for 'git@github.com' [$valid_p_key]:" => "\n", "Passphrase for $valid_p_key:" => "$passphrase\n", ] err, auth_attempts = challenge_prompt(ssh_p_ex, challenges) @@ -1758,6 +1763,7 @@ mktempdir() do dir @test auth_attempts == 1 end + # ENV credential requiring passphrase withenv("SSH_KEY_PATH" => valid_p_key, "SSH_KEY_PASS" => passphrase) do err, auth_attempts = challenge_prompt(ssh_p_ex, []) @test err == git_ok @@ -1795,6 +1801,7 @@ mktempdir() do dir challenges = [ "Username for 'github.com':" => "foo\n", "Username for 'github.com' [foo]:" => "\n", + "Private key location for 'foo@github.com' [$valid_key]:" => "\n", "Username for 'github.com' [foo]:" => "\x04", # Need to manually abort ] err, auth_attempts = challenge_prompt(ssh_u_ex, challenges) @@ -1802,7 +1809,7 @@ mktempdir() do dir @test auth_attempts == 3 # Credential callback is given an empty string in the `username_ptr` - # instead of the typical C_NULL. + # instead of the C_NULL in the other missing username tests. ssh_user_empty_ex = gen_ex(valid_cred, username="") challenges = [ "Username for 'github.com':" => "$username\n", @@ -1817,7 +1824,7 @@ mktempdir() do dir withenv("SSH_KEY_PATH" => nothing, "SSH_PUB_KEY_PATH" => nothing, "SSH_KEY_PASS" => nothing, - (Sys.iswindows() ? "USERPROFILE" : "HOME") => tempdir()) do + HOME => dir) do # Set the USERPROFILE / HOME above to be a directory that does not contain # the "~/.ssh/id_rsa" file. If this file exists the credential callback @@ -1857,22 +1864,40 @@ mktempdir() do dir err, auth_attempts = challenge_prompt(ssh_ex, challenges) @test err == abort_prompt @test auth_attempts == 2 + + # User provides an invalid private key until prompt limit reached. + # Note: the prompt should not supply an invalid default. + challenges = [ + "Private key location for 'git@github.com':" => "foo\n", + "Private key location for 'git@github.com' [foo]:" => "foo\n", + "Private key location for 'git@github.com' [foo]:" => "foo\n", + ] + err, auth_attempts = challenge_prompt(ssh_ex, challenges) + @test err == prompt_limit + @test auth_attempts == 3 end - # TODO: Tests are currently broken. Credential callback currently infinite loops - # and never prompts user to change private keys. - #= # Explicitly setting these env variables to an existing but invalid key pair # means the user will be given a prompt with that defaults to the given values. - withenv("SSH_KEY_PATH" => invalid_key, "SSH_PUB_KEY_PATH" => invalid_key * ".pub") do + withenv("SSH_KEY_PATH" => invalid_key, + "SSH_PUB_KEY_PATH" => invalid_key * ".pub") do challenges = [ "Private key location for 'git@github.com' [$invalid_key]:" => "$valid_key\n", ] err, auth_attempts = challenge_prompt(ssh_ex, challenges) @test err == git_ok @test auth_attempts == 2 + + # User repeatedly chooses the default invalid private key until prompt limit reached + challenges = [ + "Private key location for 'git@github.com' [$invalid_key]:" => "\n", + "Private key location for 'git@github.com' [$invalid_key]:" => "\n", + "Private key location for 'git@github.com' [$invalid_key]:" => "\n", + ] + err, auth_attempts = challenge_prompt(ssh_ex, challenges) + @test err == prompt_limit + @test auth_attempts == 4 end - =# # Explicitly set the public key ENV variable to a non-existent file. withenv("SSH_KEY_PATH" => valid_key, @@ -1888,9 +1913,6 @@ mktempdir() do dir @test auth_attempts == 1 end - # TODO: Tests are currently broken. Credential callback currently infinite loops - # and never prompts user to change private keys. - #= # Explicitly set the public key ENV variable to a public key that doesn't match # the private key. withenv("SSH_KEY_PATH" => valid_key, @@ -1905,7 +1927,6 @@ mktempdir() do dir @test err == git_ok @test auth_attempts == 2 end - =# end @testset "HTTPS credential prompt" begin @@ -1961,12 +1982,14 @@ mktempdir() do dir challenges = [ "Username for 'https://github.com':" => "foo\n", "Password for 'https://foo@github.com':" => "bar\n", - "Username for 'https://github.com' [foo]:" => "$valid_username\n", - "Password for 'https://$valid_username@github.com':" => "$valid_password\n", + "Username for 'https://github.com' [foo]:" => "foo\n", + "Password for 'https://foo@github.com':" => "bar\n", + "Username for 'https://github.com' [foo]:" => "foo\n", + "Password for 'https://foo@github.com':" => "bar\n", ] err, auth_attempts = challenge_prompt(https_ex, challenges) - @test err == git_ok - @test auth_attempts == 2 + @test err == prompt_limit + @test auth_attempts == 3 end @testset "SSH agent username" begin @@ -1986,17 +2009,123 @@ mktempdir() do dir # An empty string username_ptr ex = gen_ex(username="") err, auth_attempts = challenge_prompt(ex, []) - @test err == eauth_error - @test auth_attempts == 2 + @test err == exhausted_error + @test auth_attempts == 3 # A null username_ptr passed into `git_cred_ssh_key_from_agent` can cause a # segfault. ex = gen_ex(username=nothing) err, auth_attempts = challenge_prompt(ex, []) - @test err == eauth_error + @test err == exhausted_error @test auth_attempts == 2 end + @testset "SSH default" begin + mktempdir() do home_dir + url = "github.com:test/package.jl" + + default_key = joinpath(home_dir, ".ssh", "id_rsa") + mkdir(dirname(default_key)) + + valid_key = joinpath(KEY_DIR, "valid") + valid_cred = LibGit2.SSHCredentials("git", "", valid_key, valid_key * ".pub") + + valid_p_key = joinpath(KEY_DIR, "valid-passphrase") + passphrase = "secret" + valid_p_cred = LibGit2.SSHCredentials("git", passphrase, valid_p_key, valid_p_key * ".pub") + + function gen_ex(cred) + quote + valid_cred = $cred + + default_cred = deepcopy(valid_cred) + default_cred.prvkey = $default_key + default_cred.pubkey = $default_key * ".pub" + + cp(valid_cred.prvkey, default_cred.prvkey) + cp(valid_cred.pubkey, default_cred.pubkey) + + try + include($LIBGIT2_HELPER_PATH) + credential_loop(default_cred, $url, "git") + finally + rm(default_cred.prvkey) + rm(default_cred.pubkey) + end + end + end + + withenv("SSH_KEY_PATH" => nothing, + "SSH_PUB_KEY_PATH" => nothing, + "SSH_KEY_PASS" => nothing, + HOME => home_dir) do + + # Automatically use the default key + ex = gen_ex(valid_cred) + err, auth_attempts = challenge_prompt(ex, []) + @test err == git_ok + @test auth_attempts == 1 + + # Confirm the private key if any other prompting is required + ex = gen_ex(valid_p_cred) + challenges = [ + "Private key location for 'git@github.com' [$default_key]:" => "\n", + "Passphrase for $default_key:" => "$passphrase\n", + ] + err, auth_attempts = challenge_prompt(ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + end + end + end + + @testset "SSH expand tilde" begin + url = "git@github.com:test/package.jl" + + valid_key = joinpath(KEY_DIR, "valid") + valid_cred = LibGit2.SSHCredentials("git", "", valid_key, valid_key * ".pub") + + invalid_key = joinpath(KEY_DIR, "invalid") + + ssh_ex = quote + include($LIBGIT2_HELPER_PATH) + payload = CredentialPayload(allow_ssh_agent=false, allow_prompt=true) + err, auth_attempts = credential_loop($valid_cred, $url, "git", payload) + (err, auth_attempts, payload.credential) + end + + withenv("SSH_KEY_PATH" => nothing, + "SSH_PUB_KEY_PATH" => nothing, + "SSH_KEY_PASS" => nothing, + HOME => KEY_DIR) do + + # Expand tilde during the private key prompt + challenges = [ + "Private key location for 'git@github.com':" => "~/valid\n", + ] + err, auth_attempts, credential = challenge_prompt(ssh_ex, challenges) + @test err == git_ok + @test auth_attempts == 1 + @test get(credential).prvkey == abspath(valid_key) + end + + withenv("SSH_KEY_PATH" => valid_key, + "SSH_PUB_KEY_PATH" => invalid_key * ".pub", + "SSH_KEY_PASS" => nothing, + HOME => KEY_DIR) do + + # Expand tilde during the public key prompt + challenges = [ + "Private key location for 'git@github.com' [$valid_key]:" => "\n", + "Public key location for 'git@github.com' [$invalid_key.pub]:" => "~/valid.pub\n", + ] + err, auth_attempts, credential = challenge_prompt(ssh_ex, challenges) + @test err == git_ok + @test auth_attempts == 2 + @test get(credential).pubkey == abspath(valid_key * ".pub") + end + end + @testset "SSH explicit credentials" begin url = "git@github.com:test/package.jl" username = "git" @@ -2008,25 +2137,27 @@ mktempdir() do dir invalid_key = joinpath(KEY_DIR, "invalid") invalid_cred = LibGit2.SSHCredentials(username, "", invalid_key, invalid_key * ".pub") - function gen_ex(cred; allow_prompt=true) + function gen_ex(cred; allow_prompt=true, allow_ssh_agent=false) quote include($LIBGIT2_HELPER_PATH) - payload = CredentialPayload($cred, allow_ssh_agent=false, allow_prompt=$allow_prompt) + payload = CredentialPayload($cred, allow_ssh_agent=$allow_ssh_agent, + allow_prompt=$allow_prompt) credential_loop($valid_cred, $url, $username, payload) end end - # Explicitly provided credential is correct - ex = gen_ex(valid_cred, allow_prompt=true) + # Explicitly provided credential is correct. Note: allowing prompting and + # SSH agent to ensure they are skipped. + ex = gen_ex(valid_cred, allow_prompt=true, allow_ssh_agent=true) err, auth_attempts = challenge_prompt(ex, []) @test err == git_ok @test auth_attempts == 1 # Explicitly provided credential is incorrect - ex = gen_ex(invalid_cred, allow_prompt=false) + ex = gen_ex(invalid_cred, allow_prompt=false, allow_ssh_agent=false) err, auth_attempts = challenge_prompt(ex, []) - @test err == eauth_error - @test auth_attempts == 2 + @test err == exhausted_error + @test auth_attempts == 3 end @testset "HTTPS explicit credentials" begin @@ -2052,7 +2183,7 @@ mktempdir() do dir # Explicitly provided credential is incorrect ex = gen_ex(invalid_cred, allow_prompt=false) err, auth_attempts = challenge_prompt(ex, []) - @test err == eauth_error + @test err == exhausted_error @test auth_attempts == 2 end @@ -2124,7 +2255,7 @@ mktempdir() do dir # An EAUTH error should remove credentials from the cache ex = gen_ex(cached_cred=invalid_cred, allow_prompt=false) err, auth_attempts, cache = challenge_prompt(ex, []) - @test err == eauth_error + @test err == exhausted_error @test auth_attempts == 2 @test typeof(cache) == LibGit2.CachedCredentials @test cache.cred == Dict() @@ -2217,182 +2348,6 @@ mktempdir() do dir end end - #= temporarily disabled until working on the buildbots, ref https://github.com/JuliaLang/julia/pull/17651#issuecomment-238211150 - @testset "SSH" begin - sshd_command = "" - ssh_repo = joinpath(dir, "Example.SSH") - if !Sys.iswindows() - try - # SSHD needs to be executed by its full absolute path - sshd_command = strip(read(`which sshd`, String)) - catch - warn("Skipping SSH tests (Are `which` and `sshd` installed?)") - end - end - if !isempty(sshd_command) - mktempdir() do fakehomedir - mkdir(joinpath(fakehomedir,".ssh")) - # Unsetting the SSH agent serves two purposes. First, we make - # sure that we don't accidentally pick up an existing agent, - # and second we test that we fall back to using a key file - # if the agent isn't present. - withenv("HOME"=>fakehomedir,"SSH_AUTH_SOCK"=>nothing) do - # Generate user file, first an unencrypted one - wait(spawn(`ssh-keygen -N "" -C juliatest@localhost -f $fakehomedir/.ssh/id_rsa`)) - - # Generate host keys - wait(spawn(`ssh-keygen -f $fakehomedir/ssh_host_rsa_key -N '' -t rsa`)) - wait(spawn(`ssh-keygen -f $fakehomedir/ssh_host_dsa_key -N '' -t dsa`)) - - our_ssh_port = rand(13000:14000) # Chosen arbitrarily - - key_option = "AuthorizedKeysFile $fakehomedir/.ssh/id_rsa.pub" - pidfile_option = "PidFile $fakehomedir/sshd.pid" - sshp = agentp = nothing - logfile = tempname() - ssh_debug = false - function spawn_sshd() - debug_flags = ssh_debug ? `-d -d` : `` - _p = open(logfile, "a") do logfilestream - spawn(pipeline(pipeline(`$sshd_command - -e -f /dev/null $debug_flags - -h $fakehomedir/ssh_host_rsa_key - -h $fakehomedir/ssh_host_dsa_key -p $our_ssh_port - -o $pidfile_option - -o 'Protocol 2' - -o $key_option - -o 'UsePrivilegeSeparation no' - -o 'StrictModes no'`,STDOUT),stderr=logfilestream)) - end - # Give the SSH server 5 seconds to start up - yield(); sleep(5) - _p - end - sshp = spawn_sshd() - - TIOCSCTTY_str = "ccall(:ioctl, Void, (Cint, Cint, Int64), 0, - (Sys.isbsd() || Sys.isapple()) ? 0x20007461 : Sys.islinux() ? 0x540E : - error(\"Fill in TIOCSCTTY for this OS here\"), 0)" - - # To fail rather than hang - function killer_task(p, master) - @async begin - sleep(10) - kill(p) - if isopen(master) - nb_available(master) > 0 && - write(logfile, - readavailable(master)) - close(master) - end - end - end - - try - function try_clone(challenges = []) - cmd = """ - repo = nothing - try - $TIOCSCTTY_str - reponame = "ssh://$(ENV["USER"])@localhost:$our_ssh_port$cache_repo" - repo = LibGit2.clone(reponame, "$ssh_repo") - catch err - open("$logfile","a") do f - println(f,"HOME: ",ENV["HOME"]) - println(f, err) - end - finally - close(repo) - end - """ - # We try to be helpful by desperately looking for - # a way to prompt the password interactively. Pretend - # to be a TTY to suppress those shenanigans. Further, we - # need to detach and change the controlling terminal with - # TIOCSCTTY, since getpass opens the controlling terminal - TestHelpers.with_fake_pty() do slave, master - err = Base.Pipe() - let p = spawn(detach( - `$(Base.julia_cmd()) --startup-file=no -e $cmd`),slave,slave,STDERR) - killer_task(p, master) - for (challenge, response) in challenges - readuntil(master, challenge) - sleep(1) - print(master, response) - end - sleep(2) - wait(p) - close(master) - end - end - @test isfile(joinpath(ssh_repo,"testfile")) - rm(ssh_repo, recursive = true) - end - - # Should use the default files, no interaction required. - try_clone() - ssh_debug && (kill(sshp); sshp = spawn_sshd()) - - # Ok, now encrypt the file and test with that (this also - # makes sure that we don't accidentally fall back to the - # unencrypted version) - wait(spawn(`ssh-keygen -p -N "xxxxx" -f $fakehomedir/.ssh/id_rsa`)) - - # Try with the encrypted file. Needs a password. - try_clone(["Passphrase"=>"xxxxx\r\n"]) - ssh_debug && (kill(sshp); sshp = spawn_sshd()) - - # Move the file. It should now ask for the location and - # then the passphrase - mv("$fakehomedir/.ssh/id_rsa","$fakehomedir/.ssh/id_rsa2") - cp("$fakehomedir/.ssh/id_rsa.pub","$fakehomedir/.ssh/id_rsa2.pub") - try_clone(["location"=>"$fakehomedir/.ssh/id_rsa2\n", - "Passphrase"=>"xxxxx\n"]) - mv("$fakehomedir/.ssh/id_rsa2","$fakehomedir/.ssh/id_rsa") - rm("$fakehomedir/.ssh/id_rsa2.pub") - - # Ok, now start an agent - agent_sock = tempname() - agentp = spawn(`ssh-agent -a $agent_sock -d`) - while stat(agent_sock).mode == 0 # Wait until the agent is started - sleep(1) - end - - # fake pty is required for the same reason as in try_clone - # above - withenv("SSH_AUTH_SOCK" => agent_sock) do - TestHelpers.with_fake_pty() do slave, master - cmd = """ - $TIOCSCTTY_str - run(pipeline(`ssh-add $fakehomedir/.ssh/id_rsa`, - stderr = DevNull)) - """ - addp = spawn(detach(`$(Base.julia_cmd()) --startup-file=no -e $cmd`), - slave, slave, STDERR) - killer_task(addp, master) - sleep(2) - write(master, "xxxxx\n") - wait(addp) - end - - # Should now use the agent - try_clone() - end - catch err - println("SSHD logfile contents follows:") - println(read(logfile, String)) - rethrow(err) - finally - rm(logfile) - sshp !== nothing && kill(sshp) - agentp !== nothing && kill(agentp) - end - end - end - end - end - =# - # Note: Tests only work on linux as SSL_CERT_FILE is only respected on linux systems. @testset "Hostname verification" begin openssl_installed = false diff --git a/test/linalg/lapack.jl b/test/linalg/lapack.jl index 1350bff2dc995..78119dffa6e02 100644 --- a/test/linalg/lapack.jl +++ b/test/linalg/lapack.jl @@ -316,6 +316,15 @@ end @test_throws DimensionMismatch LAPACK.ormrq!('R','N',A,zeros(elty,11),rand(elty,10,10)) @test_throws DimensionMismatch LAPACK.ormrq!('L','N',A,zeros(elty,11),rand(elty,10,10)) + A = rand(elty,10,11) + Q = copy(A) + Q,tau = LAPACK.gerqf!(Q) + R = triu(Q[:,2:11]) + LAPACK.orgrq!(Q,tau) + @test Q*Q' ≈ eye(elty,10) + @test R*Q ≈ A + @test_throws DimensionMismatch LAPACK.orgrq!(zeros(elty,11,10),zeros(elty,10)) + C = rand(elty,10,10) V = rand(elty,10,10) T = zeros(elty,10,11) diff --git a/test/linalg/lq.jl b/test/linalg/lq.jl index 7c2b06ad8e0b8..e554319e71e00 100644 --- a/test/linalg/lq.jl +++ b/test/linalg/lq.jl @@ -105,7 +105,12 @@ bimg = randn(n,2)/2 end @testset "correct form of Q from lq(...) (#23729)" begin - # matrices with more rows than columns + # where the original matrix (say A) is square or has more rows than columns, + # then A's factorization's triangular factor (say L) should have the same shape + # as A independent of form (thin, square), and A's factorization's orthogonal + # factor (say Q) should be a square matrix of order of A's number of columns + # independent of form (thin, square), and L and Q should have + # multiplication-compatible shapes. m, n = 4, 2 A = randn(m, n) for thin in (true, false) @@ -114,13 +119,22 @@ end @test size(Q) == (n, n) @test isapprox(A, L*Q) end - # matrices with more columns than rows + # where the original matrix has strictly fewer rows than columns ... m, n = 2, 4 A = randn(m, n) + # ... then, for a thin factorization of A, L should be a square matrix + # of order of A's number of rows, Q should have the same shape as A, + # and L and Q should have multiplication-compatible shapes Lthin, Qthin = lq(A, thin = true) @test size(Lthin) == (m, m) @test size(Qthin) == (m, n) @test isapprox(A, Lthin * Qthin) + # ... and, for a non-thin factorization of A, L should have the same shape as A, + # Q should be a square matrix of order of A's number of columns, and L and Q + # should have multiplication-compatible shape. but instead the L returned has + # no zero-padding on the right / is L for the thin factorization, so for + # L and Q to have multiplication-compatible shapes, L must be zero-padded + # to have the shape of A. Lsquare, Qsquare = lq(A, thin = false) @test size(Lsquare) == (m, m) @test size(Qsquare) == (n, n) @@ -157,3 +171,50 @@ end @test implicitQ[1, n] == explicitQ[1, n] @test implicitQ[n, n] == explicitQ[n, n] end + +@testset "size on LQPackedQ (#23780)" begin + # size(Q::LQPackedQ) yields the shape of Q's square form + for ((mA, nA), nQ) in ( + ((3, 3), 3), # A 3-by-3 => square Q 3-by-3 + ((3, 4), 4), # A 3-by-4 => square Q 4-by-4 + ((4, 3), 3) )# A 4-by-3 => square Q 3-by-3 + @test size(lqfact(randn(mA, nA))[:Q]) == (nQ, nQ) + end +end + +@testset "postmultiplication with / right-application of LQPackedQ (#23779)" begin + function getqs(F::Base.LinAlg.LQ) + implicitQ = F[:Q] + explicitQ = A_mul_B!(implicitQ, eye(eltype(implicitQ), size(implicitQ)...)) + return implicitQ, explicitQ + end + # for any shape m-by-n of LQ-factored matrix, where Q is an LQPackedQ + # A_mul_B*(C, Q) (Ac_mul_B*(C, Q)) operations should work for + # *-by-n (n-by-*) C, which we test below via n-by-n C + for (mA, nA) in ((3, 3), (3, 4), (4, 3)) + implicitQ, explicitQ = getqs(lqfact(randn(mA, nA))) + C = randn(nA, nA) + @test *(C, implicitQ) ≈ *(C, explicitQ) + @test A_mul_Bc(C, implicitQ) ≈ A_mul_Bc(C, explicitQ) + @test Ac_mul_B(C, implicitQ) ≈ Ac_mul_B(C, explicitQ) + @test Ac_mul_Bc(C, implicitQ) ≈ Ac_mul_Bc(C, explicitQ) + end + # where the LQ-factored matrix has at least as many rows m as columns n, + # Q's square and thin forms have the same shape (n-by-n). hence we expect + # _only_ *-by-n (n-by-*) C to work in A_mul_B*(C, Q) (Ac_mul_B*(C, Q)) ops. + # and hence the n-by-n C tests above suffice. + # + # where the LQ-factored matrix has more columns n than rows m, + # Q's square form is n-by-n whereas its thin form is m-by-n. + # hence we need also test *-by-m (m-by-*) C with + # A_mul_B*(C, Q) (Ac_mul_B*(C, Q)) ops, as below via m-by-m C. + mA, nA = 3, 4 + implicitQ, explicitQ = getqs(lqfact(randn(mA, nA))) + C = randn(mA, mA) + zeroextCright = hcat(C, zeros(eltype(C), mA)) + zeroextCdown = vcat(C, zeros(eltype(C), (1, mA))) + @test *(C, implicitQ) ≈ *(zeroextCright, explicitQ) + @test A_mul_Bc(C, implicitQ) ≈ A_mul_Bc(zeroextCright, explicitQ) + @test Ac_mul_B(C, implicitQ) ≈ Ac_mul_B(zeroextCdown, explicitQ) + @test Ac_mul_Bc(C, implicitQ) ≈ Ac_mul_Bc(zeroextCdown, explicitQ) +end diff --git a/test/linalg/triangular.jl b/test/linalg/triangular.jl index bbbe49140d1c9..27fc05d824db7 100644 --- a/test/linalg/triangular.jl +++ b/test/linalg/triangular.jl @@ -512,7 +512,7 @@ end # dimensional correctness: isdefined(Main, :TestHelpers) || @eval Main include("../TestHelpers.jl") -using TestHelpers.Furlong +using Main.TestHelpers.Furlong let A = UpperTriangular([Furlong(1) Furlong(4); Furlong(0) Furlong(1)]) @test sqrt(A) == Furlong{1//2}.(UpperTriangular([1 2; 0 1])) end diff --git a/test/lineedit.jl b/test/lineedit.jl index 8baec0a7224be..a32fd51e00803 100644 --- a/test/lineedit.jl +++ b/test/lineedit.jl @@ -4,7 +4,7 @@ using Base.LineEdit using Base.LineEdit: edit_insert, buffer, content, setmark, getmark isdefined(Main, :TestHelpers) || @eval Main include(joinpath(dirname(@__FILE__), "TestHelpers.jl")) -using TestHelpers +using Main.TestHelpers # no need to have animation in tests LineEdit.REGION_ANIMATION_DURATION[] = 0.001 diff --git a/test/llvmpasses/alloc-opt.jl b/test/llvmpasses/alloc-opt.jl index 923b0bb81a0ec..e603e542c82dd 100644 --- a/test/llvmpasses/alloc-opt.jl +++ b/test/llvmpasses/alloc-opt.jl @@ -205,6 +205,25 @@ top: """) # CHECK-LABEL: } +# CHECK-LABEL: @preserve_opt +# CHECK: alloca i128, align 16 +# CHECK: call %jl_value_t*** @jl_get_ptls_states() +# CHECK-NOT: @julia.gc_alloc_obj +# CHECK-NOT: @jl_gc_pool_alloc +println(""" +define void @preserve_opt(i8* %v22) { +top: + %v6 = call %jl_value_t*** @jl_get_ptls_states() + %v18 = bitcast %jl_value_t*** %v6 to i8* + %v19 = call noalias %jl_value_t addrspace(10)* @julia.gc_alloc_obj(i8* %v18, $isz 16, %jl_value_t addrspace(10)* @tag) + %v20 = bitcast %jl_value_t addrspace(10)* %v19 to i8 addrspace(10)* + %v21 = addrspacecast i8 addrspace(10)* %v20 to i8 addrspace(11)* + %tok = call token (...) @llvm.julia.gc_preserve_begin(%jl_value_t addrspace(10)* %v19) + ret void +} +""") +# CHECK-LABEL: } + # CHECK: declare noalias %jl_value_t addrspace(10)* @jl_gc_pool_alloc(i8*, # CHECK: declare noalias %jl_value_t addrspace(10)* @jl_gc_big_alloc(i8*, println(""" @@ -213,6 +232,7 @@ declare %jl_value_t*** @jl_get_ptls_states() declare noalias %jl_value_t addrspace(10)* @julia.gc_alloc_obj(i8*, $isz, %jl_value_t addrspace(10)*) declare i64 @julia.pointer_from_objref(%jl_value_t addrspace(11)*) declare void @llvm.memcpy.p11i8.p0i8.i64(i8 addrspace(11)* nocapture writeonly, i8* nocapture readonly, i64, i32, i1) +declare token @llvm.julia.gc_preserve_begin(...) !0 = !{!1, !1, i64 0} !1 = !{!"jtbaa_tag", !2, i64 0} diff --git a/test/misc.jl b/test/misc.jl index 0422924bacb37..f09f6fdb5e535 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -231,7 +231,7 @@ struct NoMethodHasThisType end @test !isempty(methodswith(Int)) struct Type4Union end func4union(::Union{Type4Union,Int}) = () -@test !isempty(methodswith(Type4Union)) +@test !isempty(methodswith(Type4Union, @__MODULE__)) # PR #10984 # Disable on windows because of issue (missing flush) when redirecting STDERR. diff --git a/test/offsetarray.jl b/test/offsetarray.jl index 59fc81867e44c..993b7de476772 100644 --- a/test/offsetarray.jl +++ b/test/offsetarray.jl @@ -1,7 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license isdefined(Main, :TestHelpers) || @eval Main include(joinpath(dirname(@__FILE__), "TestHelpers.jl")) -using TestHelpers.OAs +using Main.TestHelpers.OAs const OAs_name = join(fullname(OAs), ".") @@ -31,11 +31,17 @@ S = OffsetArray(view(A0, 1:2, 1:2), (-1,2)) # IndexCartesian @test_throws BoundsError A[0,3,2] @test_throws BoundsError S[0,3,2] # partial indexing -S3 = OffsetArray(view(reshape(collect(1:4*3*2), 4, 3, 2), 1:3, 1:2, :), (-1,-2,1)) +S3 = OffsetArray(view(reshape(collect(1:4*3*1), 4, 3, 1), 1:3, 1:2, :), (-1,-2,1)) @test S3[1,-1] == 2 @test S3[1,0] == 6 @test_throws BoundsError S3[1,1] @test_throws BoundsError S3[1,-2] +S4 = OffsetArray(view(reshape(collect(1:4*3*2), 4, 3, 2), 1:3, 1:2, :), (-1,-2,1)) +@test S4[1,-1,2] == 2 +@test S4[1,0,2] == 6 +@test_throws BoundsError S4[1,1,2] +@test_throws BoundsError S4[1,-2,2] + # Vector indexing @test A[:, 3] == S[:, 3] == OffsetArray([1,2], (A.offsets[1],)) @@ -287,6 +293,21 @@ am = map(identity, a) @test isa(am, OffsetArray) @test am == a +# squeeze +a0 = rand(1,1,8,8,1) +a = OffsetArray(a0, (-1,2,3,4,5)) +@test @inferred(squeeze(a, 1)) == @inferred(squeeze(a, (1,))) == OffsetArray(reshape(a, (1,8,8,1)), (2,3,4,5)) +@test @inferred(squeeze(a, 5)) == @inferred(squeeze(a, (5,))) == OffsetArray(reshape(a, (1,1,8,8)), (-1,2,3,4)) +@test @inferred(squeeze(a, (1,5))) == squeeze(a, (5,1)) == OffsetArray(reshape(a, (1,8,8)), (2,3,4)) +@test @inferred(squeeze(a, (1,2,5))) == squeeze(a, (5,2,1)) == OffsetArray(reshape(a, (8,8)), (3,4)) +@test_throws ArgumentError squeeze(a, 0) +@test_throws ArgumentError squeeze(a, (1,1)) +@test_throws ArgumentError squeeze(a, (1,2,1)) +@test_throws ArgumentError squeeze(a, (1,1,2)) +@test_throws ArgumentError squeeze(a, 3) +@test_throws ArgumentError squeeze(a, 4) +@test_throws ArgumentError squeeze(a, 6) + # other functions v = OffsetArray(v0, (-3,)) @test endof(v) == 1 diff --git a/test/ranges.jl b/test/ranges.jl index 68768322f704b..187909ca58054 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -1155,7 +1155,7 @@ Base.isless(x, y::NotReal) = isless(x, y.val) # dimensional correctness: isdefined(Main, :TestHelpers) || @eval Main include("TestHelpers.jl") -using TestHelpers.Furlong +using Main.TestHelpers.Furlong @test_throws MethodError collect(Furlong(2):Furlong(10)) # step size is ambiguous @test_throws MethodError range(Furlong(2), 9) # step size is ambiguous @test collect(Furlong(2):Furlong(1):Furlong(10)) == collect(range(Furlong(2),Furlong(1),9)) == Furlong.(2:10) diff --git a/test/repl.jl b/test/repl.jl index 47f9e7176d052..062c3015070a0 100644 --- a/test/repl.jl +++ b/test/repl.jl @@ -5,7 +5,7 @@ include("testenv.jl") # REPL tests isdefined(Main, :TestHelpers) || @eval Main include(joinpath(dirname(@__FILE__), "TestHelpers.jl")) -using TestHelpers +using Main.TestHelpers import Base: REPL, LineEdit function fake_repl(f) diff --git a/test/runtests.jl b/test/runtests.jl index c2b8b4bfdc39c..927165bf4048b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,7 +4,7 @@ using Base.Test include("choosetests.jl") include("testenv.jl") -tests, net_on = choosetests(ARGS) +tests, net_on, exit_on_error = choosetests(ARGS) tests = unique(tests) const max_worker_rss = if haskey(ENV, "JULIA_TEST_MAXRSS_MB") @@ -33,6 +33,7 @@ cd(dirname(@__FILE__)) do n > 1 && addprocs_with_testenv(n) BLAS.set_num_threads(1) end + skipped = 0 @everywhere include("testdefs.jl") @@ -59,14 +60,18 @@ cd(dirname(@__FILE__)) do resp = [e] end push!(results, (test, resp)) - if resp[1] isa Exception || resp[end] > max_worker_rss + if resp[1] isa Exception + if exit_on_error + skipped = length(tests) + empty!(tests) + end + elseif resp[end] > max_worker_rss if n > 1 rmprocs(wrkr, waitfor=30) p = addprocs_with_testenv(1)[1] remotecall_fetch(include, p, "testdefs.jl") - else - # single process testing, bail if mem limit reached - resp[1] isa Exception || error("Halting tests. Memory limit reached : $resp > $max_worker_rss") + else # single process testing + error("Halting tests. Memory limit reached : $resp > $max_worker_rss") end end if !isa(resp[1], Exception) @@ -181,7 +186,9 @@ cd(dirname(@__FILE__)) do if !o_ts.anynonpass println(" \033[32;1mSUCCESS\033[0m") else - println(" \033[31;1mFAILURE\033[0m") + println(" \033[31;1mFAILURE\033[0m\n") + skipped > 0 && + println("$skipped test", skipped > 1 ? "s were" : " was", " skipped due to failure.\n") Base.Test.print_test_errors(o_ts) throw(Test.FallbackTestSetException("Test run finished with errors")) end diff --git a/test/sets.jl b/test/sets.jl index a529ae78bde2a..32ffdb92e955f 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -2,7 +2,7 @@ # Set tests isdefined(Main, :TestHelpers) || @eval Main include("TestHelpers.jl") -using TestHelpers.OAs +using Main.TestHelpers.OAs # Construction, collect @test ===(typeof(Set([1,2,3])), Set{Int}) diff --git a/test/show.jl b/test/show.jl index 938aac2d83853..e0cfb88d9479f 100644 --- a/test/show.jl +++ b/test/show.jl @@ -841,6 +841,7 @@ end @test static_shown(Symbol("")) == "Symbol(\"\")" @test static_shown(Symbol("a/b")) == "Symbol(\"a/b\")" @test static_shown(Symbol("a-b")) == "Symbol(\"a-b\")" +@test static_shown(UnionAll) == "UnionAll" @test static_shown(QuoteNode(:x)) == ":(:x)" diff --git a/test/sparse/sparsevector.jl b/test/sparse/sparsevector.jl index 3535bc7782997..e953231204ac5 100644 --- a/test/sparse/sparsevector.jl +++ b/test/sparse/sparsevector.jl @@ -1164,3 +1164,13 @@ end @testset "spzeros with index type" begin @test typeof(spzeros(Float32, Int16, 3)) == SparseVector{Float32,Int16} end + +@testset "corner cases of broadcast arithmetic operations with scalars (#21515)" begin + # test both scalar literals and variables + areequal(a, b, c) = isequal(a, b) && isequal(b, c) + inf, zeroh, zv, spzv = Inf, 0.0, zeros(1), spzeros(1) + @test areequal(spzv .* Inf, spzv .* inf, sparsevec(zv .* Inf)) + @test areequal(Inf .* spzv, inf .* spzv, sparsevec(Inf .* zv)) + @test areequal(spzv ./ 0.0, spzv ./ zeroh, sparsevec(zv ./ 0.0)) + @test areequal(0.0 .\ spzv, zeroh .\ spzv, sparsevec(0.0 .\ zv)) +end diff --git a/test/statistics.jl b/test/statistics.jl index be795660915f9..b7af59cbefe1e 100644 --- a/test/statistics.jl +++ b/test/statistics.jl @@ -409,7 +409,7 @@ end # dimensional correctness isdefined(Main, :TestHelpers) || @eval Main include("TestHelpers.jl") -using TestHelpers.Furlong +using Main.TestHelpers.Furlong @testset "Unitful elements" begin r = Furlong(1):Furlong(1):Furlong(2) a = collect(r) diff --git a/test/strings/basic.jl b/test/strings/basic.jl index 2347b8d695045..082f3659d0aa4 100644 --- a/test/strings/basic.jl +++ b/test/strings/basic.jl @@ -1,67 +1,73 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -# constructors -@test String([0x61,0x62,0x63,0x21]) == "abc!" -@test String("abc!") == "abc!" - -@test isempty(string()) -@test eltype(GenericString) == Char -@test start("abc") == 1 -@test cmp("ab","abc") == -1 -@test "abc" === "abc" -@test "ab" !== "abc" -@test string("ab", 'c') === "abc" -codegen_egal_of_strings(x, y) = (x===y, x!==y) -@test codegen_egal_of_strings(string("ab", 'c'), "abc") === (true, false) -let strs = ["", "a", "a b c", "до свидания"] - for x in strs, y in strs - @test (x === y) == (object_id(x) == object_id(y)) +@testset "constructors" begin + @test String([0x61,0x62,0x63,0x21]) == "abc!" + @test String("abc!") == "abc!" + + @test isempty(string()) + @test eltype(GenericString) == Char + @test start("abc") == 1 + @test cmp("ab","abc") == -1 + @test "abc" === "abc" + @test "ab" !== "abc" + @test string("ab", 'c') === "abc" + codegen_egal_of_strings(x, y) = (x===y, x!==y) + @test codegen_egal_of_strings(string("ab", 'c'), "abc") === (true, false) + let strs = ["", "a", "a b c", "до свидания"] + for x in strs, y in strs + @test (x === y) == (object_id(x) == object_id(y)) + end end end -# {starts,ends}with -@test startswith("abcd", 'a') -@test startswith("abcd", "a") -@test startswith("abcd", "ab") -@test !startswith("ab", "abcd") -@test !startswith("abcd", "bc") -@test endswith("abcd", 'd') -@test endswith("abcd", "d") -@test endswith("abcd", "cd") -@test !endswith("abcd", "dc") -@test !endswith("cd", "abcd") -@test startswith("ab\0cd", "ab\0c") -@test !startswith("ab\0cd", "ab\0d") +@testset "{starts,ends}with" begin + @test startswith("abcd", 'a') + @test startswith("abcd", "a") + @test startswith("abcd", "ab") + @test !startswith("ab", "abcd") + @test !startswith("abcd", "bc") + @test endswith("abcd", 'd') + @test endswith("abcd", "d") + @test endswith("abcd", "cd") + @test !endswith("abcd", "dc") + @test !endswith("cd", "abcd") + @test startswith("ab\0cd", "ab\0c") + @test !startswith("ab\0cd", "ab\0d") +end @test filter(x -> x ∈ ['f', 'o'], "foobar") == "foo" -# string iteration, and issue #1454 -str = "é" -str_a = vcat(str...) -@test length(str_a)==1 -@test str_a[1] == str[1] +@testset "string iteration, and issue #1454" begin + str = "é" + str_a = vcat(str...) + @test length(str_a)==1 + @test str_a[1] == str[1] -str = "s\u2200" -@test str[1:end] == str + str = "s\u2200" + @test str[1:end] == str +end -# sizeof -@test sizeof("abc") == 3 -@test sizeof("\u2222") == 3 +@testset "sizeof" begin + @test sizeof("abc") == 3 + @test sizeof("\u2222") == 3 +end # issue #3597 @test string(GenericString("Test")[1:1], "X") == "TX" -let b, n -for T = (UInt8,Int8,UInt16,Int16,UInt32,Int32,UInt64,Int64,UInt128,Int128,BigInt), - b = 2:62, - _ = 1:10 - n = (T != BigInt) ? rand(T) : BigInt(rand(Int128)) - @test parse(T, base(b, n), b) == n -end +@testset "parsing Int types" begin + let b, n + for T = (UInt8,Int8,UInt16,Int16,UInt32,Int32,UInt64,Int64,UInt128,Int128,BigInt), + b = 2:62, + _ = 1:10 + n = (T != BigInt) ? rand(T) : BigInt(rand(Int128)) + @test parse(T, base(b, n), b) == n + end + end end -# issue #6027 - make symbol with invalid char -let sym = Symbol(Char(0xdcdb)), res +@testset "issue #6027 - make symbol with invalid char" begin + sym = Symbol(Char(0xdcdb)) @test string(sym) == string(Char(0xdcdb)) @test String(sym) == string(Char(0xdcdb)) @test expand(Main, sym) === sym @@ -69,18 +75,19 @@ let sym = Symbol(Char(0xdcdb)), res @test res == """\$(Expr(:error, "invalid character \\\"\\udcdb\\\"\"))""" end -@test Symbol("asdf") === :asdf -@test Symbol(:abc,"def",'g',"hi",0) === :abcdefghi0 -@test :a < :b -@test startswith(string(gensym("asdf")),"##asdf#") -@test gensym("asdf") != gensym("asdf") -@test gensym() != gensym() -@test startswith(string(gensym()),"##") -@test_throws ArgumentError Symbol("ab\0") -@test_throws ArgumentError gensym("ab\0") - -# issue #6949 -let f = IOBuffer(), +@testset "Symbol and gensym" begin + @test Symbol("asdf") === :asdf + @test Symbol(:abc,"def",'g',"hi",0) === :abcdefghi0 + @test :a < :b + @test startswith(string(gensym("asdf")),"##asdf#") + @test gensym("asdf") != gensym("asdf") + @test gensym() != gensym() + @test startswith(string(gensym()),"##") + @test_throws ArgumentError Symbol("ab\0") + @test_throws ArgumentError gensym("ab\0") +end +@testset "issue #6949" begin + f = IOBuffer() x = split("1 2 3") local nb = 0 for c in x @@ -90,40 +97,41 @@ let f = IOBuffer(), @test String(take!(f)) == "123" end -# issue #7248 -@test_throws BoundsError ind2chr("hello", -1) -@test_throws BoundsError chr2ind("hello", -1) -@test_throws BoundsError ind2chr("hellø", -1) -@test_throws BoundsError chr2ind("hellø", -1) -@test_throws BoundsError ind2chr("hello", 10) -@test_throws BoundsError chr2ind("hello", 10) -@test_throws BoundsError ind2chr("hellø", 10) -@test_throws BoundsError chr2ind("hellø", 10) -@test_throws BoundsError checkbounds("hello", 0) -@test_throws BoundsError checkbounds("hello", 6) -@test_throws BoundsError checkbounds("hello", 0:3) -@test_throws BoundsError checkbounds("hello", 4:6) -@test_throws BoundsError checkbounds("hello", [0:3;]) -@test_throws BoundsError checkbounds("hello", [4:6;]) -@test checkbounds("hello", 2) -@test checkbounds("hello", 1:5) -@test checkbounds("hello", [1:5;]) - -# issue #15624 (indexing with out of bounds empty range) -@test ""[10:9] == "" -@test "hello"[10:9] == "" -@test "hellø"[10:9] == "" -@test SubString("hello", 1, 5)[10:9] == "" -@test SubString("hello", 1, 0)[10:9] == "" -@test SubString("hellø", 1, 5)[10:9] == "" -@test SubString("hellø", 1, 0)[10:9] == "" -@test SubString("", 1, 0)[10:9] == "" - -@test_throws BoundsError SubString("", 1, 6) -@test_throws BoundsError SubString("", 1, 1) - -# issue #22500 (using `get()` to index strings with default returns) -let +@testset "issue #7248" begin + @test_throws BoundsError ind2chr("hello", -1) + @test_throws BoundsError chr2ind("hello", -1) + @test_throws BoundsError ind2chr("hellø", -1) + @test_throws BoundsError chr2ind("hellø", -1) + @test_throws BoundsError ind2chr("hello", 10) + @test_throws BoundsError chr2ind("hello", 10) + @test_throws BoundsError ind2chr("hellø", 10) + @test_throws BoundsError chr2ind("hellø", 10) + @test_throws BoundsError checkbounds("hello", 0) + @test_throws BoundsError checkbounds("hello", 6) + @test_throws BoundsError checkbounds("hello", 0:3) + @test_throws BoundsError checkbounds("hello", 4:6) + @test_throws BoundsError checkbounds("hello", [0:3;]) + @test_throws BoundsError checkbounds("hello", [4:6;]) + @test checkbounds("hello", 2) + @test checkbounds("hello", 1:5) + @test checkbounds("hello", [1:5;]) +end + +@testset "issue #15624 (indexing with out of bounds empty range)" begin + @test ""[10:9] == "" + @test "hello"[10:9] == "" + @test "hellø"[10:9] == "" + @test SubString("hello", 1, 5)[10:9] == "" + @test SubString("hello", 1, 0)[10:9] == "" + @test SubString("hellø", 1, 5)[10:9] == "" + @test SubString("hellø", 1, 0)[10:9] == "" + @test SubString("", 1, 0)[10:9] == "" + + @test_throws BoundsError SubString("", 1, 6) + @test_throws BoundsError SubString("", 1, 1) +end + +@testset "issue #22500 (using `get()` to index strings with default returns)" begin utf8_str = "我很喜欢Julia" # Test that we can index in at valid locations @@ -167,312 +175,322 @@ let s = "x\u0302" @test s[1:2]==s end -# issue #9781 -# float(SubString) wasn't tolerant of trailing whitespace, which was different -# to "normal" strings. This also checks we aren't being too tolerant and allowing -# any arbitrary trailing characters. -@test parse(Float64,"1\n") == 1.0 -@test [parse(Float64,x) for x in split("0,1\n",",")][2] == 1.0 -@test_throws ArgumentError parse(Float64,split("0,1 X\n",",")[2]) -@test parse(Float32,"1\n") == 1.0 -@test [parse(Float32,x) for x in split("0,1\n",",")][2] == 1.0 -@test_throws ArgumentError parse(Float32,split("0,1 X\n",",")[2]) - -@test ucfirst("Hola")=="Hola" -@test ucfirst("hola")=="Hola" -@test ucfirst("")=="" -@test ucfirst("*")=="*" -@test ucfirst("DŽxx") == ucfirst("džxx") == "Džxx" - -@test lcfirst("Hola")=="hola" -@test lcfirst("hola")=="hola" -@test lcfirst("")=="" -@test lcfirst("*")=="*" - +@testset "issue #9781" begin + # float(SubString) wasn't tolerant of trailing whitespace, which was different + # to "normal" strings. This also checks we aren't being too tolerant and allowing + # any arbitrary trailing characters. + @test parse(Float64,"1\n") == 1.0 + @test [parse(Float64,x) for x in split("0,1\n",",")][2] == 1.0 + @test_throws ArgumentError parse(Float64,split("0,1 X\n",",")[2]) + @test parse(Float32,"1\n") == 1.0 + @test [parse(Float32,x) for x in split("0,1\n",",")][2] == 1.0 + @test_throws ArgumentError parse(Float32,split("0,1 X\n",",")[2]) + + @test ucfirst("Hola")=="Hola" + @test ucfirst("hola")=="Hola" + @test ucfirst("")=="" + @test ucfirst("*")=="*" + @test ucfirst("DŽxx") == ucfirst("džxx") == "Džxx" + + @test lcfirst("Hola")=="hola" + @test lcfirst("hola")=="hola" + @test lcfirst("")=="" + @test lcfirst("*")=="*" +end # test AbstractString functions at beginning of string.jl struct tstStringType <: AbstractString data::Array{UInt8,1} end -tstr = tstStringType(Vector{UInt8}("12")) -@test_throws ErrorException endof(tstr) -@test_throws ErrorException next(tstr, Bool(1)) +@testset "AbstractString functions" begin + tstr = tstStringType(Vector{UInt8}("12")) + @test_throws ErrorException endof(tstr) + @test_throws ErrorException next(tstr, Bool(1)) -gstr = GenericString("12") -@test string(gstr) isa GenericString + gstr = GenericString("12") + @test string(gstr) isa GenericString -@test Array{UInt8}(gstr) == [49, 50] -@test Array{Char,1}(gstr) == ['1', '2'] + @test Array{UInt8}(gstr) == [49, 50] + @test Array{Char,1}(gstr) == ['1', '2'] -@test gstr[1] == '1' -@test gstr[1:1] == "1" -@test gstr[[1]] == "1" + @test gstr[1] == '1' + @test gstr[1:1] == "1" + @test gstr[[1]] == "1" -@test done(eachindex("foobar"),7) -@test eltype(Base.EachStringIndex) == Int -@test map(uppercase, "foó") == "FOÓ" -@test chr2ind("fóobar",3) == 4 + @test done(eachindex("foobar"),7) + @test eltype(Base.EachStringIndex) == Int + @test map(uppercase, "foó") == "FOÓ" + @test chr2ind("fóobar",3) == 4 -@test Symbol(gstr)==Symbol("12") + @test Symbol(gstr)==Symbol("12") -@test_throws ErrorException sizeof(gstr) + @test_throws ErrorException sizeof(gstr) -@test length(GenericString(""))==0 + @test length(GenericString(""))==0 -@test nextind(1:1, 1) == 2 -@test nextind([1], 1) == 2 + @test nextind(1:1, 1) == 2 + @test nextind([1], 1) == 2 -@test ind2chr(gstr,2)==2 + @test ind2chr(gstr,2)==2 +end -# issue #10307 -@test typeof(map(x -> parse(Int16, x), AbstractString[])) == Vector{Int16} +@testset "issue #10307" begin + @test typeof(map(x -> parse(Int16, x), AbstractString[])) == Vector{Int16} -for T in [Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128] - for i in [typemax(T), typemin(T)] - s = "$i" - @test get(tryparse(T, s)) == i + for T in [Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128] + for i in [typemax(T), typemin(T)] + s = "$i" + @test get(tryparse(T, s)) == i + end end -end -for T in [Int8, Int16, Int32, Int64, Int128] - for i in [typemax(T), typemin(T)] - f = "$(i)0" - @test isnull(tryparse(T, f)) + for T in [Int8, Int16, Int32, Int64, Int128] + for i in [typemax(T), typemin(T)] + f = "$(i)0" + @test isnull(tryparse(T, f)) + end end end -# issue #11142 -s = "abcdefghij" -sp = pointer(s) -@test unsafe_string(sp) == s -@test unsafe_string(sp,5) == "abcde" -@test typeof(unsafe_string(sp)) == String -s = "abcde\uff\u2000\U1f596" -sp = pointer(s) -@test unsafe_string(sp) == s -@test unsafe_string(sp,5) == "abcde" -@test typeof(unsafe_string(sp)) == String - -@test get(tryparse(BigInt, "1234567890")) == BigInt(1234567890) -@test isnull(tryparse(BigInt, "1234567890-")) - -@test get(tryparse(Float64, "64")) == 64.0 -@test isnull(tryparse(Float64, "64o")) -@test get(tryparse(Float32, "32")) == 32.0f0 -@test isnull(tryparse(Float32, "32o")) - -# issue #10994: handle embedded NUL chars for string parsing -for T in [BigInt, Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128] - @test_throws ArgumentError parse(T, "1\0") -end -for T in [BigInt, Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128, Float64, Float32] - @test isnull(tryparse(T, "1\0")) -end -let s = normalize_string("tést",:NFKC) - @test unsafe_string(Base.unsafe_convert(Cstring, Base.cconvert(Cstring, s))) == s - @test unsafe_string(convert(Cstring, Symbol(s))) == s -end -@test_throws ArgumentError Base.unsafe_convert(Cstring, Base.cconvert(Cstring, "ba\0d")) - -cstrdup(s) = @static Sys.iswindows() ? ccall(:_strdup, Cstring, (Cstring,), s) : ccall(:strdup, Cstring, (Cstring,), s) -let p = cstrdup("hello") - @test unsafe_string(p) == "hello" - Libc.free(p) -end - -# iteration -@test [c for c in "ḟøøƀäṙ"] == ['ḟ', 'ø', 'ø', 'ƀ', 'ä', 'ṙ'] -@test [i for i in eachindex("ḟøøƀäṙ")] == [1, 4, 6, 8, 10, 12] -@test [x for x in enumerate("ḟøøƀäṙ")] == [(1, 'ḟ'), (2, 'ø'), (3, 'ø'), (4, 'ƀ'), (5, 'ä'), (6, 'ṙ')] - -# test all edge conditions -for (val, pass) in ( - (0, true), (0xd7ff, true), - (0xd800, false), (0xdfff, false), - (0xe000, true), (0xffff, true), - (0x10000, true), (0x10ffff, true), - (0x110000, false) - ) - @test isvalid(Char, val) == pass -end -for (val, pass) in ( - (b"\x00", true), - (b"\x7f", true), - (b"\x80", false), - (b"\xbf", false), - (b"\xc0", false), - (b"\xff", false), - (b"\xc0\x80", false), - (b"\xc1\x80", false), - (b"\xc2\x80", true), - (b"\xc2\xc0", false), - (b"\xed\x9f\xbf", true), - (b"\xed\xa0\x80", false), - (b"\xed\xbf\xbf", false), - (b"\xee\x80\x80", true), - (b"\xef\xbf\xbf", true), - (b"\xf0\x90\x80\x80", true), - (b"\xf4\x8f\xbf\xbf", true), - (b"\xf4\x90\x80\x80", false), - (b"\xf5\x80\x80\x80", false), - (b"\ud800\udc00", false), - (b"\udbff\udfff", false), - (b"\ud800\u0100", false), - (b"\udc00\u0100", false), - (b"\udc00\ud800", false) - ) - @test isvalid(String, val) == pass == isvalid(String(val)) +@testset "issue #11142" begin + s = "abcdefghij" + sp = pointer(s) + @test unsafe_string(sp) == s + @test unsafe_string(sp,5) == "abcde" + @test typeof(unsafe_string(sp)) == String + s = "abcde\uff\u2000\U1f596" + sp = pointer(s) + @test unsafe_string(sp) == s + @test unsafe_string(sp,5) == "abcde" + @test typeof(unsafe_string(sp)) == String + + @test get(tryparse(BigInt, "1234567890")) == BigInt(1234567890) + @test isnull(tryparse(BigInt, "1234567890-")) + + @test get(tryparse(Float64, "64")) == 64.0 + @test isnull(tryparse(Float64, "64o")) + @test get(tryparse(Float32, "32")) == 32.0f0 + @test isnull(tryparse(Float32, "32o")) end -# Issue #11203 -@test isvalid(String, UInt8[]) == true == isvalid("") +@testset "issue #10994: handle embedded NUL chars for string parsing" begin + for T in [BigInt, Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128] + @test_throws ArgumentError parse(T, "1\0") + end + for T in [BigInt, Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128, Float64, Float32] + @test isnull(tryparse(T, "1\0")) + end + let s = normalize_string("tést",:NFKC) + @test unsafe_string(Base.unsafe_convert(Cstring, Base.cconvert(Cstring, s))) == s + @test unsafe_string(convert(Cstring, Symbol(s))) == s + end + @test_throws ArgumentError Base.unsafe_convert(Cstring, Base.cconvert(Cstring, "ba\0d")) -# Check UTF-8 characters -# Check ASCII range (true), -# then single continuation bytes and lead bytes with no following continuation bytes (false) -for (rng,flg) in ((0:0x7f, true), (0x80:0xff, false)) - for byt in rng - @test isvalid(String, UInt8[byt]) == flg + cstrdup(s) = @static Sys.iswindows() ? ccall(:_strdup, Cstring, (Cstring,), s) : ccall(:strdup, Cstring, (Cstring,), s) + let p = cstrdup("hello") + @test unsafe_string(p) == "hello" + Libc.free(p) end end -# Check overlong lead bytes for 2-character sequences (false) -for byt = 0xc0:0xc1 - @test isvalid(String, UInt8[byt,0x80]) == false + +@testset "iteration" begin + @test [c for c in "ḟøøƀäṙ"] == ['ḟ', 'ø', 'ø', 'ƀ', 'ä', 'ṙ'] + @test [i for i in eachindex("ḟøøƀäṙ")] == [1, 4, 6, 8, 10, 12] + @test [x for x in enumerate("ḟøøƀäṙ")] == [(1, 'ḟ'), (2, 'ø'), (3, 'ø'), (4, 'ƀ'), (5, 'ä'), (6, 'ṙ')] end -# Check valid lead-in to two-byte sequences (true) -for byt = 0xc2:0xdf - for (rng,flg) in ((0x00:0x7f, false), (0x80:0xbf, true), (0xc0:0xff, false)) - for cont in rng - @test isvalid(String, UInt8[byt, cont]) == flg +@testset "isvalid edge conditions" begin + for (val, pass) in ( + (0, true), (0xd7ff, true), + (0xd800, false), (0xdfff, false), + (0xe000, true), (0xffff, true), + (0x10000, true), (0x10ffff, true), + (0x110000, false) + ) + @test isvalid(Char, val) == pass + end + for (val, pass) in ( + (b"\x00", true), + (b"\x7f", true), + (b"\x80", false), + (b"\xbf", false), + (b"\xc0", false), + (b"\xff", false), + (b"\xc0\x80", false), + (b"\xc1\x80", false), + (b"\xc2\x80", true), + (b"\xc2\xc0", false), + (b"\xed\x9f\xbf", true), + (b"\xed\xa0\x80", false), + (b"\xed\xbf\xbf", false), + (b"\xee\x80\x80", true), + (b"\xef\xbf\xbf", true), + (b"\xf0\x90\x80\x80", true), + (b"\xf4\x8f\xbf\xbf", true), + (b"\xf4\x90\x80\x80", false), + (b"\xf5\x80\x80\x80", false), + (b"\ud800\udc00", false), + (b"\udbff\udfff", false), + (b"\ud800\u0100", false), + (b"\udc00\u0100", false), + (b"\udc00\ud800", false) + ) + @test isvalid(String, val) == pass == isvalid(String(val)) + end + + # Issue #11203 + @test isvalid(String, UInt8[]) == true == isvalid("") + + # Check UTF-8 characters + # Check ASCII range (true), + # then single continuation bytes and lead bytes with no following continuation bytes (false) + for (rng,flg) in ((0:0x7f, true), (0x80:0xff, false)) + for byt in rng + @test isvalid(String, UInt8[byt]) == flg end end -end -# Check three-byte sequences -for r1 in (0xe0:0xec, 0xee:0xef) - for byt = r1 - # Check for short sequence - @test isvalid(String, UInt8[byt]) == false + # Check overlong lead bytes for 2-character sequences (false) + for byt = 0xc0:0xc1 + @test isvalid(String, UInt8[byt,0x80]) == false + end + # Check valid lead-in to two-byte sequences (true) + for byt = 0xc2:0xdf for (rng,flg) in ((0x00:0x7f, false), (0x80:0xbf, true), (0xc0:0xff, false)) + for cont in rng + @test isvalid(String, UInt8[byt, cont]) == flg + end + end + end + # Check three-byte sequences + for r1 in (0xe0:0xec, 0xee:0xef) + for byt = r1 + # Check for short sequence + @test isvalid(String, UInt8[byt]) == false + for (rng,flg) in ((0x00:0x7f, false), (0x80:0xbf, true), (0xc0:0xff, false)) + for cont in rng + @test isvalid(String, UInt8[byt, cont]) == false + @test isvalid(String, UInt8[byt, cont, 0x80]) == flg + end + end + end + end + # Check hangul characters (0xd000-0xd7ff) hangul + # Check for short sequence, or start of surrogate pair + for (rng,flg) in ((0x00:0x7f, false), (0x80:0x9f, true), (0xa0:0xff, false)) + for cont in rng + @test isvalid(String, UInt8[0xed, cont]) == false + @test isvalid(String, UInt8[0xed, cont, 0x80]) == flg + end + end + # Check valid four-byte sequences + for byt = 0xf0:0xf4 + if (byt == 0xf0) + r0 = ((0x00:0x8f, false), (0x90:0xbf, true), (0xc0:0xff, false)) + elseif byt == 0xf4 + r0 = ((0x00:0x7f, false), (0x80:0x8f, true), (0x90:0xff, false)) + else + r0 = ((0x00:0x7f, false), (0x80:0xbf, true), (0xc0:0xff, false)) + end + for (rng,flg) in r0 for cont in rng @test isvalid(String, UInt8[byt, cont]) == false - @test isvalid(String, UInt8[byt, cont, 0x80]) == flg + @test isvalid(String, UInt8[byt, cont, 0x80]) == false + @test isvalid(String, UInt8[byt, cont, 0x80, 0x80]) == flg end end end -end -# Check hangul characters (0xd000-0xd7ff) hangul -# Check for short sequence, or start of surrogate pair -for (rng,flg) in ((0x00:0x7f, false), (0x80:0x9f, true), (0xa0:0xff, false)) - for cont in rng - @test isvalid(String, UInt8[0xed, cont]) == false - @test isvalid(String, UInt8[0xed, cont, 0x80]) == flg + # Check five-byte sequences, should be invalid + for byt = 0xf8:0xfb + @test isvalid(String, UInt8[byt, 0x80, 0x80, 0x80, 0x80]) == false end + # Check six-byte sequences, should be invalid + for byt = 0xfc:0xfd + @test isvalid(String, UInt8[byt, 0x80, 0x80, 0x80, 0x80, 0x80]) == false + end + # Check seven-byte sequences, should be invalid + @test isvalid(String, UInt8[0xfe, 0x80, 0x80, 0x80, 0x80, 0x80]) == false end -# Check valid four-byte sequences -for byt = 0xf0:0xf4 - if (byt == 0xf0) - r0 = ((0x00:0x8f, false), (0x90:0xbf, true), (0xc0:0xff, false)) - elseif byt == 0xf4 - r0 = ((0x00:0x7f, false), (0x80:0x8f, true), (0x90:0xff, false)) - else - r0 = ((0x00:0x7f, false), (0x80:0xbf, true), (0xc0:0xff, false)) + +@testset "issue #11482" begin + @testset "uppercase/lowercase" begin + @test uppercase("aBc") == "ABC" + @test uppercase('A') == 'A' + @test uppercase('a') == 'A' + @test lowercase("AbC") == "abc" + @test lowercase('A') == 'a' + @test lowercase('a') == 'a' + @test uppercase('α') == '\u0391' + @test lowercase('Δ') == 'δ' + @test lowercase('\U118bf') == '\U118df' + @test uppercase('\U1044d') == '\U10425' end - for (rng,flg) in r0 - for cont in rng - @test isvalid(String, UInt8[byt, cont]) == false - @test isvalid(String, UInt8[byt, cont, 0x80]) == false - @test isvalid(String, UInt8[byt, cont, 0x80, 0x80]) == flg - end + @testset "ucfirst/lcfirst" begin + @test ucfirst("Abc") == "Abc" + @test ucfirst("abc") == "Abc" + @test lcfirst("ABC") == "aBC" + @test lcfirst("aBC") == "aBC" + @test ucfirst(GenericString("")) == "" + @test lcfirst(GenericString("")) == "" + @test ucfirst(GenericString("a")) == "A" + @test lcfirst(GenericString("A")) == "a" + @test lcfirst(GenericString("a")) == "a" + @test ucfirst(GenericString("A")) == "A" end + @testset "titlecase" begin + @test titlecase('lj') == 'Lj' + @test titlecase("ljubljana") == "Ljubljana" + @test titlecase("aBc ABC") == "ABc ABC" + @test titlecase("abcD EFG\n\thij") == "AbcD EFG\n\tHij" + end +end + +@testset "issue # 11464: uppercase/lowercase of GenericString becomes a String" begin + str = "abcdef\uff\uffff\u10ffffABCDEF" + @test typeof(uppercase("abcdef")) == String + @test typeof(uppercase(GenericString(str))) == String + @test typeof(lowercase("ABCDEF")) == String + @test typeof(lowercase(GenericString(str))) == String + + foomap(ch) = (ch > Char(65)) + foobar(ch) = Char(0xd800) + foobaz(ch) = reinterpret(Char, typemax(UInt32)) + @test_throws ArgumentError map(foomap, GenericString(str)) + @test map(foobar, GenericString(str)) == String(repeat(b"\ud800", outer=[17])) + @test map(foobaz, GenericString(str)) == String(repeat(b"\ufffd", outer=[17])) + + @test "a".*["b","c"] == ["ab","ac"] + @test ["b","c"].*"a" == ["ba","ca"] + @test ["a","b"].*["c" "d"] == ["ac" "ad"; "bc" "bd"] + + @test one(String) == "" + @test prod(["*" for i in 1:3]) == "***" + @test prod(["*" for i in 1:0]) == "" end -# Check five-byte sequences, should be invalid -for byt = 0xf8:0xfb - @test isvalid(String, UInt8[byt, 0x80, 0x80, 0x80, 0x80]) == false -end -# Check six-byte sequences, should be invalid -for byt = 0xfc:0xfd - @test isvalid(String, UInt8[byt, 0x80, 0x80, 0x80, 0x80, 0x80]) == false -end -# Check seven-byte sequences, should be invalid -@test isvalid(String, UInt8[0xfe, 0x80, 0x80, 0x80, 0x80, 0x80]) == false - -# 11482 - -# lower and upper -@test uppercase("aBc") == "ABC" -@test uppercase('A') == 'A' -@test uppercase('a') == 'A' -@test lowercase("AbC") == "abc" -@test lowercase('A') == 'a' -@test lowercase('a') == 'a' -@test uppercase('α') == '\u0391' -@test lowercase('Δ') == 'δ' -@test lowercase('\U118bf') == '\U118df' -@test uppercase('\U1044d') == '\U10425' -@test ucfirst("Abc") == "Abc" -@test ucfirst("abc") == "Abc" -@test lcfirst("ABC") == "aBC" -@test lcfirst("aBC") == "aBC" -@test ucfirst(GenericString("")) == "" -@test lcfirst(GenericString("")) == "" -@test ucfirst(GenericString("a")) == "A" -@test lcfirst(GenericString("A")) == "a" -@test lcfirst(GenericString("a")) == "a" -@test ucfirst(GenericString("A")) == "A" - -# titlecase -@test titlecase('lj') == 'Lj' -@test titlecase("ljubljana") == "Ljubljana" -@test titlecase("aBc ABC") == "ABc ABC" -@test titlecase("abcD EFG\n\thij") == "AbcD EFG\n\tHij" - -# issue # 11464: uppercase/lowercase of GenericString becomes a String -str = "abcdef\uff\uffff\u10ffffABCDEF" -@test typeof(uppercase("abcdef")) == String -@test typeof(uppercase(GenericString(str))) == String -@test typeof(lowercase("ABCDEF")) == String -@test typeof(lowercase(GenericString(str))) == String - -foomap(ch) = (ch > Char(65)) -foobar(ch) = Char(0xd800) -foobaz(ch) = reinterpret(Char, typemax(UInt32)) -@test_throws ArgumentError map(foomap, GenericString(str)) -@test map(foobar, GenericString(str)) == String(repeat(b"\ud800", outer=[17])) -@test map(foobaz, GenericString(str)) == String(repeat(b"\ufffd", outer=[17])) - -@test "a".*["b","c"] == ["ab","ac"] -@test ["b","c"].*"a" == ["ba","ca"] -@test ["a","b"].*["c" "d"] == ["ac" "ad"; "bc" "bd"] - -@test one(String) == "" -@test prod(["*" for i in 1:3]) == "***" -@test prod(["*" for i in 1:0]) == "" - -# Make sure NULL pointers are handled consistently by String -@test_throws ArgumentError unsafe_string(Ptr{UInt8}(0)) -@test_throws ArgumentError unsafe_string(Ptr{UInt8}(0), 10) - -# ascii works on ASCII strings and fails on non-ASCII strings -@test ascii("Hello, world") == "Hello, world" -@test typeof(ascii("Hello, world")) == String -@test ascii(GenericString("Hello, world")) == "Hello, world" -@test typeof(ascii(GenericString("Hello, world"))) == String -@test_throws ArgumentError ascii("Hello, ∀") -@test_throws ArgumentError ascii(GenericString("Hello, ∀")) - -# issue #17271: endof() doesn't throw an error even with invalid strings -@test endof(String(b"\x90")) == 0 -@test endof(String(b"\xce")) == 1 +@testset "NULL pointers are handled consistently by String" begin + @test_throws ArgumentError unsafe_string(Ptr{UInt8}(0)) + @test_throws ArgumentError unsafe_string(Ptr{UInt8}(0), 10) +end +@testset "ascii for ASCII strings and non-ASCII strings" begin + @test ascii("Hello, world") == "Hello, world" + @test typeof(ascii("Hello, world")) == String + @test ascii(GenericString("Hello, world")) == "Hello, world" + @test typeof(ascii(GenericString("Hello, world"))) == String + @test_throws ArgumentError ascii("Hello, ∀") + @test_throws ArgumentError ascii(GenericString("Hello, ∀")) +end +@testset "issue #17271: endof() doesn't throw an error even with invalid strings" begin + @test endof(String(b"\x90")) == 0 + @test endof(String(b"\xce")) == 1 +end # issue #17624, missing getindex method for String @test "abc"[:] == "abc" -# issue #18280: next/nextind must return past String's underlying data -for s in ("Hello", "Σ", "こんにちは", "😊😁") - local s - @test next(s, endof(s))[2] > sizeof(s) - @test nextind(s, endof(s)) > sizeof(s) +@testset "issue #18280: next/nextind must return past String's underlying data" begin + for s in ("Hello", "Σ", "こんにちは", "😊😁") + local s + @test next(s, endof(s))[2] > sizeof(s) + @test nextind(s, endof(s)) > sizeof(s) + end end - # Test cmp with AbstractStrings that don't index the same as UTF-8, which would include # (LegacyString.)UTF16String and (LegacyString.)UTF32String, among others. @@ -484,50 +502,55 @@ Base.start(x::CharStr) = start(x.chars) Base.next(x::CharStr, i::Int) = next(x.chars, i) Base.done(x::CharStr, i::Int) = done(x.chars, i) Base.endof(x::CharStr) = endof(x.chars) +@testset "cmp without UTF-8 indexing" begin + # Simple case, with just ANSI Latin 1 characters + @test "áB" != CharStr("áá") # returns false with bug + @test cmp("áB", CharStr("áá")) == -1 # returns 0 with bug + + # Case with Unicode characters + @test cmp("\U1f596\U1f596", CharStr("\U1f596")) == 1 # Gives BoundsError with bug + @test cmp(CharStr("\U1f596"), "\U1f596\U1f596") == -1 +end + +@testset "repeat" begin + @inferred repeat(GenericString("x"), 1) + @test repeat("xx",3) == repeat("x",6) == repeat('x',6) == repeat(GenericString("x"), 6) == "xxxxxx" + @test repeat("αα",3) == repeat("α",6) == repeat('α',6) == repeat(GenericString("α"), 6) == "αααααα" + @test repeat("x",1) == repeat('x',1) == "x"^1 == 'x'^1 == GenericString("x")^1 == "x" + @test repeat("x",0) == repeat('x',0) == "x"^0 == 'x'^0 == GenericString("x")^0 == "" + + for S in ["xxx", "ååå", "∀∀∀", "🍕🍕🍕"] + c = S[1] + s = string(c) + @test_throws ArgumentError repeat(c, -1) + @test_throws ArgumentError repeat(s, -1) + @test_throws ArgumentError repeat(S, -1) + @test repeat(c, 0) == "" + @test repeat(s, 0) == "" + @test repeat(S, 0) == "" + @test repeat(c, 1) == s + @test repeat(s, 1) == s + @test repeat(S, 1) == S + @test repeat(c, 3) == S + @test repeat(s, 3) == S + @test repeat(S, 3) == S*S*S + end +end +@testset "issue #12495: check that logical indexing attempt raises ArgumentError" begin + @test_throws ArgumentError "abc"[[true, false, true]] + @test_throws ArgumentError "abc"[BitArray([true, false, true])] +end + +@testset "concatenation" begin + @test "ab" * "cd" == "abcd" + @test 'a' * "bc" == "abc" + @test "ab" * 'c' == "abc" + @test 'a' * 'b' == "ab" + @test 'a' * "b" * 'c' == "abc" + @test "a" * 'b' * 'c' == "abc" +end -# Simple case, with just ANSI Latin 1 characters -@test "áB" != CharStr("áá") # returns false with bug -@test cmp("áB", CharStr("áá")) == -1 # returns 0 with bug - -# Case with Unicode characters -@test cmp("\U1f596\U1f596", CharStr("\U1f596")) == 1 # Gives BoundsError with bug -@test cmp(CharStr("\U1f596"), "\U1f596\U1f596") == -1 - -# repeat function -@inferred repeat(GenericString("x"), 1) -@test repeat("xx",3) == repeat("x",6) == repeat('x',6) == repeat(GenericString("x"), 6) == "xxxxxx" -@test repeat("αα",3) == repeat("α",6) == repeat('α',6) == repeat(GenericString("α"), 6) == "αααααα" -@test repeat("x",1) == repeat('x',1) == "x"^1 == 'x'^1 == GenericString("x")^1 == "x" -@test repeat("x",0) == repeat('x',0) == "x"^0 == 'x'^0 == GenericString("x")^0 == "" - -for S in ["xxx", "ååå", "∀∀∀", "🍕🍕🍕"] - c = S[1] - s = string(c) - @test_throws ArgumentError repeat(c, -1) - @test_throws ArgumentError repeat(s, -1) - @test_throws ArgumentError repeat(S, -1) - @test repeat(c, 0) == "" - @test repeat(s, 0) == "" - @test repeat(S, 0) == "" - @test repeat(c, 1) == s - @test repeat(s, 1) == s - @test repeat(S, 1) == S - @test repeat(c, 3) == S - @test repeat(s, 3) == S - @test repeat(S, 3) == S*S*S -end - -# issue #12495: check that logical indexing attempt raises ArgumentError -@test_throws ArgumentError "abc"[[true, false, true]] -@test_throws ArgumentError "abc"[BitArray([true, false, true])] - -@test "ab" * "cd" == "abcd" -@test 'a' * "bc" == "abc" -@test "ab" * 'c' == "abc" -@test 'a' * 'b' == "ab" -@test 'a' * "b" * 'c' == "abc" -@test "a" * 'b' * 'c' == "abc" - -# unrecognized escapes in string/char literals -@test_throws ParseError parse("\"\\.\"") -@test_throws ParseError parse("\'\\.\'") +@testset "unrecognized escapes in string/char literals" begin + @test_throws ParseError parse("\"\\.\"") + @test_throws ParseError parse("\'\\.\'") +end diff --git a/test/strings/util.jl b/test/strings/util.jl index 6b830e4a4ce6c..ac55e8a0f792a 100644 --- a/test/strings/util.jl +++ b/test/strings/util.jl @@ -1,279 +1,284 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -# padding (lpad and rpad) -@test lpad("foo", 3) == "foo" -@test rpad("foo", 3) == "foo" -@test lpad("foo", 5) == " foo" -@test rpad("foo", 5) == "foo " -@test lpad("foo", 5, " ") == " foo" -@test rpad("foo", 5, " ") == "foo " -@test lpad("foo", 6, " ") == " foo" -@test rpad("foo", 6, " ") == "foo " +@testset "padding (lpad and rpad)" begin + @test lpad("foo", 3) == "foo" + @test rpad("foo", 3) == "foo" + @test lpad("foo", 5) == " foo" + @test rpad("foo", 5) == "foo " + @test lpad("foo", 5, " ") == " foo" + @test rpad("foo", 5, " ") == "foo " + @test lpad("foo", 6, " ") == " foo" + @test rpad("foo", 6, " ") == "foo " +end # string manipulation -@test strip("") == "" -@test strip(" ") == "" -@test strip(" ") == "" -@test strip(" ") == "" -@test strip("\t hi \n") == "hi" -@test strip("foobarfoo", ['f','o']) == "bar" -@test strip("foobarfoo", ('f','o')) == "bar" - -let s, f -for s in ("", " ", " abc", "abc ", " abc "), - f in (lstrip, rstrip, strip) - - fs = f(s) - for T = (String, GenericString) - local t, b - t = convert(T,s) - ft = f(t) - @test s == t - @test fs == ft - @test typeof(ft) == SubString{T} - - b = convert(SubString{T}, t) - fb = f(b) - @test s == b - @test fs == fb - @test typeof(fb) == SubString{T} +@testset "lstrip/rstrip/strip" begin + @test strip("") == "" + @test strip(" ") == "" + @test strip(" ") == "" + @test strip(" ") == "" + @test strip("\t hi \n") == "hi" + @test strip("foobarfoo", ['f','o']) == "bar" + @test strip("foobarfoo", ('f','o')) == "bar" + + for s in ("", " ", " abc", "abc ", " abc "), + f in (lstrip, rstrip, strip) + + fs = f(s) + for T = (String, GenericString) + local t, b + t = convert(T,s) + ft = f(t) + @test s == t + @test fs == ft + @test typeof(ft) == SubString{T} + + b = convert(SubString{T}, t) + fb = f(b) + @test s == b + @test fs == fb + @test typeof(fb) == SubString{T} + end end end + +@testset "rsplit/split" begin + @test isequal(split("foo,bar,baz", 'x'), ["foo,bar,baz"]) + @test isequal(split("foo,bar,baz", ','), ["foo","bar","baz"]) + @test isequal(split("foo,bar,baz", ","), ["foo","bar","baz"]) + @test isequal(split("foo,bar,baz", r","), ["foo","bar","baz"]) + @test isequal(split("foo,bar,baz", ','; limit=0), ["foo","bar","baz"]) + @test isequal(split("foo,bar,baz", ','; limit=1), ["foo,bar,baz"]) + @test isequal(split("foo,bar,baz", ','; limit=2), ["foo","bar,baz"]) + @test isequal(split("foo,bar,baz", ','; limit=3), ["foo","bar","baz"]) + @test isequal(split("foo,bar", "o,b"), ["fo","ar"]) + + @test isequal(split("", ','), [""]) + @test isequal(split(",", ','), ["",""]) + @test isequal(split(",,", ','), ["","",""]) + @test isequal(split("", ',' ; keep=false), []) + @test isequal(split(",", ',' ; keep=false), []) + @test isequal(split(",,", ','; keep=false), []) + + @test isequal(split("a b c"), ["a","b","c"]) + @test isequal(split("a b \t c\n"), ["a","b","c"]) + + @test isequal(rsplit("foo,bar,baz", 'x'), ["foo,bar,baz"]) + @test isequal(rsplit("foo,bar,baz", ','), ["foo","bar","baz"]) + @test isequal(rsplit("foo,bar,baz", ","), ["foo","bar","baz"]) + @test isequal(rsplit("foo,bar,baz", ','; limit=0), ["foo","bar","baz"]) + @test isequal(rsplit("foo,bar,baz", ','; limit=1), ["foo,bar,baz"]) + @test isequal(rsplit("foo,bar,baz", ','; limit=2), ["foo,bar","baz"]) + @test isequal(rsplit("foo,bar,baz", ','; limit=3), ["foo","bar","baz"]) + @test isequal(rsplit("foo,bar", "o,b"), ["fo","ar"]) + + @test isequal(rsplit("", ','), [""]) + @test isequal(rsplit(",", ','), ["",""]) + @test isequal(rsplit(",,", ','), ["","",""]) + @test isequal(rsplit(",,", ','; limit=2), [",",""]) + @test isequal(rsplit("", ',' ; keep=false), []) + @test isequal(rsplit(",", ',' ; keep=false), []) + @test isequal(rsplit(",,", ','; keep=false), []) + + #@test isequal(rsplit("a b c"), ["a","b","c"]) + #@test isequal(rsplit("a b \t c\n"), ["a","b","c"]) + + let str = "a.:.ba..:..cba.:.:.dcba.:." + @test isequal(split(str, ".:."), ["a","ba.",".cba",":.dcba",""]) + @test isequal(split(str, ".:."; keep=false), ["a","ba.",".cba",":.dcba"]) + @test isequal(split(str, ".:."), ["a","ba.",".cba",":.dcba",""]) + @test isequal(split(str, r"\.(:\.)+"), ["a","ba.",".cba","dcba",""]) + @test isequal(split(str, r"\.(:\.)+"; keep=false), ["a","ba.",".cba","dcba"]) + @test isequal(split(str, r"\.+:\.+"), ["a","ba","cba",":.dcba",""]) + @test isequal(split(str, r"\.+:\.+"; keep=false), ["a","ba","cba",":.dcba"]) + + @test isequal(rsplit(str, ".:."), ["a","ba.",".cba.:","dcba",""]) + @test isequal(rsplit(str, ".:."; keep=false), ["a","ba.",".cba.:","dcba"]) + @test isequal(rsplit(str, ".:."; limit=2), ["a.:.ba..:..cba.:.:.dcba", ""]) + @test isequal(rsplit(str, ".:."; limit=3), ["a.:.ba..:..cba.:", "dcba", ""]) + @test isequal(rsplit(str, ".:."; limit=4), ["a.:.ba.", ".cba.:", "dcba", ""]) + @test isequal(rsplit(str, ".:."; limit=5), ["a", "ba.", ".cba.:", "dcba", ""]) + @test isequal(rsplit(str, ".:."; limit=6), ["a", "ba.", ".cba.:", "dcba", ""]) + end + + # zero-width splits + @test isequal(rsplit("", ""), [""]) + + @test isequal(split("", ""), [""]) + @test isequal(split("", r""), [""]) + @test isequal(split("abc", ""), ["a","b","c"]) + @test isequal(split("abc", r""), ["a","b","c"]) + @test isequal(split("abcd", r"b?"), ["a","c","d"]) + @test isequal(split("abcd", r"b*"), ["a","c","d"]) + @test isequal(split("abcd", r"b+"), ["a","cd"]) + @test isequal(split("abcd", r"b?c?"), ["a","d"]) + @test isequal(split("abcd", r"[bc]?"), ["a","","d"]) + @test isequal(split("abcd", r"a*"), ["","b","c","d"]) + @test isequal(split("abcd", r"a+"), ["","bcd"]) + @test isequal(split("abcd", r"d*"), ["a","b","c",""]) + @test isequal(split("abcd", r"d+"), ["abc",""]) + @test isequal(split("abcd", r"[ad]?"), ["","b","c",""]) end -# split -@test isequal(split("foo,bar,baz", 'x'), ["foo,bar,baz"]) -@test isequal(split("foo,bar,baz", ','), ["foo","bar","baz"]) -@test isequal(split("foo,bar,baz", ","), ["foo","bar","baz"]) -@test isequal(split("foo,bar,baz", r","), ["foo","bar","baz"]) -@test isequal(split("foo,bar,baz", ','; limit=0), ["foo","bar","baz"]) -@test isequal(split("foo,bar,baz", ','; limit=1), ["foo,bar,baz"]) -@test isequal(split("foo,bar,baz", ','; limit=2), ["foo","bar,baz"]) -@test isequal(split("foo,bar,baz", ','; limit=3), ["foo","bar","baz"]) -@test isequal(split("foo,bar", "o,b"), ["fo","ar"]) - -@test isequal(split("", ','), [""]) -@test isequal(split(",", ','), ["",""]) -@test isequal(split(",,", ','), ["","",""]) -@test isequal(split("", ',' ; keep=false), []) -@test isequal(split(",", ',' ; keep=false), []) -@test isequal(split(",,", ','; keep=false), []) - -@test isequal(split("a b c"), ["a","b","c"]) -@test isequal(split("a b \t c\n"), ["a","b","c"]) - -@test isequal(rsplit("foo,bar,baz", 'x'), ["foo,bar,baz"]) -@test isequal(rsplit("foo,bar,baz", ','), ["foo","bar","baz"]) -@test isequal(rsplit("foo,bar,baz", ","), ["foo","bar","baz"]) -@test isequal(rsplit("foo,bar,baz", ','; limit=0), ["foo","bar","baz"]) -@test isequal(rsplit("foo,bar,baz", ','; limit=1), ["foo,bar,baz"]) -@test isequal(rsplit("foo,bar,baz", ','; limit=2), ["foo,bar","baz"]) -@test isequal(rsplit("foo,bar,baz", ','; limit=3), ["foo","bar","baz"]) -@test isequal(rsplit("foo,bar", "o,b"), ["fo","ar"]) - -@test isequal(rsplit("", ','), [""]) -@test isequal(rsplit(",", ','), ["",""]) -@test isequal(rsplit(",,", ','), ["","",""]) -@test isequal(rsplit(",,", ','; limit=2), [",",""]) -@test isequal(rsplit("", ',' ; keep=false), []) -@test isequal(rsplit(",", ',' ; keep=false), []) -@test isequal(rsplit(",,", ','; keep=false), []) - -#@test isequal(rsplit("a b c"), ["a","b","c"]) -#@test isequal(rsplit("a b \t c\n"), ["a","b","c"]) - -let str = "a.:.ba..:..cba.:.:.dcba.:." -@test isequal(split(str, ".:."), ["a","ba.",".cba",":.dcba",""]) -@test isequal(split(str, ".:."; keep=false), ["a","ba.",".cba",":.dcba"]) -@test isequal(split(str, ".:."), ["a","ba.",".cba",":.dcba",""]) -@test isequal(split(str, r"\.(:\.)+"), ["a","ba.",".cba","dcba",""]) -@test isequal(split(str, r"\.(:\.)+"; keep=false), ["a","ba.",".cba","dcba"]) -@test isequal(split(str, r"\.+:\.+"), ["a","ba","cba",":.dcba",""]) -@test isequal(split(str, r"\.+:\.+"; keep=false), ["a","ba","cba",":.dcba"]) - -@test isequal(rsplit(str, ".:."), ["a","ba.",".cba.:","dcba",""]) -@test isequal(rsplit(str, ".:."; keep=false), ["a","ba.",".cba.:","dcba"]) -@test isequal(rsplit(str, ".:."; limit=2), ["a.:.ba..:..cba.:.:.dcba", ""]) -@test isequal(rsplit(str, ".:."; limit=3), ["a.:.ba..:..cba.:", "dcba", ""]) -@test isequal(rsplit(str, ".:."; limit=4), ["a.:.ba.", ".cba.:", "dcba", ""]) -@test isequal(rsplit(str, ".:."; limit=5), ["a", "ba.", ".cba.:", "dcba", ""]) -@test isequal(rsplit(str, ".:."; limit=6), ["a", "ba.", ".cba.:", "dcba", ""]) +@testset "replace" begin + @test replace("\u2202", '*', '\0') == "\u2202" + + @test replace("foobar", 'o', '0') == "f00bar" + @test replace("foobar", 'o', '0', 1) == "f0obar" + @test replace("foobar", 'o', "") == "fbar" + @test replace("foobar", 'o', "", 1) == "fobar" + @test replace("foobar", 'f', 'F') == "Foobar" + @test replace("foobar", 'r', 'R') == "foobaR" + + @test replace("foofoofoo", "foo", "bar") == "barbarbar" + @test replace("foobarfoo", "foo", "baz") == "bazbarbaz" + @test replace("barfoofoo", "foo", "baz") == "barbazbaz" + + @test replace("", "", "") == "" + @test replace("", "", "x") == "x" + @test replace("", "x", "y") == "" + + @test replace("abcd", "", "^") == "^a^b^c^d^" + @test replace("abcd", "b", "^") == "a^cd" + @test replace("abcd", r"b?", "^") == "^a^c^d^" + @test replace("abcd", r"b+", "^") == "a^cd" + @test replace("abcd", r"b?c?", "^") == "^a^d^" + @test replace("abcd", r"[bc]?", "^") == "^a^^d^" + + @test replace("foobarfoo", r"(fo|ba)", "xx") == "xxoxxrxxo" + @test replace("foobarfoo", r"(foo|ba)", "bar") == "barbarrbar" + + @test replace("foobar", 'o', 'ø') == "føøbar" + @test replace("foobar", 'o', 'ø', 1) == "føobar" + @test replace("føøbar", 'ø', 'o') == "foobar" + @test replace("føøbar", 'ø', 'o', 1) == "foøbar" + @test replace("føøbar", 'ø', 'ö') == "fööbar" + @test replace("føøbar", 'ø', 'ö', 1) == "föøbar" + @test replace("føøbar", 'ø', "") == "fbar" + @test replace("føøbar", 'ø', "", 1) == "føbar" + @test replace("føøbar", 'f', 'F') == "Føøbar" + @test replace("ḟøøbar", 'ḟ', 'F') == "Føøbar" + @test replace("føøbar", 'f', 'Ḟ') == "Ḟøøbar" + @test replace("ḟøøbar", 'ḟ', 'Ḟ') == "Ḟøøbar" + @test replace("føøbar", 'r', 'R') == "føøbaR" + @test replace("føøbaṙ", 'ṙ', 'R') == "føøbaR" + @test replace("føøbar", 'r', 'Ṙ') == "føøbaṘ" + @test replace("føøbaṙ", 'ṙ', 'Ṙ') == "føøbaṘ" + + @test replace("ḟøøḟøøḟøø", "ḟøø", "bar") == "barbarbar" + @test replace("ḟøøbarḟøø", "ḟøø", "baz") == "bazbarbaz" + @test replace("barḟøøḟøø", "ḟøø", "baz") == "barbazbaz" + + @test replace("foofoofoo", "foo", "ƀäṙ") == "ƀäṙƀäṙƀäṙ" + @test replace("fooƀäṙfoo", "foo", "baz") == "bazƀäṙbaz" + @test replace("ƀäṙfoofoo", "foo", "baz") == "ƀäṙbazbaz" + + @test replace("foofoofoo", "foo", "bar") == "barbarbar" + @test replace("foobarfoo", "foo", "ƀäż") == "ƀäżbarƀäż" + @test replace("barfoofoo", "foo", "ƀäż") == "barƀäżƀäż" + + @test replace("ḟøøḟøøḟøø", "ḟøø", "ƀäṙ") == "ƀäṙƀäṙƀäṙ" + @test replace("ḟøøƀäṙḟøø", "ḟøø", "baz") == "bazƀäṙbaz" + @test replace("ƀäṙḟøøḟøø", "ḟøø", "baz") == "ƀäṙbazbaz" + + @test replace("ḟøøḟøøḟøø", "ḟøø", "bar") == "barbarbar" + @test replace("ḟøøbarḟøø", "ḟøø", "ƀäż") == "ƀäżbarƀäż" + @test replace("barḟøøḟøø", "ḟøø", "ƀäż") == "barƀäżƀäż" + + @test replace("ḟøøḟøøḟøø", "ḟøø", "ƀäṙ") == "ƀäṙƀäṙƀäṙ" + @test replace("ḟøøƀäṙḟøø", "ḟøø", "ƀäż") == "ƀäżƀäṙƀäż" + @test replace("ƀäṙḟøøḟøø", "ḟøø", "ƀäż") == "ƀäṙƀäżƀäż" + + @test replace("", "", "ẍ") == "ẍ" + @test replace("", "ẍ", "ÿ") == "" + + @test replace("äƀçđ", "", "π") == "πäπƀπçπđπ" + @test replace("äƀçđ", "ƀ", "π") == "äπçđ" + @test replace("äƀçđ", r"ƀ?", "π") == "πäπçπđπ" + @test replace("äƀçđ", r"ƀ+", "π") == "äπçđ" + @test replace("äƀçđ", r"ƀ?ç?", "π") == "πäπđπ" + @test replace("äƀçđ", r"[ƀç]?", "π") == "πäππđπ" + + @test replace("foobarfoo", r"(fo|ba)", "ẍẍ") == "ẍẍoẍẍrẍẍo" + + @test replace("ḟøøbarḟøø", r"(ḟø|ba)", "xx") == "xxøxxrxxø" + @test replace("ḟøøbarḟøø", r"(ḟøø|ba)", "bar") == "barbarrbar" + + @test replace("fooƀäṙfoo", r"(fo|ƀä)", "xx") == "xxoxxṙxxo" + @test replace("fooƀäṙfoo", r"(foo|ƀä)", "ƀäṙ") == "ƀäṙƀäṙṙƀäṙ" + + @test replace("ḟøøƀäṙḟøø", r"(ḟø|ƀä)", "xx") == "xxøxxṙxxø" + @test replace("ḟøøƀäṙḟøø", r"(ḟøø|ƀä)", "ƀäṙ") == "ƀäṙƀäṙṙƀäṙ" + + @test replace("foo", "oo", uppercase) == "fOO" + + # Issue 13332 + @test replace("abc", 'b', 2.1) == "a2.1c" + + # test replace with a count for String and GenericString + # check that replace is a no-op if count==0 + for s in ["aaa", Base.Test.GenericString("aaa")] + # @test replace("aaa", 'a', 'z', 0) == "aaa" # enable when undeprecated + @test replace(s, 'a', 'z', 1) == "zaa" + @test replace(s, 'a', 'z', 2) == "zza" + @test replace(s, 'a', 'z', 3) == "zzz" + @test replace(s, 'a', 'z', 4) == "zzz" + @test replace(s, 'a', 'z', typemax(Int)) == "zzz" + @test replace(s, 'a', 'z') == "zzz" + end end -# zero-width splits -@test isequal(rsplit("", ""), [""]) - -@test isequal(split("", ""), [""]) -@test isequal(split("", r""), [""]) -@test isequal(split("abc", ""), ["a","b","c"]) -@test isequal(split("abc", r""), ["a","b","c"]) -@test isequal(split("abcd", r"b?"), ["a","c","d"]) -@test isequal(split("abcd", r"b*"), ["a","c","d"]) -@test isequal(split("abcd", r"b+"), ["a","cd"]) -@test isequal(split("abcd", r"b?c?"), ["a","d"]) -@test isequal(split("abcd", r"[bc]?"), ["a","","d"]) -@test isequal(split("abcd", r"a*"), ["","b","c","d"]) -@test isequal(split("abcd", r"a+"), ["","bcd"]) -@test isequal(split("abcd", r"d*"), ["a","b","c",""]) -@test isequal(split("abcd", r"d+"), ["abc",""]) -@test isequal(split("abcd", r"[ad]?"), ["","b","c",""]) - -# replace -@test replace("\u2202", '*', '\0') == "\u2202" - -@test replace("foobar", 'o', '0') == "f00bar" -@test replace("foobar", 'o', '0', 1) == "f0obar" -@test replace("foobar", 'o', "") == "fbar" -@test replace("foobar", 'o', "", 1) == "fobar" -@test replace("foobar", 'f', 'F') == "Foobar" -@test replace("foobar", 'r', 'R') == "foobaR" - -@test replace("foofoofoo", "foo", "bar") == "barbarbar" -@test replace("foobarfoo", "foo", "baz") == "bazbarbaz" -@test replace("barfoofoo", "foo", "baz") == "barbazbaz" - -@test replace("", "", "") == "" -@test replace("", "", "x") == "x" -@test replace("", "x", "y") == "" - -@test replace("abcd", "", "^") == "^a^b^c^d^" -@test replace("abcd", "b", "^") == "a^cd" -@test replace("abcd", r"b?", "^") == "^a^c^d^" -@test replace("abcd", r"b+", "^") == "a^cd" -@test replace("abcd", r"b?c?", "^") == "^a^d^" -@test replace("abcd", r"[bc]?", "^") == "^a^^d^" - -@test replace("foobarfoo", r"(fo|ba)", "xx") == "xxoxxrxxo" -@test replace("foobarfoo", r"(foo|ba)", "bar") == "barbarrbar" - -@test replace("foobar", 'o', 'ø') == "føøbar" -@test replace("foobar", 'o', 'ø', 1) == "føobar" -@test replace("føøbar", 'ø', 'o') == "foobar" -@test replace("føøbar", 'ø', 'o', 1) == "foøbar" -@test replace("føøbar", 'ø', 'ö') == "fööbar" -@test replace("føøbar", 'ø', 'ö', 1) == "föøbar" -@test replace("føøbar", 'ø', "") == "fbar" -@test replace("føøbar", 'ø', "", 1) == "føbar" -@test replace("føøbar", 'f', 'F') == "Føøbar" -@test replace("ḟøøbar", 'ḟ', 'F') == "Føøbar" -@test replace("føøbar", 'f', 'Ḟ') == "Ḟøøbar" -@test replace("ḟøøbar", 'ḟ', 'Ḟ') == "Ḟøøbar" -@test replace("føøbar", 'r', 'R') == "føøbaR" -@test replace("føøbaṙ", 'ṙ', 'R') == "føøbaR" -@test replace("føøbar", 'r', 'Ṙ') == "føøbaṘ" -@test replace("føøbaṙ", 'ṙ', 'Ṙ') == "føøbaṘ" - -@test replace("ḟøøḟøøḟøø", "ḟøø", "bar") == "barbarbar" -@test replace("ḟøøbarḟøø", "ḟøø", "baz") == "bazbarbaz" -@test replace("barḟøøḟøø", "ḟøø", "baz") == "barbazbaz" - -@test replace("foofoofoo", "foo", "ƀäṙ") == "ƀäṙƀäṙƀäṙ" -@test replace("fooƀäṙfoo", "foo", "baz") == "bazƀäṙbaz" -@test replace("ƀäṙfoofoo", "foo", "baz") == "ƀäṙbazbaz" - -@test replace("foofoofoo", "foo", "bar") == "barbarbar" -@test replace("foobarfoo", "foo", "ƀäż") == "ƀäżbarƀäż" -@test replace("barfoofoo", "foo", "ƀäż") == "barƀäżƀäż" - -@test replace("ḟøøḟøøḟøø", "ḟøø", "ƀäṙ") == "ƀäṙƀäṙƀäṙ" -@test replace("ḟøøƀäṙḟøø", "ḟøø", "baz") == "bazƀäṙbaz" -@test replace("ƀäṙḟøøḟøø", "ḟøø", "baz") == "ƀäṙbazbaz" - -@test replace("ḟøøḟøøḟøø", "ḟøø", "bar") == "barbarbar" -@test replace("ḟøøbarḟøø", "ḟøø", "ƀäż") == "ƀäżbarƀäż" -@test replace("barḟøøḟøø", "ḟøø", "ƀäż") == "barƀäżƀäż" - -@test replace("ḟøøḟøøḟøø", "ḟøø", "ƀäṙ") == "ƀäṙƀäṙƀäṙ" -@test replace("ḟøøƀäṙḟøø", "ḟøø", "ƀäż") == "ƀäżƀäṙƀäż" -@test replace("ƀäṙḟøøḟøø", "ḟøø", "ƀäż") == "ƀäṙƀäżƀäż" - -@test replace("", "", "ẍ") == "ẍ" -@test replace("", "ẍ", "ÿ") == "" - -@test replace("äƀçđ", "", "π") == "πäπƀπçπđπ" -@test replace("äƀçđ", "ƀ", "π") == "äπçđ" -@test replace("äƀçđ", r"ƀ?", "π") == "πäπçπđπ" -@test replace("äƀçđ", r"ƀ+", "π") == "äπçđ" -@test replace("äƀçđ", r"ƀ?ç?", "π") == "πäπđπ" -@test replace("äƀçđ", r"[ƀç]?", "π") == "πäππđπ" - -@test replace("foobarfoo", r"(fo|ba)", "ẍẍ") == "ẍẍoẍẍrẍẍo" - -@test replace("ḟøøbarḟøø", r"(ḟø|ba)", "xx") == "xxøxxrxxø" -@test replace("ḟøøbarḟøø", r"(ḟøø|ba)", "bar") == "barbarrbar" - -@test replace("fooƀäṙfoo", r"(fo|ƀä)", "xx") == "xxoxxṙxxo" -@test replace("fooƀäṙfoo", r"(foo|ƀä)", "ƀäṙ") == "ƀäṙƀäṙṙƀäṙ" - -@test replace("ḟøøƀäṙḟøø", r"(ḟø|ƀä)", "xx") == "xxøxxṙxxø" -@test replace("ḟøøƀäṙḟøø", r"(ḟøø|ƀä)", "ƀäṙ") == "ƀäṙƀäṙṙƀäṙ" - -@test replace("foo", "oo", uppercase) == "fOO" - -# Issue 13332 -@test replace("abc", 'b', 2.1) == "a2.1c" - -# test replace with a count for String and GenericString -# check that replace is a no-op if count==0 -for s in ["aaa", Base.Test.GenericString("aaa")] - # @test replace("aaa", 'a', 'z', 0) == "aaa" # enable when undeprecated - @test replace(s, 'a', 'z', 1) == "zaa" - @test replace(s, 'a', 'z', 2) == "zza" - @test replace(s, 'a', 'z', 3) == "zzz" - @test replace(s, 'a', 'z', 4) == "zzz" - @test replace(s, 'a', 'z', typemax(Int)) == "zzz" - @test replace(s, 'a', 'z') == "zzz" +@testset "chomp/chop" begin + @test chomp("foo\n") == "foo" + @test chomp("fo∀\n") == "fo∀" + @test chomp("fo∀") == "fo∀" + @test chop("fooε") == "foo" + @test chop("foεo") == "foε" + @test chop("∃∃∃∃") == "∃∃∃" + @test isa(chomp("foo"), SubString) + @test isa(chop("foo"), SubString) end -# chomp/chop -@test chomp("foo\n") == "foo" -@test chomp("fo∀\n") == "fo∀" -@test chomp("fo∀") == "fo∀" -@test chop("fooε") == "foo" -@test chop("foεo") == "foε" -@test chop("∃∃∃∃") == "∃∃∃" -@test isa(chomp("foo"), SubString) -@test isa(chop("foo"), SubString) - -# bytes2hex and hex2bytes -hex_str = "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592" -bin_val = hex2bytes(hex_str) - -@test div(length(hex_str), 2) == length(bin_val) -@test hex_str == bytes2hex(bin_val) - -bin_val = hex2bytes("07bf") -@test bin_val[1] == 7 -@test bin_val[2] == 191 -@test typeof(bin_val) == Array{UInt8, 1} -@test length(bin_val) == 2 - -# all valid hex chars -@test "0123456789abcdefabcdef" == bytes2hex(hex2bytes("0123456789abcdefABCDEF")) - -# odd size -@test_throws ArgumentError hex2bytes("0123456789abcdefABCDEF0") - -#non-hex characters -@test_throws ArgumentError hex2bytes("0123456789abcdefABCDEFGH") - -@testset "Issue 23161" begin - arr = b"0123456789abcdefABCDEF" - arr1 = Vector{UInt8}(length(arr) >> 1) - @test hex2bytes!(arr1, arr) === arr1 # check in-place - @test "0123456789abcdefabcdef" == bytes2hex(arr1) - @test hex2bytes("0123456789abcdefABCDEF") == hex2bytes(arr) - @test_throws ArgumentError hex2bytes!(arr1, b"") # incorrect arr1 length - @test hex2bytes(b"") == UInt8[] - @test hex2bytes(view(b"012345",1:6)) == UInt8[0x01,0x23,0x45] - @test begin - s = view(b"012345ab",1:6) - d = view(zeros(UInt8, 10),1:3) - hex2bytes!(d,s) == UInt8[0x01,0x23,0x45] - end +@testset "bytes2hex and hex2bytes" begin + hex_str = "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592" + bin_val = hex2bytes(hex_str) + + @test div(length(hex_str), 2) == length(bin_val) + @test hex_str == bytes2hex(bin_val) + + bin_val = hex2bytes("07bf") + @test bin_val[1] == 7 + @test bin_val[2] == 191 + @test typeof(bin_val) == Array{UInt8, 1} + @test length(bin_val) == 2 + + # all valid hex chars + @test "0123456789abcdefabcdef" == bytes2hex(hex2bytes("0123456789abcdefABCDEF")) + # odd size - @test_throws ArgumentError hex2bytes(b"0123456789abcdefABCDEF0") + @test_throws ArgumentError hex2bytes("0123456789abcdefABCDEF0") #non-hex characters - @test_throws ArgumentError hex2bytes(b"0123456789abcdefABCDEFGH") + @test_throws ArgumentError hex2bytes("0123456789abcdefABCDEFGH") + + @testset "Issue 23161" begin + arr = b"0123456789abcdefABCDEF" + arr1 = Vector{UInt8}(length(arr) >> 1) + @test hex2bytes!(arr1, arr) === arr1 # check in-place + @test "0123456789abcdefabcdef" == bytes2hex(arr1) + @test hex2bytes("0123456789abcdefABCDEF") == hex2bytes(arr) + @test_throws ArgumentError hex2bytes!(arr1, b"") # incorrect arr1 length + @test hex2bytes(b"") == UInt8[] + @test hex2bytes(view(b"012345",1:6)) == UInt8[0x01,0x23,0x45] + @test begin + s = view(b"012345ab",1:6) + d = view(zeros(UInt8, 10),1:3) + hex2bytes!(d,s) == UInt8[0x01,0x23,0x45] + end + # odd size + @test_throws ArgumentError hex2bytes(b"0123456789abcdefABCDEF0") + + #non-hex characters + @test_throws ArgumentError hex2bytes(b"0123456789abcdefABCDEFGH") + end end diff --git a/test/subarray.jl b/test/subarray.jl index 150f7e99ec022..890a25e3fd417 100644 --- a/test/subarray.jl +++ b/test/subarray.jl @@ -132,8 +132,8 @@ function _test_mixed(@nospecialize(A), @nospecialize(B)) m = size(A, 1) n = size(A, 2) isgood = true - for j = 1:n, i = 1:m - if A[i,j] != B[i,j] + for J in CartesianRange(size(A)[2:end]), i in 1:m + if A[i,J] != B[i,J] isgood = false break end @@ -149,16 +149,29 @@ end function test_bounds(@nospecialize(A)) @test_throws BoundsError A[0] @test_throws BoundsError A[end+1] - @test_throws BoundsError A[1, 0] - @test_throws BoundsError A[1, end+1] - @test_throws BoundsError A[1, 1, 0] - @test_throws BoundsError A[1, 1, end+1] - @test_throws BoundsError A[0, 1] - @test_throws BoundsError A[end+1, 1] - @test_throws BoundsError A[0, 1, 1] - @test_throws BoundsError A[end+1, 1, 1] - @test_throws BoundsError A[1, 0, 1] - @test_throws BoundsError A[1, end+1, 1] + trailing2 = ntuple(x->1, max(ndims(A)-2, 0)) + trailing3 = ntuple(x->1, max(ndims(A)-3, 0)) + @test_throws BoundsError A[1, 0, trailing2...] + @test_throws BoundsError A[1, end+1, trailing2...] + @test_throws BoundsError A[1, 1, 0, trailing3...] + @test_throws BoundsError A[1, 1, end+1, trailing3...] + @test_throws BoundsError A[0, 1, trailing2...] + @test_throws BoundsError A[end+1, 1, trailing2...] + @test_throws BoundsError A[0, 1, 1, trailing3...] + @test_throws BoundsError A[end+1, 1, 1, trailing3...] + @test_throws BoundsError A[1, 0, 1, trailing3...] + @test_throws BoundsError A[1, end+1, 1, trailing3...] + # TODO: PLI (re-enable after 0.7) + # @test_throws BoundsError A[1, 0] + # @test_throws BoundsError A[1, end+1] + # @test_throws BoundsError A[1, 1, 0] + # @test_throws BoundsError A[1, 1, end+1] + # @test_throws BoundsError A[0, 1] + # @test_throws BoundsError A[end+1, 1] + # @test_throws BoundsError A[0, 1, 1] + # @test_throws BoundsError A[end+1, 1, 1] + # @test_throws BoundsError A[1, 0, 1] + # @test_throws BoundsError A[1, end+1, 1] end function dim_break_linindex(I) @@ -223,12 +236,25 @@ end # indexN is a cartesian index, indexNN is a linear index for 2 dimensions, and indexNNN is a linear index for 3 dimensions function runviews(SB::AbstractArray, indexN, indexNN, indexNNN) + @assert ndims(SB) > 2 + for i3 in indexN, i2 in indexN, i1 in indexN + runsubarraytests(SB, i1, i2, i3, ntuple(x->1, max(ndims(SB)-3, 0))...) + end + for i2 in indexN, i1 in indexN + runsubarraytests(SB, i1, i2, ntuple(x->1, max(ndims(SB)-2, 0))...) + end + for i1 in indexNNN + runsubarraytests(SB, i1) + end +end + +function runviews(SB::AbstractArray{T, 3} where T, indexN, indexNN, indexNNN) @assert ndims(SB) > 2 for i3 in indexN, i2 in indexN, i1 in indexN runsubarraytests(SB, i1, i2, i3) end for i2 in indexN, i1 in indexN - runsubarraytests(SB, i1, i2) + runsubarraytests(SB, i1, i2, 1) end for i1 in indexNNN runsubarraytests(SB, i1) @@ -477,7 +503,7 @@ Y = 4:-1:1 @test X[1:end] == @.(@view X[1:end]) # test compatibility of @. and @view @test X[1:end-3] == @view X[1:end-3] @test X[1:end,2,2] == @view X[1:end,2,2] -@test X[1,1:end-2] == @view X[1,1:end-2] +@test X[1,1:end-2,1] == @view X[1,1:end-2,1] @test X[1,2,1:end-2] == @view X[1,2,1:end-2] @test X[1,2,Y[2:end]] == @view X[1,2,Y[2:end]] @test X[1:end,2,Y[2:end]] == @view X[1:end,2,Y[2:end]] @@ -528,7 +554,6 @@ end @test X[1:end] == @views X[1:end] @test X[1:end-3] == @views X[1:end-3] @test X[1:end,2,2] == @views X[1:end,2,2] -@test X[1,1:end-2] == @views X[1,1:end-2] @test X[1,2,1:end-2] == @views X[1,2,1:end-2] @test X[1,2,Y[2:end]] == @views X[1,2,Y[2:end]] @test X[1:end,2,Y[2:end]] == @views X[1:end,2,Y[2:end]] diff --git a/test/unicode/utf8proc.jl b/test/unicode/utf8proc.jl index 2c70ed21291f8..ef05772064913 100644 --- a/test/unicode/utf8proc.jl +++ b/test/unicode/utf8proc.jl @@ -266,16 +266,17 @@ end end @testset "#3721, #6939 up-to-date character widths" begin - @test charwidth('\U1f355') == 2 - @test strwidth("\U1f355") == 2 - @test strwidth(GenericString("\U1f355")) == 2 - @test strwidth("\U1f355\u0302") == 2 - @test strwidth(GenericString("\U1f355\u0302")) == 2 + @test textwidth("") == 0 + @test textwidth('\U1f355') == 2 + @test textwidth("\U1f355") == 2 + @test textwidth(GenericString("\U1f355")) == 2 + @test textwidth("\U1f355\u0302") == 2 + @test textwidth(GenericString("\U1f355\u0302")) == 2 end @testset "#10958 handling of embedded NUL chars" begin @test length("\0w") == length("\0α") == 2 - @test strwidth("\0w") == strwidth("\0α") == 1 + @test textwidth("\0w") == textwidth("\0α") == 1 @test normalize_string("\0W", casefold=true) == "\0w" end