changeset 52935:b49697bf259d amber-demo

Automatic merge with datum
author mcimadamore
date Tue, 30 Oct 2018 18:08:59 +0100
parents 3486dd38dbda b1d6664ce434
children 75b290883f15
files src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties test/langtools/tools/javac/datum/DatumShouldDeclareAtLeastOneFieldTest.java test/langtools/tools/javac/datum/DatumShouldDeclareAtLeastOneFieldTest.out
diffstat 11 files changed, 1946 insertions(+), 115 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Oct 26 19:05:30 2018 +0200
+++ b/.hgignore	Tue Oct 30 18:08:59 2018 +0100
@@ -11,6 +11,6 @@
 test/nashorn/script/external
 test/nashorn/lib
 NashornProfile.txt
-.*/JTreport/.*
-.*/JTwork/.*
+JTreport/
+JTwork/
 .*/.git/.*
--- a/src/java.base/share/classes/java/lang/compiler/Extractor.java	Fri Oct 26 19:05:30 2018 +0200
+++ b/src/java.base/share/classes/java/lang/compiler/Extractor.java	Tue Oct 30 18:08:59 2018 +0100
@@ -29,6 +29,8 @@
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
+import java.util.Objects;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 import sun.invoke.util.BytecodeName;
@@ -37,6 +39,7 @@
 import static java.lang.invoke.MethodHandleInfo.REF_invokeStatic;
 import static java.lang.invoke.MethodHandleInfo.REF_invokeVirtual;
 import static java.lang.invoke.MethodHandleInfo.REF_newInvokeSpecial;
+import static java.util.Objects.requireNonNull;
 
 /**
  * Supporting type for implementation of pattern matching.  An {@linkplain Extractor}
@@ -48,6 +51,11 @@
  * argument types of the descriptor indicate the types of the output binding
  * variables.
  *
+ * Notes:
+ *  - totality is erased;
+ *  - compilers expected to optimize away total type patterns;
+ *  - adaptation done in nest() and switch combinators
+ *
  * @author Brian Goetz
  */
 public interface Extractor {
@@ -70,10 +78,10 @@
     MethodHandle component(int i);
 
     /**
-     * Whether this extractor might fail.
-     * @return if this extractor might fail
+     * Returns the component method handles, as an array
+     * @return the component method handles
      */
-    boolean isPartial();
+    MethodHandle[] components();
 
     /**
      * The descriptor of the {@linkplain Extractor}.  The parameter types of
@@ -88,22 +96,21 @@
      * Compose an extractor with a method handle that receives the bindings
      *
      * @param target method handle to receive the bindings
+     * @param sentinel value to return when the extractor does not match
      * @return the composed method handle
      */
-    default MethodHandle compose(MethodHandle target) {
+    default MethodHandle compose(MethodHandle target, Object sentinel) {
         int count = descriptor().parameterCount();
-        MethodHandle[] components = new MethodHandle[count];
-        int[] reorder = new int[count];
-        for (int i=0; i<count; i++) {
-            components[i] = component(i);
-            reorder[i] = 0;
-        }
+        MethodHandle[] components = components();
+        Class<?> carrierType = tryMatch().type().returnType();
+        Class<?> resultType = target.type().returnType();
 
         MethodHandle mh = MethodHandles.filterArguments(target, 0, components);
-        mh = MethodHandles.permuteArguments(mh, MethodType.methodType(target.type().returnType(), tryMatch().type().returnType()),
-                                            reorder);
+        mh = MethodHandles.permuteArguments(mh, MethodType.methodType(resultType, carrierType), new int[count]);
+        mh = MethodHandles.guardWithTest(ExtractorImpl.MH_OBJECTS_NONNULL.asType(MethodType.methodType(boolean.class, carrierType)),
+                                         mh,
+                                         MethodHandles.dropArguments(MethodHandles.constant(resultType, sentinel), 0, carrierType));
         mh = MethodHandles.filterArguments(mh, 0, tryMatch());
-        // @@@ What if pattern doesn't match?
         return mh;
     }
 
@@ -124,6 +131,35 @@
     }
 
     /**
+     * Construct a partial method handle that uses the predicate as guardWithTest,
+     * which applies the target if the test succeeds, and returns null if the
+     * test fails.  The resulting method handle is of the same type as the
+     * {@code target} method handle.
+     * @param target
+     * @param predicate
+     * @return
+     */
+    private static MethodHandle partialize(MethodHandle target, MethodHandle predicate) {
+        Class<?> targetType = target.type().parameterType(0);
+        Class<?> carrierType = target.type().returnType();
+        return MethodHandles.guardWithTest(predicate,
+                                           target,
+                                           MethodHandles.dropArguments(MethodHandles.constant(carrierType, null),
+                                                                       0, targetType));
+    }
+
+    /**
+     * Construct a method handle that delegates to target, unless the nth argument
+     * is null, in which case it returns null
+     */
+    private static MethodHandle bailIfNthNull(MethodHandle target, int n) {
+        MethodHandle test = ExtractorImpl.MH_OBJECTS_ISNULL.asType(ExtractorImpl.MH_OBJECTS_ISNULL.type().changeParameterType(0, target.type().parameterType(n)));
+        test = MethodHandles.permuteArguments(test, target.type().changeReturnType(boolean.class), n);
+        MethodHandle nullh = MethodHandles.dropArguments(MethodHandles.constant(target.type().returnType(), null), 0, target.type().parameterArray());
+        return MethodHandles.guardWithTest(test, nullh, target);
+    }
+
+    /**
      * Create a total {@linkplain Extractor} with the given descriptor, which
      * operates by feeding results into a factory method handle and returning
      * the result.
@@ -134,24 +170,7 @@
      */
     public static Extractor of(MethodType descriptor,
                                MethodHandle digester) {
-        return new ExtractorImpl(descriptor, false,
-                                 MethodHandles.insertArguments(digester,
-                                                               1, ExtractorCarriers.carrierFactory(descriptor)),
-                                 ExtractorCarriers.carrierComponents(descriptor));
-    }
-
-    /**
-     * Create a partial {@linkplain Extractor} with the given descriptor, which
-     * operates by feeding results into a factory method handle and returning
-     * the result.
-     *
-     * @param descriptor the descriptor
-     * @param digester the digester method handle
-     * @return the extractor
-     */
-    public static Extractor ofPartial(MethodType descriptor,
-                                      MethodHandle digester) {
-        return new ExtractorImpl(descriptor, true,
+        return new ExtractorImpl(descriptor,
                                  MethodHandles.insertArguments(digester,
                                                                1, ExtractorCarriers.carrierFactory(descriptor)),
                                  ExtractorCarriers.carrierComponents(descriptor));
@@ -167,7 +186,7 @@
      */
     public static Extractor ofTotal(Class<?> targetType, MethodHandle... components) {
         MethodType descriptor = descriptor(targetType, components);
-        return new ExtractorImpl(descriptor, false,
+        return new ExtractorImpl(descriptor,
                                  carrierTryExtract(descriptor, components),
                                  ExtractorCarriers.carrierComponents(descriptor));
     }
@@ -181,7 +200,7 @@
      * @return the extractor
      */
     public static Extractor ofSelfTotal(Class<?> targetType, MethodHandle... components) {
-        return new ExtractorImpl(descriptor(targetType, components), false,
+        return new ExtractorImpl(descriptor(targetType, components),
                                  MethodHandles.identity(targetType), components);
     }
 
@@ -189,53 +208,186 @@
      * Create a partial {@linkplain Extractor} for a given set of component
      * method handles.
      *
+     * @param targetType the target type
      * @param predicate The match predicate
      * @param components The component method handles
      * @return the extractor
      */
-    public static Extractor ofPartial(MethodHandle predicate, MethodHandle... components) {
-        Class<?> targetType = predicate.type().parameterType(0);
+    public static Extractor ofPartial(Class<?> targetType, MethodHandle predicate, MethodHandle... components) {
         MethodType descriptor = descriptor(targetType, components);
         MethodHandle carrierTryExtract = carrierTryExtract(descriptor, components);
-        MethodHandle tryExtract = MethodHandles.guardWithTest(predicate,
-                                                              carrierTryExtract,
-                                                              MethodHandles.dropArguments(MethodHandles.constant(carrierTryExtract.type().returnType(), null),
-                                                                                          0, targetType));
-        return new ExtractorImpl(descriptor, true,
-                                 tryExtract, ExtractorCarriers.carrierComponents(descriptor));
+        return new ExtractorImpl(descriptor,
+                                 partialize(carrierTryExtract, predicate),
+                                 ExtractorCarriers.carrierComponents(descriptor));
     }
 
     /**
      * Create a partial {@linkplain Extractor} for a given set of component
      * method handles, using itself as a carrier.
      *
+     * @param targetType the target type
      * @param predicate The match predicate
      * @param components The component method handles
      * @return the extractor
      */
-    public static Extractor ofSelfPartial(MethodHandle predicate, MethodHandle... components) {
-        Class<?> targetType = predicate.type().parameterType(0);
-        MethodHandle tryExtract = MethodHandles.guardWithTest(predicate,
-                                                              MethodHandles.identity(targetType),
-                                                              MethodHandles.dropArguments(MethodHandles.constant(targetType, null),
-                                                                                          0, targetType));
-        return new ExtractorImpl(descriptor(targetType, components), true, tryExtract, components);
+    public static Extractor ofSelfPartial(Class<?> targetType, MethodHandle predicate, MethodHandle... components) {
+        return new ExtractorImpl(descriptor(targetType, components),
+                                 partialize(MethodHandles.identity(targetType), predicate),
+                                 components);
     }
 
     /**
      * Create an {@linkplain Extractor} for a type pattern, with a single binding
-     * variable
+     * variable, whose target type is {@code Object}
      *
      * @param type the type to match against
      * @return the {@linkplain Extractor}
      */
     public static Extractor ofType(Class<?> type) {
-        // tryMatch = (t instanceof type) ? t : null
-        // component = (type) o
-        return null;
+        requireNonNull(type);
+        if (type.isPrimitive())
+            throw new IllegalArgumentException("Reference type expected, found: " + type);
+        return new ExtractorImpl(MethodType.methodType(type, type),
+                                 ExtractorImpl.MH_OF_TYPE_HELPER.bindTo(type).asType(MethodType.methodType(type, type)),
+                                 MethodHandles.identity(type));
     }
 
     /**
+     * Create an {@linkplain Extractor} for a nullable type pattern, with a
+     * single binding variable, whose target type is {@code Object}
+     *
+     * @param type the type to match against
+     * @return the {@linkplain Extractor}
+     */
+    public static Extractor ofTypeNullable(Class<?> type) {
+        requireNonNull(type);
+        if (type.isPrimitive())
+            throw new IllegalArgumentException("Reference type expected, found: " + type);
+        return new ExtractorImpl(MethodType.methodType(type, type),
+                                 ExtractorImpl.MH_OF_TYPE_NULLABLE_HELPER.bindTo(type).asType(MethodType.methodType(type, type)),
+                                 MethodHandles.identity(type));
+    }
+
+    /**
+     * Create an {@linkplain Extractor} that is identical to another {@linkplain Extractor},
+     * but without the specified binding variables
+     * @param etor the original extractor
+     * @param positions which binding variables to drop
+     * @return the extractor
+     */
+    public static Extractor dropBindings(Extractor etor, int... positions) {
+        MethodHandle[] mhs = etor.components();
+        for (int position : positions)
+            mhs[position] = null;
+        mhs = Stream.of(mhs).filter(Objects::nonNull).toArray(MethodHandle[]::new);
+        return new ExtractorImpl(descriptor(etor.descriptor().returnType(), mhs), etor.tryMatch(), mhs);
+    }
+
+    /**
+     * Adapt an extractor to a new target type
+     *
+     * @param e the extractor
+     * @param newTarget the new target type
+     * @return the new extractor
+     */
+    public static Extractor adapt(Extractor e, Class<?> newTarget) {
+        if (e.descriptor().returnType().isAssignableFrom(newTarget))
+            return e;
+        MethodHandle tryMatch = partialize(e.tryMatch().asType(e.tryMatch().type().changeParameterType(0, newTarget)),
+                                           ExtractorImpl.MH_ADAPT_HELPER.bindTo(e.descriptor().returnType())
+        .asType(MethodType.methodType(boolean.class, newTarget)));
+        return new ExtractorImpl(e.descriptor().changeReturnType(newTarget),
+                                 tryMatch, e.components());
+    }
+
+    /**
+     * Construct a nested extractor, which first matches the target to the
+     * outer extractor, and then matches the resulting bindings to the inner
+     * extractors (if not null).  The resulting extractor is partial if any
+     * of the input extractors are; its target type is the target type of the
+     * outer extractor; and its bindings are the concatenation of the bindings
+     * of the outer extractor followed by the bindings of the non-null inner
+     * extractors.
+     *
+     * @param outer The outer extractor
+     * @param extractors The inner extractors, or null if no nested extraction
+     *                   for this outer binding is desired
+     * @return the nested extractor
+     */
+    public static Extractor ofNested(Extractor outer, Extractor... extractors) {
+        int outerCount = outer.descriptor().parameterCount();
+        Class<?> outerCarrierType = outer.tryMatch().type().returnType();
+
+        // Adapt inners to types of outer bindings
+        for (int i = 0; i < extractors.length; i++) {
+            Extractor extractor = extractors[i];
+            if (extractor.descriptor().returnType() != outer.descriptor().parameterType(i))
+                extractors[i] = adapt(extractor, outer.descriptor().parameterType(i));
+        }
+
+        int[] innerPositions = IntStream.range(0, extractors.length)
+                                        .filter(i -> extractors[i] != null)
+                                        .toArray();
+        MethodHandle[] innerComponents = Stream.of(extractors)
+                                               .filter(Objects::nonNull)
+                                               .map(Extractor::components)
+                                               .flatMap(Stream::of)
+                                               .toArray(MethodHandle[]::new);
+        MethodHandle[] innerTryMatches = Stream.of(extractors)
+                                               .filter(Objects::nonNull)
+                                               .map(e -> e.tryMatch())
+                                               .toArray(MethodHandle[]::new);
+        Class<?>[] innerCarriers = Stream.of(extractors)
+                                         .filter(Objects::nonNull)
+                                         .map(e -> e.tryMatch().type().returnType())
+                                         .toArray(Class[]::new);
+        Class<?>[] innerTypes = Stream.of(innerComponents)
+                                      .map(mh -> mh.type().returnType())
+                                      .toArray(Class[]::new);
+
+        MethodType descriptor = outer.descriptor().appendParameterTypes(innerTypes);
+
+        MethodHandle mh = ExtractorCarriers.carrierFactory(descriptor);
+        mh = MethodHandles.filterArguments(mh, outerCount, innerComponents);
+        int[] spreadInnerCarriers = new int[outerCount + innerComponents.length];
+        for (int i=0; i<outerCount; i++)
+            spreadInnerCarriers[i] = i;
+        int k = outerCount;
+        int j = 0;
+        for (Extractor e : extractors) {
+            if (e == null)
+                continue;
+            for (int i=0; i<e.descriptor().parameterCount(); i++)
+                spreadInnerCarriers[k++] = outerCount + j;
+            j++;
+        }
+        MethodType spreadInnerCarriersMT = outer.descriptor()
+                                                .appendParameterTypes(innerCarriers)
+                                                .changeReturnType(mh.type().returnType());
+        mh = MethodHandles.permuteArguments(mh, spreadInnerCarriersMT, spreadInnerCarriers);
+        for (int position : innerPositions)
+            mh = bailIfNthNull(mh, outerCount + position);
+        mh = MethodHandles.filterArguments(mh, outerCount, innerTryMatches);
+        int[] spreadNestedCarrier = new int[outerCount + innerPositions.length];
+        for (int i=0; i<outerCount; i++)
+            spreadNestedCarrier[i] = i;
+        for (int i=0; i<innerPositions.length; i++)
+            spreadNestedCarrier[outerCount+i] = innerPositions[i];
+        mh = MethodHandles.permuteArguments(mh, outer.descriptor().changeReturnType(mh.type().returnType()),
+                                            spreadNestedCarrier);
+        mh = MethodHandles.filterArguments(mh, 0, outer.components());
+        mh = MethodHandles.permuteArguments(mh, MethodType.methodType(mh.type().returnType(), outerCarrierType),
+                                            new int[outerCount]);
+        mh = bailIfNthNull(mh, 0);
+        mh = MethodHandles.filterArguments(mh, 0, outer.tryMatch());
+
+        MethodHandle tryExtract = mh;
+
+        return new ExtractorImpl(descriptor, tryExtract, ExtractorCarriers.carrierComponents(descriptor));
+    }
+
+
+    /**
      * Bootstrap for creating a lazy, partial, self-carrier {@linkplain Extractor} from components
      *
      * @param lookup ignored
@@ -267,6 +419,48 @@
         return ofSelfTotal(descriptor.returnType(), components);
     }
 
+    /**
+     * Condy bootstrap for creating nested extractors
+     *
+     * @param lookup ignored
+     * @param invocationName ignored
+     * @param invocationType ignored
+     * @param outer the outer extractor
+     * @param inners the inner extractors, null if no nesting is needed for this binding
+     * @return the nested extractor
+     */
+    public static Extractor ofNested(MethodHandles.Lookup lookup, String invocationName, MethodType invocationType,
+                                     Extractor outer, Extractor... inners) {
+        return ofNested(outer, inners);
+    }
+
+    /**
+     * Condy bootstrap for creating non-nullable type extractor
+     *
+     * @param lookup ignored
+     * @param invocationName ignored
+     * @param invocationType ignored
+     * @param type the type
+     * @return the extractor
+     */
+    public static Extractor ofType(MethodHandles.Lookup lookup, String invocationName, MethodType invocationType,
+                                   Class<?> type) {
+        return ofType(type);
+    }
+
+    /**
+     * Condy bootstrap for creating nullable type extractor
+     *
+     * @param lookup ignored
+     * @param invocationName ignored
+     * @param invocationType ignored
+     * @param type the type
+     * @return the extractor
+     */
+    public static Extractor ofTypeNullable(MethodHandles.Lookup lookup, String invocationName, MethodType invocationType,
+                                           Class<?> type) {
+        return ofTypeNullable(type);
+    }
 
     /**
      * Condy bootstrap for finding extractors
@@ -284,11 +478,10 @@
     public static Extractor findExtractor(MethodHandles.Lookup lookup, String constantName, Class<Extractor> constantType,
                                           Class<?> owner, MethodType descriptor, String name, int refKind) throws Throwable {
         String dd = descriptor.toMethodDescriptorString();
-        dd = dd.substring(0, dd.indexOf(')') + 1);
         String patternMethodName
                 = BytecodeName.toBytecodeName(String.format("$pattern$%s$%s",
                                                             (refKind == REF_newInvokeSpecial ? owner.getSimpleName() : name),
-                                                            dd));
+                                                            dd.substring(0, dd.indexOf(')') + 1)));
         MethodType factoryDesc = MethodType.methodType(Extractor.class);
         MethodHandle mh;
         switch (refKind) {
--- a/src/java.base/share/classes/java/lang/compiler/ExtractorImpl.java	Fri Oct 26 19:05:30 2018 +0200
+++ b/src/java.base/share/classes/java/lang/compiler/ExtractorImpl.java	Tue Oct 30 18:08:59 2018 +0100
@@ -25,18 +25,38 @@
 package java.lang.compiler;
 
 import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Non-public implementation of {@link Extractor}
  */
 class ExtractorImpl implements Extractor {
     private final MethodType descriptor;
-    private final boolean partial;
     private final MethodHandle tryMatch;
     private final List<MethodHandle> components;
 
+    // These are helpers for Extractors
+    static final MethodHandle MH_OF_TYPE_HELPER;
+    static final MethodHandle MH_OF_TYPE_NULLABLE_HELPER;
+    static final MethodHandle MH_ADAPT_HELPER;
+    static final MethodHandle MH_OBJECTS_ISNULL;
+    static final MethodHandle MH_OBJECTS_NONNULL;
+    static {
+        try {
+            MH_OF_TYPE_HELPER = MethodHandles.lookup().findStatic(ExtractorImpl.class, "ofTypeHelper", MethodType.methodType(Object.class, Class.class, Object.class));
+            MH_OF_TYPE_NULLABLE_HELPER = MethodHandles.lookup().findStatic(ExtractorImpl.class, "ofTypeNullableHelper", MethodType.methodType(Object.class, Class.class, Object.class));
+            MH_ADAPT_HELPER = MethodHandles.lookup().findStatic(ExtractorImpl.class, "adaptHelper", MethodType.methodType(boolean.class, Class.class, Object.class));
+            MH_OBJECTS_ISNULL = MethodHandles.lookup().findStatic(Objects.class, "isNull", MethodType.methodType(boolean.class, Object.class));
+            MH_OBJECTS_NONNULL = MethodHandles.lookup().findStatic(Objects.class, "nonNull", MethodType.methodType(boolean.class, Object.class));
+        }
+        catch (ReflectiveOperationException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
     /**
      * Construct an {@link Extractor} from components
      * Constraints:
@@ -48,7 +68,7 @@
      * @param tryMatch The {@code tryMatch} method handle
      * @param components The {@code component} method handles
      */
-    ExtractorImpl(MethodType descriptor, boolean partial, MethodHandle tryMatch, MethodHandle[] components) {
+    ExtractorImpl(MethodType descriptor, MethodHandle tryMatch, MethodHandle... components) {
         Class<?> carrierType = tryMatch.type().returnType();
         if (descriptor.parameterCount() != components.length)
             throw new IllegalArgumentException(String.format("MethodType %s arity should match component count %d", descriptor, components.length));
@@ -65,7 +85,6 @@
         }
 
         this.descriptor = descriptor;
-        this.partial = partial;
         this.tryMatch = tryMatch;
         this.components = List.of(components);
     }
@@ -81,12 +100,24 @@
     }
 
     @Override
+    public MethodHandle[] components() {
+        return components.toArray(new MethodHandle[0]);
+    }
+
+    @Override
     public MethodType descriptor() {
         return descriptor;
     }
 
-    @Override
-    public boolean isPartial() {
-        return partial;
+    private static Object ofTypeHelper(Class<?> type, Object o) {
+        return o != null && type.isAssignableFrom(o.getClass()) ? o : null;
+    }
+
+    private static Object ofTypeNullableHelper(Class<?> type, Object o) {
+        return o == null || type.isAssignableFrom(o.getClass()) ? o : null;
+    }
+
+    private static boolean adaptHelper(Class<?> type, Object o) {
+        return o != null && type.isAssignableFrom(o.getClass());
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/lang/compiler/SwitchBootstraps.java	Tue Oct 30 18:08:59 2018 +0100
@@ -0,0 +1,882 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.compiler;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Bootstrap methods for linking {@code invokedynamic} call sites that implement
+ * the selection functionality of the {@code switch} statement.  The bootstraps
+ * take additional static arguments corresponding to the {@code case} labels
+ * of the {@code switch}, implicitly numbered sequentially from {@code [0..N)}.
+ *
+ * <p>The bootstrap call site accepts a single parameter of the type of the
+ * operand of the {@code switch}, and return an {@code int} that is the index of
+ * the matched {@code case} label, {@code -1} if the target is {@code null},
+ * or {@code N} if the target is not null but matches no {@code case} label.
+ */
+public class SwitchBootstraps {
+
+    // Shared INIT_HOOK for all switch call sites; looks the target method up in a map
+    private static final MethodHandle CONSTANT_INIT_HOOK;
+    private static final MethodHandle PATTERN_INIT_HOOK;
+    private static final MethodHandle TYPE_INIT_HOOK;
+    private static final MethodHandle PATTERN_SWITCH_METHOD;
+    private static final MethodHandle TYPE_SWITCH_METHOD;
+    private static final Map<Class<?>, MethodHandle> switchMethods = new ConcurrentHashMap<>();
+
+    private static final Set<Class<?>> BOOLEAN_TYPES
+            = Set.of(boolean.class, Boolean.class);
+    // Types that can be handled as int switches
+    private static final Set<Class<?>> INT_TYPES
+            = Set.of(int.class, short.class, byte.class, char.class,
+                     Integer.class, Short.class, Byte.class, Character.class);
+    private static final Set<Class<?>> FLOAT_TYPES
+            = Set.of(float.class, Float.class);
+    private static final Set<Class<?>> LONG_TYPES
+            = Set.of(long.class, Long.class);
+    private static final Set<Class<?>> DOUBLE_TYPES
+            = Set.of(double.class, Double.class);
+
+    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
+    private static final Function<Class<?>, MethodHandle> lookupSwitchMethod =
+            new Function<>() {
+                @Override
+                public MethodHandle apply(Class<?> c) {
+                    try {
+                        Class<?> switchClass;
+                        if (c == Enum.class)
+                            switchClass = EnumSwitchCallSite.class;
+                        else if (c == String.class)
+                            switchClass = StringSwitchCallSite.class;
+                        else if (BOOLEAN_TYPES.contains(c) || INT_TYPES.contains(c) ||
+                                 FLOAT_TYPES.contains(c))
+                            switchClass = IntSwitchCallSite.class;
+                        else if (LONG_TYPES.contains(c) || DOUBLE_TYPES.contains(c))
+                            switchClass = LongSwitchCallSite.class;
+                        else if (c == Object.class)
+                            switchClass = TypeSwitchCallSite.class;
+                        else
+                            throw new BootstrapMethodError("Invalid switch type: " + c);
+
+                        return LOOKUP.findVirtual(switchClass, "doSwitch",
+                                                  MethodType.methodType(int.class, c));
+                    }
+                    catch (ReflectiveOperationException e) {
+                        throw new BootstrapMethodError("Invalid switch type: " + c);
+                    }
+                }
+            };
+
+    static {
+        try {
+            CONSTANT_INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "constantInitHook",
+                                                   MethodType.methodType(MethodHandle.class, CallSite.class));
+            PATTERN_INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "patternInitHook",
+                                                  MethodType.methodType(MethodHandle.class, CallSite.class));
+            TYPE_INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "typeInitHook",
+                                                  MethodType.methodType(MethodHandle.class, CallSite.class));
+            PATTERN_SWITCH_METHOD = LOOKUP.findVirtual(PatternSwitchCallSite.class, "doSwitch",
+                                                       MethodType.methodType(PatternSwitchResult.class, Object.class));
+            TYPE_SWITCH_METHOD = LOOKUP.findVirtual(TypeSwitchCallSite.class, "doSwitch",
+                                                    MethodType.methodType(int.class, Object.class));
+        }
+        catch (ReflectiveOperationException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    private static<T extends CallSite> MethodHandle constantInitHook(T receiver) {
+        return switchMethods.computeIfAbsent(receiver.type().parameterType(0), lookupSwitchMethod)
+                            .bindTo(receiver);
+    }
+
+    private static<T extends CallSite> MethodHandle typeInitHook(T receiver) {
+        return TYPE_SWITCH_METHOD.bindTo(receiver);
+    }
+
+    private static<T extends CallSite> MethodHandle patternInitHook(T receiver) {
+        return PATTERN_SWITCH_METHOD.bindTo(receiver);
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on a {@code boolean} or {@code Boolean}.
+     * The static arguments are a varargs array of {@code boolean} labels,
+     *
+     * <p>The results are undefined if the labels array contains duplicates.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @param lookup Represents a lookup context with the accessibility
+     *               privileges of the caller.  When used with {@code invokedynamic},
+     *               this is stacked automatically by the VM.
+     * @param invocationName The invocation name, which is ignored.  When used with
+     *                       {@code invokedynamic}, this is provided by the
+     *                       {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param invocationType The invocation type of the {@code CallSite}.  This
+     *                       method type should have a single parameter which is
+     *                       {@code boolean} or {@code Boolean},and return {@code int}.
+     *                       When used with {@code invokedynamic}, this is provided by
+     *                       the {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param booleanLabels boolean values corresponding to the case labels of the
+     *                  {@code switch} statement.
+     * @return the index into {@code booleanLabels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code booleanLabels.length} if the target value does
+     *         not match any of the labels.
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if the invocation type is not
+     * {@code (boolean)int} or {@code (Boolean)int}
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite booleanSwitch(MethodHandles.Lookup lookup,
+                                         String invocationName,
+                                         MethodType invocationType,
+                                         boolean... booleanLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!BOOLEAN_TYPES.contains(invocationType.parameterType(0))))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(booleanLabels);
+
+        int[] intLabels = IntStream.range(0, booleanLabels.length)
+                                   .map(i -> booleanLabels[i] ? 1 : 0)
+                                   .toArray();
+
+        assert IntStream.of(intLabels).distinct().count() == intLabels.length
+                : "switch labels are not distinct: " + Arrays.toString(booleanLabels);
+
+        return new IntSwitchCallSite(invocationType, intLabels);
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on an {@code int}, {@code short}, {@code byte},
+     * {@code char}, or one of their box types.  The static arguments are a
+     * varargs array of {@code int} labels.
+     *
+     * <p>The results are undefined if the labels array contains duplicates.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @param lookup Represents a lookup context with the accessibility
+     *               privileges of the caller.  When used with {@code invokedynamic},
+     *               this is stacked automatically by the VM.
+     * @param invocationName The invocation name, which is ignored.  When used with
+     *                       {@code invokedynamic}, this is provided by the
+     *                       {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param invocationType The invocation type of the {@code CallSite}.  This
+     *                       method type should have a single parameter which is
+     *                       one of the 32-bit or shorter primitive types, or
+     *                       one of their box types, and return {@code int}.  When
+     *                       used with {@code invokedynamic}, this is provided by
+     *                       the {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param intLabels integral values corresponding to the case labels of the
+     *                  {@code switch} statement.
+     * @return the index into {@code intLabels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code intLabels.length} if the target value does
+     *         not match any of the labels.
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if the invocation type is not
+     * {@code (T)int}, where {@code T} is one of the 32-bit or smaller integral
+     * primitive types, or one of their box types
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite intSwitch(MethodHandles.Lookup lookup,
+                                     String invocationName,
+                                     MethodType invocationType,
+                                     int... intLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!INT_TYPES.contains(invocationType.parameterType(0))))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(intLabels);
+
+        assert IntStream.of(intLabels).distinct().count() == intLabels.length
+                : "switch labels are not distinct: " + Arrays.toString(intLabels);
+
+        return new IntSwitchCallSite(invocationType, intLabels);
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on an {@code float} or {@code Float}.
+     * The static arguments are a varargs array of {@code float} labels.
+     *
+     * <p>The results are undefined if the labels array contains duplicates
+     * according to {@link Float#floatToIntBits(float)}.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @param lookup Represents a lookup context with the accessibility
+     *               privileges of the caller.  When used with {@code invokedynamic},
+     *               this is stacked automatically by the VM.
+     * @param invocationName The invocation name, which is ignored.  When used with
+     *                       {@code invokedynamic}, this is provided by the
+     *                       {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param invocationType The invocation type of the {@code CallSite}.  This
+     *                       method type should have a single parameter which is
+     *                       one of the 32-bit or shorter primitive types, or
+     *                       one of their box types, and return {@code int}.  When
+     *                       used with {@code invokedynamic}, this is provided by
+     *                       the {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param floatLabels float values corresponding to the case labels of the
+     *                    {@code switch} statement.
+     * @return the index into {@code floatLabels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code floatLabels.length} if the target value does
+     *         not match any of the labels.
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if the invocation type is not
+     * {@code (float)int} or {@code (Float)int}
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite floatSwitch(MethodHandles.Lookup lookup,
+                                       String invocationName,
+                                       MethodType invocationType,
+                                       float... floatLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!FLOAT_TYPES.contains(invocationType.parameterType(0))))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(floatLabels);
+
+        int[] intLabels = new int[floatLabels.length];
+        for (int i=0; i<floatLabels.length; i++)
+            intLabels[i] = Float.floatToIntBits(floatLabels[i]);
+
+        assert IntStream.of(intLabels).distinct().count() == intLabels.length
+                : "switch labels are not distinct: " + Arrays.toString(floatLabels);
+
+        return new IntSwitchCallSite(invocationType, intLabels);
+    }
+
+    static class IntSwitchCallSite extends ConstantCallSite {
+        private final int[] labels;
+        private final int[] indexes;
+
+        IntSwitchCallSite(MethodType targetType,
+                          int[] intLabels) throws Throwable {
+            super(targetType, CONSTANT_INIT_HOOK);
+
+            // expensive way to index an array
+            indexes = IntStream.range(0, intLabels.length)
+                               .boxed()
+                               .sorted(Comparator.comparingInt(a -> intLabels[a]))
+                               .mapToInt(Integer::intValue)
+                               .toArray();
+            labels = new int[indexes.length];
+            for (int i=0; i<indexes.length; i++)
+                labels[i] = intLabels[indexes[i]];
+        }
+
+        int doSwitch(int target) {
+            int index = Arrays.binarySearch(labels, target);
+            return (index >= 0) ? indexes[index] : indexes.length;
+        }
+
+        int doSwitch(boolean target) {
+            return doSwitch(target ? 1 : 0);
+        }
+
+        int doSwitch(float target) {
+            return doSwitch(Float.floatToIntBits(target));
+        }
+
+        int doSwitch(short target) {
+            return doSwitch((int) target);
+        }
+
+        int doSwitch(byte target) {
+            return doSwitch((int) target);
+        }
+
+        int doSwitch(char target) {
+            return doSwitch((int) target);
+        }
+
+        int doSwitch(Boolean target) {
+            return (target == null) ? -1 : doSwitch((boolean) target);
+        }
+
+        int doSwitch(Integer target) {
+            return (target == null) ? -1 : doSwitch((int) target);
+        }
+
+        int doSwitch(Float target) {
+            return (target == null) ? -1 : doSwitch((float) target);
+        }
+
+        int doSwitch(Short target) {
+            return (target == null) ? -1 : doSwitch((int) target);
+        }
+
+        int doSwitch(Character target) {
+            return (target == null) ? -1 : doSwitch((int) target);
+        }
+
+        int doSwitch(Byte target) {
+            return (target == null) ? -1 : doSwitch((int) target);
+        }
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on a {@code long} or {@code Long}.
+     * The static arguments are a varargs array of {@code long} labels.
+     *
+     * <p>The results are undefined if the labels array contains duplicates.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @param lookup Represents a lookup context with the accessibility
+     *               privileges of the caller.  When used with {@code invokedynamic},
+     *               this is stacked automatically by the VM.
+     * @param invocationName The invocation name, which is ignored.  When used with
+     *                       {@code invokedynamic}, this is provided by the
+     *                       {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param invocationType The invocation type of the {@code CallSite}.  This
+     *                       method type should have a single parameter which is
+     *                       one of the 32-bit or shorter primitive types, or
+     *                       one of their box types, and return {@code int}.  When
+     *                       used with {@code invokedynamic}, this is provided by
+     *                       the {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param longLabels long values corresponding to the case labels of the
+     *                  {@code switch} statement.
+     * @return the index into {@code longLabels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code longLabels.length} if the target value does
+     *         not match any of the labels.
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if the invocation type is not
+     * {@code (long)int} or {@code (Long)int}
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite longSwitch(MethodHandles.Lookup lookup,
+                                      String invocationName,
+                                      MethodType invocationType,
+                                      long... longLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!LONG_TYPES.contains(invocationType.parameterType(0))))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(longLabels);
+
+        assert LongStream.of(longLabels).distinct().count() == longLabels.length
+                : "switch labels are not distinct: " + Arrays.toString(longLabels);
+
+        return new LongSwitchCallSite(invocationType, longLabels);
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on a {@code double} or {@code Double}.
+     * The static arguments are a varargs array of {@code double} labels.
+     *
+     * <p>The results are undefined if the labels array contains duplicates
+     * according to {@link Double#doubleToLongBits(double)}.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @param lookup Represents a lookup context with the accessibility
+     *               privileges of the caller.  When used with {@code invokedynamic},
+     *               this is stacked automatically by the VM.
+     * @param invocationName The invocation name, which is ignored.  When used with
+     *                       {@code invokedynamic}, this is provided by the
+     *                       {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param invocationType The invocation type of the {@code CallSite}.  This
+     *                       method type should have a single parameter which is
+     *                       one of the 32-bit or shorter primitive types, or
+     *                       one of their box types, and return {@code int}.  When
+     *                       used with {@code invokedynamic}, this is provided by
+     *                       the {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param doubleLabels long values corresponding to the case labels of the
+     *                  {@code switch} statement.
+     * @return the index into {@code doubleLabels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code doubleLabels.length} if the target value does
+     *         not match any of the labels.
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if the invocation type is not
+     * {@code (double)int} or {@code (Double)int}
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite doubleSwitch(MethodHandles.Lookup lookup,
+                                        String invocationName,
+                                        MethodType invocationType,
+                                        double... doubleLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!DOUBLE_TYPES.contains(invocationType.parameterType(0))))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(doubleLabels);
+
+        long[] longLabels = new long[doubleLabels.length];
+        for (int i=0; i<doubleLabels.length; i++)
+            longLabels[i] = Double.doubleToLongBits(doubleLabels[i]);
+
+        assert LongStream.of(longLabels).distinct().count() == longLabels.length
+                : "switch labels are not distinct: " + Arrays.toString(doubleLabels);
+
+        return new LongSwitchCallSite(invocationType, longLabels);
+    }
+
+    static class LongSwitchCallSite extends ConstantCallSite {
+        private final long[] labels;
+        private final int[] indexes;
+
+        LongSwitchCallSite(MethodType targetType,
+                           long[] longLabels) throws Throwable {
+            super(targetType, CONSTANT_INIT_HOOK);
+
+            // expensive way to index an array
+            indexes = IntStream.range(0, longLabels.length)
+                               .boxed()
+                               .sorted(Comparator.comparingLong(a -> longLabels[a]))
+                               .mapToInt(Integer::intValue)
+                               .toArray();
+            labels = new long[indexes.length];
+            for (int i=0; i<indexes.length; i++)
+                labels[i] = longLabels[indexes[i]];
+        }
+
+        int doSwitch(long target) {
+            int index = Arrays.binarySearch(labels, target);
+            return (index >= 0) ? indexes[index] : indexes.length;
+        }
+
+        int doSwitch(double target) {
+            return doSwitch(Double.doubleToLongBits(target));
+        }
+
+        int doSwitch(Long target) {
+            return (target == null) ? -1 : doSwitch((long) target);
+        }
+
+        int doSwitch(Double target) {
+            return (target == null) ? -1 : doSwitch((double) target);
+        }
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on a {@code String} target.  The static
+     * arguments are a varargs array of {@code String} labels.
+     *
+     * <p>The results are undefined if the labels array contains duplicates
+     * according to {@link String#equals(Object)}.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @param lookup Represents a lookup context with the accessibility
+     *               privileges of the caller.  When used with {@code invokedynamic},
+     *               this is stacked automatically by the VM.
+     * @param invocationName The invocation name, which is ignored.  When used with
+     *                       {@code invokedynamic}, this is provided by the
+     *                       {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param invocationType The invocation type of the {@code CallSite}.  This
+     *                       method type should have a single parameter of
+     *                       {@code String}, and return {@code int}.  When
+     *                       used with {@code invokedynamic}, this is provided by
+     *                       the {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param stringLabels non-null string values corresponding to the case
+     *                     labels of the {@code switch} statement.
+     * @return the index into {@code labels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code stringLabels.length} if the target value
+     *         does not match any of the labels.
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if any labels are null, or if the
+     * invocation type is not {@code (String)int}
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite stringSwitch(MethodHandles.Lookup lookup,
+                                        String invocationName,
+                                        MethodType invocationType,
+                                        String... stringLabels) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!invocationType.parameterType(0).equals(String.class)))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(stringLabels);
+        if (Stream.of(stringLabels).anyMatch(Objects::isNull))
+            throw new IllegalArgumentException("null label found");
+
+        assert Stream.of(stringLabels).distinct().count() == stringLabels.length
+                : "switch labels are not distinct: " + Arrays.toString(stringLabels);
+
+        return new StringSwitchCallSite(invocationType, stringLabels);
+    }
+
+    static class StringSwitchCallSite extends ConstantCallSite {
+        private static final Comparator<String> STRING_BY_HASH
+                = Comparator.comparingInt(Objects::hashCode);
+
+        private final String[] sortedByHash;
+        private final int[] indexes;
+        private final boolean collisions;
+
+        StringSwitchCallSite(MethodType targetType,
+                             String[] stringLabels) throws Throwable {
+            super(targetType, CONSTANT_INIT_HOOK);
+
+            // expensive way to index an array
+            indexes = IntStream.range(0, stringLabels.length)
+                               .boxed()
+                               .sorted(Comparator.comparingInt(i -> stringLabels[i].hashCode()))
+                               .mapToInt(Integer::intValue)
+                               .toArray();
+            sortedByHash = new String[indexes.length];
+            for (int i=0; i<indexes.length; i++)
+                sortedByHash[i] = stringLabels[indexes[i]];
+
+            collisions = IntStream.range(0, sortedByHash.length-1)
+                                  .anyMatch(i -> sortedByHash[i].hashCode() == sortedByHash[i + 1].hashCode());
+        }
+
+        int doSwitch(String target) {
+            if (target == null)
+                return -1;
+
+            int index = Arrays.binarySearch(sortedByHash, target, STRING_BY_HASH);
+            if (index < 0)
+                return indexes.length;
+            else if (target.equals(sortedByHash[index])) {
+                return indexes[index];
+            }
+            else if (collisions) {
+                int hash = target.hashCode();
+                while (index > 0 && sortedByHash[index-1].hashCode() == hash)
+                    --index;
+                for (; index < sortedByHash.length && sortedByHash[index].hashCode() == hash; index++)
+                    if (target.equals(sortedByHash[index]))
+                        return indexes[index];
+            }
+
+            return indexes.length;
+        }
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on an {@code Enum} target.  The static
+     * arguments are the enum class, and a varargs arrays of {@code String}
+     * that are the names of the enum constants corresponding to the
+     * {@code case} labels.
+     *
+     * <p>The results are undefined if the names array contains duplicates.
+     *
+     * @implNote
+     *
+     * The implementation only enforces the requirement that the labels array
+     * be duplicate-free if system assertions are enabled.
+     *
+     * @param <E> the enum type
+     * @param lookup Represents a lookup context with the accessibility
+     *               privileges of the caller.  When used with {@code invokedynamic},
+     *               this is stacked automatically by the VM.
+     * @param invocationName The invocation name, which is ignored.  When used with
+     *                       {@code invokedynamic}, this is provided by the
+     *                       {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param invocationType The invocation type of the {@code CallSite}.  This
+     *                       method type should have a single parameter of
+     *                       {@code Enum}, and return {@code int}.  When
+     *                       used with {@code invokedynamic}, this is provided by
+     *                       the {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param enumClass the enum class
+     * @param enumNames names of the enum constants against which the target
+     *                  should be matched
+     * @return the index into {@code labels} of the target value, if the target
+     *         matches any of the labels, {@literal -1} if the target value is
+     *         {@code null}, or {@code stringLabels.length} if the target value
+     *         does not match any of the labels.
+     * @throws IllegalArgumentException if the specified class is not an
+     *                                  enum class, or any label name is null,
+     *                                  or if the invocation type is not
+     *                                  {@code (Enum)int}
+     * @throws NullPointerException if any required argument is null
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static<E extends Enum<E>> CallSite enumSwitch(MethodHandles.Lookup lookup,
+                                                         String invocationName,
+                                                         MethodType invocationType,
+                                                         Class<E> enumClass,
+                                                         String... enumNames) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || (!invocationType.parameterType(0).equals(Enum.class)))
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(enumClass);
+        requireNonNull(enumNames);
+        if (!enumClass.isEnum())
+            throw new IllegalArgumentException("not an enum class");
+        if (Stream.of(enumNames).anyMatch(Objects::isNull))
+            throw new IllegalArgumentException("null label found");
+
+        assert Stream.of(enumNames).distinct().count() == enumNames.length
+                : "switch labels are not distinct: " + Arrays.toString(enumNames);
+
+        return new EnumSwitchCallSite<>(invocationType, enumClass, enumNames);
+    }
+
+    static class EnumSwitchCallSite<E extends Enum<E>> extends ConstantCallSite {
+        private final int[] ordinalMap;
+
+        EnumSwitchCallSite(MethodType targetType,
+                           Class<E> enumClass,
+                           String... enumNames) throws Throwable {
+            super(targetType, CONSTANT_INIT_HOOK);
+
+            ordinalMap = new int[enumClass.getEnumConstants().length];
+            Arrays.fill(ordinalMap, enumNames.length);
+
+            for (int i=0; i<enumNames.length; i++) {
+                try {
+                    ordinalMap[E.valueOf(enumClass, enumNames[i]).ordinal()] = i;
+                }
+                catch (Exception e) {
+                    // allow non-existent labels, but never match them
+                    continue;
+                }
+            }
+        }
+
+        @SuppressWarnings("rawtypes")
+        int doSwitch(Enum target) {
+            return (target == null) ? -1 : ordinalMap[target.ordinal()];
+        }
+    }
+
+    /**
+     * Bootstrap method for linking an {@code invokedynamic} call site that
+     * implements a {@code switch} on a reference-typed target.  The static
+     * arguments are a varargs array of {@code Class} labels.
+     *
+     * @param lookup Represents a lookup context with the accessibility
+     *               privileges of the caller.  When used with {@code invokedynamic},
+     *               this is stacked automatically by the VM.
+     * @param invocationName The invocation name, which is ignored.  When used with
+     *                       {@code invokedynamic}, this is provided by the
+     *                       {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param invocationType The invocation type of the {@code CallSite}.  This
+     *                       method type should have a single parameter of
+     *                       a reference type, and return {@code int}.  When
+     *                       used with {@code invokedynamic}, this is provided by
+     *                       the {@code NameAndType} of the {@code InvokeDynamic}
+     *                       structure and is stacked automatically by the VM.
+     * @param types non-null {@link Class} values
+     * @return the index into {@code labels} of the target value, if the target
+     *         is an instance of any of the types, {@literal -1} if the target
+     *         value is {@code null}, or {@code types.length} if the target value
+     *         is not an instance of any of the types
+     * @throws NullPointerException if any required argument is null
+     * @throws IllegalArgumentException if any labels are null, or if the
+     * invocation type is not {@code (T)int for some reference type {@code T}}
+     * @throws Throwable if there is any error linking the call site
+     */
+    public static CallSite typeSwitch(MethodHandles.Lookup lookup,
+                                      String invocationName,
+                                      MethodType invocationType,
+                                      Class<?>... types) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(int.class))
+            || invocationType.parameterType(0).isPrimitive())
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(types);
+
+        types = types.clone();
+        if (Stream.of(types).anyMatch(Objects::isNull))
+            throw new IllegalArgumentException("null label found");
+
+        assert Stream.of(types).distinct().count() == types.length
+                : "switch labels are not distinct: " + Arrays.toString(types);
+
+        return new TypeSwitchCallSite(invocationType, types);
+    }
+
+    static class TypeSwitchCallSite extends ConstantCallSite {
+        private final Class<?>[] types;
+
+        TypeSwitchCallSite(MethodType targetType,
+                           Class<?>[] types) throws Throwable {
+            super(targetType, TYPE_INIT_HOOK);
+            this.types = types;
+        }
+
+        int doSwitch(Object target) {
+            if (target == null)
+                return -1;
+
+            // Dumbest possible strategy
+            Class<?> targetClass = target.getClass();
+            for (int i = 0; i < types.length; i++) {
+                Class<?> c = types[i];
+                if (c.isAssignableFrom(targetClass))
+                    return i;
+            }
+
+            return types.length;
+        }
+    }
+
+    /**
+     * Result type for pattern switches
+     */
+    public static class PatternSwitchResult {
+        /**
+         * The selected index, -1 if input was null, or length if not matched
+         */
+        public final int index;
+
+        /**
+         * The carrier
+         */
+        public final Object carrier;
+
+        /**
+         * Construct a PatternSwitchResult
+         *
+         * @param index the index
+         * @param carrier the carrier
+         */
+        public PatternSwitchResult(int index, Object carrier) {
+            this.index = index;
+            this.carrier = carrier;
+        }
+    }
+
+    /**
+     * Bootstrap for pattern switches
+     *
+     * @param lookup the lookup (ignored)
+     * @param invocationName the invocation name (ignored)
+     * @param invocationType the invocation type (must return PatternSwitchResult)
+     * @param patterns the patterns
+     * @return the result
+     * @throws Throwable if something went wrong
+     */
+    public static CallSite patternSwitch(MethodHandles.Lookup lookup,
+                                         String invocationName,
+                                         MethodType invocationType,
+                                         Extractor... patterns) throws Throwable {
+        if (invocationType.parameterCount() != 1
+            || (!invocationType.returnType().equals(PatternSwitchResult.class))
+            || invocationType.parameterType(0).isPrimitive())
+            throw new IllegalArgumentException("Illegal invocation type " + invocationType);
+        requireNonNull(patterns);
+
+        patterns = patterns.clone();
+        Class<?> targetType = invocationType.parameterType(0);
+
+        for (int i = 0; i < patterns.length; i++) {
+            Extractor pattern = patterns[i];
+            if (pattern.descriptor().returnType() != targetType)
+                patterns[i] = Extractor.adapt(pattern, targetType);
+        }
+
+        if (Stream.of(patterns).anyMatch(Objects::isNull))
+            throw new IllegalArgumentException("null pattern found");
+
+        return new PatternSwitchCallSite(invocationType, patterns);
+    }
+
+    static class PatternSwitchCallSite extends ConstantCallSite {
+        private final Extractor[] patterns;
+
+        PatternSwitchCallSite(MethodType targetType,
+                              Extractor[] patterns) throws Throwable {
+            super(targetType, PATTERN_INIT_HOOK);
+            this.patterns = patterns;
+        }
+
+        PatternSwitchResult doSwitch(Object target) throws Throwable {
+            if (target == null)
+                return new PatternSwitchResult(-1, null);
+
+            // Dumbest possible strategy
+            for (int i = 0; i < patterns.length; i++) {
+                Extractor e = patterns[i];
+                Object o = e.tryMatch().invoke(target);
+                if (o != null)
+                    return new PatternSwitchResult(i, o);
+            }
+
+            return new PatternSwitchResult(patterns.length, null);
+
+        }
+    }
+}
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java	Fri Oct 26 19:05:30 2018 +0200
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java	Tue Oct 30 18:08:59 2018 +0100
@@ -3744,10 +3744,6 @@
 
         Map<Name, JCVariableDecl> optHeaderFields = headerFields(mods);
 
-        if (optHeaderFields.isEmpty()) {
-            log.error(token.pos, Errors.RecordMustDeclareAtLeastOneField);
-        }
-
         List<JCExpression> implementing = List.nil();
         if (token.kind == IMPLEMENTS) {
             nextToken();
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties	Fri Oct 26 19:05:30 2018 +0200
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties	Tue Oct 30 18:08:59 2018 +0100
@@ -3361,9 +3361,6 @@
 compiler.err.record.cant.be.abstract=\
     records cannot be abstract
 
-compiler.err.record.must.declare.at.least.one.field=\
-    records must declare at least one field
-
 compiler.err.record.cant.declare.duplicate.fields=\
     records cannot declare fields with the same name
 
--- a/test/jdk/java/lang/compiler/ExtractorTest.java	Fri Oct 26 19:05:30 2018 +0200
+++ b/test/jdk/java/lang/compiler/ExtractorTest.java	Tue Oct 30 18:08:59 2018 +0100
@@ -27,6 +27,8 @@
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 import org.testng.annotations.Test;
@@ -48,10 +50,10 @@
 
     private enum MatchKind { CARRIER, SELF, FAIL, MATCH }
 
-    private void assertMatches(MatchKind kind, Extractor e, Object target, Object... args) throws Throwable {
+    private void assertMatch(MatchKind kind, Extractor e, Object target, Object... args) throws Throwable {
         int count = e.descriptor().parameterCount();
         Object[] bindings = new Object[count];
-        Object carrier = e.tryMatch().invoke(target);
+        Object carrier = Extractor.adapt(e, Object.class).tryMatch().invoke(target);
         if (carrier != null) {
             for (int i = 0; i < count; i++)
                 bindings[i] = e.component(i).invoke(carrier);
@@ -60,7 +62,8 @@
         if (kind == MatchKind.FAIL)
             assertNull(carrier);
         else {
-            assertNotNull(carrier);
+            if (target != null)
+                assertNotNull(carrier);
             assertEquals(bindings.length, args.length);
             for (int i = 0; i < args.length; i++)
                 assertEquals(bindings[i], args[i]);
@@ -137,64 +140,129 @@
         }
     }
 
+    private static class TestClass2 {
+        static MethodHandle MH_X;
+        static MethodType TYPE = MethodType.methodType(TestClass2.class, Object.class);
+        static {
+            try {
+                MH_X = MethodHandles.lookup().findGetter(TestClass2.class, "x", Object.class);
+            }
+            catch (ReflectiveOperationException e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+
+        Object x;
+
+        public TestClass2(Object x) {
+            this.x = x;
+        }
+    }
+
     private static final MethodHandle[] COMPONENTS = {TestClass.MH_S, TestClass.MH_I, TestClass.MH_L, TestClass.MH_B };
 
     public void testTotal() throws Throwable {
         Extractor e = Extractor.ofTotal(TestClass.class, COMPONENTS);
-        assertMatches(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5),
-                      "foo", 3, 4L, (byte) 5);
-        assertMatches(MatchKind.CARRIER, e, new TestClass(null, 0, 0L, (byte) 0),
-                      null, 0, 0L, (byte) 0);
+        assertMatch(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5),
+                    "foo", 3, 4L, (byte) 5);
+        assertMatch(MatchKind.CARRIER, e, new TestClass(null, 0, 0L, (byte) 0),
+                    null, 0, 0L, (byte) 0);
     }
 
     public void testSelfTotal() throws Throwable {
         Extractor e = Extractor.ofSelfTotal(TestClass.class, COMPONENTS);
-        assertMatches(MatchKind.SELF, e, new TestClass("foo", 3, 4L, (byte) 5),
-                      "foo", 3, 4L, (byte) 5);
-        assertMatches(MatchKind.SELF, e, new TestClass(null, 0, 0L, (byte) 0),
-                      null, 0, 0L, (byte) 0);
+        assertMatch(MatchKind.SELF, e, new TestClass("foo", 3, 4L, (byte) 5),
+                    "foo", 3, 4L, (byte) 5);
+        assertMatch(MatchKind.SELF, e, new TestClass(null, 0, 0L, (byte) 0),
+                    null, 0, 0L, (byte) 0);
     }
 
     public void testPartial() throws Throwable {
-        Extractor e = Extractor.ofPartial(TestClass.MH_PRED, COMPONENTS);
-        assertMatches(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5),
-                      "foo", 3, 4L, (byte) 5);
-        assertMatches(MatchKind.FAIL, e, new TestClass("foo", 2, 4L, (byte) 5));
-        assertMatches(MatchKind.FAIL, e, new TestClass(null, 0, 0L, (byte) 0));
+        Extractor e = Extractor.ofPartial(TestClass.class, TestClass.MH_PRED, COMPONENTS);
+        assertMatch(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5),
+                    "foo", 3, 4L, (byte) 5);
+        assertMatch(MatchKind.FAIL, e, new TestClass("foo", 2, 4L, (byte) 5));
+        assertMatch(MatchKind.FAIL, e, new TestClass(null, 0, 0L, (byte) 0));
     }
 
     public void testSelfPartial() throws Throwable {
-        Extractor e = Extractor.ofSelfPartial(TestClass.MH_PRED, COMPONENTS);
-        assertMatches(MatchKind.SELF, e, new TestClass("foo", 3, 4L, (byte) 5),
-                      "foo", 3, 4L, (byte) 5);
-        assertMatches(MatchKind.FAIL, e, new TestClass("foo", 2, 4L, (byte) 5));
-        assertMatches(MatchKind.FAIL, e, new TestClass(null, 0, 0L, (byte) 0));
+        Extractor e = Extractor.ofSelfPartial(TestClass.class, TestClass.MH_PRED, COMPONENTS);
+        assertMatch(MatchKind.SELF, e, new TestClass("foo", 3, 4L, (byte) 5),
+                    "foo", 3, 4L, (byte) 5);
+        assertMatch(MatchKind.FAIL, e, new TestClass("foo", 2, 4L, (byte) 5));
+        assertMatch(MatchKind.FAIL, e, new TestClass(null, 0, 0L, (byte) 0));
     }
 
     public void testDigest() throws Throwable {
         Extractor e = Extractor.of(TestClass.TYPE, TestClass.DIGESTER);
-        assertMatches(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5),
-                      "foo", 3, 4L, (byte) 5);
-        assertMatches(MatchKind.CARRIER, e, new TestClass("foo", 2, 4L, (byte) 5),
-                      "foo", 2, 4L, (byte) 5);
-        assertMatches(MatchKind.CARRIER, e, new TestClass(null, 0, 0L, (byte) 0),
-                      null, 0, 0L, (byte) 0);
+        assertMatch(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5),
+                    "foo", 3, 4L, (byte) 5);
+        assertMatch(MatchKind.CARRIER, e, new TestClass("foo", 2, 4L, (byte) 5),
+                    "foo", 2, 4L, (byte) 5);
+        assertMatch(MatchKind.CARRIER, e, new TestClass(null, 0, 0L, (byte) 0),
+                    null, 0, 0L, (byte) 0);
     }
 
     public void testDigestPartial() throws Throwable {
-        Extractor e = Extractor.ofPartial(TestClass.TYPE, TestClass.DIGESTER_PARTIAL);
-        assertMatches(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5),
-                      "foo", 3, 4L, (byte) 5);
-        assertMatches(MatchKind.FAIL, e, new TestClass("foo", 2, 4L, (byte) 5));
+        Extractor e = Extractor.of(TestClass.TYPE, TestClass.DIGESTER_PARTIAL);
+        assertMatch(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5),
+                    "foo", 3, 4L, (byte) 5);
+        assertMatch(MatchKind.FAIL, e, new TestClass("foo", 2, 4L, (byte) 5));
     }
 
     public void testCompose() throws Throwable {
         Extractor e = Extractor.ofTotal(TestClass.class, COMPONENTS);
-        MethodHandle mh = e.compose(TestClass.CONSTRUCTOR);
+        MethodHandle mh = e.compose(TestClass.CONSTRUCTOR, null);
         TestClass target = new TestClass("foo", 3, 4L, (byte) 5);
         Object o = mh.invoke(target);
         assertTrue(o instanceof TestClass);
         assertNotSame(target, o);
         assertEquals(target, o);
     }
+
+    public void testDropBindings() throws Throwable {
+        Extractor e = Extractor.ofTotal(TestClass.class, COMPONENTS);
+        assertMatch(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5),
+                    "foo", 3, 4L, (byte) 5);
+        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(e, 0), new TestClass("foo", 3, 4L, (byte) 5),
+                    3, 4L, (byte) 5);
+        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(e, 0, 0), new TestClass("foo", 3, 4L, (byte) 5),
+                    3, 4L, (byte) 5);
+        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(e, 3), new TestClass("foo", 3, 4L, (byte) 5),
+                    "foo", 3, 4L);
+        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(e, 0, 1, 2, 3), new TestClass("foo", 3, 4L, (byte) 5));
+    }
+
+    public void testAsType() throws Throwable {
+        assertMatch(MatchKind.SELF, Extractor.ofType(String.class), "Foo", "Foo");
+        assertMatch(MatchKind.FAIL, Extractor.ofType(String.class), 3);
+        assertMatch(MatchKind.FAIL, Extractor.ofType(String.class), null);
+
+        assertMatch(MatchKind.SELF, Extractor.ofType(List.class), List.of(3), List.of(3));
+        assertMatch(MatchKind.SELF, Extractor.ofType(List.class), List.of(), List.of());
+        assertMatch(MatchKind.SELF, Extractor.ofType(List.class), new ArrayList<>(), List.of());
+    }
+
+    public void testAsNullableType() throws Throwable {
+        assertMatch(MatchKind.SELF, Extractor.ofTypeNullable(String.class), "Foo", "Foo");
+        assertMatch(MatchKind.FAIL, Extractor.ofTypeNullable(String.class), 3);
+        assertMatch(MatchKind.MATCH, Extractor.ofTypeNullable(String.class), null, (Object) null);
+    }
+
+    public void testNested() throws Throwable {
+        Extractor TC2 = Extractor.ofTotal(TestClass2.class, TestClass2.MH_X);
+        Extractor STRING = Extractor.ofType(String.class);
+        Extractor OBJECT  = Extractor.ofType(Object.class);
+
+        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(Extractor.ofNested(TC2, STRING), 0), new TestClass2("foo"),
+                    "foo");
+        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(Extractor.ofNested(TC2, OBJECT), 0), new TestClass2("foo"),
+                    "foo");
+        assertMatch(MatchKind.FAIL, Extractor.dropBindings(Extractor.ofNested(TC2, STRING), 0), new TestClass2(List.of(3)),
+                    "foo");
+
+        assertMatch(MatchKind.CARRIER, Extractor.dropBindings(Extractor.ofNested(TC2, Extractor.ofNested(TC2, STRING)), 0, 1), new TestClass2(new TestClass2("foo")),
+                    "foo");
+
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/compiler/RecordTest.java	Tue Oct 30 18:08:59 2018 +0100
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.compiler.Extractor;
+import java.lang.compiler.SwitchBootstraps;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+import org.testng.annotations.Test;
+
+import static java.lang.invoke.MethodHandleInfo.REF_newInvokeSpecial;
+import static org.testng.Assert.assertEquals;
+
+@Test
+/**
+ * @test
+ * @run testng RecordTest
+ * @summary End-to-end test for record patterns
+ */
+public class RecordTest {
+    record R(int a, String b, double c);
+    record RR(R r1, R R2);
+
+    private Extractor recordExtractor(Class<?> recordClass,
+                                      Class<?>... paramTypes) throws Throwable {
+        return Extractor.findExtractor(MethodHandles.lookup(), "_", Extractor.class,
+                                       recordClass, MethodType.methodType(void.class, paramTypes), recordClass.getName(), REF_newInvokeSpecial);
+    }
+
+    public void testRecord() throws Throwable {
+        R r = new R(1, "two", 3.14d);
+        Extractor rExtract = recordExtractor(R.class, int.class, String.class, double.class);
+
+        MethodHandle tryExtract = Extractor.extractorTryMatch(MethodHandles.lookup(), "_", MethodHandle.class, rExtract);
+        MethodHandle a = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 0);
+        MethodHandle b = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 1);
+        MethodHandle c = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 2);
+
+        Object o = tryExtract.invoke(r);
+        assertEquals(1, a.invoke(o));
+        assertEquals("two", b.invoke(o));
+        assertEquals(3.14d, c.invoke(o));
+    }
+
+    public void testFakeNested() throws Throwable {
+        R r1 = new R(1, "two", 3.14d);
+        R r2 = new R(2, "four", 6.0d);
+        RR rr = new RR(r1, r2);
+
+        Extractor rExtract = recordExtractor(R.class, int.class, String.class, double.class);
+        Extractor rrExtract = recordExtractor(RR.class, R.class, R.class);
+
+        MethodHandle tryExtractR = Extractor.extractorTryMatch(MethodHandles.lookup(), "_", MethodHandle.class, rExtract);
+        MethodHandle ra = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 0);
+        MethodHandle rb = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 1);
+        MethodHandle rc = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 2);
+
+        MethodHandle tryExtractRr = Extractor.extractorTryMatch(MethodHandles.lookup(), "_", MethodHandle.class, rrExtract);
+        MethodHandle r1c = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rrExtract, 0);
+        MethodHandle r2c = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rrExtract, 1);
+
+        Object o = tryExtractRr.invoke(rr);
+        R o1 = (R) r1c.invoke(o);
+        R o2 = (R) r2c.invoke(o);
+
+        assertEquals(1, ra.invoke(o1));
+        assertEquals("two", rb.invoke(o1));
+        assertEquals(3.14d, rc.invoke(o1));
+
+        assertEquals(2, ra.invoke(o2));
+        assertEquals("four", rb.invoke(o2));
+        assertEquals(6.0d, rc.invoke(o2));
+    }
+
+    public void testNested() throws Throwable {
+        Extractor rExtract = recordExtractor(R.class, int.class, String.class, double.class);
+        Extractor rrExtract = recordExtractor(RR.class, R.class, R.class);
+
+        Extractor e = Extractor.ofNested(rrExtract, rExtract);
+
+        R r1 = new R(1, "two", 3.14d);
+        R r2 = new R(2, "four", 6.0d);
+        RR rr = new RR(r1, r2);
+
+        Object o = e.tryMatch().invoke(rr);
+
+        assertEquals(e.component(0).invoke(o), new R(1, "two", 3.14d));
+        assertEquals(e.component(1).invoke(o), new R(2, "four", 6.0d));
+        assertEquals(e.component(2).invoke(o), 1);
+        assertEquals(e.component(3).invoke(o), "two");
+        assertEquals(e.component(4).invoke(o), 3.14d);
+
+        Extractor ee = Extractor.ofNested(rrExtract, rExtract, rExtract);
+        o = ee.tryMatch().invoke(rr);
+
+        assertEquals(ee.component(0).invoke(o), new R(1, "two", 3.14d));
+        assertEquals(ee.component(1).invoke(o), new R(2, "four", 6.0d));
+        assertEquals(ee.component(2).invoke(o), 1);
+        assertEquals(ee.component(3).invoke(o), "two");
+        assertEquals(ee.component(4).invoke(o), 3.14d);
+        assertEquals(ee.component(5).invoke(o), 2);
+        assertEquals(ee.component(6).invoke(o), "four");
+        assertEquals(ee.component(7).invoke(o), 6.0d);
+    }
+
+    record A(int a);
+    record B(int a, int b);
+    record S(String s);
+    record T(String s, String t);
+    record U();
+
+    private Object component(Extractor e, int num, Object carrier) throws Throwable {
+        return Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class,
+                                            e, num).invoke(carrier);
+    }
+
+    public void testRecordSwitch() throws Throwable {
+        Extractor[] extractors = {
+                recordExtractor(A.class, int.class),
+                recordExtractor(B.class, int.class, int.class),
+                recordExtractor(S.class, String.class),
+                recordExtractor(T.class, String.class, String.class),
+                recordExtractor(U.class)
+        };
+
+        Object[] exemplars = {
+                new A(1),
+                new B(2, 3),
+                new S("four"),
+                new T("five", "six"),
+                new U()
+        };
+
+        CallSite cs = SwitchBootstraps.patternSwitch(MethodHandles.lookup(), "_",
+                                                     MethodType.methodType(SwitchBootstraps.PatternSwitchResult.class, Object.class),
+                                                     extractors);
+        MethodHandle mh = cs.dynamicInvoker();
+        for (int i = 0; i < exemplars.length; i++) {
+            Object exemplar = exemplars[i];
+            SwitchBootstraps.PatternSwitchResult result = (SwitchBootstraps.PatternSwitchResult) mh.invoke(exemplar);
+            assertEquals(result.index, i);
+            switch (result.index) {
+                case 0:
+                    assertEquals(component(extractors[i], 0, result.carrier), 1);
+                    break;
+                case 1:
+                    assertEquals(component(extractors[i], 0, result.carrier), 2);
+                    assertEquals(component(extractors[i], 1, result.carrier), 3);
+                    break;
+                case 2:
+                    assertEquals(component(extractors[i], 0, result.carrier), "four");
+                    break;
+                case 3:
+                    assertEquals(component(extractors[i], 0, result.carrier), "five");
+                    assertEquals(component(extractors[i], 1, result.carrier), "six");
+                    break;
+            };
+
+            result = (SwitchBootstraps.PatternSwitchResult) mh.invoke(null);
+            assertEquals(result.index, -1);
+
+            result = (SwitchBootstraps.PatternSwitchResult) mh.invoke("foo");
+            assertEquals(result.index, 5);
+        }
+    }
+
+    record Box(Object o1);
+
+    public void testNestedRecord() throws Throwable {
+        Extractor boxA = Extractor.ofNested(recordExtractor(Box.class, Object.class),
+                                            recordExtractor(A.class, int.class));
+        Extractor boxB = Extractor.ofNested(recordExtractor(Box.class, Object.class),
+                                            recordExtractor(B.class, int.class, int.class));
+
+        CallSite cs = SwitchBootstraps.patternSwitch(MethodHandles.lookup(), "_",
+                                                     MethodType.methodType(SwitchBootstraps.PatternSwitchResult.class, Object.class),
+                                                     boxA, boxB);
+        MethodHandle mh = cs.dynamicInvoker();
+
+        assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box(new A(1)))).index, 0);
+        assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box(new B(2, 3)))).index, 1);
+        assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box("foo"))).index, 2);
+        assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box(null))).index, 2);
+        assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke("foo")).index, 2);
+        assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(null)).index, -1);
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/compiler/SwitchBootstrapsTest.java	Tue Oct 30 18:08:59 2018 +0100
@@ -0,0 +1,466 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.Serializable;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.compiler.SwitchBootstraps;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import jdk.test.lib.RandomFactory;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+/**
+ * @test
+ * @key randomness
+ * @library /test/lib
+ * @build jdk.test.lib.RandomFactory
+ * @run testng SwitchBootstrapsTest
+ */
+@Test
+public class SwitchBootstrapsTest {
+    private final static Set<Class<?>> BOOLEAN_TYPES = Set.of(boolean.class, Boolean.class);
+    private final static Set<Class<?>> ALL_INT_TYPES = Set.of(int.class, short.class, byte.class, char.class,
+                                                              Integer.class, Short.class, Byte.class, Character.class);
+    private final static Set<Class<?>> SIGNED_NON_BYTE_TYPES = Set.of(int.class, Integer.class, short.class, Short.class);
+    private final static Set<Class<?>> CHAR_TYPES = Set.of(char.class, Character.class);
+    private final static Set<Class<?>> BYTE_TYPES = Set.of(byte.class, Byte.class);
+    private final static Set<Class<?>> SIGNED_TYPES
+            = Set.of(int.class, short.class, byte.class,
+                     Integer.class, Short.class, Byte.class);
+
+    public static final MethodHandle BSM_BOOLEAN_SWITCH;
+    public static final MethodHandle BSM_INT_SWITCH;
+    public static final MethodHandle BSM_LONG_SWITCH;
+    public static final MethodHandle BSM_FLOAT_SWITCH;
+    public static final MethodHandle BSM_DOUBLE_SWITCH;
+    public static final MethodHandle BSM_STRING_SWITCH;
+    public static final MethodHandle BSM_ENUM_SWITCH;
+    public static final MethodHandle BSM_TYPE_SWITCH;
+
+    private final static Random random = RandomFactory.getRandom();
+
+    static {
+        try {
+            BSM_BOOLEAN_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "booleanSwitch",
+                                                                   MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, boolean[].class));
+            BSM_INT_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "intSwitch",
+                                                               MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, int[].class));
+            BSM_LONG_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "longSwitch",
+                                                                MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, long[].class));
+            BSM_FLOAT_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "floatSwitch",
+                                                                 MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, float[].class));
+            BSM_DOUBLE_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "doubleSwitch",
+                                                                  MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, double[].class));
+            BSM_STRING_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "stringSwitch",
+                                                                  MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String[].class));
+            BSM_ENUM_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "enumSwitch",
+                                                                MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Class.class, String[].class));
+            BSM_TYPE_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "typeSwitch",
+                                                                MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Class[].class));
+        }
+        catch (NoSuchMethodException | IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private MethodType switchType(Class<?> target) {
+        return MethodType.methodType(int.class, target);
+    }
+
+    private Object box(Class<?> clazz, int i) {
+        if (clazz == Integer.class)
+            return i;
+        else if (clazz == Short.class)
+            return (short) i;
+        else if (clazz == Character.class)
+            return (char) i;
+        else if (clazz == Byte.class)
+            return (byte) i;
+        else
+            throw new IllegalArgumentException(clazz.toString());
+    }
+
+    private void testBoolean(boolean... labels) throws Throwable {
+        Map<Class<?>, MethodHandle> mhs
+                = Map.of(boolean.class, ((CallSite) BSM_BOOLEAN_SWITCH.invoke(MethodHandles.lookup(), "", switchType(boolean.class), labels)).dynamicInvoker(),
+                         Boolean.class, ((CallSite) BSM_BOOLEAN_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Boolean.class), labels)).dynamicInvoker());
+
+        List<Boolean> labelList = new ArrayList<>();
+        for (boolean label : labels)
+            labelList.add(label);
+
+        for (int i=0; i<labels.length; i++) {
+            assertEquals(i, (int) mhs.get(boolean.class).invokeExact((boolean) labels[i]));
+            assertEquals(i, (int) mhs.get(Boolean.class).invokeExact((Boolean) labels[i]));
+        }
+
+        boolean[] booleans = { false, true };
+        for (boolean b : booleans) {
+            if (!labelList.contains(b)) {
+                assertEquals(labels.length, mhs.get(boolean.class).invoke((boolean) b));
+                assertEquals(labels.length, mhs.get(Boolean.class).invoke((boolean) b));
+            }
+        }
+
+        assertEquals(-1, (int) mhs.get(Boolean.class).invoke(null));
+    }
+
+    private void testInt(Set<Class<?>> targetTypes, int... labels) throws Throwable {
+        Map<Class<?>, MethodHandle> mhs
+                = Map.of(char.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(char.class), labels)).dynamicInvoker(),
+                         byte.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(byte.class), labels)).dynamicInvoker(),
+                         short.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(short.class), labels)).dynamicInvoker(),
+                         int.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(int.class), labels)).dynamicInvoker(),
+                         Character.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Character.class), labels)).dynamicInvoker(),
+                         Byte.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Byte.class), labels)).dynamicInvoker(),
+                         Short.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Short.class), labels)).dynamicInvoker(),
+                         Integer.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Integer.class), labels)).dynamicInvoker());
+
+        List<Integer> labelList = IntStream.of(labels)
+                                           .boxed()
+                                           .collect(Collectors.toList());
+
+        for (int i=0; i<labels.length; i++) {
+            // test with invokeExact
+            if (targetTypes.contains(char.class))
+                assertEquals(i, (int) mhs.get(char.class).invokeExact((char) labels[i]));
+            if (targetTypes.contains(byte.class))
+                assertEquals(i, (int) mhs.get(byte.class).invokeExact((byte) labels[i]));
+            if (targetTypes.contains(short.class))
+                assertEquals(i, (int) mhs.get(short.class).invokeExact((short) labels[i]));
+            if (targetTypes.contains(int.class))
+                assertEquals(i, (int) mhs.get(int.class).invokeExact(labels[i]));
+            if (targetTypes.contains(Integer.class))
+                assertEquals(i, (int) mhs.get(Integer.class).invokeExact((Integer) labels[i]));
+            if (targetTypes.contains(Short.class))
+                assertEquals(i, (int) mhs.get(Short.class).invokeExact((Short) (short) labels[i]));
+            if (targetTypes.contains(Byte.class))
+                assertEquals(i, (int) mhs.get(Byte.class).invokeExact((Byte) (byte) labels[i]));
+            if (targetTypes.contains(Character.class))
+                assertEquals(i, (int) mhs.get(Character.class).invokeExact((Character) (char) labels[i]));
+
+            // and with invoke
+            assertEquals(i, (int) mhs.get(int.class).invoke(labels[i]));
+            assertEquals(i, (int) mhs.get(Integer.class).invoke(labels[i]));
+        }
+
+        for (int i=-1000; i<1000; i++) {
+            if (!labelList.contains(i)) {
+                assertEquals(labels.length, mhs.get(short.class).invoke((short) i));
+                assertEquals(labels.length, mhs.get(Short.class).invoke((short) i));
+                assertEquals(labels.length, mhs.get(int.class).invoke(i));
+                assertEquals(labels.length, mhs.get(Integer.class).invoke(i));
+                if (i >= 0) {
+                    assertEquals(labels.length, mhs.get(char.class).invoke((char)i));
+                    assertEquals(labels.length, mhs.get(Character.class).invoke((char)i));
+                }
+                if (i >= -128 && i <= 127) {
+                    assertEquals(labels.length, mhs.get(byte.class).invoke((byte)i));
+                    assertEquals(labels.length, mhs.get(Byte.class).invoke((byte)i));
+                }
+            }
+        }
+
+        assertEquals(-1, (int) mhs.get(Integer.class).invoke(null));
+        assertEquals(-1, (int) mhs.get(Short.class).invoke(null));
+        assertEquals(-1, (int) mhs.get(Byte.class).invoke(null));
+        assertEquals(-1, (int) mhs.get(Character.class).invoke(null));
+    }
+
+    private void testFloat(float... labels) throws Throwable {
+        Map<Class<?>, MethodHandle> mhs
+                = Map.of(float.class, ((CallSite) BSM_FLOAT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(float.class), labels)).dynamicInvoker(),
+                         Float.class, ((CallSite) BSM_FLOAT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Float.class), labels)).dynamicInvoker());
+
+        List<Float> labelList = new ArrayList<>();
+        for (float label : labels)
+            labelList.add(label);
+
+        for (int i=0; i<labels.length; i++) {
+            assertEquals(i, (int) mhs.get(float.class).invokeExact((float) labels[i]));
+            assertEquals(i, (int) mhs.get(Float.class).invokeExact((Float) labels[i]));
+        }
+
+        float[] someFloats = { 1.0f, Float.MIN_VALUE, 3.14f };
+        for (float f : someFloats) {
+            if (!labelList.contains(f)) {
+                assertEquals(labels.length, mhs.get(float.class).invoke((float) f));
+                assertEquals(labels.length, mhs.get(Float.class).invoke((float) f));
+            }
+        }
+
+        assertEquals(-1, (int) mhs.get(Float.class).invoke(null));
+    }
+
+    private void testDouble(double... labels) throws Throwable {
+        Map<Class<?>, MethodHandle> mhs
+                = Map.of(double.class, ((CallSite) BSM_DOUBLE_SWITCH.invoke(MethodHandles.lookup(), "", switchType(double.class), labels)).dynamicInvoker(),
+                         Double.class, ((CallSite) BSM_DOUBLE_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Double.class), labels)).dynamicInvoker());
+
+        var labelList = new ArrayList<Double>();
+        for (double label : labels)
+            labelList.add(label);
+
+        for (int i=0; i<labels.length; i++) {
+            assertEquals(i, (int) mhs.get(double.class).invokeExact((double) labels[i]));
+            assertEquals(i, (int) mhs.get(Double.class).invokeExact((Double) labels[i]));
+        }
+
+        double[] someDoubles = { 1.0, Double.MIN_VALUE, 3.14 };
+        for (double f : someDoubles) {
+            if (!labelList.contains(f)) {
+                assertEquals(labels.length, mhs.get(double.class).invoke((double) f));
+                assertEquals(labels.length, mhs.get(Double.class).invoke((double) f));
+            }
+        }
+
+        assertEquals(-1, (int) mhs.get(Double.class).invoke(null));
+    }
+
+    private void testLong(long... labels) throws Throwable {
+        Map<Class<?>, MethodHandle> mhs
+                = Map.of(long.class, ((CallSite) BSM_LONG_SWITCH.invoke(MethodHandles.lookup(), "", switchType(long.class), labels)).dynamicInvoker(),
+                         Long.class, ((CallSite) BSM_LONG_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Long.class), labels)).dynamicInvoker());
+
+        List<Long> labelList = new ArrayList<>();
+        for (long label : labels)
+            labelList.add(label);
+
+        for (int i=0; i<labels.length; i++) {
+            assertEquals(i, (int) mhs.get(long.class).invokeExact((long) labels[i]));
+            assertEquals(i, (int) mhs.get(Long.class).invokeExact((Long) labels[i]));
+        }
+
+        long[] someLongs = { 1L, Long.MIN_VALUE, Long.MAX_VALUE };
+        for (long l : someLongs) {
+            if (!labelList.contains(l)) {
+                assertEquals(labels.length, mhs.get(long.class).invoke((long) l));
+                assertEquals(labels.length, mhs.get(Long.class).invoke((long) l));
+            }
+        }
+
+        assertEquals(-1, (int) mhs.get(Long.class).invoke(null));
+    }
+
+    private void testString(String... targets) throws Throwable {
+        MethodHandle indy = ((CallSite) BSM_STRING_SWITCH.invoke(MethodHandles.lookup(), "", switchType(String.class), targets)).dynamicInvoker();
+        List<String> targetList = Stream.of(targets)
+                                        .collect(Collectors.toList());
+
+        for (int i=0; i<targets.length; i++) {
+            String s = targets[i];
+            int result = (int) indy.invoke(s);
+            assertEquals((s == null) ? -1 : i, result);
+        }
+
+        for (String s : List.of("", "A", "AA", "AAA", "AAAA")) {
+            if (!targetList.contains(s)) {
+                assertEquals(targets.length, indy.invoke(s));
+            }
+        }
+        assertEquals(-1, (int) indy.invoke(null));
+    }
+
+    private<E extends Enum<E>> void testEnum(Class<E> enumClass, String... targets) throws Throwable {
+        MethodHandle indy = ((CallSite) BSM_ENUM_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Enum.class), enumClass, targets)).dynamicInvoker();
+        List<E> targetList = Stream.of(targets)
+                                   .map(s -> Enum.valueOf(enumClass, s))
+                                   .collect(Collectors.toList());
+
+        for (int i=0; i<targets.length; i++) {
+            String s = targets[i];
+            E e = Enum.valueOf(enumClass, s);
+            int result = (int) indy.invoke(e);
+            assertEquals((s == null) ? -1 : i, result);
+        }
+
+        for (E e : enumClass.getEnumConstants()) {
+            int index = (int) indy.invoke(e);
+            if (targetList.contains(e))
+                assertEquals(e.name(), targets[index]);
+            else
+                assertEquals(targets.length, index);
+        }
+
+        assertEquals(-1, (int) indy.invoke(null));
+    }
+
+    public void testBoolean() throws Throwable {
+        testBoolean(new boolean[0]);
+        testBoolean(false);
+        testBoolean(true);
+        testBoolean(false, true);
+    }
+
+    public void testInt() throws Throwable {
+        testInt(ALL_INT_TYPES, 8, 6, 7, 5, 3, 0, 9);
+        testInt(ALL_INT_TYPES, 1, 2, 4, 8, 16);
+        testInt(ALL_INT_TYPES, 5, 4, 3, 2, 1, 0);
+        testInt(SIGNED_TYPES, 5, 4, 3, 2, 1, 0, -1);
+        testInt(SIGNED_TYPES, -1);
+        testInt(ALL_INT_TYPES, new int[] { });
+
+        for (int i=0; i<5; i++) {
+            int len = 50 + random.nextInt(800);
+            int[] arr = IntStream.generate(() -> random.nextInt(10000) - 5000)
+                                 .distinct()
+                                 .limit(len)
+                                 .toArray();
+            testInt(SIGNED_NON_BYTE_TYPES, arr);
+
+            arr = IntStream.generate(() -> random.nextInt(10000))
+                    .distinct()
+                    .limit(len)
+                    .toArray();
+            testInt(CHAR_TYPES, arr);
+
+            arr = IntStream.generate(() -> random.nextInt(127) - 64)
+                           .distinct()
+                           .limit(120)
+                           .toArray();
+            testInt(BYTE_TYPES, arr);
+        }
+    }
+
+    public void testLong() throws Throwable {
+        testLong(1L, Long.MIN_VALUE, Long.MAX_VALUE);
+        testLong(8L, 2L, 5L, 4L, 3L, 9L, 1L);
+        testLong(new long[] { });
+
+        // @@@ Random tests
+        // @@@ More tests for weird values
+    }
+
+    public void testFloat() throws Throwable {
+        testFloat(0.0f, -0.0f, -1.0f, 1.0f, 3.14f, Float.MIN_VALUE, Float.MAX_VALUE, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY);
+        testFloat(new float[] { });
+        testFloat(0.0f, 1.0f, 3.14f, Float.NaN);
+
+        // @@@ Random tests
+        // @@@ More tests for weird values
+    }
+
+    public void testDouble() throws Throwable {
+        testDouble(0.0, -0.0, -1.0, 1.0, 3.14, Double.MIN_VALUE, Double.MAX_VALUE,
+                   Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
+        testDouble(new double[] { });
+        testDouble(0.0f, 1.0f, 3.14f, Double.NaN);
+
+        // @@@ Random tests
+        // @@@ More tests for weird values
+    }
+
+    public void testString() throws Throwable {
+        testString("a", "b", "c");
+        testString("c", "b", "a");
+        testString("cow", "pig", "horse", "orangutan", "elephant", "dog", "frog", "ant");
+        testString("a", "b", "c", "A", "B", "C");
+        testString("C", "B", "A", "c", "b", "a");
+
+        // Tests with hash collisions; Ba/CB, Ca/DB
+        testString("Ba", "CB");
+        testString("Ba", "CB", "Ca", "DB");
+
+        // Test with null
+        try {
+            testString("a", null, "c");
+            fail("expected failure");
+        }
+        catch (IllegalArgumentException t) {
+            // success
+        }
+    }
+
+    enum E1 { A, B }
+    enum E2 { C, D, E, F, G, H }
+
+    public void testEnum() throws Throwable {
+        testEnum(E1.class);
+        testEnum(E1.class, "A");
+        testEnum(E1.class, "A", "B");
+        testEnum(E1.class, "B", "A");
+        testEnum(E2.class, "C");
+        testEnum(E2.class, "C", "D", "E", "F", "H");
+        testEnum(E2.class, "H", "C", "G", "D", "F", "E");
+
+        // Bad enum class
+        try {
+            testEnum((Class) String.class, "A");
+            fail("expected failure");
+        }
+        catch (IllegalArgumentException t) {
+            // success
+        }
+
+        // Bad enum constants
+        try {
+            testEnum(E1.class, "B", "A", "FILE_NOT_FOUND");
+            fail("expected failure");
+        }
+        catch (IllegalArgumentException t) {
+            // success
+        }
+
+        // Null enum constant
+        try {
+            testEnum(E1.class, "A", null, "B");
+            fail("expected failure");
+        }
+        catch (IllegalArgumentException t) {
+            // success
+        }
+    }
+
+    private void testType(Object target, int result, Class... labels) throws Throwable {
+        MethodHandle indy = ((CallSite) BSM_TYPE_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Object.class), labels)).dynamicInvoker();
+        assertEquals((int) indy.invoke(target), result);
+        assertEquals(-1, (int) indy.invoke(null));
+    }
+
+    public void testTypes() throws Throwable {
+        testType("", 0, String.class, Object.class);
+        testType("", 0, Object.class);
+        testType("", 1, Integer.class);
+        testType("", 1, Integer.class, Serializable.class);
+        testType(E1.A, 0, E1.class, Object.class);
+        testType(E2.C, 1, E1.class, Object.class);
+        testType(new Serializable() { }, 1, Comparable.class, Serializable.class);
+
+        // test failures: duplicates, nulls, dominance inversion
+    }
+}
--- a/test/langtools/tools/javac/datum/DatumShouldDeclareAtLeastOneFieldTest.java	Fri Oct 26 19:05:30 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-/*
- * @test /nodynamiccopyright/
- * @summary smoke negative test for datum classes
- * @compile/fail/ref=DatumShouldDeclareAtLeastOneFieldTest.out -XDrawDiagnostics DatumShouldDeclareAtLeastOneFieldTest.java
- */
-
-public class DatumShouldDeclareAtLeastOneFieldTest {
-    record D1() { }
-
-    static record D2() { }
-}
--- a/test/langtools/tools/javac/datum/DatumShouldDeclareAtLeastOneFieldTest.out	Fri Oct 26 19:05:30 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-DatumShouldDeclareAtLeastOneFieldTest.java:8:17: compiler.err.record.must.declare.at.least.one.field
-DatumShouldDeclareAtLeastOneFieldTest.java:10:24: compiler.err.record.must.declare.at.least.one.field
-2 errors