Skip to content

Commit

Permalink
Avoid redefining documented definitions
Browse files Browse the repository at this point in the history
Fixes issue mentioned in JuliaLang#14759. Both `multidoc` and `__doc__!` did
not take into account the `define` flag which results in them overwriting
definitions during base image generation.
  • Loading branch information
MichaelHatherly committed Jan 22, 2016
1 parent df928bd commit 07d12db
Showing 1 changed file with 39 additions and 9 deletions.
48 changes: 39 additions & 9 deletions base/docs/Docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,11 @@ function vardoc(meta, def, name)
end
end

multidoc(meta, ex) = quote $([:(@doc $(esc(meta)) $(esc(obj))) for obj in ex.args]...) end
function multidoc(meta, ex, define)
out = Expr(:toplevel)
out.args = [:(@doc($meta, $obj, $define)) for obj in ex.args]
esc(out)
end

"""
@__doc__(ex)
Expand All @@ -519,21 +523,47 @@ more than one expression is marked then the same docstring is applied to each ex
"""
:(Base.@__doc__)

function __doc__!(meta, def::Expr)
function __doc__!(meta, def, define)
# Two cases must be handled here to avoid redefining all definitions contained in `def`:
if define
# `def` has not been defined yet (this is the common case, i.e. when not generating
# the Base image). We just need to convert each `@__doc__` marker to an `@doc`.
finddoc(def) do each
each.head = :macrocall
each.args = [symbol("@doc"), meta, each.args[end], define]
end
else
# `def` has already been defined during Base image gen so we just need to find and
# document any subexpressions marked with `@__doc__`.
docs = []
found = finddoc(def) do each
push!(docs, :(@doc($meta, $(each.args[end]), $define)))
end
# If any subexpressions have been documented then replace the entire expression with
# just those documented subexpressions to avoid redefining any definitions.
if found
def.head = :toplevel
def.args = docs
end
found
end
end
# Walk expression tree `def` and call `λ` when any `@__doc__` markers are found. Returns
# `true` to signify that at least one `@__doc__` has been found, and `false` otherwise.
function finddoc(λ, def::Expr)
if isexpr(def, :block, 2) && isexpr(def.args[1], :meta, 1) && def.args[1].args[1] === :doc
# Convert `Expr(:block, Expr(:meta, :doc), ...)` created by `@__doc__` to an `@doc`.
def.head = :macrocall
def.args = [symbol("@doc"), meta, def.args[end]]
# Found the macroexpansion of an `@__doc__` expression.
λ(def)
true
else
found = false
for each in def.args
found |= __doc__!(meta, each)
found |= finddoc, each)
end
found
end
end
__doc__!(meta, def) = false
finddoc, def) = false

# Predicates and helpers for `docm` expression selection:

Expand Down Expand Up @@ -590,11 +620,11 @@ function docm(meta, ex, define = true)
# Modules and baremodules.
isexpr(x, :module) ? moddoc(meta, def, x) :
# Document several expressions with the same docstring. `a, b, c`.
isexpr(x, :tuple) ? multidoc(meta, x) :
isexpr(x, :tuple) ? multidoc(meta, x, define) :
# Errors generated by calling `macroexpand` are passed back to the call site.
isexpr(x, :error) ? esc(x) :
# When documenting macro-generated code we look for embedded `@__doc__` calls.
__doc__!(meta, x) ? esc(x) :
__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) :

Expand Down

0 comments on commit 07d12db

Please sign in to comment.