diff --git a/codebook-lvt/src/main/java/io/papermc/codebook/lvt/LvtUtil.java b/codebook-lvt/src/main/java/io/papermc/codebook/lvt/LvtUtil.java index d7091c0..9b14777 100644 --- a/codebook-lvt/src/main/java/io/papermc/codebook/lvt/LvtUtil.java +++ b/codebook-lvt/src/main/java/io/papermc/codebook/lvt/LvtUtil.java @@ -25,6 +25,7 @@ import dev.denwav.hypo.asm.HypoAsmUtil; import dev.denwav.hypo.model.data.types.JvmType; import java.util.List; +import java.util.Locale; import java.util.function.Predicate; import org.checkerframework.checker.nullness.qual.Nullable; import org.objectweb.asm.Opcodes; @@ -168,4 +169,14 @@ public static String parseSimpleTypeNameFromMethod(final String methodName, int } return prev; } + + public static String staticFinalFieldNameToLocalName(final String fieldName) { + final String[] split = fieldName.split("_"); + final StringBuilder builder = new StringBuilder(); + builder.append(split[0].toLowerCase(Locale.ENGLISH)); + for (int i = 1; i < split.length; i++) { + builder.append(capitalize(split[i].toLowerCase(Locale.ENGLISH), 0)); + } + return builder.toString(); + } } diff --git a/codebook-lvt/src/main/java/io/papermc/codebook/lvt/RootLvtSuggester.java b/codebook-lvt/src/main/java/io/papermc/codebook/lvt/RootLvtSuggester.java index 80771b7..a799618 100644 --- a/codebook-lvt/src/main/java/io/papermc/codebook/lvt/RootLvtSuggester.java +++ b/codebook-lvt/src/main/java/io/papermc/codebook/lvt/RootLvtSuggester.java @@ -33,6 +33,7 @@ import dev.denwav.hypo.model.data.MethodData; import dev.denwav.hypo.model.data.MethodDescriptor; import dev.denwav.hypo.model.data.types.JvmType; +import io.papermc.codebook.lvt.suggestion.ComplexGetSuggester; import io.papermc.codebook.lvt.suggestion.FluentGetterSuggester; import io.papermc.codebook.lvt.suggestion.GenericSuggester; import io.papermc.codebook.lvt.suggestion.LvtSuggester; @@ -56,6 +57,7 @@ import java.io.IOException; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; @@ -75,6 +77,7 @@ public final class RootLvtSuggester extends AbstractModule implements LvtSuggest MathSuggester.class, StringSuggester.class, PositionsSuggester.class, + ComplexGetSuggester.class, NewPrefixSuggester.class, SingleVerbSuggester.class, VerbPrefixBooleanSuggester.class, @@ -171,9 +174,49 @@ public static String determineFinalName(final String suggestedName, final Set BOX_METHODS = Set.of( + new BoxMethod("java/lang/Byte", "byteValue", "()B"), + new BoxMethod("java/lang/Short", "shortValue", "()S"), + new BoxMethod("java/lang/Integer", "intValue", "()I"), + new BoxMethod("java/lang/Long", "longValue", "()J"), + new BoxMethod("java/lang/Float", "floatValue", "()F"), + new BoxMethod("java/lang/Double", "doubleValue", "()D"), + new BoxMethod("java/lang/Boolean", "booleanValue", "()Z"), + new BoxMethod("java/lang/Character", "charValue", "()C") + ); + private static final Set BOX_METHOD_NAMES = BOX_METHODS.stream().map(BoxMethod::name).collect(Collectors.toUnmodifiableSet()); + + private record BoxMethod(String owner, String name, String desc) { + boolean is(final MethodInsnNode node) { + return this.owner.equals(node.owner) && this.name.equals(node.name) && this.desc.equals(node.desc) && !node.itf; + } + } + + private @Nullable AbstractInsnNode walkBack(final VarInsnNode assignmentNode) { + AbstractInsnNode prev = assignmentNode.getPrevious(); + if (prev != null) { + final int op = prev.getOpcode(); + if (op == Opcodes.INVOKEVIRTUAL) { + final MethodInsnNode methodInsnNode = (MethodInsnNode) prev; + if (BOX_METHOD_NAMES.contains(methodInsnNode.name) && BOX_METHODS.stream().anyMatch(bm -> bm.is(methodInsnNode))) { + prev = prev.getPrevious(); + if (prev != null && prev.getOpcode() == Opcodes.CHECKCAST) { + return prev.getPrevious(); + } + return prev; + } + } + return prev; + } + return null; + } + private @Nullable String suggestNameFromFirstAssignment(final MethodData parent, final VarInsnNode varInsn) throws IOException { - final AbstractInsnNode prev = varInsn.getPrevious(); + final @Nullable AbstractInsnNode prev = this.walkBack(varInsn); + if (prev == null) { + return null; + } final int op = prev.getOpcode(); if (op != Opcodes.INVOKESTATIC && op != Opcodes.INVOKEVIRTUAL && op != Opcodes.INVOKEINTERFACE) { return null; diff --git a/codebook-lvt/src/main/java/io/papermc/codebook/lvt/suggestion/ComplexGetSuggester.java b/codebook-lvt/src/main/java/io/papermc/codebook/lvt/suggestion/ComplexGetSuggester.java new file mode 100644 index 0000000..389e4d8 --- /dev/null +++ b/codebook-lvt/src/main/java/io/papermc/codebook/lvt/suggestion/ComplexGetSuggester.java @@ -0,0 +1,62 @@ +package io.papermc.codebook.lvt.suggestion; + +import static io.papermc.codebook.lvt.LvtUtil.staticFinalFieldNameToLocalName; + +import io.papermc.codebook.lvt.LvtUtil; +import io.papermc.codebook.lvt.suggestion.context.ContainerContext; +import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext; +import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext; +import java.io.IOException; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; + +public class ComplexGetSuggester implements LvtSuggester { + + private static final StaticFieldEntry BLOCK_STATE_PROPERTY = new StaticFieldEntry( + Set.of( + "net/minecraft/world/level/block/state/StateHolder", + "net/minecraft/world/level/block/state/BlockState", + "net/minecraft/world/level/block/state/BlockBehaviour$Properties", + "net/minecraft/world/level/material/FluidState" + ), + Set.of( + Map.entry("getValue", "(Lnet/minecraft/world/level/block/state/properties/Property;)Ljava/lang/Comparable;") + ), + Set.of( + "Lnet/minecraft/world/level/block/state/properties/IntegerProperty;", + "Lnet/minecraft/world/level/block/state/properties/BooleanProperty;" + ), + "Value" + ); + + @Override + public @Nullable String suggestFromMethod(final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container) throws IOException { + final MethodInsnNode node = insn.node(); + if (BLOCK_STATE_PROPERTY.test(node)) { + return BLOCK_STATE_PROPERTY.transform(node); + } + return null; + } + + private record StaticFieldEntry(Set owners, Set> methods, Set fieldTypes, @Nullable String suffix) { + + boolean test(final MethodInsnNode node) { + return this.owners.contains(node.owner) && this.methods.stream().anyMatch(e -> e.getKey().equals(node.name) && e.getValue().equals(node.desc)); + } + + @Nullable String transform(final MethodInsnNode node) { + final AbstractInsnNode prev = node.getPrevious(); + if (prev instanceof final FieldInsnNode fieldInsnNode && fieldInsnNode.getOpcode() == Opcodes.GETSTATIC && this.fieldTypes.contains(fieldInsnNode.desc)) { + return staticFinalFieldNameToLocalName(fieldInsnNode.name) + (this.suffix == null ? "" : this.suffix); + } + return null; + } + } + +}