Skip to content

Commit a6f7366

Browse files
Machine-MakerMiniDigger
authored andcommitted
Use local var context with static position methods
1 parent f21db75 commit a6f7366

3 files changed

Lines changed: 296 additions & 0 deletions

File tree

codebook-lvt/src/main/java/io/papermc/codebook/lvt/LvtUtil.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
import java.util.List;
2828
import java.util.function.Predicate;
2929
import org.checkerframework.checker.nullness.qual.Nullable;
30+
import org.objectweb.asm.Opcodes;
3031
import org.objectweb.asm.Type;
32+
import org.objectweb.asm.tree.AbstractInsnNode;
3133

3234
public final class LvtUtil {
3335

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

4042
public static String capitalize(final String name, final int index) {
43+
if (name.isEmpty()) {
44+
return name;
45+
}
4146
return Character.toUpperCase(name.charAt(index)) + name.substring(index + 1);
4247
}
4348

@@ -153,4 +158,14 @@ public static String parseSimpleTypeNameFromMethod(final String methodName, int
153158
return LvtUtil.parseSimpleTypeName(methodName.substring(prefix));
154159
}
155160
}
161+
162+
public static @Nullable AbstractInsnNode prevInsnIgnoringConvertCast(final AbstractInsnNode insn) {
163+
@Nullable AbstractInsnNode prev = insn.getPrevious();
164+
while (prev != null
165+
&& (prev.getOpcode() == Opcodes.CHECKCAST
166+
|| (prev.getOpcode() >= Opcodes.I2L && prev.getOpcode() <= Opcodes.I2S))) {
167+
prev = prev.getPrevious();
168+
}
169+
return prev;
170+
}
156171
}

codebook-lvt/src/main/java/io/papermc/codebook/lvt/RootLvtSuggester.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import io.papermc.codebook.lvt.suggestion.LvtSuggester;
3939
import io.papermc.codebook.lvt.suggestion.MathSuggester;
4040
import io.papermc.codebook.lvt.suggestion.NewPrefixSuggester;
41+
import io.papermc.codebook.lvt.suggestion.PositionsSuggester;
4142
import io.papermc.codebook.lvt.suggestion.RecordComponentSuggester;
4243
import io.papermc.codebook.lvt.suggestion.SingleVerbBooleanSuggester;
4344
import io.papermc.codebook.lvt.suggestion.SingleVerbSuggester;
@@ -73,6 +74,7 @@ public final class RootLvtSuggester extends AbstractModule implements LvtSuggest
7374
MthRandomSuggester.class,
7475
MathSuggester.class,
7576
StringSuggester.class,
77+
PositionsSuggester.class,
7678
NewPrefixSuggester.class,
7779
SingleVerbSuggester.class,
7880
VerbPrefixBooleanSuggester.class,
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
/*
2+
* codebook is a remapper utility for the PaperMC project.
3+
*
4+
* Copyright (c) 2023 Kyle Wood (DenWav)
5+
* Contributors
6+
*
7+
* This library is free software; you can redistribute it and/or
8+
* modify it under the terms of the GNU Lesser General Public
9+
* License as published by the Free Software Foundation;
10+
* version 3 only, no later versions.
11+
*
12+
* This library is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
* Lesser General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public
18+
* License along with this library; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
20+
* USA
21+
*/
22+
23+
package io.papermc.codebook.lvt.suggestion;
24+
25+
import static io.papermc.codebook.lvt.LvtUtil.capitalize;
26+
import static io.papermc.codebook.lvt.LvtUtil.decapitalize;
27+
import static io.papermc.codebook.lvt.LvtUtil.prevInsnIgnoringConvertCast;
28+
import static java.util.Objects.requireNonNull;
29+
30+
import dev.denwav.hypo.model.data.MethodData;
31+
import dev.denwav.hypo.model.data.types.PrimitiveType;
32+
import io.papermc.codebook.lvt.suggestion.context.ContainerContext;
33+
import io.papermc.codebook.lvt.suggestion.context.method.MethodCallContext;
34+
import io.papermc.codebook.lvt.suggestion.context.method.MethodInsnContext;
35+
import java.io.IOException;
36+
import java.util.ArrayList;
37+
import java.util.List;
38+
import java.util.Locale;
39+
import java.util.Objects;
40+
import java.util.Set;
41+
import org.checkerframework.checker.nullness.qual.Nullable;
42+
import org.objectweb.asm.Opcodes;
43+
import org.objectweb.asm.tree.AbstractInsnNode;
44+
import org.objectweb.asm.tree.FieldInsnNode;
45+
import org.objectweb.asm.tree.LabelNode;
46+
import org.objectweb.asm.tree.LocalVariableNode;
47+
import org.objectweb.asm.tree.MethodInsnNode;
48+
import org.objectweb.asm.tree.MethodNode;
49+
import org.objectweb.asm.tree.VarInsnNode;
50+
51+
public class PositionsSuggester implements LvtSuggester {
52+
53+
@Override
54+
public @Nullable String suggestFromMethod(
55+
final MethodCallContext call, final MethodInsnContext insn, final ContainerContext container)
56+
throws IOException {
57+
if ("net/minecraft/core/SectionPos".equals(insn.owner().name())) {
58+
return suggestNameForSectionPos(container.node(), call.data(), insn.node());
59+
} else if ("net/minecraft/core/QuartPos".equals(insn.owner().name())) {
60+
return suggestNameForQuartPos(container.node(), call.data(), insn.node());
61+
} else if ("net/minecraft/core/BlockPos".equals(insn.owner().name())) {
62+
return suggestNameForBlockPos(call.data());
63+
} else if ("net/minecraft/world/level/ChunkPos".equals(insn.owner().name())) {
64+
return suggestNameForChunkPos(call.data());
65+
}
66+
return null;
67+
}
68+
69+
enum PosType {
70+
BLOCK("block", "blockpos"),
71+
QUART("quart", "quartpos", "biome", "biomepos"),
72+
SECTION("section", "sectionpos");
73+
74+
final Set<String> possibleNames;
75+
final String localName;
76+
77+
PosType(final String... possibleNames) {
78+
this.possibleNames = Set.of(possibleNames);
79+
this.localName = this.name().toLowerCase(Locale.ENGLISH) + "Pos";
80+
}
81+
}
82+
83+
private record MethodConfig(PosType returnType, PosType paramType, String prefix) {
84+
85+
private MethodConfig(final PosType returnType, final PosType paramType) {
86+
this(returnType, paramType, "");
87+
}
88+
89+
String varName(final String suffix) {
90+
if (this.prefix.isEmpty()) {
91+
return this.returnType.localName + suffix;
92+
} else {
93+
return this.prefix + capitalize(this.returnType.localName, 0) + suffix;
94+
}
95+
}
96+
}
97+
98+
private static @Nullable String suggestNameForSectionPos(
99+
final MethodNode enclosingMethodNode, final MethodData method, final MethodInsnNode insn) {
100+
// this matches 2 methods for each x, y, z. One static that takes the packed position, the output names are
101+
// appropriate for both method types
102+
final @Nullable String possibleSimpleName =
103+
switch (method.name()) {
104+
case "x" -> "sectionX";
105+
case "y" -> "sectionY";
106+
case "z" -> "sectionZ";
107+
case "blockToSection", "asLong" -> "packedSectionPos";
108+
default -> null;
109+
};
110+
if (possibleSimpleName != null) {
111+
return possibleSimpleName;
112+
}
113+
114+
final @Nullable MethodConfig methodConfig =
115+
switch (method.name()) {
116+
case "blockToSectionCoord", "posToSectionCoord" -> new MethodConfig(PosType.SECTION, PosType.BLOCK);
117+
case "sectionToBlockCoord" -> new MethodConfig(PosType.BLOCK, PosType.SECTION);
118+
case "sectionRelative" -> new MethodConfig(PosType.BLOCK, PosType.BLOCK, "relative");
119+
default -> null;
120+
};
121+
122+
return getCoordLocalNameFromMethodPair(enclosingMethodNode, insn, method, methodConfig);
123+
}
124+
125+
private static @Nullable String suggestNameForQuartPos(
126+
final MethodNode enclosingMethodNode, final MethodData method, final MethodInsnNode insn) {
127+
// all methods in QuartPos have a single int param and return int
128+
if (method.params().size() != 1
129+
|| method.param(0) != PrimitiveType.INT
130+
|| method.returnType() != PrimitiveType.INT) {
131+
return null;
132+
}
133+
134+
final @Nullable MethodConfig methodConfig =
135+
switch (method.name()) {
136+
case "fromBlock" -> new MethodConfig(PosType.QUART, PosType.BLOCK);
137+
case "toBlock" -> new MethodConfig(PosType.BLOCK, PosType.QUART);
138+
case "fromSection" -> new MethodConfig(PosType.QUART, PosType.SECTION);
139+
case "toSection" -> new MethodConfig(PosType.SECTION, PosType.QUART);
140+
default -> null;
141+
};
142+
if (methodConfig == null) {
143+
return null;
144+
}
145+
146+
return getCoordLocalNameFromMethodPair(enclosingMethodNode, insn, method, methodConfig);
147+
}
148+
149+
private static @Nullable String suggestNameForBlockPos(final MethodData method) {
150+
final String suggestion;
151+
if (method.name().equals("asLong")) {
152+
suggestion = "packedBlockPos";
153+
} else if (method.isStatic() && method.name().equals("offset") && method.returnType() == PrimitiveType.LONG) {
154+
suggestion = "offsetPackedBlockPos";
155+
} else {
156+
return null;
157+
}
158+
return suggestion;
159+
}
160+
161+
private static @Nullable String suggestNameForChunkPos(final MethodData method) {
162+
final String suggestion;
163+
if (method.name().equals("asLong") || method.name().equals("toLong")) {
164+
suggestion = "packedChunkPos";
165+
} else {
166+
return null;
167+
}
168+
return suggestion;
169+
}
170+
171+
private static final String[] COMMON_PERSISTENT_PREFIXES = new String[] {"min", "max"};
172+
173+
private static @Nullable String getCoordLocalNameFromMethodPair(
174+
final MethodNode enclosingMethodNode,
175+
final MethodInsnNode insn,
176+
final MethodData method,
177+
final @Nullable MethodConfig methodConfig) {
178+
if (methodConfig == null) {
179+
return null;
180+
}
181+
182+
if (method.params().size() != 1) {
183+
// add "Coord" since we don't know if its x, y, or z and more
184+
// than 1 param makes it too complex to figure out
185+
return methodConfig.varName("Coord");
186+
}
187+
188+
final AbstractInsnNode prev = requireNonNull(prevInsnIgnoringConvertCast(insn));
189+
@Nullable String suggestion = null;
190+
if (prev instanceof final VarInsnNode varNode) {
191+
final LocalVariableNode paramVarNode = findLocalVar(enclosingMethodNode, insn, varNode.var);
192+
suggestion = suggestSpecificCoordName(methodConfig, paramVarNode.name, COMMON_PERSISTENT_PREFIXES);
193+
} else if (prev instanceof final MethodInsnNode methodNode) {
194+
final @Nullable String strippedName =
195+
methodNode.name.startsWith("get") ? decapitalize(methodNode.name.substring(3)) : methodNode.name;
196+
if (strippedName != null) {
197+
suggestion = suggestSpecificCoordName(methodConfig, strippedName, COMMON_PERSISTENT_PREFIXES);
198+
}
199+
} else if (prev instanceof final FieldInsnNode fieldNode && fieldNode.getOpcode() == Opcodes.GETFIELD) {
200+
suggestion = suggestSpecificCoordName(methodConfig, fieldNode.name, COMMON_PERSISTENT_PREFIXES);
201+
}
202+
return Objects.requireNonNullElseGet(
203+
suggestion, () -> methodConfig.varName("Coord")); // add "Coord" since we don't know if its x, y, or z
204+
}
205+
206+
private static @Nullable String suggestSpecificCoordName(
207+
final MethodConfig methodConfig, final String fullName, final String... persistentPrefixes) {
208+
String prefix = "";
209+
if (fullName.length() > 1) {
210+
for (final String persistentPrefix : persistentPrefixes) {
211+
if (fullName.startsWith(persistentPrefix)) {
212+
prefix = persistentPrefix;
213+
break;
214+
}
215+
}
216+
}
217+
final String nameWithoutPrefix = fullName.substring(prefix.length());
218+
final int possibleCoordIdx = getPossibleCoordIdx(nameWithoutPrefix);
219+
if (possibleCoordIdx > -1
220+
&& (nameWithoutPrefix.length() == 1
221+
|| methodConfig.paramType.possibleNames.contains(
222+
nameWithoutPrefix.substring(0, possibleCoordIdx).toLowerCase(Locale.ENGLISH)))) {
223+
return methodConfig.varName(
224+
capitalize(prefix, 0) + Character.toUpperCase(nameWithoutPrefix.charAt(possibleCoordIdx)));
225+
}
226+
return null;
227+
}
228+
229+
private static int getPossibleCoordIdx(final String name) {
230+
for (int i = name.length() - 1; i >= 0; i--) {
231+
final char ch = name.charAt(i);
232+
if (!Character.isAlphabetic(ch)) {
233+
continue;
234+
}
235+
if (isCoord(ch)) {
236+
return i;
237+
}
238+
return -1;
239+
}
240+
return -1; // don't think this is possible
241+
}
242+
243+
private static boolean isCoord(final char ch) {
244+
return ch == 'X' || ch == 'Y' || ch == 'Z' || ch == 'x' || ch == 'y' || ch == 'z';
245+
}
246+
247+
private static LocalVariableNode findLocalVar(
248+
final MethodNode enclosingMethod, final AbstractInsnNode insn, final int varIdx) {
249+
final List<LocalVariableNode> matching = new ArrayList<>();
250+
for (final LocalVariableNode lvn : requireNonNull(enclosingMethod.localVariables)) {
251+
if (lvn.index == varIdx) {
252+
matching.add(lvn);
253+
}
254+
}
255+
if (matching.isEmpty()) {
256+
throw new IllegalStateException("Cannot find idx " + varIdx + " on " + enclosingMethod.name + " "
257+
+ enclosingMethod.desc + " (no match)");
258+
} else if (matching.size() == 1) {
259+
return matching.get(0);
260+
} else {
261+
@Nullable AbstractInsnNode prev = insn.getPrevious();
262+
if (prev == null) {
263+
throw new IllegalStateException("Cannot find idx " + varIdx + " on " + enclosingMethod.name + " "
264+
+ enclosingMethod.desc + " (multiple matches)");
265+
}
266+
while (true) {
267+
while (!(prev instanceof final LabelNode labelNode)) {
268+
prev = prev.getPrevious();
269+
}
270+
for (final LocalVariableNode match : matching) {
271+
if (match.start.getLabel() == labelNode.getLabel()) {
272+
return match;
273+
}
274+
}
275+
prev = prev.getPrevious();
276+
}
277+
}
278+
}
279+
}

0 commit comments

Comments
 (0)