Skip to content

Commit a2a2133

Browse files
nsicchaclaude
andcommitted
Add inline struct definitions, bare-symbol destructuring, fix walk_rhs and PCE display
- Inline structs: `sub = struct Sub ... end` and `struct Sub ... end` inside @dynamicstruct bodies. Child struct gets __parent__ + forwarded parent properties automatically. Generated as Parent_Child to avoid kwarg shadowing. - Bare-symbol named destructuring: `(;a, b) = config` generates direct `a = config.a` accessors instead of a hidden group property. - Fix walk_rhs: skip LHS of :(=) expressions (named tuple keys and assignment targets are not variable references). - Fix PCE showerror: 1-arg method now shows stored cause backtrace so sprint(showerror, e) includes the stacktrace. - Remove PCE rethrow shortcut: nested PCEs are now properly wrapped. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b98c48a commit a2a2133

1 file changed

Lines changed: 82 additions & 13 deletions

File tree

src/DynamicObjects.jl

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -457,15 +457,14 @@ _computeproperty(o, name, indices...; __status__=nothing, kwargs...) = begin
457457
compute_property(o, vname, indices...; _status_kw..., kwargs...)
458458
end
459459
catch e
460-
e isa PropertyComputationError && rethrow()
461460
kw_tuple = isempty(kwargs) ? () : Tuple(pairs(kwargs))
462461
bt = catch_backtrace()
463462
throw(PropertyComputationError(
464463
string(typeof(o).name.name),
465464
name,
466465
indices,
467466
kw_tuple,
468-
(e, bt), # store exception + backtrace from the actual throw site
467+
(e, bt),
469468
))
470469
end
471470
end
@@ -738,6 +737,69 @@ dynamicstruct(expr; docstring=nothing, cache_type=:serial) = begin
738737
Meta.isexpr(type, :(<:)) && (type = type.args[1])
739738
Meta.isexpr(type, :(curly)) && (type = type.args[1])
740739
@assert body.head == :block
740+
# --- Extract inline struct definitions ---
741+
# Collect parent property names (excluding inline structs themselves)
742+
parent_props = Symbol[]
743+
for arg in body.args
744+
arg isa Expr || continue
745+
a = arg
746+
while Meta.isexpr(a, :macrocall); a = a.args[end]; end
747+
# Skip inline structs (both forms)
748+
Meta.isexpr(a, :struct) && continue
749+
Meta.isexpr(a, :(=)) && Meta.isexpr(a.args[2], :struct) && continue
750+
lhs = if Meta.isexpr(a, :(=))
751+
a.args[1]
752+
else
753+
a # fixed field: bare symbol or typed symbol
754+
end
755+
Meta.isexpr(lhs, (:call, :ref)) && (lhs = lhs.args[1])
756+
Meta.isexpr(lhs, :(::)) && (lhs = lhs.args[1])
757+
lhs isa Symbol && push!(parent_props, lhs)
758+
end
759+
extracted_structs = Expr[]
760+
for (i, arg) in enumerate(body.args)
761+
arg isa Expr || continue
762+
prop_name = nothing
763+
child_struct = nothing
764+
# Form 1: prop = struct Name ... end
765+
if Meta.isexpr(arg, :(=)) && Meta.isexpr(arg.args[2], :struct)
766+
prop_name = arg.args[1]
767+
child_struct = arg.args[2]
768+
# Form 2: struct Name ... end (bare)
769+
elseif Meta.isexpr(arg, :struct)
770+
child_struct = arg
771+
end
772+
isnothing(child_struct) && continue
773+
child_name = child_struct.args[2]
774+
isnothing(prop_name) && (prop_name = child_name)
775+
# Rename child struct to Parent_Child to avoid kwarg shadowing
776+
gen_name = Symbol(type, "_", child_name)
777+
child_struct.args[2] = gen_name
778+
# Collect child's own property names to avoid collision
779+
child_props = Set{Symbol}()
780+
for ca in child_struct.args[3].args
781+
ca isa Expr || continue
782+
ca2 = ca
783+
while Meta.isexpr(ca2, :macrocall); ca2 = ca2.args[end]; end
784+
Meta.isexpr(ca2, :(=)) || continue
785+
clhs = ca2.args[1]
786+
Meta.isexpr(clhs, (:call, :ref)) && (clhs = clhs.args[1])
787+
Meta.isexpr(clhs, :(::)) && (clhs = clhs.args[1])
788+
clhs isa Symbol && push!(child_props, clhs)
789+
end
790+
# Prepend __parent__ and forwarded properties to child body
791+
child_body = child_struct.args[3]
792+
forwarded = [pp for pp in parent_props if !(pp in child_props)]
793+
prepend = Expr[]
794+
push!(prepend, :(__parent__ = nothing))
795+
if !isempty(forwarded)
796+
push!(prepend, :($(Expr(:tuple, Expr(:parameters, forwarded...))) = __parent__))
797+
end
798+
child_body.args = vcat(prepend, child_body.args)
799+
push!(extracted_structs, child_struct)
800+
# Replace with property assignment
801+
body.args[i] = :($prop_name = $gen_name(; __parent__=__self__))
802+
end
741803
lnn = nothing
742804
doc = nothing
743805
docs = []
@@ -867,7 +929,15 @@ dynamicstruct(expr; docstring=nothing, cache_type=:serial) = begin
867929
)
868930
))
869931
))
870-
esc(Expr(:block,
932+
result = Expr(:block)
933+
# Prepend extracted inline child structs (processed recursively)
934+
for s in extracted_structs
935+
child_result = dynamicstruct(s)
936+
# Unwrap esc() — parent handles escaping
937+
@assert Meta.isexpr(child_result, :escape)
938+
push!(result.args, child_result.args[1])
939+
end
940+
push!(result.args, Expr(:block,
871941
:(@doc $docstring $struct_expr),
872942
quote
873943
$Base.hasproperty(__self__::$type, name::Symbol) = name in $(Tuple(keys(properties)))
@@ -911,6 +981,7 @@ dynamicstruct(expr; docstring=nothing, cache_type=:serial) = begin
911981
# directly in getorcomputeproperty (via meta check), so no zero-arg
912982
# compute_property methods are needed here.
913983
))
984+
esc(result)
914985
end
915986

916987
# Replace only the top-level LineNumberNodes in a block, leaving nested ones intact.
@@ -1116,17 +1187,15 @@ end
11161187
function Base.showerror(io::IO, e::PropertyComputationError)
11171188
key = _format_property_key(e.property, e.indices, e.kwargs)
11181189
print(io, "PropertyComputationError: computing `$key` on $(e.type_name)\n")
1190+
cause_err = _cause_error(e)
1191+
cause_bt = e.cause isa Tuple && length(e.cause) >= 2 ? e.cause[2] : nothing
11191192
print(io, " Caused by: ")
1120-
showerror(io, unwrap_error(e))
1121-
end
1122-
1123-
function Base.showerror(io::IO, e::PropertyComputationError, bt; kwargs...)
1124-
try
1125-
showerror(io, e)
1126-
catch internal_err
1127-
print(io, "PropertyComputationError (display failed: ")
1128-
showerror(io, internal_err)
1129-
print(io, ")")
1193+
if cause_err isa PropertyComputationError
1194+
showerror(io, cause_err)
1195+
elseif !isnothing(cause_bt)
1196+
showerror(io, cause_err, cause_bt)
1197+
else
1198+
showerror(io, cause_err)
11301199
end
11311200
end
11321201

0 commit comments

Comments
 (0)