Fix spread serialization in aser/async-aser wire format

Spread values from make-spread were crashing the wire format serializer:
- serialize() had no "spread" case, fell through to (str val) producing
  Python repr "<shared.sx.ref.sx_ref._Spread...>" which was treated as
  an undefined symbol
- aser-call/async-aser-call didn't handle spread children — now merges
  spread attrs as keyword args into the parent element
- aser-fragment/async-aser-fragment didn't filter spreads — now filters
  them (fragments have no parent element to merge into)
- serialize() now handles spread type: (make-spread {:key "val"})

Added 3 aser-spreads tests. All 562 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 12:20:16 +00:00
parent 427dee13f0
commit 859aad4333
6 changed files with 88 additions and 33 deletions

View File

@@ -1,3 +1,5 @@
# WARNING: special-forms.sx declares forms not in eval.sx: reset, shift
# WARNING: eval.sx dispatches forms not in special-forms.sx: form?, provide
"""
sx_ref.py -- Generated from reference SX evaluator specification.
@@ -2559,10 +2561,10 @@ def aser_fragment(children, env):
result = aser(c, env)
if sx_truthy((type_of(result) == 'list')):
for item in result:
if sx_truthy((not sx_truthy(is_nil(item)))):
if sx_truthy(((not sx_truthy(is_nil(item))) if not sx_truthy((not sx_truthy(is_nil(item)))) else (not sx_truthy(is_spread(item))))):
parts.append(serialize(item))
else:
if sx_truthy((not sx_truthy(is_nil(result)))):
if sx_truthy(((not sx_truthy(is_nil(result))) if not sx_truthy((not sx_truthy(is_nil(result)))) else (not sx_truthy(is_spread(result))))):
parts.append(serialize(result))
if sx_truthy(empty_p(parts)):
return ''
@@ -2590,12 +2592,18 @@ def aser_call(name, args, env):
else:
val = aser(arg, env)
if sx_truthy((not sx_truthy(is_nil(val)))):
if sx_truthy((type_of(val) == 'list')):
for item in val:
if sx_truthy((not sx_truthy(is_nil(item)))):
parts.append(serialize(item))
if sx_truthy(is_spread(val)):
for k in keys(spread_attrs(val)):
v = dict_get(spread_attrs(val), k)
parts.append(sx_str(':', k))
parts.append(serialize(v))
else:
parts.append(serialize(val))
if sx_truthy((type_of(val) == 'list')):
for item in val:
if sx_truthy((not sx_truthy(is_nil(item)))):
parts.append(serialize(item))
else:
parts.append(serialize(val))
_cells['i'] = (_cells['i'] + 1)
return sx_str('(', join(' ', parts), ')')
@@ -4140,10 +4148,10 @@ async def async_aser_fragment(children, env, ctx):
result = (await async_aser(c, env, ctx))
if sx_truthy((type_of(result) == 'list')):
for item in result:
if sx_truthy((not sx_truthy(is_nil(item)))):
if sx_truthy(((not sx_truthy(is_nil(item))) if not sx_truthy((not sx_truthy(is_nil(item)))) else (not sx_truthy(is_spread(item))))):
parts.append(serialize(item))
else:
if sx_truthy((not sx_truthy(is_nil(result)))):
if sx_truthy(((not sx_truthy(is_nil(result))) if not sx_truthy((not sx_truthy(is_nil(result)))) else (not sx_truthy(is_spread(result))))):
parts.append(serialize(result))
if sx_truthy(empty_p(parts)):
return make_sx_expr('')
@@ -4225,12 +4233,18 @@ async def async_aser_call(name, args, env, ctx):
else:
result = (await async_aser(arg, env, ctx))
if sx_truthy((not sx_truthy(is_nil(result)))):
if sx_truthy((type_of(result) == 'list')):
for item in result:
if sx_truthy((not sx_truthy(is_nil(item)))):
parts.append(serialize(item))
if sx_truthy(is_spread(result)):
for k in keys(spread_attrs(result)):
v = dict_get(spread_attrs(result), k)
parts.append(sx_str(':', k))
parts.append(serialize(v))
else:
parts.append(serialize(result))
if sx_truthy((type_of(result) == 'list')):
for item in result:
if sx_truthy((not sx_truthy(is_nil(item)))):
parts.append(serialize(item))
else:
parts.append(serialize(result))
_cells['i'] = (_cells['i'] + 1)
if sx_truthy(token):
svg_context_reset(token)