Skip to content

DYN-10572: Honor per-port Use Levels on variadic DSVarArgFunction nodes#17147

Merged
jasonstratton merged 5 commits into
masterfrom
10572-string-concat-list-nesting
Jun 18, 2026
Merged

DYN-10572: Honor per-port Use Levels on variadic DSVarArgFunction nodes#17147
jasonstratton merged 5 commits into
masterfrom
10572-string-concat-list-nesting

Conversation

@eamiri

@eamiri eamiri commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Closes https://autodesk.atlassian.net/browse/DYN-10572

Purpose

Fixes per-port Use Levels on every variadic DSVarArgFunction node (e.g., String.Concat, String.Join, List.Join).

Root cause: ZeroTouchVarArgNodeController.BuildOutputAst packs all variadic inputs into a single string[]/IList argument before NodeModel.UseLevelAndReplicationGuide runs, so per-port Use Levels / Level / lacing settings beyond the first variadic slot were silently dropped. As a consequence, a nested list on one variadic port would also reshape the replication rank applied to other ports.

Fix:

  • Add a virtual hook NodeModel.LevelAndReplicationGuideInputCount that bounds the post-pack level/replication-guide pass. Default is a no-op (returns the raw input count), preserving behavior on non-variadic nodes.
  • In ZeroTouchVarArgNodeController.BuildOutputAst, apply AtLevel and lacing-driven replication guides to each variadic input before packing them into argPack.
  • Override the new hook on DSVarArgFunction to scope the post-pack pass to non-variadic prefix ports only, preventing double-wrap.

Declarations

Check these if you believe they are true

API additions:

  • virtual Dynamo.Graph.Nodes.NodeModel.LevelAndReplicationGuideInputCount(int rawInputCount) -> int (entry added to src/DynamoCore/PublicAPI.Unshipped.txt).

Release Notes

Variadic nodes (e.g., String.Concat, String.Join, List.Join) now honor Use Levels on every variadic port, and per-port replication is now independent of other ports' rank. Previously, only the first variadic port's Use Levels setting was applied and a nested list on one port would unintentionally reshape the replication of other ports.

Reviewers

(FILL ME IN) Reviewer 1

FYIs

(FILL ME IN, Optional)


Implementation plan & task tracking

h1. DYN-10572 Per-port {{Use Levels}} on {{DSVarArgFunction}} — Implementation Plan

h2. Overview

Fix {{String.Concat}} (and every other variadic zero-touch node — {{String.Join}}, {{List.Join}}, …) so per-port {{Use Levels}} works on every variadic port and so a nested list on one port no longer reshapes the replication of other ports. Root cause: {{ZeroTouchVarArgNodeController.BuildOutputAst}} packs all variadic inputs into a single {{string[]}} argument before {{NodeModel.UseLevelAndReplicationGuide}} runs, so per-port level/lacing settings beyond the first variadic slot are silently dropped.

Port naming ({{string0/string1/…}}) is intentionally left unchanged — see "What We're NOT Doing".

h2. Current State Analysis

h3. Key Discoveries

  • {{DSCore.String.Concat}} is a zero-touch method at {{src/Libraries/CoreNodes/String.cs:58}}; no custom NodeModel — the node is {{DSVarArgFunction}}.
  • Pack-before-wrap is the defect: {{src/DynamoCore/Graph/Nodes/ZeroTouch/DSVarArgFunction.cs:167-191}} packs the variadic tail before {{DSFunctionBase}} invokes {{NodeModel.UseLevelAndReplicationGuide}} at {{src/DynamoCore/Graph/Nodes/NodeModel.cs:1641-1690}}.
  • {{AstFactory.AddAtLevel}} annotations inside {{ExprListNode}} literals are inert at runtime ({{src/Engine/ProtoCore/Parser/AssociativeAST.cs:3276-3296}}); fixes must wrap before packing.
  • Port labels are derived from the C# parameter name ({{DSVarArgFunction.cs:128, 162}}) and not serialized ({{VariableInputNode.cs:264}}).
  • No existing test covers per-port {{UseLevels}} on a variadic node.

h2. Desired End State

  • Toggling {{Use Levels}} and setting {{Level}} on any variadic port of {{String.Concat}}/{{String.Join}}/{{List.Join}} produces the same semantics it produces on a non-variadic node.
  • Nested list on one variadic port does not change the replication rank applied to other variadic ports.
  • All previously passing tests still pass; new tests for per-port {{Use Levels}} and rank independence pass.
  • No public API breakage; existing {{.dyn}} files load and execute identically when {{Use Levels}} was not set.

Verify by running {{dotnet test src/DynamoCoreTests/DynamoCoreTests.csproj --filter "NameConcatString|NameVarArgFunction|Name~AtLevel"}} and by reproducing the reporter's graph in DynamoSandbox.

h2. What We're NOT Doing

  • Not renaming port labels. {{string0/string1/…}} is the convention's consistent output of {{params string[] strings}}. Special-casing {{String.Concat}} would diverge from {{String.Join}}/{{List.Join}}. Renaming the C# parameter to {{lists}} is misleading (type is {{string[]}}). Labels can be revisited in a separate ticket if the convention is changed across all {{DSVarArgFunction}} nodes.
  • Not changing the {{String.Concat}} C# signature. Fix is purely in AST codegen.
  • Not changing non-variadic level/lacing behavior. {{NodeModel.cs}} changes must be additive (new virtual hook); existing call sites unchanged.
  • Not changing the {{+}}/{{-}} button UX or serialization.
  • Not migrating existing {{.dyn}} files. Today's graphs silently ignore {{Use Levels}} on variadic ports; after the fix the setting takes effect — this is the desired contract.

h2. Implementation Approach

Override-style pre-pack handling in {{ZeroTouchVarArgNodeController.BuildOutputAst}}: apply {{AtLevel}} and lacing-driven replication guides to each variadic input AST individually, then pack.

Add a virtual hook on {{NodeModel}} so the post-pack {{UseLevelAndReplicationGuide}} operates only on non-variadic prefix ports, preventing double-wrap.

New tests on {{String.Concat}} (pure variadic), {{String.Join}} (prefix + variadic), {{List.Join}} (pure variadic over {{IList}}) to prove the fix is general.

Manual verification in Sandbox against the reporter's screenshots.


h3. TASK 1: Reproduce the bug with failing tests [HIGH PRIORITY]

Status: DONE
Milestone: Two new NUnit tests fail on {{master}}, proving the bug.

  • Author {{test/core/string/TestConcatStringUseLevels.dyn}} — {{String.Concat}} with nested list on one port, scalars on others, {{Use Levels}} set on the nested port.
  • Author {{test/core/string/TestConcatStringNestedListIndependence.dyn}} — two ports with different ranks; assert each port's replication is governed by its own setting.
  • Add {{TestConcatStringUseLevelsOnVariadicPort}} and {{TestConcatStringNestedListRankIndependence}} in {{test/DynamoCoreTests/Nodes/StringTests.cs}} (#region {{concat string test cases}}, ~line 100). Naming follows {{WhenConditionThenExpectedBehavior}} per {{.claude/rules/dynamo-core-rules.md}}.
  • Confirm both tests fail on pristine {{master}}.

h3. TASK 2: Add a virtual hook to scope level/lacing pass on packed variadic nodes [HIGH PRIORITY]

Status: DONE
Milestone: {{NodeModel}} exposes an extension point bounding the range of inputs touched by {{UseLevelAndReplicationGuide}}; default is a no-op.

  • Add {{protected virtual int LevelAndReplicationGuideInputCount(int rawInputCount) => rawInputCount;}} to {{src/DynamoCore/Graph/Nodes/NodeModel.cs}} next to {{UseLevelAndReplicationGuide}} (~line 1641).
  • Bound the loops at {{NodeModel.cs:1646-1652}}, {{1657-1660}}, {{1665-1668}}, {{1674-1677}}, {{1683-1687}} by the hook's return value.
  • Add XML doc explaining the hook: variadic nodes pack tail inputs into a single argument, so per-port handling must happen before packing; this hook lets them skip the packed slot here.
  • If the Roslyn PublicAPI analyzer flags the new {{protected virtual}} member, add an entry to {{src/DynamoCore/PublicAPI.Unshipped.txt}}.

h3. TASK 3: Apply per-port levels and replication guides on variadic inputs before packing [HIGH PRIORITY]

Status: DONE
Milestone: {{ZeroTouchVarArgNodeController.BuildOutputAst}} applies {{Use Levels}} + lacing replication guides per variadic element pre-pack; {{DSVarArgFunction}} overrides the new hook to scope the post-pack pass to non-variadic prefix ports.

  • Pre-pack per-port {{AddAtLevel}} / lacing-driven {{AddReplicationGuide}} wrapping in {{ZeroTouchVarArgNodeController.BuildOutputAst}}.
  • Override the new hook on {{DSVarArgFunction}} to return the non-variadic prefix count.
  • Inline {{// Why:}} comment explaining the pack-before-wrap rationale and the override's role in preventing double-wrap.
  • Re-run TASK 1 tests; both must pass.

h3. TASK 4: Generalize verification across {{DSVarArgFunction}} nodes [MEDIUM PRIORITY]

Status: DONE
Milestone: Sister variadic nodes ({{String.Join}}, {{List.Join}}) honor per-port {{Use Levels}} and replicate independently, with regression tests.

  • Add {{TestJoinStringUseLevelsOnVariadicPort}} in {{test/DynamoCoreTests/Nodes/StringTests.cs}}.
  • Add {{TestListJoinUseLevelsOnVariadicPort}} in {{test/DynamoCoreTests/Nodes/ListTests.cs}}.
  • Author {{test/core/string/TestJoinStringUseLevelsOnVariadicPort.dyn}} and {{test/core/list/TestListJoinUseLevelsOnVariadicPort.dyn}}.

h3. TASK 5: Manual verification against the reporter's repro [MEDIUM PRIORITY]

Status: DONE
Milestone: DynamoSandbox reproduces the reporter's expected behavior.

  • {{msbuild src/Dynamo.All.sln /p:Configuration=Release}}.
  • Reporter-repro behavior in DynamoSandbox.
  • Repeat with {{String.Join}} and {{List.Join}} to confirm generality.
  • Confirm no regression on existing graphs.
  • Capture before/after notes for the PR body.

h3. TASK 6: PR + repo conventions [LOW PRIORITY]

Status: DONE
Milestone: PR is review-ready and conforms to repo conventions.

  • PR title: {{DYN-10572: Honor per-port Use Levels on variadic DSVarArgFunction nodes}}.
  • Fill {{.github/PULL_REQUEST_TEMPLATE.md}}. Release Notes: variadic nodes (e.g., {{String.Concat}}, {{String.Join}}, {{List.Join}}) now honor {{Use Levels}} on every variadic port, and per-port replication is now independent of other ports' rank.
  • Confirm {{PublicAPI.Unshipped.txt}} entry from TASK 2 is committed (entry: {{virtual Dynamo.Graph.Nodes.NodeModel.LevelAndReplicationGuideInputCount(int rawInputCount) -> int}}).
  • No CLAUDE.md / wiki updates required — the change aligns variadic nodes with the documented {{Use Levels}} contract.

@eamiri eamiri added the kiln Created by kiln label Jun 8, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the ticket for this pull request: https://jira.autodesk.com/browse/DYN-10572

Comment thread test/DynamoCoreTests/Nodes/StringTests.cs Dismissed
Comment thread test/DynamoCoreTests/Nodes/StringTests.cs Dismissed
@eamiri eamiri changed the title DynamoDS/DYN-10572: CLONE - #15837 String.Concat inconsistent behaviour of subsequent lists DYN-10572: Honor per-port Use Levels on variadic DSVarArgFunction nodes Jun 8, 2026
@eamiri eamiri marked this pull request as ready for review June 8, 2026 21:26
@eamiri eamiri added the pr_ready PR is ready for review label Jun 8, 2026
Comment thread test/DynamoCoreTests/Nodes/ListTests.cs Dismissed
Comment thread test/DynamoCoreTests/Nodes/StringTests.cs Dismissed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes DYN-10572 by ensuring per-port Use Levels and lacing-driven replication guides are applied correctly on variadic zero-touch nodes (DSVarArgFunction) by handling level/replication for variadic inputs before they are packed into a single argument, and preventing the existing post-pack pass from double-wrapping.

Changes:

  • Added a NodeModel virtual hook (LevelAndReplicationGuideInputCount) to bound the post-pack UseLevelAndReplicationGuide pass.
  • Updated ZeroTouchVarArgNodeController.BuildOutputAst to apply AtLevel and replication guides per variadic input prior to packing.
  • Added regression tests and new .dyn fixtures covering String.Concat, String.Join, and List.Join variadic use-level behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/DynamoCoreTests/Nodes/StringTests.cs Adds NUnit regression tests for variadic String.Concat/String.Join per-port Use Levels and rank independence.
test/DynamoCoreTests/Nodes/ListTests.cs Adds NUnit regression test for List.Join honoring Use Levels on variadic ports.
test/core/string/TestJoinStringUseLevelsOnVariadicPort.dyn Fixture graph exercising bounded post-pack + pre-pack variadic handling for String.Join.
test/core/string/TestConcatStringUseLevels.dyn Fixture graph validating Use Levels on a non-first variadic port for String.Concat.
test/core/string/TestConcatStringNestedListIndependence.dyn Fixture graph validating rank/replication independence across variadic ports for String.Concat.
test/core/list/TestListJoinUseLevelsOnVariadicPort.dyn Fixture graph validating variadic Use Levels for List.Join with lacing disabled.
src/DynamoCore/PublicAPI.Unshipped.txt Declares the new NodeModel.LevelAndReplicationGuideInputCount API addition.
src/DynamoCore/Graph/Nodes/ZeroTouch/DSVarArgFunction.cs Implements the bounded post-pack behavior via override and applies pre-pack per-variadic-port level/replication handling.
src/DynamoCore/Graph/Nodes/NodeModel.cs Introduces the virtual hook and bounds UseLevelAndReplicationGuide to the intended subset of inputs.

@jasonstratton jasonstratton left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved after code review ... no changes required

eamiri and others added 5 commits June 17, 2026 22:55
…ing.Concat

Add two failing NUnit tests under StringTests.cs and their .dyn fixtures
to lock in the desired post-fix behavior of per-port Use Levels and rank
independence on variadic DSVarArgFunction nodes (String.Concat).

These tests are expected to fail on master and pass after TASK 3 (the
pre-pack per-port AtLevel/replication-guide wiring in
ZeroTouchVarArgNodeController.BuildOutputAst).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a protected virtual hook so variadic nodes (DSVarArgFunction) can scope
the post-pack UseLevelAndReplicationGuide pass to non-variadic prefix ports.
Default returns the raw input count, preserving existing behavior for all
non-variadic nodes.
ZeroTouchVarArgNodeController.BuildOutputAst now wraps each variadic
input with AtLevel and lacing-driven replication guides BEFORE packing
them into an ExprListNode, since those annotations are inert when
carried inside the list literal. DSVarArgFunction overrides
LevelAndReplicationGuideInputCount so the post-pack NodeModel pass
scopes to the non-variadic prefix only, preventing double-wrap.

Fixes per-port Use Levels and per-port replication-rank independence
for String.Concat, String.Join, List.Join and any other DSVarArgFunction.
…r variadic nodes

Adds regression tests for String.Join (separator prefix port + variadic
port) and List.Join (pure-variadic over IList) confirming the per-port
Use Levels fix generalizes across DSVarArgFunction shapes. The
String.Join test also exercises the bounded post-pack
UseLevelAndReplicationGuide pass with LevelAndReplicationGuideInputCount
== 1, while the List.Join test exercises the count == 0 path on a
[IsLacingDisabled] variadic node.
@jasonstratton jasonstratton force-pushed the 10572-string-concat-list-nesting branch from dfd2226 to 31926a9 Compare June 18, 2026 02:58
@jasonstratton jasonstratton merged commit 6abaaac into master Jun 18, 2026
25 checks passed
@jasonstratton jasonstratton deleted the 10572-string-concat-list-nesting branch June 18, 2026 02:58
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kiln Created by kiln pr_ready PR is ready for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants