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 1473451213fb99e7090a168d5c6a9914d6eab051
Author: Yang Xia <[email protected]>
AuthorDate: Thu Jun 11 11:59:54 2026 -0700

    docs update
---
 CHANGELOG.asciidoc                                 |   9 ++
 docs/src/reference/the-traversal.asciidoc          | 101 ++++++++++++++
 docs/src/upgrade/release-4.x.x.asciidoc            |  42 ++++++
 .../tinkerpop/gremlin/process/traversal/P.java     | 146 ++++++++++-----------
 .../traversal/util/ChildTraversalContext.java      |  42 ------
 5 files changed, 221 insertions(+), 119 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 3d3284d480..66dc0980b1 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -28,6 +28,15 @@ 
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Added typed numeric wrappers and `preciseNumbers` connection option to 
`gremlin-javascript` for explicit control over numeric type serialization and 
deserialization.
 * Added `NextN(n)` to `Traversal` in `gremlin-go` for batched result 
iteration, providing API parity with `next(n)` in the Java, Python, and .NET 
GLVs, and updated the Go translators in `gremlin-core` and `gremlin-javascript` 
to emit `NextN(n)` for the batched form.
 * Added Gremlator, a single page web application, that translates Gremlin into 
various programming languages like Javascript and Python.
+* Added child traversal support to `has()`, `hasLabel()`, `V()`, `E()`, 
`property()`, `is()`, `where(P)`, `P.eq/neq/gt/lt/gte/lte/within/without()`, 
and `TextP` predicates. Child traversals are resolved per-traverser at runtime, 
enabling dynamic filtering and lookup patterns.
+* Added multi-traversal `P.within(trav1, trav2, ...)` and `P.without(trav1, 
trav2, ...)` which combine results from multiple child traversals for 
collection membership testing.
+* Added `V(traversal)` and `E(traversal)` as start steps with synthetic 
traverser seeding, consistent with `mergeV(traversal)` behavior.
+* Added `ChildTraversalVerificationStrategy` that blocks mutating steps 
(`addV`, `addE`, `drop`, etc.) inside child traversals. All child traversals 
must be read-only.
+* Added `hasLabel(Traversal)` overload with grammar support for dynamic label 
filtering.
+* Added traversal-bearing predicate support to `where(P)` — resolves child 
traversal and tests against current value when `P.hasTraversal()` is true.
+* Added rejection of traversal-bearing predicates in `choose(P)` — use 
`choose(__.is(P.op(traversal)), ...)` form instead.
+* Added runtime Map validation for `property(traversal)` — rejects non-Map 
results to prevent sentinel key leakage.
+* Added mixed traversal/literal detection in `P.within()` and `P.without()` — 
throws `IllegalArgumentException` with guidance to wrap literals in 
`__.constant()`.
 * Removed `uuid` dependency from `gremlin-javascript` in favor of the built-in 
`globalThis.crypto.randomUUID()`.
 * Added streaming HTTP response support to `gremlin-driver` for incremental 
result deserialization over GraphBinary.
 * Connected HTTP streaming response deserialization to the traversal API in 
`gremlin-javascript`, enabling `next()` to return the first result without 
waiting for the full response.
diff --git a/docs/src/reference/the-traversal.asciidoc 
b/docs/src/reference/the-traversal.asciidoc
index ab49ae2614..bb24250651 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -1267,6 +1267,17 @@ IMPORTANT: It is important to think of `choose()` as a 
branching step and not a
 intuitively lead to thinking the latter, where no match would mean to remove 
the traverser from the stream. As shown in
 the examples, this is not what happens.
 
+NOTE: The `choose(P)` form does not support traversal-bearing predicates 
directly. To use a dynamic comparison as the
+branch condition, wrap it in an `is()` step: `choose(__.is(P.op(traversal)), 
trueChoice, falseChoice)`.
+
+[gremlin-groovy,modern]
+----
+g.V().values('age').choose(__.is(P.gt(__.V(1).values('age'))), 
__.constant('older'), __.constant('not older')) <1>
+----
+
+<1> For each age value, compare dynamically against marko's age using a 
traversal inside the predicate. If greater,
+return "older"; otherwise return "not older".
+
 The `choose()`-step can be used within a `map()` step to apply the branching 
logic to each element in a collection.
 
 [gremlin-groovy,modern]
@@ -2088,6 +2099,10 @@ It is possible to filter vertices, edges, and vertex 
properties based on their p
   * `has(key,value)`: Remove the traverser if its element does not have the 
provided key/value property.
   * `has(label, key, value)`: Remove the traverser if its element does not 
have the specified label and provided key/value property.
   * `has(key,predicate)`: Remove the traverser if its element does not have a 
key value that satisfies the bi-predicate. For more information on predicates, 
please read <<a-note-on-predicates,A Note on Predicates>>.
+  * `has(key, traversal)`: Remove the traverser if its element's key value 
does not equal the first result of the provided traversal.
+  * `has(T, traversal)`: Remove the traverser if its element's `T`-based value 
(e.g. `T.id`, `T.label`) does not equal the first result of the provided 
traversal.
+  * `has(label, key, traversal)`: Remove the traverser if its element does not 
have the specified label and its key value does not equal the first result of 
the provided traversal.
+  * `hasLabel(traversal)`: Remove the traverser if its element's label does 
not equal the first result of the provided traversal.
   * `hasLabel(labels...)`: Remove the traverser if its element does not have 
any of the labels.
   * `hasId(ids...)`: Remove the traverser if its element does not have any of 
the ids.
   * `hasKey(keys...)`: Remove the `Property` traverser if it does not match 
one of the provided keys.
@@ -2126,6 +2141,24 @@ the key,value pairs for those vertices.
 <9> Property key is always stored as `String` and therefore an equality check 
with `null` will produce no result.
 <10> An example of using `has()` with regular expression predicate.
 
+The traversal-accepting forms of `has()` allow for dynamic property 
comparisons. Rather than providing a literal value,
+a child traversal is supplied and its first result is used as the comparison 
value. This follows the same first-result
+semantics as `by(traversal)`. The child traversal must be read-only — mutating 
steps are rejected.
+
+[gremlin-groovy,modern]
+----
+g.V().has('age', P.gt(__.V(1).values('age'))) <1>
+g.V().has('name', __.V(1).values('name')) <2>
+g.V().hasLabel(__.V(1).label()) <3>
+----
+
+<1> Find all vertices whose age is greater than marko's age using a traversal 
inside a predicate.
+<2> Find all vertices whose name equals marko's name using a traversal as the 
value argument.
+<3> Find all vertices whose label equals the label of vertex 1.
+
+NOTE: When a `Traversal` is provided directly as the value argument (not 
inside a `P`), it is internally wrapped in
+`P.eq(traversal)`. See <<a-note-on-predicates,A Note on Predicates>> for more 
details on traversal-bearing predicates.
+
 *Additional References*
 
 
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#has(java.lang.String)++[`has(String)`],
@@ -2481,6 +2514,18 @@ g.V().where(__.in('created').values('age').
 <2> Find projects having two or more contributors.
 <3> Find projects whose contributors average age is between 30 and 35.
 
+The `is()` step also supports predicates that contain traversal arguments for 
dynamic threshold comparison.
+
+[gremlin-groovy,modern]
+----
+g.V().values('age').is(P.gt(__.V(1).values('age'))) <1>
+----
+
+<1> Find all age values that are greater than marko's age using a traversal 
inside the predicate.
+
+NOTE: When `is(traversal)` is used directly (without an explicit predicate), 
it is internally wrapped in
+`P.eq(traversal)`.
+
 *Additional References*
 
 
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#is(java.lang.Object)++[`is(Object)`],
@@ -4003,6 +4048,19 @@ g.addV().property(set, null)
 <7> The label value can be specified as a property only at the time a vertex 
is added and if one is not specified in the addV()
 <8> If you pass a `null` value for the Map this will be treated as a no-op and 
the input will be returned
 
+The `property()` step also accepts a traversal that produces a `Map` of 
key-value pairs to set as properties. The
+child traversal must be read-only — mutating steps are rejected. If the 
traversal does not produce a `Map`, the result
+is rejected.
+
+[gremlin-groovy,modern]
+----
+g.V(2).property(__.V(2).project('friendCount').by(__.both('knows').count())) 
<1>
+g.V(2).valueMap()
+----
+
+<1> Set properties on vertex 2 from a Map produced by a child traversal. Here 
the `project()` step creates a Map with
+key "friendCount" and a value computed from the graph.
+
 *Additional References*
 
 
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#property(java.lang.Object,java.lang.Object,java.lang.Object...)++[`property(Object,
 Object, Object...)`],
@@ -5201,6 +5259,19 @@ g.V().has('name', within('marko', 'vadas', 
'josh')).as('person').
 <1> Normally the `V()`-step will iterate over all vertices. However, graph 
strategies can fold ``HasContainer``'s into a `GraphStep` to allow index 
lookups.
 <2> Whether the graph system provider supports mid-traversal `V()` index 
lookups or not can easily be determined by inspecting the `toString()` output 
of the iterated traversal. If `has` conditions were folded into the `V()`-step, 
an index - if one exists - will be used.
 
+The `V()` step also accepts a traversal argument. The child traversal is 
evaluated and its results are used as the
+vertex identifiers. This works both as a start step and mid-traversal. When 
used as a start step, a synthetic traverser
+is provided to the child traversal.
+
+[gremlin-groovy,modern]
+----
+g.V(__.V(1).id()).values('name') <1>
+g.inject(1).V(__.identity()).values('name') <2>
+----
+
+<1> Use a child traversal to dynamically determine the vertex ID. Here the 
traversal resolves vertex 1's ID.
+<2> Mid-traversal usage where the injected value is used via `identity()` as 
the ID argument to `V()`.
+
 *Additional References*
 
 
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#V(java.lang.Object...)++[`V(Object...)`]
@@ -5413,6 +5484,18 @@ g.V().as('a').both().both().as('b').
 <8> Marko is younger than josh, but josh knows someone equal in age to marko 
(which is marko).
 <9> The "age" property is not <<by-step,productive>> for all vertices and 
therefore those values are filtered.
 
+The `where()` step also supports predicates that contain traversal arguments. 
When a predicate contains a child
+traversal, the traversal is resolved per-traverser and the result is tested 
directly against the current value —
+bypassing scope-label resolution.
+
+[gremlin-groovy,modern]
+----
+g.V().values('age').where(P.gt(__.V(1).values('age'))) <1>
+----
+
+<1> Find all age values that are greater than marko's age. The child traversal 
inside `P.gt()` is resolved and the
+current traverser's value is compared against the result.
+
 WARNING: The anonymous traversal of `where()` processes the current object 
"locally". In OLAP, where the atomic unit
 of computing is the vertex and its local "star graph," it is important that 
the anonymous traversal does not leave
 the confines of the vertex's star graph. In other words, it can not traverse 
to an adjacent vertex's properties or
@@ -5538,6 +5621,24 @@ NOTE: The TinkerPop reference implementation uses the 
Java `Pattern` and `Matche
 engine. Other implementations may decide to use a different regular expression 
engine. It's a good idea to check
 the documentation for the implementation you are using to verify the allowed 
regular expression syntax.
 
+In addition to literal values, most predicates also accept a `Traversal` 
argument. When a traversal is provided, it is
+evaluated and only its first result is used for comparison — consistent with 
`by(traversal)` first-result semantics.
+The child traversal must be read-only; mutating steps are rejected.
+
+* For `within()` and `without()`, the first result should be a `Collection`. 
Use `fold()` in the child traversal to
+  produce one.
+* The multi-traversal form `within(trav1, trav2, ...)` takes the first result 
from each traversal and combines them
+  into a collection for membership testing.
+
+[gremlin-groovy,modern]
+----
+g.V().has('age', P.gt(__.V(1).values('age'))) <1>
+g.V().has('age', P.within(__.V(1).out('knows').values('age').fold())) <2>
+----
+
+<1> Find vertices whose age is greater than marko's age using a traversal 
inside the predicate.
+<2> Find vertices whose age is in the set of ages of marko's friends, using 
`fold()` to produce a collection.
+
 [gremlin-groovy]
 ----
 eq(2)
diff --git a/docs/src/upgrade/release-4.x.x.asciidoc 
b/docs/src/upgrade/release-4.x.x.asciidoc
index 2b4606444c..45555d65dc 100644
--- a/docs/src/upgrade/release-4.x.x.asciidoc
+++ b/docs/src/upgrade/release-4.x.x.asciidoc
@@ -41,6 +41,33 @@ anonymized form. The original gremlator.com was a prototype 
built by TinkerPop c
 previous implementation required Java and a running Gremlin Server, whereas 
the new version runs entirely in the
 browser with no server infrastructure needed.
 
+==== Traversal-Accepting Steps
+
+Steps and predicates that previously only accepted literal values now accept 
child traversals resolved per-traverser
+at runtime. This enables dynamic filtering, lookup, and mutation patterns 
without `where()`/`select()` workarounds.
+
+Affected steps: `has()`, `hasLabel()`, `V()`, `E()`, `property()`, `is()`, 
`where(P)`, `P.eq/neq/gt/lt/gte/lte()`,
+`P.within/without()`, and all `TextP` predicates.
+
+[source,groovy]
+----
+// Dynamic property comparison
+g.V().has("age", P.gt(__.V(1).values("age")))
+
+// Dynamic vertex lookup via select()
+g.V(1).id().as("x").V().has("name","josh").V(__.select("x")).values("name")
+
+// Multi-source filtering with within()
+g.V().has("name", P.within(__.V(1).out("knows").values("name").fold(), 
__.constant("peter")))
+----
+
+Child traversals take only the first result (consistent with `by(traversal)` 
semantics). For `within()`/`without()`,
+use `fold()` to collect multiple values into a list.
+
+All child traversals must be read-only. Mutating steps (`addV`, `addE`, 
`drop`, `property`) inside child traversals
+are rejected at construction time with `IllegalArgumentException`. A 
`ChildTraversalVerificationStrategy` provides
+additional safety at strategy time.
+
 ==== Removed `uuid` Dependency in gremlin-javascript
 
 The `uuid` npm package has been removed from `gremlin-javascript`. UUID 
generation now uses the built-in
@@ -505,6 +532,21 @@ re-query elements against the original graph, extract 
their ids and call `g.V(id
 
 ==== Graph System Providers
 
+===== Traversal-Accepting Steps — HasContainer Guard
+
+Steps and predicates now accept child traversals resolved at runtime. 
`HasContainer` instances that hold a child
+traversal cannot be folded into index lookups because their value is dynamic 
(resolved per-traverser). Providers that
+fold `HasContainer` predicates into their graph step must guard against this:
+
+[source,java]
+----
+if (hasContainer.hasTraversal()) continue;  // skip — dynamic value, cannot 
fold into index
+----
+
+`GraphStep.processHasContainerIds()` already includes this guard. Providers 
that implement their own `HasContainer`
+folding strategy should add the same check. Without it, `getValue()` and 
`getBiPredicate()` will throw
+`IllegalStateException` on traversal-bearing containers.
+
 ==== Graph Driver Providers
 
 == TinkerPop 4.0.0-beta.2
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 f95001a55b..810a67e372 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
@@ -340,14 +340,9 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
         if (this.traversalValue == null) return;
 
         final Traversal.Admin<Object, Object> trav = (Traversal.Admin<Object, 
Object>) (Traversal.Admin) this.traversalValue;
-        final Traverser.Admin<Object> split = (Traverser.Admin<Object>) 
traverser.split();
-        split.setSideEffects(trav.getSideEffects());
-        split.setBulk(1L);
-        trav.reset();
-        trav.addStart(split);
+        prepareChildTraversal(traverser, trav);
 
         try {
-            // Take first result only — consistent with by(traversal) 
semantics.
             if (!trav.hasNext()) {
                 this.resolvedEmpty = true;
                 this.literals = Collections.emptyList();
@@ -356,8 +351,6 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
                 this.resolvedEmpty = false;
                 final Object firstResult = trav.next();
                 if (this.biPredicate instanceof Contains) {
-                    // Contains predicates need a Collection value. If first 
result is already
-                    // a Collection (e.g., from fold()), use it directly. 
Otherwise wrap in singleton.
                     if (firstResult instanceof Collection) {
                         this.literals = (Collection<V>) firstResult;
                     } else {
@@ -383,15 +376,9 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
 
         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);
+            prepareChildTraversal(traverser, trav);
 
             try {
-                // Take first result only from each traversal.
-                // If the result is a Collection (from fold()), unpack it.
                 if (trav.hasNext()) {
                     final Object firstResult = trav.next();
                     if (firstResult instanceof Collection) {
@@ -415,6 +402,18 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
         }
     }
 
+    /**
+     * Prepares a child traversal for evaluation by splitting the current 
traverser and seeding it.
+     */
+    @SuppressWarnings("unchecked")
+    private static void prepareChildTraversal(final Traverser.Admin<?> 
traverser, final Traversal.Admin<Object, Object> trav) {
+        final Traverser.Admin<Object> split = (Traverser.Admin<Object>) 
traverser.split();
+        split.setSideEffects(trav.getSideEffects());
+        split.setBulk(1L);
+        trav.reset();
+        trav.addStart(split);
+    }
+
     //////////////// predicate traversal utilities
 
     /**
@@ -625,26 +624,8 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
      * @since 3.0.0-incubating
      */
     public static <V> P<V> within(final V... values) {
-        // If a single Traversal is passed, redirect to the 
Traversal-accepting overload.
-        // This handles cases where Java's overload resolution picks the 
varargs method
-        // instead of the Traversal-specific method.
-        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);
-        }
-        // Reject mixed traversals and literals — would silently produce wrong 
results.
-        if (values != null && values.length > 1 && anyTraversals(values)) {
-            throw new IllegalArgumentException(
-                    "Cannot mix traversals and literal values in within(). " +
-                    "Use within(__.constant(val1), __.constant(val2)) to wrap 
all values as traversals.");
-        }
+        final P<V> traversalP = handleContainsVarargs(values, Contains.within, 
"within");
+        if (traversalP != null) return traversalP;
         final V[] v = null == values ? (V[]) new Object[] { null } : (V[]) 
values;
         return P.within(Arrays.asList(v));
     }
@@ -678,24 +659,8 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
      * @since 3.0.0-incubating
      */
     public static <V> P<V> without(final V... values) {
-        // If a single Traversal is passed, redirect to the 
Traversal-accepting overload.
-        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);
-        }
-        // Reject mixed traversals and literals
-        if (values != null && values.length > 1 && anyTraversals(values)) {
-            throw new IllegalArgumentException(
-                    "Cannot mix traversals and literal values in without(). " +
-                    "Use without(__.constant(val1), __.constant(val2)) to wrap 
all values as traversals.");
-        }
+        final P<V> traversalP = handleContainsVarargs(values, 
Contains.without, "without");
+        if (traversalP != null) return traversalP;
         final V[] v = null == values ? (V[]) new Object[] { null } : values;
         return P.without(Arrays.asList(v));
     }
@@ -799,18 +764,7 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
      * @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());
-            }
-        }
-        for (final Traversal.Admin<?, ?> tv : traversals) {
-            ChildTraversalValidator.validate(tv);
-        }
-        return new P(Contains.within, traversals);
+        return containsTraversals(Contains.within, first, second, more);
     }
 
     /**
@@ -830,18 +784,7 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
      * @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());
-            }
-        }
-        for (final Traversal.Admin<?, ?> tv : traversals) {
-            ChildTraversalValidator.validate(tv);
-        }
-        return new P(Contains.without, traversals);
+        return containsTraversals(Contains.without, first, second, more);
     }
 
     /**
@@ -889,6 +832,55 @@ public class P<V> implements Predicate<V>, Serializable, 
Cloneable {
         return predicate.negate();
     }
 
+    /**
+     * Handles varargs traversal detection for within/without. Returns a P if 
traversals were found,
+     * or null if the values are plain literals.
+     */
+    @SuppressWarnings("unchecked")
+    private static <V> P<V> handleContainsVarargs(final V[] values, final 
PBiPredicate predicate, final String stepName) {
+        if (values != null && values.length == 1 && values[0] instanceof 
Traversal) {
+            final Traversal<?, ?> trav = (Traversal<?, ?>) values[0];
+            ChildTraversalValidator.validate(trav.asAdmin());
+            return new P(predicate, trav.asAdmin());
+        }
+        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());
+            }
+            for (final Traversal.Admin<?, ?> tv : traversals) {
+                ChildTraversalValidator.validate(tv);
+            }
+            return new P(predicate, traversals);
+        }
+        if (values != null && values.length > 1 && anyTraversals(values)) {
+            throw new IllegalArgumentException(
+                    "Cannot mix traversals and literal values in " + stepName 
+ "(). " +
+                    "Use " + stepName + "(__.constant(val1), 
__.constant(val2)) to wrap all values as traversals.");
+        }
+        return null;
+    }
+
+    /**
+     * Creates a Contains predicate from multiple validated child traversals.
+     */
+    @SuppressWarnings("unchecked")
+    private static <V> P<V> containsTraversals(final PBiPredicate predicate, 
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());
+            }
+        }
+        for (final Traversal.Admin<?, ?> tv : traversals) {
+            ChildTraversalValidator.validate(tv);
+        }
+        return new P(predicate, traversals);
+    }
+
     /**
      * Checks if all elements in the array are {@link Traversal} instances 
(specifically
      * {@link 
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal}).
diff --git 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalContext.java
 
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalContext.java
deleted file mode 100644
index fbb489813f..0000000000
--- 
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalContext.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.tinkerpop.gremlin.process.traversal.util;
-
-/**
- * Classifies the context in which a child traversal is used, determining what 
validation rules apply.
- *
- * <ul>
- *   <li>{@link #FILTER} — child traversals inside {@code has()}, {@code 
is()}, {@code all()}, {@code any()},
- *       {@code none()}, or {@code choose()} predicates. No {@link 
org.apache.tinkerpop.gremlin.process.traversal.step.Mutating}
- *       steps are allowed.</li>
- *   <li>{@link #LOOKUP} — child traversals inside {@code V(traversal)} or 
{@code E(traversal)}. No
- *       {@link org.apache.tinkerpop.gremlin.process.traversal.step.Mutating} 
steps are allowed.</li>
- *   <li>{@link #MUTATION} — child traversals inside {@code 
property(traversal)}. Only
- *       {@link 
org.apache.tinkerpop.gremlin.process.traversal.step.filter.DropStep} is 
blocked; other mutating
- *       steps are permitted because the traversal intentionally produces 
values from graph state.</li>
- *   <li>{@link #NONE} — not a child-traversal-bearing step, or a step whose 
children are not validated
- *       (e.g., {@code mergeV}, {@code mergeE}).</li>
- * </ul>
- */
-public enum ChildTraversalContext {
-    FILTER,
-    LOOKUP,
-    MUTATION,
-    NONE
-}

Reply via email to