You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2026/04/10 22:39:05 UTC

(tinkerpop) branch master updated (d9e450aae5 -> 10378a7bb6)

This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git


    omit d9e450aae5 fix: replace Comparable cast in Traverser.compareTo() with GremlinValueComparator (TINKERPOP-3197)
    omit d530d7494f chore: add Gas Town runtime files to .gitignore (ti-584)
    omit d3186e7863 Merge branch '3.8-dev'
    omit 6a95ff5a7b docs
    omit 9959310019 Add splitByElement() to Gherkin step definitions for nested collection parsing
     new 352312a8d3 Add splitByElement() to Gherkin step definitions for nested collection parsing
     new 10378a7bb6 docs

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (d9e450aae5)
            \
             N -- N -- N   refs/heads/master (10378a7bb6)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                                         |  6 ----
 CHANGELOG.asciidoc                                 |  2 --
 .../gremlin/process/traversal/Traverser.java       | 11 ++++---
 .../traversal/step/map/OrderGlobalStepTest.java    | 14 ---------
 .../test/cucumber/feature-steps.js                 | 12 ++++----
 .../gremlin/language/translator/translations.json  | 34 ----------------------
 .../gremlin/test/features/map/Order.feature        | 25 +---------------
 7 files changed, 12 insertions(+), 92 deletions(-)


(tinkerpop) 02/02: docs

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 10378a7bb6452da86a96cd784c64fb13ab3081c9
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Thu Apr 9 07:15:12 2026 -0400

    docs
---
 docs/src/dev/developer/for-committers.asciidoc | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/docs/src/dev/developer/for-committers.asciidoc b/docs/src/dev/developer/for-committers.asciidoc
index fa22fed4fa..3fd3cd1f12 100644
--- a/docs/src/dev/developer/for-committers.asciidoc
+++ b/docs/src/dev/developer/for-committers.asciidoc
@@ -427,6 +427,16 @@ presented here.
 
 vp[marko-name->marko]
 
+When using this syntax, it is important to remember that lists and sets may contain other lists or sets as elements,
+allowing for nested collection notation such as `l[l[d[1].i,d[2].i],l[d[3].i,d[4].i]]`. The step definitions across all
+language variants parse these correctly by tracking bracket depth so that commas inside inner brackets are not treated
+as top-level separators. However, mixed-type nesting is not supported — a list or set may not contain a map as a direct
+element (e.g. `l[m[{"name":"marko"}]]` will not parse correctly). This limitation exists because the type notation
+matchers are applied in order using substring matching, and the `m[...]` matcher fires before `l[...]` on the outer
+value, causing the map pattern to consume part of the surrounding bracket syntax. Same-type nesting (list-of-lists,
+set-of-sets) and nesting of scalar types (vertices, edges, numbers, strings) inside lists or sets works as
+expected.
+
 In addition, parameter names should adhere to a common form as they hold some meaning to certain language variant
 implementations:
 


(tinkerpop) 01/02: Add splitByElement() to Gherkin step definitions for nested collection parsing

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 352312a8d30dde1fe02785c5750d212c35906231
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Tue Apr 7 19:53:42 2026 -0400

    Add splitByElement() to Gherkin step definitions for nested collection parsing
    
    Naive split(",") breaks on nested collection tokens like l[1,2,3] inside
    s[l[1,2,3],l[4,5,6]]. Adds a bracket-depth-aware splitByElement() helper to
    all Gherkin step definition files across Java and every GLV, so that commas
    inside nested brackets are not treated as top-level separators.
    
    Also adds two new Gherkin scenarios to Fold.feature that exercise nested
    list-of-list results (g.inject([1,2],[3,4]).fold()), along with the
    corresponding traversal bindings in each GLV's translation map.
    
    Java implementation follows PR #3216.
    
    (tinkerpop-mxr, tinkerpop-32y, tinkerpop-ghp, tinkerpop-2ur, tinkerpop-70k, tinkerpop-188, tinkerpop-93a)
---
 .../Gherkin/CommonSteps.cs                         | 34 ++++++++++++++++-
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |  2 +
 gremlin-go/driver/cucumber/cucumberSteps_test.go   | 31 +++++++++++++++-
 gremlin-go/driver/cucumber/gremlin.go              |  2 +
 .../test/cucumber/feature-steps.js                 | 41 +++++++++++++++++----
 .../gremlin-javascript/test/cucumber/gremlin.js    |  2 +
 .../src/main/python/tests/feature/feature_steps.py | 25 ++++++++++++-
 .../src/main/python/tests/feature/gremlin.py       |  2 +
 .../tinkerpop/gremlin/features/StepDefinition.java | 43 ++++++++++++++++++----
 .../gremlin/test/features/map/Fold.feature         | 24 +++++++++++-
 10 files changed, 184 insertions(+), 22 deletions(-)

diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
index 1b747e1bf3..bb3cf3e671 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
@@ -492,7 +492,39 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
             {
                 return new List<object?>(0);
             }
-            return stringList.Split(',').Select(x => ParseValue(x, graphName)).ToList();
+            return SplitByElement(stringList).Select(x => ParseValue(x, graphName)).ToList();
+        }
+
+        private static List<string> SplitByElement(string s)
+        {
+            var result = new List<string>();
+            var depth = 0;
+            var current = new System.Text.StringBuilder();
+            foreach (var c in s)
+            {
+                if (c == '[')
+                {
+                    depth++;
+                    current.Append(c);
+                }
+                else if (c == ']')
+                {
+                    depth--;
+                    current.Append(c);
+                }
+                else if (c == ',' && depth == 0)
+                {
+                    result.Add(current.ToString().Trim());
+                    current.Clear();
+                }
+                else
+                {
+                    current.Append(c);
+                }
+            }
+            if (current.Length > 0)
+                result.Add(current.ToString().Trim());
+            return result;
         }
 
         private static object ToDateTime(string date, string graphName)
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 2efd471c8e..22f94c80a6 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -1187,6 +1187,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_V_age_foldX0_plusX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Fold<object>(0, Operator.Sum)}}, 
                {"g_injectXa1_b2X_foldXm_addAllX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(new Dictionary<object, object> {{ "a", 1 }}, new Dictionary<object, object> {{ "b", 2 }}).Fold<object>(new Dictionary<object, object> {}, Operator.AddAll)}}, 
                {"g_injectXa1_b2_b4X_foldXm_addAllX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(new Dictionary<object, object> {{ "a", 1 }}, new Dictionary<object, object> {{ "b", 2 }}, new Dictionary<object, object> {{ "b", 4 }}).Fold<object>(new Dictionary<object, object> {}, Operator.AddAll)}}, 
+               {"g_injectXlist1_list2X_fold", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(new List<object> { 1, 2 }, new List<object> { 3, 4 }).Fold()}}, 
+               {"g_injectXlist1_list2_list3X_fold", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(new List<object> { 1, 2 }, new List<object> { 3, 4 }, new List<object> { 5, 6 }).Fold()}}, 
                {"g_VX1X_formatXstrX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name", "marko").Format("Hello world")}}, 
                {"g_V_formatXstrX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Format("%{name} is %{age} years old")}}, 
                {"g_injectX1X_asXageX_V_formatXstrX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1).As("age").V().Format("%{name} is %{age} years old")}}, 
diff --git a/gremlin-go/driver/cucumber/cucumberSteps_test.go b/gremlin-go/driver/cucumber/cucumberSteps_test.go
index f0de2bd94c..87d1ecdae0 100644
--- a/gremlin-go/driver/cucumber/cucumberSteps_test.go
+++ b/gremlin-go/driver/cucumber/cucumberSteps_test.go
@@ -256,6 +256,33 @@ func toPath(stringObjects, graphName string) interface{} {
 	}
 }
 
+// splitByElement splits a string on commas while respecting bracket nesting depth,
+// so that nested tokens like l[1,2,3] inside s[l[1,2,3],l[4,5,6]] are not split incorrectly.
+func splitByElement(s string) []string {
+	var result []string
+	depth := 0
+	current := strings.Builder{}
+	for _, c := range s {
+		switch {
+		case c == '[':
+			depth++
+			current.WriteRune(c)
+		case c == ']':
+			depth--
+			current.WriteRune(c)
+		case c == ',' && depth == 0:
+			result = append(result, strings.TrimSpace(current.String()))
+			current.Reset()
+		default:
+			current.WriteRune(c)
+		}
+	}
+	if current.Len() > 0 {
+		result = append(result, strings.TrimSpace(current.String()))
+	}
+	return result
+}
+
 // Parse list.
 func toList(stringList, graphName string) interface{} {
 	listVal := make([]interface{}, 0)
@@ -263,7 +290,7 @@ func toList(stringList, graphName string) interface{} {
 		return listVal
 	}
 
-	for _, str := range strings.Split(stringList, ",") {
+	for _, str := range splitByElement(stringList) {
 		listVal = append(listVal, parseValue(str, graphName))
 	}
 	return listVal
@@ -275,7 +302,7 @@ func toSet(stringSet, graphName string) interface{} {
 	if len(stringSet) == 0 {
 		return setVal
 	}
-	for _, str := range strings.Split(stringSet, ",") {
+	for _, str := range splitByElement(stringSet) {
 		setVal.Add(parseValue(str, graphName))
 	}
 	return setVal
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index aa9d08ee30..a26f91342f 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -1157,6 +1157,8 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
     "g_V_age_foldX0_plusX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Fold(0, gremlingo.Operator.Sum)}}, 
     "g_injectXa1_b2X_foldXm_addAllX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(map[interface{}]interface{}{"a": 1 }, map[interface{}]interface{}{"b": 2 }).Fold(map[interface{}]interface{}{ }, gremlingo.Operator.AddAll)}}, 
     "g_injectXa1_b2_b4X_foldXm_addAllX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(map[interface{}]interface{}{"a": 1 }, map[interface{}]interface{}{"b": 2 }, map[interface{}]interface{}{"b": 4 }).Fold(map[interface{}]interface{}{ }, gremlingo.Operator.AddAll)}}, 
+    "g_injectXlist1_list2X_fold": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{1, 2}, []interface{}{3, 4}).Fold()}}, 
+    "g_injectXlist1_list2_list3X_fold": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{1, 2}, []interface{}{3, 4}, []interface{}{5, 6}).Fold()}}, 
     "g_VX1X_formatXstrX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", "marko").Format("Hello world")}}, 
     "g_V_formatXstrX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Format("%{name} is %{age} years old")}}, 
     "g_injectX1X_asXageX_V_formatXstrX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1).As("age").V().Format("%{name} is %{age} years old")}}, 
diff --git a/gremlin-js/gremlin-javascript/test/cucumber/feature-steps.js b/gremlin-js/gremlin-javascript/test/cucumber/feature-steps.js
index 59eeb1d76a..e8ac2d1c1c 100644
--- a/gremlin-js/gremlin-javascript/test/cucumber/feature-steps.js
+++ b/gremlin-js/gremlin-javascript/test/cucumber/feature-steps.js
@@ -448,22 +448,47 @@ function toMerge(value) {
   return merge[value];
 }
 
+function splitByElement(s) {
+  let depth = 0;
+  let current = '';
+  const results = [];
+  for (const c of s) {
+    if (c === '[') {
+      depth++;
+      current += c;
+    } else if (c === ']') {
+      depth--;
+      current += c;
+    } else if (c === ',' && depth === 0) {
+      results.push(current.trim());
+      current = '';
+    } else {
+      current += c;
+    }
+  }
+  if (current.length > 0) results.push(current.trim());
+  return results;
+}
+
 function toArray(stringList) {
   if (stringList === '') {
     return new Array(0);
   }
-  return stringList.split(',').map(x => parseValue.call(this, x));
-}
-
-function toMap(stringMap) {
-  return parseMapValue.call(this, JSON.parse(stringMap));
+  return splitByElement(stringList).map(x => parseValue.call(this, x));
 }
 
-function toSet(stringSet) {
-  if (stringSet === '') {
+function toSet(stringList) {
+  if (stringList === '') {
     return new Set();
   }
-  return new Set(stringSet.split(',').map(x => parseValue.call(this, x)));
+
+  const s = new Set();
+  splitByElement(stringList).forEach(x => s.add(parseValue.call(this, x)));
+  return s;
+}
+
+function toMap(stringMap) {
+  return parseMapValue.call(this, JSON.parse(stringMap));
 }
 
 function parseMapValue(value) {
diff --git a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
index 66138f47f6..fb90ed1dd4 100644
--- a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
@@ -1188,6 +1188,8 @@ const gremlins = {
     g_V_age_foldX0_plusX: [function({g}) { return g.V().values("age").fold(0, Operator.sum) }], 
     g_injectXa1_b2X_foldXm_addAllX: [function({g}) { return g.inject(new Map([["a", 1]]), new Map([["b", 2]])).fold(new Map([]), Operator.addAll) }], 
     g_injectXa1_b2_b4X_foldXm_addAllX: [function({g}) { return g.inject(new Map([["a", 1]]), new Map([["b", 2]]), new Map([["b", 4]])).fold(new Map([]), Operator.addAll) }], 
+    g_injectXlist1_list2X_fold: [function({g}) { return g.inject([1, 2], [3, 4]).fold() }], 
+    g_injectXlist1_list2_list3X_fold: [function({g}) { return g.inject([1, 2], [3, 4], [5, 6]).fold() }], 
     g_VX1X_formatXstrX: [function({g}) { return g.V().has("name", "marko").format("Hello world") }], 
     g_V_formatXstrX: [function({g}) { return g.V().format("%{name} is %{age} years old") }], 
     g_injectX1X_asXageX_V_formatXstrX: [function({g}) { return g.inject(1).as("age").V().format("%{name} is %{age} years old") }], 
diff --git a/gremlin-python/src/main/python/tests/feature/feature_steps.py b/gremlin-python/src/main/python/tests/feature/feature_steps.py
index c074bf171a..f82b65ab55 100644
--- a/gremlin-python/src/main/python/tests/feature/feature_steps.py
+++ b/gremlin-python/src/main/python/tests/feature/feature_steps.py
@@ -324,6 +324,27 @@ def unsupported_scenario(step, file):
     return
 
 
+def _split_by_element(s):
+    depth = 0
+    current = []
+    results = []
+    for c in s:
+        if c == '[':
+            depth += 1
+            current.append(c)
+        elif c == ']':
+            depth -= 1
+            current.append(c)
+        elif c == ',' and depth == 0:
+            results.append(''.join(current).strip())
+            current = []
+        else:
+            current.append(c)
+    if current:
+        results.append(''.join(current).strip())
+    return results
+
+
 def _convert(val, ctx):
     graph_name = ctx.graph_name
     if isinstance(val, dict):  # convert dictionary keys/values
@@ -334,9 +355,9 @@ def _convert(val, ctx):
             n[tuple(k) if isinstance(k, (set, list)) else k] = _convert(value, ctx)
         return n
     elif isinstance(val, str) and re.match(r"^l\[.*\]$", val):  # parse list
-        return [] if val == "l[]" else list(map((lambda x: _convert(x, ctx)), val[2:-1].split(",")))
+        return [] if val == "l[]" else list(map((lambda x: _convert(x, ctx)), _split_by_element(val[2:-1])))
     elif isinstance(val, str) and re.match(r"^s\[.*\]$", val):  # parse set
-        return set() if val == "s[]" else set(map((lambda x: _convert(x, ctx)), val[2:-1].split(",")))
+        return set() if val == "s[]" else set(map((lambda x: _convert(x, ctx)), _split_by_element(val[2:-1])))
     elif isinstance(val, str) and re.match(r"^str\[.*\]$", val):  # return string as is
         return val[4:-1]
     elif isinstance(val, str) and re.match(r"^dt\[.*\]$", val):  # parse datetime
diff --git a/gremlin-python/src/main/python/tests/feature/gremlin.py b/gremlin-python/src/main/python/tests/feature/gremlin.py
index d1cfbf06ab..fa469b2d3b 100644
--- a/gremlin-python/src/main/python/tests/feature/gremlin.py
+++ b/gremlin-python/src/main/python/tests/feature/gremlin.py
@@ -1160,6 +1160,8 @@ world.gremlins = {
     'g_V_age_foldX0_plusX': [(lambda g:g.V().values('age').fold(0, Operator.sum_))], 
     'g_injectXa1_b2X_foldXm_addAllX': [(lambda g:g.inject({ 'a': 1 }, { 'b': 2 }).fold({  }, Operator.add_all))], 
     'g_injectXa1_b2_b4X_foldXm_addAllX': [(lambda g:g.inject({ 'a': 1 }, { 'b': 2 }, { 'b': 4 }).fold({  }, Operator.add_all))], 
+    'g_injectXlist1_list2X_fold': [(lambda g:g.inject([1, 2], [3, 4]).fold())], 
+    'g_injectXlist1_list2_list3X_fold': [(lambda g:g.inject([1, 2], [3, 4], [5, 6]).fold())], 
     'g_VX1X_formatXstrX': [(lambda g:g.V().has('name', 'marko').format_('Hello world'))], 
     'g_V_formatXstrX': [(lambda g:g.V().format_('%{name} is %{age} years old'))], 
     'g_injectX1X_asXageX_V_formatXstrX': [(lambda g:g.inject(1).as_('age').V().format_('%{name} is %{age} years old'))], 
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
index 1d653c8a89..09987991ac 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
@@ -135,8 +135,8 @@ public final class StepDefinition {
         }));
         add(Pair.with(Pattern.compile("l\\[\\]"), s -> "[]"));
         add(Pair.with(Pattern.compile("l\\[(.*)\\]"), s -> {
-            final String[] items = s.split(",");
-            final String listItems = Stream.of(items).map(String::trim).map(x -> convertToString(x)).collect(Collectors.joining(","));
+            final List<String> items = splitByElement(s);
+            final String listItems = items.stream().map(String::trim).map(x -> convertToString(x)).collect(Collectors.joining(","));
             return String.format("[%s]", listItems);
         }));
         add(Pair.with(Pattern.compile("s\\[\\]"), s -> "{}"));
@@ -147,8 +147,8 @@ public final class StepDefinition {
         }));
         add(Pair.with(Pattern.compile("s\\[\\]"), s -> String.format("{}")));
         add(Pair.with(Pattern.compile("s\\[(.*)\\]"), s -> {
-            final String[] items = s.split(",");
-            final String listItems = Stream.of(items).map(String::trim).map(x -> convertToString(x)).collect(Collectors.joining(","));
+            final List<String> items = splitByElement(s);
+            final String listItems = items.stream().map(String::trim).map(x -> convertToString(x)).collect(Collectors.joining(","));
             return String.format("{%s}", listItems);
         }));
         add(Pair.with(Pattern.compile("d\\[(NaN)\\]"), s -> "NaN"));
@@ -191,14 +191,14 @@ public final class StepDefinition {
 
         add(Pair.with(Pattern.compile("l\\[\\]"), s -> new ArrayList<>()));
         add(Pair.with(Pattern.compile("l\\[(.*)\\]"), s -> {
-            final String[] items = s.split(",");
-            return Stream.of(items).map(String::trim).map(x -> convertToObject(x)).collect(Collectors.toList());
+            final List<String> items = splitByElement(s);
+            return items.stream().map(String::trim).map(x -> convertToObject(x)).collect(Collectors.toList());
         }));
 
         add(Pair.with(Pattern.compile("s\\[\\]"), s -> new HashSet<>()));
         add(Pair.with(Pattern.compile("s\\[(.*)\\]"), s -> {
-            final String[] items = s.split(",");
-            return Stream.of(items).map(String::trim).map(x -> convertToObject(x)).collect(Collectors.toSet());
+            final List<String> items = splitByElement(s);
+            return items.stream().map(String::trim).map(x -> convertToObject(x)).collect(Collectors.toSet());
         }));
 
         // return the string values as is, used to wrap results that may contain other regex patterns
@@ -713,6 +713,33 @@ public final class StepDefinition {
         return String.format("%s", v);
     }
 
+    /**
+     * Splits a string on commas while respecting bracket nesting, so that nested collection tokens
+     * like {@code l[1,2,3]} inside a set {@code s[l[1,2,3],l[4,5,6]]} are not incorrectly split.
+     */
+    private static List<String> splitByElement(final String s) {
+        final List<String> result = new ArrayList<>();
+        int depth = 0;
+        final StringBuilder current = new StringBuilder();
+        for (int i = 0; i < s.length(); i++) {
+            final char c = s.charAt(i);
+            if (c == '[') {
+                depth++;
+                current.append(c);
+            } else if (c == ']') {
+                depth--;
+                current.append(c);
+            } else if (c == ',' && depth == 0) {
+                result.add(current.toString());
+                current.setLength(0);
+            } else {
+                current.append(c);
+            }
+        }
+        if (current.length() > 0) result.add(current.toString());
+        return result;
+    }
+
     private static Triplet<String,String,String> getEdgeTriplet(final String e) {
         final Matcher m = edgeTripletPattern.matcher(e);
         if (m.matches()) {
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Fold.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Fold.feature
index a9493eae49..a721aab5b1 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Fold.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Fold.feature
@@ -81,4 +81,26 @@ Feature: Step - fold()
     When iterated to list
     Then the result should be unordered
       | result |
-      | m[{"a":"d[1].i", "b":"d[4].i"}] |
\ No newline at end of file
+      | m[{"a":"d[1].i", "b":"d[4].i"}] |
+
+  Scenario: g_injectXlist1_list2X_fold
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject([1, 2], [3, 4]).fold()
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | l[l[d[1].i,d[2].i],l[d[3].i,d[4].i]] |
+
+  Scenario: g_injectXlist1_list2_list3X_fold
+    Given the empty graph
+    And the traversal of
+      """
+      g.inject([1, 2], [3, 4], [5, 6]).fold()
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | l[l[d[1].i,d[2].i],l[d[3].i,d[4].i],l[d[5].i,d[6].i]] |
\ No newline at end of file