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

xiazcy pushed a commit to branch steps-taking-traversal-poc
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit a9b52b44c6c875e8af1d9734569105b44b8a14b6
Author: Yang Xia <[email protected]>
AuthorDate: Wed May 13 20:12:24 2026 -0700

    Add multi-traversal within()/without() support, fix HasContainer folding to 
continue past traversal-bearing steps, and reject V(traversal)/E(traversal) at 
parse time for start steps
---
 .../grammar/TraversalSourceSpawnMethodVisitor.java |  22 +--
 .../gremlin/process/traversal/GremlinLang.java     |  11 +-
 .../tinkerpop/gremlin/process/traversal/P.java     | 149 ++++++++++++++++++-
 .../gremlin/process/traversal/PTraversalTest.java  | 160 +++++++++++++++++++++
 gremlin-language/src/main/antlr4/Gremlin.g4        |   2 -
 .../gremlin/language/translator/translations.json  | 153 ++++++++++++++++++++
 .../test/features/filter/HasTraversal.feature      | 121 ++++++++++++++++
 .../test/features/filter/IsTraversal.feature       |  16 +++
 .../optimization/TinkerGraphStepStrategy.java      |   9 +-
 .../TinkerGraphStepStrategyTraversalTest.java      |  55 ++++++-
 10 files changed, 678 insertions(+), 20 deletions(-)

diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
index bed196c29e..f2ca8b7299 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java
@@ -99,12 +99,13 @@ public class TraversalSourceSpawnMethodVisitor extends 
DefaultGremlinBaseVisitor
      */
     @Override
     public GraphTraversal visitTraversalSourceSpawnMethod_E(final 
GremlinParser.TraversalSourceSpawnMethod_EContext ctx) {
-        if (ctx.nestedTraversal() != null) {
-            return this.traversalSource.E((Traversal<?, ?>) 
anonymousVisitor.visitNestedTraversal(ctx.nestedTraversal()));
-        }
         final Object[] args = 
antlr.argumentVisitor.parseObjectVarargs(ctx.genericArgumentVarargs());
-        if (args.length == 1 && args[0] instanceof Traversal) {
-            return this.traversalSource.E((Traversal<?, ?>) args[0]);
+        for (final Object arg : args) {
+            if (arg instanceof Traversal) {
+                throw new IllegalArgumentException(
+                        "E(traversal) cannot be used as a start step because 
there is no Traverser context " +
+                        "available to evaluate the child traversal. Use 
E(traversal) as a mid-traversal step instead.");
+            }
         }
         return this.traversalSource.E(args);
     }
@@ -114,12 +115,13 @@ public class TraversalSourceSpawnMethodVisitor extends 
DefaultGremlinBaseVisitor
      */
     @Override
     public GraphTraversal visitTraversalSourceSpawnMethod_V(final 
GremlinParser.TraversalSourceSpawnMethod_VContext ctx) {
-        if (ctx.nestedTraversal() != null) {
-            return this.traversalSource.V((Traversal<?, ?>) 
anonymousVisitor.visitNestedTraversal(ctx.nestedTraversal()));
-        }
         final Object[] args = 
antlr.argumentVisitor.parseObjectVarargs(ctx.genericArgumentVarargs());
-        if (args.length == 1 && args[0] instanceof Traversal) {
-            return this.traversalSource.V((Traversal<?, ?>) args[0]);
+        for (final Object arg : args) {
+            if (arg instanceof Traversal) {
+                throw new IllegalArgumentException(
+                        "V(traversal) cannot be used as a start step because 
there is no Traverser context " +
+                        "available to evaluate the child traversal. Use 
V(traversal) as a mid-traversal step instead.");
+            }
         }
         return this.traversalSource.V(args);
     }
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java
index 5fd67b617e..f38bb01c4f 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLang.java
@@ -314,7 +314,16 @@ public class GremlinLang implements Cloneable, 
Serializable {
         } else if (p.hasTraversal()) {
             // Traversal-bearing predicate: serialize as 
P.op(traversalGremlinLang)
             sb.append("P.").append(p.getPredicateName()).append("(");
-            sb.append(argAsString(p.getTraversalValue()));
+            if (p.getTraversalValues() != null) {
+                // Multi-traversal predicate (within/without with multiple 
traversals)
+                final List<Traversal.Admin<?, ?>> traversals = 
p.getTraversalValues();
+                for (int i = 0; i < traversals.size(); i++) {
+                    if (i > 0) sb.append(",");
+                    sb.append(argAsString(traversals.get(i)));
+                }
+            } else {
+                sb.append(argAsString(p.getTraversalValue()));
+            }
         } else {
             sb.append("P.").append(p.getPredicateName()).append("(");
             sb.append(argAsString(p.getValue()));
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java
index 4c05bc0135..dbaa10f7da 100644
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java
+++ 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java
@@ -56,6 +56,7 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
     private boolean isCollection = false;
     private boolean resolvedEmpty = false;
     private Traversal.Admin<?, ?> traversalValue;
+    private List<Traversal.Admin<?, ?>> traversalValues;
 
     public P(final PBiPredicate<V, V> biPredicate, final V value) {
         this.biPredicate = biPredicate;
@@ -108,6 +109,19 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
         this.traversalValue = traversalValue;
     }
 
+    /**
+     * Constructs a {@code P} with multiple child traversals whose results are 
unioned at runtime against the
+     * current traverser. Only valid for collection predicates ({@link 
Contains#within}, {@link Contains#without}).
+     * The literals and variables are left at their defaults and will be 
populated when
+     * {@link #resolve(Traverser.Admin)} is called.
+     *
+     * @since 4.0.0
+     */
+    public P(final PBiPredicate<V, V> biPredicate, final 
List<Traversal.Admin<?, ?>> traversalValues) {
+        this.biPredicate = biPredicate;
+        this.traversalValues = traversalValues;
+    }
+
     public PBiPredicate<V, V> getBiPredicate() {
         return this.biPredicate;
     }
@@ -191,6 +205,8 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
             result ^= this.literals.hashCode();
         if (null != this.traversalValue)
             result ^= this.traversalValue.hashCode();
+        if (null != this.traversalValues)
+            result ^= this.traversalValues.hashCode();
         return result;
     }
 
@@ -201,7 +217,8 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
                 ((P) other).getBiPredicate().equals(this.biPredicate) &&
                 ((((P) other).variables == null && this.variables == null) || 
(((P) other).variables != null && ((P) 
other).variables.equals(this.variables))) &&
                 ((((P) other).literals == null && this.literals == null) || 
(((P) other).literals != null && CollectionUtils.isEqualCollection(((P) 
other).literals, this.literals))) &&
-                Objects.equals(((P) other).traversalValue, 
this.traversalValue);
+                Objects.equals(((P) other).traversalValue, 
this.traversalValue) &&
+                Objects.equals(((P) other).traversalValues, 
this.traversalValues);
     }
 
     @Override
@@ -234,6 +251,12 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
             if (this.traversalValue != null) {
                 clone.traversalValue = this.traversalValue.clone();
             }
+            if (this.traversalValues != null) {
+                clone.traversalValues = new 
ArrayList<>(this.traversalValues.size());
+                for (final Traversal.Admin<?, ?> tv : this.traversalValues) {
+                    clone.traversalValues.add(tv.clone());
+                }
+            }
             return clone;
         } catch (final CloneNotSupportedException e) {
             throw new IllegalStateException(e.getMessage(), e);
@@ -262,7 +285,7 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
      * Determines if this predicate holds a child traversal whose result is 
resolved at runtime.
      */
     public boolean hasTraversal() {
-        return this.traversalValue != null;
+        return this.traversalValue != null || (this.traversalValues != null && 
!this.traversalValues.isEmpty());
     }
 
     /**
@@ -282,6 +305,16 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
         return this.traversalValue;
     }
 
+    /**
+     * Gets the list of child traversal values for multi-traversal predicates 
(e.g., {@code within(trav1, trav2)}).
+     * Returns {@code null} when this predicate uses a single traversal or 
literal values.
+     *
+     * @since 4.0.0
+     */
+    public List<Traversal.Admin<?, ?>> getTraversalValues() {
+        return this.traversalValues;
+    }
+
     /**
      * Resolves the child traversal against the given traverser, replacing the 
traversal value with the
      * resolved literal(s) for this test cycle. If no traversal is present, 
this method returns immediately.
@@ -289,9 +322,18 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
      * <p>For collection predicates ({@link Contains}), all results are 
collected into the literals
      * collection. For all other predicates ({@link Compare}, {@link Text}, 
etc.), the traversal must
      * produce exactly one result or an {@link IllegalArgumentException} is 
thrown.</p>
+     *
+     * <p>When multiple traversals are present (via {@link #traversalValues}), 
each traversal is evaluated
+     * independently and results are unioned into a single collection. This is 
only valid for collection
+     * predicates ({@link Contains#within}, {@link Contains#without}).</p>
      */
     @SuppressWarnings("unchecked")
     public void resolve(final Traverser.Admin<?> traverser) {
+        if (this.traversalValues != null && !this.traversalValues.isEmpty()) {
+            resolveMultipleTraversals(traverser);
+            return;
+        }
+
         if (this.traversalValue == null) return;
 
         // Use prepare + iteration directly to avoid ambiguous overload 
resolution of applyAll
@@ -329,6 +371,38 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
         }
     }
 
+    /**
+     * Resolves multiple child traversals, unioning their results into a 
single collection.
+     * Only valid for collection predicates ({@link Contains}).
+     */
+    @SuppressWarnings("unchecked")
+    private void resolveMultipleTraversals(final Traverser.Admin<?> traverser) 
{
+        final List<Object> allResults = new ArrayList<>();
+
+        for (final Traversal.Admin<?, ?> tv : this.traversalValues) {
+            final Traversal.Admin<Object, Object> trav = 
(Traversal.Admin<Object, Object>) (Traversal.Admin) tv;
+            final Traverser.Admin<Object> split = (Traverser.Admin<Object>) 
traverser.split();
+            split.setSideEffects(trav.getSideEffects());
+            split.setBulk(1L);
+            trav.reset();
+            trav.addStart(split);
+
+            while (trav.hasNext()) {
+                allResults.add(trav.next());
+            }
+        }
+
+        this.resolvedEmpty = allResults.isEmpty();
+
+        if (allResults.isEmpty()) {
+            this.literals = Collections.emptyList();
+            this.isCollection = false;
+        } else {
+            this.literals = (Collection<V>) (Collection<?>) allResults;
+            this.isCollection = true;
+        }
+    }
+
     //////////////// predicate traversal utilities
 
     /**
@@ -344,6 +418,10 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
             integrateTraversals(((NotP<?>) p).negate(), parent);
         } else if (p.getTraversalValue() != null) {
             parent.integrateChild(p.getTraversalValue());
+        } else if (p.getTraversalValues() != null) {
+            for (final Traversal.Admin<?, ?> tv : p.getTraversalValues()) {
+                parent.integrateChild(tv);
+            }
         }
     }
 
@@ -360,6 +438,8 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
             collectTraversals(((NotP<?>) p).negate(), traversals);
         } else if (p.getTraversalValue() != null) {
             traversals.add(p.getTraversalValue());
+        } else if (p.getTraversalValues() != null) {
+            traversals.addAll(p.getTraversalValues());
         }
     }
 
@@ -539,6 +619,14 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
         if (values != null && values.length == 1 && values[0] instanceof 
Traversal) {
             return P.within((Traversal<?, ?>) values[0]);
         }
+        // If multiple Traversals are passed, redirect to the multi-traversal 
overload.
+        if (values != null && values.length > 1 && allTraversals(values)) {
+            final List<Traversal.Admin<?, ?>> traversals = new 
ArrayList<>(values.length);
+            for (final V v : values) {
+                traversals.add(((Traversal<?, ?>) v).asAdmin());
+            }
+            return new P(Contains.within, traversals);
+        }
         final V[] v = null == values ? (V[]) new Object[] { null } : (V[]) 
values;
         return P.within(Arrays.asList(v));
     }
@@ -576,6 +664,14 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
         if (values != null && values.length == 1 && values[0] instanceof 
Traversal) {
             return P.without((Traversal<?, ?>) values[0]);
         }
+        // If multiple Traversals are passed, redirect to the multi-traversal 
overload.
+        if (values != null && values.length > 1 && allTraversals(values)) {
+            final List<Traversal.Admin<?, ?>> traversals = new 
ArrayList<>(values.length);
+            for (final V v : values) {
+                traversals.add(((Traversal<?, ?>) v).asAdmin());
+            }
+            return new P(Contains.without, traversals);
+        }
         final V[] v = null == values ? (V[]) new Object[] { null } : values;
         return P.without(Arrays.asList(v));
     }
@@ -665,6 +761,24 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
         return new P(Contains.within, traversalValue.asAdmin());
     }
 
+    /**
+     * Determines if a value is within the union of results from multiple 
child traversals resolved at runtime.
+     * Each traversal is evaluated independently and results are combined into 
a single collection.
+     *
+     * @since 4.0.0
+     */
+    public static <V> P<V> within(final Traversal<?, ?> first, final 
Traversal<?, ?> second, final Traversal<?, ?>... more) {
+        final List<Traversal.Admin<?, ?>> traversals = new ArrayList<>(2 + 
(more != null ? more.length : 0));
+        traversals.add(first.asAdmin());
+        traversals.add(second.asAdmin());
+        if (more != null) {
+            for (final Traversal<?, ?> tv : more) {
+                traversals.add(tv.asAdmin());
+            }
+        }
+        return new P(Contains.within, traversals);
+    }
+
     /**
      * Determines if a value is not within the results of a child traversal 
resolved at runtime.
      *
@@ -674,6 +788,24 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
         return new P(Contains.without, traversalValue.asAdmin());
     }
 
+    /**
+     * Determines if a value is not within the union of results from multiple 
child traversals resolved at runtime.
+     * Each traversal is evaluated independently and results are combined into 
a single collection.
+     *
+     * @since 4.0.0
+     */
+    public static <V> P<V> without(final Traversal<?, ?> first, final 
Traversal<?, ?> second, final Traversal<?, ?>... more) {
+        final List<Traversal.Admin<?, ?>> traversals = new ArrayList<>(2 + 
(more != null ? more.length : 0));
+        traversals.add(first.asAdmin());
+        traversals.add(second.asAdmin());
+        if (more != null) {
+            for (final Traversal<?, ?> tv : more) {
+                traversals.add(tv.asAdmin());
+            }
+        }
+        return new P(Contains.without, traversals);
+    }
+
     /**
      * Determines if a value is of a type denoted by {@code GType}.
      *
@@ -718,4 +850,17 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
     public static <V> P<V> not(final P<V> predicate) {
         return predicate.negate();
     }
+
+    /**
+     * Checks if all elements in the array are {@link Traversal} instances 
(specifically
+     * {@link 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal}).
+     */
+    private static <V> boolean allTraversals(final V[] values) {
+        for (final V v : values) {
+            if (!(v instanceof 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal))
 {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTraversalTest.java
 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTraversalTest.java
index 0485f9e509..3e7af19d8b 100644
--- 
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTraversalTest.java
+++ 
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTraversalTest.java
@@ -215,4 +215,164 @@ public class PTraversalTest {
             assertThat(p.test(99), is(false));
         }
     }
+
+    /**
+     * Tests for multi-traversal support in within() and without().
+     * <p>
+     * When multiple traversals are passed to within(trav1, trav2, ...), each 
traversal is evaluated
+     * independently and results are unioned into a single collection for the 
Contains test.
+     */
+    public static class MultiTraversalTest {
+
+        private Traverser.Admin<?> createTraverser(final Object value) {
+            return new B_O_Traverser<>(value, 1L);
+        }
+
+        // --- Detection ---
+
+        @Test
+        public void shouldDetectMultipleTraversalsInWithin() {
+            final P<Object> p = P.within(__.constant(1).asAdmin(), 
__.constant(2).asAdmin());
+            assertThat(p.hasTraversal(), is(true));
+        }
+
+        @Test
+        public void shouldDetectMultipleTraversalsInWithout() {
+            final P<Object> p = P.without(__.constant(1).asAdmin(), 
__.constant(2).asAdmin());
+            assertThat(p.hasTraversal(), is(true));
+        }
+
+        @Test
+        public void shouldReturnTraversalValuesListForMultiTraversal() {
+            final P<Object> p = P.within(__.constant(1).asAdmin(), 
__.constant(2).asAdmin());
+            assertThat(p.getTraversalValues() != null, is(true));
+            assertThat(p.getTraversalValues().size(), is(2));
+        }
+
+        @Test
+        public void shouldReturnNullTraversalValueForMultiTraversal() {
+            // Single traversalValue should be null when using multi-traversal 
form
+            final P<Object> p = P.within(__.constant(1).asAdmin(), 
__.constant(2).asAdmin());
+            assertThat(p.getTraversalValue() == null, is(true));
+        }
+
+        // --- Resolution and testing ---
+
+        @SuppressWarnings("unchecked")
+        @Test
+        public void shouldResolveMultipleTraversalsForWithin() {
+            // within(__.constant(1), __.constant(2)) should union results: 
[1, 2]
+            final P<Object> p = P.within(__.constant(1).asAdmin(), 
__.constant(2).asAdmin());
+            p.resolve(createTraverser("start"));
+            assertThat(p.test(1), is(true));
+            assertThat(p.test(2), is(true));
+            assertThat(p.test(3), is(false));
+        }
+
+        @SuppressWarnings("unchecked")
+        @Test
+        public void shouldResolveMultipleTraversalsForWithout() {
+            // without(__.constant(1), __.constant(2)) should union results: 
[1, 2]
+            final P<Object> p = P.without(__.constant(1).asAdmin(), 
__.constant(2).asAdmin());
+            p.resolve(createTraverser("start"));
+            assertThat(p.test(1), is(false));
+            assertThat(p.test(2), is(false));
+            assertThat(p.test(3), is(true));
+        }
+
+        @SuppressWarnings("unchecked")
+        @Test
+        public void shouldResolveMultipleTraversalsWithMultipleResultsEach() {
+            // within(__.inject(1,2), __.inject(3,4)) should union: [1, 2, 3, 
4]
+            final P<Object> p = P.within(__.inject(1, 2).asAdmin(), 
__.inject(3, 4).asAdmin());
+            p.resolve(createTraverser("start"));
+            assertThat(p.test(1), is(true));
+            assertThat(p.test(2), is(true));
+            assertThat(p.test(3), is(true));
+            assertThat(p.test(4), is(true));
+            assertThat(p.test(5), is(false));
+        }
+
+        @SuppressWarnings("unchecked")
+        @Test
+        public void shouldHandleEmptyResultFromOneTraversal() {
+            // within(__.inject(1,2), __.limit(0)) where second produces 
nothing
+            // Should still match on results from first traversal
+            final P<Object> p = P.within(__.inject(1, 2).asAdmin(), 
__.limit(0).asAdmin());
+            p.resolve(createTraverser("start"));
+            assertThat(p.isResolvedEmpty(), is(false));
+            assertThat(p.test(1), is(true));
+            assertThat(p.test(2), is(true));
+            assertThat(p.test(3), is(false));
+        }
+
+        @SuppressWarnings("unchecked")
+        @Test
+        public void shouldHandleAllEmptyResults() {
+            // within(__.limit(0), __.limit(0)) where both produce nothing
+            final P<Object> p = P.within(__.limit(0).asAdmin(), 
__.limit(0).asAdmin());
+            p.resolve(createTraverser("start"));
+            assertThat(p.isResolvedEmpty(), is(true));
+        }
+
+        @SuppressWarnings("unchecked")
+        @Test
+        public void shouldResolveThreeTraversals() {
+            // within(__.constant("a"), __.constant("b"), __.constant("c"))
+            final P<Object> p = P.within(__.constant("a").asAdmin(), 
__.constant("b").asAdmin(), __.constant("c").asAdmin());
+            p.resolve(createTraverser("start"));
+            assertThat(p.test("a"), is(true));
+            assertThat(p.test("b"), is(true));
+            assertThat(p.test("c"), is(true));
+            assertThat(p.test("d"), is(false));
+        }
+
+        // --- Clone independence ---
+
+        @SuppressWarnings("unchecked")
+        @Test
+        public void shouldCloneMultiTraversalPredicate() {
+            final P<Object> original = P.within(__.constant(1).asAdmin(), 
__.constant(2).asAdmin());
+            final P<Object> clone = original.clone();
+
+            // Clone should have independent traversal values
+            assertThat(clone.hasTraversal(), is(true));
+            assertThat(clone.getTraversalValues() != null, is(true));
+            assertThat(clone.getTraversalValues().size(), is(2));
+
+            // Modifying clone should not affect original
+            clone.resolve(createTraverser("start"));
+            assertThat(clone.test(1), is(true));
+            // Original should still be unresolved (literals empty)
+            assertThat(original.getTraversalValues().size(), is(2));
+        }
+
+        // --- Varargs detection ---
+
+        @SuppressWarnings("unchecked")
+        @Test
+        public void shouldDetectMultipleTraversalsInVarargs() {
+            // This tests the varargs path: P.within(trav1, trav2) going 
through within(V... values)
+            final Traversal<?, ?> trav1 = __.constant(10);
+            final Traversal<?, ?> trav2 = __.constant(20);
+            final P<Object> p = (P<Object>) P.within(trav1, trav2);
+            assertThat(p.hasTraversal(), is(true));
+            assertThat(p.getTraversalValues() != null, is(true));
+            assertThat(p.getTraversalValues().size(), is(2));
+            p.resolve(createTraverser("start"));
+            assertThat(p.test(10), is(true));
+            assertThat(p.test(20), is(true));
+            assertThat(p.test(30), is(false));
+        }
+
+        // --- collectTraversals and integrateTraversals ---
+
+        @Test
+        public void shouldCollectTraversalsFromMultiTraversalPredicate() {
+            final P<Object> p = P.within(__.constant(1).asAdmin(), 
__.constant(2).asAdmin(), __.constant(3).asAdmin());
+            final java.util.List<Traversal.Admin<?, ?>> collected = new 
java.util.ArrayList<>();
+            P.collectTraversals(p, collected);
+            assertThat(collected.size(), is(3));
+        }
+    }
 }
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 
b/gremlin-language/src/main/antlr4/Gremlin.g4
index 822cb59481..b161a271d3 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -123,12 +123,10 @@ traversalSourceSpawnMethod_addV
 
 traversalSourceSpawnMethod_E
     : K_E LPAREN genericArgumentVarargs RPAREN
-    | K_E LPAREN nestedTraversal RPAREN
     ;
 
 traversalSourceSpawnMethod_V
     : K_V LPAREN genericArgumentVarargs RPAREN
-    | K_V LPAREN nestedTraversal RPAREN
     ;
 
 traversalSourceSpawnMethod_inject
diff --git 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
index d1305a85ce..856a610026 100644
--- 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
+++ 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
@@ -9500,6 +9500,142 @@
             }
         ]
     },
+    {
+        "scenario": 
"g_V_hasXname_withinXVXvid1X_outXknowsX_valuesXnameX_constantXpeterXXX",
+        "traversals": [
+            {
+                "original": "g.V().has(\"name\", 
P.within(__.V(vid1).out(\"knows\").values(\"name\"), __.constant(\"peter\")))",
+                "language": "g.V().has(\"name\", 
P.within(__.V(vid1).out(\"knows\").values(\"name\"), __.constant(\"peter\")))",
+                "canonical": "g.V().has(\"name\", 
P.within(__.V(vid1).out(\"knows\").values(\"name\"), __.constant(\"peter\")))",
+                "anonymized": "g.V().has(string0, 
P.within(__.V(vid1).out(string1).values(string0), __.constant(string2)))",
+                "dotnet": "g.V().Has(\"name\", 
P.Within(__.V(vid1).Out(\"knows\").Values<object>(\"name\"), 
__.Constant<object>(\"peter\")))",
+                "go": "g.V().Has(\"name\", 
gremlingo.P.Within(gremlingo.T__.V(vid1).Out(\"knows\").Values(\"name\"), 
gremlingo.T__.Constant(\"peter\")))",
+                "groovy": "g.V().has(\"name\", 
P.within(__.V(vid1).out(\"knows\").values(\"name\"), __.constant(\"peter\")))",
+                "java": "g.V().has(\"name\", 
P.within(__.V(vid1).out(\"knows\").values(\"name\"), __.constant(\"peter\")))",
+                "javascript": "g.V().has(\"name\", 
P.within(__.V(vid1).out(\"knows\").values(\"name\"), __.constant(\"peter\")))",
+                "python": "g.V().has('name', 
P.within(__.V(vid1).out('knows').values('name'), __.constant('peter')))"
+            }
+        ]
+    },
+    {
+        "scenario": 
"g_V_hasXname_withinXVXvid1X_valuesXnonexistentX_constantXmarkoXXX",
+        "traversals": [
+            {
+                "original": "g.V().has(\"name\", 
P.within(__.V(vid1).values(\"nonexistent\"), __.constant(\"marko\")))",
+                "language": "g.V().has(\"name\", 
P.within(__.V(vid1).values(\"nonexistent\"), __.constant(\"marko\")))",
+                "canonical": "g.V().has(\"name\", 
P.within(__.V(vid1).values(\"nonexistent\"), __.constant(\"marko\")))",
+                "anonymized": "g.V().has(string0, 
P.within(__.V(vid1).values(string1), __.constant(string2)))",
+                "dotnet": "g.V().Has(\"name\", 
P.Within(__.V(vid1).Values<object>(\"nonexistent\"), 
__.Constant<object>(\"marko\")))",
+                "go": "g.V().Has(\"name\", 
gremlingo.P.Within(gremlingo.T__.V(vid1).Values(\"nonexistent\"), 
gremlingo.T__.Constant(\"marko\")))",
+                "groovy": "g.V().has(\"name\", 
P.within(__.V(vid1).values(\"nonexistent\"), __.constant(\"marko\")))",
+                "java": "g.V().has(\"name\", 
P.within(__.V(vid1).values(\"nonexistent\"), __.constant(\"marko\")))",
+                "javascript": "g.V().has(\"name\", 
P.within(__.V(vid1).values(\"nonexistent\"), __.constant(\"marko\")))",
+                "python": "g.V().has('name', 
P.within(__.V(vid1).values('nonexistent'), __.constant('marko')))"
+            }
+        ]
+    },
+    {
+        "scenario": 
"g_V_hasXname_withinXVXvid1X_valuesXnonexistentX_VXvid1X_valuesXnonexistentXXX",
+        "traversals": [
+            {
+                "original": "g.V().has(\"name\", 
P.within(__.V(vid1).values(\"nonexistent\"), 
__.V(vid1).values(\"nonexistent\")))",
+                "language": "g.V().has(\"name\", 
P.within(__.V(vid1).values(\"nonexistent\"), 
__.V(vid1).values(\"nonexistent\")))",
+                "canonical": "g.V().has(\"name\", 
P.within(__.V(vid1).values(\"nonexistent\"), 
__.V(vid1).values(\"nonexistent\")))",
+                "anonymized": "g.V().has(string0, 
P.within(__.V(vid1).values(string1), __.V(vid1).values(string1)))",
+                "dotnet": "g.V().Has(\"name\", 
P.Within(__.V(vid1).Values<object>(\"nonexistent\"), 
__.V(vid1).Values<object>(\"nonexistent\")))",
+                "go": "g.V().Has(\"name\", 
gremlingo.P.Within(gremlingo.T__.V(vid1).Values(\"nonexistent\"), 
gremlingo.T__.V(vid1).Values(\"nonexistent\")))",
+                "groovy": "g.V().has(\"name\", 
P.within(__.V(vid1).values(\"nonexistent\"), 
__.V(vid1).values(\"nonexistent\")))",
+                "java": "g.V().has(\"name\", 
P.within(__.V(vid1).values(\"nonexistent\"), 
__.V(vid1).values(\"nonexistent\")))",
+                "javascript": "g.V().has(\"name\", 
P.within(__.V(vid1).values(\"nonexistent\"), 
__.V(vid1).values(\"nonexistent\")))",
+                "python": "g.V().has('name', 
P.within(__.V(vid1).values('nonexistent'), __.V(vid1).values('nonexistent')))"
+            }
+        ]
+    },
+    {
+        "scenario": 
"g_V_hasXname_withoutXVXvid1X_valuesXnameX_VXvid2X_valuesXnameX_VXvid3X_valuesXnameXXX",
+        "traversals": [
+            {
+                "original": "g.V().has(\"name\", 
P.without(__.V(vid1).values(\"name\"), __.V(vid2).values(\"name\"), 
__.V(vid3).values(\"name\")))",
+                "language": "g.V().has(\"name\", 
P.without(__.V(vid1).values(\"name\"), __.V(vid2).values(\"name\"), 
__.V(vid3).values(\"name\")))",
+                "canonical": "g.V().has(\"name\", 
P.without(__.V(vid1).values(\"name\"), __.V(vid2).values(\"name\"), 
__.V(vid3).values(\"name\")))",
+                "anonymized": "g.V().has(string0, 
P.without(__.V(vid1).values(string0), __.V(vid2).values(string0), 
__.V(vid3).values(string0)))",
+                "dotnet": "g.V().Has(\"name\", 
P.Without(__.V(vid1).Values<object>(\"name\"), 
__.V(vid2).Values<object>(\"name\"), __.V(vid3).Values<object>(\"name\")))",
+                "go": "g.V().Has(\"name\", 
gremlingo.P.Without(gremlingo.T__.V(vid1).Values(\"name\"), 
gremlingo.T__.V(vid2).Values(\"name\"), 
gremlingo.T__.V(vid3).Values(\"name\")))",
+                "groovy": "g.V().has(\"name\", 
P.without(__.V(vid1).values(\"name\"), __.V(vid2).values(\"name\"), 
__.V(vid3).values(\"name\")))",
+                "java": "g.V().has(\"name\", 
P.without(__.V(vid1).values(\"name\"), __.V(vid2).values(\"name\"), 
__.V(vid3).values(\"name\")))",
+                "javascript": "g.V().has(\"name\", 
P.without(__.V(vid1).values(\"name\"), __.V(vid2).values(\"name\"), 
__.V(vid3).values(\"name\")))",
+                "python": "g.V().has('name', 
P.without(__.V(vid1).values('name'), __.V(vid2).values('name'), 
__.V(vid3).values('name')))"
+            }
+        ]
+    },
+    {
+        "scenario": 
"g_V_hasXname_withinXVXvid1X_outXknowsX_valuesXnameX_VXvid3X_outXcreatedX_valuesXnameXXX",
+        "traversals": [
+            {
+                "original": "g.V().has(\"name\", 
P.within(__.V(vid1).out(\"knows\").values(\"name\"), 
__.V(vid3).out(\"created\").values(\"name\")))",
+                "language": "g.V().has(\"name\", 
P.within(__.V(vid1).out(\"knows\").values(\"name\"), 
__.V(vid3).out(\"created\").values(\"name\")))",
+                "canonical": "g.V().has(\"name\", 
P.within(__.V(vid1).out(\"knows\").values(\"name\"), 
__.V(vid3).out(\"created\").values(\"name\")))",
+                "anonymized": "g.V().has(string0, 
P.within(__.V(vid1).out(string1).values(string0), 
__.V(vid3).out(string2).values(string0)))",
+                "dotnet": "g.V().Has(\"name\", 
P.Within(__.V(vid1).Out(\"knows\").Values<object>(\"name\"), 
__.V(vid3).Out(\"created\").Values<object>(\"name\")))",
+                "go": "g.V().Has(\"name\", 
gremlingo.P.Within(gremlingo.T__.V(vid1).Out(\"knows\").Values(\"name\"), 
gremlingo.T__.V(vid3).Out(\"created\").Values(\"name\")))",
+                "groovy": "g.V().has(\"name\", 
P.within(__.V(vid1).out(\"knows\").values(\"name\"), 
__.V(vid3).out(\"created\").values(\"name\")))",
+                "java": "g.V().has(\"name\", 
P.within(__.V(vid1).out(\"knows\").values(\"name\"), 
__.V(vid3).out(\"created\").values(\"name\")))",
+                "javascript": "g.V().has(\"name\", 
P.within(__.V(vid1).out(\"knows\").values(\"name\"), 
__.V(vid3).out(\"created\").values(\"name\")))",
+                "python": "g.V().has('name', 
P.within(__.V(vid1).out('knows').values('name'), 
__.V(vid3).out('created').values('name')))"
+            }
+        ]
+    },
+    {
+        "scenario": 
"g_V_hasLabelXsoftwareX_hasXname_withoutXVXvid1X_outXcreatedX_valuesXnameX_VXvid3X_outXcreatedX_valuesXnameXXX",
+        "traversals": [
+            {
+                "original": "g.V().hasLabel(\"software\").has(\"name\", 
P.without(__.V(vid1).out(\"created\").values(\"name\"), 
__.V(vid3).out(\"created\").values(\"name\")))",
+                "language": "g.V().hasLabel(\"software\").has(\"name\", 
P.without(__.V(vid1).out(\"created\").values(\"name\"), 
__.V(vid3).out(\"created\").values(\"name\")))",
+                "canonical": "g.V().hasLabel(\"software\").has(\"name\", 
P.without(__.V(vid1).out(\"created\").values(\"name\"), 
__.V(vid3).out(\"created\").values(\"name\")))",
+                "anonymized": "g.V().hasLabel(string0).has(string1, 
P.without(__.V(vid1).out(string2).values(string1), 
__.V(vid3).out(string2).values(string1)))",
+                "dotnet": "g.V().HasLabel(\"software\").Has(\"name\", 
P.Without(__.V(vid1).Out(\"created\").Values<object>(\"name\"), 
__.V(vid3).Out(\"created\").Values<object>(\"name\")))",
+                "go": "g.V().HasLabel(\"software\").Has(\"name\", 
gremlingo.P.Without(gremlingo.T__.V(vid1).Out(\"created\").Values(\"name\"), 
gremlingo.T__.V(vid3).Out(\"created\").Values(\"name\")))",
+                "groovy": "g.V().hasLabel(\"software\").has(\"name\", 
P.without(__.V(vid1).out(\"created\").values(\"name\"), 
__.V(vid3).out(\"created\").values(\"name\")))",
+                "java": "g.V().hasLabel(\"software\").has(\"name\", 
P.without(__.V(vid1).out(\"created\").values(\"name\"), 
__.V(vid3).out(\"created\").values(\"name\")))",
+                "javascript": "g.V().hasLabel(\"software\").has(\"name\", 
P.without(__.V(vid1).out(\"created\").values(\"name\"), 
__.V(vid3).out(\"created\").values(\"name\")))",
+                "python": "g.V().has_label('software').has('name', 
P.without(__.V(vid1).out('created').values('name'), 
__.V(vid3).out('created').values('name')))"
+            }
+        ]
+    },
+    {
+        "scenario": 
"g_V_hasLabelXpersonX_valuesXageX_isXwithinXVXvid1X_valuesXageX_V_hasXname_lopX_inXcreatedX_valuesXageXXX",
+        "traversals": [
+            {
+                "original": 
"g.V().hasLabel(\"person\").values(\"age\").is(P.within(__.V(vid1).values(\"age\"),
 __.V().has(\"name\",\"lop\").in(\"created\").values(\"age\")))",
+                "language": 
"g.V().hasLabel(\"person\").values(\"age\").is(P.within(__.V(vid1).values(\"age\"),
 __.V().has(\"name\", \"lop\").in(\"created\").values(\"age\")))",
+                "canonical": 
"g.V().hasLabel(\"person\").values(\"age\").is(P.within(__.V(vid1).values(\"age\"),
 __.V().has(\"name\", \"lop\").in(\"created\").values(\"age\")))",
+                "anonymized": 
"g.V().hasLabel(string0).values(string1).is(P.within(__.V(vid1).values(string1),
 __.V().has(string2, string3).in(string4).values(string1)))",
+                "dotnet": 
"g.V().HasLabel(\"person\").Values<object>(\"age\").Is(P.Within(__.V(vid1).Values<object>(\"age\"),
 __.V().Has(\"name\", \"lop\").In(\"created\").Values<object>(\"age\")))",
+                "go": 
"g.V().HasLabel(\"person\").Values(\"age\").Is(gremlingo.P.Within(gremlingo.T__.V(vid1).Values(\"age\"),
 gremlingo.T__.V().Has(\"name\", \"lop\").In(\"created\").Values(\"age\")))",
+                "groovy": 
"g.V().hasLabel(\"person\").values(\"age\").is(P.within(__.V(vid1).values(\"age\"),
 __.V().has(\"name\", \"lop\").in(\"created\").values(\"age\")))",
+                "java": 
"g.V().hasLabel(\"person\").values(\"age\").is(P.within(__.V(vid1).values(\"age\"),
 __.V().has(\"name\", \"lop\").in(\"created\").values(\"age\")))",
+                "javascript": 
"g.V().hasLabel(\"person\").values(\"age\").is(P.within(__.V(vid1).values(\"age\"),
 __.V().has(\"name\", \"lop\").in_(\"created\").values(\"age\")))",
+                "python": 
"g.V().has_label('person').values('age').is_(P.within(__.V(vid1).values('age'), 
__.V().has('name', 'lop').in_('created').values('age')))"
+            }
+        ]
+    },
+    {
+        "scenario": 
"g_VXvid1X_outEXknowsX_filterXinV_hasXname_withinXV_hasXname_lopX_inXcreatedX_valuesXnameX_V_hasXname_rippleX_inXcreatedX_valuesXnameXXXX",
+        "traversals": [
+            {
+                "original": 
"g.V(vid1).outE(\"knows\").filter(__.inV().has(\"name\", 
P.within(__.V().has(\"name\",\"lop\").in(\"created\").values(\"name\"), 
__.V().has(\"name\",\"ripple\").in(\"created\").values(\"name\"))))",
+                "language": 
"g.V(vid1).outE(\"knows\").filter(__.inV().has(\"name\", 
P.within(__.V().has(\"name\", \"lop\").in(\"created\").values(\"name\"), 
__.V().has(\"name\", \"ripple\").in(\"created\").values(\"name\"))))",
+                "canonical": 
"g.V(vid1).outE(\"knows\").filter(__.inV().has(\"name\", 
P.within(__.V().has(\"name\", \"lop\").in(\"created\").values(\"name\"), 
__.V().has(\"name\", \"ripple\").in(\"created\").values(\"name\"))))",
+                "anonymized": 
"g.V(vid1).outE(string0).filter(__.inV().has(string1, 
P.within(__.V().has(string1, string2).in(string3).values(string1), 
__.V().has(string1, string4).in(string3).values(string1))))",
+                "dotnet": 
"g.V(vid1).OutE(\"knows\").Filter(__.InV().Has(\"name\", 
P.Within(__.V().Has(\"name\", 
\"lop\").In(\"created\").Values<object>(\"name\"), __.V().Has(\"name\", 
\"ripple\").In(\"created\").Values<object>(\"name\"))))",
+                "go": 
"g.V(vid1).OutE(\"knows\").Filter(gremlingo.T__.InV().Has(\"name\", 
gremlingo.P.Within(gremlingo.T__.V().Has(\"name\", 
\"lop\").In(\"created\").Values(\"name\"), gremlingo.T__.V().Has(\"name\", 
\"ripple\").In(\"created\").Values(\"name\"))))",
+                "groovy": 
"g.V(vid1).outE(\"knows\").filter(__.inV().has(\"name\", 
P.within(__.V().has(\"name\", \"lop\").in(\"created\").values(\"name\"), 
__.V().has(\"name\", \"ripple\").in(\"created\").values(\"name\"))))",
+                "java": 
"g.V(vid1).outE(\"knows\").filter(__.inV().has(\"name\", 
P.within(__.V().has(\"name\", \"lop\").in(\"created\").values(\"name\"), 
__.V().has(\"name\", \"ripple\").in(\"created\").values(\"name\"))))",
+                "javascript": 
"g.V(vid1).outE(\"knows\").filter(__.inV().has(\"name\", 
P.within(__.V().has(\"name\", \"lop\").in_(\"created\").values(\"name\"), 
__.V().has(\"name\", \"ripple\").in_(\"created\").values(\"name\"))))",
+                "python": 
"g.V(vid1).out_e('knows').filter_(__.in_v().has('name', 
P.within(__.V().has('name', 'lop').in_('created').values('name'), 
__.V().has('name', 'ripple').in_('created').values('name'))))"
+            }
+        ]
+    },
     {
         "scenario": "g_V_properties_hasValueXnullX",
         "traversals": [
@@ -10010,6 +10146,23 @@
             }
         ]
     },
+    {
+        "scenario": 
"g_V_valuesXageX_isXwithoutXVXvid1X_valuesXageX_VXvid2X_valuesXageXXX",
+        "traversals": [
+            {
+                "original": 
"g.V().values(\"age\").is(P.without(__.V(vid1).values(\"age\"), 
__.V(vid2).values(\"age\")))",
+                "language": 
"g.V().values(\"age\").is(P.without(__.V(vid1).values(\"age\"), 
__.V(vid2).values(\"age\")))",
+                "canonical": 
"g.V().values(\"age\").is(P.without(__.V(vid1).values(\"age\"), 
__.V(vid2).values(\"age\")))",
+                "anonymized": 
"g.V().values(string0).is(P.without(__.V(vid1).values(string0), 
__.V(vid2).values(string0)))",
+                "dotnet": 
"g.V().Values<object>(\"age\").Is(P.Without(__.V(vid1).Values<object>(\"age\"), 
__.V(vid2).Values<object>(\"age\")))",
+                "go": 
"g.V().Values(\"age\").Is(gremlingo.P.Without(gremlingo.T__.V(vid1).Values(\"age\"),
 gremlingo.T__.V(vid2).Values(\"age\")))",
+                "groovy": 
"g.V().values(\"age\").is(P.without(__.V(vid1).values(\"age\"), 
__.V(vid2).values(\"age\")))",
+                "java": 
"g.V().values(\"age\").is(P.without(__.V(vid1).values(\"age\"), 
__.V(vid2).values(\"age\")))",
+                "javascript": 
"g.V().values(\"age\").is(P.without(__.V(vid1).values(\"age\"), 
__.V(vid2).values(\"age\")))",
+                "python": 
"g.V().values('age').is_(P.without(__.V(vid1).values('age'), 
__.V(vid2).values('age')))"
+            }
+        ]
+    },
     {
         "scenario": "g_V_valuesXageX_noneXgtX32XX",
         "traversals": [
diff --git 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasTraversal.feature
 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasTraversal.feature
index 4d38167e4d..a0fb83ae23 100644
--- 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasTraversal.feature
+++ 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasTraversal.feature
@@ -201,3 +201,124 @@ Feature: Step - has() with traversal arguments
     Then the result should be unordered
       | result |
       | d[29].i |
+
+  # Multi-traversal within() where one traversal produces multiple results
+  @GraphComputerVerificationMidVNotSupported
+  Scenario: 
g_V_hasXname_withinXVXvid1X_outXknowsX_valuesXnameX_constantXpeterXXX
+    Given the modern graph
+    And using the parameter vid1 defined as "v[marko].id"
+    And the traversal of
+      """
+      g.V().has("name", P.within(__.V(vid1).out("knows").values("name"), 
__.constant("peter")))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[vadas] |
+      | v[josh] |
+      | v[peter] |
+
+  # Multi-traversal within() where one traversal produces no results — still 
matches on the other
+  @GraphComputerVerificationMidVNotSupported
+  Scenario: g_V_hasXname_withinXVXvid1X_valuesXnonexistentX_constantXmarkoXXX
+    Given the modern graph
+    And using the parameter vid1 defined as "v[marko].id"
+    And the traversal of
+      """
+      g.V().has("name", P.within(__.V(vid1).values("nonexistent"), 
__.constant("marko")))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[marko] |
+
+  # Multi-traversal within() where all traversals produce no results — filters 
everything
+  @GraphComputerVerificationMidVNotSupported
+  Scenario: 
g_V_hasXname_withinXVXvid1X_valuesXnonexistentX_VXvid1X_valuesXnonexistentXXX
+    Given the modern graph
+    And using the parameter vid1 defined as "v[marko].id"
+    And the traversal of
+      """
+      g.V().has("name", P.within(__.V(vid1).values("nonexistent"), 
__.V(vid1).values("nonexistent")))
+      """
+    When iterated to list
+    Then the result should be empty
+
+  # Multi-traversal without() with three traversals
+  @GraphComputerVerificationMidVNotSupported
+  Scenario: 
g_V_hasXname_withoutXVXvid1X_valuesXnameX_VXvid2X_valuesXnameX_VXvid3X_valuesXnameXXX
+    Given the modern graph
+    And using the parameter vid1 defined as "v[marko].id"
+    And using the parameter vid2 defined as "v[vadas].id"
+    And using the parameter vid3 defined as "v[peter].id"
+    And the traversal of
+      """
+      g.V().has("name", P.without(__.V(vid1).values("name"), 
__.V(vid2).values("name"), __.V(vid3).values("name")))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[josh] |
+      | v[lop] |
+      | v[ripple] |
+
+  # Multi-traversal within() — union of relationship traversals from different 
sources
+  @GraphComputerVerificationMidVNotSupported
+  Scenario: 
g_V_hasXname_withinXVXvid1X_outXknowsX_valuesXnameX_VXvid3X_outXcreatedX_valuesXnameXXX
+    Given the modern graph
+    And using the parameter vid1 defined as "v[marko].id"
+    And using the parameter vid3 defined as "v[josh].id"
+    And the traversal of
+      """
+      g.V().has("name", P.within(__.V(vid1).out("knows").values("name"), 
__.V(vid3).out("created").values("name")))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | v[vadas] |
+      | v[josh] |
+      | v[ripple] |
+      | v[lop] |
+
+  # Multi-traversal without() — exclusion from multiple relationship sources
+  @GraphComputerVerificationMidVNotSupported
+  Scenario: 
g_V_hasLabelXsoftwareX_hasXname_withoutXVXvid1X_outXcreatedX_valuesXnameX_VXvid3X_outXcreatedX_valuesXnameXXX
+    Given the modern graph
+    And using the parameter vid1 defined as "v[marko].id"
+    And using the parameter vid3 defined as "v[josh].id"
+    And the traversal of
+      """
+      g.V().hasLabel("software").has("name", 
P.without(__.V(vid1).out("created").values("name"), 
__.V(vid3).out("created").values("name")))
+      """
+    When iterated to list
+    Then the result should be empty
+
+  # Multi-traversal within() with is() — cross-label dynamic filtering
+  @GraphComputerVerificationMidVNotSupported
+  Scenario: 
g_V_hasLabelXpersonX_valuesXageX_isXwithinXVXvid1X_valuesXageX_V_hasXname_lopX_inXcreatedX_valuesXageXXX
+    Given the modern graph
+    And using the parameter vid1 defined as "v[marko].id"
+    And the traversal of
+      """
+      
g.V().hasLabel("person").values("age").is(P.within(__.V(vid1).values("age"), 
__.V().has("name","lop").in("created").values("age")))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | d[29].i |
+      | d[32].i |
+      | d[35].i |
+
+  # Multi-traversal within() — dynamic edge filtering via inV property check
+  @GraphComputerVerificationMidVNotSupported
+  Scenario: 
g_VXvid1X_outEXknowsX_filterXinV_hasXname_withinXV_hasXname_lopX_inXcreatedX_valuesXnameX_V_hasXname_rippleX_inXcreatedX_valuesXnameXXXX
+    Given the modern graph
+    And using the parameter vid1 defined as "v[marko].id"
+    And the traversal of
+      """
+      g.V(vid1).outE("knows").filter(__.inV().has("name", 
P.within(__.V().has("name","lop").in("created").values("name"), 
__.V().has("name","ripple").in("created").values("name"))))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | e[marko-knows->josh] |
diff --git 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/IsTraversal.feature
 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/IsTraversal.feature
index 495067b6cb..42b1eb898b 100644
--- 
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/IsTraversal.feature
+++ 
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/IsTraversal.feature
@@ -255,3 +255,19 @@ Feature: Step - is() with traversal-bearing predicates
       """
     When iterated to list
     Then the result should be empty
+
+  # Multi-traversal without() in is() context
+  @GraphComputerVerificationMidVNotSupported
+  Scenario: 
g_V_valuesXageX_isXwithoutXVXvid1X_valuesXageX_VXvid2X_valuesXageXXX
+    Given the modern graph
+    And using the parameter vid1 defined as "v[marko].id"
+    And using the parameter vid2 defined as "v[josh].id"
+    And the traversal of
+      """
+      g.V().values("age").is(P.without(__.V(vid1).values("age"), 
__.V(vid2).values("age")))
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | d[27].i |
+      | d[35].i |
diff --git 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategy.java
 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategy.java
index 8194adc4e0..7b0774eaf7 100644
--- 
a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategy.java
+++ 
b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategy.java
@@ -56,10 +56,13 @@ public final class TinkerGraphStepStrategy extends 
AbstractTraversalStrategy<Tra
                 if (currentStep instanceof HasStep) {
                     final List<HasContainer> hasContainers = 
((HasContainerHolder) currentStep).getHasContainers();
 
-                    // skip folding if any HasContainer holds a child 
traversal — its value
-                    // is dynamic (resolved per-traverser) and cannot be 
pushed into TinkerGraphStep
+                    // skip folding this HasStep if any HasContainer holds a 
child traversal —
+                    // its value is dynamic (resolved per-traverser) and 
cannot be pushed into
+                    // TinkerGraphStep. Continue to the next step so that 
subsequent literal
+                    // HasSteps can still be folded.
                     if 
(hasContainers.stream().anyMatch(HasContainer::hasTraversal)) {
-                        break;
+                        currentStep = currentStep.getNextStep();
+                        continue;
                     }
 
                     for (final HasContainer hasContainer : hasContainers) {
diff --git 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategyTraversalTest.java
 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategyTraversalTest.java
index 5fdfa6fdfd..c7db04b280 100644
--- 
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategyTraversalTest.java
+++ 
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/strategy/optimization/TinkerGraphStepStrategyTraversalTest.java
@@ -135,15 +135,44 @@ public class TinkerGraphStepStrategyTraversalTest {
     @Test
     public void shouldFoldLiteralHasButNotTraversalHasInMixedTraversal() {
         // g.V().has("name", "marko").has("age", __.constant(29))
-        // The literal has("name", "marko") should be folded, but has("age", 
traversal) should not
+        // Both containers end up in the same HasStep 
(TraversalHelper.addHasContainer merges).
+        // The strategy skips the entire HasStep because it contains a 
traversal-bearing container.
         final GraphTraversal<Vertex, Vertex> traversal =
                 g.V().has("name", "marko").has("age", __.constant(29));
         final List<Step> steps = applyStrategy(traversal.asAdmin());
 
+        // TinkerGraphStep should have NO folded HasContainers (the merged 
HasStep is skipped)
+        assertThat(steps.get(0), instanceOf(TinkerGraphStep.class));
+        final TinkerGraphStep<?, ?> tinkerGraphStep = (TinkerGraphStep<?, ?>) 
steps.get(0);
+
+        // The HasStep with both containers should remain as a separate step
+        final boolean hasStepPresent = steps.stream().anyMatch(s -> s 
instanceof HasStep);
+        assertThat("HasStep with mixed containers should remain as separate 
step",
+                hasStepPresent, is(true));
+    }
+
+    @Test
+    public void shouldFoldLiteralHasAfterBarrierAndTraversalHas() {
+        // g.V().has("age", __.constant(29)).barrier().has("name", "marko")
+        // The barrier separates the two HasSteps. The strategy should:
+        // - Skip the traversal-bearing HasStep (has("age", traversal))
+        // - Stop at the barrier (not a HasStep or NoOpBarrierStep... actually 
NoOpBarrierStep IS handled)
+        // Let's use a different separator. Actually NoOpBarrierStep is 
handled by the while loop.
+        // The key test is: after skipping a traversal-bearing HasStep, the 
strategy continues
+        // and can fold subsequent literal HasSteps.
+        //
+        // With separate HasSteps (not merged), the strategy should fold the 
literal one.
+        // We can force separate HasSteps by inserting a NoOpBarrierStep 
between them.
+        final GraphTraversal<Vertex, Vertex> traversal =
+                g.V().has("age", __.constant(29)).barrier().has("name", 
"marko");
+        final List<Step> steps = applyStrategy(traversal.asAdmin());
+
         // TinkerGraphStep should have the literal "name" HasContainer folded
         assertThat(steps.get(0), instanceOf(TinkerGraphStep.class));
         final TinkerGraphStep<?, ?> tinkerGraphStep = (TinkerGraphStep<?, ?>) 
steps.get(0);
-        assertThat(tinkerGraphStep.getHasContainers(), hasSize(1));
+        assertThat("Literal HasContainer after barrier should be folded even 
when preceded by traversal-bearing HasStep",
+                tinkerGraphStep.getHasContainers(), hasSize(1));
+        assertThat(tinkerGraphStep.getHasContainers().get(0).getKey(), 
is("name"));
 
         // The traversal-bearing HasStep for "age" should remain
         final boolean hasStepPresent = steps.stream().anyMatch(s -> s 
instanceof HasStep);
@@ -151,4 +180,26 @@ public class TinkerGraphStepStrategyTraversalTest {
                 hasStepPresent, is(true));
     }
 
+    @Test
+    public void shouldFoldMultipleLiteralHasStepsSeparatedByTraversalHas() {
+        // g.V().has("name", "marko").has("age", 
__.constant(29)).barrier().has("lang", "java")
+        // has("name") is a separate literal HasStep, has("age") is a separate 
traversal HasStep,
+        // has("lang") is a separate literal HasStep after the barrier.
+        // Strategy should fold both literal HasSteps and skip the traversal 
one.
+        final GraphTraversal<Vertex, Vertex> traversal =
+                g.V().has("name", "marko").has("age", 
__.constant(29)).barrier().has("lang", "java");
+        final List<Step> steps = applyStrategy(traversal.asAdmin());
+
+        // TinkerGraphStep should have both literal HasContainers folded 
("name" and "lang")
+        assertThat(steps.get(0), instanceOf(TinkerGraphStep.class));
+        final TinkerGraphStep<?, ?> tinkerGraphStep = (TinkerGraphStep<?, ?>) 
steps.get(0);
+        assertThat("Both literal HasContainers should be folded",
+                tinkerGraphStep.getHasContainers(), hasSize(2));
+
+        // The traversal-bearing HasStep (age) should remain
+        final boolean hasStepPresent = steps.stream().anyMatch(s -> s 
instanceof HasStep);
+        assertThat("Traversal-bearing HasStep should remain as separate step",
+                hasStepPresent, is(true));
+    }
+
 }

Reply via email to