1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 import java.lang.invoke.MethodHandle;
  27 import java.lang.invoke.MethodHandles;
  28 import java.lang.invoke.MethodType;
  29 import java.lang.runtime.PatternHandle;
  30 import java.lang.runtime.PatternHandles;
  31 import java.util.ArrayList;
  32 import java.util.List;
  33 import java.util.Map;
  34 import java.util.Objects;
  35 
  36 import org.testng.annotations.Test;
  37 
  38 import static java.util.Map.entry;
  39 import static org.testng.Assert.assertEquals;
  40 import static org.testng.Assert.assertNotNull;
  41 import static org.testng.Assert.assertNotSame;
  42 import static org.testng.Assert.assertNull;
  43 import static org.testng.Assert.assertSame;
  44 import static org.testng.Assert.assertTrue;
  45 import static org.testng.Assert.fail;
  46 
  47 /**
  48  * @test
  49  * @run testng PatternHandleTest
  50  * @summary Smoke tests for java.lang.runtime.Extractor
  51  */
  52 @Test
  53 public class PatternHandleTest {
  54 
  55     enum MatchKind {
  56         /** Match succeeds, with a carrier object different form the target */
  57         MATCH_CARRIER,
  58         /** Match succeeds, with self-carrier */
  59         MATCH_SELF,
  60         /** Match succeeds, carrier provenance unknown */
  61         MATCH,
  62         /** Match fails */
  63         NO_MATCH,
  64         /** Match fails with a runtime exception */
  65         ERROR;
  66     }
  67 
  68     // We have to resort to the subterfuge of laundering the tryMatch invocation
  69     // through an ancillary object, because the only way to control the static
  70     // signature is through the static types.  So we have a bunch of invokers,
  71     // each of which embeds different assumptions about the target type.  This
  72     // way we can test mismatches between the expected and actual types.
  73 
  74     interface TryMatchInvoker<T> {
  75         Object tryMatch(MethodHandle mh, T target) throws Throwable;
  76     }
  77 
  78     static final TryMatchInvoker<Object> objectInvoker = (MethodHandle mh, Object x) -> mh.invokeExact(x);
  79     static final TryMatchInvoker<Number> numberInvoker = (MethodHandle mh, Number x) -> mh.invokeExact(x);
  80     static final TryMatchInvoker<Integer> integerInvoker = (MethodHandle mh, Integer x) -> mh.invokeExact(x);
  81     static final TryMatchInvoker<Integer> intInvoker = (MethodHandle mh, Integer x) -> mh.invokeExact((int) x);
  82     static final TryMatchInvoker<String> stringInvoker = (MethodHandle mh, String x) -> mh.invokeExact(x);
  83     static final TryMatchInvoker<List> listInvoker = (MethodHandle mh, List x) -> mh.invokeExact(x);
  84     static final TryMatchInvoker<TestClass> testClassInvoker = (MethodHandle mh, TestClass x) -> mh.invokeExact(x);
  85     static final TryMatchInvoker<TestClass2> testClass2Invoker = (MethodHandle mh, TestClass2 x) -> mh.invokeExact(x);
  86 
  87     static final Map<Class<?>, TryMatchInvoker<?>> invokers
  88             = Map.ofEntries(entry(Object.class, objectInvoker),
  89                             entry(Number.class, numberInvoker),
  90                             entry(Integer.class, integerInvoker),
  91                             entry(int.class, intInvoker),
  92                             entry(String.class, stringInvoker),
  93                             entry(List.class, listInvoker),
  94                             entry(TestClass.class, testClassInvoker),
  95                             entry(TestClass2.class, testClass2Invoker));
  96 
  97     interface Throwing {
  98         public void run() throws Throwable;
  99     }
 100 
 101     static void assertThrows(Class<? extends Throwable> exception, Throwing r) {
 102         try {
 103             r.run();
 104             fail("Expected exception: " + exception);
 105         }
 106         catch (Throwable t) {
 107             if (!exception.isAssignableFrom(t.getClass()))
 108                 fail(String.format("Expected exception %s, got %s", exception, t.getClass()), t);
 109         }
 110     }
 111 
 112     static void assertMatch(MatchKind expected,
 113                             PatternHandle e,
 114                             Object target,
 115                             Object... expectedBindings) throws Throwable {
 116         int count = e.descriptor().parameterCount();
 117         Object[] bindings = new Object[count];
 118         Object carrier;
 119         try {
 120             TryMatchInvoker inv = invokers.get(e.descriptor().returnType());
 121             // @@@ temporary hack until we break out the assert-match machinery
 122             if (inv == null)
 123                 inv = (MethodHandle mh, Object x) -> mh.invoke(x);
 124             // @@@ end temporary hack
 125             carrier = inv.tryMatch(e.tryMatch(), target);
 126         }
 127         catch (Throwable t) {
 128             carrier = null;
 129             if (expected == MatchKind.ERROR)
 130                 return;
 131             else
 132                 fail("Unexpected exception in tryMatch", t);
 133         }
 134 
 135         if (carrier != null) {
 136             for (int i = 0; i < count; i++)
 137                 bindings[i] = e.component(i).invoke(carrier);
 138         }
 139 
 140         if (expected == MatchKind.NO_MATCH)
 141             assertNull(carrier);
 142         else {
 143             assertNotNull(carrier);
 144             assertEquals(bindings.length, expectedBindings.length);
 145             for (int i = 0; i < expectedBindings.length; i++)
 146                 assertEquals(bindings[i], expectedBindings[i]);
 147 
 148             if (expected == MatchKind.MATCH_SELF)
 149                 assertSame(carrier, target);
 150             else if (expected == MatchKind.MATCH_CARRIER)
 151                 assertNotSame(carrier, target);
 152         }
 153     }
 154 
 155     private static class TestClass {
 156         static TestClass INSTANCE_A = new TestClass("foo", 3, 4L, (byte) 5);
 157         static TestClass INSTANCE_B = new TestClass(null, 0, 0L, (byte) 0);
 158         static TestClass INSTANCE_C = new TestClass("foo", 2, 4L, (byte) 5);
 159         static Object[] COMPONENTS_A = new Object[] { "foo", 3, 4L, (byte) 5 };
 160         static Object[] COMPONENTS_B = new Object[] { null, 0, 0L, (byte) 0 };
 161         static Object[] COMPONENTS_C = new Object[] { "foo", 2, 4L, (byte) 5 };
 162 
 163         static Map<TestClass, Object[]> INSTANCES = Map.of(INSTANCE_A, COMPONENTS_A,
 164                                                            INSTANCE_B, COMPONENTS_B,
 165                                                            INSTANCE_C, COMPONENTS_C);
 166 
 167         static MethodHandle MH_S, MH_I, MH_L, MH_B, MH_PRED;
 168         static MethodHandle CONSTRUCTOR;
 169         static MethodHandle DIGESTER;
 170         static MethodHandle DIGESTER_PARTIAL;
 171         static MethodType TYPE = MethodType.methodType(TestClass.class, String.class, int.class, long.class, byte.class);
 172         static {
 173             try {
 174                 MH_B = MethodHandles.lookup().findGetter(TestClass.class, "b", byte.class);
 175                 MH_S = MethodHandles.lookup().findGetter(TestClass.class, "s", String.class);
 176                 MH_I = MethodHandles.lookup().findGetter(TestClass.class, "i", int.class);
 177                 MH_L = MethodHandles.lookup().findGetter(TestClass.class, "l", long.class);
 178                 MH_PRED = MethodHandles.lookup().findVirtual(TestClass.class, "matches", MethodType.methodType(boolean.class));
 179                 CONSTRUCTOR = MethodHandles.lookup().findConstructor(TestClass.class, TYPE.changeReturnType(void.class));
 180                 DIGESTER = MethodHandles.lookup().findVirtual(TestClass.class, "digest", MethodType.methodType(Object.class, MethodHandle.class));
 181                 DIGESTER_PARTIAL = MethodHandles.lookup().findVirtual(TestClass.class, "digestPartial", MethodType.methodType(Object.class, MethodHandle.class));
 182             }
 183             catch (ReflectiveOperationException e) {
 184                 throw new ExceptionInInitializerError(e);
 185             }
 186         }
 187         static MethodHandle[] COMPONENT_MHS = {TestClass.MH_S, TestClass.MH_I, TestClass.MH_L, TestClass.MH_B };
 188 
 189         String s;
 190         int i;
 191         long l;
 192         byte b;
 193 
 194         TestClass(String s, int i, long l, byte b) {
 195             this.s = s;
 196             this.i = i;
 197             this.l = l;
 198             this.b = b;
 199         }
 200 
 201         TestClass copy() {
 202             return new TestClass(s, i, l, b);
 203         }
 204 
 205         boolean matches() { return s != null && s.length() == i; }
 206 
 207         Object digest(MethodHandle target) throws Throwable {
 208             return target.invoke(s, i, l, b);
 209         }
 210 
 211         Object digestPartial(MethodHandle target) throws Throwable {
 212             return matches() ? target.invoke(s, i, l, b) : null;
 213         }
 214 
 215         @Override
 216         public boolean equals(Object o) {
 217             if (this == o) return true;
 218             if (o == null || getClass() != o.getClass()) return false;
 219             TestClass aClass = (TestClass) o;
 220             return i == aClass.i &&
 221                    l == aClass.l &&
 222                    b == aClass.b &&
 223                    Objects.equals(s, aClass.s);
 224         }
 225 
 226         @Override
 227         public int hashCode() {
 228             return Objects.hash(s, i, l, b);
 229         }
 230     }
 231 
 232     private static class TestClass2 {
 233         static MethodHandle MH_X;
 234         static MethodType TYPE = MethodType.methodType(TestClass2.class, Object.class);
 235         static {
 236             try {
 237                 MH_X = MethodHandles.lookup().findGetter(TestClass2.class, "x", Object.class);
 238             }
 239             catch (ReflectiveOperationException e) {
 240                 throw new ExceptionInInitializerError(e);
 241             }
 242         }
 243 
 244         Object x;
 245 
 246         public TestClass2(Object x) {
 247             this.x = x;
 248         }
 249     }
 250 
 251     PatternHandle TYPE_STRING = PatternHandles.ofType(String.class);
 252     PatternHandle TYPE_LIST = PatternHandles.ofType(List.class);
 253     PatternHandle TYPE_INTEGER = PatternHandles.ofType(Integer.class);
 254     PatternHandle TYPE_NUMBER = PatternHandles.ofType(Number.class);
 255     PatternHandle TYPE_OBJECT = PatternHandles.ofType(Object.class);
 256     PatternHandle TYPE_INT = PatternHandles.ofType(int.class);
 257     PatternHandle TYPE_STRING_NULLABLE = PatternHandles.ofTypeNullable(String.class);
 258 
 259     public void testType() throws Throwable {
 260         assertMatch(MatchKind.MATCH_SELF, TYPE_STRING, "Foo", "Foo");
 261         assertMatch(MatchKind.NO_MATCH, TYPE_STRING, null);
 262         assertMatch(MatchKind.ERROR, TYPE_STRING, List.of());
 263         assertMatch(MatchKind.ERROR, TYPE_STRING, 3);
 264 
 265         assertMatch(MatchKind.MATCH_SELF, TYPE_LIST, List.of(3), List.of(3));
 266         assertMatch(MatchKind.MATCH_SELF, TYPE_LIST, List.of(), List.of());
 267         assertMatch(MatchKind.MATCH_SELF, TYPE_LIST, new ArrayList<>(), List.of());
 268         assertMatch(MatchKind.NO_MATCH, TYPE_LIST, null);
 269 
 270         assertMatch(MatchKind.MATCH_SELF, TYPE_INTEGER, 3, 3);
 271         assertMatch(MatchKind.MATCH_SELF, TYPE_NUMBER, 3, 3);
 272         assertMatch(MatchKind.MATCH_SELF, TYPE_OBJECT, 3, 3);
 273         assertMatch(MatchKind.NO_MATCH, TYPE_OBJECT, null);
 274 
 275         assertMatch(MatchKind.ERROR, TYPE_INTEGER, 3.14f);
 276         assertMatch(MatchKind.ERROR, TYPE_INTEGER, "foo");
 277     }
 278 
 279     public void testPrimitiveType() throws Throwable {
 280         assertMatch(MatchKind.MATCH_SELF, TYPE_INT, 3, 3);
 281         assertMatch(MatchKind.ERROR, TYPE_INT, 3.14f);
 282 
 283         PatternHandle asObject = PatternHandles.adaptTarget(TYPE_INT, Object.class);
 284         assertMatch(MatchKind.MATCH_SELF, asObject, 3, 3);
 285         assertMatch(MatchKind.NO_MATCH, asObject, 3.14f);
 286         assertMatch(MatchKind.NO_MATCH, asObject, null);
 287 
 288         PatternHandle asInteger = PatternHandles.adaptTarget(TYPE_INT, Integer.class);
 289         assertMatch(MatchKind.MATCH_SELF, asInteger, 3, 3);
 290         assertMatch(MatchKind.NO_MATCH, asInteger, null);
 291         assertMatch(MatchKind.ERROR, asInteger, 3.14f);
 292     }
 293 
 294     public void testNullableType() throws Throwable {
 295         assertMatch(MatchKind.MATCH_SELF, TYPE_STRING_NULLABLE, "Foo", "Foo");
 296         assertMatch(MatchKind.MATCH, TYPE_STRING_NULLABLE, null, (Object) null);
 297         assertMatch(MatchKind.ERROR, TYPE_STRING_NULLABLE, 3);
 298 
 299         PatternHandle asObjectNullable = PatternHandles.adaptTarget(TYPE_STRING_NULLABLE, Object.class);
 300         assertMatch(MatchKind.MATCH_SELF, asObjectNullable, "Foo", "Foo");
 301         assertMatch(MatchKind.MATCH, asObjectNullable, null, (Object) null);
 302         assertMatch(MatchKind.NO_MATCH, asObjectNullable, 3);
 303     }
 304 
 305     public void testAdapt() throws Throwable {
 306         PatternHandle e = PatternHandles.ofTypeNullable(Number.class);
 307         PatternHandle n = PatternHandles.adaptTarget(e, Integer.class);
 308         PatternHandle w = PatternHandles.adaptTarget(e, Object.class);
 309 
 310         assertEquals(e.descriptor().returnType(), Number.class);
 311         assertEquals(n.descriptor().returnType(), Integer.class);
 312         assertEquals(w.descriptor().returnType(), Object.class);
 313 
 314         assertMatch(MatchKind.MATCH_SELF, e, 1, 1);
 315         assertMatch(MatchKind.MATCH_SELF, n, 1, 1);
 316         assertMatch(MatchKind.MATCH_SELF, w, 1, 1);
 317 
 318         assertMatch(MatchKind.MATCH_SELF, e, 3.14f, 3.14f);
 319         assertMatch(MatchKind.ERROR, n, 3.14f);
 320         assertMatch(MatchKind.MATCH_SELF, w, 3.14f, 3.14f);
 321 
 322         assertMatch(MatchKind.MATCH, e, null, (Object) null);
 323         assertMatch(MatchKind.MATCH, n, null, (Object) null);
 324         assertMatch(MatchKind.MATCH, w, null, (Object) null);
 325 
 326         e = PatternHandles.ofType(Number.class);
 327         n = PatternHandles.adaptTarget(e, Integer.class);
 328         w = PatternHandles.adaptTarget(e, Object.class);
 329 
 330         assertMatch(MatchKind.MATCH_SELF, e, 1, 1);
 331         assertMatch(MatchKind.MATCH_SELF, n, 1, 1);
 332         assertMatch(MatchKind.MATCH_SELF, w, 1, 1);
 333         assertMatch(MatchKind.NO_MATCH, e, null);
 334         assertMatch(MatchKind.NO_MATCH, n, null);
 335         assertMatch(MatchKind.NO_MATCH, w, null);
 336 
 337         PatternHandle widenNarrow = PatternHandles.adaptTarget(PatternHandles.adaptTarget(TYPE_STRING, Object.class), String.class);
 338         assertMatch(MatchKind.MATCH_SELF, widenNarrow, "Foo", "Foo");
 339         assertMatch(MatchKind.NO_MATCH, widenNarrow, null);
 340         assertMatch(MatchKind.ERROR, widenNarrow, List.of());
 341         assertMatch(MatchKind.ERROR, widenNarrow, 3);
 342 
 343         PatternHandle widenNarrowNullable = PatternHandles.adaptTarget(PatternHandles.adaptTarget(TYPE_STRING_NULLABLE, Object.class), String.class);
 344         assertMatch(MatchKind.MATCH_SELF, widenNarrowNullable, "Foo", "Foo");
 345         assertMatch(MatchKind.MATCH, widenNarrowNullable, null, (Object) null);
 346         assertMatch(MatchKind.ERROR, widenNarrowNullable, List.of());
 347         assertMatch(MatchKind.ERROR, widenNarrowNullable, 3);
 348     }
 349 
 350     public void testConstant() throws Throwable {
 351         PatternHandle constantFoo = PatternHandles.ofConstant("foo");
 352         assertMatch(MatchKind.MATCH, constantFoo, "foo");
 353         assertMatch(MatchKind.NO_MATCH, constantFoo, "bar");
 354         assertMatch(MatchKind.ERROR, constantFoo, 3);
 355         assertMatch(MatchKind.NO_MATCH, constantFoo, null);
 356 
 357         PatternHandle constantThree = PatternHandles.ofConstant(3);
 358         assertMatch(MatchKind.MATCH, constantThree, 3);
 359         assertMatch(MatchKind.NO_MATCH, constantThree, 4);
 360         assertMatch(MatchKind.NO_MATCH, constantThree, null);
 361     }
 362 
 363     public void testNullConstant() throws Throwable {
 364         PatternHandle constantNull = PatternHandles.ofConstant(null);
 365         assertMatch(MatchKind.MATCH, constantNull, null);
 366         assertMatch(MatchKind.NO_MATCH, constantNull, "foo");
 367         assertMatch(MatchKind.NO_MATCH, constantNull, 3);
 368     }
 369 
 370     public void testProjections() throws Throwable {
 371         Map<PatternHandle, MatchKind> m
 372                 = Map.of(PatternHandles.ofLazyProjection(TestClass.class, TestClass.COMPONENT_MHS), MatchKind.MATCH_SELF,
 373                          PatternHandles.ofEagerProjection(TestClass.class, TestClass.COMPONENT_MHS), MatchKind.MATCH_CARRIER);
 374         for (var ps : m.entrySet()) {
 375             for (var entry : TestClass.INSTANCES.entrySet()) {
 376                 assertMatch(ps.getValue(), ps.getKey(), entry.getKey(), entry.getValue());
 377             }
 378             assertMatch(MatchKind.NO_MATCH, ps.getKey(), null);
 379 
 380             PatternHandle asObject = PatternHandles.adaptTarget(ps.getKey(), Object.class);
 381             for (var entry : TestClass.INSTANCES.entrySet())
 382                 assertMatch(ps.getValue(), asObject, entry.getKey(), entry.getValue());
 383             assertMatch(MatchKind.NO_MATCH, asObject, null);
 384 
 385             PatternHandle asTestClassAgain = PatternHandles.adaptTarget(asObject, TestClass.class);
 386             for (var entry : TestClass.INSTANCES.entrySet())
 387                 assertMatch(ps.getValue(), asTestClassAgain, entry.getKey(), entry.getValue());
 388             assertMatch(MatchKind.NO_MATCH, asTestClassAgain, null);
 389         }
 390     }
 391 
 392     public void testDigest() throws Throwable {
 393         PatternHandle e = PatternHandles.ofImperative(TestClass.TYPE, TestClass.DIGESTER);
 394         for (var entry : TestClass.INSTANCES.entrySet())
 395             assertMatch(MatchKind.MATCH_CARRIER, e, entry.getKey(), entry.getValue());
 396         assertMatch(MatchKind.NO_MATCH, e, null);
 397     }
 398 
 399     public void testDigestPartial() throws Throwable {
 400         PatternHandle e = PatternHandles.ofImperative(TestClass.TYPE, TestClass.DIGESTER_PARTIAL);
 401         for (var entry : TestClass.INSTANCES.entrySet()) {
 402             if (entry.getKey().matches())
 403                 assertMatch(MatchKind.MATCH_CARRIER, e, entry.getKey(), entry.getValue());
 404             else
 405                 assertMatch(MatchKind.NO_MATCH, e, entry.getKey());
 406         }
 407         assertMatch(MatchKind.NO_MATCH, e, null);
 408     }
 409 
 410     public void testCompose() throws Throwable {
 411         PatternHandle e = PatternHandles.ofLazyProjection(TestClass.class, TestClass.COMPONENT_MHS);
 412         MethodHandle mh = PatternHandles.compose(e, TestClass.CONSTRUCTOR);
 413         TestClass target = TestClass.INSTANCE_A;
 414         Object o = mh.invoke(target);
 415         assertTrue(o instanceof TestClass);
 416         assertNotSame(target, o);
 417         assertEquals(target, o);
 418 
 419         assertNull(mh.invoke((Object) null));
 420     }
 421 
 422     public void testDropBindings() throws Throwable {
 423         PatternHandle e = PatternHandles.ofEagerProjection(TestClass.class, TestClass.COMPONENT_MHS);
 424         assertMatch(MatchKind.MATCH_CARRIER, e, TestClass.INSTANCE_A,
 425                     TestClass.COMPONENTS_A);
 426         assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(e, 0), TestClass.INSTANCE_A,
 427                     3, 4L, (byte) 5);
 428         assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(e, 0, 0), TestClass.INSTANCE_A,
 429                     3, 4L, (byte) 5);
 430         assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(e, 3), TestClass.INSTANCE_A,
 431                     "foo", 3, 4L);
 432         assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(e, 0, 1, 2, 3), TestClass.INSTANCE_A);
 433 
 434         assertThrows(IndexOutOfBoundsException.class,
 435                      () -> assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(e, -1), TestClass.INSTANCE_A,
 436                                        3, 4L, (byte) 5));
 437         assertThrows(IndexOutOfBoundsException.class,
 438                      () -> assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(e, 4), TestClass.INSTANCE_A,
 439                                        3, 4L, (byte) 5));
 440     }
 441 
 442     public void testNested() throws Throwable {
 443         PatternHandle TC2 = PatternHandles.ofLazyProjection(TestClass2.class, TestClass2.MH_X);
 444         PatternHandle TC2_STRING = PatternHandles.nested(TC2, TYPE_STRING);
 445         PatternHandle TC2_OBJECT = PatternHandles.nested(TC2, TYPE_OBJECT);
 446 
 447         assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(TC2_STRING, 0), new TestClass2("foo"),
 448                     "foo");
 449         assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(TC2_OBJECT, 0), new TestClass2("foo"),
 450                     "foo");
 451         assertMatch(MatchKind.NO_MATCH, PatternHandles.dropBindings(TC2_STRING, 0), new TestClass2(List.of(3)),
 452                     "foo");
 453 
 454         assertMatch(MatchKind.MATCH_CARRIER, PatternHandles.dropBindings(PatternHandles.nested(TC2, TC2_STRING), 0, 1), new TestClass2(new TestClass2("foo")),
 455                     "foo");
 456     }
 457 }