Skip to content

Commit

Permalink
improve AbstractPipe docs and IOContext handling as an AbstractPipe (J…
Browse files Browse the repository at this point in the history
…uliaLang#52768)

Following some complains that `AbstractPipe` did not mention the
functions that comprise its API, I have updated that and some other
related details of its subtypes.

1. Mention the expected API surface for AbstractPipe
2. Expand the docs for Pipe as well
3. And add better support for explicit type parameters to IOContext.
This gives more options to users, such as creating an explicitly dynamic
`IOContext{IO}(io)` to avoid excess specialization. Any explicitly set
parameter should now be inherited by future `IOContext` constructions
around it, rather than always directly adopting the `typeof(io.io)` type
instead as the new parameter.
  • Loading branch information
vtjnash committed Jan 8, 2024
1 parent edd2223 commit d07de16
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 21 deletions.
5 changes: 3 additions & 2 deletions base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ function showerror(io::IO, ex::MethodError)
f_is_function = true
end
print(io, "no method matching ")
iob = IOContext(IOBuffer(), io) # for type abbreviation as in #49795; some, like `convert(T, x)`, should not abbreviate
buf = IOBuffer()
iob = IOContext(buf, io) # for type abbreviation as in #49795; some, like `convert(T, x)`, should not abbreviate
show_signature_function(iob, isa(f, Type) ? Type{f} : typeof(f))
print(iob, "(")
for (i, typ) in enumerate(arg_types_param)
Expand All @@ -293,7 +294,7 @@ function showerror(io::IO, ex::MethodError)
end
end
print(iob, ")")
str = String(take!(unwrapcontext(iob)[1]))
str = String(take!(buf))
str = type_limited_string_from_context(io, str)
print(io, str)
end
Expand Down
9 changes: 8 additions & 1 deletion base/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,14 @@ end
"""
AbstractPipe
`AbstractPipe` is the abstract supertype for IO pipes that provide for communication between processes.
`AbstractPipe` is an abstract supertype that exists for the convenience of creating
pass-through wrappers for other IO objects, so that you only need to implement the
additional methods relevant to your type. A subtype only needs to implement one or both of
these methods:
struct P <: AbstractPipe; ...; end
pipe_reader(io::P) = io.out
pipe_writer(io::P) = io.in
If `pipe isa AbstractPipe`, it must obey the following interface:
Expand Down
35 changes: 22 additions & 13 deletions base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -294,35 +294,43 @@ struct IOContext{IO_t <: IO} <: AbstractPipe
dict::ImmutableDict{Symbol, Any}

function IOContext{IO_t}(io::IO_t, dict::ImmutableDict{Symbol, Any}) where IO_t<:IO
@assert !(IO_t <: IOContext) "Cannot create `IOContext` from another `IOContext`."
io isa IOContext && (io = io.io) # implicitly unwrap, since the io.dict field is not useful anymore, and could confuse pipe_reader consumers
return new(io, dict)
end
end

# (Note that TTY and TTYTerminal io types have a :color property.)
unwrapcontext(io::IO) = io, get(io,:color,false) ? ImmutableDict{Symbol,Any}(:color, true) : ImmutableDict{Symbol,Any}()
unwrapcontext(io::IOContext) = io.io, io.dict
# (Note that TTY and TTYTerminal io types have an implied :color property.)
ioproperties(io::IO) = get(io, :color, false) ? ImmutableDict{Symbol,Any}(:color, true) : ImmutableDict{Symbol,Any}()
ioproperties(io::IOContext) = io.dict
# these can probably be deprecated, but there is a use in the ecosystem for them
unwrapcontext(io::IO) = (io,)
unwrapcontext(io::IOContext) = (io.io,)

function IOContext(io::IO, dict::ImmutableDict)
io0 = unwrapcontext(io)[1]
IOContext{typeof(io0)}(io0, dict)
function IOContext(io::IO, dict::ImmutableDict{Symbol, Any})
return IOContext{typeof(io)}(io, dict)
end

convert(::Type{IOContext}, io::IO) = IOContext(unwrapcontext(io)...)::IOContext
function IOContext(io::IOContext, dict::ImmutableDict{Symbol, Any})
return typeof(io)(io.io, dict)
end


convert(::Type{IOContext}, io::IOContext) = io
convert(::Type{IOContext}, io::IO) = IOContext(io, ioproperties(io))::IOContext

IOContext(io::IO) = convert(IOContext, io)

function IOContext(io::IO, KV::Pair)
io0, d = unwrapcontext(io)
IOContext(io0, ImmutableDict{Symbol,Any}(d, KV[1], KV[2]))
d = ioproperties(io)
return IOContext(io, ImmutableDict{Symbol,Any}(d, KV[1], KV[2]))
end

"""
IOContext(io::IO, context::IOContext)
Create an `IOContext` that wraps an alternate `IO` but inherits the properties of `context`.
"""
IOContext(io::IO, context::IO) = IOContext(unwrapcontext(io)[1], unwrapcontext(context)[2])
IOContext(io::IO, context::IO) = IOContext(io, ioproperties(context))

"""
IOContext(io::IO, KV::Pair...)
Expand Down Expand Up @@ -2548,7 +2556,8 @@ function show_tuple_as_call(out::IO, name::Symbol, sig::Type;
return
end
tv = Any[]
io = IOContext(IOBuffer(), out)
buf = IOBuffer()
io = IOContext(buf, out)
env_io = io
while isa(sig, UnionAll)
push!(tv, sig.var)
Expand Down Expand Up @@ -2591,7 +2600,7 @@ function show_tuple_as_call(out::IO, name::Symbol, sig::Type;
end
print_within_stacktrace(io, ")", bold=true)
show_method_params(io, tv)
str = String(take!(unwrapcontext(io)[1]))
str = String(take!(buf))
str = type_limited_string_from_context(out, str)
print(out, str)
nothing
Expand Down
19 changes: 14 additions & 5 deletions base/stream.jl
Original file line number Diff line number Diff line change
Expand Up @@ -743,19 +743,28 @@ mutable struct Pipe <: AbstractPipe
end

"""
Construct an uninitialized Pipe object.
Pipe()
The appropriate end of the pipe will be automatically initialized if
the object is used in process spawning. This can be useful to easily
obtain references in process pipelines, e.g.:
Construct an uninitialized Pipe object, especially for IO communication between multiple processes.
The appropriate end of the pipe will be automatically initialized if the object is used in
process spawning. This can be useful to easily obtain references in process pipelines, e.g.:
```
julia> err = Pipe()
# After this `err` will be initialized and you may read `foo`'s
# stderr from the `err` pipe.
# stderr from the `err` pipe, or pass `err` to other pipelines.
julia> run(pipeline(pipeline(`foo`, stderr=err), `cat`), wait=false)
# Now destroy the write half of the pipe, so that the read half will get EOF
julia> closewrite(err)
julia> read(err, String)
"stderr messages"
```
See also `Base.link_pipe!`.
"""
Pipe() = Pipe(PipeEndpoint(), PipeEndpoint())
pipe_reader(p::Pipe) = p.out
Expand Down

0 comments on commit d07de16

Please sign in to comment.