diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index 815550da0bc..5e67eacbba7 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -2341,6 +2341,11 @@ Method* ClassFileParser::parse_method(const ClassFileStream* const cfs, const u1* annotation_default = nullptr; int annotation_default_length = 0; + int pattern_length = 0; + const u1* pattern_runtime_visible_parameter_annotations = nullptr; + int pattern_runtime_visible_parameter_annotations_length = 0; + bool is_pattern = false; + // Parse code and exceptions attribute u2 method_attributes_count = cfs->get_u2_fast(); while (method_attributes_count--) { @@ -2675,7 +2680,64 @@ Method* ClassFileParser::parse_method(const ClassFileStream* const cfs, assert(runtime_invisible_type_annotations != nullptr, "null invisible type annotations"); } cfs->skip_u1(method_attribute_length, CHECK_NULL); - } else { + } + else if (method_attribute_name == vmSymbols::tag_pattern()) { + cfs->guarantee_more(6, CHECK_NULL); // pattern_name_index, pattern_flags, pattern_methodtype_index + + const u2 pattern_name_index = cfs->get_u2_fast(); + check_property( + valid_symbol_at(pattern_name_index), + "Invalid pattern attribute name index %u in class file %s", + pattern_name_index, CHECK_NULL); + const Symbol *const pattern_name = cp->symbol_at(pattern_name_index); + + const u2 pattern_flags = cfs->get_u2_fast(); + + const u2 pattern_methodtype_index = cfs->get_u2_fast(); + guarantee_property( + valid_symbol_at(pattern_methodtype_index), + "Illegal constant pool index %u for pattern method type in class file %s", + pattern_methodtype_index, CHECK_NULL); + const Symbol* const signature = cp->symbol_at(pattern_methodtype_index); + + is_pattern = true; + + cfs->guarantee_more(2, CHECK_NULL); // pattern_method_attributes_count + + u2 pattern_method_attributes_count = cfs->get_u2_fast(); + while (pattern_method_attributes_count--) { + cfs->guarantee_more(6, CHECK_NULL); // method_attribute_name_index, method_attribute_length + const u2 pattern_method_attribute_name_index = cfs->get_u2_fast(); + const u4 pattern_method_attribute_length = cfs->get_u4_fast(); + check_property( + valid_symbol_at(pattern_method_attribute_name_index), + "Invalid pattern method attribute name index %u in class file %s", + pattern_method_attribute_name_index, CHECK_NULL); + + const Symbol *const pattern_method_attribute_name = cp->symbol_at(pattern_method_attribute_name_index); + + if (pattern_method_attribute_name == vmSymbols::tag_method_parameters()) { + const int pattern_method_parameters_length = cfs->get_u1_fast(); + + cfs->skip_u2_fast(pattern_method_parameters_length); + cfs->skip_u2_fast(pattern_method_parameters_length); + } + else if (pattern_method_attribute_name == vmSymbols::tag_signature()) { + const int pattern_signature_data = parse_generic_signature_attribute(cfs, CHECK_NULL); + } else if (pattern_method_attribute_name == vmSymbols::tag_runtime_visible_parameter_annotations()) { + if (runtime_visible_type_annotations != nullptr) { + classfile_parse_error( + "Multiple RuntimeVisibleTypeAnnotations attributes for pattern method in class file %s", + THREAD); + return nullptr; + } + pattern_runtime_visible_parameter_annotations_length = pattern_method_attribute_length; + pattern_runtime_visible_parameter_annotations = cfs->current(); + cfs->skip_u1(pattern_runtime_visible_parameter_annotations_length, CHECK_NULL); + } + } + } + else { // Skip unknown attributes cfs->skip_u1(method_attribute_length, CHECK_NULL); } @@ -2730,6 +2792,8 @@ Method* ClassFileParser::parse_method(const ClassFileStream* const cfs, m->set_constants(_cp); m->set_name_index(name_index); m->set_signature_index(signature_index); + m->set_is_pattern(is_pattern); + m->constMethod()->compute_from_signature(cp->symbol_at(signature_index), access_flags.is_static()); assert(args_size < 0 || args_size == m->size_of_parameters(), ""); diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 39340243bc4..638479fbb07 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -193,6 +193,7 @@ class SerializeClosure; template(tag_enclosing_method, "EnclosingMethod") \ template(tag_bootstrap_methods, "BootstrapMethods") \ template(tag_permitted_subclasses, "PermittedSubclasses") \ + template(tag_pattern, "Pattern") \ \ /* exception klasses: at least all exceptions thrown by the VM have entries here */ \ template(java_lang_ArithmeticException, "java/lang/ArithmeticException") \ diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 99cdac3aec5..7dca55c1de7 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -611,6 +611,8 @@ JVM_GetClassDeclaredFields(JNIEnv *env, jclass ofClass, jboolean publicOnly); JNIEXPORT jobjectArray JNICALL JVM_GetClassDeclaredConstructors(JNIEnv *env, jclass ofClass, jboolean publicOnly); +JNIEXPORT jobjectArray JNICALL +JVM_GetClassDeclaredDeconstructors(JNIEnv *env, jclass ofClass, jboolean publicOnly); /* Differs from JVM_GetClassModifiers in treatment of inner classes. This returns the access flags for the class as specified in the diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp index fb06abe9174..50faaeaa856 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp @@ -2161,6 +2161,33 @@ C2V_VMENTRY_NULL(jobjectArray, getDeclaredMethods, (JNIEnv* env, jobject, ARGUME return JVMCIENV->get_jobjectArray(methods); C2V_END +C2V_VMENTRY_NULL(jobjectArray, getDeclaredDeconstructors, (JNIEnv* env, jobject, ARGUMENT_PAIR(klass))) + Klass* klass = UNPACK_PAIR(Klass, klass); + if (klass == nullptr) { + JVMCI_THROW_0(NullPointerException); + } + if (!klass->is_instance_klass()) { + JVMCIObjectArray methods = JVMCIENV->new_ResolvedJavaMethod_array(0, JVMCI_CHECK_NULL); + return JVMCIENV->get_jobjectArray(methods); + } + + InstanceKlass* iklass = InstanceKlass::cast(klass); + GrowableArray methods_array; + for (int i = 0; i < iklass->methods()->length(); i++) { + Method* m = iklass->methods()->at(i); + if (!m->is_initializer() && !m->is_overpass() && m->is_pattern()) { + methods_array.append(m); + } + } + JVMCIObjectArray methods = JVMCIENV->new_ResolvedJavaMethod_array(methods_array.length(), JVMCI_CHECK_NULL); + for (int i = 0; i < methods_array.length(); i++) { + methodHandle mh(THREAD, methods_array.at(i)); + JVMCIObject method = JVMCIENV->get_jvmci_method(mh, JVMCI_CHECK_NULL); + JVMCIENV->put_object_at(methods, i, method); + } + return JVMCIENV->get_jobjectArray(methods); +C2V_END + C2V_VMENTRY_NULL(jobjectArray, getDeclaredFieldsInfo, (JNIEnv* env, jobject, ARGUMENT_PAIR(klass))) Klass* klass = UNPACK_PAIR(Klass, klass); if (klass == nullptr) { @@ -3285,6 +3312,7 @@ JNINativeMethod CompilerToVM::methods[] = { {CC "boxPrimitive", CC "(" OBJECT ")" OBJECTCONSTANT, FN_PTR(boxPrimitive)}, {CC "getDeclaredConstructors", CC "(" HS_KLASS2 ")[" RESOLVED_METHOD, FN_PTR(getDeclaredConstructors)}, {CC "getDeclaredMethods", CC "(" HS_KLASS2 ")[" RESOLVED_METHOD, FN_PTR(getDeclaredMethods)}, + {CC "getDeclaredDeconstructors", CC "(" HS_KLASS2 ")[" RESOLVED_METHOD, FN_PTR(getDeclaredDeconstructors)}, {CC "getDeclaredFieldsInfo", CC "(" HS_KLASS2 ")[" FIELDINFO, FN_PTR(getDeclaredFieldsInfo)}, {CC "readStaticFieldValue", CC "(" HS_KLASS2 "JC)" JAVACONSTANT, FN_PTR(readStaticFieldValue)}, {CC "readFieldValue", CC "(" OBJECTCONSTANT HS_KLASS2 "JC)" JAVACONSTANT, FN_PTR(readFieldValue)}, diff --git a/src/hotspot/share/oops/constMethod.hpp b/src/hotspot/share/oops/constMethod.hpp index 5f0c49f9319..9328f36348a 100644 --- a/src/hotspot/share/oops/constMethod.hpp +++ b/src/hotspot/share/oops/constMethod.hpp @@ -200,6 +200,8 @@ class ConstMethod : public MetaspaceObj { u2 _code_size; u2 _name_index; // Method name (index in constant pool) u2 _signature_index; // Method signature (index in constant pool) + bool _is_pattern_flag; // Pattern method (index in constant pool) + u2 _method_idnum; // unique identification number for the method within the class // initially corresponds to the index into the methods array. // but this may change with redefinition @@ -296,6 +298,10 @@ class ConstMethod : public MetaspaceObj { u2 signature_index() const { return _signature_index; } void set_signature_index(int index) { _signature_index = checked_cast(index); } + // pattern + bool is_pattern() const { return _is_pattern_flag; } + void set_is_pattern(bool b) { _is_pattern_flag = b; } + // generics support u2 generic_signature_index() const { if (has_generic_signature()) { diff --git a/src/hotspot/share/oops/method.hpp b/src/hotspot/share/oops/method.hpp index 905c53a4ea3..7ee5c2bf889 100644 --- a/src/hotspot/share/oops/method.hpp +++ b/src/hotspot/share/oops/method.hpp @@ -148,6 +148,10 @@ class Method : public Metadata { u2 signature_index() const { return constMethod()->signature_index(); } void set_signature_index(int index) { constMethod()->set_signature_index(index); } + // pattern + u2 is_pattern() const { return constMethod()->is_pattern(); } + void set_is_pattern(bool b) { constMethod()->set_is_pattern(b); } + // generics support Symbol* generic_signature() const { int idx = generic_signature_index(); return ((idx != 0) ? constants()->symbol_at(idx) : nullptr); } u2 generic_signature_index() const { return constMethod()->generic_signature_index(); } diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 500036febab..22ce48969e2 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -1939,6 +1939,84 @@ JVM_ENTRY(jobjectArray, JVM_GetClassDeclaredMethods(JNIEnv *env, jclass ofClass, } JVM_END +static jobjectArray get_class_declared_deconstructors_helper( + JNIEnv *env, + jclass ofClass, jboolean publicOnly, + bool want_constructor, + Klass* klass, TRAPS) { + + JvmtiVMObjectAllocEventCollector oam; + + oop ofMirror = JNIHandles::resolve_non_null(ofClass); + // Exclude primitive types and array types + if (java_lang_Class::is_primitive(ofMirror) + || java_lang_Class::as_Klass(ofMirror)->is_array_klass()) { + // Return empty array + oop res = oopFactory::new_objArray(klass, 0, CHECK_NULL); + return (jobjectArray) JNIHandles::make_local(THREAD, res); + } + + InstanceKlass* k = InstanceKlass::cast(java_lang_Class::as_Klass(ofMirror)); + + // Ensure class is linked + k->link_class(CHECK_NULL); + + Array* methods = k->methods(); + int methods_length = methods->length(); + + // Save original method_idnum in case of redefinition, which can change + // the idnum of obsolete methods. The new method will have the same idnum + // but if we refresh the methods array, the counts will be wrong. + ResourceMark rm(THREAD); + GrowableArray* idnums = new GrowableArray(methods_length); + int num_methods = 0; + + for (int i = 0; i < methods_length; i++) { + methodHandle method(THREAD, methods->at(i)); + if (select_method(method, want_constructor)) { + if ((!publicOnly || method->is_public()) && method->is_pattern()) { + idnums->push(method->method_idnum()); + ++num_methods; + } + } + } + + // Allocate result + objArrayOop r = oopFactory::new_objArray(klass, num_methods, CHECK_NULL); + objArrayHandle result (THREAD, r); + + // Now just put the methods that we selected above, but go by their idnum + // in case of redefinition. The methods can be redefined at any safepoint, + // so above when allocating the oop array and below when creating reflect + // objects. + for (int i = 0; i < num_methods; i++) { + methodHandle method(THREAD, k->method_with_idnum(idnums->at(i))); + if (method.is_null()) { + // Method may have been deleted and seems this API can handle null + // Otherwise should probably put a method that throws NSME + result->obj_at_put(i, nullptr); + } else { + oop m; + if (want_constructor) { + m = Reflection::new_constructor(method, CHECK_NULL); + } else { + m = Reflection::new_method(method, false, CHECK_NULL); + } + result->obj_at_put(i, m); + } + } + + return (jobjectArray) JNIHandles::make_local(THREAD, result()); +} + +JVM_ENTRY(jobjectArray, JVM_GetClassDeclaredDeconstructors(JNIEnv *env, jclass ofClass, jboolean publicOnly)) +{ + return get_class_declared_deconstructors_helper(env, ofClass, publicOnly, + /*want_constructor*/ false, + vmClasses::reflect_Method_klass(), THREAD); +} +JVM_END + JVM_ENTRY(jobjectArray, JVM_GetClassDeclaredConstructors(JNIEnv *env, jclass ofClass, jboolean publicOnly)) { return get_class_declared_methods_helper(env, ofClass, publicOnly, diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index 4573a6dc690..df6878d277f 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -26,8 +26,19 @@ package java.lang; import java.lang.annotation.Annotation; +import java.lang.classfile.Attribute; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.MethodModel; +import java.lang.classfile.attribute.MethodParametersAttribute; +import java.lang.classfile.attribute.PatternAttribute; +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import java.lang.classfile.attribute.SignatureAttribute; import java.lang.constant.ClassDesc; import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; import java.lang.invoke.TypeDescriptor; import java.lang.invoke.MethodHandles; import java.lang.module.ModuleReader; @@ -40,6 +51,8 @@ import java.lang.reflect.AccessFlag; import java.lang.reflect.Array; import java.lang.reflect.Constructor; +import java.lang.reflect.Deconstructor; +import java.lang.reflect.PatternBinding; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; @@ -54,6 +67,7 @@ import java.lang.reflect.TypeVariable; import java.lang.constant.Constable; import java.net.URL; +import java.nio.ByteBuffer; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; @@ -70,10 +84,9 @@ import java.util.Set; import java.util.stream.Collectors; -import jdk.internal.javac.PreviewFeature; +import jdk.internal.classfile.impl.BoundAttribute; import jdk.internal.loader.BootLoader; import jdk.internal.loader.BuiltinClassLoader; -import jdk.internal.misc.PreviewFeatures; import jdk.internal.misc.Unsafe; import jdk.internal.module.Resources; import jdk.internal.reflect.CallerSensitive; @@ -96,6 +109,8 @@ import sun.reflect.annotation.*; import sun.reflect.misc.ReflectUtil; +import static java.lang.constant.ConstantDescs.CD_void; + /** * Instances of the class {@code Class} represent classes and * interfaces in a running Java application. An enum class and a record @@ -2241,7 +2256,8 @@ public Method[] getMethods() throws SecurityException { if (sm != null) { checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true); } - return copyMethods(privateGetPublicMethods()); + var ret = copyMethods(privateGetPublicMethods()); + return filterOutDeconstructorsFromMethods(ret); } @@ -2286,6 +2302,277 @@ public Constructor[] getConstructors() throws SecurityException { return copyConstructors(privateGetDeclaredConstructors(true)); } + private Method[] filterOutDeconstructorsFromMethods(Method[] in) { + if (this.getClassLoader() != null ) { + Map isNotPattern = new HashMap<>(); + ClassModel cm = null; + try (InputStream resource = this.getClassLoader().getResourceAsStream(getName() + ".class")) { + if (resource == null) { + return in; + } + byte[] bytes = resource.readAllBytes(); + cm = ClassFile.of().parse(bytes); + } catch (IOException e) { + throw new RuntimeException(e); + } + for (MethodModel mm : cm.methods()) { + PatternAttribute pa = null; + for (Attribute attribute : mm.attributes()) { + if (attribute instanceof PatternAttribute pap) pa = pap; + } + isNotPattern.put(mm.methodName().stringValue(), pa == null); + } + Method[] ret = Arrays.stream(in).filter(m -> isNotPattern.getOrDefault(m.getName(), true)).toArray(Method[]::new); + return ret; + } else { + return in; + } + } + + /** + * Returns an array containing {@code Deconstructor} objects. + * + * @return the array of {@code Deconstructor} objects representing the + * public deconstructors of this class + * @throws SecurityException + * If a security manager, s, is present and + * the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class. + * + * @see #getDeclaredDeconstructors() + * @since 23 + */ + public Deconstructor [] getDeconstructors() throws SecurityException { + return getDeclaredDeconstructors0(EMPTY_CLASS_ARRAY, Member.PUBLIC); + } + + /** + * Returns a {@code Deconstructor} object that reflects the specified + * public constructor of the class represented by this {@code Class} + * object. + * + * @param bindingTypes the array of the types of the bindings. + * @return the {@code Constructor} object of the public constructor that + * matches the specified {@code parameterTypes} + * @throws NoSuchPatternException if a matching deconstructor is not found. + * + * @throws SecurityException + * If a security manager, s, is present and + * the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class. + * + * @see #getDeclaredDeconstructor(Class[]) + * @since 23 + */ + public Deconstructor getDeconstructor(Class... bindingTypes) throws NoSuchPatternException, SecurityException { + var ret = getDeclaredDeconstructors0(bindingTypes, Member.PUBLIC); + + if (ret.length == 0) { + throw new NoSuchPatternException(methodToString("deconstructor", bindingTypes)); + } + + return ret[0]; + } + + /** + * Returns an array of {@code Deconstructor} objects reflecting all the + * deconstructors implicitly or explicitly declared by the class represented by this + * {@code Class} object. + * + * @return the array of {@code Deconstructor} objects representing all the + * declared deconstructors of this class + * @throws SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: + * + *
    + * + *
  • the caller's class loader is not the same as the + * class loader of this class and invocation of + * {@link SecurityManager#checkPermission + * s.checkPermission} method with + * {@code RuntimePermission("accessDeclaredMembers")} + * denies access to the declared constructors within this class + * + *
  • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + *
+ * + * @since 23 + * @see #getDeconstructors() + */ + public Deconstructor[] getDeclaredDeconstructors() throws SecurityException { + return getDeclaredDeconstructors0(EMPTY_CLASS_ARRAY, Member.DECLARED); + } + + private Deconstructor[] getDeclaredDeconstructors0(Class[] params, int which) { + ArrayList> decs = new ArrayList<>(); + try(InputStream is = this.getClassLoader().getResourceAsStream(getName() + ".class")) { + byte[] bytes = is.readAllBytes(); + ClassModel cm = ClassFile.of().parse(bytes); + for (MethodModel mm : cm.methods()) { + PatternAttribute pa = null; + for (Attribute attribute : mm.attributes()) { + if (attribute instanceof PatternAttribute pa_) pa = pa_; + } + if (pa != null) { + String descriptorFilter = null; + + // generic signature detection + SignatureAttribute sa = null; + for (Attribute attribute : pa.attributes()) { + if (attribute instanceof SignatureAttribute sa_) sa = sa_; + } + List signatures = List.of(); + if (sa != null) { + signatures = sa.asMethodSignature().arguments().stream().map(a -> a.signatureString()).toList(); + } else { + signatures = Arrays.stream(pa.patternTypeSymbol().parameterArray()).map(p -> p.descriptorString()).toList(); + } + + // filtering of deconstructors + if (params.length != 0) { + ClassDesc[] paramDescs = Arrays.stream(params).map(e -> ClassDesc.of(e.getName())).toArray(ClassDesc[]::new); + descriptorFilter = MethodTypeDesc.of(CD_void, paramDescs).descriptorString(); + } + + if ((params.length == 0 || (params.length != 0 && pa.patternTypeSymbol().descriptorString().equals(descriptorFilter))) && + (which == Member.DECLARED || mm.flags().has(AccessFlag.PUBLIC))) { + // binding annotations + RuntimeVisibleAnnotationsAttribute rva = null; + for (Attribute attribute : mm.attributes()) { + if (attribute instanceof RuntimeVisibleAnnotationsAttribute rva_) rva = rva_; + } + ByteBuffer assembled_rva = null; + if (rva != null) { + byte rvaBytes[] = ((BoundAttribute) rva).contents(); // returns the full attribute + int rva_length = ((BoundAttribute) rva).payloadLen(); // already comes after the subtraction with 4 + assembled_rva = ByteBuffer.wrap(rvaBytes, 0, rva_length); + // TODO: RuntimeInVisibleAnnotationsAttribute + } + + ArrayList deconstructorBindings = new ArrayList<>(); + Deconstructor currentDeconstructor = new Deconstructor(this, + mm.flags().flagsMask(), + pa.patternFlagsMask(), + 0, + deconstructorBindings, + pa.patternTypeSymbol().descriptorString(), + rva == null ? null : assembled_rva.array() + ); + + // parameter names + var parameterList = pa.patternTypeSymbol().parameterList(); + MethodParametersAttribute mp = null; + for (Attribute attribute : pa.attributes()) { + if (attribute instanceof MethodParametersAttribute mp_) mp = mp_; + } + List parameterNameList = mp.parameters().stream().map(p -> p.name().get().stringValue()).toList(); + + // binding annotations + RuntimeVisibleParameterAnnotationsAttribute rvpa = null; + for (Attribute attribute : pa.attributes()) { + if (attribute instanceof RuntimeVisibleParameterAnnotationsAttribute rvpa_) rvpa = rvpa_; + } + ByteBuffer assembled_rvpa = null; + if (rvpa != null) { + byte rvpaBytes[] = ((BoundAttribute) rvpa).contents(); + int rvpa_length = ((BoundAttribute) rvpa).payloadLen(); + assembled_rvpa = ByteBuffer.wrap(rvpaBytes, 0, rvpa_length); + // TODO: RuntimeInVisibleParameterAnnotationsAttribute + } + + // binding type annotations + RuntimeVisibleTypeAnnotationsAttribute rvta = null; + for (Attribute attribute : pa.attributes()) { + if (attribute instanceof RuntimeVisibleTypeAnnotationsAttribute rvta_) rvta = rvta_; + } + ByteBuffer assembled_rvta = null; + if (rvpa != null) { + byte rvtaBytes[] = ((BoundAttribute) rvpa).contents(); + int rvta_length = ((BoundAttribute) rvpa).payloadLen(); + assembled_rvta = ByteBuffer.wrap(rvtaBytes, 0, rvta_length); + // TODO: RuntimeInVisibleTypeAnnotationsAttribute + } + + for (int i = 0; i < parameterList.size(); i++) { + Class bindingClass = Class.forName(parameterList.get(i).packageName() + "." + parameterList.get(i).displayName()); + deconstructorBindings.add(new PatternBinding( + currentDeconstructor, + parameterNameList.get(i), + bindingClass, + signatures.get(i), + i, + rvpa == null ? null : assembled_rvpa.array(), + rvta == null ? null : assembled_rvta.array() + )); + } + + decs.add(currentDeconstructor); + } + } + } + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + + return decs.toArray(new Deconstructor[decs.size()]); + } + + /** + * Returns a {@code Deconstructor} object that reflects the specified + * deconstructor of the class represented by this + * {@code Class} object. + * + * @param bindingTypes the array of elements + * @return The {@code Deconstructor} object for the deconstructor with the + * specified parameter list + * @throws NoSuchPatternException if a matching deconstructor is not found, + * including when this {@code Class} object represents + * an interface, a primitive type, an array class, or void. + * @throws SecurityException + * If a security manager, s, is present and any of the + * following conditions is met: + * + *
    + * + *
  • the caller's class loader is not the same as the + * class loader of this class and invocation of + * {@link SecurityManager#checkPermission + * s.checkPermission} method with + * {@code RuntimePermission("accessDeclaredMembers")} + * denies access to the declared constructor + * + *
  • the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + *
+ * + * @see #getDeconstructor(Class[]) + * @since 1.1 + */ + public Deconstructor getDeclaredDeconstructor(Class... bindingTypes) throws NoSuchPatternException, SecurityException { + var ret = getDeclaredDeconstructors0(bindingTypes, Member.DECLARED); + + if (ret.length == 0) { + throw new NoSuchPatternException(methodToString("deconstructor", bindingTypes)); + } + + return ret[0]; + } /** * Returns a {@code Field} object that reflects the specified public member @@ -3427,11 +3714,14 @@ private static class ReflectionData { volatile Field[] publicFields; volatile Method[] declaredMethods; volatile Method[] publicMethods; + volatile Method[] declaredDeconstructors; + volatile Method[] publicDeconstructors; volatile Constructor[] declaredConstructors; volatile Constructor[] publicConstructors; // Intermediate results for getFields and getMethods volatile Field[] declaredPublicFields; volatile Method[] declaredPublicMethods; + volatile Method[] declaredPublicDeconstructors; volatile Class[] interfaces; // Cached names @@ -3888,6 +4178,7 @@ private static Constructor[] copyConstructors(Constructor[] arg) { private native Field[] getDeclaredFields0(boolean publicOnly); private native Method[] getDeclaredMethods0(boolean publicOnly); + private native Method[] getDeclaredDeconstructors0(boolean publicOnly); private native Constructor[] getDeclaredConstructors0(boolean publicOnly); private native Class[] getDeclaredClasses0(); diff --git a/src/java.base/share/classes/java/lang/NoSuchPatternException.java b/src/java.base/share/classes/java/lang/NoSuchPatternException.java new file mode 100644 index 00000000000..fe261abbf25 --- /dev/null +++ b/src/java.base/share/classes/java/lang/NoSuchPatternException.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 1995, 2020, 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; + +/** + * Thrown when a particular pattern cannot be found. + * + * @since 1.0 + */ +public class NoSuchPatternException extends ReflectiveOperationException { + @java.io.Serial + private static final long serialVersionUID = 5034388446362600923L; + + /** + * Constructs a {@code NoSuchMethodException} without a detail message. + */ + public NoSuchPatternException() { + super(); + } + + /** + * Constructs a {@code NoSuchMethodException} with a detail message. + * + * @param s the detail message. + */ + public NoSuchPatternException(String s) { + super(s); + } +} diff --git a/src/java.base/share/classes/java/lang/annotation/ElementType.java b/src/java.base/share/classes/java/lang/annotation/ElementType.java index 97275fb97ec..d85b58193a0 100644 --- a/src/java.base/share/classes/java/lang/annotation/ElementType.java +++ b/src/java.base/share/classes/java/lang/annotation/ElementType.java @@ -127,5 +127,19 @@ public enum ElementType { * * @since 16 */ - RECORD_COMPONENT; + RECORD_COMPONENT, + + /** + * Deconstructor + * + * @since 23 + */ + DECONSTRUCTOR, + + /** + * Pattern binding + * + * @since 23 + */ + PATTERN_BINDING; } diff --git a/src/java.base/share/classes/java/lang/classfile/AccessFlags.java b/src/java.base/share/classes/java/lang/classfile/AccessFlags.java index 283094b81d6..8a492f4f0fe 100644 --- a/src/java.base/share/classes/java/lang/classfile/AccessFlags.java +++ b/src/java.base/share/classes/java/lang/classfile/AccessFlags.java @@ -73,6 +73,14 @@ static AccessFlags ofClass(int mask) { return new AccessFlagsImpl(AccessFlag.Location.CLASS, mask); } + /** + * {@return an {@linkplain AccessFlags} for a matcher} + * @param mask the flags to be set, as a bit mask + */ + static AccessFlags ofMatcher(int mask) { + return new AccessFlagsImpl(AccessFlag.Location.PATTERN, mask); + } + /** * {@return an {@linkplain AccessFlags} for a class} * @param flags the flags to be set diff --git a/src/java.base/share/classes/java/lang/classfile/Attribute.java b/src/java.base/share/classes/java/lang/classfile/Attribute.java index ad67bdf5365..6519a253e15 100644 --- a/src/java.base/share/classes/java/lang/classfile/Attribute.java +++ b/src/java.base/share/classes/java/lang/classfile/Attribute.java @@ -61,6 +61,8 @@ import java.lang.classfile.attribute.StackMapTableAttribute; import java.lang.classfile.attribute.SyntheticAttribute; import java.lang.classfile.attribute.UnknownAttribute; +import java.lang.classfile.attribute.PatternAttribute; + import jdk.internal.classfile.impl.BoundAttribute; import jdk.internal.classfile.impl.UnboundAttribute; import jdk.internal.javac.PreviewFeature; @@ -86,7 +88,7 @@ public sealed interface Attribute> ConstantValueAttribute, DeprecatedAttribute, EnclosingMethodAttribute, ExceptionsAttribute, InnerClassesAttribute, LineNumberTableAttribute, LocalVariableTableAttribute, LocalVariableTypeTableAttribute, - MethodParametersAttribute, ModuleAttribute, ModuleHashesAttribute, + PatternAttribute, MethodParametersAttribute, ModuleAttribute, ModuleHashesAttribute, ModuleMainClassAttribute, ModulePackagesAttribute, ModuleResolutionAttribute, ModuleTargetAttribute, NestHostAttribute, NestMembersAttribute, PermittedSubclassesAttribute, diff --git a/src/java.base/share/classes/java/lang/classfile/AttributedElement.java b/src/java.base/share/classes/java/lang/classfile/AttributedElement.java index 63b2f1e03a9..adfa4cec939 100644 --- a/src/java.base/share/classes/java/lang/classfile/AttributedElement.java +++ b/src/java.base/share/classes/java/lang/classfile/AttributedElement.java @@ -24,6 +24,7 @@ */ package java.lang.classfile; +import java.lang.classfile.attribute.PatternAttribute; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -41,7 +42,7 @@ */ @PreviewFeature(feature = PreviewFeature.Feature.CLASSFILE_API) public sealed interface AttributedElement extends ClassFileElement - permits ClassModel, CodeModel, FieldModel, MethodModel, + permits ClassModel, CodeModel, FieldModel, MethodModel, PatternAttribute, RecordComponentInfo, AbstractUnboundModel { /** diff --git a/src/java.base/share/classes/java/lang/classfile/Attributes.java b/src/java.base/share/classes/java/lang/classfile/Attributes.java index 1e91090f8af..aa9763e7fdd 100644 --- a/src/java.base/share/classes/java/lang/classfile/Attributes.java +++ b/src/java.base/share/classes/java/lang/classfile/Attributes.java @@ -41,6 +41,7 @@ *
  • {@link #moduleResolution()} *
  • {@link #sourceDebugExtension()} *
  • {@link #synthetic()} + *
  • {@link #pattern()} * * * The mappers with {@link AttributeStability#CP_REFS CP_REFS} stability are: @@ -132,6 +133,9 @@ public final class Attributes { /** LocalVariableTypeTable */ public static final String NAME_LOCAL_VARIABLE_TYPE_TABLE = "LocalVariableTypeTable"; + /** Pattern */ + public static final String NAME_PATTERN = "Pattern"; + /** MethodParameters */ public static final String NAME_METHOD_PARAMETERS = "MethodParameters"; @@ -497,4 +501,11 @@ public static AttributeMapper stackMapTable() { public static AttributeMapper synthetic() { return SyntheticMapper.INSTANCE; } + + /** + * {@return Attribute mapper for the {@code Pattern} attribute} + * The mapper permits multiple instances in a given location. + * @since 23 + */ + public static AttributeMapper pattern() { return PatternMapper.INSTANCE; } } diff --git a/src/java.base/share/classes/java/lang/classfile/MethodElement.java b/src/java.base/share/classes/java/lang/classfile/MethodElement.java index a744952de7a..0ffb6d20526 100644 --- a/src/java.base/share/classes/java/lang/classfile/MethodElement.java +++ b/src/java.base/share/classes/java/lang/classfile/MethodElement.java @@ -27,6 +27,7 @@ import java.lang.classfile.attribute.AnnotationDefaultAttribute; import java.lang.classfile.attribute.DeprecatedAttribute; import java.lang.classfile.attribute.ExceptionsAttribute; +import java.lang.classfile.attribute.PatternAttribute; import java.lang.classfile.attribute.MethodParametersAttribute; import java.lang.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; import java.lang.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; @@ -51,7 +52,7 @@ public sealed interface MethodElement extends ClassFileElement permits AccessFlags, CodeModel, CustomAttribute, AnnotationDefaultAttribute, DeprecatedAttribute, - ExceptionsAttribute, MethodParametersAttribute, + ExceptionsAttribute, PatternAttribute, MethodParametersAttribute, RuntimeInvisibleAnnotationsAttribute, RuntimeInvisibleParameterAnnotationsAttribute, RuntimeInvisibleTypeAnnotationsAttribute, RuntimeVisibleAnnotationsAttribute, RuntimeVisibleParameterAnnotationsAttribute, RuntimeVisibleTypeAnnotationsAttribute, diff --git a/src/java.base/share/classes/java/lang/classfile/attribute/PatternAttribute.java b/src/java.base/share/classes/java/lang/classfile/attribute/PatternAttribute.java new file mode 100644 index 00000000000..716ec6644c7 --- /dev/null +++ b/src/java.base/share/classes/java/lang/classfile/attribute/PatternAttribute.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2023, 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.classfile.attribute; + +import java.lang.classfile.Attribute; +import java.lang.classfile.AttributedElement; +import java.lang.classfile.MethodElement; +import java.lang.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.UnboundAttribute; + +import java.lang.constant.MethodTypeDesc; +import java.lang.reflect.AccessFlag; +import java.util.List; +import java.util.Set; + +/** + * Models the {@code pattern} attribute {@jvms X.X.XX}, which can + * appear on patterns, and records additional information about the + * nature of this pattern represented as a method. + * + * TODO + * Delivered as a {@link MethodElement} when + * traversing the elements of a {@link java.lang.classfile.MethodModel}. + */ +public sealed interface PatternAttribute + extends Attribute, MethodElement, AttributedElement + permits BoundAttribute.BoundPatternAttribute, + UnboundAttribute.UnboundPatternAttribute { + + /** + * {@return the the module flags of the module, as a bit mask} + */ + int patternFlagsMask(); + + /** + * {@return the flags of the module, as a set of enum constants} + */ + default Set patternFlags() { + return AccessFlag.maskToAccessFlags(patternFlagsMask(), AccessFlag.Location.PATTERN); + } + + /** {@return the name of this method} */ + Utf8Entry patternName(); + + /** {@return the method descriptor of this method} */ + Utf8Entry patternMethodType(); + + /** {@return the method descriptor of this method, as a symbolic descriptor} */ + default MethodTypeDesc patternTypeSymbol() { + return MethodTypeDesc.ofDescriptor(patternMethodType().stringValue()); + } + + /** + * {@return a {@code patternAttribute} attribute} + * @param patternName the name of the pattern + * @param patternFlags the flags of the pattern + * @param patternDescriptor the descriptor of the pattern + * @param patternAttributes the list of attributes of the pattern + */ + static PatternAttribute of(String patternName, + int patternFlags, + MethodTypeDesc patternDescriptor, + List> patternAttributes) { + return new UnboundAttribute.UnboundPatternAttribute( + TemporaryConstantPool.INSTANCE.utf8Entry(patternName), + patternFlags, + TemporaryConstantPool.INSTANCE.utf8Entry(patternDescriptor.descriptorString()), + patternAttributes); + } + + /** + * {@return a {@code patternAttribute} attribute} + * @param patternName the name of the pattern + * @param patternFlags the flags of the pattern + * @param patternDescriptor the descriptor of the pattern + * @param patternAttributes the list of attributes of the pattern + */ + static PatternAttribute of(Utf8Entry patternName, + int patternFlags, + Utf8Entry patternDescriptor, + List> patternAttributes) { + return new UnboundAttribute.UnboundPatternAttribute(patternName, patternFlags, patternDescriptor, patternAttributes); + } +} \ No newline at end of file diff --git a/src/java.base/share/classes/java/lang/reflect/AccessFlag.java b/src/java.base/share/classes/java/lang/reflect/AccessFlag.java index 92c6655552e..e77b62a8e00 100644 --- a/src/java.base/share/classes/java/lang/reflect/AccessFlag.java +++ b/src/java.base/share/classes/java/lang/reflect/AccessFlag.java @@ -373,6 +373,32 @@ public Set apply(ClassFileFormatVersion cffv) { Location.EMPTY_SET;} }), + /** + * The access flag {@code DECONSTRUCTOR} with a mask value of {@code + * 0x3000}. + */ + DECONSTRUCTOR(0x3000, false, Location.SET_PATTERN, + new Function>() { + @Override + public Set apply(ClassFileFormatVersion cffv) { + return (cffv.compareTo(ClassFileFormatVersion.RELEASE_22) >= 0 ) ? + Location.SET_PATTERN : + Location.EMPTY_SET;} + }), + + /** + * The access flag {@code TOTAL} with a mask value of {@code + * 0x4000}. + */ + TOTAL(0x4000, false, Location.SET_PATTERN, + new Function>() { + @Override + public Set apply(ClassFileFormatVersion cffv) { + return (cffv.compareTo(ClassFileFormatVersion.RELEASE_22) >= 0 ) ? + Location.SET_PATTERN : + Location.EMPTY_SET;} + }), + /** * The access flag {@code ACC_ENUM} with a mask value of * {@value "0x%04x" Modifier#ENUM}. @@ -416,8 +442,7 @@ public Set apply(ClassFileFormatVersion cffv) { return (cffv.compareTo(ClassFileFormatVersion.RELEASE_9) >= 0 ) ? Location.SET_CLASS: Location.EMPTY_SET;} - }) - ; + }); // May want to override toString for a different enum constant -> // name mapping. @@ -532,6 +557,11 @@ public enum Location { */ METHOD, + /** + * Pattern location. + */ + PATTERN, + /** * Inner class location. * @jvms 4.7.6 The InnerClasses Attribute @@ -585,6 +615,8 @@ public enum Location { Set.of(FIELD, METHOD); private static final Set SET_FIELD_METHOD_INNER_CLASS = Set.of(FIELD, METHOD, INNER_CLASS); + private static final Set SET_PATTERN = Set.of(PATTERN); + private static final Set SET_METHOD = Set.of(METHOD); private static final Set SET_METHOD_PARAM = Set.of(METHOD_PARAMETER); private static final Set SET_FIELD = Set.of(FIELD); @@ -636,6 +668,8 @@ private static class LocationToFlags { STATIC, FINAL, SYNCHRONIZED, BRIDGE, VARARGS, NATIVE, ABSTRACT, STRICT, SYNTHETIC)), + entry(Location.PATTERN, + Set.of(DECONSTRUCTOR, TOTAL)), entry(Location.INNER_CLASS, Set.of(PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, INTERFACE, ABSTRACT, diff --git a/src/java.base/share/classes/java/lang/reflect/Deconstructor.java b/src/java.base/share/classes/java/lang/reflect/Deconstructor.java new file mode 100644 index 00000000000..4e01c250646 --- /dev/null +++ b/src/java.base/share/classes/java/lang/reflect/Deconstructor.java @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2024, 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.reflect; + +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.Reflection; +import jdk.internal.vm.annotation.Stable; +import sun.reflect.generics.factory.CoreReflectionFactory; +import sun.reflect.generics.factory.GenericsFactory; +import sun.reflect.generics.repository.ConstructorRepository; +import sun.reflect.generics.repository.GenericDeclRepository; +import sun.reflect.generics.scope.DeconstructorScope; + +import java.lang.annotation.Annotation; +import java.lang.invoke.MethodType; +import java.lang.runtime.Carriers; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +/** + * {@code Deconstructor} provides information about, and access to, a single + * deconstructor for a class. + * + * @param the class in which the constructor is declared + * + * @see Member + * @see Class + * @see Class#getDeconstructors() + * @see Class#getDeconstructor(Class[]) + * @see Class#getDeclaredDeconstructors() + * @see Class#getDeclaredDeconstructor(Class[]) + * + * @since 24 + */ +public final class Deconstructor extends Executable { + private final Class clazz; + private final int slot; + private final int modifiers; + private final int patternFlags; + // Generics and annotations support + private final transient String signature; + // generic info repository; lazily initialized + private transient volatile ConstructorRepository genericInfo; + private final byte[] annotations; + private ArrayList patternBindings; + + // Generics infrastructure + // Accessor for factory + private GenericsFactory getFactory() { + // create scope and factory + return CoreReflectionFactory.make(this, DeconstructorScope.make(this)); + } + + private Deconstructor root; + + @Override + Deconstructor getRoot() { + return root; + } + + /** + * TODO make private again + * Package-private deconstructor used by ReflectAccess to enable + * instantiation of these objects in Java code from the java.lang + * package via jdk.internal.access.JavaLangReflectAccess. + * + * @param declaringClass x + * @param modifiers x + * @param patternFlags x + * @param slot x + * @param patternBindings x + * @param signature x + * @param annotations x + * + */ + public Deconstructor(Class declaringClass, + int modifiers, + int patternFlags, + int slot, + ArrayList patternBindings, + String signature, + byte[] annotations) { + this.clazz = declaringClass; + this.modifiers = modifiers; + this.patternFlags = patternFlags; + this.slot = slot; + this.patternBindings = patternBindings; + this.signature = signature; + this.annotations = annotations; + } + + /** + * Package-private routine (exposed to java.lang.Class via + * ReflectAccess) which returns a copy of this Constructor. The copy's + * "root" field points to this Deconstructor. + */ + Deconstructor copy() { + // This routine enables sharing of ConstructorAccessor objects + // among Constructor objects which refer to the same underlying + // method in the VM. (All of this contortion is only necessary + // because of the "accessibility" bit in AccessibleObject, + // which implicitly requires that new java.lang.reflect + // objects be fabricated for each reflective call on Class + // objects.) + if (this.root != null) + throw new IllegalArgumentException("Can not copy a non-root Constructor"); + + Deconstructor res = new Deconstructor<>(clazz, + modifiers, + patternFlags, + slot, + patternBindings, + signature, + annotations); + res.root = this; + return res; + } + + /** + * {@inheritDoc} + * + *

    A {@code SecurityException} is also thrown if this object is a + * {@code Constructor} object for the class {@code Class} and {@code flag} + * is true.

    + * + * @param flag {@inheritDoc} + * + * @throws InaccessibleObjectException {@inheritDoc} + * @throws SecurityException if the request is denied by the security manager + * or this is a constructor for {@code java.lang.Class} + * + */ + @Override + @CallerSensitive + public void setAccessible(boolean flag) { + AccessibleObject.checkPermission(); + if (flag) { + checkCanSetAccessible(Reflection.getCallerClass()); + } + setAccessible0(flag); + } + + @Override + void checkCanSetAccessible(Class caller) { + checkCanSetAccessible(caller, clazz); + if (clazz == Class.class) { + // can we change this to InaccessibleObjectException? + throw new SecurityException("Cannot make a java.lang.Class" + + " constructor accessible"); + } + } + + /** + * Returns the {@code Class} object representing the class that + * declares the constructor represented by this object. + */ + @Override + public Class getDeclaringClass() { + return clazz; + } + + /** + * Returns the name of this constructor, as a string. This is + * the binary name of the constructor's declaring class. + */ + @Override + public String getName() { + return getDeclaringClass().getName(); + } + + /** + * {@inheritDoc} + * @jls 8.8.3 Constructor Modifiers + */ + @Override + public int getModifiers() { + return modifiers; + } + + /** + * {@inheritDoc} + * @jls X.X.X Deconstructor Modifiers + */ + @Override + public boolean isSynthetic() { + return Modifier.isSynthetic(getModifiers()); + } + + @Override + public Annotation[][] getParameterAnnotations() { + return new Annotation[0][]; + } + + @Override + boolean handleParameterNumberMismatch(int resultLength, Class[] parameterTypes) { + return false; + } + + /** + * {@inheritDoc} + * @throws GenericSignatureFormatError {@inheritDoc} + * @since 1.5 + */ + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public TypeVariable>[] getTypeParameters() { + if (getSignature() != null) { + return (TypeVariable>[])getGenericInfo().getTypeParameters(); + } else + return (TypeVariable>[])GenericDeclRepository.EMPTY_TYPE_VARS; + } + + @Override + Class[] getSharedParameterTypes() { + return new Class[0]; + } + + @Override + Class[] getSharedExceptionTypes() { + return new Class[0]; + } + + @Override + public Class[] getParameterTypes() { + return new Class[0]; + } + + @Override + public int getParameterCount() { + return 0; + } + + @Override + public Class[] getExceptionTypes() { + return new Class[0]; + } + + /** + * Returns a string describing this {@code Deconstructor}, + * including type parameters. The string is formatted as the + * constructor access modifiers, if any, followed by an + * angle-bracketed comma separated list of the constructor's type + * parameters, if any, including informative bounds of the + * type parameters, if any, followed by the fully-qualified name of the + * declaring class, followed by a parenthesized, comma-separated + * list of the constructor's generic formal parameter types. + * + * If this constructor was declared to take a variable number of + * arguments, instead of denoting the last parameter as + * "Type[]", it is denoted as + * "Type...". + * + * A space is used to separate access modifiers from one another + * and from the type parameters or class name. If there are no + * type parameters, the type parameter list is elided; if the type + * parameter list is present, a space separates the list from the + * class name. If the constructor is declared to throw + * exceptions, the parameter list is followed by a space, followed + * by the word "{@code throws}" followed by a + * comma-separated list of the generic thrown exception types. + * + *

    The only possible modifiers for constructors are the access + * modifiers {@code public}, {@code protected} or + * {@code private}. Only one of these may appear, or none if the + * constructor has default (package) access. + * + * @return a string describing this {@code Constructor}, + * include type parameters + * + * @since 1.5 + * @jls 8.8.3 Constructor Modifiers + * @jls 8.9.2 Enum Body Declarations + */ + @Override + public String toGenericString() { + return sharedToGenericString(Modifier.constructorModifiers(), false); + } + + String getSignature() { + return signature; + } + + @Override + byte[] getAnnotationBytes() { + return annotations; + } + + @Override + boolean hasGenericInformation() { + return false; + } + + ConstructorRepository getGenericInfo() { + var genericInfo = this.genericInfo; + // lazily initialize repository if necessary + if (genericInfo == null) { + // create and cache generic info repository + genericInfo = + ConstructorRepository.make(getSignature(), + getFactory()); + this.genericInfo = genericInfo; + } + return genericInfo; //return cached repository + } + + @Override + void specificToStringHeader(StringBuilder sb) { + sb.append(getDeclaringClass().getTypeName()); + } + + @Override + void specificToGenericStringHeader(StringBuilder sb) { + specificToStringHeader(sb); + } + + + String sharedToGenericString(int modifierMask, boolean isDefault) { + try { + StringBuilder sb = new StringBuilder(); + + printModifiersIfNonzero(sb, modifierMask, isDefault); + + sb.append("pattern "); + TypeVariable[] typeparms = getTypeParameters(); + if (typeparms.length > 0) { + sb.append(Arrays.stream(typeparms) + .map(Executable::typeVarBounds) + .collect(Collectors.joining(",", "<", "> "))); + } + + specificToGenericStringHeader(sb); + + sb.append('('); + StringJoiner sj = new StringJoiner(","); + Type[] params = Arrays.stream(getPatternBindings()).map(pb -> pb.getGenericType()).toArray(Type[]::new); + for (int j = 0; j < params.length; j++) { + String param = params[j].getTypeName(); + if (isVarArgs() && (j == params.length - 1)) // replace T[] with T... + param = param.replaceFirst("\\[\\]$", "..."); + sj.add(param); + } + sb.append(sj.toString()); + sb.append(')'); + + return sb.toString(); + } catch (Exception e) { + return "<" + e + ">"; + } + } + + /** + * Returns the pattern bindings of this deconstructor. + * + * @return pattern bindings + */ + public PatternBinding[] getPatternBindings() { + return patternBindings.toArray(PatternBinding[]::new); + } + + /** + * Returns the pattern flags of this deconstructor. + * + * @return pattern bindings + */ + public int getPatternFlags() { + return patternFlags; + } + + /** + * Initiate pattern matching of this deconstructor on the designated matchCandidate. + * + * @param matchCandidate the match candidate to perform pattern matching over. + * + * @return an array object created as a result of pattern matching + * + * @throws IllegalAccessException if this {@code Constructor} object + * is enforcing Java language access control and the underlying + * constructor is inaccessible. + * @throws MatchException if the pattern matching provoked + * by this deconstructor fails. + */ + public Object[] invoke(Object matchCandidate) + throws IllegalAccessException, MatchException + { + Class klass = this.getDeclaringClass(); + + String postFix = Arrays.stream(getPatternBindings()) + .map(param -> param.getType() + .descriptorString() + .replace("/", "\\\u007C") + .replace(";", "\\\u003F")) + .collect(Collectors.joining("\\\u0025")); + + String underlyingName = klass.getSimpleName() + "\\%" + postFix; + + try { + Method method = klass.getMethod(underlyingName, matchCandidate.getClass()); + + Object carrier = method.invoke(matchCandidate, matchCandidate); + + Class[] bindingClasses = Arrays.stream(this.getPatternBindings()).map(d -> d.getType()).toArray(Class[]::new); + MethodType methodType = MethodType.methodType(Object.class, bindingClasses); + + ArrayList ret = new ArrayList<>(); + for (int i = 0; i < bindingClasses.length; i++) { + ret.add(Carriers.component(methodType, i).invoke(carrier)); + } + + return ret.stream().toArray(); + } catch (Throwable e) { + throw new MatchException(e.getMessage(), e); + } + } + + byte[] getRawAnnotations() { + return annotations; + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException {@inheritDoc} + * @since 1.5 + */ + @Override + public T getAnnotation(Class annotationClass) { + return super.getAnnotation(annotationClass); + } + + /** + * {@inheritDoc} + * @since 1.5 + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return super.getDeclaredAnnotations(); + } + + @Override + public AnnotatedType getAnnotatedReturnType() { + return null; + } +} diff --git a/src/java.base/share/classes/java/lang/reflect/Executable.java b/src/java.base/share/classes/java/lang/reflect/Executable.java index b808e052fb2..03980be3d74 100644 --- a/src/java.base/share/classes/java/lang/reflect/Executable.java +++ b/src/java.base/share/classes/java/lang/reflect/Executable.java @@ -50,7 +50,7 @@ * @since 1.8 */ public abstract sealed class Executable extends AccessibleObject - implements Member, GenericDeclaration permits Constructor, Method { + implements Member, GenericDeclaration permits Constructor, Deconstructor, Method { /* * Only grant package-visibility to the constructor. */ diff --git a/src/java.base/share/classes/java/lang/reflect/PatternBinding.java b/src/java.base/share/classes/java/lang/reflect/PatternBinding.java new file mode 100644 index 00000000000..93678d9de72 --- /dev/null +++ b/src/java.base/share/classes/java/lang/reflect/PatternBinding.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2019, 2023, 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.reflect; + +import jdk.internal.access.SharedSecrets; +import sun.reflect.annotation.AnnotationParser; +import sun.reflect.annotation.TypeAnnotation; +import sun.reflect.annotation.TypeAnnotationParser; +import sun.reflect.generics.factory.CoreReflectionFactory; +import sun.reflect.generics.factory.GenericsFactory; +import sun.reflect.generics.repository.FieldRepository; +import sun.reflect.generics.scope.ClassScope; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * A {@code PatternBinding} provides information about, and dynamic access to, + * an initialized binding of a deconstructor. + * + * @see Deconstructor#getPatternBindings() () + * @see Deconstructor + * @since 23 + */ +public final class PatternBinding implements AnnotatedElement { + // declaring class + private Deconstructor declaringDeconstructor; + private String name; + private Class type; + private String signature; + private int slot; + // generic info repository; lazily initialized + private transient volatile FieldRepository genericInfo; + private byte[] annotations; + private byte[] typeAnnotations; + private PatternBinding root; + + /** + * TODO make private again + * Package-private pattern binding used by ReflectAccess to enable + * instantiation of these objects in Java code from the java.lang + * package via jdk.internal.access.JavaLangReflectAccess. + * + * @param declaringDeconstructor x + * @param name x + * @param type x + * @param signature x + * @param slot x + * @param annotations x + * @param typeAnnotations x + * + */ + public PatternBinding(Deconstructor declaringDeconstructor, + String name, + Class type, + String signature, + int slot, + byte[] annotations, + byte[] typeAnnotations) { + this.declaringDeconstructor = declaringDeconstructor; + this.name = name; + this.type = type; + this.signature = signature; + this.slot = slot; + this.annotations = annotations; + this.typeAnnotations = typeAnnotations; + } + + /** + * Returns the name of this pattern binding. + * + * @return the name of this pattern binding + */ + public String getName() { + return name; + } + + /** + * Returns a {@code Class} that identifies the declared type for this + * pattern binding. + * + * @return a {@code Class} identifying the declared type of the component + * represented by this pattern binding + */ + public Class getType() { + return type; + } + + /** + * Returns a {@code String} that describes the generic type signature for + * this pattern binding. + * + * @return a {@code String} that describes the generic type signature for + * this pattern binding + * + * @jvms 4.7.9.1 Signatures + */ + public String getGenericSignature() { + return signature; + } + + /** + * Returns a {@code Type} object that represents the declared type for + * this pattern binding. + * + *

    If the declared type of the pattern binding is a parameterized type, + * the {@code Type} object returned reflects the actual type arguments used + * in the source code. + * + *

    If the type of the underlying pattern binding is a type variable or a + * parameterized type, it is created. Otherwise, it is resolved. + * + * @return a {@code Type} object that represents the declared type for + * this pattern binding + * @throws GenericSignatureFormatError if the generic pattern binding + * signature does not conform to the format specified in + * The Java Virtual Machine Specification + * @throws TypeNotPresentException if the generic type + * signature of the underlying pattern binding refers to a non-existent + * type declaration + * @throws MalformedParameterizedTypeException if the generic + * signature of the underlying pattern binding refers to a parameterized + * type that cannot be instantiated for any reason + */ + public Type getGenericType() { + if (getGenericSignature() != null) + return getGenericInfo().getGenericType(); + else + return getType(); + } + + // Accessor for generic info repository + private FieldRepository getGenericInfo() { + var genericInfo = this.genericInfo; + // lazily initialize repository if necessary + if (genericInfo == null) { + // create and cache generic info repository + genericInfo = FieldRepository.make(getGenericSignature(), getFactory()); // TODO + this.genericInfo = genericInfo; + } + return genericInfo; //return cached repository + } + + // Accessor for factory + private GenericsFactory getFactory() { + Class c = getDeclaringDeconstructor().getDeclaringClass(); + // create scope and factory + return CoreReflectionFactory.make(c, ClassScope.make(c)); + } + + /** + * Returns an {@code AnnotatedType} object that represents the use of a type to specify + * the declared type of this pattern binding. + * + * @return an object representing the declared type of this pattern binding + */ + public AnnotatedType getAnnotatedType() { + return TypeAnnotationParser.buildAnnotatedType(typeAnnotations, + SharedSecrets.getJavaLangAccess(). + getConstantPool(getDeclaringDeconstructor().getDeclaringClass()), + this, + getDeclaringDeconstructor().getDeclaringClass(), + getGenericType(), + TypeAnnotation.TypeAnnotationTarget.FIELD); + } + + /** + * {@inheritDoc} + *

    Note that any annotation returned by this method is a + * declaration annotation. + * @throws NullPointerException {@inheritDoc} + */ + @Override + public T getAnnotation(Class annotationClass) { + Objects.requireNonNull(annotationClass); + return annotationClass.cast(declaredAnnotations().get(annotationClass)); + } + + private transient volatile Map, Annotation> declaredAnnotations; + + private Map, Annotation> declaredAnnotations() { + Map, Annotation> declAnnos; + if ((declAnnos = declaredAnnotations) == null) { + synchronized (this) { + if ((declAnnos = declaredAnnotations) == null) { + PatternBinding root = this.root; + if (root != null) { + declAnnos = root.declaredAnnotations(); + } else { + Annotation[][] ret = AnnotationParser.parseParameterAnnotations( + annotations, + SharedSecrets.getJavaLangAccess() + .getConstantPool(getDeclaringDeconstructor().getDeclaringClass()), + getDeclaringDeconstructor().getDeclaringClass()); + + int myIndex = slot; + declAnnos = Arrays.stream(ret[myIndex]).collect(Collectors.toMap(a -> a.annotationType(), a-> a)); + } + declaredAnnotations = declAnnos; + } + } + } + return declAnnos; + } + + /** + * {@inheritDoc} + *

    Note that any annotations returned by this method are + * declaration annotations. + */ + @Override + public Annotation[] getAnnotations() { + return getDeclaredAnnotations(); + } + + /** + * {@inheritDoc} + *

    Note that any annotations returned by this method are + * declaration annotations. + */ + @Override + public Annotation[] getDeclaredAnnotations() { return AnnotationParser.toArray(declaredAnnotations()); } + + /** + * Returns the deconstructor which declares this pattern binding. + * + * @return The deconstructor declaring this pattern binding. + */ + public Deconstructor getDeclaringDeconstructor() { + return declaringDeconstructor; + } +} diff --git a/src/java.base/share/classes/java/lang/runtime/Carriers.java b/src/java.base/share/classes/java/lang/runtime/Carriers.java index a74144fcbeb..82543756e5b 100644 --- a/src/java.base/share/classes/java/lang/runtime/Carriers.java +++ b/src/java.base/share/classes/java/lang/runtime/Carriers.java @@ -25,6 +25,8 @@ package java.lang.runtime; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; @@ -34,7 +36,6 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -import jdk.internal.misc.Unsafe; import jdk.internal.util.ReferencedKeyMap; import static java.lang.invoke.MethodType.methodType; @@ -85,7 +86,7 @@ * Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES. * Do not rely on its availability. */ -final class Carriers { +public final class Carriers { /** * Maximum number of components in a carrier (based on the maximum * number of args to a constructor.) @@ -238,10 +239,6 @@ static List reshapeComponents(CarrierShape carrierShape, * Factory for carriers that are backed by long[] and Object[]. */ static final class CarrierObjectFactory { - /** - * Unsafe access. - */ - private static final Unsafe UNSAFE; /* * Constructor accessor MethodHandles. @@ -256,7 +253,6 @@ static final class CarrierObjectFactory { static { try { - UNSAFE = Unsafe.getUnsafe(); Lookup lookup = MethodHandles.lookup(); CONSTRUCTOR = lookup.findConstructor(CarrierObject.class, methodType(void.class, int.class, int.class)); @@ -404,7 +400,7 @@ static class CarrierObject { /** * Carrier for primitive values. */ - private final long[] primitives; + private final MemorySegment primitives; /** * Carrier for objects; @@ -429,8 +425,8 @@ protected CarrierObject(int primitiveCount, int objectCount) { * * @return primitives array of an appropriate length. */ - private long[] createPrimitivesArray(int primitiveCount) { - return primitiveCount != 0 ? new long[(primitiveCount + 1) / LONG_SLOTS] : null; + private MemorySegment createPrimitivesArray(int primitiveCount) { + return primitiveCount != 0 ? MemorySegment.ofArray(new long[(primitiveCount + 1) / LONG_SLOTS]) : null; } /** @@ -444,49 +440,13 @@ private Object[] createObjectsArray(int objectCount) { return objectCount != 0 ? new Object[objectCount] : null; } - /** - * Compute offset for unsafe access to long. - * - * @param i index in primitive[] - * - * @return offset for unsafe access - */ - private static long offsetToLong(int i) { - return Unsafe.ARRAY_LONG_BASE_OFFSET + - (long)i * Unsafe.ARRAY_LONG_INDEX_SCALE; - } - - /** - * Compute offset for unsafe access to int. - * - * @param i index in primitive[] - * - * @return offset for unsafe access - */ - private static long offsetToInt(int i) { - return Unsafe.ARRAY_LONG_BASE_OFFSET + - (long)i * Unsafe.ARRAY_INT_INDEX_SCALE; - } - - /** - * Compute offset for unsafe access to object. - * - * @param i index in objects[] - * - * @return offset for unsafe access - */ - private static long offsetToObject(int i) { - return Unsafe.ARRAY_OBJECT_BASE_OFFSET + - (long)i * Unsafe.ARRAY_OBJECT_INDEX_SCALE; - } - /** * {@return long value at index} * * @param i array index */ private long getLong(int i) { - return CarrierObjectFactory.UNSAFE.getLong(primitives, offsetToLong(i)); + return primitives.getAtIndex(ValueLayout.JAVA_LONG, i); } /** @@ -498,7 +458,7 @@ private long getLong(int i) { * @return this object */ private CarrierObject putLong(int i, long value) { - CarrierObjectFactory.UNSAFE.putLong(primitives, offsetToLong(i), value); + primitives.setAtIndex(ValueLayout.JAVA_LONG, i, value); return this; } @@ -509,7 +469,7 @@ private CarrierObject putLong(int i, long value) { * @param i array index */ private int getInteger(int i) { - return CarrierObjectFactory.UNSAFE.getInt(primitives, offsetToInt(i)); + return primitives.getAtIndex(ValueLayout.JAVA_INT, i); } /** @@ -521,7 +481,7 @@ private int getInteger(int i) { * @return this object */ private CarrierObject putInteger(int i, int value) { - CarrierObjectFactory.UNSAFE.putInt(primitives, offsetToInt(i), value); + primitives.setAtIndex(ValueLayout.JAVA_INT, i, value); return this; } @@ -532,7 +492,7 @@ private CarrierObject putInteger(int i, int value) { * @param i array index */ private Object getObject(int i) { - return CarrierObjectFactory.UNSAFE.getReference(objects, offsetToObject(i)); + return objects[i]; } /** @@ -544,7 +504,7 @@ private Object getObject(int i) { * @return this object */ private CarrierObject putObject(int i, Object value) { - CarrierObjectFactory.UNSAFE.putReference(objects, offsetToObject(i), value); + objects[i] = value; return this; } @@ -933,7 +893,7 @@ static Class carrierClass(MethodType methodType) { * @param methodType {@link MethodType} whose parameter types supply the shape of the * carrier's components */ - static MethodHandle constructor(MethodType methodType) { + public static MethodHandle constructor(MethodType methodType) { MethodHandle constructor = CarrierFactory.of(methodType).constructor(); constructor = constructor.asType(constructor.type().changeReturnType(Object.class)); return constructor; @@ -962,7 +922,7 @@ static MethodHandle initializer(MethodType methodType) { * @param methodType {@link MethodType} whose parameter types supply the shape of the * carrier's components */ - static MethodHandle initializingConstructor(MethodType methodType) { + static public MethodHandle initializingConstructor(MethodType methodType) { MethodHandle constructor = CarrierFactory.of(methodType).initializingConstructor(); constructor = constructor.asType(constructor.type().changeReturnType(Object.class)); return constructor; @@ -976,7 +936,7 @@ static MethodHandle initializingConstructor(MethodType methodType) { * @param methodType {@link MethodType} whose parameter types supply the shape of the * carrier's components */ - static List components(MethodType methodType) { + public static List components(MethodType methodType) { return CarrierFactory .of(methodType) .components() @@ -996,7 +956,7 @@ static List components(MethodType methodType) { * * @throws IllegalArgumentException if {@code i} is out of bounds */ - static MethodHandle component(MethodType methodType, int i) { + public static MethodHandle component(MethodType methodType, int i) { MethodHandle component = CarrierFactory.of(methodType).component(i); component = component.asType(component.type().changeParameterType(0, Object.class)); return component; diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractAttributeMapper.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractAttributeMapper.java index 9bb3f275ba1..72da74c6b5c 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractAttributeMapper.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractAttributeMapper.java @@ -821,4 +821,25 @@ protected void writeBody(BufWriter buf, SyntheticAttribute attr) { // empty } } + + public static final class PatternMapper extends AbstractAttributeMapper { + public static final PatternMapper INSTANCE = new PatternMapper(); + + private PatternMapper() { + super(NAME_PATTERN, AttributeStability.STATELESS, true); + } + + @Override + public PatternAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundPatternAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, PatternAttribute attr) { + buf.writeIndex(attr.patternName()); + buf.writeU2(attr.patternFlagsMask()); + buf.writeIndex(attr.patternMethodType()); + buf.writeList(attr.attributes()); + } + } } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java index 45e0c092c3e..fbae276b784 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java @@ -25,6 +25,7 @@ package jdk.internal.classfile.impl; +import java.lang.constant.MethodTypeDesc; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -331,6 +332,47 @@ public List localVariableTypes() { } } + public static final class BoundPatternAttribute extends BoundAttribute + implements PatternAttribute { + private MethodTypeDesc mDesc; + private List> attributes; + + public BoundPatternAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry patternName() { + return classReader.readUtf8Entry(payloadStart); + } + + @Override + public int patternFlagsMask() { + return classReader.readU2(payloadStart + 2); + } + + @Override + public Utf8Entry patternMethodType() { + return classReader.readUtf8Entry(payloadStart + 4); + } + + @Override + public MethodTypeDesc patternTypeSymbol() { + if (mDesc == null) { + mDesc = MethodTypeDesc.ofDescriptor(patternMethodType().stringValue()); + } + return mDesc; + } + + @Override + public List> attributes() { + if (attributes == null) { + attributes = BoundAttribute.readAttributes(null, classReader, payloadStart + 6, classReader.customAttributes()); + } + return attributes; + } + } + public static final class BoundMethodParametersAttribute extends BoundAttribute implements MethodParametersAttribute { private List parameters = null; @@ -1060,6 +1102,8 @@ public static AttributeMapper standardAttribute(Utf8Entry name) { name.equalsString(NAME_STACK_MAP_TABLE) ? stackMapTable() : null; case 0x3dc79b7a -> name.equalsString(NAME_SYNTHETIC) ? synthetic() : null; + case 0x7B286F27 -> + name.equalsString(NAME_PATTERN) ? pattern() : null; default -> null; }; } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassRemapperImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassRemapperImpl.java index d1a02d9a93c..790dcd2064b 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassRemapperImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassRemapperImpl.java @@ -24,6 +24,7 @@ */ package jdk.internal.classfile.impl; +import java.lang.classfile.Attribute; import java.lang.classfile.Annotation; import java.lang.classfile.AnnotationElement; import java.lang.classfile.AnnotationValue; @@ -48,10 +49,14 @@ import java.lang.classfile.Superclass; import java.lang.classfile.TypeAnnotation; import java.lang.classfile.attribute.AnnotationDefaultAttribute; +import java.lang.classfile.attribute.DeprecatedAttribute; import java.lang.classfile.attribute.EnclosingMethodAttribute; import java.lang.classfile.attribute.ExceptionsAttribute; import java.lang.classfile.attribute.InnerClassInfo; import java.lang.classfile.attribute.InnerClassesAttribute; +import java.lang.classfile.attribute.PatternAttribute; +import java.lang.classfile.attribute.MethodParametersAttribute; +import java.lang.classfile.attribute.MethodParameterInfo; import java.lang.classfile.attribute.ModuleAttribute; import java.lang.classfile.attribute.ModuleProvideInfo; import java.lang.classfile.attribute.NestHostAttribute; @@ -220,6 +225,14 @@ public MethodTransform asMethodTransform() { case RuntimeInvisibleTypeAnnotationsAttribute aa -> mb.with(RuntimeInvisibleTypeAnnotationsAttribute.of( mapTypeAnnotations(aa.annotations()))); + case PatternAttribute ma -> { + List> matcherAttrs = ma.attributes().stream().>map(this::mapMatcherAttributes).toList(); + mb.with(PatternAttribute.of( + ma.patternName().stringValue(), + ma.patternFlagsMask(), + ma.patternTypeSymbol(), + matcherAttrs)); + } default -> mb.with(me); } @@ -326,6 +339,26 @@ RecordComponentInfo mapRecordComponent(RecordComponentInfo component) { }).toList()); } + Attribute mapMatcherAttributes(Attribute matcherAnnotation) { + return switch (matcherAnnotation) { + case SignatureAttribute sa -> + SignatureAttribute.of( + mapSignature(sa.asMethodSignature().result())); + case RuntimeVisibleParameterAnnotationsAttribute aa -> + RuntimeVisibleParameterAnnotationsAttribute.of( + aa.parameterAnnotations().stream().map(this::mapAnnotations).toList()); + case RuntimeInvisibleParameterAnnotationsAttribute aa -> + RuntimeInvisibleParameterAnnotationsAttribute.of( + aa.parameterAnnotations().stream().map(this::mapAnnotations).toList()); + case DeprecatedAttribute a -> + DeprecatedAttribute.of(); + case MethodParametersAttribute a -> + MethodParametersAttribute.of(a.parameters().stream().map(mp -> + MethodParameterInfo.ofParameter(mp.name().map(Utf8Entry::stringValue), mp.flagsMask())).toArray(MethodParameterInfo[]::new)); + default -> matcherAnnotation; + }; + } + DirectMethodHandleDesc mapDirectMethodHandle(DirectMethodHandleDesc dmhd) { return switch (dmhd.kind()) { case GETTER, SETTER, STATIC_GETTER, STATIC_SETTER -> diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/UnboundAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/impl/UnboundAttribute.java index 70b58e423f6..c15b61e8ad0 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/UnboundAttribute.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/UnboundAttribute.java @@ -56,6 +56,7 @@ import java.lang.classfile.attribute.LocalVariableTableAttribute; import java.lang.classfile.attribute.LocalVariableTypeInfo; import java.lang.classfile.attribute.LocalVariableTypeTableAttribute; +import java.lang.classfile.attribute.PatternAttribute; import java.lang.classfile.attribute.MethodParameterInfo; import java.lang.classfile.attribute.MethodParametersAttribute; import java.lang.classfile.attribute.ModuleAttribute; @@ -310,6 +311,43 @@ public Optional enclosingMethod() { } } + public static final class UnboundPatternAttribute + extends UnboundAttribute + implements PatternAttribute { + private final int patternFlags; + private final Utf8Entry patternName; + private final Utf8Entry patternMethodType; + private final List> attributes; + + public UnboundPatternAttribute(Utf8Entry matcherName, int matcherFlags, Utf8Entry matcherMethodType, List> attributes) { + super(Attributes.pattern()); + this.patternName = matcherName; + this.patternFlags = matcherFlags; + this.patternMethodType = matcherMethodType; + this.attributes = List.copyOf(attributes); + } + + @Override + public int patternFlagsMask() { + return patternFlags; + } + + @Override + public Utf8Entry patternName() { + return patternName; + } + + @Override + public Utf8Entry patternMethodType() { + return patternMethodType; + } + + @Override + public List> attributes() { + return attributes; + } + } + public static final class UnboundMethodParametersAttribute extends UnboundAttribute implements MethodParametersAttribute { diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index 05049effef8..09af6c50730 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -82,6 +82,8 @@ public enum Feature { @JEP(number=476, title="Module Import Declarations", status="Preview") MODULE_IMPORTS, LANGUAGE_MODEL, + @JEP(number=999, title="Pattern Declarations", status="Preview") + PATTERN_DECLARATIONS, /** * A key for testing. */ diff --git a/src/java.base/share/classes/sun/reflect/generics/scope/DeconstructorScope.java b/src/java.base/share/classes/sun/reflect/generics/scope/DeconstructorScope.java new file mode 100644 index 00000000000..3bde6ca5b93 --- /dev/null +++ b/src/java.base/share/classes/sun/reflect/generics/scope/DeconstructorScope.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024, 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 sun.reflect.generics.scope; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Deconstructor; + + +/** + * This class represents the scope containing the type variables of + * a deconstructor. + */ +public class DeconstructorScope extends AbstractScope> { + + // constructor is private to enforce use of factory method + private DeconstructorScope(Deconstructor c){ + super(c); + } + + // utility method; computes enclosing class, from which we can + // derive enclosing scope. + private Class getEnclosingClass(){ + return getRecvr().getDeclaringClass(); + } + + /** + * Overrides the abstract method in the superclass. + * @return the enclosing scope + */ + protected Scope computeEnclosingScope() { + // the enclosing scope of a (generic) constructor is the scope of the + // class in which it was declared. + return ClassScope.make(getEnclosingClass()); + } + + /** + * Factory method. Takes a {@code Constructor} object and creates a + * scope for it. + * @param c - A Constructor whose scope we want to obtain + * @return The type-variable scope for the constructor m + */ + public static DeconstructorScope make(Deconstructor c) { + return new DeconstructorScope(c); + } +} diff --git a/src/java.base/share/native/libjava/Class.c b/src/java.base/share/native/libjava/Class.c index 276101d22aa..5caac7dd377 100644 --- a/src/java.base/share/native/libjava/Class.c +++ b/src/java.base/share/native/libjava/Class.c @@ -54,36 +54,37 @@ extern jboolean VerifyFixClassname(char *utf_name); #define RC "Ljava/lang/reflect/RecordComponent;" static JNINativeMethod methods[] = { - {"initClassName", "()" STR, (void *)&JVM_InitClassName}, - {"getSuperclass", "()" CLS, NULL}, - {"getInterfaces0", "()[" CLS, (void *)&JVM_GetClassInterfaces}, - {"isInterface", "()Z", (void *)&JVM_IsInterface}, - {"getSigners", "()[" OBJ, (void *)&JVM_GetClassSigners}, - {"setSigners", "([" OBJ ")V", (void *)&JVM_SetClassSigners}, - {"isArray", "()Z", (void *)&JVM_IsArrayClass}, - {"isHidden", "()Z", (void *)&JVM_IsHiddenClass}, - {"isPrimitive", "()Z", (void *)&JVM_IsPrimitiveClass}, - {"getModifiers", "()I", (void *)&JVM_GetClassModifiers}, - {"getDeclaredFields0","(Z)[" FLD, (void *)&JVM_GetClassDeclaredFields}, - {"getDeclaredMethods0","(Z)[" MHD, (void *)&JVM_GetClassDeclaredMethods}, - {"getDeclaredConstructors0","(Z)[" CTR, (void *)&JVM_GetClassDeclaredConstructors}, - {"getProtectionDomain0", "()" PD, (void *)&JVM_GetProtectionDomain}, - {"getDeclaredClasses0", "()[" CLS, (void *)&JVM_GetDeclaredClasses}, - {"getDeclaringClass0", "()" CLS, (void *)&JVM_GetDeclaringClass}, - {"getSimpleBinaryName0", "()" STR, (void *)&JVM_GetSimpleBinaryName}, - {"getGenericSignature0", "()" STR, (void *)&JVM_GetClassSignature}, - {"getRawAnnotations", "()" BA, (void *)&JVM_GetClassAnnotations}, - {"getConstantPool", "()" CPL, (void *)&JVM_GetClassConstantPool}, - {"desiredAssertionStatus0","("CLS")Z", (void *)&JVM_DesiredAssertionStatus}, - {"getEnclosingMethod0", "()[" OBJ, (void *)&JVM_GetEnclosingMethodInfo}, - {"getRawTypeAnnotations", "()" BA, (void *)&JVM_GetClassTypeAnnotations}, - {"getNestHost0", "()" CLS, (void *)&JVM_GetNestHost}, - {"getNestMembers0", "()[" CLS, (void *)&JVM_GetNestMembers}, - {"getRecordComponents0", "()[" RC, (void *)&JVM_GetRecordComponents}, - {"isRecord0", "()Z", (void *)&JVM_IsRecord}, - {"getPermittedSubclasses0", "()[" CLS, (void *)&JVM_GetPermittedSubclasses}, - {"getClassFileVersion0", "()I", (void *)&JVM_GetClassFileVersion}, - {"getClassAccessFlagsRaw0", "()I", (void *)&JVM_GetClassAccessFlags}, + {"initClassName", "()" STR, (void *)&JVM_InitClassName}, + {"getSuperclass", "()" CLS, NULL}, + {"getInterfaces0", "()[" CLS, (void *)&JVM_GetClassInterfaces}, + {"isInterface", "()Z", (void *)&JVM_IsInterface}, + {"getSigners", "()[" OBJ, (void *)&JVM_GetClassSigners}, + {"setSigners", "([" OBJ ")V", (void *)&JVM_SetClassSigners}, + {"isArray", "()Z", (void *)&JVM_IsArrayClass}, + {"isHidden", "()Z", (void *)&JVM_IsHiddenClass}, + {"isPrimitive", "()Z", (void *)&JVM_IsPrimitiveClass}, + {"getModifiers", "()I", (void *)&JVM_GetClassModifiers}, + {"getDeclaredFields0","(Z)[" FLD, (void *)&JVM_GetClassDeclaredFields}, + {"getDeclaredMethods0","(Z)[" MHD, (void *)&JVM_GetClassDeclaredMethods}, + {"getDeclaredDeconstructors0","(Z)[" MHD, (void *)&JVM_GetClassDeclaredDeconstructors}, + {"getDeclaredConstructors0","(Z)[" CTR, (void *)&JVM_GetClassDeclaredConstructors}, + {"getProtectionDomain0", "()" PD, (void *)&JVM_GetProtectionDomain}, + {"getDeclaredClasses0", "()[" CLS, (void *)&JVM_GetDeclaredClasses}, + {"getDeclaringClass0", "()" CLS, (void *)&JVM_GetDeclaringClass}, + {"getSimpleBinaryName0", "()" STR, (void *)&JVM_GetSimpleBinaryName}, + {"getGenericSignature0", "()" STR, (void *)&JVM_GetClassSignature}, + {"getRawAnnotations", "()" BA, (void *)&JVM_GetClassAnnotations}, + {"getConstantPool", "()" CPL, (void *)&JVM_GetClassConstantPool}, + {"desiredAssertionStatus0","("CLS")Z", (void *)&JVM_DesiredAssertionStatus}, + {"getEnclosingMethod0", "()[" OBJ, (void *)&JVM_GetEnclosingMethodInfo}, + {"getRawTypeAnnotations", "()" BA, (void *)&JVM_GetClassTypeAnnotations}, + {"getNestHost0", "()" CLS, (void *)&JVM_GetNestHost}, + {"getNestMembers0", "()[" CLS, (void *)&JVM_GetNestMembers}, + {"getRecordComponents0", "()[" RC, (void *)&JVM_GetRecordComponents}, + {"isRecord0", "()Z", (void *)&JVM_IsRecord}, + {"getPermittedSubclasses0", "()[" CLS, (void *)&JVM_GetPermittedSubclasses}, + {"getClassFileVersion0", "()I", (void *)&JVM_GetClassFileVersion}, + {"getClassAccessFlagsRaw0", "()I", (void *)&JVM_GetClassAccessFlags}, }; #undef OBJ diff --git a/src/java.compiler/share/classes/javax/lang/model/element/ElementKind.java b/src/java.compiler/share/classes/javax/lang/model/element/ElementKind.java index 4190e2acdab..66c2e9002c0 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/ElementKind.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/ElementKind.java @@ -71,6 +71,8 @@ public enum ElementKind { LOCAL_VARIABLE, /** A parameter of an exception handler. */ EXCEPTION_PARAMETER, + /** A binding of a pattern declaration. */ + PATTERN_BINDING, // Executables /** A method. */ @@ -120,8 +122,16 @@ public enum ElementKind { /** * A binding variable in a pattern. * @since 16 + * + */ + BINDING_VARIABLE, + + /** + * A deconstructor. + * @since 23 + * */ - BINDING_VARIABLE; + DECONSTRUCTOR; // Maintenance note: check if the default implementation of // Elements.getOutermostTypeElement needs updating when new kind @@ -178,7 +188,7 @@ public boolean isField() { */ public boolean isExecutable() { return switch(this) { - case METHOD, CONSTRUCTOR, STATIC_INIT, INSTANCE_INIT -> true; + case METHOD, CONSTRUCTOR, STATIC_INIT, INSTANCE_INIT, DECONSTRUCTOR -> true; default -> false; }; } diff --git a/src/java.compiler/share/classes/javax/lang/model/element/ElementVisitor.java b/src/java.compiler/share/classes/javax/lang/model/element/ElementVisitor.java index 03ca36ba434..7a8a433980e 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/ElementVisitor.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/ElementVisitor.java @@ -222,4 +222,19 @@ default R visitModule(ModuleElement e, P p) { default R visitRecordComponent(RecordComponentElement e, P p) { return visitUnknown(e, p); } + + /** + * Visits a pattern declaration executable element. + * + * @implSpec The default implementation visits an {@code + * ExecutableElement} by calling {@code visitUnknown(e, p)}. + * + * @param e the element to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + * @since 23 + */ + default R visitExecutableAsDeconstructor(ExecutableElement e, P p) { + return visitUnknown(e, p); + } } diff --git a/src/java.compiler/share/classes/javax/lang/model/element/ExecutableElement.java b/src/java.compiler/share/classes/javax/lang/model/element/ExecutableElement.java index 8bff3748394..e791546ab79 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/ExecutableElement.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/ExecutableElement.java @@ -158,4 +158,13 @@ public interface ExecutableElement extends Element, Parameterizable { */ @Override Name getSimpleName(); + + /** + * Returns the binding parameters of this executable. + * They are returned in declaration order. + * + * @return the binding parameters, + * or an empty list if there are none or is not a pattern declaration. + */ + List getBindings(); } diff --git a/src/java.compiler/share/classes/javax/lang/model/element/Modifier.java b/src/java.compiler/share/classes/javax/lang/model/element/Modifier.java index 9dcd8390b60..651459053ea 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/Modifier.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/Modifier.java @@ -60,6 +60,11 @@ public enum Modifier { * @since 1.8 */ DEFAULT, + /** + * The modifier {@code default} + * @since 23 + */ + PATTERN, /** The modifier {@code static} */ STATIC, /** diff --git a/src/java.compiler/share/classes/javax/lang/model/type/ExecutableType.java b/src/java.compiler/share/classes/javax/lang/model/type/ExecutableType.java index 19953ff5e4a..7dffe93398f 100644 --- a/src/java.compiler/share/classes/javax/lang/model/type/ExecutableType.java +++ b/src/java.compiler/share/classes/javax/lang/model/type/ExecutableType.java @@ -71,6 +71,14 @@ public interface ExecutableType extends TypeMirror { */ List getParameterTypes(); + /** + * Returns the types of this executable's binding parameters. + * + * @return the types of this executable's binding parameters, + * or an empty list if there are none or is not a pattern declaration. + */ + List getBindingTypes(); + /** * Returns the receiver type of this executable, * or {@link javax.lang.model.type.NoType NoType} with diff --git a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java index e574a5b148a..1e052ae7d54 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitorPreview.java @@ -31,6 +31,7 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.ElementVisitor; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.RecordComponentElement; import static javax.lang.model.SourceVersion.*; @@ -65,4 +66,17 @@ public abstract class AbstractElementVisitorPreview extends AbstractElemen protected AbstractElementVisitorPreview(){ super(); } + + /** + * {@inheritDoc ElementVisitor} + * + * @implSpec Visits an {@code ExecutableElement} in a manner defined by a + * subclass. + * + * @param e {@inheritDoc ElementVisitor} + * @param p {@inheritDoc ElementVisitor} + * @return {@inheritDoc ElementVisitor} + */ + @Override + public abstract R visitExecutableAsDeconstructor(ExecutableElement e, P p); } diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementFilter.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementFilter.java index 88c74322c39..31038556644 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementFilter.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementFilter.java @@ -68,6 +68,8 @@ private ElementFilter() {} // Do not instantiate. ElementKind.ENUM_CONSTANT)); private static final Set METHOD_KIND = Collections.unmodifiableSet(EnumSet.of(ElementKind.METHOD)); + private static final Set DECONSTRUCTOR_KIND = + Collections.unmodifiableSet(EnumSet.of(ElementKind.DECONSTRUCTOR)); private static final Set PACKAGE_KIND = Collections.unmodifiableSet(EnumSet.of(ElementKind.PACKAGE)); @@ -159,6 +161,24 @@ private ElementFilter() {} // Do not instantiate. return setFilter(elements, METHOD_KIND, ExecutableElement.class); } + /** + * {@return a list of methods in {@code elements}} + * @param elements the elements to filter + */ + public static List + deconstructorsIn(Iterable elements) { + return listFilter(elements, DECONSTRUCTOR_KIND, ExecutableElement.class); + } + + /** + * {@return a set of methods in {@code elements}} + * @param elements the elements to filter + */ + public static Set + deconstructorsIn(Set elements) { + return setFilter(elements, DECONSTRUCTOR_KIND, ExecutableElement.class); + } + /** * {@return a list of classes and interfaces in {@code elements}} * @param elements the elements to filter diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor6.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor6.java index 41ad8ffe218..9be1bf538b6 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor6.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor6.java @@ -28,7 +28,7 @@ import javax.lang.model.element.*; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; -import javax.lang.model.element.ElementVisitor; + import static javax.lang.model.element.ElementKind.*; import static javax.lang.model.SourceVersion.*; @@ -264,6 +264,9 @@ public R visitVariable(VariableElement e, P p) { case BINDING_VARIABLE: return visitVariableAsBindingVariable(e, p); + case PATTERN_BINDING: + return visitVariableAsParameter(e, p); + default: throw new AssertionError("Bad kind " + k + " for VariableElement" + e); } @@ -392,6 +395,9 @@ public R visitExecutable(ExecutableElement e, P p) { case STATIC_INIT: return visitExecutableAsStaticInit(e, p); + case DECONSTRUCTOR: + return visitExecutableAsDeconstructor(e, p); + default: throw new AssertionError("Bad kind " + k + " for ExecutableElement" + e); } diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java index 8a59d91b886..1fe68c73ebf 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorPreview.java @@ -88,4 +88,17 @@ protected ElementKindVisitorPreview() { protected ElementKindVisitorPreview(R defaultValue) { super(defaultValue); } + + /** + * Visits a {@code PATTERN_DECLARATION} executable element. + * + * @implSpec This implementation calls {@code defaultAction}. + * + * @param e the element to visit + * @param p a visitor-specified parameter + * @return the result of {@code defaultAction} + */ + public R visitExecutableAsDeconstructor(ExecutableElement e, P p) { + return defaultAction(e, p); + } } diff --git a/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java b/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java index 2935958e4b4..4608d1f3700 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScannerPreview.java @@ -105,4 +105,31 @@ protected ElementScannerPreview(){ protected ElementScannerPreview(R defaultValue){ super(defaultValue); } + + /** + * {@inheritDoc ElementVisitor} + * + * @implSpec This implementation scans the enclosed elements. + * + * @param e {@inheritDoc ElementVisitor} + * @param p {@inheritDoc ElementVisitor} + * @return {@inheritDoc ElementScanner6} + * @since N + */ + @Override + public R visitExecutableAsDeconstructor(ExecutableElement e, P p) { + return scan(createScanningList(e, e.getBindings()), p); + } + + private List createScanningList(ExecutableElement element, + List toBeScanned) { + var typeParameters = element.getTypeParameters(); + if (typeParameters.isEmpty()) { + return toBeScanned; + } else { + List scanningList = new ArrayList<>(typeParameters); + scanningList.addAll(toBeScanned); + return scanningList; + } + } } diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/MatchTree.java b/src/jdk.compiler/share/classes/com/sun/source/tree/MatchTree.java new file mode 100644 index 00000000000..a95bf85fc00 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/MatchTree.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019, 2020, 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 com.sun.source.tree; + +import jdk.internal.javac.PreviewFeature; + +import java.util.List; + +/** + * A tree node for a {@code match} statement. + * + * For example: + *

    + *   match name ( parameters );
    + * 
    + * + * @jls XX.XX The match Statement + * + * @since 23 + */ +public interface MatchTree extends StatementTree { + + /** + * Returns the expression for this {@code match} statement. + * + * @return the expression + */ + @PreviewFeature(feature= PreviewFeature.Feature.PATTERN_DECLARATIONS, reflective=true) + List getArguments(); +} diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/MethodTree.java b/src/jdk.compiler/share/classes/com/sun/source/tree/MethodTree.java index f6dae4c3ced..cfebbffb968 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/tree/MethodTree.java +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/MethodTree.java @@ -82,6 +82,12 @@ public interface MethodTree extends Tree { */ List getParameters(); + /** + * Returns the bindings of the pattern declaration being declared. + * @return the bindings + */ + List getBindings(); + /** * Return an explicit receiver parameter ("this" parameter), * or {@code null} if none. diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java b/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java index 4b7df6185d6..af444818faa 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java @@ -25,6 +25,8 @@ package com.sun.source.tree; +import jdk.internal.javac.PreviewFeature; + /** * Common interface for all nodes in an abstract syntax tree. * @@ -704,8 +706,15 @@ public enum Kind { * * @since 14 */ - YIELD(YieldTree.class); + YIELD(YieldTree.class), + /** + * Used for instances of {@link MatchTree}. + * + * @since 23 + */ + @PreviewFeature(feature=PreviewFeature.Feature.PATTERN_DECLARATIONS, reflective=true) + MATCH(MatchTree.class); Kind(Class intf) { associatedInterface = intf; diff --git a/src/jdk.compiler/share/classes/com/sun/source/tree/TreeVisitor.java b/src/jdk.compiler/share/classes/com/sun/source/tree/TreeVisitor.java index b7851d8ff9a..0947d0e38ff 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/tree/TreeVisitor.java +++ b/src/jdk.compiler/share/classes/com/sun/source/tree/TreeVisitor.java @@ -613,4 +613,12 @@ public interface TreeVisitor { * @since 14 */ R visitYield(YieldTree node, P p); + + /** + * Visits an {@code MatchTree} node. + * @param node the node being visited + * @param p a parameter value + * @return a result value + */ + R visitMatchStatement(MatchTree node, P p); } diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java b/src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java index 1a123a49faa..c16140ba69e 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java @@ -1056,4 +1056,18 @@ public R visitOther(Tree node, P p) { public R visitYield(YieldTree node, P p) { return defaultAction(node, p); } + + /** + * {@inheritDoc} + * + * @implSpec This implementation calls {@code defaultAction}. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of {@code defaultAction} + */ + @Override + public R visitMatchStatement(MatchTree node, P p) { + return defaultAction(node, p); + } } diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java b/src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java index 4327c6f5f31..85db93f533d 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java @@ -218,6 +218,7 @@ public R visitMethod(MethodTree node, P p) { r = scanAndReduce(node.getReturnType(), p, r); r = scanAndReduce(node.getTypeParameters(), p, r); r = scanAndReduce(node.getParameters(), p, r); + r = scanAndReduce(node.getBindings(), p, r); r = scanAndReduce(node.getReceiverParameter(), p, r); r = scanAndReduce(node.getThrows(), p, r); r = scanAndReduce(node.getBody(), p, r); @@ -1209,4 +1210,18 @@ public R visitErroneous(ErroneousTree node, P p) { public R visitYield(YieldTree node, P p) { return scan(node.getValue(), p); } + + /** + * {@inheritDoc} + * + * @implSpec This implementation scans the children in left to right order. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of scanning + */ + @Override + public R visitMatchStatement(MatchTree node, P p) { + return scan(node.getArguments(), p); + } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java index 346aab18a60..1ce527de20a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java @@ -127,6 +127,10 @@ public static EnumSet asFlagSet(long flags) { */ public static final int IMPLICIT_CLASS = 1<<19; + /** Method is a matcher method. + */ + public static final int PATTERN = 1<<19; // MethodSymbols + /** Flag is set for compiler-generated anonymous method symbols * that `own' an initializer block. */ @@ -426,13 +430,15 @@ public static String toSource(long flags) { MethodFlags = AccessFlags | ABSTRACT | STATIC | NATIVE | SYNCHRONIZED | FINAL | STRICTFP, RecordMethodFlags = AccessFlags | ABSTRACT | STATIC | - SYNCHRONIZED | FINAL | STRICTFP; + SYNCHRONIZED | FINAL | STRICTFP, + MatcherMethodFlags = AccessFlags | STATIC | FINAL | SYNCHRONIZED | ABSTRACT; + public static final long ExtendedStandardFlags = (long)StandardFlags | DEFAULT | SEALED | NON_SEALED, ExtendedMemberClassFlags = (long)MemberClassFlags | SEALED | NON_SEALED, ExtendedMemberStaticClassFlags = (long) MemberStaticClassFlags | SEALED | NON_SEALED, ExtendedClassFlags = (long)ClassFlags | SEALED | NON_SEALED, - ModifierFlags = ((long)StandardFlags & ~INTERFACE) | DEFAULT | SEALED | NON_SEALED, + ModifierFlags = ((long)StandardFlags & ~INTERFACE) | DEFAULT | SEALED | NON_SEALED | PATTERN, InterfaceMethodMask = ABSTRACT | PRIVATE | STATIC | PUBLIC | STRICTFP | DEFAULT, AnnotationTypeElementMask = ABSTRACT | PUBLIC, LocalVarFlags = FINAL | PARAMETER, @@ -458,6 +464,8 @@ public static Set asModifierSet(long flags) { if (0 != (flags & NATIVE)) modifiers.add(Modifier.NATIVE); if (0 != (flags & STRICTFP)) modifiers.add(Modifier.STRICTFP); if (0 != (flags & DEFAULT)) modifiers.add(Modifier.DEFAULT); + if (0 != (flags & PATTERN)) modifiers.add(Modifier.PATTERN); + modifiers = Collections.unmodifiableSet(modifiers); modifierSets.put(flags, modifiers); } @@ -500,6 +508,7 @@ public enum Flag { DEPRECATED(Flags.DEPRECATED), HASINIT(Flags.HASINIT), IMPLICIT_CLASS(Flags.IMPLICIT_CLASS), + PATTERN(Flags.PATTERN), BLOCK(Flags.BLOCK), FROM_SOURCE(Flags.FROM_SOURCE), ENUM(Flags.ENUM), diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Kinds.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Kinds.java index e5d9a9e2ac4..c3bf81f83ab 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Kinds.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Kinds.java @@ -64,6 +64,7 @@ public enum Kind { TYP(Category.BASIC, KindName.CLASS, KindSelector.TYP), VAR(Category.BASIC, KindName.VAR, KindSelector.VAR), MTH(Category.BASIC, KindName.METHOD, KindSelector.MTH), + PD(Category.BASIC, KindName.PATTERN_DECLARATION, KindSelector.MTH), POLY(Category.BASIC, KindSelector.POLY), MDL(Category.BASIC, KindSelector.MDL), ERR(Category.ERROR, KindSelector.ERR), @@ -234,6 +235,7 @@ public enum KindName implements Formattable { CLASS("kindname.class"), STATIC_INIT("kindname.static.init"), INSTANCE_INIT("kindname.instance.init"), + PATTERN_DECLARATION("kindname.pattern.declaration"), PACKAGE("kindname.package"), MODULE("kindname.module"), RECORD_COMPONENT("kindname.record.component"), @@ -296,6 +298,7 @@ public static KindName kindName(Symbol sym) { case LOCAL_VARIABLE: case EXCEPTION_PARAMETER: case RESOURCE_VARIABLE: + case PATTERN_BINDING: return KindName.VAR; case FIELD: @@ -310,6 +313,8 @@ public static KindName kindName(Symbol sym) { return KindName.STATIC_INIT; case INSTANCE_INIT: return KindName.INSTANCE_INIT; + case DECONSTRUCTOR: + return KindName.PATTERN_DECLARATION; default: throw new AssertionError("Unexpected kind: "+sym.getKind()); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java index 5e9a75a0b6b..56b45ea4463 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java @@ -208,6 +208,7 @@ public boolean isEnabled() { public boolean isPreview(Feature feature) { return switch (feature) { case IMPLICIT_CLASSES -> true; + case PATTERN_DECLARATIONS -> true; case FLEXIBLE_CONSTRUCTORS -> true; case PRIMITIVE_PATTERNS -> true; case MODULE_IMPORTS -> true; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java index 6288cbfb01b..0c1e2a840f6 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java @@ -256,6 +256,7 @@ public enum Feature { PRIMITIVE_PATTERNS(JDK23, Fragments.FeaturePrimitivePatterns, DiagKind.PLURAL), FLEXIBLE_CONSTRUCTORS(JDK22, Fragments.FeatureFlexibleConstructors, DiagKind.NORMAL), MODULE_IMPORTS(JDK23, Fragments.FeatureModuleImports, DiagKind.PLURAL), + PATTERN_DECLARATIONS(JDK23, Fragments.FeaturePatternDeclarations, DiagKind.PLURAL), ; enum DiagKind { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java index ca420eecdc5..8249382bb0c 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java @@ -30,7 +30,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; -import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; @@ -83,6 +82,7 @@ import static com.sun.tools.javac.jvm.ByteCodes.lushrl; import static com.sun.tools.javac.jvm.ByteCodes.lxor; import static com.sun.tools.javac.jvm.ByteCodes.string_add; +import java.util.stream.Collectors; /** Root class for Java symbols. It contains subclasses * for specific sorts of symbols, such as variables, methods and operators, @@ -363,6 +363,8 @@ public Type externalType(Types types) { t.getReturnType(), t.getThrownTypes(), t.tsym); + } else if ((flags() & PATTERN) != 0) { + return new MethodType(List.of(owner.erasure(types)), types.syms.objectType, List.nil(), t.tsym); } else { return t; } @@ -464,6 +466,18 @@ public boolean isConstructor() { return name == name.table.names.init; } + /** Is this symbol a pattern declaration? + */ + public boolean isPattern() { + return (flags() & PATTERN) != 0; + } + + /** Is this symbol a deconstructor? + */ + public boolean isDeconstructor() { + return isPattern() && name == owner.name; + } + public boolean isDynamic() { return false; } @@ -991,7 +1005,6 @@ public static class ModuleSymbol extends TypeSymbol public Completer usesProvidesCompleter = Completer.NULL_COMPLETER; public final Set flags = EnumSet.noneOf(ModuleFlags.class); public final Set resolutionFlags = EnumSet.noneOf(ModuleResolutionFlags.class); - /** * Create a ModuleSymbol with an associated module-info ClassSymbol. */ @@ -1141,6 +1154,24 @@ private ModuleResolutionFlags(int value) { public final int value; } + public enum PatternFlags { + DECONSTRUCTOR(0x3000), + TOTAL(0x4000); + + public static int value(Set s) { + int v = 0; + for (PatternFlags f: s) + v |= f.value; + return v; + } + + private PatternFlags(int value) { + this.value = value; + } + + public final int value; + } + /** A class for package symbols */ public static class PackageSymbol extends TypeSymbol @@ -1751,7 +1782,9 @@ public Symbol asMemberOf(Type site, Types types) { @DefinedBy(Api.LANGUAGE_MODEL) public ElementKind getKind() { long flags = flags(); - if ((flags & PARAMETER) != 0) { + if (owner.kind == MTH && owner.isPattern()) { + return ElementKind.PATTERN_BINDING; + } else if ((flags & PARAMETER) != 0) { if (isExceptionParameter()) return ElementKind.EXCEPTION_PARAMETER; else @@ -1963,12 +1996,18 @@ public static class MethodSymbol extends Symbol implements ExecutableElement { /** The parameters of the method. */ public List params = null; + /** The bindings of the method if it is a pattern declaration. */ + public List bindings = null; + /** For an annotation type element, its default value if any. * The value is null if none appeared in the method * declaration. */ public Attribute defaultValue = null; + public final Set patternFlags = EnumSet.noneOf(PatternFlags.class); + + /** Construct a method symbol, given its flags, name, type and owner. */ public MethodSymbol(long flags, Name name, Type type, Symbol owner) { @@ -2029,6 +2068,68 @@ public boolean isHandle() { return false; } + public Name externalName(Types types) { + if ((flags() & PATTERN) != 0) { + /** TODO: improve perf + * e.g., Point\%Ljava\|lang\|Integer\?\%Ljava\|lang\|Integer\?(Point) + */ + Name postFix = name.table.names.fromString(bindings().map(param -> { + var g = new UnSharedSignatureGenerator(types, name.table.names); + g.assembleSig(param.erasure(types)); + return name.table.names.fromString(g.toName().toString().replace("/", "\\\u007C").replace(";", "\\\u003F")); + }).stream().collect(Collectors.joining("\\\u0025"))); + + return name.table.names.fromString(owner.name.toString() + "\\\u0025" + postFix); + } else { + return name; + } + } + + static class UnSharedSignatureGenerator extends Types.SignatureGenerator { + + /** + * An output buffer for type signatures. + */ + ByteBuffer sigbuf = new ByteBuffer(); + private final Names names; + + UnSharedSignatureGenerator(Types types, Names names) { + super(types); + this.names = names; + } + + @Override + protected void append(char ch) { + sigbuf.appendByte(ch); + } + + @Override + protected void append(byte[] ba) { + sigbuf.appendBytes(ba); + } + + @Override + protected void append(Name name) { + sigbuf.appendName(name); + } + + @Override + protected void classReference(ClassSymbol c) { + // enterInner(c); + } + + protected void reset() { + sigbuf.reset(); + } + + protected Name toName() { + try { + return sigbuf.toName(names); + } catch (InvalidUtfException e) { + throw new AssertionError(e); + } + } + } public MethodHandleSymbol asHandle() { return new MethodHandleSymbol(this); @@ -2256,6 +2357,23 @@ public List params() { return params; } + public List bindings() { + owner.complete(); + if (bindings == null) { + ListBuffer newBindings = new ListBuffer<>(); + int i = 0; + for (Type t : type.getBindingTypes()) { + Name bindingName = name.table.fromString("bind" + i); + VarSymbol binding = new VarSymbol(PARAMETER, bindingName, t, this); + newBindings.append(binding); + i++; + } + bindings = newBindings.toList(); + } + Assert.checkNonNull(bindings); + return bindings; + } + public Symbol asMemberOf(Type site, Types types) { return new MethodSymbol(flags_field, name, types.memberType(site, this), owner); } @@ -2268,6 +2386,8 @@ else if (name == name.table.names.clinit) return ElementKind.STATIC_INIT; else if ((flags() & BLOCK) != 0) return isStatic() ? ElementKind.STATIC_INIT : ElementKind.INSTANCE_INIT; + else if (isPattern()) + return ElementKind.DECONSTRUCTOR; else return ElementKind.METHOD; } @@ -2287,6 +2407,11 @@ public List getParameters() { return params(); } + @DefinedBy(Api.LANGUAGE_MODEL) + public List getBindings() { + return bindings(); + } + @DefinedBy(Api.LANGUAGE_MODEL) public boolean isVarArgs() { return (flags() & VARARGS) != 0; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java index 17173e480f1..8a457be687f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java @@ -178,6 +178,7 @@ public static Symtab instance(Context context) { public final Type methodHandlesType; public final Type methodHandleLookupType; public final Type methodTypeType; + public final Type carriersType; public final Type nativeHeaderType; public final Type throwableType; public final Type errorType; @@ -556,6 +557,8 @@ public R accept(ElementVisitor v, P p) { methodHandlesType = enterClass("java.lang.invoke.MethodHandles"); methodHandleLookupType = enterClass("java.lang.invoke.MethodHandles$Lookup"); methodTypeType = enterClass("java.lang.invoke.MethodType"); + carriersType = enterClass("java.lang.runtime.Carriers"); + errorType = enterClass("java.lang.Error"); illegalArgumentExceptionType = enterClass("java.lang.IllegalArgumentException"); interruptedExceptionType = enterClass("java.lang.InterruptedException"); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java index 702b51d3b96..09affc86900 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java @@ -285,20 +285,27 @@ protected boolean needsStripping() { @Override public Type visitMethodType(MethodType t, S s) { List argtypes = t.argtypes; + List bindingtypes = t.bindingtypes; Type restype = t.restype; List thrown = t.thrown; + List bindingtypes1 = bindingtypes != null ? visit(bindingtypes, s) : null; List argtypes1 = visit(argtypes, s); Type restype1 = visit(restype, s); List thrown1 = visit(thrown, s); if (argtypes1 == argtypes && + bindingtypes1 == bindingtypes && restype1 == restype && thrown1 == thrown) return t; - else return new MethodType(argtypes1, restype1, thrown1, t.tsym) { - @Override - protected boolean needsStripping() { - return true; - } - }; + else { + MethodType methodType = new MethodType(argtypes1, restype1, thrown1, t.tsym) { + @Override + protected boolean needsStripping() { + return true; + } + }; + methodType.bindingtypes = bindingtypes1; + return methodType; + } } @Override @@ -594,6 +601,7 @@ public String argtypes(boolean varargs) { public List getTypeArguments() { return List.nil(); } public Type getEnclosingType() { return null; } public List getParameterTypes() { return List.nil(); } + public List getBindingTypes() { return List.nil(); } public Type getReturnType() { return null; } public Type getReceiverType() { return null; } public List getThrownTypes() { return List.nil(); } @@ -1471,6 +1479,7 @@ public R accept(TypeVisitor v, P p) { public static class MethodType extends Type implements ExecutableType, LoadableConstant { public List argtypes; + public List bindingtypes; public Type restype; public List thrown; @@ -1517,6 +1526,10 @@ public String toString() { @DefinedBy(Api.LANGUAGE_MODEL) public List getParameterTypes() { return argtypes; } + + @DefinedBy(Api.LANGUAGE_MODEL) + public List getBindingTypes() { return bindingtypes; } + @DefinedBy(Api.LANGUAGE_MODEL) public Type getReturnType() { return restype; } @DefinedBy(Api.LANGUAGE_MODEL) @@ -1529,6 +1542,7 @@ public Type getReceiverType() { public boolean isErroneous() { return isErroneous(argtypes) || + bindingtypes != null && isErroneous(bindingtypes) || restype != null && restype.isErroneous(); } @@ -1546,6 +1560,8 @@ public boolean contains(Type elem) { public void complete() { for (List l = argtypes; l.nonEmpty(); l = l.tail) l.head.complete(); + for (List l = bindingtypes; l.nonEmpty(); l = l.tail) + l.head.complete(); restype.complete(); recvtype.complete(); for (List l = thrown; l.nonEmpty(); l = l.tail) @@ -1832,6 +1848,7 @@ public DelegatedType(TypeTag tag, Type qtype, public List getTypeArguments() { return qtype.getTypeArguments(); } public Type getEnclosingType() { return qtype.getEnclosingType(); } public List getParameterTypes() { return qtype.getParameterTypes(); } + public List getBindingTypes() { return qtype.getBindingTypes(); } public Type getReturnType() { return qtype.getReturnType(); } public Type getReceiverType() { return qtype.getReceiverType(); } public List getThrownTypes() { return qtype.getThrownTypes(); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java index 0e573844232..d3a0ceb852e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java @@ -399,7 +399,8 @@ private void separateAnnotationsKinds(JCTree pos, JCTree typetree, Type type, sym.getKind() == ElementKind.LOCAL_VARIABLE || sym.getKind() == ElementKind.RESOURCE_VARIABLE || sym.getKind() == ElementKind.EXCEPTION_PARAMETER || - sym.getKind() == ElementKind.BINDING_VARIABLE) { + sym.getKind() == ElementKind.BINDING_VARIABLE || + sym.getKind() == ElementKind.PATTERN_BINDING) { appendTypeAnnotationsToOwner(sym, typeAnnotations); } } @@ -1129,7 +1130,7 @@ public void visitMethodDef(final JCMethodDecl tree) { } if (sigOnly) { if (!tree.mods.annotations.isEmpty()) { - if (tree.sym.isConstructor()) { + if (tree.sym.isConstructor() || tree.sym.isPattern()) { final TypeAnnotationPosition pos = TypeAnnotationPosition.methodReturn(tree.pos); // Use null to mark that the annotations go diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java index 28768f2517b..490a95f825d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java @@ -89,7 +89,7 @@ public class Types { protected static final Context.Key typesKey = new Context.Key<>(); - final Symtab syms; + public final Symtab syms; final JavacMessages messages; final Names names; final Check chk; @@ -3286,8 +3286,11 @@ public Boolean visitType(Type t, Type s) { @Override public Boolean visitMethodType(MethodType t, Type s) { - return s.hasTag(METHOD) - && containsTypeEquivalent(t.argtypes, s.getParameterTypes()); + if (s.hasTag(METHOD)) { + if (t.bindingtypes != null && t.bindingtypes.size() > 0) return containsTypeEquivalent(t.bindingtypes, s.getBindingTypes()); + else return containsTypeEquivalent(t.argtypes, s.getParameterTypes()); + } + return false; } @Override @@ -5192,7 +5195,11 @@ public void assembleSig(Type type) { case METHOD: MethodType mt = (MethodType) type; append('('); - assembleSig(mt.argtypes); + if (mt.bindingtypes != null && mt.bindingtypes.size() > 0) { + assembleSig(mt.bindingtypes); + } else { + assembleSig(mt.argtypes); + } append(')'); assembleSig(mt.restype); if (hasTypeVar(mt.thrown)) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 2c735909a08..f61255c9722 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -1040,6 +1040,11 @@ public void visitMethodDef(JCMethodDecl tree) { attribStat(l.head, localEnv); } + // Attribute all bindings. + for (List l = tree.bindings; l != null && l.nonEmpty(); l = l.tail) { + attribStat(l.head, localEnv); + } + chk.checkVarargsMethodDecl(localEnv, tree); // Check that type parameters are well-formed. @@ -1152,6 +1157,14 @@ public void visitMethodDef(JCMethodDecl tree) { } } + if (m.isPattern() && tree.thrown.nonEmpty()) { + log.error(tree.pos(), Errors.PatternDeclarationCantThrowException); + } + + if (m.isDeconstructor()) { + m.patternFlags.add(PatternFlags.DECONSTRUCTOR); + } + // annotation method checks if ((owner.flags() & ANNOTATION) != 0) { // annotation method cannot have throws clause @@ -1185,7 +1198,7 @@ public void visitMethodDef(JCMethodDecl tree) { if (isDefaultMethod || (tree.sym.flags() & (ABSTRACT | NATIVE)) == 0) log.error(tree.pos(), Errors.MissingMethBodyOrDeclAbstract); } else { - if ((tree.sym.flags() & (ABSTRACT|DEFAULT|PRIVATE)) == ABSTRACT) { + if ((tree.sym.flags() & (ABSTRACT|DEFAULT|PRIVATE)) == ABSTRACT && !m.isPattern()) { if ((owner.flags() & INTERFACE) != 0) { log.error(tree.body.pos(), Errors.IntfMethCantHaveBody); } else { @@ -2372,6 +2385,36 @@ public void visitYield(JCYield tree) { result = null; } + public void visitMatch(JCMatch tree) { + Env localEnv = env.dup(tree, env.info.dup()); + tree.meth = localEnv.enclMethod; + + List bindingTypes = tree.meth.sym.type.getBindingTypes(); + List args = tree.args; + + if (!tree.meth.sym.isPattern()) { + log.error(tree.pos(), Errors.MatchOutsidePatternDeclaration); + return; + } + + if (bindingTypes.size() != args.size()) { + log.error(tree.pos(), Errors.MatchNotMatchingPatternDeclarationSignature); + } + + while (bindingTypes.nonEmpty() && args.nonEmpty()) { + attribExpr(args.head, localEnv, bindingTypes.head); + + bindingTypes = bindingTypes.tail; + args = args.tail; + } + + if (tree.clazz != tree.meth.name) { + log.error(tree.pos(), Errors.MatchPatternNameWrong); + } + + result = null; + } + public void visitContinue(JCContinue tree) { tree.target = findJumpTarget(tree.pos(), tree.getTag(), tree.label, env); result = null; @@ -2479,9 +2522,12 @@ else if (tag == CONTINUE) } public void visitReturn(JCReturn tree) { - // Check that there is an enclosing method which is - // nested within than the enclosing class. - if (env.info.returnResult == null) { + // Check that there is an enclosing method (not a pattern declaration) + // which is nested within than the enclosing class. + if (env.enclMethod != null && env.info.returnResult != null && env.enclMethod.sym.isPattern()) { + log.error(tree.pos(), Errors.RetOutsideMeth); + } + else if (env.info.returnResult == null) { log.error(tree.pos(), Errors.RetOutsideMeth); } else if (env.info.yieldResult != null) { log.error(tree.pos(), Errors.ReturnOutsideSwitchExpression); @@ -2514,7 +2560,11 @@ public void visitReturn(JCReturn tree) { public void visitThrow(JCThrow tree) { Type owntype = attribExpr(tree.expr, env, Type.noType); - chk.checkType(tree, owntype, syms.throwableType); + if (env.info.returnResult != null && env.enclMethod !=null && env.enclMethod.sym.isPattern()) { + log.error(tree.pos(), Errors.PatternDeclarationNoThrows); + } else { + chk.checkType(tree, owntype, syms.throwableType); + } result = null; } @@ -4262,22 +4312,53 @@ public void visitRecordPattern(JCRecordPattern tree) { site = types.capture(tree.type); } - List expectedRecordTypes; - if (site.tsym.kind == Kind.TYP && ((ClassSymbol) site.tsym).isRecord()) { + List expectedRecordTypes = null; + + // todo: type check cases where a record class has deconstructors as well + if (((ClassSymbol) site.tsym).isRecord()) { ClassSymbol record = (ClassSymbol) site.tsym; expectedRecordTypes = record.getRecordComponents() - .stream() - .map(rc -> types.memberType(site, rc)) - .map(t -> types.upward(t, types.captures(t)).baseType()) - .collect(List.collector()); + .stream() + .map(rc -> types.memberType(site, rc)) + .map(t -> types.upward(t, types.captures(t)).baseType()) + .collect(List.collector()); tree.record = record; - } else { + } + else if (site.tsym.kind == Kind.TYP) { + int nestedPatternCount = tree.nested.size(); + + // precalculate types of pattern components (as in method invocation) + ListBuffer patternTypesBuffer = new ListBuffer<>(); + for (JCTree arg : tree.nested.map(p -> p.getTree())) { + Type argtype = chk.checkNonVoid(arg, attribTree(arg, env, resultInfo)); + patternTypesBuffer.append(argtype); + } + List patternTypes = patternTypesBuffer.toList(); + + List patternDeclarations = getPatternDeclarationCandidates(site, nestedPatternCount); + + if (patternDeclarations.size() >= 1) { + MethodSymbol patternDeclaration = selectBestPatternDeclarationInScope(tree, + site, + patternDeclarations, + patternTypes); + if (patternDeclaration != null) { + expectedRecordTypes = types.memberType(site, patternDeclaration).getBindingTypes(); + tree.patternDeclaration = patternDeclaration; + } + } + } + + if (expectedRecordTypes == null) { log.error(tree.pos(), Errors.DeconstructionPatternOnlyRecords(site.tsym)); expectedRecordTypes = Stream.generate(() -> types.createErrorType(tree.type)) .limit(tree.nested.size()) .collect(List.collector()); tree.record = syms.errSymbol; + } else { + tree.fullComponentTypes = expectedRecordTypes; } + ListBuffer outBindings = new ListBuffer<>(); List recordTypes = expectedRecordTypes; List nestedPatterns = tree.nested; @@ -4310,6 +4391,70 @@ public void visitRecordPattern(JCRecordPattern tree) { matchBindings = new MatchBindings(outBindings.toList(), List.nil()); } + // todo: follow the protocol in Resolve::selectBest + private MethodSymbol selectBestPatternDeclarationInScope(JCRecordPattern tree, + Type site, + List patternDeclarations, + List patternTypes) { + List expectedRecordTypes = null; + ListBuffer score = new ListBuffer(); + + for (MethodSymbol matcher : patternDeclarations) { + int scoreForMatcher = 0; + + List matcherComponentTypes = matcher.bindings() + .stream() + .map(rc -> types.memberType(site, rc)) + .map(t -> types.upward(t, types.captures(t)).baseType()) + .collect(List.collector()); + + boolean applicable = true; + for (int i = 0; applicable && i < patternTypes.size(); i++) { + applicable &= types.isCastable(patternTypes.get(i), matcherComponentTypes.get(i)); + } + + if (applicable) { + // todo: need to separate scores for each parameter + for (int i = 0; i < patternTypes.size(); i++) { + if (types.isSameType(patternTypes.get(i), matcherComponentTypes.get(i))) { + scoreForMatcher += 2; + } else if (types.isCastable(patternTypes.get(i), matcherComponentTypes.get(i))) { + scoreForMatcher += 1; + } + } + score.add(scoreForMatcher); + } else { + score.add(-1); + } + } + + var maxScore = Collections.max(score); + List scoreList = score.toList(); + var indexOfMaxScore = scoreList.indexOf(maxScore); + + if (maxScore == -1) { + log.error(tree.pos(), + Errors.NoCompatibleMatcherFound); + } else if (scoreList.stream().filter(s -> s == maxScore).count() > 1) { + log.error(tree.pos(), + Errors.MatcherOverloadingAmbiguity); + } else { + return patternDeclarations.get(indexOfMaxScore); + } + return null; + } + + private static List getPatternDeclarationCandidates(Type site, int nestedPatternCount) { + var matchersIt = site.tsym.members() + .getSymbols(sym -> sym.isPattern() && sym.type.getBindingTypes().size() == nestedPatternCount) + .iterator(); + List patternDeclarations = Stream.generate(() -> null) + .takeWhile(x -> matchersIt.hasNext()) + .map(n -> (MethodSymbol) matchersIt.next()) + .collect(List.collector()); + return patternDeclarations; + } + public void visitIndexed(JCArrayAccess tree) { Type owntype = types.createErrorType(tree.type); Type atype = attribExpr(tree.indexed, env); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index 5a442bac302..d148cfdb6ce 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -3622,6 +3622,14 @@ Optional> getApplicableTargets(JCAnnotation a, Symbol s) { if (s.getKind() == ElementKind.RECORD_COMPONENT) { applicableTargets.add(names.RECORD_COMPONENT); } + } else if (target == names.DECONSTRUCTOR) { + if (s.kind == MTH && s.isDeconstructor()) { + applicableTargets.add(names.DECONSTRUCTOR); + } + } else if (target == names.PATTERN_BINDING) { + if (s.getKind() == ElementKind.PATTERN_BINDING) { + applicableTargets.add(names.PATTERN_BINDING); + } } else if (target == names.METHOD) { if (s.kind == MTH && !s.isConstructor()) applicableTargets.add(names.METHOD); @@ -4157,6 +4165,8 @@ boolean checkUnique(DiagnosticPosition pos, Symbol sym, Scope s) { duplicateErasureError(pos, sym, byName); sym.flags_field |= CLASH; return true; + } else if (sym.isPattern() && byName.isPattern() && types.hasSameArgs(sym.type, byName.type, false)) { + return true; } else if ((sym.flags() & MATCH_BINDING) != 0 && (byName.flags() & MATCH_BINDING) != 0 && (byName.flags() & MATCH_BINDING_TO_OUTER) == 0) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java index da008bc4b53..fb5323e35a8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java @@ -394,9 +394,10 @@ private JumpKind(Tag treeTag) { */ JCClassDecl initScanClass; - /** A pending exit. These are the statements return, break, and + /** A pending exit. These are the statements return, break, * continue. In addition, exception-throwing expressions or - * statements are put here when not known to be caught. This + * statements are put here when not known to be caught. + * In the case of pattern declarations match is also put here. This * will typically result in an error unless it is within a * try-finally whose finally block cannot complete normally. */ @@ -631,7 +632,7 @@ private void clearPendingExits(boolean inMethod) { while (exits.nonEmpty()) { PendingExit exit = exits.head; exits = exits.tail; - Assert.check((inMethod && exit.tree.hasTag(RETURN)) || + Assert.check((inMethod && (exit.tree.hasTag(RETURN) || exit.tree.hasTag(MATCH))) || log.hasErrorOn(exit.tree.pos())); } } @@ -690,6 +691,11 @@ public void visitForLoop(JCForLoop tree) { tree.cond != null && !tree.cond.type.isTrue()); } + public void visitMatch(JCMatch tree) { + scan(tree.args); + recordExit(new PendingExit(tree)); + } + public void visitForeachLoop(JCEnhancedForLoop tree) { visitVarDef(tree.var); ListBuffer prevPendingExits = pendingExits; @@ -1553,6 +1559,7 @@ else if ((tree.sym.flags() & (BLOCK | STATIC)) != BLOCK) exits = exits.tail; if (!(exit instanceof ThrownPendingExit)) { Assert.check(exit.tree.hasTag(RETURN) || + exit.tree.hasTag(MATCH) || log.hasErrorOn(exit.tree.pos())); } else { // uncaught throws will be reported later @@ -1809,6 +1816,11 @@ public void visitReturn(JCReturn tree) { recordExit(new PendingExit(tree)); } + public void visitMatch(JCMatch tree) { + scan(tree.args); + recordExit(new PendingExit(tree)); + } + public void visitThrow(JCThrow tree) { scan(tree.expr); Symbol sym = TreeInfo.symbol(tree.expr); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java index da0e3f9c747..7a76bd2d60e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java @@ -1284,7 +1284,7 @@ JCExpression access(Symbol sym, JCExpression tree, JCExpression enclOp, boolean boolean baseReq = base == null && sym.owner != syms.predefClass && - !sym.isMemberOf(currentClass, types); + !sym.isMemberOf(currentClass, types) && !(sym instanceof DynamicVarSymbol); if (accReq || baseReq) { make.at(tree.pos); @@ -2779,6 +2779,32 @@ public void visitMethodDef(JCMethodDecl tree) { syms.methodClass); } + if (tree.sym.isPattern()) { + tree.sym.flags_field |= STATIC; + tree.sym.flags_field |= SYNTHETIC; + tree.mods.flags |= STATIC; + tree.mods.flags |= SYNTHETIC; + + JCVariableDecl implicitThisParam = make_at(tree.pos()). + Param(names._this, tree.sym.owner.type, tree.sym); + implicitThisParam.mods.flags |= SYNTHETIC; + implicitThisParam.sym.flags_field |= SYNTHETIC; + + MethodSymbol m = tree.sym; + tree.params = tree.params.prepend(implicitThisParam); + + m.params = m.params.prepend(implicitThisParam.sym); + Type olderasure = m.erasure(types); + var mt = new MethodType( + olderasure.getParameterTypes().prepend(tree.sym.owner.type), + olderasure.getReturnType(), + olderasure.getThrownTypes(), + syms.methodClass); + mt.bindingtypes = olderasure.getBindingTypes(); + + m.erasure_field = mt; + } + JCMethodDecl prevMethodDef = currentMethodDef; MethodSymbol prevMethodSym = currentMethodSym; try { @@ -3283,7 +3309,9 @@ public void visitAssert(JCAssert tree) { public void visitApply(JCMethodInvocation tree) { Symbol meth = TreeInfo.symbol(tree.meth); - List argtypes = meth.type.getParameterTypes(); + List argtypes = meth.isPattern() + ? List.of(tree.args.head.type) + : meth.type.getParameterTypes(); if (meth.name == names.init && meth.owner == syms.enumSym) argtypes = argtypes.tail.tail; tree.args = boxArgs(argtypes, tree.args, tree.varargsElement); @@ -3388,7 +3416,9 @@ List boxArgs(List parameters, List _args, Type boxedArgs.type = new ArrayType(varargsElement, syms.arrayClass); result.append(boxedArgs); } else { - if (args.length() != 1) throw new AssertionError(args); + if (args.length() != 1) { + throw new AssertionError(args); + } JCExpression arg = translate(args.head, parameter); anyChanges |= (arg != args.head); result.append(arg); @@ -3896,7 +3926,7 @@ public void visitForLoop(JCForLoop tree) { public void visitReturn(JCReturn tree) { if (tree.expr != null) tree.expr = translate(tree.expr, - types.erasure(currentMethodDef + types.erasure(currentMethodDef.sym.isPattern() ? syms.objectType : currentMethodDef .restype.type)); result = tree; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MemberEnter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MemberEnter.java index 76a05d6e1d3..864cbb4ced0 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MemberEnter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MemberEnter.java @@ -197,10 +197,15 @@ public void visitMethodDef(JCMethodDecl tree) { DiagnosticPosition prevLintPos = deferredLintHandler.setPos(tree.pos()); try { // Compute the method type - m.type = signature(m, tree.typarams, tree.params, + Type t = signature(m, tree.typarams, tree.params, tree.restype, tree.recvparam, tree.thrown, localEnv); + if (t instanceof MethodType mt && m.isPattern()) { + mt.bindingtypes = mt.argtypes; + mt.argtypes = List.nil(); + } + m.type = t; } finally { deferredLintHandler.setPos(prevLintPos); } @@ -216,7 +221,16 @@ public void visitMethodDef(JCMethodDecl tree) { JCVariableDecl param = lastParam = l.head; params.append(Assert.checkNonNull(param.sym)); } - m.params = params.toList(); + + if (m.isPattern()) { + m.bindings = params.toList(); + m.params = List.nil(); + tree.bindings = tree.params; + tree.params = List.nil(); + } else { + m.params = params.toList(); + m.bindings = List.nil(); + } // mark the method varargs, if necessary if (lastParam != null && (lastParam.mods.flags & Flags.VARARGS) != 0) @@ -247,9 +261,14 @@ Env methodEnv(JCMethodDecl tree, Env env) { env.dup(tree, env.info.dup(env.info.scope.dupUnshared(tree.sym))); localEnv.enclMethod = tree; if (tree.sym.type != null) { + if (tree.sym.isPattern()) { + localEnv.info.returnResult = attr.new ResultInfo(KindSelector.VAL, + syms.objectType); + } else { //when this is called in the enter stage, there's no type to be set localEnv.info.returnResult = attr.new ResultInfo(KindSelector.VAL, tree.sym.type.getReturnType()); + } } if ((tree.mods.flags & STATIC) != 0) localEnv.info.staticLevel++; localEnv.info.yieldResult = null; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java index aed7b3265f8..a09f7cf743d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java @@ -31,6 +31,9 @@ import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Kinds; import com.sun.tools.javac.code.Kinds.Kind; + +import static com.sun.tools.javac.code.TypeTag.*; + import com.sun.tools.javac.code.Preview; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.BindingSymbol; @@ -55,6 +58,7 @@ import com.sun.tools.javac.tree.JCTree.JCInstanceOf; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCSwitch; +import com.sun.tools.javac.tree.JCTree.JCMatch; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.JCTree.JCBindingPattern; import com.sun.tools.javac.tree.JCTree.JCWhileLoop; @@ -67,17 +71,16 @@ import com.sun.tools.javac.util.Names; import java.util.Collections; -import java.util.Map; -import java.util.Map.Entry; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.RecordComponent; import com.sun.tools.javac.code.Type; -import static com.sun.tools.javac.code.TypeTag.BOT; import com.sun.tools.javac.jvm.PoolConstant.LoadableConstant; import com.sun.tools.javac.jvm.Target; import com.sun.tools.javac.tree.JCTree; @@ -299,22 +302,112 @@ private UnrolledRecordPattern unrollRecordPattern(JCRecordPattern recordPattern) //=> //$record $r; type-test-of($nestedPattern1) && type-test-of($nestedPattern2) && ... && // nested-conditions-of($nestedPattern1) && nested-conditions-of($nestedPattern2) - Type recordType = recordPattern.record.erasure(types); + + // A record pattern that comes with a pattern declaration is desugared into the + // following structure (utilizing DynamicVarSymbols). + // + // if (o instanceof Pattern $m$1 && + // Pattern."Pattern$Ljava\\|lang\\|String\\?$I"($m$1)) instanceof Object unmatched) { + // MethodType methodType = MethodType.methodType(Object.class, String.class, int.class); // represented as a Constant_MethodType_info + // os = (String) Carriers.component(methodType, 0).invoke($m$1); // where Carriers.component(methodType, 0) is a DynamicVarSymbol + // oi = (int) Carriers.component(methodType, 1).invoke($m$1); // where Carriers.component(methodType, 1) is a DynamicVarSymbol + // ... + // } + Type recordType = recordPattern.type.tsym.erasure(types); BindingSymbol tempBind = new BindingSymbol(Flags.SYNTHETIC, names.fromString(target.syntheticNameChar() + "b" + target.syntheticNameChar() + variableIndex++), recordType, currentMethodSym); JCVariableDecl recordBindingVar = make.at(recordPattern.pos()).VarDef(tempBind, null); VarSymbol recordBinding = recordBindingVar.sym; - List components = recordPattern.record.getRecordComponents(); + List components; List nestedFullComponentTypes = recordPattern.fullComponentTypes; List nestedPatterns = recordPattern.nested; + JCExpression firstLevelChecks = null; JCExpression secondLevelChecks = null; + int index = -1; // needed for the Carriers.component in the case of a matcher - while (components.nonEmpty()) { - RecordComponent component = components.head; + BindingSymbol mSymbol = null; + MethodSymbol carriersComponentCallSym = null; + + if (recordPattern.patternDeclaration != null) { + mSymbol = new BindingSymbol(Flags.SYNTHETIC, + names.fromString(target.syntheticNameChar() + "m" + target.syntheticNameChar() + variableIndex++), syms.objectType, + currentMethodSym); + JCVariableDecl mVar = make.VarDef(mSymbol, null); + JCExpression nullCheck = make.TypeTest( + make.App(make.Select(make.Ident(recordPattern.patternDeclaration.owner), recordPattern.patternDeclaration), + List.of(make.Ident(tempBind))).setType(syms.objectType), + make.BindingPattern(mVar).setType(mSymbol.type)).setType(syms.booleanType); + + firstLevelChecks = nullCheck; + + // Resolve Carriers.component(methodType, index) for subsequent constant dynamic call results + carriersComponentCallSym = + rs.resolveInternalMethod(recordPattern.pos(), + env, + syms.carriersType, + names.component, + List.of(syms.methodTypeType, syms.intType), + List.nil()); + components = List.nil(); + } else { + components = recordPattern.record.getRecordComponents(); + } + + while (nestedFullComponentTypes.nonEmpty()) { Type componentType = types.erasure(nestedFullComponentTypes.head); + JCExpression accessedComponentValue; + index++; + if (recordPattern.patternDeclaration != null) { + /* + * Generate invoke call for component X + * component$X.invoke(carrier); + * */ + List params = recordPattern.patternDeclaration.bindings + .map(v -> types.erasure(v.type)); + + MethodType methodType = new MethodType(params, syms.objectType, List.nil(), syms.methodClass); + + List carriersComponentParams = + List.of(methodType, + LoadableConstant.Int(index)); + + DynamicVarSymbol carriersComponentCallDynamicVar = (DynamicVarSymbol) + invokeMethodWrapper(names.fromString("component$" + index), recordPattern.pos(), + carriersComponentCallSym.asHandle(), + carriersComponentParams.toArray(LoadableConstant[]::new)); + + List invokeComponentParams = List.of(make.Ident(mSymbol)); + + MethodSymbol invokeComponentCallSym = + rs.resolveInternalMethod(recordPattern.pos(), + env, + syms.methodHandleType, + names.invoke, + List.of(syms.objectType), + List.nil()); + + JCMethodInvocation invokeComponentCall = + make.App(make.Select(make.Ident(carriersComponentCallDynamicVar), + invokeComponentCallSym), invokeComponentParams); + + accessedComponentValue = convert(invokeComponentCall, componentType); + } else { + RecordComponent component = components.head; + JCMethodInvocation componentAccessor = + make.at(recordPattern.pos()).App( + make.Select(convert(make.Ident(recordBinding), recordBinding.type), + component.accessor)).setType(types.erasure(component.accessor.getReturnType())); + if (deconstructorCalls == null) { + deconstructorCalls = Collections.newSetFromMap(new IdentityHashMap<>()); + } + deconstructorCalls.add(componentAccessor); + accessedComponentValue = convert(componentAccessor, componentType); + components = components.tail; + } + JCPattern nestedPattern = nestedPatterns.head; JCPattern nestedBinding; boolean allowNull; @@ -337,18 +430,11 @@ private UnrolledRecordPattern unrollRecordPattern(JCRecordPattern recordPattern) else { nestedBinding = (JCBindingPattern) nestedPattern; allowNull = types.isSubtype(componentType, - types.boxedTypeOrType(types.erasure(nestedBinding.type))); + types.boxedTypeOrType(types.erasure(nestedBinding.type))); } - JCMethodInvocation componentAccessor = - make.at(recordPattern.pos()).App(make.Select(convert(make.Ident(recordBinding), recordBinding.type), - component.accessor)).setType(types.erasure(component.accessor.getReturnType())); - if (deconstructorCalls == null) { - deconstructorCalls = Collections.newSetFromMap(new IdentityHashMap<>()); - } - deconstructorCalls.add(componentAccessor); - JCExpression accessedComponentValue = - convert(componentAccessor, componentType); + JCInstanceOf firstLevelCheck = (JCInstanceOf) make.TypeTest(accessedComponentValue, nestedBinding).setType(syms.booleanType); + //TODO: verify deep/complex nesting with nulls firstLevelCheck.allowNull = allowNull; if (firstLevelChecks == null) { @@ -356,12 +442,12 @@ private UnrolledRecordPattern unrollRecordPattern(JCRecordPattern recordPattern) } else { firstLevelChecks = mergeConditions(firstLevelChecks, firstLevelCheck); } - components = components.tail; nestedFullComponentTypes = nestedFullComponentTypes.tail; nestedPatterns = nestedPatterns.tail; } Assert.check(components.isEmpty() == nestedPatterns.isEmpty()); + Assert.check(nestedFullComponentTypes.isEmpty() == nestedPatterns.isEmpty()); JCExpression guard = null; if (firstLevelChecks != null) { guard = firstLevelChecks; @@ -369,6 +455,7 @@ private UnrolledRecordPattern unrollRecordPattern(JCRecordPattern recordPattern) guard = mergeConditions(guard, secondLevelChecks); } } + return new UnrolledRecordPattern((JCBindingPattern) make.BindingPattern(recordBindingVar).setType(recordBinding.type), guard); } @@ -777,6 +864,76 @@ private Symbol.DynamicVarSymbol makeBooleanConstant(DiagnosticPosition pos, int new LoadableConstant[]{}); } + /** + * A statement of the form + *
    {@code
    +     *   pattern  (binding1, binding2) {
    +     *       match  (arg1, arg2) ;
    +     *   }
    +     * }
    +     * 
    + * + * is translated to: + * + *
    {@code
    +     *     binding1 = arg1;
    +     *     binding2 = arg2;
    +     *     return carrier.invoke(binding1, binding2);
    +     * }
    + * + */ + @Override + public void visitMatch(JCMatch tree) { + List matchArguments = tree.getArguments(); + List bindings = tree.meth.bindings; + List bindingTypes = tree.meth.type.getBindingTypes(); + List invokeMethodParam = List.nil(); + + ListBuffer stats = new ListBuffer<>(); + + // Generate: + // 1. calculate the returnType MethodType as Constant_MethodType_info + // 2. generate factory code on carrier for the types we want (e.g., Object carrier = Carriers.initializingConstructor(returnType);) + // 3. generate invoke call to pass the bindings (e.g, return carrier.invoke(x, y);) + MethodSymbol factoryMethodSym = + rs.resolveInternalMethod(tree.pos(), + env, + syms.carriersType, + names.fromString("initializingConstructor"), + List.of(syms.methodTypeType), + List.nil()); + + DynamicVarSymbol factoryMethodDynamicVar = + (DynamicVarSymbol) invokeMethodWrapper( + names.fromString("carrier"), + tree.pos(), + factoryMethodSym.asHandle(), + new MethodType(tree.meth.type.getBindingTypes(), syms.objectType, List.nil(), syms.methodClass)); + + // Generate: + // parameter1 = arg1; + while (matchArguments.nonEmpty() && bindings.nonEmpty() && bindingTypes.nonEmpty()) { + JCExpressionStatement stat = + make.Exec(make.Assign(make.Ident(bindings.head), + translate(matchArguments.head)).setType(bindings.head.type)); + stats.add(stat); + + invokeMethodParam = invokeMethodParam.append(make.Ident(bindings.head)); + + bindings = bindings.tail; + bindingTypes = bindingTypes.tail; + matchArguments = matchArguments.tail; + } + + JCIdent factoryMethodCall = make.Ident(factoryMethodDynamicVar); + + JCMethodInvocation invokeMethodCall = + makeApply(factoryMethodCall, names.fromString("invoke"), invokeMethodParam); + + stats = stats.append(make.Return(invokeMethodCall)); + result = make.at(tree.pos).Block(0, stats.toList()); + } + private class PrimitiveGenerator extends Types.SignatureGenerator { /** @@ -1067,6 +1224,10 @@ private LoadableConstant createEnumDesc(DiagnosticPosition pos, ClassSymbol enum } private LoadableConstant invokeMethodWrapper(DiagnosticPosition pos, MethodHandleSymbol toCall, LoadableConstant... params) { + return invokeMethodWrapper(names.invoke, pos, toCall, params); + } + + private LoadableConstant invokeMethodWrapper(Name varName, DiagnosticPosition pos, MethodHandleSymbol toCall, LoadableConstant... params) { List bsm_staticArgs = List.of(syms.methodHandleLookupType, syms.stringType, new ClassType(syms.classType.getEnclosingType(), @@ -1084,7 +1245,7 @@ private LoadableConstant invokeMethodWrapper(DiagnosticPosition pos, MethodHandl System.arraycopy(params, 0, actualParams, 1, params.length); - return new DynamicVarSymbol(bsm.name, bsm.owner, bsm.asHandle(), toCall.getReturnType(), actualParams); + return new DynamicVarSymbol(varName, bsm.owner, bsm.asHandle(), toCall.getReturnType(), actualParams); } @Override @@ -1162,6 +1323,10 @@ public void visitMethodDef(JCMethodDecl tree) { currentMethodSym = tree.sym; variableIndex = 0; deconstructorCalls = null; + if (tree.sym.isPattern()) { + tree.body.stats = tree.body.stats.append( + make.Throw(makeNewClass(syms.matchExceptionType, List.of(makeNull(), makeNull())))); + } super.visitMethodDef(tree); preparePatternMatchingCatchIfNeeded(tree.body); } finally { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java index d1ea1651041..b441360abf4 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java @@ -590,8 +590,6 @@ public void visitSwitchExpression(JCSwitchExpression tree) { } public void visitRecordPattern(JCRecordPattern tree) { - tree.fullComponentTypes = tree.record.getRecordComponents() - .map(rc -> types.memberType(tree.type, rc)); tree.deconstructor = translate(tree.deconstructor, null); tree.nested = translate(tree.nested, null); result = tree; @@ -650,6 +648,15 @@ public void visitYield(JCYield tree) { result = tree; } + @Override + public void visitMatch(JCMatch tree) { + List argtypes = tree.meth.type.getParameterTypes(); + + tree.args = translateArgs(tree.args, argtypes, null); + + result = tree; + } + public void visitThrow(JCThrow tree) { tree.expr = translate(tree.expr, erasure(tree.expr.type)); result = tree; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java index e0cebce611d..775cc35b5aa 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java @@ -118,6 +118,10 @@ public class ClassReader { */ boolean allowRecords; + /** Switch: allow pattern declarations + */ + boolean allowPatterns; + /** Lint option: warn about classfile issues */ boolean lintClassfile; @@ -285,6 +289,7 @@ protected ClassReader(Context context) { preview = Preview.instance(context); allowModules = Feature.MODULES.allowedInSource(source); allowRecords = Feature.RECORDS.allowedInSource(source); + allowPatterns = Feature.PATTERN_DECLARATIONS.allowedInSource(source); allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source); warnOnIllegalUtf8 = Feature.WARN_ON_ILLEGAL_UTF8.allowedInSource(source); @@ -304,7 +309,8 @@ protected ClassReader(Context context) { private void enterMember(ClassSymbol c, Symbol sym) { // Synthetic members are not entered -- reason lost to history (optimization?). // Lambda methods must be entered because they may have inner classes (which reference them) - if ((sym.flags_field & (SYNTHETIC|BRIDGE)) != SYNTHETIC || sym.name.startsWith(names.lambda)) + // Pattern declarations must be entered because a client needs to link to those methods + if ((sym.flags_field & (SYNTHETIC|BRIDGE)) != SYNTHETIC || sym.name.startsWith(names.lambda) || sym.isPattern()) c.members_field.enter(sym); } @@ -1302,6 +1308,44 @@ protected void read(Symbol sym, int attrLen) { } } }, + new AttributeReader(names.Pattern, V66, MEMBER_ATTRIBUTE) { + @Override + protected boolean accepts(AttributeKind kind) { + return super.accepts(kind) && allowPatterns; + } + protected void read(Symbol sym, int attrLen) { + if (sym.kind == MTH) { + Name patternName = poolReader.getName(nextChar()); + int patternFlags = nextChar(); + Type patternType = poolReader.getType(nextChar()); + + var oldParameterAnnotations = parameterAnnotations; + var oldParameterNameIndicesLvt = parameterNameIndicesLvt; + var oldParameterAccessFlags = parameterAccessFlags; + var oldParameterNameIndicesMp = parameterNameIndicesMp; + parameterAnnotations = null; + parameterNameIndicesLvt = null; + parameterNameIndicesMp = null; + parameterAccessFlags = null; + + readMemberAttrs(sym); + + MethodSymbol msym = (MethodSymbol) sym; + msym.bindings = computeParamsFromAttribute(msym, patternType.getParameterTypes(), 0); + + parameterAnnotations = oldParameterAnnotations; + parameterNameIndicesLvt = oldParameterNameIndicesLvt; + parameterNameIndicesMp = oldParameterNameIndicesMp; + parameterAccessFlags = oldParameterAccessFlags; + + msym.name = patternName; + msym.flags_field |= PATTERN; + // todo: check if special handling is needed similar to generic methods for binding types + + msym.type.asMethodType().bindingtypes = patternType.getParameterTypes(); + } + } + }, }; for (AttributeReader r: readers) @@ -2650,7 +2694,6 @@ MethodSymbol readMethod() { } validateMethodType(name, m.type); setParameters(m, type); - if (Integer.bitCount(rawFlags & (PUBLIC | PRIVATE | PROTECTED)) > 1) throw badClassFile("illegal.flag.combo", Flags.toString((long)rawFlags), "method", m); if ((flags & VARARGS) != 0) { @@ -2738,7 +2781,7 @@ void setParameters(MethodSymbol sym, Type jvmType) { firstParamLvt += 1; } - if (sym.type != jvmType) { + if (sym.type != jvmType && !sym.isPattern()) { // reading the method attributes has caused the // symbol's type to be changed. (i.e. the Signature // attribute.) This may happen if there are hidden @@ -2752,6 +2795,15 @@ void setParameters(MethodSymbol sym, Type jvmType) { - Code.width(sym.type.getParameterTypes()); firstParamLvt += skip; } + + List params = computeParamsFromAttribute(sym, sym.type.getParameterTypes(), firstParamLvt); + + Assert.checkNull(sym.params); + + sym.params = params; + } + + private List computeParamsFromAttribute(MethodSymbol sym, List parameterTypes, int firstParamLvt) { Set paramNames = new HashSet<>(); ListBuffer params = new ListBuffer<>(); // we maintain two index pointers, one for the LocalVariableTable attribute @@ -2762,7 +2814,7 @@ void setParameters(MethodSymbol sym, Type jvmType) { int nameIndexLvt = firstParamLvt; int nameIndexMp = 0; int annotationIndex = 0; - for (Type t: sym.type.getParameterTypes()) { + for (Type t: parameterTypes) { VarSymbol param = parameter(nameIndexMp, nameIndexLvt, t, sym, paramNames); params.append(param); if (parameterAnnotations != null) { @@ -2779,12 +2831,11 @@ void setParameters(MethodSymbol sym, Type jvmType) { if (parameterAnnotations != null && parameterAnnotations.length != annotationIndex) { throw badClassFile("bad.runtime.invisible.param.annotations", sym); } - Assert.checkNull(sym.params); - sym.params = params.toList(); parameterAnnotations = null; parameterNameIndicesLvt = null; parameterNameIndicesMp = null; parameterAccessFlags = null; + return params.toList(); } /** diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java index dfa92efae74..66ea51bcd0f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java @@ -354,16 +354,20 @@ int writeFlagAttrs(long flags) { /** Write member (field or method) attributes; * return number of attributes written. */ - int writeMemberAttrs(Symbol sym, boolean isRecordComponent) { + int writeMemberAttrs(Symbol sym, boolean isRecordComponent, boolean fromPattern) { int acount = 0; if (!isRecordComponent) { acount = writeFlagAttrs(sym.flags()); } long flags = sym.flags(); - if ((flags & (SYNTHETIC | BRIDGE)) != SYNTHETIC && + boolean needsSignature = !types.isSameType(sym.type, sym.erasure(types)) || + poolWriter.signatureGen.hasTypeVar(sym.type.getThrownTypes()); + if (((flags & (SYNTHETIC | BRIDGE)) != SYNTHETIC && (flags & ANONCONSTR) == 0 && - (!types.isSameType(sym.type, sym.erasure(types)) || - poolWriter.signatureGen.hasTypeVar(sym.type.getThrownTypes()))) { + needsSignature) || + (needsSignature && + fromPattern && + sym.isPattern())) { // note that a local class with captured variables // will get a signature attribute int alenIdx = writeAttr(names.Signature); @@ -371,18 +375,19 @@ int writeMemberAttrs(Symbol sym, boolean isRecordComponent) { endAttr(alenIdx); acount++; } - acount += writeJavaAnnotations(sym.getRawAttributes()); - acount += writeTypeAnnotations(sym.getRawTypeAttributes(), false); + if (!fromPattern) { + acount += writeJavaAnnotations(sym.getRawAttributes()); + acount += writeTypeAnnotations(sym.getRawTypeAttributes(), false); + } return acount; } /** * Write method parameter names attribute. */ - int writeMethodParametersAttr(MethodSymbol m, boolean writeParamNames) { - MethodType ty = m.externalType(types).asMethodType(); - final int allparams = ty.argtypes.size(); - if (m.params != null && allparams != 0) { + int writeMethodParametersAttr(MethodSymbol m, List params, List arg_types, boolean writeParamNames) { + final int allparams = arg_types.size(); + if (params != null && allparams != 0) { final int attrIndex = writeAttr(names.MethodParameters); databuf.appendByte(allparams); // Write extra parameters first @@ -397,7 +402,7 @@ int writeMethodParametersAttr(MethodSymbol m, boolean writeParamNames) { databuf.appendChar(flags); } // Now write the real parameters - for (VarSymbol s : m.params) { + for (VarSymbol s : params) { final int flags = ((int) s.flags() & (FINAL | SYNTHETIC | MANDATED)) | ((int) m.flags() & SYNTHETIC); @@ -862,13 +867,41 @@ int writeRecordAttribute(ClassSymbol csym) { databuf.appendChar(poolWriter.putDescriptor(v)); int acountIdx = beginAttrs(); int acount = 0; - acount += writeMemberAttrs(v, true); + acount += writeMemberAttrs(v, true, false); endAttrs(acountIdx, acount); } endAttr(alenIdx); return 1; } + int writePatternAttribute(MethodSymbol m) { + final int attrIndex = writeAttr(names.Pattern); + + databuf.appendChar(poolWriter.putName(m.name)); + databuf.appendChar(PatternFlags.value(m.patternFlags)); + MethodType mt = new MethodType( + m.type.getBindingTypes(), + m.type.asMethodType().restype, + m.type.getThrownTypes(), + m.type.tsym); + databuf.appendChar(poolWriter.putDescriptor(mt)); + + int acountIdx = beginAttrs(); + int acount = 0; + + if (target.hasMethodParameters()) { + acount += writeMethodParametersAttr(m, m.bindings(), m.type.getBindingTypes(), requiresParamNames(m)); + } + + acount += writeMemberAttrs(m, false, true); + acount += writeParameterAttrs(m.bindings); + + endAttrs(acountIdx, acount); + endAttr(attrIndex); + + return 1; + } + /** * Write NestMembers attribute (if needed) */ @@ -982,7 +1015,7 @@ void writeField(VarSymbol v) { endAttr(alenIdx); acount++; } - acount += writeMemberAttrs(v, false); + acount += writeMemberAttrs(v, false, false); acount += writeExtraAttributes(v); endAttrs(acountIdx, acount); } @@ -997,7 +1030,8 @@ void writeMethod(MethodSymbol m) { pw.println("METHOD " + m.name); pw.println("---" + flagNames(m.flags())); } - databuf.appendChar(poolWriter.putName(m.name)); + Name name = m.externalName(types); + databuf.appendChar(poolWriter.putName(name)); databuf.appendChar(poolWriter.putDescriptor(m)); int acountIdx = beginAttrs(); int acount = 0; @@ -1023,16 +1057,21 @@ void writeMethod(MethodSymbol m) { endAttr(alenIdx); acount++; } - if (target.hasMethodParameters()) { + if (!m.isPattern() && target.hasMethodParameters()) { if (!m.isLambdaMethod()) { // Per JDK-8138729, do not emit parameters table for lambda bodies. boolean requiresParamNames = requiresParamNames(m); if (requiresParamNames || requiresParamFlags(m)) - acount += writeMethodParametersAttr(m, requiresParamNames); + acount += writeMethodParametersAttr(m, m.params, ((MethodType)m.externalType(types)).argtypes, requiresParamNames); } } - acount += writeMemberAttrs(m, false); - if (!m.isLambdaMethod()) + acount += writeMemberAttrs(m, false, false); + if (!m.isLambdaMethod() && !m.isPattern()) acount += writeParameterAttrs(m.params); + + if (m.isPattern()) { + acount += writePatternAttribute(m); + } + acount += writeExtraAttributes(m); endAttrs(acountIdx, acount); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Code.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Code.java index 703e1cb0c9c..9dab7507cb1 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Code.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Code.java @@ -1374,7 +1374,7 @@ StackMapFrame getInitialFrame() { List arg_types = ((MethodType)meth.externalType(types)).argtypes; int len = arg_types.length(); int count = 0; - if (!meth.isStatic()) { + if (!(meth.isStatic() || meth.isPattern())) { Type thisType = meth.owner.type; frame.locals = new Type[len+1]; if (meth.isConstructor() && thisType != syms.objectType) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java index b9bfe587c6c..2a0c489e9bb 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java @@ -1043,6 +1043,15 @@ private int initCode(JCMethodDecl tree, Env env, boolean fatcode) { code.setDefined(code.newLocal(l.head.sym)); } + // Mark all bindings as defined from the beginning of + // the pattern declaration. + if (tree.sym.isPattern()) { + for (List l = tree.bindings; l.nonEmpty(); l = l.tail) { + checkDimension(l.head.pos(), l.head.sym.type); + code.newLocal(l.head.sym); + } + } + // Get ready to generate code for method body. int startpcCrt = genCrt ? code.curCP() : 0; code.entryPoint(); @@ -1894,6 +1903,10 @@ public void visitReturn(JCReturn tree) { int tmpPos = code.pendingStatPos; if (tree.expr != null) { Assert.check(code.isStatementStart()); + Type pt = this.pt; + if (env.enclMethod != null && env.enclMethod.sym.isPattern()) { + pt = syms.objectType; + } Item r = genExpr(tree.expr, pt).load(); if (hasFinally(env.enclMethod, env)) { r = makeTemp(pt); @@ -2409,7 +2422,7 @@ public void visitSelect(JCFieldAccess tree) { } else { sym = binaryQualifier(sym, tree.selected.type); } - if ((sym.flags() & STATIC) != 0) { + if ((sym.flags() & STATIC) != 0 || sym.isPattern()) { if (!selectSuper && (ssym == null || ssym.kind != TYP)) base = base.load(); base.drop(); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Items.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Items.java index c7e88efb71b..2cc344070aa 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Items.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Items.java @@ -455,7 +455,7 @@ void store() { } Item invoke() { - MethodType mtype = (MethodType)member.erasure(types); + MethodType mtype = (MethodType)member.externalType(types); int rescode = Code.typecode(mtype.restype); code.emitInvokestatic(member, mtype); return stackItem[rescode]; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/PoolWriter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/PoolWriter.java index af6f4b67fae..126210109e2 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/PoolWriter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/PoolWriter.java @@ -56,6 +56,7 @@ import java.util.Map; import static com.sun.tools.javac.code.Kinds.Kind.TYP; +import com.sun.tools.javac.code.Symbol.MethodSymbol; import static com.sun.tools.javac.code.TypeTag.ARRAY; import static com.sun.tools.javac.code.TypeTag.CLASS; import static com.sun.tools.javac.jvm.ClassFile.CONSTANT_Class; @@ -201,7 +202,7 @@ int putName(Name name) { * Puts a name and type pair into the pool and returns its index. */ int putNameAndType(Symbol s) { - return pool.writeIfNeeded(new NameAndType(s.name, descriptorType(s))); + return pool.writeIfNeeded(new NameAndType(s instanceof MethodSymbol ms ? ms.externalName(types) : s.name, descriptorType(s))); } /** diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java index a12271c7904..ca69589fdeb 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java @@ -85,8 +85,6 @@ import static com.sun.tools.javac.code.Kinds.Kind.*; -import com.sun.tools.javac.code.Lint; -import com.sun.tools.javac.code.Lint.LintCategory; import com.sun.tools.javac.code.Symbol.ModuleSymbol; import com.sun.tools.javac.resources.CompilerProperties.Errors; @@ -1503,6 +1501,7 @@ class ScanNested extends TreeScanner { Set> dependencies = new LinkedHashSet<>(); protected boolean hasLambdas; protected boolean hasPatterns; + protected boolean hasMatchers; @Override public void visitClassDef(JCClassDecl node) { Type st = types.supertype(node.sym.type); @@ -1514,6 +1513,7 @@ public void visitClassDef(JCClassDecl node) { if (dependencies.add(stEnv)) { boolean prevHasLambdas = hasLambdas; boolean prevHasPatterns = hasPatterns; + boolean prevHasMatchers = hasMatchers; try { scan(stEnv.tree); } finally { @@ -1526,6 +1526,7 @@ public void visitClassDef(JCClassDecl node) { */ hasLambdas = prevHasLambdas; hasPatterns = prevHasPatterns; + hasMatchers = prevHasMatchers; } } envForSuperTypeFound = true; @@ -1564,6 +1565,11 @@ public void visitSwitchExpression(JCSwitchExpression tree) { hasPatterns |= tree.patternSwitch; super.visitSwitchExpression(tree); } + @Override + public void visitMethodDef(JCMethodDecl tree) { + hasMatchers |= tree.sym.isPattern(); + super.visitMethodDef(tree); + } } ScanNested scanner = new ScanNested(); scanner.scan(env.tree); @@ -1612,7 +1618,7 @@ public void visitSwitchExpression(JCSwitchExpression tree) { if (shouldStop(CompileState.TRANSPATTERNS)) return; - if (scanner.hasPatterns) { + if (scanner.hasPatterns | scanner.hasMatchers) { env.tree = TransPatterns.instance(context).translateTopLevelClass(env, env.tree, localMake); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java index ce484e14b05..aa9b08d466f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java @@ -51,6 +51,7 @@ import com.sun.tools.javac.util.JCDiagnostic.Fragment; import com.sun.tools.javac.util.List; +import static com.sun.tools.javac.code.Flags.PATTERN; import static com.sun.tools.javac.parser.Tokens.TokenKind.*; import static com.sun.tools.javac.parser.Tokens.TokenKind.ASSERT; import static com.sun.tools.javac.parser.Tokens.TokenKind.CASE; @@ -198,6 +199,7 @@ protected JavacParser(ParserFactory fac, this.allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source); this.allowRecords = Feature.RECORDS.allowedInSource(source); this.allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source); + this.allowPatternDeclarations = Feature.PATTERN_DECLARATIONS.allowedInSource(source); } /** Construct a parser from an existing parser, with minimal overhead. @@ -222,6 +224,7 @@ protected JavacParser(JavacParser parser, this.allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source); this.allowRecords = Feature.RECORDS.allowedInSource(source); this.allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source); + this.allowPatternDeclarations = Feature.PATTERN_DECLARATIONS.allowedInSource(source); } protected AbstractEndPosTable newEndPosTable(boolean keepEndPositions) { @@ -263,6 +266,10 @@ protected DocCommentTable newDocCommentTable(boolean keepDocComments, ParserFact */ boolean allowSealedTypes; + /** Switch: are pattern declarations allowed in this source level? + */ + boolean allowPatternDeclarations; + /** The type of the method receiver, as specified by a first "this" parameter. */ JCVariableDecl receiverParam; @@ -2913,6 +2920,19 @@ List blockStatement() { dc = token.docComment(); return List.of(classOrRecordOrInterfaceOrEnumDeclaration(modifiersOpt(), dc)); case IDENTIFIER: + if (token.name() == names.match && allowPatternDeclarations) { + Token next = S.token(1); + + if(next.kind == IDENTIFIER) { + checkSourceLevel(Feature.PATTERN_DECLARATIONS); + nextToken(); + Name name = ident(); + int identPos = token.pos; + List args = arguments(); + accept(SEMI); + return List.of(toP(F.at(pos).Match(name, args))); + } + } else if (token.name() == names.yield && allowYieldStatement) { Token next = S.token(1); boolean isYieldStatement; @@ -3577,6 +3597,11 @@ protected JCModifiers modifiersOpt(JCModifiers partial) { flag = Flags.SEALED; break; } + if (isPatternDeclarationStart()) { + checkSourceLevel(Feature.PATTERN_DECLARATIONS); + flag = Flags.PATTERN; + break; + } break loop; } default: break loop; @@ -4778,7 +4803,15 @@ private List constructorOrMethodOrFieldDeclaration(JCModifiers mods, Nam } return List.of(methodDeclaratorRest( - pos, mods, null, names.init, typarams, + pos, mods, null, (mods.flags & Flags.PATTERN) == 0 ? names.init : tk.name(), typarams, + isInterface, true, isRecord, dc)); + } + + if (token.kind == LPAREN && isInterface && type.hasTag(IDENT) && (mods.flags & Flags.PATTERN) != 0) { + mods.flags |= Flags.DEFAULT; + // pattern declaration in interface + return List.of(methodDeclaratorRest( + pos, mods, null, (mods.flags & Flags.PATTERN) == 0 ? names.init : tk.name(), typarams, isInterface, true, isRecord, dc)); } @@ -4946,6 +4979,25 @@ protected boolean isSealedClassStart(boolean local) { return false; } + protected boolean isPatternDeclarationStart() { + if (token.name() == names.pattern) { + Token next = S.token(1); + var allowPattern = switch (next.kind) { + case IDENTIFIER -> { + Token afterNext = S.token(2); + yield afterNext.kind == LPAREN; + } + default -> false; + }; + + if (allowPattern) { + checkSourceLevel(Feature.PATTERN_DECLARATIONS); + return true; + } + } + return false; + } + private boolean allowedAfterSealedOrNonSealed(Token next, boolean local, boolean currentIsNonSealed) { return local ? switch (next.kind) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index 74281b4ea0d..ad2f494bacc 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -204,6 +204,21 @@ compiler.err.continue.outside.switch.expression=\ compiler.err.return.outside.switch.expression=\ attempt to return out of a switch expression +compiler.err.match.outside.pattern.declaration=\ + attempt to use match out of a pattern declaration + +compiler.err.match.pattern.name.wrong=\ + match with the same pattern name needed + +compiler.err.match.not.matching.pattern.declaration.signature=\ + match does not initialize bindings by conforming to the pattern declaration signature + +compiler.err.pattern.declaration.no.throws=\ + throws illegal in pattern declaration + +compiler.err.pattern.declaration.cant.throw.exception=\ + exception declaration illegal in pattern declaration + compiler.err.rule.completes.normally=\ switch rule completes without providing a value\n\ (switch rules in switch expressions must either provide a value or throw) @@ -452,6 +467,12 @@ compiler.err.concrete.inheritance.conflict=\ compiler.err.default.allowed.in.intf.annotation.member=\ default value only allowed in an annotation interface declaration +compiler.err.matcher.overloading.ambiguity=\ + matcher overloading ambiguity + +compiler.err.no.compatible.matcher.found=\ + no compatible matcher found + # 0: symbol compiler.err.doesnt.exist=\ package {0} does not exist @@ -2992,6 +3013,9 @@ compiler.misc.kindname.value=\ compiler.misc.kindname.method=\ method +compiler.misc.kindname.pattern.declaration=\ + pattern declaration + compiler.misc.kindname.class=\ class @@ -3198,6 +3222,9 @@ compiler.misc.feature.deconstruction.patterns=\ compiler.misc.feature.unnamed.variables=\ unnamed variables +compiler.misc.feature.pattern.declarations=\ + pattern declarations + compiler.misc.feature.primitive.patterns=\ primitive patterns diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java index 5af482516b4..eb5920f7081 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java @@ -209,6 +209,10 @@ public enum Tag { */ ASSERT, + /** Match statements, of type Match. + */ + MATCH, + /** Method invocation expressions, of type Apply. */ APPLY, @@ -918,6 +922,8 @@ public static class JCMethodDecl extends JCTree implements MethodTree { public JCVariableDecl recvparam; /** value parameters */ public List params; + /** binding parameters */ + public List bindings; /** exceptions thrown by this method */ public List thrown; /** statements in the method */ @@ -973,6 +979,10 @@ public List getParameters() { return params; } @DefinedBy(Api.COMPILER_TREE) + public List getBindings() { + return bindings; + } + @DefinedBy(Api.COMPILER_TREE) public JCVariableDecl getReceiverParameter() { return recvparam; } @DefinedBy(Api.COMPILER_TREE) public List getThrows() { @@ -1730,6 +1740,35 @@ public Tag getTag() { } } + /** + * The match statement + */ + public static class JCMatch extends JCStatement implements MatchTree { + public List args; + public Name clazz; + public JCMethodDecl meth; + + protected JCMatch(Name clazz, List args) { + this.args = args; + this.clazz = clazz; + } + @Override + public void accept(Visitor v) { v.visitMatch(this); } + + @DefinedBy(Api.COMPILER_TREE) + public Kind getKind() { return Kind.MATCH; } + @DefinedBy(Api.COMPILER_TREE) + public List getArguments() { return args; } + @Override @DefinedBy(Api.COMPILER_TREE) + public R accept(TreeVisitor v, D d) { + return v.visitMatchStatement(this, d); + } + @Override + public Tag getTag() { + return MATCH; + } + } + /** * A continue of a loop. */ @@ -2478,6 +2517,7 @@ public static class JCRecordPattern extends JCPattern public List nested; public ClassSymbol record; public List fullComponentTypes; + public MethodSymbol patternDeclaration; protected JCRecordPattern(JCExpression deconstructor, List nested) { this.deconstructor = deconstructor; @@ -3476,6 +3516,7 @@ JCConditional Conditional(JCExpression cond, JCYield Yield(JCExpression value); JCContinue Continue(Name label); JCReturn Return(JCExpression expr); + JCMatch Match(Name expr, List args); JCThrow Throw(JCExpression expr); JCAssert Assert(JCExpression cond, JCExpression detail); JCMethodInvocation Apply(List typeargs, @@ -3547,6 +3588,7 @@ public abstract static class Visitor { public void visitExec(JCExpressionStatement that) { visitTree(that); } public void visitBreak(JCBreak that) { visitTree(that); } public void visitYield(JCYield that) { visitTree(that); } + public void visitMatch(JCMatch that) { visitTree(that); } public void visitContinue(JCContinue that) { visitTree(that); } public void visitReturn(JCReturn that) { visitTree(that); } public void visitThrow(JCThrow that) { visitTree(that); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java index e97d07b1d2b..c918f5ce7c8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java @@ -623,7 +623,11 @@ public void visitMethodDef(JCMethodDecl tree) { if (tree.name == tree.name.table.names.init) { print(enclClassName != null ? enclClassName : tree.name); } else { - printExpr(tree.restype); + if ((tree.mods.flags & PATTERN) != 0) { + print("pattern"); + } else { + printExpr(tree.restype); + } print(' '); print(tree.name); } @@ -1151,6 +1155,18 @@ public void visitAssert(JCAssert tree) { } } + public void visitMatch(JCMatch tree) { + try { + print("match "); + print(tree.clazz); + print('('); + printExprs(tree.args); + print(')'); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + public void visitApply(JCMethodInvocation tree) { try { if (!tree.typeargs.isEmpty()) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeCopier.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeCopier.java index 9c3ed3bbcd2..ffd97458079 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeCopier.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeCopier.java @@ -149,6 +149,14 @@ public JCTree visitYield(YieldTree node, P p) { return M.at(t.pos).Yield(value); } + @DefinedBy(Api.COMPILER_TREE) + public JCTree visitMatchStatement(MatchTree node, P p) { + JCMatch t = (JCMatch) node; + List args = copy(t.args, p); + + return M.at(t.pos).Match(t.clazz, args); + } + @DefinedBy(Api.COMPILER_TREE) public JCTree visitCase(CaseTree node, P p) { JCCase t = (JCCase) node; @@ -295,11 +303,14 @@ public JCTree visitMethod(MethodTree node, P p) { JCExpression restype = copy(t.restype, p); List typarams = copy(t.typarams, p); List params = copy(t.params, p); + List bindings = copy(t.bindings, p); JCVariableDecl recvparam = copy(t.recvparam, p); List thrown = copy(t.thrown, p); JCBlock body = copy(t.body, p); JCExpression defaultValue = copy(t.defaultValue, p); - return M.at(t.pos).MethodDef(mods, t.name, restype, typarams, recvparam, params, thrown, body, defaultValue); + JCMethodDecl decl = M.at(t.pos).MethodDef(mods, t.name, restype, typarams, recvparam, params, thrown, body, defaultValue); + decl.bindings = bindings; + return decl; } @DefinedBy(Api.COMPILER_TREE) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java index b4c6f804a2f..466148f6c3e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java @@ -396,6 +396,12 @@ public JCAssert Assert(JCExpression cond, JCExpression detail) { return tree; } + public JCMatch Match(Name clazz, List args) { + JCMatch tree = new JCMatch(clazz, args); + tree.pos = pos; + return tree; + } + public JCMethodInvocation Apply(List typeargs, JCExpression fn, List args) @@ -829,7 +835,10 @@ public JCExpression Type(Type t) { JCExpression tp; switch (t.getTag()) { case BYTE: case CHAR: case SHORT: case INT: case LONG: case FLOAT: - case DOUBLE: case BOOLEAN: case VOID: + case DOUBLE: case BOOLEAN: + tp = TypeIdent(t.getTag()); + break; + case VOID: tp = TypeIdent(t.getTag()); break; case TYPEVAR: diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeScanner.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeScanner.java index 0336f3c4191..a995ca6f7d3 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeScanner.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeScanner.java @@ -233,6 +233,10 @@ public void visitYield(JCYield tree) { scan(tree.value); } + public void visitMatch(JCMatch tree) { + scan(tree.args); + } + public void visitContinue(JCContinue tree) { } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeTranslator.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeTranslator.java index 63778fb42ff..116be80108e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeTranslator.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeTranslator.java @@ -272,6 +272,11 @@ public void visitYield(JCYield tree) { result = tree; } + public void visitMatch(JCMatch tree) { + tree.args = translate(tree.args); + result = tree; + } + public void visitContinue(JCContinue tree) { result = tree; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java index 6c48490cf16..65f72f9ee39 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java @@ -73,6 +73,8 @@ public static Names instance(Context context) { public final Name when; public final Name with; public final Name yield; + public final Name match; + public final Name pattern; // field and method names public final Name _name; @@ -94,6 +96,8 @@ public static Names instance(Context context) { public final Name hashCode; public final Name init; public final Name invoke; + public final Name component; + public final Name methodType; public final Name iterator; public final Name length; public final Name next; @@ -146,6 +150,7 @@ public static Names instance(Context context) { public final Name LineNumberTable; public final Name LocalVariableTable; public final Name LocalVariableTypeTable; + public final Name Pattern; public final Name MethodParameters; public final Name Module; public final Name ModuleResolution; @@ -181,6 +186,8 @@ public static Names instance(Context context) { public final Name TYPE_PARAMETER; public final Name TYPE_USE; public final Name RECORD_COMPONENT; + public final Name DECONSTRUCTOR; + public final Name PATTERN_BINDING; // members of java.lang.annotation.RetentionPolicy public final Name CLASS; @@ -260,6 +267,8 @@ public Names(Context context) { when = fromString("when"); with = fromString("with"); yield = fromString("yield"); + match = fromString("match"); + pattern = fromString("pattern"); // field and method names _name = fromString("name"); @@ -281,6 +290,8 @@ public Names(Context context) { hashCode = fromString("hashCode"); init = fromString(""); invoke = fromString("invoke"); + component = fromString("component"); + methodType = fromString("methodType"); iterator = fromString("iterator"); length = fromString("length"); next = fromString("next"); @@ -334,6 +345,7 @@ public Names(Context context) { LineNumberTable = fromString("LineNumberTable"); LocalVariableTable = fromString("LocalVariableTable"); LocalVariableTypeTable = fromString("LocalVariableTypeTable"); + Pattern = fromString("Pattern"); MethodParameters = fromString("MethodParameters"); Module = fromString("Module"); ModuleResolution = fromString("ModuleResolution"); @@ -369,6 +381,8 @@ public Names(Context context) { TYPE_PARAMETER = fromString("TYPE_PARAMETER"); TYPE_USE = fromString("TYPE_USE"); RECORD_COMPONENT = fromString("RECORD_COMPONENT"); + DECONSTRUCTOR = fromString("DECONSTRUCTOR"); + PATTERN_BINDING = fromString("PATTERN_BINDING"); // members of java.lang.annotation.RetentionPolicy CLASS = fromString("CLASS"); diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java index 14646fd0275..c306f1d668e 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/CompilerToVM.java @@ -1150,6 +1150,16 @@ ResolvedJavaMethod[] getDeclaredMethods(HotSpotResolvedObjectTypeImpl klass) { native ResolvedJavaMethod[] getDeclaredMethods(HotSpotResolvedObjectTypeImpl klass, long klassPointer); + /** + * Gets the {@link ResolvedJavaMethod}s for all the pattern declarations of {@code klass}. + */ + ResolvedJavaMethod[] getDeclaredDeconstructors(HotSpotResolvedObjectTypeImpl klass) { + return getDeclaredDeconstructors(klass, klass.getKlassPointer()); + } + + native ResolvedJavaMethod[] getDeclaredDeconstructors(HotSpotResolvedObjectTypeImpl klass, long klassPointer); + + HotSpotResolvedObjectTypeImpl.FieldInfo[] getDeclaredFieldsInfo(HotSpotResolvedObjectTypeImpl klass) { return getDeclaredFieldsInfo(klass, klass.getKlassPointer()); } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java index c29feec1712..b3ec1921047 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java @@ -256,6 +256,10 @@ public boolean isMethod(Element e) { return e.getKind() == METHOD; } + public boolean isPatternDeclaration(Element e) { + return e.getKind() == DECONSTRUCTOR; + } + public boolean isModule(Element e) { return e.getKind() == ElementKind.MODULE; } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ElementsTable.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ElementsTable.java index a531a987f0d..1adf492a52b 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ElementsTable.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/ElementsTable.java @@ -1257,7 +1257,7 @@ private ElementKind toTrackedKind(ElementKind kind) { case CLASS, METHOD, MODULE, PACKAGE -> kind; case RECORD, ANNOTATION_TYPE, ENUM, INTERFACE -> ElementKind.CLASS; case CONSTRUCTOR, ENUM_CONSTANT, EXCEPTION_PARAMETER, FIELD, INSTANCE_INIT, LOCAL_VARIABLE, - PARAMETER, RESOURCE_VARIABLE, STATIC_INIT, TYPE_PARAMETER, RECORD_COMPONENT -> ElementKind.METHOD; + PARAMETER, PATTERN_BINDING, RESOURCE_VARIABLE, STATIC_INIT, TYPE_PARAMETER, RECORD_COMPONENT -> ElementKind.METHOD; default -> throw new AssertionError("unsupported kind: " + kind); }; } diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/classfile/Attribute.java b/src/jdk.jdeps/share/classes/com/sun/tools/classfile/Attribute.java index 3ab3fce1863..b46a5b5e588 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/classfile/Attribute.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/classfile/Attribute.java @@ -51,6 +51,7 @@ public abstract class Attribute { public static final String LineNumberTable = "LineNumberTable"; public static final String LocalVariableTable = "LocalVariableTable"; public static final String LocalVariableTypeTable = "LocalVariableTypeTable"; + public static final String Pattern = "Pattern"; public static final String MethodParameters = "MethodParameters"; public static final String Module = "Module"; public static final String ModuleHashes = "ModuleHashes"; @@ -127,6 +128,7 @@ protected void init() { standardAttributes.put(LineNumberTable, LineNumberTable_attribute.class); standardAttributes.put(LocalVariableTable, LocalVariableTable_attribute.class); standardAttributes.put(LocalVariableTypeTable, LocalVariableTypeTable_attribute.class); + standardAttributes.put(Pattern, Pattern_attribute.class); standardAttributes.put(MethodParameters, MethodParameters_attribute.class); standardAttributes.put(Module, Module_attribute.class); standardAttributes.put(ModuleHashes, ModuleHashes_attribute.class); @@ -194,6 +196,7 @@ public interface Visitor { R visitLineNumberTable(LineNumberTable_attribute attr, P p); R visitLocalVariableTable(LocalVariableTable_attribute attr, P p); R visitLocalVariableTypeTable(LocalVariableTypeTable_attribute attr, P p); + R visitMatcher(Pattern_attribute attr, P p); R visitMethodParameters(MethodParameters_attribute attr, P p); R visitModule(Module_attribute attr, P p); R visitModuleHashes(ModuleHashes_attribute attr, P p); diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/classfile/ClassWriter.java b/src/jdk.jdeps/share/classes/com/sun/tools/classfile/ClassWriter.java index a24fb837f43..8058510533e 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/classfile/ClassWriter.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/classfile/ClassWriter.java @@ -526,6 +526,18 @@ protected void writeLocalVariableTypeTableEntry(LocalVariableTypeTable_attribute out.writeShort(entry.index); } + @Override + public Void visitMatcher(Pattern_attribute attr, ClassOutputStream out) { + out.writeShort(attr.pattern_flags); + out.writeShort(attr.pattern_name_index); + out.writeShort(attr.pattern_methodtype.descriptor_index); + int size = attr.attributes.size(); + out.writeShort(size); + for (Attribute componentAttr: attr.attributes) + write(componentAttr, out); + return null; + } + @Override public Void visitNestHost(NestHost_attribute attr, ClassOutputStream out) { out.writeShort(attr.top_index); diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/classfile/Pattern_attribute.java b/src/jdk.jdeps/share/classes/com/sun/tools/classfile/Pattern_attribute.java new file mode 100644 index 00000000000..698f3b2cc76 --- /dev/null +++ b/src/jdk.jdeps/share/classes/com/sun/tools/classfile/Pattern_attribute.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018, 2023, 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 com.sun.tools.classfile; + +import java.io.IOException; + +import static com.sun.tools.classfile.ConstantPool.*; + +/** + *

    This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Pattern_attribute extends Attribute { + public static final int DECONSTRUCTOR = 0x3000; + public static final int TOTAL = 0x4000; + + Pattern_attribute(ClassReader cr, int name_index, int length) throws IOException, ConstantPoolException { + super(name_index, length); + pattern_name_index = cr.readUnsignedShort(); + pattern_flags = cr.readUnsignedShort(); + pattern_methodtype = new CONSTANT_MethodType_info(cr.getConstantPool(), cr.readUnsignedShort()); + attributes = new Attributes(cr); + } + + public Pattern_attribute(int name_index, int pattern_flags, int pattern_name_index, CONSTANT_MethodType_info pattern_methodtype, Attributes attributes) { + super(name_index, 4); + this.pattern_name_index = pattern_name_index; + this.pattern_flags = pattern_flags; + this.pattern_methodtype = pattern_methodtype; + this.attributes = attributes; + } + + @Override + public R accept(Visitor visitor, D data) { + return visitor.visitMatcher(this, data); + } + + public final int pattern_name_index; + public final int pattern_flags; + public final CONSTANT_MethodType_info pattern_methodtype; + public final Attributes attributes; +} diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/AttributeWriter.java b/src/jdk.jdeps/share/classes/com/sun/tools/javap/AttributeWriter.java index 839ac2fd041..8df55b4ed84 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/AttributeWriter.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/AttributeWriter.java @@ -466,6 +466,25 @@ public void write(Attribute a, CodeAttribute lr) { println("// " + attr.targetPlatform().stringValue()); indent(-1); } + case PatternAttribute attr -> { + println("Pattern:"); + indent(+1); + + String nameString = attr.patternName().index() != 0 ? + constantWriter.stringValue(attr.patternName().index()) : ""; + println("pattern_name: " + nameString); + + String flagString = + (attr.patternFlags().contains(AccessFlag.DECONSTRUCTOR) ? "deconstructor " : "") + + (attr.patternFlags().contains(AccessFlag.TOTAL) ? "total" : ""); + println("pattern_flags: " + flagString); + println("pattern_type: " + attr.patternMethodType().stringValue()); + + if (options.showAllAttrs && attr.attributes().size() > 0) { + write(attr.attributes()); + } + indent(-1); + } case NestMembersAttribute attr -> { println("NestMembers:"); indent(+1); diff --git a/src/jdk.jshell/share/classes/jdk/jshell/TreeDependencyScanner.java b/src/jdk.jshell/share/classes/jdk/jshell/TreeDependencyScanner.java index 5efb1847572..369a75c78a1 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/TreeDependencyScanner.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/TreeDependencyScanner.java @@ -83,6 +83,7 @@ public Void visitMethod(MethodTree node, Set p) { scan(node.getReturnType(), p); scan(node.getTypeParameters(), p); scan(node.getParameters(), p); + scan(node.getBindings(), p); scan(node.getReceiverParameter(), p); scan(node.getThrows(), p); scan(node.getBody(), body); diff --git a/test/jdk/java/lang/reflect/Deconstructor/SimpleDeconstructorsTest.java b/test/jdk/java/lang/reflect/Deconstructor/SimpleDeconstructorsTest.java new file mode 100644 index 00000000000..cce39e3148c --- /dev/null +++ b/test/jdk/java/lang/reflect/Deconstructor/SimpleDeconstructorsTest.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +/* + * @test + * @summary SimpleDeconstructorsTest + * @enablePreview + * @compile --enable-preview --source ${jdk.version} -parameters SimpleDeconstructorsTest.java + * @run main/othervm --enable-preview SimpleDeconstructorsTest + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Deconstructor; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class SimpleDeconstructorsTest { + + public static void main(String[] args) throws NoSuchPatternException, IllegalAccessException { + testGetMethods(); + testGetDeclaredDeconstructors(); + testGetDeclaredDeconstructor(); + testDeconstructorElements(); + testGenericDeconstructorElements(); + testInvoke(); + testDeconstructorElementsAnnotations(); + testDeconstructorAnnotations(); + testGenericString(); + } + + public static void testGetMethods() { + Class class1 = Person1.class; + + Method[] methods = class1.getMethods(); + + assertEquals(methods.length, 13); + } + + public static void testGetDeclaredDeconstructors() { + Class class1 = Person1.class; + + Deconstructor[] methods = class1.getDeclaredDeconstructors(); + + assertEquals(methods.length, 3); + } + + public static void testGetDeconstructors() { + Class class1 = Person1.class; + + Deconstructor[] methods = class1.getDeconstructors(); + + assertEquals(methods.length, 2); + } + + public static void testGetDeclaredDeconstructor() throws NoSuchPatternException { + Class class1 = Person1.class; + + Deconstructor method = class1.getDeclaredDeconstructor(String.class, String.class); + + assertEquals(method.getName(), "SimpleDeconstructorsTest$Person1"); + } + + public static void testDeconstructorElements() throws NoSuchPatternException { + Class class1 = Person1.class; + + Deconstructor method = class1.getDeclaredDeconstructor(String.class, String.class); + + var elems = method.getPatternBindings(); + + assertEquals(elems.length, 2); + assertEquals(elems[0].getType(), String.class); + assertEquals(elems[1].getType(), String.class); + assertEquals(elems[0].getName(), "name"); + assertEquals(elems[1].getName(), "username"); + assertEquals(elems[0].getDeclaringDeconstructor(), method); + assertEquals(elems[1].getDeclaringDeconstructor(), method); + } + + public static void testGenericDeconstructorElements() throws NoSuchPatternException { + Class class1 = Person1.class; + + Deconstructor method = class1.getDeclaredDeconstructor(List.class); + + var elems = method.getPatternBindings(); + + assertEquals(elems.length, 1); + assertEquals(elems[0].getType(), List.class); + assertEquals(elems[0].getName(), "name"); + assertEquals(elems[0].getGenericSignature(), "Ljava/util/List;"); + assertEquals(elems[0].getGenericType() instanceof ParameterizedType, true); + } + + public static void testInvoke() throws IllegalAccessException, NoSuchPatternException { + Person1 p = new Person1("Name", "Surname", false); + + Class class1 = Person1.class; + + Deconstructor method = class1.getDeclaredDeconstructor(List.class); + + Object[] bindings = method.invoke(p); + + List expected = List.of('N', 'a', 'm', 'e'); + for (int i = 0; i < 4; i++) { + assertEquals(((List)bindings[0]).get(i), expected.get(i)); + } + } + + public static void testDeconstructorElementsAnnotations() throws NoSuchPatternException { + Class class1 = Person1.class; + + Deconstructor method = class1.getDeclaredDeconstructor(String.class, String.class); + + var elems = method.getPatternBindings(); + + Person1.BindingAnnotation ba = elems[1].getDeclaredAnnotation(Person1.BindingAnnotation.class); + + assertEquals(ba.value(), 1); + } + + public static void testDeconstructorAnnotations() throws NoSuchPatternException { + Class class1 = Person1.class; + + Deconstructor method = class1.getDeclaredDeconstructor(String.class, String.class); + + Person1.DeconstructorAnnotation da = method.getDeclaredAnnotation(Person1.DeconstructorAnnotation.class); + + assertEquals(da.value(), 1); + } + + public static void testGenericString() throws NoSuchPatternException{ + Class class1 = Person1.class; + Deconstructor method = null; + + method = class1.getDeclaredDeconstructor(String.class, String.class); + assertEquals(method.toGenericString(), "public pattern SimpleDeconstructorsTest$Person1(java.lang.String,java.lang.String)"); + + method = class1.getDeclaredDeconstructor(List.class); + assertEquals(method.toGenericString(), "public pattern SimpleDeconstructorsTest$Person1(java.util.List)"); + } + + static void assertEquals(Object actual, Object expected) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } + + public static class Person1 { + private final String name; + private final String username; + private boolean capitalize; + + // 1 declared contructors + public Person1(String name, String username, boolean capitalize) { + this.name = name; + this.username = username; + this.capitalize = capitalize; + } + + // 3 declared pattern declarations but 2 public + @DeconstructorAnnotation(value = 1) + public pattern Person1(String name, @BindingAnnotation(value = 1) String username) { + if (capitalize) { + match Person1(this.name.toUpperCase(), this.username); + } else { + match Person1(this.name, this.username); + } + } + + public pattern Person1(List name) { + match Person1(this.name.chars().mapToObj(e -> (char)e).collect(Collectors.toList())); + } + + private pattern Person1(List name, List username) { + match Person1(this.name.chars().mapToObj(e -> (char)e).collect(Collectors.toList()), + this.username.chars().mapToObj(e -> (char)e).collect(Collectors.toList())); + } + + // 3 methods + public void test1() { } + + public int test2(int i) { + return i++; + } + + public int test3(int i) { + return i++; + } + + public int test4(int i) { + return i++; + } + + @Target(ElementType.PATTERN_BINDING) + @Retention(RetentionPolicy.RUNTIME) + public @interface BindingAnnotation { + int value() default 0; + } + + @Target(ElementType.DECONSTRUCTOR) + @Retention(RetentionPolicy.RUNTIME) + public @interface DeconstructorAnnotation { + int value() default 0; + } + } +} + + + diff --git a/test/jdk/jdk/classfile/CorpusTest.java b/test/jdk/jdk/classfile/CorpusTest.java index 21e275a837d..d1472d196d1 100644 --- a/test/jdk/jdk/classfile/CorpusTest.java +++ b/test/jdk/jdk/classfile/CorpusTest.java @@ -25,6 +25,7 @@ * @test * @bug 8325485 * @summary Testing ClassFile on small Corpus. + * @enablePreview * @build helpers.* testdata.* * @run junit/othervm/timeout=480 -Djunit.jupiter.execution.parallel.enabled=true CorpusTest */ diff --git a/test/jdk/jdk/classfile/PatternTest.java b/test/jdk/jdk/classfile/PatternTest.java new file mode 100644 index 00000000000..fa852d311fc --- /dev/null +++ b/test/jdk/jdk/classfile/PatternTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022, 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. + */ + +/* + * @test + * @summary Testing parsing of a pattern declaration. + * @enablePreview + * @build testdata.* + * @run junit PatternTest + */ + +import java.lang.classfile.*; + +import java.io.IOException; +import java.lang.classfile.attribute.PatternAttribute; +import java.lang.reflect.AccessFlag; +import java.util.ArrayList; +import java.util.List; + +import static helpers.ClassRecord.assertEqualsDeep; +import static helpers.TestUtil.assertEmpty; +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.classfile.components.ClassPrinter; +import org.junit.jupiter.api.Test; + +class PatternTest { + + @Test + void testReadDeconstructor() throws Exception { + List extractedInfo = new ArrayList<>(); + ClassFile cc = ClassFile.of(); + ClassTransform xform = (clb, cle) -> { + if (cle instanceof MethodModel mm) { + clb.transformMethod(mm, (mb, me) -> { + if (me instanceof PatternAttribute ma) { + extractedInfo.add(ma.patternName().toString()); + extractedInfo.add(String.valueOf(ma.patternFlags().contains(AccessFlag.DECONSTRUCTOR))); + extractedInfo.add(ma.patternTypeSymbol().toString()); + extractedInfo.add(ma.attributes().toString()); + mb.with(me); + } else { + mb.with(me); + } + }); + } + else + clb.with(cle); + }; + cc.transform(cc.parse(PatternTest.class.getResourceAsStream("/testdata/Points.class").readAllBytes()), xform); + assertEquals("[Points, true, MethodTypeDesc[(Collection,Collection)void], [Attribute[name=MethodParameters], Attribute[name=Signature], Attribute[name=RuntimeVisibleParameterAnnotations]]]", extractedInfo.toString()); + } + + @Test + void testReadAndVerifyDeconstructor() throws IOException { + byte[] bytes = PatternTest.class.getResourceAsStream("/testdata/Points.class").readAllBytes(); + var cc = ClassFile.of().verify(bytes); + + ClassModel classModel = ClassFile.of().parse(bytes); + + if (!cc.isEmpty()) { + ClassPrinter.toYaml(classModel, ClassPrinter.Verbosity.TRACE_ALL, System.out::print); + assertEmpty(cc); + } + } + + private static void assertOut(StringBuilder out, String expected) { + assertArrayEquals(out.toString().trim().split(" *\r?\n"), expected.trim().split("\n")); + } +} diff --git a/test/jdk/jdk/classfile/StackMapsTest.java b/test/jdk/jdk/classfile/StackMapsTest.java index b3df31291bc..0190260553e 100644 --- a/test/jdk/jdk/classfile/StackMapsTest.java +++ b/test/jdk/jdk/classfile/StackMapsTest.java @@ -25,6 +25,7 @@ * @test * @summary Testing Classfile stack maps generator. * @bug 8305990 8320222 8320618 + * @enablePreview * @build testdata.* * @run junit StackMapsTest */ diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index 0c9b771c3ef..c9f3821c9ed 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -33,6 +33,7 @@ import java.lang.constant.ModuleDesc; import java.lang.constant.PackageDesc; import java.lang.classfile.components.CodeStackTracker; +import java.util.function.Consumer; class RebuildingTransformation { @@ -91,6 +92,25 @@ static byte[] transform(ClassModel clm) { case RuntimeVisibleTypeAnnotationsAttribute a -> mb.with(RuntimeVisibleTypeAnnotationsAttribute.of(transformTypeAnnotations(a.annotations(), null, null))); case SignatureAttribute a -> mb.with(SignatureAttribute.of(MethodSignature.parseFrom(a.asMethodSignature().signatureString()))); case SyntheticAttribute a -> mb.with(SyntheticAttribute.of()); + case PatternAttribute ma -> { + List> patternAttributes = ma.attributes().stream().mapMulti((Attribute rca, Consumer> rcac) -> { + switch(rca) { + case RuntimeInvisibleParameterAnnotationsAttribute a -> + rcac.accept(RuntimeInvisibleParameterAnnotationsAttribute.of(a.parameterAnnotations().stream().map(pas -> List.of(transformAnnotations(pas))).toList())); + case RuntimeVisibleParameterAnnotationsAttribute a -> + rcac.accept(RuntimeVisibleParameterAnnotationsAttribute.of(a.parameterAnnotations().stream().map(pas -> List.of(transformAnnotations(pas))).toList())); + case SignatureAttribute a -> + rcac.accept(SignatureAttribute.of(MethodSignature.parseFrom(a.asMethodSignature().signatureString()))); + case DeprecatedAttribute a -> + rcac.accept(DeprecatedAttribute.of()); + case MethodParametersAttribute a -> + rcac.accept(MethodParametersAttribute.of(a.parameters().stream().map(mp -> + MethodParameterInfo.ofParameter(mp.name().map(Utf8Entry::stringValue), mp.flagsMask())).toArray(MethodParameterInfo[]::new))); + default -> throw new AssertionError("Unexpected annotation in pattern attribute: " + rca.attributeName()); + }}).toList(); + + mb.with(PatternAttribute.of(ma.patternName().stringValue(), ma.patternFlagsMask(), ma.patternTypeSymbol(), patternAttributes)); + } case CustomAttribute a -> throw new AssertionError("Unexpected custom attribute: " + a.attributeName()); case UnknownAttribute a -> throw new AssertionError("Unexpected unknown attribute: " + a.attributeName()); } diff --git a/test/jdk/jdk/classfile/testdata/Points.java b/test/jdk/jdk/classfile/testdata/Points.java new file mode 100644 index 00000000000..7f4d5ed6872 --- /dev/null +++ b/test/jdk/jdk/classfile/testdata/Points.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023, 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 testdata; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Collection; +import java.util.List; + +record Points(Collection xs, Collection ys) { + public static List test(Object o) { + if (o instanceof Points(List xs, List ys)) { + return xs; + } + return List.of(-1); + } + + @MatcherAnnot + public pattern Points(@BindingAnnot Collection xs, @BindingAnnot Collection ys) { + match Points(this.xs, this.ys); + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface MatcherAnnot { } + + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + public @interface BindingAnnot { } +} diff --git a/test/langtools/tools/javac/MethodParameters/AttributeVisitor.java b/test/langtools/tools/javac/MethodParameters/AttributeVisitor.java new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/langtools/tools/javac/PatternDeclarationBindings/PatternReadingTest/BindingProcessor.java b/test/langtools/tools/javac/PatternDeclarationBindings/PatternReadingTest/BindingProcessor.java new file mode 100644 index 00000000000..4e5c5fbfc9a --- /dev/null +++ b/test/langtools/tools/javac/PatternDeclarationBindings/PatternReadingTest/BindingProcessor.java @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * Copyright (c) 2024, 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. + * + * 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 static java.util.stream.Collectors.joining; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Set; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.element.*; +import javax.tools.Diagnostic; + +@SupportedAnnotationTypes("BindingProcessor.Bindings") +public class BindingProcessor extends JavacTestingAbstractProcessor { + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + @interface Bindings { + String[] value() default {}; + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + for (Element element : roundEnv.getElementsAnnotatedWith(Bindings.class)) { + if (element instanceof ExecutableElement exec) { + String message = String.format("%s.%s(%s)", + exec.getEnclosingElement(), + exec.getSimpleName(), + exec.getBindings().stream().map(this::printBinding).collect(joining(", "))); + messager.printMessage(Diagnostic.Kind.OTHER, message); + } + } + return false; + } + + private String printBinding(VariableElement binding) { + return binding.getAnnotationMirrors().stream().map(String::valueOf).collect(joining(" ")) + + (binding.getAnnotationMirrors().isEmpty() ? "" : " ") + + binding.getSimpleName(); + } +} diff --git a/test/langtools/tools/javac/PatternDeclarationBindings/PatternReadingTest/PatternReadingTest.java b/test/langtools/tools/javac/PatternDeclarationBindings/PatternReadingTest/PatternReadingTest.java new file mode 100644 index 00000000000..f46e2d04a9d --- /dev/null +++ b/test/langtools/tools/javac/PatternDeclarationBindings/PatternReadingTest/PatternReadingTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * Copyright (c) 2024, 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. + * + * 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. + */ + +/* + * @test + * @summary Pattern reading of bindings + * @library /tools/javac/lib + * @modules java.compiler + * jdk.compiler + * @enablePreview + * @compile -parameters BindingProcessor.java + * @compile/process/ref=PatternReadingTest.out -XDrawDiagnostics -proc:only -processor BindingProcessor PatternReadingTest.java + */ +import static java.lang.annotation.RetentionPolicy.CLASS; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Retention; + +public class PatternReadingTest { + + @Retention(RUNTIME) + @interface RuntimeAnnotation { + int value() default 0; + } + + @Retention(CLASS) + @interface ClassAnnotation { + int value() default 0; + } + + public static class Person1 { + private final String name; + private final String username; + private boolean capitalize; + + public Person1(String name, String username, boolean capitalize) { + this.name = name; + this.username = username; + this.capitalize = capitalize; + } + + @BindingProcessor.Bindings + public pattern Person1(@ClassAnnotation(21) String name, @RuntimeAnnotation(42) String username) { + if (capitalize) { + match Person1(this.name.toUpperCase(), this.username.toUpperCase()); + } else { + match Person1(this.name, this.username); + } + } + } +} diff --git a/test/langtools/tools/javac/PatternDeclarationBindings/PatternReadingTest/PatternReadingTest.out b/test/langtools/tools/javac/PatternDeclarationBindings/PatternReadingTest/PatternReadingTest.out new file mode 100644 index 00000000000..ac13ac17128 --- /dev/null +++ b/test/langtools/tools/javac/PatternDeclarationBindings/PatternReadingTest/PatternReadingTest.out @@ -0,0 +1,3 @@ +- compiler.note.proc.messager: PatternReadingTest.Person1.Person1(@PatternReadingTest.ClassAnnotation(21) name, @PatternReadingTest.RuntimeAnnotation(42) username) +- compiler.note.preview.filename: PatternReadingTest.java, DEFAULT +- compiler.note.preview.recompile diff --git a/test/langtools/tools/javac/diags/examples.not-yet.txt b/test/langtools/tools/javac/diags/examples.not-yet.txt index 9a447927761..26ca2d4c50b 100644 --- a/test/langtools/tools/javac/diags/examples.not-yet.txt +++ b/test/langtools/tools/javac/diags/examples.not-yet.txt @@ -164,6 +164,7 @@ compiler.err.locn.cant.read.directory # file system issu compiler.err.locn.invalid.arg.for.xpatch # command line option error compiler.misc.unnamed.module # fragment uninteresting in and of itself compiler.misc.kindname.module # fragment uninteresting in and of itself +compiler.misc.kindname.pattern.declaration # fragment uninteresting in and of itself compiler.misc.locn.module_path # fragment uninteresting in and of itself compiler.misc.locn.module_source_path # fragment uninteresting in and of itself compiler.misc.locn.system_modules # fragment uninteresting in and of itself diff --git a/test/langtools/tools/javac/diags/examples/NoCompatibleMatcherFound.java b/test/langtools/tools/javac/diags/examples/NoCompatibleMatcherFound.java new file mode 100644 index 00000000000..dc146e7ae7a --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/NoCompatibleMatcherFound.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +// key: compiler.err.no.compatible.matcher.found +// key: compiler.err.prob.found.req +// key: compiler.misc.inconvertible.types +// key: compiler.misc.feature.pattern.declarations +// key: compiler.warn.preview.feature.use.plural +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +public class NoCompatibleMatcherFound { + private static int test(D o) { + if (o instanceof D(String data, Integer out)) { + return out; + } + return -1; + } + + public static class D { + public pattern D(Object v1, Float out) { + out = 10.0f; + } + + public pattern D(Float out, Integer v1) { + out = 2; + } + } +} \ No newline at end of file diff --git a/test/langtools/tools/javac/diags/examples/PatternDeclaration.java b/test/langtools/tools/javac/diags/examples/PatternDeclaration.java new file mode 100644 index 00000000000..cb1c8c8260f --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/PatternDeclaration.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023, 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. + * + * 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. + */ + + // key: compiler.misc.feature.pattern.declarations + // key: compiler.warn.preview.feature.use.plural + // options: --enable-preview -source ${jdk.version} -Xlint:preview + +public class PatternDeclaration { + + record Test(Integer x, Integer y) { + public pattern Test(Integer x, Integer y) { + match Test(this.x, this.y); + } + } + +} \ No newline at end of file diff --git a/test/langtools/tools/javac/diags/examples/PatternDeclarationCantThrowException.java b/test/langtools/tools/javac/diags/examples/PatternDeclarationCantThrowException.java new file mode 100644 index 00000000000..5e7e0014633 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/PatternDeclarationCantThrowException.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +// key: compiler.misc.feature.pattern.declarations +// key: compiler.warn.preview.feature.use.plural +// key: compiler.err.pattern.declaration.cant.throw.exception +// key: compiler.err.pattern.declaration.no.throws +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +import java.io.IOException; + +public class PatternDeclarationCantThrowException { + + public record ExceptionErrors() { + public pattern ExceptionErrors(float out) throws IOException { // no throws in pattern signature + throw new IOException(); // no throws in patter declaration body + } + } +} diff --git a/test/langtools/tools/javac/diags/examples/PatternDeclarationMatchOutside.java b/test/langtools/tools/javac/diags/examples/PatternDeclarationMatchOutside.java new file mode 100644 index 00000000000..5ca8f61c921 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/PatternDeclarationMatchOutside.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +// key: compiler.misc.feature.pattern.declarations +// key: compiler.warn.preview.feature.use.plural +// key: compiler.err.match.outside.pattern.declaration +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +public class PatternDeclarationMatchOutside { + + record Test(Integer x, Integer y) { + public void meth(Integer x, Integer y) { + match Test(1, 2); + } + } + +} \ No newline at end of file diff --git a/test/langtools/tools/javac/diags/examples/PatternDeclarationNoThrows.java b/test/langtools/tools/javac/diags/examples/PatternDeclarationNoThrows.java new file mode 100644 index 00000000000..60d3769e716 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/PatternDeclarationNoThrows.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +// key: compiler.misc.feature.pattern.declarations +// key: compiler.warn.preview.feature.use.plural +// key: compiler.err.pattern.declaration.no.throws +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +import java.io.IOException; + +public class PatternDeclarationNoThrows { + public record ExceptionErrors() { + public pattern ExceptionErrors(int out) { + throw new Error(); // no throws in patter declaration body + } + } +} diff --git a/test/langtools/tools/javac/diags/examples/PatternDeclarationNotMatchingName.java b/test/langtools/tools/javac/diags/examples/PatternDeclarationNotMatchingName.java new file mode 100644 index 00000000000..d6c565ea85c --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/PatternDeclarationNotMatchingName.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +// key: compiler.misc.feature.pattern.declarations +// key: compiler.warn.preview.feature.use.plural +// key: compiler.err.match.pattern.name.wrong +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +public class PatternDeclarationNotMatchingName { + + record Test(Integer x, Integer y) { + public pattern Test(Integer x, Integer y) { + match Test2(this.x, this.y); + } + } + +} \ No newline at end of file diff --git a/test/langtools/tools/javac/diags/examples/PatternDeclarationNotMatchingSignature.java b/test/langtools/tools/javac/diags/examples/PatternDeclarationNotMatchingSignature.java new file mode 100644 index 00000000000..1dea3cbcbab --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/PatternDeclarationNotMatchingSignature.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +// key: compiler.misc.feature.pattern.declarations +// key: compiler.warn.preview.feature.use.plural +// key: compiler.err.match.not.matching.pattern.declaration.signature +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +public class PatternDeclarationNotMatchingSignature { + + record Test(Integer x, Integer y) { + public pattern Test(Integer x, Integer y) { + match Test(this.y); + } + } + +} \ No newline at end of file diff --git a/test/langtools/tools/javac/diags/examples/PatternDeclarationOverloadingAmbiguity.java b/test/langtools/tools/javac/diags/examples/PatternDeclarationOverloadingAmbiguity.java new file mode 100644 index 00000000000..d3948f60a8d --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/PatternDeclarationOverloadingAmbiguity.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +// key: compiler.err.matcher.overloading.ambiguity +// key: compiler.misc.feature.pattern.declarations +// key: compiler.warn.preview.feature.use.plural +// options: --enable-preview -source ${jdk.version} -Xlint:preview + +public class PatternDeclarationOverloadingAmbiguity { + private static int test(D o) { + if (o instanceof D(String data, Integer out)) { + return out; + } + return -1; + } + + public static class D { + public pattern D(Object v, Integer out) { + match D(1, 1); + } + + public pattern D(CharSequence v, Integer out) { + match D("2", 2); + } + } +} \ No newline at end of file diff --git a/test/langtools/tools/javac/patterns/declarations/ClassPatternDeclarations.java b/test/langtools/tools/javac/patterns/declarations/ClassPatternDeclarations.java new file mode 100644 index 00000000000..747383c38d1 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/ClassPatternDeclarations.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +/** + * @test + * @enablePreview + * @compile -parameters ClassPatternDeclarations.java + * @run main ClassPatternDeclarations + */ +import java.util.Objects; + +public class ClassPatternDeclarations { + public static void main(String... args) { + assertEquals("A:B", test1A(new Person1("A", "B", false))); + assertEquals("A", test1B(new Person1("A", "B", false))); + + assertEquals("Duke", test2(new Person1("Duke", "Java", false))); + assertEquals("DUKE", test2(new Person1("Duke", "Java", true))); + + } + + private static String test1A(Object o) { + if (o instanceof Person1(String name, String username)) { + return name + ":" + username; + } + return null; + } + + private static String test1B(Object o) { + if (o instanceof Person1(String name)) { + return name; + } + return null; + } + + private static String test2(Object o) { + if (o instanceof Person1(String name)) { + return name; + } + return null; + } + + public static class Person1 { + private final String name; + private final String username; + private boolean capitalize; + + public Person1(String name) { + this(name, "default", false); + } + + public Person1(String name, String username, boolean capitalize) { + this.name = name; + this.username = username; + this.capitalize = capitalize; + } + + public pattern Person1(String name, String username) { + match Person1(this.name, this.username); + } + + public pattern Person1(String name) { + if (capitalize) { + match Person1(this.name.toUpperCase()); + } else { + match Person1(this.name); + } + } + } + + private static void assertEquals(T expected, T actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } +} diff --git a/test/langtools/tools/javac/patterns/declarations/ClassReaderTestPatternDeclarations.java b/test/langtools/tools/javac/patterns/declarations/ClassReaderTestPatternDeclarations.java new file mode 100644 index 00000000000..23d10f60bd6 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/ClassReaderTestPatternDeclarations.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +/* + * @test + * @enablePreview + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.util + * jdk.compiler/com.sun.tools.javac.code + * java.base/jdk.internal.classfile + * java.base/jdk.internal.classfile.attribute + * java.base/jdk.internal.classfile.constantpool + * java.base/jdk.internal.classfile.instruction + * java.base/jdk.internal.classfile.components + * java.base/jdk.internal.classfile.impl + * @build toolbox.JavacTask toolbox.ToolBox + * @run main ClassReaderTestPatternDeclarations + */ + +import toolbox.TestRunner; +import toolbox.ToolBox; +import toolbox.JavacTask; +import toolbox.Task; +import toolbox.Task.OutputKind; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +public class ClassReaderTestPatternDeclarations extends TestRunner { + + public static void main(String... args) throws Exception { + ClassReaderTestPatternDeclarations t = new ClassReaderTestPatternDeclarations(); + t.runTests(); + } + + ToolBox tb; + + ClassReaderTestPatternDeclarations() { + super(System.err); + tb = new ToolBox(); + } + + protected void runTests() throws Exception { + runTests(m -> new Object[] { Paths.get(m.getName()) }); + } + + Path[] findJavaFiles(Path... paths) throws IOException { + return tb.findJavaFiles(paths); + } + + @Test + public void testClassReadingDeconstructorInClass(Path base) throws Exception { + Path src = base.resolve("src"); + Path test = base.resolve("test"); + Path out = base.resolve("out"); + Files.createDirectories(out); + + // record with pattern declaration + tb.writeJavaFiles(src, + "package test;\n" + + "public class Point {\n" + + " public Integer x = 0, y = 0;\n" + + " public pattern Point(Integer x, Integer y) {\n" + + " match Point(this.x, this.y);" + + " }\n" + + "}"); + + new JavacTask(tb) + .outdir(out) + .options(List.of("--enable-preview", "-source", System.getProperty("java.specification.version"))) + .files(findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll() + .getOutputLines(OutputKind.DIRECT); + + //XXX: note the type is erased!!! + // test file + tb.writeJavaFiles(test, + "import test.Point;\n" + + "public class Test {\n" + + " public static Integer testPointX(Object o) {\n" + + " if (o instanceof Point(Integer x, Integer y)) {\n" + + " return x;\n" + + " }\n" + + " return -1;\n" + + " }" + + "}"); + + new JavacTask(tb) + .classpath(out) + .options(List.of("--enable-preview", "-source", System.getProperty("java.specification.version"))) + .files(findJavaFiles(test)) + .run(Task.Expect.SUCCESS) + .writeAll(); + } +} diff --git a/test/langtools/tools/javac/patterns/declarations/ExampleJSONTest.java b/test/langtools/tools/javac/patterns/declarations/ExampleJSONTest.java new file mode 100644 index 00000000000..96982917a33 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/ExampleJSONTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +/** + * @test + * @enablePreview + * @compile ExampleJSONTest.java + * @run main ExampleJSONTest + */ + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +public class ExampleJSONTest { + + public static void main(String[] args) { + JSONObject j = new JSONObject( + Map.of("configuration", new JSONString("7"), "services", new JSONArray(List.of( + new JSONObject(Map.of("name", new JSONString("a"), "id", new JSONNumber(3))), + new JSONObject(Map.of("name", new JSONString("b"), "id", new JSONNumber(4))))))); + + assertEquals("a", serviceToNameView().apply(j)); + } + + record View(Function f) implements Function { + @Override + public B apply(A a) { + return f.apply(a); + } + public static View of(Function f) { + return new View<>(f); + } + } + + private static View serviceToNameView() { + return View.of((JSONObject j) -> switch (Service.Of(j)) { + case Service(String name, int id) -> name; + default -> throw new IllegalStateException("Unexpected value: " + j); + }); + } + + sealed interface JSONValue { + } + + record JSONString(String value) implements JSONValue { + } + + record JSONNumber(double value) implements JSONValue { + } + + record JSONObject(Map members) implements JSONValue { + } + + record JSONArray(List values) implements JSONValue { + } + + public static class Service { + String name; + int id; + JSONObject blob; + + private Service(JSONObject blob) { + this.blob = blob; + } + + public Service(String name, int id) { + this.name = name; + this.id = id; + } + + public static Service Of(JSONObject blob) { + return new Service(blob); + } + + public pattern Service(String name, int id) { + if (blob instanceof JSONObject(var keys) && + keys.get("services") instanceof JSONArray(var array) && array.size() == 2 + && array.get(0) instanceof JSONObject(var keys2) + && keys2.get("name") instanceof JSONString(var name2) + && keys2.get("id") instanceof JSONNumber(int id2)) { + match Service(name2, id2); + } + } + } + + private static void assertEquals(T expected, T actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } +} diff --git a/test/langtools/tools/javac/patterns/declarations/ExampleStringTest.java b/test/langtools/tools/javac/patterns/declarations/ExampleStringTest.java new file mode 100644 index 00000000000..5b2cd8e94d7 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/ExampleStringTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +/** + * @test + * @enablePreview + * @compile ExampleStringTest.java + * @run main ExampleStringTest + */ + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class ExampleStringTest { + + interface StringOps { + interface Split { + pattern Split(String left, String right) { + String[] parts = getString().split(getDelim()); + match Split(parts[0], parts[1]); + } + + String getDelim(); + String getString(); + } + + interface Int { + pattern Int(int datum) { + int res = 0; + try { + res = Integer.parseInt(getString()); + match Int(res); + } catch (NumberFormatException n) { + match Int(0); + } + } + + String getString(); + } + + default int examineString(Split s) { + return switch (s) { + case Split(String left, String right) -> { + Int int1 = new Int() { + @Override + public String getString() { + return left; + } + }; + + Int int2 = new Int() { + @Override + public String getString() { + return right; + } + }; + + yield switch (int1) { + case Int(int res1) -> { + int res = switch (int2) { + case Int(int res2) -> { + yield res1 + res2; + } + default -> 0; + }; + yield res; + } + default -> 0; + }; + } + default -> 0; + }; + } + } + + static class StringOpsImpl implements StringOps { + static class Split1 implements Split { + @Override + public String getDelim() { + return ":"; + } + + @Override + public String getString() { + return "12:20"; + } + } + + static class Split2 implements Split { + @Override + public String getDelim() { + return "-"; + } + + @Override + public String getString() { + return "24-40"; + } + } + } + + public static void main(String[] args) { + assertEquals(32, new StringOpsImpl().examineString(new StringOpsImpl.Split1())); + assertEquals(64, new StringOpsImpl().examineString(new StringOpsImpl.Split2())); + } + + private static void assertEquals(T expected, T actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } +} diff --git a/test/langtools/tools/javac/patterns/declarations/InterfacePatternDeclarations.java b/test/langtools/tools/javac/patterns/declarations/InterfacePatternDeclarations.java new file mode 100644 index 00000000000..1985d025ba2 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/InterfacePatternDeclarations.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +/** + * @test + * @enablePreview + * @compile InterfacePatternDeclarations.java + * @run main InterfacePatternDeclarations + */ +import java.util.*; +import java.util.function.BiConsumer; + +public class InterfacePatternDeclarations { + + interface Map { + interface Entry { + pattern Entry(K k, V v) { + match Entry(this.getKey(), this.getValue()); + } + + K getKey(); + V getValue(); + } + + Set> entrySet(); + + default void forEach(BiConsumer action) { + Objects.requireNonNull(action); + for (Entry entry : entrySet()) { + if (entry instanceof Entry(K k, V v)){ + action.accept(k, v); + } + } + } + } + + static class MyMap implements Map { + class MyEntry implements Map.Entry { + @Override + public String getKey() { + return "KEY"; + } + + @Override + public String getValue() { + return "VALUE"; + } + } + + @Override + public Set> entrySet() { + return Set.of(new MyEntry()); + } + } + + public static void main(String[] args) { + MyMap map = new MyMap(); + StringBuilder res = new StringBuilder(); + map.forEach((k, v) -> res.append(k + " -> " + v)); + assertEquals("KEY -> VALUE", res.toString()); + } + + private static void assertEquals(T expected, T actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } +} diff --git a/test/langtools/tools/javac/patterns/declarations/MultiplePatternDeclarations.java b/test/langtools/tools/javac/patterns/declarations/MultiplePatternDeclarations.java new file mode 100644 index 00000000000..54c921c0182 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/MultiplePatternDeclarations.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +/** + * @test + * @enablePreview + * @compile MultiplePatternDeclarations.java + * @run main MultiplePatternDeclarations + */ +import java.util.Objects; + +public class MultiplePatternDeclarations { + public static void main(String... args) { + assertEquals("A:B", test1A(new Person1("A", "B"))); + assertEquals("A", test1B(new Person1("A", "B"))); + } + + private static String test1A(Object o) { + if (o instanceof Person1(String name, String username)) { + return name + ":" + username; + } + return null; + } + + private static String test1B(Object o) { + if (o instanceof Person1(String name)) { + return name; + } + return null; + } + + public static class Person1 { + private final String name; + private final String username; + + public Person1(String name, String username) { + this.name = name; + this.username = username; + } + + public Person1(String name) { + this(name, name); + } + + public pattern Person1(String name, String username) { + match Person1(this.name, this.username); + } + + public pattern Person1(String name) { + match Person1(this.name); + } + } + + private static void assertEquals(T expected, T actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } +} diff --git a/test/langtools/tools/javac/patterns/declarations/OverloadedPatternDeclarationErrors.java b/test/langtools/tools/javac/patterns/declarations/OverloadedPatternDeclarationErrors.java new file mode 100644 index 00000000000..5aff2c58213 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/OverloadedPatternDeclarationErrors.java @@ -0,0 +1,41 @@ +/* + * @test /nodynamiccopyright/ + * @summary Verify error related to annotations and patterns + * @enablePreview + * @compile/fail/ref=OverloadedPatternDeclarationErrors.out -XDrawDiagnostics -XDdev OverloadedPatternDeclarationErrors.java + */ +public class OverloadedPatternDeclarationErrors { + private static int test(D o) { + if (o instanceof D(String data, Integer out)) { // no compatible matcher found + return out; + } + return -1; + } + + private static int test2(D2 o) { + if (o instanceof D2(String data, Integer out)) { // ambiguous + return out; + } + return -1; + } + + public static class D { + public pattern D(Object v1, Float out) { + match D(10.0f, 10.0f); + } + + public pattern D(Float out, Integer v1) { + match D(10.0f, 2); + } + } + + public static class D2 { + public pattern D2(Object v, Integer out) { + match D2("", 1); + } + + public pattern D2(CharSequence v, Integer out) { + match D2("", 2); + } + } +} diff --git a/test/langtools/tools/javac/patterns/declarations/OverloadedPatternDeclarationErrors.out b/test/langtools/tools/javac/patterns/declarations/OverloadedPatternDeclarationErrors.out new file mode 100644 index 00000000000..dd3174f6ee0 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/OverloadedPatternDeclarationErrors.out @@ -0,0 +1,5 @@ +OverloadedPatternDeclarationErrors.java:9:26: compiler.err.no.compatible.matcher.found +OverloadedPatternDeclarationErrors.java:16:26: compiler.err.matcher.overloading.ambiguity +- compiler.note.preview.filename: OverloadedPatternDeclarationErrors.java, DEFAULT +- compiler.note.preview.recompile +2 errors \ No newline at end of file diff --git a/test/langtools/tools/javac/patterns/declarations/OverloadedPatternDeclarations.java b/test/langtools/tools/javac/patterns/declarations/OverloadedPatternDeclarations.java new file mode 100644 index 00000000000..453f6cd150f --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/OverloadedPatternDeclarations.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024, 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. + * + * 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.util.Objects; + +/** + * @test + * @enablePreview + * @compile OverloadedPatternDeclarations.java + * @run main OverloadedPatternDeclarations + */ +public class OverloadedPatternDeclarations { + public static void main(String... args) { + assertEquals( 2, test1(new D())); + assertEquals( 1, test2(new D())); + assertEquals( 1, test3(new D())); + assertEquals( 3, test4(new D())); + assertEquals( 4, test5(new D())); + } + + private static int test1(D o) { + if (o instanceof D(String data, Integer outI)) { + return outI; + } + return -1; + } + + private static int test2(D o) { + if (o instanceof D(Object data, Integer outI)) { + return outI; + } + return -1; + } + + private static int test3(D o) { + if (o instanceof D(Integer data, Integer outI)) { + return outI; + } + return -1; + } + + private static int test4(D o) { + if (o instanceof D(A data, Integer outI)) { + return outI; + } + return -1; + } + + private static Integer test5(D o) { + if (o instanceof D(B data, Integer outI)) { + return outI; + } + return null; + } + + static class A {} + static class B extends A {} + + public static class D { + public pattern D(Object out, Integer outI) { + match D(42, 1); + } + + public pattern D(String out, Integer outI) { + match D("2", 2); + } + + public pattern D(A out, Integer outI) { + match D(new A(), 3); + } + + public pattern D(B out, Integer outI) { + match D(new B(), 4); + } + } + + private static void assertEquals(int expected, int actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } +} diff --git a/test/langtools/tools/javac/patterns/declarations/OverloadedPrimitivePatternDeclarations.java b/test/langtools/tools/javac/patterns/declarations/OverloadedPrimitivePatternDeclarations.java new file mode 100644 index 00000000000..08e3fe93ea5 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/OverloadedPrimitivePatternDeclarations.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024, 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. + * + * 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.util.Objects; + +/** + * @test + * @enablePreview + * @compile OverloadedPrimitivePatternDeclarations.java + * @run main OverloadedPrimitivePatternDeclarations + */ + +public class OverloadedPrimitivePatternDeclarations { + public static void main(String... args) { + assertEquals( 1, testBoxing(new D())); + } + + private static int testBoxing(D o) { + if (o instanceof D(String data, Integer outI)) { + return outI; + } + return -1; + } + + public static class D { + public pattern D(String out, int outI) { + match D("42", 1); + } + } + + private static void assertEquals(int expected, int actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } +} diff --git a/test/langtools/tools/javac/patterns/declarations/PatternDeclarationErrors.java b/test/langtools/tools/javac/patterns/declarations/PatternDeclarationErrors.java new file mode 100644 index 00000000000..dd7888a8c18 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/PatternDeclarationErrors.java @@ -0,0 +1,56 @@ +import java.io.IOException; + +/* + * @test /nodynamiccopyright/ + * @summary Verify error related to annotations and patterns + * @enablePreview + * @compile/fail/ref=PatternDeclarationErrors.out -XDrawDiagnostics -XDdev --should-stop=at=FLOW PatternDeclarationErrors.java + */ +public class PatternDeclarationErrors { + public record D() { + public pattern D(Object v1, Float out) { + match D(10.0f); // not matching signature + } + + public pattern D(Float out, Integer v1) { + match D2(10.0f, 2); // no matching name of pattern declaration + } + + public pattern D(Float out, Double v1) { + match D(10.0f, "2"); // inconvertible types + } + + public void D3(Float out, Integer v1) { + match D3(10.0f, 2); // match in regular method + } + + public pattern D(Float out, Character v1) { + match D(10.0f, '2'); + System.out.println("unreachable"); // unreachable + } + } + + public record ExitErrors() { + public pattern ExitErrors(int out) { + break; // no break + } + + public pattern ExitErrors(float out) { + return Integer.valueOf(42); // no return + } + + public pattern ExitErrors(char out) { + yield Integer.valueOf(42); // no yield + } + } + + public record ExceptionErrors() { + public pattern ExceptionErrors(int out) { + throw new Error(); // no throws in patter declaration body + } + + public pattern ExceptionErrors(float out) throws IOException { // no throws in pattern signature + throw new IOException(); // no throws in patter declaration body + } + } +} diff --git a/test/langtools/tools/javac/patterns/declarations/PatternDeclarationErrors.out b/test/langtools/tools/javac/patterns/declarations/PatternDeclarationErrors.out new file mode 100644 index 00000000000..eb286f53b64 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/PatternDeclarationErrors.out @@ -0,0 +1,14 @@ +PatternDeclarationErrors.java:12:13: compiler.err.match.not.matching.pattern.declaration.signature +PatternDeclarationErrors.java:16:13: compiler.err.match.pattern.name.wrong +PatternDeclarationErrors.java:20:28: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.String, java.lang.Double) +PatternDeclarationErrors.java:24:13: compiler.err.match.outside.pattern.declaration +PatternDeclarationErrors.java:35:12: compiler.err.break.outside.switch.loop +PatternDeclarationErrors.java:39:13: compiler.err.ret.outside.meth +PatternDeclarationErrors.java:43:13: compiler.err.no.switch.expression +PatternDeclarationErrors.java:49:13: compiler.err.pattern.declaration.no.throws +PatternDeclarationErrors.java:52:24: compiler.err.pattern.declaration.cant.throw.exception +PatternDeclarationErrors.java:53:13: compiler.err.pattern.declaration.no.throws +PatternDeclarationErrors.java:29:13: compiler.err.unreachable.stmt +- compiler.note.preview.filename: PatternDeclarationErrors.java, DEFAULT +- compiler.note.preview.recompile +11 errors diff --git a/test/langtools/tools/javac/patterns/declarations/PatternDeclarationsBytecodeTest.java b/test/langtools/tools/javac/patterns/declarations/PatternDeclarationsBytecodeTest.java new file mode 100644 index 00000000000..8a224759015 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/PatternDeclarationsBytecodeTest.java @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +/* + * @test + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.jdeps/com.sun.tools.javap + * @enablePreview + * @build toolbox.ToolBox toolbox.JavapTask + * @run main PatternDeclarationsBytecodeTest + */ + +import toolbox.*; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Gatherers; +import java.util.stream.Stream; + +public class PatternDeclarationsBytecodeTest extends TestRunner { + private ToolBox tb; + private static final String SOURCE_VERSION = System.getProperty("java.specification.version"); + + public static void main(String... args) throws Exception { + new PatternDeclarationsBytecodeTest().runTests(); + } + + PatternDeclarationsBytecodeTest() { + super(System.err); + tb = new ToolBox(); + } + + public void runTests() throws Exception { + runTests(m -> new Object[] { Paths.get(m.getName()) }); + } + + @Test + public void testMethodSignature(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeJavaFiles(src, + """ + package test; + public class Test { + private final String name = ""; + private final String username = ""; + + public pattern Test(String name, String username) { + match Test(this.name, this.username); + } + } + """); + + Files.createDirectories(classes); + + { + new JavacTask(tb) + .options("--enable-preview", "--release", SOURCE_VERSION) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + + String javapOut = new JavapTask(tb) + .options("-v") + .classpath(classes.toString()) + .classes("test.Test") + .run() + .getOutput(Task.OutputKind.DIRECT); + + if (!javapOut.contains("public static java.lang.Object Test\\%Ljava\\|lang\\|String\\?\\%Ljava\\|lang\\|String\\?(test.Test);")) + throw new AssertionError("Wrongly generated signature of pattern declaration:\n" + javapOut); + } + } + + @Test + public void testPolymorphicSignatureInPatternAttribute(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeJavaFiles(src, + """ + package test; + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + import java.util.Collection; + import java.util.List; + import java.util.Objects; + + public class Test { + private Collection xs = null; + private Collection ys = null; + + public pattern Test(Collection xs, Collection ys) { + match Test(this.xs, this.ys); + } + } + """); + + Files.createDirectories(classes); + + { + new JavacTask(tb) + .options("--enable-preview", "--release", SOURCE_VERSION) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + + String javapOut = new JavapTask(tb) + .options("-v") + .classpath(classes.toString()) + .classes("test.Test") + .run() + .getOutput(Task.OutputKind.DIRECT); + + String[] outputs = { + "Signature: #36 // (Ljava/util/Collection;Ljava/util/Collection;)V", + }; + + if (!Arrays.stream(outputs).allMatch(o -> javapOut.contains(o))) + throw new AssertionError("Wrongly generated Signature in Pattern attribute with generic bindings:\n" + javapOut); + } + } + + @Test + public void testBindingAnnotationsInPatternAttribute(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeJavaFiles(src, + """ + package test; + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + import java.util.Collection; + import java.util.List; + import java.util.Objects; + + public class Test { + private Integer xs = null; + private Integer ys = null; + + public pattern Test(@BindingAnnotation Integer xs, @BindingAnnotation Integer ys) { + match Test(this.xs, this.ys); + } + + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + public @interface BindingAnnotation { } + } + """); + + Files.createDirectories(classes); + + { + new JavacTask(tb) + .options("--enable-preview", "--release", SOURCE_VERSION) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + + String javapOut = new JavapTask(tb) + .options("-v") + .classpath(classes.toString()) + .classes("test.Test") + .run() + .getOutput(Task.OutputKind.DIRECT); + + String o = """ + RuntimeVisibleParameterAnnotations: + parameter 0: + 0: #35() + test.Test$BindingAnnotation + parameter 1: + 0: #35() + test.Test$BindingAnnotation + """; + + containsOrdered(o, javapOut, "Wrongly generated Pattern attribute with binding annotations:\n"); + } + } + + @Test + public void testPatternAttribute(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeJavaFiles(src, + """ + package test; + public class Test { + private final String name = ""; + private final String username = ""; + + public pattern Test(String name, String username) { + match Test(this.name, this.username); + } + } + """); + + Files.createDirectories(classes); + + { + new JavacTask(tb) + .options("--enable-preview", "--release", SOURCE_VERSION) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + + String javapOut = new JavapTask(tb) + .options("-v") + .classpath(classes.toString()) + .classes("test.Test") + .run() + .getOutput(Task.OutputKind.DIRECT); + String o = """ + Pattern: + pattern_name: Test + pattern_flags: deconstructor + pattern_type: (Ljava/lang/String;Ljava/lang/String;)V + """; + containsOrdered(o, javapOut, "Wrongly generated basic structure of Pattern attribute:\n"); + } + } + + @Test + public void testParameterAttributeInPatternAttribute(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeJavaFiles(src, + """ + package test; + public class Test { + private final String name = ""; + private final String username = ""; + + public pattern Test(String name, String username) { + match Test(this.name, this.username); + } + } + """); + + Files.createDirectories(classes); + + { + new JavacTask(tb) + .options("-parameters", "--enable-preview", "--release", SOURCE_VERSION) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + + String javapOut = new JavapTask(tb) + .options("-v") + .classpath(classes.toString()) + .classes("test.Test") + .run() + .getOutput(Task.OutputKind.DIRECT); + + String output = """ + Pattern: + pattern_name: Test + pattern_flags: deconstructor + pattern_type: (Ljava/lang/String;Ljava/lang/String;)V + MethodParameters: + Name Flags + name synthetic + username synthetic + """; + + containsOrdered(output, javapOut, "Wrongly MethodParameters attribute:\n"); + } + } + + private static void containsOrdered(String expected, String actual, String message) { + List expectedLines = expected.lines().map(s -> s.strip()).toList(); + Stream actualLines = actual.lines().map(s -> s.strip()); + + if (!actualLines.gather(Gatherers.windowSliding(expectedLines.size())).anyMatch(window -> window.equals(expectedLines))) + throw new AssertionError(message + actual); + } +} \ No newline at end of file diff --git a/test/langtools/tools/javac/patterns/declarations/RecordPatternDeclarations.java b/test/langtools/tools/javac/patterns/declarations/RecordPatternDeclarations.java new file mode 100644 index 00000000000..5d3f294ebd4 --- /dev/null +++ b/test/langtools/tools/javac/patterns/declarations/RecordPatternDeclarations.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +/* + * @test + * @enablePreview + * @compile -parameters RecordPatternDeclarations.java + * @run main RecordPatternDeclarations + */ +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +public class RecordPatternDeclarations { + public static void main(String... args) { + assertEquals(1, testPointX(new Point(1, 2))); + assertEquals(2, testPointY(new Point(1, 2))); + assertEquals(2, testMultipleCasesPoint(new Point(42, 4))); + assertEquals(3, testMultipleCasesPoint(new Point(4, 42))); + assertEquals(List.of(1), testPoints(new Points(List.of(1), List.of(2)))); + } + + public static Integer testPointX(Object o) { + if (o instanceof Point(Integer x, Integer y)) { + return x; + } + return -1; + } + + public static Integer testPointY(Object o) { + if (o instanceof Point(Integer x, Integer y)) { + return y; + } + return -1; + } + + public static int testMultipleCasesPoint(Point o) { + return switch (o) { + case Point(Integer x, Integer y) when x == 42 -> 2; + case Point(Integer x, Integer y) when y == 42 -> 3; + case Point mm -> -1; + }; + } + + public static List testPoints(Object o) { + if (o instanceof Points(List xs, List ys)) { + return xs; + } + return List.of(-1); + } + + public record Points(Collection xs, Collection ys) { + @MatcherAnnotation(annotField = 42) + public pattern Points(@BindingAnnotation Collection xs, @BindingAnnotation Collection ys) { + match Points(this.xs, this.ys); + } + } + + public record Point(Integer x, Integer y) { + @MatcherAnnotation(annotField = 42) + public pattern Point(Integer x, Integer y) { + match Point(this.x, this.y); + } + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface MatcherAnnotation{ + int annotField(); + } + + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + public @interface BindingAnnotation { } + + private static void assertEquals(T expected, T actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + } +} diff --git a/test/langtools/tools/javac/processing/model/element/TestElementKindPredicates.java b/test/langtools/tools/javac/processing/model/element/TestElementKindPredicates.java index 49b3c72ac39..a5feb009073 100644 --- a/test/langtools/tools/javac/processing/model/element/TestElementKindPredicates.java +++ b/test/langtools/tools/javac/processing/model/element/TestElementKindPredicates.java @@ -69,12 +69,13 @@ public static void main(String... args) { (ElementKind k) -> k.isDeclaredType(), "isDeclaredType"); // isExecutable: Returns true if this is a kind of executable: one of - // METHOD, CONSTRUCTOR, STATIC_INIT, INSTANCE_INIT + // METHOD, CONSTRUCTOR, STATIC_INIT, INSTANCE_INIT, DECONSTRUCTOR test(ALL_KINDS, (ElementKind k) -> Set.of(ElementKind.METHOD, ElementKind.CONSTRUCTOR, ElementKind.STATIC_INIT, - ElementKind.INSTANCE_INIT).contains(k), + ElementKind.INSTANCE_INIT, + ElementKind.DECONSTRUCTOR).contains(k), (ElementKind k) -> k.isExecutable(), "isExecutable"); // isInitializer: Returns true if this is a kind of initializer: one of diff --git a/test/langtools/tools/javac/processing/model/element/TestPatternDeclarationExecutableElement.java b/test/langtools/tools/javac/processing/model/element/TestPatternDeclarationExecutableElement.java new file mode 100644 index 00000000000..802bcf9693e --- /dev/null +++ b/test/langtools/tools/javac/processing/model/element/TestPatternDeclarationExecutableElement.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +/* + * @test + * @summary Test basic properties of the PatternDeclaration kind of javax.lang.element.ExecutableElement + * @author Angelos Bimpoudis + * @library /tools/javac/lib + * @modules java.compiler + * jdk.compiler + * @enablePreview + * @build JavacTestingAbstractProcessor + * @compile TestPatternDeclarationExecutableElement.java + * @compile --enable-preview --source ${jdk.version} -processor TestPatternDeclarationExecutableElement -proc:only TestPatternDeclarationExecutableElementData.java + */ + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.NoType; +import javax.lang.model.util.ElementFilter; +import java.util.Set; + +import static javax.lang.model.util.ElementFilter.constructorsIn; +import static javax.lang.model.util.ElementFilter.deconstructorsIn; + +/** + * Test basic workings of javax.lang.element.ExecutableElement for pattern declarations + */ +public class TestPatternDeclarationExecutableElement extends JavacTestingAbstractProcessor { + private final String name = "Name"; + private boolean capitalize = false; + + public boolean process(Set annotations, + RoundEnvironment roundEnv) { + if (!roundEnv.processingOver()) { + int count = 0; + for (Element element : roundEnv.getRootElements()) { + for (ExecutableElement pattern : ElementFilter.deconstructorsIn(element.getEnclosedElements())) { + count++; + var simpleName = pattern.getSimpleName(); + var returnType = pattern.getReturnType(); + var parameters = pattern.getParameters(); + var bindings = pattern.getBindings(); + var type = pattern.asType(); + + if (!simpleName.contentEquals("TestPatternDeclarationExecutableElementData")) + throw new RuntimeException("Unexpected name for deconstructor " + simpleName); + + if (!(returnType instanceof NoType)) + throw new RuntimeException("Unexpected return type for deconstructor " + returnType); + + if (!parameters.isEmpty()) + throw new RuntimeException("Unexpected executable element parameters for the deconstructor " + parameters); + + if (bindings.isEmpty()) + throw new RuntimeException("Unexpected executable element bindings for the deconstructor " + bindings); + + if (!(type instanceof ExecutableType)) + throw new RuntimeException("Unexpected executable element type for the deconstructor " + type); + } + } + if (count != 2) + throw new RuntimeException("No valid number of deconstructors!"); + } + return true; + } +} diff --git a/test/langtools/tools/javac/processing/model/element/TestPatternDeclarationExecutableElementData.java b/test/langtools/tools/javac/processing/model/element/TestPatternDeclarationExecutableElementData.java new file mode 100644 index 00000000000..998a75d7bd4 --- /dev/null +++ b/test/langtools/tools/javac/processing/model/element/TestPatternDeclarationExecutableElementData.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +public class TestPatternDeclarationExecutableElementData { + private final String name = "Name"; + private boolean capitalize = false; + + public pattern TestPatternDeclarationExecutableElementData(String name) { + if (capitalize) { + match TestPatternDeclarationExecutableElementData(this.name.toUpperCase()); + } else { + match TestPatternDeclarationExecutableElementData(this.name); + } + } + + public pattern TestPatternDeclarationExecutableElementData(String originalName, String editedName) { + if (capitalize) { + match TestPatternDeclarationExecutableElementData(this.name, this.name.toUpperCase()); + } else { + match TestPatternDeclarationExecutableElementData(this.name, this.name); + } + } +} diff --git a/test/langtools/tools/javac/records/RecordCompilationTests.java b/test/langtools/tools/javac/records/RecordCompilationTests.java index 9f5e73cc5d0..8efdd0aaa70 100644 --- a/test/langtools/tools/javac/records/RecordCompilationTests.java +++ b/test/langtools/tools/javac/records/RecordCompilationTests.java @@ -440,7 +440,7 @@ void testAnnotationCriteria() { for (ElementType e : values()) annotations.put(e, template.replace("#", "ElementType." + e.name())); EnumSet goodSet = EnumSet.of(RECORD_COMPONENT, FIELD, METHOD, PARAMETER, TYPE_USE); - EnumSet badSet = EnumSet.of(CONSTRUCTOR, PACKAGE, TYPE, LOCAL_VARIABLE, ANNOTATION_TYPE, TYPE_PARAMETER, MODULE); + EnumSet badSet = EnumSet.of(CONSTRUCTOR, PACKAGE, TYPE, LOCAL_VARIABLE, ANNOTATION_TYPE, TYPE_PARAMETER, MODULE, DECONSTRUCTOR, PATTERN_BINDING); Assert.check(goodSet.size() + badSet.size() == values().length); String A_GOOD = template.replace("#",