Skip to content

Convert lambdas to method references when javac compiles a method reference to a lambda#532

Open
coehlrich wants to merge 4 commits intoVineflower:develop/1.12.0from
coehlrich:inline-instance-variable
Open

Convert lambdas to method references when javac compiles a method reference to a lambda#532
coehlrich wants to merge 4 commits intoVineflower:develop/1.12.0from
coehlrich:inline-instance-variable

Conversation

@coehlrich
Copy link
Contributor

Converts lambdas to method references when javac compiles a method reference to a lambda, removes synthetic instance variable, and removes Objects.requireNonNull.

Copy link
Member

@sschr15 sschr15 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some small nitpicks, but overall it looks good to me.

manual bytecode parsing is scary

&& newExprent.isLambda()
&& !newExprent.isMethodReference()) {
if (stat.getTopParent().mt.getName().contains("<init>")) {
System.out.println();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug line

argument++;
}

if (next == null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should never be true (FullInstructionSequence seems to only ever have non-null instructions). Did you mean !iterator.hasNext()?

}

private static boolean convertToMethodReference(Statement stat, int i, Exprent exp) throws IOException {
if (exp instanceof NewExprent newExprent
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably reasonable to flip this if to reduce a layer of nesting


public class MethodReferenceHelper {
public static boolean convertToMethodReference(RootStatement root) throws IOException {
return convertToMethodReferenceRec(root);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to also modify existing lambdas which could be represented with a method reference. Perhaps it should be behind a decompiler option

method.expandData(struct);
FullInstructionSequence seq = method.getInstructionSequence();
Iterator<Instruction> iterator = seq.iterator();
Instruction next = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary initializer

@sschr15 sschr15 requested a review from jaskarth December 20, 2025 00:51
Copy link
Member

@jaskarth jaskarth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for working on this! Overall, I don't really understand the approach taken here. I'll look at the generated code and see if there might be a better way.

public Optional<String> field3 = Optional.of("").map(s -> s + "3");// 12
public Stream<String> field4 = Stream.of("1", "2").sorted(Comparator.<String, Integer>comparing(s -> s.length()).thenComparing(i -> i.toString()));// 13 14
public Comparator<String> field5 = Comparator.comparing(String::length).thenComparing(i -> i.toString());// 15
public Stream<String> field4 = Stream.of("1", "2").sorted(Comparator.<String, Integer>comparing(s -> s.length()).thenComparing(String::toString));// 13 14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this output is wrong. In the original source, these are regular lambdas and not references.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would have to match the lambda that javac generates when converting a method reference to a lambda.

I could require the variable to exist but then it would only work for java 25.

argument++;
}

boolean varargs = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this code doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The varargs variable is used for checking if the amount of method parameters of the lambda is what is expected.

StructMethod method = struct.getMethod(info.content_method_key);
if (!method.hasModifier(CodeConstants.ACC_STATIC))
return false;
method.expandData(struct);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, does this need to manually parse the instructions or would going through the exprents suffice?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ClassWrapper on the ClassNode isn't set at this point and even if it was the exprents for lambdas are almost certainly not generated at this point since they are the last methods in the class file.

I did consider doing it when the lambda is written to the output like with array constructor references but that would prevent inlining the variable since if it happened when the lambda is written nothing in codeToJava would see that the variable assignment is removed whereas if the variable is removed during codeToJava then there I don't think there would be anyway for the variable to be inlined correctly since lambdas can't call a method on an instance without the instance being referenced via a variable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants