Skip to content

Commit 6f7f424

Browse files
authored
Docs: Make @doc macro return the value of documented expression (JuliaLang#59882)
Change the `@doc` macro implementation to return the result of the original expression instead of the Docs.Binding object. This allows using the documented definition in an assignment or other expression context. For example, documenting a function now returns the function itself: ```julia result = begin "docstring" function f end end ``` Add special handling for `global` declarations to return nothing instead of failing with a syntax error, since `y = begin; global x; end` is not valid Julia syntax. 🤖 Generated with help from Claude Code
1 parent bf65d07 commit 6f7f424

File tree

2 files changed

+96
-6
lines changed

2 files changed

+96
-6
lines changed

base/docs/Docs.jl

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,22 @@ function objectdoc(__source__, __module__, str, def, expr, sig = :(Union{}))
396396
@nospecialize str def expr sig
397397
binding = esc(bindingexpr(namify(expr)))
398398
docstr = esc(docexpr(__source__, __module__, lazy_iterpolate(str), metadata(__source__, __module__, expr, false)))
399-
# Note: we want to avoid introducing line number nodes here (issue #24468)
400-
return Expr(:block, esc(def), :($(doc!)($__module__, $binding, $docstr, $(esc(sig)))))
399+
# Store the result of the definition and return it after documenting
400+
docex = :($(doc!)($__module__, $binding, $docstr, $(esc(sig))))
401+
if def === nothing
402+
return Expr(:block, docex)
403+
else
404+
exdef = esc(def)
405+
if isexpr(def, :global, 1) && def.args[1] isa Union{Symbol,GlobalRef}
406+
# Special case: `global x` should return nothing to avoid syntax errors with assigning to a value
407+
val = nothing
408+
else
409+
val = :val
410+
exdef = Expr(:(=), val, exdef)
411+
end
412+
# Note: we want to avoid introducing line number nodes here (issue #24468) for def
413+
return Expr(:block, exdef, docex, val)
414+
end
401415
end
402416

403417
function calldoc(__source__, __module__, str, def::Expr)
@@ -431,7 +445,9 @@ function moduledoc(__source__, __module__, meta, def, def′::Expr)
431445
end
432446
end
433447

434-
# Shares a single doc, `meta`, between several expressions from the tuple expression `ex`.
448+
# Shares a single doc, `meta`, between several expressions from the tuple expression `ex`
449+
# (but don't actually create the tuple for the result and just return the final one,
450+
# as if this was a C++ comma operator or a block separated by `;` instead of `,`).
435451
function multidoc(__source__, __module__, meta, ex::Expr, define::Bool)
436452
@nospecialize meta
437453
out = Expr(:block)
@@ -657,7 +673,7 @@ docm(source::LineNumberNode, mod::Module, _, _, x...) = docm(source, mod, x...)
657673
# also part of a :where expression, so it unwraps the :where layers until it reaches the
658674
# "actual" expression
659675
iscallexpr(ex::Expr) = isexpr(ex, :where) ? iscallexpr(ex.args[1]) : isexpr(ex, :call)
660-
iscallexpr(ex) = false
676+
iscallexpr(@nospecialize ex) = false
661677

662678
function docm(source::LineNumberNode, mod::Module, meta, ex, define::Bool = true)
663679
@nospecialize meta ex
@@ -722,7 +738,7 @@ function _docm(source::LineNumberNode, mod::Module, meta, x, define::Bool = true
722738
# f(::T, ::U) where T where U
723739
#
724740
isexpr(x, FUNC_HEADS) && is_signature((x::Expr).args[1]) ? objectdoc(source, mod, meta, def, x::Expr, signature(x::Expr)) :
725-
isexpr(x, [:function, :macro]) && !isexpr((x::Expr).args[1], :call) ? objectdoc(source, mod, meta, def, x::Expr) :
741+
(isexpr(x, :function) || isexpr(x, :macro)) && !isexpr((x::Expr).args[1], :call) ? objectdoc(source, mod, meta, def, x::Expr) :
726742
iscallexpr(x) ? calldoc(source, mod, meta, x::Expr) :
727743

728744
# Type definitions.
@@ -742,7 +758,7 @@ function _docm(source::LineNumberNode, mod::Module, meta, x, define::Bool = true
742758
isexpr(x, BINDING_HEADS) && !isexpr((x::Expr).args[1], :call) ? objectdoc(source, mod, meta, def, x::Expr) :
743759

744760
# Quoted macrocall syntax. `:@time` / `:(Base.@time)`.
745-
isquotedmacrocall(x) ? objectdoc(source, mod, meta, def, x) :
761+
isquotedmacrocall(x) ? objectdoc(source, mod, meta, nothing, x) :
746762
# Modules and baremodules.
747763
isexpr(x, :module) ? moduledoc(source, mod, meta, def, x::Expr) :
748764
# Document several expressions with the same docstring. `a, b, c`.

test/docs.jl

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,3 +1585,77 @@ This docmacroception has a docstring
15851585
@docmacroception()
15861586

15871587
@test Docs.hasdoc(@__MODULE__, :var"@docmacrofoo")
1588+
1589+
# Test that @doc returns the value of the documented expression
1590+
module DocReturnValue
1591+
using Test
1592+
# Test function definition returns the function
1593+
result = begin
1594+
"docstring for f"
1595+
function f end
1596+
end
1597+
@test result === f
1598+
# Test with regular function syntax
1599+
result2 = begin
1600+
"docstring for g"
1601+
g(x) = x + 1
1602+
end
1603+
@test result2 === g
1604+
# Test with struct definition
1605+
result3 = begin
1606+
"docstring for S"
1607+
struct S; x; end
1608+
end
1609+
@test result3 === nothing
1610+
# Test with const binding
1611+
result4 = begin
1612+
"docstring for K"
1613+
const K = 42
1614+
end
1615+
@test result4 === 42
1616+
# Test that documenting a global declaration returns nothing to avoid syntax errors
1617+
result5 = begin
1618+
"docstring for global x"
1619+
global x
1620+
end
1621+
@test result5 === nothing
1622+
@test Base.binding_module(DocReturnValue, :x) === DocReturnValue
1623+
# Test that assignment returns the RHS
1624+
result6 = begin
1625+
"docstring for global y"
1626+
global y = 4
1627+
end
1628+
@test result6 === 4
1629+
@test y === 4
1630+
# Test that assignment returns the RHS
1631+
result7 = begin
1632+
"docstring for const z"
1633+
const z = 5
1634+
end
1635+
@test result7 === z === 5
1636+
# Test module returns module
1637+
result8 = begin
1638+
"docstring for module A"
1639+
module A end
1640+
end
1641+
@test result8 === A
1642+
# Tests without definition effect
1643+
function t end
1644+
result9 = begin
1645+
"docstring for existing value t"
1646+
:t
1647+
end
1648+
@test result9 isa Base.Docs.Binding
1649+
macro s end
1650+
result10 = begin
1651+
"docstring for existing macro s"
1652+
:@s
1653+
end
1654+
@test result10 isa Base.Docs.Binding
1655+
function h end
1656+
result11 = begin
1657+
"docstring for existing function"
1658+
h()
1659+
end
1660+
@test result11 isa Base.Docs.Binding
1661+
end

0 commit comments

Comments
 (0)