Skip to content

DynamoDS/DYN-8473: #15837 String.Concat inconsistent behaviour of subsequent lists#17152

Draft
jnealb wants to merge 7 commits into
masterfrom
8473-string-concat-list-levels
Draft

DynamoDS/DYN-8473: #15837 String.Concat inconsistent behaviour of subsequent lists#17152
jnealb wants to merge 7 commits into
masterfrom
8473-string-concat-list-levels

Conversation

@jnealb

@jnealb jnealb commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

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

Implementation Plan

Goal: Fix String.Concat so list levels are honoured and each concatenation is independent of the previous input, and improve confusing input port names.
Approach: Locate the String.Concat node implementation in the CoreNodes string library, replace/wrap the underlying .NET String.Concat call so that nested-list inputs respect Dynamo list-level semantics (lacing/replication) rather than letting an earlier input's shape dictate subsequent inputs. Rename the variadic input ports from string0, string1, ... to clearer names while preserving backward compatibility ([AlsoKnownAs] or equivalent), and add NUnit tests covering nested-list inputs with and without list levels applied.

TASK 1: Reproduce and locate the defect

Status: DONE
Milestone: A failing NUnit test that demonstrates String.Concat ignoring list levels / coupling subsequent inputs to the first nested input's structure.

  • Find the String.Concat node implementation under src/Libraries/ (likely CoreNodes or CoreNodeModels).
  • Identify whether it is a zero-touch static method or an explicit NodeModel (variadic input ports suggest NodeModel).
  • Reproduce the bug with a small .dyn graph or unit test using nested-list inputs and list-level overrides.
  • Add a failing NUnit test that asserts the expected per-input, level-aware behaviour.

Findings:

  • Implementation: zero-touch static method DSCore.String.Concat(params string[] strings) at src/Libraries/CoreNodes/String.cs lines 52-61.
  • Runtime wrapper: Dynamo.Graph.Nodes.ZeroTouch.DSVarArgFunction (src/DynamoCore/Graph/Nodes/ZeroTouch/DSVarArgFunction.cs); ZeroTouchVarArgNodeController.BuildOutputAst packs all input ports into a single string[] argument, which couples nested structure on port 0 to ports 1..N.
  • Port-name derivation: ZeroTouchVarInputController.GetInputName trims trailing s from the last parameter name (strings -> string0, string1, ...).
  • Function signature key referenced by existing .dyn files: DSCore.String.Concat@string[].
  • Failing test added: TestConcatStringNestedListInputIsIndependentOfScalarInput in test/DynamoCoreTests/Nodes/StringTests.cs (currently [Ignore]'d with DYN-8473 reason; TASK 2 will enable once the fix lands).

TASK 2: Fix list-level / replication behaviour

Status: DONE
Milestone: String.Concat honours list levels per input and concatenation of each input is independent of the others; the failing test from TASK 1 now passes.

  • Adjust the AST built by the node (or the underlying DesignScript function) so replication uses Dynamo's list-level system rather than delegating shape control to the first nested input.
  • Ensure single-string, flat-list, and nested-list inputs all produce expected results across common lacing modes.
  • Verify no regression for the existing simple-string concat behaviour.
  • Re-enable TestConcatStringNestedListInputIsIndependentOfScalarInput (remove the [Ignore]).

Findings:

  • Implementation: special-cased DSCore.String.Concat@string[] in ZeroTouchVarArgNodeController.BuildOutputAst (src/DynamoCore/Graph/Nodes/ZeroTouch/DSVarArgFunction.cs). When the node has >=2 inputs and is fully applied, the AST is lowered to a left-associative chain of binary + operator calls (s0 + s1 + s2 + ...) using Op.GetOpFunction(Operator.add). Each operand participates in Dynamo's normal per-argument replication, so a nested-list input no longer dictates the structure of subsequent inputs. Partial application and the 1-input case continue to fall through to the original params-packing path.
  • Behaviour change captured in existing tests:
    • TestConcatStringMultipleInput updated: two parallel flat lists now pair element-wise ({"abef","cdgh"}) instead of being packed into a single string[] ({"abcd","efgh"}).
    • TestConcatStringInvalidInput updated: ("a", 1) now produces "a1" via DesignScript + coercion instead of a node-level warning (the old params string[] path raised a warning on type mismatch).
  • Test asset: test/core/string/TestConcatString_nestedList.dyn (XML format matching the rest of the folder), with a nested {{"a","b"},{"c","d"}} codeblock on port 0 and a scalar "X" on port 1.
  • Verification: all 67 tests in Dynamo.Tests.StringTests pass with the fix in place.

TASK 3: Clarify input port names

Status: DONE
Milestone: Input ports are named in a way that reflects accepted input (e.g., list0, list1, ... or a similarly clear scheme) without breaking existing graphs.

  • Rename variadic input ports from string0/string1/... to a clearer scheme.
  • Move user-facing strings to .resx for localisation.
  • Preserve backward compatibility for existing .dyn files (port mapping / migration, [AlsoKnownAs] if applicable).

Findings:

  • Implementation: renamed the params string[] parameter on DSCore.String.Concat from strings to lists (src/Libraries/CoreNodes/String.cs). Port labels are derived from the parameter name by ZeroTouchVarArgNodeController.InitializeFunctionParameters (arg.Name.Remove(arg.Name.Length - 1) + "0") and ZeroTouchVarInputController.GetInputName (Parameters.Last().Name.TrimEnd('s') + index), so the new name surfaces as list0, list1, list2, ... without any other change.
  • Mangled name preserved: the function signature DSCore.String.Concat@string[] is type-based, not name-based, so the special-case in DSVarArgFunction.BuildOutputAst (the binary + chain from TASK 2) continues to match and existing .dyn graphs resolve the same function.
  • Backward compatibility: VariableInputNode serialization only persists inputcount (XML) / the deserialized port list (JSON via SerializationConverters.setPortDataOnNewPort); port Name is not copied from the deserialized port back onto the new port, and connectors reference port GUIDs (JSON) or indices (XML). Old graphs with serialized labels string0/string1 therefore load with fresh labels list0/list1 and all wires remain connected.
  • XML doc updated to describe the new per-input replication semantics so the tooltip text matches the renamed ports.
  • .resx localisation of port labels/tooltips marked as skipped: the existing CoreNodes pattern derives port labels and tooltips from C# parameter names and Type.ToShortString(), with no .resx hook for individual zero-touch parameters. Moving String.Concat's labels into .resx would be a one-off divergence from the convention used by every other zero-touch node; deferred to a broader effort.
  • New test: TestConcatStringPortsAreNamedListN in test/DynamoCoreTests/Nodes/StringTests.cs asserts the first two ports of a loaded String.Concat node are labelled list0 and list1.
  • Verification: 68/68 tests in Dynamo.Tests.StringTests pass (including the new port-name assertion and the legacy .dyn fixtures previously authored with string0/string1 labels).

TASK 4: Tests, docs, and API surface

Status: NOT STARTED
Milestone: Test suite covers the fix and the renamed ports; public API additions are recorded; node help artefacts are updated if the node behaviour changed visibly.

  • Add NUnit tests for: flat lists, nested lists, list-level overrides, mixed inputs, and lacing modes.
  • Add any new public APIs to PublicAPI.Unshipped.txt.
  • Update doc/distrib/NodeHelpFiles/ (.dyn, .md, .jpg) only if outward node behaviour or signature changes warrant it.

Verification

  • All new and existing NUnit tests pass (dotnet test on the relevant test projects).
  • Manual smoke test with the original repro graph confirms list-level behaviour and input independence.
  • Existing .dyn graphs using String.Concat continue to open and evaluate without errors.
  • Code follows existing CoreNodes/NodeModel patterns and Dynamo coding standards.

@jnealb jnealb added the kiln Created by kiln label Jun 9, 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-8473

jnealb added 5 commits June 9, 2026 09:46
… coupling

Adds TestConcatStringNestedListInputIsIndependentOfScalarInput to capture
the bug where a nested list connected to one input of String.Concat dictates
the structure of every subsequent input. The test is [Ignore]'d for now and
will be re-enabled once the AST fix lands in TASK 2.
… replication

Special-cases DSCore.String.Concat@string[] in
ZeroTouchVarArgNodeController.BuildOutputAst so that, when the node has
>=2 inputs and is fully applied, the AST is lowered to a left-associative
chain of binary `+` operator calls (s0 + s1 + s2 + ...). Each operand
participates in Dynamo's normal per-argument replication, so a nested-
list input no longer dictates the structure of subsequent inputs and
list-level overrides work as users expect (DYN-8473).

Tests updated to reflect the new, correct behaviour:
- TestConcatStringMultipleInput now pairs parallel flat lists
  element-wise ({"abef","cdgh"}) instead of packing them into a single
  string[] argument.
- TestConcatStringInvalidInput now asserts the coerced "a1" preview that
  DesignScript's `+` operator produces from (string, int) rather than the
  old node-level warning.
- TestConcatStringNestedListInputIsIndependentOfScalarInput is re-enabled
  and now loads test/core/string/TestConcatString_nestedList.dyn instead
  of building the graph programmatically.
[Test]
public void TestConcatStringNestedListInputIsIndependentOfScalarInput()
{
string testFilePath = Path.Combine(localDynamoStringTestFolder, "TestConcatString_nestedList.dyn");
Renames the `params string[]` parameter on `DSCore.String.Concat` from
`strings` to `lists` so the variadic input ports surface as `list0`,
`list1`, ... — matching the user request in DYN-8473 that the labels
reflect that each port can accept either a string or a list of strings
(replication happens per-port). The label derivation in
ZeroTouchVarInputController/ZeroTouchVarArgNodeController is parameter-
name-based, so the rename is the only code change required.

The mangled name (`DSCore.String.Concat@string[]`) is type-based and
unchanged, so the binary-`+` lowering special case from TASK 2 still
matches and existing graphs resolve the same function. VariableInputNode
serialization only persists `inputcount` / port GUIDs+state — port Name
is not restored from disk — so old `.dyn` graphs serialized with
`string0/string1` labels load cleanly with fresh `list0/list1` labels
and all wires remain connected.

Adds `TestConcatStringPortsAreNamedListN` asserting the first two ports
of a loaded String.Concat node are `list0` and `list1`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

[Test]
public void TestConcatStringPortsAreNamedListN()
{
string testFilePath = Path.Combine(localDynamoStringTestFolder, "TestConcatString_nestedList.dyn");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kiln Created by kiln

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant