Skip to content

Commit d07de16

Browse files
authored
improve AbstractPipe docs and IOContext handling as an AbstractPipe (#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.
1 parent edd2223 commit d07de16

File tree

4 files changed

+47
-21
lines changed

4 files changed

+47
-21
lines changed

base/errorshow.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,8 @@ function showerror(io::IO, ex::MethodError)
278278
f_is_function = true
279279
end
280280
print(io, "no method matching ")
281-
iob = IOContext(IOBuffer(), io) # for type abbreviation as in #49795; some, like `convert(T, x)`, should not abbreviate
281+
buf = IOBuffer()
282+
iob = IOContext(buf, io) # for type abbreviation as in #49795; some, like `convert(T, x)`, should not abbreviate
282283
show_signature_function(iob, isa(f, Type) ? Type{f} : typeof(f))
283284
print(iob, "(")
284285
for (i, typ) in enumerate(arg_types_param)
@@ -293,7 +294,7 @@ function showerror(io::IO, ex::MethodError)
293294
end
294295
end
295296
print(iob, ")")
296-
str = String(take!(unwrapcontext(iob)[1]))
297+
str = String(take!(buf))
297298
str = type_limited_string_from_context(io, str)
298299
print(io, str)
299300
end

base/io.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,14 @@ end
414414
"""
415415
AbstractPipe
416416
417-
`AbstractPipe` is the abstract supertype for IO pipes that provide for communication between processes.
417+
`AbstractPipe` is an abstract supertype that exists for the convenience of creating
418+
pass-through wrappers for other IO objects, so that you only need to implement the
419+
additional methods relevant to your type. A subtype only needs to implement one or both of
420+
these methods:
421+
422+
struct P <: AbstractPipe; ...; end
423+
pipe_reader(io::P) = io.out
424+
pipe_writer(io::P) = io.in
418425
419426
If `pipe isa AbstractPipe`, it must obey the following interface:
420427

base/show.jl

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -294,35 +294,43 @@ struct IOContext{IO_t <: IO} <: AbstractPipe
294294
dict::ImmutableDict{Symbol, Any}
295295

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

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

306-
function IOContext(io::IO, dict::ImmutableDict)
307-
io0 = unwrapcontext(io)[1]
308-
IOContext{typeof(io0)}(io0, dict)
309+
function IOContext(io::IO, dict::ImmutableDict{Symbol, Any})
310+
return IOContext{typeof(io)}(io, dict)
309311
end
310312

311-
convert(::Type{IOContext}, io::IO) = IOContext(unwrapcontext(io)...)::IOContext
313+
function IOContext(io::IOContext, dict::ImmutableDict{Symbol, Any})
314+
return typeof(io)(io.io, dict)
315+
end
316+
317+
318+
convert(::Type{IOContext}, io::IOContext) = io
319+
convert(::Type{IOContext}, io::IO) = IOContext(io, ioproperties(io))::IOContext
312320

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

315323
function IOContext(io::IO, KV::Pair)
316-
io0, d = unwrapcontext(io)
317-
IOContext(io0, ImmutableDict{Symbol,Any}(d, KV[1], KV[2]))
324+
d = ioproperties(io)
325+
return IOContext(io, ImmutableDict{Symbol,Any}(d, KV[1], KV[2]))
318326
end
319327

320328
"""
321329
IOContext(io::IO, context::IOContext)
322330
323331
Create an `IOContext` that wraps an alternate `IO` but inherits the properties of `context`.
324332
"""
325-
IOContext(io::IO, context::IO) = IOContext(unwrapcontext(io)[1], unwrapcontext(context)[2])
333+
IOContext(io::IO, context::IO) = IOContext(io, ioproperties(context))
326334

327335
"""
328336
IOContext(io::IO, KV::Pair...)
@@ -2548,7 +2556,8 @@ function show_tuple_as_call(out::IO, name::Symbol, sig::Type;
25482556
return
25492557
end
25502558
tv = Any[]
2551-
io = IOContext(IOBuffer(), out)
2559+
buf = IOBuffer()
2560+
io = IOContext(buf, out)
25522561
env_io = io
25532562
while isa(sig, UnionAll)
25542563
push!(tv, sig.var)
@@ -2591,7 +2600,7 @@ function show_tuple_as_call(out::IO, name::Symbol, sig::Type;
25912600
end
25922601
print_within_stacktrace(io, ")", bold=true)
25932602
show_method_params(io, tv)
2594-
str = String(take!(unwrapcontext(io)[1]))
2603+
str = String(take!(buf))
25952604
str = type_limited_string_from_context(out, str)
25962605
print(out, str)
25972606
nothing

base/stream.jl

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -743,19 +743,28 @@ mutable struct Pipe <: AbstractPipe
743743
end
744744

745745
"""
746-
Construct an uninitialized Pipe object.
746+
Pipe()
747747
748-
The appropriate end of the pipe will be automatically initialized if
749-
the object is used in process spawning. This can be useful to easily
750-
obtain references in process pipelines, e.g.:
748+
Construct an uninitialized Pipe object, especially for IO communication between multiple processes.
749+
750+
The appropriate end of the pipe will be automatically initialized if the object is used in
751+
process spawning. This can be useful to easily obtain references in process pipelines, e.g.:
751752
752753
```
753754
julia> err = Pipe()
754755
755756
# After this `err` will be initialized and you may read `foo`'s
756-
# stderr from the `err` pipe.
757+
# stderr from the `err` pipe, or pass `err` to other pipelines.
757758
julia> run(pipeline(pipeline(`foo`, stderr=err), `cat`), wait=false)
759+
760+
# Now destroy the write half of the pipe, so that the read half will get EOF
761+
julia> closewrite(err)
762+
763+
julia> read(err, String)
764+
"stderr messages"
758765
```
766+
767+
See also `Base.link_pipe!`.
759768
"""
760769
Pipe() = Pipe(PipeEndpoint(), PipeEndpoint())
761770
pipe_reader(p::Pipe) = p.out

0 commit comments

Comments
 (0)