Skip to content

Commit

Permalink
Documenting macro-generated code
Browse files Browse the repository at this point in the history
This adds a `@__doc__` macro, not exported currently, for use by macro authors
that what their macros to hook into the docsystem properly.

Usage:

    macro example(f)
        quote
            $(f)() = 0
            @__doc__ $(f)(x) = 1
            $(f)(x, y) = 2
        end |> esc
    end

    "Docs for `g(x)` only."
    @example g

will attach the docstring to the 1-arg method only.

Fixes JuliaLang#12705.
  • Loading branch information
MichaelHatherly committed Sep 7, 2015
1 parent 5edd47c commit 509f54f
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 0 deletions.
39 changes: 39 additions & 0 deletions base/docs/Docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,44 @@ end

multidoc(meta, objs) = quote $([:(@doc $(esc(meta)) $(esc(obj))) for obj in objs]...) end

doc"""
@__doc__(ex)
Low-level macro used to mark expressions returned by a macro that should be documented. If
more than one expression is marked then the same docstring is applied to each expression.
macro example(f)
quote
$(f)() = 0
@__doc__ $(f)(x) = 1
$(f)(x, y) = 2
end |> esc
end
`@__doc__` has no effect when a macro that uses it is not documented.
"""
macro __doc__(ex) esc(Expr(:block, symbol("#doc#"), ex)) end

isdocblock(x) = isexpr(x, :block) && x.args[1] == symbol("#doc#") && length(x.args) == 2

__doc__!(meta, def) = replace_doc!(meta, def) > 0

function replace_doc!(meta, def::Expr)
n = 0
if isdocblock(def)
n += 1
# Convert `Expr(:block, :#doc#, ...)` created by `@__doc__` to an `@doc`.
def.head = :macrocall
def.args = [symbol("@doc"), meta, def.args[end]]
else
for each in def.args
n += replace_doc!(meta, each)
end
end
n
end
replace_doc!(meta, def) = 0

fexpr(ex) = isexpr(ex, :function, :stagedfunction, :(=)) && isexpr(ex.args[1], :call)

function docm(meta, def, define = true)
Expand All @@ -428,6 +466,7 @@ function docm(meta, def, define = true)
:global) ? vardoc(meta, def, namify(def′)) :
isvar(def′) ? objdoc(meta, def′) :
isexpr(def′, :tuple) ? multidoc(meta, def′.args) :
__doc__!(meta, def′) ? esc(def′) :
isa(def′, Expr) ? error("Invalid doc expression $def′") :
objdoc(meta, def′)
end
Expand Down
48 changes: 48 additions & 0 deletions test/docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,54 @@ let fields = meta(DocsTest)[DocsTest.FieldDocs].fields
@test haskey(fields, :two) && fields[:two] == doc"two"
end

# Document specific expressions generated by macro calls.
module MacroGenerated

import Base.Docs.@__doc__

macro example_1(f)
quote
$(f)() = 0
@__doc__ $(f)(x) = x
$(f)(x, y) = x + y
end |> esc
end

"f"
@example_1 f

@example_1 _f

macro example_2(f)
quote
$(f)() = 0
@__doc__ $(f)(x) = x
@__doc__ $(f)(x, y) = x + y
end |> esc
end

"g"
@example_2 g

@example_2 _g

end

let funcdoc = meta(MacroGenerated)[MacroGenerated.f]
@test funcdoc.order == [Tuple{Any}]
@test funcdoc.meta[Tuple{Any}] == doc"f"
end

@test isdefined(MacroGenerated, :_f)

let funcdoc = meta(MacroGenerated)[MacroGenerated.g]
@test funcdoc.order == [Tuple{Any}, Tuple{Any, Any}]
@test funcdoc.meta[Tuple{Any}] == doc"g"
@test funcdoc.meta[Tuple{Any, Any}] == doc"g"
end

@test isdefined(MacroGenerated, :_g)

# Issue #12700.
@test @doc(DocsTest.@m) == doc"Inner.@m"

Expand Down

0 comments on commit 509f54f

Please sign in to comment.