diff --git a/maddi-cst-print-kotlin/src/main/java/io/codelaser/maddi/cst/print/kotlin/KotlinExpressionPrinter.java b/maddi-cst-print-kotlin/src/main/java/io/codelaser/maddi/cst/print/kotlin/KotlinExpressionPrinter.java index 9e87794b5..a5e42e939 100644 --- a/maddi-cst-print-kotlin/src/main/java/io/codelaser/maddi/cst/print/kotlin/KotlinExpressionPrinter.java +++ b/maddi-cst-print-kotlin/src/main/java/io/codelaser/maddi/cst/print/kotlin/KotlinExpressionPrinter.java @@ -1462,6 +1462,15 @@ private static boolean spread(List args, io.codelaser.maddi.cst.api. * an array. */ static OutputBuilder assigned(Expression e, ParameterizedType target, Qualification q) { + if (KotlinContext.translatingJava() && unwrap(e) instanceof MethodReference && target != null + && target.typeInfo() != null && target.typeInfo().isFunctionalInterface() && target.arrays() == 0 + && target.parameters().stream().allMatch(p -> p.wildcard() == null)) { + // return Util::parse into a Function: Kotlin types a bare reference as its own KFunction1, + // which is no Function; an argument converts by itself, a returned or assigned value needs the SAM constructor + return new OutputBuilderImpl().add(new TextImpl(KotlinTypeName.of(target.withNullable( + io.codelaser.maddi.cst.api.type.NullableState.NONNULL), q))) + .add(SymbolEnum.LEFT_PARENTHESIS).add(print(e, q)).add(SymbolEnum.RIGHT_PARENTHESIS); + } if (KotlinContext.translatingJava() && unwrap(e) instanceof MethodCall mc && target != null && !target.parameters().isEmpty() && mc.typeArguments().stream().anyMatch(KotlinTypeName::definitelyNonNull) && sameUpToStates(mc.parameterizedType(), target) && nullableArgument(mc.methodInfo())) { diff --git a/maddi-cst-print-kotlin/src/test/java/io/codelaser/maddi/cst/print/kotlin/TestJavaToKotlinCalls.java b/maddi-cst-print-kotlin/src/test/java/io/codelaser/maddi/cst/print/kotlin/TestJavaToKotlinCalls.java index 312122deb..6beaf9708 100644 --- a/maddi-cst-print-kotlin/src/test/java/io/codelaser/maddi/cst/print/kotlin/TestJavaToKotlinCalls.java +++ b/maddi-cst-print-kotlin/src/test/java/io/codelaser/maddi/cst/print/kotlin/TestJavaToKotlinCalls.java @@ -460,6 +460,30 @@ class Search { compiles(kotlin); } + /** + * A static method reference returned, or assigned, as a java.util.function.Function: Kotlin types a bare + * {@code Util::parse} as a KFunction1, which is no Function. Passed as an argument, Kotlin converts it itself. + */ + @Test + public void staticReferenceReturnedAsFunction() { + String kotlin = kotlin(""" + package a; + import java.util.function.*; + class Util { + static int parse(String s) { return s.length(); } + static Function parser() { return Util::parse; } + static Function local() { Function f = Util::parse; return f; } + static int apply(Function f) { return f.apply("x"); } + static int use() { return apply(Util::parse); } + } + """); + contains(kotlin, "fun parser(): Function = Function(Util ::parse)"); + contains(kotlin, "= Function(Util ::parse)\nreturn f"); + // the argument as it was: Kotlin converts it to the parameter's interface + contains(kotlin, "apply(Util ::parse)"); + compiles(kotlin); + } + /** * guava's Graphs.inducedSubgraph: {@code MutableGraph subgraph = c ? b.build() : b.build()}, a builder whose * {@code build()} only the target types. The local keeps its declared type.