-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use local var context with static position methods
- Loading branch information
1 parent
a911762
commit e841285
Showing
3 changed files
with
259 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
242 changes: 242 additions & 0 deletions
242
codebook-lvt/src/main/java/io/papermc/codebook/lvt/suggestion/PositionsSuggester.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} | ||
} |