From ced6482ba3556f039f8bc270686872a2fc32f946 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 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. --- base/docs/Docs.jl | 35 ++++++++++++++++++++++++++++++++++ test/docs.jl | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index f248acecdba440..e5d74059d14f63 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -484,6 +484,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) && length(def.args) == 2 && def.args[1] == symbol("#doc#") + # 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) @@ -510,6 +544,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 8f6d15b1895507..7acba5c734f7b8 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -226,6 +226,54 @@ let d1 = @doc(DocsTest.val) @test d1 !== nothing 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"