Skip to content

Commit

Permalink
fix #54664, make at-doc not change when REPL is loaded
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffBezanson committed Sep 26, 2024
1 parent a31a880 commit f4e920a
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 107 deletions.
50 changes: 34 additions & 16 deletions base/docs/Docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ docstr(object, data = Dict{Symbol,Any}()) = _docstr(object, data)
_docstr(vec::Core.SimpleVector, data::Dict{Symbol,Any}) = DocStr(vec, nothing, data)
_docstr(str::AbstractString, data::Dict{Symbol,Any}) = DocStr(Core.svec(str), nothing, data)
_docstr(object, data::Dict{Symbol,Any}) = DocStr(Core.svec(), object, data)
_docstr(strs::Vector{DocStr}, data::Dict{Symbol,Any}) = error("Cannot apply multiple docstrings to a single object.")

_docstr(doc::DocStr, data::Dict{Symbol,Any}) = (doc.data = merge(data, doc.data); doc)

Expand Down Expand Up @@ -563,33 +564,51 @@ isquotedmacrocall(@nospecialize x) =
isbasicdoc(@nospecialize x) = isexpr(x, :.) || isa(x, Union{QuoteNode, Symbol})
is_signature(@nospecialize x) = isexpr(x, :call) || (isexpr(x, :(::), 2) && isexpr(x.args[1], :call)) || isexpr(x, :where)

function _doc(binding::Binding, sig::Type = Union{})
function doc(binding::Binding, sig::Type = Union{})
if defined(binding)
result = getdoc(resolve(binding), sig)
result === nothing || return result
end
# Lookup first match for `binding` and `sig` in all modules of the docsystem.
results, groups = DocStr[], MultiDoc[]
# Lookup `binding` and `sig` for matches in all modules of the docsystem.
for mod in modules
dict = meta(mod; autoinit=false)
isnothing(dict) && continue
if haskey(dict, binding)
multidoc = dict[binding]
push!(groups, multidoc)
for msig in multidoc.order
sig <: msig && return multidoc.docs[msig]
sig <: msig && push!(results, multidoc.docs[msig])
end
# if no matching signatures, return first
if !isempty(multidoc.docs)
return first(values(multidoc.docs))
end
end
if isempty(groups)
# When no `MultiDoc`s are found that match `binding` then we check whether `binding`
# is an alias of some other `Binding`. When it is we then re-run `doc` with that
# `Binding`, otherwise we return nothing and the front end will decide what to
# display, usually a brief description of the binding.
alias = aliasof(binding)
alias == binding ? nothing : doc(alias, sig)
else
# There was at least one match for `binding` while searching. If there weren't any
# matches for `sig` then we concatenate *all* the docs from the matching `Binding`s.
if isempty(results)
for group in groups, each in group.order
push!(results, group.docs[each])
end
end
if length(results) == 1
return results[1]
else
return results
end
end
return nothing
end

# Some additional convenience `doc` methods that take objects rather than `Binding`s.
_doc(obj::UnionAll) = _doc(Base.unwrap_unionall(obj))
_doc(object, sig::Type = Union{}) = _doc(aliasof(object, typeof(object)), sig)
_doc(object, sig...) = _doc(object, Tuple{sig...})
doc(obj::UnionAll) = doc(Base.unwrap_unionall(obj))
doc(object, sig::Type = Union{}) = doc(aliasof(object, typeof(object)), sig)
doc(object, sig...) = doc(object, Tuple{sig...})

function simple_lookup_doc(ex)
if isa(ex, Expr) && ex.head !== :(.) && Base.isoperator(ex.head)
Expand All @@ -599,24 +618,23 @@ function simple_lookup_doc(ex)
if haskey(keywords, ex)
return keywords[ex]
elseif !isa(ex, Expr) && !isa(ex, Symbol)
return :($(_doc)($(typeof)($(esc(ex)))))
return :($(doc)($(typeof)($(esc(ex)))))
elseif isexpr(ex, :incomplete)
return nothing
end
binding = esc(bindingexpr(namify(ex)))
if isexpr(ex, :call) || isexpr(ex, :macrocall) || isexpr(ex, :where)
sig = esc(signature(ex))
:($(_doc)($binding, $sig))
:($(doc)($binding, $sig))
else
:($(_doc)($binding))
:($(doc)($binding))
end
end

function docm(source::LineNumberNode, mod::Module, ex)
@nospecialize ex
if isexpr(ex, :->) && length(ex.args) > 1
return docm(source, mod, ex.args...)
elseif (REPL = Base.REPL_MODULE_REF[]) !== Base
# TODO: this is a shim to continue to allow `@doc` for looking up docstrings
return invokelatest(REPL.lookup_doc, ex)
else
return simple_lookup_doc(ex)
end
Expand Down
62 changes: 21 additions & 41 deletions stdlib/REPL/src/docview.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ using Markdown
using Base.Docs: catdoc, modules, DocStr, Binding, MultiDoc, keywords, isfield, namify, bindingexpr,
defined, resolve, getdoc, meta, aliasof, signature

import Base.Docs: doc, formatdoc, parsedoc, apropos
import Base.Docs: formatdoc, parsedoc, apropos

using Base: with_output_color, mapany, isdeprecated, isexported

Expand Down Expand Up @@ -206,55 +206,31 @@ function insert_internal_warning(other, internal_access::Set{Pair{Module,Symbol}
other
end

function doc(binding::Binding, sig::Type = Union{})
function doc_as_md(binding::Binding, sig::Type = Union{})
if defined(binding)
result = getdoc(resolve(binding), sig)
result === nothing || return result
end
results, groups = DocStr[], MultiDoc[]
# Lookup `binding` and `sig` for matches in all modules of the docsystem.
for mod in modules
dict = meta(mod; autoinit=false)
isnothing(dict) && continue
if haskey(dict, binding)
multidoc = dict[binding]
push!(groups, multidoc)
for msig in multidoc.order
sig <: msig && push!(results, multidoc.docs[msig])
end
end
end
if isempty(groups)
# When no `MultiDoc`s are found that match `binding` then we check whether `binding`
# is an alias of some other `Binding`. When it is we then re-run `doc` with that
# `Binding`, otherwise if it's not an alias then we generate a summary for the
# `binding` and display that to the user instead.
alias = aliasof(binding)
alias == binding ? summarize(alias, sig) : doc(alias, sig)
result = Base.Docs.doc(binding, sig)
if result === nothing
return summarize(binding, sig)
else
# There was at least one match for `binding` while searching. If there weren't any
# matches for `sig` then we concatenate *all* the docs from the matching `Binding`s.
if isempty(results)
for group in groups, each in group.order
push!(results, group.docs[each])
end
if !(result isa Vector)
result = [result]
end
# Get parsed docs and concatenate them.
md = catdoc(mapany(parsedoc, results)...)
md = catdoc(mapany(parsedoc, result)...)
# Save metadata in the generated markdown.
if isa(md, Markdown.MD)
md.meta[:results] = results
md.meta[:results] = result
md.meta[:binding] = binding
md.meta[:typesig] = sig
end
return md
end
end

# Some additional convenience `doc` methods that take objects rather than `Binding`s.
doc(obj::UnionAll) = doc(Base.unwrap_unionall(obj))
doc(object, sig::Type = Union{}) = doc(aliasof(object, typeof(object)), sig)
doc(object, sig...) = doc(object, Tuple{sig...})
doc_as_md(obj::UnionAll) = doc_as_md(Base.unwrap_unionall(obj))
doc_as_md(object, sig::Type = Union{}) = doc_as_md(aliasof(object, typeof(object)), sig)

function lookup_doc(ex)
if isa(ex, Expr) && ex.head !== :(.) && Base.isoperator(ex.head)
Expand All @@ -266,7 +242,7 @@ function lookup_doc(ex)
elseif Meta.isexpr(ex, :incomplete)
return :($(Markdown.md"No documentation found."))
elseif !isa(ex, Expr) && !isa(ex, Symbol)
return :($(doc)($(typeof)($(esc(ex)))))
return :($(doc_as_md)($(typeof)($(esc(ex)))))
end
if isa(ex, Symbol) && Base.isoperator(ex)
str = string(ex)
Expand All @@ -287,12 +263,16 @@ function lookup_doc(ex)
binding = esc(bindingexpr(namify(ex)))
if isexpr(ex, :call) || isexpr(ex, :macrocall) || isexpr(ex, :where)
sig = esc(signature(ex))
:($(doc)($binding, $sig))
:($(doc_as_md)($binding, $sig))
else
:($(doc)($binding))
:($(doc_as_md)($binding))
end
end

macro showdoc(ex)
lookup_doc(ex)
end

# Object Summaries.
# =================

Expand Down Expand Up @@ -570,7 +550,7 @@ isregex(x) = isexpr(x, :macrocall, 3) && x.args[1] === Symbol("@r_str") && !isem

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)
repl(io::IO, str::AbstractString; brief::Bool=true, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing) = :(apropos($io, $str))
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
repl(io::IO, other; brief::Bool=true, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing) = esc(:(REPL.@showdoc $other)) # TODO: track internal_accesses
#repl(io::IO, other) = lookup_doc(other) # TODO

repl(x; brief::Bool=true, mod::Module=Main) = repl(stdout, x; brief, mod)
Expand Down Expand Up @@ -629,7 +609,7 @@ function _repl(x, brief::Bool=true, mod::Module=Main, internal_accesses::Union{N
end
end
#docs = lookup_doc(x) # TODO
docs = esc(:(@doc $x))
docs = esc(:($REPL.@showdoc $x))
docs = if isfield(x)
quote
if isa($(esc(x.args[1])), DataType)
Expand Down Expand Up @@ -983,7 +963,7 @@ apropos(io::IO, string) = apropos(io, Regex("\\Q$string", "i"))
function apropos(io::IO, needle::Regex)
for mod in modules
# Module doc might be in README.md instead of the META dict
docsearch(doc(mod), needle) && println(io, mod)
docsearch(doc_as_md(mod), needle) && println(io, mod)
dict = meta(mod; autoinit=false)
isnothing(dict) && continue
for (k, v) in dict
Expand Down
Loading

0 comments on commit f4e920a

Please sign in to comment.