@@ -415,12 +415,57 @@ wrapper_forwards(h, c, pos) :- wrapper_closure(h, c), param_decl(_, _, ppos, h,
415415 pos = to_number(ppos), expr_root(_, "RETURN", e), expr_owner(_, c, _, e), carries_param_call(e, p).
416416wrapped_result_return(e) :- wrapper_closure(h, c), param_decl(_, _, _, h, p),
417417 expr_root(_, "RETURN", e), expr_owner(_, c, _, e), carries_param_call(e, p).
418+ // ── the wrapper WRITES that parameter before the closure calls it (#1706) ──
419+ // `fn = () => x`, `if (!fn) fn = dflt`, `fn = fn || dflt`, `fn ??= dflt` in the
420+ // wrapper's own body: the closure calls what was written, so a call through the site
421+ // also returns what each written function returns. The site's argument still counts
422+ // unless the write REPLACES it: a plain `=` statement of the wrapper's body, outside
423+ // every branch, whose value does not read the parameter. A write inside the closure
424+ // (a memoizer's `fn = undefined` after the call) or in a nested function is not the
425+ // wrapper's and changes nothing here.
426+ wrapper_param_write(h, pos, p, a) :- wrapper_forwards(h, _, pos), param_decl(_, _, ppos, h, p), to_number(ppos) = pos,
427+ expr_kind(_, "ASSIGNMENT", _, a), expr_owner(_, h, _, a), expr_operator(_, op, a), assignment_op_writes_value(op),
428+ expr_child(_, a, "ASSIGNMENT_TARGET", _, tgt), expr_param(_, p, tgt).
429+ // The written value's operands, through the same transparent forms as the carry.
430+ param_write_operand(a, v) :- wrapper_param_write(_, _, _, a), expr_child(_, a, "ASSIGNMENT_VALUE", _, v).
431+ param_write_operand(a, o) :- param_write_operand(a, e), expr_kind(_, "CONDITIONAL", _, e), expr_child(_, e, r, _, o), r != "CONDITION".
432+ param_write_operand(a, o) :- param_write_operand(a, e), value_passing_binary(e), expr_child(_, e, _, _, o).
433+ param_write_operand(a, o) :- param_write_operand(a, e), expr_kind(_, "SEQUENCE", _, e), sequence_last(e, o).
434+ param_write_operand(a, o) :- param_write_operand(a, e), expr_kind(_, "ASSIGNMENT", _, e), expr_child(_, e, "ASSIGNMENT_VALUE", _, o).
435+ value_passing_binary(e) :- expr_kind(_, "BINARY", _, e), expr_operator(_, op, e), binary_op_passes_value(op).
436+ param_write_leaf(a, o) :- param_write_operand(a, o), !expr_kind(_, "CONDITIONAL", _, o), !value_passing_binary(o),
437+ !expr_kind(_, "SEQUENCE", _, o), !expr_kind(_, "ASSIGNMENT", _, o).
438+ // A write that may leave the argument in place.
439+ param_write_keeps_arg(a) :- wrapper_param_write(_, _, p, a), param_write_leaf(a, o), expr_param(_, p, o).
440+ param_write_keeps_arg(a) :- wrapper_param_write(_, _, _, a), expr_operator(_, op, a), op != "=".
441+ param_write_keeps_arg(a) :- wrapper_param_write(_, _, _, a), !expr_root(_, "EXPRESSION_STATEMENT", a).
442+ param_write_keeps_arg(a) :- wrapper_param_write(h, _, _, a), expr_owner(_, h, mod, a), expr_span(_, l, c, _, _, a),
443+ block_owner(k, h, b), block_kind_branches(k), block_span(_, mod, l1, c1, l2, c2, b),
444+ (to_number(l) > to_number(l1) ; to_number(l) = to_number(l1), to_number(c) >= to_number(c1)),
445+ (to_number(l) < to_number(l2) ; to_number(l) = to_number(l2), to_number(c) <= to_number(c2)).
446+ block_kind_branches("IF").
447+ block_kind_branches("ELSE").
448+ block_kind_branches("FOR").
449+ block_kind_branches("FOR_IN").
450+ block_kind_branches("FOR_OF").
451+ block_kind_branches("WHILE").
452+ block_kind_branches("DO").
453+ block_kind_branches("SWITCH").
454+ block_kind_branches("SWITCH_CASE").
455+ block_kind_branches("TRY").
456+ block_kind_branches("CATCH").
457+ wrapper_param_replaced(h, pos) :- wrapper_param_write(h, pos, _, a), !param_write_keeps_arg(a).
458+ wrapper_param_written(h, pos, o) :- wrapper_param_write(h, pos, p, a), param_write_leaf(a, o), !expr_param(_, p, o).
418459// The site's own value, and a call through it.
419460expr_value(s, "wrap", s) :- expr_kind(_, "CALL", _, s), expr_resolves_to_method(s, h), wrapper_forwards(h, _, _).
420461expr_value(ce, k, i) :- expr_kind(_, "CALL", _, ce), callee_value(ce, "wrap", s),
421462 !call_site(_, "FUNCTION_CALL_BIND", _, _, _, _, ce, _, _),
422- expr_resolves_to_method(s, h), wrapper_forwards(h, _, pos), call_arg(s, pos, arg),
463+ expr_resolves_to_method(s, h), wrapper_forwards(h, _, pos), !wrapper_param_replaced(h, pos), call_arg(s, pos, arg),
423464 expr_value(arg, "func", g), return_value(g, k, i).
465+ expr_value(ce, k, i) :- expr_kind(_, "CALL", _, ce), callee_value(ce, "wrap", s),
466+ !call_site(_, "FUNCTION_CALL_BIND", _, _, _, _, ce, _, _),
467+ expr_resolves_to_method(s, h), wrapper_forwards(h, _, pos), wrapper_param_written(h, pos, o),
468+ expr_value(o, "func", g), return_value(g, k, i).
424469// `return this` is POLYMORPHIC: a fluent `add()` declared on Node and called on an
425470// AbstractBlock returns the AbstractBlock. So a `this`-return is not a value of the
426471// method — it is the RECEIVER of each call, substituted at the site (below). A
0 commit comments