mirror of https://github.com/FabricMC/yarn.git
Preliminary work on getting records and sealed classes right
Signed-off-by: liach <liach@users.noreply.github.com>
This commit is contained in:
parent
defbde19d8
commit
099ddebbd0
|
@ -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<ClassBuilder> innerClasses = new ArrayList<>();
|
||||
private final Function<String, Collection<String>> superGetter;
|
||||
private final Predicate<String> 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 <T> decl
|
||||
// omits L and ;
|
||||
private String receiverSignature;
|
||||
|
||||
public ClassBuilder(MappingsStore mappings, ClassNode classNode, Function<String, Collection<String>> superGetter, ClassStaticContext context) {
|
||||
public ClassBuilder(MappingsStore mappings, ClassNode classNode, Function<String, Collection<String>> superGetter, Predicate<String> 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("<init>")) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -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<AnnotationNode> 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);
|
||||
}
|
||||
|
|
|
@ -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<String, ClassBuilder> 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<ClassNode> classes = new ArrayList<>();
|
||||
Map<String, Collection<String>> supers = new HashMap<>();
|
||||
Set<String> sealedClasses = new HashSet<>(); // their subclsses/impls need non-sealed modifier
|
||||
|
||||
Map<String, Boolean> 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<String, Collection<String>> 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<String, Boolean> instanceInnerClasses, Path librariesDir) {
|
||||
|
@ -218,7 +225,7 @@ public class Main {
|
|||
return ch >= '0' && ch <= '9';
|
||||
}
|
||||
|
||||
private static void writeClass(MappingsStore mappings, ClassNode classNode, Map<String, ClassBuilder> existingClasses, Function<String, Collection<String>> superGetter, ClassStaticContext context) {
|
||||
private static void writeClass(MappingsStore mappings, ClassNode classNode, Map<String, ClassBuilder> existingClasses, Function<String, Collection<String>> superGetter, Predicate<String> 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<String, Collection<String>> superGetter, ClassNode node, ClassStaticContext staticContext);
|
||||
void accept(Function<String, Collection<String>> superGetter, ClassNode node, ClassStaticContext staticContext, Predicate<String> sealedChecker);
|
||||
}
|
||||
|
||||
private static final class InnerClassStats implements ClassStaticContext {
|
||||
|
|
|
@ -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<String> 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<Modifier> 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
|
||||
|
|
Loading…
Reference in New Issue