From 509f54f908cf3eb6c118c4184cf7addf2293a311 Mon Sep 17 00:00:00 2001 From: Michael Hatherly Date: Tue, 8 Sep 2015 01:08:20 +0200 Subject: [PATCH] Documenting macro-generated code 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 #12705. --- base/docs/Docs.jl | 39 ++++++++++++++++++++++++++++++++++++++ test/docs.jl | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index 6bf895294aac40..0ad5135f9f4bfa 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -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) @@ -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 diff --git a/test/docs.jl b/test/docs.jl index ab3cb1df8b313f..1c956a90cf76ee 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -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"