Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions src/DynamoCore/Graph/Nodes/NodeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1643,7 +1643,15 @@ public void UseLevelAndReplicationGuide(List<AssociativeNode> inputs)
if (inputs == null || !inputs.Any())
return;

for (int i = 0; i < inputs.Count; i++)
// Variadic nodes pack tail inputs into a single argument, so per-port
// level/lacing handling for the variadic slots must happen before packing.
// The hook lets such nodes scope this post-pack pass to the non-variadic
// prefix, preventing double-wrap.
int boundedCount = Math.Min(inputs.Count, LevelAndReplicationGuideInputCount(inputs.Count));
if (boundedCount <= 0)
return;

for (int i = 0; i < boundedCount; i++)
{
if (InPorts[i].UseLevels)
{
Expand All @@ -1654,15 +1662,15 @@ public void UseLevelAndReplicationGuide(List<AssociativeNode> inputs)
switch (ArgumentLacing)
{
case LacingStrategy.Auto:
for (int i = 0; i < inputs.Count(); ++i)
for (int i = 0; i < boundedCount; ++i)
{
if (InPorts[i].UseLevels)
inputs[i] = AstFactory.AddReplicationGuide(inputs[i], new List<int> { 1 }, false);
}
break;

case LacingStrategy.Shortest:
for (int i = 0; i < inputs.Count(); ++i)
for (int i = 0; i < boundedCount; ++i)
{
inputs[i] = AstFactory.AddReplicationGuide(inputs[i], new List<int> { 1 }, false);
}
Expand All @@ -1671,7 +1679,7 @@ public void UseLevelAndReplicationGuide(List<AssociativeNode> inputs)

case LacingStrategy.Longest:

for (int i = 0; i < inputs.Count(); ++i)
for (int i = 0; i < boundedCount; ++i)
{
inputs[i] = AstFactory.AddReplicationGuide(inputs[i], new List<int> { 1 }, true);
}
Expand All @@ -1680,14 +1688,29 @@ public void UseLevelAndReplicationGuide(List<AssociativeNode> inputs)
case LacingStrategy.CrossProduct:

int guide = 1;
for (int i = 0; i < inputs.Count(); ++i)
for (int i = 0; i < boundedCount; ++i)
{
inputs[i] = AstFactory.AddReplicationGuide(inputs[i], new List<int> { guide }, false);
guide++;
}
break;
}
}

/// <summary>
/// Returns the number of leading inputs that <see cref="UseLevelAndReplicationGuide"/>
/// should wrap with <c>AtLevel</c> / replication guides.
/// </summary>
/// <remarks>
/// Variadic nodes (e.g. <c>DSVarArgFunction</c>) pack their tail inputs into a single
/// packed argument before this post-pack pass runs. Wrapping the packed slot here would
/// be either a no-op or a double-wrap, so variadic nodes override this hook to expose only
/// the non-variadic prefix and handle per-port level / lacing on the unpacked variadic
/// inputs themselves.
/// </remarks>
/// <param name="rawInputCount">Number of raw input AST nodes available to the pass.</param>
/// <returns>Number of inputs (counted from index 0) that should be wrapped.</returns>
protected virtual int LevelAndReplicationGuideInputCount(int rawInputCount) => rawInputCount;
#endregion

#region Input and Output Connections
Expand Down
92 changes: 91 additions & 1 deletion src/DynamoCore/Graph/Nodes/ZeroTouch/DSVarArgFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,25 @@
[JsonIgnore]
public VariableInputNodeController VarInputController { get; private set; }

/// <summary>
/// Scopes <see cref="NodeModel.UseLevelAndReplicationGuide"/> to the non-variadic
/// prefix of this node's inputs.
/// </summary>
/// <remarks>
/// Variadic zero-touch functions pack their tail inputs into a single
/// <c>ExprListNode</c> argument before <see cref="NodeModel.UseLevelAndReplicationGuide"/>
/// runs. AtLevel / replication-guide annotations on an <c>ExprListNode</c> are inert
/// at runtime, so per-port Use Levels and lacing on the variadic slots must be applied
/// pre-pack inside <c>ZeroTouchVarArgNodeController.BuildOutputAst</c>. This override
/// limits the post-pack pass to the non-variadic prefix so the packed variadic slot
/// is not touched twice.
/// </remarks>
protected override int LevelAndReplicationGuideInputCount(int rawInputCount)
{
int prefix = Controller.Definition.Parameters.Count() - 1;
return prefix < 0 ? 0 : prefix;
}

#region VarInput Controller
private sealed class ZeroTouchVarInputController : VariableInputNodeController
{
Expand Down Expand Up @@ -164,7 +183,7 @@
}
}

protected override void BuildOutputAst(NodeModel model, List<AssociativeNode> inputAstNodes, List<AssociativeNode> resultAst)

Check failure on line 186 in src/DynamoCore/Graph/Nodes/ZeroTouch/DSVarArgFunction.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 35 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=DynamoDS_Dynamo&issues=AZ6pHIapQw83x_y7ShLY&open=AZ6pHIapQw83x_y7ShLY&pullRequest=17147
{
// All inputs are provided, then we should pack all inputs that
// belong to variable input parameter into a single array.
Expand All @@ -172,14 +191,85 @@
{
var paramCount = Definition.Parameters.Count();

// Suppose a fucntion foo() with var args, its signature is:
// Suppose a function foo() with var args, its signature is:
//
// foo(x1, x2, ..., xn, params y)
//
// so paramCount == n + 1 here, and suppose inputs are
//
// i1, i2, ...., in, y1, y2, ..., ym
//
// Why: per-port Use Levels and lacing-driven replication guides
// on the variadic inputs (y1..ym) must be applied *before* we
// pack them into an ExprListNode. AtLevel / replication-guide
// annotations carried inside an ExprListNode are inert at runtime,
// so packing first would silently drop per-port settings on every
// variadic port (the original DYN-10572 defect). The non-variadic
// prefix (i1..in) is handled by the post-pack
// NodeModel.UseLevelAndReplicationGuide pass, which DSVarArgFunction
// scopes to indices [0, paramCount - 1) via the
// LevelAndReplicationGuideInputCount override -- preventing the
// packed slot from being wrapped twice.
int variadicStart = paramCount - 1;
if (variadicStart >= 0 && variadicStart < inputAstNodes.Count)
{
for (int i = variadicStart; i < inputAstNodes.Count; i++)
{
if (model.InPorts[i].UseLevels)
{
inputAstNodes[i] = AstFactory.AddAtLevel(
inputAstNodes[i],
-model.InPorts[i].Level,
model.InPorts[i].KeepListStructure);
}
}

switch (model.ArgumentLacing)
{
case LacingStrategy.Auto:
for (int i = variadicStart; i < inputAstNodes.Count; i++)
{
if (model.InPorts[i].UseLevels)
{
inputAstNodes[i] = AstFactory.AddReplicationGuide(
inputAstNodes[i], new List<int> { 1 }, false);
}
}
break;

case LacingStrategy.Shortest:
for (int i = variadicStart; i < inputAstNodes.Count; i++)
{
inputAstNodes[i] = AstFactory.AddReplicationGuide(
inputAstNodes[i], new List<int> { 1 }, false);
}
break;

case LacingStrategy.Longest:
for (int i = variadicStart; i < inputAstNodes.Count; i++)
{
inputAstNodes[i] = AstFactory.AddReplicationGuide(
inputAstNodes[i], new List<int> { 1 }, true);
}
break;

case LacingStrategy.CrossProduct:
// Continue cross-product indices from where the prefix
// pass would have stopped: the prefix uses guides
// 1..paramCount-1, so the first variadic port starts at
// paramCount and each subsequent variadic port gets a
// distinct rank.
int guide = paramCount;
for (int i = variadicStart; i < inputAstNodes.Count; i++)
{
inputAstNodes[i] = AstFactory.AddReplicationGuide(
inputAstNodes[i], new List<int> { guide }, false);
guide++;
}
break;
}
}

// Here we pack all var arguments in an array {y1, y2, ..., ym}
// (skipping the first n == paramCount - 1 inputs)
var argPack = AstFactory.BuildExprList(inputAstNodes.Skip(paramCount - 1).ToList());
Expand Down
1 change: 1 addition & 0 deletions src/DynamoCore/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ Dynamo.Models.DynamoModel.DefaultStartConfiguration.EnableUnTrustedLocationsNoti
Dynamo.Models.DynamoModel.IStartConfiguration.EnableUnTrustedLocationsNotifications.get -> bool
Dynamo.Models.DynamoModel.OpenFileCommand.OpenFileCommand(System.String filePath, System.Boolean forceManualExecutionMode, System.Boolean isTemplate, System.Boolean forceBlockRun) -> void
Dynamo.Models.DynamoModel.InsertFileCommand.InsertFileCommand(System.String filePath, System.Boolean forceManualExecutionMode, System.Boolean forceBlockRun) -> void
virtual Dynamo.Graph.Nodes.NodeModel.LevelAndReplicationGuideInputCount(int rawInputCount) -> int
16 changes: 16 additions & 0 deletions test/DynamoCoreTests/Nodes/ListTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,22 @@ public void TestConcatenateListsNormalInput()

}

[Test]
public void TestListJoinUseLevelsOnVariadicPort()
{
// DYN-10572: List.Join is a pure-variadic DSVarArgFunction (no non-variadic
// prefix). With [IsLacingDisabled] on the C# method the node's lacing is
// Disabled, so per-port Use Levels must still be honored pre-pack even
// though no replication guides are applied. This test confirms the
// variadic Use Levels path runs without breaking the post-pack pass
// (which is a no-op here because LevelAndReplicationGuideInputCount is 0).
string testFilePath = Path.Combine(listTestFolder, "TestListJoinUseLevelsOnVariadicPort.dyn");
Comment thread
jasonstratton marked this conversation as resolved.
Dismissed

RunModel(testFilePath);

AssertPreviewValue("dddd11110000222200003333dddd4444", new object[] { 1, 2, 3, 4 });
}

#endregion

#region Test DiagonalLeftList
Expand Down
41 changes: 41 additions & 0 deletions test/DynamoCoreTests/Nodes/StringTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,32 @@ public void TestConcatStringInListMap()
AssertPreviewValue("a105ad39-9b1c-44aa-a2cb-37866ea48dd0", new string[] { "0a", "10a", "20a", "30a", "40a", "50a" });
}

[Test]
public void TestConcatStringUseLevelsOnVariadicPort()
{
// DYN-10572: per-port Use Levels must take effect on variadic ports of a
// DSVarArgFunction. Here string0 = "hello", string1 = ["a","b"] with
// UseLevels @L1 on string1; the @L1 must replicate concat over the list.
string testFilePath = Path.Combine(localDynamoStringTestFolder, "TestConcatStringUseLevels.dyn");
Comment thread
jasonstratton marked this conversation as resolved.
Dismissed

RunModel(testFilePath);

AssertPreviewValue("aaaa1111-0000-2222-0000-3333aaaa4444", new string[] { "helloa", "hellob" });
}

[Test]
public void TestConcatStringNestedListRankIndependence()
{
// DYN-10572: each variadic port's Use Levels / replication setting must be
// honored independently of the other ports' rank. Two 1D lists with
// UseLevels @L1 on both ports must replicate in parallel under Auto lacing.
string testFilePath = Path.Combine(localDynamoStringTestFolder, "TestConcatStringNestedListIndependence.dyn");
Comment thread
jasonstratton marked this conversation as resolved.
Dismissed

RunModel(testFilePath);

AssertPreviewValue("bbbb1111-0000-2222-0000-3333bbbb4444", new string[] { "ac", "bd" });
}

#endregion

#region substring test cases
Expand Down Expand Up @@ -200,6 +226,21 @@ public void TestJoinStringNormalInput()

}

[Test]
public void TestJoinStringUseLevelsOnVariadicPort()
{
// DYN-10572: String.Join has both a non-variadic prefix port (separator)
// and variadic ports. Set UseLevels on the separator port to exercise the
// bounded post-pack pass (LevelAndReplicationGuideInputCount == 1) AND on
// a variadic port to exercise the new pre-pack handling, confirming both
// code paths cooperate correctly.
string testFilePath = Path.Combine(localDynamoStringTestFolder, "TestJoinStringUseLevelsOnVariadicPort.dyn");
Comment thread
jasonstratton marked this conversation as resolved.
Dismissed

RunModel(testFilePath);

AssertPreviewValue("cccc11110000222200003333cccc4444", new string[] { "x-a", "x-b" });
}

#endregion

#region number to string test cases
Expand Down
Loading
Loading