Skip to content

Commit

Permalink
Use local var context with static position methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Machine-Maker committed Jan 14, 2024
1 parent a911762 commit e841285
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 0 deletions.
15 changes: 15 additions & 0 deletions codebook-lvt/src/main/java/io/papermc/codebook/lvt/LvtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
import java.util.List;
import java.util.function.Predicate;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;

public final class LvtUtil {

Expand All @@ -38,6 +40,9 @@ public static JvmType toJvmType(final String desc) {
}

public static String capitalize(final String name, final int index) {
if (name.isEmpty()) {
return name;
}
return Character.toUpperCase(name.charAt(index)) + name.substring(index + 1);
}

Expand Down Expand Up @@ -108,4 +113,14 @@ public static boolean isStringAllUppercase(final String input) {
public static boolean hasPrefix(final String text, final String prefix) {
return text.length() > prefix.length() && text.startsWith(prefix);
}

public static @Nullable AbstractInsnNode prevInsnIgnoringConvertCast(final AbstractInsnNode insn) {
@Nullable AbstractInsnNode prev = insn.getPrevious();
while (prev != null
&& (prev.getOpcode() == Opcodes.CHECKCAST
|| (prev.getOpcode() >= Opcodes.I2L && prev.getOpcode() <= Opcodes.I2S))) {
prev = prev.getPrevious();
}
return prev;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import io.papermc.codebook.lvt.suggestion.LvtSuggester;
import io.papermc.codebook.lvt.suggestion.MathSuggester;
import io.papermc.codebook.lvt.suggestion.NewPrefixSuggester;
import io.papermc.codebook.lvt.suggestion.PositionsSuggester;
import io.papermc.codebook.lvt.suggestion.RecordComponentSuggester;
import io.papermc.codebook.lvt.suggestion.SingleVerbBooleanSuggester;
import io.papermc.codebook.lvt.suggestion.SingleVerbSuggester;
Expand Down Expand Up @@ -72,6 +73,7 @@ public final class RootLvtSuggester extends AbstractModule implements LvtSuggest
MthRandomSuggester.class,
MathSuggester.class,
StringSuggester.class,
PositionsSuggester.class,
NewPrefixSuggester.class,
SingleVerbSuggester.class,
VerbPrefixBooleanSuggester.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package io.papermc.codebook.lvt.suggestion;

import static io.papermc.codebook.lvt.LvtUtil.capitalize;
import static io.papermc.codebook.lvt.LvtUtil.decapitalize;
import static io.papermc.codebook.lvt.LvtUtil.prevInsnIgnoringConvertCast;
import static java.util.Objects.requireNonNull;

import dev.denwav.hypo.model.data.MethodData;
import dev.denwav.hypo.model.data.types.PrimitiveType;
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.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
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.LabelNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;

public class PositionsSuggester implements LvtSuggester {

@Override
public @Nullable String suggestFromMethod(final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container) throws IOException {
if ("net/minecraft/core/SectionPos".equals(insn.owner().name())) {
return suggestNameForSectionPos(container.node(), call.data(), insn.node());
} else if ("net/minecraft/core/QuartPos".equals(insn.owner().name())) {
return suggestNameForQuartPos(container.node(), call.data(), insn.node());
} else if ("net/minecraft/core/BlockPos".equals(insn.owner().name())) {
return suggestNameForBlockPos(call.data());
}
return LvtSuggester.super.suggestFromMethod(call, insn, container);
}

enum PosType {
BLOCK("block", "blockpos"),
QUART("quart", "quartpos", "biome", "biomepos"),
SECTION("section", "sectionpos");

final Set<String> possibleNames;
final String localName;

PosType(final String... possibleNames) {
this.possibleNames = Set.of(possibleNames);
this.localName = this.name().toLowerCase(Locale.ENGLISH) + "Pos";
}
}

private record MethodConfig(PosType returnType, PosType paramType, String prefix) {

private MethodConfig(final PosType returnType, final PosType paramType) {
this(returnType, paramType, "");
}

String varName(final String suffix) {
if (this.prefix.isEmpty()) {
return this.returnType.localName + suffix;
} else {
return this.prefix + capitalize(this.returnType.localName, 0) + suffix;
}
}
}

public static @Nullable String suggestNameForSectionPos(final MethodNode enclosingMethodNode, final MethodData method, final MethodInsnNode insn) {
// this matches 2 methods for each x, y, z. One static that takes the packed position, the output names are
// appropriate for both method types
final @Nullable String possibleSimpleName =
switch (method.name()) {
case "x" -> "sectionX";
case "y" -> "sectionY";
case "z" -> "sectionZ";
case "blockToSection", "asLong" -> "packedSectionPos";
default -> null;
};
if (possibleSimpleName != null) {
return possibleSimpleName;
}

final @Nullable MethodConfig methodConfig =
switch (method.name()) {
case "blockToSectionCoord", "posToSectionCoord" -> new MethodConfig(PosType.SECTION, PosType.BLOCK);
case "sectionToBlockCoord" -> new MethodConfig(PosType.BLOCK, PosType.SECTION);
case "sectionRelative" -> new MethodConfig(PosType.BLOCK, PosType.BLOCK, "relative");
default -> null;
};

return getCoordLocalNameFromMethodPair(enclosingMethodNode, insn, method, methodConfig);
}

private static @Nullable String suggestNameForQuartPos(
final MethodNode enclosingMethodNode,
final MethodData method,
final MethodInsnNode insn) {
// all methods in QuartPos have a single int param and return int
if (method.params().size() != 1 || method.param(0) != PrimitiveType.INT || method.returnType() != PrimitiveType.INT) {
return null;
}

final @Nullable MethodConfig methodConfig =
switch (method.name()) {
case "fromBlock" -> new MethodConfig(PosType.QUART, PosType.BLOCK);
case "toBlock" -> new MethodConfig(PosType.BLOCK, PosType.QUART);
case "fromSection" -> new MethodConfig(PosType.QUART, PosType.SECTION);
case "toSection" -> new MethodConfig(PosType.SECTION, PosType.QUART);
default -> null;
};
if (methodConfig == null) {
return null;
}

return getCoordLocalNameFromMethodPair(enclosingMethodNode, insn, method, methodConfig);
}

private static @Nullable String suggestNameForBlockPos(final MethodData method) {
final String suggestion;
if (method.name().equals("asLong")) {
suggestion = "packedBlockPos";
} else if (method.isStatic() && method.name().equals("offset") && method.returnType() == PrimitiveType.LONG) {
suggestion = "offsetPackedBlockPos";
} else {
return null;
}
return suggestion;
}

private static final String[] COMMON_PERSISTENT_PREFIXES = new String[] {"min", "max"};

private static @Nullable String getCoordLocalNameFromMethodPair(
final MethodNode enclosingMethodNode,
final MethodInsnNode insn,
final MethodData method,
final @Nullable MethodConfig methodConfig) {
if (methodConfig == null) {
return null;
}

if (method.params().size() != 1) {
// add "Coord" since we don't know if its x, y, or z and more
// than 1 param makes it too complex to figure out
return methodConfig.varName("Coord");
}

final AbstractInsnNode prev = requireNonNull(prevInsnIgnoringConvertCast(insn));
@Nullable String suggestion = null;
if (prev instanceof final VarInsnNode varNode) {
final LocalVariableNode paramVarNode = findLocalVar(enclosingMethodNode, insn, varNode.var);
suggestion = suggestSpecificCoordName(methodConfig, paramVarNode.name, COMMON_PERSISTENT_PREFIXES);
} else if (prev instanceof final MethodInsnNode methodNode) {
final @Nullable String strippedName =
methodNode.name.startsWith("get") ? decapitalize(methodNode.name, 3) : methodNode.name;
if (strippedName != null) {
suggestion = suggestSpecificCoordName(methodConfig, strippedName, COMMON_PERSISTENT_PREFIXES);
}
} else if (prev instanceof final FieldInsnNode fieldNode && fieldNode.getOpcode() == Opcodes.GETFIELD) {
suggestion = suggestSpecificCoordName(methodConfig, fieldNode.name, COMMON_PERSISTENT_PREFIXES);
}
return Objects.requireNonNullElseGet(
suggestion, () -> methodConfig.varName("Coord")); // add "Coord" since we don't know if its x, y, or z
}

private static @Nullable String suggestSpecificCoordName(
final MethodConfig methodConfig, final String fullName, final String... persistentPrefixes) {
String prefix = "";
if (fullName.length() > 1) {
for (final String persistentPrefix : persistentPrefixes) {
if (fullName.startsWith(persistentPrefix)) {
prefix = persistentPrefix;
break;
}
}
}
final String nameWithoutPrefix = fullName.substring(prefix.length());
final int possibleCoordIdx = getPossibleCoordIdx(nameWithoutPrefix);
if (possibleCoordIdx > -1
&& (nameWithoutPrefix.length() == 1
|| methodConfig.paramType.possibleNames.contains(
nameWithoutPrefix.substring(0, possibleCoordIdx).toLowerCase(Locale.ENGLISH)))) {
return methodConfig.varName(
capitalize(prefix, 0) + Character.toUpperCase(nameWithoutPrefix.charAt(possibleCoordIdx)));
}
return null;
}

private static int getPossibleCoordIdx(final String name) {
for (int i = name.length() - 1; i >= 0; i--) {
final char ch = name.charAt(i);
if (!Character.isAlphabetic(ch)) {
continue;
}
if (isCoord(ch)) {
return i;
}
return -1;
}
return -1; // don't think this is possible
}

private static boolean isCoord(final char ch) {
return ch == 'X' || ch == 'Y' || ch == 'Z' || ch == 'x' || ch == 'y' || ch == 'z';
}

private static LocalVariableNode findLocalVar(
final MethodNode enclosingMethod, final AbstractInsnNode insn, final int varIdx) {
final List<LocalVariableNode> matching = new ArrayList<>();
for (final LocalVariableNode lvn : requireNonNull(enclosingMethod.localVariables)) {
if (lvn.index == varIdx) {
matching.add(lvn);
}
}
if (matching.isEmpty()) {
throw new IllegalStateException("Cannot find idx " + varIdx + " on " + enclosingMethod.name + " "
+ enclosingMethod.desc + " (no match)");
} else if (matching.size() == 1) {
return matching.get(0);
} else {
@Nullable AbstractInsnNode prev = insn.getPrevious();
if (prev == null) {
throw new IllegalStateException("Cannot find idx " + varIdx + " on " + enclosingMethod.name + " "
+ enclosingMethod.desc + " (multiple matches)");
}
while (true) {
while (!(prev instanceof final LabelNode labelNode)) {
prev = prev.getPrevious();
}
for (final LocalVariableNode match : matching) {
if (match.start.getLabel() == labelNode.getLabel()) {
return match;
}
}
prev = prev.getPrevious();
}
}
}
}

0 comments on commit e841285

Please sign in to comment.