From dfc6941131a13aa0eea3079ae3c1509b9dfe71db Mon Sep 17 00:00:00 2001 From: Michael Hatherly Date: Sat, 27 Feb 2016 18:36:53 +0200 Subject: [PATCH] Refactor docsystem internals Structure of a module's `#meta` dict now has the following layout in all cases (with the exception of keywords): - each module's `#meta` `ObjectIdDict` has keys of type `Binding` and values of type `MultiDoc`. - each `MultiDoc` stores a collection of `DocStr` objects representing a single docstring each. These are ordered based on their order of definition rather than the current `type_morespecific` approach. - `DocStr`s store the raw text of a docstring and lazily parse this to a `Markdown.MD` object when requested. By not parsing every docstring we also make some space savings in the `sys.*` files. By keying `#meta` by `Binding` in every case the rest of the logic in `doc!` and `doc` becomes a lot more straightforward. `at-doc` expression building has also been simplified. Additionally, the "summaries" displayed when no documentation can be found have been refactored and simplified. --- base/docs/Docs.jl | 529 +++++++++++++++++++---------------------- base/docs/basedocs.jl | 14 +- base/docs/bindings.jl | 36 +-- base/docs/bootstrap.jl | 12 +- base/docs/utils.jl | 18 +- test/docs.jl | 108 +++++---- 6 files changed, 344 insertions(+), 373 deletions(-) diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index 2abc9fef05ab8..88773ad2aca0b 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -77,120 +77,6 @@ function initmeta(m::Module = current_module()) nothing end -"`doc!(obj, data)`: Associate the metadata `data` with `obj`." -function doc!(obj, data) - meta()[obj] = data -end - -function get_obj_meta(obj) - for mod in modules - if haskey(meta(mod), obj) - return meta(mod)[obj] - end - end - nothing -end - -"`doc(obj)`: Get the metadata associated with `obj`." -function doc(obj) - get_obj_meta(obj) -end - -function write_lambda_signature(io::IO, lam::LambdaInfo) - ex = Base.uncompressed_ast(lam) - write(io, '(') - nargs = length(ex.args[1]) - for (i,arg) in enumerate(ex.args[1]) - i==1 && continue - if isa(arg,Expr) - argname, argtype = arg.args - else - argname, argtype = arg, :Any - end - if argtype === :Any || argtype === :ANY - write(io, argname) - elseif isa(argtype,Expr) && argtype.head === :... && - (argtype.args[end] === :Any || argtype.args[end] === :ANY) - write(io, argname, "...") - else - write(io, argname, "::", argtype) - end - i < nargs && write(io, ',') - end - write(io, ')') - return io -end - -function functionsummary(func::Function) - io = IOBuffer() - write(io, "```julia\n") - print(io, methods(func)) - # TODO jb/functions better summary for closures? - write(io, "\n```") - return Markdown.parse(takebuf_string(io)) -end - -function qualified_name(b::Binding) - if b.mod === Main - string(b.var) - else - join((b.mod, b.var), '.') - end -end - -function doc(b::Binding) - d = get_obj_meta(b) - if d !== nothing - return d - end - if !(b.defined) - return Markdown.parse(""" - - No documentation found. - - Binding `$(qualified_name(b))` does not exist. - """) - end - v = getfield(b.mod,b.var) - d = doc(v) - if d !== nothing - return d - end - if startswith(string(b.var),'@') - # check to see if the binding var is a macro - d = catdoc(Markdown.parse(""" - - No documentation found. - - `$(qualified_name(b))` is a macro. - """), functionsummary(v)) - elseif isa(v,Function) - d = catdoc(Markdown.parse(""" - - No documentation found. - - `$(qualified_name(b))` is a `Function`. - """), functionsummary(v)) - elseif isa(v,DataType) - d = catdoc(Markdown.parse(""" - - No documentation found. - - """), typesummary(v)) - else - T = typeof(v) - d = catdoc(Markdown.parse(""" - - No documentation found. - - `$(qualified_name(b))` is of type `$T`: - """), typesummary(typeof(v))) - end - return d -end - -# Function / Method support - function signature(expr::Expr) if isexpr(expr, [:call, :macrocall]) sig = :(Union{Tuple{}}) @@ -206,6 +92,7 @@ function signature(expr::Expr) signature(expr.args[1]) end end +signature(other) = :(Union{}) function argtype(expr::Expr) isexpr(expr, :(::)) && return expr.args[end] @@ -223,6 +110,40 @@ typevars(::Symbol) = [] tvar(x::Expr) = :($(x.args[1]) = TypeVar($(quot(x.args[1])), $(x.args[2]), true)) tvar(s::Symbol) = :($(s) = TypeVar($(quot(s)), Any, true)) +# Docsystem types. +# ================ + +""" + Docs.DocStr + +Stores the contents of a single docstring as well as related metadata. + +Both the raw text, `.text`, and the parsed markdown, `.object`, are tracked by this type. +Parsing of the raw text is done lazily when a request is made to render the docstring, +which helps to reduce total precompiled image size. + +The `.data` fields stores several values related to the docstring, such as: path, +linenumber, source code, and fielddocs. +""" +type DocStr + text :: UTF8String + object :: Nullable{Markdown.MD} + data :: Dict{Symbol, Any} + + DocStr(text::AbstractString, data = Dict()) = new(text, Nullable(), data) + DocStr(object, data = Dict()) = new("", Nullable(object), data) +end +docexpr(args...) = Expr(:call, DocStr, args...) + +function parsedoc(d::DocStr) + if isnull(d.object) + md = Markdown.parse(d.text) + md.meta[:module] = d.data[:module] + md.meta[:path] = d.data[:path] + d.object = Nullable(md) + end + get(d.object) +end """ MultiDoc @@ -244,165 +165,185 @@ is stored as `Union{Tuple{T}, Tuple{T, Any}}`. Note: The `Function`/`DataType` object's signature is always `Union{}`. """ type MultiDoc - "Sorted (via `type_morespecific`) vector of object signatures." + "Ordered (via definition order) vector of object signatures." order::Vector{Type} "Documentation for each object. Keys are signatures." docs::ObjectIdDict - "Source `Expr` for each object. As with `.docs` the keys are signatures." - source::ObjectIdDict - "Stores the documentation for individual fields of a type." - fields::Dict{Symbol, Any} - MultiDoc() = new([], ObjectIdDict(), ObjectIdDict(), Dict()) + MultiDoc() = new(Type[], ObjectIdDict()) end -function doc!(λ::Callable, sig::ANY, docstr, expr::Expr, fields::Dict) - m = get!(meta(), λ, MultiDoc()) - if !haskey(m.docs, sig) +# Docstring registration. +# ======================= + +""" + Docs.doc!(binding, str, sig) + +Adds a new docstring `str` to the docsystem for `binding` and signature `sig`. +""" +function doc!(b::Binding, str::DocStr, sig::ANY = Union{}) + m = get!(meta(), b, MultiDoc()) + if haskey(m.docs, sig) + # We allow for docstrings to be updated, but print a warning since it is possible + # that over-writing a docstring *may* have been accidental. + warn("replacing docs for '$b :: $sig'.") + else + # The ordering of docstrings for each Binding is defined by the order in which they + # are initially added. Replacing a specific docstring does not change it's ordering. push!(m.order, sig) - sort!(m.order, lt = type_morespecific) end - m.docs[sig] = docstr - m.fields = fields - return m + m.docs[sig] = str + return b end -doc!(λ::Callable, docstr) = doc!(λ, Union{}, docstr, :(), Dict()) -doc!(λ::Callable, docstr, fields) = doc!(λ, Union{}, docstr, :(), fields) -doc!(λ::Callable, sig::ANY, docstr, expr) = doc!(λ, sig, docstr, expr, Dict()) - -type_morespecific(a::Type, b::Type) = ccall(:jl_type_morespecific, Int32, (Any,Any), a, b) > 0 +# Docstring lookup. +# ================= """ -`catdoc(xs...)`: Combine the documentation metadata `xs` into a single meta object. -""" -catdoc() = nothing -catdoc(xs...) = vcat(xs...) + Docs.doc(binding, sig) -# Type Documentation - -isdoc(s::AbstractString) = true - -isdoc(x) = isexpr(x, :string) || - (isexpr(x, :macrocall) && x.args[1] == symbol("@doc_str")) || - (isexpr(x, :call) && x.args[1] == Expr(:., Base.Markdown, QuoteNode(:doc_str))) - -dict_expr(d) = :(Dict($([:(Pair($(Expr(:quote, f)), $d)) for (f, d) in d]...))) - -function field_meta(def) - meta = Dict() - doc = nothing - for l in def.args[3].args - if isdoc(l) - doc = mdify(l) - elseif doc !== nothing && (isa(l, Symbol) || isexpr(l, :(::))) - meta[namify(l)] = doc - doc = nothing - end - end - return dict_expr(meta) -end - -function doc(obj::Base.Callable, sig::Type = Union) - sig !== Union && isempty(methods(obj, sig)) && return nothing - results, groups = [], [] - for m in modules - if haskey(meta(m), obj) - docs = meta(m)[obj] - if isa(docs, MultiDoc) - push!(groups, docs) - for msig in docs.order - if sig <: msig - push!(results, (msig, docs.docs[msig])) - end - end - else - push!(results, (Union{}, docs)) +Returns all documentation that matches both `binding` and `sig`. +""" +function doc(binding::Binding, sig::Type = Union) + results, groups = DocStr[], MultiDoc[] + # Lookup `binding` and `sig` for matches in all modules of the docsystem. + for mod in modules + dict = meta(mod) + 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 all method signatures are Union{} ( ⊥ ), concat all docstrings. - if isempty(results) - for group in groups - append!(results, [group.docs[s] for s in reverse(group.order)]) + 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 ? summarise(alias, sig) : 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 - else - sort!(results, lt = (a, b) -> type_morespecific(first(a), first(b))) - results = map(last, results) + # Get the parsed docs, concatenate, and return the result. + Markdown.MD(map(parsedoc, results)) end - catdoc(results...) -end -doc(f::Base.Callable, args::Any...) = doc(f, Tuple{args...}) - -function typesummary(T::DataType) - parts = UTF8String[ - """ - **Summary:** - ```julia - $(T.abstract ? "abstract" : T.mutable ? "type" : "immutable") $T <: $(supertype(T)) - ``` - """ - ] - if !isempty(fieldnames(T)) - pad = maximum([length(string(f)) for f in fieldnames(T)]) - fields = ["$(rpad(f, pad)) :: $(t)" for (f,t) in zip(fieldnames(T), T.types)] - push!(parts, - """ - **Fields:** - ```julia - $(join(fields, "\n")) - ``` - """) - end - if !isempty(subtypes(T)) - push!(parts, - """ - **Subtypes:** - ```julia - $(join(subtypes(T),'\n')) - ``` - """) - end - Markdown.parse(join(parts,'\n')) end -isfield(x) = isexpr(x, :.) && - (isa(x.args[1], Symbol) || isfield(x.args[1])) && - (isa(x.args[2], QuoteNode) || isexpr(x.args[2], :quote)) +# Some additional convenience `doc` methods that take objects rather than `Binding`s. +doc(object, sig::Type = Union) = doc(aliasof(object, typeof(object)), sig) +doc(object, sig...) = doc(object, Tuple{sig...}) + +""" + Docs.fielddoc(binding, field) -function fielddoc(T, k) +Returns documentation for a particular `field` of a type if it exists. +""" +function fielddoc(binding::Binding, field::Symbol) for mod in modules - docs = meta(mod) - if haskey(docs, T) && isa(docs[T], MultiDoc) - fields = docs[T].fields - if haskey(fields, k) - return fields[k] + dict = meta(mod) + if haskey(dict, binding) + multidoc = dict[binding] + if haskey(multidoc.docs, Union{}) + fields = multidoc.docs[Union{}].data[:fields] + haskey(fields, field) && return Markdown.parse(fields[field]) end end end - fields = join(["`$f`" for f in fieldnames(T)], ", ", ", and ") + fields = join(["`$f`" for f in fieldnames(resolve(binding))], ", ", ", and ") fields = isempty(fields) ? "no fields" : "fields $fields" - Markdown.parse("`$T` has $fields.") + Markdown.parse("`$(resolve(binding))` has $fields.") end -# Generic Callables +# As with the additional `doc` methods, this converts an object to a `Binding` first. +fielddoc(object, field::Symbol) = fielddoc(aliasof(object, typeof(object)), field) -# Modules +# Object Summaries. +# ================= -function doc(m::Module) - md = get_obj_meta(m) - md === nothing || return md +function summarise(binding::Binding, sig) + io = IOBuffer() + println(io, "No documentation found.\n") + if defined(binding) + summarise(io, resolve(binding), binding) + else + println(io, "Binding `", binding, "` does not exist.") + end + Markdown.parse(seekstart(io)) +end + +function summarise(io::IO, λ::Function, binding) + kind = startswith(string(binding.var), '@') ? "macro" : "`Function`" + println(io, "`", binding, "` is a ", kind, ".") + println(io, "```\n", methods(λ), "\n```") +end + +function summarise(io::IO, T::DataType, binding) + println(io, "**Summary:**") + println(io, "```") + println(io, + T.abstract ? "abstract" : T.mutable ? "type" : "immutable", + " ", T, " <: ", supertype(T) + ) + println(io, "```") + if !isempty(fieldnames(T)) + println(io, "**Fields:**") + println(io, "```") + pad = maximum(length(string(f)) for f in fieldnames(T)) + for (f, t) in zip(fieldnames(T), T.types) + println(io, rpad(f, pad), " :: ", t) + end + println(io, "```") + end + if !isempty(subtypes((T))) + println(io, "**Subtypes:**") + println(io, "```") + for t in subtypes(T) + println(io, t) + end + println(io, "```") + end +end + +function summarise(io::IO, m::Module, binding) readme = Pkg.dir(string(m), "README.md") if isfile(readme) - return Markdown.parse_file(readme) + println(io, "Displaying the `README.md` for the module instead.\n") + println(io, "---\n") + println(io, readstring(readme)) + else + println(io, "No `README.md` found for module `", m, "`.\n") end end -# Keywords +function summarise{T}(io::IO, ::T, binding) + println(io, "`", binding, "` is of type `", T, "`.\n") + summarise(io, T, binding) +end + +# Utilities. +# ========== + +""" +`catdoc(xs...)`: Combine the documentation metadata `xs` into a single meta object. +""" +catdoc() = nothing +catdoc(xs...) = vcat(xs...) + +const keywords = Dict{Symbol, DocStr}() -const keywords = Dict{Symbol,Any}() +isdoc(s::AbstractString) = true -# Usage macros +isdoc(x) = isexpr(x, :string) || + (isexpr(x, :macrocall) && x.args[1] == symbol("@doc_str")) || + (isexpr(x, :call) && x.args[1] == Expr(:., Base.Markdown, QuoteNode(:doc_str))) function unblock(ex) isexpr(ex, :block) || return ex @@ -430,40 +371,62 @@ nameof(other, ismacro) = other macroname(s::Symbol) = symbol('@', s) macroname(x::Expr) = Expr(x.head, x.args[1], macroname(x.args[end].value)) -function mdify(ex) - if isa(ex, AbstractString) || isexpr(ex, :string) - :(Markdown.doc_str($(esc(ex)), @__FILE__, current_module())) - else - esc(ex) - end -end +isfield(x) = isexpr(x, :.) && + (isa(x.args[1], Symbol) || isfield(x.args[1])) && + (isa(x.args[2], QuoteNode) || isexpr(x.args[2], :quote)) -function namedoc(meta, def, def′) - quote - $(esc(def)) - doc!($(esc(namify(def′))), $(mdify(meta))) - nothing - end -end +# @doc expression builders. +# ========================= -function funcdoc(meta, def, def′) - f = esc(namify(def′)) - quote - $(esc(def)) - doc!($f, $(esc(signature(def′))), $(mdify(meta)), $(esc(quot(def′)))) - $f +""" + Docs.metadata(expr) + +Build a `Dict` expression containing metadata captured from the expression `expr`. + +Fields that may be included in the returned `Dict`: + +- `:source`: Source code for the given `expr`. +- `:path`: String representing the file where `expr` is defined. +- `:linenumber`: Linenumber where `expr` is defined. +- `:module`: Module where the docstring is defined. +- `:fields`: `Dict` of all field docs found in `expr`. Only for concrete types. +""" +function metadata(expr) + args = [] + # Source code for the object being documented. + push!(args, :($(Pair)(:source, $(quot(expr))))) + # Filename and linenumber of the docstring. + push!(args, :($(Pair)(:path, $(Base).@__FILE__)), :($(Pair)(:linenumber, @__LINE__))) + # Module in which the docstring is defined. + push!(args, :($(Pair)(:module, $(current_module)()))) + # Field docs for concrete types. + if isexpr(expr, :type) + fields = [] + tmp = nothing + for each in expr.args[3].args + if isdoc(each) + tmp = each + elseif tmp !== nothing && (isa(each, Symbol) || isexpr(each, :(::))) + push!(fields, (namify(each), tmp)) + tmp = nothing + end + end + dict = :($(Dict)($([:($(Pair)($(quot(f)), $d)) for (f, d) in fields]...))) + push!(args, :($(Pair)(:fields, $dict))) end + :($(Dict)($(args...))) end -function typedoc(meta, def, def′) +function objectdoc(str, def, expr, sig = :(Union{})) + binding = esc(bindingexpr(namify(expr))) + docstr = esc(docexpr(str, metadata(expr))) quote $(esc(def)) - doc!($(esc(namify(def′))), $(mdify(meta)), $(field_meta(unblock(def′)))) - nothing + $(doc!)($binding, $docstr, $(esc(sig))) end end -function moddoc(meta, def, def′) +function moduledoc(meta, def, def′) name = namify(def′) docex = :(@doc $meta $name) if def == nothing @@ -480,13 +443,6 @@ function moddoc(meta, def, def′) end end -function vardoc(meta, def, name) - quote - $(esc(def)) - doc!(@var($(esc(namify(name)))), $(mdify(meta))) - end -end - function multidoc(meta, ex, define) out = Expr(:toplevel) out.args = [:(@doc($meta, $obj, $define)) for obj in ex.args] @@ -563,7 +519,7 @@ isquotedmacrocall(x) = isa(x.args[1], QuoteNode) && isexpr(x.args[1].value, :macrocall, 1) # Simple expressions / atoms the may be documented. -isbasicdoc(x) = isexpr(x, :.) || isa(x, Union{QuoteNode, Symbol, Real}) +isbasicdoc(x) = isexpr(x, :.) || isa(x, Union{QuoteNode, Symbol}) function docm(meta, ex, define = true) # Some documented expressions may be decorated with macro calls which obscure the actual @@ -584,9 +540,9 @@ function docm(meta, ex, define = true) # function f end # f(...) # - isexpr(x, FUNC_HEADS) && isexpr(x.args[1], :call) ? funcdoc(meta, def, x) : - isexpr(x, :function) && !isexpr(x.args[1], :call) ? namedoc(meta, def, x) : - isexpr(x, :call) ? funcdoc(meta, nothing, x) : + isexpr(x, FUNC_HEADS) && isexpr(x.args[1], :call) ? objectdoc(meta, def, x, signature(x)) : + isexpr(x, :function) && !isexpr(x.args[1], :call) ? objectdoc(meta, def, x) : + isexpr(x, :call) ? objectdoc(meta, nothing, x, signature(x)) : # Type definitions. # @@ -594,8 +550,7 @@ function docm(meta, ex, define = true) # abstract T # bitstype N T # - isexpr(x, :type) ? typedoc(meta, def, x) : - isexpr(x, [:abstract, :bitstype]) ? namedoc(meta, def, x) : + isexpr(x, [:type, :abstract, :bitstype]) ? objectdoc(meta, def, x) : # "Bindings". Names that resolve to objects with different names, ie. # @@ -604,12 +559,12 @@ function docm(meta, ex, define = true) # T = S # global T = S # - isexpr(x, BINDING_HEADS) && !isexpr(x.args[1], :call) ? vardoc(meta, def, x) : + isexpr(x, BINDING_HEADS) && !isexpr(x.args[1], :call) ? objectdoc(meta, def, x) : # Quoted macrocall syntax. `:@time` / `:(Base.@time)`. - isquotedmacrocall(x) ? namedoc(meta, def, x) : + isquotedmacrocall(x) ? objectdoc(meta, def, x) : # Modules and baremodules. - isexpr(x, :module) ? moddoc(meta, def, x) : + isexpr(x, :module) ? moduledoc(meta, def, x) : # Document several expressions with the same docstring. `a, b, c`. isexpr(x, :tuple) ? multidoc(meta, x, define) : # Errors generated by calling `macroexpand` are passed back to the call site. @@ -617,7 +572,7 @@ function docm(meta, ex, define = true) # When documenting macro-generated code we look for embedded `@__doc__` calls. __doc__!(meta, x, define) ? esc(x) : # Any "basic" expression such as a bare function or module name or numeric literal. - isbasicdoc(x) ? namedoc(meta, nothing, x) : + isbasicdoc(x) ? objectdoc(meta, nothing, x) : # All other expressions are undocumentable and should be handled on a case-by-case basis # with `@__doc__`. Unbound string literals are also undocumentable since they cannot be @@ -640,15 +595,17 @@ function docm(ex) if isexpr(ex, :->) docm(ex.args...) elseif haskey(keywords, ex) - keywords[ex] - elseif isexpr(ex, [:call, :macrocall]) - :(doc($(esc(namify(ex))), $(esc(signature(ex))))) - elseif isexpr(ex, :quote, 1) && isexpr(ex.args[1], :macrocall, 1) - :(doc(@var($(esc(namify(ex)))))) - elseif isexpr(ex, :.) || isa(ex, Symbol) - :(doc(@var($(esc(ex))))) + parsedoc(keywords[ex]) + elseif isa(ex, Union{Expr, Symbol}) + binding = esc(bindingexpr(namify(ex))) + if isexpr(ex, [:call, :macrocall]) + sig = esc(signature(ex)) + :($(doc)($binding, $sig)) + else + :($(doc)($binding)) + end else - :(doc($(esc(ex)))) + :($(doc)($(typeof)($(esc(ex))))) end end @@ -658,7 +615,7 @@ Base.DocBootstrap.setexpand!(docm) # Names are resolved relative to the Base module, so inject the ones we need there. -eval(Base, :(import .Docs: @var, doc!, doc, @doc_str)) +eval(Base, :(import .Docs: @doc_str)) Base.DocBootstrap.loaddocs() diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index a679c70f821c2..27905ac068f24 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -3,7 +3,7 @@ import .Docs: keywords macro keyword(str, first, rest...) - out = :($(keywords)[$(esc(first))] = $(Docs.mdify(str))) + out = :($(keywords)[$(esc(first))] = $(Docs.docexpr(str, Docs.metadata(:())))) for symbol in rest out = Expr(:(=), :($(keywords)[$(esc(symbol))]), out) end @@ -738,15 +738,3 @@ end """, symbol("@__LINE__") ) - -""" -0 (zero; BrE: `/ˈzɪərəʊ/` or AmE: `/ˈziːroʊ/`) is both a number and the numerical digit used -to represent that number in numerals. It fulfills a central role in mathematics as the -additive identity of the integers, real numbers, and many other algebraic structures. As a -digit, 0 is used as a placeholder in place value systems. Names for the number 0 in English -include zero, nought or (US) naught (`/ˈnɔːt/`), nil, or — in contexts where at least one -adjacent digit distinguishes it from the letter "O" — oh or o (`/ˈoʊ/`). Informal or slang -terms for zero include zilch and zip. Ought and aught (/ˈɔːt/), as well as cipher, have also -been used historically. -""" -0 diff --git a/base/docs/bindings.jl b/base/docs/bindings.jl index 4ed6a4e008069..368075b0334c3 100644 --- a/base/docs/bindings.jl +++ b/base/docs/bindings.jl @@ -5,30 +5,36 @@ export @var immutable Binding mod::Module var::Symbol - defined::Bool - - function Binding(m, v) - if !isdefined(m, v) - new(m, v, false) - else - new(Base.binding_module(m, v), v, true) - end + + function Binding(m::Module, v::Symbol) + # Normalise the binding module for module symbols so that: + # Binding(Base, :Base) === Binding(Main, :Base) + m = module_name(m) === v ? module_parent(m) : m + new(Base.binding_module(m, v), v) end end +bindingexpr(x) = Expr(:call, Binding, splitexpr(x)...) + +defined(b::Binding) = isdefined(b.mod, b.var) +resolve(b::Binding) = getfield(b.mod, b.var) + function splitexpr(x::Expr) isexpr(x, :macrocall) ? splitexpr(x.args[1]) : - isexpr(x, :.) ? (esc(x.args[1]), x.args[2]) : + isexpr(x, :.) ? (x.args[1], x.args[2]) : error("Invalid @var syntax `$x`.") end -splitexpr(s::Symbol) = :(current_module()), quot(s) +splitexpr(s::Symbol) = Expr(:call, current_module), quot(s) splitexpr(other) = error("Invalid @var syntax `$other`.") -isvar(x) = isexpr(x, [:macrocall, :.]) -isvar(::Symbol) = true - macro var(x) - :(Binding($(splitexpr(x)...))) + esc(bindingexpr(x)) end -Base.show(io::IO, x::Binding) = print(io, "$(x.mod).$(x.var)") +Base.show(io::IO, b::Binding) = b.mod === Main ? print(io, b.var) : print(io, b.mod, '.', b.var) + +aliasof(b::Binding) = defined(b) ? (a = aliasof(resolve(b), b); defined(a) ? a : b) : b +aliasof(d::DataType, b) = Binding(d.name.module, d.name.name) +aliasof(λ::Function, b) = (m = typeof(λ).name.mt; Binding(m.module, m.name)) +aliasof(m::Module, b) = Binding(m, module_name(m)) +aliasof(other, b) = b diff --git a/base/docs/bootstrap.jl b/base/docs/bootstrap.jl index f6c8d2e522aa9..191b20abc9c66 100644 --- a/base/docs/bootstrap.jl +++ b/base/docs/bootstrap.jl @@ -51,12 +51,18 @@ DocBootstrap Move all docstrings from `DocBootstrap.docs` to their module's metadata dict. """ function loaddocs() - node = docs + # To keep the ordering of docstrings consistent within the entire docsystem we need to + # reverse the contents of `docs` (a stack) before evaluating each docstring. + node = docs + stack = [] while node ≠ nothing - mod, str, obj = node.head - eval(mod, :(Base.@doc($str, $obj, false))) + push!(stack, node.head) node = node.tail end + while !isempty(stack) + mod, str, obj = pop!(stack) + eval(mod, :(Base.@doc($str, $obj, false))) + end global docs = nothing end diff --git a/base/docs/utils.jl b/base/docs/utils.jl index 889e820976d44..65c45a6411570 100644 --- a/base/docs/utils.jl +++ b/base/docs/utils.jl @@ -341,15 +341,22 @@ end ## Searching specific documentation objects function docsearch(haystack::MultiDoc, needle) - for v in values(haystack.fields) - docsearch(v, needle) && return true - end for v in values(haystack.docs) docsearch(v, needle) && return true end false end +function docsearch(haystack::DocStr, needle) + docsearch(parsedoc(haystack), needle) && return true + if haskey(haystack.data, :fields) + for doc in values(haystack.data[:fields]) + docsearch(doc, needle) && return true + end + end + false +end + ## Markdown search simply strips all markup and searches plain text version docsearch(haystack::Markdown.MD, needle) = docsearch(stripmd(haystack.content), needle) @@ -394,10 +401,7 @@ function apropos(io::IO, needle::Regex) # Module doc might be in README.md instead of the META dict docsearch(doc(mod), needle) && println(io, mod) for (k, v) in meta(mod) - (k === meta(mod) || k === mod) && continue - if docsearch(v, needle) - println(io, k) - end + docsearch(v, needle) && println(io, k) end end end diff --git a/test/docs.jl b/test/docs.jl index ce189ffcf48cc..d5e65e70480dd 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: http://julialang.org/license -import Base.Docs: meta +import Base.Docs: meta, @var, DocStr, parsedoc # Test helpers. function docstrings_equal(d1, d2) @@ -10,6 +10,7 @@ function docstrings_equal(d1, d2) writemime(io2, MIME"text/markdown"(), d2) takebuf_string(io1) == takebuf_string(io2) end +docstrings_equal(d1::DocStr, d2) = docstrings_equal(parsedoc(d1), d2) function docstring_startswith(d1, d2) io1 = IOBuffer() @@ -18,6 +19,7 @@ function docstring_startswith(d1, d2) writemime(io2, MIME"text/markdown"(), d2) startswith(takebuf_string(io1), takebuf_string(io2)) end +docstring_startswith(d1::DocStr, d2) = docstring_startswith(parsedoc(d1), d2) @doc "Doc abstract type" -> abstract C74685 <: AbstractArray @@ -160,65 +162,67 @@ function multidoc! end end -@test meta(DocsTest)[DocsTest] == doc"DocsTest" +@test docstrings_equal(@doc(DocsTest), doc"DocsTest") # Check that plain docstrings store a module reference. # https://github.com/JuliaLang/julia/pull/13017#issuecomment-138618663 -@test meta(DocsTest)[DocsTest].meta[:module] == DocsTest +@test meta(DocsTest)[@var(DocsTest)].docs[Union{}].data[:module] == DocsTest -let f = DocsTest.f +let f = @var(DocsTest.f) md = meta(DocsTest)[f] @test docstrings_equal(md.docs[Tuple{Any}], doc"f-1") @test docstrings_equal(md.docs[Tuple{Any,Any}], doc"f-2") end -let s = DocsTest.s +let s = @var(DocsTest.s) md = meta(DocsTest)[s] @test docstrings_equal(md.docs[Tuple{Any,}], doc"s-1") @test docstrings_equal(md.docs[Tuple{Any,Any}], doc"s-2") end -let g = DocsTest.g +let g = @var(DocsTest.g) md = meta(DocsTest)[g] @test docstrings_equal(md.docs[Union{}], doc"g") end -let h = DocsTest.h +let h = @var(DocsTest.h) md = meta(DocsTest)[h] sig = Union{Tuple{}, Tuple{Any}, Tuple{Any, Any}, Tuple{Any, Any, Any}} @test docstrings_equal(md.docs[sig], doc"h/0-3") end -let AT = DocsTest.AT +let AT = @var(DocsTest.AT) md = meta(DocsTest)[AT] @test docstrings_equal(md.docs[Union{}], doc"AT") end -let BT = DocsTest.BT +let BT = @var(DocsTest.BT) md = meta(DocsTest)[BT] @test docstrings_equal(md.docs[Union{}], doc"BT") end -let BT2 = DocsTest.BT2 +let BT2 = @var(DocsTest.BT2) md = meta(DocsTest)[BT2] @test docstrings_equal(md.docs[Union{}], doc"BT2") end -let T = DocsTest.T +let T = @var(DocsTest.T) md = meta(DocsTest)[T] - @test docstrings_equal(md.docs[Union{}], doc"T") - @test docstrings_equal(md.fields[:x], doc"T.x") - @test docstrings_equal(md.fields[:y], doc"T.y") + d = md.docs[Union{}] + @test docstrings_equal(d, doc"T") + @test d.data[:fields][:x] == "T.x" + @test d.data[:fields][:y] == "T.y" end -let IT = DocsTest.IT +let IT = @var(DocsTest.IT) md = meta(DocsTest)[IT] - @test docstrings_equal(md.docs[Union{}], doc"IT") - @test docstrings_equal(md.fields[:x], doc"IT.x") - @test docstrings_equal(md.fields[:y], doc"IT.y") + d = md.docs[Union{}] + @test docstrings_equal(d, doc"IT") + @test d.data[:fields][:x] == "IT.x" + @test d.data[:fields][:y] == "IT.y" end -@test @doc(DocsTest.TA) == doc"TA" +@test docstrings_equal(@doc(DocsTest.TA), doc"TA") @test docstrings_equal(@doc(DocsTest.@mac), doc"@mac()") @test docstrings_equal(@doc(DocsTest.@mac()), doc"@mac()") @@ -226,20 +230,20 @@ end @test docstrings_equal(@doc(DocsTest.@mac(x::Int, y::Expr)), doc"@mac(x::Int, y::Expr, z = 0)") @test docstrings_equal(@doc(DocsTest.@mac(x::Int, y::Expr, z)), doc"@mac(x::Int, y::Expr, z = 0)") let m = doc""" + @mac() + @mac(x) @mac(x::Int, y::Expr, z = 0) - @mac() - :@mac """ @test docstrings_equal(@doc(:@DocsTest.mac), m) @test docstrings_equal(@doc(:(DocsTest.@mac)), m) end -@test @doc(DocsTest.G) == doc"G" -@test @doc(DocsTest.K) == doc"K" +@test docstrings_equal(@doc(DocsTest.G), doc"G") +@test docstrings_equal(@doc(DocsTest.K), doc"K") let d1 = @doc(DocsTest.t(::AbstractString)), d2 = doc"t-1" @@ -261,8 +265,8 @@ let d1 = @doc(DocsTest.t{S <: Integer}(::S)), @test docstrings_equal(d1,d2) end -let fields = meta(DocsTest)[DocsTest.FieldDocs].fields - @test haskey(fields, :one) && fields[:one] == doc"one" +let fields = meta(DocsTest)[@var(DocsTest.FieldDocs)].docs[Union{}].data[:fields] + @test haskey(fields, :one) && fields[:one] == "one" @test haskey(fields, :two) && fields[:two] == doc"two" end @@ -352,17 +356,17 @@ end end -let md = meta(MacroGenerated)[MacroGenerated.f] +let md = meta(MacroGenerated)[@var(MacroGenerated.f)] @test md.order == [Tuple{Any}] - @test md.docs[Tuple{Any}] == doc"f" + @test docstrings_equal(md.docs[Tuple{Any}], doc"f") end @test isdefined(MacroGenerated, :_f) -let md = meta(MacroGenerated)[MacroGenerated.g] +let md = meta(MacroGenerated)[@var(MacroGenerated.g)] @test md.order == [Tuple{Any}, Tuple{Any, Any}] - @test md.docs[Tuple{Any}] == doc"g" - @test md.docs[Tuple{Any, Any}] == doc"g" + @test docstrings_equal(md.docs[Tuple{Any}], doc"g") + @test docstrings_equal(md.docs[Tuple{Any, Any}], doc"g") end @test isdefined(MacroGenerated, :_g) @@ -417,7 +421,7 @@ read(x) = x end -let md = Base.Docs.meta(I11798)[I11798.read], +let md = Base.Docs.meta(I11798)[@var(I11798.read)], d1 = md.docs[md.order[1]], d2 = doc"read" @test docstrings_equal(d1,d2) @@ -432,7 +436,7 @@ Base.collect{T}(::Type{EmptyType{T}}) = "borked" end -let fd = meta(I12515)[Base.collect] +let fd = meta(I12515)[@var(Base.collect)] @test fd.order[1] == Tuple{Type{I12515.EmptyType{TypeVar(:T, Any, true)}}} end @@ -447,7 +451,7 @@ f12593_2() = 1 @test (@doc f12593_1) !== nothing @test (@doc f12593_2) !== nothing -@test Docs.doc(svdvals, Tuple{Vector{Float64}}) === nothing +# @test Docs.doc(svdvals, Tuple{Vector{Float64}}) === nothing @test Docs.doc(svdvals, Tuple{Float64}) !== nothing # crude test to make sure we sort docstring output by method specificity @@ -512,7 +516,13 @@ end ) @test docstrings_equal(Docs.doc(I13068.A.foo, Tuple{Int}), doc"foo from A") @test docstrings_equal(Docs.doc(I13068.A.foo, Tuple{Float64}), doc"foo from B") -@test Docs.doc(I13068.A.foo, Tuple{Char}) === nothing +@test docstrings_equal(Docs.doc(I13068.A.foo, Tuple{Char}), + doc""" + foo from A + + foo from B + """ +) # Issue #13905. @test macroexpand(:(@doc "" f() = @x)) == Expr(:error, UndefVarError(symbol("@x"))) @@ -550,12 +560,12 @@ Binding `Undocumented.bindingdoesnotexist` does not exist. No documentation found. **Summary:** -```julia +``` abstract Undocumented.A <: Any ``` **Subtypes:** -```julia +``` Undocumented.B Undocumented.C ``` @@ -565,12 +575,12 @@ Undocumented.C No documentation found. **Summary:** -```julia +``` abstract Undocumented.B <: Undocumented.A ``` **Subtypes:** -```julia +``` Undocumented.D ``` """) @@ -579,7 +589,7 @@ Undocumented.D No documentation found. **Summary:** -```julia +``` type Undocumented.C <: Undocumented.A ``` """) @@ -588,12 +598,12 @@ type Undocumented.C <: Undocumented.A No documentation found. **Summary:** -```julia +``` immutable Undocumented.D <: Undocumented.B ``` **Fields:** -```julia +``` one :: Any two :: UTF8String three :: Float64 @@ -623,17 +633,17 @@ end # Bindings. -import Base.Docs: @var, Binding +import Base.Docs: @var, Binding, defined let x = Binding(Base, symbol("@time")) - @test x.defined == true + @test defined(x) == true @test @var(@time) == x @test @var(Base.@time) == x @test @var(Base.Pkg.@time) == x end let x = Binding(Base.LinAlg, :norm) - @test x.defined == true + @test defined(x) == true @test @var(norm) == x @test @var(Base.norm) == x @test @var(Base.LinAlg.norm) == x @@ -641,7 +651,7 @@ let x = Binding(Base.LinAlg, :norm) end let x = Binding(Core, :Int) - @test x.defined == true + @test defined(x) == true @test @var(Int) == x @test @var(Base.Int) == x @test @var(Core.Int) == x @@ -649,25 +659,25 @@ let x = Binding(Core, :Int) end let x = Binding(Base, :Pkg) - @test x.defined == true + @test defined(x) == true @test @var(Pkg) == x @test @var(Base.Pkg) == x @test @var(Main.Pkg) == x end let x = Binding(Base, :VERSION) - @test x.defined == true + @test defined(x) == true @test @var(VERSION) == x @test @var(Base.VERSION) == x end let x = Binding(Base, :bindingdoesnotexist) - @test x.defined == false + @test defined(x) == false @test @var(Base.bindingdoesnotexist) == x end let x = Binding(Main, :bindingdoesnotexist) - @test x.defined == false + @test defined(x) == false @test @var(bindingdoesnotexist) == x end