Skip to content

fix: DAG signatures treat i2's NotSet default as no default - #82

Merged
thorwhalen merged 1 commit into
masterfrom
not-set-defaults-are-required
Sep 22, 2026
Merged

thorwhalen merged 1 commit into
masterfrom
not-set-defaults-are-required

Conversation

@thorwhalen

Copy link
Copy Markdown
Member

What

Part of step 2 of the re-land plan in i2mint/i2#48: meshed treats i2's NotSet sentinel, when it appears as a func node param's default, as "no default" while it builds a DAG's signature.

  • New meshed.util.not_set_to_empty(param): param with no default if its default is NotSet.
  • DAG.src_name_params applies it before merging same-named params and before sort_params.

Why

The independent review of i2mint/dagapp#4 tried meshed with a NotSet-defaulted node (what a re-landed i2mint/i2#88 FuncFactory shows) and found:

  • Silent, high: DAG([g, h]).sig became (z, a=NotSet) instead of (a, z), because params with a default are sorted after required ones. dag(1, 2) would then bind 1 to z.
  • Loud, medium: a NotSet param next to a same-named required param raised ValidationError: ... didn't have the same default in conservative_parameter_merge, so DAGs that build fine today would stop building.

With this PR the DAG signature is exactly what it is today, and a NotSet root is required, as it is today. FuncNode signatures themselves are untouched.

No-op against the current i2: no current signature carries NotSet.

i2 compatibility (decision)

Uses i2.is_not_set (i2 >= 0.1.74, i2mint/i2#91) when available, otherwise falls back on i2.deco.NotSet (present since 2023). The i2 requirement is unchanged.

Tests

meshed/tests/test_not_set_defaults.py: the DAG signature and positional call match the plain DAG's, merging with a same-named required param works, a NotSet root is required, and real defaults are kept. 3 of the 4 tests fail without the fix. There is also a doctest on not_set_to_empty.

Gate

  • pytest --doctest-modules meshed (py3.12): 175 passed, 1 failed, 1 skipped. Master: 170 passed and the same 1 failure, which is pre-existing: test_hybrid_dag needs extrude/http2py, not installed here.
  • Dependents run against this branch vs released meshed, identical results: front 72 passed, lookbook 132 passed, titbit 24, extrude 3, dagapp 2, allude 1. uf (39 failed / 66 passed), guided (1 failed / 1 passed), dotsci (2 collection errors), theremin (4 errors): same pre-existing failures on both. know, raglab-app, smart-cv could not be tested here (not on this box, or pyaudio needs system portaudio).
  • Hosted CI: see checks.

🤖 Generated with Claude Code

`DAG.src_name_params` now maps a func node param's `NotSet` default (i2's
sentinel) to no default (`util.not_set_to_empty`) before merging and
sorting. Without this, once i2mint/i2#88 re-lands, a DAG holding a
FuncFactory node would silently reorder its positional params (NotSet
params sorted after required ones: `dag(1, 2)` binding to the wrong
roots), and merging with a same-named required param would raise.
No-op for current signatures.

Uses `i2.is_not_set` when available, else falls back on `i2.deco.NotSet`.

Refs i2mint/i2#48

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@thorwhalen

Copy link
Copy Markdown
Member Author

Independent refute-review (sub-agent): no blocker. It is a strict no-op for DAGs without NotSet (identity check only). DAG.partial (also with _consider_defaulted_arguments_as_bound), __getitem__ sub-DAGs, code_to_dag and parametrized_dag_factory all rebuild the DAG, so they get the fix. It also tried a real i2.FuncFactory with NotSet defaults by hand: the DAG signature is the same as today's.

Known limitation, documented rather than fixed here: the fix is at the DAG-signature level. The FuncNodes keep their NotSet defaults, so calls that bypass the DAG signature fill a missing root with NotSet where today they raise TypeError. These are FuncNode.call_on_scope, DAG.call_on_scope (and call_on_scope_iteratively, launch_debugger), and Slabs. For a FuncFactory node this is actually the factory's intended "call with nothing" behaviour, since it filters NotSet. A NotSet default can only reach any other node if that function declares it itself. Whether FuncNodes should also normalize is a step-3 decision and is recorded in i2mint/i2#48. dot_digraph still labels such a root a= (cosmetic).

test_real_defaults_are_kept is a no-regression guard: it is meant to pass with or without the fix.

@thorwhalen
thorwhalen merged commit d9d6096 into master Sep 22, 2026
6 checks passed
@thorwhalen
thorwhalen deleted the not-set-defaults-are-required branch September 22, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant