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 want 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 #12705.
  • Loading branch information
MichaelHatherly committed Sep 8, 2015
1 parent 6ef451c commit 3621863
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 0 deletions.
35 changes: 35 additions & 0 deletions base/docs/Docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,40 @@ 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

function __doc__!(meta, def::Expr)
if isexpr(def, :block) && def.args[1] == symbol("#doc#") && length(def.args) == 2
# Convert `Expr(:block, :#doc#, ...)` created by `@__doc__` to an `@doc`.
def.head = :macrocall
def.args = [symbol("@doc"), meta, def.args[end]]
true
else
found = false
for each in def.args
found |= __doc__!(meta, each)
end
found
end
end
__doc__!(meta, def) = false

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

function docm(meta, def, define = true)
Expand All @@ -428,6 +462,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 3621863

Please sign in to comment.