Skip to content

Commit b2f464e

Browse files
committed
docs: always add summary information for extended documentation
When a binding does not have documentation, we return the result of `summarize(::Binding)` as fallback documentation. While this summary itself is often quite useful, it is not displayed when the binding does have documentation, e.g. `help?> Base.Compiler.InferenceResult`: ```julia help?> Base.Compiler.InferenceResult ... result::InferenceResult A type that represents the result of running type inference on a chunk of code. There are two constructor available: • InferenceResult(mi::MethodInstance, [𝕃::AbstractLattice]) for regular inference, without extended lattice information included in result.argtypes. • InferenceResult(mi::MethodInstance, argtypes::Vector{Any}, overridden_by_const::BitVector) for constant inference, with extended lattice information included in result.argtypes. ``` I believe allowing summary information to be included someway could be useful, so this commit modifies the behavior in extended documentation mode to include the result of `summarize` at the end of the documentation. Now ``help?> ?Base.Compiler.InferenceResult`` would look like: ```julia help?> ?Base.Compiler.InferenceResult ... # the same contents as what rendered in the regular documentation mode ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Extended information found for private binding Compiler.InferenceResult. Summary ≡≡≡≡≡≡≡ mutable struct Compiler.InferenceResult Fields ≡≡≡≡≡≡ linfo :: Core.MethodInstance argtypes :: Vector{Any} overridden_by_const :: Union{Nothing, BitVector} result :: Any exc_result :: Any src :: Any valid_worlds :: Compiler.WorldRange ipo_effects :: Compiler.Effects effects :: Compiler.Effects analysis_results :: Compiler.AnalysisResults is_src_volatile :: Bool ci :: Core.CodeInstance ci_as_edge :: Core.CodeInstance ``` Additionally, this commit refactors the internal implementation of the help mode in REPL.jl to improve extensibility for future enhancements.
1 parent 0625b3a commit b2f464e

File tree

5 files changed

+146
-114
lines changed

5 files changed

+146
-114
lines changed

base/boot.jl

+5-2
Original file line numberDiff line numberDiff line change
@@ -708,10 +708,13 @@ using Core: CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnN
708708
end # module IR
709709

710710
# docsystem basics
711+
function _ensure_doc_expr(@nospecialize(x), mod::Module, __source__::LineNumberNode)
712+
isa(x, Expr) && x.head === :escape && return x
713+
return Expr(:escape, Expr(:var"hygienic-scope", x, mod, __source__))
714+
end
711715
macro doc(x...)
712716
docex = atdoc(__source__, __module__, x...)
713-
isa(docex, Expr) && docex.head === :escape && return docex
714-
return Expr(:escape, Expr(:var"hygienic-scope", docex, typeof(atdoc).name.module, __source__))
717+
return _ensure_doc_expr(docex, typeof(atdoc).name.module, __source__)
715718
end
716719
macro __doc__(x)
717720
return Expr(:escape, Expr(:block, Expr(:meta, :doc), x))

stdlib/REPL/src/docview.jl

+98-77
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,14 @@ function helpmode(io::IO, line::AbstractString, mod::Module=Main)
2929
end
3030
helpmode(line::AbstractString, mod::Module=Main) = helpmode(stdout, line, mod)
3131

32-
# A hack to make the line entered at the REPL available at trimdocs without
33-
# passing the string through the entire mechanism.
34-
const extended_help_on = Ref{Any}(nothing)
35-
3632
function _helpmode(io::IO, line::AbstractString, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing)
3733
line = strip(line)
3834
ternary_operator_help = (line == "?" || line == "?:")
3935
if startswith(line, '?') && !ternary_operator_help
4036
line = line[2:end]
41-
extended_help_on[] = nothing
42-
brief = false
37+
extended_help = true
4338
else
44-
extended_help_on[] = line
45-
brief = true
39+
extended_help = String(line)::String
4640
end
4741
# interpret anything starting with # or #= as asking for help on comments
4842
if startswith(line, "#")
@@ -72,7 +66,7 @@ function _helpmode(io::IO, line::AbstractString, mod::Module=Main, internal_acce
7266
end
7367
# the following must call repl(io, expr) via the @repl macro
7468
# so that the resulting expressions are evaluated in the Base.Docs namespace
75-
:($REPL.@repl $io $expr $brief $mod $internal_accesses)
69+
:($REPL.@repl $io $expr $extended_help $mod $internal_accesses)
7670
end
7771
_helpmode(line::AbstractString, mod::Module=Main) = _helpmode(stdout, line, mod)
7872

@@ -123,51 +117,35 @@ struct Message # For direct messages to the terminal
123117
msg # AbstractString
124118
fmt # keywords to `printstyled`
125119
end
126-
Message(msg) = Message(msg, ())
127-
128120
function Markdown.term(io::IO, msg::Message, columns)
129121
printstyled(io, msg.msg; msg.fmt...)
130122
end
131123

132-
trimdocs(doc, brief::Bool) = doc
133-
134-
function trimdocs(md::Markdown.MD, brief::Bool)
135-
brief || return md
136-
md, trimmed = _trimdocs(md, brief)
137-
if trimmed
138-
line = extended_help_on[]
139-
line = isa(line, AbstractString) ? line : ""
140-
push!(md.content, Message("Extended help is available with `??$line`", (color=Base.info_color(), bold=true)))
141-
end
124+
add_extended_help!(doc, ::String) = doc
125+
function add_extended_help!(md::Markdown.MD, extended_help::String)
126+
msg = Message("Extended help is available with `help?> ?$extended_help`", (color=Base.info_color(), bold=true))
127+
push!(md.content, msg)
142128
return md
143129
end
144130

145-
function _trimdocs(md::Markdown.MD, brief::Bool)
146-
content, trimmed = [], false
131+
trimdocs(doc) = doc
132+
function trimdocs(md::Markdown.MD)
133+
content = []
147134
for c in md.content
148135
if isa(c, Markdown.Header{1}) && isa(c.text, AbstractArray) && !isempty(c.text)
149136
item = c.text[1]
150137
if isa(item, AbstractString) &&
151138
lowercase(item) ("extended help",
152139
"extended documentation",
153140
"extended docs")
154-
trimmed = true
155141
break
156142
end
157143
end
158-
c, trm = _trimdocs(c, brief)
159-
trimmed |= trm
160-
push!(content, c)
144+
push!(content, trimdocs(c))
161145
end
162-
return Markdown.MD(content, md.meta), trimmed
146+
return Markdown.MD(content, md.meta)
163147
end
164148

165-
_trimdocs(md, brief::Bool) = md, false
166-
167-
168-
is_tuple(expr) = false
169-
is_tuple(expr::Expr) = expr.head == :tuple
170-
171149
struct Logged{F}
172150
f::F
173151
mod::Module
@@ -180,7 +158,7 @@ end
180158
(la::Logged)(args...) = la.f(args...)
181159

182160
function log_nonpublic_access(expr::Expr, mod::Module, internal_access::Set{Pair{Module,Symbol}})
183-
if expr.head === :. && length(expr.args) == 2 && !is_tuple(expr.args[2])
161+
if expr.head === :. && length(expr.args) == 2 && !Meta.isexpr(expr.args[2], :tuple)
184162
Expr(:call, Logged(getproperty, mod, internal_access), log_nonpublic_access.(expr.args, (mod,), (internal_access,))...)
185163
elseif expr.head === :call && expr.args[1] === Base.Docs.Binding
186164
Expr(:call, Logged(Base.Docs.Binding, mod, internal_access), log_nonpublic_access.(expr.args[2:end], (mod,), (internal_access,))...)
@@ -206,10 +184,17 @@ function insert_internal_warning(other, internal_access::Set{Pair{Module,Symbol}
206184
other
207185
end
208186

209-
function doc(binding::Binding, sig::Type = Union{})
187+
function doc(binding::Binding, @nospecialize(sig::Type = Union{});
188+
extended_help::Union{Bool,String}=false)
210189
if defined(binding)
211190
result = getdoc(resolve(binding), sig)
212-
result === nothing || return result
191+
if result !== nothing
192+
if extended_help isa String
193+
result = trimdocs(result)
194+
add_extended_help!(result, extended_help)
195+
end
196+
return result
197+
end
213198
end
214199
results, groups = DocStr[], MultiDoc[]
215200
# Lookup `binding` and `sig` for matches in all modules of the docsystem.
@@ -230,7 +215,7 @@ function doc(binding::Binding, sig::Type = Union{})
230215
# `Binding`, otherwise if it's not an alias then we generate a summary for the
231216
# `binding` and display that to the user instead.
232217
alias = aliasof(binding)
233-
alias == binding ? summarize(alias, sig) : doc(alias, sig)
218+
return alias == binding ? summarize(alias, sig) : doc(alias, sig; extended_help)
234219
else
235220
# There was at least one match for `binding` while searching. If there weren't any
236221
# matches for `sig` then we concatenate *all* the docs from the matching `Binding`s.
@@ -240,7 +225,17 @@ function doc(binding::Binding, sig::Type = Union{})
240225
end
241226
end
242227
# Get parsed docs and concatenate them.
243-
md = catdoc(mapany(parsedoc, results)...)
228+
docs = mapany(parsedoc, results)
229+
if extended_help isa String # in the "brief" documentation mode
230+
md = catdoc(docs...)
231+
md = trimdocs(md)
232+
add_extended_help!(md, extended_help)
233+
elseif extended_help # in the extended documentation mode
234+
push!(docs, summarize(binding, sig; extended=true)) # add summary docs too in extended help mode
235+
md = catdoc(docs...)
236+
else # in the simple documentation mode (e.g., called from `@doc`)
237+
md = catdoc(docs...)
238+
end
244239
# Save metadata in the generated markdown.
245240
if isa(md, Markdown.MD)
246241
md.meta[:results] = results
@@ -252,11 +247,30 @@ function doc(binding::Binding, sig::Type = Union{})
252247
end
253248

254249
# Some additional convenience `doc` methods that take objects rather than `Binding`s.
255-
doc(obj::UnionAll) = doc(Base.unwrap_unionall(obj))
256-
doc(object, sig::Type = Union{}) = doc(aliasof(object, typeof(object)), sig)
257-
doc(object, sig...) = doc(object, Tuple{sig...})
250+
doc(obj::UnionAll; kws...) = doc(Base.unwrap_unionall(obj); kws...)
251+
doc(object, @nospecialize(sig::Type=Union{}); kws...) = doc(aliasof(object, typeof(object)), sig; kws...)
252+
253+
function lookup_doc_ex(@nospecialize(x); extended_help::Union{Bool,String}=false)
254+
docex = lookup_doc(x; extended_help)
255+
return Core._ensure_doc_expr(docex, @__MODULE__, LineNumberNode(@__LINE__, @__FILE__))
256+
end
257+
258+
function lookup_doc(@nospecialize(x); extended_help::Union{Bool,String}=false)
259+
docs = _lookup_doc(x, extended_help)
260+
if isfield(x)
261+
return :(let
262+
T, field = $(esc(x.args[1])), $(esc(x.args[2]))
263+
if T isa DataType
264+
fielddoc(T, field)
265+
else
266+
$docs
267+
end
268+
end)
269+
end
270+
return docs
271+
end
258272

259-
function lookup_doc(ex)
273+
function _lookup_doc(@nospecialize(ex), extended_help::Union{Bool,String}=false)
260274
if isa(ex, Expr) && ex.head !== :(.) && Base.isoperator(ex.head)
261275
# handle syntactic operators, e.g. +=, ::, .=
262276
ex = ex.head
@@ -266,7 +280,7 @@ function lookup_doc(ex)
266280
elseif Meta.isexpr(ex, :incomplete)
267281
return :($(Markdown.md"No documentation found."))
268282
elseif !isa(ex, Expr) && !isa(ex, Symbol)
269-
return :($(doc)($(typeof)($(esc(ex)))))
283+
return :($doc(typeof($(esc(ex))); extended_help=$extended_help))
270284
end
271285
if isa(ex, Symbol) && Base.isoperator(ex)
272286
str = string(ex)
@@ -287,30 +301,42 @@ function lookup_doc(ex)
287301
binding = esc(bindingexpr(namify(ex)))
288302
if isexpr(ex, :call) || isexpr(ex, :macrocall) || isexpr(ex, :where)
289303
sig = esc(signature(ex))
290-
:($(doc)($binding, $sig))
304+
:($doc($binding, $sig; extended_help=$extended_help))
291305
else
292-
:($(doc)($binding))
306+
:($doc($binding; extended_help=$extended_help))
293307
end
294308
end
295309

296310
# Object Summaries.
297311
# =================
298312

299-
function summarize(binding::Binding, sig)
313+
function summarize(binding::Binding, @nospecialize(sig); extended::Bool=false)
300314
io = IOBuffer()
301315
if defined(binding)
302316
binding_res = resolve(binding)
303317
if !isa(binding_res, Module)
304-
varstr = "$(binding.mod).$(binding.var)"
318+
varstr = "`$(binding.mod).$(binding.var)`"
305319
if Base.ispublic(binding.mod, binding.var)
306-
println(io, "No documentation found for public binding `$varstr`.\n")
320+
if extended
321+
println(io, "Extended information found for public binding $varstr.\n")
322+
else
323+
println(io, "No documentation found for public binding $varstr.\n")
324+
end
307325
else
308-
println(io, "No documentation found for private binding `$varstr`.\n")
326+
if extended
327+
println(io, "Extended information found for private binding $varstr.\n")
328+
else
329+
println(io, "No documentation found for private binding $varstr.\n")
330+
end
309331
end
310332
end
311333
summarize(io, binding_res, binding)
312334
else
313-
println(io, "No documentation found.\n")
335+
if extended
336+
println(io, "Extended information found.\n")
337+
else
338+
println(io, "No documentation found.\n")
339+
end
314340
quot = any(isspace, sprint(print, binding)) ? "'" : ""
315341
if Base.isbindingresolved(binding.mod, binding.var)
316342
println(io, "Binding ", quot, "`", binding, "`", quot, " exists, but has not been assigned a value.")
@@ -559,10 +585,17 @@ function repl_latex(io::IO, s0::String)
559585
end
560586
repl_latex(s::String) = repl_latex(stdout, s)
561587

562-
macro repl(ex, brief::Bool=false, mod::Module=Main) repl(ex; brief, mod) end
563-
macro repl(io, ex, brief, mod, internal_accesses) repl(io, ex; brief, mod, internal_accesses) end
588+
macro repl(ex, extended_help=false, mod=Main)
589+
repl(ex; extended_help, mod)
590+
end
591+
macro repl(io, ex, extended_help, mod, internal_accesses)
592+
repl(io, ex; extended_help, mod, internal_accesses)
593+
end
564594

565-
function repl(io::IO, s::Symbol; brief::Bool=true, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing)
595+
function repl(io::IO, s::Symbol;
596+
extended_help::Union{Bool,String}=false,
597+
mod::Module=Main,
598+
internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing)
566599
str = string(s)
567600
quote
568601
repl_latex($io, $str)
@@ -571,19 +604,20 @@ function repl(io::IO, s::Symbol; brief::Bool=true, mod::Module=Main, internal_ac
571604
# n.b. we call isdefined for the side-effect of resolving the binding, if possible
572605
:(repl_corrections($io, $str, $mod))
573606
end)
574-
$(_repl(s, brief, mod, internal_accesses))
607+
$(_repl(s; extended_help, mod, internal_accesses))
575608
end
576609
end
577610
isregex(x) = isexpr(x, :macrocall, 3) && x.args[1] === Symbol("@r_str") && !isempty(x.args[3])
578611

579-
repl(io::IO, ex::Expr; brief::Bool=true, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing) = isregex(ex) ? :(apropos($io, $ex)) : _repl(ex, brief, mod, internal_accesses)
580-
repl(io::IO, str::AbstractString; brief::Bool=true, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing) = :(apropos($io, $str))
581-
repl(io::IO, other; brief::Bool=true, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing) = esc(:(@doc $other)) # TODO: track internal_accesses
582-
#repl(io::IO, other) = lookup_doc(other) # TODO
612+
repl(io::IO, ex::Expr; kws...) = isregex(ex) ? :(apropos($io, $ex)) : _repl(ex; kws...)
613+
repl(io::IO, str::AbstractString; _...) = :(apropos($io, $str))
614+
repl(io::IO, other; extended_help::Union{Bool,String}=false, _...) = lookup_doc_ex(other; extended_help)
615+
repl(x; kws...) = repl(stdout, x; kws...)
583616

584-
repl(x; brief::Bool=true, mod::Module=Main) = repl(stdout, x; brief, mod)
585-
586-
function _repl(x, brief::Bool=true, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing)
617+
function _repl(x;
618+
extended_help::Union{Bool,String}=false,
619+
mod::Module=Main,
620+
internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing)
587621
if isexpr(x, :call)
588622
x = x::Expr
589623
# determine the types of the values
@@ -636,21 +670,9 @@ function _repl(x, brief::Bool=true, mod::Module=Main, internal_accesses::Union{N
636670
x.args = Any[x.args[1], Expr(:parameters, kwargs...), pargs...]
637671
end
638672
end
639-
#docs = lookup_doc(x) # TODO
640-
docs = esc(:(@doc $x))
641-
docs = if isfield(x)
642-
quote
643-
if isa($(esc(x.args[1])), DataType)
644-
fielddoc($(esc(x.args[1])), $(esc(x.args[2])))
645-
else
646-
$docs
647-
end
648-
end
649-
else
650-
docs
651-
end
673+
docs = lookup_doc_ex(x; extended_help)
652674
docs = log_nonpublic_access(macroexpand(mod, docs), mod, internal_accesses)
653-
:(REPL.trimdocs($docs, $brief))
675+
return docs
654676
end
655677

656678
"""
@@ -686,7 +708,6 @@ end
686708
# As with the additional `doc` methods, this converts an object to a `Binding` first.
687709
fielddoc(object, field::Symbol) = fielddoc(aliasof(object, typeof(object)), field)
688710

689-
690711
# Search & Rescue
691712
# Utilities for correcting user mistakes and (eventually)
692713
# doing full documentation searches from the repl.

stdlib/REPL/test/docview.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ module InternalWarningsTests
132132
@test docstring("A.B") == "No docstring or readme file found for public module `$(@__MODULE__).A.B`.\n\n# Public names\n\n`e`\n"
133133
@test startswith(docstring("A.B.c"), prefix(["A.B.c"]))
134134
@test startswith(docstring("A.B.d"), prefix(["A.B.d"]))
135-
@test docstring("A.B.e") == "e is 6\n"
135+
@test occursin("e is 6\n", docstring("A.B.e"))
136136
@test startswith(docstring("A.B2"), prefix(["A.B2"]))
137137
@test startswith(docstring("A.B2.C"), prefix(["A.B2", "A.B2.C"]))
138138
@test startswith(docstring("A.B2.C.d"), prefix(["A.B2", "A.B2.C", "A.B2.C.d"]))

0 commit comments

Comments
 (0)