From 099ddebbd0aafb423371855b4a69fab4a27ce597 Mon Sep 17 00:00:00 2001 From: liach Date: Sat, 18 Sep 2021 13:31:17 -0500 Subject: [PATCH] Preliminary work on getting records and sealed classes right Signed-off-by: liach --- .../filament/mappingpoet/ClassBuilder.java | 90 ++- .../filament/mappingpoet/FieldBuilder.java | 530 +++++++++--------- .../fabricmc/filament/mappingpoet/Main.java | 35 +- .../filament/mappingpoet/ModifierBuilder.java | 44 +- 4 files changed, 405 insertions(+), 294 deletions(-) diff --git a/filament/src/main/java/net/fabricmc/filament/mappingpoet/ClassBuilder.java b/filament/src/main/java/net/fabricmc/filament/mappingpoet/ClassBuilder.java index b687abf96f..1a1d0b131f 100644 --- a/filament/src/main/java/net/fabricmc/filament/mappingpoet/ClassBuilder.java +++ b/filament/src/main/java/net/fabricmc/filament/mappingpoet/ClassBuilder.java @@ -16,34 +16,45 @@ package net.fabricmc.mappingpoet; -import static net.fabricmc.mappingpoet.FieldBuilder.parseAnnotation; - -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Function; - import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.TypeReference; -import org.objectweb.asm.tree.AnnotationNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.FieldNode; -import org.objectweb.asm.tree.InnerClassNode; -import org.objectweb.asm.tree.MethodNode; - import net.fabricmc.mappingpoet.signature.AnnotationAwareDescriptors; import net.fabricmc.mappingpoet.signature.AnnotationAwareSignatures; import net.fabricmc.mappingpoet.signature.ClassSignature; import net.fabricmc.mappingpoet.signature.ClassStaticContext; import net.fabricmc.mappingpoet.signature.TypeAnnotationMapping; import net.fabricmc.mappingpoet.signature.TypeAnnotationStorage; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.TypeReference; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.InnerClassNode; +import org.objectweb.asm.tree.InvokeDynamicInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +import static net.fabricmc.mappingpoet.FieldBuilder.parseAnnotation; public class ClassBuilder { + static final Handle OBJ_MTH_BOOTSTRAP = new Handle( + Opcodes.H_INVOKESTATIC, + "java/lang/runtime/ObjectMethods", + "bootstrap", + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;", + false + ); private final MappingsStore mappings; private final ClassNode classNode; @@ -51,21 +62,24 @@ public class ClassBuilder { private final TypeSpec.Builder builder; private final List innerClasses = new ArrayList<>(); private final Function> superGetter; + private final Predicate sealChecker; private final ClassStaticContext context; private final ClassSignature signature; // not really signature private final TypeAnnotationMapping typeAnnotations; private boolean annotationClass; private boolean enumClass; + private boolean recordClass; private boolean instanceInner = false; // only nonnull if any class in the inner class chain creates a generic decl // omits L and ; private String receiverSignature; - public ClassBuilder(MappingsStore mappings, ClassNode classNode, Function> superGetter, ClassStaticContext context) { + public ClassBuilder(MappingsStore mappings, ClassNode classNode, Function> superGetter, Predicate sealChecker, ClassStaticContext context) { this.mappings = mappings; this.classNode = classNode; this.superGetter = superGetter; + this.sealChecker = sealChecker; this.context = context; this.typeAnnotations = setupAnnotations(); this.signature = setupSignature(); @@ -144,6 +158,9 @@ public class ClassBuilder { } else if (classNode.superName.equals("java/lang/Enum")) { enumClass = true; builder = TypeSpec.enumBuilder(name); + } else if (classNode.superName.equals("java/lang/Record")) { + recordClass = true; + builder = TypeSpec.recordBuilder(name); } else { builder = TypeSpec.classBuilder(name) .superclass(signature.superclass()); @@ -162,7 +179,10 @@ public class ClassBuilder { } return builder - .addModifiers(new ModifierBuilder(classNode.access).getModifiers(enumClass ? ModifierBuilder.Type.ENUM : ModifierBuilder.Type.CLASS)); + .addModifiers(new ModifierBuilder(classNode.access) + .checkUnseal(classNode, sealChecker) + .getModifiers(ModifierBuilder.getType(enumClass, recordClass)) + ); } private void addInterfaces() { @@ -197,6 +217,7 @@ public class ClassBuilder { private void addMethods() { if (classNode.methods == null) return; + methodsLoop: for (MethodNode method : classNode.methods) { if ((method.access & Opcodes.ACC_SYNTHETIC) != 0 || (method.access & Opcodes.ACC_MANDATED) != 0) { continue; @@ -217,6 +238,21 @@ public class ClassBuilder { formalParamStartIndex = 2; // 0 String 1 int } } + if (recordClass) { + // skip record sugars + if (method.name.equals("equals") && method.desc.equals("(Ljava/lang/Object;)Z") + || method.name.equals("toString") && method.desc.equals("()Ljava/lang/String;") + || method.name.equals("hashCode") && method.desc.equals("()I")) { + for (AbstractInsnNode insn : method.instructions) { + if (insn instanceof InvokeDynamicInsnNode indy + && indy.bsm.equals(OBJ_MTH_BOOTSTRAP) + && indy.name.equals(method.name)) + continue methodsLoop; + } + } + + // todo test component getters + } if (instanceInner) { if (method.name.equals("")) { formalParamStartIndex = 1; // 0 this$0 @@ -229,6 +265,19 @@ public class ClassBuilder { private void addFields() { if (classNode.fields == null) return; for (FieldNode field : classNode.fields) { + if (recordClass && !Modifier.isStatic(field.access)) { + if (!Modifier.isFinal(field.access) || !Modifier.isPrivate(field.access)) { + System.out.println("abnormal instance field " + field.name + " in record " + getClassName() + ", skipping"); + } else { + var fieldBuilder = new FieldBuilder(mappings, classNode, field, context); + var paramBuilder = ParameterSpec.builder(fieldBuilder.calculateType(), field.name); + fieldBuilder.addJavaDoc(paramBuilder); + fieldBuilder.addDirectAnnotations(paramBuilder); + builder.addRecordComponent(paramBuilder.build()); + } + + continue; + } if ((field.access & Opcodes.ACC_SYNTHETIC) != 0 || (field.access & Opcodes.ACC_MANDATED) != 0) { continue; // hide synthetic stuff } @@ -285,7 +334,10 @@ public class ClassBuilder { classBuilder.builder.addModifiers(javax.lang.model.element.Modifier.STATIC); } else { classBuilder.builder.modifiers.remove(javax.lang.model.element.Modifier.PUBLIC); // this modifier may come from class access - classBuilder.builder.addModifiers(new ModifierBuilder(innerClassNode.access).getModifiers(classBuilder.enumClass ? ModifierBuilder.Type.ENUM : ModifierBuilder.Type.CLASS)); + classBuilder.builder.addModifiers(new ModifierBuilder(innerClassNode.access) + .checkUnseal(classBuilder.classNode, sealChecker) + .getModifiers(ModifierBuilder.getType(classBuilder.enumClass, classBuilder.recordClass)) + ); if (!Modifier.isStatic(innerClassNode.access)) { classBuilder.instanceInner = true; } diff --git a/filament/src/main/java/net/fabricmc/filament/mappingpoet/FieldBuilder.java b/filament/src/main/java/net/fabricmc/filament/mappingpoet/FieldBuilder.java index 5386aad7f2..c069c6718f 100644 --- a/filament/src/main/java/net/fabricmc/filament/mappingpoet/FieldBuilder.java +++ b/filament/src/main/java/net/fabricmc/filament/mappingpoet/FieldBuilder.java @@ -16,20 +16,21 @@ package net.fabricmc.mappingpoet; -import java.util.AbstractMap; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ArrayTypeName; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; +import net.fabricmc.mapping.util.EntryTriple; +import net.fabricmc.mappingpoet.signature.AnnotationAwareDescriptors; +import net.fabricmc.mappingpoet.signature.AnnotationAwareSignatures; +import net.fabricmc.mappingpoet.signature.ClassStaticContext; +import net.fabricmc.mappingpoet.signature.TypeAnnotationBank; +import net.fabricmc.mappingpoet.signature.TypeAnnotationMapping; +import net.fabricmc.mappingpoet.signature.TypeAnnotationStorage; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.TypePath; @@ -38,19 +39,18 @@ import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; -import net.fabricmc.mapping.util.EntryTriple; -import net.fabricmc.mappingpoet.signature.AnnotationAwareDescriptors; -import net.fabricmc.mappingpoet.signature.AnnotationAwareSignatures; -import net.fabricmc.mappingpoet.signature.ClassStaticContext; -import net.fabricmc.mappingpoet.signature.TypeAnnotationBank; -import net.fabricmc.mappingpoet.signature.TypeAnnotationMapping; -import net.fabricmc.mappingpoet.signature.TypeAnnotationStorage; +import java.util.AbstractMap; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; +import java.util.Map; public class FieldBuilder { private final MappingsStore mappings; private final ClassNode classNode; private final FieldNode fieldNode; - private final FieldSpec.Builder builder; + private final FieldSpec.Builder builder; // todo extract interface to build both record param/field easily private final TypeAnnotationMapping annotations; private final ClassStaticContext context; @@ -129,90 +129,90 @@ public class FieldBuilder { TypeName current; switch (desc.charAt(index)) { - case 'B': { - current = TypeName.BYTE; - index++; - break; - } - case 'C': { - current = TypeName.CHAR; - index++; - break; - } - case 'D': { - current = TypeName.DOUBLE; - index++; - break; - } - case 'F': { - current = TypeName.FLOAT; - index++; - break; - } - case 'I': { - current = TypeName.INT; - index++; - break; - } - case 'J': { - current = TypeName.LONG; - index++; - break; - } - case 'S': { - current = TypeName.SHORT; - index++; - break; - } - case 'Z': { - current = TypeName.BOOLEAN; - index++; - break; - } - case 'V': { - current = TypeName.VOID; - index++; - break; - } - case 'L': { - int classNameSeparator = index; - index++; - int nameStart = index; - ClassName currentClassName = null; - - char ch; - do { - ch = desc.charAt(index); - - if (ch == '$' || ch == ';') { - // collect class name - if (currentClassName == null) { - String packageName = nameStart < classNameSeparator ? desc.substring(nameStart, classNameSeparator).replace('/', '.') : ""; - String simpleName = desc.substring(classNameSeparator + 1, index); - currentClassName = ClassName.get(packageName, simpleName); - } else { - String simpleName = desc.substring(classNameSeparator + 1, index); - currentClassName = currentClassName.nestedClass(simpleName); - } - } - - if (ch == '/' || ch == '$') { - // Start of simple name - classNameSeparator = index; - } - + case 'B': { + current = TypeName.BYTE; index++; - } while (ch != ';'); - - if (currentClassName == null) { - throw invalidDesc(desc, index); + break; } + case 'C': { + current = TypeName.CHAR; + index++; + break; + } + case 'D': { + current = TypeName.DOUBLE; + index++; + break; + } + case 'F': { + current = TypeName.FLOAT; + index++; + break; + } + case 'I': { + current = TypeName.INT; + index++; + break; + } + case 'J': { + current = TypeName.LONG; + index++; + break; + } + case 'S': { + current = TypeName.SHORT; + index++; + break; + } + case 'Z': { + current = TypeName.BOOLEAN; + index++; + break; + } + case 'V': { + current = TypeName.VOID; + index++; + break; + } + case 'L': { + int classNameSeparator = index; + index++; + int nameStart = index; + ClassName currentClassName = null; - current = currentClassName; - break; - } - default: - throw invalidDesc(desc, index); + char ch; + do { + ch = desc.charAt(index); + + if (ch == '$' || ch == ';') { + // collect class name + if (currentClassName == null) { + String packageName = nameStart < classNameSeparator ? desc.substring(nameStart, classNameSeparator).replace('/', '.') : ""; + String simpleName = desc.substring(classNameSeparator + 1, index); + currentClassName = ClassName.get(packageName, simpleName); + } else { + String simpleName = desc.substring(classNameSeparator + 1, index); + currentClassName = currentClassName.nestedClass(simpleName); + } + } + + if (ch == '/' || ch == '$') { + // Start of simple name + classNameSeparator = index; + } + + index++; + } while (ch != ';'); + + if (currentClassName == null) { + throw invalidDesc(desc, index); + } + + current = currentClassName; + break; + } + default: + throw invalidDesc(desc, index); } for (int i = 0; i < arrayLevel; i++) { @@ -233,101 +233,101 @@ public class FieldBuilder { TypeName current; switch (desc.charAt(index)) { - case 'B': { - current = TypeName.BYTE; - index++; - break; - } - case 'C': { - current = TypeName.CHAR; - index++; - break; - } - case 'D': { - current = TypeName.DOUBLE; - index++; - break; - } - case 'F': { - current = TypeName.FLOAT; - index++; - break; - } - case 'I': { - current = TypeName.INT; - index++; - break; - } - case 'J': { - current = TypeName.LONG; - index++; - break; - } - case 'S': { - current = TypeName.SHORT; - index++; - break; - } - case 'Z': { - current = TypeName.BOOLEAN; - index++; - break; - } - case 'V': { - current = TypeName.VOID; - index++; - break; - } - case 'L': { - int classNameSeparator = index; - index++; - int nameStart = index; - ClassName currentClassName = null; - boolean instanceInner = false; + case 'B': { + current = TypeName.BYTE; + index++; + break; + } + case 'C': { + current = TypeName.CHAR; + index++; + break; + } + case 'D': { + current = TypeName.DOUBLE; + index++; + break; + } + case 'F': { + current = TypeName.FLOAT; + index++; + break; + } + case 'I': { + current = TypeName.INT; + index++; + break; + } + case 'J': { + current = TypeName.LONG; + index++; + break; + } + case 'S': { + current = TypeName.SHORT; + index++; + break; + } + case 'Z': { + current = TypeName.BOOLEAN; + index++; + break; + } + case 'V': { + current = TypeName.VOID; + index++; + break; + } + case 'L': { + int classNameSeparator = index; + index++; + int nameStart = index; + ClassName currentClassName = null; + boolean instanceInner = false; - char ch; - do { - ch = desc.charAt(index); + char ch; + do { + ch = desc.charAt(index); - if (ch == '$' || ch == ';') { - // collect class name - if (currentClassName == null) { - String packageName = nameStart < classNameSeparator ? desc.substring(nameStart, classNameSeparator).replace('/', '.') : ""; - String simpleName = desc.substring(classNameSeparator + 1, index); - currentClassName = ClassName.get(packageName, simpleName); - } else { - String simpleName = desc.substring(classNameSeparator + 1, index); + if (ch == '$' || ch == ';') { + // collect class name + if (currentClassName == null) { + String packageName = nameStart < classNameSeparator ? desc.substring(nameStart, classNameSeparator).replace('/', '.') : ""; + String simpleName = desc.substring(classNameSeparator + 1, index); + currentClassName = ClassName.get(packageName, simpleName); + } else { + String simpleName = desc.substring(classNameSeparator + 1, index); - if (!instanceInner && context.isInstanceInner(desc.substring(nameStart, index))) { - instanceInner = true; - } + if (!instanceInner && context.isInstanceInner(desc.substring(nameStart, index))) { + instanceInner = true; + } - currentClassName = currentClassName.nestedClass(simpleName); + currentClassName = currentClassName.nestedClass(simpleName); - if (instanceInner) { - currentClassName = AnnotationAwareDescriptors.annotate(currentClassName, annotations); - annotations = annotations.advance(TypePath.INNER_TYPE, 0); + if (instanceInner) { + currentClassName = AnnotationAwareDescriptors.annotate(currentClassName, annotations); + annotations = annotations.advance(TypePath.INNER_TYPE, 0); + } } } + + if (ch == '/' || ch == '$') { + // Start of simple name + classNameSeparator = index; + } + + index++; + } while (ch != ';'); + + if (currentClassName == null) { + throw invalidDesc(desc, index); } - if (ch == '/' || ch == '$') { - // Start of simple name - classNameSeparator = index; - } - - index++; - } while (ch != ';'); - - if (currentClassName == null) { - throw invalidDesc(desc, index); + current = currentClassName; + break; } - - current = currentClassName; - break; - } - default: - throw invalidDesc(desc, index); + default: + throw invalidDesc(desc, index); } while (!arrayAnnos.isEmpty()) { @@ -348,24 +348,24 @@ public class FieldBuilder { @Deprecated // use typeFromDesc, non-recursive public static TypeName getFieldType(String desc) { switch (desc) { - case "B": - return TypeName.BYTE; - case "C": - return TypeName.CHAR; - case "S": - return TypeName.SHORT; - case "Z": - return TypeName.BOOLEAN; - case "I": - return TypeName.INT; - case "J": - return TypeName.LONG; - case "F": - return TypeName.FLOAT; - case "D": - return TypeName.DOUBLE; - case "V": - return TypeName.VOID; + case "B": + return TypeName.BYTE; + case "C": + return TypeName.CHAR; + case "S": + return TypeName.SHORT; + case "Z": + return TypeName.BOOLEAN; + case "I": + return TypeName.INT; + case "J": + return TypeName.LONG; + case "F": + return TypeName.FLOAT; + case "D": + return TypeName.DOUBLE; + case "V": + return TypeName.VOID; } if (desc.startsWith("[")) { return ArrayTypeName.of(getFieldType(desc.substring(1))); @@ -390,48 +390,48 @@ public class FieldBuilder { private CodeBlock makeInitializer(String desc) { // fake initializers exclude fields from constant values switch (desc.charAt(0)) { - case 'B': - if (fieldNode.value instanceof Integer) { - return CodeBlock.builder().add("(byte) $L", fieldNode.value).build(); - } - // fake initializer falls through - case 'C': - if (fieldNode.value instanceof Integer) { - int value = (int) fieldNode.value; - char c = (char) value; - return printChar(CodeBlock.builder(), c, value).build(); - } - // fake initializer falls through - case 'D': - if (fieldNode.value instanceof Double) { - return CodeBlock.builder().add("$LD", fieldNode.value).build(); - } - // fake initializer falls through - case 'I': - if (fieldNode.value instanceof Integer) { - return CodeBlock.builder().add("$L", fieldNode.value).build(); - } - // fake initializer falls through - case 'J': - if (fieldNode.value instanceof Long) { - return CodeBlock.builder().add("$LL", fieldNode.value).build(); - } - // fake initializer falls through - case 'S': - if (fieldNode.value instanceof Integer) { - return CodeBlock.builder().add("(short) $L", fieldNode.value).build(); - } - return CodeBlock.builder().add("java.lang.Byte.parseByte(\"dummy\")").build(); - case 'F': - if (fieldNode.value instanceof Float) { - return CodeBlock.builder().add("$LF", fieldNode.value).build(); - } - return CodeBlock.builder().add("java.lang.Float.parseFloat(\"dummy\")").build(); - case 'Z': - if (fieldNode.value instanceof Integer) { - return CodeBlock.builder().add("$L", ((int) fieldNode.value) != 0).build(); - } - return CodeBlock.builder().add("java.lang.Boolean.parseBoolean(\"dummy\")").build(); + case 'B': + if (fieldNode.value instanceof Integer) { + return CodeBlock.builder().add("(byte) $L", fieldNode.value).build(); + } + // fake initializer falls through + case 'C': + if (fieldNode.value instanceof Integer) { + int value = (int) fieldNode.value; + char c = (char) value; + return printChar(CodeBlock.builder(), c, value).build(); + } + // fake initializer falls through + case 'D': + if (fieldNode.value instanceof Double) { + return CodeBlock.builder().add("$LD", fieldNode.value).build(); + } + // fake initializer falls through + case 'I': + if (fieldNode.value instanceof Integer) { + return CodeBlock.builder().add("$L", fieldNode.value).build(); + } + // fake initializer falls through + case 'J': + if (fieldNode.value instanceof Long) { + return CodeBlock.builder().add("$LL", fieldNode.value).build(); + } + // fake initializer falls through + case 'S': + if (fieldNode.value instanceof Integer) { + return CodeBlock.builder().add("(short) $L", fieldNode.value).build(); + } + return CodeBlock.builder().add("java.lang.Byte.parseByte(\"dummy\")").build(); + case 'F': + if (fieldNode.value instanceof Float) { + return CodeBlock.builder().add("$LF", fieldNode.value).build(); + } + return CodeBlock.builder().add("java.lang.Float.parseFloat(\"dummy\")").build(); + case 'Z': + if (fieldNode.value instanceof Integer) { + return CodeBlock.builder().add("$L", ((int) fieldNode.value) != 0).build(); + } + return CodeBlock.builder().add("java.lang.Boolean.parseBoolean(\"dummy\")").build(); } if (desc.equals("Ljava/lang/String;") && fieldNode.value instanceof String) { return CodeBlock.builder().add("$S", fieldNode.value).build(); @@ -448,20 +448,20 @@ public class FieldBuilder { // See https://docs.oracle.com/javase/specs/jls/se16/html/jls-3.html#jls-EscapeSequence // ignore space or ", just use direct in those cases switch (c) { - case '\b': - return builder.add("'\\b'"); - case '\t': - return builder.add("'\\t'"); - case '\n': - return builder.add("'\\n'"); - case '\f': - return builder.add("'\\f'"); - case '\r': - return builder.add("'\\r'"); - case '\'': - return builder.add("'\\''"); - case '\\': - return builder.add("'\\\\'"); + case '\b': + return builder.add("'\\b'"); + case '\t': + return builder.add("'\\t'"); + case '\n': + return builder.add("'\\n'"); + case '\f': + return builder.add("'\\f'"); + case '\r': + return builder.add("'\\r'"); + case '\'': + return builder.add("'\\''"); + case '\\': + return builder.add("'\\\\'"); } return builder.add("'$L'", c); @@ -471,6 +471,10 @@ public class FieldBuilder { mappings.addFieldDoc(builder::addJavadoc, new EntryTriple(classNode.name, fieldNode.name, fieldNode.desc)); } + void addJavaDoc(ParameterSpec.Builder paramBuilder) { + mappings.addFieldDoc(paramBuilder::addJavadoc, new EntryTriple(classNode.name, fieldNode.name, fieldNode.desc)); + } + private void addDirectAnnotations() { addDirectAnnotations(fieldNode.invisibleAnnotations); addDirectAnnotations(fieldNode.visibleAnnotations); @@ -485,7 +489,21 @@ public class FieldBuilder { } } - private TypeName calculateType() { + void addDirectAnnotations(ParameterSpec.Builder paramBuilder) { + addDirectAnnotations(paramBuilder, fieldNode.invisibleAnnotations); + addDirectAnnotations(paramBuilder, fieldNode.visibleAnnotations); + } + + private void addDirectAnnotations(ParameterSpec.Builder paramBuilder, List regularAnnotations) { + if (regularAnnotations == null) { + return; + } + for (AnnotationNode annotation : regularAnnotations) { + paramBuilder.addAnnotation(parseAnnotation(annotation)); + } + } + + TypeName calculateType() { if (fieldNode.signature != null) { return AnnotationAwareSignatures.parseFieldSignature(fieldNode.signature, annotations, context); } diff --git a/filament/src/main/java/net/fabricmc/filament/mappingpoet/Main.java b/filament/src/main/java/net/fabricmc/filament/mappingpoet/Main.java index d07c56b2ff..31a83cf49c 100644 --- a/filament/src/main/java/net/fabricmc/filament/mappingpoet/Main.java +++ b/filament/src/main/java/net/fabricmc/filament/mappingpoet/Main.java @@ -16,6 +16,14 @@ package net.fabricmc.mappingpoet; +import com.squareup.javapoet.JavaFile; +import net.fabricmc.mappingpoet.signature.ClassStaticContext; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InnerClassNode; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -33,22 +41,16 @@ import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import java.util.function.Predicate; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import com.squareup.javapoet.JavaFile; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.InnerClassNode; - -import net.fabricmc.mappingpoet.signature.ClassStaticContext; - public class Main { public static void main(String[] args) { @@ -90,7 +92,7 @@ public class Main { public static void generate(Path mappings, Path inputJar, Path outputDirectory, Path librariesDir) { final MappingsStore mapping = new MappingsStore(mappings); Map classes = new HashMap<>(); - forEachClass(inputJar, (superGetter, classNode, context) -> writeClass(mapping, classNode, classes, superGetter, context), librariesDir); + forEachClass(inputJar, (superGetter, classNode, context, sealChecker) -> writeClass(mapping, classNode, classes, superGetter, sealChecker, context), librariesDir); classes.values().stream() .filter(classBuilder -> !classBuilder.getClassName().contains("$")) @@ -111,6 +113,7 @@ public class Main { private static void forEachClass(Path jar, ClassNodeConsumer classNodeConsumer, Path librariesDir) { List classes = new ArrayList<>(); Map> supers = new HashMap<>(); + Set sealedClasses = new HashSet<>(); // their subclsses/impls need non-sealed modifier Map instanceInnerClasses = new ConcurrentHashMap<>(); @@ -149,6 +152,10 @@ public class Main { } } + if (classNode.permittedSubclasses != null) { + sealedClasses.add(classNode.name); + } + classes.add(classNode); } } @@ -161,7 +168,7 @@ public class Main { ClassStaticContext innerClassContext = new InnerClassStats(instanceInnerClasses); Function> superGetter = k -> supers.getOrDefault(k, Collections.emptyList()); - classes.forEach(node -> classNodeConsumer.accept(superGetter, node, innerClassContext)); + classes.forEach(node -> classNodeConsumer.accept(superGetter, node, innerClassContext, sealedClasses::contains)); } private static void scanInnerClasses(Map instanceInnerClasses, Path librariesDir) { @@ -218,7 +225,7 @@ public class Main { return ch >= '0' && ch <= '9'; } - private static void writeClass(MappingsStore mappings, ClassNode classNode, Map existingClasses, Function> superGetter, ClassStaticContext context) { + private static void writeClass(MappingsStore mappings, ClassNode classNode, Map existingClasses, Function> superGetter, Predicate sealChecker, ClassStaticContext context) { String name = classNode.name; { //Block anonymous class and their nested classes @@ -232,7 +239,7 @@ public class Main { } } - ClassBuilder classBuilder = new ClassBuilder(mappings, classNode, superGetter, context); + ClassBuilder classBuilder = new ClassBuilder(mappings, classNode, superGetter, sealChecker, context); if (name.contains("$")) { String parentClass = name.substring(0, name.lastIndexOf("$")); @@ -249,7 +256,7 @@ public class Main { @FunctionalInterface private interface ClassNodeConsumer { - void accept(Function> superGetter, ClassNode node, ClassStaticContext staticContext); + void accept(Function> superGetter, ClassNode node, ClassStaticContext staticContext, Predicate sealedChecker); } private static final class InnerClassStats implements ClassStaticContext { diff --git a/filament/src/main/java/net/fabricmc/filament/mappingpoet/ModifierBuilder.java b/filament/src/main/java/net/fabricmc/filament/mappingpoet/ModifierBuilder.java index dad1d81da9..dcc7c79ddb 100644 --- a/filament/src/main/java/net/fabricmc/filament/mappingpoet/ModifierBuilder.java +++ b/filament/src/main/java/net/fabricmc/filament/mappingpoet/ModifierBuilder.java @@ -16,19 +16,44 @@ package net.fabricmc.mappingpoet; -import java.util.ArrayList; -import java.util.List; +import org.objectweb.asm.tree.ClassNode; import javax.lang.model.element.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; public class ModifierBuilder { private final int access; + private boolean needsUnseal; public ModifierBuilder(int access) { this.access = access; } + public ModifierBuilder checkUnseal(ClassNode node, Predicate sealChecker) { + if (java.lang.reflect.Modifier.isFinal(node.access)) { + return this; + } + + if (node.interfaces != null) { + for (String itf : node.interfaces) { + if (sealChecker.test(itf)) { + needsUnseal = true; + return this; + } + } + } + + if (node.superName != null && sealChecker.test(node.superName)) { + needsUnseal = true; + } + + + return this; + } + public Modifier[] getModifiers(Type type) { List modifiers = new ArrayList<>(); @@ -36,7 +61,7 @@ public class ModifierBuilder { if (java.lang.reflect.Modifier.isFinal(access)) { modifiers.add(Modifier.FINAL); } - return modifiers.toArray(new Modifier[] {}); + return modifiers.toArray(new Modifier[]{}); } if (java.lang.reflect.Modifier.isPublic(access)) { @@ -57,7 +82,7 @@ public class ModifierBuilder { modifiers.add(Modifier.DEFAULT); } - if (java.lang.reflect.Modifier.isFinal(access) && type != Type.ENUM) { + if (java.lang.reflect.Modifier.isFinal(access) && type != Type.ENUM && type != Type.RECORD) { modifiers.add(Modifier.FINAL); } if (java.lang.reflect.Modifier.isTransient(access) && type == Type.FIELD) { @@ -76,12 +101,21 @@ public class ModifierBuilder { modifiers.add(Modifier.STRICTFP); } - return modifiers.toArray(new Modifier[] {}); + if (needsUnseal && type == Type.CLASS) { + modifiers.add(Modifier.NON_SEALED); + } + + return modifiers.toArray(new Modifier[]{}); + } + + public static Type getType(boolean enumType, boolean recordType) { + return enumType ? Type.ENUM : recordType ? Type.RECORD : Type.CLASS; } public enum Type { CLASS, ENUM, + RECORD, METHOD, FIELD, PARAM